KernelBench mega · RTX PRO 6000

Kimi-Linear Decode Kimi K3 (1M)

9.79×geomean speedup across shapes

manually audited: clean

Clean, genuine single-launch persistent Triton megakernel at 9.7846x geomean decode speedup (0.747/0.852/0.975 ms/tok vs baseline 7.615/8.304/9.199 at ctx 2048/8192/16384). The entire per-token forward -- 3 KDA blocks (fused int4-dequant split-K GEMVs for q/k/v/g sharing one x load, beta projection, short conv with a double-buffered conv-window ping-pong, gated-delta S recurrence with per-head fuzzy start flags, o_proj+residual, router/top-8 MoE with shared expert), 1 MLA block (q/kv_a projections, absorb-identity attention over the compressed bf16 latent cache via head-grouped online-softmax flash decode with adaptive chunking and in-kernel cache append/RoPE, kv_b value projection, o_proj, MoE), plus both RMSNorms per block -- executes as ONE Triton kernel launch per step. Stage ordering uses atomic item counters with acquire/release semantics and nanosleep-throttled polling; a warmup-based occupancy probe picks the CTA count once. int4 weights are repacked once into flat uint8/f32 regions and dequantized in-register inside every GEMV (no bf16 weight materialization). Weight flattening is cached across steps but invalidated by a register_load_state_dict_post_hook, so fresh check.py seeds re-materialize; no output memoization, no input-identity keying, no data_ptr dispatch in the final solution. A KIMI_DEBUG_EAGER env flag selects a slow eager oracle used only for the agent's own debugging; grading ran the megakernel path.

harnesskinetic-claude
Kernel source (redacted)
"""Kimi-Linear W4A16 hybrid decode — single-launch persistent megakernel.

The entire per-token forward (3 KDA + 1 MLA attention layers, 4 MoE FFNs,
every int4 dequant-GEMV, the short causal convs, the KDA recurrent-state
update, the MLA latent-cache attention via the absorb identity, the MoE
router + top-8 + shared expert GEMVs, both RMSNorms per block, residuals, and
all state/cache updates) is fused into ONE Triton kernel launch per step().
No CUDA graphs, no torch.compile, no per-op kernel loop — one persistent
co-resident grid executes ~20 sequential stages per token.

Design (profiling-driven):
  * int4 weights are repacked once into one flat uint8 region (group-128
    asymmetric scales/zeros + dense weights in a flat fp32 region), streamed
    exactly once per token. Dequant is fused into every GEMV; no bf16 weight
    ever lives in DRAM (the whole bandwidth advantage of W4A16 kept).
  * Stages synchronize via item counters (release/acquire, gpu scope): each
    CTA spins on the producer counter, processes grid-strided items, bumps
    its own counter. Chain-ordered DAG => deadlock-free. High-value stages
    also use per-head fuzzy flags so a head's KDA recurrence starts as soon
    as its panels land rather than waiting for the full grid.
  * GEMVs are split-K into ~3-group-deep items (batch-1 decode is
    latency-bound, not bandwidth-bound: long dependent load chains hurt),
    writing fp32 partials; nonlinearities (bf16 rounds/silu/softmax) are
    applied by consumers AFTER summing partials, preserving ref rounding.
    q/k/v and gate/up GEMVs are fused into shared-x items for 2-3x load
    issue density per latency window.
  * RMSNorms precompute once per norm site into a bf16 xn scratch; MoE
    h=silu(g)*u precomputes once per block; the MoE tail is one weighted
    reduce.
  * MLA decode uses the absorb identity (scores = (W_k^T q_nope).c_kv +
    q_rope.k_rope; out = (sum_l p_l c_kv_l) W_v^T), so the growing cache
    stays compressed bf16 latents, read ~once per token via a head-grouped
    (4 heads/tile) online-softmax flash pass with adaptive chunking
    (64/128/256-row chunks by context length).
"""

from __future__ import annotations

import os

# Triton >=3.2 rejects plain-int module globals referenced inside @triton.jit;
# explicitly allow reading frozen layout constants (scratch offsets, strides)
# at trace time (documented escape hatch; avoids threading dozens of offsets
# through every kernel argument list).
os.environ.setdefault("TRITON_ALLOW_NON_CONSTEXPR_GLOBALS", "1")

# Triton's on-disk cache does not content-hash globals read under the flag
# above; stale kernels from older sources can be reused. Pin the cache root
# to this file's content hash so behavior is deterministic per source.
import hashlib as _hl
import tempfile as _tf
_fp = _hl.sha256(__file__.encode()).hexdigest()[:12]
os.environ.setdefault("TRITON_CACHE_DIR", os.path.join(
    _tf.gettempdir(), f"triton_kimi_cache_{_fp}"))

import torch
import torch.nn as nn
import torch.nn.functional as F
import triton
import triton.language as tl

OP_TYPE = "kimi_linear_w4a16_decode"
EPS = 1.0e-6
GROUP_SIZE = 128

_NUM_WARPS = int(os.environ.get("KIMI_NW", "8"))
_NCTA_REQ = int(os.environ.get("KIMI_NCTA", "188"))
_DEBUG_EAGER = os.environ.get("KIMI_DEBUG_EAGER", "") == "1"
_TIMING = int(os.environ.get("KIMI_TIMING", "0"))
_SLEEPNS = int(os.environ.get("KIMI_SLEEPNS", "400"))

_ACHUNK = 256          # cache rows per attention work item
_ACH_MAX = 68          # max attention chunks (~17.4k ctx)
_COPY_R = 512
_NSM = 188

# --------------------------------------------------------------------------- #
# scratch (fp32) element offsets
# --------------------------------------------------------------------------- #
SC_XA = 0
SC_XB = 2304
SC_QP = 4608                    # q/k/v/g partials [kind 4][split 3][4096]
SC_BETA = SC_QP + 6 * 4 * 4096          # 53760  (9, 32)
SC_OPRP = SC_BETA + 288                 # 54048  (4, 2304)
SC_LOGIT = SC_OPRP + 8 * 2304           # 63264  (9, 64)
SC_GUP = SC_LOGIT + 576                 # 63840  [kind 2][slot 9][split 3][1024]
SC_DNP = SC_GUP + 2 * 9 * 6 * 1024      # 119136 (2, 9, 2304)
SC_HX = SC_DNP + 2 * 9 * 2304           # h = silu(g)*u (9, 1024)
SC_ORA = SC_HX + 9 * 1024               # 169824 (4096)
SC_QABS = SC_ORA + 4096                 # 164704 (16384)
SC_PVF = SC_QABS + 16384                # 181088 (16384)
SC_QROPE = SC_PVF + 16384               # 197472 (2048)
SC_KVP = SC_QROPE + 2048                # 199520 (3, 576)
SC_MQP = SC_KVP + 3 * 576               # 201248 (3, 6144)
SC_ATT = SC_MQP + 3 * 6144              # 219680
ATT_STRIDE = 514
SC_RIDX = SC_ATT + _ACH_MAX * 32 * ATT_STRIDE       # 8 i32 expert ids
SC_RW = SC_RIDX + 16                                # 8 f32 route weights
SC_DXN = SC_RW + 16
SC_SHAD = SC_DXN + 2304
SC_SHADL = SC_SHAD + 4 * 2304
SC_LG0 = SC_SHADL + 4 * 576
SC_GSUM = SC_LG0 + 576
SC_XN = SC_GSUM + 18 * 1024
SC_TOTAL = SC_XN + 2304

WIN_PP_STRIDE = 3 * 3 * 4096
WIN_LAYER_STRIDE = 2 * WIN_PP_STRIDE
WIN_TOTAL = 3 * WIN_LAYER_STRIDE


_CT_COPY = 46
N_SLOTS = 48
HQF_BASE = 64          # per-launch per-head flags (3 blocks x 32)
BETA_FLAG = 96          # beta-done slots (3 blocks)
XR_MAX = 192

# per-block weight field ids (par index = b*40 + fid; f32 mirror at +256)
TF_QW, TF_QS, TF_QZ = 0, 1, 2
TF_KW, TF_KS, TF_KZ = 3, 4, 5
TF_VW, TF_VS, TF_VZ = 6, 7, 8
TF_GW, TF_GS, TF_GZ = 9, 10, 11
TF_OW, TF_OS, TF_OZ = 12, 13, 14
TF_BETA, TF_CONV, TF_AN, TF_MN, TF_ROUT = 15, 16, 17, 18, 19
TF_EXW, TF_EXSZ = 20, 21
TF_SGW, TF_SUW, TF_SDW = 22, 23, 24
TF_SGS, TF_SUS, TF_SDS = 25, 26, 27
TF_SGZ, TF_SUZ, TF_SDZ = 28, 29, 30
MF_QW, MF_QS, MF_QZ = 0, 1, 2
MF_KVAW, MF_KVAS, MF_KVAZ = 3, 4, 5
MF_KVBW, MF_KVBS, MF_KVBZ = 6, 7, 8
MF_OW, MF_OS, MF_OZ = 9, 10, 11
MF_AN, MF_MN, MF_ROUT = 12, 13, 14
MF_EXW, MF_EXSZ = 15, 16
MF_SGW, MF_SUW, MF_SDW = 17, 18, 19
MF_SGS, MF_SUS, MF_SDS = 20, 21, 22
MF_SGZ, MF_SUZ, MF_SDZ = 23, 24, 25

PAR_LEN = 512
_40 = 40

GW_BYTES = 2304 * 1024 // 2
GS_ELEMS = 18 * 1024
EXB_U8 = 3 * GW_BYTES
EXB_F32 = 6 * GS_ELEMS


# --------------------------------------------------------------------------- #
# module tree — identical structure/names to the reference for state_dict
# --------------------------------------------------------------------------- #
class QuantLinear(nn.Module):
    def __init__(self, in_f: int, out_f: int, group: int = GROUP_SIZE):
        super().__init__()
        self.in_f, self.out_f, self.group = in_f, out_f, group
        ng = in_f // group
        self.register_buffer("w_q", torch.zeros(in_f // 2, out_f, dtype=torch.uint8))
        self.register_buffer("scales", torch.zeros(ng, out_f, dtype=torch.bfloat16))
        self.register_buffer("zeros", torch.zeros(ng, out_f, dtype=torch.bfloat16))


class QuantExperts(nn.Module):
    def __init__(self, n: int, in_f: int, out_f: int, group: int = GROUP_SIZE):
        super().__init__()
        self.n, self.in_f, self.out_f, self.group = n, in_f, out_f, group
        ng = in_f // group
        self.register_buffer("w_q", torch.zeros(n, in_f // 2, out_f, dtype=torch.uint8))
        self.register_buffer("scales", torch.zeros(n, ng, out_f, dtype=torch.bfloat16))
        self.register_buffer("zeros", torch.zeros(n, ng, out_f, dtype=torch.bfloat16))


class KDA(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.cfg = cfg
        H, Dk, d = cfg.kda_heads, cfg.kda_head_dim, cfg.hidden
        self.q_proj = QuantLinear(d, H * Dk, cfg.group)
        self.k_proj = QuantLinear(d, H * Dk, cfg.group)
        self.v_proj = QuantLinear(d, H * Dk, cfg.group)
        self.g_proj = QuantLinear(d, H * Dk, cfg.group)
        self.beta_proj = nn.Linear(d, H, bias=False, dtype=cfg.dtype)
        self.conv_w = nn.Parameter(torch.empty(3, H * Dk, cfg.short_conv, dtype=cfg.dtype))
        self.o_proj = QuantLinear(H * Dk, d, cfg.group)
        self.scale = Dk ** -0.5


class MLA(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.cfg = cfg
        H, d = cfg.mla_heads, cfg.hidden
        self.q_proj = QuantLinear(d, H * (cfg.qk_nope + cfg.qk_rope), cfg.group)
        self.kv_a = QuantLinear(d, cfg.kv_lora + cfg.qk_rope, cfg.group)
        self.kv_b = QuantLinear(cfg.kv_lora, H * (cfg.qk_nope + cfg.v_head), cfg.group)
        self.o_proj = QuantLinear(H * cfg.v_head, d, cfg.group)
        self.scale = (cfg.qk_nope + cfg.qk_rope) ** -0.5


class MoE(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.cfg = cfg
        d, m, E = cfg.hidden, cfg.moe_inter, cfg.n_experts
        self.router = nn.Linear(d, E, bias=False, dtype=cfg.dtype)
        self.gate = QuantExperts(E, d, m, cfg.group)
        self.up = QuantExperts(E, d, m, cfg.group)
        self.down = QuantExperts(E, m, d, cfg.group)
        self.s_gate = QuantExperts(cfg.n_shared, d, m, cfg.group)
        self.s_up = QuantExperts(cfg.n_shared, d, m, cfg.group)
        self.s_down = QuantExperts(cfg.n_shared, m, d, cfg.group)


class Block(nn.Module):
    def __init__(self, cfg, kind: str):
        super().__init__()
        self.kind = kind
        self.attn_norm = nn.Parameter(torch.ones(cfg.hidden, dtype=cfg.dtype))
        self.moe_norm = nn.Parameter(torch.ones(cfg.hidden, dtype=cfg.dtype))
        self.attn = KDA(cfg) if kind == "K" else MLA(cfg)
        self.moe = MoE(cfg)


def _align256(x):
    return (x + 255) // 256 * 256


# --------------------------------------------------------------------------- #
# megakernel helpers
# --------------------------------------------------------------------------- #
@triton.jit
def _wait(ctr, pos, target, dbg, cb, TIMING: tl.constexpr):
    one = tl.arange(0, 1)
    cur = tl.sum(tl.atomic_add(ctr + pos + one, 0, sem="acquire", scope="gpu"))
    if cur < target:
        while tl.sum(tl.load(ctr + pos + one, volatile=True)) < target:
            # throttle polling to keep L2 atomic traffic off the workers
            if _SLEEPNS > 0:
                tl.inline_asm_elementwise("nanosleep.u32 2000; mov.u32 $0, $1;", "=r,r", [one],
                                          dtype=tl.int32, is_pure=False, pack=1)
        tl.atomic_add(ctr + pos + one, 0, sem="acquire", scope="gpu")
    if TIMING:
        tl.atomic_min(dbg + 2 * (pos - cb) + one, tl.inline_asm_elementwise(
            "mov.u64 $0, %globaltimer;", "=l,l", [one], dtype=tl.int64, is_pure=False, pack=1))
    tl.debug_barrier()


@triton.jit
def _bump(ctr, pos, n, pid, NCTA, dbg, cb, TIMING: tl.constexpr):
    tl.debug_barrier()
    if TIMING:
        one = tl.arange(0, 1)
        tl.atomic_max(dbg + 2 * (pos - cb) + 1 + one, tl.inline_asm_elementwise(
            "mov.u64 $0, %globaltimer;", "=l,l", [one], dtype=tl.int64, is_pure=False, pack=1))
    if pid < n:
        cnt = (n - pid + NCTA - 1) // NCTA
        tl.atomic_add(ctr + pos + tl.arange(0, 1), cnt, sem="release", scope="gpu")


@triton.jit
def _xn_make(scr, nx_ptr, x_ptr, nrm, ctr, ctr_base, sflag, pid, ncta, dbg, TIMING: tl.constexpr,
             skip_wait: tl.constexpr):
    # produce normed+bf16-rounded x into SC_XN from raw fp32 x_ptr (2304)
    r16 = tl.arange(0, 16)
    c128 = tl.arange(0, 128)
    rstd = _rstd(x_ptr, 2304)
    for it in range(pid, 9, ncta):
        xk = it * 256 + tl.arange(0, 256)
        xv = tl.load(x_ptr + xk).to(tl.float32)
        nv = tl.load(nrm + xk).to(tl.float32)
        xv = ((xv * rstd) * nv).to(tl.bfloat16).to(tl.float32)
        tl.store(scr + SC_XN + xk, xv)
    if not skip_wait:
        _bump(ctr, ctr_base + sflag, 9, pid, ncta, dbg, ctr_base, TIMING)
        _wait(ctr, ctr_base + sflag, 9, dbg, ctr_base, TIMING)


@triton.jit
def _rstd(x_ptr, K):
    """Wide parallel sum-of-squares for RMSNorm (one or two shot loads)."""
    ssq = 0.0
    r16 = tl.arange(0, 16)
    c128 = tl.arange(0, 128)
    for k0 in range(0, K, 2048):
        idx = k0 + r16[:, None] * 128 + c128[None, :]
        xv = tl.load(x_ptr + idx, mask=idx < K, other=0.0).to(tl.float32)
        ssq += tl.sum(xv * xv)
    return tl.rsqrt(ssq / K + 1e-6)


@triton.jit
def _route(logits_ptr):
    """Rebuild router logits from 9 split partials, softmax, top-8."""
    j = tl.arange(0, 64)
    lg = tl.zeros((64,), dtype=tl.float32)
    for s in tl.static_range(9):
        lg += tl.load(logits_ptr + s * 64 + j)
    lg = lg.to(tl.bfloat16).to(tl.float32)
    m = tl.max(lg)
    e = tl.exp(lg - m)
    p = e / tl.sum(e)
    idxs = tl.zeros((8,), dtype=tl.int32)
    ws = tl.zeros((8,), dtype=tl.float32)
    pw = p
    acc_w = 0.0
    for t in tl.static_range(8):
        i = tl.argmax(pw, 0)
        v = tl.max(pw, 0)
        acc_w += v
        id8 = tl.arange(0, 8)
        idxs = tl.where(id8 == t, i.to(tl.int32), idxs)
        ws = tl.where(id8 == t, v, ws)
        pw = tl.where(j == i, -1.0, pw)
    return idxs, ws * (2.446 / (acc_w + 1e-9))


@triton.jit
def _moe_in(base, par, slot, EXWF, EXSZF, SGF, SUF, SDF, SGSF, SUSF, SDSF, SGZF, SUZF, SDZF,
            kind, e):
    """Address offsets of expert weight for (slot, kind): kind 0=gate 1=up 2=down."""
    if slot < 8:
        wb = tl.load(par + base + EXWF) + e * (3 * GW_BYTES)
        sb = tl.load(par + 256 + base + EXSZF) + e * (6 * GS_ELEMS)
        if kind == 0:
            wq = wb
            sp = sb
            zp = sb + GS_ELEMS
        elif kind == 1:
            wq = wb + GW_BYTES
            sp = sb + 2 * GS_ELEMS
            zp = sb + 3 * GS_ELEMS
        else:
            wq = wb + 2 * GW_BYTES
            sp = sb + 4 * GS_ELEMS
            zp = sb + 5 * GS_ELEMS
    else:
        if kind == 0:
            wq = tl.load(par + base + SGF)
            sp = tl.load(par + 256 + base + SGSF)
            zp = tl.load(par + 256 + base + SGZF)
        elif kind == 1:
            wq = tl.load(par + base + SUF)
            sp = tl.load(par + 256 + base + SUSF)
            zp = tl.load(par + 256 + base + SUZF)
        else:
            wq = tl.load(par + base + SDF)
            sp = tl.load(par + 256 + base + SDSF)
            zp = tl.load(par + 256 + base + SDZF)
    return wq, sp, zp


@triton.jit
def _gemv_split(scr, x_ptr, w, s, z, NSTR, N, rstd, nrm, ns, sp, NG0,
                NORMED: tl.constexpr, BN: tl.constexpr):
    """Split-k fused int4 GEMV piece: groups [6*sp, 6*sp+6) of the (K,NSTR) weight.

    Returns fp32 partial accumulator over that group range (no rounding; the
    consumer applies rounding/nonlinearities after summing partials).
    """
    acc = tl.zeros((BN,), dtype=tl.float32)
    for g in range(NG0 * sp, NG0 * sp + NG0):
        sv = tl.load(s + g * N + ns, mask=ns < N, other=0.0).to(tl.float32)
        zv = tl.load(z + g * N + ns, mask=ns < N, other=0.0).to(tl.float32)
        dot = tl.zeros((BN,), dtype=tl.float32)
        sx = 0.0
        for kk in tl.static_range(2):
            k0 = g * 128 + kk * 64
            r32 = tl.arange(0, 32)
            wp = tl.load(w + (k0 // 2 + r32)[:, None] * N + ns[None, :],
                         mask=(ns < N)[None, :], other=0)
            lo = (wp & 0xF).to(tl.float32)
            hi = ((wp >> 4) & 0xF).to(tl.float32)
            xe = tl.load(x_ptr + k0 + 2 * tl.arange(0, 32)).to(tl.float32)
            xo = tl.load(x_ptr + k0 + 1 + 2 * tl.arange(0, 32)).to(tl.float32)
            dot += tl.sum(lo * xe[:, None], 0) + tl.sum(hi * xo[:, None], 0)
            sx += tl.sum(xe) + tl.sum(xo)
        acc += sv * (dot - zv * sx)
    return acc


@triton.jit
def _megakernel(
    wq_g, wf_g, par, scr, win,
    s0_ptr, s1_ptr, s2_ptr,
    cq0_ptr, ck0_ptr, cv0_ptr,
    cq1_ptr, ck1_ptr, cv1_ptr,
    cq2_ptr, ck2_ptr, cv2_ptr,
    ckv_in, kr_in, kvbig, krbig,
    inh_ptr, outh_ptr, ctr, dbg,
    ctr_base, L0, nc, flags, ppflags,
    BN: tl.constexpr, BR: tl.constexpr, TIMING: tl.constexpr,
):
    pid = tl.program_id(0)
    ncta = tl.num_programs(0)
    cold_cache = flags & 1

    wqb = wq_g
    wfb = wf_g
    pb = par
    kda_slo0 = (2 * ((0) * 5 + (0)))

    # ================= KDA blocks =================
    for b in tl.static_range(3):
        XB_IN = SC_XA if b % 2 == 0 else SC_XB
        XB_OUT = SC_XB if b % 2 == 0 else SC_XA
        BASE = b * _40
        x_ptr = scr + XB_IN

        # ---- QKVG (split-k int4 partials + beta) ; block 0 prepends EXT
        if b == 0:
            for it in range(pid, 9, ncta):
                i = it * 256 + tl.arange(0, 256)
                v = tl.load(inh_ptr + i).to(tl.float32)
                tl.store(scr + SC_XA + i, v)
            _bump(ctr, ctr_base + 1, 9, pid, ncta, dbg, ctr_base, TIMING)
            _wait(ctr, ctr_base + 1, 9, dbg, ctr_base, TIMING)
            _xn_make(scr, SC_XN, scr + SC_XA, wfb + tl.load(pb + 256 + BASE + TF_AN), ctr, ctr_base, 1, pid, ncta, dbg, TIMING, False)
        else:
            _wait(ctr, ctr_base + (2 * ((b - 1) * 5 + (4))), 414, dbg, ctr_base, TIMING)
            _xn_make(scr, SC_XN, scr + XB_IN, wfb + tl.load(pb + 256 + BASE + TF_AN), ctr, ctr_base, (2 * ((b) * 5 + (0))) + 1, pid, ncta, dbg, TIMING, False)
        rstd = _rstd(x_ptr, 2304)
        nrm = wfb + tl.load(pb + 256 + BASE + TF_AN)
        NQ = 4096 // BN
        wq0 = wqb + tl.load(pb + BASE + TF_QW)
        sq0 = wfb + tl.load(pb + 256 + BASE + TF_QS)
        zq0 = wfb + tl.load(pb + 256 + BASE + TF_QZ)
        wk0 = wqb + tl.load(pb + BASE + TF_KW)
        sk0 = wfb + tl.load(pb + 256 + BASE + TF_KS)
        zk0 = wfb + tl.load(pb + 256 + BASE + TF_KZ)
        wv0 = wqb + tl.load(pb + BASE + TF_VW)
        sv0 = wfb + tl.load(pb + 256 + BASE + TF_VS)
        zv0 = wfb + tl.load(pb + 256 + BASE + TF_VZ)
        for it in range(pid, NQ * 6, ncta):
            n0 = (it // 6) * BN
            sp = it % 6
            ns = n0 + tl.arange(0, BN)
            aq = tl.zeros((BN,), dtype=tl.float32)
            ak = tl.zeros((BN,), dtype=tl.float32)
            av = tl.zeros((BN,), dtype=tl.float32)
            for g in range(sp * 3, sp * 3 + 3):
                svg = tl.load(sq0 + g * 4096 + ns).to(tl.float32)
                zvg = tl.load(zq0 + g * 4096 + ns).to(tl.float32)
                skg = tl.load(sk0 + g * 4096 + ns).to(tl.float32)
                zkg = tl.load(zk0 + g * 4096 + ns).to(tl.float32)
                svc = tl.load(sv0 + g * 4096 + ns).to(tl.float32)
                zvc = tl.load(zv0 + g * 4096 + ns).to(tl.float32)
                dotq = tl.zeros((BN,), dtype=tl.float32)
                dotk = tl.zeros((BN,), dtype=tl.float32)
                dotv = tl.zeros((BN,), dtype=tl.float32)
                sx = 0.0
                for kk in tl.static_range(2):
                    k0 = g * 128 + kk * 64
                    r32 = tl.arange(0, 32)
                    wq_ = tl.load(wq0 + (k0 // 2 + r32)[:, None] * 4096 + ns[None, :])
                    wk_ = tl.load(wk0 + (k0 // 2 + r32)[:, None] * 4096 + ns[None, :])
                    wv_ = tl.load(wv0 + (k0 // 2 + r32)[:, None] * 4096 + ns[None, :])
                    xe = tl.load(scr + SC_XN + k0 + 2 * tl.arange(0, 32))
                    xo = tl.load(scr + SC_XN + k0 + 1 + 2 * tl.arange(0, 32))
                    loq = (wq_ & 0xF).to(tl.float32)
                    hiq = ((wq_ >> 4) & 0xF).to(tl.float32)
                    lok = (wk_ & 0xF).to(tl.float32)
                    hik = ((wk_ >> 4) & 0xF).to(tl.float32)
                    lov = (wv_ & 0xF).to(tl.float32)
                    hiv = ((wv_ >> 4) & 0xF).to(tl.float32)
                    dotq += tl.sum(loq * xe[:, None], 0) + tl.sum(hiq * xo[:, None], 0)
                    dotk += tl.sum(lok * xe[:, None], 0) + tl.sum(hik * xo[:, None], 0)
                    dotv += tl.sum(lov * xe[:, None], 0) + tl.sum(hiv * xo[:, None], 0)
                    sx += tl.sum(xe) + tl.sum(xo)
                aq += svg * (dotq - zvg * sx)
                ak += skg * (dotk - zkg * sx)
                av += svc * (dotv - zvc * sx)
            tl.store(scr + SC_QP + (0 * 6 + sp) * 4096 + ns, aq)
            tl.store(scr + SC_QP + (1 * 6 + sp) * 4096 + ns, ak)
            tl.store(scr + SC_QP + (2 * 6 + sp) * 4096 + ns, av)
            tl.debug_barrier()
            tl.atomic_add(ctr + ctr_base + HQF_BASE + b * 32 + (it // 6) + tl.arange(0, 1), 1, sem="release", scope="gpu")
        for it in range(pid, NQ * 6, ncta):
            n0 = (it // 6) * BN
            sp = it % 6
            ns = n0 + tl.arange(0, BN)
            wg0 = wqb + tl.load(pb + BASE + TF_GW)
            sg0 = wfb + tl.load(pb + 256 + BASE + TF_GS)
            zg0 = wfb + tl.load(pb + 256 + BASE + TF_GZ)
            acc = _gemv_split(scr, scr + SC_XN, wg0, sg0, zg0, 4096, 4096, rstd, nrm, ns, sp, 3, True, BN)
            tl.store(scr + SC_QP + (3 * 6 + sp) * 4096 + ns, acc)
            tl.debug_barrier()
            tl.atomic_add(ctr + ctr_base + HQF_BASE + b * 32 + (it // 6) + tl.arange(0, 1), 1, sem="release", scope="gpu")
        NGEMV = 4 * NQ * 6
        for it in range(pid, 9, ncta):
            c = it
            bp = wfb + tl.load(pb + 256 + BASE + TF_BETA)
            xk = tl.arange(0, 256)
            xe = tl.load(scr + SC_XN + c * 256 + xk)
            for hb in tl.static_range(4):
                rows = hb * 8 + tl.arange(0, 8)
                wt = tl.load(bp + rows[:, None] * 2304 + c * 256 + xk[None, :]).to(tl.float32)
                part = tl.sum(wt * xe[None, :], 1)
                tl.store(scr + SC_BETA + c * 32 + rows, part)
            tl.debug_barrier()
            tl.atomic_add(ctr + ctr_base + BETA_FLAG + b + tl.arange(0, 1), 1, sem="release", scope="gpu")
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (0))), NGEMV + 9, pid, ncta, dbg, ctr_base, TIMING)

        # ---- REC: conv + recurrence (fuzzy per-head gated)
        warm = (ppflags >> b) & 1
        ppq = (ppflags >> (3 + b)) & 1
        oneff = tl.arange(0, 1)
        S_ptr = s0_ptr if b == 0 else (s1_ptr if b == 1 else s2_ptr)
        cq_in = cq0_ptr if b == 0 else (cq1_ptr if b == 1 else cq2_ptr)
        ck_in = ck0_ptr if b == 0 else (ck1_ptr if b == 1 else ck2_ptr)
        cv_in = cv0_ptr if b == 0 else (cv1_ptr if b == 1 else cv2_ptr)
        convp = wfb + tl.load(pb + 256 + BASE + TF_CONV)
        wsrc = win + b * WIN_LAYER_STRIDE + ppq * WIN_PP_STRIDE
        wdst = win + b * WIN_LAYER_STRIDE + (1 - ppq) * WIN_PP_STRIDE
        TOTR = 128
        for it in range(pid, TOTR, ncta):
            h = it // 4
            tt = it % 4
            curf = tl.sum(tl.atomic_add(ctr + ctr_base + HQF_BASE + b * 32 + h + oneff, 0, sem="acquire", scope="gpu"))
            while curf < 12:
                tl.inline_asm_elementwise("nanosleep.u32 400; mov.u32 $0, $1;", "=r,r", [oneff],
                                          dtype=tl.int32, is_pure=False, pack=1)
                curf = tl.sum(tl.atomic_add(ctr + ctr_base + HQF_BASE + b * 32 + h + oneff, 0, sem="acquire", scope="gpu"))
            curb = tl.sum(tl.atomic_add(ctr + ctr_base + BETA_FLAG + b + oneff, 0, sem="acquire", scope="gpu"))
            while curb < 9:
                tl.inline_asm_elementwise("nanosleep.u32 400; mov.u32 $0, $1;", "=r,r", [oneff],
                                          dtype=tl.int32, is_pure=False, pack=1)
                curb = tl.sum(tl.atomic_add(ctr + ctr_base + BETA_FLAG + b + oneff, 0, sem="acquire", scope="gpu"))
            tl.debug_barrier()
            dv0 = tt * 32
            ch0 = h * 128
            dk = tl.arange(0, 128)
            dr = tl.arange(0, 32)
            cwq0 = tl.load(convp + 0 * 16384 + (ch0 + dk) * 4 + 0)
            cwq1 = tl.load(convp + 0 * 16384 + (ch0 + dk) * 4 + 1)
            cwq2 = tl.load(convp + 0 * 16384 + (ch0 + dk) * 4 + 2)
            cwq3 = tl.load(convp + 0 * 16384 + (ch0 + dk) * 4 + 3)
            cwk0 = tl.load(convp + 1 * 16384 + (ch0 + dk) * 4 + 0)
            cwk1 = tl.load(convp + 1 * 16384 + (ch0 + dk) * 4 + 1)
            cwk2 = tl.load(convp + 1 * 16384 + (ch0 + dk) * 4 + 2)
            cwk3 = tl.load(convp + 1 * 16384 + (ch0 + dk) * 4 + 3)
            cwv0 = tl.load(convp + 2 * 16384 + (ch0 + dv0 + dr) * 4 + 0)
            cwv1 = tl.load(convp + 2 * 16384 + (ch0 + dv0 + dr) * 4 + 1)
            cwv2 = tl.load(convp + 2 * 16384 + (ch0 + dv0 + dr) * 4 + 2)
            cwv3 = tl.load(convp + 2 * 16384 + (ch0 + dv0 + dr) * 4 + 3)
            # raw projections = 3 split partial sums, bf16 rounded (ref order)
            qraw = (tl.load(scr + SC_QP + (0 * 6 + 0) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (0 * 6 + 1) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (0 * 6 + 2) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (0 * 6 + 3) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (0 * 6 + 4) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (0 * 6 + 5) * 4096 + ch0 + dk)).to(tl.bfloat16).to(tl.float32)
            kraw = (tl.load(scr + SC_QP + (1 * 6 + 0) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (1 * 6 + 1) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (1 * 6 + 2) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (1 * 6 + 3) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (1 * 6 + 4) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (1 * 6 + 5) * 4096 + ch0 + dk)).to(tl.bfloat16).to(tl.float32)
            graw = (tl.load(scr + SC_QP + (3 * 6 + 0) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (3 * 6 + 1) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (3 * 6 + 2) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (3 * 6 + 3) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (3 * 6 + 4) * 4096 + ch0 + dk)
                    + tl.load(scr + SC_QP + (3 * 6 + 5) * 4096 + ch0 + dk)).to(tl.bfloat16).to(tl.float32)
            vraw = (tl.load(scr + SC_QP + (2 * 6 + 0) * 4096 + ch0 + dv0 + dr)
                    + tl.load(scr + SC_QP + (2 * 6 + 1) * 4096 + ch0 + dv0 + dr)
                    + tl.load(scr + SC_QP + (2 * 6 + 2) * 4096 + ch0 + dv0 + dr)
                    + tl.load(scr + SC_QP + (2 * 6 + 3) * 4096 + ch0 + dv0 + dr)
                    + tl.load(scr + SC_QP + (2 * 6 + 4) * 4096 + ch0 + dv0 + dr)
                    + tl.load(scr + SC_QP + (2 * 6 + 5) * 4096 + ch0 + dv0 + dr)).to(tl.bfloat16).to(tl.float32)
            if warm == 1:
                qw0 = tl.load(wsrc + 0 * 12288 + 0 * 4096 + ch0 + dk)
                qw1 = tl.load(wsrc + 0 * 12288 + 1 * 4096 + ch0 + dk)
                qw2 = tl.load(wsrc + 0 * 12288 + 2 * 4096 + ch0 + dk)
                kw0 = tl.load(wsrc + 1 * 12288 + 0 * 4096 + ch0 + dk)
                kw1 = tl.load(wsrc + 1 * 12288 + 1 * 4096 + ch0 + dk)
                kw2 = tl.load(wsrc + 1 * 12288 + 2 * 4096 + ch0 + dk)
                vw0 = tl.load(wsrc + 2 * 12288 + 0 * 4096 + ch0 + dv0 + dr)
                vw1 = tl.load(wsrc + 2 * 12288 + 1 * 4096 + ch0 + dv0 + dr)
                vw2 = tl.load(wsrc + 2 * 12288 + 2 * 4096 + ch0 + dv0 + dr)
                kqw1 = tl.load(wsrc + 0 * 12288 + 1 * 4096 + ch0 + dv0 + dr)
                kqw2 = tl.load(wsrc + 0 * 12288 + 2 * 4096 + ch0 + dv0 + dr)
                kkw1 = tl.load(wsrc + 1 * 12288 + 1 * 4096 + ch0 + dv0 + dr)
                kkw2 = tl.load(wsrc + 1 * 12288 + 2 * 4096 + ch0 + dv0 + dr)
                kvw1 = vw1
                kvw2 = vw2
            else:
                qw0 = tl.load(cq_in + 0 * 4096 + ch0 + dk).to(tl.float32)
                qw1 = tl.load(cq_in + 1 * 4096 + ch0 + dk).to(tl.float32)
                qw2 = tl.load(cq_in + 2 * 4096 + ch0 + dk).to(tl.float32)
                kw0 = tl.load(ck_in + 0 * 4096 + ch0 + dk).to(tl.float32)
                kw1 = tl.load(ck_in + 1 * 4096 + ch0 + dk).to(tl.float32)
                kw2 = tl.load(ck_in + 2 * 4096 + ch0 + dk).to(tl.float32)
                vw0 = tl.load(cv_in + 0 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                vw1 = tl.load(cv_in + 1 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                vw2 = tl.load(cv_in + 2 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                kqw1 = tl.load(cq_in + 1 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                kqw2 = tl.load(cq_in + 2 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                kkw1 = tl.load(ck_in + 1 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                kkw2 = tl.load(ck_in + 2 * 4096 + ch0 + dv0 + dr).to(tl.float32)
                kvw1 = vw1
                kvw2 = vw2
            # window dst shift (own 32 channels only, all 3 windows)
            tl.store(wdst + 0 * 12288 + 0 * 4096 + ch0 + dv0 + dr, kqw1)
            tl.store(wdst + 0 * 12288 + 1 * 4096 + ch0 + dv0 + dr, kqw2)
            tl.store(wdst + 0 * 12288 + 2 * 4096 + ch0 + dv0 + dr, vraw)
            tl.store(wdst + 1 * 12288 + 0 * 4096 + ch0 + dv0 + dr, kkw1)
            tl.store(wdst + 1 * 12288 + 1 * 4096 + ch0 + dv0 + dr, kkw2)
            tl.store(wdst + 1 * 12288 + 2 * 4096 + ch0 + dv0 + dr,
                     (tl.load(scr + SC_QP + (1 * 6 + 0) * 4096 + ch0 + dv0 + dr)
                      + tl.load(scr + SC_QP + (1 * 6 + 1) * 4096 + ch0 + dv0 + dr)
                      + tl.load(scr + SC_QP + (1 * 6 + 2) * 4096 + ch0 + dv0 + dr)
                      + tl.load(scr + SC_QP + (1 * 6 + 3) * 4096 + ch0 + dv0 + dr)
                      + tl.load(scr + SC_QP + (1 * 6 + 4) * 4096 + ch0 + dv0 + dr)
                      + tl.load(scr + SC_QP + (1 * 6 + 5) * 4096 + ch0 + dv0 + dr)).to(tl.bfloat16).to(tl.float32))
            tl.store(wdst + 2 * 12288 + 0 * 4096 + ch0 + dv0 + dr, kvw1)
            tl.store(wdst + 2 * 12288 + 1 * 4096 + ch0 + dv0 + dr, kvw2)
            tl.store(wdst + 2 * 12288 + 2 * 4096 + ch0 + dv0 + dr, vraw)
            # conv = sum over 4 rows, silu, bf16 round (ref rounds conv outputs)
            qacc = qw0 * cwq0 + qw1 * cwq1 + qw2 * cwq2 + qraw * cwq3
            kacc = kw0 * cwk0 + kw1 * cwk1 + kw2 * cwk2 + kraw * cwk3
            vacc = vw0 * cwv0 + vw1 * cwv1 + vw2 * cwv2 + vraw * cwv3
            qs = (qacc * tl.sigmoid(qacc)).to(tl.bfloat16).to(tl.float32) * 0.08838834764831845
            ks = (kacc * tl.sigmoid(kacc)).to(tl.bfloat16).to(tl.float32)
            vs = (vacc * tl.sigmoid(vacc)).to(tl.bfloat16).to(tl.float32)
            decay = tl.sigmoid(-graw)
            beta = tl.load(scr + SC_BETA + h)
            for jb in tl.static_range(1, 9):
                beta += tl.load(scr + SC_BETA + jb * 32 + h)
            beta = tl.sigmoid(beta.to(tl.bfloat16).to(tl.float32))
            sptr = S_ptr + h * 128 * 128 + dk[:, None] * 128 + dv0 + dr[None, :]
            Sb = tl.load(sptr)
            Sb = Sb * decay[:, None]
            pred = tl.sum(Sb * ks[:, None], 0)
            err = vs - pred
            Sb = Sb + beta * ks[:, None] * err[None, :]
            tl.store(sptr, Sb)
            o = tl.sum(Sb * qs[:, None], 0)
            tl.store(scr + SC_ORA + ch0 + dv0 + dr, o.to(tl.bfloat16).to(tl.float32))
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (1))), TOTR, pid, ncta, dbg, ctr_base, TIMING)

        # ---- OPROJ: split-k partials + reduce(residual)
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (1))), TOTR, dbg, ctr_base, TIMING)
        w_o = wqb + tl.load(pb + BASE + TF_OW)
        s_o = wfb + tl.load(pb + 256 + BASE + TF_OS)
        z_o = wfb + tl.load(pb + 256 + BASE + TF_OZ)
        NOP = (2304 // BN) * 4
        for it in range(pid, NOP, ncta):
            n0 = (it // 4) * BN
            sp = it % 4
            ns = n0 + tl.arange(0, BN)
            acc = _gemv_split(scr, scr + SC_ORA, w_o, s_o, z_o, 2304, 2304, 0.0, wfb, ns, sp, 8, False, BN)
            tl.store(scr + SC_OPRP + sp * 2304 + ns, acc)
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (2))) + 1, NOP, pid, ncta, dbg, ctr_base, TIMING)
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (2))) + 1, NOP, dbg, ctr_base, TIMING)
        NQO = 2304 // BN
        for it in range(pid, NQO, ncta):
            ns = it * BN + tl.arange(0, BN)
            a4 = tl.load(scr + SC_OPRP + 0 * 2304 + ns)
            a4 += tl.load(scr + SC_OPRP + 1 * 2304 + ns)
            a4 += tl.load(scr + SC_OPRP + 2 * 2304 + ns)
            a4 += tl.load(scr + SC_OPRP + 3 * 2304 + ns)
            cur = tl.load(scr + XB_IN + ns)
            tl.store(scr + XB_OUT + ns, (cur + a4).to(tl.bfloat16).to(tl.float32))
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (2))) + 2, NQO, pid, ncta, dbg, ctr_base, TIMING)
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (2))) + 2, NQO, dbg, ctr_base, TIMING)

        # ---- router partials (folded into OPROJ stage)
        _xn_make(scr, SC_XN, scr + XB_OUT, wfb + tl.load(pb + 256 + BASE + TF_MN), ctr, ctr_base, (2 * ((b) * 5 + (2))) + 2, pid, ncta, dbg, TIMING, False)
        mptr = scr + XB_OUT
        rstd_m = _rstd(mptr, 2304)
        nrmm = wfb + tl.load(pb + 256 + BASE + TF_MN)
        rwp = wfb + tl.load(pb + 256 + BASE + TF_ROUT)
        if b == 0:
            for it in range(pid, 9, ncta):
                kc9 = it * 256
                xk9 = kc9 + tl.arange(0, 256)
                x9 = tl.load(mptr + xk9).to(tl.float32)
                nv9 = tl.load(nrmm + xk9).to(tl.float32)
                x9 = ((x9 * rstd_m) * nv9).to(tl.bfloat16).to(tl.float32)
        TOTRO = 72
        for it in range(pid, TOTRO, ncta):
            kc = it // 8
            jc = it % 8
            rows = jc * 8 + tl.arange(0, 8)
            k0 = kc * 256
            xk = tl.arange(0, 256)
            xe = tl.load(scr + SC_XN + k0 + xk)
            wt = tl.load(rwp + rows[:, None] * 2304 + k0 + xk[None, :]).to(tl.float32)
            part = tl.sum(wt * xe[None, :], 1)
            tl.store(scr + SC_LOGIT + kc * 64 + rows, part)
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (2))), NOP + NQO + TOTRO, pid, ncta, dbg, ctr_base, TIMING)

        # ---- RGU: gate/up fused gemv (waits full OPROJ incl. router)
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (2))), NOP + NQO + TOTRO, dbg, ctr_base, TIMING)
        idxs, wsel = _route(scr + SC_LOGIT)
        NGCH = 1024 // BN
        TOTG = 9 * NGCH * 6
        for it in range(pid, TOTG, ncta):
            slot = it // (NGCH * 6)
            n0 = ((it // 6) % NGCH) * BN
            sp = it % 6
            ns = n0 + tl.arange(0, BN)
            e = tl.sum(tl.where(tl.arange(0, 8) == slot, idxs, 0))
            wg, sg, zg = _moe_in(BASE, pb, slot,
                                 TF_EXW, TF_EXSZ, TF_SGW, TF_SUW, TF_SDW,
                                 TF_SGS, TF_SUS, TF_SDS, TF_SGZ, TF_SUZ, TF_SDZ, 0, e)
            wu, su, zu = _moe_in(BASE, pb, slot,
                                 TF_EXW, TF_EXSZ, TF_SGW, TF_SUW, TF_SDW,
                                 TF_SGS, TF_SUS, TF_SDS, TF_SGZ, TF_SUZ, TF_SDZ, 1, e)
            ag = tl.zeros((BN,), dtype=tl.float32)
            au = tl.zeros((BN,), dtype=tl.float32)
            for g in range(sp * 3, sp * 3 + 3):
                svg = tl.load(wfb + sg + g * 1024 + ns).to(tl.float32)
                zvg = tl.load(wfb + zg + g * 1024 + ns).to(tl.float32)
                sug = tl.load(wfb + su + g * 1024 + ns).to(tl.float32)
                zug = tl.load(wfb + zu + g * 1024 + ns).to(tl.float32)
                dg = tl.zeros((BN,), dtype=tl.float32)
                du = tl.zeros((BN,), dtype=tl.float32)
                sx = 0.0
                for kk in tl.static_range(2):
                    k0 = g * 128 + kk * 64
                    r32 = tl.arange(0, 32)
                    wg_ = tl.load(wqb + wg + (k0 // 2 + r32)[:, None] * 1024 + ns[None, :])
                    wu_ = tl.load(wqb + wu + (k0 // 2 + r32)[:, None] * 1024 + ns[None, :])
                    log = (wg_ & 0xF).to(tl.float32)
                    hig = ((wg_ >> 4) & 0xF).to(tl.float32)
                    lou = (wu_ & 0xF).to(tl.float32)
                    hiu = ((wu_ >> 4) & 0xF).to(tl.float32)
                    xe = tl.load(scr + SC_XN + k0 + 2 * tl.arange(0, 32))
                    xo = tl.load(scr + SC_XN + k0 + 1 + 2 * tl.arange(0, 32))
                    dg += tl.sum(log * xe[:, None], 0) + tl.sum(hig * xo[:, None], 0)
                    du += tl.sum(lou * xe[:, None], 0) + tl.sum(hiu * xo[:, None], 0)
                    sx += tl.sum(xe) + tl.sum(xo)
                ag += svg * (dg - zvg * sx)
                au += sug * (du - zug * sx)
            tl.store(scr + SC_GUP + ((0 * 9 + slot) * 6 + sp) * 1024 + ns, ag)
            tl.store(scr + SC_GUP + ((1 * 9 + slot) * 6 + sp) * 1024 + ns, au)
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (3))), TOTG, pid, ncta, dbg, ctr_base, TIMING)

        # ---- DOWN: h-precompute (own slot) -> split gemv -> reduce
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (3))), TOTG, dbg, ctr_base, TIMING)
        NHX = 9 * (1024 // 128)
        for it in range(pid, NHX, ncta):
            slot = it // (1024 // 128)
            n0 = (it % (1024 // 128)) * 128
            nsr = n0 + tl.arange(0, 128)
            g0 = (tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 0) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 1) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 2) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 3) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 4) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 5) * 1024 + nsr))
            u0 = (tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 0) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 1) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 2) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 3) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 4) * 1024 + nsr)
                  + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 5) * 1024 + nsr))
            tl.store(scr + SC_HX + slot * 1024 + nsr, g0 * tl.sigmoid(g0) * u0)
        _bump(ctr, ctr_base + 42 + b, NHX, pid, ncta, dbg, ctr_base, TIMING)
        _wait(ctr, ctr_base + 42 + b, NHX, dbg, ctr_base, TIMING)
        NDN = 2304 // BN
        TOTDG = 9 * NDN * 2
        for it in range(pid, TOTDG, ncta):
            slot = it // (NDN * 2)
            n0 = ((it // 2) % NDN) * BN
            sp = it % 2
            ns = n0 + tl.arange(0, BN)
            e = tl.sum(tl.where(tl.arange(0, 8) == slot, idxs, 0))
            w, s, z = _moe_in(BASE, pb, slot,
                              TF_EXW, TF_EXSZ, TF_SGW, TF_SUW, TF_SDW,
                              TF_SGS, TF_SUS, TF_SDS, TF_SGZ, TF_SUZ, TF_SDZ, 2, e)
            acc = tl.zeros((BN,), dtype=tl.float32)
            for g in range(sp * 4, sp * 4 + 4):
                sv = tl.load(wfb + s + g * 2304 + ns).to(tl.float32)
                zv = tl.load(wfb + z + g * 2304 + ns).to(tl.float32)
                dot = tl.zeros((BN,), dtype=tl.float32)
                sx = 0.0
                for kk in tl.static_range(2):
                    k0 = g * 128 + kk * 64
                    r32 = tl.arange(0, 32)
                    wp = tl.load(wqb + w + (k0 // 2 + r32)[:, None] * 2304 + ns[None, :])
                    lo = (wp & 0xF).to(tl.float32)
                    hi = ((wp >> 4) & 0xF).to(tl.float32)
                    # h = silu(g)*u precomputed into SC_HX (see HPRE stage)
                    he = tl.load(scr + SC_HX + slot * 1024 + k0 + 2 * tl.arange(0, 32))
                    ho = tl.load(scr + SC_HX + slot * 1024 + k0 + 1 + 2 * tl.arange(0, 32))
                    dot += tl.sum(lo * he[:, None], 0) + tl.sum(hi * ho[:, None], 0)
                    sx += tl.sum(he) + tl.sum(ho)
                acc += sv * (dot - zv * sx)
            tl.store(scr + SC_DNP + (sp * 9 + slot) * 2304 + ns, acc)
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (4))) + 1, TOTDG, pid, ncta, dbg, ctr_base, TIMING)
        _wait(ctr, ctr_base + (2 * ((b) * 5 + (4))) + 1, TOTDG, dbg, ctr_base, TIMING)
        for it in range(pid, NDN, ncta):
            ns = it * BN + tl.arange(0, BN)
            outv = tl.load(scr + XB_OUT + ns)
            for slot in tl.static_range(9):
                wsel_s = tl.sum(tl.where(tl.arange(0, 8) == slot, wsel, 0.0))
                if slot == 8:
                    wsel_s = 1.0
                pv = tl.load(scr + SC_DNP + (0 * 9 + slot) * 2304 + ns)
                pv += tl.load(scr + SC_DNP + (1 * 9 + slot) * 2304 + ns)
                outv += wsel_s * pv
            tl.store(scr + XB_OUT + ns, outv)
        _bump(ctr, ctr_base + (2 * ((b) * 5 + (4))), NHX + TOTDG + NDN, pid, ncta, dbg, ctr_base, TIMING)

    # ================= MLA block ============
    BASE = 3 * _40
    mla_x = scr + SC_XB
    # ---- QABS: q_proj/kv_a split partials -> absorb + rope + append
    _wait(ctr, ctr_base + (2 * ((2) * 5 + (4))), 414, dbg, ctr_base, TIMING)
    _xn_make(scr, SC_XN, scr + SC_XB, wfb + tl.load(pb + 256 + BASE + MF_AN), ctr, ctr_base, 44, pid, ncta, dbg, TIMING, False)
    rstd = _rstd(mla_x, 2304)
    nrm = wfb + tl.load(pb + 256 + BASE + MF_AN)
    NQQ = 6144 // BN
    NKV = (576 + BN - 1) // BN
    NQG = NQQ * 3 + NKV * 3
    for it in range(pid, NQG, ncta):
        if it < NQQ * 3:
            n0 = (it // 3) * BN
            sp = it % 6
            ns = n0 + tl.arange(0, BN)
            w = wqb + tl.load(pb + BASE + MF_QW)
            s = wfb + tl.load(pb + 256 + BASE + MF_QS)
            z = wfb + tl.load(pb + 256 + BASE + MF_QZ)
            acc = _gemv_split(scr, scr + SC_XN, w, s, z, 6144, 6144, rstd, nrm, ns, sp, 6, True, BN)
            tl.store(scr + SC_MQP + sp * 6144 + ns, acc)
        else:
            it2 = it - NQQ * 3
            n0 = (it2 // 3) * BN
            sp = it2 % 3
            ns = n0 + tl.arange(0, BN)
            nmk = ns < 576
            w = wqb + tl.load(pb + BASE + MF_KVAW)
            s = wfb + tl.load(pb + 256 + BASE + MF_KVAS)
            z = wfb + tl.load(pb + 256 + BASE + MF_KVAZ)
            acc = _gemv_split(scr, scr + SC_XN, w, s, z, 576, 576, rstd, nrm, ns, sp, 6, True, BN)
            tl.store(scr + SC_KVP + sp * 576 + ns, acc, mask=nmk)
    _bump(ctr, ctr_base + 31, NQG, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 31, NQG, dbg, ctr_base, TIMING)
    kvbw = wqb + tl.load(pb + BASE + MF_KVBW)
    kvbs = wfb + tl.load(pb + 256 + BASE + MF_KVBS)
    kvbz = wfb + tl.load(pb + 256 + BASE + MF_KVBZ)
    TOTA = 512 + 1
    log_th = -9.210340371976184 / 32.0
    for it in range(pid, TOTA, ncta):
        if it < 512:
            h = it // 16
            cc = it % 16
            c0 = cc * 32
            d = tl.arange(0, 128)
            qn = (tl.load(scr + SC_MQP + 0 * 6144 + h * 192 + d)
                  + tl.load(scr + SC_MQP + 1 * 6144 + h * 192 + d)
                  + tl.load(scr + SC_MQP + 2 * 6144 + h * 192 + d)).to(tl.bfloat16).to(tl.float32)
            g0 = c0 // 128
            sv = tl.load(kvbs + g0 * 8192 + h * 256 + d).to(tl.float32)
            zv = tl.load(kvbz + g0 * 8192 + h * 256 + d).to(tl.float32)
            sq = sv * qn
            zsq = tl.sum(zv * sq)
            r16 = tl.arange(0, 16)
            wp = tl.load(kvbw + (c0 // 2 + r16)[:, None] * 8192 + (h * 256 + d)[None, :])
            lo = (wp & 0xF).to(tl.float32)
            hi = ((wp >> 4) & 0xF).to(tl.float32)
            de = tl.sum(lo * sq[None, :], 1) - zsq
            do = tl.sum(hi * sq[None, :], 1) - zsq
            tl.store(scr + SC_QABS + h * 512 + c0 + 2 * tl.arange(0, 16), de)
            tl.store(scr + SC_QABS + h * 512 + c0 + 2 * tl.arange(0, 16) + 1, do)
            if cc == 0:
                # rope q_rope[h] from summed partials (round before rope, ref order)
                freq = tl.exp(log_th * tl.arange(0, 32).to(tl.float32))
                ang = L0 * freq
                cv = tl.cos(ang)
                svv = tl.sin(ang)
                p0 = h * 192 + 128 + 2 * tl.arange(0, 32)
                p1 = h * 192 + 129 + 2 * tl.arange(0, 32)
                ee = (tl.load(scr + SC_MQP + 0 * 6144 + p0)
                      + tl.load(scr + SC_MQP + 1 * 6144 + p0)
                      + tl.load(scr + SC_MQP + 2 * 6144 + p0)).to(tl.bfloat16).to(tl.float32)
                eo = (tl.load(scr + SC_MQP + 0 * 6144 + p1)
                      + tl.load(scr + SC_MQP + 1 * 6144 + p1)
                      + tl.load(scr + SC_MQP + 2 * 6144 + p1)).to(tl.bfloat16).to(tl.float32)
                re = (ee * cv - eo * svv).to(tl.bfloat16).to(tl.float32)
                ro = (eo * cv + ee * svv).to(tl.bfloat16).to(tl.float32)
                tl.store(scr + SC_QROPE + h * 64 + 2 * tl.arange(0, 32), re)
                tl.store(scr + SC_QROPE + h * 64 + 2 * tl.arange(0, 32) + 1, ro)
        else:
            i512 = tl.arange(0, 512)
            ckv = (tl.load(scr + SC_KVP + 0 * 576 + i512)
                   + tl.load(scr + SC_KVP + 1 * 576 + i512)
                   + tl.load(scr + SC_KVP + 2 * 576 + i512)).to(tl.bfloat16)
            tl.store(kvbig + L0 * 512 + i512, ckv)
            freq = tl.exp(log_th * tl.arange(0, 32).to(tl.float32))
            ang = L0 * freq
            cv = tl.cos(ang)
            svv = tl.sin(ang)
            ee = (tl.load(scr + SC_KVP + 0 * 576 + 512 + 2 * tl.arange(0, 32))
                  + tl.load(scr + SC_KVP + 1 * 576 + 512 + 2 * tl.arange(0, 32))
                  + tl.load(scr + SC_KVP + 2 * 576 + 512 + 2 * tl.arange(0, 32)))
            eo = (tl.load(scr + SC_KVP + 0 * 576 + 512 + 1 + 2 * tl.arange(0, 32))
                  + tl.load(scr + SC_KVP + 1 * 576 + 512 + 1 + 2 * tl.arange(0, 32))
                  + tl.load(scr + SC_KVP + 2 * 576 + 512 + 1 + 2 * tl.arange(0, 32)))
            re = (ee * cv - eo * svv).to(tl.bfloat16)
            ro = (eo * cv + ee * svv).to(tl.bfloat16)
            tl.store(krbig + L0 * 64 + 2 * tl.arange(0, 32), re)
            tl.store(krbig + L0 * 64 + 2 * tl.arange(0, 32) + 1, ro)
    _bump(ctr, ctr_base + 30, TOTA, pid, ncta, dbg, ctr_base, TIMING)

    # ---- ATT: online softmax over latent cache (256-row chunks, prefetched)
    _wait(ctr, ctr_base + 30, TOTA, dbg, ctr_base, TIMING)
    # ATT: head-grouped online softmax (4 heads per item share each cache tile)
    TOTATT = 8 * nc
    i512a = tl.arange(0, 512)
    i64a = tl.arange(0, 64)
    HPI: tl.constexpr = 4
    for it in range(pid, TOTATT, ncta):
        hg = it // nc
        ch = it % nc
        r0 = ch * _ACHUNK
        r1 = tl.minimum(r0 + _ACHUNK, L0 + 1)
        lim = tl.minimum(r1, L0)
        hh = hg * HPI + tl.arange(0, HPI)
        qa = tl.load(scr + SC_QABS + hh[:, None] * 512 + i512a[None, :])          # (HPI, 512)
        qr = tl.load(scr + SC_QROPE + hh[:, None] * 64 + i64a[None, :])           # (HPI, 64)
        m_run = tl.full((HPI,), -1e30, dtype=tl.float32)
        l_run = tl.zeros((HPI,), dtype=tl.float32)
        pv = tl.zeros((HPI, 512), dtype=tl.float32)
        # double-buffered prefetch pipeline over BR-row tiles
        mk0 = r0 + tl.arange(0, BR) < lim
        rlast = r0
        ct0 = tl.load(ckv_in + (r0 + tl.arange(0, BR))[:, None] * 512 + i512a[None, :],
                      mask=mk0[:, None], other=0.0).to(tl.float32)
        krt0 = tl.load(kr_in + (r0 + tl.arange(0, BR))[:, None] * 64 + i64a[None, :],
                       mask=mk0[:, None], other=0.0).to(tl.float32)
        for rr in range(r0 + BR, lim, BR):
            mk1 = rr + tl.arange(0, BR) < lim
            ct1 = tl.load(ckv_in + (rr + tl.arange(0, BR))[:, None] * 512 + i512a[None, :],
                          mask=mk1[:, None], other=0.0).to(tl.float32)
            krt1 = tl.load(kr_in + (rr + tl.arange(0, BR))[:, None] * 64 + i64a[None, :],
                           mask=mk1[:, None], other=0.0).to(tl.float32)
            s = (tl.sum(ct0[None, :, :] * qa[:, None, :], 2)
                 + tl.sum(krt0[None, :, :] * qr[:, None, :], 2)) * 0.07216878364870323
            s = tl.where((rlast + tl.arange(0, BR) < lim)[None, :], s, -1e30)
            m_new = tl.maximum(m_run, tl.max(s, 1))
            alpha = tl.exp(m_run - m_new)
            wj = tl.exp(s - m_new[:, None])
            l_run = l_run * alpha + tl.sum(wj, 1)
            pv = pv * alpha[:, None] + tl.sum(wj[:, :, None] * ct0[None, :, :], 1)
            m_run = m_new
            ct0 = ct1
            krt0 = krt1
            rlast = rr
        s = (tl.sum(ct0[None, :, :] * qa[:, None, :], 2)
             + tl.sum(krt0[None, :, :] * qr[:, None, :], 2)) * 0.07216878364870323
        s = tl.where((rlast + tl.arange(0, BR) < lim)[None, :], s, -1e30)
        m_new = tl.maximum(m_run, tl.max(s, 1))
        alpha = tl.exp(m_run - m_new)
        wj = tl.exp(s - m_new[:, None])
        l_run = l_run * alpha + tl.sum(wj, 1)
        pv = pv * alpha[:, None] + tl.sum(wj[:, :, None] * ct0[None, :, :], 1)
        m_run = m_new
        if r1 > L0:
            ct = tl.load(kvbig + L0 * 512 + i512a).to(tl.float32)
            krt = tl.load(krbig + L0 * 64 + i64a).to(tl.float32)
            sb = (tl.sum(qa * ct[None, :], 1) + tl.sum(qr * krt[None, :], 1)) * 0.07216878364870323
            m_new = tl.maximum(m_run, sb)
            alpha = tl.exp(m_run - m_new)
            wjb = tl.exp(sb - m_new)
            l_run = l_run * alpha + wjb
            pv = pv * alpha[:, None] + wjb[:, None] * ct[None, :]
            m_run = m_new
        for hi in tl.static_range(HPI):
            base = SC_ATT + ((ch * 32 + hg * HPI + hi) * ATT_STRIDE)
            tl.store(scr + base + 0, tl.sum(tl.where(tl.arange(0, HPI) == hi, m_run, 0.0)))
            tl.store(scr + base + 1, tl.sum(tl.where(tl.arange(0, HPI) == hi, l_run, 0.0)))
            tl.store(scr + base + 2 + i512a,
                     tl.sum(tl.where(tl.arange(0, HPI)[:, None] == hi, pv, 0.0), 0))
    # seed copy (cold only)
    NCOPY = 0 if cold_cache == 0 else (L0 + _COPY_R - 1) // _COPY_R
    for it in range(pid + TOTATT, TOTATT + NCOPY, ncta):
        it0 = it - TOTATT
        r0c = it0 * _COPY_R
        for rr in range(r0c, tl.minimum(r0c + _COPY_R, L0), 32):
            i32 = tl.arange(0, 32)
            mk = rr + i32 < L0
            v = tl.load(ckv_in + (rr + i32)[:, None] * 512 + i512a[None, :], mask=mk[:, None], other=0.0)
            tl.store(kvbig + (rr + i32)[:, None] * 512 + i512a[None, :], v, mask=mk[:, None])
            v2 = tl.load(kr_in + (rr + i32)[:, None] * 64 + i64a[None, :], mask=mk[:, None], other=0.0)
            tl.store(krbig + (rr + i32)[:, None] * 64 + i64a[None, :], v2, mask=mk[:, None])
    _bump(ctr, ctr_base + 32, TOTATT + NCOPY, pid, ncta, dbg, ctr_base, TIMING)

    # ---- CAO: combine partials (intra) -> AOUT through W_v
    _wait(ctr, ctr_base + 32, TOTATT + NCOPY, dbg, ctr_base, TIMING)
    for it in range(pid, 32, ncta):
        h = it
        m_star = -1e30
        for c in range(0, nc):
            m_c = tl.load(scr + SC_ATT + (c * 32 + h) * ATT_STRIDE + 0)
            m_star = tl.maximum(m_star, m_c)
        l_star = 0.0
        pvf = tl.zeros((512,), dtype=tl.float32)
        for c in range(0, nc):
            base = SC_ATT + (c * 32 + h) * ATT_STRIDE
            m_c = tl.load(scr + base + 0)
            l_c = tl.load(scr + base + 1)
            pv_c = tl.load(scr + base + 2 + i512a)
            wv = tl.exp(m_c - m_star)
            l_star += l_c * wv
            pvf += pv_c * wv
        tl.store(scr + SC_PVF + h * 512 + i512a, pvf / l_star)
    _bump(ctr, ctr_base + 35, 32, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 35, 32, dbg, ctr_base, TIMING)
    TOTAO = 128
    for it in range(pid, TOTAO, ncta):
        h = it // 4
        dc = it % 4
        d0 = dc * 32
        ns32 = h * 256 + 128 + d0 + tl.arange(0, 32)
        acc = tl.zeros((32,), dtype=tl.float32)
        for g in range(0, 4):
            sv = tl.load(kvbs + g * 8192 + ns32).to(tl.float32)
            zv = tl.load(kvbz + g * 8192 + ns32).to(tl.float32)
            dot = tl.zeros((32,), dtype=tl.float32)
            sx = 0.0
            for kk in tl.static_range(2):
                k0 = g * 128 + kk * 64
                r32 = tl.arange(0, 32)
                wp = tl.load(kvbw + (k0 // 2 + r32)[:, None] * 8192 + ns32[None, :])
                lo = (wp & 0xF).to(tl.float32)
                hi = ((wp >> 4) & 0xF).to(tl.float32)
                xe = tl.load(scr + SC_PVF + h * 512 + k0 + 2 * tl.arange(0, 32))
                xo = tl.load(scr + SC_PVF + h * 512 + k0 + 1 + 2 * tl.arange(0, 32))
                dot += tl.sum(lo * xe[:, None], 0) + tl.sum(hi * xo[:, None], 0)
                sx += tl.sum(xe) + tl.sum(xo)
            acc += sv * (dot - zv * sx)
        tl.store(scr + SC_ORA + h * 128 + d0 + tl.arange(0, 32), acc.to(tl.bfloat16).to(tl.float32))
    _bump(ctr, ctr_base + 34, TOTAO + 32, pid, ncta, dbg, ctr_base, TIMING)

    # ---- MLA OPROJ (split + reduce)
    _wait(ctr, ctr_base + 34, TOTAO + 32, dbg, ctr_base, TIMING)
    w_o = wqb + tl.load(pb + BASE + MF_OW)
    s_o = wfb + tl.load(pb + 256 + BASE + MF_OS)
    z_o = wfb + tl.load(pb + 256 + BASE + MF_OZ)
    NOP = (2304 // BN) * 4
    for it in range(pid, NOP, ncta):
        n0 = (it // 4) * BN
        sp = it % 4
        ns = n0 + tl.arange(0, BN)
        acc = _gemv_split(scr, scr + SC_ORA, w_o, s_o, z_o, 2304, 2304, 0.0, wfb, ns, sp, 8, False, BN)
        tl.store(scr + SC_OPRP + sp * 2304 + ns, acc)
    _bump(ctr, ctr_base + 37, NOP, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 37, NOP, dbg, ctr_base, TIMING)
    NQO = 2304 // BN
    for it in range(pid, NQO, ncta):
        ns = it * BN + tl.arange(0, BN)
        a4 = tl.load(scr + SC_OPRP + 0 * 2304 + ns)
        a4 += tl.load(scr + SC_OPRP + 1 * 2304 + ns)
        a4 += tl.load(scr + SC_OPRP + 2 * 2304 + ns)
        a4 += tl.load(scr + SC_OPRP + 3 * 2304 + ns)
        cur = tl.load(scr + SC_XB + ns)
        tl.store(scr + SC_XA + ns, (cur + a4).to(tl.bfloat16).to(tl.float32))
    _bump(ctr, ctr_base + 47, NQO, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 47, NQO, dbg, ctr_base, TIMING)

    # ---- MLA router partials (folded into MOPR stage)
    _xn_make(scr, SC_XN, scr + SC_XA, wfb + tl.load(pb + 256 + BASE + MF_MN), ctr, ctr_base, 47, pid, ncta, dbg, TIMING, False)
    mptr = scr + SC_XA
    rstd_m = _rstd(mptr, 2304)
    nrmm = wfb + tl.load(pb + 256 + BASE + MF_MN)
    rwp = wfb + tl.load(pb + 256 + BASE + MF_ROUT)
    for it in range(pid, 9, ncta):
        kc9 = it * 256
        xk9 = kc9 + tl.arange(0, 256)
        x9 = tl.load(mptr + xk9).to(tl.float32)
        nv9 = tl.load(nrmm + xk9).to(tl.float32)
        x9 = ((x9 * rstd_m) * nv9).to(tl.bfloat16).to(tl.float32)
    TOTRO = 72
    for it in range(pid, TOTRO, ncta):
        kc = it // 8
        jc = it % 8
        rows = jc * 8 + tl.arange(0, 8)
        k0 = kc * 256
        xk = tl.arange(0, 256)
        xe = tl.load(scr + SC_XN + k0 + xk)
        wt = tl.load(rwp + rows[:, None] * 2304 + k0 + xk[None, :]).to(tl.float32)
        part = tl.sum(wt * xe[None, :], 1)
        tl.store(scr + SC_LOGIT + kc * 64 + rows, part)
    _bump(ctr, ctr_base + 36, NOP + NQO + TOTRO, pid, ncta, dbg, ctr_base, TIMING)

    # ---- MLA RGU: gate/up fused gemv (waits full MOPR incl. router)
    _wait(ctr, ctr_base + 36, NOP + NQO + TOTRO, dbg, ctr_base, TIMING)
    idxs, wsel = _route(scr + SC_LOGIT)
    NGCH = 1024 // BN
    TOTG = 9 * NGCH * 6
    for it in range(pid, TOTG, ncta):
        slot = it // (NGCH * 6)
        n0 = ((it // 6) % NGCH) * BN
        sp = it % 6
        ns = n0 + tl.arange(0, BN)
        e = tl.sum(tl.where(tl.arange(0, 8) == slot, idxs, 0))
        wg, sg, zg = _moe_in(BASE, pb, slot,
                             MF_EXW, MF_EXSZ, MF_SGW, MF_SUW, MF_SDW,
                             MF_SGS, MF_SUS, MF_SDS, MF_SGZ, MF_SUZ, MF_SDZ, 0, e)
        wu, su, zu = _moe_in(BASE, pb, slot,
                             MF_EXW, MF_EXSZ, MF_SGW, MF_SUW, MF_SDW,
                             MF_SGS, MF_SUS, MF_SDS, MF_SGZ, MF_SUZ, MF_SDZ, 1, e)
        ag = tl.zeros((BN,), dtype=tl.float32)
        au = tl.zeros((BN,), dtype=tl.float32)
        for g in range(sp * 3, sp * 3 + 3):
            svg = tl.load(wfb + sg + g * 1024 + ns).to(tl.float32)
            zvg = tl.load(wfb + zg + g * 1024 + ns).to(tl.float32)
            sug = tl.load(wfb + su + g * 1024 + ns).to(tl.float32)
            zug = tl.load(wfb + zu + g * 1024 + ns).to(tl.float32)
            dg = tl.zeros((BN,), dtype=tl.float32)
            du = tl.zeros((BN,), dtype=tl.float32)
            sx = 0.0
            for kk in tl.static_range(2):
                k0 = g * 128 + kk * 64
                r32 = tl.arange(0, 32)
                wg_ = tl.load(wqb + wg + (k0 // 2 + r32)[:, None] * 1024 + ns[None, :])
                wu_ = tl.load(wqb + wu + (k0 // 2 + r32)[:, None] * 1024 + ns[None, :])
                log = (wg_ & 0xF).to(tl.float32)
                hig = ((wg_ >> 4) & 0xF).to(tl.float32)
                lou = (wu_ & 0xF).to(tl.float32)
                hiu = ((wu_ >> 4) & 0xF).to(tl.float32)
                xe = tl.load(scr + SC_XN + k0 + 2 * tl.arange(0, 32))
                xo = tl.load(scr + SC_XN + k0 + 1 + 2 * tl.arange(0, 32))
                dg += tl.sum(log * xe[:, None], 0) + tl.sum(hig * xo[:, None], 0)
                du += tl.sum(lou * xe[:, None], 0) + tl.sum(hiu * xo[:, None], 0)
                sx += tl.sum(xe) + tl.sum(xo)
            ag += svg * (dg - zvg * sx)
            au += sug * (du - zug * sx)
        tl.store(scr + SC_GUP + ((0 * 9 + slot) * 6 + sp) * 1024 + ns, ag)
        tl.store(scr + SC_GUP + ((1 * 9 + slot) * 6 + sp) * 1024 + ns, au)
    _bump(ctr, ctr_base + 38, TOTG, pid, ncta, dbg, ctr_base, TIMING)


    # ---- MLA DOWN + FINAL
    _wait(ctr, ctr_base + 38, TOTG, dbg, ctr_base, TIMING)
    NHX = 9 * (1024 // 128)
    for it in range(pid, NHX, ncta):
        slot = it // (1024 // 128)
        n0 = (it % (1024 // 128)) * 128
        nsr = n0 + tl.arange(0, 128)
        g0 = (tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 0) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 1) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 2) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 3) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 4) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((0 * 9 + slot) * 6 + 5) * 1024 + nsr))
        u0 = (tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 0) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 1) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 2) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 3) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 4) * 1024 + nsr)
              + tl.load(scr + SC_GUP + ((1 * 9 + slot) * 6 + 5) * 1024 + nsr))
        tl.store(scr + SC_HX + slot * 1024 + nsr, g0 * tl.sigmoid(g0) * u0)
    _bump(ctr, ctr_base + 45, NHX, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 45, NHX, dbg, ctr_base, TIMING)
    NDN = 2304 // BN
    TOTDG = 9 * NDN * 2
    for it in range(pid, TOTDG, ncta):
        slot = it // (NDN * 2)
        n0 = ((it // 2) % NDN) * BN
        sp = it % 2
        ns = n0 + tl.arange(0, BN)
        e = tl.sum(tl.where(tl.arange(0, 8) == slot, idxs, 0))
        w, s, z = _moe_in(BASE, pb, slot,
                          MF_EXW, MF_EXSZ, MF_SGW, MF_SUW, MF_SDW,
                          MF_SGS, MF_SUS, MF_SDS, MF_SGZ, MF_SUZ, MF_SDZ, 2, e)
        acc = tl.zeros((BN,), dtype=tl.float32)
        for g in range(sp * 4, sp * 4 + 4):
            sv = tl.load(wfb + s + g * 2304 + ns).to(tl.float32)
            zv = tl.load(wfb + z + g * 2304 + ns).to(tl.float32)
            dot = tl.zeros((BN,), dtype=tl.float32)
            sx = 0.0
            for kk in tl.static_range(2):
                k0 = g * 128 + kk * 64
                r32 = tl.arange(0, 32)
                wp = tl.load(wqb + w + (k0 // 2 + r32)[:, None] * 2304 + ns[None, :])
                lo = (wp & 0xF).to(tl.float32)
                hi = ((wp >> 4) & 0xF).to(tl.float32)
                he = tl.load(scr + SC_HX + slot * 1024 + k0 + 2 * tl.arange(0, 32))
                ho = tl.load(scr + SC_HX + slot * 1024 + k0 + 1 + 2 * tl.arange(0, 32))
                dot += tl.sum(lo * he[:, None], 0) + tl.sum(hi * ho[:, None], 0)
                sx += tl.sum(he) + tl.sum(ho)
            acc += sv * (dot - zv * sx)
        tl.store(scr + SC_DNP + (sp * 9 + slot) * 2304 + ns, acc)
    _bump(ctr, ctr_base + 41, TOTDG, pid, ncta, dbg, ctr_base, TIMING)
    _wait(ctr, ctr_base + 41, TOTDG, dbg, ctr_base, TIMING)
    for it in range(pid, NDN, ncta):
        ns = it * BN + tl.arange(0, BN)
        outv = tl.load(scr + SC_XA + ns)
        for slot in tl.static_range(9):
            wsel_s = tl.sum(tl.where(tl.arange(0, 8) == slot, wsel, 0.0))
            if slot == 8:
                wsel_s = 1.0
            pv = tl.load(scr + SC_DNP + (0 * 9 + slot) * 2304 + ns)
            pv += tl.load(scr + SC_DNP + (1 * 9 + slot) * 2304 + ns)
            outv += wsel_s * pv
        tl.store(scr + SC_XA + ns, outv)
        tl.store(outh_ptr + ns, outv.to(tl.bfloat16))
    _bump(ctr, ctr_base + 40, NHX + TOTDG + NDN, pid, ncta, dbg, ctr_base, TIMING)


# --------------------------------------------------------------------------- #
# flat weight materialization
# --------------------------------------------------------------------------- #
def _materialize(model: "Model", dev):
    plans = []

    def add(fid, region, numel, getter):
        plans.append((fid, region, numel, getter))

    def qline(fidw, fids, fidz, m):
        add(fidw, "u8", m.w_q.numel(), lambda: m.w_q)
        add(fids, "f32", m.scales.numel(), lambda: m.scales)
        add(fidz, "f32", m.zeros.numel(), lambda: m.zeros)

    for b in range(3):
        base = b * _40
        blk = model.blocks[b]
        at = blk.attn
        qline(base + TF_QW, base + TF_QS, base + TF_QZ, at.q_proj)
        qline(base + TF_KW, base + TF_KS, base + TF_KZ, at.k_proj)
        qline(base + TF_VW, base + TF_VS, base + TF_VZ, at.v_proj)
        qline(base + TF_GW, base + TF_GS, base + TF_GZ, at.g_proj)
        qline(base + TF_OW, base + TF_OS, base + TF_OZ, at.o_proj)
        add(base + TF_BETA, "f32", at.beta_proj.weight.numel(), lambda at=at: at.beta_proj.weight)
        add(base + TF_CONV, "f32", at.conv_w.numel(), lambda blk=blk: blk.attn.conv_w)
        add(base + TF_AN, "f32", 2304, lambda blk=blk: blk.attn_norm)
        add(base + TF_MN, "f32", 2304, lambda blk=blk: blk.moe_norm)
        add(base + TF_ROUT, "f32", 64 * 2304, lambda blk=blk: blk.moe.router.weight)
        add(base + TF_EXW, "u8", 64 * EXB_U8, None)
        add(base + TF_EXSZ, "f32", 64 * EXB_F32, None)
        for fidw, fids, fidz, mod in (
            (TF_SGW, TF_SGS, TF_SGZ, blk.moe.s_gate),
            (TF_SUW, TF_SUS, TF_SUZ, blk.moe.s_up),
            (TF_SDW, TF_SDS, TF_SDZ, blk.moe.s_down),
        ):
            add(base + fidw, "u8", mod.w_q[0].numel(), lambda mod=mod: mod.w_q[0])
            add(base + fids, "f32", mod.scales[0].numel(), lambda mod=mod: mod.scales[0])
            add(base + fidz, "f32", mod.zeros[0].numel(), lambda mod=mod: mod.zeros[0])
    base = 3 * _40
    blk = model.blocks[3]
    at = blk.attn
    qline(base + MF_QW, base + MF_QS, base + MF_QZ, at.q_proj)
    qline(base + MF_KVAW, base + MF_KVAS, base + MF_KVAZ, at.kv_a)
    qline(base + MF_KVBW, base + MF_KVBS, base + MF_KVBZ, at.kv_b)
    qline(base + MF_OW, base + MF_OS, base + MF_OZ, at.o_proj)
    add(base + MF_AN, "f32", 2304, lambda blk=blk: blk.attn_norm)
    add(base + MF_MN, "f32", 2304, lambda blk=blk: blk.moe_norm)
    add(base + MF_ROUT, "f32", 64 * 2304, lambda blk=blk: blk.moe.router.weight)
    add(base + MF_EXW, "u8", 64 * EXB_U8, None)
    add(base + MF_EXSZ, "f32", 64 * EXB_F32, None)
    for fidw, fids, fidz, mod in (
        (MF_SGW, MF_SGS, MF_SGZ, blk.moe.s_gate),
        (MF_SUW, MF_SUS, MF_SUZ, blk.moe.s_up),
        (MF_SDW, MF_SDS, MF_SDZ, blk.moe.s_down),
    ):
        add(base + fidw, "u8", mod.w_q[0].numel(), lambda mod=mod: mod.w_q[0])
        add(base + fids, "f32", mod.scales[0].numel(), lambda mod=mod: mod.scales[0])
        add(base + fidz, "f32", mod.zeros[0].numel(), lambda mod=mod: mod.zeros[0])

    par = torch.zeros(PAR_LEN, dtype=torch.int32)
    off8 = 0
    off32 = 0
    offs = {}
    for fid, region, numel, getter in plans:
        if region == "u8":
            off8 = _align256(off8)
            par[fid] = off8
            offs[fid] = off8
            off8 += numel
        else:
            off32 = _align256(off32)
            par[256 + fid] = off32 // 4
            offs[256 + fid] = off32 // 4
            off32 += numel * 4
    wq = torch.zeros(off8 + 256, dtype=torch.uint8, device=dev)
    wf = torch.zeros(off32 // 4 + 256, dtype=torch.float32, device=dev)
    # copies
    for fid, region, numel, getter in plans:
        if getter is None:
            continue
        if region == "u8":
            o8 = offs[fid]
            src = getter().contiguous().view(-1)
            wq[o8:o8 + src.numel()].copy_(src)
        else:
            o32 = offs[256 + fid]
            src = getter().float().contiguous().view(-1)
            wf[o32:o32 + src.numel()].copy_(src)
    # expert tensors
    for b in range(3):
        base = b * _40
        moe = model.blocks[b].moe
        o8 = offs[base + TF_EXW]
        o32 = offs[256 + base + TF_EXSZ]
        for e in range(64):
            du8 = o8 + e * EXB_U8
            df32 = o32 + e * EXB_F32
            for i, mod in enumerate((moe.gate, moe.up, moe.down)):
                src = mod.w_q[e].contiguous().view(-1)
                wq[du8 + i * GW_BYTES: du8 + i * GW_BYTES + src.numel()].copy_(src)
                ss = mod.scales[e].float().contiguous().view(-1)
                zz = mod.zeros[e].float().contiguous().view(-1)
                wf[df32 + (2 * i) * GS_ELEMS: df32 + (2 * i) * GS_ELEMS + ss.numel()].copy_(ss)
                wf[df32 + (2 * i + 1) * GS_ELEMS: df32 + (2 * i + 1) * GS_ELEMS + zz.numel()].copy_(zz)
    base = 3 * _40
    moe = model.blocks[3].moe
    o8 = offs[base + MF_EXW]
    o32 = offs[256 + base + MF_EXSZ]
    for e in range(64):
        du8 = o8 + e * EXB_U8
        df32 = o32 + e * EXB_F32
        for i, mod in enumerate((moe.gate, moe.up, moe.down)):
            src = mod.w_q[e].contiguous().view(-1)
            wq[du8 + i * GW_BYTES: du8 + i * GW_BYTES + src.numel()].copy_(src)
            ss = mod.scales[e].float().contiguous().view(-1)
            zz = mod.zeros[e].float().contiguous().view(-1)
            wf[df32 + (2 * i) * GS_ELEMS: df32 + (2 * i) * GS_ELEMS + ss.numel()].copy_(ss)
            wf[df32 + (2 * i + 1) * GS_ELEMS: df32 + (2 * i + 1) * GS_ELEMS + zz.numel()].copy_(zz)
    model._flat = (wq, wf, par.to(dev))


# --------------------------------------------------------------------------- #
# Model
# --------------------------------------------------------------------------- #
class Model(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.cfg = cfg
        self.blocks = nn.ModuleList(Block(cfg, k) for k in cfg.pattern)
        self._flat = None
        self._scratch = None
        self._win = None
        self._ctr = None
        self._ctr_base = 0
        self._outh = None
        self._ncta = None
        self.register_load_state_dict_post_hook(_invalidate)

    def _win_view(self, layer, pp, qkv):
        base = layer * WIN_LAYER_STRIDE + pp * WIN_PP_STRIDE + qkv * 3 * 4096
        return self._win[base: base + 3 * 4096].view(3, 4096)

    def step(self, hidden, state):
        if _DEBUG_EAGER:
            return _step_eager(self, hidden, state)
        dev = hidden.device
        if self._flat is None:
            _materialize(self, dev)
            self._scratch = torch.zeros(SC_TOTAL, dtype=torch.float32, device=dev)
            self._win = torch.zeros(WIN_TOTAL, dtype=torch.float32, device=dev)
            self._ctr = torch.zeros(65536, dtype=torch.int32, device=dev)
            self._ctr_base = 0
            self._outh = torch.zeros(2304, dtype=torch.bfloat16, device=dev)
            self._dbg = torch.zeros(128, dtype=torch.int64, device=dev)
        wq, wf, par = self._flat
        scr = self._scratch
        win = self._win

        ppflags = 0
        for i in range(3):
            st = state[i]
            if "_pp" in st:
                pp = int(st["_pp"]) & 1
                ppflags |= (1 << i) | (pp << (3 + i))
        mst = state[3]
        if "_kv" in mst:
            cold_cache = 0
            kvbig, krbig = mst["_kv"]
            L0 = int(mst["_L"])
            ckvin, krin = kvbig, krbig
        else:
            cold_cache = 1
            L0 = int(mst["c_kv"].shape[0])
            cap = L0 + 4096
            kvbig = torch.empty((cap, 512), dtype=torch.bfloat16, device=dev)
            krbig = torch.empty((cap, 64), dtype=torch.bfloat16, device=dev)
            ckvin, krin = mst["c_kv"], mst["k_rope"]
        achunk = 64 if L0 + 1 <= 4096 else (128 if L0 + 1 <= 8192 else 256)
        nc = (L0 + 1 + achunk - 1) // achunk
        if self._ctr_base + XR_MAX > self._ctr.numel():
            self._ctr = torch.zeros(65536, dtype=torch.int32, device=dev)
            self._ctr_base = 0
        ctr_base = self._ctr_base
        self._ctr_base += XR_MAX
        flags = cold_cache

        if self._ncta is None:
            self._ncta = _pick_ncta(wq, wf, par, scr, win, state, ckvin, krin,
                                    kvbig, krbig, hidden, self._outh, self._ctr,
                                    ctr_base, L0, nc, flags, ppflags, achunk)
        ncta = self._ncta
        _megakernel[(ncta,)](
            wq, wf, par, scr, win,
            state[0]["S"], state[1]["S"], state[2]["S"],
            state[0]["cq"], state[0]["ck"], state[0]["cv"],
            state[1]["cq"], state[1]["ck"], state[1]["cv"],
            state[2]["cq"], state[2]["ck"], state[2]["cv"],
            ckvin, krin, kvbig, krbig,
            hidden, self._outh, self._ctr, self._dbg,
            ctr_base, L0, nc, flags, ppflags,
            BN=int(os.environ.get("KIMI_BN", "128")), BR=int(os.environ.get("KIMI_BR", "16")), TIMING=_TIMING,
            num_warps=_NUM_WARPS,
        )
        new_state = list(state)
        for i in range(3):
            st = state[i]
            if "_pp" in st:
                npp = 1 - (int(st["_pp"]) & 1)
            else:
                npp = 0
            new_state[i] = {
                "S": st["S"],
                "cq": self._win_view(i, npp, 0),
                "ck": self._win_view(i, npp, 1),
                "cv": self._win_view(i, npp, 2),
                "_pp": npp,
            }
        new_state[3] = {
            "c_kv": kvbig[: L0 + 1],
            "k_rope": krbig[: L0 + 1],
            "_kv": (kvbig, krbig),
            "_L": L0 + 1,
        }
        return self._outh, new_state

    # ------------------------------------------------------------------ #
    # eager debug path (mirrors the reference math exactly)
    # ------------------------------------------------------------------ #
    def _eager(self, hidden, state):
        return _step_eager(self, hidden, state)


def _invalidate(module, incompatible_keys=None, *args, **kwargs):
    module._flat = None
    module._ncta = None


def _pick_ncta(wq, wf, par, scr, win, state, ckvin, krin, kvbig, krbig,
               inh, outh, ctr, ctr_base, L0, nc, flags, ppflags, achunk):
    try:
        kc = _megakernel.warmup(
            wq, wf, par, scr, win,
            state[0]["S"], state[1]["S"], state[2]["S"],
            state[0]["cq"], state[0]["ck"], state[0]["cv"],
            state[1]["cq"], state[1]["ck"], state[1]["cv"],
            state[2]["cq"], state[2]["ck"], state[2]["cv"],
            ckvin, krin, kvbig, krbig,
            inh, outh, ctr, torch.zeros(128, dtype=torch.int64, device=inh.device),
            ctr_base, L0, nc, flags, ppflags, achunk,
            BN=128, BR=16, TIMING=0,
            grid=(1,), num_warps=_NUM_WARPS,
        )
        n_regs = getattr(kc, "n_regs", None)
        shared = getattr(getattr(kc, "metadata", None), "shared", 0) or 0
        if n_regs is None:
            return _NCTA_REQ
        per_cta = n_regs * _NUM_WARPS * 32
        by_regs = max(1, 65536 // max(1, per_cta))
        by_smem = max(1, 232448 // max(1, shared)) if shared else 32
        by_warps = 64 // _NUM_WARPS
        per_sm = max(1, min(by_regs, by_smem, by_warps, 32))
        return max(_NSM, min(_NSM * per_sm, _NCTA_REQ))
    except Exception:
        return _NSM


# --------------------------------------------------------------------------- #
# eager reference (debug only)
# --------------------------------------------------------------------------- #
def _dequant(w_q, scales, zeros, K, group):
    wu = torch.empty((K, w_q.shape[1]), dtype=torch.uint8, device=w_q.device)
    wu[[REDACTED: IP]] = w_q & 0xF
    wu[[REDACTED: IP]] = (w_q >> 4) & 0xF
    s = scales.repeat_interleave(group, dim=0)
    z = zeros.repeat_interleave(group, dim=0)
    return (wu.to(torch.bfloat16) - z) * s


def _rmsnorm(x, w):
    xf = x.float()
    xf = xf * torch.rsqrt(xf.pow(2).mean(-1, keepdim=True) + EPS)
    return (xf * w.float()).to(x.dtype)


def _rope_cossin(pos, dim, theta, device):
    inv = 1.0 / (theta ** (torch.arange(0, dim, 2, device=device, dtype=torch.float32) / dim))
    ang = pos * inv
    return torch.cos(ang), torch.sin(ang)


def _apply_rope(x, cos, sin):
    xf = x.float()
    even, odd = xf[..., [REDACTED: IP]], xf[..., [REDACTED: IP]]
    out = torch.empty_like(xf)
    out[..., [REDACTED: IP]] = even * cos - odd * sin
    out[..., [REDACTED: IP]] = odd * cos + even * sin
    return out.to(x.dtype)


def _qmm(x, ql):
    w = _dequant(ql.w_q, ql.scales, ql.zeros, ql.in_f, ql.group)
    return (x.float() @ w.float()).to(torch.bfloat16)


def _kda_eager(at, x, st, cfg):
    H, Dk = cfg.kda_heads, cfg.kda_head_dim
    q, k, v = _qmm(x, at.q_proj), _qmm(x, at.k_proj), _qmm(x, at.v_proj)
    nst = {}
    outs = []
    for idx, (val, prev) in enumerate(((q, st["cq"]), (k, st["ck"]), (v, st["cv"]))):
        win = torch.cat([prev, val[None]], dim=0)
        w = at.conv_w[idx].float().transpose(0, 1)
        out = F.silu((win.float() * w).sum(0)).to(val.dtype)
        outs.append(out)
        nst[["cq", "ck", "cv"][idx]] = win[1:]
    q, k, v = outs
    q = q.view(H, Dk).float() * at.scale
    k = k.view(H, Dk).float()
    v = v.view(H, Dk).float()
    g = (-F.softplus(_qmm(x, at.g_proj).float())).view(H, Dk)
    beta = torch.sigmoid(at.beta_proj(x).float())
    S = st["S"] * g.exp()[:, :, None]
    pred = (S * k[:, :, None]).sum(1)
    S = S + beta[:, None, None] * k[:, :, None] * (v - pred)[:, None, :]
    o = (S * q[:, :, None]).sum(1)
    nst["S"] = S
    return _qmm(o.reshape(H * Dk).to(torch.bfloat16), at.o_proj), nst


def _mla_eager(at, x, st, cfg):
    H = cfg.mla_heads
    pos = st["c_kv"].shape[0]
    q = _qmm(x, at.q_proj).view(H, cfg.qk_nope + cfg.qk_rope)
    q_nope = q[:, : cfg.qk_nope].float()
    q_rope = q[:, cfg.qk_nope:]
    kv = _qmm(x, at.kv_a)
    c_kv = kv[: cfg.kv_lora]
    k_rope = kv[cfg.kv_lora:]
    cos, sin = _rope_cossin(pos, cfg.qk_rope, cfg.rope_theta, x.device)
    q_rope = _apply_rope(q_rope, cos, sin).float()
    k_rope = _apply_rope(k_rope, cos, sin)
    nst = {
        "c_kv": torch.cat([st["c_kv"], c_kv[None]], 0),
        "k_rope": torch.cat([st["k_rope"], k_rope[None]], 0),
    }
    kvb = _qmm(nst["c_kv"], at.kv_b).view(-1, H, cfg.qk_nope + cfg.v_head).float()
    k_nope = kvb[..., : cfg.qk_nope]
    v = kvb[..., cfg.qk_nope:]
    scores = (torch.einsum("hd,lhd->lh", q_nope, k_nope)
              + torch.einsum("hd,ld->lh", q_rope, nst["k_rope"].float())) * at.scale
    p = torch.softmax(scores, dim=0)
    o = torch.einsum("lh,lhd->hd", p, v)
    return _qmm(o.reshape(H * cfg.v_head).to(torch.bfloat16), at.o_proj), nst


def _moe_eager(moe, x, cfg):
    probs = torch.softmax(moe.router(x).float(), dim=-1)
    w, idx = torch.topk(probs, cfg.n_active)
    w = w / (w.sum() + 1e-9) * cfg.routed_scaling
    out = x.new_zeros(cfg.hidden, dtype=torch.float32)
    xf = x.float()
    for j in range(cfg.n_active):
        e = int(idx[j])
        h = F.silu(_qmm(x, _ExpertView2(moe.gate, e)).float()) * _qmm(x, _ExpertView2(moe.up, e)).float()
        out = out + w[j] * (h @ _dequant(moe.down.w_q[e], moe.down.scales[e], moe.down.zeros[e], moe.down.in_f, moe.down.group).float())
    for s in range(cfg.n_shared):
        h = F.silu(_qmm(x, _ExpertView2(moe.s_gate, s)).float()) * _qmm(x, _ExpertView2(moe.s_up, s)).float()
        out = out + h @ _dequant(moe.s_down.w_q[s], moe.s_down.scales[s], moe.s_down.zeros[s], moe.s_down.in_f, moe.s_down.group).float()
    return out.to(torch.bfloat16)


class _ExpertView2:
    def __init__(self, qe, e):
        self.w_q = qe.w_q[e]
        self.scales = qe.scales[e]
        self.zeros = qe.zeros[e]
        self.in_f = qe.in_f
        self.group = qe.group


def _step_eager(model, hidden, state):
    with torch.no_grad():
        x = hidden.clone()
        new_state = []
        for i, blk in enumerate(model.blocks):
            st = state[i]
            xn = _rmsnorm(x, blk.attn_norm)
            if blk.kind == "K":
                h, nst = _kda_eager(blk.attn, xn, st, model.cfg)
            else:
                h, nst = _mla_eager(blk.attn, xn, st, model.cfg)
            x = x + h
            xn = _rmsnorm(x, blk.moe_norm)
            x = x + _moe_eager(blk.moe, xn, model.cfg)
            new_state.append(nst)
        return x, new_state

20260716_233348_kinetic-claude_kinetic-0715_1m__02_kimi_linear_decode