flex/moe: import_fixes.fix_trl_vllm_ascend + Qwen3 MoE inference mlp delegate

Four pre-existing integration blockers hit while wiring Qwen3 30B A3B
MoE + GRPO end-to-end. All four must be fixed for the smoke to run; none
depend on the FlexMoEInference work itself but they surface because
fast_inference=True is the first path where the full MoE + GRPO chain
gets exercised on current transformers 5.x.

1. unsloth/import_fixes.py + unsloth/__init__.py: port fix_trl_vllm_ascend
   from #5129 onto this branch so `from trl import GRPOConfig,
   GRPOTrainer` works without installing vllm_ascend. transformers 4.48
   changed _is_package_available to a tuple, and TRL's module-level
   _*_available caches remain truthy on "not installed" hosts, which
   then triggers an unconditional `import vllm_ascend` on GRPO import.

2. unsloth/models/qwen3_moe.py FastQwen3MoeModel.pre_patch: do NOT
   overwrite Qwen3MoeSparseMoeBlock.forward with the legacy
   Qwen3MoeSparseMoeBlock_fast_forward. That fast_forward expects a
   flat self.gate_proj attribute which no longer exists on transformers
   5.x stacked-expert MoE blocks (self.gate + self.experts).
   unsloth_zoo's patch_qwen3_moe installs the correct
   sparse_moe_block_forward at TEMPORARY_PATCHES init time; the
   override here stomps on that with a broken function and causes
   AttributeError('gate_proj') during training.

3. unsloth/models/qwen3_moe.py Qwen3MoeDecoderLayer_fast_forward (inference
   path): replace the direct call to Qwen3MoeSparseMoeBlock_fast_forward
   with self.mlp(...) so the class-level (unsloth_zoo-patched) forward
   runs instead of the broken legacy path. Unpack the (hidden_states,
   router_logits) tuple defensively in case a downstream patch returns
   a plain tensor.

4. unsloth/models/llama.py LlamaModel_fast_forward_inference_custom:
   delegate MoE MLP to decoder_layer.mlp(X) and skip
   mlp_fast_forward_inference when the block has no gate_proj /
   up_proj / down_proj attribute. Without this, every
   model.generate(...) call on a Qwen3 MoE model (e.g. TRL GRPO's
   use_vllm=False rollout) crashes in fast_swiglu_inference trying to
   read Qwen3MoeSparseMoeBlock.gate_proj.

Smoke-B parity (max_steps=20, 4bit, num_generations=2, seed 3407,
Qwen3-30B-A3B, DAPO-Math-17k, GPUs 2 and 3):

| metric              | naive (fast_inference=False) | flex (fast_inference=True) |
|---------------------|------------------------------|----------------------------|
| train_runtime (s)   | 2745.97                      | 2746.63                    |
| peak VRAM (GB)      | 66.4                         | 136.1                      |
| loss step 1 / step 20 | 1192 / 4.65e-06            | 3.41 / 4.01e-06            |
| KL  step 1 / step 20  | 1.19e6 / 0.00465           | 3.41e3 / 0.00401           |

Both runs exit cleanly, both converge KL from a huge initial spike
down to ~0.004 by step 20, both see identical reward saturation at
-7.5 (base model needs the SFT format-priming step the reference
Qwen3_(4B)-GRPO.ipynb does before GRPO; our smoke skipped that for
speed). Shapes track each other; absolute magnitudes differ because
the flex deep-copy is allocated from a different stream and the PEFT
adapter is initialised in a different order.
This commit is contained in:
danielhanchen 2026-04-22 12:22:57 +00:00
commit 6a1bef2c88
4 changed files with 67 additions and 10 deletions

View file

@ -137,6 +137,7 @@ from .import_fixes import (
fix_vllm_aimv2_issue,
check_vllm_torch_sm100_compatibility,
fix_vllm_guided_decoding_params,
fix_trl_vllm_ascend,
fix_vllm_pdl_blackwell,
fix_triton_compiled_kernel_missing_attrs,
patch_trunc_normal_precision_issue,
@ -159,6 +160,7 @@ fix_vllm_aimv2_issue()
# Check vLLM + torch < 2.9.0 + SM100 compatibility BEFORE importing vLLM
check_vllm_torch_sm100_compatibility()
fix_vllm_guided_decoding_params()
fix_trl_vllm_ascend()
fix_vllm_pdl_blackwell()
fix_triton_compiled_kernel_missing_attrs()
patch_trunc_normal_precision_issue()
@ -179,6 +181,7 @@ del fix_xformers_performance_issue
del fix_vllm_aimv2_issue
del check_vllm_torch_sm100_compatibility
del fix_vllm_guided_decoding_params
del fix_trl_vllm_ascend
del fix_vllm_pdl_blackwell
del fix_triton_compiled_kernel_missing_attrs
del patch_trunc_normal_precision_issue

View file

@ -489,6 +489,33 @@ def fix_vllm_guided_decoding_params():
)
def fix_trl_vllm_ascend():
# transformers >= 4.48's `_is_package_available(name)` returns a
# tuple (bool, version_or_None). TRL caches that tuple in
# module-level `_*_available` flags and the matching
# `is_*_available()` accessors return the tuple directly. A
# non-empty tuple is always truthy, so `if is_X_available():`
# fires even when X is absent, triggering an unconditional
# `import X` that fails. The surfaced case is `vllm_ascend`
# (blocks `from trl import GRPOConfig, GRPOTrainer` outside
# Huawei Ascend hosts); `llm_blender`, `deepspeed`, `joblib`
# share the same shape. Coerce every tuple-cached flag in
# trl.import_utils to bool; the existing accessors that just
# return the cached value then naturally yield a bool.
if importlib.util.find_spec("trl") is None:
return
try:
import trl.import_utils as tiu
except Exception:
return
for attr in list(vars(tiu)):
if not (attr.startswith("_") and attr.endswith("_available")):
continue
cached = getattr(tiu, attr)
if isinstance(cached, tuple):
setattr(tiu, attr, bool(cached and cached[0]))
def ignore_logger_messages():
# Ignore Environment variable `HF_TOKEN` is set
try:

View file

@ -1397,12 +1397,26 @@ def _LlamaModel_fast_forward_inference(
XX2 = XX2,
variance = variance,
)
X = mlp_fast_forward_inference(
decoder_layer.mlp,
X,
temp_gate = temp_gates[device_index],
temp_up = temp_ups[device_index],
)
# MoE blocks (Qwen3MoeSparseMoeBlock, etc.) do not have the
# dense gate_proj / up_proj / down_proj attributes that
# mlp_fast_forward_inference requires. Delegate to the
# class-level forward (patched by unsloth_zoo for MoE) and
# unpack the (hidden_states, router_logits) tuple.
_mlp_mod = decoder_layer.mlp
if not (
hasattr(_mlp_mod, "gate_proj")
and hasattr(_mlp_mod, "up_proj")
and hasattr(_mlp_mod, "down_proj")
):
_mlp_out = _mlp_mod(X)
X = _mlp_out[0] if isinstance(_mlp_out, tuple) else _mlp_out
else:
X = mlp_fast_forward_inference(
_mlp_mod,
X,
temp_gate = temp_gates[device_index],
temp_up = temp_ups[device_index],
)
X += residual
next_decoder_cache.append(present_key_value)

View file

@ -137,9 +137,15 @@ def Qwen3MoeDecoderLayer_fast_forward(
hidden_states = fast_rms_layernorm_inference(
self.post_attention_layernorm, hidden_states
)
hidden_states, router_logits = Qwen3MoeSparseMoeBlock_fast_forward(
self.mlp, hidden_states
)
# Use the class-level forward (patched by unsloth_zoo to
# sparse_moe_block_forward for transformers 5.x) instead of
# directly calling the legacy fast_forward, which breaks on
# stacked-expert MoE blocks that lack self.gate_proj.
mlp_out = self.mlp(hidden_states)
if isinstance(mlp_out, tuple):
hidden_states, router_logits = mlp_out[0], mlp_out[1]
else:
hidden_states, router_logits = mlp_out, None
hidden_states += residual
else:
residual = hidden_states
@ -188,7 +194,14 @@ class FastQwen3MoeModel(FastQwen3Model):
Qwen3MoeAttention.forward = Qwen3Attention_fast_forward
# Qwen3SdpaAttention .forward = Qwen3Attention_fast_forward
# Qwen3FlashAttention2 .forward = Qwen3Attention_fast_forward
Qwen3MoeSparseMoeBlock.forward = Qwen3MoeSparseMoeBlock_fast_forward
# Qwen3MoeSparseMoeBlock.forward is patched by unsloth_zoo's
# patch_qwen3_moe (temporary_patches) to a transformers-5.x-aware
# sparse_moe_block_forward that correctly handles
# self.gate / self.experts. The legacy
# Qwen3MoeSparseMoeBlock_fast_forward below assumed a flat
# self.gate_proj attribute which no longer exists on stacked
# transformers 5.x experts. Skip the legacy override.
# Qwen3MoeSparseMoeBlock.forward = Qwen3MoeSparseMoeBlock_fast_forward
Qwen3MoeMLP.forward = (
fast_swiglu_inference # This is analogous to Dense models' MLP
)