"""Task 38 · sigmoid_gate_topk_renorm MoE 路由门算子 (DeepSeek-V2/V3 / Qwen3-MoE 风格) - 3-kernel Triton 实现: K1 (_sigmoid_bias_kernel) : sel = sigmoid(routed) + bias K2 (_topk_kernel) : indices = topk(sel, k) (自写, 迭代 tl.argmax) K3 (_gate_finalize_kernel): gather 原始 routed → cat shared → sigmoid → renorm → scale → split 输出 国产 NPU 套路全套 (来自 D245 验证): - input 指针 cast 到 int16/int32 (防 NaN 转换) - stride / pid 用 int64 cast (防 overflow) - with torch.get_device_module(x.device).device(x.device): 切设备 - enable_fp_fusion=False, num_warps=4 - 不用 tl.sigmoid, 用 1.0 / (1.0 + tl.exp(-x)) """ import torch import triton import triton.language as tl # ============================================================ # Kernel 1: sel = sigmoid(routed) + bias # ============================================================ @triton.jit def _sigmoid_bias_kernel( routed_ptr, # [T, N] fp16/bf16 bias_ptr, # [N] fp32 sel_ptr, # [T, N] fp32 (中间 buffer) N, stride_t, # int (T 维度 stride) BLOCK_N: tl.constexpr, ): # input 指针 cast 防 NaN if routed_ptr.dtype.element_ty.primitive_bitwidth == 16: routed_ptr = routed_ptr.to(tl.pointer_type(tl.int16)) pid_t = tl.program_id(0).to(tl.int64) pid_n = tl.program_id(1).to(tl.int64) offs = pid_n * BLOCK_N + tl.arange(0, BLOCK_N).to(tl.int64) mask = offs < N x = tl.load(routed_ptr + pid_t * stride_t + offs, mask=mask, other=0).to(tl.float32) b = tl.load(bias_ptr + offs, mask=mask, other=0.0) # 不用 tl.sigmoid (国产 NPU 不支持), 用 1/(1+exp(-x)) sig = 1.0 / (1.0 + tl.exp(-x)) sel = sig + b tl.store(sel_ptr + pid_t * N + offs, sel, mask=mask) # ============================================================ # Kernel 2: top-k 选 indices (自写, 迭代 argmax) # ============================================================ @triton.jit def _topk_kernel( sel_ptr, # [T, N] fp32 (来自 K1) idx_ptr, # [T, k] int32 N, K: tl.constexpr, BLOCK_N: tl.constexpr, ): pid = tl.program_id(0).to(tl.int64) offs = tl.arange(0, BLOCK_N) mask = offs < N sel = tl.load(sel_ptr + pid * N + offs, mask=mask, other=-float('inf')) NEG_INF: tl.constexpr = float('-inf') for i in tl.static_range(K): # 找最大值的索引 (scalar) idx = tl.argmax(sel, axis=0) # 把 sel[idx] 设为 -inf (避免重复选) is_max = (offs == idx) sel = tl.where(is_max & mask, NEG_INF, sel) # 写 indices tl.store(idx_ptr + pid * K + i, idx.to(tl.int32)) # ============================================================ # Kernel 3: gather + cat + sigmoid + renorm + scale + split # ============================================================ @triton.jit def _gate_finalize_kernel( routed_ptr, # [T, N] fp16/bf16 shared_ptr, # [T, S] fp16/bf16 idx_ptr, # [T, k] int32 route_scale, # float global_scale_ptr, # [1] fp32 routed_w_ptr, # [T, k] output dtype (input dtype) shared_w_ptr, # [T, S] output dtype (input dtype) N, S, stride_routed_t, stride_shared_t, stride_routed_w_t, stride_shared_w_t, K: tl.constexpr, BLOCK_S: tl.constexpr, # next_pow2(S) ): # input 指针 cast 防 NaN, output 不 cast (output 是计算结果, 不需保 NaN bits) if routed_ptr.dtype.element_ty.primitive_bitwidth == 16: routed_ptr = routed_ptr.to(tl.pointer_type(tl.int16)) shared_ptr = shared_ptr.to(tl.pointer_type(tl.int16)) pid = tl.program_id(0).to(tl.int64) # ===== 加载 routed 的 K 个值 (按 indices gather) ===== idx_offs = tl.arange(0, K) indices = tl.load(idx_ptr + pid * K + idx_offs).to(tl.int64) routed_vals = tl.load(routed_ptr + pid * stride_routed_t + indices).to(tl.float32) routed_sigmoid = 1.0 / (1.0 + tl.exp(-routed_vals)) # ===== 加载 shared 的 S 个值 ===== s_offs = tl.arange(0, BLOCK_S) s_mask = s_offs < S shared_vals = tl.load( shared_ptr + pid * stride_shared_t + s_offs, mask=s_mask, other=0 ).to(tl.float32) shared_sigmoid = 1.0 / (1.0 + tl.exp(-shared_vals)) # ===== 计算 sum (routed + shared 一起归一化) ===== sum_routed = tl.sum(routed_sigmoid, axis=0) sum_shared = tl.sum(shared_sigmoid, axis=0) total_sum = sum_routed + sum_shared # ===== scale ===== gs = tl.load(global_scale_ptr) inv = (route_scale * gs) / total_sum # ===== 写 routed_w ===== routed_w = (routed_sigmoid * inv).to(routed_w_ptr.dtype.element_ty) tl.store(routed_w_ptr + pid * stride_routed_w_t + idx_offs, routed_w) # ===== 写 shared_w ===== shared_w = (shared_sigmoid * inv).to(shared_w_ptr.dtype.element_ty) tl.store(shared_w_ptr + pid * stride_shared_w_t + s_offs, shared_w, mask=s_mask) # ============================================================ # Python wrapper # ============================================================ def _next_pow2(x): p = 1 while p < x: p *= 2 return p def sigmoid_gate_topk_renorm(logits, k, n_shared_experts, route_scale, global_scale, bias): T, G = logits.shape N = G - n_shared_experts S = n_shared_experts if logits.ndim != 2: raise ValueError('logits must have shape [T, N+S]') if not isinstance(k, int) or k <= 0 or k > N: raise ValueError(f'k must be 0 < k <= N={N}, got {k}') if not isinstance(n_shared_experts, int) or n_shared_experts < 0 or n_shared_experts > S: raise ValueError(f'n_shared_experts must be 0 <= S={S}, got {n_shared_experts}') if bias.shape != (N,): raise ValueError(f'bias must have shape [{N}], got {tuple(bias.shape)}') if global_scale.numel() != 1: raise ValueError(f'global_scale must be a scalar tensor, got shape {tuple(global_scale.shape)}') if logits.device.type in ('cpu', 'meta', 'mps'): raise RuntimeError('a real Triton accelerator backend is required') routed = logits[:, :N] shared = logits[:, N:] # ===== 中间 buffer ===== sel = torch.empty((T, N), dtype=torch.float32, device=logits.device) indices = torch.empty((T, k), dtype=torch.int32, device=logits.device) BLOCK_N = max(16, _next_pow2(N)) BLOCK_S = max(16, _next_pow2(S)) module = torch.get_device_module(logits.device) with module.device(logits.device): # ===== Kernel 1: sigmoid + bias ===== grid1 = (T, triton.cdiv(N, BLOCK_N)) _sigmoid_bias_kernel[grid1]( routed, bias, sel, N, routed.stride(0), BLOCK_N=BLOCK_N, enable_fp_fusion=False, num_warps=4, ) # ===== Kernel 2: topk ===== grid2 = (T,) _topk_kernel[grid2]( sel, indices, N, K=k, BLOCK_N=BLOCK_N, enable_fp_fusion=False, num_warps=4, ) # ===== Kernel 3: gate finalize ===== routed_w = torch.empty((T, k), dtype=logits.dtype, device=logits.device) shared_w = torch.empty((T, S), dtype=logits.dtype, device=logits.device) grid3 = (T,) _gate_finalize_kernel[grid3]( routed, shared, indices, route_scale, global_scale, routed_w, shared_w, N, S, routed.stride(0), shared.stride(0), routed_w.stride(0), shared_w.stride(0), K=k, BLOCK_S=BLOCK_S, enable_fp_fusion=False, num_warps=4, ) return routed_w, indices, shared_w reference = sigmoid_gate_topk_renorm