Skip to content

Add fp32 winograd for gfx12#5092

Open
pfultz2 wants to merge 21 commits into
developfrom
fp32-winograd
Open

Add fp32 winograd for gfx12#5092
pfultz2 wants to merge 21 commits into
developfrom
fp32-winograd

Conversation

@pfultz2

@pfultz2 pfultz2 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Technical Details

Changelog Category

Add a CHANGELOG.md entry for any option other than Not Applicable

    • Added: New functionality.
    • Changed: Changes to existing functionality.
    • Removed: Functionality or support that has been removed. (Compared to a previous release)
    • Optimized: Component performance that has been optimized or improved.
    • Resolved Issues: Known issues from a previous version that have been resolved.
    • Not Applicable: This PR is not to be included in the changelog.

Follow the LLVM AI Tool Use Policy for contributions using AI.

@pfultz2
pfultz2 requested a review from causten as a code owner July 23, 2026 15:59
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5092      +/-   ##
===========================================
+ Coverage    92.89%   92.90%   +0.01%     
===========================================
  Files          603      603              
  Lines        32448    32528      +80     
===========================================
+ Hits         30140    30218      +78     
- Misses        2308     2310       +2     

see 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gh-app-migraphx-bot-pr-write

Copy link
Copy Markdown
Test Batch New Rate (c2d267) Old Rate (c7984a) Diff Status
torchvision-resnet50 64 1,881.03 3,147.92 -40.25% 🔴
torchvision-resnet50_fp16 64 6,700.74 6,683.54 0.26%
torchvision-densenet121 32 2,705.38 2,706.70 -0.05%
torchvision-densenet121_fp16 32 4,577.62 4,584.01 -0.14%
torchvision-inceptionv3 32 1,797.27 1,796.69 0.03%
torchvision-inceptionv3_fp16 32 2,846.87 2,852.74 -0.21%
cadene-inceptionv4 16 365.56 823.24 -55.59% 🔴
cadene-resnext64x4 16 783.07 783.82 -0.09%
slim-mobilenet 64 8,426.40 8,427.50 -0.01%
slim-nasnetalarge 64 123.11 228.91 -46.22% 🔴
slim-resnet50v2 64 3,132.11 3,173.08 -1.29%
bert-mrpc-onnx 8 1,171.32 1,166.14 0.44%
bert-mrpc-tf 1 356.58 484.48 -26.40% 🔴
pytorch-examples-wlang-gru 1 490.75 473.62 3.62%
pytorch-examples-wlang-lstm 1 377.75 382.56 -1.26%
torchvision-resnet50_1 1 516.54 768.62 -32.80% 🔴
cadene-dpn92_1 1 237.51 445.78 -46.72% 🔴
cadene-resnext101_1 1 175.57 365.52 -51.97% 🔴
onnx-taau-downsample 1 398.60 401.90 -0.82%
dlrm-criteoterabyte 1 32.56 32.50 0.20%
dlrm-criteoterabyte_fp16 1 50.90 52.75 -3.51%
agentmodel 1 9,562.61 7,840.62 21.96% 🔆
unet_fp16 2 52.90 57.37 -7.80% 🔴
resnet50v1_fp16 1 122.93 931.81 -86.81% 🔴
resnet50v1_int8 1 931.76 935.43 -0.39%
bert_base_cased_fp16 64 901.02 1,102.59 -18.28% 🔴
bert_large_uncased_fp16 32 89.38 346.63 -74.21% 🔴
bert_large_fp16 1 31.40 203.73 -84.59% 🔴
distilgpt2_fp16 16 900.60 2,091.42 -56.94% 🔴
yolov5s 1 52.83 557.67 -90.53% 🔴
tinyllama 1 19.48 45.81 -57.48% 🔴
vicuna-fastchat 1 40.24 44.12 -8.81% 🔴
whisper-tiny-encoder 1 414.69 415.14 -0.11%
whisper-tiny-decoder 1 412.68 410.13 0.62%
llama2_7b 1 10.88 20.97 -48.13% 🔴
qwen1.5-7b 1 10.60 23.60 -55.08% 🔴
phi3-3.8b 1 5.97 26.85 -77.75% 🔴
llama3-8b 1 12.27 21.83 -43.82% 🔴
whisper-large-encoder 1 3.60 10.21 -64.71% 🔴
whisper-large-decoder 1 83.52 104.58 -20.13% 🔴
mistral-7b 1 15.15 23.80 -36.36% 🔴
FLUX.1-schnell 1 757.60 746.40 1.50%

Regressions detected 🔴

@gh-app-migraphx-bot-pr-write

Copy link
Copy Markdown
Test Status Result
bert-mrpc-onnx PASSED: MIGraphX meets tolerance
bert-mrpc-tf PASSED: MIGraphX meets tolerance
pytorch-examples-wlang-gru PASSED: MIGraphX meets tolerance
pytorch-examples-wlang-lstm PASSED: MIGraphX meets tolerance
dlrm-criteoterabyte PASSED: MIGraphX meets tolerance
agentmodel PASSED: MIGraphX meets tolerance
unet PASSED: MIGraphX meets tolerance
resnet50v1 PASSED: MIGraphX meets tolerance
bert_base_cased_fp16 PASSED: MIGraphX meets tolerance
bert_large_uncased_fp16 🔴 FAILED: MIGraphX is not within tolerance - check verbose output
bert_large PASSED: MIGraphX meets tolerance
yolov5s PASSED: MIGraphX meets tolerance
tinyllama PASSED: MIGraphX meets tolerance
vicuna-fastchat PASSED: MIGraphX meets tolerance
whisper-tiny-encoder PASSED: MIGraphX meets tolerance
whisper-tiny-decoder PASSED: MIGraphX meets tolerance
distilgpt2_fp16 PASSED: MIGraphX meets tolerance
llama2_7b PASSED: MIGraphX meets tolerance
qwen1.5-7b PASSED: MIGraphX meets tolerance
phi3-3.8b PASSED: MIGraphX meets tolerance
llama3-8b PASSED: MIGraphX meets tolerance
whisper-large-encoder PASSED: MIGraphX meets tolerance
whisper-large-decoder PASSED: MIGraphX meets tolerance
mistral-7b PASSED: MIGraphX meets tolerance
FLUX.1-schnell PASSED: MIGraphX meets tolerance

Copilot AI review requested due to automatic review settings July 24, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the GPU Winograd F(2,3) convolution path on gfx12 to support fp32 by adding a new FMA/DPP-based kernel, wiring it through prefuse + JIT compilation, and adding a verification test to exercise the new selection/weight-transform logic.

Changes:

  • Add a gfx12 fp32 Winograd F(2,3) kernel (winograd_conv_fp32.hpp) plus JIT wiring to compile/tune it.
  • Extend prefuse_ops Winograd matching/heuristics and add fp32-specific host-side weight transforms (full-U, S-store, v-innermost).
  • Factor gfx12 “OOB raw buffer load returns 0” helpers into a shared kernel header and update the existing fp16 WMMA kernel to use it.
  • Add a new test/verify case intended to exercise the fp32 Winograd path.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/verify/test_conv_3x3_winograd_fp32.cpp New verify test to exercise fp32 Winograd selection and edge cases
src/targets/gpu/prefuse_ops.cpp fp32 Winograd matcher enablement, fp32 heuristics, and fp32 weight-literal transforms (full-U / S-store / vinner)
src/targets/gpu/kernels/include/migraphx/kernels/winograd_conv.hpp Refactor to use shared OOB buffer-load helpers
src/targets/gpu/kernels/include/migraphx/kernels/winograd_conv_fp32.hpp New fp32 FMA/DPP Winograd kernel implementation
src/targets/gpu/kernels/include/migraphx/kernels/buffer_load.hpp New shared gfx12 OOB-tolerant raw buffer-load utilities
src/targets/gpu/jit/winograd_conv.cpp JIT: add fp32 kernel codegen path, tuning configs, and integer_divide_ceil usage

Comment on lines +46 to +51
mm->add_instruction(
migraphx::make_op("convolution",
{{"padding", {1, 1}}, {"stride", {1, 1}}, {"dilation", {1, 1}}}),
x,
w);
return p;
Comment on lines +168 to +182
const auto nw = v.get("nw", std::size_t{4});
const auto ko = v.get("ko", std::size_t{8});
const auto tiles = v.get("tiles", std::size_t{2});
// sk = within-WG channel-split factor (must divide nw). sk>1 has nw/sk
// NT-groups whose sk waves split the channel contraction.
const auto sk = v.get("sk", std::size_t{1});
// pipe = software-pipeline the input transform into the FMA loop (true) vs
// the simple transform-then-FMA path (false). pipe costs a second live
// v_reg, so the tuner only offers it on small (non-spilling) solutions.
const bool pipe = v.get("pipe", false);
// cu = channel-unroll (weight load width): 4=b128, 2=b64, 1=b32. Smaller
// cu shrinks the pipelined double-buffer (finer-grained pipeline) at the
// cost of more, narrower weight loads.
const auto cu = v.get("cu", std::size_t{4});
// Weight layout (chosen by prefuse, read back from the literal's shape):
Comment on lines +84 to +90
static winograd_fp32_weight_layout winograd_fp32_weight_layout_of(const shape& w)
{
if(w.lens().at(0) == 3)
return winograd_fp32_weight_layout::sstore;
return w.lens().at(1) == 4 ? winograd_fp32_weight_layout::full_u
: winograd_fp32_weight_layout::full_u_vinner;
}
Comment on lines +150 to +153
static_assert(KO >= 1, "KO must be >= 1");
static_assert(TILES >= 1, "TILES must be >= 1");
static_assert(SK >= 1 and SK <= NW and (NW % SK) == 0, "SK must divide NW");
static_assert(CU == 1 or CU == 2 or CU == 4, "CU must be 1, 2, or 4");
Comment on lines +324 to +346
// BOTTLENECK (measured by address-isolation on 256->256@64, replacing a load's
// offset with a constant so it hits one cached line): full 0.698ms; with the
// INPUT load coalesced 0.229; with the WEIGHT load coalesced 0.239; with BOTH
// coalesced = pure compute 0.089ms. So the fused COMPUTE is ~3x FASTER than MLIR
// (0.089 vs 0.276) -- the whole gap is the two SCATTERED loads (both read
// lane==v_col: input W-columns are C-strided; weight U v-slices are K*C apart),
// which THRASH the cache super-linearly (0.698 >> 0.089+0.14+0.15). Traffic is
// ~23 GB/s (<<peak) so it is cache-miss latency/thrashing, not bandwidth.
//
// FIX SHIPPED for the WEIGHT scatter (the host-controllable one): U is laid out
// v-innermost [u,k,c,v] (see prefuse compute_winograd_weights_f23_fp32 vinner)
// so the 4 v_col lanes read consecutive floats -> the weight load coalesces,
// relieving the thrash. GATED to out_c>=128 && in_c<=out_c: its strided (b32)
// channel load adds issue overhead that regresses small/cached-weight shapes.
// Net +4.5% geomean vs the scattered path (0.878->0.918x MLIR), memory-bound
// configs -61%->-47..-56%. The INPUT scatter (17MB, uncacheable, no layout
// freedom) is the dominant residual and has no clean in-kernel fix -- real
// input coalescing (lane==channel rewrite, LDS spatial-blocking) was measured
// NET-NEUTRAL-to-LOSS, and a full fused implicit-GEMM (scratchpad/
// winograd_conv_fp32_gemm.hpp) helps memory-bound (~0.69x) but is a big
// aggregate loss (0.48x, wrecks small shapes) -- the 16 winograd positions cap
// its arithmetic intensity. Beating MLIR outright would need a MULTI-kernel
// winograd (transform kernels + a library batched GEMM on materialized V/M).
Comment on lines +33 to +46
// gfx12 buffer-resource word 3 (from composable_kernel): makes raw buffer loads
// return 0 for out-of-range byte offsets, so bounds/halo checks collapse to an
// offset select against a sentinel instead of a per-load branch.
constexpr uint32_t oob_buffer_rsrc_word3 = 0x31004000;

// Build an out-of-bounds-tolerant buffer descriptor for a read-only pointer (the
// resource is only ever loaded from; the const_cast is required by the builtin's
// non-const pointer parameter).
template <class T>
__device__ inline __amdgpu_buffer_rsrc_t make_oob_buffer_rsrc(const T* p, uint32_t byte_count)
{
auto* base = const_cast<T*>(p); // NOLINT(cppcoreguidelines-pro-type-const-cast)
return __builtin_amdgcn_make_buffer_rsrc(base, 0, byte_count, oob_buffer_rsrc_word3);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants