Files
wehub-resource-sync 94057c3d3e
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
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

962 lines
35 KiB
Python

from __future__ import annotations
import logging
import math
import statistics
import time
from collections import deque
from contextlib import contextmanager, nullcontext
from enum import Enum
from typing import Callable, ContextManager, Iterator, Optional, Union
import msgspec
import torch
from sglang.srt.environ import envs
from sglang.srt.kv_canary.runner.future_tensor import FutureTensors
from sglang.srt.runtime_context import get_parallel
from sglang.srt.sampling.sampling_params import TOP_K_ALL
from sglang.srt.speculative.dflash_utils import compute_dflash_correct_drafts_and_bonus
from sglang.srt.speculative.dspark_components.dspark_block_accept_estimator import (
create_block_accept_estimate_recorder,
)
from sglang.srt.speculative.dspark_components.dspark_sts import StsDataRecorder
from sglang.srt.speculative.dspark_components.dspark_verify import (
verify_logits_adjustments_are_noop,
)
logger = logging.getLogger(__name__)
_NULL_SEGMENT = nullcontext()
ALL_COMPONENTS_TOKEN = "all"
class InfoComponent(str, Enum):
CORE = "core"
STEP_CPU_TIME = "step_cpu_time"
STEP_GPU_TIME = "step_gpu_time"
DRAFT_GPU_TIME = "draft_gpu_time"
TARGET_VERIFY_GPU_TIME = "target_verify_gpu_time"
REQS = "reqs"
class InfoSegment(str, Enum):
STEP = "step"
DRAFT = "draft"
TARGET_VERIFY = "target_verify"
INFO_DUMP_MAX_RECORDS = 200_000
INFO_DUMP_MAX_STEP_CPU_SECONDS = 1.0
def resolve_enabled_components() -> set[InfoComponent]:
"""Components enabled via env: SGLANG_DSPARK_DEBUG_DUMP tokens, plus the
published SPS-profiling switch SGLANG_DSPARK_ENABLE_SPS_RECORD=1, which is
an alias for the core,step_cpu_time components the SPS table fit needs."""
components = resolve_components(envs.SGLANG_DSPARK_DEBUG_DUMP.get())
if envs.SGLANG_DSPARK_ENABLE_SPS_RECORD.get():
components |= {InfoComponent.CORE, InfoComponent.STEP_CPU_TIME}
return components
def resolve_components(raw: tuple[str, ...]) -> set[InfoComponent]:
tokens = {token.strip() for token in raw if token.strip()}
if not tokens:
return set()
if ALL_COMPONENTS_TOKEN in tokens:
return set(InfoComponent)
try:
return {InfoComponent(token) for token in tokens}
except ValueError as exc:
valid = [component.value for component in InfoComponent]
raise ValueError(
f"Invalid SGLANG_DSPARK_DEBUG_DUMP token in {sorted(tokens)}; "
f"valid: {valid} or '{ALL_COMPONENTS_TOKEN}'."
) from exc
class ReqDetail(msgspec.Struct, omit_defaults=True):
req_pool_index: int
prefix_len: int
verify_len: int
acc_len: int
correct_drafts: int
cap_trim: int
bonus_token: int
draft_tokens: list[int]
rid: Optional[str] = None
confidence: Optional[list[float]] = None
survival: Optional[list[float]] = None
class DecodeStepRecord(msgspec.Struct, omit_defaults=True):
forward_ct: int
bs: int = -1
mode: str = ""
budget: Optional[int] = None
lag_steps: Optional[int] = None
num_running_reqs: int = -1
num_verify_tokens: int = -1
verify_tokens_local: int = -1
verify_tokens_dp_synced: int = -1
verify_tokens_graph_key: int = -1
predicted_step_ms: Optional[float] = None
predicted_theta: Optional[float] = None
step_cpu_ms: Optional[float] = None
step_gpu_ms: Optional[float] = None
draft_gpu_ms: Optional[float] = None
target_verify_gpu_ms: Optional[float] = None
reqs: Optional[list[ReqDetail]] = None
class DecodeStepObservation(msgspec.Struct):
forward_ct: int
bs: int
mode: str
budget: Optional[int]
lag_steps: Optional[int]
num_verify_tokens: int
verify_tokens_local: int
verify_tokens_dp_synced: int
verify_tokens_graph_key: int
predicted_step_ms: Optional[float]
predicted_theta: Optional[float]
verify_lens: Optional[torch.Tensor]
confidence: Optional[torch.Tensor]
req_pool_indices: torch.Tensor
prefix_lens: torch.Tensor
draft_tokens: torch.Tensor
bonus_tokens: torch.Tensor
correct_len: torch.Tensor
cap_trim_lens: torch.Tensor
commit_lens: torch.Tensor
rids: Optional[list[str]]
class _PendingStep(msgspec.Struct):
forward_ct: int
bs: int
mode: str
budget: Optional[int]
lag_steps: Optional[int]
num_verify_tokens: int
verify_tokens_local: int
verify_tokens_dp_synced: int
verify_tokens_graph_key: int
predicted_step_ms: Optional[float]
predicted_theta: Optional[float]
step_cpu_ms: Optional[float]
rids: Optional[list[str]]
future: Optional[FutureTensors]
segment_events: dict[InfoSegment, tuple[torch.cuda.Event, torch.cuda.Event]]
class DsparkInfoDumper:
def __init__(
self,
*,
components: set[Union[InfoComponent, str]],
gamma: int,
verify_num_draft_tokens: int,
attn_tp_rank: int,
device: torch.device,
mode_value: str,
sps_report_interval: int = 0,
max_records: int = INFO_DUMP_MAX_RECORDS,
max_step_cpu_seconds: float = INFO_DUMP_MAX_STEP_CPU_SECONDS,
clock: Callable[[], float] = time.monotonic,
) -> None:
self.gamma = int(gamma)
self.verify_num_draft_tokens = int(verify_num_draft_tokens)
self.attn_tp_rank = int(attn_tp_rank)
self.device = device
self.mode_value = mode_value
self._clock = clock
self._max_step_cpu_seconds = max_step_cpu_seconds
self._components: set[InfoComponent] = {
InfoComponent(component) for component in components
}
self._sps_report_interval = int(sps_report_interval)
if self._sps_report_interval > 0:
self._components.add(InfoComponent.STEP_GPU_TIME)
# Dedup within an attention-TP group only: records describe the
# DP-rank-local batch, so under dp-attention every DP rank must keep
# dumping (the SPS profiler reads one payload per DP rank).
self.enabled = bool(self._components) and self.attn_tp_rank == 0
self._sps_window: list[tuple[float, float]] = []
self._sps_mismatched = 0
self._records: deque[DecodeStepRecord] = deque(maxlen=max_records)
self._pending: Optional[_PendingStep] = None
self._prev_stamp: Optional[float] = None
self._d2h_stream: Optional[torch.cuda.Stream] = None
if self.enabled and InfoComponent.REQS in self._components:
self._d2h_stream = torch.cuda.Stream(device=device)
self._current_segments: dict[
InfoSegment, tuple[torch.cuda.Event, torch.cuda.Event]
] = {}
self._open_segments: dict[InfoSegment, torch.cuda.Event] = {}
def begin_step(self) -> None:
if not self.enabled:
return
self._current_segments = {}
self._open_segments = {}
if InfoComponent.STEP_GPU_TIME in self._components:
self._open_segment(InfoSegment.STEP)
def segment(self, name: Union[InfoSegment, str]) -> ContextManager[None]:
if not self.enabled:
return _NULL_SEGMENT
segment = InfoSegment(name)
if not self._segment_enabled(segment):
return _NULL_SEGMENT
return self._active_segment(segment)
@contextmanager
def _active_segment(self, segment: InfoSegment) -> Iterator[None]:
self._open_segment(segment)
try:
yield
finally:
self._close_segment(segment)
def observe_decode_step(self, obs: DecodeStepObservation) -> None:
if not self.enabled:
return
if InfoComponent.STEP_GPU_TIME in self._components:
self._close_segment(InfoSegment.STEP)
now = self._clock()
step_cpu_ms = self._step_cpu_ms(now=now)
self._drain_pending()
future = (
self._stage_reqs(obs) if InfoComponent.REQS in self._components else None
)
self._pending = _PendingStep(
forward_ct=int(obs.forward_ct),
bs=int(obs.bs),
mode=obs.mode,
budget=None if obs.budget is None else int(obs.budget),
lag_steps=None if obs.lag_steps is None else int(obs.lag_steps),
num_verify_tokens=int(obs.num_verify_tokens),
verify_tokens_local=int(obs.verify_tokens_local),
verify_tokens_dp_synced=int(obs.verify_tokens_dp_synced),
verify_tokens_graph_key=int(obs.verify_tokens_graph_key),
predicted_step_ms=obs.predicted_step_ms,
predicted_theta=obs.predicted_theta,
step_cpu_ms=step_cpu_ms,
rids=obs.rids,
future=future,
segment_events=self._current_segments,
)
self._current_segments = {}
self._prev_stamp = now
def note_non_decode_step(self) -> None:
if not self.enabled:
return
self._drain_pending()
self._prev_stamp = None
self._current_segments = {}
self._open_segments = {}
def flush(self) -> None:
if not self.enabled:
return
self._drain_pending()
def clear(self) -> None:
self._records.clear()
self._pending = None
self._prev_stamp = None
self._current_segments = {}
self._open_segments = {}
self._sps_window = []
self._sps_mismatched = 0
def dump(self) -> Optional[dict]:
if not self.enabled:
return None
self.flush()
return {
"mode": self.mode_value,
"gamma": self.gamma,
"verify_num_draft_tokens": self.verify_num_draft_tokens,
"components": sorted(component.value for component in self._components),
"records": [msgspec.to_builtins(record) for record in self._records],
}
def _segment_enabled(self, segment: InfoSegment) -> bool:
if segment is InfoSegment.STEP:
return InfoComponent.STEP_GPU_TIME in self._components
if segment is InfoSegment.DRAFT:
return InfoComponent.DRAFT_GPU_TIME in self._components
if segment is InfoSegment.TARGET_VERIFY:
return InfoComponent.TARGET_VERIFY_GPU_TIME in self._components
return False
def _open_segment(self, segment: InfoSegment) -> None:
start = torch.cuda.Event(enable_timing=True)
start.record()
self._open_segments[segment] = start
def _close_segment(self, segment: InfoSegment) -> None:
start = self._open_segments.pop(segment, None)
if start is None:
return
end = torch.cuda.Event(enable_timing=True)
end.record()
self._current_segments[segment] = (start, end)
def _stage_reqs(self, obs: DecodeStepObservation) -> Optional[FutureTensors]:
tensors: dict[str, torch.Tensor] = {
"req_pool_indices": obs.req_pool_indices,
"prefix_lens": obs.prefix_lens,
"draft_tokens": obs.draft_tokens,
"bonus_tokens": obs.bonus_tokens,
"correct_len": obs.correct_len,
"cap_trim_lens": obs.cap_trim_lens,
"commit_lens": obs.commit_lens,
}
if obs.verify_lens is not None:
tensors["verify_lens"] = obs.verify_lens
if obs.confidence is not None:
tensors["confidence"] = obs.confidence
return FutureTensors.device_to_host(tensors, d2h_stream=self._d2h_stream)
def _drain_pending(self) -> None:
pending = self._pending
self._pending = None
if pending is None:
return
record = DecodeStepRecord(forward_ct=pending.forward_ct)
if InfoComponent.CORE in self._components:
record.bs = pending.bs
record.mode = pending.mode
record.budget = pending.budget
record.lag_steps = pending.lag_steps
record.num_running_reqs = pending.bs
record.num_verify_tokens = pending.num_verify_tokens
record.verify_tokens_local = pending.verify_tokens_local
record.verify_tokens_dp_synced = pending.verify_tokens_dp_synced
record.verify_tokens_graph_key = pending.verify_tokens_graph_key
record.predicted_step_ms = pending.predicted_step_ms
record.predicted_theta = pending.predicted_theta
if InfoComponent.STEP_CPU_TIME in self._components:
record.step_cpu_ms = pending.step_cpu_ms
if InfoComponent.STEP_GPU_TIME in self._components:
record.step_gpu_ms = self._segment_ms(pending, InfoSegment.STEP)
if InfoComponent.DRAFT_GPU_TIME in self._components:
record.draft_gpu_ms = self._segment_ms(pending, InfoSegment.DRAFT)
if InfoComponent.TARGET_VERIFY_GPU_TIME in self._components:
record.target_verify_gpu_ms = self._segment_ms(
pending, InfoSegment.TARGET_VERIFY
)
if InfoComponent.REQS in self._components and pending.future is not None:
record.reqs = self._build_reqs(
host=pending.future.wait(), bs=pending.bs, rids=pending.rids
)
elif pending.future is not None:
pending.future.wait()
self._records.append(record)
if self._sps_report_interval > 0:
self._report_sps_prediction(pending=pending, step_gpu_ms=record.step_gpu_ms)
def _report_sps_prediction(
self, *, pending: _PendingStep, step_gpu_ms: Optional[float]
) -> None:
predicted = pending.predicted_step_ms
if predicted is None or step_gpu_ms is None:
return
matched = (
pending.budget is not None
and pending.bs + pending.budget == pending.num_verify_tokens
)
if not matched:
self._sps_mismatched += 1
return
self._sps_window.append((predicted, step_gpu_ms))
if len(self._sps_window) < self._sps_report_interval:
return
predictions = [p for p, _ in self._sps_window]
actuals = [a for _, a in self._sps_window]
abs_err = [abs(p - a) for p, a in self._sps_window]
rel_err = [abs(p - a) / a * 100 for p, a in self._sps_window if a > 0]
total = len(self._sps_window) + self._sps_mismatched
logger.info(
"DSpark SPS prediction: n=%d mean predicted=%.3fms mean actual=%.3fms "
"MAE=%.3fms median rel-err=%.1f%% mean bias(pred-actual)=%+.3fms "
"M_mismatch_rate=%.1f%% (%d/%d)",
len(self._sps_window),
statistics.fmean(predictions),
statistics.fmean(actuals),
statistics.fmean(abs_err),
statistics.median(rel_err) if rel_err else float("nan"),
statistics.fmean([p - a for p, a in self._sps_window]),
self._sps_mismatched / total * 100 if total else 0.0,
self._sps_mismatched,
total,
)
self._sps_window = []
self._sps_mismatched = 0
def _step_cpu_ms(self, *, now: float) -> Optional[float]:
prev = self._prev_stamp
if prev is None:
return None
step_cpu = now - prev
if not (0.0 < step_cpu <= self._max_step_cpu_seconds):
return None
return round(step_cpu * 1000.0, 4)
def _segment_ms(
self, pending: _PendingStep, segment: InfoSegment
) -> Optional[float]:
events = pending.segment_events.get(segment)
if events is None:
return None
start, end = events
end.synchronize()
elapsed_ms = start.elapsed_time(end)
if elapsed_ms > self._max_step_cpu_seconds * 1000.0:
return None
return round(elapsed_ms, 4)
def _build_reqs(
self, *, host: dict, bs: int, rids: Optional[list[str]]
) -> list[ReqDetail]:
req_ids = host["req_pool_indices"].tolist()
prefixes = host["prefix_lens"].tolist()
draft_rows = host["draft_tokens"].tolist()
bonus = host["bonus_tokens"].tolist()
correct = host["correct_len"].tolist()
cap_trim = host["cap_trim_lens"].tolist()
commit = host["commit_lens"].tolist()
verify_lens = host["verify_lens"].tolist() if "verify_lens" in host else None
if "confidence" in host:
conf_host = host["confidence"].float()
conf_rows = conf_host.tolist()
survival_rows = torch.cumprod(conf_host, dim=1).tolist()
else:
conf_rows = None
survival_rows = None
reqs: list[ReqDetail] = []
for row in range(bs):
verify_len = (
self.verify_num_draft_tokens
if verify_lens is None
else int(verify_lens[row])
)
reqs.append(
ReqDetail(
rid=None if rids is None else rids[row],
req_pool_index=int(req_ids[row]),
prefix_len=int(prefixes[row]),
verify_len=verify_len,
acc_len=int(commit[row]),
correct_drafts=int(correct[row]),
cap_trim=int(cap_trim[row]),
bonus_token=int(bonus[row]),
draft_tokens=[int(t) for t in draft_rows[row]],
confidence=(
None
if conf_rows is None
else [round(float(p), 4) for p in conf_rows[row]]
),
survival=(
None
if survival_rows is None
else [round(float(p), 4) for p in survival_rows[row]]
),
)
)
return reqs
EPS_PROB = 1e-8
def _format_float(value: float, digits: int = 4) -> str:
value = float(value)
if math.isnan(value):
return "nan"
return f"{value:.{digits}f}"
class PerPositionConfidenceMetrics:
def __init__(
self,
*,
gamma: int,
device: torch.device,
num_coarse_bins: int = 15,
num_fine_bins: int = 1024,
) -> None:
self.gamma = int(gamma)
self.num_coarse_bins = int(num_coarse_bins)
self.num_fine_bins = int(num_fine_bins)
self.coarse_count = torch.zeros(
(self.gamma, self.num_coarse_bins), dtype=torch.float64, device=device
)
self.coarse_pred = torch.zeros_like(self.coarse_count)
self.coarse_target = torch.zeros_like(self.coarse_count)
self.fine_pos = torch.zeros(
(self.gamma, self.num_fine_bins), dtype=torch.float64, device=device
)
self.fine_neg = torch.zeros_like(self.fine_pos)
self.brier_num = torch.zeros(self.gamma, dtype=torch.float64, device=device)
def update(self, *, survival: torch.Tensor, prefix_mask: torch.Tensor) -> None:
assert survival.shape == prefix_mask.shape
assert survival.dim() == 2 and survival.shape[1] == self.gamma
probs = survival.to(torch.float64).clamp(EPS_PROB, 1.0 - EPS_PROB)
targets = prefix_mask.to(torch.float64)
bs = probs.shape[0]
probs_flat = probs.reshape(-1)
targets_flat = targets.reshape(-1)
weights = torch.ones_like(probs_flat)
pos_idx = (
torch.arange(self.gamma, device=probs.device)
.view(1, -1)
.expand(bs, self.gamma)
.reshape(-1)
)
coarse_idx = (
(probs_flat * self.num_coarse_bins)
.long()
.clamp_(0, self.num_coarse_bins - 1)
)
flat_coarse = pos_idx * self.num_coarse_bins + coarse_idx
self.coarse_count.view(-1).scatter_add_(0, flat_coarse, weights)
self.coarse_pred.view(-1).scatter_add_(0, flat_coarse, probs_flat)
self.coarse_target.view(-1).scatter_add_(0, flat_coarse, targets_flat)
fine_idx = (
(probs_flat * self.num_fine_bins).long().clamp_(0, self.num_fine_bins - 1)
)
flat_fine = pos_idx * self.num_fine_bins + fine_idx
self.fine_pos.view(-1).scatter_add_(0, flat_fine, targets_flat)
self.fine_neg.view(-1).scatter_add_(0, flat_fine, 1.0 - targets_flat)
self.brier_num.add_((probs - targets).pow(2).sum(dim=0))
@staticmethod
def _auroc_from_hist(pos_hist: torch.Tensor, neg_hist: torch.Tensor) -> float:
total_pos = float(pos_hist.sum())
total_neg = float(neg_hist.sum())
if total_pos <= 0.0 or total_neg <= 0.0:
return float("nan")
cum_neg = torch.cumsum(neg_hist, dim=0)
cum_neg_before = cum_neg - neg_hist
pair = (pos_hist * cum_neg_before).sum() + 0.5 * (pos_hist * neg_hist).sum()
return float(pair) / (total_pos * total_neg)
def compute(self) -> list[dict]:
coarse_count = self.coarse_count.cpu()
coarse_pred = self.coarse_pred.cpu()
coarse_target = self.coarse_target.cpu()
fine_pos = self.fine_pos.cpu()
fine_neg = self.fine_neg.cpu()
brier_num = self.brier_num.cpu()
out: list[dict] = []
for pos in range(self.gamma):
weights = coarse_count[pos]
total = float(weights.sum())
if total <= 1e-12:
out.append(
{
"position": pos,
"total_weight": 0.0,
"ece": float("nan"),
"auc": float("nan"),
"brier": float("nan"),
"pred_mean": float("nan"),
"target_mean": float("nan"),
"reliability": [],
}
)
continue
denom = weights.clamp_min(1e-12)
avg_pred = coarse_pred[pos] / denom
avg_target = coarse_target[pos] / denom
bin_err = (avg_pred - avg_target).abs()
ece = float((bin_err * weights).sum()) / total
auc = self._auroc_from_hist(fine_pos[pos], fine_neg[pos])
brier = float(brier_num[pos]) / total
reliability = []
for bin_idx in range(self.num_coarse_bins):
weight = float(weights[bin_idx])
if weight <= 0.0:
continue
reliability.append(
{
"bin": bin_idx,
"range": [
bin_idx / self.num_coarse_bins,
(bin_idx + 1) / self.num_coarse_bins,
],
"avg_pred": float(avg_pred[bin_idx]),
"avg_target": float(avg_target[bin_idx]),
"weight": weight,
}
)
out.append(
{
"position": pos,
"total_weight": total,
"ece": ece,
"auc": auc,
"brier": brier,
"pred_mean": float(coarse_pred[pos].sum()) / total,
"target_mean": float(coarse_target[pos].sum()) / total,
"reliability": reliability,
}
)
return out
def format_table(self) -> str:
rows = self.compute()
header = (
f"{'pos':>3} {'count':>12} {'pred':>8} {'target':>8} "
f"{'ece':>8} {'auc':>8} {'brier':>8}"
)
lines = [
"DSpark confidence-head per-position calibration "
"(cumprod survival vs leading-correct-prefix)",
header,
]
for row in rows:
lines.append(
f"{row['position']:>3} {row['total_weight']:>12.0f} "
f"{_format_float(row['pred_mean']):>8} "
f"{_format_float(row['target_mean']):>8} "
f"{_format_float(row['ece']):>8} "
f"{_format_float(row['auc']):>8} "
f"{_format_float(row['brier']):>8}"
)
return "\n".join(lines)
class ConfidenceMetricsProbe:
def __init__(
self,
*,
gamma: int,
verify_num_draft_tokens: int,
tp_rank: int,
print_every: int = 256,
) -> None:
self.gamma = int(gamma)
self.verify_num_draft_tokens = int(verify_num_draft_tokens)
self.tp_rank = int(tp_rank)
self.print_every = int(print_every)
self._metrics: Optional[PerPositionConfidenceMetrics] = None
self._step_ct: int = 0
self._compact_warned: bool = False
def maybe_observe(
self,
*,
carries_confidence: bool,
is_compact_mode: bool,
confidence_raw: Optional[torch.Tensor],
verify_ids_2d: torch.Tensor,
target_logits: torch.Tensor,
bs: int,
) -> None:
if not envs.SGLANG_DSPARK_DEBUG_CONFIDENCE_METRICS.get():
return
if self.tp_rank != 0:
return
if not carries_confidence:
return
if is_compact_mode:
if not self._compact_warned:
logger.warning(
"SGLANG_DSPARK_DEBUG_CONFIDENCE_METRICS is ignored under "
"SGLANG_RAGGED_VERIFY_MODE=compact (padded verify rows corrupt the "
"per-position prefix label); run cap-accept or static to measure it."
)
self._compact_warned = True
return
if confidence_raw is None:
return
target_predict = torch.argmax(target_logits, dim=-1).view(
bs, self.verify_num_draft_tokens
)
num_correct_drafts, _ = compute_dflash_correct_drafts_and_bonus(
candidates=verify_ids_2d,
target_predict=target_predict,
)
positions = torch.arange(self.gamma, device=confidence_raw.device).view(1, -1)
prefix_mask = (positions < num_correct_drafts.view(-1, 1)).to(torch.float32)
survival = torch.cumprod(torch.sigmoid(confidence_raw.float()), dim=1)
if self._metrics is None:
self._metrics = PerPositionConfidenceMetrics(
gamma=self.gamma, device=confidence_raw.device
)
self._metrics.update(survival=survival, prefix_mask=prefix_mask)
self._step_ct += 1
if self._step_ct % self.print_every == 0:
logger.info("%s", self._metrics.format_table())
_STS_COLLECT_FLUSH_EVERY: int = 256
class DsparkStepObservers:
"""Facade over the per-step observability sinks (info dumper, confidence
probe, STS collection, block-accept estimator). The worker's decode path
makes one call per step; all sink gating and field derivation live here
so the hot path stays free of observer plumbing."""
def __init__(
self,
*,
planner,
gamma: int,
verify_num_draft_tokens: int,
tp_rank: int,
device,
simulate_acc_len: float,
) -> None:
self._planner = planner
self._gamma = int(gamma)
self._verify_num_draft_tokens = int(verify_num_draft_tokens)
self._simulate_acc_len = float(simulate_acc_len)
self._confidence_probe = ConfidenceMetricsProbe(
gamma=gamma,
verify_num_draft_tokens=verify_num_draft_tokens,
tp_rank=tp_rank,
)
self._info_dumper = DsparkInfoDumper(
components=resolve_enabled_components(),
gamma=gamma,
verify_num_draft_tokens=verify_num_draft_tokens,
attn_tp_rank=get_parallel().attn_tp_rank,
device=device,
mode_value=planner.mode_value,
sps_report_interval=envs.SGLANG_DSPARK_LOG_SPS_PRED_INTERVAL.get(),
)
self._block_accept_recorder = create_block_accept_estimate_recorder(
gamma=gamma, device=device, tp_rank=tp_rank
)
if self._simulate_acc_len > 0 and self._block_accept_recorder is not None:
raise ValueError(
"SGLANG_DSPARK_BLOCK_ACCEPT_ESTIMATE_PATH cannot be combined with "
"SGLANG_SIMULATE_ACC_LEN (simulated correct_len breaks the "
"accept-probability bookkeeping of the estimator)."
)
self._sts_collect_path = envs.SGLANG_DSPARK_STS_COLLECT_PATH.get()
self._sts_recorder: Optional[StsDataRecorder] = None
# --- step lifecycle -------------------------------------------------
def begin_step(self) -> None:
self._info_dumper.begin_step()
def segment(self, name: Union[InfoSegment, str]) -> ContextManager[None]:
return self._info_dumper.segment(name)
def note_prefill_step(self) -> None:
self._info_dumper.note_non_decode_step()
if self._block_accept_recorder is not None:
self._block_accept_recorder.flush()
def note_idle_decode_step(self) -> None:
self._info_dumper.note_non_decode_step()
# --- scheduler-facing hooks ------------------------------------------
def dump_info_records(self) -> Optional[dict]:
dumped = self._info_dumper.dump()
if dumped is None:
return None
dumped["simulate_acc_len"] = (
self._simulate_acc_len if self._simulate_acc_len > 0 else None
)
return dumped
def clear_info_records(self) -> None:
self._info_dumper.clear()
def block_accept_estimate_log_suffix(self) -> Optional[str]:
if self._block_accept_recorder is None:
return None
return self._block_accept_recorder.estimate_log_suffix()
def note_request_finished(self, *, rid: str, natural_stop: bool) -> None:
if self._block_accept_recorder is None:
return
self._block_accept_recorder.note_request_finished(
rid=rid, natural_stop=natural_stop
)
# --- per-step observation --------------------------------------------
def observe_verify_step(
self,
*,
forward_ct: int,
reqs,
bs: int,
proposal_folded: bool,
verify_ids_2d: torch.Tensor,
target_logits: Optional[torch.Tensor],
layout,
confidence: Optional[torch.Tensor],
prefix_lens: torch.Tensor,
draft_tokens: torch.Tensor,
draft_block,
sampling_info,
correct_len: torch.Tensor,
cap_trim_lens: torch.Tensor,
bonus: torch.Tensor,
commit_lens: torch.Tensor,
verify_token_budget: Optional[int],
req_pool_indices: torch.Tensor,
verify_tier_num_tokens: int,
dp_tier_num_tokens: Optional[int],
) -> None:
planner = self._planner
if not proposal_folded:
self._maybe_record_sts_collect(
verify_ids_2d=verify_ids_2d,
target_logits=target_logits,
bs=bs,
)
self._confidence_probe.maybe_observe(
carries_confidence=planner.carries_confidence,
is_compact_mode=planner.is_compact_mode,
confidence_raw=planner.last_confidence_raw,
verify_ids_2d=verify_ids_2d,
target_logits=target_logits,
bs=bs,
)
if self._block_accept_recorder is not None and not proposal_folded:
self._block_accept_recorder.observe_verify_step(
forward_ct=forward_ct,
rids=[req.rid for req in reqs],
draft_tokens=draft_tokens,
corrected_logits=draft_block.corrected_logits,
draft_temperatures=draft_block.temperatures,
greedy_mask=draft_block.greedy_mask,
target_logits=target_logits,
target_temperatures=(
sampling_info.temperatures
if sampling_info is not None
else draft_block.temperatures
),
truncated_sampling_mask=(
(sampling_info.top_ks != TOP_K_ALL)
| (sampling_info.top_ps != 1.0)
| (sampling_info.min_ps > 0)
if sampling_info is not None
else None
),
logits_adjustments_are_noop=verify_logits_adjustments_are_noop(
sampling_info
),
correct_len=correct_len,
cap_trim_lens=cap_trim_lens,
bonus=bonus,
prefix_lens=prefix_lens,
layout=layout,
)
if self._info_dumper.enabled:
budget_decision = planner.take_budget_decision()
predicted_step_ms = (
None
if budget_decision is None
or budget_decision.predicted_step_seconds is None
else budget_decision.predicted_step_seconds * 1e3
)
predicted_theta = (
None if budget_decision is None else budget_decision.predicted_theta
)
num_verify_tokens = (
layout.graph_num_tokens
if layout is not None
else int(verify_ids_2d.numel())
)
self._info_dumper.observe_decode_step(
DecodeStepObservation(
forward_ct=forward_ct,
bs=bs,
mode=planner.mode_value,
budget=verify_token_budget,
lag_steps=planner.lag_steps,
num_verify_tokens=num_verify_tokens,
verify_tokens_local=verify_tier_num_tokens,
verify_tokens_dp_synced=(
-1 if dp_tier_num_tokens is None else int(dp_tier_num_tokens)
),
verify_tokens_graph_key=num_verify_tokens,
predicted_step_ms=predicted_step_ms,
predicted_theta=predicted_theta,
verify_lens=layout.verify_lens if layout is not None else None,
confidence=confidence,
req_pool_indices=req_pool_indices,
prefix_lens=prefix_lens,
draft_tokens=draft_tokens,
bonus_tokens=bonus,
correct_len=correct_len,
cap_trim_lens=cap_trim_lens,
commit_lens=commit_lens,
rids=[req.rid for req in reqs],
)
)
def _maybe_record_sts_collect(
self,
*,
verify_ids_2d: torch.Tensor,
target_logits: Optional[torch.Tensor],
bs: int,
) -> None:
if not self._sts_collect_path:
return
if not self._planner.carries_confidence:
return
confidence_raw = self._planner.last_confidence_raw
if confidence_raw is None:
return
if self._sts_recorder is None:
self._sts_recorder = StsDataRecorder(
path_stem=self._sts_collect_path,
gamma=self._gamma,
flush_every=_STS_COLLECT_FLUSH_EVERY,
)
target_predict = torch.argmax(target_logits, dim=-1).view(
bs, self._verify_num_draft_tokens
)
num_correct_drafts, _ = compute_dflash_correct_drafts_and_bonus(
candidates=verify_ids_2d,
target_predict=target_predict,
)
self._sts_recorder.record(
confidence_raw=confidence_raw,
num_correct_drafts=num_correct_drafts,
)