1use cubecl::{
2 Runtime,
3 client::ComputeClient,
4 prelude::*,
5 std::tensor::{
6 launch::ViewArg,
7 layout::{
8 VirtualLayoutLaunch,
9 chain::{Chain, ChainLaunch},
10 },
11 },
12 zspace::{shape, strides},
13};
14use cubek_matmul::{
15 args::*,
16 components::global::memory::{GlobalLayoutConfig, NoopLayout, NoopLayoutLaunch},
17 definition::{BatchMatmulBlueprint, Blueprint, MatmulElems},
18 routines::BatchMatmulRoutine,
19};
20use cubek_std::launch::tma::remap_storage_for_tma;
21use cubek_std::{InputBinding, MatrixLayout, stage::SwizzleMode};
22use enumset::EnumSet;
23
24use crate::components::{
25 ConvolutionParams, ConvolutionProblem,
26 global::{
27 args::{RuntimeArgs, RuntimeArgsLaunch},
28 layout::{
29 BiasLayout, Im2colLayout, Im2colLayoutLaunch, NhwcCheck, NhwcLayout, NhwcLayoutLaunch,
30 OutLayout, OutLayoutLaunch, TmaIm2colLayout, TmaIm2colLayoutLaunch, WeightLayout,
31 WeightLayoutLaunch,
32 },
33 },
34};
35
36pub trait ConcreteArgs<A: BatchMatmulRoutine<RuntimeArgs>>:
37 MatmulArgs<
38 Input<Vector<Lhs, LhsSize>, Vector<Rhs, RhsSize>, Vector<Acc, AccSize>>: ConcreteInputsFactory<A>,
39 Output<Vector<Acc, AccSize>>: ConcreteOutputFactory<A>,
40 Config = RuntimeArgs,
41 >
42{
43 fn adjust_problem<R: Runtime>(
44 client: &ComputeClient<R>,
45 problem: ConvolutionProblem,
46 blueprint: &A::Blueprint,
47 dtypes: &MatmulElems,
48 ) -> ConvolutionProblem;
49}
50
51impl<A: BatchMatmulRoutine<RuntimeArgs>> ConcreteArgs<A> for TensorArgs<RuntimeArgs> {
52 fn adjust_problem<R: Runtime>(
53 client: &ComputeClient<R>,
54 mut problem: ConvolutionProblem,
55 _blueprint: &A::Blueprint,
56 dtypes: &MatmulElems,
57 ) -> ConvolutionProblem {
58 let load_width = client.properties().hardware.load_width;
59 let channel_align = load_width as usize / dtypes.lhs_global.size_bits();
60 let padded_channels = problem.channels.next_multiple_of(channel_align);
61 let shape_k = problem.kernel_size.iter().product::<u32>() as usize * padded_channels;
62
63 problem.k = shape_k;
64 problem.padded_channels = padded_channels;
65
66 problem
67 }
68}
69
70impl<A: BatchMatmulRoutine<RuntimeArgs, Blueprint = BatchMatmulBlueprint>> ConcreteArgs<A>
71 for TensorMapArgs<RuntimeArgs>
72{
73 fn adjust_problem<R: Runtime>(
74 _client: &ComputeClient<R>,
75 mut problem: ConvolutionProblem,
76 blueprint: &BatchMatmulBlueprint,
77 _dtypes: &MatmulElems,
78 ) -> ConvolutionProblem {
79 let channel_align = match blueprint.swizzle_modes.lhs {
80 SwizzleMode::None => blueprint.tiling_scheme.tile_size.k() as usize,
81 _ => blueprint.tiling_scheme.elements_per_stage_along_k() as usize,
82 };
83 let padded_channels = problem.channels.next_multiple_of(channel_align);
84 let shape_k = problem.kernel_size.iter().product::<u32>() as usize * padded_channels;
85
86 problem.k = shape_k;
87 problem.padded_channels = padded_channels;
88
89 problem
90 }
91}
92
93pub trait ConcreteInputsFactory<A: BatchMatmulRoutine<RuntimeArgs>>: LaunchArg {
96 #[allow(clippy::too_many_arguments)]
97 fn create<R: Runtime>(
98 lhs: InputBinding<R>,
99 rhs: InputBinding<R>,
100 bias: Option<InputBinding<R>>,
101 blueprint: &A::Blueprint,
102 problem: &ConvolutionProblem,
103 dtypes: &MatmulElems,
104 ) -> (Self::RuntimeArg<R>, RuntimeArgsLaunch<R>);
105}
106
107pub trait ConcreteOutputFactory<A: BatchMatmulRoutine<RuntimeArgs>>: LaunchArg {
110 fn create<R: Runtime>(
111 out: TensorBinding<R>,
112 blueprint: &A::Blueprint,
113 problem: &ConvolutionProblem,
114 dtypes: &MatmulElems,
115 ) -> Self::RuntimeArg<R>;
116}
117
118impl<Lhs: CubePrimitive, Rhs: CubePrimitive, EO: CubePrimitive, A: BatchMatmulRoutine<RuntimeArgs>>
119 ConcreteInputsFactory<A> for TensorInputs<Lhs, Rhs, EO>
120{
121 fn create<R: Runtime>(
122 lhs: InputBinding<R>,
123 rhs: InputBinding<R>,
124 bias: Option<InputBinding<R>>,
125 blueprint: &A::Blueprint,
126 problem: &ConvolutionProblem,
127 _dtypes: &MatmulElems,
128 ) -> (Self::RuntimeArg<R>, RuntimeArgsLaunch<R>) {
129 type LhsLayout = Chain<NhwcLayout, Im2colLayout>;
130 type RhsLayout = Chain<NhwcLayout, WeightLayout>;
131
132 let padded_channels = problem.padded_channels as u32;
133 let conv_params = ConvolutionParams::from_problem(problem);
134
135 let layout_lhs = Im2colLayoutLaunch::from_args(
136 problem,
137 conv_params,
138 blueprint.lhs_global_layout_config(),
139 );
140 let layout_rhs =
141 WeightLayoutLaunch::from_args(problem, blueprint.rhs_global_layout_config());
142
143 let layout_lhs = {
144 let mut checks = EnumSet::empty();
145 if problem.should_check_spatial_bounds() {
146 checks.insert(NhwcCheck::Spatial);
147 }
148 if problem.should_check_channel() {
149 checks.insert(NhwcCheck::Channel);
150 }
151 let global = NhwcLayoutLaunch::checked(checks);
152 ChainLaunch::new(global, layout_lhs)
153 };
154 let layout_rhs = {
155 let mut checks = EnumSet::empty();
156 if problem.should_check_channel() {
157 checks.insert(NhwcCheck::Channel);
158 }
159 let global = NhwcLayoutLaunch::checked(checks);
160 ChainLaunch::new(global, layout_rhs)
161 };
162
163 let inputs = TensorInputsLaunch::new(
164 VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()),
165 ViewArg::new_tensor::<LhsLayout>(lhs.into_data().into_tensor_arg(), layout_lhs),
166 VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()),
167 ViewArg::new_tensor::<RhsLayout>(rhs.into_data().into_tensor_arg(), layout_rhs),
168 bias.as_ref()
169 .map(|_| VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()))
170 .into(),
171 bias.map(|bias| {
172 ViewArg::new_tensor::<BiasLayout>(bias.into_data().into_tensor_arg(), ())
173 })
174 .into(),
175 );
176
177 let runtime_args = RuntimeArgsLaunch::new(
178 problem.k as u32,
179 problem.channels as u32,
180 padded_channels,
181 conv_params.operation,
182 );
183
184 (inputs, runtime_args)
185 }
186}
187
188impl<EG: CubePrimitive, A: BatchMatmulRoutine<RuntimeArgs>> ConcreteOutputFactory<A>
189 for TensorOutput<EG>
190{
191 fn create<R: Runtime>(
192 out: TensorBinding<R>,
193 blueprint: &A::Blueprint,
194 problem: &ConvolutionProblem,
195 _dtypes: &MatmulElems,
196 ) -> Self::RuntimeArg<R> {
197 type Layout = Chain<NhwcLayout, OutLayout>;
198
199 let global = NhwcLayoutLaunch::unchecked();
200 let layout = OutLayoutLaunch::from_args(problem, blueprint.out_global_layout_config());
201 let layout = ChainLaunch::new(global, layout);
202 let view = ViewArg::new_tensor::<Layout>(out.into_tensor_arg(), layout);
203 let batch = VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new());
204 TensorOutputLaunch::new(view, batch)
205 }
206}
207
208impl<
209 Lhs: CubePrimitive,
210 Rhs: CubePrimitive,
211 EO: CubePrimitive,
212 A: BatchMatmulRoutine<RuntimeArgs, Blueprint = BatchMatmulBlueprint>,
213> ConcreteInputsFactory<A> for TensorMapInputs<Lhs, Rhs, EO>
214{
215 fn create<R: Runtime>(
216 lhs: InputBinding<R>,
217 rhs: InputBinding<R>,
218 bias: Option<InputBinding<R>>,
219 blueprint: &BatchMatmulBlueprint,
220 problem: &ConvolutionProblem,
221 dtypes: &MatmulElems,
222 ) -> (Self::RuntimeArg<R>, RuntimeArgsLaunch<R>) {
223 let tiling_scheme = blueprint.tiling_scheme;
224 let stage_m = tiling_scheme.elements_per_stage_along_m();
225 let stage_n = tiling_scheme.elements_per_stage_along_n();
226
227 let tile_size_k = match blueprint.swizzle_modes.lhs {
228 SwizzleMode::None => tiling_scheme.tile_size.k,
229 _ => tiling_scheme.elements_per_stage_along_k(),
230 };
231
232 let mut stage_size_rhs = shape![1; problem.dimensionality.num_dims()];
233 stage_size_rhs.insert(0, stage_n as usize);
234 stage_size_rhs.push(tile_size_k as usize);
235
236 let lhs_elem = remap_storage_for_tma(dtypes.lhs_stage);
237
238 let mut elem_stride = strides![1; 2 + problem.stride.len()];
239
240 for (i, stride) in problem.stride.iter().enumerate() {
241 elem_stride[i + 1] = *stride as usize;
242 }
243
244 let lhs = TensorMapArg::new(
245 Im2colArgs {
246 pixel_box_lower_corner: calculate_lower_corner(&problem.padding),
247 pixel_box_upper_corner: calculate_upper_corner(
248 &problem.padding,
249 &problem.kernel_size,
250 &problem.dilation,
251 ),
252 channels_per_pixel: tile_size_k,
253 pixels_per_column: stage_m,
254 },
255 lhs.clone().into_data().into_tensor_arg(),
256 lhs_elem,
257 )
258 .with_elem_stride(elem_stride)
259 .with_swizzle(blueprint.swizzle_modes.lhs.into());
260
261 let rhs = TensorMapArg::new(
262 TiledArgs {
263 tile_size: stage_size_rhs,
264 },
265 rhs.clone().into_data().into_tensor_arg(),
266 dtypes.rhs_global,
267 )
268 .with_swizzle(blueprint.swizzle_modes.rhs.into());
269
270 let padded_channels = problem.padded_channels as u32;
271 let shape_k = problem.k as u32;
272
273 let stages_lhs = A::num_stages().lhs;
277 let stages_size_k = blueprint.tiling_scheme.elements_per_stage_along_k() * stages_lhs;
278 let check_kernel = !shape_k.is_multiple_of(stages_size_k);
279 let lhs_layout = TmaIm2colLayoutLaunch::from_args(problem, check_kernel);
280 let rhs_layout = WeightLayoutLaunch::from_args(
281 problem,
282 GlobalLayoutConfig {
283 check_row_bounds: false,
284 check_col_bounds: false,
285 matrix_layout: MatrixLayout::ColMajor,
286 },
287 );
288
289 let bias = bias
290 .map(|bias| ViewArg::new_tensor::<BiasLayout>(bias.into_data().into_tensor_arg(), ()));
291
292 let inputs = TensorMapInputsLaunch::new(
293 ViewArg::new_tensor_map_im2col::<TmaIm2colLayout, _, _>(lhs, lhs_layout),
294 ViewArg::new_tensor_map_tiled::<WeightLayout>(rhs, rhs_layout),
295 bias.into(),
296 ComptimeOptionArgs::Some(VirtualLayoutLaunch::new::<NoopLayout>(
297 NoopLayoutLaunch::new(),
298 )),
299 );
300
301 let runtime_args = RuntimeArgsLaunch::new(
302 shape_k,
303 problem.channels as u32,
304 padded_channels,
305 problem.operation,
306 );
307
308 (inputs, runtime_args)
309 }
310}
311
312fn calculate_lower_corner(padding: &[i32]) -> Vec<i32> {
313 padding.iter().map(|padding| -*padding).collect()
314}
315
316fn calculate_upper_corner(padding: &[i32], kernel_size: &[u32], dilation: &[u32]) -> Vec<i32> {
317 padding
318 .iter()
319 .zip(kernel_size)
320 .zip(dilation)
321 .map(|((padding, kernel_size), dilation)| {
322 *padding - (*kernel_size - 1) as i32 * *dilation as i32
323 })
324 .collect()
325}