Adds `--dispatch-mode {tracets,thinktime}` to the replayer and documents that
agentic serving should be benchmarked with `thinktime` (the faithful load).
- `tracets` (old default): turn-k at the absolute trace timestamp, i.e.
max(prev_finished, trace_ts) -- collapses inter-turn think-time to ~0 when the
system is behind, manufacturing request bursts.
- `thinktime`: turn-1 at trace arrival; turn-k at prev_finished +
time_to_parent_chat (real production gap). scripts/add_time_to_parent.py
annotates a trace with that gap from the raw trace's request_ready/end_ms.
exp(c) ablation (v2/exp_c_dispatch_ablation/): at N=8 (capacity slack) thinktime
beats tracets -- E2E p90 -28% (73.5 vs 102.8s), TTFT p90 -29%, TPS +7%, because
tracets' bursts spike concurrency -> KV pressure -> preemption. At N=6
(saturated) they converge. So tracets makes the system look ~30% worse on tail
latency than realistic agent pacing. Root README.md carries the headline
guidance; raw per-request metrics gitignored (perf_summary.json kept).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
"""Trace data structures and loader for the Ali agentic-coder trace format.
|
|
|
|
Trace format (one JSON per line):
|
|
chat_id, parent_chat_id, timestamp, input_length, output_length,
|
|
type, turn, hash_ids[]
|
|
|
|
Sessions are derived from parent_chat_id chains:
|
|
- parent_chat_id == -1 → new session root
|
|
- parent_chat_id >= 0 → belongs to the same session as the parent
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TraceRequest:
|
|
request_id: str
|
|
session_id: str
|
|
chat_id: int
|
|
parent_chat_id: int
|
|
timestamp_s: float
|
|
input_length: int
|
|
output_length: int
|
|
request_type: str
|
|
turn_id: int
|
|
hash_ids: tuple[int, ...]
|
|
# real production gap (s) from parent turn finishing to this turn arriving;
|
|
# None for turn-1 / unannotated traces. Used by --dispatch-mode thinktime.
|
|
time_to_parent_chat_s: float | None = None
|
|
|
|
|
|
def load_trace(
|
|
path: Path,
|
|
*,
|
|
request_limit: int | None = None,
|
|
) -> list[TraceRequest]:
|
|
"""Load trace and resolve session IDs from parent_chat_id chains."""
|
|
chat_to_session: dict[int, str] = {}
|
|
requests: list[TraceRequest] = []
|
|
|
|
with path.open("r", encoding="utf-8") as fh:
|
|
for idx, line in enumerate(fh):
|
|
if request_limit is not None and len(requests) >= request_limit:
|
|
break
|
|
row = json.loads(line)
|
|
chat_id = int(row["chat_id"])
|
|
parent_chat_id = int(row["parent_chat_id"])
|
|
|
|
if "session_id" in row:
|
|
session_id = str(row["session_id"])
|
|
else:
|
|
session_id = _resolve_session_id(
|
|
chat_id, parent_chat_id, chat_to_session,
|
|
)
|
|
chat_to_session[chat_id] = session_id
|
|
|
|
requests.append(TraceRequest(
|
|
request_id=f"{session_id}:{row['turn']}:{chat_id}:{idx}",
|
|
session_id=session_id,
|
|
chat_id=chat_id,
|
|
parent_chat_id=parent_chat_id,
|
|
timestamp_s=float(row["timestamp"]),
|
|
input_length=int(row["input_length"]),
|
|
output_length=int(row["output_length"]),
|
|
request_type=str(row["type"]),
|
|
turn_id=int(row["turn"]),
|
|
hash_ids=tuple(int(h) for h in row.get("hash_ids", [])),
|
|
time_to_parent_chat_s=(
|
|
float(row["time_to_parent_chat"])
|
|
if row.get("time_to_parent_chat") is not None else None),
|
|
))
|
|
|
|
return requests
|
|
|
|
|
|
def _resolve_session_id(
|
|
chat_id: int,
|
|
parent_chat_id: int,
|
|
chat_to_session: dict[int, str],
|
|
) -> str:
|
|
if parent_chat_id < 0:
|
|
session_id = str(chat_id)
|
|
else:
|
|
session_id = chat_to_session.get(parent_chat_id, str(parent_chat_id))
|
|
chat_to_session[chat_id] = session_id
|
|
return session_id
|