콘텐츠로 이동

Qwen3-VL-2B(VLM)

개요

이 가이드는 optimum-rbln 기본 사용 경험이 있는 사용자를 대상으로, Vision-Language Model(VLM)을 Rebel100™(RBLN-CR13) NPU 1개에서 효과적으로 활용하기 위한 두 가지 핵심 기법을 소개합니다.

  • 다양한 입력 해상도와 디코더 배치 크기를 버케팅(bucketing)을 사용해 한 번의 컴파일로 효율적으로 처리하기
  • 각 submodule별 rbln_config를 구성하여 제어하기

예제 모델은 Qwen/Qwen3-VL-2B-Instruct이며, 코드 예제의 기본값은 RBLN Model Zoo의 Rebel100™ 예제(REBEL/huggingface/transformers/image-text-to-text/qwen3-vl/qwen3-vl-2b/ 아래의 compile.py, inference.py)와 일치합니다.

Rebel100™에서 서빙하기

Rebel100™에서는 VLM 서빙에 RBLN vLLM(vllm serve)를 기본으로 사용하는 것을 권장합니다. 모델 컴파일과 OpenAI 호환 API 서빙을 한 번에 처리합니다. 자세한 내용은 Qwen3-VL-2B-Instruct 레시피에서 확인할 수 있습니다. 이 가이드는 optimum-rbln Python API를 다루며, 컴파일을 확인하거나 추론을 직접 실행할 때 유용합니다.

Note

Rebel100™은 RSD를 지원하지 않으며(버전 매트릭스 참고), Model Zoo의 모든 Rebel100™ 예제가 NPU 1개용으로 컴파일하므로 이 가이드에서도 visual submodule과 LM 모두 num_devices를 1로 설정합니다.

Note

에러 상황 디버깅(예: Failed to create RBLN runtime, No memory blocks are available)은 이 문서의 범위가 아닙니다. 메모리나 runtime 생성 관련 에러의 해결 방법은 다중 모듈 모델 트러블슈팅에 정리되어 있습니다.


submodule로 구성된 모델 구조

VLM은 비전 인코더, 언어 모델 등 여러 신경망 구성요소로 이루어진 모델입니다. optimum-rbln은 이러한 구성요소 중 주축이 되는 하나를 최상위(top-level) 모델로 두고, 나머지를 submodule로 선언하여 각각 별개의 그래프로 컴파일하고 별개의 runtime으로 실행합니다. 이러한 구조에서는 각 submodule마다 원하는 설정을 독립적으로 지정할 수 있습니다.

Qwen3-VL(Qwen3VLForConditionalGeneration)의 모델 구조는 다음과 같습니다.

RBLNQwen3VLForConditionalGeneration   ← 최상위 (causal LM)
└── visual: RBLNQwen3VLVisionModel    ← submodule (Vision Transformer)

최상위 모델은 causal Language Model(LM)이며, 이 모델은 이미지와 비디오 프레임을 인코딩하는 Vision Transformer를 visual이라는 단일 submodule로 포함합니다. 각 구성요소의 역할은 다음과 같습니다.

  • 최상위(top) LM: visual이 만든 이미지 임베딩을 받아 텍스트 토큰을 auto-regressive 방식으로 생성합니다.
  • visual submodule: Vision Transformer. 이미지 또는 비디오 프레임 단위로 실행됩니다.

이 트리 구조는 컴파일 시에 전달하는 rbln_config의 키 구조와 그대로 대응합니다. 최상위 키는 LM에, "visual" 아래에 중첩한 키는 visual submodule에 적용됩니다.

rbln_config = {
    "visual": {  # ← submodule 레벨 (Vision Transformer)
        "max_seq_len": 16384,
        "num_devices": 1,
        "create_runtimes": False,
    },
    "num_devices": 1,  # ← 최상위 (LM)
    "kvcache_partition_len": 8_192,
    "max_seq_len": 262_144,
    "create_runtimes": False,
}

Tip

각 필드의 정의는 해당 모델의 RBLN config 파일에서 찾을 수 있습니다. 예를 들어 Qwen3-VL의 visual submodule 필드는 RBLNQwen3VLVisionModelConfig에 정의되어 있습니다.

Note

어떤 구성요소가 최상위 모델이고 어떤 것이 submodule인지는 모델마다 다릅니다. 예를 들어 LLaVA, Gemma3는 최상위가 Conditional Generation 래퍼이고 vision_tower와 language_model이 둘 다 submodule이며, Idefics3는 vision_model, text_model이 함께 submodule로 선언되어 있습니다. 사용하려는 모델의 구조는 해당 모델의 RBLN config 파일(configuration_<model>.py)에서 submodules = [...] 선언으로 확인할 수 있습니다.


submodule의 rbln_config 사용하기

이 섹션에서는 Rebel100™에서 rbln_config로 optimum-rbln의 VLM을 운용할 때 자주 사용하는 패턴을 정리했습니다. 두 submodule을 NPU 1개에서 컴파일하고 로드하는 예제로 시작해, 두 축의 버케팅, 그 외 메모리 및 워크플로우 시나리오로 이어집니다.

Qwen3-VL에서 주로 사용하는 rbln_config 필드를 정리하면 다음과 같습니다.

필드 레벨 용도
batch_size 공통 모델 전체의 동시 시퀀스 수. submodule별로 따로 줄 수 없음
visual.max_seq_len submodule(visual) ViT 그래프가 이미지, 프레임당 받아들이는 최대 패치(patch) 수. int 하나 또는 List[int](bucketing)
visual.num_devices submodule(visual) visual에 사용할 device 수(Rebel100™에서는 1)
visual.device submodule(visual) visual runtime이 올라갈 device
visual.create_runtimes submodule(visual) 컴파일 시 visual runtime 생성 여부
num_devices top(LM) LM에 사용할 device 수(Rebel100™에서는 1)
kvcache_partition_len top(LM) Flash attention 파티션 크기. max_seq_len을 나누어 떨어져야 함
max_seq_len top(LM) LM의 최대 position embedding. kvcache_partition_len의 배수
decoder_batch_sizes top(LM) 디코더 배치 크기 목록(bucketing). 모든 값이 batch_size 이하
device top(LM) LM runtime이 올라갈 device
create_runtimes top(LM) 컴파일 시 LM runtime 생성 여부

Caution

두 레벨에 모두 쓸 수 있는 키의 주의 사항은 다음과 같습니다.

  • num_devices는 submodule별로 다르게 줄 수 있지만, Rebel100™에서는 두 레벨 모두 1이며 각 submodule의 device는 device 번호 하나(예: 0)입니다.
  • batch_size는 모델 전체에 단일 값으로 적용되며, submodule별로 따로 지정할 수 없습니다. visual과 LM은 배치를 다른 방식으로 실행하지만 optimum-rbln이 내부에서 조율합니다.

Note

Rebel100™의 flash attention 한도는 ATOM™의 절반입니다. kvcache_partition_len은 1,024 이상 16,384 이하여야 하며(ATOM™은 최대 32,768), 기본값은 8,192입니다(ATOM™은 16,384). max_seq_len은 kvcache_partition_len의 배수이면서 그 2배 이상이어야 합니다. Model Zoo 값(kvcache_partition_len=8_192, max_seq_len=262_144, 파티션 32개)은 이 한도를 만족합니다.

두 submodule을 NPU 1개에서 컴파일하고 로드하기

Rebel100™에서는 visual과 LM이 각각 NPU 1개에서 실행됩니다. Model Zoo 예제는 두 레벨 모두 create_runtimes를 False로 지정하므로, 컴파일 단계에서는 runtime을 만들지 않고 컴파일 결과물만 생성해 저장합니다.

import os

from optimum.rbln import RBLNAutoModelForImageTextToText

model_id = "Qwen/Qwen3-VL-2B-Instruct"
model = RBLNAutoModelForImageTextToText.from_pretrained(
    model_id,
    export=True,
    rbln_config={
        "visual": {
            "max_seq_len": 16384,
            "num_devices": 1,
            "create_runtimes": False,  # 컴파일 시 visual runtime을 만들지 않음
        },
        "num_devices": 1,
        "kvcache_partition_len": 8_192,
        "max_seq_len": 262_144,
        "create_runtimes": False,  # 컴파일 시 LM runtime을 만들지 않음
    },
)
model.save_pretrained(os.path.basename(model_id))

로드할 때는 rbln_config의 device로 각 runtime의 위치를 지정합니다. Model Zoo inference.py는 visual과 LM을 모두 device 0에 올립니다.

import os

from optimum.rbln import RBLNAutoModelForImageTextToText

model_id = "Qwen/Qwen3-VL-2B-Instruct"
model = RBLNAutoModelForImageTextToText.from_pretrained(
    os.path.basename(model_id),
    export=False,
    rbln_config={
        "visual": {
            "device": 0,  # visual은 device 0
        },
        "device": 0,  # LM은 device 0
    },
)

이 배치에서는 ViT 그래프, LM 그래프, KV cache가 모두 NPU 1개의 메모리에 들어가야 합니다. 배치 크기나 컨텍스트 길이를 늘리다가 runtime 생성에 실패하면 아래 그 외 시나리오를 참고해 워크로드를 줄이면 됩니다.

ViT 입력 길이 버케팅: visual.max_seq_len

Qwen3-VL의 ViT는 이미지(또는 비디오 프레임) 한 장 단위로 실행되며, 그래프 모양은 컴파일 타임에 고정됩니다. 즉 max_seq_len=16384로 컴파일한 ViT는 실제 입력이 1,024 패치든 16,384 패치든 항상 16,384 패치 분량을 계산합니다. 실제 서빙에서는 한 요청에 크기가 다양한 여러 이미지, 혹은 프레임 수가 다른 비디오가 섞여 들어오므로, 가장 큰 입력 기준으로 단일 max_seq_len을 설정하면 작은 입력에서도 그 용량을 그대로 소모하므로 지연이 늘고 메모리가 낭비됩니다.

optimum-rbln은 이 문제를 multi-size 입력에 대한 버케팅 전략으로 해결합니다. visual.max_seq_len은 int 하나뿐 아니라 List[int]도 받으며, 동작은 다음과 같습니다.

  • 리스트로 지정하면 optimum-rbln은 길이마다 별도의 ViT 그래프를 한꺼번에 컴파일합니다.
  • 추론 시 실제 패치 수에 맞춰 그 값을 담을 수 있는 가장 작은 버킷이 자동으로 선택됩니다.
  • 모든 버킷보다 큰 입력이 들어오면 에러가 발생합니다. 리스트의 가장 큰 값은 워크로드 상한을 포함할 수 있도록 설정해야 합니다.

Model Zoo compile.py의 단일 값(max_seq_len: 16384)을 리스트로 바꿔 세 개의 버킷을 함께 컴파일하는 예제입니다.

import os

from optimum.rbln import RBLNAutoModelForImageTextToText

model_id = "Qwen/Qwen3-VL-2B-Instruct"
model = RBLNAutoModelForImageTextToText.from_pretrained(
    model_id,
    export=True,
    rbln_config={
        "visual": {
            "max_seq_len": [1024, 3136, 16384],  # ← 세 개의 버킷을 함께 컴파일
            "num_devices": 1,
            "create_runtimes": False,
        },
        "num_devices": 1,
        "kvcache_partition_len": 8_192,
        "max_seq_len": 262_144,
        "create_runtimes": False,
    },
)
model.save_pretrained(os.path.basename(model_id))

이렇게 컴파일한 모델은 런타임에 다음과 같이 동작합니다.

입력 패치 수 선택되는 버킷
800 1024
2000 3136
5000 16384
20000 에러: 모든 버킷 초과

Note

max_seq_len의 각 값은 ViT가 이미지 한 장(또는 비디오의 temporal patch 하나, 즉 2프레임)에서 처리할 패치 수의 상한입니다. ViT는 spatial_merge_size=2 병합 이전의 패치 수를 기준으로 하므로, Qwen3-VL(patch_size=16)에서 해상도 H × W 이미지의 패치 수는 다음과 같이 계산할 수 있습니다.

patches = (H / 16) × (W / 16)

H와 W는 processor가 리사이즈한 뒤의 크기이며, 각 변은 가장 가까운 32의 배수로 맞춰집니다. 예를 들어 512×512는 1,024개, 896×896은 3,136개, 2048×2048은 16,384개입니다. 앞선 예제의 버킷 값(1024, 3136, 16384)은 각각 이 세 해상도까지를 커버합니다. Model Zoo inference.py는 processor를 max_pixels=2048 * 2048로 생성하므로 리사이즈된 이미지는 16,384 패치를 넘지 않으며, 이는 가장 큰 버킷과 일치합니다.

Caution

버킷 수가 많을수록 컴파일 시간과 디바이스 메모리 사용량이 함께 늘어납니다. Rebel100™에서는 모든 ViT 버킷이 LM, KV cache와 함께 NPU 1개를 공유하므로, 너무 많은 가짓수를 넣으면 디바이스 메모리가 부족해질 수 있습니다. 실제 트래픽의 해상도 분포를 보고 고른 2–4개 정도면 일반적으로 충분합니다.

Tip

버케팅의 일반적인 동작 원리, 트레이드오프, 모델 zoo 예제는 Bucketing 일반 가이드에 정리되어 있습니다. ViT 입력 길이 버케팅은 이를 지원하는 모델에서만 리스트 입력이 유효하며, 지원 여부는 각 모델의 RBLN config 파일에서 vision submodule의 max_seq_len 필드가 Union[int, List[int]]로 선언되어 있는지 보면 알 수 있습니다. LM 측 max_seq_len은 단일 int로만 선언되어 있어 리스트 입력을 받지 않습니다.

디코더 배치 크기 버케팅: decoder_batch_sizes

Qwen3-VL의 언어 모델 디코더 역시 그래프 모양이 컴파일 타임에 고정됩니다. batch_size=4로 컴파일된 디코더는 실제 배치가 3이든 4든 항상 4 슬롯 분량을 계산합니다. 하지만 실제 서빙(continuous batching, in-flight batching)에서는 요청마다 끝나는 시점이 달라 실제 배치 크기가 자주 줄어듭니다. 이때마다 사용되지 않는 슬롯에 대한 계산이 그대로 낭비됩니다.

decoder_batch_sizes는 이 문제를 디코더 그래프 차원의 버케팅으로 해결합니다. LM 측 top-level 필드(RBLNDecoderOnlyModelConfig에 정의)이며 List[int]를 받고, 동작은 다음과 같습니다.

  • 리스트로 주면 optimum-rbln은 배치 크기마다 별도의 디코더 그래프(decoder_batch_{N})를 함께 컴파일합니다.
  • 모든 값은 batch_size 이하여야 하며, 최댓값은 batch_size와 같아야 합니다(작으면 자동으로 추가되며 경고가 출력됩니다).
  • 추론 시에는 실제 배치 크기를 기준으로 가장 적합한 디코더 그래프가 선택되어 디코딩 단계에서 사용됩니다.

Model Zoo compile.py 설정에 batch_size=4를 추가하고 1, 2, 4 세 개의 디코더 그래프를 함께 만드는 예제입니다. max_seq_len도 32,768로 낮췄습니다. batch_size=4에서는 Model Zoo의 262,144 토큰 컨텍스트가 ViT와 함께 Rebel100™ 1장에 들어가지 않아 로드 시 Failed to create RBLN runtime ... Lack of device memory 오류가 발생합니다. 32,768이면 두 submodule이 NPU 1장에 함께 로드됩니다. 더 큰 batch에서 전체 컨텍스트를 유지하려면 visual을 다른 NPU에 배치해야 합니다(다중 모듈 모델 문제 해결 참고).

rbln_config = {
    "visual": {
        "max_seq_len": 16384,
        "num_devices": 1,
        "create_runtimes": False,
    },
    "num_devices": 1,
    "kvcache_partition_len": 8_192,
    "max_seq_len": 32_768,  # ← batch_size=4에서 262_144는 Rebel100™ 1장에 들어가지 않음
    "batch_size": 4,
    "decoder_batch_sizes": [1, 2, 4],  # ← 3개의 디코더 그래프 함께 컴파일
    "create_runtimes": False,
}

이렇게 컴파일한 모델은 런타임에 입력으로 들어오는 실제 배치 크기에 따라 다음과 같은 디코더 그래프를 사용합니다.

실제 배치 선택되는 디코더 그래프
1 decoder_batch_1
2 decoder_batch_2
3–4 decoder_batch_4

Note

디코더 그래프는 서빙 엔진이 디코딩 단계마다 고릅니다. RBLN vLLM은 이 그래프들을 decode batch bucket으로 사용합니다. optimum-rbln만 사용할 때 Qwen3-VL의 generate()는 batch_size 그래프를 실행하며 정확히 batch_size개의 시퀀스를 입력으로 받습니다. 더 작은 배치를 넣으면 Cache position size mismatch 에러가 발생합니다.

Caution

디코더 그래프 수가 많을수록 컴파일 시간과 디바이스 메모리 사용량이 늘어납니다. 보통 1, batch_size//2, batch_size처럼 2–4개 정도를 고르며, 트래픽 패턴에 맞춰 조정할 수 있습니다. Rebel100™에서는 batch_size가 커질수록 같은 NPU의 KV cache도 커지므로, runtime 생성에 실패할 때의 대응 방법은 그 외 시나리오에 정리되어 있습니다.

Tip

ViT 입력 길이 버케팅(visual.max_seq_len)과 디코더 배치 크기 버케팅(decoder_batch_sizes)은 서로 독립적이며, 한 컴파일 안에서 함께 사용할 수 있습니다.

그 외 시나리오

특정 메모리, device 환경이나 컴파일, 추론 워크플로우는 다중 모듈 모델 트러블슈팅에서 단계별로 다룹니다. 자주 마주치는 시나리오와 해당 가이드는 다음과 같습니다.

시나리오 가이드
컴파일과 runtime 생성 분리(create_runtimes: False) Step 1
큰 batch에서 KV cache 블록 pool 고갈(kvcache_num_blocks 조정) Step 3
실제 워크로드에 맞춘 LM 컨텍스트 길이 제한 Step 4
타겟 해상도에 맞춘 ViT 입력 적정화 Step 5

Note

트러블슈팅 가이드는 Rebel100™과 ATOM™ 설정을 별도의 탭으로 보여줍니다. 적용할 때는 Rebel100™ 탭의 설정을 따르면 됩니다.


관련 자료