Skip to content

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

Open
promptsmith1990 wants to merge 1 commit into
deepspeedai:masterfrom
promptsmith1990:fix/auto-tp-qwen3_5-load-modules
Open

Recognize Qwen3.5's RMSNorm variants in AutoTP module loading#8306
promptsmith1990 wants to merge 1 commit into
deepspeedai:masterfrom
promptsmith1990:fix/auto-tp-qwen3_5-load-modules

Conversation

@promptsmith1990

Copy link
Copy Markdown

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.

@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!

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