[MIOpen] Add cross-architecture LGBM-based immediate-mode solver picker#8399
[MIOpen] Add cross-architecture LGBM-based immediate-mode solver picker#8399jdcampbe wants to merge 16 commits into
Conversation
Adds ai::lgbm::PickSolver, which scores candidate solvers using two
LightGBM models (rank + applicability) ported from
~/AutoResearchAllLGBM. Works across gfx90a-mi210x, gfx942-{mi300a,
mi300x,mi325x}, gfx950-{mi350x,mi355x}, gfx1150, gfx1151, and gfx1201.
When MIOPEN_FIND_MODE=2, short-circuits the ND TunaNet model with the
LGBM pick when applicable; falls through to TunaNet otherwise.
Abstains on unknown spec_id, low-confidence margin (per-spec
threshold), or applicability VETO. Gated by MIOPEN_DEBUG_LGBM_PICK
(on by default).
Inference uses Treelite --source-only generated C (checked in under
src/conv/heuristics/lgbm_models/{rank,appl}) so MIOpen has no runtime
dependency on libtreelite / libLightGBM. The generated predict()
exports are renamed via -Dpredict=lgbm_{rank,appl}_predict at compile
time to avoid symbol collision.
GPU features come from a static lookup table generated from
~/AutoResearchAllLGBM/GPUInfo/*.json at port time; the SKU is
resolved at runtime from Handle::GetDeviceName() +
GetMaxComputeUnits() + GetGlobalMemorySize(), so no JSON shipping is
required. gfx950 mi350x/mi355x are indistinguishable from HIP info
and default to mi355x (more conservative abstain thresholds).
Model metadata (categorical vocabularies, triple_vocab,
per-spec thresholds) ships as two JSON files under
DATABASE_INSTALL_DIR, loaded at runtime via GetSystemDbPath().
Per-TU compile-verified under the HIPNOGPU Docker dev image. No
real-GPU smoke run yet; no test fixture yet.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Treelite-generated rank/ and appl/ C sources both export predict, postprocess, quantize, predict_unit0..7, the metadata accessors (get_num_target, get_num_class, get_num_feature, get_threshold_type, get_leaf_output_type), and the is_categorical global. The previous wiring only renamed predict via -Dpredict=..., so the other 16 symbols clashed at link time. Replace the single -D with a CMake loop that emits -D<sym>=lgbm_rank_<sym> (or lgbm_appl_<sym>) for every exported symbol, applied per-file via COMPILE_OPTIONS. Verified via nm that all rank/appl object-file symbols are now disjoint. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
FillGpuFeatures already uses absolute feature-row indices (kIdxCuCount = 37, etc.). The caller was passing row.data() + kIdxCuCount, so each write was offset twice and scribbled 31 entries past the end of the 69-element std::array on the stack. Symptom: every PickSolver call that reached the rank-scoring step corrupted the caller's frame and segfaulted on return, regardless of abstain/select outcome. Pass the base pointer; FillGpuFeatures is self-indexing. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Upstream re-fit the policy thresholds against a realistic 3x
quick-tuning fallback cost (the previous compact thresholds were tuned
against a fictional 10x invalid penalty and were net-negative under
the corrected cost model):
- margin abstain disabled: per_spec_thresh = {}, default = 1.0
(margin = 1 + max(top - runner, 0) >= 1.0, so the gate never fires)
- applicability VETO loosened: per_spec_appl_thresh = {},
appl_prob_thresh = 0.10 (only rejects extremely-low-confidence picks)
Reported val metrics under 3x cost: geomean 1.0090, abstain 0.01%,
top-1 95.56%, coverage 99.70%, invalid 0.29%.
Model files (rank, appl) and feature schema (model_meta.json) are
unchanged; thresholds-only refresh.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Tracks the upstream AutoResearchAllLGBM v4/v5 simplification:
- v4 dropped the applicability model entirely (appl threshold -> 0 with
no quality loss under the 3x cost model) and removed margin abstain.
The picker is now one model load, one predict per candidate, argmin.
Deletes lgbm_models/appl/ and all appl wiring; PickSolver no longer
runs a VETO or margin gate.
- v5 pruned the rank feature set 69 -> 59 (10 zero-gain features
removed: tensor_vect_type/length, convolution_mode, pad_mode,
max_waves_per_cu, vgpr/sgpr_per_simd, peak_tflops_fp4,
dtype_support_count, matrix_core_gen). Rewrites the kIdx feature-row
constants, the GpuFeatures struct, FillProblemFeatures /
FillGpuFeatures, and regenerates the GPU lookup table accordingly.
- New rank model source is model_rank_pruned59_t800.txt; regenerated
Treelite C (rank dir is now 48MB, was 72MB; appl's 29MB is gone).
- triple_vocab moved to a top-level model_meta.json key; loader accepts
both the v5 top-level location and the legacy appl-nested one.
Feed NaN for the perf-DB workload features (flop_cnt, bytes_*, gflops,
bandwidth_gbps) instead of textbook estimates. The DB byte counts are
direction-aware (read/write tensors swap for Bwd passes), which a forward
estimate cannot reproduce. A clean-room replay over the new 225-vector
deploy/test_vectors.json fixture confirmed NaN reproduces the reference
pick on 217/225 vectors vs 211/225 for the textbook estimate -- wrong
values mislead the trees more than missing ones do. Feature order, GPU
table values, and categorical encoding were verified bit-exact against
the deploy artifacts.
Adds the validation fixture as test/gtest/lgbm_test_vectors.json.
Per-TU compile-verified under the HIPNOGPU Docker dev image.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
CPU-only tests (no GPU required), mirroring the TunaNet
conv_ai_nd_heuristics gtest layout:
- CPU_LgbmMetadata: metadata loader sanity (52 solvers, triple_vocab
candidates resolve to valid solver codes, categorical encoding +
unknown-value handling, SolverCode==vocab index).
- CPU_LgbmGpuTable: ResolveSpecId disambiguation (gfx942 by CU/VRAM,
gfx950 -> mi355x, single-SKU archs, unknown archs abstain) and the
spec_id_code == array-index invariant.
- CPU_LgbmPickerFixture: rebuilds a ProblemDescription per
lgbm_test_vectors.json entry, runs PickSolverForSpec, and asserts the
picker never abstains (v5 property), never returns an unknown solver,
and reproduces >=90% of the reference picks. The bound is below 100%
because the ProblemDescription path cannot recover fixture rows with a
NaN layout or missing perf-DB workload values (a clean-room replay of
the exact C++ path reproduces 210/225 = 93.3%).
CMake stages lgbm_test_vectors.json into DATABASE_INSTALL_DIR so the
fixture is found via GetSystemDbPath() at run time; the test source is
auto-globbed into miopen_gtest.
Per-TU compile-verified under the HIPNOGPU Docker dev image.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The upstream model dropped all curated per-SKU GPU data: v10 sources its
GPU features directly from the live device instead of a shipped JSON
table. Port the MIOpen integration to match.
Model/algorithm changes (v5 -> v10):
- 51 features (was 59); dropped spec_id, l3_infinity_cache_kb, and all
six peak_tflops_* (the JSON-only fields). Regenerated the rank
Treelite C from model_rank_v10_pure_final.txt.
- No spec_id anywhere: removed the per-SKU lgbm_gpu_table.cpp +
lgbm_gpu_features.hpp (kGpuTable / ResolveSpecId) and the
gen_gpu_table.py sidecar.
- GPU features are now gathered at runtime: cu_count, wave_size,
l2_cache_total_kb, boost_clock_mhz, lds_size_per_workgroup_kb and
vram_bytes come from hipDeviceProp_t via the Handle; the 10
arch-invariant fields come from a compiled-in gfx_id -> constants
table (gpu_constants.h, generated by gen_gpu_constants_header.py).
- Score the full solver vocabulary and take the global argmax. v10
removed the spec_id-keyed candidate masking; a clean-room replay
confirmed full-vocab argmax reproduces the reference picks (50/50)
while the old masking matched only 17/50.
Handle: add GetL2CacheSize() and GetClockRateKhz(), implemented for HIP
(hipDeviceGetAttribute), OpenCL (clGetDeviceInfo), and stubbed for NOGPU.
Metadata loader trimmed to vocab + solvers (dropped triple_vocab and the
dead margin/appl threshold machinery); kNumFeatures 59 -> 51; removed the
now-unused lgbm_per_spec_thresh.json.
Tests: rework lgbm_picker.cpp for the new fixture format -- the v10
test_vectors ship the fully-encoded 51-feature row, so the new
ScoreRowArgmaxForTest seam validates the scoring + argmax path exactly
(50/50) without a ProblemDescription round-trip. Dropped the obsolete
GPU-table test.
Per-TU compile-verified under the HIPNOGPU Docker dev image (HIP/OpenCL
handle TUs are not built in that backend, but the new getters mirror the
existing attribute-query getters and the required HIP/CL enums exist).
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Refresh the rank model from model_rank_v11_runtime.txt. Same 51-feature runtime-pure schema as v10 (no spec_id / l3 / peak_tflops; GPU features from hipDeviceProp_t + gpu_constants.h), so no C++ changes are needed -- only the regenerated Treelite tree TUs, model_meta.json, and the validation fixture change. The retrain reverts the v9/v10 data filter that excluded Xdlops solvers on RDNA3: those solvers are applicable on RDNA3 via Composable Kernel (MIOpen's IsApplicable has no arch gate), so the filtered training data was wrong. Oracle val geomean 1.0147, top-1 94.5%. Validated: full-vocab argmax reproduces the refreshed test_vectors.json 50/50. Regenerated rank C compile-checked under the HIPNOGPU Docker image. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This is still only a subset of the supported GPU lineup though, unless I misunderstand something. |
DataTypeName used a switch over miopenDataType_t that listed only the four dtypes in the model vocab plus a default. MIOpen builds with -Werror -Wswitch-enum, which flags every unlisted enumerator even when a default is present, so the full gfx942/gfx950 build failed (the HIPNOGPU per-TU compile-check does not pass -Wswitch-enum, which is why it slipped through). Replace the switch with an if-chain; semantics unchanged (unhandled dtypes still map to "", the missing category). Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Remove all curated per-architecture GPU data so the model is sourced
entirely from the runtime device and can project to unseen
architectures.
Model (v5/v10/v11 -> v16): retrained to 41 features. Dropped every GPU
feature not directly readable from hipDeviceProp_t -- the 8 HSA-only
fields (simds_per_cu, l1_cache_kb_per_cu, shader_engines,
cacheline_size_bytes, xcd_count, mfma_shape_count, winograd_support,
asm_implicit_gemm_support) plus lds_size_per_cu_kb and arch_family. The
only GPU inputs are now cu_count, wave_size, lds_size_per_workgroup_kb,
l2_cache_total_kb, boost_clock_mhz, vram_bytes (all hipDeviceProp_t) and
gfx_id (gcnArchName). Oracle val geomean 1.0147, top-1 94.6% --
unchanged from v11.
C++:
- Deleted the embedded gpu_constants.h table and its generator; nothing
in the picker reads curated arch data anymore.
- Rewrote FillGpuFeatures to populate only the six hipDeviceProp_t
numerics + gfx_id; new 41-feature index map; kNumFeatures 51 -> 41.
- Architecture gating retained: the picker runs only on gfx_ids in the
model vocab and abstains (falls through to TunaNet) otherwise. The
feature set is fully runtime-derived, so lifting the gate later allows
projection to new silicon without any data change.
Regenerated rank Treelite C from model_rank_v16s_strict.txt; refreshed
model_meta.json and the test fixture. Full-vocab argmax reproduces the
new test_vectors 50/50. Per-TU compile-checked (incl. -Wswitch-enum)
under the HIPNOGPU Docker image.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
You're correct. In theory it should work across all architectures, but there is an artificial gate to include architectures that we have some data for. The artificial gates will be removed as we test on other architectures. |
|
I went through the latest CI run (v16, commit 47cf542). Good news is the failures are all mechanical or packaging related, nothing in the model logic itself. Here is the breakdown. 1.
|
Refresh the rank model from model_rank_v17_0616.txt. Same 41-feature HIP-only schema as v16 (no embedded GPU data; inputs are the six hipDeviceProp_t fields + gfx_id), so no C++ changes are needed -- only the regenerated Treelite tree TUs, model_meta.json, and the validation fixture. The retrain pulls in additional data (notably more gfx90a and gfx1100 coverage) and grows the solver vocabulary 70 -> 79 (new Winograd multipass WrW variants and a direct 11x11 solver); the picker reads the vocab dynamically so this needs no code change. Oracle val geomean 1.0142, top-1 94.7%. Validated: full-vocab argmax reproduces the refreshed test_vectors 50/50. Regenerated rank C compile-checked under the HIPNOGPU Docker image. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
… output)
Promote the TunaNet+Align model: 61 features = the 41 HIP-only base
features + 20 engineered derived features the C++ caller computes.
Derived features (no new embedded data; all from conv dims + cu_count):
- 13 tn_* GEMM-geometry features (indices 28-40) are computed by reusing
MIOpen's production miopen::ai::common::EngineeredConvFeatures verbatim
-- the same single-source-of-truth the TunaNet/candidate-selection
encoders use, so the C++ and the Python training pipeline cannot drift.
H_out/W_out are taken from the output descriptor (exact). 2D geometry is
used for all convs, matching how the model was trained; 3D extent is
still carried by the base depth/spatial features.
- 7 al_* tile-alignment features (indices 41-47): integer divisibility /
last-64-tile under-fill of channels/output_channels/batch.
Ranked-list output, lazy applicability downstream:
- The shipped model requires candidate masking (full-vocab argmax matches
the reference only 7/60; masked candidate scoring matches 60/60). Rather
than mask inside the picker, PickSolverRanked now scores the full vocab,
sorts by predicted speed, and returns the ranked solver-id list. The
existing GetSolutionsFallback caller walks that list applying
IsApplicable lazily (the TunaNet contract) -- typically 1-3 checks, no
spec_id, no embedded GPU data. Replaces the prior single-solver
PickSolver + post-hoc applicability check.
Regenerated rank Treelite C from model_rank_median_tn_align_t600.txt;
refreshed model_meta.json + the test fixture (new format: per-vector
candidate_solvers + feature_matrix + expected_scores). The gtest now scores
each vector's candidate matrix and asserts argmax == argmax_solver.
Validated: candidate-matrix argmax reproduces the fixture 60/60 with
max|score-expected| = 0.0. Per-TU compile-checked (incl. -Wswitch-enum)
under the HIPNOGPU Docker image.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
…sion The LGBM ranker over-selected ConvDirectNaiveConv* on out-of-distribution shapes (~6x more than develop's ND TunaNet), causing ~4-20x slowdowns (results correct, perf only). Root cause: the ConvDirectNaiveConv* fallbacks are ALWAYS applicable, and the model frequently ranks one #1 on unseen shapes; MIOpen's fallback walk keeps applicable solvers in rank order and picks the best-ranked, so a #1 naive solver wins over a far faster real kernel that ranked lower. Fix (ranking only; no model retrain, no .so change): in PickSolverRanked, demote every naive fallback below all non-naive solvers (relative score order preserved within each group). A naive solver is now reached only when no non-naive solver applies -- matching develop's ND TunaNet, which ranks naive last. The naive set is read from model_meta.json -> rank.naive_fallback_solvers (not hardcoded); ConvDepthwiseFwd2D is a real optimized kernel, is not in that list, and is not demoted. The walk stays lazy (the caller still applies IsApplicable only to solvers it reaches). model_meta.json refreshed to the same v18 model with the added naive_fallback_solvers key (feature_order/solvers/vocab and test_vectors byte-identical). The gtest test seam does raw argmax and is unaffected. Offline gold-val for this policy: naive pick 51.6% -> 0.09%, coverage 0.51 -> 1.00, geomean on non-naive-best shapes unchanged (1.0136 -> 1.0132). Per-TU compile-checked (incl. -Wswitch-enum) under HIPNOGPU. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The previous fix demoted ConvDirectNaiveConv* unconditionally, which over-corrects on grouped convs: at high group counts naive is genuinely the fastest solver a large fraction of the time (~40% at groups>=512), so forcing a non-naive pick swaps in the slow grouped-XDLOPS solver and regresses hard (offline gold-val geomean 1.3649 on groups>=64). Per deploy/README_CPP_DERIVED.md, gate the demotion on group count: demote naive only when GetGroupCount() < rank.naive_guard_max_groups (default 64, read from model_meta.json); at or above the threshold, leave the raw score order so naive can win on merit. Low-group OOD shapes still get the naive fix (the ~6x over-selection / ~4-20x slowdowns this addresses). model_meta.json refreshed to add naive_guard_max_groups (same v18 model; test_vectors byte-identical). Offline gold-val for the group-aware policy: naive 5.4%, geomean(all) 1.0204, geomean(groups>=64) 1.0308 (vs 1.3649 for the unconditional guard), geomean(groups<64) 1.0201. Per-TU compile-checked (incl. -Wswitch-enum) under the HIPNOGPU Docker image. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Refresh the rank model from model_rank_expanded_tnalign_t600.txt (expanded-data retrain: 866k gold rows, 13 specs). Same 61-feature TunaNet+Align schema, same gfx_id vocab, same naive-guard fields (naive_fallback_solvers + naive_guard_max_groups=64), so no C++ changes -- the picker reads the solver list dynamically. The retrain grows the solver vocabulary 70 -> 72, adding ConvHipImplicitGemmFwdXdlops and ConvHipImplicitGemmBwdXdlops (the grouped-XDLOPS family the ranker previously over-valued vs naive; giving the model their non-grouped variants as first-class candidates is the data-side complement to the group-aware naive guard). Reported oracle gold-val geo 1.0124 / top1 93.2%. Regenerated rank Treelite C; refreshed model_meta.json and the test fixture (65 vectors). Candidate-matrix argmax reproduces the fixture 65/65 with max|score-expected| = 0.0. Per-TU compile-checked (incl. -Wswitch-enum) under the HIPNOGPU Docker image. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
|
This PR will be merged in with #9680 |
Motivation
MIOpen's immediate-mode solver selection uses per-architecture TunaNet models, which need a separately trained model per arch and cover only a subset of the GPU lineup. This PR adds a single cross-architecture solver picker driven by a gradient-boosted (LightGBM) rank model that selects a convolution solver from one model across gfx90a, gfx942, gfx950, gfx1151, gfx1201, and the gfx110x family. The picker reads everything it needs about the GPU directly from the device at runtime, so it requires no curated per-SKU data and stays correct on the actual silicon.
Technical Details
The picker is a self-contained inference path with no runtime dependency on libLightGBM or Treelite. The rank model is compiled to plain C via Treelite
--source-onlyand checked in underprojects/miopen/src/conv/heuristics/lgbm_models/rank/; each generated translation unit's exported symbols (predict,quantize,predict_unit0..7, the metadata accessors, theis_categoricalglobal) are renamed with anlgbm_rank_prefix via per-file-Ddefines inprojects/miopen/src/CMakeLists.txtto keep them out of MIOpen's global namespace. The project enables the C language (project(MIOpen C CXX)) to compile these.Inference (
conv/heuristics/lgbm_pick.cpp):gfx_idfromHandle::GetDeviceName(); abstains if the arch is not in the model vocab / arch-constants table.solver::Idand abstains if this MIOpen build doesn't know it.GPU features are sourced two ways:
cu_count,wave_size,l2_cache_total_kb,boost_clock_mhz,lds_size_per_workgroup_kb,vram_bytes. This adds two smallHandlegetters —GetL2CacheSize()andGetClockRateKhz()— implemented for HIP (hipDeviceGetAttribute), OpenCL (clGetDeviceInfo), and stubbed for NOGPU, mirroring the existing attribute-query getters insrc/hip/handlehip.cpp.gfx_id→ constants table (conv/heuristics/lgbm_models/gpu_constants.h).Model metadata (
lgbm_model_meta.json) installs toDATABASE_INSTALL_DIRand loads at runtime viaGetSystemDbPath(). The hook is an early short-circuit inai_heuristics.cpp::PredictSolver, gated byMIOPEN_DEBUG_LGBM_PICK(on by default) underMIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK, mirroring the existing TunaNet dispatch.The 6 derived workload features (
flop_cnt/bytes_*/gflops/bandwidth_gbps) are fed as NaN: the values these were trained on are direction-aware in a way a runtime textbook estimate cannot reproduce, and validation showed NaN reproduces more reference picks than an estimate.LightGBM treats NaN as a real branch direction.
Not changed: the TunaNet path and WTI fallback. When the picker abstains (unknown arch, or model recommends an unavailable solver) it falls through to the existing selection path unchanged.
Test Plan
projects/miopen/test/gtest/lgbm_picker.cpp(targettest_lgbm_picker):CPU_LgbmMetadata— metadata loads, categorical encoding resolves, solver code matches vocab index.CPU_LgbmPickerFixture.ReproducesReferenceArgmax— feeds each encoded feature row fromlgbm_test_vectors.jsonthrough the scoring path and asserts the argmax solver matches the reference exactly.MIOPEN_LOG_LEVEL=6, and confirm thelgbm picked …log line.
Test Result
hipDeviceAttribute*/CL_DEVICE_*enums were confirmed present.Risk Assessment
Risk level: Low
conv/heuristics/lgbm_*andconv/heuristics/lgbm_models/; thePredictSolverhook inai_heuristics.cpp; two newHandlegetters across the HIP/OpenCL/NOGPU backends;src/CMakeLists.txt(sources, install, C-language enable); a new gtest. The TunaNet and WTI paths are untouched, and behavior is identical whenMIOPEN_DEBUG_LGBM_PICKis disabled.Handlegetters are additive internal methods).MIOPEN_DEBUG_LGBM_PICK(runtime toggle, no rebuild) and compiled only underMIOPEN_ENABLE_AI_IMMED_MODE_FALLBACK.lgbm_test_vectors.jsonfixture makes picks reproducible and checkable pre-merge.Submission Checklist