Enhance diff and tensor-info logging in dumper grafter (#24512)
This commit is contained in:
@@ -885,7 +885,7 @@ class _Grafter:
|
|||||||
if is_send:
|
if is_send:
|
||||||
_log(
|
_log(
|
||||||
f"[Grafter] send role={role.value} dir={direction.value} "
|
f"[Grafter] send role={role.value} dir={direction.value} "
|
||||||
f"tags={tags} extras={extras}"
|
f"tags={tags} extras={extras} local={get_tensor_info(value)}"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -902,6 +902,7 @@ class _Grafter:
|
|||||||
# crash the whole training/inference run. On error we log the full
|
# crash the whole training/inference run. On error we log the full
|
||||||
# traceback and skip this graft point; downstream sees the recv
|
# traceback and skip this graft point; downstream sees the recv
|
||||||
# side's original tensor unchanged.
|
# side's original tensor unchanged.
|
||||||
|
info_before_overridden = get_tensor_info(value)
|
||||||
try:
|
try:
|
||||||
value_to_override = self._apply_transform(
|
value_to_override = self._apply_transform(
|
||||||
tags=tags,
|
tags=tags,
|
||||||
@@ -909,10 +910,14 @@ class _Grafter:
|
|||||||
received_extras_list=sender_extras,
|
received_extras_list=sender_extras,
|
||||||
target=value,
|
target=value,
|
||||||
)
|
)
|
||||||
|
diff = _compare_tensors_quick(value, value_to_override)
|
||||||
_log(
|
_log(
|
||||||
f"[Grafter] recv role={role.value} dir={direction.value} "
|
f"[Grafter] recv role={role.value} dir={direction.value} "
|
||||||
f"tags={tags} n_senders={len(sender_tensors)} "
|
f"tags={tags} n_senders={len(sender_tensors)} "
|
||||||
f"sender_extras={sender_extras}"
|
f"sender_extras={sender_extras} "
|
||||||
|
f"before_overridden={info_before_overridden} "
|
||||||
|
f"to_override={get_tensor_info(value_to_override)} "
|
||||||
|
f"diff_pre_vs_new={diff}"
|
||||||
)
|
)
|
||||||
value.copy_(value_to_override)
|
value.copy_(value_to_override)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1176,6 +1181,35 @@ def _log(msg: str) -> None:
|
|||||||
print(f"[Dumper, rank={_get_rank()}, t={time.time():.3f}] {msg}", flush=True)
|
print(f"[Dumper, rank={_get_rank()}, t={time.time():.3f}] {msg}", flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _compare_tensors_quick(a: "torch.Tensor", b: "torch.Tensor") -> str:
|
||||||
|
"""One-line summary of how close two tensors are. Inspired by
|
||||||
|
sglang.srt.debug_utils.dump_comparator._compute_and_print_diff;
|
||||||
|
intentionally inlined here to keep dumper.py free of cross-file imports.
|
||||||
|
|
||||||
|
Different dtypes are fine -- we unify by casting both to fp32, which is
|
||||||
|
enough for the order-of-magnitude diff summary we log."""
|
||||||
|
if a.shape != b.shape:
|
||||||
|
return f"shape mismatch (a={tuple(a.shape)} vs b={tuple(b.shape)})"
|
||||||
|
if a.numel() == 0:
|
||||||
|
return "empty"
|
||||||
|
a_float = a.detach().to(torch.float32)
|
||||||
|
b_float = b.detach().to(torch.float32)
|
||||||
|
raw_abs = (a_float - b_float).abs()
|
||||||
|
max_abs = raw_abs.max().item()
|
||||||
|
mean_abs = raw_abs.mean().item()
|
||||||
|
rel_diff = _calc_rel_diff(a_float, b_float).item()
|
||||||
|
return f"rel_diff={rel_diff:.6g} max_abs={max_abs:.6g} mean_abs={mean_abs:.6g}"
|
||||||
|
|
||||||
|
|
||||||
|
# Copied verbatim from sglang.srt.debug_utils.dump_comparator (originally from
|
||||||
|
# DeepGEMM). Kept inline here so dumper.py has no cross-file imports.
|
||||||
|
def _calc_rel_diff(x: "torch.Tensor", y: "torch.Tensor"):
|
||||||
|
x, y = x.double(), y.double()
|
||||||
|
denominator = (x * x + y * y).sum()
|
||||||
|
sim = 2 * (x * y).sum() / denominator
|
||||||
|
return 1 - sim
|
||||||
|
|
||||||
|
|
||||||
def _obj_to_dict(obj):
|
def _obj_to_dict(obj):
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import torch.distributed as dist
|
|||||||
from sglang.srt.debug_utils.dumper import (
|
from sglang.srt.debug_utils.dumper import (
|
||||||
DumperConfig,
|
DumperConfig,
|
||||||
_collective_with_timeout,
|
_collective_with_timeout,
|
||||||
|
_compare_tensors_quick,
|
||||||
_deepcopy_or_clone,
|
_deepcopy_or_clone,
|
||||||
_detect_recompute_status,
|
_detect_recompute_status,
|
||||||
_Dumper,
|
_Dumper,
|
||||||
@@ -397,6 +398,37 @@ class TestLog:
|
|||||||
assert ", t=" in out, out
|
assert ", t=" in out, out
|
||||||
|
|
||||||
|
|
||||||
|
class TestCompareTensorsQuick:
|
||||||
|
def test_identical(self):
|
||||||
|
a = torch.tensor([1.0, 2.0, 3.0])
|
||||||
|
s = _compare_tensors_quick(a, a.clone())
|
||||||
|
assert "rel_diff=0" in s, s
|
||||||
|
assert "max_abs=0" in s, s
|
||||||
|
|
||||||
|
def test_diverged(self):
|
||||||
|
a = torch.tensor([1.0, 2.0, 3.0])
|
||||||
|
b = torch.tensor([1.0, 2.0, 4.0]) # last element differs by 1
|
||||||
|
s = _compare_tensors_quick(a, b)
|
||||||
|
assert "max_abs=1" in s, s
|
||||||
|
assert "rel_diff=" in s, s
|
||||||
|
|
||||||
|
def test_shape_mismatch(self):
|
||||||
|
s = _compare_tensors_quick(torch.zeros(3), torch.zeros(4))
|
||||||
|
assert "shape mismatch" in s, s
|
||||||
|
|
||||||
|
def test_dtype_unified(self):
|
||||||
|
s = _compare_tensors_quick(
|
||||||
|
torch.zeros(3, dtype=torch.float32),
|
||||||
|
torch.zeros(3, dtype=torch.float64),
|
||||||
|
)
|
||||||
|
assert "rel_diff=" in s, s
|
||||||
|
assert "max_abs=" in s, s
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
s = _compare_tensors_quick(torch.zeros(0), torch.zeros(0))
|
||||||
|
assert s == "empty"
|
||||||
|
|
||||||
|
|
||||||
class TestCollectiveTimeout:
|
class TestCollectiveTimeout:
|
||||||
def test_watchdog_fires_on_timeout(self):
|
def test_watchdog_fires_on_timeout(self):
|
||||||
block_event = threading.Event()
|
block_event = threading.Event()
|
||||||
|
|||||||
Reference in New Issue
Block a user