Qwen3-VL-2B (VLM)¶
Overview¶
This guide is intended for users who are already familiar with the basics of optimum-rbln, and introduces two key techniques for using a Vision-Language Model (VLM) effectively on a single Rebel100™ (RBLN-CR13) NPU.
- Use bucketing to efficiently handle variable input resolutions and decoder batch sizes within a single compilation.
- Configure
rbln_configper submodule to control behavior at each level.
The running example is Qwen/Qwen3-VL-2B-Instruct. All default values used in the snippets below match the Rebel100™ example in the RBLN Model Zoo (compile.py and inference.py under REBEL/huggingface/transformers/image-text-to-text/qwen3-vl/qwen3-vl-2b/).
Serving on Rebel100™
On Rebel100™, use RBLN vLLM (vllm serve) as the main way to serve VLMs. It compiles the model and exposes an OpenAI-compatible API in one step; see the Qwen3-VL-2B-Instruct recipe. This guide covers the optimum-rbln Python API, which is useful for checking compilation and running inference directly.
Note
Rebel100™ does not support RSD (see the version matrix), and every Rebel100™ example in the Model Zoo compiles for one NPU, so num_devices is set to 1 for both the visual submodule and the LM in this guide.
Note
Debugging error conditions (for example Failed to create RBLN runtime or No memory blocks are available) is out of scope for this guide. For memory- or runtime-creation-related errors, refer to Troubleshooting Multi-Module Models.
Model structure with submodules¶
A VLM is a model made up of several neural-network components such as a vision encoder and a language model. optimum-rbln places the principal component as the top-level model and declares the rest as submodules, compiling each into its own graph and serving each with its own runtime. With this structure, the desired settings can be specified independently for each submodule.
The structure of Qwen3-VL (Qwen3VLForConditionalGeneration) is as follows.
The top-level model is a causal language model (LM), and it contains a Vision Transformer that encodes images and video frames as a single submodule named visual. The roles of each component are as follows.
- Top-level LM: consumes the image embeddings produced by
visualand generates text tokens auto-regressively. visualsubmodule: a Vision Transformer, executed per image or per video frame.
This tree structure maps directly onto the key structure of the rbln_config passed at compile time: top-level keys apply to the LM, while keys nested under "visual" apply to the visual submodule.
Tip
Each field is defined in the RBLN config file of the corresponding model. For example, the fields available on Qwen3-VL's visual submodule are defined in RBLNQwen3VLVisionModelConfig.
Note
Which component is the top-level model and which are submodules varies across models. For example, in LLaVA and Gemma3, the top-level is a Conditional Generation wrapper and both vision_tower and language_model are submodules; in Idefics3, vision_model and text_model are declared as submodules together. To check the structure of a specific model, look at the submodules = [...] declaration in its RBLN config file (configuration_<model>.py).
Using a submodule's rbln_config¶
This section covers common patterns for configuring optimum-rbln's VLMs with rbln_config on Rebel100™: compiling and loading both submodules on one NPU, bucketing along two axes, and handling other memory and workflow scenarios.
The rbln_config fields most commonly used with Qwen3-VL are summarized below.
| Field | Level | Purpose |
|---|---|---|
batch_size | shared | Number of concurrent sequences for the whole model; cannot be set per submodule |
visual.max_seq_len | submodule (visual) | Maximum number of patches per image or frame accepted by the ViT graph. Accepts either a single int or a List[int] (bucketing) |
visual.num_devices | submodule (visual) | Number of devices for visual (1 on Rebel100™) |
visual.device | submodule (visual) | Device on which the visual runtime is placed |
visual.create_runtimes | submodule (visual) | Whether to create the visual runtime at compile time |
num_devices | top (LM) | Number of devices for the LM (1 on Rebel100™) |
kvcache_partition_len | top (LM) | Flash attention partition size; must divide max_seq_len evenly |
max_seq_len | top (LM) | Maximum position embedding for the LM; must be a multiple of kvcache_partition_len |
decoder_batch_sizes | top (LM) | List of decoder batch sizes for batch-size bucketing; every value must be ≤ batch_size |
device | top (LM) | Device on which the LM runtime is placed |
create_runtimes | top (LM) | Whether to create the LM runtime at compile time |
Caution
Notes for keys that may appear at both levels:
num_devicescan be set per submodule, but on Rebel100™ it is1at both levels, and each submodule'sdeviceis a single device index (for example0).batch_sizeis a single value that applies to the whole model and cannot be set per submodule.visualand the LM process batches in different ways internally, butoptimum-rblnreconciles this difference.
Note
The flash attention limits on Rebel100™ are half of those on ATOM™: kvcache_partition_len must be between 1,024 and 16,384 (ATOM™ allows up to 32,768), and its default is 8,192 (ATOM™: 16,384). max_seq_len must be a multiple of kvcache_partition_len and at least twice its value. The Model Zoo values (kvcache_partition_len=8_192, max_seq_len=262_144, 32 partitions) satisfy these limits.
Compiling and loading both submodules on one NPU¶
On Rebel100™, visual and the LM each run on a single NPU. The Model Zoo example sets create_runtimes to False at both levels, so compilation only produces and saves the compiled artifacts without creating runtimes.
At load time, the device field in rbln_config places each runtime. The Model Zoo inference.py places both visual and the LM on device 0.
With this layout, the ViT graphs, the LM graphs, and the KV cache must all fit in the memory of one NPU. If runtime creation fails as the batch size or context length grows, reduce the workload as described in Other scenarios below.
Bucketing the ViT input: visual.max_seq_len¶
Qwen3-VL's ViT runs on one image (or video frame) at a time, and its graph shape is fixed at compile time. A ViT compiled with max_seq_len=16384 always performs work proportional to 16,384 patches, regardless of whether the actual input contains 1,024 patches or 16,384 patches. In real serving workloads, a single request often contains images of different sizes or videos with different numbers of frames. If a single max_seq_len is sized for the largest expected input, smaller inputs still consume the full capacity, which increases latency and wastes memory.
optimum-rbln addresses this with a multi-size bucketing strategy. visual.max_seq_len accepts not only a single int but also a List[int], and the behavior is as follows.
- When a list is provided,
optimum-rblncompiles a separate ViT graph for each length in a single compilation. - At inference time, the smallest bucket that can accommodate the actual patch count is selected automatically.
- If an input exceeds every bucket in the list, an error is raised. Choose the largest value so that it covers the upper bound of your workload.
The example below is a modified version of the Model Zoo compile.py, where the single value (max_seq_len: 16384) is replaced with a list of three buckets compiled together.
At runtime, the compiled model behaves as follows.
| Input patch count | Selected bucket |
|---|---|
| 800 | 1024 |
| 2000 | 3136 |
| 5000 | 16384 |
| 20000 | error: exceeds all buckets |
Note
Each value in max_seq_len is the upper bound on the number of patches the ViT processes for a single image (or one temporal patch, 2 frames, of a video). The ViT counts patches before the spatial_merge_size=2 merger combines them, so with Qwen3-VL's patch_size=16 the patch count for an H × W image is:
H and W are the size after the processor resizes the image, with each side rounded to the nearest multiple of 32. For example, 512×512 yields 1,024 patches, 896×896 yields 3,136, and 2048×2048 yields 16,384. The bucket values in the example above (1024, 3136, 16384) cover up to those three resolutions, respectively. The Model Zoo inference.py creates the processor with max_pixels=2048 * 2048, so a resized image never exceeds 16,384 patches, which matches the largest bucket.
Caution
The more buckets you add, the longer compilation takes and the more device memory the compiled model uses. On Rebel100™, all ViT buckets share one NPU with the LM and its KV cache, so selecting too many can exhaust device memory. Two to four buckets chosen from the actual resolution distribution of your traffic are typically sufficient.
Tip
The general mechanics, trade-offs, and standalone examples of bucketing are covered in the Bucketing guide. ViT input-length bucketing applies only to models that explicitly support it; check the model's RBLN config file for a max_seq_len field on the vision submodule declared as Union[int, List[int]]. The LM-side max_seq_len is declared as plain int and does not accept a list.
Bucketing the decoder batch size: decoder_batch_sizes¶
Qwen3-VL's language-model decoder is also a graph whose shape is fixed at compile time. A decoder compiled with batch_size=4 always performs work proportional to 4 slots, whether the actual batch is 3 or 4. In real serving (continuous batching, in-flight batching), requests finish at different times and the active batch frequently shrinks below the compiled maximum, so the compute spent on unused slots is wasted.
decoder_batch_sizes addresses this with bucketing at the decoder graph level. It is a top-level (LM-side) field defined on RBLNDecoderOnlyModelConfig and accepts a List[int]. The behavior is as follows.
- When a list is provided,
optimum-rblncompiles a separate decoder graph (decoder_batch_{N}) for each batch size. - Every value must be ≤
batch_size, and the maximum value should equalbatch_size. If it is smaller,optimum-rblnautomatically appendsbatch_sizeand emits a warning. - At inference time, the engine selects the most appropriate decoder graph for the active batch size during the decoding phase.
The configuration below adds batch_size=4 to the Model Zoo compile.py settings and compiles three decoder graphs (1, 2, 4). It also lowers max_seq_len to 32,768: at batch_size=4 the Model Zoo's 262,144-token context does not fit on one Rebel100™ together with the ViT, and loading fails with Failed to create RBLN runtime ... Lack of device memory. With 32,768, both submodules load on one NPU. To keep the full context at a larger batch, place visual on a second NPU (see Troubleshooting Multi-Module Models).
At runtime, the compiled model selects a decoder graph based on the active batch size.
| Active batch | Selected decoder graph |
|---|---|
| 1 | decoder_batch_1 |
| 2 | decoder_batch_2 |
| 3–4 | decoder_batch_4 |
Note
The decoder graph is picked at each decoding step by the serving engine. RBLN vLLM uses these graphs as its decode batch buckets. With optimum-rbln alone, Qwen3-VL's generate() runs the batch_size graph and expects exactly batch_size sequences; a smaller batch fails with Cache position size mismatch.
Caution
More decoder graphs mean longer compilation time and higher device memory usage. A common pattern is 1, batch_size//2, batch_size (two to four buckets), adjusted to your traffic profile. On Rebel100™, a larger batch_size also enlarges the KV cache on the same NPU; if runtime creation fails, see Other scenarios.
Tip
ViT input-length bucketing (visual.max_seq_len) and decoder batch-size bucketing (decoder_batch_sizes) are independent and can be combined within a single compilation.
Other scenarios¶
Specific memory and device configurations, as well as compile/inference workflows, are covered step-by-step in Troubleshooting Multi-Module Models. Frequently encountered scenarios and their corresponding guides:
| Scenario | Guide |
|---|---|
Separate compilation from runtime creation (create_runtimes: False) | Step 1 |
KV cache block pool exhaustion at large batches (tune kvcache_num_blocks) | Step 3 |
| Cap the LM context length to the actual workload | Step 4 |
| Right-size the ViT input for the target resolution | Step 5 |
Note
The troubleshooting guide shows Rebel100™ and ATOM™ settings in separate tabs. Select the Rebel100™ tab when applying them.