Skip to main content

rudnn/convolution/components/global/layout/
tma_im2col.rs

1use ruda_kernel::dsl as kernel_dsl;
2use ruda_kernel::dsl::prelude::*;
3use ruda_kernel::library::FastDivmod;
4use ruda_kernel::library::tensor::layout::CoordsDyn;
5use ruda_kernel::library::tensor::layout::Layout;
6use ruda_kernel::library::tensor::layout::LayoutExpand;
7use rublas::kernel_ir::launch::BatchedCoords;
8
9use crate::convolution::components::{
10    ConvolutionOperation, ConvolutionParams, ConvolutionProblem, global::layout::NhwcCoords,
11};
12
13/// Im2col layout, producing both the position and offset
14#[derive(RudaType, RudaLaunch)]
15pub struct TmaIm2colLayout {
16    shape_out: Sequence<FastDivmod<u32>>,
17    padded_channels: FastDivmod<u32>,
18    rows: u32,
19    cols: u32,
20    #[ruda(comptime)]
21    params: ConvolutionParams,
22    #[ruda(comptime)]
23    check_kernel: bool,
24}
25
26#[ruda]
27impl TmaIm2colLayout {
28    pub fn new(
29        shape_out: Sequence<FastDivmod<u32>>,
30        padded_channels: FastDivmod<u32>,
31        rows: u32,
32        cols: u32,
33        #[comptime] params: ConvolutionParams,
34        #[comptime] check_kernel: bool,
35    ) -> Self {
36        TmaIm2colLayout {
37            shape_out,
38            padded_channels,
39            params,
40            check_kernel,
41            rows,
42            cols,
43        }
44    }
45}
46
47#[ruda]
48impl Layout for TmaIm2colLayout {
49    type Coordinates = BatchedCoords;
50    type SourceCoordinates = (NhwcCoords, CoordsDyn);
51
52    fn to_source_pos(&self, pos: Self::Coordinates) -> Self::SourceCoordinates {
53        let (_, m, k) = pos;
54        let params = self.params.comptime();
55
56        let (n_offs, spatial_offsets) = div_mod_seq(m, &self.shape_out);
57        let spatial_dims = spatial_offsets.len();
58
59        let mut in_offs = Sequence::<i32>::new();
60
61        #[unroll]
62        for dim in 0..spatial_dims {
63            let stride = params.stride[dim] as i32;
64            let pad = params.padding[dim];
65            let out_pos = spatial_offsets[dim] as i32;
66            let offs = match params.operation {
67                ConvolutionOperation::Forward | ConvolutionOperation::BackwardWeight => {
68                    out_pos * stride - pad
69                }
70                ConvolutionOperation::ForwardTransposed | ConvolutionOperation::BackwardData => {
71                    let ksize = params.kernel_size[dim] as i32;
72                    (out_pos + pad - ((ksize - 1) * params.dilation[dim] as i32)) / stride
73                }
74            };
75            in_offs.push(offs);
76        }
77
78        let (mut k_idx, channel_start) = self.padded_channels.div_mod(k);
79
80        let mut pos = NhwcCoords {
81            batch: n_offs,
82            spatial: in_offs,
83            channel: channel_start,
84        };
85
86        let mut k_offs = Sequence::new();
87        let k_rank = params.dimensionality.num_dims();
88
89        #[unroll]
90        for i in 0..k_rank {
91            let dim = k_rank - i - 1;
92            let k_size = params.kernel_size[dim];
93            let k_pos = k_idx % k_size;
94
95            let k_pos = match params.operation {
96                ConvolutionOperation::Forward | ConvolutionOperation::BackwardWeight => k_pos,
97                ConvolutionOperation::ForwardTransposed | ConvolutionOperation::BackwardData => {
98                    // Since kernels are always positive, we need to subtract the bottom right
99                    // corner (see position above), then add the inverted index to it.
100                    k_size - k_pos - 1
101                }
102            };
103            k_offs.push(k_pos * params.dilation[dim]);
104            k_idx /= k_size;
105        }
106
107        if self.check_kernel.comptime() {
108            // This is the largest index that's aligned to the channel count in all cases.
109            // Alignment is 256, and that's the largest tile size possible with TMA.
110            // Could alternatively solve this by only loading if in bounds, and adjusting the awaited
111            // bytes by the in-bounds tiles but that's more complicated than just trying to load a very
112            // large channel index and letting bounds checks handle it.
113            let kernel_mask = (k_idx > 0) as u32 * 0x7FFFFF00u32;
114            pos.channel = pos.channel.max(kernel_mask);
115        }
116
117        (pos, k_offs.rev())
118    }
119
120    fn is_in_bounds(&self, _pos: Self::Coordinates) -> bool {
121        true.runtime()
122    }
123
124    fn shape(&self) -> Self::Coordinates {
125        (1, self.rows, self.cols)
126    }
127
128    fn to_source_pos_checked(&self, pos: Self::Coordinates) -> (Self::SourceCoordinates, bool) {
129        (self.to_source_pos(pos), self.is_in_bounds(pos))
130    }
131}
132
133/// Decompose a linear index into local positions along each dimension in `shape`. Also returns the
134/// left over remainder.
135#[ruda]
136pub(crate) fn div_mod_seq(pos: u32, shape: &Sequence<FastDivmod<u32>>) -> (u32, Sequence<u32>) {
137    let rank = shape.len().comptime();
138    let mut offs = pos;
139    let mut out = Sequence::new();
140
141    #[unroll]
142    for i in 0..rank {
143        let dim = rank - i - 1;
144        let (rem, offs_local) = shape[dim].div_mod(offs);
145        out.push(offs_local);
146        offs = rem;
147    }
148
149    (offs, out.rev())
150}
151
152impl<R: Runtime> TmaIm2colLayoutLaunch<R> {
153    pub fn from_args(problem: &ConvolutionProblem, check_kernel: bool) -> Self {
154        let shape_out = problem.out_shape.iter().map(|it| *it as u32).collect();
155
156        let padded_channels = problem.padded_channels as u32;
157        let params = ConvolutionParams::from_problem(problem);
158
159        match problem.operation {
160            ConvolutionOperation::Forward
161            | ConvolutionOperation::ForwardTransposed
162            | ConvolutionOperation::BackwardData => {
163                Self::from_args_lhs(problem, shape_out, padded_channels, params, check_kernel)
164            }
165            ConvolutionOperation::BackwardWeight => {
166                Self::from_args_rhs(problem, shape_out, padded_channels, params, check_kernel)
167            }
168        }
169    }
170
171    fn from_args_lhs(
172        problem: &ConvolutionProblem,
173        shape_out: SequenceArg<R, FastDivmod<u32>>,
174        padded_channels: u32,
175        params: ConvolutionParams,
176        check_kernel: bool,
177    ) -> Self {
178        let shape_m = problem.m as u32;
179        let shape_k = problem.k as u32;
180
181        TmaIm2colLayoutLaunch::new(
182            shape_out,
183            padded_channels,
184            shape_m,
185            shape_k,
186            params,
187            check_kernel,
188        )
189    }
190
191    fn from_args_rhs(
192        problem: &ConvolutionProblem,
193        shape_out: SequenceArg<R, FastDivmod<u32>>,
194        padded_channels: u32,
195        params: ConvolutionParams,
196        check_kernel: bool,
197    ) -> Self {
198        let shape_k = problem.k as u32;
199        let shape_n = problem.n as u32;
200
201        TmaIm2colLayoutLaunch::new(
202            shape_out,
203            padded_channels,
204            shape_k,
205            shape_n,
206            params,
207            check_kernel,
208        )
209    }
210}