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