다중 모듈 모델 트러블슈팅¶
이 가이드는 다중 모듈 Vision-Language 모델을 RBLN NPU에서 컴파일·배포할 때 자주 발생하는 이슈를 Qwen3-VL-2B Model Zoo 예제를 기준으로 단계별로 다룹니다. 독립적인 submodule로 구성된 RBLN Optimum 모델 전반에 같은 패턴이 적용됩니다.
전제 조건
아래 예제는 제품별 Qwen3-VL-2B Model Zoo 컴파일 설정을 따르며, 장비 구성은 아래 탭과 같습니다. rbln-smi로 사용 가능한 device를 확인하고, 하드웨어에 맞게 num_devices와 device 할당을 조정해야 합니다.
아래 예제는 num_devices=1을 사용하며, Rebel100™ Qwen3-VL-2B Model Zoo 컴파일 설정(kvcache_partition_len=8_192)과 일치합니다. Rebel100™은 하나의 submodule을 여러 NPU에 나눠 올리지 않지만, submodule마다 다른 NPU에 배치할 수는 있습니다. 이 가이드는 두 가지 device layout을 사용합니다.
- 1 Rebel100™(device 0): Model Zoo baseline. ViT와 LM이 NPU 1장을 공유합니다. 기본 컴파일 설정과
batch_size=1에서 동작합니다. - 2 Rebel100™(device 0, 1): ViT와 LM을 서로 다른 NPU에 배치합니다. 이 가이드 전반의
batch_size=8시나리오처럼 메모리 부담이 큰 설정에서는 이 구성이 필요합니다.
아래 예제는 num_devices=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): ViT와 LM을 disjoint pool(각각 8–15, 0–7)로 분리합니다. 이 가이드 전반의
batch_size=8시나리오처럼 메모리 부담이 큰 설정에서는 이 구성이 필요합니다.
요약¶
| 증상 | 근본 원인 | 해결 |
|---|---|---|
컴파일 직후 Failed to create RBLN runtime | 기본값 create_runtimes=True가 컴파일 중 runtime 생성을 수행하며 실패 | Step 1 |
저장된 모델 로딩 중 Failed to create RBLN runtime | 모든 submodule이 같은 [0..num_devices-1] pool로 기본 배치되며 device당 합산 footprint가 device memory를 초과 | Step 2 |
No memory blocks are available for allocation | 큰 batch에서 paged attention 블록 pool 고갈 | Step 3 |
Insufficient memory for the required KV cache | KV 캐시가 실제 워크로드가 아닌 아키텍처 최대치로 설정됨 | Step 4 |
| 작은 이미지에 비해 ViT 지연이 높음 | max_seq_len가 실제 해상도 대비 과다 할당됨 | Step 5 |
Step 1: Runtime 생성을 분리해서 컴파일하기¶
증상¶
batch_size=8로 컴파일하면 컴파일 완료 직후 크래시가 발생합니다:
근본 원인¶
from_pretrained(..., export=True)는 두 단계를 차례로 실행합니다:
- 모델을 RBLN IR로 컴파일(성공).
- 컴파일된 모듈을 device memory에 올려 runtime을 생성(실패).
Qwen3-VL은 독립적인 두 submodule로 구성됩니다.
visual: 이미지·비디오 프레임을 인코딩하는 Vision Transformermodel: 텍스트를 생성하는 causal language model
runtime 생성 시 각 submodule의 device는 list(range(num_devices))로 기본 배치되며, num_devices=1이면 [0]입니다. 따라서 ViT와 LM이 KV 캐시와 함께 같은 NPU에 올라갑니다. batch_size=8, max_seq_len=262_144에서는 합산 footprint가 Rebel100™ 1장의 140 GiB를 넘어, runtime 생성이 Failed to create RBLN runtime: (System) code=511 SYS_EBUSY: Lack of device memory 오류로 실패합니다.
runtime 생성 시 각 submodule의 device는 list(range(num_devices))로 기본 배치되며, num_devices=8이면 [0, 1, …, 7]입니다. 따라서 ViT와 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 생성 단계를 건너뜁니다. RBLN Model Zoo compile.py도 이 패턴을 사용합니다.
컴파일이 완료되고 산출물이 디스크에 저장됩니다. 근본 메모리 압박은 해소되지 않으며 로드 시점에 다시 드러납니다. 이는 Step 2에서 명시적 device 배치로 처리합니다.
레벨별 필드 정리:
| 필드 | 레벨 | 용도 |
|---|---|---|
visual.max_seq_len | submodule(ViT) | ViT 그래프가 이미지·프레임당 받아들이는 최대 patch 수(spatial merge 이전 기준) |
visual.num_devices | submodule(ViT) | ViT에 사용할 device 수 |
visual.create_runtimes | submodule(ViT) | 컴파일 시 ViT runtime 생성을 건너뜀 |
num_devices | top(LM) | Language model에 사용할 device 수 |
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 힌트 없이 로드합니다:
이번에도 로드 중에 Failed to create RBLN runtime이 발생합니다.
근본 원인¶
Step 1과 동일한 메모리 압박입니다. 명시적인 device 설정이 없으면 ViT와 LM이 기본 pool [0..num_devices-1]을 공유하며, 각 device의 누적 ViT + LM + KV 캐시 footprint가 device memory를 초과합니다. Step 1은 runtime 생성을 건너뛰어 이를 우회했지만, 저장된 산출물을 로드하는 시점에 다시 드러납니다.
해결¶
submodule마다 device를 명시합니다. Step 1의 batch_size=8 컴파일에 대해서는 ViT와 LM을 서로 다른 Rebel100™ NPU에 배치하여 어떤 NPU도 ViT와 LM 메모리를 동시에 담지 않도록 합니다.
이 layout에서는 batch_size=8, max_seq_len=262_144 컴파일이 로드되고 생성까지 동작하며, KV 캐시는 150블록으로 추정됩니다.
Rebel100™ 1장만 가용할 때는 RBLN Model Zoo inference.py의 구성(ViT와 LM 모두 device 0)을 사용합니다. 합산 footprint가 NPU에 들어맞아야 동작하므로 Step 1의 batch_size=8 컴파일은 이 layout으로 로드되지 않습니다. Step 3 또는 Step 4로 워크로드를 먼저 줄여야 합니다.
submodule마다 device 리스트를 명시합니다. Step 1의 batch_size=8 컴파일에 대해서는 ViT와 LM을 16 ATOM™에 걸친 disjoint pool로 배치하여 어떤 device도 ViT와 LM 메모리를 동시에 담지 않도록 합니다.
8 ATOM™만 가용할 때는 RBLN Model Zoo inference.py의 공유 pool 구성(ViT와 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에서 고갈됩니다:
에러 메시지 자체가 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를 명시합니다.
flash attention 모드에서는 kvcache_block_size가 kvcache_partition_len과 같으므로, 위 식에서 두 값은 동일하게 다룰 수 있습니다.
유효 범위:
이 구성의 kvcache_num_blocks 최소 유효값은 max((max_seq_len / kvcache_block_size), batch_size) + 1 = 33이고 전량은 256이며, 이보다 작은 값은 컴파일 시점에 거부됩니다. max_seq_len / kvcache_block_size = 32가 batch_size = 8을 초과하므로 시퀀스 길이 항이 하한을 결정합니다. 다만 전량은 Rebel100™ 1장에 들어가지 않습니다. 256블록으로 컴파일하면 DRAM OOM으로 실패합니다(요청 39,163,045,120바이트, 용량 38,654,705,664바이트). kvcache_num_blocks를 지정하지 않으면 추정이 들어가는 최대 pool인 150블록을 고르며, 예제는 이 값을 사용합니다.
이 구성의 kvcache_num_blocks 최소 유효값은 max((max_seq_len / kvcache_block_size), batch_size) + 1 = 17이고 전량은 128이며, 이보다 작은 값은 컴파일 시점에 거부됩니다. max_seq_len / kvcache_block_size = 16이 batch_size = 8을 초과하므로 시퀀스 길이 항이 하한을 결정합니다. 다만 전량은 장치 8개에 들어가지 않습니다. 128블록으로 컴파일하면 DRAM OOM으로 실패합니다(요청 17,639,009,280바이트, 장치당 용량 17,179,869,184바이트). kvcache_num_blocks를 지정하지 않으면 추정이 들어가는 최대 pool인 69블록을 고르며, 예제는 이 값을 사용합니다.
유효 범위 안에서도 전량보다 작은 값을 쓰면 추론이 정상적으로 시작되더라도 max_seq_len에 도달하기 전에 pool이 고갈되어 OOM이 발생할 수 있습니다.
batch_size 항은 batch 내 각 시퀀스가 추론 시작 시 최소 1개의 블록을 확보하도록 보장하고, + 1은 시퀀스가 계속 길어지는 동안 블록 테이블의 빈 슬롯을 채우기 위한 여분 블록입니다. batch가 max_seq_len / kvcache_block_size를 초과하는 워크로드에서는 batch_size 항이 하한을 결정합니다.
Step 4: 실제 워크로드에 맞춘 LM 컨텍스트 길이 제한¶
증상¶
batch_size=8, max_seq_len=262_144에서는 컴파일은 성공하지만, ViT와 LM을 Rebel100™ 1장에 로드하면 실패합니다.
KV 캐시 크기는 컴파일 시점에 LM만을 기준으로 정해지므로 256블록 중 150블록을 차지하고, 로드 시점에 같은 NPU를 쓰는 ViT의 자리를 남기지 않습니다. 하한인 33블록(Step 3)조차 들어가지 않으면(예: batch_size가 훨씬 크거나 모델이 더 클 때) 컴파일 자체가 거부됩니다.
batch가 커질 때 컴파일이 다음 에러로 거부됩니다:
<ESTIMATED_BLOCKS>는 device DRAM이 허용한 블록 수이고, 17은 위 구성의 하한입니다(Step 3).
근본 원인¶
flash attention 모드(kvcache_partition_len 설정 시 활성화)에서 KV 캐시는 컴파일 타임에 미리 할당됩니다. 메모리 footprint는 다음과 같이 스케일합니다:
Model Zoo compile.py는 max_seq_len을 Qwen3-VL의 아키텍처 최대치인 262,144로 설정합니다. batch_size > 1이면 엔진이 시퀀스마다 256K 토큰 크기의 슬롯을 예약하므로, 실제 요청이 훨씬 짧더라도 device memory가 고갈됩니다.
해결¶
max_seq_len을 타겟 워크로드의 현실적 상한으로 줄입니다. 요청이 32K 토큰 이하라면:
Rebel100™ 1장에서 이 설정은 ViT와 LM을 함께 로드하고 생성까지 동작하며, KV 캐시는 전량인 32블록을 사용합니다.
| 파라미터 | 제약 | 효과 |
|---|---|---|
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_len은 ViT 그래프가 이미지 한 장, 또는 비디오의 temporal patch(2프레임) 하나당 받아들이는 최대 patch 수를 spatial merge 이전 기준으로 결정합니다. Model Zoo compile.py는 기본값 16,384를 사용하며, 이는 2048×2048까지의 입력을 커버합니다. Qwen3-VL processor는 기본적으로 약 16.7 MP(4096×4096)까지는 이미지를 줄이지 않으므로, max_pixels로 제한하지 않으면 2048×2048보다 큰 이미지에서 Required seq_len(...) is larger than available max_seq_len(...) 오류가 발생합니다(아래 해결 방법 참고). 실제 배포나 서빙 시나리오의 최대 해상도가 2048×2048보다 작다면, max_seq_len을 해당 시나리오의 상한에 맞춰 축소해 ViT 연산·메모리 낭비를 줄일 수 있습니다.
patch 수는 모델의 patch_size(config.json에 정의)에 따라 결정됩니다. ViT는 모든 patch를 처리하고, spatial_merge_size 병합은 ViT 이후에 일어나므로 patch 수를 줄이지 않습니다.
H와 W는 processor가 리사이즈한 뒤의 크기입니다. 각 변은 가장 가까운 32(patch_size × spatial_merge_size)의 배수로 맞춰지고, 면적은 min_pixels–max_pixels 범위 안으로 조정됩니다. 예를 들어 1000×1000 이미지는 992×992가 되어 3,844 patch가 됩니다.
Qwen3-VL의 경우(config.json의 patch_size=16, spatial_merge_size=2. 병합은 ViT 이후에 일어나므로 patch 수는 patch_size로만 결정됩니다):
| 이미지 해상도 | Patches |
|---|---|
| 896 × 896 | 3,136 |
| 2048 × 2048 | 16,384 |
896×896 배포 환경에서 max_seq_len: 16384로 컴파일하면 실제 사용량 대비 약 5배의 patch 슬롯을 예약합니다(16,384 vs. 3,136). ViT는 실제 입력과 무관하게 컴파일된 용량 그대로 실행되므로, 초과분은 그대로 지연과 메모리 낭비로 이어집니다.
해결¶
두 파라미터는 파이프라인으로 연결되며 반드시 일치해야 합니다.
processor.max_pixels: patch 추출 전에 이미지 크기를 제한합니다.rbln_config["visual"]["max_seq_len"]: 컴파일된 ViT 그래프가 받아들이는 patch 수를 제한합니다.
| 관계 | 결과 |
|---|---|
max_pixels가 만드는 patch 수가 max_seq_len 범위 안에 들어감 | 추론 정상 동작 |
max_pixels가 만드는 patch 수가 max_seq_len과 근접함 | 연산·메모리 효율 유지 |
896×896을 상한으로 하는 배포의 경우는 다음과 같이 설정합니다.
컴파일
로드
Note
max_seq_len은 컴파일된 그래프의 모양을 고정합니다. 모델이 이론적으로 지원하는 최대치가 아니라 해당 배포의 현실적 최대치에 맞춰 설정하는 것을 권장합니다. 배포 해상도가 커지면 다시 컴파일해야 합니다.
비디오 입력
max_pixels는 이미지에만 적용됩니다. 비디오 processor는 샘플링된 전체 프레임에 대한 자체 픽셀 예산(size["longest_edge"], 기본값 25,165,824)으로 프레임 크기를 정하므로, 위 processor 설정에서도 짧은 고해상도 영상은 max_seq_len을 넘을 수 있습니다. 예를 들어 1792×1792 영상에서 프레임 4개가 샘플링되면 temporal patch당 12,544 patch, 4096×4096 영상의 프레임 2개는 48,400 patch가 됩니다. 비디오를 입력으로 받는 배포라면 비디오 processor가 만드는 가장 큰 프레임에 맞춰 max_seq_len을 정하거나, 프레임을 미리 축소해서 넣어야 합니다.
Bucketing — 가변 이미지 크기¶
배포가 단일 해상도가 아니라 다양한 이미지 크기를 처리해야 한다면, ViT를 여러 max_seq_len bucket으로 컴파일합니다. runtime이 요청마다 가장 작은 적합 그래프를 선택하므로, 큰 입력에 필요한 그래프를 유지하면서도 작은 입력에서 앞서 설명한 연산 낭비를 피할 수 있습니다.
이미지 크기 bucketing 적용 예시는 VLM 튜토리얼에서 확인할 수 있습니다.