Skip to content

Commit b82279f

Browse files
justinchubyCopilotCopilot
authored
Register qwen3_5_moe_text for text-only Qwen3.6 export (#445)
## What Adds the missing text-only registry sibling for **Qwen3.6-35B-A3B** (`Qwen/Qwen3.6-35B-A3B`, HF `model_type=qwen3_5_moe` with a `vision_config`). Every other Qwen/Gemma VL family registers a `*_text` sibling (`gemma3_text`, `qwen3_vl_text`, `qwen3_5_vl_text`), but the MoE VL did not. As a result: - `registry.get("qwen3_5_moe_text")` raised `KeyError` (the VL `text_config`'s own `model_type` is `qwen3_5_moe_text`); and - `build(..., text_only=True)` on the VL checkpoint failed — no `_TEXT_ONLY_MODEL_TYPE` entry for `qwen3_5_moe_vl`. ## How `Qwen35MoECausalLMModel.preprocess_weights` already strips the `language_model.` prefix, drops `visual.`/MTP keys, and unpacks fused MoE experts, so it consumes the VL text weights directly (no new class needed). - Register `qwen3_5_moe_text` → `Qwen35MoECausalLMModel`. - Wire `_TEXT_ONLY_MODEL_TYPE`: `qwen3_5_moe_vl` → `qwen3_5_moe_text` (+ idempotent self-map). - Add default-id (`Qwen/Qwen3.6-35B-A3B`) and family (`qwen`) map entries. - Add an L1 build-graph tiny config reusing the `qwen3_5_moe` hybrid-MoE config (added to `_CHECKER_SKIP_MODELS` like its sibling, since it uses `LinearAttention`/`CausalConvWithState` custom ops). ## Verification - L1 build + registry-completeness pass; broader qwen suite green (107 passed). - Verified against the **real** Qwen3.6 config (config-only) that both `text_only=True` routing and `text_config.model_type` now resolve to `Qwen35MoECausalLMModel` (previously a `KeyError`). > Note: a full weight-level (L4/L5) export requires the 35B checkpoint + GPU and is out of scope here; this PR is the routing/registration fix with L1 coverage. Please review — do not merge yet. --------- Signed-off-by: justinchuby <justinchuby@users.noreply.github.com> Signed-off-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 13491cd commit b82279f

4 files changed

Lines changed: 41 additions & 1 deletion

File tree

src/mobius/_builder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,8 @@ def build(
417417
can use ``GroupQueryAttention`` on GQA-capable execution providers.
418418
Raises :class:`ValueError` if the resolved ``model_type`` has no
419419
text-only sibling. Currently supported for ``gemma4_unified``
420-
(``google/gemma-4-12B``).
420+
(``google/gemma-4-12B``) and ``qwen3_5_moe_vl``
421+
(``Qwen/Qwen3.6-35B-A3B``).
421422
422423
Returns:
423424
A :class:`ModelPackage` containing the built model(s).

src/mobius/_registry.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,13 @@ def _detect_fallback_registration(hf_config) -> ModelRegistration | None:
604604
"qwen2_vl_text": ModelRegistration(Qwen25VLTextModel),
605605
"qwen3_5": ModelRegistration(Qwen35VL3ModelCausalLMModel, task="hybrid-qwen-vl"),
606606
"qwen3_5_moe_vl": ModelRegistration(Qwen35MoEVL3ModelCausalLMModel, task="hybrid-qwen-vl"),
607+
# Text-only sibling of ``qwen3_5_moe_vl`` (Qwen3.6-35B-A3B). The MoE
608+
# backbone ``Qwen35MoECausalLMModel`` already strips ``language_model.``
609+
# and drops ``visual.``/MTP keys, so it consumes the VL checkpoint's text
610+
# weights directly; ``build(..., text_only=True)`` routes here via
611+
# ``_TEXT_ONLY_MODEL_TYPE``. It also matches the VL ``text_config``'s own
612+
# ``model_type=qwen3_5_moe_text`` so that config resolves cleanly.
613+
"qwen3_5_moe_text": ModelRegistration(Qwen35MoECausalLMModel),
607614
"qwen3_5_vl": ModelRegistration(Qwen35VL3ModelCausalLMModel, task="hybrid-qwen-vl"),
608615
"qwen3_5_vl_text": ModelRegistration(Qwen35VLTextModel),
609616
"qwen3_vl": ModelRegistration(Qwen3VL3ModelCausalLMModel, task="qwen-vl"),
@@ -791,6 +798,12 @@ def _create_default_registry() -> ModelRegistry:
791798
_TEXT_ONLY_MODEL_TYPE: dict[str, str] = {
792799
"gemma4_unified": "gemma4_unified_text",
793800
"gemma4_unified_text": "gemma4_unified_text",
801+
# Qwen3.5-MoE-VL (Qwen3.6-35B-A3B): export just the hybrid MoE text
802+
# backbone as a standalone decoder-only LLM. The builder overrides
803+
# ``qwen3_5_moe`` -> ``qwen3_5_moe_vl`` when a ``vision_config`` is present,
804+
# so the text-only override keys off the VL type here.
805+
"qwen3_5_moe_vl": "qwen3_5_moe_text",
806+
"qwen3_5_moe_text": "qwen3_5_moe_text",
794807
}
795808

796809

@@ -894,6 +907,7 @@ def _create_default_registry() -> ModelRegistry:
894907
"qwen2_moe": "Qwen/Qwen1.5-MoE-A2.7B-Chat",
895908
"qwen3_moe": "Qwen/Qwen3-30B-A3B",
896909
"qwen3_5_moe": "Qwen/Qwen3.5-MoE-A3B-128K",
910+
"qwen3_5_moe_text": "Qwen/Qwen3.6-35B-A3B",
897911
"qwen3_next": "Qwen/Qwen3-235B-A22B",
898912
"granitemoe": "ibm-granite/granite-3.0-1b-a400m-instruct",
899913
"olmoe": "allenai/OLMoE-1B-7B-0924",
@@ -1141,6 +1155,7 @@ def _create_default_registry() -> ModelRegistry:
11411155
"qwen3_moe": "qwen",
11421156
"qwen3_5_text": "qwen",
11431157
"qwen3_5_moe": "qwen",
1158+
"qwen3_5_moe_text": "qwen",
11441159
"qwen3_next": "qwen",
11451160
"qwen2_vl": "qwen",
11461161
"qwen2_vl_text": "qwen",

tests/_test_configs.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,29 @@ def _base_config(config_cls=None, **overrides) -> ArchitectureConfig:
519519
},
520520
True,
521521
),
522+
# Text-only sibling of the Qwen3.5-MoE-VL (Qwen3.6-35B-A3B) checkpoint,
523+
# exported via ``build(..., text_only=True)``. Same hybrid MoE backbone as
524+
# ``qwen3_5_moe`` above; registered separately so the VL ``text_config``'s
525+
# ``model_type=qwen3_5_moe_text`` and the text-only override both resolve.
526+
(
527+
"qwen3_5_moe_text",
528+
{
529+
"hidden_act": "silu",
530+
"layer_types": ["linear_attention", "full_attention"],
531+
"partial_rotary_factor": 0.25,
532+
"mrope_interleaved": True,
533+
"num_local_experts": 4,
534+
"num_experts_per_tok": 2,
535+
"moe_intermediate_size": 32,
536+
"shared_expert_intermediate_size": 32,
537+
"linear_num_value_heads": 4,
538+
"linear_num_key_heads": 2,
539+
"linear_key_head_dim": 16,
540+
"linear_value_head_dim": 16,
541+
"linear_conv_kernel_dim": 4,
542+
},
543+
True,
544+
),
522545
# === Falcon and Bloom ===
523546
# dual_ln=True: Falcon with new_decoder_architecture uses separate ln_attn + ln_mlp.
524547
# hidden_act="gelu": real Falcon uses GELU (HF FalconConfig.activation default);

tests/build_graph_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"minimax",
8181
"qwen3_5_text",
8282
"qwen3_5_moe",
83+
"qwen3_5_moe_text",
8384
"qwen3_next",
8485
# Models using LinearAttention / CausalConvWithState custom ops
8586
# prevent full shape/type propagation through com.microsoft domain.

0 commit comments

Comments
 (0)