Skip to content

Commit bb62ba0

Browse files
namgyu-younclaude
andcommitted
clean-up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 39d14de commit bb62ba0

46 files changed

Lines changed: 1848 additions & 10 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
name: ci-fails-ort
3+
description: Fetch and diagnose ONNX Runtime CI failures (GitHub Actions + Azure DevOps pipelines) on a PR, with focus on the CUDA/TensorRT workflows. Use when investigating failing CI checks on a PR, when the user pastes a GitHub Actions or dev.azure.com URL, or asks to fetch/diagnose CI logs.
4+
---
5+
6+
# Diagnosing ONNX Runtime CI Failures
7+
8+
ORT PRs run two CI systems, both public (no login needed for logs):
9+
10+
1. **GitHub Actions**`.github/workflows/*.yml` (the CUDA-relevant ones:
11+
`linux_cuda_ci.yml`, `windows_cuda.yml`, `linux_tensorrt_ci.yml`,
12+
`linux_cuda_plugin_ci.yml`, `lint.yml`).
13+
2. **Azure DevOps pipelines** — defined in
14+
`tools/ci_build/github/azure-pipelines/`, shown as "Azure Pipelines" checks
15+
on the PR; results live at `dev.azure.com/onnxruntime/onnxruntime/_build`.
16+
17+
## Step 1 — enumerate failing checks
18+
19+
```bash
20+
gh pr checks <PR> -R microsoft/onnxruntime | grep -v pass
21+
```
22+
23+
Each line carries the check name and a URL — the URL's host tells you which
24+
system to fetch from.
25+
26+
## Step 2a — GitHub Actions logs
27+
28+
```bash
29+
# run id is the number in .../actions/runs/<run-id>/...
30+
gh run view <run-id> -R microsoft/onnxruntime --log-failed > ci-<run-id>.log
31+
32+
# or per-job (job id from the check URL's /job/<job-id> suffix):
33+
gh run view -R microsoft/onnxruntime --job <job-id> --log-failed
34+
```
35+
36+
## Step 2b — Azure DevOps logs
37+
38+
From a check URL like `.../results?buildId=<id>`, the REST API serves logs
39+
anonymously:
40+
41+
```bash
42+
curl -s "https://dev.azure.com/onnxruntime/onnxruntime/_apis/build/builds/<id>/logs?api-version=7.1" \
43+
| python3 -c "import json,sys; [print(l['id'], l['lineCount']) for l in json.load(sys.stdin)['value']]"
44+
curl -s "https://dev.azure.com/onnxruntime/onnxruntime/_apis/build/builds/<id>/logs/<log-id>?api-version=7.1" > ci-azp-<id>.log
45+
```
46+
47+
Maintainers can rerun individual Azure pipelines with a PR comment
48+
(`/azp run <pipeline name>`); external contributors need a maintainer to
49+
trigger it.
50+
51+
## Step 3 — classify the failure
52+
53+
Work from the bottom of the log up; the build.py invocation echoes the exact
54+
failing command. Common ORT failure classes:
55+
56+
| Symptom | Likely cause |
57+
|---|---|
58+
| `lintrunner` diff in `lint.yml` | run `lintrunner -a` locally, commit the fixes |
59+
| `OperatorKernels.md` / `ContribOperators.md` mismatch | regenerate the docs (`tools/python/gen_doc` scripts referenced in the failing step) after kernel/schema changes |
60+
| gtest failure only in the CUDA workflow | arch/driver-dependent kernel dispatch — check which attention/gemm backend the CI GPU selects vs yours (see [write-ort-test](../write-ort-test/SKILL.md) on pinning backends) |
61+
| fp16 tolerance failures on one GPU generation | tolerance too tight for a different kernel path, not necessarily your bug — compare against the baseline run on main |
62+
| timeout in `onnxruntime_test_all` | usually a hang in a newly added test's EP wait, not slowness |
63+
64+
Reproduce locally with the same flags CI used: the workflow yml shows the
65+
`tools/ci_build/build.py` arguments; run the failing gtest via
66+
`onnxruntime_test_all --gtest_filter=<failing test>`.
67+
68+
Flaky-looking failures: check whether the same check fails on recent main
69+
(`gh run list -R microsoft/onnxruntime --workflow <workflow> --branch main -L 10`)
70+
before bisecting your own change.
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
name: debug-ep-placement
3+
description: Debug ONNX Runtime node placement and CUDA kernel dispatch for LLM/VLM models — which nodes fall back to CPU, where Memcpy nodes appear, and which attention/gemm kernel a contrib op actually dispatches to. Use when a CUDA EP run is slower than expected, outputs differ across machines, or you need to confirm which kernel path executed.
4+
---
5+
6+
# Debugging EP Placement and CUDA Kernel Dispatch
7+
8+
Two distinct questions, two toolsets:
9+
10+
1. **Graph level** — which nodes run on `CUDAExecutionProvider` vs fall back
11+
to CPU, and where `MemcpyToHost`/`MemcpyFromHost` transfer nodes got
12+
inserted at the boundary.
13+
2. **Kernel level** — which backend an LLM contrib op (Attention,
14+
MultiHeadAttention, GroupQueryAttention) actually dispatched to: flash
15+
attention, memory-efficient (cutlass), cuDNN flash, TRT fused, lean, or the
16+
unfused fallback.
17+
18+
## Step 1 — Node placement (graph level)
19+
20+
Verbose session logging prints a "Node placements" section listing every node
21+
grouped by EP (`onnxruntime/core/framework/session_state.cc`):
22+
23+
```python
24+
import onnxruntime as ort
25+
so = ort.SessionOptions()
26+
so.log_severity_level = 0 # VERBOSE
27+
so.optimized_model_filepath = "/tmp/optimized.onnx" # inspect in Netron
28+
sess = ort.InferenceSession(model, so,
29+
providers=["CUDAExecutionProvider", "CPUExecutionProvider"])
30+
```
31+
32+
Read the placements for:
33+
34+
- Compute nodes on `CPUExecutionProvider` — each one forces a device sync +
35+
transfer. For LLM decode loops this is usually the whole regression.
36+
- `MemcpyToHost` / `MemcpyFromHost` nodes — inserted automatically at EP
37+
boundaries; their count and position show exactly where data bounces.
38+
39+
A profile gives the same answer quantitatively: every `Node` event carries
40+
`args.provider`, and the [ort-profile-analysis skill](../ort-profile-analysis/SKILL.md)
41+
prints a per-provider table plus Memcpy share.
42+
43+
Common causes of CPU fallback for transformer models:
44+
45+
- No CUDA kernel for the op/dtype combination — check `docs/OperatorKernels.md`
46+
(CUDA column) for the op and opset actually in the graph.
47+
- Shape/attribute constraints a kernel doesn't support (e.g. int64 compute,
48+
dynamic axes a fused op rejects).
49+
- The graph wasn't fused: run the transformer optimizer first
50+
(`python -m onnxruntime.transformers.optimizer --model_type <t> ...` or the
51+
fusion logic in `onnxruntime/python/tools/transformers/`) so Attention/
52+
SkipLayerNorm/MatMulNBits contrib ops exist for CUDA to claim.
53+
54+
## Step 2 — Attention kernel dispatch (kernel level)
55+
56+
Attention backend selection is centralized in
57+
`onnxruntime/contrib_ops/cuda/bert/attention_kernel_options.cc`, driven by the
58+
CUDA provider option `sdpa_kernel` and the `ORT_*` env vars declared in
59+
`onnxruntime/contrib_ops/cpu/bert/attention_common.h`.
60+
61+
To see what actually ran:
62+
63+
```bash
64+
export ORT_ENABLE_ATTENTION_KERNEL_DEBUG_INFO=1 # prints chosen kernel per op
65+
```
66+
67+
To pin or ablate a backend (bisection of a perf/precision difference):
68+
69+
```bash
70+
export ORT_DISABLE_FLASH_ATTENTION=1
71+
export ORT_DISABLE_MEMORY_EFFICIENT_ATTENTION=1
72+
export ORT_ENABLE_CUDNN_FLASH_ATTENTION=1
73+
export ORT_ENABLE_LEAN_ATTENTION=1
74+
# thresholds that silently change dispatch with sequence length:
75+
export ORT_MIN_SEQ_LEN_FLASH_ATTENTION_PACKED_QKV=<n>
76+
export ORT_MIN_SEQ_LEN_EFFICIENT_ATTENTION_FP32=<n>
77+
```
78+
79+
Remember dispatch also depends on SM architecture, dtype, head size, and
80+
past/present KV layout — a result that differs between two GPUs is often just
81+
two different backends. Always record the resolved kernel (debug-info output)
82+
next to any benchmark number.
83+
84+
## Step 3 — Confirm with a profile
85+
86+
Capture a profile (see [generate-profile](../generate-profile/SKILL.md)); with
87+
a `--enable_cuda_profiling` build the `Kernel`-category events name the actual
88+
CUDA kernels (e.g. `flash_fwd_kernel` vs `fmha_cutlassF_*`), which is ground
89+
truth when the debug-info flag isn't available in the installed wheel.
90+
`nsys profile` + `onnxruntime/test/python/transformers/parse_nsys.py` is the
91+
lower-overhead alternative for kernel-level timing.
92+
93+
## Step 4 — Node input/output dumping (debug builds)
94+
95+
For numerical divergence (not placement), build with
96+
`--cmake_extra_defines onnxruntime_DEBUG_NODE_INPUTS_OUTPUTS=1` and use the
97+
`ORT_DEBUG_NODE_IO_*` env vars
98+
(`onnxruntime/core/framework/debug_node_inputs_outputs_utils.h`):
99+
`ORT_DEBUG_NODE_IO_DUMP_NODE_PLACEMENT=1`, `ORT_DEBUG_NODE_IO_DUMP_OUTPUT_DATA=1`,
100+
`ORT_DEBUG_NODE_IO_NAME_FILTER=<substr>` to bisect the first diverging node
101+
between two configurations.
102+
103+
## Quick Reference
104+
105+
| Question | Tool |
106+
|---|---|
107+
| Which nodes fell back to CPU? | `log_severity_level=0` → "Node placements"; profile per-provider table |
108+
| Where do device↔host copies happen? | `MemcpyToHost/FromHost` nodes in optimized model / profile |
109+
| Which attention backend ran? | `ORT_ENABLE_ATTENTION_KERNEL_DEBUG_INFO=1`; Kernel events in profile |
110+
| Force a specific backend | `ORT_DISABLE_*` / `ORT_ENABLE_*` env vars, `sdpa_kernel` provider option |
111+
| First numerically diverging node | debug build + `ORT_DEBUG_NODE_IO_*` |
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---
2+
name: env-var-conventions
3+
description: Conventions for ONNX Runtime configuration knobs — session/run config keys vs provider options vs ORT_* environment variables, parsing helpers, and naming. Use when adding, renaming, or reviewing any ORT_* environment variable, CUDA provider option, or session config entry.
4+
---
5+
6+
# Configuration Knobs — Conventions
7+
8+
ORT has **no central env-var registry** (nothing like a single `envs.py`).
9+
The order of preference for a new knob is fixed; env vars are the last resort.
10+
11+
## Rule 1 — Prefer config surfaces over env vars
12+
13+
| If the knob is… | Use |
14+
|---|---|
15+
| Per-session behavior (optimization, memory, debug dump) | Session config entry: string key in `include/onnxruntime/core/session/onnxruntime_session_options_config_keys.h` (`kOrtSessionOptions*`, keys namespaced like `"session.x"`, `"ep.x"`), read via `session_options.config_options.GetConfigOrDefault(...)` |
16+
| Per-run behavior | Run config entry: `onnxruntime_run_options_config_keys.h` |
17+
| EP-specific (CUDA arena, TF32, `sdpa_kernel`, TRT engine cache) | Provider option: `include/onnxruntime/core/providers/cuda/cuda_provider_options.h` + parsing in `cuda_execution_provider_info.cc`; exposed to Python via `providers=[("CUDAExecutionProvider", {...})]` |
18+
| Process-wide expert toggle, kill-switch, or debug/CI-only hook with no session in scope | `ORT_*` env var (rules below) |
19+
20+
The TensorRT EP's `ORT_TENSORRT_*` env vars are the cautionary precedent:
21+
they predate provider options and survive only as legacy fallbacks — don't
22+
copy that pattern for new work. When both surfaces exist, the provider option
23+
must win over the env var.
24+
25+
## Rule 2 — Parsing
26+
27+
Always parse through `onnxruntime/core/platform/env_var_utils.h`, never raw
28+
`getenv` arithmetic:
29+
30+
```cpp
31+
#include "core/platform/env_var_utils.h"
32+
33+
// optional<T>; empty when unset/unparseable
34+
auto v = ParseEnvironmentVariable<int>("ORT_MY_KNOB");
35+
// or with default:
36+
bool flag = ParseEnvironmentVariableWithDefault<bool>("ORT_MY_KNOB", false);
37+
// test-only knobs log a warning when used:
38+
auto t = ParseTestOnlyEnvironmentVariable<int>("ORT_TEST_ONLY_KNOB", ...);
39+
```
40+
41+
Read the var **once at session/provider/kernel-options initialization**, cache
42+
it in the options struct, and pass it down — never call `ParseEnvironmentVariable`
43+
inside `Compute()`/per-Run paths. `attention_kernel_options.cc` is the model:
44+
env vars are parsed once into `AttentionKernelOptions`, and the CUDA provider
45+
option (`sdpa_kernel`) takes precedence over the env-var defaults.
46+
47+
## Rule 3 — Naming
48+
49+
- Prefix `ORT_`; feature families share a token so they grep together:
50+
the attention set uses `ORT_DISABLE_FLASH_ATTENTION`,
51+
`ORT_ENABLE_LEAN_ATTENTION`, `ORT_MIN_SEQ_LEN_*`
52+
(all declared as `constexpr const char*` in
53+
`onnxruntime/contrib_ops/cpu/bert/attention_common.h` — declare names as
54+
constants next to their consumers like this, never inline string literals).
55+
- `ORT_DISABLE_X` for opting out of a default-on path, `ORT_ENABLE_X` for
56+
opting into a default-off one — don't create double negatives.
57+
- Debug-build instrumentation follows the `ORT_DEBUG_NODE_IO_*` family style
58+
(`onnxruntime/core/framework/debug_node_inputs_outputs_utils.h`).
59+
- EP-specific vars carry the EP in the name (`ORT_TENSORRT_*` — legacy only,
60+
see Rule 1).
61+
62+
## Rule 4 — Kernel-dispatch knobs affect benchmarks
63+
64+
Any env var that changes CUDA kernel selection (attention backend, min-seq-len
65+
thresholds, fused-op toggles) invalidates perf comparisons that don't hold it
66+
fixed. When adding one:
67+
68+
- default must preserve current dispatch on all architectures;
69+
- document it in the same header as the constant;
70+
- mention it in the PR description so benchmark scripts can pin it — and
71+
record it in any runbook this workdir produces (see
72+
[debug-ep-placement](../debug-ep-placement/SKILL.md)).
73+
74+
## Renames and removals
75+
76+
There is no alias machinery. For a rename, read the old name as a fallback at
77+
the single parse site, log a deprecation warning there, and note the removal
78+
target. Never flip a default in the same change as a rename.
79+
80+
## Review checklist
81+
82+
- [ ] Could this be a session config key or provider option instead? (usually yes)
83+
- [ ] Name declared as a constant next to the consumer, `ORT_` prefixed
84+
- [ ] Parsed once at init via `env_var_utils.h`, not per-Run
85+
- [ ] Provider option / config entry takes precedence if both exist
86+
- [ ] Dispatch-affecting knob documented for benchmark pinning
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
name: generate-profile
3+
description: Capture an ONNX Runtime profile of a CUDA EP run for an LLM/VLM model — session profiling (chrome-trace JSON), onnxruntime_perf_test -p, CUPTI kernel events via --enable_cuda_profiling, and Nsight Systems. Returns the profile path for analysis.
4+
---
5+
6+
# Generate a Profile of an ORT CUDA Run
7+
8+
ORT's built-in profiler writes a chrome-trace JSON per session. Reference
9+
implementation: `onnxruntime/core/common/profiler.cc`. Profiling adds
10+
overhead — keep the profiled run short, and never compare a profiled number
11+
against an unprofiled one.
12+
13+
## Option A — Python session profiling (LLM/VLM scripts)
14+
15+
```python
16+
import onnxruntime as ort
17+
18+
so = ort.SessionOptions()
19+
so.enable_profiling = True
20+
# so.profile_file_prefix = "gqa_fused" # optional
21+
22+
sess = ort.InferenceSession(model_path, so,
23+
providers=[("CUDAExecutionProvider", {"device_id": 0}), "CPUExecutionProvider"])
24+
25+
for _ in range(3 + 10): # first iterations include first-run allocations and
26+
sess.run(None, feeds) # kernel autotuning — aggregate over the later ones
27+
28+
path = sess.end_profiling() # onnxruntime_profile_<prefix>_<ts>.json
29+
print(path)
30+
```
31+
32+
For decode-loop workloads, profile prefill and decode as separate sessions/
33+
runs where possible so per-op aggregates aren't a blend of the two regimes.
34+
Record next to the trace: GPU model, wheel version, and any `ORT_*` attention
35+
env vars in effect (they change kernel dispatch — see
36+
[debug-ep-placement](../debug-ep-placement/SKILL.md)).
37+
38+
## Option B — onnxruntime_perf_test
39+
40+
For a model file with generated inputs, no script needed:
41+
42+
```bash
43+
./build/Linux/RelWithDebInfo/onnxruntime_perf_test \
44+
-e cuda -m times -r 100 -p /tmp/myprofile model.onnx
45+
# -p <prefix> enables profiling and dumps the profile file
46+
# -I generates dummy inputs when you have no input data
47+
```
48+
49+
## Kernel-level events — CUPTI build
50+
51+
A stock build's `Node` events time the host-side kernel launch. For actual
52+
GPU kernel names/durations (`Kernel`-category events in the same JSON), the
53+
wheel must be built with:
54+
55+
```bash
56+
./build.sh --config RelWithDebInfo --use_cuda --enable_cuda_profiling ...
57+
```
58+
59+
## Nsight Systems alternative (lower overhead)
60+
61+
```bash
62+
nsys profile -o gqa_run python benchmark_script.py
63+
python onnxruntime/test/python/transformers/parse_nsys.py gqa_run.nsys-rep
64+
```
65+
66+
The in-tree LLM kernel benchmarks (`onnxruntime/test/python/transformers/`:
67+
`benchmark_mha.py`, `benchmark_gqa.py`, `profile_gqa.py`,
68+
`profile_matmul_nbits.py`) already wire this up — extend those before writing
69+
a new harness.
70+
71+
## Viewing and analyzing
72+
73+
- **Perfetto UI**: <https://ui.perfetto.dev/> (drag-and-drop) or `chrome://tracing`.
74+
- For op/provider/kernel aggregate tables, hand the JSON to the
75+
[ort-profile-analysis skill](../ort-profile-analysis/SKILL.md).
76+
77+
Trace anatomy: events carry `cat` ∈ {`Session`, `Node`, `Kernel`, `Api`};
78+
per-node timing is the `<node_name>_kernel_time` events, whose `args` include
79+
`op_name` and `provider`.

0 commit comments

Comments
 (0)