Change dump output format to dict with value and metadata (#18879)
This commit is contained in:
@@ -280,6 +280,9 @@ def _load_object(path):
|
|||||||
print(f"Skip load {path} since error {e}")
|
print(f"Skip load {path} since error {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if isinstance(x, dict) and "value" in x:
|
||||||
|
x = x["value"]
|
||||||
|
|
||||||
if not isinstance(x, torch.Tensor):
|
if not isinstance(x, torch.Tensor):
|
||||||
print(f"Skip load {path} since {type(x)=} is not a Tensor ({x=})")
|
print(f"Skip load {path} since {type(x)=} is not a Tensor ({x=})")
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class DumpLoader:
|
|||||||
|
|
||||||
path = self._directory / row["filename"]
|
path = self._directory / row["filename"]
|
||||||
output = torch.load(path, weights_only=False)
|
output = torch.load(path, weights_only=False)
|
||||||
|
if isinstance(output, dict) and "value" in output:
|
||||||
|
output = output["value"]
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"[DumpLoader] load from {path=} (query: {name=} {kwargs=}, output: {type(output)})"
|
f"[DumpLoader] load from {path=} (query: {name=} {kwargs=}, output: {type(output)})"
|
||||||
|
|||||||
@@ -164,7 +164,11 @@ class _Dumper:
|
|||||||
|
|
||||||
if self._enable_write_file and save:
|
if self._enable_write_file and save:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
_torch_save(value, str(path))
|
output_data = {
|
||||||
|
"value": value.data if isinstance(value, torch.nn.Parameter) else value,
|
||||||
|
"meta": full_kwargs,
|
||||||
|
}
|
||||||
|
_torch_save(output_data, str(path))
|
||||||
|
|
||||||
|
|
||||||
def _torch_save(value, path: str):
|
def _torch_save(value, path: str):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import torch
|
|||||||
import torch.distributed as dist
|
import torch.distributed as dist
|
||||||
|
|
||||||
from sglang.srt.debug_utils.dumper import (
|
from sglang.srt.debug_utils.dumper import (
|
||||||
|
_Dumper,
|
||||||
_obj_to_dict,
|
_obj_to_dict,
|
||||||
_torch_save,
|
_torch_save,
|
||||||
get_tensor_info,
|
get_tensor_info,
|
||||||
@@ -161,8 +162,12 @@ class TestDumperDistributed:
|
|||||||
|
|
||||||
dist.barrier()
|
dist.barrier()
|
||||||
path = _find_dump_file(tmpdir, rank=rank, name="content_check")
|
path = _find_dump_file(tmpdir, rank=rank, name="content_check")
|
||||||
loaded = torch.load(path, map_location="cpu", weights_only=True)
|
raw = _load_dump(path)
|
||||||
assert torch.equal(loaded, tensor.cpu())
|
assert isinstance(raw, dict), f"Expected dict, got {type(raw)}"
|
||||||
|
assert "value" in raw and "meta" in raw
|
||||||
|
assert torch.equal(raw["value"], tensor.cpu())
|
||||||
|
assert raw["meta"]["name"] == "content_check"
|
||||||
|
assert raw["meta"]["rank"] == rank
|
||||||
|
|
||||||
|
|
||||||
class TestDumperFileWriteControl:
|
class TestDumperFileWriteControl:
|
||||||
@@ -230,6 +235,54 @@ class TestDumperFileWriteControl:
|
|||||||
assert len(_get_filenames(tmpdir)) == 0
|
assert len(_get_filenames(tmpdir)) == 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpDictFormat:
|
||||||
|
"""Verify that dump files use the dict output format: {"value": ..., "meta": {...}}."""
|
||||||
|
|
||||||
|
def test_dict_format_structure(self, tmp_path):
|
||||||
|
dumper = _make_test_dumper(tmp_path)
|
||||||
|
tensor = torch.randn(4, 4)
|
||||||
|
dumper.dump("fmt_test", tensor, custom_key="hello")
|
||||||
|
|
||||||
|
path = _find_dump_file(str(tmp_path), rank=0, name="fmt_test")
|
||||||
|
raw = _load_dump(path)
|
||||||
|
|
||||||
|
assert isinstance(raw, dict)
|
||||||
|
assert set(raw.keys()) == {"value", "meta"}
|
||||||
|
assert torch.equal(raw["value"], tensor)
|
||||||
|
|
||||||
|
meta = raw["meta"]
|
||||||
|
assert meta["name"] == "fmt_test"
|
||||||
|
assert meta["custom_key"] == "hello"
|
||||||
|
assert "forward_pass_id" in meta
|
||||||
|
assert "rank" in meta
|
||||||
|
assert "dump_index" in meta
|
||||||
|
|
||||||
|
def test_dict_format_with_context(self, tmp_path):
|
||||||
|
dumper = _make_test_dumper(tmp_path)
|
||||||
|
dumper.set_ctx(ctx_val=42)
|
||||||
|
tensor = torch.randn(2, 2)
|
||||||
|
dumper.dump("ctx_fmt", tensor)
|
||||||
|
|
||||||
|
path = _find_dump_file(str(tmp_path), rank=0, name="ctx_fmt")
|
||||||
|
raw = _load_dump(path)
|
||||||
|
|
||||||
|
assert raw["meta"]["ctx_val"] == 42
|
||||||
|
assert torch.equal(raw["value"], tensor)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_test_dumper(tmp_path: Path, **overrides) -> _Dumper:
|
||||||
|
"""Create a _Dumper for CPU testing without HTTP server or distributed."""
|
||||||
|
defaults: dict = dict(
|
||||||
|
enable=True,
|
||||||
|
base_dir=tmp_path,
|
||||||
|
partial_name="test",
|
||||||
|
enable_http_server=False,
|
||||||
|
)
|
||||||
|
d = _Dumper(**{**defaults, **overrides})
|
||||||
|
d.on_forward_pass_start()
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def _get_filenames(tmpdir):
|
def _get_filenames(tmpdir):
|
||||||
return {f.name for f in Path(tmpdir).glob("sglang_dump_*/*.pt")}
|
return {f.name for f in Path(tmpdir).glob("sglang_dump_*/*.pt")}
|
||||||
|
|
||||||
@@ -243,6 +296,11 @@ def _assert_files(filenames, *, exist=(), not_exist=()):
|
|||||||
), f"{p} should not exist in {filenames}"
|
), f"{p} should not exist in {filenames}"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_dump(path: Path) -> dict:
|
||||||
|
"""Load a dump file and return the raw dict (with 'value' and 'meta' keys)."""
|
||||||
|
return torch.load(path, map_location="cpu", weights_only=False)
|
||||||
|
|
||||||
|
|
||||||
def _find_dump_file(tmpdir, *, rank: int = 0, name: str) -> Path:
|
def _find_dump_file(tmpdir, *, rank: int = 0, name: str) -> Path:
|
||||||
matches = [
|
matches = [
|
||||||
f
|
f
|
||||||
|
|||||||
Reference in New Issue
Block a user