@@ -774,30 +774,43 @@ def _launch_function_streamk(self, config: KernelConfig) -> str:
774774 "tree" : "Tree" ,
775775 }[config .reduction_strategy ]
776776 return f"""
777- static float launch(const ck_tile::StreamKHostArgs& args, const stream_config& stream) {{
778- constexpr auto scheduler = { self .tm .SCHEDULER_TO_CK [config .trait .scheduler ]} ;
779- constexpr auto ReductionStrategy = ck_tile::StreamKReductionStrategy::{ reduction_ck } ;
780-
781- using GemmUniversalTraits = TileGemmUniversalTraits<kPadM, kPadN, kPadK, DoubleSmemBuffer,
782- ALayout, BLayout, CLayout, TransposeC,
783- UseStructuredSparsity, UsePersistentKernel,
784- NumWaveGroups, Preshuffle>;
785-
786- using UniversalGemmProblem = UniversalGemmPipelineProblem<
787- ADataType, BDataType, AccDataType, TileShape, GemmUniversalTraits, scheduler>;
788-
789- using GemmPipeline = { self .tm .PIPELINE_TO_CK [config .trait .pipeline ]} <UniversalGemmProblem>;
790- { self ._epilogue_code (config )}
777+ // ---- Stream-K kernel type, hoisted to struct scope so the workspace API
778+ // ---- (GetWorkSpaceSize + external-workspace launch) can reuse the same type. ----
779+ static constexpr auto SkScheduler = { self .tm .SCHEDULER_TO_CK [config .trait .scheduler ]} ;
780+ static constexpr auto SkReductionStrategy = ck_tile::StreamKReductionStrategy::{ reduction_ck } ;
781+ static constexpr int SkBlockPerCu = { config .k_block_per_cu } ;
782+
783+ using SkGemmUniversalTraits = TileGemmUniversalTraits<kPadM, kPadN, kPadK, DoubleSmemBuffer,
784+ ALayout, BLayout, CLayout, TransposeC,
785+ UseStructuredSparsity, UsePersistentKernel,
786+ NumWaveGroups, Preshuffle>;
787+ using SkUniversalGemmProblem = UniversalGemmPipelineProblem<
788+ ADataType, BDataType, AccDataType, TileShape, SkGemmUniversalTraits, SkScheduler>;
789+ using SkGemmPipeline = { self .tm .PIPELINE_TO_CK [config .trait .pipeline ]} <SkUniversalGemmProblem>;
790+ { self ._epilogue_code (config )}
791+ using SkStreamKTilePartitioner =
792+ ck_tile::StreamKTilePartitioner<TileShape, SkReductionStrategy, UsePersistentKernel>;
793+ using StreamKGemmKernel =
794+ ck_tile::StreamKKernel<SkStreamKTilePartitioner, SkGemmPipeline, GemmEpilogue>;
795+
796+ // Device workspace (bytes) this kernel needs for `args`. 0 for Atomic;
797+ // >0 for Linear/Tree. The Dispatcher uses this to size the buffer it owns.
798+ static std::size_t GetWorkSpaceSize(const ck_tile::StreamKHostArgs& args) {{
799+ auto kargs = StreamKGemmKernel::MakeKernelArgs(args);
800+ return StreamKGemmKernel::GetWorkSpaceSize(kargs);
801+ }}
791802
792- using StreamKTilePartitioner =
793- ck_tile::StreamKTilePartitioner<TileShape, ReductionStrategy, UsePersistentKernel>;
794- using StreamKGemmKernel =
795- ck_tile::StreamKKernel<StreamKTilePartitioner, GemmPipeline, GemmEpilogue>;
803+ // Whether the kernel can actually partition this problem (enough tiles across
804+ // CUs). Lets the dispatcher's supports() reject too-small problems and fall
805+ // back to a non-Stream-K kernel instead of throwing at launch.
806+ static bool IsSupported(const ck_tile::StreamKHostArgs& args) {{
807+ return StreamKGemmKernel::IsSupportedArgument(StreamKGemmKernel::MakeKernelArgs(args));
808+ }}
796809
810+ // Internal-workspace launch: allocates a fresh DeviceMem on every call.
811+ // Kept unchanged for the bridge ctypes lib and the standalone 03 driver.
812+ static float launch(const ck_tile::StreamKHostArgs& args, const stream_config& stream) {{
797813 auto kargs = StreamKGemmKernel::MakeKernelArgs(args);
798-
799- // Allocate the reduction workspace INTERNALLY (dispatcher idiom), not via
800- // an external pointer the way Tile Engine does.
801814 const auto ws_size = StreamKGemmKernel::GetWorkSpaceSize(kargs);
802815 ck_tile::DeviceMem workspace_dev(ws_size);
803816 workspace_dev.SetZero();
@@ -809,42 +822,66 @@ def _launch_function_streamk(self, config: KernelConfig) -> str:
809822
810823 const dim3 grids = StreamKGemmKernel::GridSize(kargs.tile_partitioner);
811824 const dim3 blocks = StreamKGemmKernel::BlockSize();
812- constexpr int kBlockPerCu = { config .k_block_per_cu } ;
813825
814- // Atomic reduction accumulates into C, so reset it before every run.
815- // Zero only the used MxN region honoring the leading dimension
816- // (stride_E): a flat M*N memset would skip elements when C has a padded
817- // stride and corrupt the atomic accumulation. hipMemset2DAsync handles
818- // the pitched 2D extent and its status is checked, not discarded.
826+ // Atomic reduction accumulates into C, so reset buffers before each run.
819827 auto reset_data_buffers = [&]() {{
820- if constexpr (ReductionStrategy == ck_tile::StreamKReductionStrategy::Atomic) {{
821- constexpr size_t c_elem = sizeof(CDataType);
822- const size_t c_pitch = static_cast<size_t>(args.stride_E) * c_elem;
823- hipError_t memset_status;
824- if constexpr (std::is_same_v<CLayout, ck_tile::tensor_layout::gemm::RowMajor>) {{
825- // Row-major C: M rows of N elements, row pitch = stride_E.
826- memset_status = hipMemset2DAsync(args.e_ptr, c_pitch, 0,
827- static_cast<size_t>(args.N) * c_elem,
828- static_cast<size_t>(args.M), stream.stream_id_);
829- }} else {{
830- // Column-major C: N columns of M elements, column pitch = stride_E.
831- memset_status = hipMemset2DAsync(args.e_ptr, c_pitch, 0,
832- static_cast<size_t>(args.M) * c_elem,
833- static_cast<size_t>(args.N), stream.stream_id_);
834- }}
835- if(memset_status != hipSuccess) {{
836- throw std::runtime_error(
837- std::string("hipMemset2DAsync failed to zero C: ") +
838- hipGetErrorString(memset_status));
839- }}
828+ if constexpr (SkReductionStrategy == ck_tile::StreamKReductionStrategy::Atomic) {{
829+ // Stride-aware: CLayout is row-major with stride_E elems/row, so a
830+ // padded C is zeroed correctly (not just the contiguous M*N case).
831+ (void)hipMemset2DAsync(args.e_ptr,
832+ args.stride_E * sizeof(CDataType),
833+ 0,
834+ args.N * sizeof(CDataType),
835+ args.M,
836+ stream.stream_id_);
840837 }} else {{
841838 workspace_dev.SetZero();
842839 }}
843840 }};
844841 std::function<void()> preprocess = reset_data_buffers;
845842
846843 float ave_time = launch_kernel_time_mask(stream, preprocess,
847- make_kernel<kBlockPerCu>(StreamKGemmKernel{{}}, grids, blocks, 0, kargs));
844+ make_kernel<SkBlockPerCu>(StreamKGemmKernel{{}}, grids, blocks, 0, kargs));
845+ return ave_time;
846+ }}
847+
848+ // External-workspace launch (PR-D): the Dispatcher owns and reuses the
849+ // reduction buffer and passes it in. `workspace` may be null for Atomic
850+ // (size 0). The per-iteration reset stays here because it needs CDataType
851+ // and the reduction strategy, which the dtype-erased Dispatcher lacks.
852+ static float launch(const ck_tile::StreamKHostArgs& args, const stream_config& stream,
853+ void* workspace) {{
854+ auto kargs = StreamKGemmKernel::MakeKernelArgs(args);
855+ const auto ws_size = StreamKGemmKernel::GetWorkSpaceSize(kargs);
856+ if (workspace != nullptr) {{
857+ StreamKGemmKernel::SetWorkSpacePointer(kargs, workspace);
858+ }}
859+
860+ if (!StreamKGemmKernel::IsSupportedArgument(kargs)) {{
861+ throw std::runtime_error("Arguments not supported for stream-k kernel!");
862+ }}
863+
864+ const dim3 grids = StreamKGemmKernel::GridSize(kargs.tile_partitioner);
865+ const dim3 blocks = StreamKGemmKernel::BlockSize();
866+
867+ auto reset_data_buffers = [&]() {{
868+ if constexpr (SkReductionStrategy == ck_tile::StreamKReductionStrategy::Atomic) {{
869+ // Stride-aware: CLayout is row-major with stride_E elems/row, so a
870+ // padded C is zeroed correctly (not just the contiguous M*N case).
871+ (void)hipMemset2DAsync(args.e_ptr,
872+ args.stride_E * sizeof(CDataType),
873+ 0,
874+ args.N * sizeof(CDataType),
875+ args.M,
876+ stream.stream_id_);
877+ }} else {{
878+ (void)hipMemsetAsync(workspace, 0, ws_size, stream.stream_id_);
879+ }}
880+ }};
881+ std::function<void()> preprocess = reset_data_buffers;
882+
883+ float ave_time = launch_kernel_time_mask(stream, preprocess,
884+ make_kernel<SkBlockPerCu>(StreamKGemmKernel{{}}, grids, blocks, 0, kargs));
848885 return ave_time;
849886 }}"""
850887
@@ -899,12 +936,49 @@ def generate(
899936 output_dtype = self .tm .get_output_dtype (self .datatype )
900937 rel_path = kernel_path .relative_to (output_dir )
901938
939+ # Stream-K kernels need the Stream-K backend (StreamKHostArgs launch) and
940+ # the SK key fields, so the registry can tell atomic/linear/tree apart and
941+ # the right launch path compiles. All other variants use the regular backend.
942+ is_streamk = config .variant == GemmVariant .STREAM_K
943+ backend_inc = (
944+ "generated_tile_backend_streamk.hpp"
945+ if is_streamk
946+ else "generated_kernel_backend.hpp"
947+ )
948+
949+ sk_fields = ""
950+ if is_streamk :
951+ rs = {"atomic" : "Atomic" , "linear" : "Linear" , "tree" : "Tree" }[
952+ config .reduction_strategy
953+ ]
954+ ws = str (config .reduction_strategy != "atomic" ).lower ()
955+ sk_fields = f"""
956+ key.algorithm.pad_m = { str (config .trait .pad_m ).lower ()} ;
957+ key.algorithm.pad_n = { str (config .trait .pad_n ).lower ()} ;
958+ key.algorithm.pad_k = { str (config .trait .pad_k ).lower ()} ;
959+ key.algorithm.streamk = true;
960+ key.algorithm.reduction_strategy = ::ck_tile::dispatcher::ReductionStrategy::{ rs } ;
961+ key.algorithm.workspace = { ws } ;"""
962+
963+ if is_streamk :
964+ ret_stmt = (
965+ "return backends::create_generated_streamk_kernel<KernelStruct, "
966+ "KernelStruct::ADataType, KernelStruct::BDataType, "
967+ "KernelStruct::CDataType, KernelStruct::AccDataType>"
968+ f'(key, "{ kernel_name } ");'
969+ )
970+ else :
971+ ret_stmt = (
972+ "return std::make_shared<backends::GeneratedKernelInstance<KernelStruct>>"
973+ f'(key, "{ kernel_name } ");'
974+ )
975+
902976 return f"""// SPDX-License-Identifier: MIT
903977// Auto-generated dispatcher wrapper
904978#pragma once
905979
906980#include "ck_tile/dispatcher.hpp"
907- #include "ck_tile/dispatcher/backends/generated_kernel_backend.hpp "
981+ #include "ck_tile/dispatcher/backends/{ backend_inc } "
908982#include "{ rel_path } "
909983
910984namespace ck_tile {{
@@ -955,11 +1029,11 @@ def generate(
9551029 key.algorithm.persistent = { str (config .trait .persistent ).lower ()} ;
9561030 key.algorithm.preshuffle = { str (config .preshuffle ).lower ()} ;
9571031 key.algorithm.transpose_c = false;
958- key.algorithm.num_wave_groups = { config .num_wave_groups } ;
959-
1032+ key.algorithm.num_wave_groups = { config .num_wave_groups } ;{ sk_fields }
1033+
9601034 key.gfx_arch = gfx_arch;
961-
962- return std::make_shared<backends::GeneratedKernelInstance<KernelStruct>>(key, " { kernel_name } ");
1035+
1036+ { ret_stmt }
9631037}}
9641038
9651039}}}}}}
0 commit comments