콘텐츠로 이동

다중 모듈 모델 트러블슈팅

이 가이드는 다중 모듈 Vision-Language 모델을 RBLN NPU에서 컴파일·배포할 때 자주 발생하는 이슈를 Qwen3-VL-2B Model Zoo 예제를 기준으로 단계별로 다룹니다. 독립적인 submodule로 구성된 Optimum RBLN 모델 전반에 같은 패턴이 적용됩니다.

전제 조건

아래 예제는 tensor_parallel_size=8을 사용하며, Qwen3-VL-2B Model Zoo 컴파일 설정과 일치합니다. 이 가이드는 두 가지 device layout을 사용합니다.

  • 8 ATOM™ (device 0–7) — Model Zoo baseline. 두 submodule이 8 device pool을 공유합니다. 기본 컴파일 설정과 소규모 batch에서 동작합니다.
  • 16 ATOM™ (device 0–15) — visual과 LM을 disjoint pool (각각 8–15, 0–7)로 분리합니다. 이 가이드 전반의 batch_size=8 시나리오처럼 메모리 부담이 큰 설정에서 요구됩니다.

rbln-smi로 사용 가능한 device를 확인하고, 하드웨어에 맞게 tensor_parallel_size와 device 할당을 조정하세요.

요약

증상 근본 원인 해결
컴파일 직후 Failed to create RBLN runtime 기본값 create_runtimes=True가 컴파일 중 runtime 생성을 수행하며 실패 Step 1
저장된 모델 로딩 중 Failed to create RBLN runtime 모든 submodule이 같은 [0..TP-1] pool로 기본 배치되며 device당 합산 footprint가 device memory를 초과 Step 2
No memory blocks are available for allocation 큰 batch에서 paged attention 블록 pool 고갈 Step 3
Memory is not enough for full sequence length KV 캐시가 실제 워크로드가 아닌 아키텍처 최대치로 설정됨 Step 4
작은 이미지에 비해 ViT 지연이 높음 max_seq_lens가 실제 해상도 대비 과다 할당됨 Step 5

Step 1: Runtime 생성을 분리해서 컴파일하기

증상

batch_size=8로 컴파일하면 컴파일 완료 직후 크래시가 발생합니다:

from optimum.rbln import RBLNAutoModelForVision2Seq

model = RBLNAutoModelForVision2Seq.from_pretrained(
    "Qwen/Qwen3-VL-2B-Instruct",
    export=True,
    rbln_config={
        "visual": {
            "max_seq_lens": 16384,
            "tensor_parallel_size": 8,
        },
        "tensor_parallel_size": 8,
        "kvcache_partition_len": 16_384,
        "max_seq_len": 262_144,
        "batch_size": 8,
    },
)
1
2
3
4
5
Failed to create RBLN runtime: ...

If you only need to compile the model without loading it to NPU, you can use:
  from_pretrained(..., rbln_create_runtimes=False) or
  from_pretrained(..., rbln_config={..., 'create_runtimes': False})

근본 원인

from_pretrained(..., export=True)는 두 단계를 순차적으로 수행합니다:

  1. 모델을 RBLN IR로 컴파일 — 성공.
  2. 컴파일된 모듈을 device memory에 올려 runtime을 생성 — 실패.

Qwen3-VL은 독립적인 두 submodule로 구성됩니다.

  • visual — 이미지·비디오 프레임을 인코딩하는 Vision Transformer
  • model — 텍스트를 생성하는 causal language model

runtime 생성 시 각 submodule의 devicelist(range(tensor_parallel_size)) — TP=8이면 [0, 1, …, 7] — 로 기본 배치됩니다. 따라서 visual과 LM 모두 동일한 8 device pool에 샤딩되며, 각 device는 ViT 슬라이스, LM tensor-parallel shard, KV 캐시의 일부를 함께 보유합니다. device당 합산 footprint가 device memory를 초과하면 runtime 생성이 실패합니다.

해결

create_runtimes의 기본값은 True이므로 from_pretrained는 컴파일 직후 runtime 생성 단계로 진행하며 이 단계에서 실패합니다. submodule 레벨과 최상위 레벨 양쪽에 create_runtimes: False를 설정하면 from_pretrained가 컴파일 직후 종료되어 실패하는 runtime 생성 단계를 건너뜁니다. 이는 Model Zoo compile.py의 표준 패턴입니다.

import os

from optimum.rbln import RBLNAutoModelForVision2Seq

model_id = "Qwen/Qwen3-VL-2B-Instruct"
model = RBLNAutoModelForVision2Seq.from_pretrained(
    model_id,
    export=True,
    rbln_config={
        "visual": {
            "max_seq_lens": 16384,
            "tensor_parallel_size": 8,
            "create_runtimes": False,                   # ← 추가
        },
        "tensor_parallel_size": 8,
        "kvcache_partition_len": 16_384,
        "max_seq_len": 262_144,
        "batch_size": 8,
        "create_runtimes": False,                       # ← 추가
    },
)
model.save_pretrained(os.path.basename(model_id))

컴파일이 완료되고 산출물이 디스크에 저장됩니다. 근본 메모리 압박은 해소되지 않으며 로드 시점에 다시 드러납니다. 이는 Step 2에서 명시적 device 배치로 처리합니다.

레벨별 필드 정리:

필드 레벨 용도
visual.max_seq_lens submodule (ViT) ViT 그래프가 이미지·프레임당 받아들이는 최대 merged patch 수
visual.tensor_parallel_size submodule (ViT) ViT의 TP degree
visual.create_runtimes submodule (ViT) 컴파일 시 ViT runtime 생성을 건너뜀
tensor_parallel_size top (LM) Language model의 TP degree
kvcache_partition_len top (LM) Flash attention 파티션 크기 — max_seq_len을 나누어 떨어져야 함
max_seq_len top (LM) LM의 최대 position embedding — kvcache_partition_len의 배수여야 함
create_runtimes top (LM) 컴파일 시 LM runtime 생성을 건너뜀

Tip

다중 모듈 모델을 컴파일할 때는 항상 create_runtimes: False를 사용하세요.


Step 2: 로드 시점에 submodule을 device별로 분리 배치하기

증상

Step 1에서 batch_size=8로 컴파일한 모델을 device 힌트 없이 로드합니다:

1
2
3
4
model = RBLNAutoModelForVision2Seq.from_pretrained(
    "Qwen3-VL-2B-Instruct",
    export=False,
)

이번에도 로드 중에 Failed to create RBLN runtime이 발생합니다.

근본 원인

Step 1과 동일한 메모리 압박입니다. 명시적인 device 설정이 없으면 visual과 LM이 기본 pool [0..TP-1]을 공유하며, 각 device의 누적 ViT + LM + KV 캐시 footprint가 device memory를 초과합니다. Step 1은 runtime 생성을 건너뛰어 이를 우회했지만, 저장된 산출물을 로드하는 시점에 다시 드러납니다.

해결

submodule마다 device 리스트를 명시합니다. Step 1batch_size=8 컴파일에 대해서는 visual과 LM을 16 ATOM™에 걸친 disjoint pool로 배치하여 어떤 device도 ViT와 LM 메모리를 동시에 담지 않도록 합니다.

from optimum.rbln import RBLNAutoModelForVision2Seq

model = RBLNAutoModelForVision2Seq.from_pretrained(
    "Qwen3-VL-2B-Instruct",
    export=False,
    rbln_config={
        "visual": {
            "device": [8, 9, 10, 11, 12, 13, 14, 15],   # ViT는 device 8–15
        },
        "device": [0, 1, 2, 3, 4, 5, 6, 7],             # LM은 device 0–7
    },
)

8 ATOM™만 가용할 때는 Model Zoo inference.py의 공유 pool 구성(visual과 LM 모두 device 0–7)을 사용합니다. 합산 footprint가 각 device에 들어맞아야 동작하므로 Step 1의 batch_size=8 컴파일은 이 layout으로 로드되지 않습니다. Step 3 또는 Step 4로 워크로드를 먼저 줄이세요.


Step 3: 더 큰 batch는 vllm-rbln으로 서빙

증상

batch가 커지면 paged attention 블록 pool이 runtime에서 고갈됩니다:

1
2
3
4
RuntimeError: No memory blocks are available for allocation.
The generate() API cannot complete this inference task because Paged Attention is not fully supported by optimum-rbln.
This is supported by vllm-rbln (see: https://docs.rbln.ai/software/model_serving/vllm_support/vllm-rbln.html).
Using vllm-rbln should fix this issue and enhance inference performance.

에러 메시지 자체가 vllm-rbln 사용을 권고하며, 본 step은 그 마이그레이션 경로를 단계별로 안내합니다.

근본 원인

KV 캐시는 paged attention을 사용합니다. optimum-rbln만 단독 사용할 경우 블록 pool은 컴파일 타임에 고정된 kvcache_num_blocks로 선할당되며, 활성 batch가 예약된 블록 수를 초과하면 생성 중에 pool이 고갈되어 위 에러가 발생합니다.

해결

컴파일된 산출물을 직접 호출하지 말고 vllm-rbln을 통해 서빙하세요. vllm-rbln은 paged-attention 블록 pool을 동적으로 관리합니다. 블록 할당, eviction, admission control이 엔진 차원에서 처리되므로 pool을 초과하는 워크로드는 큐잉되거나 안전하게 거부될 뿐 생성 도중 크래시되지 않습니다.

optimum-rbln 단독 운용 — kvcache_num_blocks 조정

optimum-rbln 단독으로 운용해야 한다면 batch 전체가 들어가도록 kvcache_num_blocks를 명시합니다.

num_full_blocks = batch_size × (max_seq_len / kvcache_block_size)

flash attention 모드에서는 kvcache_block_sizekvcache_partition_len과 같으므로, 위 식에서 두 값은 동일하게 다룰 수 있습니다.

1
2
3
4
5
6
7
8
9
rbln_config={
    "visual": {...},                 # 전체 visual 블록은 Step 1 참고
    "tensor_parallel_size": 8,
    "kvcache_partition_len": 16_384,
    "max_seq_len": 262_144,
    "kvcache_num_blocks": 128,       # = num_full_blocks
    "batch_size": 8,
    "create_runtimes": False,
}

유효 범위:

(max_seq_len / kvcache_block_size) + 1   ≤   kvcache_num_blocks   ≤   num_full_blocks

이 구성의 kvcache_num_blocks 최소 유효값은 (max_seq_len / kvcache_block_size) + 1 = 17입니다. 유효 범위 안에서 그보다 작은 값을 쓰면 추론이 정상적으로 시작되더라도 max_seq_len에 도달하기 전에 pool이 고갈되어 OOM이 발생합니다.


Step 4: 실제 워크로드에 맞춘 LM 컨텍스트 길이 제한

증상

batch가 커질 때 컴파일이 다음 에러로 거부됩니다:

ValueError: Memory is not enough for full sequence length.
Please consider decreasing `max_seq_len` to reduce the number of blocks.

근본 원인

flash attention 모드(kvcache_partition_len 설정 시 활성화)에서 KV 캐시는 컴파일 타임에 미리 할당됩니다. 메모리 footprint는 다음과 같이 스케일합니다:

KV memory  ∝  batch_size  ×  (max_seq_len / kvcache_partition_len)

Model Zoo compile.pymax_seq_len을 262,144 — Qwen3-VL의 아키텍처 최대치 — 로 설정합니다. batch_size > 1이면 엔진이 시퀀스마다 256K 토큰 크기의 슬롯을 예약하므로, 실제 요청이 훨씬 짧더라도 device memory가 빠르게 고갈됩니다.

해결

max_seq_len을 타겟 워크로드의 현실적 상한으로 줄입니다. 요청이 32K 토큰 이하라면:

rbln_config={
    "visual": {
        "max_seq_lens": 16384,
        "tensor_parallel_size": 8,
        "create_runtimes": False,
    },
    "tensor_parallel_size": 8,
    "kvcache_partition_len": 16_384,
    "max_seq_len": 32_768,           # 262_144 → 32_768
    "batch_size": 8,
    "create_runtimes": False,
}
파라미터 제약 효과
max_seq_len kvcache_partition_len의 배수 시퀀스당 KV 메모리 상한 결정
kvcache_partition_len Flash attention 파티션 크기 작을수록 유연, 클수록 오버헤드 적음
batch_size 동시 시퀀스 수 KV 메모리에 선형 비례

max_seq_len을 절반으로 줄이면 KV 캐시 메모리도 대략 절반이 됩니다. 긴 컨텍스트와 큰 batch가 동시에 필요하다면 Step 3과 조합하세요. 먼저 max_seq_len을 줄이고, 남은 메모리 예산 안에서 kvcache_num_blocks를 조정합니다.


Step 5: 타겟 해상도에 맞춰 ViT 입력 적정화

증상

배치 추론은 정상 동작하지만, 작은 이미지에서도 ViT 지연이 예상보다 높게 나타납니다.

근본 원인

visual.max_seq_lens는 ViT 그래프가 이미지·비디오 프레임당 받아들이는 최대 merged patch 수를 결정합니다. Model Zoo compile.py는 기본값 16,384를 사용하며, 이는 대략 4096×4096까지의 모든 입력을 커버하는 보수적 상한입니다. 실제 배포나 서빙 시나리오의 최대 해상도가 그보다 작다면, max_seq_lens를 해당 시나리오의 상한에 맞춰 축소해 ViT 연산·메모리 낭비를 줄일 수 있습니다.

merged patch 수는 모델의 patch_sizespatial_merge_size (두 값 모두 config.json에 정의)에 따라 결정됩니다.

patches = (H / patch_size / spatial_merge_size) × (W / patch_size / spatial_merge_size)

Qwen3-VL의 경우 (patch_size=16, spatial_merge_size=2):

이미지 해상도 Merged patches
1792 × 1792 3,136
4096 × 4096 16,384

1792×1792 배포 환경에서 max_seq_lens: 16384로 컴파일하면 실제 사용량 대비 약 5배의 patch 슬롯을 예약합니다 (16,384 vs. 3,136). ViT는 실제 입력과 무관하게 컴파일된 용량 그대로 실행되므로, 초과분은 그대로 지연과 메모리 낭비로 이어집니다.

해결

두 파라미터는 파이프라인으로 연결되며 반드시 일치해야 합니다.

  1. processor.max_pixels — patch 추출 전에 이미지 크기를 제한합니다.
  2. rbln_config["visual"]["max_seq_lens"] — 컴파일된 ViT 그래프가 받아들이는 patch 수를 제한합니다.
관계 결과
max_pixels가 만드는 patch 수가 max_seq_lens 범위 안에 들어감 추론 정상 동작
max_pixels가 만드는 patch 수가 max_seq_lens와 근접함 연산·메모리 효율 유지

1792×1792을 상한으로 하는 배포의 경우는 다음과 같이 설정합니다.

컴파일

rbln_config={
    "visual": {
        "max_seq_lens": 3136,
        "tensor_parallel_size": 8,
        "create_runtimes": False,
    },
    "tensor_parallel_size": 8,
    "kvcache_partition_len": 16_384,
    "max_seq_len": 262_144,
    "batch_size": 8,
    "create_runtimes": False,
}

로드

1
2
3
4
5
processor = AutoProcessor.from_pretrained(
    model_id,
    min_pixels=256 * 16 * 16,
    max_pixels=1792 * 1792,
)

Note

max_seq_lens는 컴파일된 그래프의 모양을 고정합니다. 모델이 이론적으로 지원하는 최대치가 아니라 해당 배포의 현실적 최대치에 맞춰 설정하세요. 배포 해상도가 커지면 다시 컴파일해야 합니다.

Bucketing — 가변 이미지 크기

배포가 단일 해상도가 아니라 다양한 이미지 크기를 처리해야 한다면, ViT를 여러 max_seq_lens bucket으로 컴파일합니다. runtime이 요청마다 가장 작은 적합 그래프를 선택하므로, 큰 입력에 필요한 그래프를 유지하면서도 작은 입력에서 앞서 설명한 연산 낭비를 피할 수 있습니다.

1
2
3
4
5
6
7
8
rbln_config={
    "visual": {
        "max_seq_lens": [3136, 8192, 16384],   # 배포 해상도 분포에 맞춘 tier
        "tensor_parallel_size": 8,
        "create_runtimes": False,
    },
    ...
}

이미지 크기 bucketing 적용 예시는 VLM 튜토리얼을 참고하세요.

관련 자료