Repository navigation
[Feature] Support batch invariance for ShortConv (LFM2) models - #62367
Conversation
LFM2 models refused to start under VLLM_BATCH_INVARIANT=1 because SHORT_CONV_ATTN did not declare batch invariance. causal_conv1d builds each token from its own window and conv-state slot, so opt it in and add a kernel test that pins that property. PLE_SHORT_CONV_ATTN stays opted out until its dilated conv path is validated. Fixes vllm-project#61319 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: wenfei qi <ms1419344875@outlook.com>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
|
Documentation preview: https://vllm--62367.org.readthedocs.build/en/62367/ |
|
This pull request has merge conflicts that must be resolved before it can be |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: wenfei qi <ms1419344875@outlook.com>
|
Merged current main; the only conflict was the tested-models list in E2E runs in the format requested on #60013/#60163, so they're here before review. One RTX 3090 (sm86, driver 580.65.06, CUDA 13.0) on Runpod, image Reproduce. $ env VLLM_BATCH_INVARIANT=1 vllm serve LiquidAI/LFM2-8B-A1B --port 8000 --max-model-len 4096 --gpu-memory-utilization 0.9
# main
(EngineCore pid=14778) ERROR 10-20 07:01:06 [core.py:1543] RuntimeError: VLLM batch_invariant mode is not supported for SHORT_CONV_ATTN.
# PR
(EngineCore pid=15303) INFO 10-20 07:01:46 [cuda.py:508] Using FLASH_ATTN attention backend out of potential backends: ['FLASH_ATTN', 'TRITON_ATTN', 'FLEX_ATTENTION'].
INFO: Application startup complete.Batch invariance: # main, `vllm serve LiquidAI/LFM2-8B-A1B --port 8000 --max-model-len 4096 --gpu-memory-utilization 0.9`
$ python3 repro_bi.py --model LiquidAI/LFM2-8B-A1B --rounds 3
round 0: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at 4, max |dlogprob| on agreeing positions 0.1060
round 1: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.1735
round 2: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.1735
# PR, `env VLLM_BATCH_INVARIANT=1 vllm serve LiquidAI/LFM2-8B-A1B --port 8000 --max-model-len 4096 --gpu-memory-utilization 0.9`
$ python3 repro_bi.py --model LiquidAI/LFM2-8B-A1B --rounds 3
round 0: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000
round 1: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000
round 2: A=954 tokens, B=1915 tokens | A alone vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000 | A batched with B vs A alone: first token diff at None, max |dlogprob| on agreeing positions 0.0000
|
|
Hi @Manfredss, thanks for this work, just reviewed this as an outside contributor (not a maintainer). |
|
Thanks for taking a look, @Monishver11. Agreed: the kernel test only pins the conv property, so CI wouldn't notice if LFM2 stopped starting or stopped being batch invariant under - VLLM_TEST_MODEL=LiquidAI/LFM2-1.2B pytest -v -s v1/determinism/test_batch_invariance.py::test_v1_generation_is_deterministic_across_batch_sizes_with_needle -k FLASH_ATTNOn the A40 the |
|
This pull request has merge conflicts that must be resolved before it can be |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: wenfei qi <ms1419344875@outlook.com>
|
Merged main again to clear the conflict. #63609 commented out the Batch Invariance steps until the pytest plugin installs are pinned, so the new test line now sits inside the disabled H100 block, and |
yewentao256
left a comment
There was a problem hiding this comment.
Thanks for the work! I don't think we need these added tests as they can pass in main.
It passes on main too, so it does not guard this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: wenfei qi <ms1419344875@outlook.com>
|
Thanks, agreed. f37ca2b removes it, along with its line in the disabled H100 block and its tethering allowlist entry. The PR is now just the backend opt-in plus the docs line. |
yewentao256
left a comment
There was a problem hiding this comment.
LGTM, thanks for the work!
|
✅ @Manfredss, CI is now available for this PR.
|
|
Hi @Manfredss, the pre-commit checks have failed. Please run: uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-filesThen, commit the changes and push to your branch. For future commits, |
|
/ci run |
|
✅ Triggered Buildkite CI #98485 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #98893 for commit |
|
/ci run |
|
✅ Triggered Buildkite CI #99341 for commit ℹ️ This PR is 12 commits behind upstream |
|
Hi @yewentao256, all CI checks have passed. Could you take a look when you get a chance? Thanks! |
…project#62367) Signed-off-by: wenfei qi <ms1419344875@outlook.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: vllm-agent <claw@inferact.ai>
Overview
Fix #61319. LFM2 models (
Lfm2ForCausalLM,Lfm2MoeForCausalLM) can't use batch-invariant mode: withVLLM_BATCH_INVARIANT=1the engine refuses to start withRuntimeError: VLLM batch_invariant mode is not supported for SHORT_CONV_ATTN.That leaves them without the documented way to get reproducible greedy outputs. This PR letsSHORT_CONV_ATTNdeclare batch invariance.PLE_SHORT_CONV_ATTNstays opted out.Claims
VLLM_BATCH_INVARIANT=1.LiquidAI/LFM2-8B-A1Bpasses the fulltests/v1/determinism/test_batch_invariance.pysuite (22/22) on an A40.generate()calls differs from later rounds) goes away in batch-invariant mode, with prefix caching on.VLLM_BATCH_INVARIANTis unset.Validation
A40 (SM86), official image
vllm/vllm-openai:nightly-39983a5e(no upstream changes to the touched files between that commit and this branch's base). Probe: 8 prompts × 128 greedy tokens, 4generate()rounds on one engine, then each prompt alone. Counts are prompts (out of 8) whose token ids differ. Theenforce_eager/ no-prefix-cache rows and the 22-test suite ran with an env-gated copy of the same one-line opt-in. The rest ran with this branch's file installed.enable_prefix_caching=FalseVLLM_BATCH_INVARIANT=1VLLM_BATCH_INVARIANT=1enforce_eagerenable_prefix_caching=FalseVLLM_BATCH_INVARIANT=1Upstream BI suite with this change:
Details
Why #61319 happens. LFM2 runs with prefix caching on by default (
mamba_cache_mode=align, block size 16). From round 1 on, any prompt with a full block gets a cache hit, so the first prefill step shrinks (92 → 76 tokens in my probe, where only the 17-token prompt hits). Without batch invariance, kernel selection depends on batch shape. Every sequence in that step then moves by a few ULPs, and near-tied argmaxes flip later. In my runs, two warm rounds never disagreed. With prefix caching on but no prompt long enough to hit, all rounds match. This is the documented "no bitwise reproducibility by default" behavior, and the documented fix is batch-invariant mode, which LFM2 couldn't use. SettingCUBLAS_WORKSPACE_CONFIG=:16:8 CUBLASLT_WORKSPACE_SIZE=1, as #61440 does, still leaves 2/8 prompts differing, because this isn't run-to-run atomics.Why ShortConv qualifies.
causal_conv1d_fn(prefill) andcausal_conv1d_update(decode) compute each output token from that sequence's ownL_cachewindow plus its conv-state slot. There is no reduction across sequences and no split depending on batch size. The rest of the layer isin_proj/out_proj, which already go through the batch-invariant linear path, plus elementwise gating.Scope and limitations.
PLE_SHORT_CONV_ATTN(Qwen4Exp's dilated conv) inherits fromShortConvAttentionBackend. It is explicitly kept atFalse: it uses its own packing path, and I couldn't validate it.get_mamba_attn_backend, but V1 wasn't run.VLLM_TEST_MODEL=LiquidAI/LFM2-1.2B ... -k FLASH_ATTNon the needle test costs ~35 s per variant, and I'm happy to add it.Not a duplicate. The only PR linked to #61319 is #61440. It forces deterministic cuBLAS/cuDNN settings globally, which doesn't address this (see above), and its diff currently spans 891 files. I searched open PRs for
supports_batch_invariance,SHORT_CONV_ATTN/ShortConv, and LFM2 batch invariance. #45819 (GDN) and the Mamba2 BI PRs touch other backends only.Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.
AI assistance (Claude Code) was used to reproduce and root-cause the issue, write the change, and run the GPU validation above. I reviewed every changed line.
🤖 Generated with Claude Code