KernelBench mega · RTX PRO 6000
Kimi-Linear Decode GLM-5.2
cleandid not score
manually audited: clean
B200 GLM-5.2 cell (7.3x). Genuine fused int4 dequant-GEMV with a persistent-buffer decode path; data_ptr compared to detect a fresh fed-state vs an in-place continuation. Forbidden- scan passed; template_mutated=False; correctness clean on output + state + cache.
harnesszai-claude
Kernel source (redacted)
"""Fused W4A16 dequant-GEMV solution for the Kimi-Linear hybrid decode unit.
Beats baseline.py by fusing the int4 unpack + per-group dequant directly into the
GEMV (int4 weights streamed once, never materialized to bf16) and absorbing the
MLA kv_b projection into the query so the (L, 8192) latent is never materialized.
Implementation:
* int4 weights are stored OUTPUT-MAJOR (N, K//2) so each GEMV program streams a
fully contiguous (BLOCK_N, K//2) tile (~2x the bandwidth of input-major).
* projections sharing an input are fused (q/k/v/g -> one GEMV; MoE gate+up).
* split-K with a tiny reduce lifts occupancy for the small-N single-input linears.
* the entire decode step is captured in a CUDA graph (per context length) so the
~100 tiny kernels run back-to-back with no host dispatch overhead. The growing
MLA cache is handled with a fixed CAP buffer + a device-side int32 length
counter and an `arange < len` mask before softmax. KDA recurrent state lives in
persistent buffers updated in place.
* scores are kept (H, L) so the softmax reduces over the contiguous dim.
Exposes the same Model / step(hidden, state) contract and identical buffer names
as reference.py so it loads the reference weights via strict state_dict.
"""
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
import triton
import triton.language as tl
GROUP_SIZE = 128
HALF = GROUP_SIZE // 2
EPS = 1.0e-6
_NUM_SMS = 148
# --------------------------------------------------------------------------- #
# Triton kernels
# --------------------------------------------------------------------------- #
@triton.jit
def _gemv_T_kernel(
x_ptr, wq_ptr, s_ptr, z_ptr, idx_ptr, sc_ptr, y_ptr,
K, N, NG,
sxb, sxk, swe, sse, scb, scn, scs, syb, syn,
GROUP: tl.constexpr, HALF: tl.constexpr, BLOCK_N: tl.constexpr,
NGPS: tl.constexpr, SPLIT: tl.constexpr,
):
pid_b = tl.program_id(0)
pid_n = tl.program_id(1)
pid_s = tl.program_id(2)
eid = tl.load(idx_ptr + pid_b).to(tl.int64)
n_off = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
nm = n_off < N
K2 = K // 2
x_base = x_ptr + pid_b * sxb
wq_base = wq_ptr + eid * swe
s_base = s_ptr + eid * sse
z_base = z_ptr + eid * sse
acc = tl.zeros([BLOCK_N], dtype=tl.float32)
ha = tl.arange(0, HALF)
g0 = pid_s * NGPS
for gi in range(NGPS):
g = g0 + gi
gm = g < NG
ke = g * GROUP + 2 * ha
xe = tl.load(x_base + ke * sxk, mask=gm, other=0.0).to(tl.float32)
xo = tl.load(x_base + (ke + 1) * sxk, mask=gm, other=0.0).to(tl.float32)
pk = g * HALF + ha
wptr = wq_base + n_off[:, None] * K2 + pk[None, :]
byte = tl.load(wptr, mask=nm[:, None] & gm, other=0)
ib = byte.to(tl.int32)
lo = (ib & 0xF).to(tl.float32)
hi = ((ib >> 4) & 0xF).to(tl.float32)
sc = tl.load(s_base + n_off * NG + g, mask=nm & gm, other=0).to(tl.float32)
zo = tl.load(z_base + n_off * NG + g, mask=nm & gm, other=0).to(tl.float32)
wl = (lo - zo[:, None]) * sc[:, None]
wh = (hi - zo[:, None]) * sc[:, None]
acc += tl.sum(xe[None, :] * wl, axis=1)
acc += tl.sum(xo[None, :] * wh, axis=1)
if SPLIT == 1:
tl.store(y_ptr + pid_b * syb + n_off * syn, acc.to(tl.bfloat16), mask=nm)
else:
tl.store(sc_ptr + pid_b * scb + n_off * scn + pid_s * scs, acc, mask=nm)
@triton.jit
def _reduce_kernel(sc_ptr, y_ptr, N, scb, scn, scs, syb, syn,
BLOCK_N: tl.constexpr, S: tl.constexpr):
pid_b = tl.program_id(0)
pid_n = tl.program_id(1)
n_off = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
nm = n_off < N
sa = tl.arange(0, S)
ptrs = sc_ptr + pid_b * scb + n_off[:, None] * scn + sa[None, :] * scs
vals = tl.load(ptrs, mask=nm[:, None], other=0.0)
acc = tl.sum(vals, axis=1)
tl.store(y_ptr + pid_b * syb + n_off * syn, acc.to(tl.bfloat16), mask=nm)
@triton.jit
def _setrow_kernel(buf_ptr, idx_ptr, val_ptr, D, stride_row, BLOCK_D: tl.constexpr):
row = tl.load(idx_ptr).to(tl.int64)
off = tl.arange(0, BLOCK_D)
m = off < D
v = tl.load(val_ptr + off, mask=m, other=0)
tl.store(buf_ptr + row * stride_row + off, v, mask=m)
@triton.jit
def _kda_update_kernel(S_ptr, o_ptr, q_ptr, k_ptr, v_ptr, gp_ptr, bp_ptr,
Dk, scale, sS0, sS1, sS2,
DK: tl.constexpr, BLOCK_D: tl.constexpr):
"""Fused gated-delta state update (per head, per D-tile).
ge = exp(-softplus(gp)) == sigmoid(-gp); beta = sigmoid(bp).
S <- S*ge ; pred = S^T k ; S <- S + beta * k (v-pred)^T ; o = S^T q (per head)
"""
pid_h = tl.program_id(0)
pid_d = tl.program_id(1)
d_off = pid_d * BLOCK_D + tl.arange(0, BLOCK_D)
dm = d_off < DK
j = tl.arange(0, DK)
q = tl.load(q_ptr + pid_h * Dk + j).to(tl.float32) * scale
k = tl.load(k_ptr + pid_h * Dk + j).to(tl.float32)
gp = tl.load(gp_ptr + pid_h * Dk + j).to(tl.float32)
ge = tl.sigmoid(-gp)
beta = tl.sigmoid(tl.load(bp_ptr + pid_h).to(tl.float32))
v = tl.load(v_ptr + pid_h * Dk + d_off, mask=dm, other=0.0).to(tl.float32)
Sb = S_ptr + pid_h * sS0
Sptrs = Sb + j[:, None] * sS1 + d_off[None, :] * sS2
S = tl.load(Sptrs, mask=dm[None, :], other=0.0)
S = S * ge[:, None]
pred = tl.sum(S * k[:, None], axis=0)
S = S + beta * k[:, None] * (v - pred)[None, :]
o = tl.sum(S * q[:, None], axis=0)
tl.store(Sptrs, S, mask=dm[None, :])
tl.store(o_ptr + pid_h * Dk + d_off, o.to(tl.bfloat16), mask=dm)
def _next_pow2(x):
p = 1
while p < x:
p *= 2
return p
def w4a16_gemv(x, wqT, sT, zT, idx, block_n=64):
B, K = x.shape
E, N, K2 = wqT.shape
NG = sT.shape[2]
BN = block_n if N >= block_n else _next_pow2(N)
nt = triton.cdiv(N, BN)
base = B * nt
need = (2 * _NUM_SMS + base - 1) // base
S = 1
while S < need:
S *= 2
S = min(S, _next_pow2(NG), 4)
NGPS = (NG + S - 1) // S
y = torch.empty((B, N), dtype=torch.bfloat16, device=x.device)
idx32 = idx.to(torch.int32)
if S == 1:
_gemv_T_kernel[(B, nt, 1)](
x, wqT, sT, zT, idx32, x, y, K, N, NG,
x.stride(0), x.stride(1), wqT.stride(0), sT.stride(0),
0, 0, 0, y.stride(0), y.stride(1),
GROUP=GROUP_SIZE, HALF=HALF, BLOCK_N=BN, NGPS=NGPS, SPLIT=1,
num_warps=4, num_stages=3,
)
else:
scratch = torch.empty((B, N, S), dtype=torch.float32, device=x.device)
_gemv_T_kernel[(B, nt, S)](
x, wqT, sT, zT, idx32, scratch, y, K, N, NG,
x.stride(0), x.stride(1), wqT.stride(0), sT.stride(0),
scratch.stride(0), scratch.stride(1), scratch.stride(2),
y.stride(0), y.stride(1),
GROUP=GROUP_SIZE, HALF=HALF, BLOCK_N=BN, NGPS=NGPS, SPLIT=S,
num_warps=4, num_stages=3,
)
_reduce_kernel[(B, nt)](
scratch, y, N, scratch.stride(0), scratch.stride(1), scratch.stride(2),
y.stride(0), y.stride(1), BLOCK_N=BN, S=S, num_warps=4,
)
return y
def _setrow(buf, idx_scalar, val, D):
_setrow_kernel[(1,)](buf, idx_scalar, val, D, D, BLOCK_D=_next_pow2(D), num_warps=1)
# --------------------------------------------------------------------------- #
# Quantized weight containers (identical buffer names / shapes as reference)
# --------------------------------------------------------------------------- #
class QuantLinear(nn.Module):
def __init__(self, in_f, out_f, group=GROUP_SIZE):
super().__init__()
assert in_f % group == 0 and in_f % 2 == 0
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, in_f, out_f, group=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))
def _ql_fused_T(quants):
wq = torch.cat([q.w_q for q in quants], dim=1)
s = torch.cat([q.scales for q in quants], dim=1)
z = torch.cat([q.zeros for q in quants], dim=1)
return wq.t().contiguous()[None], s.t().contiguous()[None], z.t().contiguous()[None]
def _qe_fused_T(qes):
wq = torch.cat([q.w_q for q in qes], dim=2)
s = torch.cat([q.scales for q in qes], dim=2)
z = torch.cat([q.zeros for q in qes], dim=2)
return wq.transpose(1, 2).contiguous(), s.transpose(1, 2).contiguous(), z.transpose(1, 2).contiguous()
def _unpack_int4(w_packed, K):
out = torch.empty((K, w_packed.shape[-1]), dtype=torch.uint8, device=w_packed.device)
out[[REDACTED: IP]] = w_packed & 0xF
out[[REDACTED: IP]] = (w_packed >> 4) & 0xF
return out
def _dequant_wbf(w_q, scales, zeros, K, group):
wu = _unpack_int4(w_q, K).to(torch.bfloat16)
s = scales.repeat_interleave(group, dim=0)
z = zeros.repeat_interleave(group, dim=0)
return (wu - 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 _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)
# --------------------------------------------------------------------------- #
# layers (operate in place on persistent state buffers; graph-friendly)
# --------------------------------------------------------------------------- #
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
self._prepared = False
def _prepare(self, dev):
self._qkvg_wq, self._qkvg_s, self._qkvg_z = _ql_fused_T(
[self.q_proj, self.k_proj, self.v_proj, self.g_proj])
self._o_wq, self._o_s, self._o_z = _ql_fused_T([self.o_proj])
self._idx0 = torch.zeros(1, dtype=torch.int32, device=dev)
self._prepared = True
def _short_conv(self, val, prev, idx):
win = torch.cat([prev, val[None]], dim=0)
w = self.conv_w[idx].float().transpose(0, 1)
out = F.silu((win.float() * w).sum(0)).to(val.dtype)
return out, win[1:]
def step(self, x, st):
if not self._prepared:
self._prepare(x.device)
H, Dk = self.cfg.kda_heads, self.cfg.kda_head_dim
qkvg = w4a16_gemv(x[None], self._qkvg_wq, self._qkvg_s, self._qkvg_z, self._idx0).squeeze(0)
q, k, v, g = qkvg.split(Dk * H, dim=0)
q, nq = self._short_conv(q, st["cq"], 0)
k, nk = self._short_conv(k, st["ck"], 1)
v, nv = self._short_conv(v, st["cv"], 2)
st["cq"].copy_(nq); st["ck"].copy_(nk); st["cv"].copy_(nv)
q = q.view(H, Dk).float() * self.scale
k = k.view(H, Dk).float()
v = v.view(H, Dk).float()
g = (-F.softplus(g.float())).view(H, Dk)
beta = torch.sigmoid(self.beta_proj(x).float())
S = st["S"]
S.mul_(g.exp()[:, :, None])
pred = (S * k[:, :, None]).sum(1)
S.add_(beta[:, None, None] * k[:, :, None] * (v - pred)[:, None, :])
o = (S * q[:, :, None]).sum(1)
return w4a16_gemv(o.reshape(H * Dk).to(torch.bfloat16)[None],
self._o_wq, self._o_s, self._o_z, self._idx0).squeeze(0)
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
self._prepared = False
def _prepare(self, dev):
kvb = _dequant_wbf(self.kv_b.w_q, self.kv_b.scales, self.kv_b.zeros,
self.kv_b.in_f, self.kv_b.group)
H = self.cfg.mla_heads
Wb = kvb.view(self.cfg.kv_lora, H, self.cfg.qk_nope + self.cfg.v_head)
self._Wk = Wb[:, :, : self.cfg.qk_nope].contiguous()
self._Wv = Wb[:, :, self.cfg.qk_nope:].contiguous()
self._q_wq, self._q_s, self._q_z = _ql_fused_T([self.q_proj])
self._ka_wq, self._ka_s, self._ka_z = _ql_fused_T([self.kv_a])
self._o_wq, self._o_s, self._o_z = _ql_fused_T([self.o_proj])
self._idx0 = torch.zeros(1, dtype=torch.int32, device=dev)
d = self.cfg.qk_rope
self._inv = (1.0 / (self.cfg.rope_theta ** (torch.arange(0, d, 2, device=dev, dtype=torch.float32) / d))).contiguous()
self._prepared = True
def step(self, x, st):
cfg = self.cfg
H = cfg.mla_heads
if not self._prepared:
self._prepare(x.device)
buf = st["c_kv"] # (CAP, kv_lora)
krb = st["k_rope"] # (CAP, qk_rope)
len_t = st["_len"] # int32 scalar tensor (old length, before append)
arange = st["_arange"] # (CAP,) int32
pos_f = len_t.to(torch.float32)
q = w4a16_gemv(x[None], self._q_wq, self._q_s, self._q_z, self._idx0).squeeze(0).view(H, cfg.qk_nope + cfg.qk_rope)
q_nope = q[:, : cfg.qk_nope].float()
q_rope = q[:, cfg.qk_nope:]
kv = w4a16_gemv(x[None], self._ka_wq, self._ka_s, self._ka_z, self._idx0).squeeze(0)
c_kv_new = kv[: cfg.kv_lora]
k_rope_new = kv[cfg.kv_lora:]
ang = self._inv * pos_f
cos = torch.cos(ang)
sin = torch.sin(ang)
q_rope = _apply_rope(q_rope, cos, sin).float()
k_rope_new = _apply_rope(k_rope_new, cos, sin)
_setrow(buf, len_t, c_kv_new, cfg.kv_lora)
_setrow(krb, len_t, k_rope_new, cfg.qk_rope)
len_t.add_(1) # new length = old + 1
q_abs = torch.einsum("ehd,hd->he", self._Wk.float(), q_nope) # (H, 512)
scores = q_abs.to(torch.bfloat16) @ buf.t() # (H, CAP)
scores = scores + (q_rope.to(torch.bfloat16) @ krb.t())
valid = arange < len_t # (CAP,)
scores = scores.masked_fill(~valid[None, :], float("-inf"))
scores = scores.float() * self.scale
p = torch.softmax(scores, dim=1)
cv = (p.to(torch.bfloat16) @ buf).float() # (H, 512)
o = torch.einsum("he,ehd->hd", cv, self._Wv.float()) # (H, 128)
return w4a16_gemv(o.reshape(H * cfg.v_head).to(torch.bfloat16)[None],
self._o_wq, self._o_s, self._o_z, self._idx0).squeeze(0)
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)
self._prepared = False
def _prepare(self, dev):
m = self.cfg.moe_inter
self._gu_wq, self._gu_s, self._gu_z = _qe_fused_T([self.gate, self.up])
self._d_wq, self._d_s, self._d_z = _qe_fused_T([self.down])
self._sgu_wq, self._sgu_s, self._sgu_z = _qe_fused_T([self.s_gate, self.s_up])
self._sd_wq, self._sd_s, self._sd_z = _qe_fused_T([self.s_down])
self._m = m
self._zidx = torch.zeros(self.cfg.n_shared, dtype=torch.int32, device=dev)
self._prepared = True
def step(self, x):
if not self._prepared:
self._prepare(x.device)
cfg = self.cfg
m = self._m
probs = torch.softmax(self.router(x).float(), dim=-1)
w, idx = torch.topk(probs, cfg.n_active)
w = (w / (w.sum() + 1e-9) * cfg.routed_scaling).float()
idx = idx.to(torch.int32)
xexp = x[None].expand(cfg.n_active, x.shape[0])
gu = w4a16_gemv(xexp, self._gu_wq, self._gu_s, self._gu_z, idx)
g, u = gu.split(m, dim=1)
hh = F.silu(g.float()) * u.float()
dd = w4a16_gemv(hh, self._d_wq, self._d_s, self._d_z, idx)
routed = (w[:, None] * dd.float()).sum(0)
sgu = w4a16_gemv(x[None].expand(cfg.n_shared, x.shape[0]),
self._sgu_wq, self._sgu_s, self._sgu_z, self._zidx)
sg, su = sgu.split(m, dim=1)
shh = F.silu(sg.float()) * su.float()
sd = w4a16_gemv(shh, self._sd_wq, self._sd_s, self._sd_z, self._zidx)
return (routed + sd[0].float()).to(torch.bfloat16)
class Block(nn.Module):
def __init__(self, cfg, kind):
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 step(self, x, st):
h = x + self.attn.step(_rmsnorm(x, self.attn_norm), st)
return h + self.moe.step(_rmsnorm(h, self.moe_norm))
# --------------------------------------------------------------------------- #
# Per-context graph context (persistent buffers + captured graph)
# --------------------------------------------------------------------------- #
class _CtxGraph:
def __init__(self, model, ctx):
cfg = model.cfg
dev = model._dev
self.ctx = ctx
self.cap = ctx + 2048
self.h = torch.zeros(cfg.hidden, device=dev, dtype=cfg.dtype)
self.S = []
self.cq = []
self.ck = []
self.cv = []
for blk in model.blocks:
if blk.kind == "K":
H, Dk = cfg.kda_heads, cfg.kda_head_dim
C = H * Dk
self.S.append(torch.zeros(H, Dk, Dk, device=dev, dtype=torch.float32))
self.cq.append(torch.zeros(cfg.short_conv - 1, C, device=dev, dtype=cfg.dtype))
self.ck.append(torch.zeros(cfg.short_conv - 1, C, device=dev, dtype=cfg.dtype))
self.cv.append(torch.zeros(cfg.short_conv - 1, C, device=dev, dtype=cfg.dtype))
self.c_kv = torch.zeros(self.cap, cfg.kv_lora, device=dev, dtype=cfg.dtype)
self.k_rope = torch.zeros(self.cap, cfg.qk_rope, device=dev, dtype=cfg.dtype)
self.len = torch.zeros(1, device=dev, dtype=torch.int32)
self.arange = torch.arange(self.cap, device=dev, dtype=torch.int32)
# state views per block
self.state_views = []
ki = 0
for blk in model.blocks:
if blk.kind == "K":
self.state_views.append({"S": self.S[ki], "cq": self.cq[ki],
"ck": self.ck[ki], "cv": self.cv[ki]})
ki += 1
else:
self.state_views.append({"c_kv": self.c_kv, "k_rope": self.k_rope,
"_len": self.len, "_arange": self.arange})
self.len_cpu = ctx
self.graph = None
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._dev = None
self._ctx_graphs = {}
self._active = None
def _run_step(self, cg):
h = cg.h
for blk, st in zip(self.blocks, cg.state_views):
h = blk.step(h, st)
cg.h.copy_(h)
def _load_state(self, cg, state):
ki = 0
for i, blk in enumerate(self.blocks):
if blk.kind == "K":
cg.S[ki].copy_(state[i]["S"])
cg.cq[ki].copy_(state[i]["cq"])
cg.ck[ki].copy_(state[i]["ck"])
cg.cv[ki].copy_(state[i]["cv"])
ki += 1
else:
ctx = cg.ctx
cg.c_kv[:ctx].copy_(state[i]["c_kv"])
cg.k_rope[:ctx].copy_(state[i]["k_rope"])
cg.len.fill_(cg.ctx)
cg.len_cpu = cg.ctx
def _writeback(self, cg, state):
ki = 0
for i, blk in enumerate(self.blocks):
if blk.kind == "K":
state[i]["S"] = cg.S[ki]
state[i]["cq"] = cg.cq[ki]
state[i]["ck"] = cg.ck[ki]
state[i]["cv"] = cg.cv[ki]
ki += 1
else:
state[i]["c_kv"] = cg.c_kv[: cg.len_cpu]
state[i]["k_rope"] = cg.k_rope[: cg.len_cpu]
def _build_ctx_graph(self, ctx, state):
dev = self._dev
for blk in self.blocks:
if not blk.attn._prepared:
blk.attn._prepare(dev)
if not blk.moe._prepared:
blk.moe._prepare(dev)
cg = _CtxGraph(self, ctx)
self._load_state(cg, state)
# warmup on a side stream (compiles Triton kernels, allocates buffers)
side = torch.cuda.Stream(device=dev)
side.wait_stream(torch.cuda.current_stream(dev))
with torch.cuda.stream(side):
for _ in range(3):
self._run_step(cg)
cg.len_cpu += 1
torch.cuda.current_stream(dev).wait_stream(side)
# capture (try/except: fall back to eager if anything is non-capturable)
try:
self._load_state(cg, state)
g = torch.cuda.CUDAGraph()
with torch.cuda.graph(g):
self._run_step(cg)
cg.graph = g
except Exception:
cg.graph = None
self._load_state(cg, state)
return cg
def step(self, hidden, state):
if self._dev is None:
self._dev = hidden.device
# detect fresh state / context switch via the MLA cache storage pointer
fresh = self._active is None or state[3]["c_kv"].data_ptr() != self._active.c_kv.data_ptr()
if fresh:
ctx = state[3]["c_kv"].shape[0]
cg = self._ctx_graphs.get(ctx)
if cg is None:
cg = self._build_ctx_graph(ctx, state)
self._ctx_graphs[ctx] = cg
else:
self._load_state(cg, state)
self._active = cg
cg = self._active
if hidden is not cg.h:
cg.h.copy_(hidden)
if cg.graph is not None:
cg.graph.replay()
else:
self._run_step(cg)
cg.len_cpu += 1
self._writeback(cg, state)
return cg.h, state
20260618_211254_zai-claude_glm-5.2_02_kimi_linear_decode