GPU Performance Evidence
Skill navigation
- Parent/orchestrator: gpu-code-optimizer
- gpu-code-optimizer — return to overall routing and priority selection
- gpu-memory-fusion-layout — load when evidence points to memory traffic, temporaries, or layout cost
- gpu-resource-lifetime-allocation — load when peak live overlap, workspaces, allocation reuse, or rematerialization matter
- gpu-virtual-memory-fragmentation — load when capacity and allocatability differ or backing policy is material
- gpu-memory-tiering-migration — load when residency, placement, movement, or oversubscription matters
- gpu-state-reuse-eviction — load when retained-state identity, validity, value, or logical eviction matters
- gpu-persistent-state — load when cross-call growth, mutation, ownership, reconstruction, or cleanup matters
- gpu-memory-scheduling — load when exposed memory stalls or joint compute/memory ordering matters
- gpu-kernel-execution — load when a specific hot kernel needs execution-level tuning
- gpu-compiler-runtime — load when timeline gaps, compiler behavior, or runtime overhead dominate
- gpu-optimization-validation — load before accepting or reporting the optimization
Load linked skills only when their trigger applies. Do not duplicate their full workflow here.
Core principle
Performance work starts from a measured baseline and ends with a measured end-to-end result. Source inspection can generate hypotheses; it cannot by itself establish the bottleneck or prove speedup.
Use the highest-level measurement that still answers the question. First locate the expensive phase in an application timeline. Then drill into a hot kernel only when kernel-level details can change the decision. Avoid collecting every hardware counter before knowing which kernel matters.
Baseline record
Before modifying code, identify what is known.
Record:
- Hardware: Target GPU or GPU family, compute capability, memory bandwidth.
- Software: Framework, compiler, runtime, backend (e.g., JAX/XLA, torch.compile/Inductor, CUDA, Triton).
- Kernel purpose: What the code computes and why.
- Shapes: Input shapes, dtypes, strides, layouts, alignments, batch sizes.
- Boundary cases: Non-divisible sizes, singleton dimensions, empty inputs.
- Current metrics: Runtime (median, p95), throughput, bandwidth, occupancy, kernel count, peak memory, allocation count.
- Correctness: Existing tests, tolerance, deterministic mode requirements.
- Target: Latency, throughput, memory footprint, energy, compilation time, or end-to-end wall time.
If data is missing, proceed with conservative assumptions. State which measurements would confirm or reject the optimization.
Representative workload checklist
Do not benchmark only on random-normal inputs with a single shape. Test:
- Small, medium, target, and maximum input sizes.
- Non-power-of-2 sizes and non-divisible dimensions.
- Batch size sweeps.
- Channel/head/feature dimension sweeps.
- Different dtypes (fp32, fp16, bf16, mixed).
- Different layouts (contiguous vs non-contiguous, channel-last vs channel-first).
- Real data distributions and extreme data distributions.
- Sparse, all-zero, constant, large-value, small-value inputs.
- Training and inference paths (if the code is used for both).
- Gradient computation (if the code is differentiated).
An optimization that only wins on hand-picked shapes must not become an unguarded general default; a shape-specialized kernel behind a guard/flag in the dispatcher is acceptable when its domain is stated.
Benchmark state must be explicit
Record whether each timing includes or excludes:
- compilation/JIT/autotuning;
- allocator warm-up and memory-pool initialization;
- data loading and host preprocessing;
- host↔device or device↔device transfers;
- synchronization inserted only for measurement;
- graph capture/warm-up versus graph replay;
- forward only versus forward+backward+optimizer;
- distributed collectives and synchronization;
- cache-hot versus cache-cold state.
Do not compare timings with different scopes. If one path includes compilation and another does not, report both cold and steady-state numbers separately.
Bottleneck classification
Classify the dominant bottleneck before choosing an optimization.
Use these categories:
- Global memory bandwidth.
- Cache bandwidth.
- Memory latency.
- Arithmetic throughput.
- Matrix/tensor-core utilization.
- Launch overhead.
- Kernel count (many small launches).
- Synchronization overhead.
- Atomic contention.
- Warp, wavefront, or subgroup divergence.
- Irregular memory access.
- Layout conversion overhead.
- Intermediate materialization.
- Register pressure.
- Shared/local memory pressure.
- Low occupancy.
- Instruction dependency latency.
- Host-device transfer.
- Device-device copy.
- Communication between GPUs.
- Work imbalance.
- Compiler-generated overhead.
- Allocation/deallocation overhead.
- Capacity or allocatability failure.
- Internal or external fragmentation.
- Mapping, fault, or address-translation overhead.
- Residency miss or migration overhead.
- State lookup, invalidation, or retention interference.
- Critical-path memory stall, staging pressure, starvation, or resource-wait cycle.
Do not optimize for occupancy, arithmetic intensity, fusion, or vectorization blindly. Optimize the observed bottleneck. Re-classify the bottleneck after every optimization round — yesterday's bottleneck is rarely today's.
Evidence hierarchy
Use an application timeline first to answer: where is wall time spent? Use kernel-level analysis second to answer: why is this hot kernel slow? Use compiler IR or generated code to answer: did the intended lowering happen? Use hardware counters to answer: which execution resource limits the kernel?
A practical sequence is:
- End-to-end wall time and throughput/latency distribution.
- Timeline: CPU launch gaps, kernels, copies, collectives, synchronizations.
- Kernel ranking by total time and call count.
- Roofline or byte/FLOP estimate for the top candidates.
- Targeted counters: achieved bandwidth, cache behavior, occupancy/resources, matrix-unit utilization, stalls, divergence, atomics.
- Re-measure after each material change.
On NVIDIA, Nsight Systems is typically the timeline tool and Nsight Compute the kernel/counter tool. On AMD, rocprofv3/rocProfiler-SDK and ROCprof Compute Viewer provide analogous trace and counter workflows. Framework profilers are useful for attributing kernels back to Python or graph operators. Tool names and available counters vary by version; use the current toolchain for the target environment.
Roofline and byte accounting
Use roofline reasoning as a model, not as a decorative chart. Estimate arithmetic intensity as useful operations divided by bytes transferred at the relevant memory level. Compare the measured kernel against the memory and compute ceilings of the target device and precision mode.
For a memory-bound hypothesis:
- count required input reads and output writes;
- count large intermediate write/read pairs;
- distinguish requested bytes from actual transactions when access is poorly coalesced;
- consider cache reuse, but do not assume a cache hit without evidence;
- estimate the lower bound
time >= bytes / sustainable_bandwidth.
For a compute-bound hypothesis:
- count the relevant arithmetic operations;
- use the throughput ceiling for the actual instruction/data type, not a marketing peak for a different precision;
- verify that the generated kernel actually uses the intended matrix/tensor/vector units;
- estimate
time >= operations / sustainable_compute_rate.
For launch-bound paths, roofline can be the wrong abstraction. Many short kernels may each be efficient while the application remains dominated by dispatch gaps. Count launches and inspect the timeline.
Anchor operation and data lifetime
Find the expensive operation that already touches the data. Use it as the anchor.
An anchor can be:
- Matrix multiplication.
- Convolution.
- Tensor contraction.
- Attention-like tiled computation.
- Reduction.
- Scan / prefix-sum / prefix-product.
- Stencil.
- Sort, select, or histogram phase.
- FFT-like stage.
- Batched small matrix operation.
- Image, video, or signal-processing tile.
- Physics, graph, or simulation update.
- Any dominant kernel in the profile.
Then inspect the data lifetime around the anchor.
Ask:
- Which values are already in registers, fragments, vector lanes, shared/local memory, cache, or a workgroup tile?
- Which neighboring operation consumes the anchor output immediately?
- Which neighboring operation produces an input for the anchor immediately?
- Which temporary buffer exists only because two operations are separated?
- Which reduction can emit compact partial results instead of a full intermediate buffer?
- Which layout conversion can be folded into a load, store, prologue, epilogue, or consumer read?
- Which scalar, row-wise, column-wise, channel-wise, head-wise, block-wise, or tile-wise parameter can be applied while data is already on chip?
The main pattern: move cheap memory-bound work into the lifetime of expensive tiled work.
Do not assume Tensor Core usage
Writing code in the shape of a matmul (Q @ K^T) does not guarantee Tensor Core execution. Verify through:
- Profiler trace (NVIDIA Nsight, rocprof, JAX profiler, PyTorch profiler).
- Compiler IR (HLO, StableHLO, FX graph, Inductor IR, Triton IR, PTX, SASS).
- Precision configuration (TF32, BF16, FP16, FP8, mixed precision).
- Shape alignment to tile constraints (e.g., M/N/K multiples of 8/16/32).
- Absence of implicit casts or copies that disable the fast path.
Claiming "uses Tensor Cores" without evidence is a red flag.
Small-matrix matmul warning
When a matmul, convolution, or contraction operates on very small dimensions (e.g., 64×64 or smaller), the bottleneck is frequently launch overhead, runtime dispatch, memory traffic, synchronization, or insufficient grid parallelism rather than raw FLOPs. This is an empirical tendency, not a hard rule keyed to the matrix size.
For small matrices, tile tuning should not be assumed to be the first optimization lever. First determine whether launch/dispatch overhead, memory traffic, insufficient parallelism, library dispatch, or kernel execution dominates. The following remain valid candidates when profiler evidence shows meaningful kernel-level headroom:
- Batching multiple small operations together.
- Fusing the small matmul into a larger kernel.
- Reducing operator boundaries around the matmul.
- Using grouped GEMM, batched GEMM, split-K, or sliced-K.
- Changing layout so small tasks become large contiguous tasks.
- Persistent scheduling to amortize dispatch and prologue cost.
- Selecting a different library algorithm when the heuristic chose a suboptimal kernel for this shape corner.
Rule: matrix dimensions alone do not classify the bottleneck. A 64×64 GEMM may be launch-bound, bandwidth-bound, compute-relevant, or dominated by library dispatch depending on batch, K, fusion, dtype, hardware, and library path.
Small matrix and tiny-kernel nuance
Small matrix operations often have low arithmetic work per launch, so dispatch, batching granularity, memory traffic, and surrounding operator boundaries can dominate. Do not turn this into a fixed size rule. A 64×64 GEMM can be launch-bound, bandwidth-bound, or compute-relevant depending on batch count, fusion, reuse, data type, hardware, library path, and whether many matrices are grouped into one launch.
Before concluding a small GEMM is or is not worth kernel-level tuning, record the following from a profiler trace — do not infer any of it from the matrix size alone:
- M, N, K, and dtype;
- batch / group count;
- kernel count and dominant kernel duration;
- CTA count and SM utilization;
- achieved matrix-pipe / tensor-core utilization;
- launch gap and total launch overhead;
- current library / kernel dispatch path;
- whether split-K, sliced-K, grouped GEMM, or a persistent strategy is in use;
- the GEMM's share of end-to-end request time.
Then verify the bottleneck hypothesis:
- whether grouped/batched GEMM reduces dispatch cost;
- whether the operation can be fused into a larger producer or consumer;
- whether data layout causes copies or prevents a library fast path;
- whether the library call is already close to the end-to-end optimum;
- whether tile tuning, split-K, sliced-K, grouped execution, persistent scheduling, or library algorithm selection changes the measured hot path rather than only a microbenchmark.
Small-GEMM eval
Given: a 64×64×8192 FP16 GEMM occupies 35% of request time; tensor-core utilization is high but only a few CTAs run and most SMs are idle.
Expected analysis considers: split-K, sliced-K, grouped GEMM, persistent scheduling, library algorithm selection, and fusion of neighboring operations. It does not conclude "this is 64×64, so do not tune the kernel."
Kernel-count audit
Before and after every optimization, record:
| Metric |
Before |
After |
| Kernel launches |
|
|
| Operator / graph-node count |
|
|
| Fusion groups |
|
|
| Device allocations |
|
|
| Host-device synchronizations |
|
|
| Device-device copies |
|
|
| Graph breaks (framework compile) |
|
|
| Command-buffer or graph replay success |
|
|
| Dominant kernel median time |
|
|
| Dominant kernel time % of total |
|
|
Many GPU programs are not bound by any single kernel. They are bound by having too many small kernels. Without a kernel count, you are guessing about launch overhead.
Allocation audit
Record before and after:
- Temporary buffer count and total bytes.
- Peak device memory.
- Allocator call count.
- Memory pool hit rate.
- Extra workspace buffers introduced.
- Implicit copies (reshape/transpose that materialize).
- Host staging buffers.
- Saved tensors for backward (count and total bytes).
- In-place update status (preserved or broken).
- Memory fragmentation risk.
A kernel microbenchmark that looks faster but increases peak memory or allocation count has not passed the real test.
Conditional resource and state evidence
Collect these fields only when the corresponding trigger is material. Do not burden an ordinary hot-kernel task with every resource-management audit.
| Trigger |
Required evidence |
| Lifetime/allocation |
Resource sizes and growth, alignment, complete consumers, first/last-use frontiers, asynchronous completion, workspace, peak overlap, reconstruction cost |
| Backing/fragmentation |
Reserved, committed, resident, requested, charged, eligible-free bytes, largest allocatable extent, internal waste, extent distribution, mapping/fault/translation cost |
| Tiering/migration |
Tier capacity, directional bandwidth/latency/topology, working set, next-use distribution, transfer/staging bytes, exposed stalls, late or unused prefetch, reversals, movement amplification |
| Reuse/eviction |
Identity fields, validity predicate, mutation epoch, owner/isolation domain, valid-hit probability, work avoided, footprint, lookup, movement, maintenance, and interference |
| Persistent state |
Growth law, mutation model, version lineage, ownership, retention scope, checkpoint coverage, reconstruction cost, cleanup boundary |
| Memory scheduling |
Typed dependencies, readiness, critical path, exposed stalls, overlap windows, contention, staging lifetime, pressure-time, tail latency, starvation and resource-wait evidence |
Use the same snapshot and workload scope for related memory quantities. Aggregate free bytes, nominal bandwidth, hit rate, overlap duration, and average latency are insufficient on their own.
Separate measured, modeled, inferred, and assumed values. Every modeled policy needs a falsifying measurement before it becomes a finding.
Profiling source of truth
Every performance claim must cite its evidence source:
| Evidence tier |
Source |
| Profiler trace |
Kernel duration, count, copy, sync, launch overhead. |
| Compiler IR |
Fusion confirmation, dot lowering, layout conversion, graph break. |
| Roofline / byte-FLOP |
Bottleneck classification (memory vs compute vs launch). |
| Benchmark |
Wall-time improvement (isolated + end-to-end). |
| Memory profile |
Peak memory, allocation count, saved tensors. |
| Hardware counters |
Occupancy, bandwidth, cache hit, tensor-core utilization, stall reasons. |
| Correctness test |
Error statistics, tolerance compliance. |
Classify every performance claim by epistemic strength — direct observation > derived measurement > analytical model > inference > assumption — and do not promote a claim past its weakest link. A profiler or counter result is still a measurement, not ground truth: collection can change the experiment.
Measurement validity
Required for any finding that relies on a profiler, counter, or replay-based metric:
- collection mode (trace / sampling / replay);
- replay count and whether Range / Application Range Replay preserved concurrency;
- deterministic across replays, or variance noted;
- profiler overhead and whether it altered the timed path;
- direct metric vs one derived from other counters;
- multi-pass artifacts: metrics gathered across different replay passes may disagree or read out-of-range for short kernels, variable workloads, or spin/concurrent behavior.
Require end-to-end timing for any claim about application-level benefit or speedup. When the collection-mode, replay, determinism, and overhead checks above pass, validated direct or derived kernel-level findings (occupancy, cache hits, stall reasons, tensor-core utilization) may be reported as findings even when end-to-end timing does not measure that property. Until those checks pass, treat the number as hypothetical.
Causal probes and prediction records
Use a causal probe when the same symptom has more than one plausible explanation. Establish Baseline controls first: hold the same workload, device state, correctness contract, measurement scope, and unrelated code constant. A source edit is not a causal probe if the compiler, dispatcher, or runtime removes or bypasses it; confirm compiler/dispatcher/runtime reachability through generated code or the realized schedule before interpreting the result.
Before running a probe or variant, record both the target-metric prediction and an independent mechanism prediction:
Prediction record:
- Hypothesis ID: <stable identifier>
- Baseline ID: <immutable snapshot and reset/restore procedure>
- Baseline controls: <same workload, device state, correctness contract, measurement scope, and unrelated code>
- Observed symptom: <fact, scope, and evidence source>
- Proposed mechanism: <causal path from the relation to the cost>
- Required preconditions: <mapping, semantic, resource, or runtime facts>
- Predicted target-metric effect: <direction, magnitude range if justified, and scope>
- Predicted independent evidence: <counter, trace, IR, allocation, or correctness movement>
- Cheapest falsifying probe: <one-factor observation or variant>
- Evidence source/scope: <trace, counter, IR, benchmark, and exact workload scope>
- Confounders and controls: <what could mimic the result>
- Cost: <implementation, compilation, and measurement cost>
- Confidence: <high / medium / low, with update reason>
- Outcome: <not run / supports / weakens / surprises / unreachable>
Choose probes by diagnostic value rather than expected speedup alone. Prefer the least expensive probe that separates several plausible mechanisms and can still affect the user's target metric. Keep a probe that only observes a mechanism distinct from a production variant that changes behavior; compare production variants independently against the same baseline.
Make the controls operational rather than ceremonial. When they can affect the result, record warm-up and compilation state, synchronization scope, allocator and cache state, clock/power state, input seed and data distribution, contention, and profiler-validity checks. If a control cannot be held constant, mark the comparison as confounded and lower confidence instead of treating it as a clean intervention.
Classify the result after measurement:
- metric and mechanism move as predicted: increase confidence, but still check correctness, scope, and downstream cost before promotion;
- neither moves: the mechanism is likely falsified, the perturbation did not reach the machine, or the signal is below measurement noise; verify reachability before editing again;
- mechanism moves but target metric does not: inspect a compensating cost, an unimportant phase, or a downstream bottleneck; keep the mechanism finding separate from an application-speedup claim;
- target metric moves without the predicted mechanism: audit measurement scope, compiler/runtime changes, cache and clock state, and other confounders, then write a different mechanism;
- movement has the opposite sign: preserve the observation as high-value residual evidence and investigate the newly exposed cost instead of discarding it automatically.
Update the prediction record with the observation, uncertainty, confidence change, and next falsifier. A counter movement does not override an unchanged end-to-end target, and an unchanged counter does not prove that a valid target improvement is impossible. After an accepted change, re-profile the path and regenerate predictions because the limiting resource and the validity of old negative results may have changed.
End-to-end priority rule
If an isolated kernel or ops-level benchmark accelerates but end-to-end wall time does not improve, the change cannot be claimed as a performance improvement. It is a local micro-optimization at best.
Report performance hierarchically:
- Single-kernel time.
- Operator-level time.
- Module-level time.
- Full forward step time.
- Full training iteration time (forward + backward + optimizer).
- Full inference-request time.
- Memory peak.
- Compile time.
- Allocation/transfer time.
Many optimizations make a local kernel faster while increasing compile time, adding layout conversions, increasing backward cost, reducing fusion, or raising memory peak — causing end-to-end regression. Judge by the user's target metric.
Evidence-driven rejection rule
Reject an optimization direction when the profiler, IR, benchmark, or byte/FLOP analysis does not support the bottleneck hypothesis.
Common evidence-free traps:
- Tuning occupancy without evidence that occupancy is the bottleneck.
- Changing tile sizes without profiling.
- Replacing a library primitive without proving composition overhead.
- Introducing shared memory without evidence it helps.
- Blindly fusing all adjacent kernels.
- Assuming Tensor Cores are used because the code contains a matmul.
- Assuming memory bandwidth is the bottleneck without a roofline check.
- Assuming launch overhead is the bottleneck without a kernel count.
Optimization must be driven by evidence. Without evidence, state the hypothesis and what measurement would test it. Do not implement the hypothesis as fact.
Deliverable from this skill
Return a concise bottleneck statement with:
- target metric and workload scope;
- baseline numbers and measurement method;
- dominant phase/kernel/operator;
- bottleneck class with evidence;
- one ranked next probe or one small round of independent variants, each tied to a hypothesis;
- the measurement that would falsify the hypothesis.
When a resource or runtime-state trigger applies, also name the primary decision layer: lifetime, backing, residency, logical reuse, state semantics, or scheduling. Do not collapse them into a generic “memory issue.”
Then jump to the specialist skill that matches the evidence. Do not jump directly to low-level tuning merely because a GPU kernel exists.
1---2name: gpu-performance-evidence3description: Load this skill and follow it when establishing a GPU performance baseline, analyzing profiler data, roofline results, or hardware counters, classifying bottlenecks, or validating evidence for a claimed speedup.4---56# GPU Performance Evidence78## Skill navigation9- Parent/orchestrator: [gpu-code-optimizer](../gpu-code-optimizer/SKILL.md)10- [gpu-code-optimizer](../gpu-code-optimizer/SKILL.md) — return to overall routing and priority selection11- [gpu-memory-fusion-layout](../gpu-memory-fusion-layout/SKILL.md) — load when evidence points to memory traffic, temporaries, or layout cost12- [gpu-resource-lifetime-allocation](../gpu-resource-lifetime-allocation/SKILL.md) — load when peak live overlap, workspaces, allocation reuse, or rematerialization matter13- [gpu-virtual-memory-fragmentation](../gpu-virtual-memory-fragmentation/SKILL.md) — load when capacity and allocatability differ or backing policy is material14- [gpu-memory-tiering-migration](../gpu-memory-tiering-migration/SKILL.md) — load when residency, placement, movement, or oversubscription matters15- [gpu-state-reuse-eviction](../gpu-state-reuse-eviction/SKILL.md) — load when retained-state identity, validity, value, or logical eviction matters16- [gpu-persistent-state](../gpu-persistent-state/SKILL.md) — load when cross-call growth, mutation, ownership, reconstruction, or cleanup matters17- [gpu-memory-scheduling](../gpu-memory-scheduling/SKILL.md) — load when exposed memory stalls or joint compute/memory ordering matters18- [gpu-kernel-execution](../gpu-kernel-execution/SKILL.md) — load when a specific hot kernel needs execution-level tuning19- [gpu-compiler-runtime](../gpu-compiler-runtime/SKILL.md) — load when timeline gaps, compiler behavior, or runtime overhead dominate20- [gpu-optimization-validation](../gpu-optimization-validation/SKILL.md) — load before accepting or reporting the optimization2122Load linked skills only when their trigger applies. Do not duplicate their full workflow here.2324## Core principle2526Performance work starts from a measured baseline and ends with a measured end-to-end result. Source inspection can generate hypotheses; it cannot by itself establish the bottleneck or prove speedup.2728Use the highest-level measurement that still answers the question. First locate the expensive phase in an application timeline. Then drill into a hot kernel only when kernel-level details can change the decision. Avoid collecting every hardware counter before knowing which kernel matters.2930## Baseline record3132Before modifying code, identify what is known.3334Record:3536- **Hardware**: Target GPU or GPU family, compute capability, memory bandwidth.37- **Software**: Framework, compiler, runtime, backend (e.g., JAX/XLA, torch.compile/Inductor, CUDA, Triton).38- **Kernel purpose**: What the code computes and why.39- **Shapes**: Input shapes, dtypes, strides, layouts, alignments, batch sizes.40- **Boundary cases**: Non-divisible sizes, singleton dimensions, empty inputs.41- **Current metrics**: Runtime (median, p95), throughput, bandwidth, occupancy, kernel count, peak memory, allocation count.42- **Correctness**: Existing tests, tolerance, deterministic mode requirements.43- **Target**: Latency, throughput, memory footprint, energy, compilation time, or end-to-end wall time.4445If data is missing, proceed with conservative assumptions. State which measurements would confirm or reject the optimization.4647### Representative workload checklist4849Do not benchmark only on random-normal inputs with a single shape. Test:5051- Small, medium, target, and maximum input sizes.52- Non-power-of-2 sizes and non-divisible dimensions.53- Batch size sweeps.54- Channel/head/feature dimension sweeps.55- Different dtypes (fp32, fp16, bf16, mixed).56- Different layouts (contiguous vs non-contiguous, channel-last vs channel-first).57- Real data distributions and extreme data distributions.58- Sparse, all-zero, constant, large-value, small-value inputs.59- Training and inference paths (if the code is used for both).60- Gradient computation (if the code is differentiated).6162An optimization that only wins on hand-picked shapes must not become an unguarded general default; a shape-specialized kernel behind a guard/flag in the dispatcher is acceptable when its domain is stated.6364---6566### Benchmark state must be explicit6768Record whether each timing includes or excludes:6970- compilation/JIT/autotuning;71- allocator warm-up and memory-pool initialization;72- data loading and host preprocessing;73- host↔device or device↔device transfers;74- synchronization inserted only for measurement;75- graph capture/warm-up versus graph replay;76- forward only versus forward+backward+optimizer;77- distributed collectives and synchronization;78- cache-hot versus cache-cold state.7980Do not compare timings with different scopes. If one path includes compilation and another does not, report both cold and steady-state numbers separately.8182## Bottleneck classification8384Classify the dominant bottleneck before choosing an optimization.8586Use these categories:8788- Global memory bandwidth.89- Cache bandwidth.90- Memory latency.91- Arithmetic throughput.92- Matrix/tensor-core utilization.93- Launch overhead.94- Kernel count (many small launches).95- Synchronization overhead.96- Atomic contention.97- Warp, wavefront, or subgroup divergence.98- Irregular memory access.99- Layout conversion overhead.100- Intermediate materialization.101- Register pressure.102- Shared/local memory pressure.103- Low occupancy.104- Instruction dependency latency.105- Host-device transfer.106- Device-device copy.107- Communication between GPUs.108- Work imbalance.109- Compiler-generated overhead.110- Allocation/deallocation overhead.111- Capacity or allocatability failure.112- Internal or external fragmentation.113- Mapping, fault, or address-translation overhead.114- Residency miss or migration overhead.115- State lookup, invalidation, or retention interference.116- Critical-path memory stall, staging pressure, starvation, or resource-wait cycle.117118Do not optimize for occupancy, arithmetic intensity, fusion, or vectorization blindly. Optimize the observed bottleneck. Re-classify the bottleneck after every optimization round — yesterday's bottleneck is rarely today's.119120---121122### Evidence hierarchy123124Use an application timeline first to answer: *where is wall time spent?* Use kernel-level analysis second to answer: *why is this hot kernel slow?* Use compiler IR or generated code to answer: *did the intended lowering happen?* Use hardware counters to answer: *which execution resource limits the kernel?*125126A practical sequence is:1271281. End-to-end wall time and throughput/latency distribution.1292. Timeline: CPU launch gaps, kernels, copies, collectives, synchronizations.1303. Kernel ranking by total time and call count.1314. Roofline or byte/FLOP estimate for the top candidates.1325. Targeted counters: achieved bandwidth, cache behavior, occupancy/resources, matrix-unit utilization, stalls, divergence, atomics.1336. Re-measure after each material change.134135On NVIDIA, Nsight Systems is typically the timeline tool and Nsight Compute the kernel/counter tool. On AMD, rocprofv3/rocProfiler-SDK and ROCprof Compute Viewer provide analogous trace and counter workflows. Framework profilers are useful for attributing kernels back to Python or graph operators. Tool names and available counters vary by version; use the current toolchain for the target environment.136137## Roofline and byte accounting138139Use roofline reasoning as a model, not as a decorative chart. Estimate arithmetic intensity as useful operations divided by bytes transferred at the relevant memory level. Compare the measured kernel against the memory and compute ceilings of the target device and precision mode.140141For a memory-bound hypothesis:142143- count required input reads and output writes;144- count large intermediate write/read pairs;145- distinguish requested bytes from actual transactions when access is poorly coalesced;146- consider cache reuse, but do not assume a cache hit without evidence;147- estimate the lower bound `time >= bytes / sustainable_bandwidth`.148149For a compute-bound hypothesis:150151- count the relevant arithmetic operations;152- use the throughput ceiling for the actual instruction/data type, not a marketing peak for a different precision;153- verify that the generated kernel actually uses the intended matrix/tensor/vector units;154- estimate `time >= operations / sustainable_compute_rate`.155156For launch-bound paths, roofline can be the wrong abstraction. Many short kernels may each be efficient while the application remains dominated by dispatch gaps. Count launches and inspect the timeline.157158## Anchor operation and data lifetime159160Find the expensive operation that already touches the data. Use it as the anchor.161162An anchor can be:163164- Matrix multiplication.165- Convolution.166- Tensor contraction.167- Attention-like tiled computation.168- Reduction.169- Scan / prefix-sum / prefix-product.170- Stencil.171- Sort, select, or histogram phase.172- FFT-like stage.173- Batched small matrix operation.174- Image, video, or signal-processing tile.175- Physics, graph, or simulation update.176- Any dominant kernel in the profile.177178Then inspect the data lifetime around the anchor.179180Ask:181182- Which values are already in registers, fragments, vector lanes, shared/local memory, cache, or a workgroup tile?183- Which neighboring operation consumes the anchor output immediately?184- Which neighboring operation produces an input for the anchor immediately?185- Which temporary buffer exists only because two operations are separated?186- Which reduction can emit compact partial results instead of a full intermediate buffer?187- Which layout conversion can be folded into a load, store, prologue, epilogue, or consumer read?188- Which scalar, row-wise, column-wise, channel-wise, head-wise, block-wise, or tile-wise parameter can be applied while data is already on chip?189190The main pattern: move cheap memory-bound work into the lifetime of expensive tiled work.191192### Do not assume Tensor Core usage193194Writing code in the shape of a matmul (`Q @ K^T`) does **not** guarantee Tensor Core execution. Verify through:195196- Profiler trace (NVIDIA Nsight, rocprof, JAX profiler, PyTorch profiler).197- Compiler IR (HLO, StableHLO, FX graph, Inductor IR, Triton IR, PTX, SASS).198- Precision configuration (TF32, BF16, FP16, FP8, mixed precision).199- Shape alignment to tile constraints (e.g., M/N/K multiples of 8/16/32).200- Absence of implicit casts or copies that disable the fast path.201202Claiming "uses Tensor Cores" without evidence is a red flag.203204### Small-matrix matmul warning205206When a matmul, convolution, or contraction operates on very small dimensions (e.g., 64×64 or smaller), the bottleneck is **frequently** launch overhead, runtime dispatch, memory traffic, synchronization, or insufficient grid parallelism rather than raw FLOPs. This is an empirical tendency, not a hard rule keyed to the matrix size.207208For small matrices, tile tuning should **not** be assumed to be the first optimization lever. First determine whether launch/dispatch overhead, memory traffic, insufficient parallelism, library dispatch, or kernel execution dominates. The following remain valid candidates when profiler evidence shows meaningful kernel-level headroom:209210- Batching multiple small operations together.211- Fusing the small matmul into a larger kernel.212- Reducing operator boundaries around the matmul.213- Using grouped GEMM, batched GEMM, split-K, or sliced-K.214- Changing layout so small tasks become large contiguous tasks.215- Persistent scheduling to amortize dispatch and prologue cost.216- Selecting a different library algorithm when the heuristic chose a suboptimal kernel for this shape corner.217218**Rule: matrix dimensions alone do not classify the bottleneck.** A 64×64 GEMM may be launch-bound, bandwidth-bound, compute-relevant, or dominated by library dispatch depending on batch, K, fusion, dtype, hardware, and library path.219220---221222### Small matrix and tiny-kernel nuance223224Small matrix operations often have low arithmetic work per launch, so dispatch, batching granularity, memory traffic, and surrounding operator boundaries can dominate. Do not turn this into a fixed size rule. A 64×64 GEMM can be launch-bound, bandwidth-bound, or compute-relevant depending on batch count, fusion, reuse, data type, hardware, library path, and whether many matrices are grouped into one launch.225226Before concluding a small GEMM is or is not worth kernel-level tuning, **record** the following from a profiler trace — do not infer any of it from the matrix size alone:227228- M, N, K, and dtype;229- batch / group count;230- kernel count and dominant kernel duration;231- CTA count and SM utilization;232- achieved matrix-pipe / tensor-core utilization;233- launch gap and total launch overhead;234- current library / kernel dispatch path;235- whether split-K, sliced-K, grouped GEMM, or a persistent strategy is in use;236- the GEMM's share of end-to-end request time.237238Then verify the bottleneck hypothesis:239240- whether grouped/batched GEMM reduces dispatch cost;241- whether the operation can be fused into a larger producer or consumer;242- whether data layout causes copies or prevents a library fast path;243- whether the library call is already close to the end-to-end optimum;244- whether tile tuning, split-K, sliced-K, grouped execution, persistent scheduling, or library algorithm selection changes the measured hot path rather than only a microbenchmark.245246#### Small-GEMM eval247248Given: a 64×64×8192 FP16 GEMM occupies 35% of request time; tensor-core utilization is high but only a few CTAs run and most SMs are idle.249250Expected analysis considers: split-K, sliced-K, grouped GEMM, persistent scheduling, library algorithm selection, and fusion of neighboring operations. It does **not** conclude "this is 64×64, so do not tune the kernel."251252## Kernel-count audit253254Before and after every optimization, record:255256| Metric | Before | After |257|:-------|-------:|------:|258| Kernel launches | | |259| Operator / graph-node count | | |260| Fusion groups | | |261| Device allocations | | |262| Host-device synchronizations | | |263| Device-device copies | | |264| Graph breaks (framework compile) | | |265| Command-buffer or graph replay success | | |266| Dominant kernel median time | | |267| Dominant kernel time % of total | | |268269Many GPU programs are not bound by any single kernel. They are bound by having too many small kernels. Without a kernel count, you are guessing about launch overhead.270271---272273## Allocation audit274275Record before and after:276277- Temporary buffer count and total bytes.278- Peak device memory.279- Allocator call count.280- Memory pool hit rate.281- Extra workspace buffers introduced.282- Implicit copies (reshape/transpose that materialize).283- Host staging buffers.284- Saved tensors for backward (count and total bytes).285- In-place update status (preserved or broken).286- Memory fragmentation risk.287288A kernel microbenchmark that looks faster but increases peak memory or allocation count has not passed the real test.289290---291292## Conditional resource and state evidence293294Collect these fields only when the corresponding trigger is material. Do not burden an ordinary hot-kernel task with every resource-management audit.295296| Trigger | Required evidence |297|---|---|298| Lifetime/allocation | Resource sizes and growth, alignment, complete consumers, first/last-use frontiers, asynchronous completion, workspace, peak overlap, reconstruction cost |299| Backing/fragmentation | Reserved, committed, resident, requested, charged, eligible-free bytes, largest allocatable extent, internal waste, extent distribution, mapping/fault/translation cost |300| Tiering/migration | Tier capacity, directional bandwidth/latency/topology, working set, next-use distribution, transfer/staging bytes, exposed stalls, late or unused prefetch, reversals, movement amplification |301| Reuse/eviction | Identity fields, validity predicate, mutation epoch, owner/isolation domain, valid-hit probability, work avoided, footprint, lookup, movement, maintenance, and interference |302| Persistent state | Growth law, mutation model, version lineage, ownership, retention scope, checkpoint coverage, reconstruction cost, cleanup boundary |303| Memory scheduling | Typed dependencies, readiness, critical path, exposed stalls, overlap windows, contention, staging lifetime, pressure-time, tail latency, starvation and resource-wait evidence |304305Use the same snapshot and workload scope for related memory quantities. Aggregate free bytes, nominal bandwidth, hit rate, overlap duration, and average latency are insufficient on their own.306307Separate measured, modeled, inferred, and assumed values. Every modeled policy needs a falsifying measurement before it becomes a finding.308309---310311## Profiling source of truth312313Every performance claim must cite its evidence source:314315| Evidence tier | Source |316|:-------------|:-------|317| **Profiler trace** | Kernel duration, count, copy, sync, launch overhead. |318| **Compiler IR** | Fusion confirmation, dot lowering, layout conversion, graph break. |319| **Roofline / byte-FLOP** | Bottleneck classification (memory vs compute vs launch). |320| **Benchmark** | Wall-time improvement (isolated + end-to-end). |321| **Memory profile** | Peak memory, allocation count, saved tensors. |322| **Hardware counters** | Occupancy, bandwidth, cache hit, tensor-core utilization, stall reasons. |323| **Correctness test** | Error statistics, tolerance compliance. |324325Classify every performance claim by epistemic strength — direct observation > derived measurement > analytical model > inference > assumption — and do not promote a claim past its weakest link. A profiler or counter result is still a measurement, not ground truth: collection can change the experiment.326327### Measurement validity328329Required for any finding that relies on a profiler, counter, or replay-based metric:330331- collection mode (trace / sampling / replay);332- replay count and whether Range / Application Range Replay preserved concurrency;333- deterministic across replays, or variance noted;334- profiler overhead and whether it altered the timed path;335- direct metric vs one derived from other counters;336- multi-pass artifacts: metrics gathered across different replay passes may disagree or read out-of-range for short kernels, variable workloads, or spin/concurrent behavior.337338Require end-to-end timing for any claim about application-level benefit or speedup. When the collection-mode, replay, determinism, and overhead checks above pass, validated direct or derived kernel-level findings (occupancy, cache hits, stall reasons, tensor-core utilization) may be reported as findings even when end-to-end timing does not measure that property. Until those checks pass, treat the number as hypothetical.339340---341342## Causal probes and prediction records343344Use a causal probe when the same symptom has more than one plausible explanation. Establish **Baseline controls** first: hold the same workload, device state, correctness contract, measurement scope, and unrelated code constant. A source edit is not a causal probe if the compiler, dispatcher, or runtime removes or bypasses it; confirm compiler/dispatcher/runtime reachability through generated code or the realized schedule before interpreting the result.345346Before running a probe or variant, record both the target-metric prediction and an independent mechanism prediction:347348```text349Prediction record:350- Hypothesis ID: <stable identifier>351- Baseline ID: <immutable snapshot and reset/restore procedure>352- Baseline controls: <same workload, device state, correctness contract, measurement scope, and unrelated code>353- Observed symptom: <fact, scope, and evidence source>354- Proposed mechanism: <causal path from the relation to the cost>355- Required preconditions: <mapping, semantic, resource, or runtime facts>356- Predicted target-metric effect: <direction, magnitude range if justified, and scope>357- Predicted independent evidence: <counter, trace, IR, allocation, or correctness movement>358- Cheapest falsifying probe: <one-factor observation or variant>359- Evidence source/scope: <trace, counter, IR, benchmark, and exact workload scope>360- Confounders and controls: <what could mimic the result>361- Cost: <implementation, compilation, and measurement cost>362- Confidence: <high / medium / low, with update reason>363- Outcome: <not run / supports / weakens / surprises / unreachable>364```365366Choose probes by diagnostic value rather than expected speedup alone. Prefer the least expensive probe that separates several plausible mechanisms and can still affect the user's target metric. Keep a probe that only observes a mechanism distinct from a production variant that changes behavior; compare production variants independently against the same baseline.367368Make the controls operational rather than ceremonial. When they can affect the result, record warm-up and compilation state, synchronization scope, allocator and cache state, clock/power state, input seed and data distribution, contention, and profiler-validity checks. If a control cannot be held constant, mark the comparison as confounded and lower confidence instead of treating it as a clean intervention.369370Classify the result after measurement:371372- **metric and mechanism move as predicted**: increase confidence, but still check correctness, scope, and downstream cost before promotion;373- **neither moves**: the mechanism is likely falsified, the perturbation did not reach the machine, or the signal is below measurement noise; verify reachability before editing again;374- **mechanism moves but target metric does not**: inspect a compensating cost, an unimportant phase, or a downstream bottleneck; keep the mechanism finding separate from an application-speedup claim;375- **target metric moves without the predicted mechanism**: audit measurement scope, compiler/runtime changes, cache and clock state, and other confounders, then write a different mechanism;376- **movement has the opposite sign**: preserve the observation as high-value residual evidence and investigate the newly exposed cost instead of discarding it automatically.377378Update the prediction record with the observation, uncertainty, confidence change, and next falsifier. A counter movement does not override an unchanged end-to-end target, and an unchanged counter does not prove that a valid target improvement is impossible. After an accepted change, re-profile the path and regenerate predictions because the limiting resource and the validity of old negative results may have changed.379380## End-to-end priority rule381382If an isolated kernel or ops-level benchmark accelerates but end-to-end wall time does **not** improve, the change cannot be claimed as a performance improvement. It is a local micro-optimization at best.383384Report performance hierarchically:3853861. Single-kernel time.3872. Operator-level time.3883. Module-level time.3894. Full forward step time.3905. Full training iteration time (forward + backward + optimizer).3916. Full inference-request time.3927. Memory peak.3938. Compile time.3949. Allocation/transfer time.395396Many optimizations make a local kernel faster while increasing compile time, adding layout conversions, increasing backward cost, reducing fusion, or raising memory peak — causing end-to-end regression. Judge by the user's target metric.397398---399400## Evidence-driven rejection rule401402Reject an optimization direction when the profiler, IR, benchmark, or byte/FLOP analysis does **not** support the bottleneck hypothesis.403404Common evidence-free traps:405406- Tuning occupancy without evidence that occupancy is the bottleneck.407- Changing tile sizes without profiling.408- Replacing a library primitive without proving composition overhead.409- Introducing shared memory without evidence it helps.410- Blindly fusing all adjacent kernels.411- Assuming Tensor Cores are used because the code contains a matmul.412- Assuming memory bandwidth is the bottleneck without a roofline check.413- Assuming launch overhead is the bottleneck without a kernel count.414415Optimization must be driven by evidence. Without evidence, state the hypothesis and what measurement would test it. Do not implement the hypothesis as fact.416417---418419## Deliverable from this skill420421Return a concise bottleneck statement with:422423- target metric and workload scope;424- baseline numbers and measurement method;425- dominant phase/kernel/operator;426- bottleneck class with evidence;427- one ranked next probe or one small round of independent variants, each tied to a hypothesis;428- the measurement that would falsify the hypothesis.429430When a resource or runtime-state trigger applies, also name the primary decision layer: lifetime, backing, residency, logical reuse, state semantics, or scheduling. Do not collapse them into a generic “memory issue.”431432Then jump to the specialist skill that matches the evidence. Do not jump directly to low-level tuning merely because a GPU kernel exists.