chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,219 @@
from __future__ import annotations
from typing import Optional
import torch
from einops import rearrange
from sglang.srt.debug_utils.comparator.dims_spec import (
_FUSED_NAME_SEP,
SEQ_DIM_NAME,
TOKEN_DIM_NAME,
DimSpec,
_SingletonDimUtil,
parse_dims,
without_dim_names,
)
from sglang.srt.debug_utils.comparator.log_sink import log_sink
from sglang.srt.debug_utils.comparator.utils import Pair, _FrozenBase
# --- types ---
class AxisAlignerPlan(_FrozenBase):
pattern: Pair[Optional[str]] # einops pattern per side, None = no-op
# --- planner ---
def compute_axis_aligner_plan(
dims_str_pair: Pair[Optional[str]],
) -> Optional[AxisAlignerPlan]:
if dims_str_pair.x is None or dims_str_pair.y is None:
return None
dims_pair: Pair[str] = Pair(x=dims_str_pair.x, y=dims_str_pair.y)
specs_pair: Pair[list[DimSpec]] = dims_pair.map(lambda s: parse_dims(s).dims)
if not _semantic_names_match(specs_pair):
return None
# Canonical dim order follows y; fused groups stay fused (flatten, not unflatten).
canonical_order: Optional[list[str]] = _build_canonical_order(specs_pair)
if canonical_order is None:
return None
pattern: Pair[Optional[str]] = specs_pair.map(
lambda specs: _build_side_pattern(specs=specs, canonical_order=canonical_order)
)
if pattern.x is None and pattern.y is None:
return None
return AxisAlignerPlan(pattern=pattern)
_SEQ_DIM_EQUIVALENCES: frozenset[frozenset[str]] = frozenset(
{
frozenset({SEQ_DIM_NAME, TOKEN_DIM_NAME}), # s ≡ t
}
)
def _normalize_dim_name(name: str) -> str:
for equiv_set in _SEQ_DIM_EQUIVALENCES:
if name in equiv_set:
return min(equiv_set)
return name
def _semantic_names_match(specs_pair: Pair[list[DimSpec]]) -> bool:
"""Check that both sides share the same semantic name set (ignoring squeeze dims)."""
names_pair: Pair[list[str]] = specs_pair.map(_expand_and_skip_squeeze)
if set(map(_normalize_dim_name, names_pair.x)) == set(
map(_normalize_dim_name, names_pair.y)
):
return True
# Local import to avoid circular dependency:
# output_types -> aligner/entrypoint/types -> axis_aligner -> output_types
from sglang.srt.debug_utils.comparator.output_types import ErrorLog
log_sink.add(
ErrorLog(
category="axis_aligner_dim_mismatch",
message=(
f"AxisAligner: dim name sets differ (x={names_pair.x}, y={names_pair.y}), "
f"skipping axis swap"
),
)
)
return False
def _expand_and_skip_squeeze(specs: list[DimSpec]) -> list[str]:
"""Expand DimSpecs to flat semantic names, skipping squeeze dims."""
return [
name
for spec in specs
if not _SingletonDimUtil.is_squeeze(spec)
for name in spec.sub_dims
]
def _build_canonical_order(specs_pair: Pair[list[DimSpec]]) -> Optional[list[str]]:
"""Build canonical dim order following y, preferring fused representation.
Each element is either a plain name (``"c"``) or a fused placeholder (``"a___b"``).
Fused groups from *either* side are merged — the separate side must flatten.
Squeeze dims are excluded.
Returns ``None`` if the two sides have overlapping but incompatible fused groups
(e.g. x fuses ``(a*b)`` while y fuses ``(b*c)``).
"""
# Map each sub-dim name → (placeholder, siblings) from both sides
fused_lookup: dict[str, tuple[str, frozenset[str]]] = {}
for spec in (*specs_pair.x, *specs_pair.y):
if spec.is_fused:
placeholder: str = spec.sanitized_name
siblings: frozenset[str] = frozenset(spec.sub_dims)
for sub_name in spec.sub_dims:
existing: Optional[tuple[str, frozenset[str]]] = fused_lookup.get(
sub_name
)
if existing is not None and existing[1] != siblings:
from sglang.srt.debug_utils.comparator.output_types import ErrorLog
log_sink.add(
ErrorLog(
category="axis_aligner_fused_conflict",
message=(
f"AxisAligner: overlapping fused groups for sub-dim {sub_name!r} "
f"({existing[0]} vs {placeholder}), skipping axis alignment"
),
)
)
return None
fused_lookup.setdefault(sub_name, (placeholder, siblings))
result: list[str] = []
consumed: set[str] = set()
for spec in specs_pair.y:
if _SingletonDimUtil.is_squeeze(spec):
continue
names: list[str] = spec.sub_dims
if any(n in consumed for n in names):
continue
entry: Optional[tuple[str, frozenset[str]]] = fused_lookup.get(names[0])
if entry is not None:
fused_placeholder, sibs = entry
result.append(fused_placeholder)
consumed.update(sibs)
else:
result.append(_normalize_dim_name(spec.name))
consumed.update(names)
return result
def _build_side_pattern(
*, specs: list[DimSpec], canonical_order: list[str]
) -> Optional[str]:
"""Build an einops pattern for one side to reach ``canonical_order``.
Fused specs become their placeholder; separate specs that belong to a fused group
stay as individual names on the LHS and become ``(a b)`` on the RHS (einops flatten).
Squeeze dims (``1``) appear on the LHS but are dropped from the RHS.
"""
source_tokens: list[str] = [spec.sanitized_name for spec in specs]
# Map normalized dim names back to this side's original names so that
# einops patterns use consistent identifiers on LHS and RHS.
norm_to_original: dict[str, str] = {
_normalize_dim_name(spec.name): spec.name for spec in specs
}
def _to_side_name(token: str) -> str:
return norm_to_original.get(token, token)
# Build per-side target: replace fused placeholders with ``(a b)`` only if this side
# has the sub-dims as separate (non-fused) names in the source
fused_placeholders: set[str] = {
spec.sanitized_name for spec in specs if spec.is_fused
}
translated_order: list[str] = [_to_side_name(t) for t in canonical_order]
target_tokens: list[str] = [
(
f"({t.replace(_FUSED_NAME_SEP, ' ')})"
if _FUSED_NAME_SEP in t and t not in fused_placeholders
else t
)
for t in translated_order
]
if source_tokens == target_tokens:
return None
return f"{' '.join(source_tokens)} -> {' '.join(target_tokens)}"
# --- executor ---
def execute_axis_aligner_plan(
tensor: torch.Tensor, plan: AxisAlignerPlan, *, side: str
) -> torch.Tensor:
if side not in ("x", "y"):
raise ValueError(f"side must be 'x' or 'y', got {side!r}")
pattern: Optional[str] = plan.pattern.x if side == "x" else plan.pattern.y
if pattern is not None:
tensor = rearrange(without_dim_names(tensor), pattern)
return tensor
@@ -0,0 +1,212 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import NamedTuple, Optional
import torch
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import (
execute_axis_aligner_plan,
)
from sglang.srt.debug_utils.comparator.aligner.entrypoint.traced_types import (
TracedAlignerPlan,
TracedSidePlan,
TracedStepPlan,
TracedSubPlan,
)
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.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,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
UnsharderResult,
execute_unsharder_plan,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
from sglang.srt.debug_utils.comparator.output_types import (
ReplicatedCheckResult,
ShapeSnapshot,
)
from sglang.srt.debug_utils.comparator.utils import Pair
class StepPlansResult(NamedTuple):
tensors: dict[int, torch.Tensor]
checks: list[ReplicatedCheckResult]
traced_side: TracedSidePlan
class SubPlansResult(NamedTuple):
tensor: Optional[torch.Tensor]
checks: list[ReplicatedCheckResult]
snapshots: list[ShapeSnapshot]
@dataclass(frozen=True)
class AlignerResult:
tensors: Optional[Pair[torch.Tensor]]
failed_side_xy: Optional[str] # "x" or "y"; None if success
replicated_checks: list[ReplicatedCheckResult] = field(default_factory=list)
traced_plan: Optional[TracedAlignerPlan] = None
def execute_aligner_plan(
*,
tensors_pair: Pair[list[torch.Tensor]],
plan: AlignerPlan,
) -> AlignerResult:
"""Execute unified unshard/reorder + token-align."""
all_checks: list[ReplicatedCheckResult] = []
# Per-side: unshard + reorder -> dict[step, tensor]
result_x: StepPlansResult = _execute_step_plans(
tensors=tensors_pair.x, step_plans=plan.per_step_plans.x
)
all_checks.extend(result_x.checks)
result_y: StepPlansResult = _execute_step_plans(
tensors=tensors_pair.y, step_plans=plan.per_step_plans.y
)
all_checks.extend(result_y.checks)
traced_plan: TracedAlignerPlan = TracedAlignerPlan(
plan=plan,
per_side=Pair(x=result_x.traced_side, y=result_y.traced_side),
)
if not result_x.tensors or not result_y.tensors:
failed_side_xy: str = "x" if not result_x.tensors else "y"
return AlignerResult(
tensors=None,
failed_side_xy=failed_side_xy,
replicated_checks=all_checks,
traced_plan=traced_plan,
)
# Cross-side: token alignment (or direct extraction for single-step)
step_pair: Pair[dict[int, torch.Tensor]] = Pair(
x=result_x.tensors, y=result_y.tensors
)
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,
tensor_of_step_pair=step_pair,
)
else:
assert len(result_x.tensors) == 1 and len(result_y.tensors) == 1
combined = Pair(
x=list(result_x.tensors.values())[0],
y=list(result_y.tensors.values())[0],
)
# Cross-side: axis alignment (squeeze singletons + rearrange dim order)
if (aligner_plan := plan.axis_aligner_plan) is not None:
combined = Pair(
x=execute_axis_aligner_plan(tensor=combined.x, plan=aligner_plan, side="x"),
y=execute_axis_aligner_plan(tensor=combined.y, plan=aligner_plan, side="y"),
)
return AlignerResult(
tensors=combined,
failed_side_xy=None,
replicated_checks=all_checks,
traced_plan=traced_plan,
)
def _execute_step_plans(
tensors: list[torch.Tensor],
step_plans: list[AlignerPerStepPlan],
) -> StepPlansResult:
result: dict[int, torch.Tensor] = {}
all_checks: list[ReplicatedCheckResult] = []
traced_steps: list[TracedStepPlan] = []
for step_plan in step_plans:
step_tensors: list[torch.Tensor] = [
tensors[i] for i in step_plan.input_object_indices
]
sub_result: SubPlansResult = execute_sub_plans(
tensors=step_tensors, plans=step_plan.sub_plans
)
all_checks.extend(sub_result.checks)
traced_subs: list[TracedSubPlan] = [
TracedSubPlan(plan=sub_plan, snapshot=snapshot)
for sub_plan, snapshot in zip(step_plan.sub_plans, sub_result.snapshots)
]
traced_steps.append(
TracedStepPlan(
step=step_plan.step,
input_object_indices=step_plan.input_object_indices,
sub_plans=traced_subs,
)
)
if sub_result.tensor is not None:
result[step_plan.step] = sub_result.tensor
return StepPlansResult(
tensors=result,
checks=all_checks,
traced_side=TracedSidePlan(step_plans=traced_steps),
)
def execute_sub_plans(
tensors: list[torch.Tensor],
plans: list[AlignerPerStepSubPlan],
) -> SubPlansResult:
if not tensors:
return SubPlansResult(tensor=None, checks=[], snapshots=[])
if not plans:
if len(tensors) != 1:
return SubPlansResult(tensor=None, checks=[], snapshots=[])
return SubPlansResult(tensor=tensors[0], checks=[], snapshots=[])
current: list[torch.Tensor] = tensors
all_checks: list[ReplicatedCheckResult] = []
all_snapshots: list[ShapeSnapshot] = []
for plan in plans:
input_shapes: list[list[int]] = [list(t.shape) for t in current]
current, checks = execute_sub_plan(tensors=current, plan=plan)
output_shapes: list[list[int]] = [list(t.shape) for t in current]
all_checks.extend(checks)
all_snapshots.append(
ShapeSnapshot(
input_shapes=input_shapes,
output_shapes=output_shapes,
)
)
assert len(current) == 1
return SubPlansResult(tensor=current[0], checks=all_checks, snapshots=all_snapshots)
def execute_sub_plan(
tensors: list[torch.Tensor],
plan: AlignerPerStepSubPlan,
) -> tuple[list[torch.Tensor], list[ReplicatedCheckResult]]:
if isinstance(plan, UnsharderPlan):
unsharder_result: UnsharderResult = execute_unsharder_plan(plan, tensors)
return unsharder_result.tensors, unsharder_result.replicated_checks
elif isinstance(plan, ReordererPlan):
return execute_reorderer_plan(plan, tensors), []
else:
raise NotImplementedError(f"Unknown {plan=}")
@@ -0,0 +1,134 @@
from __future__ import annotations
from typing import Any, Optional
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import (
AxisAlignerPlan,
compute_axis_aligner_plan,
)
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.token_aligner.smart.types import (
TokenAlignerPlan,
)
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_spec import (
DimSpec,
DimsSpec,
ParallelAxis,
_SingletonDimUtil,
parse_dims,
)
from sglang.srt.debug_utils.comparator.utils import Pair
def compute_aligner_plan(
*,
metas_pair: Pair[list[dict[str, Any]]],
token_aligner_mode: Optional[str],
token_aligner_plan: Optional[TokenAlignerPlan],
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
x=None, y=None
),
) -> AlignerPlan:
dims_str_pair: Pair[Optional[str]] = metas_pair.map(
lambda metas: metas[0].get("dims") if metas else None
)
axis_aligner_plan: Optional[AxisAlignerPlan] = compute_axis_aligner_plan(
dims_str_pair=dims_str_pair
)
return AlignerPlan(
per_step_plans=Pair(
x=_compute_per_step_plans(
metas=metas_pair.x,
thd_seq_lens_by_step=thd_seq_lens_by_step_pair.x,
),
y=_compute_per_step_plans(
metas=metas_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,
axis_aligner_plan=axis_aligner_plan,
)
def _compute_per_step_plans(
metas: list[dict[str, Any]],
*,
thd_seq_lens_by_step: Optional[dict[int, list[int]]] = None,
) -> 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]
step_seq_lens: Optional[list[int]] = (
thd_seq_lens_by_step.get(step) if thd_seq_lens_by_step is not None else None
)
plans: list[AlignerPerStepSubPlan] = compute_per_step_sub_plans(
metas=step_metas,
thd_global_seq_lens=step_seq_lens,
)
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]],
*,
thd_global_seq_lens: Optional[list[int]] = None,
) -> list[AlignerPerStepSubPlan]:
if not metas or len(metas) == 1:
return []
dims_str = metas[0].get("dims")
if dims_str is None:
return []
dims_spec: DimsSpec = parse_dims(dims_str)
dim_specs: list[DimSpec] = _SingletonDimUtil.filter_out(dims_spec.dims)
replicated_axes: frozenset[ParallelAxis] = dims_spec.replicated_axes
parallel_infos = [normalize_parallel_info(meta) for meta in metas]
dp_axis: ParallelAxis = (
ParallelAxis(dims_spec.dp_group_alias)
if dims_spec.dp_group_alias
else ParallelAxis.DP
)
unsharder_plans = compute_unsharder_plan(
dim_specs=dim_specs,
parallel_infos=parallel_infos,
explicit_replicated_axes=replicated_axes,
thd_global_seq_lens=thd_global_seq_lens,
dp_filtered_axis=dims_spec.dp_axis,
)
reorderer_plans = compute_reorderer_plans(
dim_specs=dim_specs,
parallel_infos=parallel_infos,
thd_global_seq_lens=thd_global_seq_lens,
)
return [*unsharder_plans, *reorderer_plans]
@@ -0,0 +1,37 @@
"""Traced wrapper types that embed execution traces (ShapeSnapshots) into plan nodes.
These types are created *after* execution, pairing each sub-plan with its
observed shape snapshot so that downstream formatters never need to manually
zip plan + trace by index.
"""
from __future__ import annotations
from typing import Optional
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
AlignerPerStepSubPlan,
AlignerPlan,
)
from sglang.srt.debug_utils.comparator.output_types import ShapeSnapshot
from sglang.srt.debug_utils.comparator.utils import Pair, _StrictBase
class TracedSubPlan(_StrictBase):
plan: AlignerPerStepSubPlan
snapshot: Optional[ShapeSnapshot] = None
class TracedStepPlan(_StrictBase):
step: int
input_object_indices: list[int]
sub_plans: list[TracedSubPlan]
class TracedSidePlan(_StrictBase):
step_plans: list[TracedStepPlan]
class TracedAlignerPlan(_StrictBase):
plan: AlignerPlan
per_side: Pair[TracedSidePlan]
@@ -0,0 +1,31 @@
from __future__ import annotations
from typing import Annotated, Optional, Union
from pydantic import Discriminator
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.token_aligner.smart.types import (
TokenAlignerPlan,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
from sglang.srt.debug_utils.comparator.utils import Pair, _FrozenBase
AlignerPerStepSubPlan = Annotated[
Union[UnsharderPlan, ReordererPlan],
Discriminator("type"),
]
class AlignerPerStepPlan(_FrozenBase):
step: int
input_object_indices: list[int]
sub_plans: list[AlignerPerStepSubPlan]
class AlignerPlan(_FrozenBase):
per_step_plans: Pair[list[AlignerPerStepPlan]]
token_aligner_mode: Optional[str] = None # "concat_steps" | "smart" | None
token_aligner_plan: Optional[TokenAlignerPlan] = None
axis_aligner_plan: Optional[AxisAlignerPlan] = None
@@ -0,0 +1,103 @@
from typing import Optional
import torch
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import (
ReordererPlan,
ZigzagToNaturalParams,
ZigzagToNaturalThdParams,
)
from sglang.srt.debug_utils.comparator.dims_spec import (
apply_dim_names,
get_dim_names,
resolve_dim_by_name,
without_dim_names,
)
def execute_reorderer_plan(
plan: ReordererPlan,
tensors: list[torch.Tensor],
) -> list[torch.Tensor]:
if isinstance(plan.params, ZigzagToNaturalThdParams):
thd_dim: int = resolve_dim_by_name(tensors[0], plan.params.dim_name)
return [
_reorder_zigzag_to_natural_thd(
tensor,
dim=thd_dim,
cp_size=plan.params.cp_size,
seq_lens=plan.params.seq_lens,
)
for tensor in tensors
]
if isinstance(plan.params, ZigzagToNaturalParams):
dim: int = resolve_dim_by_name(tensors[0], plan.params.dim_name)
return [
_reorder_zigzag_to_natural(tensor, dim=dim, cp_size=plan.params.cp_size)
for tensor in tensors
]
raise ValueError(f"Unsupported reorderer params type: {type(plan.params).__name__}")
def _reorder_zigzag_to_natural_thd(
tensor: torch.Tensor, *, dim: int, cp_size: int, seq_lens: list[int]
) -> torch.Tensor:
"""Undo CP zigzag interleaving for THD (packed-seq) format.
Each seq in seq_lens is independently reordered from zigzag to natural order
along the given dim.
"""
names: tuple[Optional[str], ...] = get_dim_names(tensor)
stripped: torch.Tensor = without_dim_names(tensor)
split_sizes: list[int] = list(seq_lens)
remainder: int = stripped.shape[dim] - sum(split_sizes)
if remainder < 0:
raise ValueError(
f"sum(seq_lens)={sum(split_sizes)} exceeds tensor dim size "
f"{stripped.shape[dim]} along dim={dim}"
)
if remainder > 0:
split_sizes.append(remainder)
segments: list[torch.Tensor] = list(stripped.split(split_sizes, dim=dim))
reordered_segments: list[torch.Tensor] = [
_reorder_zigzag_to_natural(seg, dim=dim, cp_size=cp_size)
for seg in segments[: len(seq_lens)]
]
# Tail padding — pass through unchanged
if remainder > 0:
reordered_segments.append(segments[-1])
result: torch.Tensor = torch.cat(reordered_segments, dim=dim)
if names[0] is not None:
result = apply_dim_names(result, list(names))
return result
def _reorder_zigzag_to_natural(
tensor: torch.Tensor, *, dim: int, cp_size: int
) -> torch.Tensor:
"""Undo CP zigzag interleaving, restoring natural chunk order.
Generalized from Megatron-LM _undo_attention_load_balancing
(megatron/core/ssm/mamba_context_parallel.py:360-373).
"""
names: tuple[Optional[str], ...] = get_dim_names(tensor)
stripped: torch.Tensor = without_dim_names(tensor)
num_chunks: int = cp_size * 2
chunks: tuple[torch.Tensor, ...] = stripped.chunk(num_chunks, dim=dim)
order: list[int] = [2 * i for i in range(cp_size)] + [
num_chunks - 2 * i - 1 for i in range(cp_size)
]
result: torch.Tensor = torch.cat([chunks[i] for i in order], dim=dim)
if names[0] is not None:
result = apply_dim_names(result, list(names))
return result
@@ -0,0 +1,67 @@
from typing import Optional
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import (
ReordererPlan,
ZigzagToNaturalParams,
ZigzagToNaturalThdParams,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import AxisInfo
from sglang.srt.debug_utils.comparator.dims_spec import (
SEQ_DIM_NAME,
TOKEN_DIM_NAME,
DimSpec,
Ordering,
ParallelAxis,
)
_ALLOWED_ZIGZAG_DIM_NAMES: set[str] = {SEQ_DIM_NAME, TOKEN_DIM_NAME}
def compute_reorderer_plans(
dim_specs: list[DimSpec],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
*,
thd_global_seq_lens: Optional[list[int]] = None,
) -> list[ReordererPlan]:
plans: list[ReordererPlan] = []
for spec in dim_specs:
for modifier in spec.parallel_modifiers:
if modifier.ordering is None or modifier.ordering == Ordering.NATURAL:
continue
if spec.name not in _ALLOWED_ZIGZAG_DIM_NAMES:
raise ValueError(
f"Zigzag ordering is only supported on sequence dims "
f"(dim name must be one of "
f"{sorted(_ALLOWED_ZIGZAG_DIM_NAMES)}), "
f"but got dim name {spec.name!r} in {spec}"
)
if modifier.ordering != Ordering.ZIGZAG:
raise ValueError(
f"Unsupported ordering {modifier.ordering!r} for dim {spec.name!r}"
)
axis_size: int = parallel_infos[0][modifier.axis].axis_size
if spec.name == TOKEN_DIM_NAME:
if thd_global_seq_lens is None:
raise ValueError(
"thd_global_seq_lens is required for zigzag reorder on 't' dimension"
)
params = ZigzagToNaturalThdParams(
dim_name=spec.name,
cp_size=axis_size,
seq_lens=thd_global_seq_lens,
)
elif spec.name == SEQ_DIM_NAME:
params = ZigzagToNaturalParams(dim_name=spec.name, cp_size=axis_size)
else:
raise ValueError(
f"Unsupported zigzag dim name {spec.name!r}, "
f"expected one of {sorted(_ALLOWED_ZIGZAG_DIM_NAMES)}"
)
plans.append(ReordererPlan(params=params))
return plans
@@ -0,0 +1,29 @@
from typing import Annotated, Literal, Union
from pydantic import Field
from sglang.srt.debug_utils.comparator.utils import _FrozenBase
class ZigzagToNaturalParams(_FrozenBase):
op: Literal["zigzag_to_natural"] = "zigzag_to_natural"
dim_name: str
cp_size: int
class ZigzagToNaturalThdParams(_FrozenBase):
op: Literal["zigzag_to_natural_thd"] = "zigzag_to_natural_thd"
dim_name: str
cp_size: int
seq_lens: list[int] # unshard-ed per-seq token counts, e.g. [100, 64, 92]
ReordererParams = Annotated[
Union[ZigzagToNaturalParams, ZigzagToNaturalThdParams],
Field(discriminator="op"),
]
class ReordererPlan(_FrozenBase):
type: Literal["reorderer"] = "reorderer"
params: ReordererParams
@@ -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",
]
@@ -0,0 +1,45 @@
from __future__ import annotations
from typing import Optional
import torch
from sglang.srt.debug_utils.comparator.dims_spec import (
SEQ_DIM_NAME,
TOKEN_DIM_NAME,
get_dim_names,
)
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."""
names: tuple[Optional[str], ...] = get_dim_names(tensor)
if names[0] is None:
return _UNNAMED_TOKEN_DIM_FALLBACK
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)
@@ -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
@@ -0,0 +1,132 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Literal, Optional
import polars as pl
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,
load_and_normalize_aux,
)
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.planner import (
compute_token_aligner_plan,
)
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.seq_info_builder import (
build_seqs_info,
)
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
TokenAlignerGlobalAux,
TokenAlignerPlan,
TokenAlignerSeqsInfo,
)
from sglang.srt.debug_utils.comparator.log_sink import log_sink
from sglang.srt.debug_utils.comparator.output_types import InfoLog
from sglang.srt.debug_utils.comparator.utils import Pair
_NONE_THD: Pair[Optional[dict[int, list[int]]]] = Pair(x=None, y=None)
TokenAlignerMode = Literal["concat_steps", "smart"]
@dataclass(frozen=True)
class TokenAlignerResult:
"""Result of token aligner computation, bundling mode + plan with THD metadata."""
mode: Optional[TokenAlignerMode]
plan: Optional[TokenAlignerPlan]
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]]
def compute_maybe_token_aligner_result(
*,
dir_pair: Pair[Path],
dfs: Pair[pl.DataFrame],
token_aligner_mode: Optional[TokenAlignerMode],
) -> TokenAlignerResult:
if token_aligner_mode is None:
return TokenAlignerResult(
mode=None, plan=None, thd_seq_lens_by_step_pair=_NONE_THD
)
if token_aligner_mode == "concat_steps":
thd_pair: Pair[Optional[dict[int, list[int]]]] = _load_thd_seq_lens_pair(
dir_pair=dir_pair, 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)):
log_sink.add(
InfoLog(
category="aux_tensors_missing",
message="Aux tensors missing, skipping token alignment",
)
)
return TokenAlignerResult(
mode=None, plan=None, thd_seq_lens_by_step_pair=_NONE_THD
)
return _build_smart_result(dir_pair=dir_pair, dfs=dfs)
else:
raise NotImplementedError(f"Unknown {token_aligner_mode=}")
def _build_smart_result(
*,
dir_pair: Pair[Path],
dfs: Pair[pl.DataFrame],
) -> TokenAlignerResult:
"""Load aux tensors, build token indices, and compute the alignment plan."""
aux_pair: Pair[Optional[TokenAlignerGlobalAux]] = Pair(
x=load_and_normalize_aux(dump_path=dir_pair.x, df=dfs.x),
y=load_and_normalize_aux(dump_path=dir_pair.y, df=dfs.y),
)
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = aux_pair.map(
lambda aux: aux.thd_seq_lens_by_step if aux is not None else None
)
if aux_pair.x is None or aux_pair.y is None:
log_sink.add(
InfoLog(
category="framework_detection_failed",
message="Framework detection failed, skipping token alignment",
)
)
return TokenAlignerResult(
mode=None,
plan=None,
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
)
global_aux: Pair[TokenAlignerGlobalAux] = Pair(x=aux_pair.x, y=aux_pair.y)
seqs_info: Pair[TokenAlignerSeqsInfo] = global_aux.map(build_seqs_info)
plan: Optional[TokenAlignerPlan] = compute_token_aligner_plan(
seqs_info_pair=seqs_info
)
return TokenAlignerResult(
mode="smart",
plan=plan,
thd_seq_lens_by_step_pair=thd_seq_lens_by_step_pair,
)
def _load_thd_seq_lens_pair(
*,
dir_pair: Pair[Path],
dfs: Pair[pl.DataFrame],
) -> Pair[Optional[dict[int, list[int]]]]:
"""Load only thd_seq_lens for each side (lightweight, no full aux loading)."""
return Pair(
x=load_thd_seq_lens_only(dump_path=dir_pair.x, df=dfs.x),
y=load_thd_seq_lens_only(dump_path=dir_pair.y, df=dfs.y),
)
@@ -0,0 +1,286 @@
from __future__ import annotations
from pathlib import Path
from typing import Any, Optional
import polars as pl
import torch
from sglang.srt.debug_utils.comparator.aligner.entrypoint.executor import (
execute_sub_plans,
)
from sglang.srt.debug_utils.comparator.aligner.entrypoint.planner import (
compute_per_step_sub_plans,
)
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.aux_plugins import (
AUX_NAMES,
_AuxFrameworkPlugin,
_plugins,
)
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
TokenAlignerGlobalAux,
TokenAlignerStepAux,
)
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
normalize_parallel_info,
)
from sglang.srt.debug_utils.comparator.dims_spec import (
ParallelAxis,
TokenLayout,
apply_dim_names,
resolve_dim_names,
without_dim_names,
)
from sglang.srt.debug_utils.comparator.dp_utils import filter_to_non_empty_dp_rank
from sglang.srt.debug_utils.comparator.log_sink import log_sink
from sglang.srt.debug_utils.comparator.output_types import ErrorLog, InfoLog
from sglang.srt.debug_utils.dump_loader import ValueWithMeta, filter_rows
# re-export for existing callers
__all__ = [
"AUX_NAMES",
"has_aux_tensors",
"load_and_normalize_aux",
]
def load_and_normalize_aux(
dump_path: Path, df: pl.DataFrame
) -> Optional[TokenAlignerGlobalAux]:
"""Bootstrap: load, unshard, and normalize auxiliary tensors for one side."""
plugin: Optional[_AuxFrameworkPlugin] = _detect_plugin(df, dump_path=dump_path)
if plugin is None:
return None
available_names: set[str] = set(df["name"].unique().to_list()) & plugin.all_names
steps: list[int] = sorted(df["step"].unique().to_list())
tensor_names: set[str] = available_names & plugin.tensor_names
non_tensor_names: set[str] = available_names & plugin.non_tensor_names
steps_data: dict[int, dict[str, object]] = {}
thd_seq_lens_by_step: dict[int, list[int]] = {}
for step in steps:
step_data, thd_seq_lens = _load_step_data(
step=step,
tensor_names=tensor_names,
non_tensor_names=non_tensor_names,
df=df,
dump_path=dump_path,
plugin=plugin,
)
if step_data:
steps_data[step] = step_data
if thd_seq_lens is not None:
thd_seq_lens_by_step[step] = thd_seq_lens
layout: TokenLayout = plugin.detect_layout(steps_data)
step_auxs: dict[int, TokenAlignerStepAux] = {
step: plugin.compute_step_aux(step_data, layout=layout, step=step)
for step, step_data in steps_data.items()
}
return TokenAlignerGlobalAux(
step_auxs=step_auxs,
framework=plugin.name,
layout=layout,
thd_seq_lens_by_step=thd_seq_lens_by_step or None,
)
def has_aux_tensors(df: pl.DataFrame) -> bool:
"""Check if the DataFrame contains the minimum auxiliary tensors for alignment."""
names: set[str] = set(df["name"].unique().to_list())
return any(plugin.has_required_names(names) for plugin in _plugins)
def _detect_plugin(df: pl.DataFrame, dump_path: Path) -> Optional[_AuxFrameworkPlugin]:
names: set[str] = set(df["name"].unique().to_list())
for plugin in _plugins:
if names & plugin.discriminating_names:
return plugin
first_row: dict = df.row(0, named=True)
value: ValueWithMeta = ValueWithMeta.load(dump_path / first_row["filename"])
for plugin in _plugins:
if f"{plugin.name}_parallel_info" in value.meta:
return plugin
return None
def _load_step_data(
*,
step: int,
tensor_names: set[str],
non_tensor_names: set[str],
df: pl.DataFrame,
dump_path: Path,
plugin: _AuxFrameworkPlugin,
) -> tuple[dict[str, object], Optional[list[int]]]:
"""Load all tensor and non-tensor aux values for a single step.
Two-pass loading: non-CP-sharded tensors first (to obtain cu_seqlens_q
for seq_lens), then CP-sharded tensors with seq_lens for THD unshard/reorder.
Returns (step_data, thd_global_seq_lens).
"""
result: dict[str, object] = {}
# Pass 0: non-tensor values
for name in non_tensor_names:
value = _load_non_tensor_aux(name=name, step=step, df=df, dump_path=dump_path)
if value is not None:
result[name] = value
# Pass 1: non-CP-sharded tensors (e.g. cu_seqlens_q, seq_lens)
non_cp_tensor_names: set[str] = tensor_names - plugin.cp_sharded_names
cp_tensor_names: set[str] = tensor_names & plugin.cp_sharded_names
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:
result[name] = tensor
# Derive global seq_lens for THD unshard (framework-specific extraction)
thd_global_seq_lens: Optional[list[int]] = plugin.extract_global_seq_lens(result)
# Pass 2: CP-sharded tensors (input_ids, position_ids, etc.)
for name in cp_tensor_names:
tensor = _load_and_align_aux_tensor(
name=name,
step=step,
df=df,
dump_path=dump_path,
plugin=plugin,
thd_global_seq_lens=thd_global_seq_lens,
)
if tensor is not None:
result[name] = tensor
return result, thd_global_seq_lens
def _load_non_tensor_aux(
*, name: str, step: int, df: pl.DataFrame, dump_path: Path
) -> Optional[object]:
"""Load a non-tensor auxiliary value for a step, validating consistency across ranks."""
rows = filter_rows(df, conditions={"name": name, "step": step})
if not rows:
return None
loaded: list[ValueWithMeta] = [
ValueWithMeta.load(dump_path / r["filename"]) for r in rows
]
loaded = filter_to_non_empty_dp_rank(loaded, dp_axis=ParallelAxis.DP)
if len(loaded) > 1:
first_value = loaded[0].value
for i, item in enumerate(loaded[1:], start=1):
if item.value != first_value:
log_sink.add(
ErrorLog(
category=f"{name}_mismatch",
message=(
f"{name} mismatch across ranks: rank 0 has {first_value}, "
f"rank {i} has {item.value}"
),
)
)
break
return loaded[0].value
def _load_and_align_aux_tensor(
*,
name: str,
step: int,
df: pl.DataFrame,
dump_path: Path,
plugin: _AuxFrameworkPlugin,
thd_global_seq_lens: Optional[list[int]] = None,
) -> Optional[torch.Tensor]:
"""Load an auxiliary tensor for (name, step), align if needed."""
rows = filter_rows(df, conditions={"name": name, "step": step})
if not rows:
return None
loaded: list[ValueWithMeta] = [
ValueWithMeta.load(dump_path / r["filename"]) for r in rows
]
loaded = filter_to_non_empty_dp_rank(loaded, dp_axis=ParallelAxis.DP)
tensors: list[torch.Tensor] = [
item.value for item in loaded if isinstance(item.value, torch.Tensor)
]
if not tensors:
return None
if len(tensors) == 1:
return tensors[0]
metas: list[dict[str, Any]] = [item.meta for item in loaded]
metas = _ensure_dims_in_metas(
name=name, plugin=plugin, metas=metas, ndim=tensors[0].ndim
)
sub_plans = compute_per_step_sub_plans(
metas=metas,
thd_global_seq_lens=(
thd_global_seq_lens if name in plugin.cp_sharded_names else None
),
)
if sub_plans:
dims_str: Optional[str] = metas[0].get("dims")
if dims_str is not None:
dim_names: list[str] = resolve_dim_names(dims_str)
tensors = [apply_dim_names(t, dim_names) for t in tensors]
sub_result = execute_sub_plans(tensors=tensors, plans=sub_plans)
assert sub_result.tensor is not None
return without_dim_names(
sub_result.tensor
) # strip named dims before returning to plugin
log_sink.add(
InfoLog(
category="aux_no_dims",
message=(
f"aux tensor '{name}' has {len(tensors)} ranks "
f"but no dims metadata, using rank 0 only"
),
)
)
return tensors[0]
def _ensure_dims_in_metas(
*,
name: str,
plugin: _AuxFrameworkPlugin,
metas: list[dict[str, Any]],
ndim: int,
) -> list[dict[str, Any]]:
"""Inject inferred dims into metas if not already present.
Returns metas unchanged if dims is already set, or a new list with dims
injected if inference succeeds for CP-sharded tensors.
"""
if metas[0].get("dims") is not None:
return metas
parallel_infos = [normalize_parallel_info(m) for m in metas]
has_cp: bool = any(ParallelAxis.CP in info for info in parallel_infos)
if not has_cp:
return metas
if name in plugin.cp_sharded_names:
inferred_dims: str = plugin.infer_cp_sharded_dims(name=name, ndim=ndim)
return [{**m, "dims": inferred_dims} for m in metas]
return metas
@@ -0,0 +1,292 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Optional
import torch
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
PositionalSeqId,
SeqId,
SGLangSeqId,
TokenAlignerStepAux,
)
from sglang.srt.debug_utils.comparator.dims_spec import TokenLayout
from sglang.srt.debug_utils.comparator.log_sink import log_sink
from sglang.srt.debug_utils.comparator.output_types import InfoLog
# ── plugin ABC ─────────────────────────────────────────────────────
class _AuxFrameworkPlugin(ABC):
@property
@abstractmethod
def name(self) -> str: ...
@property
@abstractmethod
def tensor_names(self) -> frozenset[str]: ...
@property
@abstractmethod
def non_tensor_names(self) -> frozenset[str]: ...
@property
def cp_sharded_names(self) -> frozenset[str]:
return frozenset()
@property
def discriminating_names(self) -> frozenset[str]:
"""Field names unique to this framework (excluding shared names like input_ids)."""
return frozenset()
@abstractmethod
def detect_layout(self, raw: dict[int, dict[str, object]]) -> TokenLayout: ...
@abstractmethod
def compute_step_aux(
self, step_data: dict[str, object], *, layout: TokenLayout, step: int
) -> TokenAlignerStepAux: ...
@abstractmethod
def has_required_names(self, names: set[str]) -> bool:
"""Whether the minimum set of aux names needed for alignment is present."""
...
@property
def all_names(self) -> frozenset[str]:
return self.tensor_names | self.non_tensor_names
def extract_global_seq_lens(
self, step_data: dict[str, object]
) -> Optional[list[int]]:
"""Extract per-seq token counts from loaded step data.
Returns None if this framework doesn't support THD / no relevant data available.
"""
return None
def infer_cp_sharded_dims(self, name: str, ndim: int) -> str:
"""Infer dims string for a CP-sharded aux tensor based on its ndim."""
raise NotImplementedError(
f"infer_cp_sharded_dims not implemented for {type(self).__name__}"
)
# ── sglang plugin ─────────────────────────────────────────────────
class _SGLangPlugin(_AuxFrameworkPlugin):
@property
def name(self) -> str:
return "sglang"
@property
def tensor_names(self) -> frozenset[str]:
return frozenset({"input_ids", "positions", "seq_lens", "req_pool_indices"})
@property
def non_tensor_names(self) -> frozenset[str]:
return frozenset({"rids"})
@property
def cp_sharded_names(self) -> frozenset[str]:
return frozenset({"input_ids", "positions"})
@property
def discriminating_names(self) -> frozenset[str]:
return frozenset({"seq_lens", "positions", "req_pool_indices", "rids"})
def has_required_names(self, names: set[str]) -> bool:
return "input_ids" in names and "seq_lens" in names
def detect_layout(self, raw: dict[int, dict[str, object]]) -> TokenLayout:
return TokenLayout.T
def extract_global_seq_lens(
self, step_data: dict[str, object]
) -> Optional[list[int]]:
if not self.cp_sharded_names:
return None
seq_lens = step_data.get("seq_lens")
if not isinstance(seq_lens, torch.Tensor):
return None
return seq_lens.tolist()
def infer_cp_sharded_dims(self, name: str, ndim: int) -> str:
"""Infer dims for CP-sharded aux tensors.
NOTE: assumes zigzag ordering — natural-order CP without explicit dims
will be mishandled. Callers should set dims explicitly for non-zigzag CP.
"""
if ndim == 1:
return "t[cp:zigzag]"
raise ValueError(
f"SGLang: cannot infer dims for CP-sharded '{name}' with ndim={ndim}"
)
def compute_step_aux(
self, step_data: dict[str, object], *, layout: TokenLayout, step: int
) -> TokenAlignerStepAux:
input_ids = step_data["input_ids"]
positions = step_data["positions"]
seq_lens = step_data["seq_lens"]
rids_raw = step_data.get("rids")
assert isinstance(
input_ids, torch.Tensor
), f"input_ids: expected Tensor, got {type(input_ids)}"
assert isinstance(
positions, torch.Tensor
), f"positions: expected Tensor, got {type(positions)}"
assert isinstance(
seq_lens, torch.Tensor
), f"seq_lens: expected Tensor, got {type(seq_lens)}"
seq_lens_list: list[int] = seq_lens.tolist()
num_seqs: int = len(seq_lens_list)
seq_ids: list[SeqId]
if rids_raw is not None and isinstance(rids_raw, (list, tuple)):
seq_ids = [SGLangSeqId(rid=str(r)) for r in rids_raw]
else:
seq_ids = [PositionalSeqId(step=step, seq_index=i) for i in range(num_seqs)]
return TokenAlignerStepAux(
input_ids=input_ids.tolist(),
positions=positions.tolist(),
seq_lens=seq_lens_list,
seq_ids=seq_ids,
)
# ── megatron plugin ───────────────────────────────────────────────
class _MegatronPlugin(_AuxFrameworkPlugin):
@property
def name(self) -> str:
return "megatron"
@property
def tensor_names(self) -> frozenset[str]:
return frozenset({"input_ids", "position_ids", "cu_seqlens_q", "cu_seqlens_kv"})
@property
def non_tensor_names(self) -> frozenset[str]:
return frozenset({"qkv_format"})
@property
def cp_sharded_names(self) -> frozenset[str]:
return frozenset({"input_ids", "position_ids"})
@property
def discriminating_names(self) -> frozenset[str]:
return frozenset({"cu_seqlens_q", "cu_seqlens_kv", "qkv_format"})
def has_required_names(self, names: set[str]) -> bool:
return "input_ids" in names
def extract_global_seq_lens(
self, step_data: dict[str, object]
) -> Optional[list[int]]:
if not self.cp_sharded_names:
return None
cu_seqlens_q = step_data.get("cu_seqlens_q")
if not isinstance(cu_seqlens_q, torch.Tensor):
return None
return (cu_seqlens_q[1:] - cu_seqlens_q[:-1]).tolist()
def infer_cp_sharded_dims(self, name: str, ndim: int) -> str:
"""Infer dims for CP-sharded aux tensors.
NOTE: assumes zigzag ordering — natural-order CP without explicit dims
will be mishandled. Callers should set dims explicitly for non-zigzag CP.
"""
if ndim == 1:
return "t[cp:zigzag]"
if ndim == 2:
return "b s[cp:zigzag]"
raise ValueError(
f"Megatron: cannot infer dims for CP-sharded '{name}' with ndim={ndim}"
)
def detect_layout(self, raw: dict[int, dict[str, object]]) -> TokenLayout:
for step_data in raw.values():
if (qkv_format := step_data.get("qkv_format")) is not None:
fmt = qkv_format if isinstance(qkv_format, str) else str(qkv_format)
if "bshd" in fmt.lower():
return TokenLayout.BS
return TokenLayout.T
input_ids = step_data.get("input_ids")
if isinstance(input_ids, torch.Tensor) and input_ids.ndim == 2:
return TokenLayout.BS
log_sink.add(
InfoLog(
category="layout_detection_fallback",
message=(
"Megatron layout detection: no qkv_format or 2D input_ids found, "
"falling back to T"
),
)
)
return TokenLayout.T
def compute_step_aux(
self, step_data: dict[str, object], *, layout: TokenLayout, step: int
) -> TokenAlignerStepAux:
input_ids: torch.Tensor = step_data["input_ids"]
is_bshd: bool = layout == TokenLayout.BS
# BSHD [B, S] → flat [B*S]; THD [T] stays as-is
flat_ids: list[int] = input_ids.reshape(-1).tolist()
if (cu_seqlens_q := step_data.get("cu_seqlens_q")) is not None:
seq_lens_list: list[int] = (cu_seqlens_q[1:] - cu_seqlens_q[:-1]).tolist()
elif is_bshd:
seq_lens_list = [input_ids.shape[1]] * input_ids.shape[0]
else:
seq_lens_list = [input_ids.shape[0]]
if (position_ids := step_data.get("position_ids")) is not None:
flat_positions: list[int] = position_ids.reshape(-1).tolist()
elif is_bshd:
flat_positions = list(range(input_ids.shape[1])) * input_ids.shape[0]
else:
flat_positions = _infer_positions(
seq_lens=torch.tensor(seq_lens_list)
).tolist()
num_seqs: int = len(seq_lens_list)
seq_ids: list[SeqId] = [
PositionalSeqId(step=step, seq_index=seq_index)
for seq_index in range(num_seqs)
]
return TokenAlignerStepAux(
input_ids=flat_ids,
positions=flat_positions,
seq_lens=seq_lens_list,
seq_ids=seq_ids,
)
# ── plugin registry ───────────────────────────────────────────────
_plugins: list[_AuxFrameworkPlugin] = [_SGLangPlugin(), _MegatronPlugin()]
AUX_NAMES: frozenset[str] = frozenset().union(*(p.all_names for p in _plugins))
# ── helpers ────────────────────────────────────────────────────────
def _infer_positions(*, seq_lens: torch.Tensor) -> torch.Tensor:
"""Infer positions when position_ids is missing (THD only)."""
return torch.cat([torch.arange(int(slen.item())) for slen in seq_lens])
@@ -0,0 +1,150 @@
from __future__ import annotations
import torch
from einops import rearrange
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
TokenAlignerPlan,
TokenLocator,
)
from sglang.srt.debug_utils.comparator.dims_spec import (
BATCH_DIM_NAME,
SEQ_DIM_NAME,
TOKEN_DIM_NAME,
TokenLayout,
apply_dim_names,
get_dim_names,
resolve_dim_by_name,
)
from sglang.srt.debug_utils.comparator.utils import Pair
_UNNAMED_TOKEN_DIM_FALLBACK: int = 0
def execute_token_aligner(
plan: TokenAlignerPlan,
tensor_of_step_pair: Pair[dict[int, torch.Tensor]],
) -> Pair[torch.Tensor]:
flat_pair: Pair[dict[int, torch.Tensor]] = Pair(
x=_collapse_bs_to_t(
tensor_of_step=tensor_of_step_pair.x, layout=plan.layouts.x
),
y=_collapse_bs_to_t(
tensor_of_step=tensor_of_step_pair.y, layout=plan.layouts.y
),
)
if not plan.locators.x.steps:
return Pair(
x=_make_empty(tensor_of_step=flat_pair.x),
y=_make_empty(tensor_of_step=flat_pair.y),
)
return Pair(
x=_extract_and_stack_tokens(
tensor_of_step=flat_pair.x, locator=plan.locators.x
),
y=_extract_and_stack_tokens(
tensor_of_step=flat_pair.y, locator=plan.locators.y
),
)
# ── BS → T preprocessing ─────────────────────────────────────────
def _collapse_bs_to_t(
*,
tensor_of_step: dict[int, torch.Tensor],
layout: TokenLayout,
) -> dict[int, torch.Tensor]:
"""Collapse B and S dims into a single flat token dim (always batch-major).
Handles both ``b s`` and ``s b`` orderings correctly via einops rearrange.
Returns the original tensors unchanged if layout is T.
"""
if layout != TokenLayout.BS:
return tensor_of_step
some_tensor: torch.Tensor = next(iter(tensor_of_step.values()))
batch_dim: int = _resolve_dim_or_fallback(some_tensor, BATCH_DIM_NAME)
seq_dim: int = _resolve_dim_or_fallback(some_tensor, SEQ_DIM_NAME)
if abs(batch_dim - seq_dim) != 1:
raise ValueError(
f"BS dims must be adjacent: "
f"{BATCH_DIM_NAME}={batch_dim}, "
f"{SEQ_DIM_NAME}={seq_dim}"
)
lhs_pattern, rhs_pattern, new_names = _build_bs_collapse_pattern(
names=list(get_dim_names(some_tensor)),
batch_dim=batch_dim,
seq_dim=seq_dim,
)
result: dict[int, torch.Tensor] = {}
for step, tensor in tensor_of_step.items():
collapsed: torch.Tensor = rearrange(tensor, f"{lhs_pattern} -> {rhs_pattern}")
collapsed = apply_dim_names(collapsed, [n for n in new_names if n is not None])
result[step] = collapsed
return result
def _build_bs_collapse_pattern(
*,
names: list[str | None],
batch_dim: int,
seq_dim: int,
) -> tuple[str, str, list[str | None]]:
"""Build einops lhs/rhs patterns and output dim names for BS→T collapse.
Always produces batch-major order ``(b s)`` regardless of input ordering.
Uses the tensor's own dim names as einops axis names.
"""
lo: int = min(batch_dim, seq_dim)
hi: int = max(batch_dim, seq_dim)
lhs: str = " ".join(names) # type: ignore[arg-type]
rhs_names: list[str] = list(names[:lo]) + [f"({BATCH_DIM_NAME} {SEQ_DIM_NAME})"] + list(names[hi + 1 :]) # type: ignore[misc]
rhs: str = " ".join(rhs_names)
new_names: list[str | None] = (
list(names[:lo]) + [TOKEN_DIM_NAME] + list(names[hi + 1 :])
)
return lhs, rhs, new_names
# ── core logic (T layout only) ───────────────────────────────────
def _resolve_dim_or_fallback(tensor: torch.Tensor, name: str) -> int:
if get_dim_names(tensor)[0] is None:
return _UNNAMED_TOKEN_DIM_FALLBACK
return resolve_dim_by_name(tensor, name)
def _make_empty(*, tensor_of_step: dict[int, torch.Tensor]) -> torch.Tensor:
dummy: torch.Tensor = next(iter(tensor_of_step.values()))
token_dim: int = _resolve_dim_or_fallback(dummy, TOKEN_DIM_NAME)
shape: list[int] = list(dummy.shape)
shape[token_dim] = 0
return torch.empty(shape, dtype=dummy.dtype)
def _extract_and_stack_tokens(
*,
tensor_of_step: dict[int, torch.Tensor],
locator: TokenLocator,
) -> torch.Tensor:
some_tensor: torch.Tensor = next(iter(tensor_of_step.values()))
token_dim: int = _resolve_dim_or_fallback(some_tensor, TOKEN_DIM_NAME)
tokens: list[torch.Tensor] = [
tensor_of_step[s].select(dim=token_dim, index=i)
for s, i in zip(locator.steps, locator.token_index_in_step)
]
return torch.stack(tokens, dim=token_dim)
@@ -0,0 +1,135 @@
from __future__ import annotations
from collections import defaultdict
from typing import NamedTuple, Optional
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
SeqId,
TokenAlignerPlan,
TokenAlignerSeqInfo,
TokenAlignerSeqsInfo,
TokenLocator,
)
from sglang.srt.debug_utils.comparator.utils import Pair
def compute_token_aligner_plan(
seqs_info_pair: Pair[TokenAlignerSeqsInfo],
) -> TokenAlignerPlan:
"""Compute a token alignment plan from two side token seqs_info_pair."""
matched_pairs: list[tuple[SeqId, SeqId]] = _match_sequences(
seqs=Pair(x=seqs_info_pair.x.sequences, y=seqs_info_pair.y.sequences)
)
_empty = TokenLocator(steps=[], token_index_in_step=[])
locator_x: TokenLocator = _empty
locator_y: TokenLocator = _empty
for seq_id_x, seq_id_y in matched_pairs:
rec: Pair[TokenAlignerSeqInfo] = Pair(
x=seqs_info_pair.x.sequences[seq_id_x],
y=seqs_info_pair.y.sequences[seq_id_y],
)
# positions is validated to be [0, 1, ..., N-1], so position == index
# and the common range is simply [0, min(len_x, len_y)).
common_len: int = min(len(rec.x.positions), len(rec.y.positions))
x_ids = rec.x.input_ids[:common_len]
y_ids = rec.y.input_ids[:common_len]
assert x_ids == y_ids, f"{seq_id_x=} {seq_id_y=} {x_ids=} {y_ids=}"
locator_x = locator_x + TokenLocator(
steps=rec.x.locator.steps[:common_len],
token_index_in_step=rec.x.locator.token_index_in_step[:common_len],
)
locator_y = locator_y + TokenLocator(
steps=rec.y.locator.steps[:common_len],
token_index_in_step=rec.y.locator.token_index_in_step[:common_len],
)
return TokenAlignerPlan(
locators=Pair(x=locator_x, y=locator_y),
layouts=seqs_info_pair.map(lambda s: s.layout),
)
# -------------------- Sequence matcher --------------------
def _match_sequences(
seqs: Pair[dict[SeqId, TokenAlignerSeqInfo]],
) -> list[tuple[SeqId, SeqId]]:
"""For each y (target) sequence, find a matching x (baseline) sequence.
Two-pass: exact match first, then prefix match for remaining.
"""
x_lookup: dict[tuple[int, ...], list[SeqId]] = defaultdict(list)
for seq_id, rec in seqs.x.items():
x_lookup[tuple(rec.input_ids)].append(seq_id)
claimed_x_ids: set[SeqId] = set()
matched_seq_id_pairs: list[tuple[SeqId, SeqId]] = []
for seq_id_y in sorted(seqs.y.keys()):
seq_y: TokenAlignerSeqInfo = seqs.y[seq_id_y]
matched_x: Optional[SeqId] = _find_matching_x_exact(
seq_y=seq_y, x_lookup=x_lookup, claimed_x_ids=claimed_x_ids
)
if matched_x is None:
matched_x = _find_matching_x_prefix(
seq_y=seq_y, x_seqs=seqs.x, claimed_x_ids=claimed_x_ids
)
if matched_x is not None:
matched_seq_id_pairs.append((matched_x, seq_id_y))
claimed_x_ids.add(matched_x)
return matched_seq_id_pairs
def _find_matching_x_exact(
*,
seq_y: TokenAlignerSeqInfo,
x_lookup: dict[tuple[int, ...], list[SeqId]],
claimed_x_ids: set[SeqId],
) -> Optional[SeqId]:
"""Find an x sequence with identical input_ids."""
ids_y_key: tuple[int, ...] = tuple(seq_y.input_ids)
candidates: list[SeqId] = x_lookup.get(ids_y_key, [])
for candidate in candidates:
if candidate not in claimed_x_ids:
return candidate
return None
class _PrefixCandidate(NamedTuple):
seq_id_x: SeqId
overlap_len: int
def _find_matching_x_prefix(
*,
seq_y: TokenAlignerSeqInfo,
x_seqs: dict[SeqId, TokenAlignerSeqInfo],
claimed_x_ids: set[SeqId],
) -> Optional[SeqId]:
"""Find the x sequence with the longest prefix relationship to y."""
ids_y: list[int] = seq_y.input_ids
candidates: list[_PrefixCandidate] = [
_PrefixCandidate(
seq_id_x=seq_id_x, overlap_len=min(len(seq_x.input_ids), len(ids_y))
)
for seq_id_x, seq_x in x_seqs.items()
if seq_id_x not in claimed_x_ids and _is_prefix_pair(seq_x.input_ids, ids_y)
]
if not candidates:
return None
return max(candidates, key=lambda c: c.overlap_len).seq_id_x
def _is_prefix_pair(a: list[int], b: list[int]) -> bool:
"""True if a is a prefix of b, or b is a prefix of a."""
shorter_len: int = min(len(a), len(b))
return a[:shorter_len] == b[:shorter_len]
@@ -0,0 +1,81 @@
from __future__ import annotations
from dataclasses import dataclass, field
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
SeqId,
TokenAlignerGlobalAux,
TokenAlignerSeqInfo,
TokenAlignerSeqsInfo,
TokenAlignerStepAux,
TokenLocator,
)
@dataclass
class _SeqInfoAccumulator:
"""Mutable accumulator for building TokenAlignerSeqInfo without per-step validation."""
input_ids: list[int] = field(default_factory=list)
positions: list[int] = field(default_factory=list)
steps: list[int] = field(default_factory=list)
token_index_in_step: list[int] = field(default_factory=list)
def extend(
self,
*,
input_ids: list[int],
positions: list[int],
steps: list[int],
token_index_in_step: list[int],
) -> None:
self.input_ids.extend(input_ids)
self.positions.extend(positions)
self.steps.extend(steps)
self.token_index_in_step.extend(token_index_in_step)
def build(self) -> TokenAlignerSeqInfo:
return TokenAlignerSeqInfo(
input_ids=self.input_ids,
positions=self.positions,
locator=TokenLocator(
steps=self.steps,
token_index_in_step=self.token_index_in_step,
),
)
def build_seqs_info(global_aux: TokenAlignerGlobalAux) -> TokenAlignerSeqsInfo:
"""Build sequence info for one side from its auxiliary tensors."""
return TokenAlignerSeqsInfo(
sequences=_build_token_aligner_seq_infos(global_aux),
layout=global_aux.layout,
)
def _build_token_aligner_seq_infos(
global_aux: TokenAlignerGlobalAux,
) -> dict[SeqId, TokenAlignerSeqInfo]:
"""Build token index for any framework/layout using seq_ids for identity tracking."""
accum: dict[SeqId, _SeqInfoAccumulator] = {}
for step in sorted(global_aux.step_auxs.keys()):
aux: TokenAlignerStepAux = global_aux.step_auxs[step]
offset: int = 0
for seq_index, seq_len in enumerate(aux.seq_lens):
seq_id: SeqId = aux.seq_ids[seq_index]
if seq_id not in accum:
accum[seq_id] = _SeqInfoAccumulator()
accum[seq_id].extend(
input_ids=aux.input_ids[offset : offset + seq_len],
positions=aux.positions[offset : offset + seq_len],
steps=[step] * seq_len,
token_index_in_step=list(range(offset, offset + seq_len)),
)
offset += seq_len
return {seq_id: acc.build() for seq_id, acc in accum.items()}
@@ -0,0 +1,128 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import NamedTuple, Optional, Union
from pydantic import model_validator
from sglang.srt.debug_utils.comparator.dims_spec import TokenLayout
from sglang.srt.debug_utils.comparator.utils import (
Pair,
_check_equal_lengths,
_FrozenBase,
)
class SGLangSeqId(NamedTuple):
rid: str
class PositionalSeqId(NamedTuple):
step: int
seq_index: int
SeqId = Union[SGLangSeqId, PositionalSeqId]
@dataclass(frozen=True)
class TokenAlignerStepAux:
"""Normalized auxiliary tensors for a single step (framework-agnostic)."""
input_ids: list[int] # [num_tokens]
positions: list[int] # [num_tokens]
seq_lens: list[int] # [num_seqs]
seq_ids: list[SeqId] # [num_seqs] — sequence identity
def __post_init__(self) -> None:
_check_equal_lengths(input_ids=self.input_ids, positions=self.positions)
_check_equal_lengths(seq_lens=self.seq_lens, seq_ids=self.seq_ids)
token_count: int = sum(self.seq_lens)
if token_count != len(self.input_ids):
raise ValueError(
f"sum(seq_lens)={token_count} != len(input_ids)={len(self.input_ids)}"
)
@dataclass(frozen=True)
class TokenAlignerGlobalAux:
"""Auxiliary tensors for one side across all steps + side-level metadata."""
step_auxs: dict[int, TokenAlignerStepAux]
framework: str # "sglang" | "megatron"
layout: TokenLayout
thd_seq_lens_by_step: Optional[dict[int, list[int]]] = field(default=None)
class TokenLocator(_FrozenBase):
"""Locates tokens within a multi-step tensor store.
token i is at tensor_of_step[steps[i]][token_index_in_step[i]].
"""
steps: list[int]
token_index_in_step: list[int]
def __add__(self, other: TokenLocator) -> TokenLocator:
return TokenLocator(
steps=self.steps + other.steps,
token_index_in_step=self.token_index_in_step + other.token_index_in_step,
)
class TokenAlignerSeqInfo(_FrozenBase):
"""Information for a sequence, containing information to locate all the tokens inside the sequence."""
# All these fields are of shape (num_tokens_in_seq,)
input_ids: list[int]
positions: list[int]
locator: TokenLocator
@model_validator(mode="after")
def _validate_fields(self) -> TokenAlignerSeqInfo:
n: int = len(self.input_ids)
_check_equal_lengths(
input_ids=self.input_ids,
positions=self.positions,
locator_steps=self.locator.steps,
locator_token_index_in_step=self.locator.token_index_in_step,
)
if self.positions != list(range(n)):
raise ValueError(
f"positions must be [0, 1, ..., {n - 1}], got {self.positions}"
)
return self
def __add__(self, other: TokenAlignerSeqInfo) -> TokenAlignerSeqInfo:
return TokenAlignerSeqInfo(
input_ids=self.input_ids + other.input_ids,
positions=self.positions + other.positions,
locator=self.locator + other.locator,
)
class TokenAlignerSeqsInfo(_FrozenBase):
"""All sequences for one side across all steps."""
sequences: dict[SeqId, TokenAlignerSeqInfo]
layout: TokenLayout
class TokenAlignerPlan(_FrozenBase):
"""Token alignment plan. locators.x[i] and locators.y[i] correspond to the same logical token."""
locators: Pair[TokenLocator]
layouts: Pair[TokenLayout]
@model_validator(mode="after")
def _validate_fields(self) -> TokenAlignerPlan:
_check_equal_lengths(
locators_x_steps=self.locators.x.steps,
locators_x_token_index_in_step=self.locators.x.token_index_in_step,
locators_y_steps=self.locators.y.steps,
locators_y_token_index_in_step=self.locators.y.token_index_in_step,
)
return self
@@ -0,0 +1,190 @@
from dataclasses import dataclass, field
from typing import Optional
import torch
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import (
ConcatParams,
CpThdConcatParams,
PickParams,
ReduceSumParams,
UnsharderParams,
UnsharderPlan,
)
from sglang.srt.debug_utils.comparator.dims_spec import (
ParallelAxis,
apply_dim_names,
get_dim_names,
resolve_dim_by_name,
without_dim_names,
)
from sglang.srt.debug_utils.comparator.output_types import ReplicatedCheckResult
from sglang.srt.debug_utils.comparator.tensor_comparator.comparator import compute_diff
_REPLICATED_ATOL: float = 1e-6
@dataclass(frozen=True)
class UnsharderResult:
tensors: list[torch.Tensor]
replicated_checks: list[ReplicatedCheckResult] = field(default_factory=list)
def execute_unsharder_plan(
plan: UnsharderPlan,
tensors: list[torch.Tensor],
) -> UnsharderResult:
result_tensors: list[torch.Tensor] = []
all_checks: list[ReplicatedCheckResult] = []
for group_idx, group in enumerate(plan.groups):
group_tensors = [tensors[i] for i in group]
tensor, checks = _apply_unshard(
plan.params,
group_tensors,
axis=plan.axis,
group_index=group_idx,
)
result_tensors.append(tensor)
all_checks.extend(checks)
return UnsharderResult(tensors=result_tensors, replicated_checks=all_checks)
def _apply_unshard(
params: UnsharderParams,
ordered_tensors: list[torch.Tensor],
*,
axis: ParallelAxis,
group_index: int,
) -> tuple[torch.Tensor, list[ReplicatedCheckResult]]:
if isinstance(params, PickParams):
checks: list[ReplicatedCheckResult] = _verify_replicated_group(
ordered_tensors,
axis=axis,
group_index=group_index,
)
return ordered_tensors[0], checks
if isinstance(params, ConcatParams):
dim: int = resolve_dim_by_name(ordered_tensors[0], params.dim_name)
names: tuple[Optional[str], ...] = get_dim_names(ordered_tensors[0])
result = torch.cat(ordered_tensors, dim=dim)
if names[0] is not None:
result = apply_dim_names(result, list(names))
return result, []
if isinstance(params, CpThdConcatParams):
thd_dim: int = resolve_dim_by_name(ordered_tensors[0], params.dim_name)
return (
_thd_concat(
ordered_tensors,
dim=thd_dim,
seq_lens_per_rank=params.seq_lens_per_rank,
),
[],
)
if isinstance(params, ReduceSumParams):
names: tuple[Optional[str], ...] = get_dim_names(ordered_tensors[0])
stripped: list[torch.Tensor] = [without_dim_names(t) for t in ordered_tensors]
result: torch.Tensor = torch.stack(stripped).sum(dim=0)
if names[0] is not None:
result = apply_dim_names(result, list(names))
return result, []
raise ValueError(f"Unsupported unshard operation: {type(params).__name__}")
def _verify_replicated_group(
ordered_tensors: list[torch.Tensor],
*,
axis: ParallelAxis,
group_index: int,
) -> list[ReplicatedCheckResult]:
baseline: torch.Tensor = ordered_tensors[0].float()
return [
_check_replicated_pair(
baseline=baseline,
other=ordered_tensors[i],
axis=axis,
group_index=group_index,
compared_index=i,
)
for i in range(1, len(ordered_tensors))
]
def _check_replicated_pair(
*,
baseline: torch.Tensor,
other: torch.Tensor,
axis: ParallelAxis,
group_index: int,
compared_index: int,
) -> ReplicatedCheckResult:
other_float: torch.Tensor = other.float()
if baseline.shape != other_float.shape:
passed = False
diff_info = None
else:
diff_info = compute_diff(
x_baseline=baseline,
x_target=other_float,
predicate=f"max_abs <= {_REPLICATED_ATOL}",
)
passed = diff_info.passed
return ReplicatedCheckResult(
axis=axis.value,
group_index=group_index,
compared_index=compared_index,
baseline_index=0,
passed=passed,
atol=_REPLICATED_ATOL,
diff=diff_info,
)
def _thd_concat(
ordered_tensors: list[torch.Tensor],
*,
dim: int,
seq_lens_per_rank: list[int],
) -> torch.Tensor:
"""Per-seq concat across ranks for THD format.
Each rank holds segments of each seq packed contiguously:
rank_data = [seq0_tokens | seq1_tokens | ... | pad_tokens]
This function splits each rank by seq_lens, then interleaves across ranks
per-seq: [seqA_r0 + seqA_r1 + ... | seqB_r0 + seqB_r1 + ... | tail_pad].
"""
names: tuple[Optional[str], ...] = get_dim_names(ordered_tensors[0])
stripped: list[torch.Tensor] = [without_dim_names(t) for t in ordered_tensors]
# Split each rank into [seq0, seq1, ..., tail_remainder]
split_sizes: list[int] = list(seq_lens_per_rank)
remainder: int = stripped[0].shape[dim] - sum(split_sizes)
if remainder < 0:
raise ValueError(
f"sum(seq_lens_per_rank)={sum(split_sizes)} exceeds tensor dim size "
f"{stripped[0].shape[dim]} along dim={dim}"
)
if remainder > 0:
split_sizes.append(remainder)
per_rank_splits: list[tuple[torch.Tensor, ...]] = [
t.split(split_sizes, dim=dim) for t in stripped
]
# Per-seq concat across ranks, then concatenate all seqs
result: torch.Tensor = torch.cat(
[torch.cat(rank_parts, dim=dim) for rank_parts in zip(*per_rank_splits)],
dim=dim,
)
if names[0] is not None:
result = apply_dim_names(result, list(names))
return result
@@ -0,0 +1,45 @@
from typing import Optional
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import AxisInfo
from sglang.srt.debug_utils.comparator.dims_spec import ParallelAxis
_PARALLEL_INFO_KEYS = ("sglang_parallel_info", "megatron_parallel_info")
def _is_error_sentinel(value: dict) -> bool:
"""Check if a parallel_info dict is an error sentinel (e.g. {'megatron_error': True})."""
return any(k.endswith("_error") for k in value)
def normalize_parallel_info(meta: dict) -> dict[ParallelAxis, AxisInfo]:
"""Extract unified parallel info from dump meta."""
info: Optional[dict] = None
for key in _PARALLEL_INFO_KEYS:
value = meta.get(key)
if isinstance(value, dict) and value and not _is_error_sentinel(value):
if info is not None:
raise ValueError(
f"Meta contains multiple parallel_info keys among {_PARALLEL_INFO_KEYS}"
)
info = value
if info is None:
info = {}
result: dict[ParallelAxis, AxisInfo] = {}
for axis in ParallelAxis:
axis_rank = info.get(f"{axis.value}_rank")
axis_size = info.get(f"{axis.value}_size")
# Recompute pseudo-axis lives at top-level meta, not inside parallel_info
if axis_rank is None:
axis_rank = meta.get(f"{axis.value}_rank")
axis_size = meta.get(f"{axis.value}_size")
if axis_rank is not None and axis_size is not None and axis_size > 1:
result[axis] = AxisInfo(
axis_rank=axis_rank,
axis_size=axis_size,
)
return result
@@ -0,0 +1,373 @@
from collections import defaultdict
from typing import NamedTuple, Optional
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import (
AxisInfo,
ConcatParams,
CpThdConcatParams,
PickParams,
ReduceSumParams,
UnsharderParams,
UnsharderPlan,
)
from sglang.srt.debug_utils.comparator.dims_spec import (
TOKEN_DIM_NAME,
DimSpec,
ParallelAxis,
ParallelModifier,
)
# _CoordsList[tensor_index][axis] =
# the axis_rank (shard position) of the tensor_index-th tensor along `axis`
# (e.g. coords[2] = {TP: 3} means tensor 2 is the 3rd shard in TP axis)
_CoordsList = list[dict[ParallelAxis, int]]
class _GroupResult(NamedTuple):
groups: list[list[int]]
projected_coords: _CoordsList
def compute_unsharder_plan(
dim_specs: list[DimSpec],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
*,
explicit_replicated_axes: frozenset[ParallelAxis] = frozenset(),
thd_global_seq_lens: Optional[list[int]] = None,
dp_filtered_axis: Optional[ParallelAxis] = None,
) -> list[UnsharderPlan]:
if not parallel_infos:
raise ValueError("parallel_infos must not be empty")
# Within each dim spec, reverse modifier order: innermost shard (rightmost) unshards first.
reversed_sharded_modifiers: list[tuple[str, ParallelModifier]] = [
(spec.sanitized_name, m)
for spec in dim_specs
for m in reversed(spec.parallel_modifiers)
]
sharded_axes_raw: set[ParallelAxis] = {
m.axis for _, m in reversed_sharded_modifiers
}
all_axes: set[ParallelAxis] = {axis for info in parallel_infos for axis in info}
# axis annotated in dims but absent from all parallel_infos -> axis_size=1, skip
sharded_axes: set[ParallelAxis] = sharded_axes_raw & all_axes
reversed_sharded_modifiers = [
(name, m) for name, m in reversed_sharded_modifiers if m.axis in sharded_axes
]
# RECOMPUTE_PSEUDO is always implicitly replicated (system-injected, not user-facing)
auto_replicated: frozenset[ParallelAxis] = frozenset(
{ParallelAxis.RECOMPUTE_PSEUDO} & all_axes
)
effective_replicated: frozenset[ParallelAxis] = (
explicit_replicated_axes | auto_replicated
)
_validate_explicit_replicated(
explicit_replicated_axes=effective_replicated,
sharded_axes=sharded_axes,
all_axes=all_axes,
parallel_infos=parallel_infos,
dp_filtered_axis=dp_filtered_axis,
)
replicated_axes: frozenset[ParallelAxis] = effective_replicated
if not sharded_axes and not replicated_axes:
return []
_validate(
axes_to_validate=sharded_axes | replicated_axes,
parallel_infos=parallel_infos,
)
current_coords: _CoordsList = [
{axis: info[axis].axis_rank for axis in sharded_axes | replicated_axes}
for info in parallel_infos
]
axis_and_params: list[tuple[ParallelAxis, UnsharderParams]] = [
(axis, PickParams()) for axis in sorted(replicated_axes, key=lambda a: a.value)
] + [
(
modifier.axis,
_resolve_unshard_params(
modifier=modifier,
dim_name=dim_name,
parallel_infos=parallel_infos,
thd_global_seq_lens=thd_global_seq_lens,
),
)
for dim_name, modifier in reversed_sharded_modifiers
]
plans: list[UnsharderPlan] = []
for axis, params in axis_and_params:
result = _group_and_project(
current_coords=current_coords,
target_axis=axis,
)
plans.append(UnsharderPlan(axis=axis, params=params, groups=result.groups))
current_coords = result.projected_coords
return plans
def _validate_explicit_replicated(
*,
explicit_replicated_axes: frozenset[ParallelAxis],
sharded_axes: set[ParallelAxis],
all_axes: set[ParallelAxis],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
dp_filtered_axis: Optional[ParallelAxis] = None,
) -> None:
"""Validate explicit replicated declarations against sharded axes and parallel_infos."""
invalid: frozenset[ParallelAxis] = explicit_replicated_axes - all_axes
if invalid:
invalid_names: str = ", ".join(sorted(a.value for a in invalid))
raise ValueError(
f"Declared replicated axes {{{invalid_names}}} not found in parallel_infos "
f"(active axes: {{{', '.join(sorted(a.value for a in all_axes))}}})"
)
conflict: set[ParallelAxis] = explicit_replicated_axes & sharded_axes
if conflict:
conflict_names: str = ", ".join(sorted(a.value for a in conflict))
raise ValueError(
f"Axes {{{conflict_names}}} declared as both sharded and replicated"
)
_validate_replicated_axes_orthogonal(
explicit_replicated_axes=explicit_replicated_axes,
parallel_infos=parallel_infos,
)
candidate_axes: set[ParallelAxis] = (
all_axes - sharded_axes - explicit_replicated_axes
)
implicitly_replicated: frozenset[ParallelAxis] = _compute_dependent_axes(
parent_axes=explicit_replicated_axes,
candidate_axes=candidate_axes,
parallel_infos=parallel_infos,
)
implicitly_sharded: frozenset[ParallelAxis] = _compute_dependent_axes(
parent_axes=sharded_axes,
candidate_axes=candidate_axes - implicitly_replicated,
parallel_infos=parallel_infos,
)
declared_axes: frozenset[ParallelAxis] = frozenset(
sharded_axes
| explicit_replicated_axes
| implicitly_replicated
| implicitly_sharded
| ({dp_filtered_axis} if dp_filtered_axis is not None else set())
)
undeclared: set[ParallelAxis] = all_axes - declared_axes
jointly_determined: frozenset[ParallelAxis] = frozenset(
child
for child in undeclared
if _is_jointly_determined(
parallel_infos, parent_axes=declared_axes, child=child
)
)
undeclared -= jointly_determined
if undeclared:
undeclared_names: str = ", ".join(sorted(a.value for a in undeclared))
raise ValueError(
f"Axes {{{undeclared_names}}} are active (axis_size > 1) but not declared "
f"in dims. Annotate as sharded in dim spec or as '# axis:replicated'."
)
def _validate_replicated_axes_orthogonal(
*,
explicit_replicated_axes: frozenset[ParallelAxis],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
) -> None:
"""Every pair of explicitly replicated axes must be fully orthogonal (no dependency)."""
axes: list[ParallelAxis] = sorted(explicit_replicated_axes, key=lambda a: a.value)
if len(axes) < 2:
return
violations: list[str] = []
for i, axis_a in enumerate(axes):
for axis_b in axes[i + 1 :]:
for parent, child in [(axis_a, axis_b), (axis_b, axis_a)]:
if _is_dependent_axis(parallel_infos, parent=parent, child=child):
violations.append(
f"'{parent.value}' determines '{child.value}'"
f"remove '{child.value}:replicated'"
)
if violations:
details = "; ".join(violations)
raise ValueError(
f"Explicitly-replicated axes overlap (not orthogonal): {details}"
)
def _validate(
*,
axes_to_validate: set[ParallelAxis],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
) -> None:
"""Check that every rank has all axes, sizes are consistent, and ranks are complete."""
axis_sizes: dict[ParallelAxis, int] = {}
for world_rank, parallel_info in enumerate(parallel_infos):
for axis in axes_to_validate:
if axis not in parallel_info:
raise ValueError(
f"world_rank={world_rank} missing parallel_info for "
f"axis {axis.value!r}"
)
axis_info = parallel_info[axis]
if axis not in axis_sizes:
axis_sizes[axis] = axis_info.axis_size
elif axis_info.axis_size != axis_sizes[axis]:
raise ValueError(
f"Inconsistent axis_size for {axis.value}: "
f"expected {axis_sizes[axis]}, got {axis_info.axis_size} "
f"at world_rank={world_rank}"
)
for axis, expected_size in axis_sizes.items():
seen_ranks = {info[axis].axis_rank for info in parallel_infos}
if seen_ranks != set(range(expected_size)):
raise ValueError(
f"axis_rank coverage for {axis.value} is incomplete: "
f"got {sorted(seen_ranks)}, expected 0..{expected_size - 1}"
)
def _compute_dependent_axes(
parent_axes: set[ParallelAxis] | frozenset[ParallelAxis],
candidate_axes: set[ParallelAxis],
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
) -> frozenset[ParallelAxis]:
"""Return candidate axes whose rank is uniquely determined by some parent axis."""
return frozenset(
child
for child in candidate_axes
if any(
_is_dependent_axis(parallel_infos, parent=parent, child=child)
for parent in parent_axes
)
)
def _is_jointly_determined(
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
*,
parent_axes: frozenset[ParallelAxis],
child: ParallelAxis,
) -> bool:
"""True if child's rank is uniquely determined by the joint tuple of parent ranks.
Unlike ``_is_dependent_axis`` which checks single-parent dependency, this
checks whether the *combination* of all parent axes jointly determines the
child. For example, ``edp_rank`` may not be a function of ``tp_rank`` alone
or ``cp_rank`` alone, but it *is* a function of ``(tp_rank, cp_rank)``.
Parent axes that are absent from *every* info are ignored (they carry no
information — e.g. DP with size 1 filtered by ``normalize_parallel_info``).
However, a parent axis present in *some* infos but missing from an info
that contains the child makes the determination incomplete → ``False``.
"""
if not parent_axes:
return False
active_parents: frozenset[ParallelAxis] = frozenset(
ax for ax in parent_axes if any(ax in info for info in parallel_infos)
)
if not active_parents:
return False
mapping: dict[frozenset, int] = {}
for info in parallel_infos:
if child not in info:
continue
if not active_parents.issubset(info):
return False
parent_key = frozenset((ax, info[ax].axis_rank) for ax in active_parents)
child_rank: int = info[child].axis_rank
if mapping.setdefault(parent_key, child_rank) != child_rank:
return False
return bool(mapping)
def _is_dependent_axis(
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
*,
parent: ParallelAxis,
child: ParallelAxis,
) -> bool:
"""True if child's rank is uniquely determined by parent's rank."""
parent_rank_to_child_rank: dict[int, int] = {}
for info in parallel_infos:
if parent not in info or child not in info:
continue
parent_rank = info[parent].axis_rank
child_rank = info[child].axis_rank
if parent_rank_to_child_rank.setdefault(parent_rank, child_rank) != child_rank:
return False
return True
def _group_and_project(
*,
current_coords: _CoordsList,
target_axis: ParallelAxis,
) -> _GroupResult:
"""Group tensors by other-axes coords, sort within group by target_axis rank."""
# buckets[coords_excluding_target] = [(axis_rank, tensor_index), ...]
# e.g. when target_axis=CP: buckets[{(TP,0)}] = [(0, 1), (1, 3)]
# means tensor 1 (CP rank 0) and tensor 3 (CP rank 1) share TP rank 0
buckets: dict[frozenset, list[tuple[int, int]]] = defaultdict(list)
for idx, coords in enumerate(current_coords):
key = frozenset((k, v) for k, v in coords.items() if k != target_axis)
buckets[key].append((coords[target_axis], idx))
groups: list[list[int]] = []
projected: _CoordsList = []
for key in sorted(buckets, key=lambda k: sorted((a.value, v) for a, v in k)):
entries = sorted(buckets[key])
groups.append([idx for _, idx in entries])
projected.append(dict(key))
return _GroupResult(groups=groups, projected_coords=projected)
def _resolve_unshard_params(
*,
modifier: ParallelModifier,
dim_name: str,
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
thd_global_seq_lens: Optional[list[int]] = None,
) -> UnsharderParams:
if modifier.reduction is not None:
return ReduceSumParams()
if (
dim_name == TOKEN_DIM_NAME
and modifier.axis == ParallelAxis.CP
and thd_global_seq_lens is not None
):
axis_size: int = parallel_infos[0][modifier.axis].axis_size
for s in thd_global_seq_lens:
if s % axis_size != 0:
raise ValueError(
f"THD seq_len {s} is not divisible by cp_size {axis_size}. "
f"Sequences must be padded to a multiple of cp_size for CP zigzag."
)
seq_lens_per_rank: list[int] = [s // axis_size for s in thd_global_seq_lens]
return CpThdConcatParams(dim_name=dim_name, seq_lens_per_rank=seq_lens_per_rank)
return ConcatParams(dim_name=dim_name)
@@ -0,0 +1,60 @@
from __future__ import annotations
from typing import Annotated, Literal, Union
from pydantic import Field, model_validator
from sglang.srt.debug_utils.comparator.dims_spec import ParallelAxis
from sglang.srt.debug_utils.comparator.utils import _FrozenBase
class AxisInfo(_FrozenBase):
axis_rank: int
axis_size: int
@model_validator(mode="after")
def _validate_bounds(self) -> AxisInfo:
if self.axis_size <= 0:
raise ValueError(f"axis_size must be > 0, got {self.axis_size}")
if not (0 <= self.axis_rank < self.axis_size):
raise ValueError(
f"axis_rank must be in [0, {self.axis_size}), got {self.axis_rank}"
)
return self
class ConcatParams(_FrozenBase):
op: Literal["concat"] = "concat"
dim_name: str
class CpThdConcatParams(_FrozenBase):
op: Literal["cp_thd_concat"] = "cp_thd_concat"
dim_name: str
seq_lens_per_rank: list[int] # per-seq token count on each rank, e.g. [50, 32, 46]
class PickParams(_FrozenBase):
op: Literal["pick"] = "pick"
class ReduceSumParams(_FrozenBase):
op: Literal["reduce_sum"] = "reduce_sum"
UnsharderParams = Annotated[
Union[ConcatParams, CpThdConcatParams, PickParams, ReduceSumParams],
Field(discriminator="op"),
]
class UnsharderPlan(_FrozenBase):
type: Literal["unsharder"] = "unsharder"
axis: ParallelAxis
params: UnsharderParams
# groups[i] = indices in the input tensor list, which will be operated (e.g. concat) into i-th output tensor.
#
# Multistep example (CP=2, TP=2, 4 input tensors):
# plan[0] (CP): groups=[[0,2],[1,3]] — 4 tensors → 2 tensors
# plan[1] (TP): groups=[[0,1]] — 2 tensors → 1 tensor
groups: list[list[int]]