Skip to main content

cubek_convolution/kernels/forward/
args.rs

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
95/// Create the input runtime arguments for a matmul kernel that works on concrete inputs and
96/// output (not fused).
97pub 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
109/// Create the output runtime arguments for a matmul kernel that works on concrete inputs and
110/// output (not fused).
111pub 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        // Im2col needs extra checking because if `k` is OOB it wraps around the kernel and can load
272        // in-bounds but not in-kernel elements. Other TMA layouts are always outside the shape if
273        // any matrix dim is out of bounds.
274        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
314/// The upper corner bounds the pixel box the im2col tensor map may be addressed in: a base
315/// coordinate is legal only while it stays under `in_shape + upper_corner`.
316pub(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            // `problem.padding` is the padding at the *beginning* of each dimension, so it cannot bound the
331            // far edge on its own. Where padding is asymmetric, `out_shape` is derived from the leading *and*
332            // trailing padding while the corner would only see the leading half, and the last output position
333            // addresses one pixel past the box. So take whichever is larger.
334            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}