mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-10-29 07:17:08 +08:00
- 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>
120 lines
5.5 KiB
Python
120 lines
5.5 KiB
Python
"""CPU correctness tests for the pure-torch deep_select vendor package.
|
|
|
|
The checks are ported from vendor/DeepSelect/tests/test.py (NaN guard, index range, uniqueness,
|
|
gathered value, topk condition, sorted index / sorted value).
|
|
"""
|
|
|
|
import itertools
|
|
import sys
|
|
|
|
import torch
|
|
|
|
from deep_select.interface import get_stride_requirement, topk as ds_topk
|
|
|
|
torch.manual_seed(0)
|
|
FAILS = []
|
|
NAN_SENTINEL = 0x3F3F3F3F
|
|
|
|
|
|
def chk(name, cond):
|
|
print(f"[{'ok ' if cond else 'FAIL'}] {name}")
|
|
if not cond:
|
|
FAILS.append(name)
|
|
|
|
|
|
def run_case(b, vocab, k, dtype, idx_dtype, sorted_value, sorted_index, return_value,
|
|
use_end, use_offset, with_nan=False, use_out=False):
|
|
tag = (f"b{b} v{vocab} k{k} {str(dtype).split('.')[-1]} {str(idx_dtype).split('.')[-1]} "
|
|
f"sv{int(sorted_value)} si{int(sorted_index)} rv{int(return_value)} "
|
|
f"end{int(use_end)} off{int(use_offset)} nan{int(with_nan)} out{int(use_out)}")
|
|
x = (torch.randn(b, vocab) * 3).to(dtype)
|
|
end = torch.randint(1, vocab + 1, (b,), dtype=torch.int32) if use_end else None
|
|
if use_end:
|
|
end[0] = min(k // 2 + 1, vocab) # a short row, fewer valid entries than k
|
|
offset = torch.randint(0, 1000, (b,), dtype=torch.int32) if use_offset else None
|
|
nan_rows = torch.zeros(b, dtype=torch.bool)
|
|
if with_nan:
|
|
nan_rows[0] = True
|
|
x[0, 0] = float("nan")
|
|
out_buf = torch.empty((b, k), dtype=idx_dtype) if use_out else None
|
|
|
|
val, idx = ds_topk(
|
|
x, k, sorted=sorted_value, end=end, indices_type=idx_dtype, sorted_index=sorted_index,
|
|
output_idx=out_buf, output_idx_offset=offset, idx_oob_fill_value=-1,
|
|
value_oob_fill_value=float("-inf"), return_value=return_value, abort_when_nan_found=False,
|
|
)
|
|
chk(f"{tag}: dtypes", idx.dtype is idx_dtype and (val is None) == (not return_value)
|
|
and (val is None or val.dtype is dtype))
|
|
chk(f"{tag}: shapes", tuple(idx.shape) == (b, k) and (val is None or tuple(val.shape) == (b, k)))
|
|
if use_out:
|
|
chk(f"{tag}: writes output_idx in place", idx.data_ptr() == out_buf.data_ptr())
|
|
|
|
valid_len = (torch.full((b,), vocab, dtype=torch.int64) if end is None else end.to(torch.int64))
|
|
counts = torch.clamp(valid_len, max=k)
|
|
sel = torch.arange(k).unsqueeze(0) < counts.unsqueeze(1)
|
|
|
|
# NaN guard: whole row filled with the sentinel (Ascend behaviour)
|
|
if with_nan:
|
|
chk(f"{tag}: NaN row sentinel", bool((idx[nan_rows] == NAN_SENTINEL).all()))
|
|
sel = sel & ~nan_rows.unsqueeze(1)
|
|
|
|
raw = idx.to(torch.int64)
|
|
if offset is not None:
|
|
raw = raw - offset.to(torch.int64).unsqueeze(1)
|
|
in_range = (raw >= 0) & (raw < valid_len.unsqueeze(1))
|
|
chk(f"{tag}: index range", bool((in_range | ~sel).all()))
|
|
chk(f"{tag}: padding fill", bool((idx[~sel & ~nan_rows.unsqueeze(1)] == -1).all()))
|
|
|
|
srt = torch.sort(torch.where(sel, raw, torch.iinfo(torch.int64).max), dim=1).values
|
|
dup = (srt[:, 1:] == srt[:, :-1]) & sel[:, 1:]
|
|
chk(f"{tag}: unique index", not bool(dup.any()))
|
|
|
|
safe = torch.where(sel & in_range, raw, torch.zeros_like(raw))
|
|
gathered = x.gather(1, safe)
|
|
rows = ~nan_rows
|
|
if rows.any():
|
|
if val is not None:
|
|
exp = gathered[rows].masked_fill(~sel[rows], float("-inf"))
|
|
chk(f"{tag}: gathered value bitwise", torch.equal(val[rows], exp))
|
|
gv = gathered[rows].float()
|
|
smin = gv.masked_fill(~sel[rows], float("inf")).amin(dim=1)
|
|
unsel = x[rows].float().clone()
|
|
unsel.scatter_(1, safe[rows], float("-inf"))
|
|
col = torch.arange(vocab).unsqueeze(0)
|
|
unsel = torch.where(col >= valid_len[rows].unsqueeze(1), torch.full_like(unsel, float("-inf")), unsel)
|
|
unsel = torch.where(unsel != unsel, torch.full_like(unsel, float("-inf")), unsel)
|
|
chk(f"{tag}: topk condition", bool((smin >= unsel.amax(dim=1)).all()))
|
|
|
|
ordered = sel[:, 1:]
|
|
if sorted_index:
|
|
ok = (idx[:, 1:].to(torch.int64) >= idx[:, :-1].to(torch.int64)) | ~ordered
|
|
chk(f"{tag}: sorted index", bool(ok.all()))
|
|
if sorted_value and val is not None:
|
|
ok = (val[:, :-1].float() >= val[:, 1:].float()) | ~ordered
|
|
chk(f"{tag}: sorted value", bool(ok.all()))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
chk("stride requirement == (32, 32)", get_stride_requirement() == (32, 32))
|
|
|
|
combos = [(False, False, False), (False, False, True), (True, False, True),
|
|
(False, True, True), (False, True, False)]
|
|
for dtype in (torch.float32, torch.bfloat16):
|
|
for idx_dtype in (torch.int32, torch.int64):
|
|
for sv, si, rv in combos:
|
|
if sv and dtype is torch.bfloat16:
|
|
continue # sorted_value is fp32-only upstream
|
|
run_case(5, 97, 8, dtype, idx_dtype, sv, si, rv, use_end=True, use_offset=True)
|
|
for use_end, use_offset in itertools.product((False, True), repeat=2):
|
|
run_case(3, 64, 16, torch.float32, torch.int32, False, True, False, use_end, use_offset)
|
|
run_case(4, 33, 8, torch.float32, torch.int32, False, True, False, True, True, with_nan=True)
|
|
run_case(4, 33, 8, torch.float32, torch.int32, True, False, True, False, False, with_nan=True)
|
|
run_case(3, 16, 8, torch.float32, torch.int32, False, True, False, True, True, use_out=True)
|
|
# k larger than the vocab / every row short
|
|
run_case(2, 6, 8, torch.float32, torch.int32, False, True, False, True, False)
|
|
# DeepInfer indexer call shape: big-ish k, end, offset, sorted_index, no values
|
|
run_case(2, 512, 128, torch.float32, torch.int32, False, True, False, True, True)
|
|
|
|
print("\nFAILURES:", FAILS if FAILS else "none")
|
|
sys.exit(1 if FAILS else 0)
|