Repository navigation
feat(rocke): improve conv sweep benchmarks - #14379
Conversation
✅ All Checks Passed — Ready for Review
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🎉 All checks passed! This PR is ready for review. |
9f0b99a to
f3f8279
Compare
…arkocot/benchmark-impr
The conv sweeps timed N launches back to back between one event pair and
averaged. hipDNN's dnn-benchmark (and MIOpen, where it records events around
a launch) times each call on its own, so it pays the fixed device cost of an
event pair per call that a back-to-back loop amortises away. On small kernels
that made the sweep's numbers look faster than the same kernels measure
through hipDNN, which skewed every rocKE-vs-MIOpen comparison built on them.
- rocke.runtime.StallGate / time_launches_staged: the ctypes twin of
hipdnn_data_sdk's StallGate (hipStreamWaitValue32 on signal memory,
released by the host, with a watchdog) and of dnn-benchmark's staged loop:
one stalled start/stop event pair per call, host submission outside it.
- --timing {staged,block} on both conv sweeps and benchmark_conv_compare.py,
default staged: every reported time is the median of stalled single-call
samples. Early stopping and the --fast-rank probe still rank with cheap
block launches; only the reported number changes protocol.
- Per-case rows record their timing (rows without the field are block);
--skip-measured / --resume skip only cases measured the same way, and a
merge takes one protocol's rows instead of letting the cheaper protocol
win the faster-row rule.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pre-commit check failed⛔ pre-commit failed Please run locally:
This repo uses |
CongMa13
left a comment
There was a problem hiding this comment.
I found two actionable issues in the AOT sweep support. The inline comments describe the triggers and impact.
- case_results.py: ResultsWriter no longer truncates --results in place when it names the same file as --skip-measured; measured_keys is read first and the writer appends instead of overwriting. - module_cache.py: an over-budget launcher is now released on the next miss (or at clear()) instead of leaking a HIP module per miss for the rest of the run. - benchmark_conv_compare.py: _run_script returns the child's exit status; main() now fails the shard when a sweep dies instead of always exiting 0. - CMakeLists.txt: wire the new CPU-only library tests into the conv pytest list. - batched_timing.py: time_batch_staged checks hipStreamWaitValue32 support once up front and falls back to block timing with a warning, instead of silently reporting every finalist as unranked. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…arkocot/benchmark-impr # Conflicts: # dnn-providers/hip-kernel-provider/rocke/library/benchmarks/common/benchmark_direct_conv.py
plan_for() now calls cache.entry_meta() (a meta dict) instead of cache.get() (a (bytes, meta) tuple), and _streamk_launch() now returns an extras dict plus rt.memset_async() instead of a free() closure and rt.memset(). The test fixtures in test_grouped_conv_dispatch.py and test_streamk_sweep_launch.py still matched the old contracts, which is what failed rocke_library_conv_pytest on CI.
The merge commit only ran pre-commit on conflicted files, so these two black violations from develop slipped through. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #14379 — improve conv sweep benchmarks (bartekxk, AICK-2563) — 🟠 First-round: closer look — the module-cache leak fix unloads launches the fast-rank probe still holds
Utility: 🟢 Positive — hipDNN-comparable staged timing and resumable sharded sweeps; module-unload fix needs rework. Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good · ⚪ retracted First-round review: a local qwen3.6 model made the independent first pass, and the orchestrator (Claude Opus 5.5) verified it. On a clean merge with develop, the orchestrator ran the native build and ctest, byte identity, library parity, the representative and differential goldens, the PR's test files and a ROCm clang 24 compile. It read both gfx94X hipkernelprovider logs and the failed summary job at head. It traced the module cache through the fast-rank and direct-conv bind paths and probed it on CPU under the real ranking, with HIP stubbed and the pre-fix cache as a control. It also re-ran the shard-runner tests the way the worker ran them. The orchestrator owns this merge-worthiness call. This review flags whether a closer look is needed; it is not an approval or a rejection. |
|
Automated merge-queue status (rocKE merge monitor) Hi @bartekxk, I turned auto-merge off on this PR for now. Before a rocKE PR lands, the rocKE merge monitor checks it on top of today's develop branch, and this version would not land cleanly:
What to do: merge the current develop branch into this PR and fix the failing build or tests. After that, turn auto-merge back on; the monitor re-checks every push. Nothing else changed: the monitor does not approve, reject or merge PRs. It only turned auto-merge off to keep develop green. This comment is updated in place. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical defects allow incorrect kernels to win, break JIT sweeps, unload modules still in use, and discard aggregate shard results.
11 open issues
Preserve staged timing protocol through ranking and result writing · New Exclude failed finalists from winner selection · New Use asynchronous clears for JIT staged timing · New Update JIT stream-K caller for dictionary return value · New Exclude explicitly failed rows from merged winners · New Convert remaining JIT clears to asynchronous memset · New Keep modules alive until pending launches are drained · New Support comparison format in result merging · New Persist comparison shard rows before final merge · New Remove absolute latency estimate from source · New Remove absolute cache latency from shard-count documentation · New
Review effort: Max
- module_cache: keep over-budget launchers alive until end_case() confirms the case's launches have drained, instead of unloading on the next miss (--fast-rank and multi-step direct plans could race an in-flight launch) - benchmark_direct_conv / case_results: exclude verification-failed rows from winner ranking and merge, so a fast-but-wrong kernel cannot become Best - batched_timing / early_stop: raise on unsupported staged-timing gate instead of silently falling back to block timing under a "staged" label - benchmark_implicit_gemm_conv: convert the remaining JIT zeroing closures to memset_async (matching the AOT path) and fix the stream-K caller that still unpacked _streamk_launch's old 3-tuple return - run_conv_shards: handle --script compare's already-merged shard rows in the final merge and in --resume's --skip-measured handling - benchmark_conv_compare: persist per-engine shard rows beside --results instead of an ephemeral tempdir, so a killed run leaves them discoverable Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Automated review: Model - qwen3.6:35b-a3b-coding-mtp-q4_K_M, effort: max PR #14379 — improve conv sweep benchmarks (bartekxk, AICK-2563) — 🟡 Follow-up: no closer look needed — our module-cache 🟠 is fixed, and so are 10 of Copilot's 11 threads (compare-mode resume only partly); the fix commit breaks one of the PR's own CPU tests, which CI never runs, CongMa13's approval predates it, and CI at head is still building
Utility: 🟢 Positive — hipDNN-comparable staged timing, sharded resumable sweeps, module reuse; unload race fixed. Legend: 🔴 blocking · 🟠 should-fix · 🟡 minor nit · 🟢 info/good · ⚪ retracted Follow-up review: a local qwen3.6 model made the independent first pass, and the orchestrator (Claude Opus 5.5) verified it. It reviewed the one new commit since our review, |
cgmillette
left a comment
There was a problem hiding this comment.
Automated merge-queue hold (rocKE merge monitor)
Hi @bartekxk, this PR is held in the rocKE merge queue: one of its own new tests fails on a merge with today's develop (587f9942).
What fails: library/tests/test_early_stop.py::test_staged_timing_reports_the_median_of_stalled_samples (1 failed; develop has no such failure). EarlyStop._timed now builds the stall gate before the first staged sample (benchmarks/common/early_stop.py:273), and the test does not stub it. On a host without a ROCm device it raises HipError: hipGetDevice: hipError(100) no ROCm-capable device is detected; this device cannot be timed with --timing staged, rerun with --timing block. Our 10-29 follow-up review noted this as N1.
Fix: stub the gate in the test, next to the time_launches_staged patch (test_early_stop.py:221), for example:
monkeypatch.setattr("rocke.runtime.stall_gate.default_gate", lambda: None)With that stub, the test passes on the same merge.
Once it's fixed, push and re-request a review from @cgmillette. A held PR needs that explicit re-approval before it can merge.
Brings in #14393 (gfx1250 split-K forward conv) and #14379 (conv sweep benchmarks). Only the representative IR golden conflicted, on its summary counters: develop's split-K cases and this branch's five depthwise dgrad cases per flavor do not overlap. Merged case by case against the merge base, with the counters recomputed from the merged cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The direct predicate reads the column padding as PAD_W, which pad_w overrides, but DIRECT_PROBLEM_CAPABILITY_ATTRS from #14379 left pad_w out. Two problems differing only in pad_w then shared a capability key, so a cache filter could answer a per-axis padding with the verdict for an equal one. test_direct_problem_key_covers_every_extent_the_predicate_reads caught it after the merge; a new test pins the separation directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Motivation
On wave32 targets, the row-streaming depthwise dgrad
(`DirectDepthwiseDgradStreamSpec`, AOT variant `direct_depthwise_dgrad`)
runs one channel per lane. It uses 16-bit loads and stores and scalar
FMAs. Depthwise dgrad also has no direct kernel for filters past 11x11
or for non-square decomposed large kernels (51x5 / 5x51), so those
layers fall back to implicit GEMM.
This PR does three things:
- it adds channel packing to the row-streaming depthwise dgrad;
- it adds a rotated-filter dgrad mode to the output-stationary depthwise
kernel, with per-axis padding for non-square filters;
- it mirrors both in the C++ engine with byte-identical IR. The C++
engine had no port of the row-streaming depthwise dgrad, so this PR also
adds its scalar form.
Like `conv_direct_dgrad_wmma`, the new binaries are served by the
direct-conv AOT cache. There is no dispatcher wiring.
JIRA ID: AICK-2337
## Technical Details
**Channel-packed row-streaming dgrad
(`DirectDepthwiseDgradStreamSpec.cpl`,
`library/kernels/common/conv_direct_grouped.py`)**
- With `cpl` 2, 4 or 8, every lane owns `cpl` adjacent channels:
- dY rows load and dX rows store as one `cpl`-wide vector per lane;
- every tap runs as `cpl / 2` packed `<2 x f32>` FMAs over channel
pairs.
- The schedule does not change: dY rows stream into `stride * KH`
circular accumulator slots with a few rows prefetched, and a tail loop
flushes the rows past the last dY row.
- `cpl == 1` emits the unchanged kernel.
- Validator rules:
- `cpl` in {1, 2, 4, 8} and `groups % cpl == 0`;
- `cpl * KH * KW <= 200` for the packed weights;
- `cpl * block_w * KH <= 192` for the accumulator slots.
- `block_ch` grows by `cpl`, and the kernel name gains `cpl{N}`.
**Rotated-filter dgrad (`DirectDepthwiseTiledSpec.dgrad`)**
- A stride-1 depthwise dgrad is the forward correlation of dY with the
filter rotated by 180 degrees. The padding is `KH-1-PAD` rows and
`KW-1-PAD_W` columns.
- The output-stationary forward kernel computes it through the dgrad
kernarg block:
- the extents and the A-side strides swap roles;
- weight row `r` is read from row `KH-1-r`, reversed;
- the epilogue addresses dX through its kernarg strides;
- the grid tiles `H x W`.
- Only one filter row is register-resident, so this mode takes filters
up to 64 on a side, including non-square ones. The forward mode keeps
its 32 limit.
- The kernel name gains the `dg` flag. `dgrad=False` emits the unchanged
kernel.
- The mode packs one channel per lane: both engines refuse `dgrad` with
`cpl > 1` (the forward mode's channel-packed kernel, from #14392, is
forward only).
**Per-axis padding (`DirectConvProblem.pad_w`)**
- `pad_w = -1` means `PAD`. `PAD_W` is the effective column padding, and
`Wo` uses it.
- Only the rotated-filter dgrad takes a problem with `PAD_W != PAD`:
- the forward tiled mode refuses one;
- the WMMA convolutions (`direct_fwd_wmma`, `fwd_smallc_wmma`,
`fwd_smallk_wmma` through `wmma_fwd_gate`, `direct_dgrad_wmma`, and
#14387's stem dgrad kernels `dgrad_patch_wmma` and `dgrad_smallc_wmma`)
refuse one in their validators, in both engines. They offset columns by
`PAD`. The check is `one_padding_reason` /
`rocke_direct_conv_one_padding_reason`, shared with the row-streaming
dgrad;
- `direct_spec_for_problem` refuses one for every other variant;
- `direct_job_for_identity` builds non-square / per-axis identities only
for the new variant.
- `kernel_identity` matches `pad_w` against `PAD_W`, and `conv_args`
passes `pW = PAD_W`.
- The direct problem's capability key
(`DIRECT_PROBLEM_CAPABILITY_ATTRS`, from #14379) includes `pad_w`, so
problems that differ only in their column padding never share a cached
support verdict.
- The benchmark accepts `-p != -q` only for a stride-1 depthwise dgrad.
**AOT (`library/kernels/conv/direct_variants.py`,
`direct_aot_jobs.py`)**
- `direct_depthwise_dgrad` gains the `DW_DGRAD_CPL_GRID` knobs. The `cpl
== 1` knobs keep their identities.
- New variant `direct_depthwise_tiled_dgrad`, in
`DIRECT_WAVE_PARAMETRIC`:
- filters 13x13 to 31x31, plus 51x5 and 5x51, all at "same" padding;
- tiles `DW_TILED_DGRAD_BLOCK_W` x `DW_TILED_DGRAD_BLOCK_H`.
- `DirectCaps` gains `kw` / `pad_w` for those rows.
**C++ engine**
- New `rocke/instance_conv_direct_grouped_dw_dgrad.h` and
`conv_direct_grouped_build_depthwise_dgrad_stream.cpp` port
`DirectDepthwiseDgradStreamSpec`. This covers the defaults, `block_ch`,
the kernel name, `validate` / `is_valid` (same order and reason text as
Python), and the scalar and `cpl > 1` builders. They also add `_new` and
`lower_to_llvm` entries.
- `rocke_direct_depthwise_tiled_spec_t.dgrad` covers the builder mode,
the `dg` name flag, the dgrad filter limit and the padding checks of
`validate()`.
- `rocke_direct_conv_problem_t.pad_w` (default -1),
`rocke_direct_conv_problem_pad_w` and
`rocke_direct_conv_one_padding_reason`. Every C++ mirror of
`DirectConvProblem.Wo` in the direct-conv family uses it, as the Python
property does.
- The depthwise kinds have no `rocke_engine` binding, here or on
`develop`. The parity pair is the cross-engine gate.
**Docs:** `dsl_docs/instances/convolution.md` (the dgrad mode in the
tiled section, and a new row-streaming depthwise dgrad section),
`dsl_docs/instances/index.md`, `SUPPORT_MATRIX.md`.
## Test Plan
- `check_byte_identity.py`, all families, at llvm20, llvm22 and llvm23.
- `conv_direct_grouped_emit.{py,c}` parity configs 179-200 (after
develop's wave32 forward configs 166-178):
- row-streaming dgrad, scalar and `cpl` 2 / 4 / 8, in fp16 and bf16, at
stride 1 and 2, on wave32 (gfx1250) and wave64 (gfx950);
- rotated-filter dgrad on 13x13, 31x31, 51x5 (`pad_w`), 5x51 (`pad_w`,
unrolled rows), a 7x7 with a padding that differs per axis and is not
"same", and a 5x5 with `pad_w = 0` (the kernel name carries the per-axis
padding);
- nine specs both engines reject:
- per-axis padding on the forward tiled mode;
- the dgrad mode at stride 2;
- a 65-row filter;
- `groups % cpl != 0`;
- `cpl = 3`;
- the packed-accumulator bound;
- `pad_w < -1` on the rotated-filter dgrad;
- per-axis padding on the row-streaming kernel;
- `pad_w < -1` on the row-streaming kernel.
- Differential golden
(`tests/instances/differential/golden/llvm_gfx_all.json`): the new
`conv_direct_grouped` configs that both engines emit are blessed for
`ll` (llvm20 / llvm22 / llvm23) and `ir_canonical`.
- Golden IR: five new gfx1250 cases (row-streaming scalar, `cpl` 2,
`cpl` 8 stride 2 bf16; rotated-filter 31x31 and 51x5) at every flavor.
- `library/tests/test_direct_depthwise_dgrad_v2.py`, now in CTest:
validators, AOT grid entries (including the packed-entry spill gate),
the padding gate, and numerics against a numpy reference on any arch
rocke models (each case built at the arch's wave size; cases the arch's
validator refuses are skipped).
- `rocke_conv_direct_dw_dgrad_host` ctest: the stream name-overflow
cleanup, `pad_w < -1`, the per-axis padding name, the `dgrad` / `cpl >
1` refusal, and the per-axis padding refusal of the six WMMA kernels
(including the two stem dgrad kernels). It is listed in the `quick` and
`ffm-quick` categories of `ROCKE_ENGINE_test_categories_external.yaml`,
so CI runs it.
- Targeted CPU pytest, with and without the `rocke_engine` binding on
the path.
- black 25.12.0 / clang-format 18.1.4 on the changed files.
## Test Result
- Byte identity GREEN at llvm20, llvm22 and llvm23.
`conv_direct_grouped` has 201 configs: the 13 new compiled configs are
IDENTICAL, and the 9 new reject configs are BOTH_REJECTED.
- Golden `--check` passes at every flavor. Every existing hash is
reproduced: `cpl == 1` and `dgrad=False` emit the old IR. The golden
diff adds case keys and updates the summary counters.
- Differential golden `run_diff.py --python-only --check-golden` is
GOLDEN OK for `ll` at every flavor and for `ir_canonical`, with 0
unblessed changes and 0 missing. The bless adds 13 `conv_direct_grouped`
configs (179-190, 197) to each section; no existing hash changes or
drops.
- `test_direct_depthwise_dgrad_v2.py` on gfx1250 (before the latest
develop merge): 11 passed, 10 subtests (the GPU cases).
- After merging develop at `311cb32338e` (#14387, wave32 WMMA stem
dgrad), CPU only:
- byte identity GREEN at llvm20, llvm22 and llvm23 for `--only
conv,gfx1250`, including `conv_direct_grouped` (201 configs),
`conv_dgrad_patch_wmma` (9) and `conv_dgrad_smallc_wmma` (9);
- golden `--check` passes (llvm20 / 22 / 23 / 24); the differential
golden is GOLDEN OK for `ll` at every flavor and for `ir_canonical`,
with 0 unblessed changes and 0 missing;
- native build and ctest: 34/34 passed, including
`rocke_conv_direct_dw_dgrad_host`;
- `test_direct_depthwise_dgrad_v2.py`, `test_direct_kernel_cache.py`,
`test_direct_dgrad_wmma.py`, `test_direct_fwd_wmma.py`,
`test_fwd_small_wmma.py`, `test_dgrad_stem_wmma.py`,
`test_kernel_label.py`: 213 passed, 8 skipped with the `rocke_engine`
binding; 203 passed, 18 skipped without it. The skips need a GPU or the
binding;
- `test_direct_depthwise_fwd_cpl.py`,
`test_conv_direct_grouped_backend_parity.py`,
`test_direct_depthwise_col_validate.py`,
`test_conv_dgrad_depthwise_windowed.py`: 76 passed, 8 skipped with the
binding; 70 passed, 14 skipped without it.
- The merge's only conflict was the llvm23 summary counters of the
representative golden: develop adds 4 gfx1250 stem-dgrad cases and this
PR 5 gfx1250 depthwise-dgrad cases. Cases and expected failures are the
union of both sides, and the counters are recomputed from them:
`ok_cases` is 314 / 314 / 342 (llvm20 / 22 / 23). Every case hash equals
develop's or this branch's.
- Develop adds no `conv_direct_grouped` parity configs and changes no
`conv_direct_grouped` entry of the differential golden (#14387 adds its
own `conv_dgrad_patch_wmma` / `conv_dgrad_smallc_wmma` sections), so
this PR's configs keep 179-200.
- After that merge, #14387's two stem dgrad kernels also refuse a
per-axis padding (they offset columns by `PAD`); their emitted IR is
unchanged.
- After merging develop at `32aed8707aa` (#14393 split-K forward conv,
#14379 conv sweep benchmarks), CPU only:
- the only conflict was again the representative golden's summary
counters; merged case by case against the merge base, `ok_cases` is 318
/ 318 / 347. The golden differs from develop only by this PR's 5 cases
per flavor, and every hash equals develop's or this branch's;
- #14379's `test_capability_keys.py` caught that its direct capability
key left out `pad_w`; fixed in `23b898c8d2c` with a regression test;
- byte identity GREEN at llvm20, llvm22 and llvm23 for `--only
conv,gfx1250` (`conv_direct_grouped` 201 configs); golden `--check`
passes (llvm20 / 22 / 23 / 24); the differential golden is GOLDEN OK for
`ll` and `ir_canonical`;
- native build and ctest: 34/34 passed;
- the seven test files above: 213 passed, 8 skipped with the binding;
the four above: 76 passed, 8 skipped with the binding;
- develop's new sweep tests (`test_capability_keys`,
`test_run_conv_shards`, `test_module_cache`, `test_aot_launch_zeroing`,
`test_batched_timing`, `test_early_stop`, `test_streamk_sweep_launch`)
plus the grouped-conv dispatch, split-K forward and this PR's tests: 445
passed, 10 skipped, 1 failed. The failure,
`test_early_stop.py::test_staged_timing_reports_the_median_of_stalled_samples`,
fails identically on `develop`.
- After merging develop at `b33ff848a7b` (#14494 split the rocKE CMake
test registration into `tests/cmake/*.cmake`), the
`rocke_conv_direct_dw_dgrad_host` registration moves verbatim into
`platform/tests/cmake/InstanceTests.cmake`; `tests/CMakeLists.txt` is
develop's unchanged. CPU only: byte identity GREEN at llvm20 / 22 / 23
(`conv_direct_grouped` 201 configs), golden `--check` passes (llvm20 /
22 / 23 / 24), the differential golden is GOLDEN OK, native ctest 34/34
(`rocke_conv_direct_dw_dgrad_host` passes),
`test_cmake_test_registration.py` 5 passed, and the test counts above
are unchanged.
- After merging develop at `64c2954a8e` (#14679 split the
representative-IR golden into per-family shards), this PR's 5 cases per
flavor move unchanged into the `conv_direct` and
`conv_direct_depthwise_tiled` shards under
`platform/tests/golden/rocke_representative_ir_sha256/`; the shards
differ from develop's by those additions only. CPU only: golden
`--check` against the shard directory passes (llvm20 / 22 / 23 / 24),
byte identity GREEN at llvm20 / 22 / 23, the differential golden is
GOLDEN OK, native ctest 34/34, and `test_ir_golden.py` /
`test_rocke_ci_static.py` / `test_cmake_test_registration.py` pass; the
test counts above are unchanged.
- The full CPU pytest suite, run before the latest develop merges,
showed no new failures against `develop`. The remaining failures are
pre-existing and identical on `develop`:
`test_conv_abi.py::test_cache_applies_runtime_launch_limits`, six cases
in `test_conv_batch_and_miopen_parse.py`,
`test_conv_dgrad_patch.py::TestPatchIdentity::test_off_keeps_the_historical_hash`,
and
`test_engine_linkage.py::test_installed_pytest_gets_distinct_artifact_environment_variables`.
- No GPU run on gfx950 / gfx942 (no hardware here). Those targets are
pinned by the parity and golden gates.
- No performance results are included in this PR.
## Submission Checklist
- [x] Both engines emit byte-identical IR for every new code path; the
parity pair and golden cases are added.
- [x] Defaults keep every existing configuration and its IR unchanged.
- [x] New C++ code is in new files where the change allowed it.
- [x] CTest entry, docs and support-matrix rows.
- [ ] Dispatcher wiring: not part of this PR; the new binaries are
served by the direct-conv AOT cache.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
TheRock Submodule Bump ActivityNewest first
|



Motivation
Improve sweep time by new fast-rank mechanism. Change rocke conv measurements method to be the same as miopen.
Technical Details
-probe by less timing runs and the measure winners
Test Plan
test_conv_*
Test Result
passed
Submission Checklist
JIRA ID: AICK-2563