dsv41-offload / offload /dspark /native /reference.py
ProprietaryLegal's picture
PLI Labs public snapshot 2026-10-28 (history squashed)
0693474
Raw History Blame Contribute Delete
6.86 kB
"""Reference ops with the OFFICIAL 1017 semantics (inference/model.py + kernel.py @ 1bae0e10), device-agnostic torch.
These are the oracles for N1's loader / integration and for the KX kernels (NATIVE.md "Kernel contract"). They are
written for clarity and exactness, not speed. Accumulation: ``acc="f64"`` sums exact products in float64 and rounds
once (the real-number result rounded to the output dtype: the best any kernel can do); ``acc="blocked"`` is the
official kernels' order: each 32-wide K block summed (float64, then fp32, standing in for the MMA's fp32 block sum),
scaled, and accumulated in fp32 block by block (kernel.fp8_gemm / fp4_gemm C_local_accum). A correct kernel lands
within fp32 summation-order noise of both; anything further is a bug.
Rounding points the official model has and every native path must keep (NATIVE.md):
* every FP8/FP4 linear quantizes its input with act_qdq (per 32, power-of-two scale, e4m3) and returns bf16;
* Expert: gate = bf16(w1 x).float(), up = bf16(w3 x).float(); up clamped to [-10, 10], gate to <= 10;
h = silu(gate) * up (fp32), h = route_w * h (fp32), bf16(h) -> act_qdq -> w2 -> bf16 per expert;
* MoE: y = 0 (fp32); experts in ASCENDING expert id: y[rows] += bf16 expert output; y += bf16 shared output;
out = bf16(y);
* Gate: scores = fp32(x) @ fp32(W)^T (no TF32) -> sqrt(softplus); top-3 of (scores + bias) with the fp32 bias;
weights = scores at those ids / (sum + 1e-20) * 1.5 (bias only selects).
"""
from __future__ import annotations
import torch
import torch.nn.functional as Fn
from . import formats as F
SWIGLU_LIMIT = 10.0
ROUTE_SCALE = 1.5
TOPK = 3
def _matmul_acc(a: torch.Tensor, w: torch.Tensor, sa: torch.Tensor | None, sw: torch.Tensor | None, acc: str
) -> torch.Tensor:
"""sum_k a[m,k] * w[n,k] with optional per-32 scales already folded (sa/sw None) or blocked (acc='blocked':
a, w are CODES, sa [M, K/32], sw [N, K/32] the per-block scales). Returns fp32 [M, N]."""
if acc == "f64":
return (a.double() @ w.double().t()).float()
if acc != "blocked":
raise ValueError(acc)
M, K = a.shape
N = w.shape[0]
nb = K // F.BLOCK
ab = a.double().view(M, nb, F.BLOCK)
wb = w.double().view(N, nb, F.BLOCK)
out = torch.zeros(M, N, dtype=torch.float32, device=a.device)
for b in range(nb):
part = (ab[:, b] @ wb[:, b].t()).float() # exact block sum, rounded to fp32
if sa is not None:
part = part * sa[:, b:b + 1] * sw[:, b].unsqueeze(0) # (C * sa) * sb, powers of two: exact
out += part
return out
def linear_fp4(x: torch.Tensor, w_packed: torch.Tensor, w_scale: torch.Tensor, acc: str = "f64") -> torch.Tensor:
"""model.linear for a float4_e2m1fn_x2 weight: act_quant(x) then fp4_gemm; bf16 [M, N]."""
shape = x.shape
x2 = x.reshape(-1, shape[-1])
if acc == "blocked":
q, s = F.act_quant(x2)
codes = F._fp4_lut(x.device)[F.fp4_codes(w_packed).long()]
y = _matmul_acc(q, codes, s, F.e8m0_to_f32(w_scale), acc)
else:
y = _matmul_acc(F.act_qdq(x2).float(), F.decode_fp4(w_packed, w_scale), None, None, acc)
return y.to(torch.bfloat16).view(*shape[:-1], -1)
def linear_fp8(x: torch.Tensor, w_u8: torch.Tensor, w_scale: torch.Tensor, acc: str = "f64",
rows: tuple[int, int] | None = None, cols: tuple[int, int] | None = None) -> torch.Tensor:
"""model.linear for a float8_e4m3fn weight (32x32 E8M0 blocks): act_quant(x) then fp8_gemm; bf16 [M, N].
``rows`` / ``cols``: the window of the full tensor ``w_u8`` holds (w_scale is always the full tensor's)."""
shape = x.shape
x2 = x.reshape(-1, shape[-1])
if acc == "blocked":
q, s = F.act_quant(x2)
r0 = rows[0] if rows else 0
c0 = cols[0] if cols else 0
N, K = w_u8.shape
sw = F.e8m0_to_f32(w_scale)[r0 // 32:(r0 + N + 31) // 32, c0 // 32:(c0 + K + 31) // 32]
sw = sw.repeat_interleave(32, 0)[:N]
y = _matmul_acc(q, F.e4m3_codes_to_f32(w_u8), s, sw, acc)
else:
y = _matmul_acc(F.act_qdq(x2).float(), F.decode_fp8_block(w_u8, w_scale, rows, cols), None, None, acc)
return y.to(torch.bfloat16).view(*shape[:-1], -1)
def linear_bf16(x: torch.Tensor, w: torch.Tensor, acc: str = "f64") -> torch.Tensor:
"""F.linear(x, w) for bf16 x, w: fp32 accumulation, bf16 out."""
shape = x.shape
y = _matmul_acc(x.reshape(-1, shape[-1]).float(), w.float(), None, None, "f64" if acc == "blocked" else acc)
return y.to(torch.bfloat16).view(*shape[:-1], -1)
def swiglu(g16: torch.Tensor, u16: torch.Tensor, route_w: torch.Tensor | None, limit: float = SWIGLU_LIMIT
) -> torch.Tensor:
"""Expert.forward between w1/w3 and w2: bf16 in (the linears' outputs), bf16 out (x.to(dtype))."""
gate = g16.float()
up = u16.float()
if limit > 0:
up = torch.clamp(up, min=-limit, max=limit)
gate = torch.clamp(gate, max=limit)
h = Fn.silu(gate) * up
if route_w is not None:
h = route_w * h
return h.to(torch.bfloat16)
def expert(x: torch.Tensor, w1, s1, w3, s3, w2, s2, route_w: torch.Tensor | None, fmt: str = "fp4",
acc: str = "f64") -> torch.Tensor:
"""One official Expert on rows x (bf16 [M, 5120]); route_w fp32 [M, 1] or None (shared expert). bf16 out."""
lin = linear_fp4 if fmt == "fp4" else linear_fp8
h = swiglu(lin(x, w1, s1, acc), lin(x, w3, s3, acc), route_w)
return lin(h, w2, s2, acc)
def gate(x: torch.Tensor, gate_w: torch.Tensor, bias: torch.Tensor, topk: int = TOPK,
route_scale: float = ROUTE_SCALE) -> tuple[torch.Tensor, torch.Tensor]:
"""Gate.forward (text tokens): (weights fp32 [T, k], indices int64 [T, k])."""
scores = (x.float().double() @ gate_w.float().double().t()).float() # F.linear fp32, no TF32
scores = Fn.softplus(scores).sqrt()
idx = (scores + bias.float()).topk(topk, dim=-1)[1]
w = scores.gather(1, idx)
if topk > 1:
w = w / (w.sum(dim=-1, keepdim=True) + 1e-20)
return w * route_scale, idx
def moe(x: torch.Tensor, gate_w: torch.Tensor, bias: torch.Tensor, get_expert, shared: tuple, acc: str = "f64"
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""MoE.forward, world_size 1. get_expert(e) -> (w1, s1, w3, s3, w2, s2) packed FP4; shared = the FP8 shared
expert's (w1, s1, w3, s3, w2, s2). Returns (bf16 out [T, 5120], weights, indices)."""
w, idx = gate(x, gate_w, bias)
y = torch.zeros(x.shape, dtype=torch.float32, device=x.device)
for e in sorted(set(idx.flatten().tolist())):
rows, slot = torch.where(idx == e)
y[rows] += expert(x[rows], *get_expert(e), w[rows, slot, None], "fp4", acc).float()
y += expert(x, *shared, None, "fp8", acc).float()
return y.to(x.dtype), w, idx