mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-12 20:15:31 +08:00
feat: add hardware retrieval cycle performance measurement
Add cycle-level performance measurement for hardware CAM retrieval benchmarks to complement existing quality metrics. - Add query_topk_once_with_latency with accept→first/last cycle timing - Add QueryTiming dataclass and summarize_query_timings helper - Integrate cycle performance into benchmark outputs (CSV + Markdown) - Log RETRIEVAL_PERF_RESULT with cycles/query and queries/cycle - Update experiment docs with hardware cycle performance section - Add unit tests for summarize_query_timings and output writers
This commit is contained in:
@@ -20,7 +20,7 @@ from tests.top.utils import (
|
||||
dut_lanes,
|
||||
dut_num_rows,
|
||||
get_param,
|
||||
query_topk_once,
|
||||
query_topk_once_with_latency,
|
||||
reset_dut,
|
||||
write_rows,
|
||||
)
|
||||
@@ -46,6 +46,13 @@ class RetrievalDataset:
|
||||
seed: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class QueryTiming:
|
||||
accept_to_first_result_cycles: int
|
||||
accept_to_last_result_cycles: int
|
||||
total_query_cycles: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MetricAccumulator:
|
||||
precision_sum: float = 0.0
|
||||
@@ -203,6 +210,38 @@ def mode_from_params(write_noise_en: int) -> str:
|
||||
return "no_noise"
|
||||
|
||||
|
||||
def summarize_query_timings(timings: list[QueryTiming]) -> dict[str, float]:
|
||||
if not timings:
|
||||
return {
|
||||
"num_queries": 0,
|
||||
"total_query_cycles": 0,
|
||||
"mean_total_query_cycles": 0.0,
|
||||
"min_total_query_cycles": 0,
|
||||
"max_total_query_cycles": 0,
|
||||
"mean_accept_to_first_result_cycles": 0.0,
|
||||
"mean_accept_to_last_result_cycles": 0.0,
|
||||
"cycles_per_query": 0.0,
|
||||
"queries_per_cycle": 0.0,
|
||||
}
|
||||
|
||||
total_cycles = sum(t.total_query_cycles for t in timings)
|
||||
total_first = sum(t.accept_to_first_result_cycles for t in timings)
|
||||
total_last = sum(t.accept_to_last_result_cycles for t in timings)
|
||||
count = len(timings)
|
||||
mean_last = total_last / float(count)
|
||||
return {
|
||||
"num_queries": count,
|
||||
"total_query_cycles": total_cycles,
|
||||
"mean_total_query_cycles": total_cycles / float(count),
|
||||
"min_total_query_cycles": min(t.total_query_cycles for t in timings),
|
||||
"max_total_query_cycles": max(t.total_query_cycles for t in timings),
|
||||
"mean_accept_to_first_result_cycles": total_first / float(count),
|
||||
"mean_accept_to_last_result_cycles": mean_last,
|
||||
"cycles_per_query": mean_last,
|
||||
"queries_per_cycle": count / float(total_cycles) if total_cycles > 0 else 0.0,
|
||||
}
|
||||
|
||||
|
||||
def output_dir_for(mode: str) -> Path:
|
||||
run_id = os.environ.get("CAM_RETRIEVAL_RUN_ID")
|
||||
if not run_id:
|
||||
@@ -225,7 +264,9 @@ def write_outputs(out_dir: Path, result: dict) -> None:
|
||||
"write_noise_en", "write_noise_rate_num",
|
||||
"write_noise_rate_den",
|
||||
"num_queries", "k", "macro_precision", "retrieval_recall", "macro_f1",
|
||||
"recall@k", "exact_match_rate", "status",
|
||||
"recall@k", "exact_match_rate", "cycles_per_query",
|
||||
"mean_accept_to_first_result_cycles", "mean_accept_to_last_result_cycles",
|
||||
"mean_total_query_cycles", "total_query_cycles", "queries_per_cycle", "status",
|
||||
]
|
||||
with metrics_csv.open("w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
||||
@@ -248,6 +289,16 @@ def write_outputs(out_dir: Path, result: dict) -> None:
|
||||
"macro_f1": metrics["macro_f1"],
|
||||
"recall@k": metrics["recall@k"],
|
||||
"exact_match_rate": metrics["exact_match_rate"],
|
||||
"cycles_per_query": result.get("performance", {}).get("cycles_per_query", ""),
|
||||
"mean_accept_to_first_result_cycles": result.get("performance", {}).get(
|
||||
"mean_accept_to_first_result_cycles", "",
|
||||
),
|
||||
"mean_accept_to_last_result_cycles": result.get("performance", {}).get(
|
||||
"mean_accept_to_last_result_cycles", "",
|
||||
),
|
||||
"mean_total_query_cycles": result.get("performance", {}).get("mean_total_query_cycles", ""),
|
||||
"total_query_cycles": result.get("performance", {}).get("total_query_cycles", ""),
|
||||
"queries_per_cycle": result.get("performance", {}).get("queries_per_cycle", ""),
|
||||
"status": result["status"],
|
||||
}
|
||||
writer.writerow(row)
|
||||
@@ -260,6 +311,16 @@ def write_outputs(out_dir: Path, result: dict) -> None:
|
||||
f"- status: `{result['status']}`",
|
||||
f"- num_queries: `{result['dataset']['num_queries']}`",
|
||||
"",
|
||||
"## Hardware performance",
|
||||
"",
|
||||
f"- cycles_per_query: `{result.get('performance', {}).get('cycles_per_query', '')}`",
|
||||
f"- accept_to_first_result_cycles: `{result.get('performance', {}).get('mean_accept_to_first_result_cycles', '')}`",
|
||||
f"- accept_to_last_result_cycles: `{result.get('performance', {}).get('mean_accept_to_last_result_cycles', '')}`",
|
||||
f"- total_query_cycles: `{result.get('performance', {}).get('total_query_cycles', '')}`",
|
||||
f"- queries_per_cycle: `{result.get('performance', {}).get('queries_per_cycle', '')}`",
|
||||
"",
|
||||
"## Retrieval quality",
|
||||
"",
|
||||
"| k | macro_precision | retrieval_recall | macro_f1 | recall@k | exact_match_rate |",
|
||||
"|---:|---:|---:|---:|---:|---:|",
|
||||
]
|
||||
@@ -300,9 +361,11 @@ async def cam_retrieval_benchmark(dut):
|
||||
await write_rows(dut, dataset.rows)
|
||||
|
||||
accumulators = {k: MetricAccumulator() for k in BENCHMARK_KS}
|
||||
timings: list[QueryTiming] = []
|
||||
|
||||
for query, query_label in zip(dataset.queries, dataset.query_labels):
|
||||
beats, _, _, _ = await query_topk_once(dut, query)
|
||||
beats, _, _, _, timing = await query_topk_once_with_latency(dut, query)
|
||||
timings.append(QueryTiming(**timing))
|
||||
if len(beats) < max(BENCHMARK_KS):
|
||||
raise AssertionError(f"Expected at least {max(BENCHMARK_KS)} Top-K beats, got {len(beats)}")
|
||||
|
||||
@@ -339,6 +402,7 @@ async def cam_retrieval_benchmark(dut):
|
||||
"seed": dataset.seed,
|
||||
},
|
||||
"metrics": {str(k): accumulators[k].as_dict() for k in BENCHMARK_KS},
|
||||
"performance": summarize_query_timings(timings),
|
||||
}
|
||||
|
||||
out_dir = output_dir_for(mode)
|
||||
@@ -353,6 +417,21 @@ async def cam_retrieval_benchmark(dut):
|
||||
str(out_dir.relative_to(_project_root())),
|
||||
)
|
||||
|
||||
performance = result["performance"]
|
||||
dut._log.info(
|
||||
"RETRIEVAL_PERF_RESULT mode=%s num_queries=%d cycles_per_query=%.6f "
|
||||
"accept_to_first_result_cycles=%.6f accept_to_last_result_cycles=%.6f "
|
||||
"total_query_cycles=%d queries_per_cycle=%.9f status=pass output_dir=%s",
|
||||
mode,
|
||||
performance["num_queries"],
|
||||
performance["cycles_per_query"],
|
||||
performance["mean_accept_to_first_result_cycles"],
|
||||
performance["mean_accept_to_last_result_cycles"],
|
||||
performance["total_query_cycles"],
|
||||
performance["queries_per_cycle"],
|
||||
str(out_dir.relative_to(_project_root())),
|
||||
)
|
||||
|
||||
if write_noise_en == 0:
|
||||
assert result["metrics"]["5"]["exact_match_rate"] == 1.0, (
|
||||
f"Expected perfect exact match with no noise, got "
|
||||
|
||||
Reference in New Issue
Block a user