Skip to content

Recognize Qwen3.5's RMSNorm variants in AutoTP module loading - #8306

Merged
delock merged 6 commits into
deepspeedai:masterfrom
promptsmith1990:fix/auto-tp-qwen3_5-load-modules
Sep 2, 2026
Merged

Recognize Qwen3.5's RMSNorm variants in AutoTP module loading#8306
delock merged 6 commits into
deepspeedai:masterfrom
promptsmith1990:fix/auto-tp-qwen3_5-load-modules

Conversation

@promptsmith1990

Copy link
Copy Markdown
Contributor

Fixes #7947.

Problem

Loading.is_load_module() in auto_tp.py gates whether a leaf module's parameters get loaded from a raw state dict during AutoTP-based checkpoint loading (both call sites — auto_tp.py and replace_module.py — use it the same way). It matches by an exact class-name allowlist that gets a new entry each time a model family ships its own RMSNorm class, but Qwen3.5's classes were never added.

Qwen3_5RMSNorm, Qwen3_5RMSNormGated (dense), Qwen3_5MoeRMSNorm and Qwen3_5MoeRMSNormGated (MoE) each own a weight nn.Parameter, same shape as every other listed *RMSNorm class — without an allowlist entry, is_load_module() returns False for them and that weight is never loaded from the checkpoint, silently left at its random init value instead.

Verification

Verified against the real modeling code (transformers 5.3.0, the version reported in the issue): built a small Qwen3_5TextModel and confirmed Qwen3_5RMSNorm/Qwen3_5RMSNormGated instances only carry a weight parameter, matching the pattern of the already-listed LlamaRMSNorm et al. Confirmed the MoE variants carry the same single weight parameter.

Deliberately not adding Qwen3_5TextRotaryEmbedding: it carries no parameters, only two buffers (inv_freq, original_inv_freq), and both call sites already load any child's buffers unconditionally (if len(child._buffers) != 0) regardless of is_load_module() — so listing it would be a no-op, not a functional fix. (Happy to add it anyway for consistency with the codebase's existing Phi3RotaryEmbedding/YuanRotaryEmbedding entries if maintainers prefer explicitness over strict minimality here — wasn't sure which this repo's convention favors.)

Test

tests/unit/module_inject/test_auto_tp_is_load_module.py (new). Uses name-matched dummy nn.Module stand-ins rather than real transformers classes, since is_load_module() matches by class name only and the dev requirement (transformers>=4.51.3) predates Qwen3.5 — importing the real classes would make the test depend on a newer transformers than the repo's own pinned minimum. Covers all four new allowlist entries plus one negative case for an unrelated class name.

$ pytest tests/unit/module_inject/test_auto_tp_is_load_module.py -v
...
5 passed in 1.70s

pre-commit run --files deepspeed/module_inject/auto_tp.py tests/unit/module_inject/test_auto_tp_is_load_module.py (yapf, flake8, check-license, codespell) is clean.


Prepared with AI assistance under my review; the root cause, the parameter-vs-buffer distinction behind the RotaryEmbedding decision, and the test were verified locally before opening this PR.

Loading.is_load_module() in auto_tp.py gates whether a leaf module's
parameters get loaded from a raw state dict during AutoTP-based
checkpoint loading (both call sites in auto_tp.py and
replace_module.py use it the same way). It matches by an exact
class-name allowlist that gets a new entry each time a model family
ships its own RMSNorm class, but Qwen3.5's classes were never added.

Qwen3_5RMSNorm, Qwen3_5RMSNormGated (dense), Qwen3_5MoeRMSNorm and
Qwen3_5MoeRMSNormGated (MoE) each own a `weight` nn.Parameter, same
shape as every other listed *RMSNorm class - without an allowlist
entry, is_load_module() returns False for them and that weight is
never loaded from the checkpoint, silently left at its random init
value instead. (deepspeedai#7947)

Verified against the real modeling code (transformers 5.3.0, the
version reported in the issue): built a small Qwen3_5TextModel and
confirmed Qwen3_5RMSNorm/Qwen3_5RMSNormGated instances only carry a
`weight` parameter, matching the pattern of the already-listed
LlamaRMSNorm et al. Confirmed the MoE variants (Qwen3_5MoeRMSNorm,
Qwen3_5MoeRMSNormGated) carry the same single `weight` parameter.

Deliberately not adding Qwen3_5TextRotaryEmbedding: it carries no
parameters, only two buffers (inv_freq, original_inv_freq), and both
call sites already load any child's buffers unconditionally
(`if len(child._buffers) != 0`) regardless of is_load_module() - so
listing it would be a no-op, not a functional fix.

Test: tests/unit/module_inject/test_auto_tp_is_load_module.py (new).
Uses name-matched dummy nn.Module stand-ins rather than real
transformers classes, since is_load_module() matches by class name
only and the dev requirement (transformers>=4.51.3) predates Qwen3.5 -
importing the real classes would make the test depend on a newer
transformers than the repo's own pinned minimum. Covers all four new
allowlist entries plus one negative case for an unrelated class name.
5 passed locally.

pre-commit (yapf, flake8, check-license, codespell) clean on both
changed files.

Signed-off-by: promptsmith1990 <319963136+promptsmith1990@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a348922b39

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"Phi3RMSNorm", "YuanRMSNorm", "YuanRotaryEmbedding", "Phi3LongRoPEScaledRotaryEmbedding", "Qwen2RMSNorm",
"Qwen3RMSNorm", "Qwen3MoeRMSNorm", "DeepseekV2RMSNorm", "DeepseekV3RMSNorm",
"DeepseekV2YarnRotaryEmbedding", "DeepseekV3YarnRotaryEmbedding", "MoEGate"
"Qwen3RMSNorm", "Qwen3MoeRMSNorm", "Qwen3_5RMSNorm", "Qwen3_5RMSNormGated", "Qwen3_5MoeRMSNorm",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required Signed-off-by trailer

This non-merge commit has no Signed-off-by trailer, so it violates the repository's mandatory commit policy. Recreate the commit with --signoff using the configured Git name and email before merging.

AGENTS.md reference: AGENTS.md:L8-L8

Useful? React with 👍 / 👎.

@delock

delock commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Hi @promptsmith1990 , thanks for your fix. Does this PR fix issue #7947, so that finetuning Qwen 3.5 family with AutoTP is possible, or it just fix the stated Qwen3.5 RMSNorm variants issue? Thanks!


class TestIsLoadModule:

@pytest.mark.parametrize("class_name", [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think we should either remove this test or verify all elements in is_load_module. Given that it is unlikely that an item be taken out of is_load_module with out causing attention, I suggest not having this test at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the test file entirely per your suggestion — it only covered the four new Qwen3.5 entries rather than the full allow-list, and as you noted, removals from is_load_module would be obvious in code review. The functional change in auto_tp.py stands unchanged.

@tohtana tohtana left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please use the new copyright header showing DeepSpeed team for new files.

# Copyright (c) DeepSpeed Team.
# SPDX-License-Identifier: Apache-2.0

# DeepSpeed Team

@delock

delock commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

@promptsmith1990 any updates? Thanks!

@promptsmith1990

Copy link
Copy Markdown
Contributor Author

@promptsmith1990 any updates? Thanks!

Hi! Thanks for your review, really appreciate it. Working on your comments.

The test only exercised the four newly-added Qwen3.5 class names, not
the full allow-list in is_load_module(). Rather than expand it into an
exhaustive list (which would need updating every time a new family is
added), drop the test entirely per delock's suggestion — removals from
is_load_module() would be immediately visible in code review anyway.

Signed-off-by: promptsmith1990 <319963136+promptsmith1990@users.noreply.github.com>
@promptsmith1990

promptsmith1990 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for your review! Reviewer feedback addressed:

  • @delock: Removed the test file entirely — it only covered the 4 new Qwen3.5 entries rather than the full allow-list, making it misleadingly partial. As you noted, any future removal from is_load_module would be caught in code review. The functional fix in auto_tp.py is unchanged.

  • @tohtana: The new file with the old Microsoft copyright header is now gone, so no new files with incorrect headers remain. The modified auto_tp.py is an existing file with its existing header.

  • Signed-off-by trailer included in the new commit.

@promptsmith1990

Copy link
Copy Markdown
Contributor Author

Hi @promptsmith1990 , thanks for your fix. Does this PR fix issue #7947, so that finetuning Qwen 3.5 family with AutoTP is possible, or it just fix the stated Qwen3.5 RMSNorm variants issue? Thanks!

Hi @thuanaislab — good question. This PR specifically fixes Loading.is_load_module() in auto_tp.py, which controls whether a module's parameters get loaded from the checkpoint during AutoTP-based model loading. Without this fix, Qwen3.5's RMSNorm layers (Qwen3_5RMSNorm, Qwen3_5RMSNormGated, Qwen3_5MoeRMSNorm, Qwen3_5MoeRMSNormGated) silently start with randomly initialized weights instead of checkpoint values — which is the root cause of the failure reported in #7947 when using transformers ≥ 5.3.0.

So yes, it addresses the is_load_module gap that was blocking Qwen3.5 + AutoTP.

@tohtana tohtana left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for the update, @promptsmith1990! Looks good to me.

@delock
delock enabled auto-merge September 2, 2026 01:07
@delock
delock added this pull request to the merge queue Sep 2, 2026
Merged via the queue into deepspeedai:master with commit 8e64a09 Sep 2, 2026
12 of 13 checks passed
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.

Has DeepSpeed supported Qwen 3.5 yet?

3 participants