Pull requests / #3266

#3266 [Engine] logit_bias: accept it with --batch (one bias table per slot)

open · @qni-live · 0 コメント · GitHub で見る

Setup & installServer & APIMulti-GPUAMD / HIPNVIDIA / CUDAModels & quantsDocumentationWindowsLinux

本文

## Problem and result

`logit_bias` is refused whenever the engine runs with `--batch`. That makes the Han/Kana/Hangul ban (docs/LOGIT_BIAS.md) unusable on a 4-slot server, which is the setup for several parallel agents.

Result: each batch slot can carry its own bias table, and a bias reaches only its own request. A slot that is reused by a request without a bias carries nothing over. `--batch-mtp` still refuses it.

This PR is rebased on `main` for 0.2.0-beta (`e88ee39d`, tag `v0.2.0-beta`); it applies without conflicts. #1891 is already in `main`, so nothing of it is repeated here.

## Related discussion

Replaces #2977 (GitHub would not reopen it after the rebase).

No issue and no scope agreement: none. There is no open issue; `--batch` answers `logit_bias` with HTTP 400 today. It is a focused change that lifts one refusal and adds no new request field.

## Approach

- `include/strata/core/verify.hpp`, `src/core/verify.cpp`: `Verifier::set_slot_logit_bias(slot, bias, err)` and `slot_has_bias(slot)`. Only the final stage allocates a table (one float per vocabulary entry, about 1 MB for 248,320 tokens) and copies it, and only when it changed. `set_slot_sampling` keeps the slot's table pointer. `init_slots` frees and clears the tables.
- `sample_rows`: a greedy row whose slot has a table is drawn on the last stage like a sampled row, because the graph's argmax does not know the bias. Rows without a table take the same path as before.
- `src/program/generate.cpp`: the bias is set at admission, before `set_slot_sampling`; an empty one clears the slot. `--batch-mtp` still answers `ERR logit_bias: --batch-mtp does not take it yet`. `INFO` advertises `logit_bias_batch=1`.
- `serve/logit_bias.py`: the server refuses `--batch` only when the engine does not advertise `logit_bias_batch=1`, so older engines keep the clear 400.
- Tests: `tools/batch_test.py --bias-han` (every second prompt carries the ban and asks for Chinese) and the new `tools/batch_bias_reuse_test.py` (3 waves, banned and free prompts swap slots every wave; a slot's tokens must equal its solo tokens). `docs/LOGIT_BIAS.md` and `serve/test_logit_bias.py` updated.

## Validation

Environment: 2x RTX PRO 4500 Blackwell (32 GB each), Linux 7.0.0-38, driver 595.91.07, CUDA 13.4 source build (sm_120) of `main` e88ee39d plus this commit (a7026916), Swift IQ3_XXS, `--batch 4 --trim-stage-weights --pipeline-windows 2`, layer split 25, `STRATA_IQ_MT_MIN=1 --pcie-frac 0 --adapt-every 1000000`, 64 new tokens, 65,842 banned tokens (all Han/Kana/Hangul). Each test compares a slot's tokens with the same request run alone. Run on 2026-10-19; log `pr2977-tests-lauf.log`.

**Passed:**
- `python3 tools/batch_test.py ... --batch 4 --n 4 --extra "--pcie-frac 0 --adapt-every 1000000 --batch-groups 1"`, no bias anywhere: 4 of 4 slots identical to solo.
- same with `--bias-han`, ban on 2 of 4 slots, greedy, `--batch-groups 1`: 4 of 4 identical, 0 Han/Kana/Hangul characters in the banned slots.
- same, `--batch-groups` auto: 4 of 4 identical, 0 CJK characters in the banned slots.
- `python3 tools/batch_bias_reuse_test.py ... --waves 3`, greedy: 12 of 12 slot rows identical to solo, RESULT: PASS.
- same, sampled (`temperature=0.7 top_k=20 seed=7`): 12 of 12 identical, RESULT: PASS.
- `python3 -m pytest serve/test_logit_bias.py`: 12 passed, 19 subtests passed.
- `cmake --build build -j 40`: builds (CUDA).

**Failed:**
none observed on this revision. My first run of the series used a relative `--exe` that resolved to an older build and answered `continuous batching is not supported`; I discarded it and reran with an absolute path. The results above are from the rerun only.

**Not run:**
- HIP and SYCL builds: the change touches `src/core/verify.cpp` and `src/program/generate.cpp`; only CUDA was built and run.
- Throughput cost and the CJK rate over 150 prompt pairs in batch: measured on the 0.1.45 base for the earlier version of this PR (10 of 150 answers in Chinese script without the ban, 0 of 150 with it; -1 to -3 % batch throughput; +21 ms fixed cost per request), not repeated on 0.2.0-beta.
- A byte comparison of the default path (no bias, `--batch`) against the packaged 0.2.0-beta engine 0.1.45.3. The no-bias slots equal their solo run on the changed build; I did not diff them against the release binary.
- `--batch-mtp` with a bias after the rebase (it is unchanged by this PR), the Qwen3.6 engine path, layer splits over three or more GPUs, and a start with the Windows or Mac engine.

## Limits

- `--batch-mtp` still refuses `logit_bias`.
- Tested on two GPUs, CUDA only, four slots, 64-token requests.
- Written with Claude Code (AI-assisted); I ran and checked every test above on my hardware.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

関連リンク

インストール・モデル・リリースへの站内リンク。