NPU에서 OpenVINO GenAI 추론하기

NPU에서 OpenVINO GenAI 추론하기

NPU(신경망 처리 장치) 위에서 OpenVINO GenAI로 LLM과 그 밖의 파이프라인을 실행하는 방법을 좀 더 자세히 다뤄볼게요. 시작 방법은 설치 가이드를 먼저 확인하는 게 좋아요. 이 글에서는 모델을 내보내는 필수 설정, LLM·VLM·Whisper 추론 코드, 그리고 NPU 특유의 성능·캐시 옵션까지 순서대로 설명할게요.

출처: OpenVINO GenAI on NPU — OpenVINO™ documentation

사전 요구사항 (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_HINTSTATIC으로 설정하면 이 기능을 끌 수 있어요.

기본 설정을 바꾸는 코드는 다음과 같아요.

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_HINTGENERATE_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_BLOBBLOB_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

더 알아보기 (Learn more)