Skip to content

[Model] LongCat-Flash: scale MLA norms while loading and drop the post-load sweep - #60080

Merged
aoshen02 merged 3 commits into
vllm-project:mainfrom
aoshen02:wu/longcat-scale-norms-on-load
Oct 6, 2026
Merged

aoshen02 merged 3 commits into
vllm-project:mainfrom
aoshen02:wu/longcat-scale-norms-on-load

Conversation

@aoshen02

@aoshen02 aoshen02 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Overview

LongCat-Flash's load_weights ends with a post-processing sweep. It block-dequantizes kv_b_proj into w_kc / w_vc, which nothing reads, and scales the MLA LoRA norms in place behind once-only flags. This PR removes the sweep and scales the norms in their weight loaders. It also fixes the MTP-weight skip in the target loader, without which LongCat-Flash-Chat cannot load. It is a prerequisite for #57733: load_weights now only loads.

Claims

  • LongCat-Flash-Chat-FP8 gives correct output: GSM8K 0.000 → 0.916. On main its MLA LoRA norms end up unscaled because the post-load sweep runs before they are loaded (details below).
  • LongCat-Flash-Chat-FP8 loads again. The target loader skipped MTP weights with ".mtp." in name, but AutoWeightsLoader strips the model. prefix, so the names are mtp.layers.… and loading fails with KeyError: 'mtp.layers.0.hnorm.m.weight'.
  • reload_weights keeps the MLA LoRA norms scaled. On main the _mla_*_lora_scaled flags survive the reload, so the reloaded norms stay unscaled and accuracy collapses.
  • reload_weights no longer crashes for FP8 checkpoints with quantized attention (LongCat-Flash-Lite-FP8): the removed block_dequant of kv_b_proj asserts on the reloaded shard.
  • Removes dead code: w_kc / w_vc are written and never read; MLAAttention.process_weights_after_loading derives W_UK_T / W_UV from kv_b_proj itself. The removed block also left w unbound for FP8 with weight_block_size=None; no public LongCat checkpoint reaches that branch.

Validation

GB200, real weights, vllm serve, --enforce-eager (see below). GSM8K: 1319 questions, 5-shot, greedy. "Control" is the image's LongCat loader (same sweep and flags as main) plus only the MTP-prefix fix, so that Chat can load at all.

model, parallelism control: GSM8K → reload_weights → GSM8K this PR
LongCat-Flash-Chat-FP8, DP2 × TP4, EP8 (2 nodes) 0.000 (norms left unscaled, see below) → 0.000 0.916 → 0.914
LongCat-Flash-Lite (BF16), TP4, EP 0.841 → 0.036 0.846 → 0.844
LongCat-Flash-Lite-FP8, TP4, EP 0.834 → reload fails (block_dequant: assert n_tiles == x_s.shape[0]) 0.839 → 0.843
LongCat-Flash-Chat-FP8 + MTP k=1 (control: this PR's target with main's MTP): GSM8K, draft acceptance, after reload 0.912, 92.4%, 0.916 0.909, 92.5%, 0.911

pre-commit passes.

Setup: the vLLM image nightly-dev-arm64-cu130-ac9126e58aa7 plus this change, overlaid file by file with SHA-256 gates. On this image torch.compile fails for LongCat on both arms (a Dynamo data-dependent assert in the zero-expert path, moe_runner._maybe_add_zero_expert_output), hence --enforce-eager; the change is load-time only.

Details

Why Chat-FP8 scores 0 on main. AutoWeightsLoader calls FlashModel.load_weights once per contiguous run of model.* names. LongCat-Flash-Chat-FP8 streams 16 model.* tensors, then lm_head, then the remaining 73046, so the sweep runs at the end of the first call: it scales the norms before they are loaded and sets the flags. The second call loads the real norms, and the flags skip the scaling. A debug build shows layer 0's q_a_layernorm summing to 1892.8 on main and 3785.6 with this PR (the expected factor √(6144/1536) = 2), and kv_a_layernorm 6.74 vs 23.34 (√(6144/512) ≈ 3.46).

Norm scaling. scale_mla_lora_norms_on_load sets the weight_loader of q_a_layernorm.weight / kv_a_layernorm.weight to composed_weight_loader(default_weight_loader, lambda x: x * factor) when the config enables the scaling (the same pattern as plamo3). Every load, including a reload, writes checkpoint × factor, so no flag is needed. It is applied to both attention modules of each decoder layer and to the MTP block.

MTP skip. FlashModel.load_weights now skips names starting with mtp. once, at the top of the loop, instead of three ".mtp." in name checks that never matched. LongCatFlashMTP loads those weights.

Not a duplicate. No open PR changes these loaders. #53811 proposes removing the native LongCat implementation in favour of the Transformers backend; it is not merged and has conflicts. This PR fixes the native implementation while it exists.

AI assistance. Written with Claude Code. The submitter reviewed every changed line and ran the tests and GPU checks above.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (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.

🤖 Generated with Claude Code

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added the ci/build label Oct 5, 2026
…t-load sweep

FlashModel.load_weights ended with a sweep over every attention module that
block-dequantized kv_b_proj into self_attn.w_kc / w_vc, which nothing reads
(MLAAttention.process_weights_after_loading derives W_UK_T / W_UV from
kv_b_proj), and scaled q_a_layernorm / kv_a_layernorm in place behind
_mla_*_lora_scaled flags. LongCatFlashMTP.load_weights had the same sweep.

The flags make the scaling depend on how the weights arrive:
- AutoWeightsLoader calls FlashModel.load_weights once per contiguous run of
  model.* names. LongCat-Flash-Chat-FP8 streams 16 model.* tensors, lm_head,
  then the rest, so the first call scales the not-yet-loaded norms and sets
  the flags, and the second call loads the real norms unscaled (GSM8K 0.000).
- reload_weights loads the norms again and the flags skip the scaling
  (LongCat-Flash-Lite: GSM8K 0.841 -> 0.036 after a reload).
- With FP8 attention (LongCat-Flash-Lite-FP8) the dead block_dequant asserts on
  the shards a layerwise reload passes in, so reload_weights fails.

Scale the norms in their weight loaders instead (composed_weight_loader, as
plamo3 does), so every load writes checkpoint * factor, and remove the sweep
from both files. load_weights now only loads.

FlashModel.load_weights also skipped MTP weights with ".mtp." in name, but
the names arrive as mtp.layers.* (AutoWeightsLoader strips "model."), so a
checkpoint with MTP layers failed with KeyError. Skip the mtp. prefix once,
at the top of the loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
@aoshen02
aoshen02 force-pushed the wu/longcat-scale-norms-on-load branch from f973ba7 to acb7cf8 Compare October 6, 2026 00:19
@aoshen02 aoshen02 added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 6, 2026
@aoshen02

aoshen02 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #93009 for commit 0b3832e3d316.

@vllm-agent

vllm-agent commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

CI selector (shadow): 6 test steps (26 jobs) instead of 59 (91 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 59 (91) 6 (26) 56 (73) 3 (8)
AMD mirrors 57 (84) 2 (15) 55 (69) 0 (0)
Selector would run (6)
  • arm-cpu-test ×3
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-other
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-multi-modal-model-tests-n ×4
  • cpu-multimodal-config
Would skip (today's rules run them) (56)
  • amd-lm-eval-small-models-harness
  • amd-qwen3-next-mtp-async-eplb-accuracy
  • ascend-npu-test
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-test-other-cpu
  • basic-models-tests-initialization
  • benchmarks-cli-test
  • distributed-model-tests-2-gpus ×3
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • model-executor
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • pipeline-context-parallelism-4-gpus
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-sample
  • v1-spec-decode
Would add (today's rules do not run them) (3)
  • arm-cpu-test ×3 (code map)
  • cpu-multi-modal-model-tests-n ×4 (code map)
  • cpu-multimodal-config (code map)
AMD mirrors: would skip (55)
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-initialization
  • benchmarks-cli-test
  • distributed-model-tests-2-gpus ×3
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-extra-standard ×2
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-small-models
  • metrics-tracing-2-gpus
  • model-executor
  • mrcr-eval-small-models
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • pipeline-context-parallelism-4-gpus
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-sample
  • v1-spec-decode
AMD mirrors: would add (0)

none

2 changed files · base 6ad2e06c4a · head 3f5770203a · Python record: build 92956 at 18c8a65edd · kernel record: table 18c8a65 (build 92956), map 18c8a65 · not counted: 10 build steps, 5 A100 steps the generator no longer emits, 2 optional steps the selector would also run

@aoshen02

aoshen02 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

/ci run

@aoshen02
aoshen02 enabled auto-merge (squash) October 6, 2026 07:04
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #93046 for commit 3f5770203a36.

@aoshen02
aoshen02 merged commit 31e2443 into vllm-project:main Oct 6, 2026
148 of 149 checks passed
@aoshen02
aoshen02 deleted the wu/longcat-scale-norms-on-load branch October 6, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants