[CPU][MLA] Fix prefill backend selection so MLA runs end-to-end on CPU - #51471
[CPU][MLA] Fix prefill backend selection so MLA runs end-to-end on CPU#51471maobaolong wants to merge 7 commits into
Conversation
cd03b4a to
ec4f482
Compare
|
@bigPYJ1151 Hi Thanks for the review and help on #49453, would you like to take a look at this PR also? After this PR, we can run vllm + deepseek-v2-lite end2end test. |
MatthewBonanni
left a comment
There was a problem hiding this comment.
Seems reasonable to me, but please add a GSM8k eval result as well as vllm bench serve results in comparison to the pure decode pathway
|
Thanks @MatthewBonanni for your review! I now have local validation with real DeepSeek-V2-Lite weights on CPU for both paths:
Both runs used PTAL 🙏 |
|
@MatthewBonanni I'm so sorry to ping @MatthewBonanni |
|
Hi @bigPYJ1151 , would you like to help to take another look at this PR? Thanks a lot! |
|
This pull request has merge conflicts that must be resolved before it can be |
4fbe47a to
4dde42f
Compare
|
Sorry to bother you, but could you please keep an eye on this PR for me? Thank you so much. @MatthewBonanni @bigPYJ1151 |
Isotr0py
left a comment
There was a problem hiding this comment.
Overall look reasonable, have some minor comments. PTAL!
PR vllm-project#49453 added the CPU MLA backend (decode via mla_decode_kvcache plus an SDPA-based prefill) but never updated the MLA prefill backend selector, so on CPU get_mla_prefill_backend() still returned the FlashAttention backend. flash-attn is not installable on CPU, so DeepSeek-style MLA models still failed before the first successful prefill. This change keeps the existing CPU SDPA MLA prefill implementation and finishes the missing CPU routing. It also tightens the smoke test to use dummy weights plus prompt_token_ids, so DeepSeek-V2-Lite can be verified end-to-end locally without loading real weights or depending on tokenizer initialization. Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Complete the CPU MLA prefill path for DeepSeek-V2-Lite by handling context chunks, softmax LSE return values, CPU-side MLA KV gather, and CPU-side attention-state merging.\n\nThis also strengthens the CPU MLA test coverage with a smoke test that forces an external KV hit path using dummy weights, so the LMCache-style reload flow is exercised without loading real model weights.\n\nCo-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Move the CPU MLA backend check ahead of the device capability query and make the CPU merge_attn_states fallback an explicit top-level branch, matching review feedback without changing behavior.\n\nCo-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Co-authored-by: Codex <codex@openai.com> Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Co-authored-by: Codex <codex@openai.com> Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Signed-off-by: baoloongmao <baoloongmao@tencent.com>
4dde42f to
29e77a0
Compare
Apple Silicon (M3) validation — CPU SDPA MLA prefill backend worksThanks for this fix! I ported the PR's changes ( Environment: Mac M3, macOS 15.7.4, 24 GB, CPU backend, Test 1 — official smoke scenario (mirrors Test 2 — GLM-5.2 (glm_moe_dsa) truncated to 3/78 layers: Both pass with only the PR's changes applied (no other patches). The log confirms the selector routes to Notes / suggestions:
Happy to run any additional scenario you'd like checked on Apple Silicon. |
|
@sunlei1992 Thanks a lot for the Apple Silicon validation! This is very helpful. The sparse-attention / GLM-5.2 issue looks out of scope for this PR, but it can be a followup work. The main focus here is the CPU SDPA MLA prefill path for DeepSeek-style MLA models, and I have separately validated the repeated request for LMCache path with DeepSeek-V2-Lite. Really appreciate the detailed check! |
|
@bigPYJ1151 Thanks for the review and suggested changes, addressed all the comments i guess, would you like to take another look? Thanks! |
Purpose
Make DeepSeek-V2-Lite run end-to-end on CPU when MLA is enabled, including the
contextful prefill path needed by external KV reloads (for example LMCache).
This is the missing follow-up to #49453: that PR brought up the CPU MLA decode
path and the basic CPU MLA backend plumbing, but it still left CPU MLA unable
to run real external-hit prefill flows.
Why #49453 was insufficient
#49453 added the following pieces:
CPUMLABackend/CPUMLAImplundervllm/v1/attention/backends/mla/torch.ops._C.mla_decode_kvcacheblock_size=16, disable chunked prefill / prefixcaching for MLA)
That was enough to make the CPU MLA decode path exist, but not enough to make a
DeepSeek MLA model actually run through the full prefill lifecycle.
The remaining gaps were:
CPU-native one.
not implement:
return_softmax_lse=Truefor suffix prefillrun_prefill_context_chunk(...)for prefill rows that already have context_C_cache_ops.gather_and_maybe_dequant_cacheexisted, but that op is notavailable on CPU.
Triton merge kernel, which is not usable in this CPU environment.
backend selection and the cold/no-context CPU MLA path, but it never created
a prefill row with
num_computed_tokens > 0, so it never executed theLMCache-style external-hit/contextful prefill path.
Because LMCache / KV connectors create prefill rows with
num_computed_tokens > 0, they necessarily exercise the contextful MLA prefillpath. That is why #49453 could merge while still failing for
DeepSeek-V2-Lite + CPU + LMCache.
What this PR adds
CPUSDPAMLAPrefillBackend(
vllm/v1/attention/backends/mla/prefill/cpu_sdpa.py).run_prefill_new_tokens(..., return_softmax_lse=True)run_prefill_context_chunk(...)vllm/model_executor/layers/attention/mla_attention.py, avoiding themissing
_C_cache_ops.gather_and_maybe_dequant_cachedependency.merge_attn_states(...)invllm/v1/attention/ops/merge_attn_states.py, so prefix/suffix partialresults can be merged without Triton.
using
ExampleConnectorwithdummyweights and token-id prompts, so itdoes not need to load real model weights and does not depend on tokenizer
initialization.
Why this is not duplicate work
This PR is not duplicating #49453. #49453 brought up the CPU MLA backend and
made the decode path possible, but it left CPU MLA prefill incomplete and did
not cover the external-hit/contextful prefill path at all. This PR closes the
remaining CPU MLA correctness gaps so DeepSeek-V2-Lite can run through cold
prefill, external KV reload, and prefix/suffix merge on CPU.
I also checked current open PRs for overlapping work before proceeding. I did
not find an open PR that fixes this CPU MLA contextful prefill path.
Tests
Added / updated coverage in
tests/v1/attention/test_cpu_mla_backend.py:test_kv_cache_cpu_writeVerifies the CPU MLA KV-cache write path still stores MLA latent KV rows in
the layout expected by the CPU decode kernel.
test_cpu_mla_prefill_backend_selectedVerifies CPU MLA prefill selects the CPU backend.
test_cpu_mla_prefill_new_tokensVerifies the CPU SDPA MLA prefill backend matches a reference ragged causal
attention implementation, including
return_softmax_lse=True.test_cpu_mla_prefill_context_chunkVerifies the CPU SDPA MLA backend correctly handles prefill context chunks
and returns the expected per-head LSEs.
Verification
1. Targeted pytest coverage
Ran:
Result:
2. Local shell e2e: CPU TP=2 + LMCache + no prefix cache
This validation uses:
load_format="dummy"so no real model weights are loadedhf_overridesto shrink DeepSeek-V2-Lite to 2 layers / 4 routed expertsskip_tokenizer_init=Trueplus token-id prompts so the offline path doesnot depend on tokenizer startup
tensor_parallel_size=2LMCacheMPConnector--no-enable-prefix-cachingOn this single-NUMA local machine I also had to set
VLLM_CPU_SIM_MULTI_NUMA=1, otherwise CPU worker autobinding refused to launchtwo local workers. I also had to set
gpu_memory_utilization=0.25, because onthe CPU backend that flag controls the fraction of system memory reserved per
worker.
On my local machine, TCP port
6555and LMCache's default HTTP port8080were already in use, so the verified run below uses
6556and18080.Started LMCache:
Started vLLM:
Prepared a prompt with 300 token so LMCache has at least one full
256-token chunk to store and later reload:
Sent the first request to warm/store KV:
Sent the second identical request to exercise the external KV hit path:
Observed results:
This second identical prompt is the important case: it confirms the external KV
reload path no longer crashes on CPU, and that
DeepSeek-V2-Lite + CPU MLA + TP=2 + LMCache + no prefix cacheruns end-to-end.Model evaluation
No model eval was run.
Reason:
oriented
load_format="dummy"specificallyto avoid loading real weights during development
Notes
backend and fallbacks are correctness-first implementations.
CPUSparseMLAImpl; that is separate work.