perf(prefill): reduce with Tensor.max() instead of the Python builtin in plan() - #5043
perf(prefill): reduce with Tensor.max() instead of the Python builtin in plan()#5043gf239 wants to merge 1 commit into
Conversation
… in plan() BatchPrefillWithPagedKVCacheWrapper.plan() computes _max_q_len and _max_kv_len with the Python builtin max() over a 1-D int32 host tensor. The builtin iterates the tensor element by element, materialising a 0-d tensor per element; Tensor.max() is a single reduction dispatch. Both lines are on the path every caller that omits max_token_per_sequence and max_sequence_kv takes on each plan() call -- vLLM among them. Measured on the host (torch 2.14, CPU): 12.7 us -> 3.1 us and 10.0 us -> 1.2 us at 8 requests, about 18 us per plan(); about 1 us at a single request. Value-identical on every reachable input: the operand is 1-D int32 with numel >= 1, since plan() is only reached for a non-empty batch. The two spellings differ only where the builtin already raised (empty, 0-d, 2-D and higher), and int32 admits no NaN. Adds a test pinning both attributes against the builtin over batch size, page size and dtype, including the single-element reduction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: gf239 <gf239@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthrough
ChangesPrefill maximum length handling
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to Prefill planning now computes query and KV length maxima through tensor reductions while retaining the same integer results. Covered inputs preserve prior behavior, with no remaining merge-blocking risk. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📌 Description
BatchPrefillWithPagedKVCacheWrapper.plan()derives_max_q_lenand_max_kv_lenwith the Python builtinmax()over a 1-D int32 host tensor:The builtin iterates the tensor element by element and materialises a 0-d tensor per element;
Tensor.max()is a single reduction dispatch. This PR switches both lines to the tensor reduction.Both lines run on every
plan()call for any caller that omitsmax_token_per_sequence/max_sequence_kv-- vLLM's FlashInfer backend among them, which callsplan()twice per engine step under speculative decoding (target verify batch + drafter). Measured on the host (torch 2.14, CPU):_max_q_len_max_kv_len_max_q_len_max_kv_lenAbout 18 µs per
plan()at 8 requests, ~1 µs at a single request. Small, but it sits on the CPU critical path of every step when CUDA graphs are piecewise, and it costs nothing.Equivalence. On every reachable input the two spellings return the same Python
int: the operand is 1-D,int32,numel >= 1(plan()is only reached for a non-empty batch). They differ only where the builtin already raised -- empty (ValueErrorvsRuntimeError), 0-d, and 2-D or higher (the builtin iterates rows and compares tensors) -- andint32admits no NaN, so themax/amaxNaN distinction cannot arise. Checked over 1800 random 1-D int32 tensors withnumel ∈ {1, 2, 3, 8, 16, 64}: 0 mismatches, identical types.Test. Adds
tests/attention/test_prefill_plan_max_lens.py, which pins both attributes afterplan()against the builtin-maxcomputation (spelled the old way) and against the input lengths directly, overbatch_size ∈ {1, 3, 8, 17},page_size ∈ {1, 16}and fp16/bf16 -- including the single-element reduction.🔍 Related Issues
None.
🚀 Pull Request Checklist
✅ Pre-commit Checks
pre-commitby runningpip install pre-commit(or used your preferred method).pre-commit install.pre-commit run --all-filesand fixed any reported issues.🧪 Tests
unittest, etc.).Results. Run on an RTX 4090 (sm89), each time twice -- once against the stock package as a control that the environment and JIT-compiled kernels work, once with the two-line patch overlaid first on
PYTHONPATH-- so a failure could be attributed:plan()is line-identical tomainat both sites)test_prefill_plan_max_lens.py(16) +tests/attention/test_batch_prefill.py(4)version.txtonmain)The 0.6.18 row could not be completed on this machine and I am not going to pretend otherwise: 0.6.18's JIT needs a CUDA toolchain newer than the system CUDA 12.0 (
nvcc fatal: Unknown option '--compress-mode=size'), and the pip-provided CUDA 13 packages I tried in a fresh venv failed in three successive layers (bundled CCCL rejecting a 13.3 nvcc against 13.0 runtime headers; a leftover 13.3ciccemitting PTX 9.3 for a 13.0ptxas; thencrt/host_runtime.hvs cudafe stub disagreeing on__cudaLaunch). The control run fails identically to the patched run in every one of those attempts, so none of it is attributable to this change. Sinceplan()is line-identical between 0.6.16.post3 andmainat both edited sites, the 0.6.16.post3 row is the same code path; CI on its supported toolchain covers 0.6.18.pre-commit run --files flashinfer/prefill.py tests/attention/test_prefill_plan_max_lens.py: all hooks passed (mypy, ruff check, ruff format, whitespace/EOL/tab checks); no file modified.🔬 Experimental Track
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests