mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-12 20:15:31 +08:00
feat: vectorize CAM retrieval with NumPy and add multi-worker support
- Replace scalar hamming distance with NumPy bitwise_count for batch retrieval - Add ThreadPoolExecutor-based multi-worker query parallelism - Improve missing dataset error message with generation command hint - Increase DEFAULT_MAX_QUERIES from 128 to 8192 for meaningful throughput tests
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import numpy as np
|
||||
|
||||
from scripts.prepare_cam_retrieval_dataset import (
|
||||
DEFAULT_MAX_QUERIES,
|
||||
dataset_config,
|
||||
pack_bits_to_words_le,
|
||||
stratified_indices,
|
||||
@@ -15,6 +16,10 @@ def test_dataset_config_resolves_cifar10_and_cifar100() -> None:
|
||||
assert dataset_config("cifar100") == ("uoft-cs/cifar100", "fine_label")
|
||||
|
||||
|
||||
def test_default_query_count_is_large_enough_for_software_throughput() -> None:
|
||||
assert DEFAULT_MAX_QUERIES == 8192
|
||||
|
||||
|
||||
def test_dataset_config_rejects_unknown_dataset() -> None:
|
||||
try:
|
||||
dataset_config("mnist")
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HW_SIM_DIR = Path(__file__).resolve().parents[1] / "hw" / "sim"
|
||||
if str(HW_SIM_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(HW_SIM_DIR))
|
||||
if str(HW_SIM_DIR) in sys.path:
|
||||
sys.path.remove(str(HW_SIM_DIR))
|
||||
sys.path.insert(0, str(HW_SIM_DIR))
|
||||
benchmark_path = HW_SIM_DIR / "benchmarks" / "retrieval" / "test_retrieval_benchmark.py"
|
||||
spec = importlib.util.spec_from_file_location("hw_retrieval_benchmark", benchmark_path)
|
||||
assert spec is not None
|
||||
assert spec.loader is not None
|
||||
hw_retrieval_benchmark = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = hw_retrieval_benchmark
|
||||
spec.loader.exec_module(hw_retrieval_benchmark)
|
||||
|
||||
from benchmarks.retrieval.test_retrieval_benchmark import ( # noqa: E402
|
||||
QueryTiming,
|
||||
build_hardware_performance,
|
||||
summarize_query_timings,
|
||||
write_outputs,
|
||||
)
|
||||
QueryTiming = hw_retrieval_benchmark.QueryTiming
|
||||
build_hardware_performance = hw_retrieval_benchmark.build_hardware_performance
|
||||
summarize_query_timings = hw_retrieval_benchmark.summarize_query_timings
|
||||
write_outputs = hw_retrieval_benchmark.write_outputs
|
||||
|
||||
|
||||
def test_summarize_query_timings_uses_query_only_accept_to_last_cycles() -> None:
|
||||
|
||||
@@ -76,8 +76,10 @@ def test_run_benchmark_reports_quality_and_query_speed(tmp_path):
|
||||
timer_ns=lambda: next(timer_values),
|
||||
)
|
||||
|
||||
assert result["mode"] == "software-hamming"
|
||||
assert result["mode"] == "software-numpy"
|
||||
assert result["status"] == "pass"
|
||||
assert result["params"]["engine"] == "numpy"
|
||||
assert result["params"]["workers"] == 1
|
||||
assert result["dataset"]["num_queries"] == 2
|
||||
assert result["params"]["num_rows"] == 3
|
||||
assert result["params"]["topk_k"] == 2
|
||||
@@ -88,6 +90,59 @@ def test_run_benchmark_reports_quality_and_query_speed(tmp_path):
|
||||
assert result["performance"]["queries_per_second"] == 1_000_000.0
|
||||
|
||||
|
||||
def test_numpy_topk_matches_reference_with_tiebreak(tmp_path):
|
||||
bench = load_sw_benchmark()
|
||||
dataset_path = tmp_path / "dataset.npz"
|
||||
np.savez_compressed(
|
||||
dataset_path,
|
||||
rows_words=np.array(
|
||||
[
|
||||
[0b11110000],
|
||||
[0b11110000],
|
||||
[0b11100000],
|
||||
[0b00001111],
|
||||
],
|
||||
dtype=np.uint64,
|
||||
),
|
||||
row_labels=np.array([0, 0, 0, 1], dtype=np.int64),
|
||||
queries_words=np.array([[0b11110000]], dtype=np.uint64),
|
||||
query_labels=np.array([0], dtype=np.int64),
|
||||
)
|
||||
dataset = bench.load_retrieval_dataset_npz(dataset_path)
|
||||
|
||||
assert bench.match_topk_numpy(dataset.queries_words[0], dataset.rows_words, width=64, k=3) == [0, 1, 2]
|
||||
|
||||
|
||||
def test_numpy_batch_topk_vectorizes_across_queries(monkeypatch):
|
||||
bench = load_sw_benchmark()
|
||||
rows_words = np.array(
|
||||
[
|
||||
[0b11110000],
|
||||
[0b11100000],
|
||||
[0b00001111],
|
||||
],
|
||||
dtype=np.uint64,
|
||||
)
|
||||
queries_words = np.array(
|
||||
[
|
||||
[0b11110000],
|
||||
[0b00001111],
|
||||
],
|
||||
dtype=np.uint64,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
bench,
|
||||
"match_topk_numpy",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("scalar path called")),
|
||||
)
|
||||
|
||||
assert bench._match_topk_numpy_batch(queries_words, rows_words, width=64, k=2) == [
|
||||
[0, 1],
|
||||
[2, 1],
|
||||
]
|
||||
|
||||
|
||||
def test_run_benchmark_exact_match_compares_against_reference(tmp_path, monkeypatch):
|
||||
bench = load_sw_benchmark()
|
||||
dataset_path = tmp_path / "dataset.npz"
|
||||
@@ -105,6 +160,35 @@ def test_run_benchmark_exact_match_compares_against_reference(tmp_path, monkeypa
|
||||
assert result["metrics"]["1"]["exact_match_rate"] == 0.0
|
||||
|
||||
|
||||
def test_run_benchmark_threaded_numpy_matches_single_worker(tmp_path):
|
||||
bench = load_sw_benchmark()
|
||||
dataset_path = tmp_path / "dataset.npz"
|
||||
_write_dataset(dataset_path)
|
||||
|
||||
single = bench.run_benchmark(
|
||||
dataset_path,
|
||||
hash_bits=64,
|
||||
topk_values=(1, 2),
|
||||
run_id="single",
|
||||
workers=1,
|
||||
timer_ns=lambda: 0,
|
||||
)
|
||||
threaded = bench.run_benchmark(
|
||||
dataset_path,
|
||||
hash_bits=64,
|
||||
topk_values=(1, 2),
|
||||
run_id="threaded",
|
||||
workers=2,
|
||||
timer_ns=lambda: 0,
|
||||
)
|
||||
|
||||
assert single["mode"] == "software-numpy"
|
||||
assert threaded["mode"] == "software-numpy"
|
||||
assert single["params"]["engine"] == "numpy"
|
||||
assert threaded["params"]["workers"] == 2
|
||||
assert threaded["metrics"] == single["metrics"]
|
||||
|
||||
|
||||
def test_run_benchmark_rejects_hash_bits_that_do_not_match_npz_width(tmp_path):
|
||||
bench = load_sw_benchmark()
|
||||
dataset_path = tmp_path / "dataset.npz"
|
||||
@@ -114,13 +198,46 @@ def test_run_benchmark_rejects_hash_bits_that_do_not_match_npz_width(tmp_path):
|
||||
bench.run_benchmark(dataset_path, hash_bits=8)
|
||||
|
||||
|
||||
def test_cli_missing_dataset_prints_generation_hint_without_traceback(monkeypatch, capsys):
|
||||
bench = load_sw_benchmark()
|
||||
missing_path = Path("outputs/cam_retrieval_benchmark/datasets/cifar100_hash512_rows512_queries8192.npz")
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"sw_retrieval_benchmark.py",
|
||||
"--dataset",
|
||||
str(missing_path),
|
||||
"--hash-bits",
|
||||
"512",
|
||||
"--topk-k",
|
||||
"5",
|
||||
"--workers",
|
||||
"1",
|
||||
],
|
||||
)
|
||||
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
bench.main()
|
||||
|
||||
assert exc_info.value.code == 2
|
||||
captured = capsys.readouterr()
|
||||
assert "retrieval dataset not found" in captured.err
|
||||
assert "python scripts/prepare_cam_retrieval_dataset.py" in captured.err
|
||||
assert "--dataset cifar100" in captured.err
|
||||
assert "--num-rows 512" in captured.err
|
||||
assert "--max-queries 8192" in captured.err
|
||||
assert "--hash-bits 512" in captured.err
|
||||
assert "Traceback" not in captured.err
|
||||
|
||||
|
||||
def test_write_outputs_includes_quality_and_performance_csv(tmp_path):
|
||||
bench = load_sw_benchmark()
|
||||
result = {
|
||||
"run_id": "unit-test",
|
||||
"mode": "software-hamming",
|
||||
"mode": "software-numpy",
|
||||
"status": "pass",
|
||||
"params": {"num_rows": 3, "hash_bits": 64, "topk_k": 2},
|
||||
"params": {"num_rows": 3, "hash_bits": 64, "topk_k": 2, "workers": 2, "engine": "numpy"},
|
||||
"dataset": {"num_classes": 2, "num_queries": 2},
|
||||
"metrics": {
|
||||
"1": {
|
||||
@@ -149,4 +266,7 @@ def test_write_outputs_includes_quality_and_performance_csv(tmp_path):
|
||||
assert "queries_per_second" in metrics_csv
|
||||
assert "1000000.0" in metrics_csv
|
||||
assert "Software CAM Retrieval Benchmark Summary" in summary_md
|
||||
assert "software-numpy" in metrics_csv
|
||||
assert "workers" in metrics_csv
|
||||
assert "workers: `2`" in summary_md
|
||||
assert "queries_per_second" in summary_md
|
||||
|
||||
Reference in New Issue
Block a user