Trace P0 batch-level DP coordination

This commit is contained in:
2026-07-13 17:45:08 +08:00
parent f1cd859eea
commit 7f4ae1708b
3 changed files with 82 additions and 16 deletions

View File

@@ -15,6 +15,38 @@ class P0Worker(Worker):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._p0_execute_epoch = 0 self._p0_execute_epoch = 0
self._p0_batch_epoch = 0
def init_device(self): # type: ignore[no-untyped-def]
result = super().init_device()
model_runner = self.model_runner
if getattr(model_runner, "_collectivespec_p0_wrapped", False):
return result
original = model_runner._determine_batch_execution_and_padding
def traced_determine(*args: Any, **kwargs: Any): # type: ignore[no-untyped-def]
output = original(*args, **kwargs)
self._p0_batch_epoch += 1
scheduler_output = kwargs.get("scheduler_output")
if scheduler_output is None and args:
scheduler_output = args[0]
cudagraph_mode, batch_desc, should_ubatch, rows_across_dp, _ = output
log_event(
"worker",
"batch_execution_plan",
batch_phase_epoch=self._p0_batch_epoch,
**self._p0_rank_fields(),
**plan_summary(scheduler_output),
cudagraph_mode=getattr(cudagraph_mode, "value", str(cudagraph_mode)),
physical_batch_rows=getattr(batch_desc, "num_tokens", None),
should_ubatch=should_ubatch,
rows_across_dp=rows_across_dp,
)
return output
model_runner._determine_batch_execution_and_padding = traced_determine
model_runner._collectivespec_p0_wrapped = True
return result
def _p0_rank_fields(self) -> dict[str, Any]: def _p0_rank_fields(self) -> dict[str, Any]:
config = self.vllm_config.parallel_config config = self.vllm_config.parallel_config

View File

@@ -200,6 +200,7 @@ def main() -> int:
"overlay": str(overlay), "overlay": str(overlay),
"run_id": args.run_id, "run_id": args.run_id,
"topology_claim": "inherited_from_base_spec", "topology_claim": "inherited_from_base_spec",
"gpu_count": base.get("hardware", {}).get("gpu_count"),
"request_count": args.request_count, "request_count": args.request_count,
"completion_tokens": args.completion_tokens, "completion_tokens": args.completion_tokens,
"policies": ["control_k3", "heterogeneous"], "policies": ["control_k3", "heterogeneous"],

View File

@@ -97,19 +97,26 @@ def probe_integrity(cell: Path, expected_requests: int, expected_tokens: int) ->
} }
def rank_key(event: dict[str, Any]) -> str | None:
dp_rank = event.get("data_parallel_rank")
local_rank = event.get("local_rank")
if dp_rank is None or local_rank is None:
return None
return f"dp{dp_rank}/local{local_rank}"
def sequence_agreement(worker_events: list[dict[str, Any]]) -> dict[str, Any]: def sequence_agreement(worker_events: list[dict[str, Any]]) -> dict[str, Any]:
begins = [event for event in worker_events if event.get("event") == "target_execute_begin"] begins = [event for event in worker_events if event.get("event") == "batch_execution_plan"]
per_rank: dict[str, list[dict[str, Any]]] = defaultdict(list) per_rank: dict[str, list[dict[str, Any]]] = defaultdict(list)
per_dp: dict[str, set[str]] = defaultdict(set) per_dp: dict[str, set[str]] = defaultdict(set)
for event in begins: for event in begins:
rank = event.get("global_rank") rank = rank_key(event)
dp_rank = event.get("data_parallel_rank") dp_rank = event.get("data_parallel_rank")
if rank is None: if rank is None:
continue continue
key = str(rank) per_rank[rank].append(event)
per_rank[key].append(event)
if dp_rank is not None: if dp_rank is not None:
per_dp[str(dp_rank)].add(key) per_dp[str(dp_rank)].add(rank)
sequences: dict[str, list[str]] = {} sequences: dict[str, list[str]] = {}
for rank, values in per_rank.items(): for rank, values in per_rank.items():
values.sort(key=lambda item: (integer(item.get("worker_phase_epoch")) or -1, float(item.get("monotonic_s", 0.0)))) values.sort(key=lambda item: (integer(item.get("worker_phase_epoch")) or -1, float(item.get("monotonic_s", 0.0))))
@@ -117,7 +124,7 @@ def sequence_agreement(worker_events: list[dict[str, Any]]) -> dict[str, Any]:
counts = {rank: len(values) for rank, values in sequences.items()} counts = {rank: len(values) for rank, values in sequences.items()}
within_dp: dict[str, Any] = {} within_dp: dict[str, Any] = {}
for dp_rank, ranks in per_dp.items(): for dp_rank, ranks in per_dp.items():
rank_list = sorted(ranks, key=int) rank_list = sorted(ranks)
distinct = {tuple(sequences[rank]) for rank in rank_list} distinct = {tuple(sequences[rank]) for rank in rank_list}
within_dp[dp_rank] = { within_dp[dp_rank] = {
"ranks": rank_list, "ranks": rank_list,
@@ -129,7 +136,7 @@ def sequence_agreement(worker_events: list[dict[str, Any]]) -> dict[str, Any]:
return { return {
"worker_begin_event_count": len(begins), "worker_begin_event_count": len(begins),
"worker_rank_count": len(sequences), "worker_rank_count": len(sequences),
"phase_count_by_global_rank": counts, "phase_count_by_rank": counts,
"phase_count": { "phase_count": {
"n": len(count_values), "n": len(count_values),
"min": min(count_values) if count_values else None, "min": min(count_values) if count_values else None,
@@ -152,12 +159,13 @@ def summarize_cell(cell: Path, expected_requests: int, expected_tokens: int) ->
verify_schedule_events = [ verify_schedule_events = [
event for event in schedule_events if integer(event.get("spec_request_count")) not in (None, 0) event for event in schedule_events if integer(event.get("spec_request_count")) not in (None, 0)
] ]
worker_metadata = [ batch_events = [event for event in worker_events if event.get("event") == "batch_execution_plan"]
event.get("scheduled_dp_metadata") rows_across_dp = [event.get("rows_across_dp") for event in batch_events]
for event in worker_events physical_rows = [
if event.get("event") == "target_execute_begin" integer(event.get("physical_batch_rows"))
for event in batch_events
if integer(event.get("physical_batch_rows")) is not None
] ]
metadata_present = sum(value is not None for value in worker_metadata)
return { return {
"probe_integrity": probe_integrity(cell, expected_requests, expected_tokens), "probe_integrity": probe_integrity(cell, expected_requests, expected_tokens),
"event_parse_errors": parse_errors, "event_parse_errors": parse_errors,
@@ -179,13 +187,20 @@ def summarize_cell(cell: Path, expected_requests: int, expected_tokens: int) ->
}, },
"worker": { "worker": {
**sequence_agreement(worker_events), **sequence_agreement(worker_events),
"dp_metadata_present_count": metadata_present, "batch_execution_plan_count": len(batch_events),
"dp_metadata_missing_count": len(worker_metadata) - metadata_present, "rows_across_dp_present_count": sum(value is not None for value in rows_across_dp),
"physical_batch_rows": {
"n": len(physical_rows),
"min": min(physical_rows) if physical_rows else None,
"max": max(physical_rows) if physical_rows else None,
"distinct_value_count": len(set(physical_rows)),
"non_negative": all(value >= 0 for value in physical_rows),
},
}, },
} }
def data_sanity(cells: dict[str, dict[str, Any]]) -> dict[str, Any]: def data_sanity(cells: dict[str, dict[str, Any]], expected_gpu_count: int | None) -> dict[str, Any]:
event_counts = [integer(cell.get("event_count")) or 0 for cell in cells.values()] event_counts = [integer(cell.get("event_count")) or 0 for cell in cells.values()]
candidate_distinct = [ candidate_distinct = [
integer(cell.get("scheduler", {}).get("after_k_distinct_value_count")) or 0 integer(cell.get("scheduler", {}).get("after_k_distinct_value_count")) or 0
@@ -220,6 +235,20 @@ def data_sanity(cells: dict[str, dict[str, Any]]) -> dict[str, Any]:
bool(cell.get("worker", {}).get("all_within_dp_sequences_identical")) bool(cell.get("worker", {}).get("all_within_dp_sequences_identical"))
for cell in cells.values() for cell in cells.values()
), ),
"all_dp_coordination_records_observed": all(
integer(cell.get("worker", {}).get("rows_across_dp_present_count"))
== integer(cell.get("worker", {}).get("batch_execution_plan_count"))
and integer(cell.get("worker", {}).get("batch_execution_plan_count", 0)) > 0
for cell in cells.values()
),
"all_expected_workers_observed": (
all(
integer(cell.get("worker", {}).get("worker_rank_count")) == expected_gpu_count
for cell in cells.values()
)
if expected_gpu_count is not None
else None
),
}, },
} }
@@ -282,7 +311,11 @@ def main() -> int:
policy: summarize_cell(args.root / policy, expected_requests, expected_tokens) policy: summarize_cell(args.root / policy, expected_requests, expected_tokens)
for policy in policies for policy in policies
} }
payload = {"manifest": manifest, "cells": cells, "data_sanity": data_sanity(cells)} payload = {
"manifest": manifest,
"cells": cells,
"data_sanity": data_sanity(cells, integer(manifest.get("gpu_count"))),
}
args.output_json.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") args.output_json.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
args.output_md.write_text(markdown(payload), encoding="utf-8") args.output_md.write_text(markdown(payload), encoding="utf-8")
print(json.dumps(payload, indent=2, sort_keys=True)) print(json.dumps(payload, indent=2, sort_keys=True))