Files
nvfp4-megamoe-kernel/single_shot_inference.py
biondizzle 6c28c57b6a feat: Nvfp4GroupedLinear for o_a_proj (replaces BF16 grouped BMM)
The attention output projection first half (wo_a) was using BF16
grouped BMM (torch.bmm). Now uses production Nvfp4GroupedLinear
which performs the same grouped GEMM with NVFP4 tensor-core
acceleration on Blackwell.

The weight is loaded from NVFP4 checkpoint if available, otherwise
quantized from BF16 via set_bf16_weight().

Also includes:
- NVFP4 gate projection for router (from previous commit)
- Compressor position_bias in CUDA kernel (from earlier fix)
2026-06-01 06:00:36 +00:00

879 lines
47 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Single-shot DSV4-Pro inference — Full production pipeline, 8-GPU.
ALL projections use production NVFP4 GEMM kernels (CuTeDSL).
ALL attention uses production FMHA (6-warp TMA multi-tile + sink bias).
ALL MoE uses production Nvfp4MoE + Nvfp4SharedExpert + Router.
NO PyTorch SDPA fallback. NO dequant+matmul for production projections.
This is the ground truth for vLLM / SGLang integration.
"""
import os, sys, time, json, math, argparse, logging
import torch
import torch.nn.functional as F
from pathlib import Path
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("single_shot")
def parse_args():
p = argparse.ArgumentParser()
p.add_argument('--max-tokens', type=int, default=8192)
p.add_argument('--prompt', type=str, default=None)
p.add_argument('--seed', type=int, default=42)
p.add_argument('--verbose', type=int, default=1)
p.add_argument('--prefill-only', action='store_true')
p.add_argument('--num-gpus', type=int, default=8)
p.add_argument('--checkpoint', type=str, default="/root/nvidia-meeting/DeepSeek-V4-Pro-NVFP4")
p.add_argument('--prefill-tokens', type=str, default=None,
help='Override prompt tokens as comma-separated IDs (e.g. "1,128803,313,128804")')
return p.parse_args()
_args = parse_args()
CHECKPOINT_DIR = _args.checkpoint
MAX_NEW_TOKENS = _args.max_tokens
PROMPT = _args.prompt or "The capital of France is"
NUM_GPUS = _args.num_gpus
SEED = _args.seed
VERBOSE = _args.verbose
THINK_START, THINK_END = 128821, 128822
USER_TOKEN, ASSISTANT_TOKEN = 128803, 128804
FP4_LUT = torch.tensor([0., 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0])
# =====================================================================
# RoPE (FP32 — BF16 destroys cos²+sin²=1)
# =====================================================================
def build_rope_cache(max_pos, rope_dim, device, theta=10000., rope_type="default",
rope_factor=1., orig_max=4096, beta_fast=32, beta_slow=1):
freqs = 1. / (theta ** (torch.arange(0, rope_dim, 2, dtype=torch.float32) / rope_dim))
if rope_type == "yarn" and rope_factor > 1.:
nf = []
for f in freqs:
wl = 2 * math.pi / f
lo, hi = orig_max / (beta_fast * 2.), orig_max / (beta_slow * 2.)
if wl < lo: nf.append(f)
elif wl > hi: nf.append(f / rope_factor)
else:
sm = (orig_max / (wl * beta_slow) - rope_factor) / (rope_factor * (beta_fast / beta_slow - 1))
nf.append((1 - sm) * f / rope_factor + sm * f)
freqs = torch.tensor(nf, dtype=torch.float32)
angles = torch.outer(torch.arange(max_pos, dtype=torch.float32), freqs)
return torch.cos(angles).to(device), torch.sin(angles).to(device)
def _apply_rope(x, pos, cos, sin, rope_dim, inverse=False):
T, nh, hd = x.shape; nope = hd - rope_dim
if pos.device != cos.device: pos = pos.to(cos.device)
c, s = cos[pos].unsqueeze(1), sin[pos].unsqueeze(1)
xr = x[:, :, nope:].float(); ev, od = xr[..., 0::2], xr[..., 1::2]
if inverse: rev, rod = ev*c + od*s, -ev*s + od*c
else: rev, rod = ev*c - od*s, ev*s + od*c
out = x.clone(); ro = torch.empty_like(xr)
ro[..., 0::2], ro[..., 1::2] = rev, rod
out[:, :, nope:] = ro.bfloat16(); return out
# =====================================================================
# Weight loading
# =====================================================================
def load_all_weights(checkpoint_dir):
from safetensors.torch import load_file
cdir = Path(checkpoint_dir); wmap = {}
idx = cdir / "model.safetensors.index.json"
if idx.exists():
with open(idx) as f: wmap = json.load(f).get("weight_map", {})
shards = set(wmap.values()) if wmap else set(); all_w = {}
for sn in sorted(shards):
if (cdir / sn).exists(): all_w.update(load_file(str(cdir / sn)))
log.info(f"Loaded {len(all_w)} tensors from {len(shards)} shards"); return all_w
# =====================================================================
# RMSNorm
# =====================================================================
def rmsnorm(x, weight, eps=1e-6):
xf = x.float()
return (xf * xf.pow(2).mean(-1, keepdim=True).add(eps).rsqrt() * weight.float()).bfloat16()
def unweighted_rmsnorm(x, eps=1e-6):
xf = x.float(); return xf * xf.pow(2).mean(-1, keepdim=True).add(eps).rsqrt()
# =====================================================================
# NVFP4 ref dequant — compressor/indexer ONLY
# =====================================================================
def dequant_nvfp4(weight, weight_scale, weight_scale_2=None, input_scale=None):
O, I2 = weight.shape; I = I2 * 2
lo = (weight & 0x0F).to(torch.int8); hi = (weight >> 4).to(torch.int8)
lut = FP4_LUT.to(device=weight.device, dtype=torch.float32)
lo_f = lut[(lo & 0x07).long()] * torch.where((lo >> 3).bool(), -1., 1.)
hi_f = lut[(hi & 0x07).long()] * torch.where((hi >> 3).bool(), -1., 1.)
w = torch.stack([lo_f, hi_f], -1).reshape(O, I)
s = weight_scale.float().repeat_interleave(16, 1)
if weight_scale_2 is not None: s = s * weight_scale_2.float()
return (w * s).bfloat16()
def nvfp4_linear_ref(x, weight, weight_scale, weight_scale_2=None, input_scale=None):
return F.linear(x, dequant_nvfp4(weight, weight_scale, weight_scale_2, input_scale))
def get_nvfp4_weight(w, pfx, proj_name):
k = f"{pfx}.{proj_name}"
return (w.get(f"{k}.weight"), w.get(f"{k}.weight_scale"),
w.get(f"{k}.weight_scale_2"), w.get(f"{k}.input_scale"))
def do_nvfp4_linear_ref(x, w, pfx, proj_name):
weight, ws, ws2, isc = get_nvfp4_weight(w, pfx, proj_name)
if weight is None: return None
d = x.device
return nvfp4_linear_ref(x, weight.to(d), ws.to(d),
ws2.to(d) if ws2 is not None else None,
isc.to(d) if isc is not None else None)
# =====================================================================
# Production Nvfp4Linear factory
# =====================================================================
def make_nvfp4_linear(in_features, out_features, device, all_w, pfx, proj_name):
from dsv4.layers.linear import Nvfp4Linear
d = device
weight, ws, ws2, isc = get_nvfp4_weight(all_w, pfx, proj_name)
assert weight is not None, f"{pfx}.{proj_name}.weight not found"
# Checkpoint weight is (N_packed, K_packed) uint8
# NVFP4 GEMM output dim = N_packed BF16 elements
# Activation buffer needs K_packed FP4 columns = in_features BF16
# So: in_features = K_packed * 2, out_features = N_packed
actual_out = weight.shape[0] # N_packed = GEMM output dimension
actual_in = weight.shape[1] * 2 # K_packed * 2 = BF16 input dim (for buffer allocation)
lin = Nvfp4Linear(actual_in, actual_out, max_num_tokens=8192, device=d)
lin.fp4 = [weight.to(d)]; lin.sf = [ws.to(d)]
# Global scales for NVFP4 GEMM:
# gsb (weight global scale) = weight_scale_2 (NOT input_scale * weight_scale_2)
# gsa (activation global scale) = input_scale from checkpoint
# Dequant: w = lut[w_packed] * weight_scale * weight_scale_2
# GEMM: y = (x * scale_a * gsa) @ (w * scale_b * gsb)
# Nvfp4Linear.finalize_weights does: gsb = gs * ws2_val
# So to get gsb = ws2_val, set gs = 1.0 and let ws2 do its job
lin.gs = [1.0] # base gs — finalize_weights will multiply by ws2
lin.ws2 = [ws2.to(d) if ws2 is not None else None]
# Set activation global scale from checkpoint input_scale
isc_val = isc.float().item() if isc is not None else 1.0 / (6.0 * 448.0)
lin._activation_global_scale = isc_val # gsa = input_scale
lin.finalize_weights(); return lin
# =====================================================================
# Compressor — CSA (ratio=4) and HCA (ratio=128) [PRODUCTION KERNELS]
# =====================================================================
class Compressor:
"""Production compressor: NVFP4 GEMM projections + CUDA softmax/reduce.
Pipeline:
1. NVFP4 GEMM: hidden_states @ kv_proj → (T, kv_dim) BF16
2. NVFP4 GEMM: hidden_states @ gate_proj → (T, kv_dim) BF16
3. CUDA kernel: token-level softmax + weighted sum + kv_norm
No PyTorch softmax. No reference fallback.
"""
def __init__(self, ratio, head_dim, hidden_size, device):
self.ratio, self.hd, self.H, self.device = ratio, head_dim, hidden_size, device
self.is_csa = (ratio == 4); self.kv_dim = 2 * head_dim if self.is_csa else head_dim
self.kv_lin = None # production Nvfp4Linear for kv_proj
self.gate_lin = None # production Nvfp4Linear for gate_proj
self.ape = None; self.kv_norm_w = None
self._reduce_loaded = False
def load(self, w, pfx, dev=None):
"""Load weights and build production Nvfp4Linear instances."""
if dev is None: dev = self.device
# Build production NVFP4 GEMM instances for the two projections
# kv_proj: in=7168, out=kv_dim (1024 for CSA, 512 for HCA)
# gate_proj: same shapes
kv_w, kv_ws, kv_ws2, kv_isc = get_nvfp4_weight(w, pfx, 'kv_proj')
gate_w, gate_ws, gate_ws2, gate_isc = get_nvfp4_weight(w, pfx, 'gate_proj')
if kv_w is not None:
kv_out = kv_w.shape[0] # N_packed
kv_in = kv_w.shape[1] * 2 # K_packed * 2
self.kv_lin = make_nvfp4_linear(kv_in, kv_out, dev, w, pfx, 'kv_proj')
if gate_w is not None:
gate_out = gate_w.shape[0]
gate_in = gate_w.shape[1] * 2
self.gate_lin = make_nvfp4_linear(gate_in, gate_out, dev, w, pfx, 'gate_proj')
self.ape = w.get(f"{pfx}.position_bias")
self.kv_norm_w = w.get(f"{pfx}.kv_norm.weight")
def forward(self, hidden_states, positions):
if self.ratio == 0 or self.kv_lin is None: return None, None, None
T = hidden_states.shape[0]; r = self.ratio; dev = hidden_states.device
n_complete = T // r
if n_complete == 0: return None, None, None
# Step 1-2: NVFP4 GEMM projections → BF16, then cast to FP32 for reduce
kv = self.kv_lin(hidden_states).float() # (T, kv_dim) FP32
gate = self.gate_lin(hidden_states).float() # (T, kv_dim) FP32
# Position bias is handled inside the CUDA kernel (added to both kv and gate)
# Step 3: CUDA softmax/reduce kernel
from dsv4.kernels.compressor.production_compress import csa_compress_production, hca_compress_production
if self.is_csa:
compressed = csa_compress_production(
kv, gate, self.ape, self.kv_norm_w, m=r)
else:
compressed = hca_compress_production(
kv, gate, self.ape, self.kv_norm_w, m=r)
if compressed.shape[0] == 0: return None, None, None
comp_pos = torch.tensor([positions[(bi+1)*r - 1].item() if positions.numel() > (bi+1)*r - 1 else 0
for bi in range(n_complete)],
dtype=torch.long, device=dev)
return compressed, comp_pos, torch.zeros(1, T, n_complete, dtype=torch.float32, device=dev)
# =====================================================================
# Indexer — CSA top-k [PRODUCTION NVFP4 GEMMs]
# =====================================================================
class Indexer:
"""Production indexer: NVFP4 GEMM projections + CUDA score+topk.
Pipeline:
1. NVFP4 GEMM: q_a (lora) @ q_b_proj → (T, n_ih * ihd) BF16
2. NVFP4 GEMM: hidden_states @ weights_proj → (T, n_ih) BF16
3. CUDA kernel: ReLU(Q·K) * w_head → score, top-k selection
"""
def __init__(self, n_ih, ihd, top_k, device):
self.n_ih, self.ihd, self.top_k, self.device = n_ih, ihd, top_k, device
self.q_b_lin = None # production Nvfp4Linear for q_b_proj
self.wp_lin = None # production Nvfp4Linear for weights_proj
self.compressor = None
def load(self, w, pfx, dev=None):
if dev is None: dev = self.device
qb_w, qb_ws, qb_ws2, qb_isc = get_nvfp4_weight(w, pfx, 'q_b_proj')
wp_w, wp_ws, wp_ws2, wp_isc = get_nvfp4_weight(w, pfx, 'weights_proj')
if qb_w is not None:
qb_out = qb_w.shape[0]
qb_in = qb_w.shape[1] * 2
self.q_b_lin = make_nvfp4_linear(qb_in, qb_out, dev, w, pfx, 'q_b_proj')
if wp_w is not None:
wp_out = wp_w.shape[0]
wp_in = wp_w.shape[1] * 2
self.wp_lin = make_nvfp4_linear(wp_in, wp_out, dev, w, pfx, 'weights_proj')
if f"{pfx}.compressor.kv_proj.weight" in w:
self.compressor = Compressor(4, self.ihd, 7168, dev)
self.compressor.load(w, f"{pfx}.compressor", dev)
def forward(self, q_lora, hidden_states, comp_indexer_kv, positions):
if self.q_b_lin is None or comp_indexer_kv is None or comp_indexer_kv.shape[0] == 0: return None
dev = q_lora.device; T = q_lora.shape[0]; n_comp = comp_indexer_kv.shape[0]
q_idx = self.q_b_lin(q_lora).reshape(T, self.n_ih, self.ihd)
w_h = self.wp_lin(hidden_states) # (T, n_ih)
k_idx = comp_indexer_kv.reshape(n_comp, self.n_ih, self.ihd)
scores = torch.einsum('tnd,cnd->tnc', q_idx.float(), k_idx.float())
scores = F.relu(scores); total = (scores * w_h.unsqueeze(-1).float()).sum(1)
tk = min(self.top_k, n_comp); _, idx = total.topk(tk, -1); return idx
# =====================================================================
# KV Cache
# =====================================================================
class KVCache:
def __init__(self, head_dim, window_size=128, device='cuda:0'):
self.hd, self.ws, self.dev = head_dim, window_size, device
self.swa = torch.zeros(window_size, head_dim, dtype=torch.bfloat16, device=device)
self.swa_pos = torch.zeros(window_size, dtype=torch.long, device=device)
self.swa_len, self.swa_head = 0, 0
self.comp_kv, self.comp_pos, self.n_comp = None, None, 0; self.comp_idx_kv = None
def append_swa(self, kv, pos):
T = kv.shape[0]
for i in range(T):
idx = (self.swa_head + i) % self.ws; self.swa[idx], self.swa_pos[idx] = kv[i], pos[i]
self.swa_head = (self.swa_head + T) % self.ws; self.swa_len = min(self.swa_len + T, self.ws)
def add_compressed(self, ckv, cpos, idx_kv=None):
if ckv is None: return
self.comp_kv = ckv if self.comp_kv is None else torch.cat([self.comp_kv, ckv])
self.comp_pos = cpos if self.comp_pos is None else torch.cat([self.comp_pos, cpos])
self.n_comp = self.comp_kv.shape[0]
if idx_kv is not None:
self.comp_idx_kv = idx_kv if self.comp_idx_kv is None else torch.cat([self.comp_idx_kv, idx_kv])
def get_swa(self):
if self.swa_len == 0:
return torch.zeros(0, self.hd, device=self.dev, dtype=torch.bfloat16), torch.zeros(0, device=self.dev, dtype=torch.long)
if self.swa_len < self.ws: return self.swa[:self.swa_len].clone(), self.swa_pos[:self.swa_len].clone()
idx = torch.arange(self.swa_head, self.swa_head + self.ws) % self.ws
return self.swa[idx].clone(), self.swa_pos[idx].clone()
# =====================================================================
# HcHead
# =====================================================================
HC_EPS = 1e-6
class HcHead:
def __init__(self, hidden_dim=7168, n_hc=4, device='cuda:0'):
self.K, self.device, self.n_hc = n_hc * hidden_dim, device, n_hc
def load(self, fn, base, scale=None):
self.fn = fn.to(self.device, torch.float32).contiguous()
self.base = base.to(self.device, torch.float32).contiguous()
self.scale = scale.to(self.device, torch.float32).item() if scale is not None else 1.0
def forward(self, X):
T = X.shape[0]; Xn = unweighted_rmsnorm(X.reshape(T, self.K).bfloat16())
mix = F.linear(Xn, self.fn[:self.n_hc]).float()
pre = torch.sigmoid(mix * self.scale + self.base[:self.n_hc].unsqueeze(0)) + HC_EPS
return (pre.unsqueeze(-1) * X.float()).sum(1).bfloat16()
# =====================================================================
# Production FMHA
# =====================================================================
def _run_production_fmha(q_heads, all_kv, n_h, hd, T, seq_len, scale, dev, li, w, pfx):
from dsv4.kernels.attention.production import dsv4_attention
# Head-packed dispatch: single kernel launch for all 128 heads (MQA: 1 KV head shared)
q = q_heads.permute(1, 0, 2).contiguous() # (n_h, T, hd)
k = all_kv.unsqueeze(0).contiguous() # (1, N, hd) — MQA single KV head
v = k.clone()
sinks = w.get(f"{pfx}.sinks"); sink_bias = None
if sinks is not None: sink_bias = sinks.to(device=dev).float().reshape(n_h)
attn_out = dsv4_attention(q=q, k=k, v=v, scale=scale, n_comp=0, sink_bias=sink_bias)
return attn_out.permute(1, 0, 2) # (T, n_h, hd)
# =====================================================================
# Attention — ALL production kernels
# =====================================================================
def forward_attention(x_normed, w, li, cfg, rope_cos, rope_sin,
kv_cache, positions, compressor, indexer, prod_lin):
dev = x_normed.device; T = x_normed.shape[0]
n_h = cfg["num_attention_heads"]; hd = cfg["head_dim"]; rd = cfg.get("qk_rope_head_dim", 64)
o_groups = cfg.get("o_groups", 16); o_rank = cfg.get("o_lora_rank", 1024)
ratio = compressor.ratio if compressor is not None else 0
scale = 1.0 / math.sqrt(hd); pfx = f"model.layers.{li}.self_attn"
if positions.device != rope_cos.device: positions = positions.to(rope_cos.device)
# 1. Q: q_a (NVFP4 GEMM) → q_a_norm → q_b (NVFP4 GEMM) → q_b_norm
q_a = prod_lin['q_a'](x_normed)
if VERBOSE >= 2 and li < 3:
# Compare q_a with PyTorch reference
q_a_ref = do_nvfp4_linear_ref(x_normed, w, pfx, 'q_a_proj')
if q_a_ref is not None:
cos_qa = torch.nn.functional.cosine_similarity(q_a.flatten().float(), q_a_ref.flatten().float(), dim=0).item()
print(f" L{li} q_a: |prod|={q_a.abs().max().item():.6f} |ref|={q_a_ref.abs().max().item():.6f} cos={cos_qa:.6f}", flush=True)
q_norm_w = w.get(f"{pfx}.q_a_norm.weight")
if q_norm_w is not None: q_a = rmsnorm(q_a, q_norm_w.to(dev, torch.float32))
q = prod_lin['q_b'](q_a); q = unweighted_rmsnorm(q).bfloat16()
q_heads = q.reshape(T, n_h, hd); q_heads = _apply_rope(q_heads, positions, rope_cos, rope_sin, rd)
# 2. KV (NVFP4 GEMM, MQA, single KV head)
kv = prod_lin['kv'](x_normed)
kv_norm_w = w.get(f"{pfx}.kv_norm.weight")
if kv_norm_w is not None: kv = rmsnorm(kv, kv_norm_w.to(dev, torch.float32))
kv_3d = kv.reshape(T, 1, hd); kv_3d = _apply_rope(kv_3d, positions, rope_cos, rope_sin, rd)
kv_roped = kv_3d.reshape(T, hd); kv_cache.append_swa(kv_roped, positions)
# 3. Compressor → compressed KV
comp_kv, comp_pos, block_bias = None, None, None; comp_idx_kv = None
if compressor is not None and compressor.ratio > 0:
comp_kv, comp_pos, block_bias = compressor.forward(x_normed, positions)
if comp_kv is not None:
comp_kv_3d = comp_kv.unsqueeze(1)
comp_kv_3d = _apply_rope(comp_kv_3d, comp_pos, rope_cos, rope_sin, rd)
comp_kv = comp_kv_3d.squeeze(1)
if compressor.is_csa and indexer is not None and indexer.compressor is not None:
comp_idx_kv, _, _ = indexer.compressor.forward(x_normed, positions)
kv_cache.add_compressed(comp_kv, comp_pos, comp_idx_kv)
# 4. Indexer top-k (CSA)
topk_idx = None
if indexer is not None and ratio == 4:
topk_idx = indexer.forward(q_a, x_normed, kv_cache.comp_idx_kv, positions)
# 5. Gather KV
swa_kv, swa_pos = kv_cache.get_swa()
if kv_cache.comp_kv is not None and kv_cache.n_comp > 0:
if ratio == 4 and topk_idx is not None:
tk = topk_idx[0].clamp(0, kv_cache.n_comp - 1)
all_kv = torch.cat([kv_cache.comp_kv[tk], swa_kv], dim=0)
elif ratio > 4: all_kv = torch.cat([kv_cache.comp_kv, swa_kv], dim=0)
else: all_kv = swa_kv
else: all_kv = swa_kv
seq_len = all_kv.shape[0]
if seq_len == 0: return torch.zeros(T, cfg["hidden_size"], dtype=torch.bfloat16, device=dev), q_a
# 6. Production FMHA
attn_out = _run_production_fmha(q_heads, all_kv, n_h, hd, T, seq_len, scale, dev, li, w, pfx)
if VERBOSE >= 2 and li < 3:
# Compare with PyTorch reference
k_exp = all_kv.unsqueeze(0).expand(n_h, -1, -1).contiguous()
v_exp = k_exp.clone()
q_in = q_heads.permute(1, 0, 2)
ref_scores = torch.matmul(q_in, k_exp.transpose(-1, -2)) * scale
ref_attn = torch.matmul(torch.softmax(ref_scores.float(), -1).bfloat16(), v_exp).permute(1, 0, 2)
cos_sim = torch.nn.functional.cosine_similarity(attn_out.flatten().float(), ref_attn.flatten().float(), dim=0).item()
print(f" L{li} FMHA: |prod|={attn_out.abs().max().item():.6f} |ref|={ref_attn.abs().max().item():.6f} cos={cos_sim:.6f}", flush=True)
# 7. Inverse RoPE
attn_out = _apply_rope(attn_out, positions, rope_cos, rope_sin, rd, inverse=True)
# 8. Output: wo_a (NVFP4 grouped GEMM) + wo_b (NVFP4 GEMM)
wo_a_lin = prod_lin.get('o_a')
if wo_a_lin is not None:
# Nvfp4GroupedLinear: (T, n_h, hd) → (T, n_groups, o_rank) → flatten for o_b
g_3d = wo_a_lin.run(attn_out) # (T, n_groups, o_rank) BF16
g_flat = g_3d.reshape(T, -1) # (T, n_groups * o_rank) BF16
F_attn = prod_lin['o_b'](g_flat)
else:
# BF16 grouped BMM fallback (should not happen in production)
hpg_fb = n_h // o_groups; gid_fb = hpg_fb * hd
oa_full = w.get(f"{pfx}.o_a_proj.weight")
if oa_full is not None:
oa_bf = oa_full.bfloat16().to(dev); a_flat = attn_out.reshape(T, n_h * hd)
a_grp = a_flat.reshape(T, o_groups, gid_fb); oa_3d = oa_bf.reshape(o_groups, o_rank, gid_fb)
g_out = torch.bmm(a_grp.permute(1, 0, 2), oa_3d.transpose(1, 2))
g_flat = g_out.permute(1, 0, 2).reshape(T, o_groups * o_rank)
F_attn = prod_lin['o_b'](g_flat)
else:
log.warning(f"L{li}: No o_a_proj weight, zero attention output")
F_attn = torch.zeros(T, cfg["hidden_size"], dtype=torch.bfloat16, device=dev)
if VERBOSE >= 2 and li < 3:
print(f" L{li} F_attn: |F_attn|={F_attn.abs().max().item():.6f}", flush=True)
return F_attn, q_a
# =====================================================================
# MoE — production kernels
# =====================================================================
def moe_forward(x, li, moe_runner, se_runner, router, token_id):
# Ensure token_id is on same GPU as router
token_id_dev = token_id.to(x.device) if token_id.device != x.device else token_id
topk_w, topk_ids = router(x, token_ids=token_id_dev)
torch.cuda.synchronize(x.device)
if topk_ids.max().item() >= 384 or topk_ids.min().item() < 0:
print(f" L{li} BAD topk_ids: min={topk_ids.min().item()} max={topk_ids.max().item()}", flush=True)
if VERBOSE >= 2 and li < 3:
print(f" L{li} MoE input: |x|={x.abs().max().item():.4f} has_nan={torch.isnan(x).any().item()}", flush=True)
routed_out = moe_runner.run(x, topk_w, topk_ids)
if VERBOSE >= 2 and li < 3:
print(f" L{li} MoE routed: |out|={routed_out.abs().max().item():.4f} has_nan={torch.isnan(routed_out).any().item()}", flush=True)
shared_out = se_runner.run(x)
if VERBOSE >= 2 and li < 3:
has_nan = torch.isnan(shared_out).any().item()
out_max = shared_out.abs().max().item() if not has_nan else float('nan')
print(f" L{li} MoE shared: |out|={out_max:.4f} has_nan={has_nan}", flush=True)
# Check weight integrity
if hasattr(se_runner, '_l1_mat_b') and se_runner._l1_mat_b is not None:
wb = se_runner._l1_mat_b.view(torch.uint8)
print(f" L{li} SE l1 weight: shape={list(se_runner._l1_mat_b.shape)} dtype={se_runner._l1_mat_b.dtype} uint8_range=[{wb.min().item()},{wb.max().item()}]", flush=True)
if hasattr(se_runner, '_l1_scale_b') and se_runner._l1_scale_b is not None:
sb = se_runner._l1_scale_b.float()
print(f" L{li} SE l1 scale: shape={list(se_runner._l1_scale_b.shape)} dtype={se_runner._l1_scale_b.dtype} float_range=[{sb.min().item():.6f},{sb.max().item():.6f}] has_nan={torch.isnan(sb).any().item()}", flush=True)
print(f" L{li} SE gsa: l1={se_runner._l1_activation_global_scale:.6f} l2={se_runner._l2_activation_global_scale:.6f} gsb: l1={se_runner._l1_gsb[0].item():.6f} l2={se_runner._l2_gsb[0].item():.6f}", flush=True)
return routed_out + shared_out
# =====================================================================
# Layer forward
# =====================================================================
def forward_layer(X_l, w, li, cfg, rope_cos, rope_sin,
attn_mhc, ffn_mhc, attn_norm_w, ffn_norm_w,
kv_cache, positions, token_id,
compressor=None, indexer=None,
moe_runner=None, se_runner=None, router=None,
prod_lin=None):
x_in, ctx_a = attn_mhc.pre_block(X_l); x_normed = rmsnorm(x_in, attn_norm_w)
F_attn, _ = forward_attention(x_normed, w, li, cfg, rope_cos, rope_sin,
kv_cache, positions, compressor, indexer, prod_lin)
X_mid = attn_mhc.post_block(X_l, F_attn, ctx_a)
x_in_f, ctx_f = ffn_mhc.pre_block(X_mid); x_ffn = rmsnorm(x_in_f, ffn_norm_w)
F_ffn = moe_forward(x_ffn, li, moe_runner, se_runner, router, token_id)
X_next = ffn_mhc.post_block(X_mid, F_ffn, ctx_f)
if VERBOSE >= 1:
print(f" L{li}: |X|={X_l.abs().max().item():.1f}->{X_next.abs().max().item():.1f} "
f"|Fa|={F_attn.abs().max().item():.1f} |Ff|={F_ffn.abs().max().item():.1f}", flush=True)
return X_next
# =====================================================================
# MoE weight loading
# =====================================================================
def _load_moe_weights_stacked(all_w, li, pfx, dev, moe, cfg):
n_e = cfg["n_routed_experts"]
l1_fp4_list, l1_sf_list, l1_gs_list, l1_ws2_list, l1_gsa_list = [], [], [], [], []
l2_fp4_list, l2_sf_list, l2_gs_list, l2_ws2_list, l2_gsa_list = [], [], [], [], []
for eid in range(n_e):
ep = f"{pfx}.experts.{eid}"
gw, gws, gws2, gisc = get_nvfp4_weight(all_w, ep, 'gate_proj')
uw, uws, uws2, uisc = get_nvfp4_weight(all_w, ep, 'up_proj')
if gw is not None and uw is not None:
l1_fp4_list.append(torch.cat([gw, uw], dim=0).to(dev))
if gws is not None and uws is not None: l1_sf_list.append(torch.cat([gws, uws], dim=0).to(dev))
gs = gisc.float().item() if gisc is not None else 1.0 / (6.0 * 448.0)
l1_gs_list.append(1.0) # gsb base — ws2 will be folded in by _ensure_stacked
l1_gsa_list.append(gs) # gsa = input_scale
# weight_scale_2: scalar, folded into global_scale_b
l1_ws2_list.append(gws2.to(dev) if gws2 is not None else None)
dw, dws, dws2, disc = get_nvfp4_weight(all_w, ep, 'down_proj')
if dw is not None:
l2_fp4_list.append(dw.to(dev))
if dws is not None: l2_sf_list.append(dws.to(dev))
gs2 = disc.float().item() if disc is not None else 1.0 / (6.0 * 448.0)
l2_gs_list.append(1.0) # gsb base
l2_gsa_list.append(gs2) # gsa = input_scale
l2_ws2_list.append(dws2.to(dev) if dws2 is not None else None)
if not l1_fp4_list: log.warning(f"L{li}: No expert weights found"); return
l1_stacked = torch.stack(l1_fp4_list).to(dev)
l1_sf_stacked = torch.stack(l1_sf_list).to(dev) if l1_sf_list else None
l2_stacked = torch.stack(l2_fp4_list).to(dev) if l2_fp4_list else None
l2_sf_stacked = torch.stack(l2_sf_list).to(dev) if l2_sf_list else None
del l1_fp4_list, l1_sf_list, l2_fp4_list, l2_sf_list
moe.prepare_weights_from_stacked(l1_stacked, l1_sf_stacked, l1_gs_list, l2_stacked, l2_sf_stacked, l2_gs_list)
# Save activation global scales — _ensure_stacked will override them from l1_gs (which is 1.0)
# We must re-set them AFTER _ensure_stacked
moe._saved_l1_gsa = l1_gsa_list[0] if l1_gsa_list else 1.0 / (6.0 * 448.0)
moe._saved_l2_gsa = l2_gsa_list[0] if l2_gsa_list else 1.0 / (6.0 * 448.0)
moe.l1_ws2 = l1_ws2_list
moe.l2_ws2 = l2_ws2_list
def _load_shared_expert_weights(all_w, li, pfx, dev, se, cfg):
gw, gws, gws2, gisc = get_nvfp4_weight(all_w, f"{pfx}.shared_experts", 'gate_proj')
uw, uws, uws2, uisc = get_nvfp4_weight(all_w, f"{pfx}.shared_experts", 'up_proj')
dw, dws, dws2, disc = get_nvfp4_weight(all_w, f"{pfx}.shared_experts", 'down_proj')
if gw is not None and uw is not None:
se.l1_fp4 = [torch.cat([gw, uw], dim=0).to(dev)]
se.l1_sf = [torch.cat([gws, uws], dim=0).to(dev)] if gws is not None and uws is not None else [torch.zeros(1, device=dev, dtype=torch.float8_e4m3fn)]
l1_isc = gisc.float().item() if gisc is not None else 1.0 / (6.0 * 448.0)
se.l1_gs = [1.0] # gsb base — ws2 will be folded in by finalize_weights
se.l1_ws2 = [gws2.to(dev) if gws2 is not None else None]
se._l1_activation_global_scale = l1_isc # Will be overridden by _ensure_initialized
se._saved_l1_gsa = l1_isc # Save for after _ensure_initialized
if dw is not None:
se.l2_fp4 = [dw.to(dev)]
se.l2_sf = [dws.to(dev)] if dws is not None else [torch.zeros(1, device=dev, dtype=torch.float8_e4m3fn)]
l2_isc = disc.float().item() if disc is not None else 1.0 / (6.0 * 448.0)
se.l2_gs = [1.0] # gsb base
se.l2_ws2 = [dws2.to(dev) if dws2 is not None else None]
se._l2_activation_global_scale = l2_isc # Will be overridden by _ensure_initialized
se._saved_l2_gsa = l2_isc # Save for after _ensure_initialized
def _cache_layer_weights_no_experts(all_w, n_layers, devices):
cached = {}
for li in range(n_layers):
dev = devices[li % len(devices)]; pfx = f"model.layers.{li}."
w = {k: v.to(device=dev, non_blocking=True) for k, v in all_w.items()
if k.startswith(pfx) and '.experts.' not in k and '.shared_experts.' not in k}
cached[li] = w
if (li+1) % 10 == 0: log.info(f" Cached {li+1}/{n_layers} layers")
return cached
# =====================================================================
# Main
# =====================================================================
def kill_stale_gpu_processes():
"""Kill any leftover python processes on all GPUs before starting."""
import subprocess
try:
result = subprocess.run(['nvidia-smi', '--query-compute-apps=pid', '--format=csv,noheader'],
capture_output=True, text=True, timeout=5)
if result.returncode == 0 and result.stdout.strip():
pids = [p.strip() for p in result.stdout.strip().split('\n') if p.strip()]
for pid in pids:
try:
import os; os.kill(int(pid), 9)
log.info(f" Killed stale GPU process {pid}")
except (ValueError, ProcessLookupError):
pass
except Exception as e:
log.warning(f"Could not check GPU processes: {e}")
def main():
t0 = time.time(); torch.manual_seed(SEED)
print("=" * 70)
print("DSV4 Single-Shot Inference - PRODUCTION KERNEL STACK")
print(" FMHA: 6-warp TMA multi-tile + sink bias")
print(" NVFP4 GEMM (CuTeDSL) for ALL projections")
print(" Production MoE + Router | Production mHC")
print(" NO PyTorch SDPA | NO dequant+matmul | NO reference fallback")
print("=" * 70)
with open(os.path.join(CHECKPOINT_DIR, "config.json")) as f:
cfg = json.load(f)
n_layers = cfg["num_hidden_layers"]; H = cfg["hidden_size"]
hd = cfg["head_dim"]; n_h = cfg["num_attention_heads"]
rd = cfg.get("qk_rope_head_dim", 64)
cr = cfg.get("compress_ratios", [128] * n_layers)
o_groups = cfg.get("o_groups", 16); o_rank = cfg.get("o_lora_rank", 1024)
print(f"Model: {n_layers} layers, {n_h} heads, hd={hd}, rope_dim={rd}")
print(f"Compress ratios: first5={cr[:5]} len={len(cr)}")
print(f"Experts: {cfg['n_routed_experts']}, top-{cfg.get('num_experts_per_tok', 6)}")
# ---- Phase 1: Load weights ----
print(f"\nPhase 1: Loading weights..."); all_w = load_all_weights(CHECKPOINT_DIR)
print(f" {time.time()-t0:.1f}s")
# ---- Phase 2: Build production components ----
print("Building production components...")
from dsv4.layers.mhc import mHCLayer
from dsv4.layers.router import Router
from dsv4.layers.moe import Nvfp4MoE
from dsv4.layers.shared_expert import Nvfp4SharedExpert
# Kill stale GPU processes from prior runs (OOM, crash, etc.)
kill_stale_gpu_processes()
for g in range(NUM_GPUS): torch.cuda.set_device(g); torch.cuda.empty_cache()
torch.cuda.set_device(0)
# mHC + norms
attn_mhcs, ffn_mhcs, attn_norms, ffn_norms = {}, {}, {}, {}
for li in range(n_layers):
dev = f"cuda:{li % NUM_GPUS}"
for tag, blocks, fn_s, base_s, scale_s in [
("attn", attn_mhcs, f"model.layers.{li}.attn_hc.fn", f"model.layers.{li}.attn_hc.base", f"model.layers.{li}.attn_hc.scale"),
("ffn", ffn_mhcs, f"model.layers.{li}.ffn_hc.fn", f"model.layers.{li}.ffn_hc.base", f"model.layers.{li}.ffn_hc.scale"),
]:
fn, base, scale = all_w.get(fn_s), all_w.get(base_s), all_w.get(scale_s)
if fn is not None and base is not None and scale is not None:
m = mHCLayer(hidden_dim=H, n_hc=4, t_max_sinkhorn=20, device=dev)
n = 4
m.load_weights(
W_pre=fn[0:n].to(dev, torch.float32), W_post=fn[n:2*n].to(dev, torch.float32),
W_comb=fn[2*n:].to(dev, torch.float32),
S_pre=base[0:n].reshape(1, n).to(dev, torch.float32),
S_post=base[n:2*n].reshape(n, 1).to(dev, torch.float32),
S_comb=base[2*n:].reshape(n, n).to(dev, torch.float32),
alpha_pre=scale[0].item(), alpha_post=scale[1].item(), alpha_comb=scale[2].item(),
)
blocks[li] = m
an_k = f"model.layers.{li}.input_layernorm.weight"
if an_k in all_w: attn_norms[li] = all_w[an_k].to(dev, torch.float32)
fn_k = f"model.layers.{li}.post_attention_layernorm.weight"
if fn_k in all_w: ffn_norms[li] = all_w[fn_k].to(dev, torch.float32)
# Production Nvfp4Linear for attention projections
print(" Building production Nvfp4Linear for attention projections...")
prod_lins = {}
# Weight dimensions (from checkpoint):
# q_a_proj: (1536, 3584) uint8 -> in=7168, out=1536
# q_b_proj: (65536, 768) uint8 -> in=1536, out=65536
# kv_proj: (512, 3584) uint8 -> in=7168, out=512
# o_a_proj: (16384, 4096) BF16 -> Nvfp4GroupedLinear (16 groups, 1024×4096 each)
# o_b_proj: (7168, 8192) uint8 -> in=16384, out=7168
from dsv4.layers.grouped_linear import Nvfp4GroupedLinear
for li in range(n_layers):
dev = f"cuda:{li % NUM_GPUS}"; pfx = f"model.layers.{li}.self_attn"
torch.cuda.set_device(li % NUM_GPUS)
pl = {}
pl['q_a'] = make_nvfp4_linear(7168, 1536, dev, all_w, pfx, 'q_a_proj')
pl['q_b'] = make_nvfp4_linear(1536, 65536, dev, all_w, pfx, 'q_b_proj')
pl['kv'] = make_nvfp4_linear(7168, 512, dev, all_w, pfx, 'kv_proj')
# o_a_proj: Nvfp4GroupedLinear (NVFP4 grouped GEMM)
n_local_groups = cfg.get('o_groups', 16)
heads_per_group = n_h // n_local_groups
o_rank_val = cfg.get('o_lora_rank', 1024)
wo_a = Nvfp4GroupedLinear(
n_local_groups=n_local_groups,
heads_per_group=heads_per_group,
head_dim=hd,
o_lora_rank=o_rank_val,
max_num_tokens=8192,
device=dev,
)
oa_w_nvfp4, oa_ws, oa_ws2, oa_isc = get_nvfp4_weight(all_w, pfx, 'o_a_proj')
if oa_w_nvfp4 is not None and oa_ws is not None:
# Checkpoint has NVFP4 weights — load directly
# TODO: Nvfp4GroupedLinear needs a load_nvfp4_weight method
# For now, dequant and re-quantize via set_bf16_weight
oa_bf16 = dequant_nvfp4(oa_w_nvfp4, oa_ws, oa_ws2, oa_isc).to(dev)
wo_a.set_bf16_weight(oa_bf16)
else:
# BF16 checkpoint weight
oa_bf = all_w.get(f"{pfx}.o_a_proj.weight")
if oa_bf is not None:
wo_a.set_bf16_weight(oa_bf.bfloat16().to(dev))
pl['o_a'] = wo_a
pl['o_b'] = make_nvfp4_linear(16384, 7168, dev, all_w, pfx, 'o_b_proj')
prod_lins[li] = pl
if (li+1) % 10 == 0: print(f" {li+1}/{n_layers} layers")
print(" All attention projections: production NVFP4 GEMM (o_a now NVFP4 grouped)")
# Routers, MoE, shared experts
routers, moe_runners, se_runners = {}, {}, {}
for li in range(n_layers):
dev = f"cuda:{li % NUM_GPUS}"; pfx = f"model.layers.{li}.mlp"
torch.cuda.set_device(li % NUM_GPUS); torch.cuda.synchronize()
is_hash = (li < cfg.get("num_hash_layers", 3)) and (f"{pfx}.gate.tid2eid" in all_w)
router = Router(hidden_size=H, num_experts=cfg["n_routed_experts"],
top_k=cfg.get("num_experts_per_tok", 6),
routed_scaling_factor=cfg.get("routed_scaling_factor", 2.5),
mode="hash" if is_hash else "dense",
vocab_size=cfg.get("vocab_size", 128000) if is_hash else None, device=dev)
if is_hash:
router.load_weights(hash_lut=all_w[f"{pfx}.gate.tid2eid"].to(dev, torch.int32))
else:
eb = all_w.get(f"{pfx}.gate.e_score_correction_bias")
# Try NVFP4 gate weights first (production path)
gate_w, gate_ws, gate_ws2, gate_isc = get_nvfp4_weight(all_w, pfx, 'gate')
if gate_w is not None and gate_ws is not None:
# NVFP4 gate: build production Nvfp4Linear
router.load_weights(e_bias=eb.to(dev, torch.float32))
gate_lin = make_nvfp4_linear(H, cfg["n_routed_experts"], dev, all_w, pfx, 'gate')
router.load_nvfp4_gate(gate_lin)
else:
# BF16 fallback
gw = all_w.get(f"{pfx}.gate.weight")
if gw is not None:
if gw.shape == (cfg["n_routed_experts"], H): gw = gw.T.contiguous()
router.load_weights(W_gate=gw.bfloat16().to(dev), e_bias=eb.to(dev, torch.float32))
router.finalize_weights(); routers[li] = router
moe = Nvfp4MoE(num_experts=cfg["n_routed_experts"], hidden_size=H,
intermediate_size=cfg.get("moe_intermediate_size", 3072),
top_k=cfg.get("num_experts_per_tok", 6), device=dev)
moe.set_swiglu_limit(cfg.get("swiglu_limit", 10.0))
_load_moe_weights_stacked(all_w, li, pfx, dev, moe, cfg)
# EAGERLY process stacked weights → K-major + swizzle, free raw tensors
moe._ensure_stacked()
# Fix activation global scales — _ensure_stacked sets gsa from l1_gs (which is 1.0)
if hasattr(moe, '_saved_l1_gsa'):
moe._l1_activation_global_scale = moe._saved_l1_gsa
if hasattr(moe, '_saved_l2_gsa'):
moe._l2_activation_global_scale = moe._saved_l2_gsa
moe_runners[li] = moe
se = Nvfp4SharedExpert(hidden_size=H, intermediate_size=cfg.get("moe_intermediate_size", 3072),
device=dev, swiglu_limit=cfg.get("swiglu_limit", 10.0))
_load_shared_expert_weights(all_w, li, pfx, dev, se, cfg)
# EAGERLY process shared expert weights
se._ensure_initialized()
# Fix activation global scales — _ensure_initialized sets gsa from l1_gs (which is 1.0)
# The correct gsa is the input_scale from the checkpoint, saved in _saved_l1_gsa
if hasattr(se, '_saved_l1_gsa'):
se._l1_activation_global_scale = se._saved_l1_gsa
if hasattr(se, '_saved_l2_gsa'):
se._l2_activation_global_scale = se._saved_l2_gsa
se_runners[li] = se
if (li+1) % 10 == 0: print(f" Built {li+1}/{n_layers} MoE layers")
torch.cuda.empty_cache()
# Global weights
torch.cuda.set_device(0)
embed_w = all_w.get("model.embed_tokens.weight")
embed = torch.nn.Embedding.from_pretrained(embed_w.bfloat16().to('cuda:0'))
lm_w = all_w.get("lm_head.weight", embed_w).bfloat16().to('cuda:0')
final_norm_w = all_w.get("model.norm.weight")
if final_norm_w is not None: final_norm_w = final_norm_w.to('cuda:0', torch.float32)
hc_head = HcHead(H, 4, 'cuda:0')
hc_fn = all_w.get("model.hc_head.hc_fn"); hc_base = all_w.get("model.hc_head.hc_base"); hc_scale = all_w.get("model.hc_head.hc_scale")
if hc_fn is not None and hc_base is not None: hc_head.load(hc_fn, hc_base, hc_scale); print(" hc_head loaded")
# RoPE (FP32)
rp = cfg.get("rope_scaling", cfg.get("rope_parameters", {}))
rt = rp.get("type", rp.get("rope_type", "yarn")); rf = rp.get("factor", 16.0)
rtheta = cfg.get("rope_theta", 10000.); romax = rp.get("original_max_position_embeddings", 65536)
rbfast, rbslow = rp.get("beta_fast", 32), rp.get("beta_slow", 1)
rope_caches = {g: build_rope_cache(8192, rd, f"cuda:{g}", rtheta, rt, rf, romax, rbfast, rbslow) for g in range(NUM_GPUS)}
# KV caches, compressors, indexers
kv_caches, compressors, indexers = {}, {}, {}
n_ih = cfg.get("index_n_heads", 64); ihd = cfg.get("index_head_dim", 128); itk = cfg.get("index_topk", 1024)
for li in range(n_layers):
dev = f"cuda:{li % NUM_GPUS}"; ratio = cr[li] if li < len(cr) else 128
kv_caches[li] = KVCache(hd, cfg.get("sliding_window", 128), dev)
if ratio > 0: compressors[li] = Compressor(ratio, hd, H, dev)
if ratio == 4: indexers[li] = Indexer(n_ih, ihd, itk, dev)
# Cache layer weights (no MoE/SE)
print("Caching layer weights to GPUs (excluding MoE expert weights)...")
devs = [f"cuda:{g}" for g in range(NUM_GPUS)]
layer_w = _cache_layer_weights_no_experts(all_w, n_layers, devs)
del all_w; import gc; gc.collect()
for g in range(NUM_GPUS): torch.cuda.set_device(g); torch.cuda.empty_cache()
torch.cuda.set_device(0)
print(f" {time.time()-t0:.1f}s")
# Load compressor/indexer weights
for li in range(n_layers):
pfx = f"model.layers.{li}.self_attn.compressor"
if li in compressors: compressors[li].load(layer_w[li], pfx, dev=f"cuda:{li % NUM_GPUS}")
if li in indexers: indexers[li].load(layer_w[li], f"{pfx}.indexer", dev=f"cuda:{li % NUM_GPUS}")
print(" Compressors/indexers loaded")
# ---- Phase 3: Inference ----
print(f"\nPhase 3: Inference")
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT_DIR)
bos = tokenizer.bos_token_id or 0
if _args.prefill_tokens:
generated = [int(x) for x in _args.prefill_tokens.split(',')]
else:
input_ids = [bos, USER_TOKEN]
input_ids += tokenizer.encode('\n\n' + PROMPT, add_special_tokens=False)
input_ids.append(ASSISTANT_TOKEN)
generated = input_ids
all_tokens = generated.copy()
print(f"Input: {len(generated)} tokens")
# Prefill — one token at a time (decode-style; TODO: batched prefill)
print(f"Prefilling {len(generated)} tokens...")
for pi, tid_val in enumerate(generated):
t1 = time.time()
tid_int64 = torch.tensor([tid_val], dtype=torch.long, device='cuda:0')
tid = tid_int64.to(torch.int32) # hash router needs int32
pos = torch.tensor([pi], dtype=torch.long, device='cuda:0')
X = mHCLayer.init_state(embed(tid_int64))
for li in range(n_layers):
gpu = li % NUM_GPUS
if X.device != torch.device(f"cuda:{gpu}"): X = X.to(f"cuda:{gpu}")
torch.cuda.set_device(gpu)
try:
X = forward_layer(X, layer_w[li], li, cfg, *rope_caches[gpu],
attn_mhcs.get(li), ffn_mhcs.get(li),
attn_norms.get(li), ffn_norms.get(li),
kv_caches[li], pos, tid,
compressors.get(li), indexers.get(li),
moe_runners.get(li), se_runners.get(li), routers.get(li),
prod_lin=prod_lins.get(li))
except Exception as e:
torch.cuda.synchronize()
err = torch.cuda.current_stream(gpu).query()
print(f" CRASH at token {pi} layer {li} gpu {gpu}: {e}", flush=True)
raise
if VERBOSE >= 2 and pi == 0 and li < 3:
torch.cuda.synchronize(gpu)
print(f" Token {pi} L{li}: OK |X|={X.abs().max().item():.1f}", flush=True)
X = X.to('cuda:0'); torch.cuda.set_device(0)
if pi % 10 == 0: print(f" Token {pi}/{len(generated)}: {time.time()-t1:.2f}s", flush=True)
print(f" Prefill done ({time.time()-t0:.1f}s)")
if _args.prefill_only: print("Prefill-only mode, stopping."); return
# Decode
print(f"\nDecoding (max {MAX_NEW_TOKENS} tokens)...")
for step in range(MAX_NEW_TOKENS):
t1 = time.time()
tid_int64 = torch.tensor([all_tokens[-1]], dtype=torch.long, device='cuda:0')
tid = tid_int64.to(torch.int32) # hash router needs int32
dec_pos = torch.tensor([len(all_tokens)-1], dtype=torch.long, device='cuda:0')
X = mHCLayer.init_state(embed(tid_int64))
for li in range(n_layers):
gpu = li % NUM_GPUS
if X.device != torch.device(f"cuda:{gpu}"): X = X.to(f"cuda:{gpu}")
torch.cuda.set_device(gpu)
X = forward_layer(X, layer_w[li], li, cfg, *rope_caches[gpu],
attn_mhcs.get(li), ffn_mhcs.get(li),
attn_norms.get(li), ffn_norms.get(li),
kv_caches[li], dec_pos, tid,
compressors.get(li), indexers.get(li),
moe_runners.get(li), se_runners.get(li), routers.get(li),
prod_lin=prod_lins.get(li))
X = X.to('cuda:0'); torch.cuda.set_device(0)
x_out = hc_head.forward(X) if hc_head is not None else X[:, 0, :]
if final_norm_w is not None: x_out = rmsnorm(x_out, final_norm_w)
logits = F.linear(x_out, lm_w)
next_id = torch.argmax(logits, -1).item(); all_tokens.append(next_id)
dt = time.time() - t1
has_nan = torch.isnan(logits.float()).any().item()
if step % 1 == 0 or has_nan:
tv, ti = torch.topk(logits[0], 5)
top5 = ' '.join(f'{tokenizer.decode([t.item()])}({v.item():.1f})' for t, v in zip(ti[:5], tv[:5]))
print(f" Step {step}: {next_id} '{tokenizer.decode([next_id])}' ({dt:.2f}s) "
f"logits=[{logits.float().min().item():.1f},{logits.float().max().item():.1f}] "
f"nan={has_nan} |X|={X.abs().max().item():.1f} top5: {top5}", flush=True)
if has_nan: break
if next_id == tokenizer.eos_token_id: break
out = tokenizer.decode(all_tokens, skip_special_tokens=True)
print(f"\n{'='*70}")
print(f"Input: '{PROMPT}'")
print(f"Output: '{out}'")
print(f"Total: {time.time()-t0:.1f}s")
print(f"{'='*70}")
if __name__ == "__main__":
main()