rpx_benchmark.model_profiler
Unified model profiler for all RPX tasks.
One class, any model, all metrics. No per-task boilerplate.
Usage
from rpx_benchmark.model_profiler import ModelProfiler
profiler = ModelProfiler(model)
report = profiler.profile()
# Hardware-agnostic (same on any machine)
print(report.params_m) # 524.4
print(report.flops_g) # 187.3
# Measured (hardware-specific, tagged with system card)
print(report.latency_p50_ms) # 142.7
print(report.memory_peak_mb) # 3200.0
# Deployability verdicts (novel — directly answers "can I use this?")
print(report.realtime_at_hz(10)) # False (needs 142ms, budget is 100ms)
print(report.realtime_at_hz(5)) # True (needs 142ms, budget is 200ms)
print(report.budget_fraction(10)) # 1.42 (exceeds 10Hz budget by 42%)
# Get the EfficiencyMetadata for the runner
eff = report.to_efficiency_metadata()
Design
The profiler handles two distinct phases:
Pre-run (called once, before inference): Params, FLOPs, memory traffic estimate, system card. These are model-intrinsic or estimated — no data needed.
Post-run (filled by the runner during inference):
Latency percentiles, peak memory, FLOP counter refinement.
The profiler returns an EfficiencyMetadata that the runner
mutates in-place during run_with_deployment_readiness.
This split means the profiler works for any task — depth, pose, segmentation, detection — without knowing what task it is. The task script just does::
profiler = ModelProfiler(model_or_adapter)
eff = profiler.pre_run_profile()
# ... pass eff to runner, runner fills latency/memory ...
report = profiler.full_report(eff)
Novel metric: Real-Time Budget Analysis
For robotics deployment, the key question isn't "what's the latency?" but "does it fit in my control loop?" RPX reports a budget fraction:
$$\beta(f) = \frac{\tau_{\text{model}}}{1/f}$$
where \( f \) is the target control frequency (Hz) and \( \tau_{\text{model}} \) is measured inference latency.
\( \beta < 1 \) means the model fits within the real-time budget. \( \beta > 1 \) means it exceeds it, and by how much.
Reported at standard robot control frequencies: 5 Hz (slow manipulation), 10 Hz (fast manipulation), 30 Hz (navigation). No existing perception benchmark reports this.
1"""Unified model profiler for all RPX tasks. 2 3One class, any model, all metrics. No per-task boilerplate. 4 5Usage 6----- 7 from rpx_benchmark.model_profiler import ModelProfiler 8 9 profiler = ModelProfiler(model) 10 report = profiler.profile() 11 12 # Hardware-agnostic (same on any machine) 13 print(report.params_m) # 524.4 14 print(report.flops_g) # 187.3 15 16 # Measured (hardware-specific, tagged with system card) 17 print(report.latency_p50_ms) # 142.7 18 print(report.memory_peak_mb) # 3200.0 19 20 # Deployability verdicts (novel — directly answers "can I use this?") 21 print(report.realtime_at_hz(10)) # False (needs 142ms, budget is 100ms) 22 print(report.realtime_at_hz(5)) # True (needs 142ms, budget is 200ms) 23 print(report.budget_fraction(10)) # 1.42 (exceeds 10Hz budget by 42%) 24 25 # Get the EfficiencyMetadata for the runner 26 eff = report.to_efficiency_metadata() 27 28Design 29------ 30The profiler handles two distinct phases: 31 32**Pre-run** (called once, before inference): 33 Params, FLOPs, memory traffic estimate, system card. 34 These are model-intrinsic or estimated — no data needed. 35 36**Post-run** (filled by the runner during inference): 37 Latency percentiles, peak memory, FLOP counter refinement. 38 The profiler returns an :class:`EfficiencyMetadata` that the runner 39 mutates in-place during ``run_with_deployment_readiness``. 40 41This split means the profiler works for any task — depth, pose, segmentation, 42detection — without knowing what task it is. The task script just does:: 43 44 profiler = ModelProfiler(model_or_adapter) 45 eff = profiler.pre_run_profile() 46 # ... pass eff to runner, runner fills latency/memory ... 47 report = profiler.full_report(eff) 48 49Novel metric: Real-Time Budget Analysis 50---------------------------------------- 51For robotics deployment, the key question isn't "what's the latency?" but 52"does it fit in my control loop?" RPX reports a **budget fraction**: 53 54.. math:: 55 56 \\beta(f) = \\frac{\\tau_{\\text{model}}}{1/f} 57 58where :math:`f` is the target control frequency (Hz) and 59:math:`\\tau_{\\text{model}}` is measured inference latency. 60 61:math:`\\beta < 1` means the model fits within the real-time budget. 62:math:`\\beta > 1` means it exceeds it, and by how much. 63 64Reported at standard robot control frequencies: 5 Hz (slow manipulation), 6510 Hz (fast manipulation), 30 Hz (navigation). No existing perception 66benchmark reports this. 67""" 68 69from __future__ import annotations 70 71from dataclasses import dataclass 72from typing import Any, Dict, Optional, Tuple 73 74from .logging_utils import get_logger 75from .profiler import ( 76 EfficiencyMetadata, 77 SystemCard, 78 count_parameters, 79 estimate_memory_traffic_gb, 80) 81 82log = get_logger(__name__) 83 84# Standard robot control frequencies for budget analysis. 85ROBOT_CONTROL_HZ: Tuple[float, ...] = (5.0, 10.0, 30.0) 86"""Slow manipulation (5 Hz), fast manipulation (10 Hz), navigation (30 Hz).""" 87 88 89def _find_torch_module(model: Any) -> Any: 90 """Walk common attribute paths to find an ``nn.Module`` with ``.parameters()``. 91 92 Works for BenchmarkableModel, raw adapters, and torch modules directly. 93 Returns ``None`` if no torch module is found. 94 """ 95 # If it already has .parameters(), it IS a torch module 96 if hasattr(model, "parameters") and callable(model.parameters): 97 return model 98 99 # Walk common adapter layouts 100 for path in ( 101 "model", 102 "torch_module", 103 "_model", 104 "_pipe.model", 105 "adapter.model", 106 "adapter._model", 107 "adapter.torch_module", 108 ): 109 cur = model 110 for part in path.split("."): 111 cur = getattr(cur, part, None) 112 if cur is None: 113 break 114 if cur is not None and hasattr(cur, "parameters") and callable(cur.parameters): 115 return cur 116 117 return None 118 119 120@dataclass 121class ProfileReport: 122 """Complete profiling result for a model. 123 124 Tier 1 and budget analysis are the primary outputs. 125 Tier 3 fields are filled after the runner completes. 126 """ 127 128 # ── Tier 1: Hardware-agnostic ──────────────────────────────────────── 129 params_m: Optional[float] = None 130 flops_g: Optional[float] = None 131 macs_g: Optional[float] = None 132 memory_traffic_gb: Optional[float] = None 133 arithmetic_intensity: Optional[float] = None 134 135 # ── Tier 2: Roofline (hardware-parametric) ─────────────────────────── 136 roofline: Optional[Dict[str, Dict[str, float]]] = None 137 138 # ── Tier 3: Measured (hardware-specific) ───────────────────────────── 139 latency_p50_ms: Optional[float] = None 140 latency_p95_ms: Optional[float] = None 141 latency_p99_ms: Optional[float] = None 142 peak_cpu_mb: Optional[float] = None 143 peak_cuda_mb: Optional[float] = None 144 peak_mps_mb: Optional[float] = None 145 system_card: Optional[Dict[str, Any]] = None 146 147 # ── Budget analysis (novel) ────────────────────────────────────────── 148 budget_fractions: Optional[Dict[str, float]] = None 149 """Maps ``"5hz"`` → β, ``"10hz"`` → β, ``"30hz"`` → β.""" 150 151 def budget_fraction(self, hz: float) -> Optional[float]: 152 """Compute β = latency / (1000/hz). < 1 means real-time feasible.""" 153 if self.latency_p50_ms is None or hz <= 0: 154 return None 155 budget_ms = 1000.0 / hz 156 return round(self.latency_p50_ms / budget_ms, 3) 157 158 def realtime_at_hz(self, hz: float) -> Optional[bool]: 159 """Can this model sustain ``hz`` frames per second?""" 160 beta = self.budget_fraction(hz) 161 if beta is None: 162 return None 163 return beta <= 1.0 164 165 def compute_budget_fractions(self) -> Dict[str, float]: 166 """Compute budget fractions at standard robot control frequencies.""" 167 out: Dict[str, float] = {} 168 for hz in ROBOT_CONTROL_HZ: 169 beta = self.budget_fraction(hz) 170 if beta is not None: 171 out[f"{hz:.0f}hz"] = beta 172 self.budget_fractions = out 173 return out 174 175 def to_dict(self) -> Dict[str, Any]: 176 """Structured output for JSON serialization.""" 177 return { 178 "tier1_hardware_agnostic": { 179 "params_m": self.params_m, 180 "flops_g": self.flops_g, 181 "macs_g": self.macs_g, 182 "memory_traffic_gb": self.memory_traffic_gb, 183 "arithmetic_intensity": self.arithmetic_intensity, 184 }, 185 "tier2_roofline": self.roofline, 186 "tier3_measured": { 187 "latency_p50_ms": self.latency_p50_ms, 188 "latency_p95_ms": self.latency_p95_ms, 189 "latency_p99_ms": self.latency_p99_ms, 190 "peak_cpu_mb": self.peak_cpu_mb, 191 "peak_cuda_mb": self.peak_cuda_mb, 192 "peak_mps_mb": self.peak_mps_mb, 193 "system_card": self.system_card, 194 }, 195 "realtime_budget": self.budget_fractions, 196 } 197 198 def summary_line(self) -> str: 199 """One-line summary for terminal output.""" 200 parts = [] 201 if self.params_m is not None: 202 parts.append(f"{self.params_m:.1f}M params") 203 if self.flops_g is not None: 204 parts.append(f"{self.flops_g:.1f}G FLOPs") 205 if self.latency_p50_ms is not None: 206 parts.append(f"{self.latency_p50_ms:.1f}ms p50") 207 if self.budget_fractions: 208 for hz_key, beta in self.budget_fractions.items(): 209 symbol = "✓" if beta <= 1.0 else "✗" 210 parts.append(f"{symbol}@{hz_key}") 211 return " | ".join(parts) if parts else "no profile data" 212 213 214class ModelProfiler: 215 """Unified profiler for any RPX model. Works across all tasks. 216 217 Parameters 218 ---------- 219 model : Any 220 A BenchmarkableModel, adapter, torch nn.Module, or callable. 221 The profiler auto-discovers the torch module for Tier 1 metrics. 222 input_shape : tuple 223 ``(C, H, W)`` for FLOPs estimation. Default: RPX standard 640×480 RGB. 224 device : str 225 Device for profiling. Default ``"cuda"``. 226 227 Example 228 ------- 229 :: 230 231 profiler = ModelProfiler(my_adapter) 232 233 # Pre-run: get Tier 1 metrics + EfficiencyMetadata for the runner 234 eff = profiler.pre_run_profile() 235 236 # ... runner.run_with_deployment_readiness(efficiency=eff) ... 237 # (runner fills latency, memory, FLOPs into eff) 238 239 # Post-run: build the full report from the now-populated eff 240 report = profiler.full_report(eff) 241 print(report.summary_line()) 242 # → "524.4M params | 187.3G FLOPs | 142.7ms p50 | ✗@10hz | ✓@5hz" 243 """ 244 245 def __init__( 246 self, 247 model: Any, 248 input_shape: Tuple[int, ...] = (3, 480, 640), 249 device: str = "cuda", 250 ) -> None: 251 self.model = model 252 self.input_shape = input_shape 253 self.device = device 254 self._torch_mod = _find_torch_module(model) 255 256 @property 257 def has_torch_module(self) -> bool: 258 return self._torch_mod is not None 259 260 def pre_run_profile(self) -> EfficiencyMetadata: 261 """Compute Tier 1 metrics and return an EfficiencyMetadata for the runner. 262 263 The returned object is passed to 264 ``runner.run_with_deployment_readiness(efficiency=eff)``. 265 The runner will fill in Tier 3 fields (latency, memory, FLOPs) 266 during the benchmark run. 267 """ 268 eff = EfficiencyMetadata( 269 model_type="local" if self._torch_mod is not None else "classical", 270 ) 271 272 if self._torch_mod is not None: 273 try: 274 eff.params_m = count_parameters(self._torch_mod) 275 except Exception as e: 276 log.warning("params count failed: %s", e) 277 278 try: 279 eff.memory_traffic_gb = estimate_memory_traffic_gb( 280 self._torch_mod, self.input_shape, device=self.device, 281 ) 282 except Exception as e: 283 log.warning("memory traffic estimate failed: %s", e) 284 285 try: 286 eff.system_card = SystemCard.auto_detect( 287 input_resolution=f"{self.input_shape[-1]}x{self.input_shape[-2]}", 288 ) 289 except Exception as e: 290 log.warning("system card detection failed: %s", e) 291 292 return eff 293 294 def full_report(self, eff: EfficiencyMetadata) -> ProfileReport: 295 """Build a complete ProfileReport from a (now runner-populated) EfficiencyMetadata. 296 297 Call this AFTER the runner has completed — ``eff`` will have 298 latency, memory, and FLOPs filled in by the runner. 299 """ 300 # Ensure derived fields are populated 301 eff.derive_tier1() 302 if eff.flops_g is not None and eff.roofline is None: 303 eff.compute_roofline() 304 305 # Build roofline dict for serialization 306 roofline_dict = None 307 if eff.roofline: 308 roofline_dict = { 309 name: { 310 "compute_ms": b.compute_ms, 311 "memory_ms": b.memory_ms, 312 "latency_ms": b.latency_ms, 313 "bottleneck": b.bottleneck, 314 } 315 for name, b in eff.roofline.items() 316 } 317 318 report = ProfileReport( 319 # Tier 1 320 params_m=eff.params_m, 321 flops_g=eff.flops_g, 322 macs_g=eff.macs_g, 323 memory_traffic_gb=eff.memory_traffic_gb, 324 arithmetic_intensity=eff.arithmetic_intensity, 325 # Tier 2 326 roofline=roofline_dict, 327 # Tier 3 328 latency_p50_ms=eff.latency_p50_ms, 329 latency_p95_ms=eff.latency_p95_ms, 330 latency_p99_ms=eff.latency_p99_ms, 331 peak_cpu_mb=eff.peak_cpu_mb, 332 peak_cuda_mb=eff.peak_cuda_mb, 333 peak_mps_mb=eff.peak_mps_mb, 334 system_card=eff.system_card.to_dict() if eff.system_card else None, 335 ) 336 337 # Compute real-time budget analysis 338 report.compute_budget_fractions() 339 340 return report
Slow manipulation (5 Hz), fast manipulation (10 Hz), navigation (30 Hz).
121@dataclass 122class ProfileReport: 123 """Complete profiling result for a model. 124 125 Tier 1 and budget analysis are the primary outputs. 126 Tier 3 fields are filled after the runner completes. 127 """ 128 129 # ── Tier 1: Hardware-agnostic ──────────────────────────────────────── 130 params_m: Optional[float] = None 131 flops_g: Optional[float] = None 132 macs_g: Optional[float] = None 133 memory_traffic_gb: Optional[float] = None 134 arithmetic_intensity: Optional[float] = None 135 136 # ── Tier 2: Roofline (hardware-parametric) ─────────────────────────── 137 roofline: Optional[Dict[str, Dict[str, float]]] = None 138 139 # ── Tier 3: Measured (hardware-specific) ───────────────────────────── 140 latency_p50_ms: Optional[float] = None 141 latency_p95_ms: Optional[float] = None 142 latency_p99_ms: Optional[float] = None 143 peak_cpu_mb: Optional[float] = None 144 peak_cuda_mb: Optional[float] = None 145 peak_mps_mb: Optional[float] = None 146 system_card: Optional[Dict[str, Any]] = None 147 148 # ── Budget analysis (novel) ────────────────────────────────────────── 149 budget_fractions: Optional[Dict[str, float]] = None 150 """Maps ``"5hz"`` → β, ``"10hz"`` → β, ``"30hz"`` → β.""" 151 152 def budget_fraction(self, hz: float) -> Optional[float]: 153 """Compute β = latency / (1000/hz). < 1 means real-time feasible.""" 154 if self.latency_p50_ms is None or hz <= 0: 155 return None 156 budget_ms = 1000.0 / hz 157 return round(self.latency_p50_ms / budget_ms, 3) 158 159 def realtime_at_hz(self, hz: float) -> Optional[bool]: 160 """Can this model sustain ``hz`` frames per second?""" 161 beta = self.budget_fraction(hz) 162 if beta is None: 163 return None 164 return beta <= 1.0 165 166 def compute_budget_fractions(self) -> Dict[str, float]: 167 """Compute budget fractions at standard robot control frequencies.""" 168 out: Dict[str, float] = {} 169 for hz in ROBOT_CONTROL_HZ: 170 beta = self.budget_fraction(hz) 171 if beta is not None: 172 out[f"{hz:.0f}hz"] = beta 173 self.budget_fractions = out 174 return out 175 176 def to_dict(self) -> Dict[str, Any]: 177 """Structured output for JSON serialization.""" 178 return { 179 "tier1_hardware_agnostic": { 180 "params_m": self.params_m, 181 "flops_g": self.flops_g, 182 "macs_g": self.macs_g, 183 "memory_traffic_gb": self.memory_traffic_gb, 184 "arithmetic_intensity": self.arithmetic_intensity, 185 }, 186 "tier2_roofline": self.roofline, 187 "tier3_measured": { 188 "latency_p50_ms": self.latency_p50_ms, 189 "latency_p95_ms": self.latency_p95_ms, 190 "latency_p99_ms": self.latency_p99_ms, 191 "peak_cpu_mb": self.peak_cpu_mb, 192 "peak_cuda_mb": self.peak_cuda_mb, 193 "peak_mps_mb": self.peak_mps_mb, 194 "system_card": self.system_card, 195 }, 196 "realtime_budget": self.budget_fractions, 197 } 198 199 def summary_line(self) -> str: 200 """One-line summary for terminal output.""" 201 parts = [] 202 if self.params_m is not None: 203 parts.append(f"{self.params_m:.1f}M params") 204 if self.flops_g is not None: 205 parts.append(f"{self.flops_g:.1f}G FLOPs") 206 if self.latency_p50_ms is not None: 207 parts.append(f"{self.latency_p50_ms:.1f}ms p50") 208 if self.budget_fractions: 209 for hz_key, beta in self.budget_fractions.items(): 210 symbol = "✓" if beta <= 1.0 else "✗" 211 parts.append(f"{symbol}@{hz_key}") 212 return " | ".join(parts) if parts else "no profile data"
Complete profiling result for a model.
Tier 1 and budget analysis are the primary outputs. Tier 3 fields are filled after the runner completes.
152 def budget_fraction(self, hz: float) -> Optional[float]: 153 """Compute β = latency / (1000/hz). < 1 means real-time feasible.""" 154 if self.latency_p50_ms is None or hz <= 0: 155 return None 156 budget_ms = 1000.0 / hz 157 return round(self.latency_p50_ms / budget_ms, 3)
Compute β = latency / (1000/hz). < 1 means real-time feasible.
159 def realtime_at_hz(self, hz: float) -> Optional[bool]: 160 """Can this model sustain ``hz`` frames per second?""" 161 beta = self.budget_fraction(hz) 162 if beta is None: 163 return None 164 return beta <= 1.0
Can this model sustain hz frames per second?
166 def compute_budget_fractions(self) -> Dict[str, float]: 167 """Compute budget fractions at standard robot control frequencies.""" 168 out: Dict[str, float] = {} 169 for hz in ROBOT_CONTROL_HZ: 170 beta = self.budget_fraction(hz) 171 if beta is not None: 172 out[f"{hz:.0f}hz"] = beta 173 self.budget_fractions = out 174 return out
Compute budget fractions at standard robot control frequencies.
176 def to_dict(self) -> Dict[str, Any]: 177 """Structured output for JSON serialization.""" 178 return { 179 "tier1_hardware_agnostic": { 180 "params_m": self.params_m, 181 "flops_g": self.flops_g, 182 "macs_g": self.macs_g, 183 "memory_traffic_gb": self.memory_traffic_gb, 184 "arithmetic_intensity": self.arithmetic_intensity, 185 }, 186 "tier2_roofline": self.roofline, 187 "tier3_measured": { 188 "latency_p50_ms": self.latency_p50_ms, 189 "latency_p95_ms": self.latency_p95_ms, 190 "latency_p99_ms": self.latency_p99_ms, 191 "peak_cpu_mb": self.peak_cpu_mb, 192 "peak_cuda_mb": self.peak_cuda_mb, 193 "peak_mps_mb": self.peak_mps_mb, 194 "system_card": self.system_card, 195 }, 196 "realtime_budget": self.budget_fractions, 197 }
Structured output for JSON serialization.
199 def summary_line(self) -> str: 200 """One-line summary for terminal output.""" 201 parts = [] 202 if self.params_m is not None: 203 parts.append(f"{self.params_m:.1f}M params") 204 if self.flops_g is not None: 205 parts.append(f"{self.flops_g:.1f}G FLOPs") 206 if self.latency_p50_ms is not None: 207 parts.append(f"{self.latency_p50_ms:.1f}ms p50") 208 if self.budget_fractions: 209 for hz_key, beta in self.budget_fractions.items(): 210 symbol = "✓" if beta <= 1.0 else "✗" 211 parts.append(f"{symbol}@{hz_key}") 212 return " | ".join(parts) if parts else "no profile data"
One-line summary for terminal output.
215class ModelProfiler: 216 """Unified profiler for any RPX model. Works across all tasks. 217 218 Parameters 219 ---------- 220 model : Any 221 A BenchmarkableModel, adapter, torch nn.Module, or callable. 222 The profiler auto-discovers the torch module for Tier 1 metrics. 223 input_shape : tuple 224 ``(C, H, W)`` for FLOPs estimation. Default: RPX standard 640×480 RGB. 225 device : str 226 Device for profiling. Default ``"cuda"``. 227 228 Example 229 ------- 230 :: 231 232 profiler = ModelProfiler(my_adapter) 233 234 # Pre-run: get Tier 1 metrics + EfficiencyMetadata for the runner 235 eff = profiler.pre_run_profile() 236 237 # ... runner.run_with_deployment_readiness(efficiency=eff) ... 238 # (runner fills latency, memory, FLOPs into eff) 239 240 # Post-run: build the full report from the now-populated eff 241 report = profiler.full_report(eff) 242 print(report.summary_line()) 243 # → "524.4M params | 187.3G FLOPs | 142.7ms p50 | ✗@10hz | ✓@5hz" 244 """ 245 246 def __init__( 247 self, 248 model: Any, 249 input_shape: Tuple[int, ...] = (3, 480, 640), 250 device: str = "cuda", 251 ) -> None: 252 self.model = model 253 self.input_shape = input_shape 254 self.device = device 255 self._torch_mod = _find_torch_module(model) 256 257 @property 258 def has_torch_module(self) -> bool: 259 return self._torch_mod is not None 260 261 def pre_run_profile(self) -> EfficiencyMetadata: 262 """Compute Tier 1 metrics and return an EfficiencyMetadata for the runner. 263 264 The returned object is passed to 265 ``runner.run_with_deployment_readiness(efficiency=eff)``. 266 The runner will fill in Tier 3 fields (latency, memory, FLOPs) 267 during the benchmark run. 268 """ 269 eff = EfficiencyMetadata( 270 model_type="local" if self._torch_mod is not None else "classical", 271 ) 272 273 if self._torch_mod is not None: 274 try: 275 eff.params_m = count_parameters(self._torch_mod) 276 except Exception as e: 277 log.warning("params count failed: %s", e) 278 279 try: 280 eff.memory_traffic_gb = estimate_memory_traffic_gb( 281 self._torch_mod, self.input_shape, device=self.device, 282 ) 283 except Exception as e: 284 log.warning("memory traffic estimate failed: %s", e) 285 286 try: 287 eff.system_card = SystemCard.auto_detect( 288 input_resolution=f"{self.input_shape[-1]}x{self.input_shape[-2]}", 289 ) 290 except Exception as e: 291 log.warning("system card detection failed: %s", e) 292 293 return eff 294 295 def full_report(self, eff: EfficiencyMetadata) -> ProfileReport: 296 """Build a complete ProfileReport from a (now runner-populated) EfficiencyMetadata. 297 298 Call this AFTER the runner has completed — ``eff`` will have 299 latency, memory, and FLOPs filled in by the runner. 300 """ 301 # Ensure derived fields are populated 302 eff.derive_tier1() 303 if eff.flops_g is not None and eff.roofline is None: 304 eff.compute_roofline() 305 306 # Build roofline dict for serialization 307 roofline_dict = None 308 if eff.roofline: 309 roofline_dict = { 310 name: { 311 "compute_ms": b.compute_ms, 312 "memory_ms": b.memory_ms, 313 "latency_ms": b.latency_ms, 314 "bottleneck": b.bottleneck, 315 } 316 for name, b in eff.roofline.items() 317 } 318 319 report = ProfileReport( 320 # Tier 1 321 params_m=eff.params_m, 322 flops_g=eff.flops_g, 323 macs_g=eff.macs_g, 324 memory_traffic_gb=eff.memory_traffic_gb, 325 arithmetic_intensity=eff.arithmetic_intensity, 326 # Tier 2 327 roofline=roofline_dict, 328 # Tier 3 329 latency_p50_ms=eff.latency_p50_ms, 330 latency_p95_ms=eff.latency_p95_ms, 331 latency_p99_ms=eff.latency_p99_ms, 332 peak_cpu_mb=eff.peak_cpu_mb, 333 peak_cuda_mb=eff.peak_cuda_mb, 334 peak_mps_mb=eff.peak_mps_mb, 335 system_card=eff.system_card.to_dict() if eff.system_card else None, 336 ) 337 338 # Compute real-time budget analysis 339 report.compute_budget_fractions() 340 341 return report
Unified profiler for any RPX model. Works across all tasks.
Parameters
model : Any
A BenchmarkableModel, adapter, torch nn.Module, or callable.
The profiler auto-discovers the torch module for Tier 1 metrics.
input_shape : tuple
(C, H, W) for FLOPs estimation. Default: RPX standard 640×480 RGB.
device : str
Device for profiling. Default "cuda".
Example
::
profiler = ModelProfiler(my_adapter)
# Pre-run: get Tier 1 metrics + EfficiencyMetadata for the runner
eff = profiler.pre_run_profile()
# ... runner.run_with_deployment_readiness(efficiency=eff) ...
# (runner fills latency, memory, FLOPs into eff)
# Post-run: build the full report from the now-populated eff
report = profiler.full_report(eff)
print(report.summary_line())
# → "524.4M params | 187.3G FLOPs | 142.7ms p50 | ✗@10hz | ✓@5hz"
261 def pre_run_profile(self) -> EfficiencyMetadata: 262 """Compute Tier 1 metrics and return an EfficiencyMetadata for the runner. 263 264 The returned object is passed to 265 ``runner.run_with_deployment_readiness(efficiency=eff)``. 266 The runner will fill in Tier 3 fields (latency, memory, FLOPs) 267 during the benchmark run. 268 """ 269 eff = EfficiencyMetadata( 270 model_type="local" if self._torch_mod is not None else "classical", 271 ) 272 273 if self._torch_mod is not None: 274 try: 275 eff.params_m = count_parameters(self._torch_mod) 276 except Exception as e: 277 log.warning("params count failed: %s", e) 278 279 try: 280 eff.memory_traffic_gb = estimate_memory_traffic_gb( 281 self._torch_mod, self.input_shape, device=self.device, 282 ) 283 except Exception as e: 284 log.warning("memory traffic estimate failed: %s", e) 285 286 try: 287 eff.system_card = SystemCard.auto_detect( 288 input_resolution=f"{self.input_shape[-1]}x{self.input_shape[-2]}", 289 ) 290 except Exception as e: 291 log.warning("system card detection failed: %s", e) 292 293 return eff
Compute Tier 1 metrics and return an EfficiencyMetadata for the runner.
The returned object is passed to
runner.run_with_deployment_readiness(efficiency=eff).
The runner will fill in Tier 3 fields (latency, memory, FLOPs)
during the benchmark run.
295 def full_report(self, eff: EfficiencyMetadata) -> ProfileReport: 296 """Build a complete ProfileReport from a (now runner-populated) EfficiencyMetadata. 297 298 Call this AFTER the runner has completed — ``eff`` will have 299 latency, memory, and FLOPs filled in by the runner. 300 """ 301 # Ensure derived fields are populated 302 eff.derive_tier1() 303 if eff.flops_g is not None and eff.roofline is None: 304 eff.compute_roofline() 305 306 # Build roofline dict for serialization 307 roofline_dict = None 308 if eff.roofline: 309 roofline_dict = { 310 name: { 311 "compute_ms": b.compute_ms, 312 "memory_ms": b.memory_ms, 313 "latency_ms": b.latency_ms, 314 "bottleneck": b.bottleneck, 315 } 316 for name, b in eff.roofline.items() 317 } 318 319 report = ProfileReport( 320 # Tier 1 321 params_m=eff.params_m, 322 flops_g=eff.flops_g, 323 macs_g=eff.macs_g, 324 memory_traffic_gb=eff.memory_traffic_gb, 325 arithmetic_intensity=eff.arithmetic_intensity, 326 # Tier 2 327 roofline=roofline_dict, 328 # Tier 3 329 latency_p50_ms=eff.latency_p50_ms, 330 latency_p95_ms=eff.latency_p95_ms, 331 latency_p99_ms=eff.latency_p99_ms, 332 peak_cpu_mb=eff.peak_cpu_mb, 333 peak_cuda_mb=eff.peak_cuda_mb, 334 peak_mps_mb=eff.peak_mps_mb, 335 system_card=eff.system_card.to_dict() if eff.system_card else None, 336 ) 337 338 # Compute real-time budget analysis 339 report.compute_budget_fractions() 340 341 return report
Build a complete ProfileReport from a (now runner-populated) EfficiencyMetadata.
Call this AFTER the runner has completed — eff will have
latency, memory, and FLOPs filled in by the runner.