Add window-scoped session closure fallback

This commit is contained in:
2026-07-13 16:23:38 +08:00
parent eb67212b17
commit 5ae0525611
3 changed files with 324 additions and 11 deletions

View File

@@ -200,3 +200,16 @@ diverge因此做 ragged verifier”这一表述太宽不能作为实现前
queue policy。若全部通过最小原型的边界也只应是 verifier-side compaction当前
EAGLE 仍按 Kmax 产生 candidate`k_i` 不会自动消除 draft-side work不能把 verifier
节省误报为完整 draft+verify speedup。
### Trace 数据可用性与 window-closure fallback
本轮发现 `prepare_trace_windows.py` 依赖的 2026-03-27 原始格式化 trace span 在 dash0
已不存在,不能把它的 streaming full-session root 当作已经复验。现有的完整 600 秒
materialized window 仍含 prompt、`chat_id``parent_chat_id`,因此新增的 fallback 仅将
窗口内图的 connected component 作为选择原子:保留 384/384 条窗口内 parent edge且把
同一个窗口外 parent 的 sibling 归为同一 component。该窗口有 15,479 requests、15,095
components414 条 non-root edge 中 30 条7.25%)父节点落在窗口外。
这只能称为 **window-session-closed**,不等于 full-session coherent任何结果都必须报告
这 30 条 boundary-parent residual且不能据此声称跨窗口 KV reuse。若原始 span 恢复,必须
重新从完整 source resolve root 与重新采样,不能沿用 fallback 的 score/threshold。

View File

@@ -86,7 +86,9 @@ def main() -> int:
if not source_trace.is_file():
raise SystemExit(f"source trace does not exist: {source_trace}")
source_sampling_strategy = source_window.get("sampling_strategy")
session_coherent_source = source_sampling_strategy == "session_coherent_uniform_score"
full_session_source = source_sampling_strategy == "session_coherent_uniform_score"
window_session_source = source_sampling_strategy == "window_session_closed_uniform_score"
session_closed_source = full_session_source or window_session_source
args.output_dir.mkdir(parents=True, exist_ok=True)
output_trace = args.output_dir / f"{args.output_window_id}.jsonl"
@@ -105,10 +107,10 @@ def main() -> int:
raise SystemExit(f"row {row_index}: expected an object")
sampling_u = numeric(row.get("sampling_u"), field="sampling_u", row_index=row_index)
session_entry: dict[str, int] | None = None
if session_coherent_source:
if session_closed_source:
if "session_root" not in row:
raise SystemExit(
f"row {row_index}: session-coherent source has no session_root"
f"row {row_index}: session-closed source has no session_root"
)
session_key = stable_session_key(row["session_root"])
session_entry = session_counts.setdefault(
@@ -153,8 +155,12 @@ def main() -> int:
"num_requests": selected,
"sampling_strategy": (
"session_coherent_uniform_score_then_greedy_temperature"
if session_coherent_source
else "fixed_uniform_score_then_greedy_temperature"
if full_session_source
else (
"window_session_closed_uniform_score_then_greedy_temperature"
if window_session_source
else "fixed_uniform_score_then_greedy_temperature"
)
),
"collectivespec_source_window_id": args.window_id,
"collectivespec_sampling_u_threshold": threshold,
@@ -162,7 +168,10 @@ def main() -> int:
"collectivespec_source_trace_path": str(source_trace),
"collectivespec_source_trace_sha256": sha256(source_trace),
"collectivespec_source_sampling_strategy": source_sampling_strategy,
"collectivespec_session_closed_within_window": session_coherent_source,
"collectivespec_session_closed_within_window": session_closed_source,
"collectivespec_session_closure_scope": (
"full_session" if full_session_source else "window_only" if window_session_source else None
),
}
)
output_windows = {
@@ -197,13 +206,14 @@ def main() -> int:
},
"temperature": {"distinct_value_count": 1, "all_zero": True},
"session": {
"source_is_session_coherent": session_coherent_source,
"session_root_count": len(session_counts) if session_coherent_source else None,
"source_is_session_coherent": full_session_source,
"source_is_window_session_closed": window_session_source,
"session_root_count": len(session_counts) if session_closed_source else None,
"selected_session_count": (
len(selected_session_turn_counts) if session_coherent_source else None
len(selected_session_turn_counts) if session_closed_source else None
),
"split_session_count": len(split_sessions) if session_coherent_source else None,
"all_selected_or_dropped": not split_sessions if session_coherent_source else None,
"split_session_count": len(split_sessions) if session_closed_source else None,
"all_selected_or_dropped": not split_sessions if session_closed_source else None,
"selected_turns": {
"min": min(selected_session_turn_counts)
if selected_session_turn_counts

View File

@@ -0,0 +1,290 @@
#!/usr/bin/env python3
"""Build a session-closed replay window from an already materialized trace.
This is deliberately a *window-scoped fallback* for when the raw trace span
needed by ``prepare_trace_windows.py`` is unavailable. It closes every known
parent/child edge within the supplied window and groups siblings whose common
parent lies outside the window. It cannot recover turns outside the window;
the manifest records that residual rather than calling the result fully
session-coherent.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import math
from collections import Counter
from pathlib import Path
from typing import Any
SCORE_ALGORITHM = "collectivespec-window-cc-v1"
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def typed_id(value: Any) -> str:
"""Encode IDs without conflating values such as ``1`` and ``"1"``."""
return json.dumps(
{"type": type(value).__name__, "value": value},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
def session_uniform(*, seed: int, window_id: str, component_key: str) -> float:
payload = json.dumps(
{
"algorithm": SCORE_ALGORITHM,
"seed": seed,
"window_id": window_id,
"component_key": component_key,
},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
digest = hashlib.blake2b(payload, digest_size=8).digest()
return int.from_bytes(digest, byteorder="big", signed=False) / float(1 << 64)
def resolve_trace_path(windows_path: Path, trace_file: str) -> Path:
candidate = Path(trace_file)
if candidate.is_absolute():
return candidate
return (windows_path.parent / candidate).resolve()
def is_root_parent(value: Any) -> bool:
return value is None or (
isinstance(value, (int, float)) and not isinstance(value, bool) and int(value) < 0
)
class UnionFind:
def __init__(self) -> None:
self.parent: dict[str, str] = {}
def add(self, item: str) -> None:
self.parent.setdefault(item, item)
def find(self, item: str) -> str:
parent = self.parent[item]
if parent != item:
self.parent[item] = self.find(parent)
return self.parent[item]
def union(self, left: str, right: str) -> None:
left_root, right_root = self.find(left), self.find(right)
if left_root != right_root:
# A canonical representative makes the component key independent
# of trace order and union order.
self.parent[max(left_root, right_root)] = min(left_root, right_root)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--windows-path", type=Path, required=True)
parser.add_argument("--window-id", required=True)
parser.add_argument("--sample-seed", type=int, default=20260325)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--output-window-id", required=True)
args = parser.parse_args()
source_payload = json.loads(args.windows_path.read_text(encoding="utf-8"))
source_windows = source_payload.get("windows") if isinstance(source_payload, dict) else source_payload
if not isinstance(source_windows, list):
raise SystemExit("windows payload must contain a list")
source_window = next(
(item for item in source_windows if item.get("window_id") == args.window_id),
None,
)
if not isinstance(source_window, dict):
raise SystemExit(f"window id not found: {args.window_id}")
trace_file = source_window.get("trace_file")
if not isinstance(trace_file, str) or not trace_file:
raise SystemExit(f"window {args.window_id} has no trace_file")
source_trace = resolve_trace_path(args.windows_path, trace_file)
if not source_trace.is_file():
raise SystemExit(f"source trace does not exist: {source_trace}")
rows: list[dict[str, Any]] = []
id_to_index: dict[str, int] = {}
with source_trace.open("r", encoding="utf-8") as source_handle:
for row_index, raw in enumerate(source_handle):
if not raw.strip():
continue
row = json.loads(raw)
if not isinstance(row, dict):
raise SystemExit(f"row {row_index}: expected an object")
if "chat_id" not in row or "parent_chat_id" not in row:
raise SystemExit(f"row {row_index}: chat_id and parent_chat_id are required")
if "timestamp" not in row:
raise SystemExit(f"row {row_index}: timestamp is required")
try:
timestamp = float(row["timestamp"])
except (TypeError, ValueError) as error:
raise SystemExit(f"row {row_index}: timestamp must be numeric") from error
if not math.isfinite(timestamp):
raise SystemExit(f"row {row_index}: timestamp must be finite")
key = typed_id(row["chat_id"])
if key in id_to_index:
raise SystemExit(f"row {row_index}: duplicate chat_id {row['chat_id']!r}")
id_to_index[key] = len(rows)
rows.append(row)
if not rows:
raise SystemExit("source trace has no rows")
union_find = UnionFind()
for chat_key in id_to_index:
union_find.add(chat_key)
known_parent_edges = 0
absent_parent_edges = 0
root_request_count = 0
for row in rows:
chat_key = typed_id(row["chat_id"])
parent = row["parent_chat_id"]
if is_root_parent(parent):
root_request_count += 1
continue
parent_key = typed_id(parent)
union_find.add(parent_key)
if parent_key in id_to_index:
union_find.union(chat_key, parent_key)
known_parent_edges += 1
else:
# Keep the absent parent as a virtual node. This groups siblings
# with a common boundary parent without pretending that the parent
# turn itself is in this window.
union_find.union(chat_key, parent_key)
absent_parent_edges += 1
component_members: dict[str, list[str]] = {}
for node in union_find.parent:
component_members.setdefault(union_find.find(node), []).append(node)
component_key_by_chat: dict[str, str] = {}
for members in component_members.values():
# Virtual boundary parents intentionally participate: they ensure all
# children of an unavailable parent share a deterministic component.
component_key = min(members)
for chat_key in (member for member in members if member in id_to_index):
component_key_by_chat[chat_key] = component_key
for row in rows:
parent = row["parent_chat_id"]
if is_root_parent(parent):
continue
parent_key = typed_id(parent)
if parent_key in id_to_index and union_find.find(typed_id(row["chat_id"])) != union_find.find(parent_key):
raise SystemExit("same-window parent edge did not close into one component")
args.output_dir.mkdir(parents=True, exist_ok=True)
trace_dir = args.output_dir / "traces"
trace_dir.mkdir(exist_ok=True)
output_trace = trace_dir / f"{args.output_window_id}.jsonl"
score_values: list[float] = []
timestamps: list[float] = []
component_counts: Counter[str] = Counter()
with output_trace.open("w", encoding="utf-8") as handle:
for row in rows:
chat_key = typed_id(row["chat_id"])
component_key = component_key_by_chat[chat_key]
component_id = f"window-component:{component_key}"
out = dict(row)
out["collectivespec_original_sampling_u"] = out.get("sampling_u")
out["session_root"] = component_id
out["sampling_u"] = session_uniform(
seed=args.sample_seed,
window_id=args.window_id,
component_key=component_key,
)
handle.write(json.dumps(out, ensure_ascii=False, separators=(",", ":")) + "\n")
score_values.append(float(out["sampling_u"]))
timestamps.append(float(out["timestamp"]))
component_counts[component_id] += 1
component_sizes = list(component_counts.values())
output_window = dict(source_window)
output_window.update(
{
"window_id": args.output_window_id,
"trace_file": str(output_trace.resolve()),
"num_requests": len(rows),
"sampling_strategy": "window_session_closed_uniform_score",
"sampling_seed": args.sample_seed,
"collectivespec_sampling_algorithm": SCORE_ALGORITHM,
"collectivespec_source_window_id": args.window_id,
"collectivespec_source_trace_path": str(source_trace),
"collectivespec_source_trace_sha256": sha256(source_trace),
"collectivespec_session_closure_scope": "window_only",
"collectivespec_known_parent_edges": known_parent_edges,
"collectivespec_absent_parent_edges": absent_parent_edges,
}
)
output_windows = {
"kind": "collectivespec_window_session_closed_fallback",
"source_windows_path": str(args.windows_path.resolve()),
"source_window_id": args.window_id,
"sample_seed": args.sample_seed,
"windows": [output_window],
}
output_manifest = args.output_dir / "windows.json"
output_manifest.write_text(
json.dumps(output_windows, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
sanity = {
"n": len(rows),
"timestamp": {
"min": min(timestamps),
"max": max(timestamps),
"non_negative": all(value >= 0 for value in timestamps),
"nondecreasing": all(
timestamps[index] >= timestamps[index - 1]
for index in range(1, len(timestamps))
),
},
"sampling_u": {
"min": min(score_values),
"max": max(score_values),
"distinct_value_count": len(set(score_values)),
"within_0_1": all(0 <= value < 1 for value in score_values),
},
"window_session_closure": {
"component_count": len(component_sizes),
"component_size_min": min(component_sizes),
"component_size_max": max(component_sizes),
"component_size_distinct_value_count": len(set(component_sizes)),
"known_parent_edges": known_parent_edges,
"absent_parent_edges": absent_parent_edges,
"root_request_count": root_request_count,
"all_known_parent_edges_closed": True,
"scope": "window_only",
},
"source_trace_sha256": sha256(source_trace),
"output_trace_sha256": sha256(output_trace),
}
print(
json.dumps(
{
"windows_path": str(output_manifest),
"trace_path": str(output_trace),
"data_sanity": sanity,
},
ensure_ascii=False,
indent=2,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())