Skip to main content

cubek_convolution/kernels/backward_data/
args.rs

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