Understanding results¶
TorchScan measures one concrete execution. Results are useful only with the input metadata, execution context, method, status, and diagnostics that accompany them.
Metric result states¶
Every measured metric uses the same trust contract:
| Status | Meaning | Numeric field |
|---|---|---|
complete |
The method covered the requested execution scope. | value |
partial |
Some work was counted and some was not. | known_value, a lower bound |
unavailable |
The method could not produce a result. | Neither field is authoritative |
A complete value of zero is different from an incomplete result. TorchScan does not report a coverage percentage: one uncounted operator can dominate a workload, so the fraction of recognized operator kinds would be misleading.
Use strict=True with crawl_module or summary when partial or unavailable module metrics must raise
IncompleteAnalysisError instead of returning a report.
Parameters and model storage¶
Parameter and buffer counts come from tensors registered on the model. Storage size is not peak memory: it excludes or separates activations, gradients, optimizer state, allocator behavior, Python objects, and third-party allocations.
Module FLOPs, MACs, and DMAs¶
Module metrics use TorchScan formulas for recognized module families:
- FLOPs count formula-defined arithmetic for the forward pass.
- MACs count multiply-accumulate work for supported modules.
- DMAs estimate formula-defined logical element reads and writes, including parameters and intermediates.
These are theoretical counts, not FLOP/s, memory bandwidth, or latency. Diagnostics identify unsupported module work. Native Transformer MACs are independently derived from matrix dimensions, and their DMAs describe a staged logical algorithm rather than cache or fused-kernel traffic. See Native Transformer estimates for counts, mask handling, returned attention weights, and supported boundaries.
Operator FLOPs¶
Operator FLOPs use PyTorch's torch.utils.flop_counter.FlopCounterMode. This observes dispatcher operations, including
functional calls and operations inside custom modules, but can count only operators with registered or caller-provided
formulas. Counts are grouped globally, by module, and by operator.
Operator and module FLOPs can differ because their formulas, boundaries, and decomposition differ. Report both with their method labels; do not average, add, or substitute one silently for the other.
Receptive field¶
Receptive-field values follow module execution order. Sequential convolutional paths can be described, including dilation, but hook order does not reconstruct arbitrary branch topology. Residual and other skip-connected models can therefore yield partial or unavailable results.
Native attention and Transformer stacks instead provide optional module-local token_dependencies records. These
distinguish query/target and key-value/source axes and represent all-token or position-dependent causal dependencies.
Feature-only normalization and feed-forward operations are token-local. These records do not claim graph-wide
effective receptive fields; legacy spatial receptive-field, stride, and padding fields are unavailable. See
token dependencies for relation semantics and mask limitations.
Peak memory¶
measure_peak_memory measures one owner-provided workload:
- CPU uses PyTorch profiler memory categories.
- CUDA and supported MPS versions use public PyTorch allocator peak statistics.
It does not report process RSS, total device use, driver memory, or third-party allocations. Compare memory only with matching hardware, PyTorch version, model state, inputs, dtype, optimizer state, allocator warmup, and workload. Accelerator statistics are process-global, so unrelated concurrent allocations can affect the result.
Latency and throughput¶
The unreleased measure_latency API uses
torch.utils.benchmark.Timer to measure one
caller-controlled workload. It synchronizes the selected CPU/CUDA/MPS device explicitly, including on older
supported PyTorch versions. It returns JSON-serializable first-call time, warmed latency, variability, and throughput:
Prefer a current PyTorch release for timing. PyTorch 2.1's native benchmark imports require compatible dependencies
(setuptools<70 and numpy<2). TorchScan imports these tools only when timing is requested, so this requirement does
not affect the model inspection APIs. Missing benchmark dependencies raise an actionable import error before execution.
import json
import torch
from torchscan import measure_latency
torch.set_num_threads(1) # Configure once, before constructing the model.
model = torch.nn.Linear(64, 16).eval()
inputs = torch.ones(32, 64)
def workload():
with torch.inference_mode():
return model(inputs)
report = measure_latency(
workload,
device=inputs.device,
inputs=inputs,
work_units=inputs.shape[0],
work_unit="samples",
)
print(json.dumps(report, indent=2))
totals metric |
Meaning |
|---|---|
first_call_latency |
One completed call before warmup, including all callable work, in seconds. |
latency |
Median seconds per call, computed from warmed block averages. |
latency_iqr |
Interquartile range of the same block averages, in seconds. |
throughput |
Declared work units completed across timed blocks, divided by their total elapsed seconds. |
work_units describes what one call completes; work_unit gives the unit name. For example, a call processing
32 samples uses work_units=32, work_unit="samples". Tokens must count the work actually completed, not a requested
maximum. The default unit is calls per second. These are local workload measurements, not queued-service throughput.
The callable owns evaluation/training state, gradient mode, precision, transfers, preprocessing, and other side effects. Only work inside that callable is included. It is invoked once for the first-call measurement, explicitly warmed up, and then invoked many times by native timer calibration and timed blocks. Training workloads must manage gradients and changing optimizer state themselves. No weights are downloaded or inputs moved automatically.
Configure threads with torch.set_num_threads before constructing the model. TorchScan records the active count and
passes it to the native timer; it exposes no separate thread override. Keep thread configuration fixed during the
workload. Timing calls are serialized because native timing uses process-global settings; unrelated work can still
affect measurements. device must match where the callable executes; it does not choose a CUDA execution context or
move tensors. On CPU/MPS, device_name records the CPU/SoC model; device distinguishes the backend.
warmup, min_run_time, and min_repeats control explicit warmup and minimum block measurements. PyTorch performs
additional warmup/calibration, so min_run_time is not a wall-clock timeout. Timed blocks use timeit's default garbage
collection behavior; first-call timing uses an ordinary synchronized clock.
Inputs are optional caller-supplied metadata, not arguments forwarded to the callable. The report labels whether they
were supplied and stores shapes/dtypes rather than tensor contents. Hardware, software, thread settings, work units,
timing options, raw block durations, calls per block, and timed call count accompany the metrics. See
BenchmarkReport.
Keep block-average latency separate from individual request percentiles. First-call time also includes a completion
synchronization that warmed blocks amortize over several calls; its difference from warmed latency is not a pure
startup-cost measurement. Use one device per workload. Multi-device/distributed synchronization, memory measurement,
FLOP counting, profiling, and output-quality checks are separate tasks. Use
compare_benchmarks for checked timing comparisons and render_report for their offline
HTML evidence. compare_reports compares model analysis only. Counting can be partial without preventing timing.
Reproducible reporting¶
For research or regression analysis, retain:
- The complete report, including
schema_versionand execution context. - Exact model revision and configuration.
- Input shapes, dtypes, devices, and non-sensitive call structure.
- TorchScan, PyTorch, and Python versions.
- Every diagnostic and custom formula definition.
- Hardware, warmup, allocator, model, gradient, autocast, and optimizer state for memory or latency measurements.
Use Report comparison only when both reports use the same schema and compatible methods.