glm52: allocate the MLA absorbed pair before the dequant scratch (stacks on #146)#148
Conversation
Per-layer outer-scale calibration for nvfp4_ds_mla KV (VLLM_NVFP4_MLA_SCALES_FILE, format v1) with an explicit default-off knob in serve-glm52.sh. Collapses the NVFP4-vs-FP8 KV KLD gap to ~+0.008-0.009 (0.1345/0.1356 vs 0.1263, 5 fresh boots each, rtx6kpro protocol) while raising max context from 373k to 550k/600k+ on 4x96GB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With no MHA prefill backend the MLA runtime consumes only the absorbed W_UK_T/W_UV pair, so kv_b_proj's quantized pack is dead after the absorb dequant: free it (~290 MiB/GPU at GLM-5.2 TP4). Draft configs never receive the target's dict hf_overrides, so a shrunken target max_position_embeddings left the draft allocating duplicate full-length rotary tables (128 MiB/GPU): propagate the smaller bound, which is exact because scheduling is limited by the target. Validated on GLM-5.2 4x96GB TP4+DCP4: KV pool 600,255 -> 700,255 tokens at unchanged KLD (5 fresh-boot runs 0.1360+-0.0019 vs 0.1356+-0.0054 cert), 157,932-token prefill and decode throughput unchanged, MTP accept rate 94.1% unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The absorb path materializes W_UK_T/W_UV after the per-layer dequant scratch has churned, interleaving long-lived tensors with transient allocations. Allocating the pair first lets each layer's scratch free into clean segments: measured allocator reserved-but-unallocated drops 0.577 -> 0.225 GiB/GPU at GLM-5.2 TP4 geometry, with identical tensors served to decode. Validated on 4x96GB TP4+DCP4: graduated prefill ladder (2k/16k/60k/ 157,932 tokens) green, decode 106.7 t/s and MTP acceptance 92.8% unchanged, and the recovered headroom seats a 740,255-token KV pool that passes the same ladder. Teacher-forced prefill KLD re-certified across three 5-run fresh-boot campaigns: pooled 15 runs 0.1366+-0.0042 vs the standing 0.1356+-0.0054 cert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.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. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging. To run CI, PR reviewers can either: Add 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. 🚀 |
📝 WalkthroughWalkthroughAdds GLM-5.2 NVFP4 MLA outer-scale artifacts and launch configuration, clamps speculative draft context limits, and updates MLA absorbed-weight storage and cleanup behavior. ChangesNVFP4 MLA outer scales
Runtime configuration and MLA storage
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
kv-scales/README.md (1)
28-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider updating the results to reflect the latest validation runs.
The PR objectives indicate that the latest evaluation across 15 fresh-cache runs yielded a KLD of
0.1366 +/- 0.0042, whereas the table currently documents the standing certification of 5 fresh boots with0.1356 +/- 0.0054. Consider updating the documentation to reflect the most recent validation results if they supersede the standing certification.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kv-scales/README.md` around lines 28 - 36, Update the Results table entry for nvfp4_ds_mla with scales and fp8 rope to reflect the latest 15 fresh-cache validation result, using KLD 0.1366 +/- 0.0042 if it supersedes the documented 5-boot certification. Preserve the existing configuration label and max-context value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@vllm/model_executor/layers/attention/mla_attention.py`:
- Around line 1288-1293: Update the device selection for both temporary tensors
in the affected initialization flow, replacing direct access to
self.kv_b_proj.weight.device with the device obtained safely from
self.kv_b_proj’s parameters. Preserve the existing shapes and dtype while
supporting quantized modules that lack a weight attribute.
- Around line 1375-1380: Update the replace_parameter calls for W_UV and W_UK_T
in the initialization flow to use prefer_copy=True instead of prefer_copy=False.
Preserve the existing preallocated tensor preparation and parameter names so
initial loading reuses those tensors while subsequent updates copy in place and
retain data_ptr stability for CUDA graphs.
---
Nitpick comments:
In `@kv-scales/README.md`:
- Around line 28-36: Update the Results table entry for nvfp4_ds_mla with scales
and fp8 rope to reflect the latest 15 fresh-cache validation result, using KLD
0.1366 +/- 0.0042 if it supersedes the documented 5-boot certification. Preserve
the existing configuration label and max-context value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d869cb96-085d-4c67-b688-1f255aa63565
📒 Files selected for processing (5)
kv-scales/README.mdkv-scales/glm52-nvfp4-nf3-hybrid_mla_outer_scales_v1.jsonserve-glm52.shvllm/config/speculative.pyvllm/model_executor/layers/attention/mla_attention.py
| pre_w_uv = torch.empty( | ||
| (self.num_heads, self.kv_lora_rank, self.v_head_dim), | ||
| dtype=act_dtype, device=self.kv_b_proj.weight.device) | ||
| pre_w_uk_t = torch.empty( | ||
| (self.num_heads, self.qk_nope_head_dim, self.kv_lora_rank), | ||
| dtype=act_dtype, device=self.kv_b_proj.weight.device) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Prevent AttributeError on quantized layers missing a weight attribute.
If the model is quantized (e.g., AWQ, GPTQ), self.kv_b_proj might not have a weight attribute, using qweight instead. Accessing self.kv_b_proj.weight.device will crash in these scenarios. As evidenced by the hasattr(self.kv_b_proj, "weight") check in __init__, the attribute's existence is not guaranteed.
Retrieve the device safely from the module's parameters.
🐛 Proposed fix
- pre_w_uv = torch.empty(
- (self.num_heads, self.kv_lora_rank, self.v_head_dim),
- dtype=act_dtype, device=self.kv_b_proj.weight.device)
- pre_w_uk_t = torch.empty(
- (self.num_heads, self.qk_nope_head_dim, self.kv_lora_rank),
- dtype=act_dtype, device=self.kv_b_proj.weight.device)
+ device = (
+ self.kv_b_proj.weight.device
+ if hasattr(self.kv_b_proj, "weight")
+ else next(self.kv_b_proj.parameters()).device
+ )
+ pre_w_uv = torch.empty(
+ (self.num_heads, self.kv_lora_rank, self.v_head_dim),
+ dtype=act_dtype,
+ device=device,
+ )
+ pre_w_uk_t = torch.empty(
+ (self.num_heads, self.qk_nope_head_dim, self.kv_lora_rank),
+ dtype=act_dtype,
+ device=device,
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pre_w_uv = torch.empty( | |
| (self.num_heads, self.kv_lora_rank, self.v_head_dim), | |
| dtype=act_dtype, device=self.kv_b_proj.weight.device) | |
| pre_w_uk_t = torch.empty( | |
| (self.num_heads, self.qk_nope_head_dim, self.kv_lora_rank), | |
| dtype=act_dtype, device=self.kv_b_proj.weight.device) | |
| device = ( | |
| self.kv_b_proj.weight.device | |
| if hasattr(self.kv_b_proj, "weight") | |
| else next(self.kv_b_proj.parameters()).device | |
| ) | |
| pre_w_uv = torch.empty( | |
| (self.num_heads, self.kv_lora_rank, self.v_head_dim), | |
| dtype=act_dtype, | |
| device=device, | |
| ) | |
| pre_w_uk_t = torch.empty( | |
| (self.num_heads, self.qk_nope_head_dim, self.kv_lora_rank), | |
| dtype=act_dtype, | |
| device=device, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@vllm/model_executor/layers/attention/mla_attention.py` around lines 1288 -
1293, Update the device selection for both temporary tensors in the affected
initialization flow, replacing direct access to self.kv_b_proj.weight.device
with the device obtained safely from self.kv_b_proj’s parameters. Preserve the
existing shapes and dtype while supporting quantized modules that lack a weight
attribute.
| pre_w_uv.copy_(W_UV.transpose(0, 1)) | ||
| replace_parameter(self, "W_UV", pre_w_uv, prefer_copy=False) | ||
| # Convert from (L, N, P) to (N, P, L) | ||
| replace_parameter(self, "W_UK_T", W_UK.permute(1, 2, 0), prefer_copy=True) | ||
| pre_w_uk_t.copy_(W_UK.permute(1, 2, 0)) | ||
| replace_parameter(self, "W_UK_T", pre_w_uk_t, prefer_copy=False) | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Enable in-place updates to preserve data_ptr for CUDA graphs.
Explicitly setting prefer_copy=False forces replace_parameter to re-register the parameter. In workflows that perform in-place weight updates (such as RL training loops), this re-registration changes the underlying storage address (data_ptr) and breaks captured CUDA graphs.
Changing this to prefer_copy=True achieves both goals: during the initial load (when the parameter doesn't exist), it wraps the pre-allocated pre_w_uv tensor directly to avoid allocator fragmentation. During subsequent weight updates, it copies the new values into the existing parameter in-place, keeping the data_ptr intact.
🔄 Proposed fix
# Convert from (L, N, V) to (N, L, V)
pre_w_uv.copy_(W_UV.transpose(0, 1))
- replace_parameter(self, "W_UV", pre_w_uv, prefer_copy=False)
+ replace_parameter(self, "W_UV", pre_w_uv, prefer_copy=True)
# Convert from (L, N, P) to (N, P, L)
pre_w_uk_t.copy_(W_UK.permute(1, 2, 0))
- replace_parameter(self, "W_UK_T", pre_w_uk_t, prefer_copy=False)
+ replace_parameter(self, "W_UK_T", pre_w_uk_t, prefer_copy=True)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pre_w_uv.copy_(W_UV.transpose(0, 1)) | |
| replace_parameter(self, "W_UV", pre_w_uv, prefer_copy=False) | |
| # Convert from (L, N, P) to (N, P, L) | |
| replace_parameter(self, "W_UK_T", W_UK.permute(1, 2, 0), prefer_copy=True) | |
| pre_w_uk_t.copy_(W_UK.permute(1, 2, 0)) | |
| replace_parameter(self, "W_UK_T", pre_w_uk_t, prefer_copy=False) | |
| pre_w_uv.copy_(W_UV.transpose(0, 1)) | |
| replace_parameter(self, "W_UV", pre_w_uv, prefer_copy=True) | |
| # Convert from (L, N, P) to (N, P, L) | |
| pre_w_uk_t.copy_(W_UK.permute(1, 2, 0)) | |
| replace_parameter(self, "W_UK_T", pre_w_uk_t, prefer_copy=True) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@vllm/model_executor/layers/attention/mla_attention.py` around lines 1375 -
1380, Update the replace_parameter calls for W_UV and W_UK_T in the
initialization flow to use prefer_copy=True instead of prefer_copy=False.
Preserve the existing preallocated tensor preparation and parameter names so
initial loading reuses those tensors while subsequent updates copy in place and
retain data_ptr stability for CUDA graphs.
|
Superseded by #156, which is a one-commit PR directly against the current |
One-commit follow-up to #146 (branch includes #145+#146; review the tip commit only). AI-assisted (Claude); the submitter has reviewed every changed line. Not a duplicate of any open PR (searched
absorb,W_UK_T,fragmentationon the repo).The MLA absorb materializes
W_UK_T/W_UVafter the per-layer dequant scratch has churned, so the long-lived pair lands interleaved with transients. Allocating the pair first lets each layer's scratch free into clean segments: allocator reserved-but-unallocated drops 0.577 → 0.225 GiB/GPU (GLM-5.2 TP4 geometry), with identical tensors served to decode. This is the general MLA absorb path — every MLA model pays some version of this churn.Tests/validation on 4×96GB TP4+DCP4 (
nvfp4_ds_mla+ fp8 rope + #145 scales), servingGLM-5.2viaserve-glm52.sh:glm52-kld-evaluation, fresh caches): three 5-run campaigns pooled = 0.1366 ± 0.0042 over 15 runs vs the standing 0.1356 ± 0.0054 cert — statistically identical (the capture rig runs none of the serving-side changes by construction).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
VLLM_NVFP4_MLA_SCALES_FILE.Bug Fixes