profile-model — Claude Code skill: what it does, how to install
Profiles MAX models to check GPU utilization and identify the GPU kernels taking the most time. Use it when inference is slow or a trace needs inspection.
npx skills add modular/skills --skill profile-model -a claude-codeWhat profile-model does
This skill profiles any model that MAX can run, including built-in architectures and models loaded with --custom-architectures. It starts with GPU utilization, memory, and clock checks through max.profiler.gpu, then moves to kernel analysis with max ... --profile, nsys, or rocprofv3.
When one kernel dominates runtime, it can take a deeper look with ncu on NVIDIA hardware. Low gpu_usage_percent during a workload points to CPU work, transfers, synchronization, or a small batch instead. A kernel trace is not the main answer in that case.
You need MAX installed through pip or pixi, plus an NVIDIA or AMD GPU. Kernel breakdown and deep analysis need vendor tools: nsys or ncu for NVIDIA, and rocprofv3 for AMD. Custom architectures can fail before profiling because of MAX-version compatibility, PYTHONPATH, or architecture-name collisions, so checking model loading comes first.
What it helps with
- check whether a GPU is busy during inference
- find kernels responsible for most GPU time
- inspect why a single kernel runs slowly
- separate GPU limits from host-side delays
- profile a model loaded with
--custom-architectures
Example prompts for the agent
- Profile my MAX model at
<model-path>and check GPU utilization. - Show which GPU kernels take time for
max serve <model-path>. - Capture a trace for
max generate <model-path>and check for a dominant kernel.
How to install profile-model
1Skills CLI (Vercel)
npx skills add modular/skills --skill profile-model -a claude-code2Manually: put the files in place
~/.claude/skills/profile-model/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/profile-model/
1Skills CLI (Vercel)
npx skills add modular/skills --skill profile-model -a codex2Manually: put the files in place
.agents/skills/profile-model/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add modular/skills --skill profile-model -a cursor2Manually: put the files in place
.cursor/skills/profile-model/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install modular/skills profile-model2Manually: put the files in place
.github/skills/profile-model/SKILL.md1Skills CLI (Vercel)
npx skills add modular/skills --skill profile-model -a gemini-cli2Platform CLI
gemini skills install https://github.com/modular/skills --path profile-modelManually: put the files in place
.devin/skills/profile-model/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add modular/skills --skill profile-model -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about profile-model
How do I install profile-model in Claude Code?
Run npx skills add modular/skills --skill profile-model -a claude-code, then run npx skills add modular/skills --skill profile-model -a claude-code again. The skill file is at ~/.claude/skills/profile-model/SKILL.md.
What does profile-model do?
It measures GPU utilization and shows where a MAX model spends GPU time. The skill helps choose between a utilization check, a kernel breakdown, and a single-kernel investigation.
What is required for GPU kernel analysis?
You need MAX from pip or pixi and an NVIDIA or AMD GPU. Kernel analysis requires nsys or ncu on NVIDIA, or rocprofv3 on AMD.
Source text
SKILL.md · Apache-2.0
--- name: profile-model description: > Profile a model running on MAX to find where it spends time and whether the GPU is saturated. Use when the user asks to "profile my model," "where is my model spending time," "why is inference slow," "is my GPU being utilized," "how much GPU am I using," "get a kernel breakdown," "capture an nsys/rocprof/ncu trace of max serve," or wants to measure MAX inference performance. Works for any model MAX can run — built-in architectures and custom ones loaded with --custom-architectures — from a pip or pixi install (max generate, max serve, or a Python script) on NVIDIA or AMD GPUs. Decide cheapest-first: a GPU utilization check, then a kernel breakdown, then a single-kernel deep dive only when one kernel dominates. compatibility: Requires a pip/pixi MAX install and a GPU (NVIDIA or AMD). The kernel-breakdown and deep-dive steps also need the vendor profiler (nsys/ncu for NVIDIA, rocprofv3 for AMD). argument-hint: "[model-path] [serve/generate flags]" --- # Profile a model on MAX This skill answers three questions about any model you can run on MAX, whether it's a built-in architecture or a custom one loaded with `--custom-architectures`: 1. **Is my GPU actually being used?** (utilization, memory, clocks) 2. **Where does my model spend the most time?** (which kernels / phases) 3. **Why is *this* kernel slow?** (occupancy, stalls, roofline) It works from a `pip` or `pixi` install of MAX, driving the public `max` CLI or a Python script. ## How to decide what to run Each question needs a deeper, more expensive capture than the last. Work from the cheapest check toward the most invasive, and let each result decide whether going deeper is even worth it — don't capture more than the question needs. 1. **Start with the utilization check**. If the GPU is idle or lightly loaded while the workload runs, the bottleneck is host-side (CPU, transfers, small batch) — stop and report that. A kernel trace won't tell you anything a busy GPU wouldn't. 2. **If the GPU is busy, capture a kernel breakdown**. This is the common case and usually the final answer: it shows which kernels dominate GPU time. 3. **Only if one kernel dominates and you need to know *why*, do a single-kernel deep dive** on that one kernel. Skip this unless a breakdown has already pointed at a specific kernel — it replays the kernel many times and is slow. | What you're answering | Tool | Cost | |--------------------------|-------------------------------------------------------------|-----------------------------| | Is the GPU busy? | `max.profiler.gpu` (pure Python, NVIDIA + AMD) | seconds, no extra installs | | Where does time go? | `max ... --profile`, or `nsys` (NVIDIA) / `rocprofv3` (AMD) | a couple of profiled runs | | Why is one kernel slow? | Nsight Compute (`ncu`), NVIDIA only | one slow capture per kernel | Read the reference for the step you're on rather than loading all of them: - **Utilization check** → [`references/utilization-api.md`](references/utilization-api.md) (and the bundled `scripts/gpu_snapshot.py`) - **Kernel breakdown, NVIDIA** → [`references/nsys.md`](references/nsys.md) - **Kernel breakdown, AMD** → [`references/rocprofv3.md`](references/rocprofv3.md) - **Single-kernel deep dive (NVIDIA)** → [`references/ncu.md`](references/ncu.md) ### Models loaded with `--custom-architectures` Profiling itself works the same for a custom architecture as for a built-in one. What differs is loading: a custom architecture has extra prerequisites and failure modes — import compatibility with your installed MAX version, `PYTHONPATH` traps, architecture-name collisions, base models with no chat template, and a `max serve` limitation for custom embeddings models. Those stop the model from loading *before* any profiling can run. If you're profiling a model passed via `--custom-architectures`, read [`references/custom-architectures.md`](references/custom-architectures.md) **first**. ## Reading the result (what the kernel mix tells you) Profiling answers two things: *is the GPU saturated* (the utilization check) and *where does time go* (the kernel breakdown). Map what you see back to a diagnosis: - **Decode dominated by `gemv_split_k_*` GEMVs, with `*_mha_*` attention and `rms_norm` below** — this is the normal, healthy shape for a *dense* LLM token generation (decode is memory-bound GEMV). Nothing to chase. - **Sparse-MoE decode dominated by grouped / block-scaled matmul plus routing kernels** — the per-expert grouped matmul (`*grouped*` / `block_scaled_*` matmul) is the dominant kernel instead of a dense GEMM, alongside top-k routing (`topk_*`, `moe_create_indices`) and expert gather/scatter; on multi-GPU, EP `dispatch`/`combine` collectives appear too. This is the healthy shape for a Mixture-of-Experts model — routing and gather overhead is expected, and only worth chasing if it rivals the matmul itself. - **Low-bit weights (FP8 / FP4 / NVFP4) add quant-prep kernels** — alongside the matmul you'll see dynamic activation-quantization and scale-layout kernels (`*quantize*`, `*block_scales*`) and block-scaled matmul variants rather than plain `gemv`/`gemm`. Time spent in quant/dequant prep is normal for a low-bit model; flag it only if it dwarfs the matmul it feeds. Kernel *prefixes* are GPU-arch-specific (`sm100_*` on Blackwell, different on Hopper / MI) — match on the kernel *family*, not the exact name. - **A custom architecture *not* hitting the expected fused kernel** (for example attention showing as generic `elementwise`/`matmul` instead of a `*_mha_*` kernel, or norms/RoPE unfused) — a wiring signal: the graph may not be built the way you think, even if logits pass. Worth a look during bring-up. - **Pathologically low decode tok/s + an outsized prefill-shaped kernel mix repeating every step** — classic O(n²) re-prefill (no/broken KV cache). This is a correctness-adjacent bug, not just slowness. - **Low `gpu_usage_percent` while a workload runs** — host-bound, launch/sync overhead, or batch too small; the GPU isn't the bottleneck. - **Throttle reasons present** — the GPU is clock-limited; numbers aren't representative until you address thermal/power. ## The durable algorithm Tooling and MAX packaging change often; this shape is the part worth preserving. Whichever capture you run, follow it: 1. **Detect the environment before committing to a tool**. Run `nvidia-smi` or `rocm-smi` to learn the vendor — the kernel-breakdown and deep-dive tools differ by vendor. Confirm the profiler is installed (`which nsys` / `which rocprofv3` / `which ncu`); if it's missing, tell the user the exact install line (see the reference) rather than failing midway. 2. **Confirm before any long-lived run**. Profiling `max serve` warms a compile cache (cold compile can take minutes), launches a server, and runs a benchmark. Before doing that, show the user the model, the flags, and the planned commands, and wait for confirmation. A one-shot `max generate --profile` on a tiny model is cheap enough to skip this. 3. **Warm caches first, profile second**. The first run of a model pays one-time compile and weight-load costs that drown out the real kernel time. Do an unprofiled warm-up run, *then* the profiled run, so the capture reflects steady-state inference — not compilation. 4. **Keep the capture window small**. A 10-second window at concurrency 1 is enough to see the kernel mix. Long captures produce huge trace files that are slow to open and no more informative. 5. **Fail fast**. After launching a server, check within a few seconds that the process is alive and the log has no early error (model not found, OOM, bad flag) before you start polling for `/health`. Don't wait out a full timeout on a server that already died. 6. **Verify the artifact, then report**. Confirm the output file exists and is non-trivial in size before claiming success. Report the artifact path, how to open it, and the headline numbers (top kernels, or peak GPU utilization). 7. **Clean up only what you started**. Kill the server / benchmark process group you launched. Avoid broad `pkill -f max` on a shared box — you may stop someone else's run. ## Nightly vs stable Profiling features land in nightly before stable, so this skill targets the nightly `max-serve` build. Install it with pixi: ```bash pixi init quickstart -c https://conda.modular.com/max-nightly/ -c conda-forge cd quickstart && pixi add max-serve pixi run max --version # expect a *.dev build ``` The utilization API (`GPUDiagContext`, `BackgroundRecorder`) ships in the **conda** `max` package, so install via pixi or conda rather than a plain `pip install max` wheel, which may not expose `max.profiler.gpu`. If a command below 404s or an import fails, confirm you're on a recent nightly build. ## Install notes - MAX itself: a project with the nightly `max-serve` package installed via pixi (see [Nightly vs stable](#nightly-vs-stable) above). All `max` CLI commands below assume you can run `pixi run max ...` in that project. The `max-serve` package includes extra dependencies required to serve a model with MAX, which aren't included with the main `max` package. - `nsys` and `ncu` ship with the CUDA Toolkit. If `which nsys` fails but CUDA is present, `export PATH=/usr/local/cuda/bin:$PATH`. Otherwise install Nsight Systems / Nsight Compute from NVIDIA, or `sudo apt-get install nsight-systems`. - `rocprofv3` ships with ROCm (`/opt/rocm/bin`). Add it to PATH if needed. - The utilization API needs no extra tooling beyond `max` — it talks to NVML / ROCm SMI directly through `max.profiler.gpu`. ## Fast paths The lowest-friction commands, each expanded in its reference file: - **"Is my GPU being used?"** → run `scripts/gpu_snapshot.py` alongside a short benchmark. Pure Python, works on NVIDIA and AMD. - **"Give me a kernel breakdown, fast."** → `pixi run max generate --model <m> --prompt "hello" --num-warmups 1 --profile`. Prints a ranked top-N GPU-kernel table and writes a `.nsys-rep` (NVIDIA). Falls back to a CPU summary without a GPU. - **"Profile my serving benchmark."** → the serving-endpoint flow with `MODULAR_ENABLE_PROFILING=detailed` + `nsys launch` / `rocprofv3`.
© modular · Apache-2.0 · modular/skills