Skip to content

Extend ARCH_TO_2D_MAPPINGS to Qwen3.5-MoE - #3050

Closed
kgt392 wants to merge 2 commits into
vllm-project:mainfrom
kgt392:fix/3037-qwen3-5-moe-2d-mappings
Closed

Extend ARCH_TO_2D_MAPPINGS to Qwen3.5-MoE#3050
kgt392 wants to merge 2 commits into
vllm-project:mainfrom
kgt392:fix/3037-qwen3-5-moe-2d-mappings

Conversation

@kgt392

@kgt392 kgt392 commented Aug 18, 2026

Copy link
Copy Markdown

SUMMARY:

Fixes #3037. Qwen3.5-MoE checkpoints already store 2D per-expert tensors, but has_linearize_load_mappings returned False for them, so load_quantizable_moe fell back to loading normally and calling linearize_moe — a 2D → 3D → 2D round trip that leaves an extra copy of every expert resident for the process lifetime. On a memory-constrained host that is fatal during load, before calibration starts.

Registers both released spellings, reusing the Qwen2-MoE 2D body (its fusing rules are identical):

  • qwen3_5_moe — multimodal wrapper (Qwen3_5MoeForConditionalGeneration)
  • qwen3_5_text — what _MODEL_TO_CONVERSION_PATTERN remaps the text-only checkpoint's qwen3_5_moe_text to, plus its ARCH_TO_IMPORT_PATHS entry

The issue notes that adding a 2D entry alone is not sufficient, and that is confirmed: transformers registers the rules on the text tower, so get_checkpoint_conversion_mapping("qwen3_5_moe") returns None while llm-compressor keys on the top-level type. Of the three options raised in the issue this takes the "resolve the text spelling" one, as the least invasive — _resolve_checkpoint_conversion_mapping falls back to f"{model_type}_text", and has_linearize_load_mappings now additionally requires a resolvable mapping, so a future 2D entry cannot satisfy the predicate and then hand None to get_linearize_load_mappings. Happy to switch to registering both spellings or normalising the suffix if you prefer a different convention.

One thing worth flagging: taking the mapping from the text tower also preserves its model.language_model.* prefix rule, which the Qwen2-MoE rules do not carry — copying that body verbatim would have silently dropped it. There is a test pinning it.

TEST PLAN:

New CPU-only unit tests in tests/llmcompressor/modeling/test_moe_conversion_mappings.py (15 cases, transformers 5.15.0): the predicate for both Qwen3.5-MoE spellings, no WeightConverter surviving in the load/save mappings (a survivor means fusion on load, i.e. the round trip), the per-expert renames, the language_model prefix rule, the shared 2D body, and guards that qwen2_moe / qwen3_moe / qwen3_next / deepseek_v4 / hy_v3 still resolve while qwen3_vl_moe keeps using post-load conversion (its rules are identity, so it has no round trip to avoid).

  • 15 passed with the change; 9 of them fail on main, and the 6 that pass either way are the regression guards.
  • Swept has_linearize_load_mappings over all 32 ARCH_TO_IMPORT_PATHS keys before and after: exactly qwen3_5_moe and qwen3_5_moe_text flip to True, no architecture regresses.
  • ruff check and ruff format --check clean on both files.
  • Not verified: an end-to-end load of a real Qwen3.5-MoE checkpoint, which needs hardware I do not have. The memory claim in the issue is the reporter's measurement, not mine — what I verified is the mapping resolution.

Qwen3.5-MoE checkpoints already store 2D per-expert tensors, but
`has_linearize_load_mappings` returned False for them, so `load_quantizable_moe`
fell back to loading normally and calling `linearize_moe`. That performs a
2D -> 3D -> 2D round trip and leaves an extra copy of every expert resident,
which is fatal during load for a large MoE on a memory-constrained host.

Register both released spellings - `qwen3_5_moe` for the multimodal wrapper and
`qwen3_5_text` for the text-only checkpoint - reusing the Qwen2-MoE 2D body,
whose fusing rules are identical.

Resolving the transformers mapping needs care: it is registered on the text
tower (`qwen3_5_moe_text`), so `get_checkpoint_conversion_mapping("qwen3_5_moe")`
returns None while llm-compressor keys on the top-level type. Add
`_resolve_checkpoint_conversion_mapping`, which falls back to the text spelling,
and require a resolvable mapping in `has_linearize_load_mappings` so a future 2D
entry cannot pass the predicate and then hand None to the mapping consumer.

Taking the mapping from the text tower also preserves its
`model.language_model.*` prefix rule, which copying the Qwen2-MoE rules verbatim
would have dropped.

Fixes vllm-project#3037
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to llm-compressor. Please add the ready label when the PR is ready for review.

Note: This is required to complete the testing suite, please only add the label once the PR is code complete and local testing has been performed.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ec692c5e-86fd-42b8-a6af-8e26e1b9f203

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@mergify

mergify Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require one maintainer review 👀 reviews

🔴 Require one maintainer review

Waiting for any of

  • approved-reviews-by=HDCharles
  • approved-reviews-by=brian-dellabetta
  • approved-reviews-by=dsikka
  • approved-reviews-by=kylesayrs
  • approved-reviews-by=yiliu30
This rule is failing.

All PRs must have at least one approving review from a maintainer before merging.

  • any of:
    • approved-reviews-by=HDCharles
    • approved-reviews-by=brian-dellabetta
    • approved-reviews-by=dsikka
    • approved-reviews-by=kylesayrs
    • approved-reviews-by=yiliu30
  • #changes-requested-reviews-by = 0

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for Qwen3.5-MoE models (both multimodal and text-only checkpoints) by mapping them to the Qwen2-MoE 2D per-expert tensor structure and resolving their checkpoint conversion mappings with fallback logic for text-only suffixes. Unit tests are added to verify these mappings. The review feedback identifies a potential type mismatch and runtime error in get_linearize_load_mappings where _resolve_checkpoint_conversion_mapping could return None, and suggests adding a defensive check to raise a ValueError if the mapping is not found.

experts_cls = import_or_none(expert_paths)

mapping: list[WeightTransform] = get_checkpoint_conversion_mapping(model_type)
mapping: list[WeightTransform] = _resolve_checkpoint_conversion_mapping(model_type)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The function _resolve_checkpoint_conversion_mapping can return None. Assigning its result directly to mapping (annotated as list[WeightTransform]) without a None check will cause a static type checker error (type mismatch) and can lead to a TypeError: 'NoneType' object is not iterable at runtime when mapping is iterated over in the list comprehension below if get_linearize_load_mappings is called directly with an unsupported model type.

Adding a defensive None check and raising a clear ValueError resolves both the type mismatch and prevents runtime crashes.

Suggested change
mapping: list[WeightTransform] = _resolve_checkpoint_conversion_mapping(model_type)
mapping = _resolve_checkpoint_conversion_mapping(model_type)
if mapping is None:
raise ValueError(f"No checkpoint conversion mapping found for model type: {model_type}")

`_resolve_checkpoint_conversion_mapping` is Optional, so assigning it straight to
a `list[WeightTransform]` was a type mismatch, and a caller reaching
`get_linearize_load_mappings` for a type with no mapping would fail on a
downstream lookup rather than on the actual cause.

Raise a ValueError naming the model type and pointing at
`has_linearize_load_mappings`, which is what gates this call.
@kgt392

kgt392 commented Aug 18, 2026

Copy link
Copy Markdown
Author

Fixed in 0df550c — the Optional return being assigned straight to a list[WeightTransform] was a real type mismatch on my part.

Small correction to the failure mode, for the record: with the current tables the crash is a KeyError on ARCH_TO_2D_MAPPINGS rather than a TypeError from iterating None, because the 2D lookup happens before the mapping is iterated. Every model type that resolves to no mapping today (gpt_oss, llama4, gemma4, openai_privacy_filter) is absent from ARCH_TO_2D_MAPPINGS, so it trips that lookup first. The TypeError becomes reachable only once a type has a 2D entry but no conversion mapping, which is the combination has_linearize_load_mappings now rejects.

Either way the error pointed away from the cause, so the guard raises a ValueError naming the model type and pointing at has_linearize_load_mappings. Test added using gpt_oss, asserting both that the predicate rejects it and that the direct call now explains why. 16 tests passing, and the sweep over all 32 ARCH_TO_IMPORT_PATHS keys still shows only qwen3_5_moe / qwen3_5_moe_text changing, with no regressions.

@kylesayrs

Copy link
Copy Markdown
Collaborator

See comment: #3037 (comment)

@kylesayrs

Copy link
Copy Markdown
Collaborator

#3037 (comment)

@kylesayrs kylesayrs closed this Aug 18, 2026
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.

Qwen3.5-MoE misses ARCH_TO_2D_MAPPINGS, forcing a 2D→3D→2D expert round-trip that doubles expert memory at load

2 participants