Skip to content

Troubleshooting KV Cache Block Estimation

When kvcache_num_blocks is left unset, RBLN Optimum sizes the paged-attention KV cache after compilation: it takes the largest block pool that fits on the NPU, up to the pool that holds a full max_seq_len sequence for every batch item. This guide covers how to bound that estimate with memory_budget, using the Qwen3-8B Model Zoo example as the reference. memory_budget sets how much of each NPU's DRAM the estimate may assume, expressed as a fraction of the available DRAM (0.5), a percentage ("50%"), or a byte count ("8GB"). It covers the weights and workspace as well as the KV cache, which gets whatever remains (Step 1). It applies only when the KV cache is paged into blocks smaller than a sequence, which in RBLN Optimum means flash attention (attn_impl="flash_attn", or kvcache_partition_len set). Under eager attention each sequence takes one block of max_seq_len, so an unset block count becomes batch_size and there is nothing to estimate.

Prerequisites

The measurements below come from Qwen3-8B compiled with max_seq_len=40_960 and kvcache_partition_len=8_192, on each product as listed in the tabs. Block counts depend on the model, the compile configuration, and the NPU, so read them as the shape of the trade-off rather than as values to copy.

Run rbln-smi to list the NPUs on the host, and adjust num_devices to match your hardware.

One Rebel100™ (RBLN-CR13), num_devices=1, at batch_size=8.

Four ATOM™-Max (RBLN-CA25), num_devices=4, at batch_size=8.

Quick reference

Symptom Root cause Go to
The compiled model reserves the whole NPU, leaving no room for anything else on it Automatic block estimation gives the KV cache all the DRAM it can use, up to the full pool Step 1
Insufficient memory for the required KV cache after a budget is set The budget starves the estimate below the minimum block count the configuration needs Step 2
memory_budget ... exceeds the target NPU's available DRAM The budget is larger than the DRAM the NPU actually offers Step 3
memory_budget and an explicit kvcache_num_blocks are mutually exclusive Both were set, and estimation runs only when the block count is unset Step 3

Step 1: Bound the automatic KV cache estimate

Symptom

The model compiles and runs correctly on its own, but the compiled artifact can occupy the entire NPU. Nothing else fits on the same NPU, including a second model, another submodule of the same pipeline, or a later increase in batch size.

Root cause

kvcache_num_blocks defaults to 0, which means "decide after compilation". The estimator subtracts the allocations of the compilation it runs in from the DRAM the NPU reports, then checks whether the full pool (num_full_blocks, see Step 2) fits in the rest. If it does, it takes the full pool; if not, it searches for the largest pool that still fits. It never goes above the full pool, but either way the KV cache takes as much of the remaining DRAM as it can use, and nothing reserves headroom for anything else.

For the reference configuration the full pool of 40 blocks fits, which reserves 45 GiB of KV cache on the NPU.

For the reference configuration the full pool of 40 blocks fits, which reserves 11.25 GB of KV cache on each of the 4 NPUs.

Resolution

Set memory_budget to cap the DRAM the estimate may assume. The budget applies per NPU and resolves against the DRAM that NPU actually offers.

from optimum.rbln import RBLNQwen3ForCausalLM

model = RBLNQwen3ForCausalLM.from_pretrained(
    "Qwen/Qwen3-8B",
    export=True,
    rbln_batch_size=8,
    rbln_max_seq_len=40_960,
    rbln_num_devices=1,
    rbln_kvcache_partition_len=8_192,
    rbln_memory_budget=0.3,
    rbln_create_runtimes=False,
)
model.save_pretrained("rbln-Qwen3-8B")

Rebel100™ (RBLN-CR13) offers 150,323,855,360 bytes (140 GiB) once the system reserve is set aside, which is what rbln-smi reports as its total memory.

from optimum.rbln import RBLNQwen3ForCausalLM

model = RBLNQwen3ForCausalLM.from_pretrained(
    "Qwen/Qwen3-8B",
    export=True,
    rbln_batch_size=8,
    rbln_max_seq_len=40_960,
    rbln_num_devices=4,
    rbln_kvcache_partition_len=8_192,
    rbln_memory_budget=0.5,
    rbln_create_runtimes=False,
)
model.save_pretrained("rbln-Qwen3-8B")

ATOM™-Max (RBLN-CA25) offers 16,877,879,296 bytes (15.72 GB) once the system reserve is set aside, which is what rbln-smi reports as its total memory.

The budget accepts three forms:

Form Example Meaning
Fraction 0.5 Half of the available DRAM. A float in the range (0, 1], and the recommended form.
Percent string "50%" Identical to 0.5. Convenient when the value passes through a command line.
Byte count "8GB", "512MB", 8 * 2**30 An absolute budget. Units are binary (KB = 1024 bytes, up to TB) and case-insensitive; a bare int is a byte count.

The budget covers everything the compiled model loads onto the NPU, not the KV cache alone. Weights and workspace are charged against it first, and the KV cache takes whatever remains. This is why the KV cache block count falls faster than the budget itself.

Varying only memory_budget in the example above:

memory_budget Estimated kvcache_num_blocks KV cache on the NPU
unset 40 45.00 GiB
0.9 … 0.5, "64GB" 40 45.00 GiB
0.45 39 43.88 GiB
0.4 33 37.13 GiB
0.35 27 30.38 GiB
"48GB" 26 29.25 GiB
0.3 21 23.63 GiB
"40GB" 19 21.38 GiB
"36GB" 15 16.88 GiB
0.25 14 15.75 GiB
0.22 11 12.38 GiB

The NPU's DRAM holds the full pool down to 0.5; below that, the estimate falls toward the floor of 9 blocks (Step 2).

Confirm the resolved count in the saved artifact:

$ python -c "import json; print(json.load(open('rbln-Qwen3-8B/rbln_config.json'))['kvcache_num_blocks'])"
21

Varying only memory_budget in the example above:

memory_budget Estimated kvcache_num_blocks KV cache per NPU
unset 40 11.25 GB
0.9 37 10.41 GB
0.8 31 8.72 GB
"12GB" 29 8.16 GB
0.7 25 7.03 GB
0.6 20 5.62 GB
"8GB" 15 4.22 GB
0.5 14 3.94 GB
0.4 9 2.53 GB

Confirm the resolved count in the saved artifact:

$ python -c "import json; print(json.load(open('rbln-Qwen3-8B/rbln_config.json'))['kvcache_num_blocks'])"
14

A compiled artifact never records 0: an explicit kvcache_num_blocks is saved as given, and estimation writes the count it resolved. Under eager attention an unset count is saved as the full pool, whatever memory_budget says.

The same option is available from the compile CLI, where all three forms work:

1
2
3
optimum-rbln-cli --model-id Qwen/Qwen3-8B --output-dir ./rbln-Qwen3-8B \
  --batch_size 8 --max_seq_len 40960 --num_devices 1 \
  --kvcache_partition_len 8192 --memory-budget 0.3
1
2
3
optimum-rbln-cli --model-id Qwen/Qwen3-8B --output-dir ./rbln-Qwen3-8B \
  --batch_size 8 --max_seq_len 40960 --num_devices 4 \
  --kvcache_partition_len 8192 --memory-budget 0.5

Step 2: Keep the budget above the minimum block count

Symptom

Setting the budget too low makes compilation fail:

1
2
3
ValueError: Insufficient memory for the required KV cache: only 8 blocks can be allocated,
but at least 9 blocks are required. Consider reducing `max_seq_len` or `batch_size`
(currently 8) to lower the memory requirement.

Produced with memory_budget=0.2.

1
2
3
ValueError: Insufficient memory for the required KV cache: only 3 blocks can be allocated,
but at least 9 blocks are required. Consider reducing `max_seq_len` or `batch_size`
(currently 8) to lower the memory requirement.

Produced with memory_budget=0.3.

Root cause

A block pool below a certain size can neither hold one full sequence nor give every item in the batch a block of its own. RBLN Optimum therefore rejects the estimate instead of settling for a smaller pool. Under flash attention the floor and the full pool are:

num_min_blocks  =  min(max((max_seq_len / kvcache_block_size), batch_size) + 1, num_full_blocks)
num_full_blocks =  (max_seq_len / kvcache_block_size) × batch_size

The + 1 is the spare block that fills the unused slots of a block table while a sequence is still growing, and it is needed whichever of the two terms sets the floor.

In the reference configurations:

kvcache_block_size is kvcache_partition_len here, so the sequence term of the reference configuration is 5 blocks, batch_size=8 raises it to 8, and the floor is 9 against a full pool of 40. 0.22 lands just above the floor at 11 blocks; 0.2 estimates 8 blocks and is rejected with the error above.

kvcache_block_size is kvcache_partition_len here, so the sequence term of the reference configuration is 5 blocks, batch_size=8 raises it to 8, and the floor is 9 against a full pool of 40. 0.4 lands exactly on the floor at 9 blocks; 0.3 estimates 3 blocks and is rejected with the error above.

The room to tighten the budget therefore comes from the gap between the two values, and batch_size is what opens that gap: the full pool grows by one full sequence per batch item, while the floor follows only the larger of the two terms. At batch_size=1 the same configuration has a full pool of 5 blocks and a floor of 5, so the two coincide and no budget below the full requirement can succeed.

Resolution

Raise the budget until the estimate clears the floor, then verify the resolved count as in Step 1. When the workload needs a tighter budget than the floor allows, shrink the per-sequence reservation instead, since lowering max_seq_len lowers the floor along with it:

Change Effect on the budget range
Raise batch_size Widens the gap between floor and full pool, so the budget has more room
Lower max_seq_len Lowers both the floor and the full pool, so the same DRAM still holds a sequence
Lower kvcache_partition_len Raises the sequence term of the floor, but each block is smaller

Step 3: Resolve budget configuration errors

Symptom

Either of two validation errors:

ValueError: memory_budget (1099511627776 bytes) exceeds the target NPU's available DRAM (150323855360 bytes).
ValueError: memory_budget (107374182400 bytes) exceeds the target NPU's available DRAM (16877879296 bytes).
ValueError: `memory_budget` and an explicit `kvcache_num_blocks` are mutually exclusive. `memory_budget` only guides automatic block estimation, which runs when `kvcache_num_blocks` is unset.

Root cause

The first error compares a byte count against the DRAM the target NPU offers, 150,323,855,360 bytes (140 GiB) on Rebel100™ and 16,877,879,296 bytes (15.72 GB) on ATOM™-Max, as shown in Step 1. A value above that is rejected rather than silently clamped, because it asks for capacity the device does not have.

The second error guards the estimation path. Automatic estimation is what consumes memory_budget, and it runs only while kvcache_num_blocks is unset. Setting both would skip estimation and silently ignore the budget, so the combination is rejected at configuration time.

Resolution

Express the budget as a fraction rather than a byte count when the artifact is meant to be portable, since a fraction resolves against whichever NPU it is compiled for. Keep an absolute byte count below the available DRAM of the target NPU.

Choose one of the two controls, not both:

Goal Set
Let the estimate decide the block count within a memory bound memory_budget
Pin the block count exactly kvcache_num_blocks

Note

memory_budget is a compile-time input and is not written to rbln_config.json. The artifact records only the resolved kvcache_num_blocks, which is what inference reads. Recompile to change the memory_budget.