NPU에서 OpenVINO GenAI 추론하기
NPU에서 OpenVINO GenAI 추론하기
NPU(신경망 처리 장치) 위에서 OpenVINO GenAI로 LLM과 그 밖의 파이프라인을 실행하는 방법을 좀 더 자세히 다뤄볼게요. 시작 방법은 설치 가이드를 먼저 확인하는 게 좋아요. 이 글에서는 모델을 내보내는 필수 설정, LLM·VLM·Whisper 추론 코드, 그리고 NPU 특유의 성능·캐시 옵션까지 순서대로 설명할게요.
사전 요구사항 (Prerequisites)
먼저 모델 변환에 필요한 의존성을 설치해요. Linux든 Windows든 nncf, onnx, optimum-intel, transformers, 그리고 OpenVINO 계열 패키지를 버전을 지정해 설치해요.
Linux:
python3 -m venv npu-env
source npu-env/bin/activate
pip install nncf==2.18.0 onnx==1.18.0 optimum-intel==1.25.2 transformers==5.0.0
pip install openvino==2026.3.1 openvino-tokenizers==2026.3.1.0 openvino-genai==2026.3.1.0
프리프로덕션 버전을 쓸 땐 다음 줄을 대신 사용해요.
pip install --pre openvino openvino-tokenizers openvino-genai --extra-index-url https://storage.openvinotoolkit.org/simple/wheels/nightly
Windows:
python -m venv npu-env
npu-env\Scripts\activate
pip install nncf==2.18.0 onnx==1.18.0 optimum-intel==1.25.2 transformers==5.0.0
pip install openvino==2026.3.1 openvino-tokenizers==2026.3.1.0 openvino-genai==2026.3.1.0
참고: OpenVINO 2026.3에서는 대부분의 모델을 Intel NPU용으로 생성할 때
transformers==5.0.0을 권장해요.
참고: Intel Core Ultra Processor Series 2 기반 시스템에서 Llama-2-7B, Mistral-0.2-7B, Qwen-2-7B처럼 7B 파라미터를 넘는 모델로 1024 토큰보다 긴 프롬프트를 처리하려면 16GB보다 많은 RAM이 필요할 수 있어요.
NPU에서 LLM 추론
Hugging Face에서 LLM 내보내기
NPU 추론용 Hugging Face 모델을 내보낼 때는 Optimum Intel이 주된 방법이에요. LLM은 반드시 다음 설정으로 내보내야 해요.
- 대칭 가중치 압축:
--sym - 4비트 가중치 형식(INT4 또는 NF4):
--weight-format int4또는--weight-format nf4 - 채널 단위 또는 그룹 단위 가중치 양자화:
--group-size -1또는--group-size 128 - 모델의 4비트 가중치 비율 최대화:
--ratio 1.0
그룹 양자화(GQ)는 최대 약 4B~5B 파라미터의 작은 모델에 그룹 크기 128을 권장해요. 더 큰 모델도 그룹 양자화가 작동하지만, 보통 채널 단위 양자화에서 더 나은 성능을 보여요.
채널 단위 양자화(CW)는 일반적으로 최고 성능을 주지만 모델 정확도가 떨어질 수 있어요. OpenVINO NNCF는 데이터 인지 압축 방법이나 GPTQ 같은 여러 방법으로 품질 손실을 보정할 수 있게 해줘요.
전체 optimum-cli 명령 예시를 보여드릴게요.
INT4-CW — INT4 대칭 채널 단위 데이터 프리 압축:
optimum-cli export openvino -m meta-llama/Meta-Llama-3.1-8B-Instruct --weight-format int4 --sym --ratio 1.0 --group-size -1 Meta-Llama-3.1-8B-Instruct
INT4-CW, data-aware — 채널 단위 양자화 모델의 정확도를 높이려면 Scale Estimation(--scale_estimation)과/또는 AWQ(--awq)를 써요. 이 옵션들은 데이터셋(--dataset <dataset_name>)이 필요해요.
optimum-cli export openvino -m meta-llama/Meta-Llama-3.1-8B-Instruct --weight-format int4 --sym --group-size -1 --ratio 1.0 --awq --scale-estimation --dataset wikitext2 Meta-Llama-3.1-8B-Instruct
NF4-CW — NF4 대칭 데이터 프리 채널 단위 압축:
optimum-cli export openvino -m meta-llama/Meta-Llama-3.1-8B-Instruct --weight-format nf4 --sym --group-size -1 --ratio 1.0 Meta-Llama-3.1-8B-Instruct
보통 NF4-CW는 데이터 프리 압축에서도 INT4-CW보다 정확도가 좋아요. 데이터 인지 방법도 사용 가능해 압축 모델 정확도를 더 높일 수 있어요.
INT4-GQ — INT4 대칭 데이터 프리 그룹 양자화:
optimum-cli export openvino -m microsoft/Phi-3.5-mini-instruct --weight-format int4 --sym --ratio 1.0 --group-size 128 Phi-3.5-mini-instruct
참고: NF4 데이터 타입은 Intel Core Ultra Processor Series 2 NPU(구 Lunar Lake 코드네임) 이상에서만 지원돼요. NF4는 채널 단위 양자화와 함께 쓰세요.
중요: 채널 단위 양자화에서 그룹 크기 인자는
-1("minus one")이어야 해요.1이 아니에요.
Hugging Face에는 그대로 내보낼 수 있는 사전 압축 모델도 있어요.
- 4비트(INT4) GPTQ 모델
- OpenVINO가 호스팅·관리하는 NPU 최적화 LLM
이 경우 명령은 이렇게 단순해져요.
optimum-cli export openvino -m TheBloke/Llama-2-7B-Chat-GPTQ
optimum-cli export openvino -m OpenVINO/Mistral-7B-Instruct-v0.2-int4-cw-ov
텍스트 생성 실행하기
최신 NPU 드라이버를 설치하는 것을 권장해요. 다음 코드로 OpenVINO GenAI API를 이용해 생성을 수행할 수 있어요.
import openvino_genai as ov_genai
model_path = "TinyLlama"
pipe = ov_genai.LLMPipeline(model_path, "NPU")
print(pipe.generate("The Sun is yellow because", max_new_tokens=100))
#include "openvino/genai/llm_pipeline.hpp"
#include <iostream>
int main(int argc, char* argv[]) {
std::string model_path = "TinyLlama";
ov::genai::LLMPipeline pipe(models_path, "NPU");
ov::genai::GenerationConfig config;
config.max_new_tokens=100;
std::cout << pipe.generate("The Sun is yellow because", config);
}
추가 설정 옵션
중요: 이 글에서 설명하는 옵션은 NPU 디바이스 전용이라 다른 디바이스에서는 작동하지 않을 수 있어요.
프롬프트·응답 길이 옵션
NPU용 LLM 파이프라인은 정적 셰이프 접근을 활용해 실행 성능을 최적화하지만, 일부 사용 제약이 생길 수 있어요. 기본적으로 LLM 파이프라인은 입력 프롬프트 길이를 최대 1024 토큰으로 지원해요. 또 생성 중 EOS 토큰을 만나거나 사용자가 응답 길이 제한을 더 낮게 명시하지 않는 한, 생성된 응답이 최소 128 토큰은 되도록 보장해요.
다음 파라미터로 '최대 입력 프롬프트 길이'와 '최소 응답 길이'를 모두 설정할 수 있어요.
MAX_PROMPT_LEN— LLM 파이프라인이 입력 프롬프트로 처리할 수 있는 최대 토큰 수(기본값: 1024)MIN_RESPONSE_LEN— LLM 파이프라인이 응답으로 생성할 수 있는 최소 토큰 수(기본값: 128)
NPU의 LLM 최대 컨텍스트 크기는 이 두 값의 합으로 정의돼요. 기본적으로 입력 프롬프트가 MAX_PROMPT_LEN 토큰보다 짧으면 첫 토큰까지의 시간(TTFT)이 전체 길이 프롬프트를 넣었을 때와 동일해요. 하지만 짧은 프롬프트일수록 모델이 가용 컨텍스트 안에서 더 많은 토큰을 생성할 수 있어요. 예를 들어 입력 프롬프트가 30 토큰이라면 모델은 최대 1024 + 128 - 30 = 1122 토큰까지 생성할 수 있죠.
OpenVINO 2025.3에서 NPU 동적 입력 프롬프트 지원이 도입됐어요. 동적 세분성은 새 속성 NPUW_LLM_PREFILL_CHUNK_SIZE(기본값: 1024)로 제어돼요. MAX_PROMPT_LEN 속성이 청크 크기보다 큰 값으로 설정되면 이 메커니즘이 자동으로 활성화되고, PREFILL_HINT를 STATIC으로 설정하면 이 기능을 끌 수 있어요.
기본 설정을 바꾸는 코드는 다음과 같아요.
pipeline_config = { "MAX_PROMPT_LEN": 1024, "MIN_RESPONSE_LEN": 512 }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "MAX_PROMPT_LEN", 1024 }, { "MIN_RESPONSE_LEN", 512 } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
GenAI LLM 채팅 시나리오에서는 대화 기록이 컨텍스트에 쌓여서, 기록을 제대로 처리하려면 더 큰 MAX_PROMPT_LEN이 필요할 수 있어요.
성능 힌트
PREFILL_HINT와 GENERATE_HINT 옵션으로 NPU LLM 파이프라인 성능을 미세 조정할 수 있어요. 이 옵션들은 각각 프롬프트 처리(첫 토큰)와 텍스트 생성(이후 토큰) 동작에 영향을 줘요.
PREFILL_HINT — 프롬프트 처리 단계를 미세 조정해요.
DYNAMIC(OpenVINO 2025.3부터 기본값) — 동적 프롬프트 실행을 활성화해 더 긴 프롬프트를 지원해요.STATIC— 동적 프롬프트 실행을 비활성화하고, 특정 프롬프트 크기에서 더 나은 성능을 보일 수 있어요. OpenVINO 2025.3 이전의 기본 동작이에요.
GENERATE_HINT — 텍스트 생성 단계를 미세 조정해요.
FAST_COMPILE(기본값) — 성능 대신 빠른 컴파일을 써요.BEST_PERF— 더 낮은 컴파일 속도로 최상의 성능을 보장해요.
pipeline_config = { "GENERATE_HINT": "BEST_PERF" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "GENERATE_HINT", "BEST_PERF" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
캐싱과 사전 컴파일(AoT)
NPU용 LLM 컴파일은 실행 중(on-the-fly)에 이뤄져 시간이 상당히 걸릴 수 있어요. 사용자 경험을 개선하기 위해 OpenVINO 캐싱과 AoT(Ahead-of-time) 컴파일 옵션을 제공해요.
OpenVINO 캐싱
컴파일된 모델을 캐시하면 이후 파이프라인 실행의 초기화 시간을 줄일 수 있어요. NPU 파이프라인의 pipeline_config에 다음 옵션 중 하나를 지정하면 돼요.
CACHE_DIR — 기본 OpenVINO 캐싱 메커니즘이에요. CACHE_MODE 힌트가 캐시 블롭이 가중치를 저장하는 방식을 정의해요. OPTIMIZE_SPEED는 가중치를 포함해 그룹 양자화 모델을 더 빨리 로드하고, OPTIMIZE_SIZE는 가중치를 제외해 무가중(weightless) 블롭을 만들며 원본 모델이 디스크에 있어야 해요.
pipeline_config = { "CACHE_DIR": ".npucache" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "CACHE_DIR", ".npucache" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
NPUW_CACHE_DIR — 레거시 NPU 전용 무가중 캐싱 옵션이에요. OpenVINO 2025.1부터는 디바이스 중립적인 OpenVINO 캐싱(CACHE_DIR)이 선호돼요.
Ahead-of-time 컴파일
EXPORT_BLOB와 BLOB_PATH 파라미터를 지정하는 것은 CACHE_DIR와 비슷하게 작동하지만, 차이가 있어요.
- 컴파일된 모델을 저장할 위치를 명시적으로 지정할 수 있어요.
- 이후 실행에서는 컴파일된 모델을 가져오려면 같은
BLOB_PATH가 필요해요. - 블롭 타입도
CACHE_MODE로 정의돼요.- 기본적으로
OPTIMIZE_SIZE를 사용해 무가중 블롭을 만들어요. 이 블롭을 로드하려면 원본 가중치 파일이나ov::Model객체가 필요해요. OPTIMIZE_SPEED를 넘기면 전체 가중치를 담은 블롭을 내보내요.
- 기본적으로
- 블롭을 무가중으로 내보내면 설정에
"WEIGHTS_PATH" : "path\\to\\original\\model.bin"또는"MODEL_PTR" : original ov::Model object를 제공해야 해요. - 무가중 모드의 AoT 가져오기는 일반 컴파일이나
CACHE_DIR사용보다 메모리를 덜 쓰도록 최적화돼 있어요.
무가중 내보내기:
pipeline_config = { "EXPORT_BLOB": "YES", "BLOB_PATH": ".npucache\\compiled_model.blob" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "EXPORT_BLOB", "YES" }, { "BLOB_PATH", ".npucache\\compiled_model.blob" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
무가중 가져오기:
pipeline_config = { "BLOB_PATH": ".npucache\\compiled_model.blob", "WEIGHTS_PATH": "path\\to\\original\\model.bin" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "BLOB_PATH", ".npucache\\compiled_model.blob" }, { "WEIGHTS_PATH", "path\\to\\original\\model.bin" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
전체 가중치 내보내기:
pipeline_config = { "EXPORT_BLOB": "YES", "BLOB_PATH": ".npucache\\compiled_model.blob", "CACHE_MODE" : "OPTIMIZE_SPEED" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "EXPORT_BLOB", "YES" }, { "BLOB_PATH", ".npucache\\compiled_model.blob" }, { "CACHE_MODE", "OPTIMIZE_SPEED" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
전체 가중치 가져오기:
pipeline_config = { "BLOB_PATH": ".npucache\\compiled_model.blob" }
pipe = ov_genai.LLMPipeline(model_path, "NPU", pipeline_config)
ov::AnyMap pipeline_config = { { "BLOB_PATH", ".npucache\\compiled_model.blob" } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
블롭 암호화
NPU LLM 블롭을 내보낼 때 블롭의 암호화·복호화 함수도 지정할 수 있어요. 무가중 블롭이면 블롭 전체가 암호화되고, 가중치 있는 블롭이면 모델 가중치를 제외한 나머지가 암호화돼요.
내보내기 예시:
ov::EncryptionCallbacks encryption_callbacks;
encryption_callbacks.encrypt = [](const std::string& s) { return s; };
ov::AnyMap pipeline_config = { { "EXPORT_BLOB", "YES" }, { "BLOB_PATH", ".npucache\\compiled_model.blob" }, { "CACHE_ENCRYPTION_CALLBACKS", encryption_callbacks } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
가져오기 예시:
ov::EncryptionCallbacks encryption_callbacks;
encryption_callbacks.decrypt = [](const std::string& s) { return s; };
ov::AnyMap pipeline_config = { { "BLOB_PATH", ".npucache\\compiled_model.blob" }, { "WEIGHTS_PATH", "path\\to\\original\\model.bin" }, { "CACHE_ENCRYPTION_CALLBACKS", encryption_callbacks } };
ov::genai::LLMPipeline pipe(model_path, "NPU", pipeline_config);
NPU에서 VLM 추론
VLM도 NPU에서 지원되며, LLM과 같은 방식으로 GenAI API로 추론할 수 있어요.
import numpy as np
from PIL import Image
from openvino import Tensor
import openvino_genai as ov_genai
model_path = "Google-Gemma-3-4B-it"
image_path = "cat.png"
image = Tensor(np.array(Image.open(image_path).convert("RGB")))
pipe = ov_genai.VLMPipeline(model_path, "NPU")
print(pipe.generate("Describe the image", images=image, max_new_tokens=100))
#include "load_image.hpp"
#include <openvino/genai/visual_language/pipeline.hpp>
#include <iostream>
bool print_subword(std::string&& subword) {
return !(std::cout << subword << std::flush);
}
int main(int argc, char* argv[]) {
std::string model_path = "Google-Gemma-3-4B-it";
std::string image_path = "cat.png";
std::vector<ov::Tensor> rgbs = utils::load_images(image_path);
ov::genai::VLMPipeline pipe(model_path, "NPU");
ov::genai::GenerationConfig config;
config.max_new_tokens=100;
std::cout << pipe.generate("Describe the image",
ov::genai::images(rgbs),
ov::genai::generation_config(config),
ov::genai::streamer(print_subword));
}
VLM에 설정 전달하기
위에서 설명한 모든 파라미터(MAX_PROMPT_LEN, MIN_RESPONSE_LEN, CACHE_DIR 등)가 VLM에도 적용돼요. 다만 조금 다른 방식으로 넘겨야 해요. 설정의 {"DEVICE_PROPERTIES": {"NPU" : ... } } 섹션에 넣어야 하죠.
import numpy as np
from PIL import Image
from openvino import Tensor
import openvino_genai as ov_genai
model_path = "Phi-4-multimodal-instruct"
image_path = "cat.png"
image = Tensor(np.array(Image.open(image_path).convert("RGB")))
pipeline_config = {
"DEVICE_PROPERTIES": {
"NPU": {
"MAX_PROMPT_LEN": 2048,
"MIN_RESPONSE_LEN": 512
},
}
}
pipe = ov_genai.VLMPipeline(model_path, "NPU", config=pipeline_config)
print(pipe.generate("Describe the image", images=image, max_new_tokens=100))
#include "load_image.hpp"
#include <openvino/genai/visual_language/pipeline.hpp>
#include <iostream>
bool print_subword(std::string&& subword) {
return !(std::cout << subword << std::flush);
}
int main(int argc, char* argv[]) {
std::string model_path = "Phi-4-multimodal-instruct";
std::string image_path = "cat.png";
std::vector<ov::Tensor> rgbs = utils::load_images(image_path);
ov::AnyMap pipeline_config = {
{"DEVICE_PROPERTIES", ov::AnyMap{
{"NPU", ov::AnyMap{
{"MAX_PROMPT_LEN", 2048},
{"MIN_RESPONSE_LEN", 512}
}}
}}
};
ov::genai::VLMPipeline pipe(model_path, "NPU", pipeline_config);
ov::genai::GenerationConfig config;
config.max_new_tokens=100;
std::cout << pipe.generate("Describe the image",
ov::genai::images(rgbs),
ov::genai::generation_config(config),
ov::genai::streamer(print_subword));
}
NPU에서 Whisper 추론
OpenAI Whisper 지원(whisper-tiny, whisper-base, whisper-small, whisper-large 모델)은 OpenVINO 2024.5에서 처음 도입됐어요. NPU에서 Whisper GenAI 파이프라인을 실행할 때 NPU 전용 요구사항은 없어서, 표준 OpenVINO GenAI 샘플이 제약 없이 작동해요.
Hugging Face에서 Whisper 모델 내보내기
OpenVINO 2025.1 이전에는 Whisper 파이프라인이 --disable-stateful 플래그로 내보낸 상태 없는(stateless) Whisper 모델만 받았어요.
optimum-cli export openvino --trust-remote-code --model openai/whisper-tiny whisper-tiny --disable-stateful
OpenVINO 2025.1부터는 이제 그럴 필요가 없어요. 가중치는 FP16으로 두거나 INT8로 압축하면 돼요.
optimum-cli export openvino --trust-remote-code --model openai/whisper-base whisper-base-int8 --weight-format int8
문제 해결 (Troubleshooting)
L0 메모리 할당 비활성화하기
조용히 실패하거나 오류를 내는 실행 실패가 발생하면 NPU 드라이버를 32.0.100.3104 이상으로 업데이트해 보세요. 업데이트가 어렵고 "out of memory" 오류가 나면 DISABLE_OPENVINO_GENAI_NPU_L0 환경 변수를 설정해 Level0 메모리 할당을 끌 수 있어요.
Linux:
export DISABLE_OPENVINO_GENAI_NPU_L0=1
Windows:
set DISABLE_OPENVINO_GENAI_NPU_L0=1