Skip to main content

cubek_convolution/kernels/backward_data/
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::{
15    definition::MatmulElems,
16    multi_level::{
17        BatchMatmulRoutine,
18        args::*,
19        components::global::memory::{GlobalLayoutConfig, NoopLayout, NoopLayoutLaunch},
20        definition::{BatchMatmulBlueprint, Blueprint},
21    },
22};
23use cubek_std::{InputBinding, MatrixLayout, launch::tma::remap_storage_for_tma};
24
25use crate::components::{
26    ConvolutionParams, ConvolutionProblem,
27    global::{
28        args::{RuntimeArgs, RuntimeArgsLaunch},
29        layout::{
30            Im2colLayout, Im2colLayoutLaunch, NhwcCheck, NhwcLayout, NhwcLayoutLaunch, OutLayout,
31            OutLayoutLaunch, TmaIm2colLayout, TmaIm2colLayoutLaunch, WeightLayout,
32            WeightLayoutLaunch,
33        },
34    },
35};
36
37pub trait ConcreteArgs<A: BatchMatmulRoutine<RuntimeArgs>>:
38    MatmulArgs<
39        Input<Vector<Lhs, LhsSize>, Vector<Rhs, RhsSize>, Vector<Acc, AccSize>>: ConcreteInputsFactory<A>,
40        Output<Vector<Acc, AccSize>>: ConcreteOutputFactory<A>,
41        Config = RuntimeArgs,
42    >
43{
44    fn adjust_problem(
45        client: &Client,
46        problem: ConvolutionProblem,
47        selection: &A::Blueprint,
48        dtypes: &MatmulElems,
49    ) -> ConvolutionProblem;
50}
51
52impl<A: BatchMatmulRoutine<RuntimeArgs>> ConcreteArgs<A> for TensorArgs<RuntimeArgs> {
53    fn adjust_problem(
54        client: &Client,
55        mut problem: ConvolutionProblem,
56        _blueprint: &A::Blueprint,
57        dtypes: &MatmulElems,
58    ) -> ConvolutionProblem {
59        let io_width = client.properties().io_width();
60        let channel_align = io_width as usize / dtypes.lhs_global.size_bits();
61        let padded_channels = problem.out_channels.next_multiple_of(channel_align);
62        let shape_k = problem.kernel_size.iter().product::<u32>() as usize * padded_channels;
63
64        problem.k = shape_k;
65        problem.padded_channels = padded_channels;
66
67        problem
68    }
69}
70
71impl<A: BatchMatmulRoutine<RuntimeArgs, Blueprint = BatchMatmulBlueprint>> ConcreteArgs<A>
72    for TensorMapArgs<RuntimeArgs>
73{
74    fn adjust_problem(
75        _client: &Client,
76        mut problem: ConvolutionProblem,
77        blueprint: &BatchMatmulBlueprint,
78        _dtypes: &MatmulElems,
79    ) -> ConvolutionProblem {
80        let channel_align = blueprint.tiling_scheme.tile_size.k() as usize;
81        let padded_channels = problem.out_channels.next_multiple_of(channel_align);
82        let shape_k = problem.kernel_size.iter().product::<u32>() as usize * padded_channels;
83
84        problem.k = shape_k;
85        problem.padded_channels = padded_channels;
86
87        problem
88    }
89}
90
91/// Create the input runtime arguments for a matmul kernel that works on concrete inputs and
92/// output (not fused).
93pub trait ConcreteInputsFactory<A: BatchMatmulRoutine<RuntimeArgs>>: LaunchArg {
94    #[allow(clippy::too_many_arguments)]
95    fn create(
96        out_grad: InputBinding,
97        weights: InputBinding,
98        blueprint: &A::Blueprint,
99        problem: &ConvolutionProblem,
100        dtypes: &MatmulElems,
101    ) -> (Self::RuntimeArg, RuntimeArgsLaunch);
102}
103
104/// Create the output runtime arguments for a matmul kernel that works on concrete inputs and
105/// output (not fused).
106pub trait ConcreteOutputFactory<A: BatchMatmulRoutine<RuntimeArgs>>: LaunchArg {
107    fn create(
108        out: TensorBinding,
109        blueprint: &A::Blueprint,
110        problem: &ConvolutionProblem,
111    ) -> Self::RuntimeArg;
112}
113
114impl<Lhs: CubePrimitive, Rhs: CubePrimitive, EO: CubePrimitive, A: BatchMatmulRoutine<RuntimeArgs>>
115    ConcreteInputsFactory<A> for TensorInputs<Lhs, Rhs, EO>
116{
117    fn create(
118        out_grad: InputBinding,
119        weights: InputBinding,
120        blueprint: &A::Blueprint,
121        problem: &ConvolutionProblem,
122        _dtypes: &MatmulElems,
123    ) -> (Self::RuntimeArg, RuntimeArgsLaunch) {
124        type LhsLayout = Chain<NhwcLayout, Im2colLayout>;
125        type RhsLayout = Chain<NhwcLayout, WeightLayout>;
126
127        let padded_channels = problem.padded_channels as u32;
128        let params = ConvolutionParams::from_problem(problem);
129
130        let layout_lhs =
131            Im2colLayoutLaunch::from_args(problem, params, blueprint.lhs_global_layout_config());
132        let layout_rhs =
133            WeightLayoutLaunch::from_args(problem, blueprint.rhs_global_layout_config());
134
135        let layout_lhs = {
136            let mut checks = EnumSet::empty();
137            if problem.should_check_spatial_bounds() {
138                checks.insert(NhwcCheck::Spatial);
139            }
140            if problem.should_check_channel() {
141                checks.insert(NhwcCheck::Channel);
142            }
143            let global = NhwcLayoutLaunch::checked(checks);
144            ChainLaunch::new(global, layout_lhs)
145        };
146        let layout_rhs = {
147            let mut checks = EnumSet::empty();
148            if problem.should_check_channel() {
149                checks.insert(NhwcCheck::Batch);
150            }
151            let global = NhwcLayoutLaunch::checked(checks);
152            ChainLaunch::new(global, layout_rhs)
153        };
154
155        let inputs = TensorInputsLaunch::new(
156            VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()),
157            ViewArg::new_tensor::<LhsLayout>(out_grad.into_data().into_tensor_arg(), layout_lhs),
158            VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new()),
159            ViewArg::new_tensor::<RhsLayout>(weights.into_data().into_tensor_arg(), layout_rhs),
160            ComptimeOptionArgs::None,
161            ComptimeOptionArgs::None,
162        );
163
164        let runtime_args = RuntimeArgsLaunch::new(
165            problem.k as u32,
166            problem.out_channels as u32,
167            padded_channels,
168            problem.operation,
169        );
170
171        (inputs, runtime_args)
172    }
173}
174
175impl<EG: CubePrimitive, A: BatchMatmulRoutine<RuntimeArgs>> ConcreteOutputFactory<A>
176    for TensorOutput<EG>
177{
178    fn create(
179        out: TensorBinding,
180        blueprint: &A::Blueprint,
181        problem: &ConvolutionProblem,
182    ) -> Self::RuntimeArg {
183        type Layout = Chain<NhwcLayout, OutLayout>;
184
185        let global = NhwcLayoutLaunch::unchecked();
186        let layout = OutLayoutLaunch::from_args(problem, blueprint.out_global_layout_config());
187        let layout = ChainLaunch::new(global, layout);
188        let view = ViewArg::new_tensor::<Layout>(out.into_tensor_arg(), layout);
189        let batch = VirtualLayoutLaunch::new::<NoopLayout>(NoopLayoutLaunch::new());
190        TensorOutputLaunch::new(view, batch)
191    }
192}
193
194impl<
195    Lhs: CubePrimitive,
196    Rhs: CubePrimitive,
197    EO: CubePrimitive,
198    A: BatchMatmulRoutine<RuntimeArgs, Blueprint = BatchMatmulBlueprint>,
199> ConcreteInputsFactory<A> for TensorMapInputs<Lhs, Rhs, EO>
200{
201    fn create(
202        out_grad: InputBinding,
203        weights: InputBinding,
204        blueprint: &BatchMatmulBlueprint,
205        problem: &ConvolutionProblem,
206        dtypes: &MatmulElems,
207    ) -> (Self::RuntimeArg, RuntimeArgsLaunch) {
208        type LhsLayout = TmaIm2colLayout;
209        type RhsLayout = WeightLayout;
210
211        let tiling_scheme = blueprint.tiling_scheme;
212        let stage_m = tiling_scheme.elements_per_stage_along_m();
213        let stage_n = tiling_scheme.elements_per_stage_along_n();
214        let stage_k = tiling_scheme.elements_per_stage_along_k();
215        let tile_size_k = tiling_scheme.tile_size.k;
216
217        let mut stage_size_rhs = shape![1; problem.dimensionality.num_dims()];
218        stage_size_rhs.insert(0, stage_k as usize);
219        stage_size_rhs.push(stage_n as usize);
220
221        let lhs_elem = remap_storage_for_tma(dtypes.lhs_stage);
222
223        let mut elem_stride = strides![1; 2 + problem.stride.len()];
224
225        for (i, stride) in problem.stride.iter().enumerate() {
226            elem_stride[i + 1] = *stride as usize;
227        }
228
229        let lhs = TensorMapArg::new(
230            Im2colArgs {
231                pixel_box_lower_corner: calculate_lower_corner(problem),
232                pixel_box_upper_corner: calculate_upper_corner(problem),
233                channels_per_pixel: tile_size_k,
234                pixels_per_column: stage_m,
235            },
236            out_grad.into_data().into_tensor_arg(),
237            lhs_elem,
238        )
239        .with_elem_stride(elem_stride);
240
241        let rhs = TensorMapArg::new(
242            TiledArgs {
243                tile_size: stage_size_rhs,
244            },
245            weights.into_data().into_tensor_arg(),
246            dtypes.rhs_global,
247        );
248
249        let padded_channels = problem.padded_channels as u32;
250        let shape_k = problem.k as u32;
251
252        // Im2col needs extra checking because if `k` is OOB it wraps around the kernel and can load
253        // in-bounds but not in-kernel elements. Other TMA layouts are always outside the shape if
254        // any matrix dim is out of bounds.
255        let stages_lhs = A::num_stages().lhs;
256        let stages_size_k = blueprint.tiling_scheme.elements_per_stage_along_k() * stages_lhs;
257        let check_kernel = !shape_k.is_multiple_of(stages_size_k);
258        let lhs_layout = TmaIm2colLayoutLaunch::from_args(problem, check_kernel);
259        let rhs_layout = WeightLayoutLaunch::from_args(
260            problem,
261            GlobalLayoutConfig {
262                check_row_bounds: false,
263                check_col_bounds: false,
264                matrix_layout: MatrixLayout::default(),
265            },
266        );
267
268        let inputs = TensorMapInputsLaunch::new(
269            ViewArg::new_tensor_map_im2col::<LhsLayout, _, _>(lhs, lhs_layout),
270            ViewArg::new_tensor_map_tiled::<RhsLayout>(rhs, rhs_layout),
271            ComptimeOptionArgs::None,
272            ComptimeOptionArgs::None,
273        );
274
275        let runtime_args = RuntimeArgsLaunch::new(
276            shape_k,
277            problem.out_channels as u32,
278            padded_channels,
279            problem.operation,
280        );
281
282        (inputs, runtime_args)
283    }
284}
285
286#[allow(clippy::needless_range_loop)]
287fn calculate_lower_corner(problem: &ConvolutionProblem) -> Vec<i32> {
288    let mut out = vec![0; problem.padding.len()];
289    for i in 0..problem.padding.len() {
290        out[i] =
291            problem.padding[i] - (problem.kernel_size[i] as i32 - 1) * problem.dilation[i] as i32;
292    }
293    out
294}
295
296#[allow(clippy::needless_range_loop)]
297fn calculate_upper_corner(problem: &ConvolutionProblem) -> Vec<i32> {
298    let mut out = vec![0; problem.padding.len()];
299    for i in 0..problem.padding.len() {
300        out[i] = problem.padding[i]
301            - (problem.kernel_size[i] as i32 - 1) * problem.dilation[i] as i32
302            + problem.in_shape[i] as i32
303            - problem.out_shape[i] as i32;
304    }
305    out
306}