Skip to content

mla: wire dynamic per-token NVFP4 scaling and cache ABI - #189

Merged
lukealonso merged 4 commits into
local-inference-lab:dev/gilded-gnosisfrom
yatesdr:nvfp4-dynamic-token-scale
Jul 29, 2026
Merged

mla: wire dynamic per-token NVFP4 scaling and cache ABI#189
lukealonso merged 4 commits into
local-inference-lab:dev/gilded-gnosisfrom
yatesdr:nvfp4-dynamic-token-scale

Conversation

@yatesdr

@yatesdr yatesdr commented Jul 28, 2026

Copy link
Copy Markdown

Summary

Wire SparkInfer's per-token NVFP4 MLA scale mode
(local-inference-lab/sparkinfer#86) through vLLM and make the resulting cache
format safe for persistent DRAM/NVMe offload.

VLLM_NVFP4_MLA_DYNAMIC_SCALE=1 selects the mode once per process. Both
writer sites and both sparse-attention readers use the same immutable
configuration. Incompatible SparkInfer APIs, layouts, or simultaneous static
calibration fail during initialization.

Why

The 368-byte nvfp4_ds_mla record uses E2M1 values and E4M3 group scales for
the compressed latent. With an implicit outer scale of 1.0, low-magnitude
layers place many group scales in E4M3's subnormal range. The reconstruction
error changes the hidden-state trajectory consumed by sparse indexing and
caused repeatable deep-context retrieval failures in the tested GLM-5.2
NF3/NVFP4 configuration.

SparkInfer #86 stores one per-token FP32 outer scale in existing record
padding and applies it during decode and MG-prefill dequantization. This PR
connects that mechanism to serving and prevents records with different scale
semantics from sharing a persistent cache namespace.

Changes

Immutable record mode

  • Add frozen Nvfp4MlaCacheFormat, captured once at process import.
  • Reject dynamic scaling combined with
    VLLM_NVFP4_MLA_SCALES_FILE.
  • Require KV_FP8_ROPE=1 for the dynamic 368-byte layout.
  • Register both NVFP4 scale variables in vllm.envs.

Writer and reader wiring

  • Propagate per_token_scale=True through:
    • the normal per-step KV writer;
    • the DCP gathered-current-chunk re-quantization writer.
  • Propagate latent_scale_per_token=True through decode and extend.
  • Fail closed if the SparkInfer writer lacks per_token_scale, either reader
    lacks latent_scale_per_token, or the record is not the 368-byte FP8-RoPE
    NVFP4 layout.
  • Mode off omits the new writer keyword for compatibility with older
    SparkInfer builds.

Persistent cache ABI

The record bytes do not contain a version marker. The external cache identity
therefore includes:

  • dynamic versus static-calibrated scale semantics;
  • FP8-RoPE record layout;
  • SHA-256 of a static calibration file;
  • resolved bytes per KV block.

All writer/readers import the same frozen format object. Dynamic and static
records cannot share a DRAM/NVMe namespace.

Backward compatibility is explicit: non-NVFP4 dtypes and implicit/default
NVFP4 configurations retain the literal vllm-default-v1 sentinel and their
existing FileMapper namespace. Only dynamic or calibrated modes opt into the
new ABI fields.

Validation

Focused vLLM tests

67 passed on the integrated CUDA 13.2 image:

  • immutable environment capture;
  • dynamic + static rejection;
  • dynamic + non-368-byte rejection;
  • missing writer/reader signature rejection;
  • propagation through both writer sites;
  • static-file content hash in ABI;
  • dynamic/static/geometry namespace separation;
  • unchanged namespace through the real offloading-config path for float16,
    bfloat16, auto, and implicit NVFP4;
  • registered environment variables.

Pinned Ruff check and format pass across all 11 touched files.

SparkInfer GPU integration

28 production-path tests passed on 4× SM120, covering static/dynamic records,
zero-token handling, multisplit decode, and multitile MG prefill.

End-to-end quality

Gate Static/uncalibrated Dynamic per-token
Frozen 245,497-token control correct correct
Frozen 343,727-token rows 0/3 correct 3/3 correct
Randomized ladder, 49,098–466,493 tokens 6/6 correct

KLD versus BF16 reference (n=3, lower is better):

Mode Mean Sample SD
Static calibrated (#145 file) 0.146228 0.004688
Dynamic per-token 0.139036 0.002010

Performance

Matched MTP0 decode, 10 fixed 1,024-token samples per arm:

Mode Mean Sample SD
Static calibrated 46.076 tok/s 0.0268
Dynamic per-token 45.901 tok/s 0.0218

Observed delta: -0.175 tok/s (-0.380%), inside the pre-committed 1% reader
overhead limit. MTP was disabled, so the result is not confounded by draft
acceptance.

Full retrieval, KLD, prefill, and capacity tables are in SparkInfer #86.

Reproduction

--kv-cache-dtype nvfp4_ds_mla
KV_FP8_ROPE=1
VLLM_NVFP4_MLA_DYNAMIC_SCALE=1
# VLLM_NVFP4_MLA_SCALES_FILE must be unset

Persistent offload automatically derives a distinct dynamic record namespace.
No manual cache-path suffix is required after this PR.

Behavioral evaluation image:
ghcr.io/yatesdr/glm52-serve@sha256:db82fdcb5756d4a547853ba1330538bdd8a3dc0c6443c29bc49ba77b69b51cd1.
The later PR-head commits add cache-contract hardening, tests, and env
registration without changing the measured dynamic arithmetic.

Limitations


Implementation and text were drafted with AI assistance. All changed lines
were independently reviewed, and measurements were executed and validated by
the submitting operators. This is not a duplicate of #145: #145 supplies
checkpoint-specific static calibration; this PR wires calibration-free
per-token scaling and its cache-format contract.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared NVFP4 MLA cache-format configuration, dynamic per-token scaling for compatible B12x sparse-MLA paths, and ABI-aware KV offload namespaces. Static calibration is validated and fingerprinted, while backend capability checks cover writer and kernel interfaces.

Changes

NVFP4 MLA cache format and integration

Layer / File(s) Summary
Cache format configuration
vllm/model_executor/layers/mla_cache_format.py, tests/model_executor/layers/test_mla_cache_format.py
Introduces environment-derived NVFP4 MLA settings, validation, static-scale fingerprinting, and ABI generation with unit coverage.
MLA configuration integration
vllm/model_executor/layers/mla.py
Uses the shared cache format for FP8 RoPE and outer-scale setup, validates dynamic/static configuration conflicts, and retains scale-file denominator checks.
Dynamic backend propagation
vllm/v1/attention/backends/mla/b12x_mla_sparse.py, tests/v1/attention/test_b12x_mla_fp8_rope_writer.py
Validates backend callable support, passes per_token_scale=True through NVFP4 writer paths, and emits dynamic kernel scale arguments.
Offloading ABI namespace
vllm/v1/kv_offload/config.py, vllm/v1/kv_offload/file_mapper.py, vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py, tests/v1/kv_offload/*
Adds cache ABI metadata to offloading configuration and incorporates ABI and worker KV geometry into persistent mapper namespaces.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Environment
  participant NVFP4_MLA_CACHE_FORMAT
  participant B12xMLASparseImpl
  participant SparkInfer
  participant KVOffload
  Environment->>NVFP4_MLA_CACHE_FORMAT: Parse NVFP4 MLA settings
  NVFP4_MLA_CACHE_FORMAT->>B12xMLASparseImpl: Provide dynamic and FP8-RoPE configuration
  B12xMLASparseImpl->>SparkInfer: Validate and invoke dynamic NVFP4 writer
  NVFP4_MLA_CACHE_FORMAT->>KVOffload: Generate cache ABI
  KVOffload->>KVOffload: Include ABI and KV geometry in mapper namespace
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: dynamic per-token NVFP4 MLA scaling plus cache ABI wiring.
✨ 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.

…_DYNAMIC_SCALE)

The nvfp4_ds_mla writer's outer scale is statically 1.0, parking
shallow-layer group scales in E4M3 subnormals (the deep-retrieval
regression root cause; PR local-inference-lab#145 calibrates it statically per layer).
This mode removes the calibration requirement: the SparkInfer writer
derives a per-token second-level scale stored inside the record
([292,296) of the existing pad; record stays 368 bytes) and the
readers consume it from the record instead of a per-layer launch
scalar.

- VLLM_NVFP4_MLA_DYNAMIC_SCALE=1 gate; requires KV_FP8_ROPE=1
  368-byte records and a SparkInfer build with per_token_scale /
  latent_scale_per_token support (fail-closed inspect checks)
- mutually exclusive with VLLM_NVFP4_MLA_SCALES_FILE (ValueError)
- writer calls (do_kv_cache_update + DCP gather re-quantization)
  pass per_token_scale only when enabled, keeping older SparkInfer
  builds working with the mode off
- kernel format kwargs pin latent_scale=1.0 and add
  latent_scale_per_token in mode

Draft: not yet compiled/run (no CUDA on authoring host); CN4
validation runbook in glm52-opt design/nvfp4-dynamic-second-level-
scale-phaseA-addendum.md.

Co-authored-by: Fable <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHuYXXq9krPdvcgpzY4EfE
@yatesdr
yatesdr force-pushed the nvfp4-dynamic-token-scale branch from 91dff5a to ae541fc Compare July 28, 2026 02:32
@yatesdr
yatesdr changed the base branch from main to dev/gilded-gnosis July 28, 2026 02:32
@yatesdr
yatesdr marked this pull request as ready for review July 28, 2026 05:59
Capture one server-static writer/reader mode, fail closed on incompatible SparkInfer APIs, and include the record ABI in persistent offload namespaces. Add focused tests for invalid modes, both writer sites, and stale-cache separation.

Assisted-by: OpenAI Codex

@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: 4

🧹 Nitpick comments (1)
vllm/model_executor/layers/mla_cache_format.py (1)

26-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the public configuration API in Google style.

from_env() and validate() lack docstrings, and record_abi() omits its argument, return value, and failure contract.

Proposed fix
 `@classmethod`
 def from_env(cls) -> Nvfp4MlaCacheFormat:
+    """Capture the process-wide NVFP4 MLA cache configuration.
+
+    Returns:
+        The immutable configuration derived from environment variables.
+    """
     return cls(
 
 def validate(self) -> None:
+    """Reject configurations without a stable NVFP4 MLA record format.
+
+    Raises:
+        ValueError: If dynamic scaling conflicts with static scales or FP8 RoPE.
+    """
     if self.dynamic_scale and self.scales_file:
 
 def record_abi(self, cache_dtype: str) -> str:
-    """Return an identity suitable for persistent external-cache keys."""
+    """Build the persistent cache ABI for a cache dtype.
+
+    Args:
+        cache_dtype: Cache data type supplied by the backend.
+
+    Returns:
+        A persistent cache ABI identifier.
+
+    Raises:
+        ValueError: If NVFP4 MLA configuration is invalid or static scales
+            cannot be fingerprinted.
+    """

As per coding guidelines, use Google-style docstrings with Args:/Returns:/Raises: sections.

🤖 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/mla_cache_format.py` around lines 26 - 65, Add
Google-style docstrings to Nvfp4MlaCacheFormat.from_env and validate, and expand
record_abi’s docstring with Args, Returns, and Raises sections. Document the
environment-based configuration, ABI string returned, and ValueError conditions
from validation or scale-file fingerprinting without changing behavior.

Source: Coding guidelines

🤖 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 `@tests/v1/kv_offload/test_factory.py`:
- Around line 451-471: Extend the offloading configuration tests around
test_offloading_config_carries_nvfp4_record_abi to cover a non-NVFP4 cache_dtype
through build_offloading_config. Assert that
offloading_config.model.kv_cache_abi remains the "vllm-default-v1" sentinel
expected by FileMapper, while preserving the existing NVFP4 assertion.

In `@tests/v1/kv_offload/test_file_mapper.py`:
- Around line 147-172: Update
test_default_record_abi_preserves_existing_namespace to construct its mapper
through the real build_offloading_config record_abi flow for a non-NVFP4 dtype,
rather than relying on make_mapper_from_offloading_spec’s omitted kv_cache_abi
default. Assert the resulting mapper preserves the existing namespace and
validates the actual sentinel produced by record_abi.

In `@vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py`:
- Around line 150-152: Update build_offloading_config so kv_cache_abi remains
the literal "vllm-default-v1" sentinel for all non-NVFP4 cache dtypes, and call
NVFP4_MLA_CACHE_FORMAT.record_abi only when cache_dtype is "nvfp4_ds_mla" and
the dynamic-scale configuration requires it. Preserve the existing ABI sentinel
for legacy users so FileMapper.__init__ does not add new hash fields unless
NVFP4 dynamic-scale behavior is active.

In `@vllm/v1/kv_offload/file_mapper.py`:
- Around line 63-65: Update the guard in the file-mapping initialization to
recognize the upstream default ABI values produced by build_offloading_config,
rather than checking only the literal "vllm-default-v1". Preserve the legacy
namespace for all default configurations and add kv_cache_abi and
worker_kv_bytes_per_block only for configurations that require the new
namespace.

---

Nitpick comments:
In `@vllm/model_executor/layers/mla_cache_format.py`:
- Around line 26-65: Add Google-style docstrings to Nvfp4MlaCacheFormat.from_env
and validate, and expand record_abi’s docstring with Args, Returns, and Raises
sections. Document the environment-based configuration, ABI string returned, and
ValueError conditions from validation or scale-file fingerprinting without
changing behavior.
🪄 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: 46f46e32-e0be-4458-9bca-e467ae2d9fee

📥 Commits

Reviewing files that changed from the base of the PR and between ae541fc and 72445d4.

📒 Files selected for processing (10)
  • tests/model_executor/layers/test_mla_cache_format.py
  • tests/v1/attention/test_b12x_mla_fp8_rope_writer.py
  • tests/v1/kv_offload/test_factory.py
  • tests/v1/kv_offload/test_file_mapper.py
  • vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py
  • vllm/model_executor/layers/mla.py
  • vllm/model_executor/layers/mla_cache_format.py
  • vllm/v1/attention/backends/mla/b12x_mla_sparse.py
  • vllm/v1/kv_offload/config.py
  • vllm/v1/kv_offload/file_mapper.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • vllm/model_executor/layers/mla.py
  • vllm/v1/attention/backends/mla/b12x_mla_sparse.py

Comment thread tests/v1/kv_offload/test_factory.py
Comment thread tests/v1/kv_offload/test_file_mapper.py
Comment thread vllm/distributed/kv_transfer/kv_connector/v1/offloading/config.py
Comment thread vllm/v1/kv_offload/file_mapper.py
yatesdr added 2 commits July 28, 2026 10:18
Recognize the dynamic and static calibration variables so validated configurations do not emit unknown-vLLM-environment warnings.

Assisted-by: OpenAI Codex
@yatesdr yatesdr changed the title mla: wire dynamic per-token outer scaling for B12X NVFP4 KV cache mla: wire dynamic per-token NVFP4 scaling and cache ABI Jul 28, 2026
@yatesdr

yatesdr commented Jul 28, 2026

Copy link
Copy Markdown
Author

CI is currently blocked by repository policy rather than a test failure: .github/workflows/pre-commit.yml requires one of verified, ready, or ready-run-all-tests, but the repository currently defines none of those labels and this account has no triage permission to create/apply one. Local/CN4 validation is complete (67 focused tests, Ruff clean, 28 SparkInfer GPU integration tests, matched MTP0 decode delta -0.380%). A maintainer needs to create/apply a workflow-accepted label to start hosted pre-commit.

@lukealonso
lukealonso merged commit 8752e11 into local-inference-lab:dev/gilded-gnosis Jul 29, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants