Skip to content

glm52: allocate the MLA absorbed pair before the dequant scratch (stacks on #146)#148

Closed
MadeBy561 wants to merge 3 commits into
local-inference-lab:dev/gilded-gnosisfrom
MadeBy561:feat/glm52-prealloc-absorb-pair
Closed

glm52: allocate the MLA absorbed pair before the dequant scratch (stacks on #146)#148
MadeBy561 wants to merge 3 commits into
local-inference-lab:dev/gilded-gnosisfrom
MadeBy561:feat/glm52-prealloc-absorb-pair

Conversation

@MadeBy561

@MadeBy561 MadeBy561 commented Jul 21, 2026

Copy link
Copy Markdown

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, fragmentation on the repo).

The MLA absorb materializes W_UK_T/W_UV after 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), serving GLM-5.2 via serve-glm52.sh:

  • graduated prefill ladder 2k / 16k / 60k / 157,932 tokens: green, timings unchanged
  • decode 106.7 t/s (band 99–108), MTP acceptance 92.8% — unchanged
  • the recovered headroom seats a 740,255-token KV pool that passes the same ladder
  • teacher-forced prefill KLD (rtx6kpro 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

    • Added support for configuring per-layer NVFP4 MLA KV-cache outer scales through VLLM_NVFP4_MLA_SCALES_FILE.
    • Included a calibrated GLM-5.2 scale configuration and usage documentation, including evaluation results and cache-refresh guidance.
  • Bug Fixes

    • Draft models now respect the target model’s maximum sequence length, preventing unnecessary position-table allocation.
    • Reduced unused quantized attention weight storage after loading in applicable configurations.

MadeBy561 and others added 3 commits July 20, 2026 15:02
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>
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

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 ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: 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.

🚀

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

NVFP4 MLA outer scales

Layer / File(s) Summary
Scale artifact and configuration
kv-scales/glm52-nvfp4-nf3-hybrid_mla_outer_scales_v1.json, serve-glm52.sh
Adds per-layer calibration metadata, max_abs values, scale factors, and the VLLM_NVFP4_MLA_SCALES_FILE environment variable.
Scale usage and validation documentation
kv-scales/README.md
Documents the calibration formula, launch usage, evaluation results, and required CuTeDSL/B12X cache invalidation.

Runtime configuration and MLA storage

Layer / File(s) Summary
Speculative draft context clamp
vllm/config/speculative.py
Clamps draft max_position_embeddings to the target limit when the draft and target configurations are distinct.
MLA absorbed-weight storage lifecycle
vllm/model_executor/layers/attention/mla_attention.py
Pre-allocates absorbed-weight buffers, copies transformed weights into them, and clears unused packed or quantized KV projection storage.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: lukealonso, voipmonitor

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: allocating the MLA absorbed pair before dequant scratch.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
kv-scales/README.md (1)

28-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider 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 with 0.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

📥 Commits

Reviewing files that changed from the base of the PR and between 73e4a8c and df2ebb1.

📒 Files selected for processing (5)
  • kv-scales/README.md
  • kv-scales/glm52-nvfp4-nf3-hybrid_mla_outer_scales_v1.json
  • serve-glm52.sh
  • vllm/config/speculative.py
  • vllm/model_executor/layers/attention/mla_attention.py

Comment on lines +1288 to +1293
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Comment on lines +1375 to +1380
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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.

Suggested change
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.

@voipmonitor

Copy link
Copy Markdown

Superseded by #156, which is a one-commit PR directly against the current dev/gilded-gnosis branch. This PR accidentally carried the unrelated #145/#146 stack. The replacement preserves the unique preallocation optimization and incorporates both valid CodeRabbit findings: safe device discovery for quantized modules without .weight, and reload-safe prefer_copy=True pointer stability. It also avoids unused preallocation on Aiter FP4/FP8 paths and adds focused unit tests. The kv-scales/README.md KLD nit was intentionally not ported because that file belongs to the unrelated #145 stack. No replacement PR has been merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants