Support concat mode in token aligner in dump comparator (#19599)
This commit is contained in:
@@ -17,7 +17,10 @@ from sglang.srt.debug_utils.comparator.aligner.reorderer.executor import (
|
|||||||
execute_reorderer_plan,
|
execute_reorderer_plan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.executor import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps import (
|
||||||
|
execute_token_aligner_concat_steps,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.executor import (
|
||||||
execute_token_aligner,
|
execute_token_aligner,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
|
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
|
||||||
@@ -64,10 +67,15 @@ def execute_aligner_plan(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Cross-side: token alignment (or direct extraction for single-step)
|
# Cross-side: token alignment (or direct extraction for single-step)
|
||||||
if plan.token_aligner_plan is not None:
|
step_pair: Pair[dict[int, torch.Tensor]] = Pair(x=step_tensors_x, y=step_tensors_y)
|
||||||
combined: Pair[torch.Tensor] = execute_token_aligner(
|
combined: Pair[torch.Tensor]
|
||||||
|
if plan.token_aligner_mode == "concat_steps":
|
||||||
|
combined = execute_token_aligner_concat_steps(tensor_of_step_pair=step_pair)
|
||||||
|
elif plan.token_aligner_mode == "smart":
|
||||||
|
assert plan.token_aligner_plan is not None
|
||||||
|
combined = execute_token_aligner(
|
||||||
plan=plan.token_aligner_plan,
|
plan=plan.token_aligner_plan,
|
||||||
tensor_of_step_pair=Pair(x=step_tensors_x, y=step_tensors_y),
|
tensor_of_step_pair=step_pair,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert len(step_tensors_x) == 1 and len(step_tensors_y) == 1
|
assert len(step_tensors_x) == 1 and len(step_tensors_y) == 1
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
|||||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.planner import (
|
from sglang.srt.debug_utils.comparator.aligner.reorderer.planner import (
|
||||||
compute_reorderer_plans,
|
compute_reorderer_plans,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
|
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
|
||||||
@@ -34,6 +34,7 @@ from sglang.srt.debug_utils.comparator.utils import Pair
|
|||||||
def compute_aligner_plan(
|
def compute_aligner_plan(
|
||||||
*,
|
*,
|
||||||
metas_pair: Pair[list[dict[str, Any]]],
|
metas_pair: Pair[list[dict[str, Any]]],
|
||||||
|
token_aligner_mode: Optional[str],
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
||||||
x=None, y=None
|
x=None, y=None
|
||||||
@@ -57,6 +58,7 @@ def compute_aligner_plan(
|
|||||||
thd_seq_lens_by_step=thd_seq_lens_by_step_pair.y,
|
thd_seq_lens_by_step=thd_seq_lens_by_step_pair.y,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
token_aligner_mode=token_aligner_mode,
|
||||||
token_aligner_plan=token_aligner_plan,
|
token_aligner_plan=token_aligner_plan,
|
||||||
axis_aligner_plan=axis_aligner_plan,
|
axis_aligner_plan=axis_aligner_plan,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pydantic import Discriminator
|
|||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import AxisAlignerPlan
|
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import AxisAlignerPlan
|
||||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
|
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
|
||||||
@@ -26,5 +26,6 @@ class AlignerPerStepPlan(_FrozenBase):
|
|||||||
|
|
||||||
class AlignerPlan(_FrozenBase):
|
class AlignerPlan(_FrozenBase):
|
||||||
per_step_plans: Pair[list[AlignerPerStepPlan]]
|
per_step_plans: Pair[list[AlignerPerStepPlan]]
|
||||||
|
token_aligner_mode: Optional[str] = None # "concat_steps" | "smart" | None
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan] = None
|
token_aligner_plan: Optional[TokenAlignerPlan] = None
|
||||||
axis_aligner_plan: Optional[AxisAlignerPlan] = None
|
axis_aligner_plan: Optional[AxisAlignerPlan] = None
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps.executor import (
|
||||||
|
execute_token_aligner_concat_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"execute_token_aligner_concat_steps",
|
||||||
|
]
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.debug_utils.comparator.dims import (
|
||||||
|
SEQ_DIM_NAME,
|
||||||
|
TOKEN_DIM_NAME,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.utils import Pair
|
||||||
|
|
||||||
|
_UNNAMED_TOKEN_DIM_FALLBACK: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
def execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair: Pair[dict[int, torch.Tensor]],
|
||||||
|
) -> Pair[torch.Tensor]:
|
||||||
|
"""Concat all steps in order, then truncate to min(total_x, total_y) tokens."""
|
||||||
|
some_tensor: torch.Tensor = next(iter(tensor_of_step_pair.x.values()))
|
||||||
|
token_dim: int = _resolve_token_dim(some_tensor)
|
||||||
|
|
||||||
|
concatenated: Pair[torch.Tensor] = tensor_of_step_pair.map(
|
||||||
|
lambda d: _concat_steps(d, dim=token_dim)
|
||||||
|
)
|
||||||
|
common: int = min(concatenated.x.shape[token_dim], concatenated.y.shape[token_dim])
|
||||||
|
return concatenated.map(lambda t: t.narrow(dim=token_dim, start=0, length=common))
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_token_dim(tensor: torch.Tensor) -> int:
|
||||||
|
"""Find the token/seq dim index. Falls back to dim 0 for unnamed tensors or
|
||||||
|
tensors without a recognised token/seq dim."""
|
||||||
|
if tensor.names[0] is None:
|
||||||
|
return _UNNAMED_TOKEN_DIM_FALLBACK
|
||||||
|
|
||||||
|
names: tuple[Optional[str], ...] = tensor.names
|
||||||
|
for candidate in (TOKEN_DIM_NAME, SEQ_DIM_NAME):
|
||||||
|
if candidate in names:
|
||||||
|
return list(names).index(candidate)
|
||||||
|
|
||||||
|
return _UNNAMED_TOKEN_DIM_FALLBACK
|
||||||
|
|
||||||
|
|
||||||
|
def _concat_steps(tensor_of_step: dict[int, torch.Tensor], *, dim: int) -> torch.Tensor:
|
||||||
|
return torch.cat([tensor_of_step[s] for s in sorted(tensor_of_step)], dim=dim)
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import polars as pl
|
||||||
|
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader import (
|
||||||
|
_detect_plugin,
|
||||||
|
_load_and_align_aux_tensor,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
|
||||||
|
_AuxFrameworkPlugin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_thd_seq_lens_only(
|
||||||
|
dump_path: Path, df: pl.DataFrame
|
||||||
|
) -> Optional[dict[int, list[int]]]:
|
||||||
|
plugin: Optional[_AuxFrameworkPlugin] = _detect_plugin(df, dump_path=dump_path)
|
||||||
|
if plugin is None or not plugin.cp_sharded_names:
|
||||||
|
return None
|
||||||
|
|
||||||
|
non_cp_tensor_names: set[str] = (
|
||||||
|
set(df["name"].unique().to_list()) & plugin.tensor_names
|
||||||
|
) - plugin.cp_sharded_names
|
||||||
|
steps: list[int] = sorted(df["step"].unique().to_list())
|
||||||
|
|
||||||
|
result: dict[int, list[int]] = {}
|
||||||
|
for step in steps:
|
||||||
|
step_data: dict[str, object] = {}
|
||||||
|
for name in non_cp_tensor_names:
|
||||||
|
tensor = _load_and_align_aux_tensor(
|
||||||
|
name=name, step=step, df=df, dump_path=dump_path, plugin=plugin
|
||||||
|
)
|
||||||
|
if tensor is not None:
|
||||||
|
step_data[name] = tensor
|
||||||
|
|
||||||
|
seq_lens: Optional[list[int]] = plugin.extract_global_seq_lens(step_data)
|
||||||
|
if seq_lens is not None:
|
||||||
|
result[step] = seq_lens
|
||||||
|
|
||||||
|
return result or None
|
||||||
@@ -3,21 +3,24 @@ from __future__ import annotations
|
|||||||
import argparse
|
import argparse
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps.thd_seq_lens_loader import (
|
||||||
|
load_thd_seq_lens_only,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader import (
|
||||||
has_aux_tensors,
|
has_aux_tensors,
|
||||||
load_and_normalize_aux,
|
load_and_normalize_aux,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.planner import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.planner import (
|
||||||
compute_token_aligner_plan,
|
compute_token_aligner_plan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.seq_info_builder import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.seq_info_builder import (
|
||||||
build_seqs_info,
|
build_seqs_info,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerGlobalAux,
|
TokenAlignerGlobalAux,
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenAlignerSeqsInfo,
|
TokenAlignerSeqsInfo,
|
||||||
@@ -26,11 +29,17 @@ from sglang.srt.debug_utils.comparator.output_types import GeneralWarning
|
|||||||
from sglang.srt.debug_utils.comparator.utils import 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.comparator.warning_sink import warning_sink
|
||||||
|
|
||||||
|
_NONE_THD: Pair[Optional[dict[int, list[int]]]] = Pair(x=None, y=None)
|
||||||
|
|
||||||
|
|
||||||
|
TokenAlignerMode = Literal["concat_steps", "smart"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class TokenAlignerResult:
|
class TokenAlignerResult:
|
||||||
"""Result of token aligner computation, bundling the plan with THD metadata."""
|
"""Result of token aligner computation, bundling mode + plan with THD metadata."""
|
||||||
|
|
||||||
|
mode: Optional[TokenAlignerMode]
|
||||||
plan: Optional[TokenAlignerPlan]
|
plan: Optional[TokenAlignerPlan]
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]]
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]]
|
||||||
|
|
||||||
@@ -39,7 +48,23 @@ def compute_maybe_token_aligner_result(
|
|||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
dfs: Pair[pl.DataFrame],
|
dfs: Pair[pl.DataFrame],
|
||||||
) -> TokenAlignerResult:
|
) -> TokenAlignerResult:
|
||||||
if args.grouping == "logical":
|
if args.grouping != "logical":
|
||||||
|
return TokenAlignerResult(
|
||||||
|
mode=None, plan=None, thd_seq_lens_by_step_pair=_NONE_THD
|
||||||
|
)
|
||||||
|
|
||||||
|
token_aligner_mode: TokenAlignerMode = getattr(
|
||||||
|
args, "token_aligner", "concat_steps"
|
||||||
|
)
|
||||||
|
|
||||||
|
if token_aligner_mode == "concat_steps":
|
||||||
|
thd_pair: Pair[Optional[dict[int, list[int]]]] = _load_thd_seq_lens_pair(
|
||||||
|
args=args, dfs=dfs
|
||||||
|
)
|
||||||
|
return TokenAlignerResult(
|
||||||
|
mode="concat_steps", plan=None, thd_seq_lens_by_step_pair=thd_pair
|
||||||
|
)
|
||||||
|
elif token_aligner_mode == "smart":
|
||||||
if not (has_aux_tensors(dfs.x) and has_aux_tensors(dfs.y)):
|
if not (has_aux_tensors(dfs.x) and has_aux_tensors(dfs.y)):
|
||||||
warning_sink.add(
|
warning_sink.add(
|
||||||
GeneralWarning(
|
GeneralWarning(
|
||||||
@@ -48,15 +73,15 @@ def compute_maybe_token_aligner_result(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return TokenAlignerResult(
|
return TokenAlignerResult(
|
||||||
plan=None, thd_seq_lens_by_step_pair=Pair(x=None, y=None)
|
mode=None, plan=None, thd_seq_lens_by_step_pair=_NONE_THD
|
||||||
)
|
)
|
||||||
|
|
||||||
return _build_token_aligner_result(args=args, dfs=dfs)
|
return _build_smart_result(args=args, dfs=dfs)
|
||||||
|
else:
|
||||||
return TokenAlignerResult(plan=None, thd_seq_lens_by_step_pair=Pair(x=None, y=None))
|
raise NotImplementedError(f"Unknown {token_aligner_mode=}")
|
||||||
|
|
||||||
|
|
||||||
def _build_token_aligner_result(
|
def _build_smart_result(
|
||||||
*,
|
*,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
dfs: Pair[pl.DataFrame],
|
dfs: Pair[pl.DataFrame],
|
||||||
@@ -84,7 +109,9 @@ def _build_token_aligner_result(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return TokenAlignerResult(
|
return TokenAlignerResult(
|
||||||
plan=None, thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair
|
mode=None,
|
||||||
|
plan=None,
|
||||||
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
)
|
)
|
||||||
|
|
||||||
global_aux: Pair[TokenAlignerGlobalAux] = Pair(
|
global_aux: Pair[TokenAlignerGlobalAux] = Pair(
|
||||||
@@ -98,5 +125,20 @@ def _build_token_aligner_result(
|
|||||||
seqs_info_pair=seqs_info
|
seqs_info_pair=seqs_info
|
||||||
)
|
)
|
||||||
return TokenAlignerResult(
|
return TokenAlignerResult(
|
||||||
plan=plan, thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair
|
mode="smart",
|
||||||
|
plan=plan,
|
||||||
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_thd_seq_lens_pair(
|
||||||
|
*,
|
||||||
|
args: argparse.Namespace,
|
||||||
|
dfs: Pair[pl.DataFrame],
|
||||||
|
) -> Pair[Optional[dict[int, list[int]]]]:
|
||||||
|
"""Load only thd_seq_lens for each side (lightweight, no full aux loading)."""
|
||||||
|
dump_paths: Pair[Path] = Pair(x=Path(args.baseline_path), y=Path(args.target_path))
|
||||||
|
return Pair(
|
||||||
|
x=load_thd_seq_lens_only(dump_path=dump_paths.x, df=dfs.x),
|
||||||
|
y=load_thd_seq_lens_only(dump_path=dump_paths.y, df=dfs.y),
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-4
@@ -12,12 +12,12 @@ from sglang.srt.debug_utils.comparator.aligner.entrypoint.executor import (
|
|||||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.planner import (
|
from sglang.srt.debug_utils.comparator.aligner.entrypoint.planner import (
|
||||||
compute_per_step_sub_plans,
|
compute_per_step_sub_plans,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_plugins import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
|
||||||
AUX_NAMES,
|
AUX_NAMES,
|
||||||
_AuxFrameworkPlugin,
|
_AuxFrameworkPlugin,
|
||||||
_plugins,
|
_plugins,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerGlobalAux,
|
TokenAlignerGlobalAux,
|
||||||
TokenAlignerStepAux,
|
TokenAlignerStepAux,
|
||||||
)
|
)
|
||||||
@@ -36,7 +36,11 @@ from sglang.srt.debug_utils.comparator.warning_sink import warning_sink
|
|||||||
from sglang.srt.debug_utils.dump_loader import ValueWithMeta, filter_rows
|
from sglang.srt.debug_utils.dump_loader import ValueWithMeta, filter_rows
|
||||||
|
|
||||||
# re-export for existing callers
|
# re-export for existing callers
|
||||||
__all__ = ["AUX_NAMES", "has_aux_tensors", "load_and_normalize_aux"]
|
__all__ = [
|
||||||
|
"AUX_NAMES",
|
||||||
|
"has_aux_tensors",
|
||||||
|
"load_and_normalize_aux",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def load_and_normalize_aux(
|
def load_and_normalize_aux(
|
||||||
@@ -124,7 +128,7 @@ def _load_step_data(
|
|||||||
"""
|
"""
|
||||||
result: dict[str, object] = {}
|
result: dict[str, object] = {}
|
||||||
|
|
||||||
# Pass 1: non-tensor values
|
# Pass 0: non-tensor values
|
||||||
for name in non_tensor_names:
|
for name in non_tensor_names:
|
||||||
value = _load_non_tensor_aux(name=name, step=step, df=df, dump_path=dump_path)
|
value = _load_non_tensor_aux(name=name, step=step, df=df, dump_path=dump_path)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
+1
-1
@@ -5,7 +5,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
PositionalSeqId,
|
PositionalSeqId,
|
||||||
SeqId,
|
SeqId,
|
||||||
SGLangSeqId,
|
SGLangSeqId,
|
||||||
+1
-1
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import torch
|
import torch
|
||||||
from einops import rearrange
|
from einops import rearrange
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenLocator,
|
TokenLocator,
|
||||||
)
|
)
|
||||||
+1
-1
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import NamedTuple, Optional
|
from typing import NamedTuple, Optional
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
SeqId,
|
SeqId,
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenAlignerSeqInfo,
|
TokenAlignerSeqInfo,
|
||||||
+1
-1
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
SeqId,
|
SeqId,
|
||||||
TokenAlignerGlobalAux,
|
TokenAlignerGlobalAux,
|
||||||
TokenAlignerSeqInfo,
|
TokenAlignerSeqInfo,
|
||||||
@@ -15,7 +15,7 @@ from sglang.srt.debug_utils.comparator.aligner.entrypoint.planner import (
|
|||||||
compute_aligner_plan,
|
compute_aligner_plan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import AlignerPlan
|
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import AlignerPlan
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.dims import (
|
from sglang.srt.debug_utils.comparator.dims import (
|
||||||
@@ -48,6 +48,7 @@ def compare_bundle_pair(
|
|||||||
filenames_pair: Pair[list[str]],
|
filenames_pair: Pair[list[str]],
|
||||||
baseline_path: Path,
|
baseline_path: Path,
|
||||||
target_path: Path,
|
target_path: Path,
|
||||||
|
token_aligner_mode: Optional[str],
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||||
diff_threshold: float,
|
diff_threshold: float,
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
||||||
@@ -63,6 +64,7 @@ def compare_bundle_pair(
|
|||||||
filenames_pair=filenames_pair,
|
filenames_pair=filenames_pair,
|
||||||
baseline_path=baseline_path,
|
baseline_path=baseline_path,
|
||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
|
token_aligner_mode=token_aligner_mode,
|
||||||
token_aligner_plan=token_aligner_plan,
|
token_aligner_plan=token_aligner_plan,
|
||||||
diff_threshold=diff_threshold,
|
diff_threshold=diff_threshold,
|
||||||
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
@@ -80,6 +82,7 @@ def _compare_bundle_pair_inner(
|
|||||||
filenames_pair: Pair[list[str]],
|
filenames_pair: Pair[list[str]],
|
||||||
baseline_path: Path,
|
baseline_path: Path,
|
||||||
target_path: Path,
|
target_path: Path,
|
||||||
|
token_aligner_mode: Optional[str],
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||||
diff_threshold: float,
|
diff_threshold: float,
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
||||||
@@ -134,6 +137,7 @@ def _compare_bundle_pair_inner(
|
|||||||
return _compare_bundle_pair_tensor_type(
|
return _compare_bundle_pair_tensor_type(
|
||||||
name=name,
|
name=name,
|
||||||
valid_pair=all_pair,
|
valid_pair=all_pair,
|
||||||
|
token_aligner_mode=token_aligner_mode,
|
||||||
token_aligner_plan=token_aligner_plan,
|
token_aligner_plan=token_aligner_plan,
|
||||||
diff_threshold=diff_threshold,
|
diff_threshold=diff_threshold,
|
||||||
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
@@ -146,6 +150,7 @@ def _compare_bundle_pair_tensor_type(
|
|||||||
*,
|
*,
|
||||||
name: str,
|
name: str,
|
||||||
valid_pair: Pair[list[ValueWithMeta]],
|
valid_pair: Pair[list[ValueWithMeta]],
|
||||||
|
token_aligner_mode: Optional[str],
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||||
diff_threshold: float,
|
diff_threshold: float,
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
||||||
@@ -164,6 +169,7 @@ def _compare_bundle_pair_tensor_type(
|
|||||||
)
|
)
|
||||||
plan: AlignerPlan = compute_aligner_plan(
|
plan: AlignerPlan = compute_aligner_plan(
|
||||||
metas_pair=metas_pair,
|
metas_pair=metas_pair,
|
||||||
|
token_aligner_mode=token_aligner_mode,
|
||||||
token_aligner_plan=token_aligner_plan,
|
token_aligner_plan=token_aligner_plan,
|
||||||
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ from typing import Any, Iterator, Optional, Union
|
|||||||
|
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader import (
|
|
||||||
AUX_NAMES,
|
|
||||||
)
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.entrypoint import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.entrypoint import (
|
||||||
TokenAlignerResult,
|
TokenAlignerResult,
|
||||||
compute_maybe_token_aligner_result,
|
compute_maybe_token_aligner_result,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader import (
|
||||||
|
AUX_NAMES,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.bundle_comparator import compare_bundle_pair
|
from sglang.srt.debug_utils.comparator.bundle_comparator import compare_bundle_pair
|
||||||
@@ -69,12 +69,13 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
ta_result: TokenAlignerResult = compute_maybe_token_aligner_result(args, dfs)
|
ta_result: TokenAlignerResult = compute_maybe_token_aligner_result(args, dfs)
|
||||||
|
|
||||||
|
if ta_result.mode == "smart":
|
||||||
dfs = dfs.map(lambda df: df.filter(~pl.col("name").is_in(AUX_NAMES)))
|
dfs = dfs.map(lambda df: df.filter(~pl.col("name").is_in(AUX_NAMES)))
|
||||||
|
|
||||||
bundle_info_pairs: list[Pair[TensorBundleInfo]] = match_bundles(
|
bundle_info_pairs: list[Pair[TensorBundleInfo]] = match_bundles(
|
||||||
dfs=dfs,
|
dfs=dfs,
|
||||||
skip_keys=_compute_skip_keys(
|
skip_keys=_compute_skip_keys(
|
||||||
args, has_token_aligner_plan=ta_result.plan is not None
|
args, has_token_aligner=ta_result.mode is not None
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
bundle_info_pairs=bundle_info_pairs,
|
bundle_info_pairs=bundle_info_pairs,
|
||||||
baseline_path=Path(args.baseline_path),
|
baseline_path=Path(args.baseline_path),
|
||||||
target_path=Path(args.target_path),
|
target_path=Path(args.target_path),
|
||||||
|
token_aligner_mode=ta_result.mode,
|
||||||
token_aligner_plan=ta_result.plan,
|
token_aligner_plan=ta_result.plan,
|
||||||
diff_threshold=args.diff_threshold,
|
diff_threshold=args.diff_threshold,
|
||||||
thd_seq_lens_by_step_pair=ta_result.thd_seq_lens_by_step_pair,
|
thd_seq_lens_by_step_pair=ta_result.thd_seq_lens_by_step_pair,
|
||||||
@@ -145,11 +147,11 @@ def _read_df(args: argparse.Namespace) -> Pair[pl.DataFrame]:
|
|||||||
return Pair(x=df_baseline, y=df_target)
|
return Pair(x=df_baseline, y=df_target)
|
||||||
|
|
||||||
|
|
||||||
def _compute_skip_keys(args, *, has_token_aligner_plan: bool):
|
def _compute_skip_keys(args, *, has_token_aligner: bool) -> set[str]:
|
||||||
skip_keys: set[str] = {"dump_index", "filename"}
|
skip_keys: set[str] = {"dump_index", "filename"}
|
||||||
if args.grouping == "logical":
|
if args.grouping == "logical":
|
||||||
skip_keys |= {"rank", "recompute_status"}
|
skip_keys |= {"rank", "recompute_status"}
|
||||||
if has_token_aligner_plan:
|
if has_token_aligner:
|
||||||
skip_keys |= {"step"}
|
skip_keys |= {"step"}
|
||||||
return skip_keys
|
return skip_keys
|
||||||
|
|
||||||
@@ -159,6 +161,7 @@ def _compare_bundle_pairs(
|
|||||||
bundle_info_pairs: list[Pair[TensorBundleInfo]],
|
bundle_info_pairs: list[Pair[TensorBundleInfo]],
|
||||||
baseline_path: Path,
|
baseline_path: Path,
|
||||||
target_path: Path,
|
target_path: Path,
|
||||||
|
token_aligner_mode: Optional[str],
|
||||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||||
diff_threshold: float,
|
diff_threshold: float,
|
||||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]],
|
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]],
|
||||||
@@ -179,6 +182,7 @@ def _compare_bundle_pairs(
|
|||||||
filenames_pair=filenames_pair,
|
filenames_pair=filenames_pair,
|
||||||
baseline_path=baseline_path,
|
baseline_path=baseline_path,
|
||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
|
token_aligner_mode=token_aligner_mode,
|
||||||
token_aligner_plan=token_aligner_plan,
|
token_aligner_plan=token_aligner_plan,
|
||||||
diff_threshold=diff_threshold,
|
diff_threshold=diff_threshold,
|
||||||
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
|
||||||
@@ -239,6 +243,13 @@ def _parse_args() -> argparse.Namespace:
|
|||||||
default="logical",
|
default="logical",
|
||||||
help="Grouping mode: logical (cross-rank unshard) or raw (rank-by-rank)",
|
help="Grouping mode: logical (cross-rank unshard) or raw (rank-by-rank)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--token-aligner",
|
||||||
|
type=str,
|
||||||
|
choices=["smart", "concat_steps"],
|
||||||
|
default="concat_steps",
|
||||||
|
help="Token aligner mode: concat_steps (BS=1, no aux needed) or smart (BS>1, sequence matching)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--tokenizer",
|
"--tokenizer",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
|||||||
AlignerPerStepPlan,
|
AlignerPerStepPlan,
|
||||||
AlignerPlan,
|
AlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenLocator,
|
TokenLocator,
|
||||||
)
|
)
|
||||||
@@ -234,6 +234,7 @@ class TestExecuteAlignerPlanWithTokenDim:
|
|||||||
x=[self._make_step_plan(step=0, indices=[0])],
|
x=[self._make_step_plan(step=0, indices=[0])],
|
||||||
y=[self._make_step_plan(step=0, indices=[0])],
|
y=[self._make_step_plan(step=0, indices=[0])],
|
||||||
),
|
),
|
||||||
|
token_aligner_mode="smart",
|
||||||
token_aligner_plan=token_plan,
|
token_aligner_plan=token_plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -285,6 +286,7 @@ class TestExecuteAlignerPlanWithTokenDim:
|
|||||||
x=[self._make_step_plan(step=0, indices=[0])],
|
x=[self._make_step_plan(step=0, indices=[0])],
|
||||||
y=[self._make_step_plan(step=0, indices=[0])],
|
y=[self._make_step_plan(step=0, indices=[0])],
|
||||||
),
|
),
|
||||||
|
token_aligner_mode="smart",
|
||||||
token_aligner_plan=token_plan,
|
token_aligner_plan=token_plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ class TestComputeAlignerPlan:
|
|||||||
|
|
||||||
plan: AlignerPlan = compute_aligner_plan(
|
plan: AlignerPlan = compute_aligner_plan(
|
||||||
metas_pair=Pair(x=metas_x, y=metas_y),
|
metas_pair=Pair(x=metas_x, y=metas_y),
|
||||||
|
token_aligner_mode=None,
|
||||||
token_aligner_plan=None,
|
token_aligner_plan=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ class TestComputeAlignerPlan:
|
|||||||
assert plan.token_aligner_plan is None
|
assert plan.token_aligner_plan is None
|
||||||
|
|
||||||
def test_preserves_token_aligner_plan(self) -> None:
|
def test_preserves_token_aligner_plan(self) -> None:
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenLocator,
|
TokenLocator,
|
||||||
)
|
)
|
||||||
@@ -166,10 +167,12 @@ class TestComputeAlignerPlan:
|
|||||||
|
|
||||||
plan: AlignerPlan = compute_aligner_plan(
|
plan: AlignerPlan = compute_aligner_plan(
|
||||||
metas_pair=Pair(x=[_make_meta()], y=[_make_meta()]),
|
metas_pair=Pair(x=[_make_meta()], y=[_make_meta()]),
|
||||||
|
token_aligner_mode="smart",
|
||||||
token_aligner_plan=ta_plan,
|
token_aligner_plan=ta_plan,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert plan.token_aligner_plan is ta_plan
|
assert plan.token_aligner_plan is ta_plan
|
||||||
|
assert plan.token_aligner_mode == "smart"
|
||||||
|
|
||||||
|
|
||||||
class TestComputePerStepSubPlansThd:
|
class TestComputePerStepSubPlansThd:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import polars as pl
|
|||||||
import pytest
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader import (
|
||||||
_detect_plugin,
|
_detect_plugin,
|
||||||
_ensure_dims_in_metas,
|
_ensure_dims_in_metas,
|
||||||
_load_and_align_aux_tensor,
|
_load_and_align_aux_tensor,
|
||||||
_load_non_tensor_aux,
|
_load_non_tensor_aux,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_plugins import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
|
||||||
_MegatronPlugin,
|
_MegatronPlugin,
|
||||||
_SGLangPlugin,
|
_SGLangPlugin,
|
||||||
)
|
)
|
||||||
@@ -214,7 +214,7 @@ class TestLoadNonTensorAux:
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader.warning_sink",
|
"sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader.warning_sink",
|
||||||
sink,
|
sink,
|
||||||
):
|
):
|
||||||
result = _load_non_tensor_aux(
|
result = _load_non_tensor_aux(
|
||||||
@@ -273,7 +273,7 @@ class TestLoadAndAlignAuxTensor:
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader.warning_sink",
|
"sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader.warning_sink",
|
||||||
sink,
|
sink,
|
||||||
):
|
):
|
||||||
result = _load_and_align_aux_tensor(
|
result = _load_and_align_aux_tensor(
|
||||||
@@ -329,7 +329,7 @@ class TestLoadNonTensorAuxDp:
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_loader.warning_sink",
|
"sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_loader.warning_sink",
|
||||||
sink,
|
sink,
|
||||||
):
|
):
|
||||||
result = _load_non_tensor_aux(
|
result = _load_non_tensor_aux(
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.aux_plugins import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
|
||||||
_infer_positions,
|
_infer_positions,
|
||||||
_MegatronPlugin,
|
_MegatronPlugin,
|
||||||
_SGLangPlugin,
|
_SGLangPlugin,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
PositionalSeqId,
|
PositionalSeqId,
|
||||||
SGLangSeqId,
|
SGLangSeqId,
|
||||||
TokenAlignerStepAux,
|
TokenAlignerStepAux,
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps import (
|
||||||
|
execute_token_aligner_concat_steps,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.utils import Pair
|
||||||
|
from sglang.test.ci.ci_register import register_cpu_ci
|
||||||
|
|
||||||
|
register_cpu_ci(est_time=15, suite="default", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExecuteConcat:
|
||||||
|
def test_single_step_equal_length(self) -> None:
|
||||||
|
x = torch.tensor([1.0, 2.0, 3.0])
|
||||||
|
y = torch.tensor([4.0, 5.0, 6.0])
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(x={0: x}, y={0: y}),
|
||||||
|
)
|
||||||
|
assert torch.equal(result.x, x)
|
||||||
|
assert torch.equal(result.y, y)
|
||||||
|
|
||||||
|
def test_truncates_to_min(self) -> None:
|
||||||
|
x = torch.tensor([1.0, 2.0, 3.0, 4.0])
|
||||||
|
y = torch.tensor([5.0, 6.0])
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(x={0: x}, y={0: y}),
|
||||||
|
)
|
||||||
|
assert torch.equal(result.x, torch.tensor([1.0, 2.0]))
|
||||||
|
assert torch.equal(result.y, y)
|
||||||
|
|
||||||
|
def test_multi_step_sorted_concat(self) -> None:
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(
|
||||||
|
x={1: torch.tensor([3.0, 4.0]), 0: torch.tensor([1.0, 2.0])},
|
||||||
|
y={0: torch.tensor([5.0, 6.0, 7.0, 8.0])},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assert torch.equal(result.x, torch.tensor([1.0, 2.0, 3.0, 4.0]))
|
||||||
|
assert torch.equal(result.y, torch.tensor([5.0, 6.0, 7.0, 8.0]))
|
||||||
|
|
||||||
|
def test_named_token_dim_nonzero(self) -> None:
|
||||||
|
"""Token dim at dim=1 (not dim=0) — concat and truncate along correct dim."""
|
||||||
|
# shape [2, 3, 4]: dim0=batch, dim1=token, dim2=hidden
|
||||||
|
x_step0 = torch.randn(2, 3, 4).refine_names("b", "t", "h")
|
||||||
|
x_step1 = torch.randn(2, 5, 4).refine_names("b", "t", "h")
|
||||||
|
y_step0 = torch.randn(2, 6, 4).refine_names("b", "t", "h")
|
||||||
|
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(
|
||||||
|
x={0: x_step0, 1: x_step1},
|
||||||
|
y={0: y_step0},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# x: 3+5=8 tokens; y: 6 tokens → truncate to 6
|
||||||
|
assert result.x.shape == (2, 6, 4)
|
||||||
|
assert result.y.shape == (2, 6, 4)
|
||||||
|
|
||||||
|
def test_named_dims_no_token_dim_fallback(self) -> None:
|
||||||
|
"""Named dims without t or s → fallback to dim 0."""
|
||||||
|
x = torch.randn(4, 8).refine_names("b", "h")
|
||||||
|
y = torch.randn(3, 8).refine_names("b", "h")
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(x={0: x}, y={0: y}),
|
||||||
|
)
|
||||||
|
assert result.x.shape == (3, 8)
|
||||||
|
assert result.y.shape == (3, 8)
|
||||||
|
|
||||||
|
def test_seq_dim_fallback(self) -> None:
|
||||||
|
"""Named dims with s but no t → uses s as token dim."""
|
||||||
|
x = torch.randn(2, 5, 4).refine_names("b", "s", "h")
|
||||||
|
y = torch.randn(2, 3, 4).refine_names("b", "s", "h")
|
||||||
|
result: Pair[torch.Tensor] = execute_token_aligner_concat_steps(
|
||||||
|
tensor_of_step_pair=Pair(x={0: x}, y={0: y}),
|
||||||
|
)
|
||||||
|
assert result.x.shape == (2, 3, 4)
|
||||||
|
assert result.y.shape == (2, 3, 4)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(pytest.main([__file__]))
|
||||||
@@ -5,16 +5,16 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.executor import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.executor import (
|
||||||
execute_token_aligner,
|
execute_token_aligner,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.planner import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.planner import (
|
||||||
compute_token_aligner_plan,
|
compute_token_aligner_plan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.seq_info_builder import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.seq_info_builder import (
|
||||||
build_seqs_info,
|
build_seqs_info,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
SGLangSeqId,
|
SGLangSeqId,
|
||||||
TokenAlignerGlobalAux,
|
TokenAlignerGlobalAux,
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import sys
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.planner import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.planner import (
|
||||||
_match_sequences,
|
_match_sequences,
|
||||||
compute_token_aligner_plan,
|
compute_token_aligner_plan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.seq_info_builder import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.seq_info_builder import (
|
||||||
build_seqs_info,
|
build_seqs_info,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
PositionalSeqId,
|
PositionalSeqId,
|
||||||
SeqId,
|
SeqId,
|
||||||
SGLangSeqId,
|
SGLangSeqId,
|
||||||
|
|||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import polars as pl
|
||||||
|
import pytest
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps.thd_seq_lens_loader import (
|
||||||
|
load_thd_seq_lens_only,
|
||||||
|
)
|
||||||
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
|
||||||
|
_SGLangPlugin,
|
||||||
|
)
|
||||||
|
from sglang.test.ci.ci_register import register_cpu_ci
|
||||||
|
|
||||||
|
register_cpu_ci(est_time=15, suite="default", nightly=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _save_pt(
|
||||||
|
dump_path: Path,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
step: int,
|
||||||
|
rank: int,
|
||||||
|
value: object,
|
||||||
|
meta: dict | None = None,
|
||||||
|
) -> str:
|
||||||
|
filename: str = f"name={name}___step={step}___rank={rank}.pt"
|
||||||
|
payload: dict = {"value": value, "meta": meta or {}}
|
||||||
|
torch.save(payload, dump_path / filename)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def _make_df_from_filenames(filenames: list[str]) -> pl.DataFrame:
|
||||||
|
rows: list[dict] = []
|
||||||
|
for fn in filenames:
|
||||||
|
parts: dict = {}
|
||||||
|
stem: str = fn.removesuffix(".pt")
|
||||||
|
for kv in stem.split("___"):
|
||||||
|
if "=" in kv:
|
||||||
|
k, v = kv.split("=", 1)
|
||||||
|
parts[k] = v
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"filename": fn,
|
||||||
|
"name": parts["name"],
|
||||||
|
"step": int(parts["step"]),
|
||||||
|
"rank": int(parts["rank"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return pl.DataFrame(rows)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoadThdSeqLensOnly:
|
||||||
|
"""Tests for load_thd_seq_lens_only."""
|
||||||
|
|
||||||
|
def test_returns_none_when_no_plugin(self, tmp_path: Path) -> None:
|
||||||
|
"""No recognized plugin → returns None."""
|
||||||
|
fn: str = _save_pt(
|
||||||
|
tmp_path, name="unrelated_tensor", step=0, rank=0, value=torch.tensor([1])
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn])
|
||||||
|
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_returns_none_when_no_cp_sharded_names(self, tmp_path: Path) -> None:
|
||||||
|
"""Plugin detected but cp_sharded_names is empty → returns None."""
|
||||||
|
|
||||||
|
class _NoCpPlugin(_SGLangPlugin):
|
||||||
|
@property
|
||||||
|
def cp_sharded_names(self) -> frozenset[str]:
|
||||||
|
return frozenset()
|
||||||
|
|
||||||
|
fn: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="seq_lens",
|
||||||
|
step=0,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([3, 5]),
|
||||||
|
meta={"sglang_parallel_info": {"tp_rank": 0, "tp_size": 1}},
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn])
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps.thd_seq_lens_loader._detect_plugin",
|
||||||
|
return_value=_NoCpPlugin(),
|
||||||
|
):
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_sglang_extracts_seq_lens(self, tmp_path: Path) -> None:
|
||||||
|
"""SGLang format: seq_lens tensor present → extracts per-seq lengths."""
|
||||||
|
fn: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="seq_lens",
|
||||||
|
step=0,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([3, 5]),
|
||||||
|
meta={"sglang_parallel_info": {"tp_rank": 0, "tp_size": 1}},
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn])
|
||||||
|
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result == {0: [3, 5]}
|
||||||
|
|
||||||
|
def test_megatron_extracts_from_cu_seqlens(self, tmp_path: Path) -> None:
|
||||||
|
"""Megatron format: cu_seqlens_q tensor → derives seq_lens via diff."""
|
||||||
|
fn: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="cu_seqlens_q",
|
||||||
|
step=0,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([0, 3, 8], dtype=torch.int64),
|
||||||
|
meta={"megatron_parallel_info": {"cp_rank": 0, "cp_size": 2}},
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn])
|
||||||
|
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result == {0: [3, 5]}
|
||||||
|
|
||||||
|
def test_multi_step(self, tmp_path: Path) -> None:
|
||||||
|
"""Two steps with different seq_lens → returns both in result dict."""
|
||||||
|
fn0: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="seq_lens",
|
||||||
|
step=0,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([3, 5]),
|
||||||
|
meta={"sglang_parallel_info": {"tp_rank": 0, "tp_size": 1}},
|
||||||
|
)
|
||||||
|
fn1: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="seq_lens",
|
||||||
|
step=1,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([10, 20, 30]),
|
||||||
|
meta={"sglang_parallel_info": {"tp_rank": 0, "tp_size": 1}},
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn0, fn1])
|
||||||
|
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
|
assert result == {0: [3, 5], 1: [10, 20, 30]}
|
||||||
|
|
||||||
|
def test_returns_none_when_seq_lens_missing(self, tmp_path: Path) -> None:
|
||||||
|
"""Plugin with cp_sharded_names but no seq_lens/cu_seqlens_q tensor → None."""
|
||||||
|
fn: str = _save_pt(
|
||||||
|
tmp_path,
|
||||||
|
name="cu_seqlens_kv",
|
||||||
|
step=0,
|
||||||
|
rank=0,
|
||||||
|
value=torch.tensor([0, 4], dtype=torch.int64),
|
||||||
|
meta={"megatron_parallel_info": {"cp_rank": 0, "cp_size": 2}},
|
||||||
|
)
|
||||||
|
df: pl.DataFrame = _make_df_from_filenames([fn])
|
||||||
|
|
||||||
|
result = load_thd_seq_lens_only(dump_path=tmp_path, df=df)
|
||||||
|
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(pytest.main([__file__]))
|
||||||
@@ -544,7 +544,7 @@ class TestEntrypointGroupingLogical:
|
|||||||
assert summary.skipped == 0
|
assert summary.skipped == 0
|
||||||
|
|
||||||
def test_multi_step_tp(self, tmp_path, capsys):
|
def test_multi_step_tp(self, tmp_path, capsys):
|
||||||
"""Two steps with TP=2 shards produce two per-step comparisons (no aux → no alignment)."""
|
"""Two steps with TP=2 shards: concat mode merges into one comparison."""
|
||||||
torch.manual_seed(42)
|
torch.manual_seed(42)
|
||||||
full_tensor = torch.randn(4, 8)
|
full_tensor = torch.randn(4, 8)
|
||||||
|
|
||||||
@@ -574,14 +574,14 @@ class TestEntrypointGroupingLogical:
|
|||||||
|
|
||||||
records = _run_and_parse(args, capsys)
|
records = _run_and_parse(args, capsys)
|
||||||
comparisons = _get_comparisons(records)
|
comparisons = _get_comparisons(records)
|
||||||
assert len(comparisons) == 2
|
assert len(comparisons) == 1
|
||||||
assert comparisons[0].baseline.shape == [4, 8]
|
# concat along dim 0 (fallback, no token dim) → 2 steps × [4, 8] = [8, 8]
|
||||||
assert comparisons[1].baseline.shape == [4, 8]
|
assert comparisons[0].baseline.shape == [8, 8]
|
||||||
|
|
||||||
summary = records[-1]
|
summary = records[-1]
|
||||||
assert isinstance(summary, SummaryRecord)
|
assert isinstance(summary, SummaryRecord)
|
||||||
assert summary.total == 2
|
assert summary.total == 1
|
||||||
assert summary.passed == 2
|
assert summary.passed == 1
|
||||||
|
|
||||||
def test_cp_axis_unshard(self, tmp_path, capsys):
|
def test_cp_axis_unshard(self, tmp_path, capsys):
|
||||||
"""CP-sharded tensors are correctly concatenated along the sequence dim."""
|
"""CP-sharded tensors are correctly concatenated along the sequence dim."""
|
||||||
@@ -1046,6 +1046,447 @@ class TestEntrypointGroupingLogical:
|
|||||||
assert comp.name == "hidden"
|
assert comp.name == "hidden"
|
||||||
|
|
||||||
|
|
||||||
|
class TestEntrypointConcatMode:
|
||||||
|
"""Test concat token-aligner mode through the full entrypoint pipeline."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _make_dirs(tmp_path: Path) -> tuple[Path, Path]:
|
||||||
|
baseline_dir: Path = tmp_path / "baseline"
|
||||||
|
target_dir: Path = tmp_path / "target"
|
||||||
|
baseline_dir.mkdir()
|
||||||
|
target_dir.mkdir()
|
||||||
|
return baseline_dir, target_dir
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _create_both_sides(
|
||||||
|
tmp_path: Path,
|
||||||
|
*,
|
||||||
|
baseline_steps: list[torch.Tensor],
|
||||||
|
target_steps: list[torch.Tensor],
|
||||||
|
name: str = "hidden",
|
||||||
|
dims: str | None = None,
|
||||||
|
) -> tuple[Path, Path]:
|
||||||
|
"""Create multi-step rank-0 dumps for both sides and return exp paths."""
|
||||||
|
baseline_dir, target_dir = TestEntrypointConcatMode._make_dirs(tmp_path)
|
||||||
|
|
||||||
|
for side_dir, steps in [
|
||||||
|
(baseline_dir, baseline_steps),
|
||||||
|
(target_dir, target_steps),
|
||||||
|
]:
|
||||||
|
_create_multi_step_rank_dump(
|
||||||
|
side_dir,
|
||||||
|
rank=0,
|
||||||
|
name=name,
|
||||||
|
tensors_per_step=steps,
|
||||||
|
dims=dims,
|
||||||
|
)
|
||||||
|
|
||||||
|
return baseline_dir / _FIXED_EXP_NAME, target_dir / _FIXED_EXP_NAME
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _run_concat(
|
||||||
|
tmp_path: Path,
|
||||||
|
capsys: pytest.CaptureFixture,
|
||||||
|
*,
|
||||||
|
baseline_steps: list[torch.Tensor],
|
||||||
|
target_steps: list[torch.Tensor],
|
||||||
|
name: str = "hidden",
|
||||||
|
dims: str | None = None,
|
||||||
|
diff_threshold: float = 0.01,
|
||||||
|
) -> list[AnyRecord]:
|
||||||
|
"""Create both-side dumps, run comparator, return parsed records."""
|
||||||
|
baseline_path, target_path = TestEntrypointConcatMode._create_both_sides(
|
||||||
|
tmp_path,
|
||||||
|
baseline_steps=baseline_steps,
|
||||||
|
target_steps=target_steps,
|
||||||
|
name=name,
|
||||||
|
dims=dims,
|
||||||
|
)
|
||||||
|
args: Namespace = _make_args(
|
||||||
|
baseline_path, target_path, diff_threshold=diff_threshold
|
||||||
|
)
|
||||||
|
return _run_and_parse(args, capsys)
|
||||||
|
|
||||||
|
def test_concat_multi_step_different_data(self, tmp_path, capsys):
|
||||||
|
"""Multi-step concat with different data per step + truncation."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
# baseline: 2 steps [5,4] + [3,4] → concat → [8,4]
|
||||||
|
baseline_step0 = torch.randn(5, 4)
|
||||||
|
baseline_step1 = torch.randn(3, 4)
|
||||||
|
baseline_concat = torch.cat([baseline_step0, baseline_step1], dim=0)
|
||||||
|
|
||||||
|
# target: 1 step [6,4] — will be truncated to min(8,6)=6
|
||||||
|
target_step0 = baseline_concat[:6] + torch.randn(6, 4) * 0.0001
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[baseline_step0, baseline_step1],
|
||||||
|
target_steps=[target_step0],
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
# truncated to min(8,6) = 6 along concat dim
|
||||||
|
assert comparisons[0].baseline.shape == [6, 4]
|
||||||
|
assert comparisons[0].target.shape == [6, 4]
|
||||||
|
|
||||||
|
def test_concat_multi_step_tp_unshard(self, tmp_path, capsys):
|
||||||
|
"""Multi-step different data + TP=2 unshard + concat."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
baseline_dir = tmp_path / "baseline"
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
|
||||||
|
# 2 steps: [4,8] each → concat → [8,8]
|
||||||
|
full_step0 = torch.randn(4, 8)
|
||||||
|
full_step1 = torch.randn(4, 8)
|
||||||
|
|
||||||
|
_create_multi_step_tp_sharded_dumps(
|
||||||
|
baseline_dir,
|
||||||
|
full_tensors_per_step=[full_step0, full_step1],
|
||||||
|
name="hidden",
|
||||||
|
tp_size=2,
|
||||||
|
shard_dim=1,
|
||||||
|
dims_str="b h(tp)",
|
||||||
|
)
|
||||||
|
_create_multi_step_tp_sharded_dumps(
|
||||||
|
target_dir,
|
||||||
|
full_tensors_per_step=[
|
||||||
|
full_step0 + torch.randn(4, 8) * 0.0001,
|
||||||
|
full_step1 + torch.randn(4, 8) * 0.0001,
|
||||||
|
],
|
||||||
|
name="hidden",
|
||||||
|
tp_size=2,
|
||||||
|
shard_dim=1,
|
||||||
|
dims_str="b h(tp)",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = _make_args(
|
||||||
|
baseline_dir / _FIXED_EXP_NAME,
|
||||||
|
target_dir / _FIXED_EXP_NAME,
|
||||||
|
diff_threshold=0.01,
|
||||||
|
)
|
||||||
|
|
||||||
|
records = _run_and_parse(args, capsys)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
# 2 steps × [4, 8] concat along dim 0 (fallback) → [8, 8]
|
||||||
|
assert comparisons[0].baseline.shape == [8, 8]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_unequal_step_counts(self, tmp_path, capsys):
|
||||||
|
"""Baseline 3 steps vs target 2 steps with truncation."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
# baseline: 3 steps [3]+[4]+[2] = 9 tokens along dim 0
|
||||||
|
b_step0 = torch.randn(3, 4)
|
||||||
|
b_step1 = torch.randn(4, 4)
|
||||||
|
b_step2 = torch.randn(2, 4)
|
||||||
|
b_concat = torch.cat([b_step0, b_step1, b_step2], dim=0)
|
||||||
|
|
||||||
|
# target: 2 steps [5]+[3] = 8 tokens along dim 0
|
||||||
|
t_step0 = b_concat[:5] + torch.randn(5, 4) * 0.0001
|
||||||
|
t_step1 = b_concat[5:8] + torch.randn(3, 4) * 0.0001
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1, b_step2],
|
||||||
|
target_steps=[t_step0, t_step1],
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
# truncated to min(9,8) = 8
|
||||||
|
assert comparisons[0].baseline.shape == [8, 4]
|
||||||
|
assert comparisons[0].target.shape == [8, 4]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_token_dim_nonzero(self, tmp_path, capsys):
|
||||||
|
"""Token dim at dim=1 (dims='b t h') — concat along dim 1."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
# 2 steps: [2,5,4] + [2,3,4] → concat along dim 1 → [2,8,4]
|
||||||
|
b_step0 = torch.randn(2, 5, 4)
|
||||||
|
b_step1 = torch.randn(2, 3, 4)
|
||||||
|
b_concat = torch.cat([b_step0, b_step1], dim=1)
|
||||||
|
|
||||||
|
t_step0 = b_concat[:, :5, :] + torch.randn(2, 5, 4) * 0.0001
|
||||||
|
t_step1 = b_concat[:, 5:, :] + torch.randn(2, 3, 4) * 0.0001
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1],
|
||||||
|
target_steps=[t_step0, t_step1],
|
||||||
|
dims="b t h",
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
assert comparisons[0].baseline.shape == [2, 8, 4]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_seq_dim_fallback(self, tmp_path, capsys):
|
||||||
|
"""No 't' dim but 's' dim present (dims='b s h') → concat along s."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
# 2 steps: [2,5,4] + [2,3,4] → concat along dim 1 (s) → [2,8,4]
|
||||||
|
b_step0 = torch.randn(2, 5, 4)
|
||||||
|
b_step1 = torch.randn(2, 3, 4)
|
||||||
|
b_concat = torch.cat([b_step0, b_step1], dim=1)
|
||||||
|
|
||||||
|
t_step0 = b_concat[:, :5, :] + torch.randn(2, 5, 4) * 0.0001
|
||||||
|
t_step1 = b_concat[:, 5:, :] + torch.randn(2, 3, 4) * 0.0001
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1],
|
||||||
|
target_steps=[t_step0, t_step1],
|
||||||
|
dims="b s h",
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
assert comparisons[0].baseline.shape == [2, 8, 4]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_no_dims_fallback(self, tmp_path, capsys):
|
||||||
|
"""No dims annotation → fallback to concat along dim 0."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
# 2 steps: [5,4] + [3,4] → concat along dim 0 → [8,4]
|
||||||
|
b_step0 = torch.randn(5, 4)
|
||||||
|
b_step1 = torch.randn(3, 4)
|
||||||
|
b_concat = torch.cat([b_step0, b_step1], dim=0)
|
||||||
|
|
||||||
|
t_step0 = b_concat[:5] + torch.randn(5, 4) * 0.0001
|
||||||
|
t_step1 = b_concat[5:] + torch.randn(3, 4) * 0.0001
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1],
|
||||||
|
target_steps=[t_step0, t_step1],
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
assert comparisons[0].baseline.shape == [8, 4]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_preserves_step_order(self, tmp_path, capsys):
|
||||||
|
"""Verify step0 data precedes step1 data in the concatenated result."""
|
||||||
|
# deterministic integer data: step0=[1,2,3], step1=[4,5]
|
||||||
|
b_step0 = torch.tensor([[1.0], [2.0], [3.0]])
|
||||||
|
b_step1 = torch.tensor([[4.0], [5.0]])
|
||||||
|
|
||||||
|
# target: same data, single step [1,2,3,4,5]
|
||||||
|
t_full = torch.tensor([[1.0], [2.0], [3.0], [4.0], [5.0]])
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1],
|
||||||
|
target_steps=[t_full],
|
||||||
|
)
|
||||||
|
comp = _assert_single_comparison_passed(records)
|
||||||
|
# if order were wrong, diff would not pass with exact integer data
|
||||||
|
assert comp.baseline.shape == [5, 1]
|
||||||
|
assert comp.diff is not None
|
||||||
|
assert comp.diff.max_abs_diff == 0.0
|
||||||
|
|
||||||
|
def test_concat_aux_tensors_not_filtered(self, tmp_path, capsys):
|
||||||
|
"""Concat mode does not filter aux tensors — all participate in comparison."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
baseline_dir, target_dir = self._make_dirs(tmp_path)
|
||||||
|
|
||||||
|
hidden = torch.randn(4, 8)
|
||||||
|
input_ids = torch.randint(0, 100, (4,))
|
||||||
|
positions = torch.arange(4)
|
||||||
|
|
||||||
|
_create_rank_dump(
|
||||||
|
baseline_dir,
|
||||||
|
rank=0,
|
||||||
|
name="hidden_states",
|
||||||
|
tensor=hidden,
|
||||||
|
extra_dumps=[("input_ids", input_ids), ("positions", positions)],
|
||||||
|
)
|
||||||
|
_create_rank_dump(
|
||||||
|
target_dir,
|
||||||
|
rank=0,
|
||||||
|
name="hidden_states",
|
||||||
|
tensor=hidden + torch.randn(4, 8) * 0.0001,
|
||||||
|
extra_dumps=[("input_ids", input_ids), ("positions", positions)],
|
||||||
|
)
|
||||||
|
|
||||||
|
args = _make_args(
|
||||||
|
baseline_dir / _FIXED_EXP_NAME,
|
||||||
|
target_dir / _FIXED_EXP_NAME,
|
||||||
|
diff_threshold=0.01,
|
||||||
|
)
|
||||||
|
|
||||||
|
records = _run_and_parse(args, capsys)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
# all 3 tensors should be compared (not filtered out)
|
||||||
|
names = {c.name for c in comparisons}
|
||||||
|
assert "hidden_states" in names
|
||||||
|
assert "input_ids" in names
|
||||||
|
assert "positions" in names
|
||||||
|
assert len(comparisons) == 3
|
||||||
|
|
||||||
|
def test_concat_aligner_plan_fields(self, tmp_path, capsys):
|
||||||
|
"""ComparisonRecord.aligner_plan reports mode='concat' with plan=None."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[torch.randn(3, 4), torch.randn(2, 4)],
|
||||||
|
target_steps=[torch.randn(3, 4), torch.randn(2, 4)],
|
||||||
|
diff_threshold=100.0,
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
plan = comparisons[0].aligner_plan
|
||||||
|
assert plan is not None
|
||||||
|
assert plan.token_aligner_mode == "concat_steps"
|
||||||
|
assert plan.token_aligner_plan is None
|
||||||
|
|
||||||
|
def test_concat_comparison_fails(self, tmp_path, capsys):
|
||||||
|
"""Completely different data → comparison fails."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
b_step0 = torch.randn(4, 4)
|
||||||
|
b_step1 = torch.randn(3, 4)
|
||||||
|
|
||||||
|
# target: completely different random data
|
||||||
|
torch.manual_seed(99)
|
||||||
|
t_step0 = torch.randn(4, 4) * 100
|
||||||
|
t_step1 = torch.randn(3, 4) * 100
|
||||||
|
|
||||||
|
records = self._run_concat(
|
||||||
|
tmp_path,
|
||||||
|
capsys,
|
||||||
|
baseline_steps=[b_step0, b_step1],
|
||||||
|
target_steps=[t_step0, t_step1],
|
||||||
|
diff_threshold=1e-6,
|
||||||
|
)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert not comparisons[0].diff.passed
|
||||||
|
|
||||||
|
summary = records[-1]
|
||||||
|
assert isinstance(summary, SummaryRecord)
|
||||||
|
assert summary.failed == 1
|
||||||
|
assert summary.passed == 0
|
||||||
|
|
||||||
|
def test_concat_multi_step_cp_unshard(self, tmp_path, capsys):
|
||||||
|
"""Multi-step different data + CP=2 unshard along seq dim + concat."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
|
||||||
|
baseline_dir = tmp_path / "baseline"
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
|
||||||
|
# 2 steps: [4,8,6] each → concat along seq dim (dim 1) → [4,16,6]
|
||||||
|
full_step0 = torch.randn(4, 8, 6)
|
||||||
|
full_step1 = torch.randn(4, 8, 6)
|
||||||
|
|
||||||
|
for side_dir, steps in [
|
||||||
|
(baseline_dir, [full_step0, full_step1]),
|
||||||
|
(
|
||||||
|
target_dir,
|
||||||
|
[
|
||||||
|
full_step0 + torch.randn(4, 8, 6) * 0.0001,
|
||||||
|
full_step1 + torch.randn(4, 8, 6) * 0.0001,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]:
|
||||||
|
for cp_rank in range(2):
|
||||||
|
per_step_shards: list[torch.Tensor] = [
|
||||||
|
t.chunk(2, dim=1)[cp_rank] for t in steps
|
||||||
|
]
|
||||||
|
_create_multi_step_rank_dump(
|
||||||
|
side_dir,
|
||||||
|
rank=cp_rank,
|
||||||
|
name="attn_out",
|
||||||
|
tensors_per_step=per_step_shards,
|
||||||
|
dims="b s(cp) h",
|
||||||
|
parallel_info={"cp_rank": cp_rank, "cp_size": 2},
|
||||||
|
)
|
||||||
|
|
||||||
|
args = _make_args(
|
||||||
|
baseline_dir / _FIXED_EXP_NAME,
|
||||||
|
target_dir / _FIXED_EXP_NAME,
|
||||||
|
diff_threshold=0.01,
|
||||||
|
)
|
||||||
|
|
||||||
|
records = _run_and_parse(args, capsys)
|
||||||
|
comparisons = _get_comparisons(records)
|
||||||
|
assert len(comparisons) == 1
|
||||||
|
# CP unshard: [4,4,6] × 2 ranks → [4,8,6] per step
|
||||||
|
# concat along seq dim (dim 1): 2 steps × [4,8,6] → [4,16,6]
|
||||||
|
assert comparisons[0].baseline.shape == [4, 16, 6]
|
||||||
|
assert comparisons[0].diff is not None
|
||||||
|
assert comparisons[0].diff.passed
|
||||||
|
|
||||||
|
def test_concat_thd_cp_zigzag(self, tmp_path: Path, capsys) -> None:
|
||||||
|
"""Concat mode with THD CP=2 zigzag (Megatron format) — unshard + reorder works."""
|
||||||
|
torch.manual_seed(42)
|
||||||
|
cp_size: int = 2
|
||||||
|
seq_lens: list[int] = [100, 64]
|
||||||
|
total_tokens: int = sum(seq_lens)
|
||||||
|
total_per_rank: int = 128
|
||||||
|
num_steps: int = 2
|
||||||
|
|
||||||
|
full_tensor: torch.Tensor = torch.randn(total_tokens + 92)
|
||||||
|
|
||||||
|
baseline_dir: Path = tmp_path / "baseline"
|
||||||
|
target_dir: Path = tmp_path / "target"
|
||||||
|
baseline_dir.mkdir()
|
||||||
|
target_dir.mkdir()
|
||||||
|
|
||||||
|
baseline_path: Path = _create_thd_cp_zigzag_dumps(
|
||||||
|
baseline_dir,
|
||||||
|
full_tensor=full_tensor,
|
||||||
|
name="hidden_states",
|
||||||
|
seq_lens=seq_lens,
|
||||||
|
cp_size=cp_size,
|
||||||
|
total_per_rank=total_per_rank,
|
||||||
|
num_steps=num_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
target_tensor: torch.Tensor = full_tensor + torch.randn_like(full_tensor) * 1e-5
|
||||||
|
target_path: Path = _create_thd_cp_zigzag_dumps(
|
||||||
|
target_dir,
|
||||||
|
full_tensor=target_tensor,
|
||||||
|
name="hidden_states",
|
||||||
|
seq_lens=seq_lens,
|
||||||
|
cp_size=cp_size,
|
||||||
|
total_per_rank=total_per_rank,
|
||||||
|
num_steps=num_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
args: Namespace = _make_args(
|
||||||
|
baseline_path,
|
||||||
|
target_path,
|
||||||
|
token_aligner="concat_steps",
|
||||||
|
diff_threshold=1e-3,
|
||||||
|
)
|
||||||
|
records: list[AnyRecord] = _run_and_parse(args, capsys)
|
||||||
|
|
||||||
|
comparisons: list[ComparisonRecord] = _get_comparisons(records)
|
||||||
|
hidden_comparisons: list[ComparisonRecord] = [
|
||||||
|
c for c in comparisons if c.name == "hidden_states"
|
||||||
|
]
|
||||||
|
assert len(hidden_comparisons) >= 1
|
||||||
|
assert all(c.diff is not None and c.diff.passed for c in hidden_comparisons)
|
||||||
|
|
||||||
|
|
||||||
class TestEntrypointAxisAligner:
|
class TestEntrypointAxisAligner:
|
||||||
"""Test cross-framework dim reordering through the full entrypoint pipeline."""
|
"""Test cross-framework dim reordering through the full entrypoint pipeline."""
|
||||||
|
|
||||||
@@ -1406,7 +1847,9 @@ class TestEntrypointAlignment:
|
|||||||
|
|
||||||
exp_paths.append(d / _FIXED_EXP_NAME)
|
exp_paths.append(d / _FIXED_EXP_NAME)
|
||||||
|
|
||||||
args = _make_args(exp_paths[0], exp_paths[1], grouping="logical")
|
args = _make_args(
|
||||||
|
exp_paths[0], exp_paths[1], grouping="logical", token_aligner="smart"
|
||||||
|
)
|
||||||
records = _run_and_parse(args, capsys)
|
records = _run_and_parse(args, capsys)
|
||||||
|
|
||||||
comparisons = _get_comparisons(records)
|
comparisons = _get_comparisons(records)
|
||||||
@@ -1518,6 +1961,7 @@ class TestEntrypointAlignment:
|
|||||||
sglang_dir / _FIXED_EXP_NAME,
|
sglang_dir / _FIXED_EXP_NAME,
|
||||||
megatron_dir / _FIXED_EXP_NAME,
|
megatron_dir / _FIXED_EXP_NAME,
|
||||||
grouping="logical",
|
grouping="logical",
|
||||||
|
token_aligner="smart",
|
||||||
)
|
)
|
||||||
|
|
||||||
records = _run_and_parse(args, capsys)
|
records = _run_and_parse(args, capsys)
|
||||||
@@ -1549,7 +1993,11 @@ class TestEntrypointAlignment:
|
|||||||
"""Without aux tensors, logical grouping skips alignment and compares per-step."""
|
"""Without aux tensors, logical grouping skips alignment and compares per-step."""
|
||||||
baseline_path, target_path = _create_dumps(tmp_path, ["tensor_a"], num_steps=2)
|
baseline_path, target_path = _create_dumps(tmp_path, ["tensor_a"], num_steps=2)
|
||||||
args = _make_args(
|
args = _make_args(
|
||||||
baseline_path, target_path, grouping="logical", diff_threshold=0.1
|
baseline_path,
|
||||||
|
target_path,
|
||||||
|
grouping="logical",
|
||||||
|
token_aligner="smart",
|
||||||
|
diff_threshold=0.1,
|
||||||
)
|
)
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
@@ -1885,6 +2333,7 @@ def _make_args(baseline_path: Path, target_path: Path, **overrides) -> Namespace
|
|||||||
filter=None,
|
filter=None,
|
||||||
output_format="json",
|
output_format="json",
|
||||||
grouping="logical",
|
grouping="logical",
|
||||||
|
token_aligner="concat_steps",
|
||||||
viz_bundle_details=False,
|
viz_bundle_details=False,
|
||||||
viz_output_dir="/tmp/comparator_viz/",
|
viz_output_dir="/tmp/comparator_viz/",
|
||||||
visualize_per_token=None,
|
visualize_per_token=None,
|
||||||
@@ -1948,6 +2397,44 @@ def _create_rank_dump(
|
|||||||
return directory / _FIXED_EXP_NAME
|
return directory / _FIXED_EXP_NAME
|
||||||
|
|
||||||
|
|
||||||
|
def _create_multi_step_rank_dump(
|
||||||
|
directory: Path,
|
||||||
|
*,
|
||||||
|
rank: int,
|
||||||
|
name: str,
|
||||||
|
tensors_per_step: list[torch.Tensor],
|
||||||
|
dims: str | None = None,
|
||||||
|
parallel_info: dict | None = None,
|
||||||
|
framework: str = "sglang",
|
||||||
|
) -> Path:
|
||||||
|
"""Create a dump file with *different* tensors per step.
|
||||||
|
|
||||||
|
Unlike ``_create_rank_dump`` (which repeats the same tensor),
|
||||||
|
this helper accepts a list of tensors — one per step.
|
||||||
|
"""
|
||||||
|
with pytest.MonkeyPatch.context() as mp:
|
||||||
|
mp.setattr(_dumper_module, "_get_rank", lambda: rank)
|
||||||
|
|
||||||
|
dumper = _Dumper(
|
||||||
|
config=DumperConfig(
|
||||||
|
enable=True,
|
||||||
|
dir=str(directory),
|
||||||
|
exp_name=_FIXED_EXP_NAME,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_meta: dict = {"world_rank": rank, "world_size": 1}
|
||||||
|
if parallel_info is not None:
|
||||||
|
static_meta[f"{framework}_parallel_info"] = parallel_info
|
||||||
|
dumper.__dict__["_static_meta"] = static_meta
|
||||||
|
|
||||||
|
for tensor in tensors_per_step:
|
||||||
|
dumper.dump(name, tensor, dims=dims)
|
||||||
|
dumper.step()
|
||||||
|
|
||||||
|
return directory / _FIXED_EXP_NAME
|
||||||
|
|
||||||
|
|
||||||
def _create_cp_tp_sharded_dumps(
|
def _create_cp_tp_sharded_dumps(
|
||||||
directory: Path,
|
directory: Path,
|
||||||
*,
|
*,
|
||||||
@@ -2153,6 +2640,38 @@ def _create_tp_sharded_dumps(
|
|||||||
return directory / _FIXED_EXP_NAME
|
return directory / _FIXED_EXP_NAME
|
||||||
|
|
||||||
|
|
||||||
|
def _create_multi_step_tp_sharded_dumps(
|
||||||
|
directory: Path,
|
||||||
|
*,
|
||||||
|
full_tensors_per_step: list[torch.Tensor],
|
||||||
|
name: str,
|
||||||
|
tp_size: int,
|
||||||
|
shard_dim: int,
|
||||||
|
dims_str: str,
|
||||||
|
) -> Path:
|
||||||
|
"""Create TP-sharded dump files with *different* tensors per step.
|
||||||
|
|
||||||
|
Each step's full tensor is chunked across TP ranks, then
|
||||||
|
``_create_multi_step_rank_dump`` writes one file per rank.
|
||||||
|
"""
|
||||||
|
shards_per_rank: list[list[torch.Tensor]] = [[] for _ in range(tp_size)]
|
||||||
|
for full_tensor in full_tensors_per_step:
|
||||||
|
shards = list(full_tensor.chunk(tp_size, dim=shard_dim))
|
||||||
|
for tp_rank in range(tp_size):
|
||||||
|
shards_per_rank[tp_rank].append(shards[tp_rank])
|
||||||
|
|
||||||
|
for tp_rank in range(tp_size):
|
||||||
|
_create_multi_step_rank_dump(
|
||||||
|
directory,
|
||||||
|
rank=tp_rank,
|
||||||
|
name=name,
|
||||||
|
tensors_per_step=shards_per_rank[tp_rank],
|
||||||
|
dims=dims_str,
|
||||||
|
parallel_info={"tp_rank": tp_rank, "tp_size": tp_size},
|
||||||
|
)
|
||||||
|
return directory / _FIXED_EXP_NAME
|
||||||
|
|
||||||
|
|
||||||
def _create_tp_partial_dumps(
|
def _create_tp_partial_dumps(
|
||||||
directory: Path,
|
directory: Path,
|
||||||
*,
|
*,
|
||||||
@@ -2470,6 +2989,7 @@ class TestEntrypointThdCpZigzag:
|
|||||||
sglang_dir / _FIXED_EXP_NAME,
|
sglang_dir / _FIXED_EXP_NAME,
|
||||||
megatron_dir / _FIXED_EXP_NAME,
|
megatron_dir / _FIXED_EXP_NAME,
|
||||||
grouping="logical",
|
grouping="logical",
|
||||||
|
token_aligner="smart",
|
||||||
diff_threshold=1e-3,
|
diff_threshold=1e-3,
|
||||||
)
|
)
|
||||||
records: list[AnyRecord] = _run_and_parse(args, capsys)
|
records: list[AnyRecord] = _run_and_parse(args, capsys)
|
||||||
@@ -2517,7 +3037,11 @@ class TestEntrypointThdCpZigzag:
|
|||||||
)
|
)
|
||||||
|
|
||||||
args: Namespace = _make_args(
|
args: Namespace = _make_args(
|
||||||
baseline_path, target_path, grouping="logical", diff_threshold=1e-3
|
baseline_path,
|
||||||
|
target_path,
|
||||||
|
grouping="logical",
|
||||||
|
token_aligner="smart",
|
||||||
|
diff_threshold=1e-3,
|
||||||
)
|
)
|
||||||
records: list[AnyRecord] = _run_and_parse(args, capsys)
|
records: list[AnyRecord] = _run_and_parse(args, capsys)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
|||||||
AlignerPerStepPlan,
|
AlignerPerStepPlan,
|
||||||
AlignerPlan,
|
AlignerPlan,
|
||||||
)
|
)
|
||||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.types import (
|
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||||
PositionalSeqId,
|
PositionalSeqId,
|
||||||
TokenAlignerPlan,
|
TokenAlignerPlan,
|
||||||
TokenAlignerSeqInfo,
|
TokenAlignerSeqInfo,
|
||||||
|
|||||||
Reference in New Issue
Block a user