[feature] Enable Dynamic Batching - #1305
Conversation
04d168e to
19ba931
Compare
Compile one QPC that serves multiple live decode batch sizes b <= B_max by
riding the continuous-batching export path: every decode specialization pins
the retained KV cache at full_batch_size (B_max) while only the input batch
axis varies. This is the only path where the input batch axis (ONNX symbol
"batch_size") is decoupled from the retained KV axis ("full_batch_size"), so
varying the input batch is a non-retained-axis change the compiler accepts --
unlike the earlier per-batch-KV attempt, which asked one QPC to hold multiple
retained-state shapes and was rejected with "inconsistent retained state".
Compile side (modeling_auto.py):
- batch_size accepts List[int]; a list requires continuous_batching=True and
every value must be <= full_batch_size (B_max).
- build_decode_specialization gains decode_input_batch_size so decode specs
vary the input batch while full_batch_size/kv_cache_batch_size pin the KV.
- Reject combining a batch list with CCL (comp_ctx_lengths): the two vary
different identifying inputs and the compiler cannot disambiguate them.
Runtime (text_generation_inference.py):
- Drive a live batch via decode_batch_size; batch_index = arange(b) routes b
sequences into b of the B_max KV slots.
- generate(execution_batch_size=b), validated against the compiled batches.
- Fix _fetch_full_batch_size to handle 3+ allowed shapes; add
_fetch_compiled_batch_sizes and _resolve_execution_batch_size.
Validated: 206/206 CPU spec tests; single-QPC compile confirmed with
qaic-exec; 5/5 on_qaic tests pass on device, including token-id parity of the
dynamic-batch QPC at live batch b vs a natively-compiled batch-b QPC.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: eplatero <erick7451@gmail.com>
…ialization build_decode_specialization carried two params for the same concept — the live decode *input* batch of one specialization: batch_size (read only on the non-CB path) and decode_input_batch_size (the CB override). At the single call site the loop variable was passed under both names, and on the CB path batch_size was dead. Collapse them into a single batch_size that means "decode input batch for this spec" on every path (exec_input_bs = batch_size). The caller now computes the one input-batch value (full_batch_size for plain CB, the per-spec bs for dynamic batching and the non-CB path), so emitted specializations are unchanged. Harden the retained-batch write to fall back to full_batch_size when kv_cache_batch_size is omitted so a direct caller still pins B_max. Also update the dynamic-batch unit test to express the live batch via batch_size. Validation: 219 CPU unit tests pass across test_auto_model_api, test_modeling_auto_cpu, test_speculative_decoding, and test_pld_inference; ruff check/format clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: eplatero <erick7451@gmail.com>
Signed-off-by: eplatero <erick7451@gmail.com>
Signed-off-by: eplatero <erick7451@gmail.com>
19ba931 to
281eb33
Compare
Dynamic batching decode latency performanceRan latency comparison performance between baseline and dynamic QPCs compiled using the same Test configuration
Experiment 1:
|
| Batch | Baseline p50 ms | Dynamic p50 ms | p50 delta |
|---|---|---|---|
| 1 | 1321.9 | 1283.0 | -2.9% |
| 2 | 1385.7 | 1294.7 | -6.6% |
| 4 | 1301.9 | 1283.8 | -1.4% |
| 8 | 1336.4 | 1289.7 | -3.5% |
| 16 | 1360.5 | 1298.7 | -4.5% |
| 32 | 1375.1 | 1327.0 | -3.5% |
| 64 | 1439.5 | 1352.9 | -6.0% |
| 128 | 1412.0 | 1343.8 | -4.8% |
Experiment 2: full_batch_size=8, paired run
Baseline: one decode specialization per QPC, batch_size=B, full_batch_size=8
Dynamic: one QPC with decode batches [1,2,4,8], full_batch_size=8
| Batch | Baseline p50 ms | Dynamic p50 ms | p50 delta |
|---|---|---|---|
| 1 | 76.5 | 77.5 | -0.5% |
| 2 | 75.8 | 75.8 | -1.5% |
| 4 | 75.9 | 75.4 | -1.4% |
| 8 | 78.3 | 84.2 | +8.5% |
Interpretation
With matched full_batch_size=128, the dynamic QPC with 8 decode specializations is within a few percent of the single-specialization baselines across all tested batches. The measured p50 deltas range from -1.4% to -6.6%.
With matched full_batch_size=8, the dynamic QPC with 4 decode specializations is effectively at parity for batches 1, 2, and 4. Batch 8 shows the clearest dynamic overhead signal, with p50 latency increasing from 78.3 ms to 84.2 ms (+8.5%).
Limitations
- These results measure steady-state decode
QAICInferenceSession.run()latency only. - The tested model is
JackFram/llama-68m, which is a small non-production model. - They do not cover production serving latency, throughput, compile time, multi-device behavior, or other models/configs.
|
follow-up experiment: Experiment 3: Baseline: 8 QPCs, one per batch size. Each QPC contains five decode Dynamic: one QPC with decode batches The corresponding decode sequence lengths are
The batch-level values summarize the five K values for each batch. Baseline
Interpretation With
|
Objective
This PR enables dynamic batching for QAIC continuous-batching QPCs by using
batch_indexKV-cache slicing.
For example,
compile(batch_size=[1, 2, 4], continuous_batching=True, full_batch_size=4)builds one QPC whose retained KV cache is always sized for batch 4, while runtime execution can
drive live decode batches 1, 2, or 4.
Assumptions and limitations
Dynamic batching is implemented only for continuous-batching QPCs. A list-valued
batch_sizerequires
continuous_batching=Trueand an explicitfull_batch_size.The supported cartesian product is dynamic batching with multi-spec speculative decoding. Both
specialization discriminators are carried by
input_idsshape: batch varies on axis 0, andspeculative decode length varies on axis 1.
By "uniquely identify," we mean the QAIC runtime/compiler must be able to select one decode
specialization from the input tensor shapes:
input_idsaxis 0 (batch)input_idsaxis 1 (seq_len)input_idsaxes 0 and 1comp_ctx_lengthslengthinput_idsandcomp_ctx_lengthsinput_idsandcomp_ctx_lengthsFor example:
CCL is different because it uses the length of a separate
comp_ctx_lengthsinput:The rejected products previously reached the QAIC compiler and failed with:
This PR rejects those combinations at compile time with clear
ValueErrors instead. Plain CCL,dynamic batching without CCL, scalar speculative decoding with CCL, and dynamic batching with
multi-spec speculative decoding remain supported as applicable.