콘텐츠로 이동

Qwen3-VL-2B (VLM)

개요

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

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

예제 모델은 Qwen/Qwen3-VL-2B-Instruct이며, 코드 예제의 기본값은 RBLN Model Zoo의 compile.py, inference.py와 일치합니다.

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": 8,
    },
    "num_devices": 8,           # ← 최상위 (LM)
    "kvcache_partition_len": 16_384,
    "max_seq_len": 262_144,
    "device": [0, 1, 2, 3, 4, 5, 6, 7],
}

Tip

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

Note

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


submodule의 rbln_config 사용하기

이 섹션에서는 rbln_configoptimum-rbln의 VLM을 운용할 때 자주 사용하는 패턴을 정리했습니다. submodule과 top 모델에 device를 분리 배치하는 단순 예제로 시작해, 두 축의 버케팅, 그 외 메모리 및 워크플로우 시나리오로 이어집니다.

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 수
visual.device submodule (visual) visual runtime이 올라갈 device(들)
visual.create_runtimes submodule (visual) 컴파일 시 visual runtime 생성 여부
num_devices top (LM) LM에 사용할 device 수
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별로 다르게 줄 수 있지만, 각 submodule의 device 리스트 길이와 반드시 일치해야 합니다.
  • batch_size는 모델 전체에 단일 값으로 적용되며, submodule별로 따로 지정할 수 없습니다. visual과 LM은 배치를 다른 방식으로 실행하지만 optimum-rbln이 내부에서 조율합니다.

submodule과 top 모델의 device 분리 배치

rbln_config의 2-레벨 구조 덕분에 submodule과 top 모델의 device를 나누어 배치할 수 있습니다. 예를 들어 16 device 서버에서 rbln_configdevice를 사용하여 visual을 앞쪽 8개에, LM을 뒤쪽 8개에 분리해 두면, 어떤 device도 두 submodule의 메모리를 동시에 보유하지 않아 큰 배치와 긴 컨텍스트에서도 메모리 부족을 피할 수 있습니다.

rbln_config={
    "visual": {
        "max_seq_len": 16384,
        "num_devices": 8,
        "device": [0, 1, 2, 3, 4, 5, 6, 7],     # visual은 device 0–7
    },
    "num_devices": 8,
    "kvcache_partition_len": 16_384,
    "max_seq_len": 262_144,
    "batch_size": 8,
    "device": [8, 9, 10, 11, 12, 13, 14, 15],   # LM은 device 8–15
}

8 device 환경에서 두 모델이 같은 pool을 공유하는 baseline이나 device, 메모리 압박이 발생할 때의 진단 흐름은 그 외 시나리오에서 트러블슈팅 가이드로 안내합니다.

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_lenint 하나뿐 아니라 List[int]도 받으며, 동작은 다음과 같습니다.

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

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

import os

from optimum.rbln import RBLNQwen3VLForConditionalGeneration

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

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

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

Note

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

    merged patches = (H / 16 / 2) × (W / 16 / 2)
    ```

    예를 들어 1024×1024는 1,024개, 1792×1792는 3,136개, 4096×4096는 16,384개입니다. 앞선 예제의 버켓 값(1024, 3136, 16384)은 각각 이 세 해상도까지를 커버합니다.

!!! caution
    버켓 수가 많을수록 컴파일 시간과 디바이스 메모리 사용량이 함께 늘어납니다. 너무 많은 가짓수를 넣으면 디바이스 메모리가 부족해질 수 있으니, 실제 트래픽의 해상도 분포를 보고 2–4개 정도를 고르는 것을 추천합니다.

!!! tip
    버케팅의 일반적인 동작 원리, 트레이드오프, 모델 zoo 예제는 [Bucketing 일반 가이드](../../api/python/tutorial/advanced/bucketing.md)에 정리되어 있습니다. ViT 입력 길이 버케팅은 이를 지원하는 모델에서만 리스트 입력이 유효하며, 지원 여부는 각 모델의 RBLN config 파일에서 vision submodule의 `max_seq_len` 필드가 `Union[int, List[int]]`로 선언되어 있는지 확인하세요. LM 측 `max_seq_len`은 단일 `int`로만 선언되어 있어 리스트 입력을 받지 않습니다.

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

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

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

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

`batch_size=8`로 컴파일하면서 1, 2, 4, 8 네 개의 디코더 그래프를 함께 만드는 예제입니다.

```python
rbln_config={
    "visual": {
        "max_seq_len": 16384,
        "num_devices": 8,
    },
    "num_devices": 8,
    "kvcache_partition_len": 16_384,
    "max_seq_len": 262_144,
    "batch_size": 8,
    "decoder_batch_sizes": [1, 2, 4, 8],   # ← 4개의 디코더 그래프 함께 컴파일
    "device": [0, 1, 2, 3, 4, 5, 6, 7],
}

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

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

Caution

디코더 그래프 수가 많을수록 컴파일 시간과 디바이스 메모리 사용량이 늘어납니다. 보통 1, batch_size//2, batch_size처럼 2–4개 정도를 고르는 것이 일반적이며, 트래픽 패턴에 맞춰 조정하세요.

Tip

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

그 외 시나리오

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

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

관련 자료