fifth-domain/zz-flagos-s2-2026/d246/sigmoid_gate_topk_renorm.py

212 lines
7.5 KiB
Python
Raw Normal View History

"""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