cubek_convolution/definition/
cost.rs1use cubecl::{
2 client::Client,
3 ir::ElemType,
4 throughput::{ThroughputKey, ThroughputMode},
5 tune::Work,
6};
7use cubek_matmul::definition::{MatmulCost, MatmulGlobalElems};
8
9#[derive(Debug, Clone)]
12pub struct Conv2dCost {
13 pub batch: usize,
15 pub channels_in: usize,
16 pub spatial_in: [usize; 2],
17 pub channels_out: usize,
19 pub kernel: [usize; 2],
20 pub spatial_out: [usize; 2],
22 pub bias_elems: usize,
24 pub elems: MatmulGlobalElems,
26}
27
28impl Conv2dCost {
29 fn gemm(&self) -> MatmulCost {
36 let [h_out, w_out] = self.spatial_out;
37 let [k_h, k_w] = self.kernel;
38
39 MatmulCost {
40 batches: 1,
41 m: self.batch * h_out * w_out,
42 n: self.channels_out,
43 k: self.channels_in * k_h * k_w,
44 elems: self.elems.clone(),
45 }
46 }
47
48 pub fn work(&self) -> Work {
51 let (read, written) = self.traffic();
52
53 Work {
54 compute_ops: self.compute_ops(),
55 bytes: read + written,
56 }
57 }
58
59 pub fn compute_ops(&self) -> usize {
61 self.gemm().compute_ops()
62 }
63
64 pub fn traffic(&self) -> (usize, usize) {
71 let [h_in, w_in] = self.spatial_in;
72 let [h_out, w_out] = self.spatial_out;
73 let [k_h, k_w] = self.kernel;
74
75 let input = self.batch * self.channels_in * h_in * w_in;
76 let filter = self.channels_out * self.channels_in * k_h * k_w;
77 let output = self.batch * self.channels_out * h_out * w_out;
78
79 (
80 input * self.elems.lhs.size()
81 + filter * self.elems.rhs.size()
82 + self.bias_elems * self.elems.out.size(),
85 output * self.elems.out.size(),
86 )
87 }
88
89 pub fn compute_key(&self, client: &Client) -> ThroughputKey {
92 self.gemm().compute_key(client)
93 }
94}
95
96#[derive(Debug, Clone, Copy)]
100pub struct DepthwiseCost {
101 pub batch: usize,
102 pub channels: usize,
103 pub size: usize,
105 pub out_size: usize,
107 pub kernel: usize,
109 pub dtype: ElemType,
111}
112
113impl DepthwiseCost {
114 pub fn work(&self) -> Work {
117 let (read, written) = self.traffic();
118
119 Work {
120 compute_ops: self.compute_ops(),
121 bytes: read + written,
122 }
123 }
124
125 pub fn compute_ops(&self) -> usize {
128 let taps = self.kernel * self.kernel;
129
130 self.batch * self.out_size * self.out_size * self.channels * (2 * taps).saturating_sub(1)
131 }
132
133 pub fn traffic(&self) -> (usize, usize) {
136 let maps = self.batch * self.channels;
137 let filter = self.channels * self.kernel * self.kernel;
138 let size = self.dtype.size();
139
140 (
141 (maps * self.size * self.size + filter) * size,
142 maps * self.out_size * self.out_size * size,
143 )
144 }
145
146 pub fn compute_key(&self) -> ThroughputKey {
149 ThroughputKey {
150 mode: ThroughputMode::ComputeDirect { dtype: self.dtype },
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use cubecl::ir::{ElemType, FloatKind};
159
160 fn cost() -> Conv2dCost {
161 let f16 = ElemType::Float(FloatKind::F16);
162
163 Conv2dCost {
164 batch: 2,
165 channels_in: 3,
166 spatial_in: [8, 8],
167 channels_out: 4,
168 kernel: [3, 3],
169 spatial_out: [6, 6],
170 bias_elems: 0,
171 elems: MatmulGlobalElems {
172 lhs: f16,
173 rhs: f16,
174 out: f16,
175 },
176 }
177 }
178
179 #[test]
180 fn contracts_over_the_filter_window_for_every_output_pixel() {
181 let outputs = 2 * 6 * 6 * 4;
182 let taps = 3 * 3 * 3;
183
184 assert_eq!(cost().compute_ops(), outputs * (2 * taps - 1));
185 }
186
187 #[test]
188 fn moves_the_maps_and_the_filter_once_each() {
189 let (read, written) = cost().traffic();
190
191 assert_eq!(read, (2 * 3 * 8 * 8 + 4 * 3 * 3 * 3) * 2);
192 assert_eq!(written, 2 * 4 * 6 * 6 * 2);
193 }
194
195 #[test]
196 fn a_bias_is_read_alongside_the_filter() {
197 let mut with_bias = cost();
198 with_bias.bias_elems = 4;
199
200 assert_eq!(with_bias.traffic().0, cost().traffic().0 + 4 * 2);
201 }
202}