-
Notifications
You must be signed in to change notification settings - Fork 504
fix(model): stop building a DeepSeek query norm HF does not define #5262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
yaoyu-33
merged 12 commits into
NVIDIA-NeMo:main
from
bzantium:fix/deepseek-linear-q-proj-input-norm-mapping
Aug 24, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b2bf7ed
fix(ckpt): skip unmapped conversion task slots instead of crashing
bzantium 7b9b62c
fix(model): stop building a DeepSeek query norm HF does not define
bzantium bd25f3e
test(model): cover the affected DeepSeek bridges for the query-norm fix
bzantium b67b8d9
fix(model): cover the paths the DeepSeek query-norm override missed
bzantium ee9f7d9
fix(conversion): reject incomplete weight mappings
yaoyu-33 ff91efc
docs(ckpt): document the absent-weight use of allow_hf_name_mismatch
bzantium 96c66bd
Merge remote-tracking branch 'upstream/main' into HEAD
bzantium 49bb737
Merge branch 'main' into fix/deepseek-linear-q-proj-input-norm-mapping
kamran-nvidia b6a6612
Merge branch 'main' into fix/deepseek-linear-q-proj-input-norm-mapping
kamran-nvidia 7fa58ea
Merge remote-tracking branch 'origin/main' into onecommit
bzantium abd5d21
test(model): build toy checkpoints in the released layout
bzantium bdbe7eb
Merge branch 'main' into fix/deepseek-linear-q-proj-input-norm-mapping
adityavavreNVDA File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """MLA attention spec helpers for the DeepSeek family.""" | ||
|
|
||
| from dataclasses import replace | ||
| from typing import Optional | ||
|
|
||
| from megatron.core.models.gpt.gpt_layer_specs import get_gpt_decoder_block_spec | ||
| from megatron.core.transformer.identity_op import IdentityOp | ||
| from megatron.core.transformer.mla_qk_norm_config import get_backend | ||
| from megatron.core.transformer.multi_latent_attention import MLASelfAttention | ||
| from megatron.core.transformer.spec_utils import ModuleSpec | ||
| from megatron.core.transformer.transformer_config import TransformerConfig | ||
|
|
||
|
|
||
| class MLASelfAttentionWithoutQueryNorm(MLASelfAttention): | ||
| """MLA self-attention that does not add a query norm when there is no query LoRA. | ||
|
|
||
| MCore derives Q and KV normalization from a single ``qk_layernorm`` flag. DeepSeek | ||
| needs it enabled for ``kv_a_layernorm``, which every checkpoint ships. When | ||
| ``q_lora_rank`` is None, that same flag also makes MCore fuse a query normalization | ||
| into ``linear_q_proj`` (``QKNormConfigResolver._resolve_mla_qk_layernorm``), but the | ||
| HF architecture defines no query-side norm in that case: ``DeepseekV3Attention`` | ||
| builds a bare ``q_proj``. | ||
|
|
||
| The result is a trainable parameter with no HF counterpart, which cannot be loaded | ||
| and is silently dropped on export. This subclass keeps the KV norm and drops the | ||
| query norm so the converted model matches the source architecture. | ||
|
|
||
| Transformer Engine is required for the no-query-LoRA case. MCore builds | ||
| ``linear_q_proj`` from the backend's fused norm+linear implementation, which only | ||
| Transformer Engine provides, so the local backend is rejected with an explicit | ||
| message rather than an internal one. | ||
| """ | ||
|
|
||
| def _resolve_qk_norm_config(self, submodules): | ||
| """Replace the fused query projection with a plain one when there is no query LoRA. | ||
|
|
||
| The standalone-``q_layernorm`` case is neutralised *before* delegating: an MLA spec | ||
| may set ``q_layernorm`` to a real norm whenever ``qk_layernorm`` is on, and the | ||
| parent resolver rejects that outright when there is no query LoRA to consume it | ||
| (``_raise_unused_q_norm``). Dropping the query norm is exactly what this class | ||
| exists to do, so the rejection would fire on a configuration this class already | ||
| knows how to satisfy. | ||
| """ | ||
| if self.config.q_lora_rank is not None: | ||
| return super()._resolve_qk_norm_config(submodules) | ||
|
|
||
| backend = get_backend(self.config.transformer_impl) | ||
| if backend.column_parallel_layer_norm_linear() is None: | ||
| raise ValueError( | ||
| "DeepSeek without a query LoRA (`q_lora_rank=None`) requires " | ||
| f"`transformer_impl='transformer_engine'`; `{self.config.transformer_impl}` " | ||
| "provides no fused norm+linear projection. MCore's MLA resolver builds " | ||
| "`linear_q_proj` from that fused implementation whenever `qk_layernorm` is " | ||
| "on, and DeepSeek needs `qk_layernorm` on for `kv_a_layernorm`, so this " | ||
| "backend cannot express the architecture." | ||
| ) | ||
|
|
||
| if submodules.q_layernorm not in (None, IdentityOp): | ||
| submodules = replace(submodules, q_layernorm=IdentityOp) | ||
|
|
||
| layer_classes = super()._resolve_qk_norm_config(submodules) | ||
| layer_classes["linear_q_proj"] = backend.column_parallel_linear() | ||
| return layer_classes | ||
|
|
||
|
|
||
| def get_deepseek_decoder_block_spec( | ||
| config: TransformerConfig, | ||
| use_transformer_engine: bool, | ||
| normalization: Optional[str] = None, | ||
| qk_l2_norm: Optional[bool] = False, | ||
| vp_stage: Optional[int] = None, | ||
| pp_rank: Optional[int] = None, | ||
| ) -> ModuleSpec: | ||
| """Build the decoder block spec, omitting the query norm when ``q_lora_rank`` is None. | ||
|
|
||
| The signature mirrors ``get_gpt_decoder_block_spec`` exactly, including ``vp_stage`` | ||
| and ``pp_rank``. ``GPTModelProvider.provide()`` inspects the callable's parameters and | ||
| only forwards ``vp_stage`` when it is declared, so dropping it here would leave | ||
| interleaved pipeline parallelism calling MCore's layer-offset helper without a virtual | ||
| stage, which asserts. | ||
|
|
||
| Args: | ||
| config: The model provider / transformer config. | ||
| use_transformer_engine: Whether to build Transformer Engine submodules. | ||
| normalization: Optional normalization override, forwarded unchanged. | ||
| qk_l2_norm: Optional QK L2 norm flag, forwarded unchanged. | ||
| vp_stage: Virtual pipeline stage, forwarded unchanged. | ||
| pp_rank: Pipeline rank, forwarded unchanged. | ||
|
|
||
| Returns: | ||
| The decoder block spec, with MLA self-attention replaced by | ||
| :class:`MLASelfAttentionWithoutQueryNorm` when there is no query LoRA. | ||
| """ | ||
| spec = get_gpt_decoder_block_spec( | ||
| config, | ||
| use_transformer_engine=use_transformer_engine, | ||
| normalization=normalization, | ||
| qk_l2_norm=qk_l2_norm, | ||
| vp_stage=vp_stage, | ||
| pp_rank=pp_rank, | ||
| ) | ||
| return replace_mla_self_attention(config, spec) | ||
|
|
||
|
|
||
| def replace_mla_self_attention(config: TransformerConfig, spec: ModuleSpec) -> ModuleSpec: | ||
| """Swap MLA self-attention for the query-norm-free variant, in place, on every layer. | ||
|
|
||
| Shared with the MTP path: a standalone MTP pipeline stage owns no decoder layers, so | ||
| the provider re-derives a layer spec straight from MCore and never passes through | ||
| :func:`get_deepseek_decoder_block_spec`. Without this the MTP layer regains the query | ||
| norm that the decoder layers just dropped. | ||
|
|
||
| Accepts either a block spec (``.layer_specs``) or a single layer spec. | ||
| """ | ||
| if getattr(config, "q_lora_rank", None) is not None: | ||
| return spec | ||
|
|
||
| layer_specs = getattr(spec, "layer_specs", None) | ||
| for layer_spec in layer_specs if layer_specs is not None else [spec]: | ||
| self_attention = getattr(layer_spec.submodules, "self_attention", None) | ||
| if self_attention is not None and self_attention.module is MLASelfAttention: | ||
| self_attention.module = MLASelfAttentionWithoutQueryNorm | ||
| return spec | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.