ProprietaryLegal's picture
PLI Labs public snapshot 2026-10-28 (history squashed)
e1acc15
Raw History Blame Contribute Delete
34.1 kB
"""Fused Triton kernels for the draft step's elementwise chains (SM86). Each one has a plain-torch twin in ops.py and a
test (tests/test_fused.py) that holds it to that twin: bit-identical where the math allows, else <= 1 bf16 step.
Unfused, the step ran ~1,400 kernels (the 20-iteration Sinkhorn alone ~480): ~2 ms of launch-bound work per round.
"""
from __future__ import annotations
import torch
import triton
import triton.language as tl
from triton.language.extra import libdevice
from .ops import _markov_bias_kernel # noqa: F401 (the serve's kernel; markov_argmax repeats its arithmetic)
# --------------------------------------------------------------------------------------------- mHC mixes + Sinkhorn
@triton.jit
def _hc_mix_partial_kernel(x_ptr, fn_ptr, part_ptr, K, CH, NS, BK: tl.constexpr):
"""Split-K partial dot products of one token's flattened stream with the 24 mix rows, plus its sum of squares."""
t = tl.program_id(0)
sp = tl.program_id(1)
r32 = tl.arange(0, 32)
rmask = r32 < 24
acc = tl.zeros([32], tl.float32)
ss = tl.zeros([BK], tl.float32)
for k0 in range(sp * CH, sp * CH + CH, BK):
cols = k0 + tl.arange(0, BK)
x = tl.load(x_ptr + t.to(tl.int64) * K + cols).to(tl.float32)
ss += x * x
w = tl.load(fn_ptr + r32[:, None] * K + cols[None, :], mask=rmask[:, None], other=0.0)
acc += tl.sum(w * x[None, :], axis=1)
acc = tl.where(r32 == 24, tl.sum(ss, axis=0), acc)
tl.store(part_ptr + (t * NS + sp) * 32 + r32, acc)
@triton.jit
def _hc_mix_finish_kernel(part_ptr, scale_ptr, base_ptr, pre_ptr, post_ptr, comb_ptr, K, NS, rms_eps, hc_eps,
ITERS: tl.constexpr, NSP: tl.constexpr):
t = tl.program_id(0)
sidx = tl.arange(0, NSP)
smask = sidx < NS
r4 = tl.arange(0, 4)
r16 = tl.arange(0, 16)
rows = part_ptr + (t * NS + sidx) * 32
acc_pre = tl.sum(tl.load(rows[:, None] + r4[None, :], mask=smask[:, None], other=0.0), axis=0)
acc_post = tl.sum(tl.load(rows[:, None] + 4 + r4[None, :], mask=smask[:, None], other=0.0), axis=0)
acc_comb = tl.sum(tl.load(rows[:, None] + 8 + r16[None, :], mask=smask[:, None], other=0.0), axis=0)
ss = tl.sum(tl.load(rows + 24, mask=smask, other=0.0), axis=0)
r = 1.0 / tl.sqrt(ss / K + rms_eps)
s0 = tl.load(scale_ptr)
s1 = tl.load(scale_ptr + 1)
s2 = tl.load(scale_ptr + 2)
pre = 1.0 / (1.0 + tl.exp(-(acc_pre * r * s0 + tl.load(base_ptr + r4)))) + hc_eps
post = 2.0 / (1.0 + tl.exp(-(acc_post * r * s1 + tl.load(base_ptr + 4 + r4))))
c = tl.reshape(acc_comb * r * s2 + tl.load(base_ptr + 8 + r16), (4, 4))
c = tl.exp(c - tl.max(c, axis=1)[:, None])
c = c / tl.sum(c, axis=1)[:, None] + hc_eps
c = c / (tl.sum(c, axis=0)[None, :] + hc_eps)
for _ in range(ITERS - 1):
c = c / (tl.sum(c, axis=1)[:, None] + hc_eps)
c = c / (tl.sum(c, axis=0)[None, :] + hc_eps)
tl.store(pre_ptr + t * 4 + r4, pre)
tl.store(post_ptr + t * 4 + r4, post)
tl.store(comb_ptr + t * 16 + r16, tl.reshape(c, (16,)))
_HC_SPLITS = 40 # 20480 / 40 = 512 columns a program: T x 40 programs (a 3090 has 82 SMs)
def hc_mixes(stream: torch.Tensor, fn: torch.Tensor, scale: torch.Tensor, base: torch.Tensor, rms_eps: float,
hc_eps: float, iters: int):
"""ops.hc_mixes: split-K GEMV of the flattened stream with the 24 mix rows (fixed-order sums, deterministic),
then sigmoid / softmax / Sinkhorn per token."""
T, hc, H = stream.shape
K = hc * H
assert hc == 4 and stream.is_contiguous() and fn.is_contiguous() and fn.shape == (24, K)
ns = _HC_SPLITS
assert K % (ns * 128) == 0
part = torch.empty(T, ns, 32, dtype=torch.float32, device=stream.device)
_hc_mix_partial_kernel[(T, ns)](stream, fn, part, K, K // ns, ns, BK=128, num_warps=4)
pre = torch.empty(T, 4, dtype=torch.float32, device=stream.device)
post = torch.empty(T, 4, dtype=torch.float32, device=stream.device)
comb = torch.empty(T, 4, 4, dtype=torch.float32, device=stream.device)
_hc_mix_finish_kernel[(T,)](part, scale, base, pre, post, comb, K, ns, rms_eps, hc_eps, ITERS=iters,
NSP=triton.next_power_of_2(ns), num_warps=1)
return pre, post, comb
# --------------------------------------------------------------------------------------------- collapse + RMSNorm
@triton.jit
def _collapse_norm_kernel(s_ptr, pre_ptr, w_ptr, out_ptr, H, eps, HAS_PRE: tl.constexpr, BH: tl.constexpr):
t = tl.program_id(0)
base = s_ptr + t.to(tl.int64) * 4 * H
ss = tl.zeros([BH], tl.float32)
if HAS_PRE:
p0 = tl.load(pre_ptr + t * 4)
p1 = tl.load(pre_ptr + t * 4 + 1)
p2 = tl.load(pre_ptr + t * 4 + 2)
p3 = tl.load(pre_ptr + t * 4 + 3)
for h0 in range(0, H, BH):
h = h0 + tl.arange(0, BH)
if HAS_PRE:
y = (p0 * tl.load(base + h).to(tl.float32) + p1 * tl.load(base + H + h).to(tl.float32)
+ p2 * tl.load(base + 2 * H + h).to(tl.float32) + p3 * tl.load(base + 3 * H + h).to(tl.float32))
yb = y.to(tl.bfloat16)
else:
yb = tl.load(base + h)
yf = yb.to(tl.float32)
ss += yf * yf
tl.store(out_ptr + t.to(tl.int64) * H + h, yb)
r = 1.0 / tl.sqrt(tl.sum(ss, axis=0) / H + eps)
for h0 in range(0, H, BH):
h = h0 + tl.arange(0, BH)
yf = tl.load(out_ptr + t.to(tl.int64) * H + h).to(tl.float32)
w = tl.load(w_ptr + h).to(tl.float32)
tl.store(out_ptr + t.to(tl.int64) * H + h, (w * (yf * r)).to(tl.bfloat16))
def collapse_norm(stream: torch.Tensor, pre: torch.Tensor | None, w: torch.Tensor, eps: float) -> torch.Tensor:
"""rms_norm(hc_collapse(stream, pre), w): collapse with pre-mix (or copy 0), round to bf16, RMSNorm."""
T, hc, H = stream.shape
assert hc == 4 and stream.is_contiguous()
out = torch.empty(T, H, dtype=torch.bfloat16, device=stream.device)
_collapse_norm_kernel[(T,)](stream, pre if pre is not None else stream, w, out, H, eps,
HAS_PRE=pre is not None, BH=1024, num_warps=4, enable_fp_fusion=False)
return out
@triton.jit
def _collapse_kernel(s_ptr, pre_ptr, out_ptr, H, BH: tl.constexpr):
t = tl.program_id(0)
h = tl.program_id(1) * BH + tl.arange(0, BH)
m = h < H
base = s_ptr + t.to(tl.int64) * 4 * H
y = (tl.load(pre_ptr + t * 4) * tl.load(base + h, mask=m).to(tl.float32)
+ tl.load(pre_ptr + t * 4 + 1) * tl.load(base + H + h, mask=m).to(tl.float32)
+ tl.load(pre_ptr + t * 4 + 2) * tl.load(base + 2 * H + h, mask=m).to(tl.float32)
+ tl.load(pre_ptr + t * 4 + 3) * tl.load(base + 3 * H + h, mask=m).to(tl.float32))
tl.store(out_ptr + t.to(tl.int64) * H + h, y.to(tl.bfloat16), mask=m)
def collapse(stream: torch.Tensor, pre: torch.Tensor) -> torch.Tensor:
T, hc, H = stream.shape
out = torch.empty(T, H, dtype=torch.bfloat16, device=stream.device)
_collapse_kernel[(T, triton.cdiv(H, 1024))](stream, pre, out, H, BH=1024, num_warps=4, enable_fp_fusion=False)
return out
# --------------------------------------------------------------------------------------------- mHC post
@triton.jit
def _hc_post_kernel(y_ptr, s_ptr, post_ptr, comb_ptr, out_ptr, H, BH: tl.constexpr):
t = tl.program_id(0)
h = tl.program_id(1) * BH + tl.arange(0, BH)
m = h < H
base = t.to(tl.int64) * 4 * H
y = tl.load(y_ptr + t.to(tl.int64) * H + h, mask=m).to(tl.float32)
s0 = tl.load(s_ptr + base + h, mask=m).to(tl.float32)
s1 = tl.load(s_ptr + base + H + h, mask=m).to(tl.float32)
s2 = tl.load(s_ptr + base + 2 * H + h, mask=m).to(tl.float32)
s3 = tl.load(s_ptr + base + 3 * H + h, mask=m).to(tl.float32)
for j in tl.static_range(4):
c0 = tl.load(comb_ptr + t * 16 + j)
c1 = tl.load(comb_ptr + t * 16 + 4 + j)
c2 = tl.load(comb_ptr + t * 16 + 8 + j)
c3 = tl.load(comb_ptr + t * 16 + 12 + j)
v = (c0 * s0 + c1 * s1 + c2 * s2 + c3 * s3) + tl.load(post_ptr + t * 4 + j) * y
tl.store(out_ptr + base + j * H + h, v.to(tl.bfloat16), mask=m)
def hc_post(y: torch.Tensor, stream: torch.Tensor, post: torch.Tensor, comb: torch.Tensor) -> torch.Tensor:
T, hc, H = stream.shape
out = torch.empty_like(stream)
_hc_post_kernel[(T, triton.cdiv(H, 1024))](y, stream, post, comb, out, H, BH=1024, num_warps=4,
enable_fp_fusion=False)
return out
# --------------------------------------------------------------------------------------------- RMSNorm
@triton.jit
def _rms_norm_kernel(x_ptr, w_ptr, out_ptr, D, eps, BD: tl.constexpr):
r_ = tl.program_id(0).to(tl.int64)
ss = tl.zeros([BD], tl.float32)
for d0 in range(0, D, BD):
d = d0 + tl.arange(0, BD)
m = d < D
x = tl.load(x_ptr + r_ * D + d, mask=m, other=0.0).to(tl.float32)
ss += x * x
r = 1.0 / tl.sqrt(tl.sum(ss, axis=0) / D + eps)
for d0 in range(0, D, BD):
d = d0 + tl.arange(0, BD)
m = d < D
x = tl.load(x_ptr + r_ * D + d, mask=m, other=0.0).to(tl.float32)
w = tl.load(w_ptr + d, mask=m, other=0.0).to(tl.float32)
tl.store(out_ptr + r_ * D + d, (w * (x * r)).to(tl.bfloat16), mask=m)
def rms_norm(x: torch.Tensor, w: torch.Tensor, eps: float) -> torch.Tensor:
"""x [N, D] bf16 (or fp32: rounded to bf16 first, as ops.rms_norm(x.to(bf16), ...))."""
if x.dtype != torch.bfloat16:
x = x.to(torch.bfloat16)
x = x.contiguous()
N, D = x.shape
out = torch.empty_like(x)
_rms_norm_kernel[(N,)](x, w, out, D, eps, BD=1024 if D >= 1024 else 512, num_warps=4, enable_fp_fusion=False)
return out
# --------------------------------------------------------------------------------------------- RoPE (+ KV record)
@triton.jit
def _rope_kernel(x_ptr, out_ptr, pos_ptr, cs_ptr, ROWS_PER_TOKEN, D: tl.constexpr, RD: tl.constexpr, INV: tl.constexpr,
QDQ: tl.constexpr):
row = tl.program_id(0).to(tl.int64)
tok = row // ROWS_PER_TOKEN
p = tl.load(pos_ptr + tok)
NOPE: tl.constexpr = D - RD
i = tl.arange(0, RD // 2)
c = tl.load(cs_ptr + p * RD + i)
s = tl.load(cs_ptr + p * RD + RD // 2 + i)
if INV:
s = -s
x0 = tl.load(x_ptr + row * D + NOPE + 2 * i).to(tl.float32)
x1 = tl.load(x_ptr + row * D + NOPE + 2 * i + 1).to(tl.float32)
tl.store(out_ptr + row * D + NOPE + 2 * i, (x0 * c - x1 * s).to(tl.bfloat16))
tl.store(out_ptr + row * D + NOPE + 2 * i + 1, (x1 * c + x0 * s).to(tl.bfloat16))
g = tl.arange(0, 64)
for b in tl.static_range(NOPE // 64):
v = tl.load(x_ptr + row * D + b * 64 + g)
if QDQ:
vf = v.to(tl.float32)
amax = tl.maximum(tl.max(tl.abs(vf), axis=0) * (1.0 / 448.0), 2.0 ** -126)
bits = amax.to(tl.uint32, bitcast=True)
e = ((bits >> 23) & 0xFF).to(tl.int32) - 127 + tl.where((bits & 0x7FFFFF) != 0, 1, 0)
sc = ((e + 127) << 23).to(tl.uint32).to(tl.float32, bitcast=True)
q = tl.minimum(tl.maximum(vf / sc, -448.0), 448.0)
# e4m3 round-to-nearest-even by hand (SM86 has no fp8 conversion): quantum 2^(max(e, -6) - 3)
qb = q.to(tl.uint32, bitcast=True)
qe = tl.maximum(((qb >> 23) & 0xFF).to(tl.int32) - 127, -6)
quant = ((qe - 3 + 127) << 23).to(tl.uint32).to(tl.float32, bitcast=True)
q = libdevice.rint(q / quant) * quant
v = (q * sc).to(tl.bfloat16)
tl.store(out_ptr + row * D + b * 64 + g, v)
def rope(x: torch.Tensor, pos: torch.Tensor, cos_sin: torch.Tensor, inverse: bool = False) -> torch.Tensor:
"""ops.apply_rope for bf16 x [N, D] or [N, heads, D] (rotates the last 64 channels)."""
x = x.contiguous()
D = x.shape[-1]
rows = x.numel() // D
per = rows // x.shape[0]
out = torch.empty_like(x)
_rope_kernel[(rows,)](x, out, pos, cos_sin, per, D=D, RD=cos_sin.shape[-1], INV=inverse, QDQ=False,
num_warps=1, enable_fp_fusion=False)
return out
def kv_rope_qdq(kv: torch.Tensor, pos: torch.Tensor, cos_sin: torch.Tensor) -> torch.Tensor:
"""ops.kv_record_qdq(ops.apply_rope(kv, pos)) for kv [N, 512] bf16: the V4 record values."""
kv = kv.contiguous()
out = torch.empty_like(kv)
_rope_kernel[(kv.shape[0],)](kv, out, pos, cos_sin, 1, D=kv.shape[-1], RD=cos_sin.shape[-1], INV=False,
QDQ=True, num_warps=1, enable_fp_fusion=False)
return out
# --------------------------------------------------------------------------------------------- Markov bias + argmax
@triton.jit
def _markov_argmax_kernel(ids_ptr, slot_ptr, cache_ptr, emb_ptr, w2_ptr, base_ptr, vals_ptr, idx_ptr, V,
cache_stride, w2_stride, emb_stride, base_stride, NBLK, ids_stride, R: tl.constexpr,
BLOCK_V: tl.constexpr, EMB_BY_TOKEN: tl.constexpr):
b = tl.program_id(0)
blk = tl.program_id(1)
cols = blk * BLOCK_V + tl.arange(0, BLOCK_V)
mask = cols < V
tok = tl.load(ids_ptr + b * ids_stride).to(tl.int64)
slot = tl.load(slot_ptr + tok)
if slot >= 0:
bias = tl.load(cache_ptr + slot.to(tl.int64) * cache_stride + cols, mask=mask, other=0.0)
else:
r = tl.arange(0, R)
if EMB_BY_TOKEN: # emb_ptr = the embedding table: row of the previous token
e = tl.load(emb_ptr + tok * emb_stride + r).to(tl.float32)
else:
e = tl.load(emb_ptr + b * emb_stride + r).to(tl.float32)
w = tl.load(w2_ptr + cols[:, None].to(tl.int64) * w2_stride + r[None, :], mask=mask[:, None], other=0.0)
bias = tl.sum(w.to(tl.float32) * e[None, :], axis=1).to(tl.bfloat16)
lg = tl.load(base_ptr + b.to(tl.int64) * base_stride + cols, mask=mask, other=0.0)
li = (lg.to(tl.float32) + bias.to(tl.float32)).to(tl.bfloat16).to(tl.float32)
li = tl.where(mask, li, float("-inf"))
v, i = tl.max(li, axis=0, return_indices=True, return_indices_tie_break_left=True)
tl.store(vals_ptr + b * NBLK + blk, v)
tl.store(idx_ptr + b * NBLK + blk, (blk * BLOCK_V + i).to(tl.int64))
def markov_argmax(base: torch.Tensor, prev: torch.Tensor, emb: torch.Tensor, mb) -> torch.Tensor:
"""argmax_v bf16(base[b, v] + bias(prev_b)[v]) per row, ties -> lowest id. base [B, V] bf16 (any row stride)."""
B, V = base.shape
assert base.stride(1) == 1
BLOCK_V = 64
nblk = triton.cdiv(V, BLOCK_V)
vals = torch.empty(B, nblk, dtype=torch.float32, device=base.device)
idx = torch.empty(B, nblk, dtype=torch.int64, device=base.device)
emb = emb.contiguous()
_markov_argmax_kernel[(B, nblk)](prev, mb.slot_of_token, mb.cache, emb, mb.w2, base, vals, idx, V,
mb.cache.stride(0), mb.w2.stride(0), emb.stride(0), base.stride(0), nblk,
prev.stride(0), R=emb.shape[1], BLOCK_V=BLOCK_V, EMB_BY_TOKEN=False,
num_warps=4)
best = vals.argmax(dim=-1, keepdim=True)
return idx.gather(1, best).squeeze(1)
@triton.jit
def _markov_cached_kernel(ids_ptr, slot_ptr, cache_ptr, base_ptr, vals_ptr, idx_ptr, V, cache_stride, base_stride,
NBLK, ids_stride, BLOCK_V: tl.constexpr):
"""Cached previous token: block max of bf16(base + cache row) (first maximum = lowest id). Exits otherwise."""
b = tl.program_id(0)
blk = tl.program_id(1)
tok = tl.load(ids_ptr + b * ids_stride).to(tl.int64)
slot = tl.load(slot_ptr + tok)
if slot >= 0:
cols = blk * BLOCK_V + tl.arange(0, BLOCK_V)
mask = cols < V
bias = tl.load(cache_ptr + slot.to(tl.int64) * cache_stride + cols, mask=mask, other=0.0)
lg = tl.load(base_ptr + b.to(tl.int64) * base_stride + cols, mask=mask, other=0.0)
li = (lg.to(tl.float32) + bias.to(tl.float32)).to(tl.bfloat16).to(tl.float32)
li = tl.where(mask, li, float("-inf"))
v, i = tl.max(li, axis=0, return_indices=True, return_indices_tie_break_left=True)
tl.store(vals_ptr + b * NBLK + blk, v)
tl.store(idx_ptr + b * NBLK + blk, (blk * BLOCK_V + i).to(tl.int64))
@triton.jit
def _markov_uncached_kernel(ids_ptr, slot_ptr, emb_ptr, w2_ptr, base_ptr, vals_ptr, idx_ptr, V, w2_stride,
emb_stride, base_stride, NBLK, ids_stride, R: tl.constexpr, BLOCK_V: tl.constexpr,
SUB: tl.constexpr):
"""Uncached previous token: the serve's bias arithmetic (_markov_bias_kernel: [BLOCK_V, R] tile, fp32 products,
one bf16 rounding) over SUB consecutive BLOCK_V-column blocks, running max with strict > (lowest id on ties).
Exits for cached tokens."""
b = tl.program_id(0)
pb = tl.program_id(1)
tok = tl.load(ids_ptr + b * ids_stride).to(tl.int64)
slot = tl.load(slot_ptr + tok)
if slot < 0:
r = tl.arange(0, R)
e = tl.load(emb_ptr + tok * emb_stride + r).to(tl.float32)
best_v = float("-inf")
best_i = (pb * SUB * BLOCK_V).to(tl.int64)
for sb in range(SUB):
cols = (pb * SUB + sb) * BLOCK_V + tl.arange(0, BLOCK_V)
mask = cols < V
w = tl.load(w2_ptr + cols[:, None].to(tl.int64) * w2_stride + r[None, :], mask=mask[:, None], other=0.0)
bias = tl.sum(w.to(tl.float32) * e[None, :], axis=1).to(tl.bfloat16)
lg = tl.load(base_ptr + b.to(tl.int64) * base_stride + cols, mask=mask, other=0.0)
li = (lg.to(tl.float32) + bias.to(tl.float32)).to(tl.bfloat16).to(tl.float32)
li = tl.where(mask, li, float("-inf"))
v, i = tl.max(li, axis=0, return_indices=True, return_indices_tie_break_left=True)
take = v > best_v
best_i = tl.where(take, ((pb * SUB + sb) * BLOCK_V + i).to(tl.int64), best_i)
best_v = tl.where(take, v, best_v)
tl.store(vals_ptr + b * NBLK + pb, best_v)
tl.store(idx_ptr + b * NBLK + pb, best_i)
@triton.jit
def _argmax_finish_kernel(ids_ptr, slot_ptr, vals_c_ptr, idx_c_ptr, vals_u_ptr, idx_u_ptr, out_ptr, NBLK_C, NBLK_U,
ids_stride, out_stride, col, BLOCK: tl.constexpr):
b = tl.program_id(0)
tok = tl.load(ids_ptr + b * ids_stride).to(tl.int64)
cached = tl.load(slot_ptr + tok) >= 0
j = tl.arange(0, BLOCK)
vc = tl.load(vals_c_ptr + b * NBLK_C + j, mask=j < NBLK_C, other=float("-inf"))
vu = tl.load(vals_u_ptr + b * NBLK_U + j, mask=j < NBLK_U, other=float("-inf"))
v = tl.where(cached, vc, vu)
_, jm = tl.max(v, axis=0, return_indices=True, return_indices_tie_break_left=True)
if cached:
d = tl.load(idx_c_ptr + b * NBLK_C + jm)
else:
d = tl.load(idx_u_ptr + b * NBLK_U + jm)
tl.store(out_ptr + b * out_stride + col, d)
def markov_argmax_into(base: torch.Tensor, prev: torch.Tensor, mb, out: torch.Tensor, col: int) -> None:
"""out[:, col] = markov_argmax(base, prev, mb.embed(prev), mb) (same values; ties -> lowest id). Three launches:
cached previous tokens take a wide-block kernel (2048 columns per program), uncached ones the serve's bias tile
looped over 8 blocks per program (each kernel exits for the other kind), and a finish kernel reduces the block
maxima straight into the output column. The Markov embedding row is read by token. prev may be a strided column
(out[:, col - 1])."""
B, V = base.shape
assert base.stride(1) == 1 and out.dtype == torch.int64
BC, BU, SUB = 2048, 64, 8
nc, nu = triton.cdiv(V, BC), triton.cdiv(V, BU * SUB)
vals_c = torch.empty(B, nc, dtype=torch.float32, device=base.device)
idx_c = torch.empty(B, nc, dtype=torch.int64, device=base.device)
vals_u = torch.empty(B, nu, dtype=torch.float32, device=base.device)
idx_u = torch.empty(B, nu, dtype=torch.int64, device=base.device)
_markov_cached_kernel[(B, nc)](prev, mb.slot_of_token, mb.cache, base, vals_c, idx_c, V, mb.cache.stride(0),
base.stride(0), nc, prev.stride(0), BLOCK_V=BC, num_warps=8)
_markov_uncached_kernel[(B, nu)](prev, mb.slot_of_token, mb.w1, mb.w2, base, vals_u, idx_u, V, mb.w2.stride(0),
mb.w1.stride(0), base.stride(0), nu, prev.stride(0), R=mb.w1.shape[1],
BLOCK_V=BU, SUB=SUB, num_warps=4)
_argmax_finish_kernel[(B,)](prev, mb.slot_of_token, vals_c, idx_c, vals_u, idx_u, out, nc, nu, prev.stride(0),
out.stride(0), col, BLOCK=triton.next_power_of_2(max(nc, nu)), num_warps=4)
# --------------------------------------------------------------------------------------------- block attention
@triton.jit
def _block_attn_kernel(q_ptr, ring_ptr, ring_pos_ptr, slots_ptr, lo_ptr, last_ptr, blk_ptr, sink_ptr, out_ptr,
scale, k, ring_slot_stride, ring_pos_stride, W: tl.constexpr, D: tl.constexpr, HH: tl.constexpr,
BM: tl.constexpr, KB: tl.constexpr, BD: tl.constexpr, DO: tl.constexpr):
"""Program (b, m, n): BM query rows (row = t * HH + h) of request b, output dims [n * DO, (n + 1) * DO).
Scores over the W window ring rows of b's slot (masked to positions in [lo, last]) and its k block rows (padded
to KB), sink in the denominator, single-pass softmax, probabilities rounded to bf16 for P @ V. Keys = values."""
b = tl.program_id(0)
rows = tl.program_id(1) * BM + tl.arange(0, BM)
d_out = tl.program_id(2) * DO + tl.arange(0, DO)
nrows = k * HH
rmask = rows < nrows
slot = tl.load(slots_ptr + b).to(tl.int64)
lo = tl.load(lo_ptr + b)
hi = tl.load(last_ptr + b)
jc = tl.arange(0, W)
jb = tl.arange(0, KB)
bmask = jb < k
cpos = tl.load(ring_pos_ptr + slot * ring_pos_stride + jc)
cvalid = (cpos >= lo) & (cpos <= hi)
ring_base = ring_ptr + slot * ring_slot_stride
blk_base = blk_ptr + b.to(tl.int64) * k * D
q_base = q_ptr + b.to(tl.int64) * nrows * D
sc = tl.zeros([BM, W], tl.float32)
sb = tl.zeros([BM, KB], tl.float32)
for d0 in tl.static_range(0, D, BD):
d = d0 + tl.arange(0, BD)
qc = tl.load(q_base + rows[:, None] * D + d[None, :], mask=rmask[:, None], other=0.0)
kc = tl.load(ring_base + jc[:, None] * D + d[None, :])
kb = tl.load(blk_base + jb[:, None] * D + d[None, :], mask=bmask[:, None], other=0.0)
sc += tl.dot(qc, tl.trans(kc))
sb += tl.dot(qc, tl.trans(kb))
sc = tl.where(cvalid[None, :], sc * scale, float("-inf"))
sb = tl.where(bmask[None, :], sb * scale, float("-inf"))
sk = tl.load(sink_ptr + rows % HH, mask=rmask, other=0.0)
mx = tl.maximum(tl.maximum(tl.max(sc, axis=1), tl.max(sb, axis=1)), sk)
pc = tl.exp(sc - mx[:, None])
pb = tl.exp(sb - mx[:, None])
denom = tl.sum(pc, axis=1) + tl.sum(pb, axis=1) + tl.exp(sk - mx)
vc = tl.load(ring_base + jc[:, None] * D + d_out[None, :])
vb = tl.load(blk_base + jb[:, None] * D + d_out[None, :], mask=bmask[:, None], other=0.0)
acc = tl.dot(pc.to(tl.bfloat16), vc) + tl.dot(pb.to(tl.bfloat16), vb)
o_base = out_ptr + b.to(tl.int64) * nrows * D
tl.store(o_base + rows[:, None] * D + d_out[None, :], (acc / denom[:, None]).to(tl.bfloat16), mask=rmask[:, None])
def block_attention_ring(q: torch.Tensor, ring_stage: torch.Tensor, ring_pos: torch.Tensor, slots: torch.Tensor,
ctx_lo: torch.Tensor, last_pos: torch.Tensor, blk_kv: torch.Tensor, sink: torch.Tensor,
scale: float, BM: int | None = None, DO: int = 256, BD: int = 64,
num_warps: int | None = None) -> torch.Tensor:
"""ops.block_attention with the context read straight from the window ring of each request's slot.
q [B, k, Hh, D] bf16; ring_stage [S, W, D] (one stage's ring, any slot stride); ring_pos [S, W] int64;
slots / ctx_lo / last_pos int64 [B]; blk_kv [B, k, D] bf16; sink [Hh] fp32. Returns bf16 [B, k, Hh, D].
Default tiles (3090, profiler kernel time per stage): <= 640 query rows BM 16 / 8 warps (14.7 us at 1x5x64),
else BM 32 / 4 warps (21.0 us at 4x5x64); the gathered torch path took 70-155 us."""
B, k, Hh, D = q.shape
if BM is None:
BM, num_warps = (16, 8) if B * k * Hh <= 640 else (32, 4)
W = ring_stage.shape[1]
KB = 16
assert k <= KB and ring_stage.stride(2) == 1 and ring_stage.stride(1) == D and ring_pos.stride(1) == 1
assert W == triton.next_power_of_2(W) and D % DO == 0 and D % BD == 0
q = q.contiguous()
blk_kv = blk_kv.contiguous()
out = torch.empty_like(q)
_block_attn_kernel[(B, triton.cdiv(k * Hh, BM), D // DO)](
q, ring_stage, ring_pos, slots, ctx_lo, last_pos, blk_kv, sink, out, scale, k, ring_stage.stride(0),
ring_pos.stride(0), W=W, D=D, HH=Hh, BM=BM, KB=KB, BD=BD, DO=DO, num_warps=num_warps, num_stages=1)
return out
# --------------------------------------------------------------------------------------------- context insert
@triton.jit
def _amax_kernel(x_ptr, amax_ptr, N, BLOCK: tl.constexpr):
offs = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
v = tl.load(x_ptr + offs, mask=offs < N, other=0.0).to(tl.float32)
tl.atomic_max(amax_ptr, tl.max(tl.abs(v), axis=0))
@triton.jit
def _prescale_kernel(x_ptr, amax_ptr, out_ptr, sc_ptr, N, BLOCK: tl.constexpr):
# the plugin's sc = exp2(clamp_min(ceil(log2(clamp_min(amax, 1) / 16384)), 0)) with the libdevice functions torch
# calls; x / sc is exact (a power of two), then fp16
a = tl.maximum(tl.load(amax_ptr), 1.0) / 16384.0
sc = libdevice.exp2(tl.maximum(libdevice.ceil(libdevice.log2(a)), 0.0))
pid = tl.program_id(0)
offs = pid.to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
m = offs < N
v = tl.load(x_ptr + offs, mask=m, other=0.0).to(tl.float32)
tl.store(out_ptr + offs, (v / sc).to(tl.float16), mask=m)
if pid == 0:
tl.store(sc_ptr, sc)
def prescale_fp16(x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""main_proj's input scaling (vllm_exl3_moe dense.py): x (any float) -> (fp16 x / sc, fp32 sc [1]), sc the power
of two >= 1 that brings max |x| under 2^14. Two launches (+ a memset) instead of the ~12 torch kernels."""
x = x.contiguous()
N = x.numel()
amax = torch.zeros(1, dtype=torch.float32, device=x.device)
out = torch.empty(x.shape, dtype=torch.float16, device=x.device)
sc = torch.empty(1, dtype=torch.float32, device=x.device)
BLOCK = 4096
grid = (triton.cdiv(N, BLOCK),)
_amax_kernel[grid](x, amax, N, BLOCK=BLOCK, num_warps=8)
_prescale_kernel[grid](x, amax, out, sc, N, BLOCK=BLOCK, num_warps=8, enable_fp_fusion=False)
return out, sc
@triton.jit
def _scaled_rms_norm_kernel(y_ptr, sc_ptr, w_ptr, out_ptr, D, eps, BD: tl.constexpr):
r_ = tl.program_id(0).to(tl.int64)
sc = tl.load(sc_ptr)
ss = tl.zeros([BD], tl.float32)
for d0 in range(0, D, BD):
d = d0 + tl.arange(0, BD)
m = d < D
x = (tl.load(y_ptr + r_ * D + d, mask=m, other=0.0) * sc).to(tl.bfloat16).to(tl.float32)
ss += x * x
r = 1.0 / tl.sqrt(tl.sum(ss, axis=0) / D + eps)
for d0 in range(0, D, BD):
d = d0 + tl.arange(0, BD)
m = d < D
x = (tl.load(y_ptr + r_ * D + d, mask=m, other=0.0) * sc).to(tl.bfloat16).to(tl.float32)
w = tl.load(w_ptr + d, mask=m, other=0.0).to(tl.float32)
tl.store(out_ptr + r_ * D + d, (w * (x * r)).to(tl.bfloat16), mask=m)
def scaled_rms_norm(y: torch.Tensor, sc: torch.Tensor, w: torch.Tensor, eps: float) -> torch.Tensor:
"""rms_norm(y * sc, w) for fp32 y [N, D] and a device scalar sc: the product rounded to bf16 first, as
rms_norm(x.to(bf16)) after the plugin's y * sc."""
y = y.contiguous()
N, D = y.shape
out = torch.empty(N, D, dtype=torch.bfloat16, device=y.device)
_scaled_rms_norm_kernel[(N,)](y, sc, w, out, D, eps, BD=1024 if D >= 1024 else 512, num_warps=4,
enable_fp_fusion=False)
return out
@triton.jit
def _kv_store_kernel(y_ptr, w_ptr, normed_ptr, pos_ptr, cs_ptr, slot_ptr, ring_ptr, ring_pos_ptr, eps,
ring_slot_stride, ring_pos_stride, W, D: tl.constexpr, RD: tl.constexpr,
STORE_POS: tl.constexpr):
"""Row r: kv_norm (as _rms_norm_kernel) -> RoPE + V4 record (as _rope_kernel QDQ) -> ring[slot, pos % W]."""
row = tl.program_id(0).to(tl.int64)
d = tl.arange(0, D)
x = tl.load(y_ptr + row * D + d).to(tl.bfloat16).to(tl.float32)
ss = x * x
r = 1.0 / tl.sqrt(tl.sum(ss, axis=0) / D + eps)
w = tl.load(w_ptr + d).to(tl.float32)
tl.store(normed_ptr + row * D + d, (w * (x * r)).to(tl.bfloat16))
tl.debug_barrier()
p = tl.load(pos_ptr + row)
slot = tl.load(slot_ptr + row).to(tl.int64)
dst = ring_ptr + slot * ring_slot_stride + (p % W) * D
NOPE: tl.constexpr = D - RD
i = tl.arange(0, RD // 2)
c = tl.load(cs_ptr + p * RD + i)
s = tl.load(cs_ptr + p * RD + RD // 2 + i)
x0 = tl.load(normed_ptr + row * D + NOPE + 2 * i).to(tl.float32)
x1 = tl.load(normed_ptr + row * D + NOPE + 2 * i + 1).to(tl.float32)
tl.store(dst + NOPE + 2 * i, (x0 * c - x1 * s).to(tl.bfloat16))
tl.store(dst + NOPE + 2 * i + 1, (x1 * c + x0 * s).to(tl.bfloat16))
g = tl.arange(0, 64)
for b in tl.static_range(NOPE // 64):
vf = tl.load(normed_ptr + row * D + b * 64 + g).to(tl.float32)
amax = tl.maximum(tl.max(tl.abs(vf), axis=0) * (1.0 / 448.0), 2.0 ** -126)
bits = amax.to(tl.uint32, bitcast=True)
e = ((bits >> 23) & 0xFF).to(tl.int32) - 127 + tl.where((bits & 0x7FFFFF) != 0, 1, 0)
sc = ((e + 127) << 23).to(tl.uint32).to(tl.float32, bitcast=True)
q = tl.minimum(tl.maximum(vf / sc, -448.0), 448.0)
qb = q.to(tl.uint32, bitcast=True)
qe = tl.maximum(((qb >> 23) & 0xFF).to(tl.int32) - 127, -6)
quant = ((qe - 3 + 127) << 23).to(tl.uint32).to(tl.float32, bitcast=True)
q = libdevice.rint(q / quant) * quant
tl.store(dst + b * 64 + g, (q * sc).to(tl.bfloat16))
if STORE_POS:
tl.store(ring_pos_ptr + slot * ring_pos_stride + p % W, p)
def kv_store(y: torch.Tensor, w: torch.Tensor, eps: float, pos: torch.Tensor, cos_sin: torch.Tensor,
ring_stage: torch.Tensor, slots: torch.Tensor, ring_pos: torch.Tensor | None = None) -> None:
"""ring_stage[slots, pos % W] = kv_rope_qdq(rms_norm(y, w), pos) for fp32 y [N, D] (wkv output); also
ring_pos[slots, pos % W] = pos when ring_pos is given. Callers pass distinct (slot, pos % W) per call."""
y = y.contiguous()
N, D = y.shape
W = ring_stage.shape[1]
assert ring_stage.stride(2) == 1 and ring_stage.stride(1) == D and (ring_pos is None or ring_pos.stride(1) == 1)
normed = torch.empty(N, D, dtype=torch.bfloat16, device=y.device)
_kv_store_kernel[(N,)](y, w, normed, pos, cos_sin, slots, ring_stage, ring_pos if ring_pos is not None else pos,
eps, ring_stage.stride(0), ring_pos.stride(0) if ring_pos is not None else 0, W, D=D,
RD=cos_sin.shape[-1], STORE_POS=ring_pos is not None, num_warps=4, enable_fp_fusion=False)
# --------------------------------------------------------------------------------------------- wo_a (FP8 W8A16)
@triton.jit
def _wo_a_fp8_kernel(o_ptr, w_ptr, sc_ptr, out_ptr, T, o_stride, out_stride, C: tl.constexpr, R: tl.constexpr,
BM: tl.constexpr, BN: tl.constexpr, BK: tl.constexpr):
"""out[t, g*R + r] = sum_c o[t, g*C + c] * (w[g*R + r, c] * sc[(g*R + r) // 32, c // 32]); the dequantized weight is
exact in bf16, products accumulate in fp32 (bf16 MMA), bf16 out. BN <= 32 and BN | 32: one scale row per tile.
e4m3 -> value: the byte's sign and 7 magnitude bits placed as fp16 bits (exponent field = e4m3 exponent, mantissa
top bits = e4m3 mantissa) are the value / 2^8 exactly, subnormals included; x (2^8 * scale) is exact."""
g = tl.program_id(0)
r0 = g * R + tl.program_id(1) * BN
t = tl.arange(0, BM)
r = r0 + tl.arange(0, BN)
tmask = t < T
acc = tl.zeros([BM, BN], tl.float32)
for k0 in range(0, C, BK):
c = k0 + tl.arange(0, BK)
a = tl.load(o_ptr + t[:, None].to(tl.int64) * o_stride + g * C + c[None, :], mask=tmask[:, None], other=0.0)
b16 = tl.load(w_ptr + r[:, None].to(tl.int64) * C + c[None, :]).to(tl.int16)
h = (((b16 & 0x80) << 8) | ((b16 & 0x7F) << 7)).to(tl.float16, bitcast=True)
scv = tl.load(sc_ptr + (r0 // 32) * (C // 32) + c // 32) * 256.0
w = (h.to(tl.float32) * scv[None, :]).to(tl.bfloat16)
acc += tl.dot(a, tl.trans(w))
tl.store(out_ptr + t[:, None].to(tl.int64) * out_stride + r[None, :], acc.to(tl.bfloat16), mask=tmask[:, None])
def wo_a_fp8(o: torch.Tensor, w8: torch.Tensor, sc: torch.Tensor, groups: int, BN: int = 32, BK: int = 128,
num_warps: int = 2, num_stages: int | None = None) -> torch.Tensor:
"""o [T, groups * C] bf16 (rows may be strided), w8 [groups * R, C] uint8 (e4m3fn bytes, no NaN),
sc [groups*R/32, C/32] fp32 (UE8M0 values) -> [T, groups * R] bf16: the engine's W8A16 wo_a
(Sm12xMarlinMxfp8Bmm) on SM86, reading the FP8 bytes (32 MiB per stage instead of 64 MiB of expanded bf16)."""
T = o.shape[0]
GR, C = w8.shape
R = GR // groups
assert o.shape[1] == groups * C and o.stride(1) == 1 and w8.is_contiguous() and sc.is_contiguous()
assert 32 % BN == 0 and R % 32 == 0 and C % BK == 0
out = torch.empty(T, GR, dtype=torch.bfloat16, device=o.device)
BM = max(16, triton.next_power_of_2(T))
if num_stages is None: # 3090, profiler: 51 us/stage at <= 16 rows (bf16 bmm 95), 80 us at 17-32 rows
num_stages = 3 if BM == 16 else 2
_wo_a_fp8_kernel[(groups, R // BN)](o, w8, sc, out, T, o.stride(0), out.stride(0), C=C, R=R, BM=BM, BN=BN,
BK=BK, num_warps=num_warps, num_stages=num_stages)
return out