whn09 760e3cadc0 Neuron: test suite for new boxes, device-vs-host differential tool
- tests/neuron/setup_trn2.sh: one-shot setup of a fresh Trn2 (venv layered
  on the DLAMI torch-neuronx venv, C++ build, official reference + tokenizer,
  tiny checkpoint, CPU golden).
- tests/neuron/run_all.sh cpu|device|all [stage...]: vendor kernel tests,
  tilelang_ops interface tests (basic-mode stubs reported as skips via
  nie_skip), deep_ep at world size 1/2/4 (gloo) and 2/4/8 on the neuron
  process group, device primitive checks, end-to-end vs golden on CPU and
  on a NeuronCore, and the device diff; PASS/FAIL summary.
- tests/neuron/device/test_device_primitives.py: torch-neuronx behaviours
  the port depends on or works around (fp8 strided-view indexing, uint64,
  1-rank all_to_all, inference-mode version counters, ...).
- examples/neuron/device_diff.py: re-runs every kernel call of a NeuronCore
  run on host copies and ranks kernels by device/host difference.
- Unit tests written during bring-up moved from scratch dirs into
  tests/neuron/{deep_gemm,flash_mla,deep_ep,tile_kernels,sampling}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:46:29 +08:00

90 lines
3.9 KiB
Python

"""Reference quant/dequant ported from vendor/FlashMLA/tests/quant.py (CPU, no kernelkit)."""
import torch
META = {
"v41_fp8": (512, 448, 64, 32, 16),
"v41_fp4": (512, 448, 64, 16, 32),
}
BPT = {"v41_fp8": 528, "v41_fp4": 288}
_E2M1 = torch.tensor([0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0], dtype=torch.float32)
def _cast_scale_inv_to_ue8m0(scales_inv, out_dtype=torch.float32):
return torch.pow(2, torch.clamp_min(scales_inv, 1e-4).log2().ceil()).to(out_dtype)
def _quantize_to_e2m1(x):
x = x.float()
mags = _E2M1
sign = (torch.signbit(x)).to(torch.uint8) << 3
a = torch.nan_to_num(x.abs(), nan=0.0, posinf=6.0).clamp_max(6.0)
mids = (mags[:-1] + mags[1:]) / 2
code = torch.bucketize(a, mids, right=True)
on_tie = (a.unsqueeze(-1) == mids).any(dim=-1)
tie_code = torch.bucketize(a, mids, right=False)
code = torch.where(on_tie, tie_code + (tie_code & 1), code)
return (sign | code.to(torch.uint8)).to(torch.uint8)
def quantize_k_cache(input_k_cache, fmt):
"""input_k_cache: (num_blocks, block_size, 1, 512) bf16 -> uint8 (nb, bs, 1, bpt)."""
d, d_nope, d_rope, tile_size, num_tiles = META[fmt]
num_blocks, block_size, h_k, _ = input_k_cache.shape
assert h_k == 1
x = input_k_cache.squeeze(2)
bpt = BPT[fmt]
result = torch.empty((num_blocks, block_size, bpt), dtype=torch.uint8)
if fmt == "v41_fp8":
data = result[..., : d_nope + d_rope].view(torch.float8_e4m3fn)
sf = result[..., d_nope + d_rope :]
for t in range(16):
sl = x[..., t * tile_size : (t + 1) * tile_size]
inv = torch.abs(sl).max(dim=-1).values.float() / 448.0
inv = _cast_scale_inv_to_ue8m0(inv)
# ue8m0 byte = bf16 bits >> 7 (Ascend path of tests/quant.py)
sf[:, :, t] = (inv.view(torch.int32) // 2**23).to(torch.uint8)
data[:, :, t * tile_size : (t + 1) * tile_size] = (sl.float() / inv.unsqueeze(-1)).to(
torch.float8_e4m3fn
)
else:
data = result[..., : d // 2]
sf = result[..., d // 2 :].view(torch.float8_e4m3fn)
xf = x.float()
amax = torch.nan_to_num(xf.abs(), nan=float("inf")).view(num_blocks, block_size, num_tiles, tile_size).amax(-1)
scale = torch.clamp(amax / 6.0, 2.0**-9, 448.0).to(torch.float8_e4m3fn)
normalized = xf.view(num_blocks, block_size, num_tiles, tile_size) / scale.float().unsqueeze(-1)
codes = _quantize_to_e2m1(normalized).view(num_blocks, block_size, d)
data[...] = codes[..., 0::2] | (codes[..., 1::2] << 4)
sf[...] = scale
return result.view(num_blocks, block_size, 1, bpt)
def dequantize_k_cache(quant_k_cache, fmt):
d, d_nope, d_rope, tile_size, num_tiles = META[fmt]
num_blocks, block_size, h_k, _ = quant_k_cache.shape
assert h_k == 1
out = torch.empty((num_blocks, block_size, d), dtype=torch.bfloat16)
q = quant_k_cache.view(num_blocks, block_size, -1)
if fmt == "v41_fp8":
raw = q[..., : d_nope + d_rope].view(torch.float8_e4m3fn)
sf = q[..., d_nope + d_rope :]
for t in range(16):
cur = raw[..., t * tile_size : (t + 1) * tile_size].to(torch.bfloat16)
scales = (sf[:, :, t].to(torch.int32) * 2**7).to(torch.int16).view(torch.bfloat16)
out[..., t * tile_size : (t + 1) * tile_size] = cur * scales.unsqueeze(-1)
else:
data = q[..., : d // 2]
sf = q[..., d // 2 :].view(torch.float8_e4m3fn)
codes = torch.empty((num_blocks, block_size, d), dtype=torch.uint8)
codes[..., 0::2] = data & 0xF
codes[..., 1::2] = data >> 4
val = _E2M1[(codes & 7).long()]
val = torch.where((codes & 8) != 0, -val, val).view(num_blocks, block_size, num_tiles, tile_size)
out[...] = (val.to(torch.bfloat16) * sf.to(torch.bfloat16).unsqueeze(-1)).view(
num_blocks, block_size, d
)
return out.view(num_blocks, block_size, 1, d)