# Copyright (c) 2026 LightSeek Foundation # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """Inference-only Qwen3 MoE model compatible with HuggingFace weights.""" from __future__ import annotations from collections.abc import Iterable import torch from tokenspeed.runtime.configs.qwen3_moe_config import Qwen3MoeConfig from tokenspeed.runtime.distributed.comm_manager import CommManager from tokenspeed.runtime.distributed.mapping import Mapping from tokenspeed.runtime.execution.context import ForwardContext from tokenspeed.runtime.layers.moe import ( ExpertCheckpointSchema, build_moe_checkpoint_loader, ) from tokenspeed.runtime.layers.quantization.base_config import QuantizationConfig from tokenspeed.runtime.layers.utils import get_layer_id from tokenspeed.runtime.model_loader.weight_utils import default_weight_loader from tokenspeed.runtime.models.qwen3 import ( Qwen3DecoderLayer, Qwen3ForCausalLM, Qwen3MLP, Qwen3Model, ) from tokenspeed.runtime.models.qwen3_5_moe import ( Qwen3_5MoeMLP, Qwen3_5MoeSparseMoeBlock, _is_moe_layer, ) from tokenspeed.runtime.utils import add_prefix class Qwen3MoeDecoderLayer(Qwen3DecoderLayer): def __init__( self, config: Qwen3MoeConfig, mapping: Mapping, layer_id: int = 0, quant_config: QuantizationConfig | None = None, prefix: str = "", ) -> None: super().__init__( config=config, mapping=mapping, layer_id=layer_id, quant_config=quant_config, prefix=prefix, ) if _is_moe_layer(layer_id, config): self.mlp = Qwen3_5MoeSparseMoeBlock( config=config, mapping=self.mapping, quant_config=quant_config, layer_index=layer_id, prefix=add_prefix("mlp", prefix), ) elif isinstance(self.mlp, Qwen3MLP): self.mlp = Qwen3_5MoeMLP( hidden_size=config.hidden_size, intermediate_size=config.intermediate_size, hidden_act=config.hidden_act, mapping=self.mapping, quant_config=quant_config, reduce_results=False, prefix=add_prefix("mlp", prefix), ) is_moe = isinstance(self.mlp, Qwen3_5MoeSparseMoeBlock) self.comm_manager = CommManager( mapping=self.mapping, layer_id=layer_id, is_moe=is_moe, prev_is_moe=_is_moe_layer(layer_id - 1, config), input_layernorm=self.input_layernorm, post_attn_layernorm=self.post_attention_layernorm, ) def forward( self, positions: torch.Tensor, hidden_states: torch.Tensor, ctx: ForwardContext, out_cache_loc: torch.Tensor, residual: torch.Tensor | None, cos_sin: tuple[torch.Tensor, torch.Tensor] | None, ) -> tuple[torch.Tensor, torch.Tensor]: if not ctx.forward_mode.is_idle(): hidden_states, residual = self.comm_manager.input_reduce_norm( hidden_states, residual ) hidden_states = self.comm_manager.pre_attn_comm(hidden_states, ctx) hidden_states = self.self_attn( positions=positions, hidden_states=hidden_states, ctx=ctx, out_cache_loc=out_cache_loc, cos_sin=cos_sin, ) hidden_states, residual = self.comm_manager.post_attn_reduce_norm( hidden_states, residual, ctx ) hidden_states, residual = self.forward_mlp(hidden_states, residual, ctx) return hidden_states, residual def forward_mlp( self, hidden_states: torch.Tensor, residual: torch.Tensor, ctx: ForwardContext, ) -> tuple[torch.Tensor, torch.Tensor]: if isinstance(self.mlp, Qwen3_5MoeSparseMoeBlock): num_global_tokens, max_num_tokens_per_gpu = ( self.mlp.comm_manager.get_num_tokens(ctx) ) hidden_states = self.mlp( hidden_states, num_global_tokens, max_num_tokens_per_gpu, ctx, ) return hidden_states, residual hidden_states = self.comm_manager.pre_mlp_comm(hidden_states, ctx) hidden_states = self.mlp(hidden_states) hidden_states, residual = self.comm_manager.post_mlp_fused( hidden_states, residual, ctx ) return hidden_states, residual class Qwen3MoeModel(Qwen3Model): def __init__( self, config: Qwen3MoeConfig, mapping: Mapping, quant_config: QuantizationConfig | None = None, prefix: str = "", ) -> None: super().__init__( config=config, mapping=mapping, quant_config=quant_config, prefix=prefix, decoder_layer_type=Qwen3MoeDecoderLayer, ) def forward( self, input_ids: torch.Tensor, positions: torch.Tensor, ctx: ForwardContext, out_cache_loc: torch.Tensor, input_embeds: torch.Tensor | None = None, ) -> tuple[torch.Tensor, None]: if input_embeds is None: hidden_states = self.embed_tokens(input_ids) else: hidden_states = input_embeds residual = None for layer in self.layers: hidden_states, residual = layer( positions, hidden_states, ctx, out_cache_loc, residual, cos_sin=None, ) if not ctx.forward_mode.is_idle(): hidden_states, _ = layer.comm_manager.final_norm( hidden_states, residual, ctx, self.norm ) return hidden_states, None class Qwen3MoeForCausalLM(Qwen3ForCausalLM): model_cls = Qwen3MoeModel default_bitsandbytes_target_modules = [ ".gate_proj.", ".down_proj.", ".up_proj.", ".q_proj.", ".k_proj.", ".v_proj.", ".o_proj.", ] bitsandbytes_stacked_params_mapping = { "q_proj": ("qkv_proj", 0), "k_proj": ("qkv_proj", 1), "v_proj": ("qkv_proj", 2), "gate_proj": ("gate_up_proj", 0), "up_proj": ("gate_up_proj", 1), } def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]): stacked_params_mapping = [ ("qkv_proj", "q_proj", "q"), ("qkv_proj", "k_proj", "k"), ("qkv_proj", "v_proj", "v"), ("gate_up_proj", "gate_proj", 0), ("gate_up_proj", "up_proj", 1), ] ignore_suffixes = ( ".bias", "_bias", ".k_scale", "_k_scale", ".v_scale", "_v_scale", ".weight_scale", "_weight_scale", ".input_scale", "_input_scale", ) params_dict = dict(self.named_parameters(remove_duplicate=False)) moe_loader = build_moe_checkpoint_loader( params_dict=params_dict, expert_schema=ExpertCheckpointSchema( gate_proj_name="gate_proj", down_proj_name="down_proj", up_proj_name="up_proj", ), fused_schema=ExpertCheckpointSchema( gate_up_fused_name="gate_up_proj", down_proj_name="down_proj", ), num_experts=self.config.num_experts, ep_rank=self.mapping.moe.ep_rank, ep_size=self.mapping.moe.ep_size, ) for name, loaded_weight in weights: if "Embedding" in self.config.name_or_path: name = add_prefix(name, "model") layer_id = get_layer_id(name) if ( layer_id is not None and hasattr(self.model, "start_layer") and ( layer_id < self.model.start_layer or layer_id >= self.model.end_layer ) ): continue if "rotary_emb.inv_freq" in name or "projector" in name: continue if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: continue if self.config.tie_word_embeddings and "lm_head.weight" in name: continue if name.startswith("model.vision_tower") and name not in params_dict: continue for param_name, weight_name, shard_id in stacked_params_mapping: if weight_name not in name: continue if "mlp.experts" in name: continue name = name.replace(weight_name, param_name) if name.endswith(ignore_suffixes) and name not in params_dict: continue if name not in params_dict: continue param = params_dict[name] weight_loader = param.weight_loader weight_loader(param, loaded_weight, shard_id) break else: if name.endswith((".bias", "_bias")) and name not in params_dict: continue if moe_loader.matches(name): moe_loader.load(name, loaded_weight) continue if name.endswith(ignore_suffixes) and name not in params_dict: continue if name not in params_dict: continue param = params_dict[name] weight_loader = getattr(param, "weight_loader", default_weight_loader) weight_loader(param, loaded_weight) EntryClass = Qwen3MoeForCausalLM