mirror of
https://github.com/deepseek-ai/DeepInfer.git
synced 2026-11-09 22:59:44 +08:00
Three torch-neuronx 2.14 device behaviours, each worked around in a bit-identical way: - torch.cat of float8 tensors is wrong on the device: merge_linear_weights (fused q_a/kv, q_b, ... projections) now concatenates the raw bytes, like the HF loader's read_concat. - int64 constants are narrowed to int32 by the compiler (--implicit-integer-downcast): deep_select.topk used int64.max as the sort sentinel, which became -1 and pushed the selected indices out of rows with end < topk -- the first compressed layer then attended to no compressed KV for short prompts. The sentinel is now vocab_size. - advanced indexing of non-contiguous float8 views is wrong: the remaining site (deep_gemm sparse MQA logits) goes through uint8 views; deep_ep's byte path also covers e8m0. DEEPINFER_NEURON_FP8_GUARD=1|raise (neuron_ops/fp8_guard.py) reports every cat/stack/advanced-index/gather/scatter/where on a float8 tensor with its call site; the tiny end-to-end run and all CPU unit tests report zero sites. Tiny checkpoint on one NeuronCore, teacher-forced vs the official reference: short 9/9 (min cos 0.9985), medium 8/9 (0.9991), long 8/9 (0.9978); logits rel <= 0.066, within the reference's own noise floor -- same as CPU mode. Also: probe_layer0 PROBE_LAYERS=N and attention-input capture, device_diff copies tensor attributes (replays deep_gemm sparse MQA metadata), device primitive checks for the narrowing and strided fp8 casts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
215 lines
8.8 KiB
Python
215 lines
8.8 KiB
Python
"""Debug guard: report every float8 concatenation / advanced-indexing call site.
|
|
|
|
torch-neuronx 2.14 returns wrong data on the ``neuron`` device for ``torch.cat`` of float8 tensors and for
|
|
advanced indexing on non-contiguous float8 views, while the same ops on ``uint8`` views of the same bytes are
|
|
correct (tests/neuron/device/test_device_primitives.py). Kernel and loader code must therefore move float8 bytes
|
|
through ``uint8`` views. This guard finds the sites that do not, **without a NeuronCore**: it also works with
|
|
``DEEPINFER_NEURON_DEVICE=cpu``, where the ops are correct and only the call sites are recorded.
|
|
|
|
DEEPINFER_NEURON_FP8_GUARD=1 record, print a deduplicated summary at exit
|
|
DEEPINFER_NEURON_FP8_GUARD=raise raise RuntimeError at the first offending call
|
|
DEEPINFER_NEURON_FP8_GUARD_LOG=<path> also append the summary to <path> (one block per process)
|
|
|
|
Installed by ``PlatformNeuron.__init__`` only when the variable is set. It monkey-patches the Python entry points
|
|
(``torch.cat``/``stack``/``concat``/..., ``Tensor.__getitem__``/``__setitem__`` with an advanced index,
|
|
``gather``/``index_select``/``take*``/``embedding``, ``index_put(_)``/``scatter*``/``index_copy_``/..., ``where``)
|
|
and records, for calls that involve a 1-byte float tensor, the first stack frame outside torch and outside this
|
|
module together with dtype / shape / contiguity. Basic indexing (ints, slices, None, Ellipsis) and dtype casts are
|
|
views or copies that behave correctly and are not reported. Debug only: the overhead is not tuned.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import atexit
|
|
import numbers
|
|
import os
|
|
import sys
|
|
import threading
|
|
from collections import OrderedDict
|
|
from typing import Any, Callable
|
|
|
|
import torch
|
|
|
|
_MODE: str | None = None # None (not installed), "record" or "raise"
|
|
_LOCK = threading.Lock()
|
|
_TLS = threading.local()
|
|
_SITES: "OrderedDict[tuple[str, str], dict[str, Any]]" = OrderedDict()
|
|
_MAX_SIGS = 4 # distinct dtype/shape/contiguity signatures kept per site
|
|
_SKIP_DIRS = (os.path.dirname(torch.__file__) + os.sep,)
|
|
_THIS_FILE = os.path.abspath(__file__)
|
|
_SKIP_FILES = (_THIS_FILE, os.path.join(os.path.dirname(_THIS_FILE), "transfer_to_neuron.py")) # wrappers, not sites
|
|
|
|
|
|
def _is_fp8(x: Any) -> bool:
|
|
return isinstance(x, torch.Tensor) and x.dtype.is_floating_point and x.dtype.itemsize == 1
|
|
|
|
|
|
def _call_site() -> str:
|
|
f = sys._getframe(2)
|
|
while f is not None:
|
|
fn = os.path.abspath(f.f_code.co_filename)
|
|
if fn not in _SKIP_FILES and not fn.startswith(_SKIP_DIRS):
|
|
return f"{fn}:{f.f_lineno} ({f.f_code.co_name})"
|
|
f = f.f_back
|
|
return "<unknown>"
|
|
|
|
|
|
def _desc(t: torch.Tensor) -> str:
|
|
dt = str(t.dtype).replace("torch.", "")
|
|
return f"{dt}{list(t.shape)}{'' if t.is_contiguous() else ' non-contig'}"
|
|
|
|
|
|
def _report(op: str, tensors: list[torch.Tensor]) -> None:
|
|
site = _call_site()
|
|
sig = ", ".join(_desc(t) for t in tensors[:4]) + (", ..." if len(tensors) > 4 else "")
|
|
with _LOCK:
|
|
entry = _SITES.setdefault((op, site), {"count": 0, "sigs": []})
|
|
entry["count"] += 1
|
|
if sig not in entry["sigs"] and len(entry["sigs"]) < _MAX_SIGS:
|
|
entry["sigs"].append(sig)
|
|
if _MODE == "raise":
|
|
raise RuntimeError(f"DEEPINFER_NEURON_FP8_GUARD: float8 {op} at {site}: {sig} "
|
|
"(route the bytes through .view(torch.uint8))")
|
|
|
|
|
|
def _tensors_in(obj: Any) -> list[torch.Tensor]:
|
|
if isinstance(obj, torch.Tensor):
|
|
return [obj]
|
|
if isinstance(obj, (list, tuple)):
|
|
return [t for t in obj if isinstance(t, torch.Tensor)]
|
|
return []
|
|
|
|
|
|
def _is_advanced_index(idx: Any) -> bool:
|
|
"""Tensor / list / array index anywhere in the index tuple (a top-level tuple is basic indexing)."""
|
|
items = idx if isinstance(idx, tuple) else (idx,)
|
|
for it in items:
|
|
if isinstance(it, (torch.Tensor, list)):
|
|
return True
|
|
if hasattr(it, "__array__") and not isinstance(it, numbers.Integral) and getattr(it, "ndim", 1) > 0:
|
|
return True # numpy array (numpy integer scalars are basic indexing)
|
|
return False
|
|
|
|
|
|
def _wrap(fn: Callable, op: str, pick: Callable[..., list[torch.Tensor]]) -> Callable:
|
|
if getattr(fn, "_fp8_guard_wrapped", False):
|
|
return fn
|
|
|
|
def wrapper(*args, **kwargs):
|
|
if not getattr(_TLS, "busy", False):
|
|
_TLS.busy = True
|
|
try:
|
|
involved = [t for t in pick(*args, **kwargs) if _is_fp8(t)]
|
|
except Exception: # never let the guard change behaviour of a bad call; the real call raises below
|
|
involved = []
|
|
finally:
|
|
_TLS.busy = False
|
|
if involved:
|
|
_report(op, involved)
|
|
return fn(*args, **kwargs)
|
|
|
|
wrapper._fp8_guard_wrapped = True # type: ignore[attr-defined]
|
|
wrapper.__wrapped__ = fn # type: ignore[attr-defined]
|
|
wrapper.__name__ = getattr(fn, "__name__", op)
|
|
wrapper.__doc__ = getattr(fn, "__doc__", None)
|
|
return wrapper
|
|
|
|
|
|
# --- operand pickers: return the tensors whose float8-ness makes the call suspicious -------------------------------
|
|
def _p_seq(tensors=None, *a, **k): # cat / stack / concat(tensors, dim)
|
|
return _tensors_in(tensors if tensors is not None else k.get("tensors"))
|
|
|
|
|
|
def _p_self(self, *a, **k): # gather / index_select / take ...: the source tensor
|
|
return [self] if isinstance(self, torch.Tensor) else []
|
|
|
|
|
|
def _p_getitem(self, idx):
|
|
return [self] if _is_advanced_index(idx) else []
|
|
|
|
|
|
def _p_setitem(self, idx, value):
|
|
return [self, *_tensors_in(value)] if _is_advanced_index(idx) else []
|
|
|
|
|
|
def _p_all(*a, **k): # every tensor operand (index_put / scatter / where ...)
|
|
out: list[torch.Tensor] = []
|
|
for x in list(a) + list(k.values()):
|
|
out.extend(_tensors_in(x))
|
|
return out
|
|
|
|
|
|
def _p_embedding(input, weight, *a, **k):
|
|
return [weight]
|
|
|
|
|
|
_TORCH_FNS = {
|
|
"cat": _p_seq, "concat": _p_seq, "concatenate": _p_seq, "stack": _p_seq,
|
|
"hstack": _p_seq, "vstack": _p_seq, "dstack": _p_seq, "column_stack": _p_seq,
|
|
"gather": _p_self, "index_select": _p_self, "take": _p_self, "take_along_dim": _p_self,
|
|
"index_put": _p_all, "index_put_": _p_all, "scatter": _p_all, "scatter_add": _p_all,
|
|
"scatter_reduce": _p_all, "index_copy": _p_all, "index_add": _p_all, "index_fill": _p_all,
|
|
"masked_scatter": _p_all, "masked_select": _p_all, "where": _p_all,
|
|
}
|
|
_TENSOR_METHODS = {
|
|
"gather": _p_self, "index_select": _p_self, "take": _p_self, "take_along_dim": _p_self,
|
|
"index_put": _p_all, "index_put_": _p_all, "scatter": _p_all, "scatter_": _p_all,
|
|
"scatter_add": _p_all, "scatter_add_": _p_all, "scatter_reduce": _p_all, "scatter_reduce_": _p_all,
|
|
"index_copy": _p_all, "index_copy_": _p_all, "index_add": _p_all, "index_add_": _p_all,
|
|
"index_fill": _p_all, "index_fill_": _p_all, "masked_scatter": _p_all, "masked_scatter_": _p_all,
|
|
"masked_select": _p_all, "where": _p_all,
|
|
}
|
|
|
|
|
|
def _summary() -> str:
|
|
lines = [f"===== DEEPINFER_NEURON_FP8_GUARD (pid {os.getpid()}): {len(_SITES)} float8 cat/advanced-index site(s)"]
|
|
for (op, site), e in _SITES.items():
|
|
lines.append(f" {op:<20} x{e['count']:<7} {site}")
|
|
for sig in e["sigs"]:
|
|
lines.append(f" {sig}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def _at_exit() -> None:
|
|
text = _summary()
|
|
print(text, file=sys.stderr, flush=True)
|
|
path = os.getenv("DEEPINFER_NEURON_FP8_GUARD_LOG")
|
|
if path:
|
|
with open(path, "a") as f:
|
|
f.write(text + "\n")
|
|
|
|
|
|
def sites() -> dict[tuple[str, str], dict[str, Any]]:
|
|
"""Recorded (op, call site) -> {count, sigs}; for tests."""
|
|
with _LOCK:
|
|
return {k: {"count": v["count"], "sigs": list(v["sigs"])} for k, v in _SITES.items()}
|
|
|
|
|
|
def reset() -> None:
|
|
with _LOCK:
|
|
_SITES.clear()
|
|
|
|
|
|
def install(mode: str | None = None) -> None:
|
|
"""Install the guard. ``mode``: ``"raise"`` or anything truthy for record mode (default: from the env var)."""
|
|
global _MODE
|
|
if mode is None:
|
|
mode = os.getenv("DEEPINFER_NEURON_FP8_GUARD", "")
|
|
if not mode or mode == "0" or _MODE is not None:
|
|
return
|
|
_MODE = "raise" if mode == "raise" else "record"
|
|
for name, pick in _TORCH_FNS.items():
|
|
if hasattr(torch, name):
|
|
setattr(torch, name, _wrap(getattr(torch, name), f"torch.{name}", pick))
|
|
for name, pick in _TENSOR_METHODS.items():
|
|
if hasattr(torch.Tensor, name):
|
|
setattr(torch.Tensor, name, _wrap(getattr(torch.Tensor, name), f"Tensor.{name}", pick))
|
|
torch.Tensor.__getitem__ = _wrap(torch.Tensor.__getitem__, "getitem[adv]", _p_getitem)
|
|
torch.Tensor.__setitem__ = _wrap(torch.Tensor.__setitem__, "setitem[adv]", _p_setitem)
|
|
import torch.nn.functional as F
|
|
|
|
F.embedding = _wrap(F.embedding, "F.embedding", _p_embedding)
|
|
atexit.register(_at_exit)
|
|
print(f"[deepinfer] DEEPINFER_NEURON_FP8_GUARD={_MODE}: recording float8 cat/advanced-index sites",
|
|
file=sys.stderr, flush=True)
|