Inference graph diff
Skill cfregly/gpu-perf-tune/plugins/profile-and-optimize/skills/inference-graph-diff
31 GPU inference profiling and optimization skills for Claude Code, with a bundled MCP server
npx -y skills add cfregly/gpu-perf-tune --skill inference-graph-diffAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Diff the compiled FX / Inductor graphs across two vLLM versions or two helm configs to see exactly which fused kernels / passes / partitions changed. Uses `torch._dynamo.explain` + `torch.compile`'s graph-dump hooks. Useful when you anticipate a model-graph-level change (e.g. swapping cudagraph_mode, enabling fuse_allreduce_rms, toggling pass_config.fuse_attn_quant). Triggers on "graph diff", "dynamo explain", "fx graph", "compile graph", "torch.compile diff", "compilation pass change", or any combination of "graph / fx / inductor / dynamo" with "diff / compare / explain / dump".
SKILL.md
9.7 KB, as published. Nobody here has run it
inference-graph-diff
Purpose
Diff the compiled torch FX / Inductor graphs between two vLLM
configs to surface exactly which compilation choices changed. The most
common need: "I added pass_config.fuse_allreduce_rms=true,
which graph nodes were affected?" - torch.compile decisions are not
visible in the vllm startup logs alone.
Three diff layers cover the stack:
inference-kernel-profile(nsys.nsys-reptimelines - runtime)analyze-zymtrace-workload(per-category kernel time)- This skill (the COMPILATION graph - build-time)
When to use
- Confirming a
compilation_configchange actually took effect (the vllm logs print the requested config, not the post-fusion graph) - Investigating an unexpected perf delta after a 1-line config tweak ("did the fused op activate?")
- Pre-deploy gate when migrating between vllm versions (catch silent inductor pass behavior changes)
- Documenting WHY a variant landed for the eventual upstream PR or release notes
Do not use this skill for:
- Runtime timeline analysis - use nsys or zymtrace instead
- Per-request latency breakdown - those are different layers
- SGLang arms - this skill is vLLM-specific (it diffs vLLM's
torch.compileFX / Inductor graph dumps viaTORCH_LOGS=+dynamo,+inductor). SGLang has a different compilation/fusion model (no vLLMcompilation_config/cudagraph_mode), so this skill fails closed on an SGLang arm. To compare two SGLang configs' execution structure, capture a per-kernel nsys timeline for each (launch-wrapsglang.launch_serverunder nsys) and diff thecuda_gpu_kern_sumkernel sets instead.
Recipe
1. Dump the graph from each side of the diff
For each helm config A and B:
NS=inference
TARGET_POD=$(kubectl -n $NS get pods -l app=basic-inference -o jsonpath='{.items[0].metadata.name}')
# Need vllm to log the compile graph. Set the env var BEFORE engine init:
kubectl -n $NS set env deployment/basic-inference \
TORCH_LOGS="+dynamo,+inductor,+graph_breaks" \
TORCHDYNAMO_VERBOSE=1 \
PT_LOGGING_LEVEL=DEBUG
# Wait for rolling restart...
# Then grab the logs which contain the FX dump:
kubectl -n $NS logs $TARGET_POD --tail=10000 > side-A.log
Repeat with the alternate config to get side-B.log.
2. Extract the FX graphs
import re, pathlib
def extract_fx_graphs(log_path):
text = pathlib.Path(log_path).read_text(errors="replace")
graphs = re.findall(r"=== FX GRAPH ===\n(.*?)\n=== END FX GRAPH ===", text, re.DOTALL)
return graphs
a_graphs = extract_fx_graphs("side-A.log")
b_graphs = extract_fx_graphs("side-B.log")
(The exact log marker strings depend on vLLM version + torch version,
the recipe above is the v0.21.x default. For other versions, dump via
TORCH_COMPILE_DEBUG=1 and parse the compile_debug_*.txt files
torch.compile writes under ~/.cache/torch/.)
3. Diff the graphs
diff -u side-A-graph0.fx side-B-graph0.fx > graph0.diff
# For the inductor lowered graph:
diff -u side-A-inductor.py side-B-inductor.py > inductor.diff
4. Optional: render summary with torch._dynamo.explain
kubectl -n $NS exec $TARGET_POD -- python3 -c "
import torch._dynamo as dynamo
# torch._dynamo.explain takes a callable + sample inputs and prints
# the compile decisions (recompiles, graph breaks, ops list)
# For vllm the callable is the model.forward; sample inputs are mocked.
# See the dynamo.explain doc for the full surface.
"
5. Emit a structured graph_diff.json into the bundle
{
"schema": "inference_graph_diff_v1",
"side_a": {"helm_rev": 28, "config_summary": "...", "compile_passes": [...]},
"side_b": {"helm_rev": 29, "config_summary": "...", "compile_passes": [...]},
"added_passes": ["fuse_allreduce_rms"],
"removed_passes": [],
"graph_size_delta": {"nodes_before": 1234, "nodes_after": 1187, "delta_pct": -3.8},
"notes": "1-line helm bump landed; -47 nodes from the allreduce-rms fusion."
}
Output
Bundle artifact layout:
experiments/artifacts/inference-perf-bench/<bundle>/
graph-diff/
side-A.log # raw torch.compile log from config A
side-B.log # raw torch.compile log from config B
side-A-graph0.fx # extracted FX graph
side-B-graph0.fx
graph0.diff # unified diff
inductor.diff # diff of the lowered inductor code
graph_diff.json # structured summary
Skill maturity
This skill is research-grade (v0.1 - same status as the
nsys-sidecar approach). The recipe is documented but not yet wrapped in
a dedicated MCP verb because the dump-format depends on torch version.
A future v1.x of profile_and_optimize could add a perf_tune_report_graph_diff verb
that automates the recipe end-to-end.
Kernel rubric (K/R/H/P/A)
A compilation-graph change is often a representation (R) or hardware
specialization (H) move in the kernel rubric (docs/METHODOLOGY.md
"Kernel-work classification"). A fusion pass that replaces a generic Triton
op with a library/sm100f tensor-core kernel raises both R (R2→R1) and H (toward H4),
fuse_allreduce_rms / fuse_attn_quant shift which hardware path the graph dispatches.
When a graph diff backs a custom-kernel comparison, note the R/H delta in
graph_diff.json notes and carry the candidate + baseline (K,R,H,P,A) coordinates
into the bundle's SOURCE.md/summary.md. The graph diff shows R/H changed. It does
NOT prove the new path engages tensor cores or hits its roofline - defer that H + P
proof to inference-kernel-ncu-profile, the
gate's enforcement point. A win over a strictly-lower-H/R baseline stays a DRAFT, never
a VERDICT. If the comparison campaign reaches L4 (an ncu roofline renders), the
candidate + baseline (K,R,H,P,A) must also be emitted as a structured krhpa: block
in config.yaml (see inference-kernel-ncu-profile
for the YAML) - publish_to_lake fails closed without it.
Cross-references
perf-baseline-difffor run-over-run baseline diffsanalyze-zymtrace-workloadfor per-category zymtrace diffsinference-kernel-profilefor sidecar nsys captures
Full-context reporting (no bare numbers)
Per the canon "Every performance number carries its full context (no bare numbers)"
(docs/METHODOLOGY.md "Full-context reporting"): every number this
skill emits MUST carry its full measurement-context descriptor, and every comparison MUST be
matched on it. A bare tok/s / TPOT / BW / %SoL / speedup is a defect - it cannot set a
default, ship a config, or appear in a report.
- Identity: model (+HF path), hardware (exact ceiling token
GB300/B200), quant, kv-cache dtype. - Parallelism: TP, DP (replicas), PP, EP, parallel_strategy.
- Serving cfg: max-num-seqs, max-num-batched-tokens, gpu-memory-utilization, max-model-len, cudagraph_mode/enforce_eager, async_scheduling, prefix-caching.
- Workload: dataset, ISL/OSL (or mean in/out tokens), concurrency, num-prompts.
- Regime: warm vs cold. Latency vs throughput tier.
- Stack: image/vllm commit, bench backend, serving engine.
- Grounding:
%SoL(+ ceiling key fromconfigs/sol-ceilings.yaml- never inline a peak), sol_rigor (L1-L4), trials n (mean±std), same-node, baseline named. - Per-number exact shape (no smoothing): when reporting more than one number, keep EACH with its own exact shape (ISL/OSL, concurrency, dataset, regime) - never normalize a set to one uniform descriptor that hides per-point variation (e.g.
c=1 @ ISL1024/OSL256+c=64 @ ISL4096/OSL512, NOT one shared "random").
A graph diff is structural, not a measurement, so it is exempt from the %SoL
requirement. If you pair it with a perf measurement (tok/s, latency, speedup),
that measurement follows the rigor discipline: capture L1+L3 (L4 where
feasible) Speed-of-Light + publish --strict. Canonical map:
docs/METHODOLOGY.md.
Next lever / BREAKTHROUGH (Grind Mandate)
If this skill emits a measured result, its output MUST end by naming the next perf lever,
its expected unlock (direction + rough magnitude), and the gate that proves/refutes it,
per docs/METHODOLOGY.md "Always be grinding (next-lever framing)". A
measured win is the new floor, not the finish -- so do everything we can to find the next
BREAKTHROUGH: the highest-EV unlock toward Speed-of-Light (a new champion / kernel / router /
quant / parallelism / spec-decode win, or an unblocked stack), not just the next micro-lever.
Rank the candidate breakthrough levers by value x cost (the GRIND FRONTIER, perftunereport value_view), pursue the top, bank the rest with evidence. Record WHY a refuted lever loses,
update the standing frontier in the active bundle's HANDOFF.md. Never conclude
"exhausted/optimal/done" without an explicit next-lever frontier (an empty frontier AND a
documented SoL wall only). Delete this section ONLY if the skill produces no measurements.