Spaces:
Paused
Paused
File size: 8,315 Bytes
6342806 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | """Compute deduplication over the bevfusion pipeline + quantified savings.
The insight: when N models are evaluated on the same scenario, the sensor
simulation, BEV rasterization and fusion decode (the "backbone-equivalent"
stages of sensorflow.bevfusion — build_modality_map / fuse_maps / decode_bev)
are IDENTICAL across models. Only the model-specific head (confidence
recalibration + class thresholds here) differs. So:
naive cost = scenarios x models x (backbone + head)
optimized cost = scenarios x backbone + scenarios x models x head
The shared stage is cached content-addressed (cache.py) under
(scenario_id, sensor_version, preprocessing_version, backbone_version,
feature_schema_version); a version bump on any component is a miss by
construction. `benchmark` runs the real pipeline twice and reports MEASURED
timings and hit rates — no estimated numbers.
Honest scope: the "backbone" is the numpy bevfusion pipeline, not a neural
network, and the "heads" are deterministic calibration/threshold profiles.
The cost STRUCTURE (shared expensive stage, cheap per-model stage) is the
real thing being demonstrated; absolute times would scale with a real stack.
"""
from __future__ import annotations
import time
from typing import Dict, List, Optional
from uuid import uuid4
from sensorflow.bevfusion.fusion import build_modality_map, decode_bev, fuse_maps
from sensorflow.bevfusion.geometry import BEVGrid
from sensorflow.bevfusion.scenes import SceneSequence, generate_sequences
from sensorflow.bevfusion.sensors import (
camera_rng, lidar_rng, simulate_camera, simulate_lidar,
)
from sensorflow.nextgen import store
from sensorflow.nextgen.cache import CacheKeyVersions, get_feature_cache
from sensorflow.nextgen.models import ComputeOptimizationResult
# Model-specific head profiles: deterministic post-processing over the shared
# fused features. Cheap by design — that is the point of the split.
HEAD_PROFILES: Dict[str, Dict[str, float]] = {
"candidate-v4": {"confidence_gain": 1.05, "min_confidence": 0.30,
"vru_boost": 1.10},
"candidate-v4.1": {"confidence_gain": 1.02, "min_confidence": 0.35,
"vru_boost": 1.15},
"baseline-v3": {"confidence_gain": 1.00, "min_confidence": 0.40,
"vru_boost": 1.00},
}
VRU = {"pedestrian", "cyclist", "motorcycle"}
def compute_backbone_features(seq: SceneSequence, seed: int = 7,
seq_index: int = 0) -> List[Dict]:
"""Shared stage: sensor sim -> BEV rasterization -> fusion -> decode.
REUSES the bevfusion pipeline verbatim; returns JSON-serializable
per-frame decoded fused boxes (the cacheable IR)."""
grid = BEVGrid()
cam_gen = camera_rng(seed, seq_index)
lid_gen = lidar_rng(seed, seq_index)
frames: List[Dict] = []
for frame in seq.frames:
cam = simulate_camera(frame, seq, cam_gen)
lid = simulate_lidar(frame, seq, lid_gen)
fused = fuse_maps(build_modality_map(cam, grid, "camera"),
build_modality_map(lid, grid, "lidar"))
decoded = decode_bev(fused)
frames.append({"frame_id": frame.frame_id,
"boxes": [{"bbox_3d": b["bbox_3d"],
"class_name": b["class_name"],
"confidence": round(float(b["confidence"]), 4)}
for b in decoded]})
return frames
def run_model_head(features: List[Dict], model_version: str) -> Dict:
"""Model-specific stage: recalibrate + threshold the shared features."""
prof = HEAD_PROFILES.get(model_version, HEAD_PROFILES["baseline-v3"])
n_out = 0
n_vru = 0
conf_sum = 0.0
for frame in features:
for b in frame["boxes"]:
c = b["confidence"] * prof["confidence_gain"]
if b["class_name"] in VRU:
c *= prof["vru_boost"]
if c >= prof["min_confidence"]:
n_out += 1
conf_sum += min(c, 1.0)
if b["class_name"] in VRU:
n_vru += 1
return {"model_version": model_version, "n_detections": n_out,
"n_vru": n_vru,
"mean_confidence": round(conf_sum / n_out, 4) if n_out else None}
def evaluate_scenarios(sequences: List[SceneSequence], models: List[str],
versions: Optional[CacheKeyVersions] = None,
seed: int = 7) -> Dict:
"""Evaluate models x scenarios with backbone dedup. Returns measured
per-stage timings + head outputs."""
versions = versions or CacheKeyVersions()
cache = get_feature_cache()
backbone_times: List[float] = []
head_times: List[float] = []
outputs = []
t_start = time.perf_counter()
for si, seq in enumerate(sequences):
key = versions.key_for(seq.sequence_id, kind="bev-features")
def _compute(seq=seq, si=si):
t0 = time.perf_counter()
feats = compute_backbone_features(seq, seed=seed, seq_index=si)
backbone_times.append(time.perf_counter() - t0)
return feats
features = cache.get_or_compute(key, _compute, kind="bev-features")
for model in models:
t0 = time.perf_counter()
head = run_model_head(features, model)
head_times.append(time.perf_counter() - t0)
outputs.append({"scenario_id": seq.sequence_id, **head})
wall = time.perf_counter() - t_start
return {"outputs": outputs, "wall_s": wall,
"backbone_times": backbone_times, "head_times": head_times}
def benchmark(n_scenarios: int = 6, frames_per_sequence: int = 20,
models: Optional[List[str]] = None, seed: int = 7,
versions: Optional[CacheKeyVersions] = None,
persist: bool = True) -> ComputeOptimizationResult:
"""Real benchmark: cold pass (misses) + warm pass (hits) over the same
suite; every timing measured, nothing estimated except the naive
extrapolation which uses the MEASURED mean backbone/head times."""
models = models or list(HEAD_PROFILES)
versions = versions or CacheKeyVersions()
cache = get_feature_cache()
cache.reset_stats()
sequences = generate_sequences(n_sequences=n_scenarios,
frames_per_sequence=frames_per_sequence,
seed=seed)
cold = evaluate_scenarios(sequences, models, versions, seed)
warm = evaluate_scenarios(sequences, models, versions, seed)
stats = cache.stats
all_backbone = cold["backbone_times"] + warm["backbone_times"]
all_heads = cold["head_times"] + warm["head_times"]
mean_backbone = sum(all_backbone) / len(all_backbone) if all_backbone else 0.0
mean_head = sum(all_heads) / len(all_heads) if all_heads else 0.0
n_units = 2 * len(sequences) * len(models) # two passes
naive_cost = n_units * (mean_backbone + mean_head)
optimized_cost = cold["wall_s"] + warm["wall_s"]
result = ComputeOptimizationResult(
report_id=f"compute-{uuid4().hex[:8]}",
n_scenarios=len(sequences), n_models=len(models),
naive_full_inferences=n_units,
optimized_backbone_computes=stats.misses,
optimized_head_computes=len(all_heads),
cache_hits=stats.hits, cache_misses=stats.misses,
hit_rate=round(stats.hit_rate, 4),
naive_cost_s=round(naive_cost, 4),
optimized_cost_s=round(optimized_cost, 4),
savings_ratio=round(1.0 - optimized_cost / naive_cost, 4) if naive_cost else 0.0,
measured_backbone_s=round(mean_backbone, 4),
measured_head_s=round(mean_head, 6),
invalidation="content-addressed keys over (scenario_id, sensor, "
"preprocessing, backbone, feature-schema versions); any "
"version bump is a new key (cross-version reuse "
"impossible); LRU eviction bounds disk growth")
if persist:
store.write_json(result.model_dump(mode="json"), "compute",
f"{result.report_id}.json")
store.write_json(result.model_dump(mode="json"), "compute", "latest.json")
return result
def latest_report() -> Optional[Dict]:
return store.read_json("compute", "latest.json")
|