Automated summary
Walks through a step-by-step diagnosis of a CUDA kernel — roofline, occupancy, per-tile compute/load ratio, and SASS inspection — to classify it as compute-bound, memory-bound, or latency-bound and derive an optimization strategy.
Strengths
- Quantitative procedure with explicit formulas (arithmetic intensity, effective GFLOPS and bandwidth) plus a decision matrix mapping each bottleneck to a concrete optimization: cp.async, tiling, double-buffering, or CuAssembler.
- Bundles GA104 reference peaks and derived balance points, so the classification translates directly into action.
- Every step states its expected output and failure remedy, and ties into real tooling (nvcc, CUDA events, Nsight Compute reports).
- Also covers the shared memory cliff and SASS stall codes, not just headline metrics.
Limitations
- Tightly CUDA-specific and defaulted to GA104/sm_86: peak values for other architectures must be substituted by hand.
- Assumes a benchmark harness with CUDA event timing and known problem dimensions already exist.
- Stops at strategy selection; implementing and validating the optimizations is left to the user.
Reach for it before optimizing a CUDA kernel, or when a kernel falls short of theoretical peak and you need to know which optimization path to take.
Skip it for non-CUDA backends (ROCm, Metal, Triton) or when a plain end-to-end timing is enough and no root-cause diagnosis is needed.
Security analysis
CautionThe skill instructs compiling and running GPU kernels with nvcc and benchmark executables. While the commands themselves are legitimate and non-destructive, executing user-supplied code via Bash carries inherent execution risk. No data exfiltration, destructive commands, or obfuscation were found.
- •Uses Bash to compile and run user-provided CUDA code and benchmark binaries, which could execute arbitrary code if the source is malicious.
Examples
My tiled FP32 GEMM kernel in kernel.cu reaches only ~6 TFLOPS at 4096^3 on an RTX 3070 Ti. Run the bottleneck analysis: build the .cubin, measure the baseline with the CUDA-event bench harness, compute arithmetic intensity, and place it on the GA104 roofline. Tell me whether it is compute-, memory-, or latency-bound and which optimization to try next.Inspect the SASS of kernel.sm_86.cubin: dump the instruction mix and stall reasons, then combine that with the occupancy and per-tile compute/load ratio to tell me if this kernel is latency-bound and whether cp.async double-buffering would actually help.Check whether my Flash Attention kernel has hit the shared memory cliff: compute occupancy at the current tile size, then recommend whether to grow the tile, switch to warp interleaving, or hand-tune with CuAssembler.name: analyze-kernel-bottleneck description: > Systematically identify whether a GPU kernel is compute-bound, memory-bound, or latency-bound using roofline analysis, occupancy calculations, compute/load ratio per tile, and SASS instruction inspection. Produces a decision matrix for optimization strategy selection (cp.async, warp interleaving, tiling, double-buffering, or CuAssembler hand-tuning). license: MIT allowed-tools: Read Grep Glob Bash metadata: author: Philipp Thoss version: "1.0" domain: gpu-optimization complexity: advanced language: CUDA tags: gpu, roofline, occupancy, sass, tensor-core, bottleneck-analysis, compute-load-ratio
Analyze Kernel Bottleneck
Systematically identify whether a GPU kernel is compute-bound, memory-bound, or latency-bound by measuring baseline performance, classifying on the roofline, computing occupancy and compute/load ratio per tile, inspecting SASS instruction mix and stall codes, checking the shared memory cliff, and applying a decision matrix to select the right optimization strategy.
When to Use
- Before optimizing any CUDA kernel -- establish baseline and classify bottleneck type
- After writing a first working version of a kernel to identify the optimization path
- When a kernel underperforms expectations relative to theoretical peak
- When deciding between cp.async, larger tiles, or algorithmic restructuring
Inputs
- Required: Compiled kernel (
.cubinor.cusource with build command) - Required: Benchmark harness that launches the kernel with CUDA event timing
- Required: Problem dimensions (e.g., M, N, K for GEMM; seq_len, heads, head_dim for attention)
- Optional: Target GPU architecture (default: GA104 / sm_86 / RTX 3070 Ti)
- Optional: Expected peak utilization percentage for comparison
- Optional: Prior profiling data (Nsight Compute reports)
Procedure
Step 1: Measure Baseline Performance
Run the kernel with CUDA events (BenchTimer), record time in milliseconds. Calculate effective throughput metrics:
- Compile the kernel if not already built:
nvcc --cubin -arch=sm_86 -O2 -o kernel.sm_86.cubin kernel.cu nvcc -arch=sm_86 -O2 -o bench bench.cu -lcuda -I../../phase2/common - Run with representative problem sizes, ensuring warmup runs precede measurement:
./bench 4096 4096 4096 - Record kernel time in ms from CUDA events (not wall-clock).
- Calculate effective GFLOPS and effective bandwidth:
- GEMM:
effective_gflops = (2 * M * N * K) / (time_ms / 1000) / 1e9 - Bandwidth-limited kernels:
effective_bw = total_bytes / (time_ms / 1000) / 1e9 - Flash Attention:
effective_gflops = (4 * batch * heads * seq_len^2 * head_dim) / (time_ms / 1000) / 1e9
- GEMM:
Expected: Baseline numbers: kernel time in ms, effective GFLOPS, and effective bandwidth.
On failure: Check that the kernel launches without error (CHECK_CU macro). Verify warmup runs precede measurement. Ensure problem dimensions are large enough to saturate the GPU (small problems may bottleneck on launch overhead).
Step 2: Classify on the Roofline
Compute arithmetic intensity and compare against the machine balance point to classify the kernel:
- Calculate arithmetic intensity:
AI = FLOPs / bytes_loaded_from_global_memory. Count only unique bytes loaded from DRAM (not shared memory or register reuse). - Look up machine balance point:
balance = peak_compute / peak_bandwidth. - Classify: If
AI < balance, the kernel is memory-bound. IfAI > balance, the kernel is compute-bound.
GA104 (RTX 3070 Ti) Reference Values:
| Resource | Peak | Unit | |---|---|---| | FP32 FFMA | 21.7 | TFLOPS | | FP16 Tensor Core (HMMA) | 174 | TFLOPS | | INT8 Tensor Core (IMMA) | 696 | TOPS | | DRAM Bandwidth | 608 | GB/s | | L2 Cache | 4 | MB | | SMs | 48 | |
Derived Balance Points:
| Precision | Balance Point (FLOP/byte) | |---|---| | FP32 FFMA | 21700 / 608 = 35.7 | | FP16 TC | 174000 / 608 = 286.2 | | INT8 TC | 696000 / 608 = 1144.7 |
- Compute attained fraction:
attained = effective_throughput / peak_throughput. If memory-bound: compare effective bandwidth to 608 GB/s. If compute-bound: compare effective GFLOPS to the relevant peak.
Expected: Classification as compute-bound, memory-bound, or latency-bound (low occupancy causing neither compute nor memory saturation) with numerical justification.
On failure: Recheck byte counting. Watch for redundant re-reads (e.g., 9x in direct conv2d without im2col). If neither compute nor memory is saturated, the kernel is likely latency-bound (see Step 3).
Step 3: Calculate Occupancy
Determine active warps per SM from the launch configuration and resource usage:
- Extract resource usage:
nvcc --cubin -arch=sm_86 -O2 --resource-usage -o kernel.sm_86.cubin kernel.cu 2>&1 | grep -E 'registers|smem' - From launch config:
warps_per_block = threads_per_block / 32. - Compute blocks/SM from each limiting factor:
- Register limit:
floor(65536 / (registers_per_thread * threads_per_block)) - Smem limit:
floor(available_smem_per_SM / smem_per_block)-- see Step 6 for cliff - Warp limit:
floor(48 / warps_per_block)(GA104 max: 48 warps/SM) - Block limit: 16 blocks/SM max on GA104
- Register limit:
- Actual blocks/SM =
min(register_limit, smem_limit, warp_limit, block_limit). - Active warps/SM =
blocks_per_SM * warps_per_block. - Key threshold: 8 warps/SM is sufficient for latency hiding on GA104. Below 8 = structural problem causing latency-bound behavior.
Expected: Occupancy table showing blocks/SM, active warps/SM, and the limiting factor (registers, smem, or warps).
On failure: Check cuFuncSetAttribute for dynamic shared memory. Verify --resource-usage reports match the actual launch configuration. If register count is unexpectedly high, try --maxrregcount=N to cap registers (trading register spills for occupancy).
Step 4: Compute the Compute/Load Ratio Per Tile
Count compute instructions and load bytes per K-tile from SASS (not source code):
- Disassemble:
cuobjdump -sass kernel.sm_86.cubin > kernel.sass - Count compute instructions per tile (the inner loop over one K-tile):
grep -c 'HMMA' kernel.sass-- FP16 Tensor Core opsgrep -c 'IMMA' kernel.sass-- INT8 Tensor Core opsgrep -c 'FFMA' kernel.sass-- FP32 fused multiply-add
- Count global loads per tile:
grep -c 'LDG' kernel.sass-- global memory loads- Multiply by bytes per load (typically 16 bytes for LDG.128)
- Calculate ratio:
compute_ops / load_opsper tile. - Classify using the cp.async decision threshold (from gpu_reflections.md Insight 2):
- High (>20:1): cp.async is net-negative; warp interleaving already hides DRAM latency. Focus on algorithmic changes. Reference: Flash Attention has 64 HMMA per tile = high ratio, cp.async measured -5%.
- Medium (5-20:1): cp.async may help, benchmark both paths.
- Low (<5:1): cp.async strongly beneficial; loads dominate and async copy hides latency. Reference: IGEMM has 8 IMMA per tile = low ratio, cp.async measured +35%.
Expected: Compute/load ratio with classification (high/medium/low) and cp.async recommendation.
On failure: Count from SASS disassembly, not source code -- the compiler may fuse, eliminate, or reorder instructions. Ensure you are counting instructions within the inner loop only (the K-tile iteration), not the entire kernel.
Step 5: Inspect SASS Instructions
Examine the full SASS instruction mix and stall codes:
- Disassemble (if not done in Step 4):
cuobjdump -sass kernel.sm_86.cubin > kernel.sass - Count key instruction types:
grep -c 'HMMA.16816' kernel.sass # FP16 Tensor Core grep -c 'IMMA.16816' kernel.sass # INT8 Tensor Core grep -c 'FFMA' kernel.sass # FP32 fused multiply-add grep -c 'LDGSTS' kernel.sass # cp.async (global->shared) grep -c 'LDG' kernel.sass # Global load grep -c 'STS' kernel.sass # Shared store grep -c 'LDS' kernel.sass # Shared load grep -c 'BAR.SYNC' kernel.sass # Barrier synchronization grep -c 'SHFL' kernel.sass # Warp shuffle (reductions) grep -c 'MUFU' kernel.sass # Special function unit - Check stall codes on critical instructions:
grep 'HMMA' kernel.sass | head -5 # Expect S08 minimum (hardware constraint) grep 'IMMA' kernel.sass | head -5 # Compiler emits S04, reducible to S02 via CuAssembler grep 'FFMA' kernel.sass | head -5 # Check for S04 (reducible to S01 on independent FFMAs) - Identify optimization targets:
- HMMA S08 stalls: hardware minimum on Ampere, cannot be reduced. Focus elsewhere.
- IMMA S04 stalls: compiler is conservative. CuAssembler can tighten to S02 (measured 15-20% gain).
- FFMA S04 stalls: if independent, reducible to S01 via CuAssembler.
- Excessive BAR.SYNC: may indicate over-synchronization between pipeline stages.
Expected: Instruction count table and stall code summary with identified optimization targets.
On failure: Ensure cuobjdump architecture matches the kernel compilation target (both must be sm_86). If SASS output is empty, the cubin may be corrupt -- recompile.
Step 6: Check the Smem Cliff
Determine whether shared memory usage crosses the architecture-specific occupancy cliff:
- Read smem/block from
--resource-usageoutput (Step 3) orcuobjdump --res-usage kernel.sm_86.cubin. - Compare against cliff threshold:
- GA104 (sm_86): 100 KB max smem/SM. Cliff at 50 KB/block.
- Confirmed empirically: 48 KB/block -> 2 blocks/SM (good), 56 KB/block -> 1 block/SM (2x regression).
- If above cliff (smem > 50 KB/block):
- Blocks/SM drops to 1, active warps drop to warps_per_block (typically 4).
- 2x performance regression expected from exposed DRAM stalls.
- Check double-buffering impact: Double-buffering doubles smem usage. If current smem is 30 KB, double-buffered = 60 KB, which crosses the cliff. Evaluate whether the async benefit outweighs the occupancy loss.
- Record smem/block, blocks/SM, and whether the cliff is crossed.
Expected: Smem/block value with blocks/SM count and explicit statement of whether the 50 KB cliff is crossed.
On failure: If above cliff and occupancy is the bottleneck, the optimization strategy must change: reduce tile size to get smem under 50 KB, or accept 1 block/SM and compensate with higher compute/load ratio per tile (more register reuse, longer K-tiles).
Step 7: Build the Decision Matrix
Synthesize findings from Steps 2-6 into an optimization strategy:
| Condition | Strategy | |---|---| | Memory-bound + low compute/load ratio (<5:1) + smem under cliff | Software pipelining with cp.async (LDGSTS). Overlap global loads with compute. | | Memory-bound + high compute/load ratio (>20:1) + 8+ warps | Warp interleaving already hides latency. Focus on algorithmic changes: implicit GEMM, split-Q, im2col. | | Compute-bound + FFMA-heavy | CuAssembler stall code tightening: S04 -> S01 on independent FFMAs. | | Compute-bound + HMMA-heavy | S08 is hardware minimum, cannot reduce. Increase tile reuse (larger M/N tiles, longer K-loop). | | Compute-bound + IMMA-heavy | CuAssembler: S04 -> S02 on IMMA instructions (compiler is conservative). | | Latency-bound (low occupancy, neither saturated) | Reduce smem or registers to get more blocks/SM. Get above 8 warps/SM. | | Smem above cliff | Reduce tile size or restructure to get smem/block under 50 KB (GA104). |
- Rank applicable strategies by expected gain, using the compute/load ratio and occupancy data.
- Estimate gain range for each strategy based on how far the kernel is from the relevant ceiling.
- Flag conflicts: e.g., cp.async doubles smem (may cross cliff), larger tiles increase register pressure (may reduce occupancy).
Expected: Ranked list of recommended optimizations with predicted gain range and potential conflicts.
On failure: If no clear winner emerges, run micro-benchmarks isolating each strategy (e.g., test cp.async alone, test reduced tile size alone) to measure actual impact before combining.
Step 8: Document Findings
Produce a structured bottleneck report:
- Baseline: kernel time, effective GFLOPS, effective bandwidth, problem dimensions.
- Roofline position: arithmetic intensity, classification, attained fraction of peak.
- Occupancy: blocks/SM, active warps/SM, limiting factor.
- Compute/load ratio: ratio value, classification (high/medium/low), cp.async recommendation.
- SASS summary: instruction counts table, stall code findings, CuAssembler targets.
- Smem cliff: smem/block, blocks/SM, cliff status.
- Recommendation: ranked optimization strategies with gain estimates.
## Bottleneck Analysis Report: [kernel_name]
### Baseline
- Problem: [dimensions]
- Kernel time: [X] ms
- Effective GFLOPS: [Y] | Effective BW: [Z] GB/s
### Roofline Classification
- Arithmetic intensity: [AI] FLOP/byte
- Balance point: [BP] FLOP/byte ([precision])
- Classification: **[compute|memory|latency]-bound**
- Attained fraction: [X]% of peak
### Occupancy
| Resource | Per Block | Limit/SM | Blocks/SM |
|----------|-----------|----------|-----------|
| Registers | [N]/thread | 65536 | [B] |
| Shared mem | [X] KB | 100 KB (cliff: 50 KB) | [B] |
| Warps | [W] | 48 | [B] |
| **Limiting** | | | **[min(B)]** |
- Active warps/SM: [W] ([sufficient|insufficient] for latency hiding)
### Compute/Load Ratio
- Compute ops/tile: [N] [HMMA|IMMA|FFMA]
- Load bytes/tile: [N] bytes ([N] LDG x [N] bytes)
- Ratio: [X]:1 — **[high|medium|low]**
- cp.async recommendation: [beneficial|neutral|detrimental]
### SASS Instruction Mix
| Instruction | Count | Notes |
|-------------|-------|-------|
| HMMA.16816 | [N] | Stall: S08 (hardware min) |
| IMMA.16816 | [N] | Stall: S04 (reducible to S02) |
| FFMA | [N] | Stall: S04 (reducible to S01) |
| LDG | [N] | |
| LDGSTS | [N] | cp.async |
| BAR.SYNC | [N] | |
### Smem Cliff
- Smem/block: [X] KB — [under|over] 50 KB cliff
- Blocks/SM: [B] — [no occupancy loss|occupancy halved]
### Recommended Optimizations (ranked)
1. [Strategy] — estimated [X-Y]% gain
2. [Strategy] — estimated [X-Y]% gain
3. [Strategy] — estimated [X-Y]% gain
Expected: Complete markdown report consumable by a kernel-optimizer agent or human developer.
On failure: Re-run with different problem sizes (e.g., 1024, 2048, 4096, 8192) to confirm findings are not size-specific. Small problems may appear latency-bound when the real bottleneck at scale is memory bandwidth.
Validation
- [ ] Baseline measured with CUDA events (not wall-clock)
- [ ] Roofline classification determined (compute/memory/latency bound)
- [ ] Occupancy computed with limiting factor identified
- [ ] Compute/load ratio per tile calculated from SASS
- [ ] SASS instruction mix and stall codes documented
- [ ] Smem cliff checked against architecture threshold
- [ ] Decision matrix applied with strategy recommendation
- [ ] Findings documented in structured report
Common Pitfalls
- Re-read multiplication: Direct conv2d reads each weight 9x without im2col, inflating the byte count by 9x. Use actual unique bytes loaded from DRAM, not total load instructions, when computing arithmetic intensity.
- Confusing FP16 Tensor Core peak with FP32 peak: FP16 TC peak is 174 TFLOPS, FP32 FFMA peak is 21.7 TFLOPS -- an 8x difference. Using the wrong peak makes roofline classification meaningless.
- Using 64 KB as smem cliff instead of 50 KB on GA104: GA104 (sm_86) has 100 KB max smem/SM. The cliff is at 100/2 = 50 KB/block, not 64 KB. This is architecture-specific; other GPUs differ.
- Ignoring warp interleaving when evaluating cp.async: 8 warps with long compute phases (high compute/load ratio) already hide DRAM latency through warp scheduling. Adding cp.async in this regime adds smem pressure and barrier overhead for no benefit (measured -5% on Flash Attention).
- Counting instructions from source code instead of SASS: The compiler may fuse operations, eliminate dead code, unroll loops differently, or reorder instructions. Always count from
cuobjdump -sassoutput. - Not running warmup iterations: The first kernel launch includes JIT compilation overhead and cold cache effects. Always run 2-5 warmup iterations before the measured run.
Related Skills
pipeline-gpu-kernel-- implement software pipelining with cp.async when analysis identifies a memory-bound kernel with low compute/load ratiosimulate-cpu-architecture-- complementary architecture analysis for CPU-side bottlenecks in host-device workflows
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/analyze-kernel-bottleneck-mrx3xxru/download' --output '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md.part' && mv '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md.part' '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
Tools declared by the author (check availability in your agent): Read Grep Glob Bash
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
After inspection, copy the complete folder into your project using one of the commands below, then open a new tool session. The command stops if a folder with the same name already exists: compare the files before replacing it.
Claude Code
test -f '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && test -s '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/analyze-kernel-bottleneck' && cp -R '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru' '.claude/skills/analyze-kernel-bottleneck'Cursor
test -f '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && test -s '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/analyze-kernel-bottleneck' && cp -R '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru' '.cursor/skills/analyze-kernel-bottleneck'Codex
test -f '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && test -s '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md' && mkdir -p '.agents/skills' && test ! -e '.agents/skills/analyze-kernel-bottleneck' && cp -R '.skill-downloads/analyze-kernel-bottleneck-mrx3xxru' '.agents/skills/analyze-kernel-bottleneck'OpenSkills
OpenSkills must already be installed. The local install/read procedure is checked with OpenSkills 1.5.0, which requires Node.js ≥ 20.6.0. Catalog slugs are not resolved automatically.
Official OpenSkills installationopenskills install './.skill-downloads/analyze-kernel-bottleneck-mrx3xxru'openskills read 'analyze-kernel-bottleneck-mrx3xxru'Reading should display the installed file content and its base directory. It does not run the skill or install its dependencies.
For an agent that reads AGENTS.md, then sync the index. This command changes AGENTS.md: review the diff before using it.
openskills syncManual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/analyze-kernel-bottleneck-mrx3xxru/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
React Hook Form Best Practices
Development
Master React Hook Form: avoid anti-patterns, manage subscriptions, and handle reset, dirty state, and controlled inputs correctly.
Emotion to Mantine Migration
Development
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports.
Scope Creep Detector
Development
Analyzes git diffs against a stated intent to detect scope creep, unrelated files, broad PRs, and oversized changes, then suggests keep, split, or justify actions.