Skip to content

Commit f04147c

Browse files
[Conv] Backport CK split_k reduction workspace for grouped bwd/wrw solvers
Backport of the workspace portion of PR #1426 (commit e290adb, ALMIOPEN-706) to ROCm 7.0.2. MIOpen previously sized the CK workspace for the grouped bwd, grouped wrw, and 3D grouped wrw solvers via GetCKAlphaBetaWorkspace (the alpha/beta backward-weights workspace query). That query does not account for CK's split_k reduction workspace, so once a tuned split_k>1 configuration was selected the reduction ran against an under-allocated workspace and could return silently wrong results. This change: - Adds NextCKSplitkValue with CK's split_k autodeduce value (-1). - Adds GetCKSplitkWorkspaceSize / GetCKSplitkMaxWorkspaceSize and queries CK for the actual per-instance workspace size across the split_k sweep. - Uses that CK workspace size in GetWorkspaceSize (falling back to GetCKAlphaBetaWorkspace only when CK reports 0) and binds a correctly-sized workspace pointer in the NCHW/NHWC invoker factories. - Removes the generic-search assertion that workspace size must be independent of the PerformanceConfig. Verified via an A/B on the grouped bwd numerical tests: workspace-requiring split_k>1 CK configurations are now selected and produce correct results on int32-safe shapes. Shapes whose total element count exceeds INT_MAX additionally require the large-tensor CK instances; after this change they fault rather than return silently wrong results, and are not fully resolved here (see ALMIOPEN-2305). Also folds in the follow-up fix from mainline commit 54f9acb (SreecharanGundaboluAMD, #3414): GetCKSplitkMaxWorkspaceSize must initialize the sweep at split_k=1, not CkSplitkAutoDeduce. Starting at the autodeduce sentinel makes NextCKSplitkValue return true on the first step, so the loop evaluates only the autodeduce workspace and never sweeps split_k=1,2,...,128 -- under-reporting the max workspace and reintroducing the under-allocation this change is meant to fix. Also widens the workspace accumulator to std::size_t to avoid int truncation for >2GB workspaces. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a1e0e6f commit f04147c

7 files changed

Lines changed: 225 additions & 65 deletions

projects/miopen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ Full documentation for MIOpen is available [here](https://rocm.docs.amd.com/proj
2121
* [Conv] Eliminated redundant clearing of output buffers
2222
* [RNN] Updated selection heuristics
2323
* Updated tuning for MI300
24+
* [Conv] Improved Composable Kernel (CK) kernel selection during tuning for grouped bwd/wrw solvers
2425

2526
### Resolved issues
2627
* Fixed a segmentation fault when user specifies workspace smaller than what is required
28+
* [Conv] Fixed wrong results in grouped CK xdlops bwd/wrw solvers when a tuned split_k configuration required a reduction workspace that was not allocated
2729
* Fixed a layout calculation logic error that returned incorrect results and enabled less restrictive layout selection
2830
* Fixed memory access faults in misa kernels due to out-of-bounds memory usage
2931
* Fixed performance drop on gfx950 due to transpose kernel use

projects/miopen/src/include/miopen/conv/solvers.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4643,6 +4643,10 @@ struct ConvHipImplicitGemm3DGroupWrwXdlops final
46434643
private:
46444644
template <typename DataType>
46454645
bool CheckCKApplicability(const miopen::conv::ProblemDescription&) const;
4646+
4647+
template <typename DataType>
4648+
std::size_t GetCKMaxWorkspaceSize(const miopen::conv::ProblemDescription&) const;
4649+
size_t GetCKMaxWorkspaceSize(const miopen::conv::ProblemDescription& problem) const;
46464650
};
46474651

46484652
struct PerformanceConfigHipImplicitGemm3DGroupBwdXdlops
@@ -4815,6 +4819,8 @@ struct ConvHipImplicitGemmGroupBwdXdlops final
48154819
private:
48164820
template <typename DataType>
48174821
bool CheckCKApplicability(const miopen::conv::ProblemDescription&) const;
4822+
4823+
size_t GetCKMaxWorkspaceSize(const miopen::conv::ProblemDescription& problem) const;
48184824
};
48194825

48204826
struct PerformanceConfigHipImplicitGemmGroupWrwXdlops
@@ -4909,6 +4915,8 @@ struct ConvHipImplicitGemmGroupWrwXdlops final
49094915
private:
49104916
template <typename DataType>
49114917
bool CheckCKApplicability(const miopen::conv::ProblemDescription&) const;
4918+
4919+
size_t GetCKMaxWorkspaceSize(const miopen::conv::ProblemDescription& problem) const;
49124920
};
49134921

49144922
} // namespace conv

projects/miopen/src/include/miopen/generic_search.hpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -498,15 +498,6 @@ auto GenericSearch(const Solver s,
498498

499499
try
500500
{
501-
if(default_solution.workspace_sz != current_solution.workspace_sz)
502-
{
503-
ret = -2;
504-
MIOPEN_LOG_E('#' << n_current << " (" << n_runs_total << ") "
505-
<< "Workspace size should not depend on PerformanceConfig: "
506-
<< default_solution.workspace_sz
507-
<< " != " << current_solution.workspace_sz);
508-
}
509-
510501
invoker = profile_h.PrepareInvoker(*current_solution.invoker_factory,
511502
current_solution.construction_params);
512503

projects/miopen/src/include/miopen/solver/implicitgemm_ck_util.hpp

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <miopen/tensor_ops.hpp>
3434
#include <miopen/miopen_internal.h>
3535
#include <miopen/fusion/fusion_invoke_params.hpp>
36+
#include <miopen/solver/implicitgemm_util.hpp>
3637

3738
#if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL
3839
#include <ck/utility/data_type.hpp>
@@ -50,7 +51,30 @@ struct ProblemDescription;
5051
} // namespace conv
5152

5253
namespace solver {
54+
55+
static constexpr int CkSplitkAutoDeduce = -1;
56+
57+
template <int L, int H>
58+
inline static bool NextCKSplitkValue(int& v)
59+
{
60+
assert((IsTwoPower<L, H>(v) || v == CkSplitkAutoDeduce));
61+
if(v == H)
62+
{
63+
v = CkSplitkAutoDeduce;
64+
return false;
65+
}
66+
if(v == CkSplitkAutoDeduce)
67+
{
68+
v = L;
69+
return true;
70+
}
71+
72+
v *= 2;
73+
return false;
74+
}
75+
5376
#if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL
77+
5478
namespace conv {
5579
template <typename DataType>
5680
using DeviceOpGWrw = ck::tensor_operation::device::DeviceGroupedConvBwdWeight<
@@ -337,6 +361,35 @@ bool IsCKApplicable(const ProblemDescriptionType& problem)
337361
});
338362
}
339363

364+
template <typename DeviceOpType,
365+
typename CKArgsType,
366+
typename ProblemDescriptionType = miopen::conv::ProblemDescription>
367+
size_t GetCKSplitkMaxWorkspaceSize(const ProblemDescriptionType& problem)
368+
{
369+
const auto args = CKArgsType{problem};
370+
std::size_t max_workspace_size = 0;
371+
372+
const auto ptrs = DeviceOpType::GetInstances();
373+
for(auto& ptr : ptrs)
374+
{
375+
// Cycle `split_k` over {1,2,4,...,128} then `CkSplitkAutoDeduce`.
376+
// The loop then restarts from 1 for the next conv instance.
377+
auto split_k = 1;
378+
do
379+
{
380+
if(args.IsSupportedBySplitK(ptr, split_k))
381+
{
382+
auto workspace_size = args.GetCKSplitkWorkspaceSize(ptr, split_k);
383+
if(workspace_size > max_workspace_size)
384+
max_workspace_size = workspace_size;
385+
}
386+
} while(!NextCKSplitkValue<1, 128>(split_k));
387+
}
388+
389+
MIOPEN_LOG_I("Max workspace size reported by CK: " << max_workspace_size);
390+
return max_workspace_size;
391+
}
392+
340393
#define WORKAROUND_CK_ISSUE_1184 1
341394
#if WORKAROUND_CK_ISSUE_1184
342395
using WorkAroundHipEventProfiler = HipEventProfiler;
@@ -781,13 +834,14 @@ inline bool CKWrwRequireWorkspace(
781834
}
782835

783836
/// \todo move to a cpp file
784-
inline size_t GetWorkspaceSizeLayoutTransformConv(const miopen::conv::ProblemDescription& problem)
837+
inline size_t GetWorkspaceSizeLayoutTransformConv(const miopen::conv::ProblemDescription& problem,
838+
size_t ck_ws_size = 0)
785839
{
786840
if(problem.IsLayoutNHWC())
787841
{
788842
if(problem.GetDirection() == ::miopen::conv::Direction::BackwardWeights)
789843
{
790-
return GetCKAlphaBetaWorkspace(problem);
844+
return (ck_ws_size > 0) ? ck_ws_size : GetCKAlphaBetaWorkspace(problem);
791845
}
792846
return 0;
793847
}
@@ -796,10 +850,11 @@ inline size_t GetWorkspaceSizeLayoutTransformConv(const miopen::conv::ProblemDes
796850

797851
if(problem.GetDirection() == ::miopen::conv::Direction::BackwardWeights)
798852
{
799-
MultiBufferWorkspaceTraits wt({GetPackedSize(problem.GetIn()),
800-
GetPackedSize(problem.GetWeights()),
801-
GetPackedSize(problem.GetOut()),
802-
GetCKAlphaBetaWorkspace(problem)});
853+
MultiBufferWorkspaceTraits wt(
854+
{GetPackedSize(problem.GetIn()),
855+
GetPackedSize(problem.GetWeights()),
856+
GetPackedSize(problem.GetOut()),
857+
(ck_ws_size > 0) ? ck_ws_size : GetCKAlphaBetaWorkspace(problem)});
803858
return wt.GetSize();
804859
}
805860

@@ -1116,18 +1171,24 @@ ConvSolution InitInvokerFactoryNCHW(const ExecutionContext& ctx,
11161171

11171172
std::optional<CKBWDWeightBufferDescriptor> _ck_buff_des;
11181173

1119-
if(problem.IsDirectionBackwardWrW())
1120-
{
1121-
_ck_buff_des.emplace(GetCKAlphaBetaWorkspace(problem), 0);
1122-
}
1123-
11241174
auto ptr_iter = FindConvPtrByID(conv_ptrs, id_string);
11251175
if(ptr_iter == conv_ptrs.end())
11261176
{
11271177
MIOPEN_LOG_E("PerformanceConfig kernel '" + kernel_id + "' does not exist.");
11281178
return {miopenStatusInvalidValue};
11291179
}
11301180

1181+
if constexpr(std::is_same_v<CastType, miopen::conv::WrWInvokeParams>)
1182+
{
1183+
auto ck_ws_size = ck_args.GetCKSplitkWorkspaceSize(*ptr_iter, split_k.value_or(1));
1184+
_ck_buff_des.emplace(ck_ws_size, 0);
1185+
result.workspace_sz = GetWorkspaceSizeLayoutTransformConv(problem, ck_ws_size);
1186+
}
1187+
else
1188+
{
1189+
result.workspace_sz = GetWorkspaceSizeLayoutTransformConv(problem);
1190+
}
1191+
11311192
auto [_input1_tr_inst, _input2_tr_inst, _output_tr_inst, _output_init_tr_inst] =
11321193
internal::MakeTaggedTransposeInstances<CKArgsType>(
11331194
result, ctx, problem, ck_args, input1_op, input2_op, output_op, _ck_buff_des);
@@ -1234,8 +1295,6 @@ ConvSolution InitInvokerFactoryNCHW(const ExecutionContext& ctx,
12341295
output_tr_inst.ConvertTo(handle, kernels, conv_tensors);
12351296
};
12361297
};
1237-
1238-
result.workspace_sz = GetWorkspaceSizeLayoutTransformConv(problem);
12391298
#endif
12401299
return result;
12411300
}
@@ -1272,8 +1331,9 @@ ConvSolution InitInvokerFactoryNHWC(const ExecutionContext&,
12721331
ConvSolution result;
12731332
#if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL
12741333
miopenAlphaBetaCase_t alpha_beta_case = problem.GetAlphaBetaCase();
1275-
[[maybe_unused]] bool should_allocated_wrw_buffer =
1276-
ShouldAllocateWorkSpaceBufferForWRW(problem);
1334+
auto ck_args = CKArgsType{problem};
1335+
auto ck_ws_size = ck_args.GetCKSplitkWorkspaceSize(*ptr_iter, split_k.value_or(1));
1336+
[[maybe_unused]] bool should_allocated_wrw_buffer = ck_ws_size > 0;
12771337

12781338
result.invoker_factory = [kernel_id = kernel_id,
12791339
split_k = split_k,
@@ -1334,7 +1394,7 @@ ConvSolution InitInvokerFactoryNHWC(const ExecutionContext&,
13341394
}
13351395
};
13361396
};
1337-
result.workspace_sz = GetWorkspaceSizeLayoutTransformConv(problem);
1397+
result.workspace_sz = GetWorkspaceSizeLayoutTransformConv(problem, ck_ws_size);
13381398
#endif
13391399
return result;
13401400
}

projects/miopen/src/solver/conv/conv_hip_implicit_gemm_3d_grouped_wrw_xdlops.cpp

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -308,29 +308,32 @@ struct CKArgs
308308
template <typename ConvPtr>
309309
bool IsSupportedBy(const ConvPtr& conv_ptr) const
310310
{
311-
auto arg_ptr = MakeArgPtr(conv_ptr, nullptr, nullptr, nullptr, 1.0f, 0.0f, 1);
312-
// Creat dummy workspace to pass the ck IsSupportedArgument check.
313-
314-
int dummy_var = 1;
315-
conv_ptr->SetWorkSpacePointer(arg_ptr.get(), &dummy_var);
316-
311+
auto arg_ptr = MakeArgPtr(conv_ptr, nullptr, nullptr, nullptr, 1.0f, 0.0f, 1);
312+
auto workspace_size = conv_ptr->GetWorkSpaceSize(arg_ptr.get());
313+
if(workspace_size != 0)
314+
conv_ptr->SetWorkSpacePointer(arg_ptr.get(), &workspace_size);
317315
return conv_ptr->IsSupportedArgument(arg_ptr.get());
318316
}
319317

320318
template <typename ConvPtr>
321319
bool IsSupportedBySplitK(const ConvPtr& conv_ptr, int split_k) const
322320
{
323-
auto arg_ptr = MakeArgPtr(conv_ptr, nullptr, nullptr, nullptr, 1.0f, 0.0f, split_k);
324-
325-
if(CKWrwRequireWorkspace(G, C1, K1, data_type, alpha_beta_case))
321+
auto arg_ptr = MakeArgPtr(conv_ptr, nullptr, nullptr, nullptr, 1.0f, 0.0f, split_k);
322+
auto workspace_size = conv_ptr->GetWorkSpaceSize(arg_ptr.get());
323+
if(workspace_size != 0)
326324
{
327-
// Creat dummy workspace to pass the ck IsSupportedArgument check.
328-
int dummy_var = 1;
329-
conv_ptr->SetWorkSpacePointer(arg_ptr.get(), &dummy_var);
325+
conv_ptr->SetWorkSpacePointer(arg_ptr.get(), &workspace_size);
330326
}
331327
return conv_ptr->IsSupportedArgument(arg_ptr.get());
332328
}
333329

330+
template <typename ConvPtr>
331+
std::size_t GetCKSplitkWorkspaceSize(const ConvPtr& conv_ptr, int split_k) const
332+
{
333+
auto arg_ptr = MakeArgPtr(conv_ptr, nullptr, nullptr, nullptr, 1.0f, 0.0f, split_k);
334+
return conv_ptr->GetWorkSpaceSize(arg_ptr.get());
335+
}
336+
334337
// Dim members are int64 (and length/stride arrays use ck::long_index_t)
335338
// so the NCHW stride builder above (e.g. Di*Hi*Wi*G*C) does not silently
336339
// overflow on tensors whose contiguous stride exceeds INT_MAX. Argument
@@ -469,7 +472,7 @@ bool PerformanceConfigHipImplicitGemm3DGroupWrwXdlops::SetNextValue(
469472
}
470473
do
471474
{
472-
bool flag = NextTwoPower<1, 128>(split_k);
475+
bool flag = NextCKSplitkValue<1, 128>(split_k);
473476
if(!flag)
474477
{
475478
kernel_id = valid_kernels[index] + "+" + std::to_string(split_k);
@@ -536,11 +539,54 @@ bool ConvHipImplicitGemm3DGroupWrwXdlops::IsValidPerformanceConfig(
536539
return config.IsValid(problem);
537540
}
538541

542+
template <typename DataType>
543+
size_t
544+
ConvHipImplicitGemm3DGroupWrwXdlops::GetCKMaxWorkspaceSize(const ProblemDescription& problem) const
545+
{
546+
#if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL
547+
switch(problem.GetAlphaBetaCase())
548+
{
549+
case BILINEAR:
550+
return GetCKSplitkMaxWorkspaceSize<DeviceOpGBwdWeightBilinearPtrs<DataType>,
551+
CKArgs<DataType>>(problem);
552+
case SCALE:
553+
return GetCKSplitkMaxWorkspaceSize<DeviceOpGBwdWeightScalePtrs<DataType>, CKArgs<DataType>>(
554+
problem);
555+
default:
556+
return GetCKSplitkMaxWorkspaceSize<DeviceOpGBwdWeightDefaultPtrs<DataType>,
557+
CKArgs<DataType>>(problem);
558+
}
559+
#else
560+
return 0;
561+
#endif
562+
}
563+
564+
size_t
565+
ConvHipImplicitGemm3DGroupWrwXdlops::GetCKMaxWorkspaceSize(const ProblemDescription& problem) const
566+
{
567+
#if MIOPEN_BACKEND_HIP && MIOPEN_USE_COMPOSABLEKERNEL
568+
switch(problem.GetInDataType())
569+
{
570+
case miopenHalf: return GetCKMaxWorkspaceSize<ck::half_t>(problem);
571+
case miopenFloat: return GetCKMaxWorkspaceSize<float>(problem);
572+
case miopenInt8: return GetCKMaxWorkspaceSize<int8_t>(problem);
573+
case miopenBFloat16: return GetCKMaxWorkspaceSize<ck::bhalf_t>(problem);
574+
case miopenInt64:
575+
case miopenInt32:
576+
case miopenFloat8_fnuz:
577+
case miopenBFloat8_fnuz:
578+
case miopenDouble: break;
579+
}
580+
#endif
581+
return 0; // other types not applicable for this solver
582+
}
583+
539584
size_t
540585
ConvHipImplicitGemm3DGroupWrwXdlops::GetWorkspaceSize(const ExecutionContext&,
541586
const ProblemDescription& problem) const
542587
{
543-
return GetWorkspaceSizeLayoutTransformConv(problem);
588+
auto ck_ws_size = GetCKMaxWorkspaceSize(problem);
589+
return GetWorkspaceSizeLayoutTransformConv(problem, ck_ws_size);
544590
}
545591

546592
PerformanceConfigHipImplicitGemm3DGroupWrwXdlops

0 commit comments

Comments
 (0)