Add aligner entrypoint and bundle handler in dump comparator (#19375)

This commit is contained in:
fzyzcjy
2026-02-26 10:03:22 +08:00
committed by GitHub
parent 2ad475b4ed
commit e8dd14519d
12 changed files with 926 additions and 195 deletions

View File

@@ -0,0 +1,105 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
import torch
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
AlignerPerStepPlan,
AlignerPerStepSubPlan,
AlignerPlan,
)
from sglang.srt.debug_utils.comparator.aligner.reorderer.executor import (
execute_reorderer_plan,
)
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
execute_unsharder_plan,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
from sglang.srt.debug_utils.comparator.utils import Pair
@dataclass(frozen=True)
class AlignerResult:
tensors: Optional[Pair[torch.Tensor]]
failed_side_xy: Optional[str] # "x" or "y"; None if success
def execute_aligner_plan(
*,
tensors_pair: Pair[list[torch.Tensor]],
plan: AlignerPlan,
) -> AlignerResult:
"""Execute unified unshard/reorder per side, then combine."""
step_tensors_x: dict[int, torch.Tensor] = _execute_step_plans(
tensors=tensors_pair.x, step_plans=plan.per_step_plans.x
)
step_tensors_y: dict[int, torch.Tensor] = _execute_step_plans(
tensors=tensors_pair.y, step_plans=plan.per_step_plans.y
)
if not step_tensors_x or not step_tensors_y:
failed_side_xy: str = "x" if not step_tensors_x else "y"
return AlignerResult(tensors=None, failed_side_xy=failed_side_xy)
assert len(step_tensors_x) == 1 and len(step_tensors_y) == 1
combined = Pair(
x=list(step_tensors_x.values())[0],
y=list(step_tensors_y.values())[0],
)
return AlignerResult(tensors=combined, failed_side_xy=None)
def _execute_step_plans(
tensors: list[torch.Tensor],
step_plans: list[AlignerPerStepPlan],
) -> dict[int, torch.Tensor]:
result: dict[int, torch.Tensor] = {}
for step_plan in step_plans:
step_tensors: list[torch.Tensor] = [
tensors[i] for i in step_plan.input_object_indices
]
tensor: Optional[torch.Tensor] = execute_sub_plans(
tensors=step_tensors, plans=step_plan.sub_plans
)
if tensor is not None:
result[step_plan.step] = tensor
return result
def execute_sub_plans(
tensors: list[torch.Tensor],
plans: list[AlignerPerStepSubPlan],
) -> Optional[torch.Tensor]:
if not tensors:
return None
if not plans:
if len(tensors) != 1:
return None
return tensors[0]
current = tensors
for plan in plans:
current = execute_sub_plan(tensors=current, plan=plan)
assert len(current) == 1
return current[0]
def execute_sub_plan(
tensors: list[torch.Tensor],
plan: AlignerPerStepSubPlan,
) -> list[torch.Tensor]:
if isinstance(plan, UnsharderPlan):
return execute_unsharder_plan(plan, tensors)
elif isinstance(plan, ReordererPlan):
return execute_reorderer_plan(plan, tensors)
else:
raise NotImplementedError(f"Unknown {plan=}")

View File

@@ -0,0 +1,75 @@
from __future__ import annotations
from typing import Any
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
AlignerPerStepPlan,
AlignerPerStepSubPlan,
AlignerPlan,
)
from sglang.srt.debug_utils.comparator.aligner.reorderer.planner import (
compute_reorderer_plans,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
normalize_parallel_info,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.planner import (
compute_unsharder_plan,
)
from sglang.srt.debug_utils.comparator.dims import parse_dims
from sglang.srt.debug_utils.comparator.utils import Pair
def compute_aligner_plan(
*,
metas_pair: Pair[list[dict[str, Any]]],
) -> AlignerPlan:
return AlignerPlan(
per_step_plans=metas_pair.map(
lambda metas: _compute_per_step_plans(metas=metas)
),
)
def _compute_per_step_plans(metas: list[dict[str, Any]]) -> list[AlignerPerStepPlan]:
step_to_input_indices: dict[int, list[int]] = {}
for i, meta in enumerate(metas):
step: int = int(meta["step"])
step_to_input_indices.setdefault(step, []).append(i)
result: list[AlignerPerStepPlan] = []
for step in sorted(step_to_input_indices):
input_indices: list[int] = step_to_input_indices[step]
step_metas: list[dict[str, Any]] = [metas[idx] for idx in input_indices]
plans: list[AlignerPerStepSubPlan] = compute_per_step_sub_plans(
metas=step_metas
)
result.append(
AlignerPerStepPlan(
step=step, input_object_indices=input_indices, sub_plans=plans
)
)
return result
def compute_per_step_sub_plans(
metas: list[dict[str, Any]],
) -> list[AlignerPerStepSubPlan]:
if not metas or len(metas) == 1:
return []
dims_str = metas[0].get("dims")
if dims_str is None:
return []
dim_specs = parse_dims(dims_str)
parallel_infos = [normalize_parallel_info(meta) for meta in metas]
unsharder_plans = compute_unsharder_plan(
dim_specs=dim_specs, parallel_infos=parallel_infos
)
reorderer_plans = compute_reorderer_plans(
dim_specs=dim_specs, parallel_infos=parallel_infos
)
return [*unsharder_plans, *reorderer_plans]

View File

@@ -0,0 +1,22 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Union
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
from sglang.srt.debug_utils.comparator.utils import Pair
AlignerPerStepSubPlan = Union[UnsharderPlan, ReordererPlan]
@dataclass(frozen=True)
class AlignerPerStepPlan:
step: int
input_object_indices: list[int]
sub_plans: list[AlignerPerStepSubPlan]
@dataclass(frozen=True)
class AlignerPlan:
per_step_plans: Pair[list[AlignerPerStepPlan]]

View File

@@ -0,0 +1,105 @@
"""Compare two tensor bundles."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Union
import torch
from sglang.srt.debug_utils.comparator.aligner.entrypoint.executor import (
AlignerResult,
execute_aligner_plan,
)
from sglang.srt.debug_utils.comparator.aligner.entrypoint.planner import (
compute_aligner_plan,
)
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import AlignerPlan
from sglang.srt.debug_utils.comparator.output_types import (
ComparisonRecord,
SkipRecord,
)
from sglang.srt.debug_utils.comparator.tensor_comparator.comparator import (
compare_tensor_pair,
)
from sglang.srt.debug_utils.comparator.utils import Pair
from sglang.srt.debug_utils.comparator.warning_sink import warning_sink
from sglang.srt.debug_utils.dump_loader import ValueWithMeta
_FAILED_SIDE_MAP: dict[str, str] = {"x": "baseline", "y": "target"}
def compare_bundle_pair(
*,
name: str,
filenames_pair: Pair[list[str]],
baseline_path: Path,
target_path: Path,
diff_threshold: float,
) -> Union[ComparisonRecord, SkipRecord]:
with warning_sink.context() as collected_warnings:
result = _compare_bundle_pair_raw(
name=name,
filenames_pair=filenames_pair,
baseline_path=baseline_path,
target_path=target_path,
diff_threshold=diff_threshold,
)
return result.model_copy(update={"warnings": collected_warnings})
def _compare_bundle_pair_raw(
*,
name: str,
filenames_pair: Pair[list[str]],
baseline_path: Path,
target_path: Path,
diff_threshold: float,
) -> Union[ComparisonRecord, SkipRecord]:
# 1. Load (tensor + meta, ungrouped)
valid_pair: Pair[list[ValueWithMeta]] = Pair(
x=_load_valid_tensors(filenames=filenames_pair.x, base_path=baseline_path),
y=_load_valid_tensors(filenames=filenames_pair.y, base_path=target_path),
)
if not valid_pair.x or not valid_pair.y:
reason = "baseline_load_failed" if not valid_pair.x else "target_load_failed"
return SkipRecord(name=name, reason=reason)
# 2. Plan (meta only, no tensor)
metas_pair: Pair[list[dict[str, Any]]] = valid_pair.map(
lambda items: [it.meta for it in items]
)
plan: AlignerPlan = compute_aligner_plan(metas_pair=metas_pair)
# 3. Execute (tensor + plan only, no meta)
tensors_pair: Pair[list[torch.Tensor]] = valid_pair.map(
lambda items: [it.value for it in items]
)
aligner_result: AlignerResult = execute_aligner_plan(
tensors_pair=tensors_pair, plan=plan
)
if aligner_result.tensors is None:
assert aligner_result.failed_side_xy is not None
side_name: str = _FAILED_SIDE_MAP[aligner_result.failed_side_xy]
reason = f"{side_name}_load_failed"
return SkipRecord(name=name, reason=reason)
# 4. Compare
info = compare_tensor_pair(
x_baseline=aligner_result.tensors.x,
x_target=aligner_result.tensors.y,
name=name,
diff_threshold=diff_threshold,
)
return ComparisonRecord(**info.model_dump())
def _load_valid_tensors(filenames: list[str], base_path: Path) -> list[ValueWithMeta]:
return [
x
for f in filenames
if isinstance((x := ValueWithMeta.load(base_path / f)).value, torch.Tensor)
]

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
import dataclasses
from dataclasses import dataclass
from typing import Any
import polars as pl
from sglang.srt.debug_utils.comparator.utils import Pair
from sglang.srt.debug_utils.dump_loader import filter_rows
@dataclass(frozen=True)
class TensorFileInfo:
filename: str
name: str
step: int
TensorBundleInfo = list[TensorFileInfo]
def match_bundles(
*,
dfs: Pair[pl.DataFrame],
skip_keys: set[str],
) -> list[Pair[TensorBundleInfo]]:
match_key_cols: list[str] = [c for c in dfs.y.columns if c not in skip_keys]
unique_keys: pl.DataFrame = dfs.y.select(match_key_cols).unique(maintain_order=True)
results: list[Pair[TensorBundleInfo]] = []
for key_values in unique_keys.iter_rows(named=True):
result = dfs.map(
lambda df: _rows_to_tensor_infos(filter_rows(df, conditions=key_values))
)
results.append(result)
return results
def _rows_to_tensor_infos(rows: list[dict[str, Any]]) -> list[TensorFileInfo]:
tensor_info_fields: set[str] = {f.name for f in dataclasses.fields(TensorFileInfo)}
return [
TensorFileInfo(**{k: v for k, v in row.items() if k in tensor_info_fields})
for row in rows
]

View File

@@ -1,17 +1,26 @@
from __future__ import annotations
import argparse
from pathlib import Path
from typing import Iterator, Union
import polars as pl
from sglang.srt.debug_utils.comparator.bundle_comparator import compare_bundle_pair
from sglang.srt.debug_utils.comparator.bundle_matcher import (
TensorBundleInfo,
match_bundles,
)
from sglang.srt.debug_utils.comparator.output_types import (
ComparisonRecord,
ConfigRecord,
SkipRecord,
SummaryRecord,
print_record,
)
from sglang.srt.debug_utils.comparator.pipeline import process_tensor_group
from sglang.srt.debug_utils.dump_loader import filter_rows, read_meta
_NON_KEY_COLS = {"dump_index", "filename"}
from sglang.srt.debug_utils.comparator.utils import Pair
from sglang.srt.debug_utils.comparator.warning_sink import warning_sink
from sglang.srt.debug_utils.dump_loader import read_meta
def main() -> None:
@@ -20,6 +29,32 @@ def main() -> None:
def run(args: argparse.Namespace) -> None:
print_record(
ConfigRecord.from_args(args),
output_format=args.output_format,
)
warning_sink.set_output_format(args.output_format)
dfs: Pair[pl.DataFrame] = _read_df(args)
bundle_info_pairs: list[Pair[TensorBundleInfo]] = match_bundles(
dfs=dfs,
skip_keys=_compute_skip_keys(args),
)
comparison_records = _compare_bundle_pairs(
bundle_info_pairs=bundle_info_pairs,
baseline_path=Path(args.baseline_path),
target_path=Path(args.target_path),
diff_threshold=args.diff_threshold,
)
_consume_comparison_records(
comparison_records=comparison_records, output_format=args.output_format
)
def _read_df(args: argparse.Namespace) -> Pair[pl.DataFrame]:
df_baseline = read_meta(args.baseline_path)
df_target = read_meta(args.target_path)
@@ -30,37 +65,54 @@ def run(args: argparse.Namespace) -> None:
df_target = df_target.filter(pl.col("filename").str.contains(args.filter))
assert all(c in df_target.columns for c in ["rank", "step", "dump_index", "name"])
print_record(
ConfigRecord.from_args(args),
output_format=args.output_format,
)
return Pair(x=df_baseline, y=df_target)
counts: dict[str, int] = {"passed": 0, "failed": 0, "skipped": 0}
grouping: str = args.grouping
non_key_cols = _NON_KEY_COLS | ({"rank"} if grouping == "logical" else set())
key_cols = [c for c in df_target.columns if c not in non_key_cols]
tensor_group_keys = df_target.unique(subset=key_cols)
def _compute_skip_keys(args: argparse.Namespace) -> set[str]:
skip_keys: set[str] = {"dump_index", "filename"}
if args.grouping == "logical":
skip_keys |= {"rank"}
return skip_keys
for tensor_group_key in tensor_group_keys.iter_rows(named=True):
conditions = {k: tensor_group_key[k] for k in key_cols}
baseline_rows = filter_rows(df_baseline, conditions=conditions)
target_rows = filter_rows(df_target, conditions=conditions)
record = process_tensor_group(
name=tensor_group_key["name"],
baseline_filenames=[r["filename"] for r in baseline_rows],
target_filenames=[r["filename"] for r in target_rows],
baseline_path=Path(args.baseline_path),
target_path=Path(args.target_path),
diff_threshold=args.diff_threshold,
def _compare_bundle_pairs(
*,
bundle_info_pairs: list[Pair[TensorBundleInfo]],
baseline_path: Path,
target_path: Path,
diff_threshold: float,
) -> Iterator[Union[ComparisonRecord, SkipRecord]]:
for bundle_info_pair in bundle_info_pairs:
if not bundle_info_pair.y:
continue
name: str = bundle_info_pair.y[0].name
filenames_pair: Pair[list[str]] = bundle_info_pair.map(
lambda infos: [info.filename for info in infos]
)
yield compare_bundle_pair(
name=name,
filenames_pair=filenames_pair,
baseline_path=baseline_path,
target_path=target_path,
diff_threshold=diff_threshold,
)
def _consume_comparison_records(
*,
comparison_records: Iterator[Union[ComparisonRecord, SkipRecord]],
output_format: str,
) -> None:
counts: dict[str, int] = {"passed": 0, "failed": 0, "skipped": 0}
for record in comparison_records:
counts[record.category] += 1
print_record(record, output_format=args.output_format)
print_record(record, output_format=output_format)
print_record(
SummaryRecord(total=sum(counts.values()), **counts),
output_format=args.output_format,
output_format=output_format,
)

View File

@@ -1,169 +0,0 @@
from pathlib import Path
from typing import Any, Optional, Union
import torch
from sglang.srt.debug_utils.comparator.aligner.reorderer.executor import (
execute_reorderer_plan,
)
from sglang.srt.debug_utils.comparator.aligner.reorderer.planner import (
compute_reorderer_plans,
)
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
execute_unsharder_plan,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
normalize_parallel_info,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.planner import (
compute_unsharder_plan,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
from sglang.srt.debug_utils.comparator.dims import parse_dims
from sglang.srt.debug_utils.comparator.output_types import (
AnyWarning,
ComparisonRecord,
SkipRecord,
)
from sglang.srt.debug_utils.comparator.tensor_comparator.comparator import (
compare_tensor_pair,
)
from sglang.srt.debug_utils.comparator.warning_sink import warning_sink
from sglang.srt.debug_utils.dump_loader import ValueWithMeta
Plan = Union[UnsharderPlan, ReordererPlan]
def process_tensor_group(
*,
name: str,
baseline_filenames: list[str],
target_filenames: list[str],
baseline_path: Path,
target_path: Path,
diff_threshold: float,
) -> ComparisonRecord | SkipRecord:
with warning_sink.context() as collected_warnings:
return _process_tensor_group_raw(
name=name,
baseline_filenames=baseline_filenames,
target_filenames=target_filenames,
baseline_path=baseline_path,
target_path=target_path,
diff_threshold=diff_threshold,
collected_warnings=collected_warnings,
)
def _process_tensor_group_raw(
*,
name: str,
baseline_filenames: list[str],
target_filenames: list[str],
baseline_path: Path,
target_path: Path,
diff_threshold: float,
collected_warnings: list[AnyWarning],
) -> ComparisonRecord | SkipRecord:
b_tensors = _load_tensors(baseline_filenames, baseline_path)
t_tensors = _load_tensors(target_filenames, target_path)
b_plans, t_plans = _compute_plans(
baseline_metas=[item.meta for item in b_tensors],
target_metas=[item.meta for item in t_tensors],
)
b_extracted = _extract_tensors(b_tensors)
t_extracted = _extract_tensors(t_tensors)
del b_tensors, t_tensors
b_tensor = _execute_plans(b_extracted, b_plans)
t_tensor = _execute_plans(t_extracted, t_plans)
if b_tensor is None or t_tensor is None:
reason = "baseline_load_failed" if b_tensor is None else "target_load_failed"
return SkipRecord(name=name, reason=reason, warnings=collected_warnings)
info = compare_tensor_pair(
x_baseline=b_tensor,
x_target=t_tensor,
name=name,
diff_threshold=diff_threshold,
)
return ComparisonRecord(**info.model_dump(), warnings=collected_warnings)
def _load_tensors(filenames: list[str], base_path: Path) -> list[ValueWithMeta]:
return [ValueWithMeta.load(base_path / f) for f in filenames]
def _compute_plans(
*,
baseline_metas: list[dict[str, Any]],
target_metas: list[dict[str, Any]],
) -> tuple[list[Plan], list[Plan]]:
"""This function deliberately takes metadata, since plan computation must never inspect actual tensor data."""
return (
_compute_plans_for_group(baseline_metas),
_compute_plans_for_group(target_metas),
)
def _compute_plans_for_group(metas: list[dict[str, Any]]) -> list[Plan]:
if not metas or len(metas) == 1:
return []
dims_str = metas[0].get("dims")
if dims_str is None:
return []
dim_specs = parse_dims(dims_str)
parallel_infos = [normalize_parallel_info(meta) for meta in metas]
unsharder_plans = compute_unsharder_plan(
dim_specs=dim_specs, parallel_infos=parallel_infos
)
reorderer_plans = compute_reorderer_plans(
dim_specs=dim_specs, parallel_infos=parallel_infos
)
return [*unsharder_plans, *reorderer_plans]
def _extract_tensors(
loaded: list[ValueWithMeta],
) -> Optional[list[torch.Tensor]]:
return [value for item in loaded if isinstance(value := item.value, torch.Tensor)]
def _execute_plans(
tensors: list[torch.Tensor],
plans: list[Plan],
) -> Optional[torch.Tensor]:
if not tensors:
return None
if not plans:
if len(tensors) != 1:
return None
return tensors[0]
current = tensors
for plan in plans:
current = _execute_plan(current, plan)
assert len(current) == 1
return current[0]
def _execute_plan(
tensors: list[torch.Tensor],
plan: Plan,
) -> list[torch.Tensor]:
if isinstance(plan, UnsharderPlan):
return execute_unsharder_plan(plan, tensors)
elif isinstance(plan, ReordererPlan):
return execute_reorderer_plan(plan, tensors)
else:
raise NotImplementedError(f"Unknown {plan=}")