import math import torch import torch.nn.functional as F from typing import Optional, Tuple from .model_mapper import ModelMapper from .custom_op import FusedAttention, FusedRoPE, MoE, FusedLinearAttention class Embedding(torch.nn.Module): def __init__(self, embed, config): super().__init__() self.hidden_size = config.hidden_size self.embed = embed self.embed_scale = 1.0 config_embed_scale = getattr(config, 'scale_emb', None) if config_embed_scale is not None: self.embed_scale = config_embed_scale elif config.model_type == 'gemma' or config.model_type == 'gemma2': self.embed_scale = self.hidden_size**0.5 if hasattr(embed, 'embed_scale'): self.embed_scale = embed.embed_scale # Replace ScaledWordEmbedding with plain Embedding to avoid double # scaling (scale is applied separately via model.scale_emb) if hasattr(embed, 'scalar_embed_scale'): plain_embed = torch.nn.Embedding(embed.num_embeddings, embed.embedding_dim, embed.padding_idx) plain_embed.weight = embed.weight self.embed = plain_embed def forward(self, input_ids): inputs_embeds = self.embed(input_ids).view(-1, 1, self.hidden_size) return inputs_embeds class RMSNorm(torch.nn.Module): def __init__(self, hidden_size, eps=1e-6): """ LlamaRMSNorm is equivalent to T5LayerNorm """ super().__init__() self.weight = torch.nn.Parameter(torch.ones(hidden_size)) self.variance_epsilon = eps def forward(self, hidden_states, gate=None): input_dtype = hidden_states.dtype hidden_states = hidden_states.to(torch.float32) variance = hidden_states.pow(2).mean(-1, keepdim=True) hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon) hidden_states = self.weight * hidden_states.to(input_dtype) if gate is not None: hidden_states = hidden_states * F.silu(gate.to(torch.float32)) return hidden_states.to(input_dtype) def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor: batch, num_key_value_heads, slen, head_dim = hidden_states.shape if n_rep == 1: return hidden_states hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim) return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim) class Attention(torch.nn.Module): def __init__(self, attn, layer_id, config, rotary, mapper): super().__init__() self.export_fused_attn = False if config is None: return self.config = config self.kv_cache = True self.layer_id = layer_id self.rotary = rotary export_args = getattr(config, 'export_args', None) self.export_fused_rope = getattr(export_args, 'transformer_c4', False) self.hidden_size = config.hidden_size self.head_dim = config.head_dim if isinstance(config.num_attention_heads, list): self.num_heads = config.num_attention_heads[layer_id] self.num_key_value_heads = config.num_key_value_heads[layer_id] else: self.head_dim = config.head_dim self.num_heads = config.num_attention_heads self.num_key_value_heads = config.num_key_value_heads self.num_key_value_groups = self.num_heads // self.num_key_value_heads ModelMapper.do_map(self, attn, mapper['attention']) self.qk_norm_after_rope = getattr(config, 'qk_norm_after_rope', False) if not self.qk_norm_after_rope: self.qk_norm_after_rope = ( hasattr(attn, 'query_layernorm') and hasattr(attn, 'key_layernorm') ) # Read attention scaling from the original HF attention module if hasattr(attn, 'scaling'): self.attn_scaling = attn.scaling # k_eq_v / KV sharing detection (gemma4 and similar models) # Mapper key 'k_eq_v' acts as sentinel: its presence means per-layer detection is needed. # Detection is structural (works across HF versions): # - k_proj exists, v_proj missing → k_eq_v (K serves as both K and V) # - both missing + is_kv_shared_layer → pure KV sharing (no local K/V computation) if getattr(self, 'k_eq_v', None) is not None: has_k_proj = hasattr(self, 'k_proj') and self.k_proj is not None has_v_proj = hasattr(self, 'v_proj') and self.v_proj is not None self.k_eq_v = has_k_proj and not has_v_proj # per-layer head_dim auto-detection (gemma4 has varying head_dim) if hasattr(self, 'q_proj') and self.q_proj is not None: actual_head_dim = self.q_proj.out_features // self.num_heads if actual_head_dim != self.head_dim: self.head_dim = actual_head_dim if has_k_proj: actual_kv_heads = self.k_proj.out_features // self.head_dim if actual_kv_heads != self.num_key_value_heads: self.num_key_value_heads = actual_kv_heads self.num_key_value_groups = self.num_heads // self.num_key_value_heads else: self.k_eq_v = False # KV sharing (gemma4): track which layers share KV self.is_kv_shared_layer = getattr(attn, 'is_kv_shared_layer', False) self.kv_shared_layer_index = getattr(attn, 'kv_shared_layer_index', None) self.store_full_length_kv = getattr(attn, 'store_full_length_kv', False) # Create FusedAttention with KV sharing info kv_shared_idx = self.kv_shared_layer_index if self.is_kv_shared_layer else -1 self.fused_attn = FusedAttention(self.num_heads * self.head_dim, self.kv_cache, f'/layers.{layer_id}/self_attn/FusedAttention', layer_id, kv_shared_idx) self.fused_rope = FusedRoPE(self.head_dim, f'/layers.{layer_id}/self_attn/FusedRoPE') if hasattr(self, 'qkv_proj') and self.qkv_proj is not None: # split qkv linear to q, k, v split_sizes = [self.hidden_size] * 3 if self.qkv_proj.weight.shape[0] != self.hidden_size * 3: # M/GQA split_sizes = [ self.num_heads * self.head_dim, # q_size self.num_key_value_heads * self.head_dim, # k_size self.num_key_value_heads * self.head_dim # v_size ] self.q_proj = torch.nn.Linear(self.hidden_size, split_sizes[0]) self.k_proj = torch.nn.Linear(self.hidden_size, split_sizes[1]) self.v_proj = torch.nn.Linear(self.hidden_size, split_sizes[2]) if config.model_type == 'chatglm': # chatglm-6b qkv_weight = self.qkv_proj.weight.data.view(self.num_heads, 3, self.head_dim, self.hidden_size) self.q_proj.weight.data = qkv_weight[:, 0, :, :].reshape(self.hidden_size, self.hidden_size) self.k_proj.weight.data = qkv_weight[:, 1, :, :].reshape(self.hidden_size, self.hidden_size) self.v_proj.weight.data = qkv_weight[:, 2, :, :].reshape(self.hidden_size, self.hidden_size) qkv_bias = self.qkv_proj.bias.data.view(self.num_heads, 3, self.head_dim) self.q_proj.bias.data = qkv_bias[:, 0, :].reshape(self.hidden_size) self.k_proj.bias.data = qkv_bias[:, 1, :].reshape(self.hidden_size) self.v_proj.bias.data = qkv_bias[:, 2, :].reshape(self.hidden_size) else: # other qw, kw, vw = torch.split(self.qkv_proj.weight, split_sizes) self.q_proj.weight.data = qw self.k_proj.weight.data = kw self.v_proj.weight.data = vw if self.qkv_proj.bias is not None: qb, kb, vb = torch.split(self.qkv_proj.bias, split_sizes) self.q_proj.bias.data = qb self.k_proj.bias.data = kb self.v_proj.bias.data = vb else: data_type = self.q_proj.weight.dtype self.q_proj.bias.data = torch.zeros(split_sizes[0], dtype=data_type) self.k_proj.bias.data = torch.zeros(split_sizes[1], dtype=data_type) self.v_proj.bias.data = torch.zeros(split_sizes[2], dtype=data_type) self.q_proj.weight.requires_grad = False self.k_proj.weight.requires_grad = False self.v_proj.weight.requires_grad = False self.q_proj.bias.requires_grad = False self.k_proj.bias.requires_grad = False self.v_proj.bias.requires_grad = False self.past_key_value = None def forward( self, hidden_states: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, rotary_pos_emb: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor, torch.Tensor]: bsz, q_len, _ = hidden_states.size() query_states = self.q_proj(hidden_states) key_states = None value_states = None if self.q_proj.out_features == 2 * self.num_heads * self.head_dim: reshaped = query_states.view(bsz, q_len, self.num_heads, self.head_dim * 2) query_states, gate = torch.split(reshaped, self.head_dim, dim=-1) gate = gate.reshape(bsz, q_len, -1) else: gate = None qk_norm_after_rope = getattr(self, 'qk_norm_after_rope', getattr(self.config, 'qk_norm_after_rope', False)) query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim) q_norm_before_rope = not qk_norm_after_rope and hasattr(self, 'q_norm') and self.q_norm is not None # KV sharing: for shared layers, reuse KV from source layer (test mode only) shared_kv_cache = getattr(self, '_shared_kv_cache', None) use_shared_kv = (self.is_kv_shared_layer and shared_kv_cache is not None and self.kv_shared_layer_index in shared_kv_cache and not torch.onnx.is_in_onnx_export()) k_norm_before_rope = False if use_shared_kv: key_states, value_states = shared_kv_cache[self.kv_shared_layer_index] elif self.k_proj is not None: key_states = self.k_proj(hidden_states) if self.k_eq_v: value_states = key_states.clone() else: value_states = self.v_proj(hidden_states) key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim) value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim) k_norm_before_rope = not qk_norm_after_rope and hasattr(self, 'k_norm') and self.k_norm is not None # gemma4 has v_norm (RMSNorm without scale) if hasattr(self, 'v_norm') and self.v_norm is not None: value_states = self.v_norm(value_states) else: # Pure KV sharing layer: no local K/V projections (e.g. gemma4 in HF>=5.5.4) # Dummy K/V for ONNX tracing; FusedAttention handles sharing via kv_shared_layer_index key_states = query_states.new_zeros(bsz, q_len, self.num_key_value_heads, self.head_dim) value_states = key_states k_norm_before_rope = False kv_seq_len = key_states.shape[1] if self.past_key_value is not None: kv_seq_len += self.past_key_value[0].shape[1] # rope if self.rotary is not None: cos, sin = rotary_pos_emb[0], rotary_pos_emb[1] use_fused_rope = ( self.export_fused_attn and torch.onnx.is_in_onnx_export() and self.export_fused_rope and not qk_norm_after_rope and not use_shared_kv and self.k_proj is not None and self.rotary.model_type not in ['chatglm', 'chatglm2', 'ernie4_5', 'glm_ocr'] and cos.shape[-1] == self.head_dim and sin.shape[-1] == self.head_dim ) fuse_qk_norm = use_fused_rope and q_norm_before_rope and k_norm_before_rope if use_fused_rope: if not fuse_qk_norm: if q_norm_before_rope: query_states = self.q_norm(query_states) if k_norm_before_rope: key_states = self.k_norm(key_states) query_states, key_states = self.fused_rope( query_states, key_states, cos, sin, self.q_norm if fuse_qk_norm else None, self.k_norm if fuse_qk_norm else None, ) else: # Most models apply q/k norm before rotary, but HunYuan applies it after rotary. if q_norm_before_rope: query_states = self.q_norm(query_states) if k_norm_before_rope: key_states = self.k_norm(key_states) query_states = self.rotary.apply_rotary_pos(query_states, cos, sin) if not use_shared_kv and self.k_proj is not None: key_states = self.rotary.apply_rotary_pos(key_states, cos, sin) elif q_norm_before_rope or k_norm_before_rope: if q_norm_before_rope: query_states = self.q_norm(query_states) if k_norm_before_rope: key_states = self.k_norm(key_states) if qk_norm_after_rope: if hasattr(self, 'q_norm') and self.q_norm is not None: query_states = self.q_norm(query_states) if not use_shared_kv and self.k_proj is not None and hasattr(self, 'k_norm') and self.k_norm is not None: key_states = self.k_norm(key_states) # MobileLLM model llama4_text has qk_norm after rotary if hasattr(self, 'qk_norm') and self.qk_norm is not None : query_states = self.qk_norm(query_states) key_states = self.qk_norm(key_states) if self.export_fused_attn and torch.onnx.is_in_onnx_export(): attn_output = self.fused_attn(query_states, key_states, value_states, attention_mask) if gate is not None: attn_output = attn_output * torch.sigmoid(gate) attn_output = self.o_proj(attn_output) return attn_output # kv cache if self.past_key_value is not None: past_key, past_value = self.past_key_value[0], self.past_key_value[1] key_states = torch.cat((past_key, key_states), dim=1) value_states = torch.cat((past_value, value_states), dim=1) if not use_shared_kv: self.past_key_value = torch.stack((key_states, value_states)) query_states = query_states.transpose(1, 2) if use_shared_kv: # Shared KV is already in transposed format [B, heads, head_dim, seq] / [B, heads, seq, head_dim] pass else: key_states = key_states.permute([0, 2, 3, 1]) value_states = value_states.transpose(1, 2) # Store KV for sharing (source layers that other layers will read from) if self.store_full_length_kv and shared_kv_cache is not None: shared_kv_cache[self.layer_id] = (key_states.clone(), value_states.clone()) # repeat k/v heads if n_kv_heads < n_heads key_states = repeat_kv(key_states, self.num_key_value_groups) value_states = repeat_kv(value_states, self.num_key_value_groups) #------- attention ---------- # query_states @ key_states attn_scaling = getattr(self, 'attn_scaling', 1.0 / math.sqrt(self.head_dim)) attn_weights = torch.matmul(query_states, key_states) * attn_scaling # attention_mask if attention_mask.dtype in (torch.bool, torch.int32): # chatglm attn_weights.masked_fill_(attention_mask, -10000.0) else: attn_weights = attn_weights + attention_mask if hasattr(self, 'sinks'): sinks = self.sinks.reshape(1, -1, 1, 1).to(torch.float32).expand(query_states.shape[0], -1, query_states.shape[-2], -1) combined_logits = torch.cat([attn_weights, sinks], dim=-1) combined_logits = combined_logits - combined_logits.max(dim=-1, keepdim=True).values probs = F.softmax(combined_logits, dim=-1, dtype=torch.float32).to(query_states.dtype) attn_weights = probs[..., :-1] # we drop the sink here else: # upcast softmax to fp32 attn_weights = F.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype) # attn_weights @ value_states attn_output = torch.matmul(attn_weights, value_states) attn_output = attn_output.transpose(1, 2).contiguous() attn_output = attn_output.reshape(bsz, q_len, -1) if gate is not None: attn_output = attn_output * torch.sigmoid(gate) attn_output = self.o_proj(attn_output) return attn_output def l2norm(x: torch.FloatTensor, dim: int = -1, eps: float = 1e-6): """This function is intended to align with the l2norm implementation in the FLA library.""" inv_norm = torch.rsqrt((x * x).sum(dim=dim, keepdim=True) + eps) return x * inv_norm def torch_chunk_gated_delta_rule( query, key, value, g, beta, chunk_size=64, initial_state=None, output_final_state=False, use_qk_l2norm_in_kernel=False, ): initial_dtype = query.dtype if use_qk_l2norm_in_kernel: query = l2norm(query, dim=-1, eps=1e-6) key = l2norm(key, dim=-1, eps=1e-6) query, key, value, beta, g = [ x.transpose(1, 2).contiguous().to(torch.float32) for x in (query, key, value, beta, g) ] batch_size, num_heads, sequence_length, k_head_dim = key.shape v_head_dim = value.shape[-1] pad_size = (chunk_size - sequence_length % chunk_size) % chunk_size query = F.pad(query, (0, 0, 0, pad_size)) key = F.pad(key, (0, 0, 0, pad_size)) value = F.pad(value, (0, 0, 0, pad_size)) beta = F.pad(beta, (0, pad_size)) g = F.pad(g, (0, pad_size)) total_sequence_length = sequence_length + pad_size scale = 1 / (query.shape[-1] ** 0.5) query = query * scale v_beta = value * beta.unsqueeze(-1) k_beta = key * beta.unsqueeze(-1) # reshape to chunks query, key, value, k_beta, v_beta = [ x.reshape(x.shape[0], x.shape[1], -1, chunk_size, x.shape[-1]) for x in (query, key, value, k_beta, v_beta) ] g = g.reshape(g.shape[0], g.shape[1], -1, chunk_size) mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=query.device), diagonal=0) # chunk decay g = g.cumsum(dim=-1) decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril() attn = -((k_beta @ key.transpose(-1, -2)) * decay_mask).masked_fill(mask, 0) for i in range(1, chunk_size): row = attn[..., i, :i].clone() sub = attn[..., :i, :i].clone() attn[..., i, :i] = row + (row.unsqueeze(-1) * sub).sum(-2) attn = attn + torch.eye(chunk_size, dtype=attn.dtype, device=attn.device) value = attn @ v_beta k_cumdecay = attn @ (k_beta * g.exp().unsqueeze(-1)) last_recurrent_state = ( torch.zeros(batch_size, num_heads, k_head_dim, v_head_dim).to(value) if initial_state is None else initial_state.to(value) ) core_attn_out = torch.zeros_like(value) mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=query.device), diagonal=1) # for each chunk for i in range(0, total_sequence_length // chunk_size): q_i, k_i, v_i = query[:, :, i], key[:, :, i], value[:, :, i] attn = (q_i @ k_i.transpose(-1, -2) * decay_mask[:, :, i]).masked_fill_(mask, 0) v_prime = (k_cumdecay[:, :, i]) @ last_recurrent_state v_new = v_i - v_prime attn_inter = (q_i * g[:, :, i, :, None].exp()) @ last_recurrent_state core_attn_out[:, :, i] = attn_inter + attn @ v_new last_recurrent_state = ( last_recurrent_state * g[:, :, i, -1, None, None].exp() + (k_i * (g[:, :, i, -1, None] - g[:, :, i]).exp()[..., None]).transpose(-1, -2) @ v_new ) if not output_final_state: last_recurrent_state = None core_attn_out = core_attn_out.reshape(core_attn_out.shape[0], core_attn_out.shape[1], -1, core_attn_out.shape[-1]) core_attn_out = core_attn_out[:, :, :sequence_length] core_attn_out = core_attn_out.transpose(1, 2).contiguous().to(initial_dtype) return core_attn_out, last_recurrent_state def torch_recurrent_gated_delta_rule( query, key, value, g, beta, initial_state, output_final_state, use_qk_l2norm_in_kernel=False ): initial_dtype = query.dtype if use_qk_l2norm_in_kernel: query = l2norm(query, dim=-1, eps=1e-6) key = l2norm(key, dim=-1, eps=1e-6) query, key, value, beta, g = [ x.transpose(1, 2).contiguous().to(torch.float32) for x in (query, key, value, beta, g) ] batch_size, num_heads, sequence_length, k_head_dim = key.shape v_head_dim = value.shape[-1] scale = 1 / (query.shape[-1] ** 0.5) query = query * scale core_attn_out = torch.zeros(batch_size, num_heads, sequence_length, v_head_dim).to(value) last_recurrent_state = ( torch.zeros(batch_size, num_heads, k_head_dim, v_head_dim).to(value) if initial_state is None else initial_state.to(value) ) for i in range(sequence_length): q_t = query[:, :, i] k_t = key[:, :, i] v_t = value[:, :, i] g_t = g[:, :, i].exp().unsqueeze(-1).unsqueeze(-1) beta_t = beta[:, :, i].unsqueeze(-1) last_recurrent_state = last_recurrent_state * g_t kv_mem = (last_recurrent_state * k_t.unsqueeze(-1)).sum(dim=-2) delta = (v_t - kv_mem) * beta_t last_recurrent_state = last_recurrent_state + k_t.unsqueeze(-1) * delta.unsqueeze(-2) core_attn_out[:, :, i] = (last_recurrent_state * q_t.unsqueeze(-1)).sum(dim=-2) if not output_final_state: last_recurrent_state = None core_attn_out = core_attn_out.transpose(1, 2).contiguous().to(initial_dtype) return core_attn_out, last_recurrent_state def torch_gated_delta_rule( query, # [B, L, H, K] query vectors key, # [B, L, H, K] key vectors value, # [B, L, H, V] value vectors g, # [B, L, H] log-space decay (negative values) beta, # [B, L, H] learning rate for delta update initial_state=None, # [B, H, K, V] initial recurrent state output_final_state=False, use_qk_l2norm_in_kernel=False, ): """ Non-chunk implementation of Gated Delta Rule (Linear Attention). Mathematically equivalent to torch_chunk_gated_delta_rule. Maintains a key-value memory (state S) of shape [K, V] per head, updated at each timestep using the Delta Learning Rule with gating. Per-step formula (for each head independently): ───────────────────────────────────────────────── S_t = S_{t-1} * exp(g_t) # 1. decay old memory v_pred = S_t^T @ k_t # 2. predict value for current key delta = beta_t * (v_t - v_pred) # 3. prediction error * learning rate S_t = S_t + k_t @ delta^T # 4. update memory (outer product) o_t = S_t^T @ (q_t / sqrt(d_k)) # 5. query the memory ───────────────────────────────────────────────── Shapes: S: [B, H, K, V] recurrent state (key-value memory) k_t: [B, H, K] key at timestep t v_t: [B, H, V] value at timestep t q_t: [B, H, K] query at timestep t g_t: [B, H] log-decay scalar per head beta_t:[B, H] learning rate scalar per head o_t: [B, H, V] output at timestep t """ initial_dtype = query.dtype # Optional: L2 normalize Q, K before computation if use_qk_l2norm_in_kernel: query = l2norm(query, dim=-1, eps=1e-6) key = l2norm(key, dim=-1, eps=1e-6) # [B, L, H, D] -> [B, H, L, D], all in float32 query, key, value, beta, g = [ x.transpose(1, 2).contiguous().to(torch.float32) for x in (query, key, value, beta, g) ] B, H, L, K = key.shape V = value.shape[-1] # Scale query: q = q / sqrt(d_k) query = query * (K ** -0.5) # Initialize recurrent state S: [B, H, K, V] if initial_state is None: S = torch.zeros(B, H, K, V, device=query.device, dtype=torch.float32) else: S = initial_state.to(torch.float32) outputs = [] for t in range(L): q_t = query[:, :, t] # [B, H, K] k_t = key[:, :, t] # [B, H, K] v_t = value[:, :, t] # [B, H, V] g_t = g[:, :, t] # [B, H] beta_t = beta[:, :, t] # [B, H] # ── Step 1: Decay ── # S = S * exp(g_t), g_t < 0 so this shrinks old memory S = S * g_t[:, :, None, None].exp() # ── Step 2: Read (predict value for current key) ── # v_pred = S^T @ k_t : [B, H, K, V]^T @ [B, H, K, 1] -> [B, H, V] v_pred = (S.transpose(-1, -2) @ k_t.unsqueeze(-1)).squeeze(-1) # ── Step 3: Delta (prediction error * learning rate) ── # delta = beta_t * (v_t - v_pred) : [B, H, V] delta = beta_t[:, :, None] * (v_t - v_pred) # ── Step 4: Write (update memory with outer product) ── # S += k_t @ delta^T : [B, H, K, 1] @ [B, H, 1, V] -> [B, H, K, V] S = S + k_t.unsqueeze(-1) @ delta.unsqueeze(-2) # ── Step 5: Query (read output from updated memory) ── # o_t = S^T @ q_t : [B, H, K, V]^T @ [B, H, K, 1] -> [B, H, V] o_t = (S.transpose(-1, -2) @ q_t.unsqueeze(-1)).squeeze(-1) outputs.append(o_t) # Stack: list of [B, H, V] -> [B, H, L, V] core_attn_out = torch.stack(outputs, dim=2) if not output_final_state: S = None # [B, H, L, V] -> [B, L, H, V], restore original dtype core_attn_out = core_attn_out.transpose(1, 2).contiguous().to(initial_dtype) return core_attn_out, S class ShortConvAttention(torch.nn.Module): def __init__(self, attn, layer_id, config, mapper): super().__init__() self.layer_id = layer_id self.hidden_size = config.hidden_size self.conv_kernel_size = config.conv_L_cache ModelMapper.do_map(self, attn, mapper['linear_attention']) self.fused_attn = FusedLinearAttention( name=f'/layers.{layer_id}/self_attn/FusedLinearAttention', attn_type="short_conv", num_k_heads=1, num_v_heads=1, head_k_dim=self.hidden_size, head_v_dim=self.hidden_size, use_qk_l2norm=False ) self.conv_state = None def forward( self, hidden_states: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, ) -> torch.Tensor: # Note: ShortConvAttention is mask-free; `attention_mask` is accepted # only to keep the call signature uniform with `Attention.forward` and # is intentionally unused. batch_size, seq_len, _ = hidden_states.shape # in_proj: [B, L, H] -> [B, L, 3H] bcx = self.in_proj(hidden_states) if torch.onnx.is_in_onnx_export(): # ONNX path: pass through FusedLinearAttention custom op bcx_t = bcx.transpose(1, 2) # [B, 3H, L] gate = torch.zeros(batch_size, seq_len, 1, dtype=bcx.dtype, device=bcx.device) beta = torch.zeros(batch_size, seq_len, 1, dtype=bcx.dtype, device=bcx.device) attn_out = self.fused_attn(bcx_t, gate, beta, self.conv.weight.data.detach()) # attn_out: [B, L, 1, H] -> [B, L, H] attn_out = attn_out.view(batch_size, seq_len, -1) output = self.out_proj(attn_out) return output # Test path: manual computation # Split into B_, C_, x_ each [B, L, H] B_, C_, x_ = bcx.chunk(3, dim=-1) # Bx = B_ * x_ Bx = B_ * x_ # Transpose for conv: [B, H, L] Bx = Bx.transpose(1, 2) conv_state_size = self.conv_kernel_size - 1 if self.conv_state is not None: conv_input = torch.cat([self.conv_state, Bx], dim=-1) conv_out = F.conv1d(conv_input, self.conv.weight, padding=0, groups=self.hidden_size) new_conv_state = conv_input[:, :, -conv_state_size:] else: new_conv_state = F.pad(Bx, (conv_state_size - Bx.shape[-1], 0)) conv_out = self.conv(Bx)[:, :, :seq_len] # No SiLU for short_conv (unlike gated_delta_rule) # Transpose back: [B, H, L] -> [B, L, H] conv_out = conv_out.transpose(1, 2) # y = C_ * conv_out y = C_ * conv_out output = self.out_proj(y) self.conv_state = new_conv_state return output class LinearAttention(torch.nn.Module): def __init__(self, attn, layer_id, config, rotary, mapper): super().__init__() self.layer_id = layer_id self.config = config self.hidden_size = config.hidden_size self.head_dim = config.head_dim self.conv_kernel_size = config.linear_conv_kernel_dim self.conv_state_size = self.conv_kernel_size - 1 self.head_k_dim = config.linear_key_head_dim self.head_v_dim = config.linear_value_head_dim self.num_k_heads = config.linear_num_key_heads self.num_v_heads = config.linear_num_value_heads self.key_dim = self.head_k_dim * self.num_k_heads self.value_dim = self.head_v_dim * self.num_v_heads self.conv_dim = self.key_dim * 2 + self.value_dim ModelMapper.do_map(self, attn, mapper['linear_attention']) original_norm = self.norm self.norm = RMSNorm(self.head_v_dim, eps=config.rms_norm_eps) self.norm.weight.data = original_norm.weight.data self.fused_attn = FusedLinearAttention( name=f'/layers.{layer_id}/self_attn/FusedLinearAttention', attn_type="gated_delta_rule", num_k_heads=self.num_k_heads, num_v_heads=self.num_v_heads, head_k_dim=self.head_k_dim, head_v_dim=self.head_v_dim, use_qk_l2norm=True ) self.conv_state = None self.rnn_state = None def forward( self, hidden_states: torch.Tensor, attention_mask: Optional[torch.Tensor] = None, ) -> Tuple[torch.Tensor, torch.Tensor]: # Note: LinearAttention is mask-free; `attention_mask` is accepted # only to keep the call signature uniform with `Attention.forward` and # is intentionally unused. batch_size, seq_len, _ = hidden_states.shape # 1. Linear Projections # mixed_qkv: [B, L, 2*key_dim + value_dim] mixed_qkv = self.in_proj_qkv(hidden_states) # Transpose for Conv1d: [B, Dim, L] mixed_qkv = mixed_qkv.transpose(1, 2) # Gate, Beta, Z projections z = self.in_proj_z(hidden_states) # [B, L, value_dim] b = self.in_proj_b(hidden_states) # [B, L, num_v_heads] a = self.in_proj_a(hidden_states) # [B, L, num_v_heads] # 2. Pre-compute gates beta = torch.sigmoid(b) gate = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias) if torch.onnx.is_in_onnx_export(): attn_out = self.fused_attn(mixed_qkv, gate, beta, self.conv1d.weight.data.detach()) attn_out = attn_out.reshape(-1, self.head_v_dim) z = z.reshape(-1, self.head_v_dim) attn_out = self.norm(attn_out, z) attn_out = attn_out.view(batch_size, seq_len, -1) output = self.out_proj(attn_out) return output # === Normal path: full computation for testing === # 3. State Management (Conv State & Recurrent State) if self.conv_state is not None: conv_state = self.conv_state conv_input = torch.cat([conv_state, mixed_qkv], dim=-1) mixed_qkv = F.silu(F.conv1d(conv_input, self.conv1d.weight, self.conv1d.bias, padding=0, groups=self.conv_dim)) new_conv_state = conv_input[:, :, -self.conv_state_size:] else: new_conv_state = F.pad(mixed_qkv, (self.conv_state_size - mixed_qkv.shape[-1], 0)) mixed_qkv = F.silu(self.conv1d(mixed_qkv)[:, :, :seq_len]) # 4. Split Q, K, V mixed_qkv = mixed_qkv.transpose(1, 2) query, key, value = torch.split( mixed_qkv, [self.key_dim, self.key_dim, self.value_dim], dim=-1 ) query = query.view(batch_size, seq_len, self.num_k_heads, self.head_k_dim) key = key.view(batch_size, seq_len, self.num_k_heads, self.head_k_dim) value = value.view(batch_size, seq_len, self.num_v_heads, self.head_v_dim) # 5. GQA Expansion if self.num_v_heads > self.num_k_heads: factor = self.num_v_heads // self.num_k_heads query = query.repeat_interleave(factor, dim=2) key = key.repeat_interleave(factor, dim=2) # 6. Gated Delta Rule if self.rnn_state is None: attn_out, last_recurrent_state = torch_gated_delta_rule( query, key, value, g=gate, beta=beta, initial_state=None, output_final_state=True, use_qk_l2norm_in_kernel=True, ) else: recurrent_state = self.rnn_state attn_out, last_recurrent_state = torch_recurrent_gated_delta_rule( query, key, value, g=gate, beta=beta, initial_state=recurrent_state, output_final_state=True, use_qk_l2norm_in_kernel=True, ) # 7. Post-process attn_out = attn_out.reshape(-1, self.head_v_dim) z = z.reshape(-1, self.head_v_dim) attn_out = self.norm(attn_out, z) attn_out = attn_out.view(batch_size, seq_len, -1) output = self.out_proj(attn_out) # Update internal state self.conv_state = new_conv_state self.rnn_state = last_recurrent_state return output def create_linear_attention(attn, layer_id, config, rotary, mapper): """Factory function for creating LinearAttention variants based on config.""" if hasattr(config, 'conv_L_cache') and config.conv_L_cache > 0: return ShortConvAttention(attn, layer_id, config, mapper) return LinearAttention(attn, layer_id, config, rotary, mapper) def rotate_half(x): x1 = x[..., : x.shape[-1] // 2] x2 = x[..., x.shape[-1] // 2 :] return torch.cat((-x2, x1), dim=-1) def _compute_yarn_parameters(rotary_dim, base_theta, scaling_config, max_position_embeddings): """ 计算 YaRN (Yet another RoPE extensioN method) 的参数。 此函数等价于 Hugging Face Transformers 中的 YaRN 实现。 Args: rotary_dim (int): RoPE 的维度。 base_theta (float): RoPE 的基础 theta 值。 scaling_config (dict): 包含 YaRN 特定配置的字典。 max_position_embeddings (int): 模型的最大位置编码。 Returns: tuple[torch.Tensor, float]: - inv_freq (torch.Tensor): 计算好的、用于 RoPE 的逆频率 (即 theta)。 - attention_scaling (float): 应用于 Query 向量的缩放因子。 """ def get_mscale(scale, m_scale): if scale <= 1: return 1.0 return 0.1 * m_scale * math.log(scale) + 1.0 def find_correction_dim(num_rotations, d, b, max_pos): return (d * math.log(max_pos / (num_rotations * 2 * math.pi))) / (2 * math.log(b)) def find_correction_range(low_rot, high_rot, d, b, max_pos): low = find_correction_dim(low_rot, d, b, max_pos) high = find_correction_dim(high_rot, d, b, max_pos) return max(0, math.floor(low)), min(d - 1, math.ceil(high)) def linear_ramp_factor(mn, mx, d): if mn == mx: mx += 0.001 linear_func = (torch.arange(d, dtype=torch.float32) - mn) / (mx - mn) return torch.clamp(linear_func, 0, 1) # 1. 提取 YaRN 参数 factor = scaling_config['factor'] beta_fast = scaling_config.get("beta_fast", 32) beta_slow = scaling_config.get("beta_slow", 1) original_max_pos = scaling_config.get("original_max_position_embeddings", max_position_embeddings) mscale = scaling_config.get("mscale", 1.0) # 2. 计算 attention_scaling (即 attention_factor) attention_scaling = get_mscale(factor, mscale) # 3. 计算 inv_freq (即 theta) dim = rotary_dim # 计算插值和外推的频率 pos_freqs = base_theta ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim) inv_freq_extrapolation = 1.0 / pos_freqs inv_freq_interpolation = 1.0 / (factor * pos_freqs) # 找到需要修正的维度范围 low, high = find_correction_range(beta_fast, beta_slow, dim, base_theta, original_max_pos) # 创建维度混合的 ramp (作用于 dim//2 的频率上) inv_freq_extrapolation_factor = 1 - linear_ramp_factor(low, high, dim // 2) # 混合插值和外推频率,得到最终的 inv_freq inv_freq = ( inv_freq_interpolation * (1 - inv_freq_extrapolation_factor) + inv_freq_extrapolation * inv_freq_extrapolation_factor ) return inv_freq, attention_scaling class Rotary(torch.nn.Module): def __init__(self, config): super().__init__() if config is None: return self.rope_theta = config.rope_theta self.rope_ratio = config.rope_ratio if self.rope_ratio is not None: self.rope_theta *= self.rope_ratio self.rotary_dim = config.head_dim self.model_type = config.model_type if hasattr(config, 'rotary_dim'): self.rotary_dim = config.rotary_dim if self.model_type == 'chatglm': self.rotary_dim = config.head_dim // 2 # Qwen3.5 / LFM2 style: flat rope_parameters dict if hasattr(config, 'rope_parameters') and config.rope_parameters is not None: rp = config.rope_parameters # Detect gemma4-style per-layer-type rope_parameters (dict of dicts) is_per_layer_type = any(isinstance(v, dict) for v in rp.values()) if not is_per_layer_type: if 'rope_theta' in rp: self.rope_theta = rp['rope_theta'] if 'partial_rotary_factor' in rp: self.partial_rotary_factor = rp['partial_rotary_factor'] self.rotary_dim = int(self.rotary_dim * self.partial_rotary_factor) config.rope_scaling = rp self.mrope_section = None self.theta_sections = None self.attention_scaling = 1.0 self.is_scaled = False self.mrope_interleaved = False def get_theta(): return 1.0 / (self.rope_theta ** (torch.arange(0, self.rotary_dim, 2, dtype=torch.float32) / self.rotary_dim)) # default rope type's theta self.theta = get_theta() # other type if hasattr(config, 'rope_scaling') and config.rope_scaling is not None: scaling_config = config.rope_scaling # get rope_type rope_type = 'default' if 'type' in config.rope_scaling: rope_type = config.rope_scaling['type'] elif 'rope_type' in config.rope_scaling: rope_type = config.rope_scaling['rope_type'] # gen theta for rope_type if rope_type == 'dynamic': # NTK if 'alpha' in config.rope_scaling: # NTKAlpha in Hunyuan self.rope_theta *= (config.rope_scaling['alpha'] ** (self.rotary_dim / (self.rotary_dim - 2))) else: # NTKScaling pass self.theta = get_theta() elif rope_type == 'yarn': self.is_scaled = True self.theta, self.attention_scaling = _compute_yarn_parameters( rotary_dim=self.rotary_dim, base_theta=self.rope_theta, scaling_config=scaling_config, max_position_embeddings=config.max_position_embeddings ) elif rope_type == 'longrope': # longrope in MiniCPM self.is_scaled = True original_max_position_embeddings = config.rope_scaling['original_max_position_embeddings'] scale = (config.max_position_embeddings / original_max_position_embeddings) self.attention_scaling = math.sqrt(1 + math.log(scale) / math.log(original_max_position_embeddings)) # long_factor = config.rope_scaling['long_factor'] short_factor = config.rope_scaling['short_factor'] self.theta = get_theta() / torch.tensor(short_factor, dtype=torch.float32) # mrope for multimode if 'mrope_section' in scaling_config: self.mrope_interleaved = scaling_config.get('mrope_interleaved', False) self.mrope_section = scaling_config['mrope_section'] self.theta = get_theta().unsqueeze(0) self.theta_sections = self.theta.split(self.mrope_section, dim=-1) def apply_interleaved_mrope(freqs, mrope_section): # mrope apply func from qwen3-vl freqs_t = freqs[0] # just overwrite the first dimension T for dim, offset in enumerate((1, 2), start=1): # H, W length = mrope_section[dim] * 3 idx = slice(offset, length, 3) freqs_t[..., idx] = freqs[dim, ..., idx] return freqs_t if self.mrope_interleaved: half_rotary = self.rotary_dim // 2 freq_idx = torch.arange(0, 3 * half_rotary).reshape(3, 1, half_rotary) self.mrope_reindex = apply_interleaved_mrope(freq_idx, self.mrope_section).flatten() self.is_mrope = self.theta_sections is not None or self.mrope_interleaved def forward(self, position_ids): if self.is_mrope: return self.mrope_forward(position_ids) position_ids = position_ids.float().reshape(-1, 1) idx_theta = position_ids * self.theta.to(position_ids.device) rotary_pos_emb = torch.stack([torch.cos(idx_theta), torch.sin(idx_theta)]) if self.model_type == 'ernie4_5': rotary_pos_emb = torch.stack((rotary_pos_emb, rotary_pos_emb), dim=-1) rotary_pos_emb = rotary_pos_emb.reshape(*rotary_pos_emb.shape[:-2], -1) elif self.model_type != 'chatglm2': rotary_pos_emb = torch.cat((rotary_pos_emb, rotary_pos_emb), dim=-1) rotary_pos_emb = rotary_pos_emb.unsqueeze(2).unsqueeze(1) if self.is_scaled: rotary_pos_emb *= self.attention_scaling return rotary_pos_emb def mrope_forward(self, position_ids): position_ids = position_ids.float().unsqueeze(-1) if self.mrope_interleaved: idx_theta = position_ids * self.theta.to(position_ids.device) idx_theta = idx_theta.transpose(1, 0).reshape(-1, 3 * self.rotary_dim // 2) idx_theta = idx_theta[:, self.mrope_reindex] else: idx_theta = torch.concat([ position_ids[0] * self.theta_sections[0], position_ids[1] * self.theta_sections[1], position_ids[2] * self.theta_sections[2] ], dim=-1) rotary_pos_emb = torch.stack([torch.cos(idx_theta), torch.sin(idx_theta)]) if self.model_type in ['glm_ocr']: # interleaved doubling: [c0,c0,c1,c1,...,cn,cn] rotary_pos_emb = torch.stack((rotary_pos_emb, rotary_pos_emb), dim=-1) rotary_pos_emb = rotary_pos_emb.reshape(*rotary_pos_emb.shape[:-2], -1) else: rotary_pos_emb = torch.cat((rotary_pos_emb, rotary_pos_emb), dim=-1) rotary_pos_emb = rotary_pos_emb.unsqueeze(2).unsqueeze(1) return rotary_pos_emb def apply_rotary_pos(self, x, cos, sin): if self.model_type == 'chatglm': return self.chatglm_rotary_pos(x, cos, sin) if self.model_type == 'chatglm2': return self.chatglm2_rotary_pos(x, cos, sin) if self.model_type in ['phi-msft', 'qwen3_5', 'qwen3_5_moe']: return self.phi_rotary_pos(x, cos, sin) if self.model_type in ['ernie4_5', 'glm_ocr']: return self.ernie_rotary_pos(x, cos, sin) # Auto-detect partial rotary: cos/sin dim < x dim if cos.shape[-1] < x.shape[-1]: return self.phi_rotary_pos(x, cos, sin) return self.llama_rotary_pos(x, cos, sin) def llama_rotary_pos(self, x, cos, sin): x = (x * cos) + (rotate_half(x) * sin) return x def ernie_rotary_pos(self, x, cos, sin): rotate_half_x = torch.stack( [-x[:, :, :, 1::2], x[:, :, :, 0::2]], dim=-1 ).reshape(x.shape) x = (x * cos) + (rotate_half_x * sin) return x def phi_rotary_pos(self, x, cos, sin): # Use cos dim to determine rotary_dim (handles per-layer different rotary_dim) rotary_dim = cos.shape[-1] x, x_pass = x[..., :rotary_dim], x[..., rotary_dim:] x = (x * cos) + (rotate_half(x) * sin) return torch.cat((x, x_pass), dim=-1) def chatglm2_rotary_pos(self, x, cos, sin): x, x_pass = x[..., :self.rotary_dim], x[..., self.rotary_dim:] b, s, n, h = x.shape xshaped = x.view(b, s, n, h//2, 2) x = torch.concat( [ xshaped[..., 0] * cos - xshaped[..., 1] * sin, xshaped[..., 1] * cos + xshaped[..., 0] * sin, ], -1, ) return torch.cat((x, x_pass), dim=-1) def chatglm_rotary_pos(self, x, cos, sin): seq = x.shape[1] x1, x2 = x[..., :self.rotary_dim], x[..., self.rotary_dim:] cos1, sin1 = cos[:, :seq, ...], sin[:, :seq, ...] cos2, sin2 = cos[:, seq:, ...], sin[:, seq:, ...] x1 = (x1 * cos1) + (rotate_half(x1) * sin1) x2 = (x2 * cos2) + (rotate_half(x2) * sin2) return torch.cat((x1, x2), dim=-1) class VisionRotary(Rotary): def __init__(self, config): super().__init__(config) # support [h_pos, w_pos] def forward(self, position_ids): # [2, patch_len, 1] position_ids = position_ids.float().unsqueeze(-1) idx_theta = position_ids * self.theta # [patch_len, rotary_dim] idx_theta = idx_theta.permute(1, 0, 2).reshape(-1, self.rotary_dim) rotary_pos_emb = torch.stack([torch.cos(idx_theta), torch.sin(idx_theta)]) rotary_pos_emb = torch.cat((rotary_pos_emb, rotary_pos_emb), dim=-1) rotary_pos_emb = rotary_pos_emb.unsqueeze(2).unsqueeze(1) return rotary_pos_emb def apply_rotary_pos(self, x, cos, sin): x = (x * cos) + (rotate_half(x) * sin) return x class GptOssExpert(torch.nn.Module): def __init__(self, hidden_size, expert_dim): super().__init__() self.expert_dim = expert_dim self.gate_up_proj_linear = torch.nn.Linear(hidden_size, 2 * expert_dim) self.down_proj_linear = torch.nn.Linear(expert_dim, hidden_size) self.alpha = 1.702 self.limit = 7.0 def forward(self, hidden_states: torch.Tensor, debug=False) -> torch.Tensor: gate_up = self.gate_up_proj_linear(hidden_states) gate, up = gate_up[..., ::2], gate_up[..., 1::2] # gate = gate.clamp(min=None, max=self.limit) limit_tensor = torch.tensor(self.limit, device=gate.device, dtype=gate.dtype) gate = torch.min(gate, limit_tensor) up = up.clamp(min=-self.limit, max=self.limit) glu = gate * torch.sigmoid(gate * self.alpha) gated_output = (up + 1) * glu out = self.down_proj_linear(gated_output) return out class Qwen3Expert(torch.nn.Module): def __init__(self, hidden_size, expert_dim, act_fn): super().__init__() self.expert_dim = expert_dim self.gate_up_proj_linear = torch.nn.Linear(hidden_size, 2 * expert_dim, bias=False) self.down_proj_linear = torch.nn.Linear(expert_dim, hidden_size, bias=False) self.act_fn = act_fn def forward(self, hidden_states: torch.Tensor, debug=False) -> torch.Tensor: gate_up = self.gate_up_proj_linear(hidden_states) # gate, up = gate_up[..., ::2], gate_up[..., 1::2] gate, up = gate_up.chunk(2, dim=-1) out = self.down_proj_linear(up * self.act_fn(gate)) return out class Mlp(torch.nn.Module): def __init__(self, mlp, mapper, layer_id): super().__init__() self.layer_id = layer_id ModelMapper.do_map(self, mlp, mapper['mlp']) self.is_moe = hasattr(self, 'experts') self.export_moe = False self.custom_moe = MoE(self.num_experts, self.top_k, layer_id) if isinstance(self.experts, torch.nn.ModuleList): self.moe_type = 'qwen3_moe' else: self.moe_type = 'qwen3_5_moe' self.norm_topk_prob = True # refacte experts to qwen3_experts original_experts = self.experts hidden_size = getattr(original_experts, 'hidden_dim', None) or getattr(original_experts, 'hidden_size') expert_dim = getattr(original_experts, 'intermediate_dim', None) or getattr(original_experts, 'intermediate_size') act_fn = original_experts.act_fn new_experts_list = torch.nn.ModuleList() for i in range(self.num_experts): expert_mlp = Qwen3Expert(hidden_size, expert_dim, act_fn) expert_mlp.gate_up_proj_linear.weight.data = original_experts.gate_up_proj.data[i] expert_mlp.down_proj_linear.weight.data = original_experts.down_proj.data[i] new_experts_list.append(expert_mlp) self.experts = new_experts_list if not isinstance(self.gate, torch.nn.Linear): gate = torch.nn.Linear(hidden_size, self.num_experts, bias=False) gate.weight.data = self.gate.weight.data self.gate = gate if hasattr(self, 'expert_bias') and self.expert_bias is not None: self.moe_type = 'lfm2_moe' if hasattr(self, 'router'): self.moe_type = 'gpt_oss' hidden_dim = self.router.weight.shape[1] self.gate = torch.nn.Linear(hidden_dim, self.num_experts, bias=True) self.gate.weight.data = self.router.weight.data self.gate.bias.data = self.router.bias.data # refacte experts to qwen3_experts original_experts = self.experts expert_dim = original_experts.expert_dim new_experts_list = torch.nn.ModuleList() for i in range(self.num_experts): expert_mlp = GptOssExpert(hidden_dim, expert_dim) expert_mlp.gate_up_proj_linear.weight.data = original_experts.gate_up_proj.data[i].transpose(0, 1) expert_mlp.gate_up_proj_linear.bias.data = original_experts.gate_up_proj_bias.data[i] expert_mlp.down_proj_linear.weight.data = original_experts.down_proj.data[i].transpose(0, 1) expert_mlp.down_proj_linear.bias.data = original_experts.down_proj_bias.data[i] new_experts_list.append(expert_mlp) self.experts = new_experts_list del self.router def forward(self, hidden_states: torch.Tensor): if not self.is_moe: # general Mlp return self.down_proj(self.act_fn(self.gate_proj(hidden_states)) * self.up_proj(hidden_states)) # MoE Mlp batch_size, sequence_length, hidden_dim = hidden_states.shape hidden_states = hidden_states.view(-1, hidden_dim) if hasattr(self, 'shared_expert'): shared_expert_output = F.sigmoid(self.shared_expert_gate(hidden_states)) * self.shared_expert(hidden_states) shared_expert_output = shared_expert_output.reshape(batch_size, sequence_length, hidden_dim) else: shared_expert_output = None if self.moe_type == 'lfm2_moe': router_logits = self.gate(hidden_states) routing_weights = router_logits.sigmoid() scores_for_routing = routing_weights + self.expert_bias _, selected_experts = torch.topk(scores_for_routing, self.top_k, dim=-1) routing_weights = torch.gather(routing_weights, dim=-1, index=selected_experts) if self.norm_topk_prob: routing_weights = routing_weights / (routing_weights.sum(dim=-1, keepdim=True) + 1e-6) routing_weights = (routing_weights * self.routed_scaling_factor).to(hidden_states.dtype) elif self.moe_type == 'gpt_oss': router_logits = self.gate(hidden_states) routing_weights, selected_experts = torch.topk(router_logits, self.top_k, dim=-1) routing_weights = F.softmax(routing_weights, dim=-1, dtype=torch.float).to(hidden_states.dtype) else: router_logits = self.gate(hidden_states) routing_weights = F.softmax(router_logits, dim=-1, dtype=torch.float) routing_weights, selected_experts = torch.topk(routing_weights, self.top_k, dim=-1) if self.norm_topk_prob: # only diff with mixtral sparse moe block! routing_weights /= routing_weights.sum(dim=-1, keepdim=True) # we cast back to the input dtype routing_weights = routing_weights.to(hidden_states.dtype) if self.export_moe: expert_output = self.custom_moe(hidden_states, routing_weights, selected_experts) if shared_expert_output is not None: expert_output = expert_output + shared_expert_output return expert_output final_hidden_states = torch.zeros( (batch_size * sequence_length, hidden_dim), dtype=hidden_states.dtype, device=hidden_states.device ) if False: # cpp impl seqlen, topk = selected_experts.shape if seqlen == 1: expert_idx = int(selected_experts[0, 0]) scale = float(routing_weights[0, 0]) output = self.experts[expert_idx](hidden_states) * scale for i in range(1, topk): expert_idx = int(selected_experts[0, i]) scale = float(routing_weights[0, i]) output += self.experts[expert_idx](hidden_states) * scale return output hss = torch.split(hidden_states, 1) expertWorks = [[] for i in range(self.num_experts)] for i in range(seqlen): for j in range(topk): expert_idx = int(selected_experts[i, j]) scale = float(routing_weights[i, j]) expertWorks[expert_idx].append((i, scale)) for i in range(self.num_experts): if len(expertWorks[i]) == 0: continue input_hs = [] for token_id, scale in expertWorks[i]: input_hs.append(hss[token_id]) output_hs = self.experts[i](torch.concat(input_hs)) output_hss = torch.split(output_hs, 1) for j in range(len(expertWorks[i])): token_id, scale = expertWorks[i][j] scale_hs = output_hss[j] * scale final_hidden_states[token_id] += scale_hs.squeeze(0) return final_hidden_states # One hot encode the selected experts to create an expert mask # this will be used to easily index which expert is going to be sollicitated expert_mask = F.one_hot(selected_experts, num_classes=self.num_experts).permute(2, 1, 0) expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero() # Loop over all available experts in the model and perform the computation on each expert # for expert_idx in range(self.num_experts): for expert_idx in expert_hit: expert_idx = expert_idx[0] idx, top_x = torch.where(expert_mask[expert_idx]) expert_layer = self.experts[expert_idx] # Index the correct hidden states and compute the expert hidden state for # the current expert. We need to make sure to multiply the output hidden # states by `routing_weights` on the corresponding tokens (top-1 and top-2) current_state = hidden_states[None, top_x].reshape(-1, hidden_dim) current_hidden_states = expert_layer(current_state) * routing_weights[top_x, idx, None] # However `index_add_` only support torch tensors for indexing so we'll use # the `top_x` tensor here. final_hidden_states.index_add_(0, top_x, current_hidden_states.to(hidden_states.dtype)) final_hidden_states = final_hidden_states.reshape(batch_size, sequence_length, hidden_dim) if shared_expert_output is not None: final_hidden_states = final_hidden_states + shared_expert_output return final_hidden_states class Decoder(torch.nn.Module): def __init__(self, decoder, layer_id, config, rotary=None, mapper=None): super().__init__() if rotary is None: rotary = config.rotary if mapper is None: mapper = config.model_map ModelMapper.do_map(self, decoder, mapper['decoder']) if 'mlp' in mapper and hasattr(self.mlp, 'experts'): self.mlp = Mlp(self.mlp, mapper, layer_id) # gemma4 MoE: router and experts are at decoder layer level (parallel to dense MLP) self.has_gemma4_moe = hasattr(self, 'experts') and self.experts is not None if self.has_gemma4_moe: original_experts = self.experts num_experts = original_experts.num_experts hidden_size = original_experts.hidden_dim expert_dim = original_experts.intermediate_dim act_fn = original_experts.act_fn # Refactor 3D Parameter experts into ModuleList of Qwen3Expert new_experts_list = torch.nn.ModuleList() for i in range(num_experts): expert_mlp = Qwen3Expert(hidden_size, expert_dim, act_fn) expert_mlp.gate_up_proj_linear.weight.data = original_experts.gate_up_proj.data[i] expert_mlp.down_proj_linear.weight.data = original_experts.down_proj.data[i] new_experts_list.append(expert_mlp) self.experts = new_experts_list # Extract gate Linear from router (router has norm+scale+proj+per_expert_scale) self.moe_gate = torch.nn.Linear(hidden_size, num_experts, bias=False) self.moe_gate.weight.data = self.router.proj.weight.data self.moe_router_norm = self.router.norm self.moe_router_scale = self.router.scale.data self.moe_router_scalar_root = self.router.scalar_root_size self.moe_per_expert_scale = self.router.per_expert_scale.data self.moe_num_experts = num_experts self.moe_top_k = config.origin_config.text_config.top_k_experts self.custom_moe = MoE(num_experts, self.moe_top_k, layer_id) self.export_moe = False del self.router self.layer_type = 'full_attention' if hasattr(self, 'self_attn') and self.self_attn is not None: self.self_attn = Attention(self.self_attn, layer_id, config, rotary, mapper) if hasattr(self, 'linear_attn') and self.linear_attn is not None: self.self_attn = create_linear_attention(self.linear_attn, layer_id, config, rotary, mapper) self.layer_type = 'linear_attention' self.hidden_size = config.hidden_size if hasattr(config, 'num_hidden_layers'): # minicpm self.num_hidden_layers = config.num_hidden_layers # chatglm self.alpha = (2 * config.num_hidden_layers) ** 0.5 if config.model_type == 'chatglm' else 1.0 def forward( self, hidden_states: torch.Tensor, rotary_pos_emb: Optional[torch.Tensor] = None, attention_mask: Optional[torch.Tensor] = None, ) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]: hidden_states = hidden_states.view(1, -1, self.hidden_size) residual = hidden_states hidden_states = self.input_layernorm(hidden_states) norm_hidden_states = hidden_states # Self Attention or Linear Attention if self.layer_type == 'full_attention': hidden_states = self.self_attn( hidden_states=hidden_states, rotary_pos_emb=rotary_pos_emb, attention_mask=attention_mask, ) elif self.layer_type == 'linear_attention': hidden_states = self.self_attn( hidden_states=hidden_states, attention_mask=attention_mask, ) # Fully Connected if not hasattr(self, 'post_attention_layernorm'): # phi feed_forward_hidden_states = self.mlp(norm_hidden_states) hidden_states = hidden_states + feed_forward_hidden_states + residual elif hasattr(self, 'alpha') and self.alpha != 1.0: # chatglm-6b hidden_states = norm_hidden_states * self.alpha + hidden_states mlp_input = self.post_attention_layernorm(hidden_states) mlp_output = self.mlp(mlp_input) hidden_states = mlp_input * self.alpha + mlp_output elif hasattr(self, 'pre_feedforward_layernorm'): # gemma2 / gemma4 hidden_states = self.post_attention_layernorm(hidden_states) hidden_states = residual + hidden_states residual = hidden_states hidden_states = self.pre_feedforward_layernorm(hidden_states) hidden_states = self.mlp(hidden_states) if self.has_gemma4_moe: # gemma4 MoE: dense MLP + MoE experts in parallel mlp_output = self.post_feedforward_layernorm_1(hidden_states) # Router uses residual (pre-MLP hidden states) router_input = residual.reshape(-1, residual.shape[-1]) # Routing: norm -> scale -> proj -> softmax -> topk -> normalize -> per_expert_scale normed = self.moe_router_norm(router_input) normed = normed * self.moe_router_scale * self.moe_router_scalar_root router_logits = self.moe_gate(normed) routing_weights = F.softmax(router_logits, dim=-1, dtype=torch.float) routing_weights, selected_experts = torch.topk(routing_weights, self.moe_top_k, dim=-1) routing_weights = routing_weights / (routing_weights.sum(dim=-1, keepdim=True) + 1e-6) routing_weights = (routing_weights * self.moe_per_expert_scale[selected_experts]).to(router_input.dtype) if self.export_moe: expert_input = self.pre_feedforward_layernorm_2(router_input) expert_output = self.custom_moe(expert_input, routing_weights, selected_experts) else: # Expert computation expert_input = self.pre_feedforward_layernorm_2(router_input) batch_size, sequence_length = residual.shape[0], residual.shape[1] hidden_dim = residual.shape[-1] expert_output = torch.zeros_like(router_input) expert_mask = F.one_hot(selected_experts, num_classes=self.moe_num_experts).permute(2, 1, 0) expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero() for expert_idx in expert_hit: expert_idx = expert_idx[0] idx, top_x = torch.where(expert_mask[expert_idx]) current_state = expert_input[top_x] current_hidden = self.experts[expert_idx](current_state) * routing_weights[top_x, idx, None] expert_output.index_add_(0, top_x, current_hidden.to(expert_output.dtype)) expert_output = expert_output.reshape(residual.shape) expert_output = self.post_feedforward_layernorm_2(expert_output) hidden_states = mlp_output + expert_output hidden_states = self.post_feedforward_layernorm(hidden_states) hidden_states = residual + hidden_states elif hasattr(self, 'scale_depth'): # minicpm hidden_states = residual + hidden_states * (self.scale_depth / math.sqrt(self.num_hidden_layers)) residual = hidden_states hidden_states = self.post_attention_layernorm(hidden_states) hidden_states = self.mlp(hidden_states) hidden_states = residual + hidden_states * (self.scale_depth / math.sqrt(self.num_hidden_layers)) else: # general hidden_states = residual + hidden_states residual = hidden_states hidden_states = self.post_attention_layernorm(hidden_states) hidden_states = self.mlp(hidden_states) hidden_states = residual + hidden_states # gemma4 PLE (Per-Layer Embeddings) if hasattr(self, 'per_layer_input_gate') and self.per_layer_input_gate is not None: per_layer_input = getattr(self, '_per_layer_input', None) if per_layer_input is not None: residual = hidden_states hidden_states = self.per_layer_input_gate(hidden_states) hidden_states = self.act_fn(hidden_states) hidden_states = hidden_states * per_layer_input hidden_states = self.per_layer_projection(hidden_states) hidden_states = self.post_per_layer_input_norm(hidden_states) hidden_states = residual + hidden_states # gemma4 layer_scalar if hasattr(self, 'layer_scalar') and self.layer_scalar is not None: hidden_states = hidden_states * self.layer_scalar return hidden_states class Lm(torch.nn.Module): def __init__(self, lm_, final_logit_softcapping=None): super().__init__() self.lm = lm_ self.final_logit_softcapping = final_logit_softcapping def forward(self, hidden_states): m_logits = self.lm(hidden_states) if self.final_logit_softcapping is not None: m_logits = m_logits / self.final_logit_softcapping m_logits = torch.tanh(m_logits) m_logits = m_logits * self.final_logit_softcapping return m_logits