1use std::fmt;
7
8use nxpu_ir::Module;
9
10use crate::Pass;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct TileConfig {
15 pub dim_name: String,
17 pub tile_size: u32,
19}
20
21impl fmt::Display for TileConfig {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "{}={}", self.dim_name, self.tile_size)
24 }
25}
26
27#[derive(Debug, Clone)]
29pub struct TilingPlan {
30 pub op_name: String,
32 pub tiles: Vec<TileConfig>,
34 pub reuse_factor: f64,
36}
37
38impl fmt::Display for TilingPlan {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "TilingPlan({}: ", self.op_name)?;
41 for (i, tile) in self.tiles.iter().enumerate() {
42 if i > 0 {
43 write!(f, ", ")?;
44 }
45 write!(f, "{tile}")?;
46 }
47 write!(f, " reuse={:.2})", self.reuse_factor)
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct TilingDefaults {
54 pub matmul_m: u32,
56 pub matmul_n: u32,
58 pub matmul_k: u32,
60 pub conv2d_oh: u32,
62 pub conv2d_ow: u32,
64}
65
66impl Default for TilingDefaults {
67 fn default() -> Self {
68 Self {
69 matmul_m: 32,
70 matmul_n: 32,
71 matmul_k: 32,
72 conv2d_oh: 4,
73 conv2d_ow: 4,
74 }
75 }
76}
77
78#[derive(Debug, Clone)]
80pub struct MatMulShape {
81 pub m: u32,
83 pub n: u32,
85 pub k: u32,
87}
88
89#[derive(Debug, Clone)]
91pub struct Conv2DShape {
92 pub oh: u32,
94 pub ow: u32,
96 pub kh: u32,
98 pub kw: u32,
100}
101
102pub fn tile_matmul(shape: &MatMulShape, defaults: &TilingDefaults) -> TilingPlan {
107 let tm = defaults.matmul_m.min(shape.m);
108 let tn = defaults.matmul_n.min(shape.n);
109 let tk = defaults.matmul_k.min(shape.k);
110
111 let tiles_m = (shape.m as f64) / (tm as f64);
116 let tiles_n = (shape.n as f64) / (tn as f64);
117 let tiles_k = (shape.k as f64) / (tk as f64);
118 let reuse_factor = if tm + tn > 0 {
127 (tm as f64 * tn as f64) / ((tm + tn) as f64)
128 } else {
129 1.0
130 };
131 let _ = (tiles_m, tiles_n, tiles_k);
132
133 TilingPlan {
134 op_name: "matmul".into(),
135 tiles: vec![
136 TileConfig {
137 dim_name: "M".into(),
138 tile_size: tm,
139 },
140 TileConfig {
141 dim_name: "N".into(),
142 tile_size: tn,
143 },
144 TileConfig {
145 dim_name: "K".into(),
146 tile_size: tk,
147 },
148 ],
149 reuse_factor,
150 }
151}
152
153pub fn tile_conv2d(shape: &Conv2DShape, defaults: &TilingDefaults) -> TilingPlan {
155 let toh = defaults.conv2d_oh.min(shape.oh);
156 let tow = defaults.conv2d_ow.min(shape.ow);
157
158 let reuse_factor = if toh + tow > 0 {
161 (shape.kh as f64 * shape.kw as f64 * toh as f64 * tow as f64)
162 / ((toh + shape.kh - 1) as f64 * (tow + shape.kw - 1) as f64)
163 } else {
164 1.0
165 };
166
167 TilingPlan {
168 op_name: "conv2d".into(),
169 tiles: vec![
170 TileConfig {
171 dim_name: "OH".into(),
172 tile_size: toh,
173 },
174 TileConfig {
175 dim_name: "OW".into(),
176 tile_size: tow,
177 },
178 ],
179 reuse_factor,
180 }
181}
182
183#[derive(Debug)]
185pub struct TilingPass {
186 defaults: TilingDefaults,
187}
188
189impl TilingPass {
190 pub fn new(defaults: TilingDefaults) -> Self {
192 Self { defaults }
193 }
194}
195
196impl Default for TilingPass {
197 fn default() -> Self {
198 Self::new(TilingDefaults::default())
199 }
200}
201
202impl Pass for TilingPass {
203 fn name(&self) -> &str {
204 "tiling"
205 }
206
207 fn run(&self, module: &mut Module) -> bool {
208 let mut any_tiled = false;
210 for i in 0..module.entry_points.len() {
211 if let Ok(pattern) = nxpu_analysis::classify_entry_point(module, i) {
212 match &pattern {
213 nxpu_analysis::KernelPattern::MatMul { shape, .. } => {
214 let m = shape.m.parse::<u32>().unwrap_or(256);
215 let n = shape.n.parse::<u32>().unwrap_or(256);
216 let k = shape.k.parse::<u32>().unwrap_or(256);
217 let _plan = tile_matmul(&MatMulShape { m, n, k }, &self.defaults);
218 any_tiled = true;
219 }
220 nxpu_analysis::KernelPattern::Conv2D { shape, .. } => {
221 let oh = shape.height.parse::<u32>().unwrap_or(32);
222 let ow = shape.width.parse::<u32>().unwrap_or(32);
223 let _plan = tile_conv2d(
224 &Conv2DShape {
225 oh,
226 ow,
227 kh: shape.kernel_h_val as u32,
228 kw: shape.kernel_w_val as u32,
229 },
230 &self.defaults,
231 );
232 any_tiled = true;
233 }
234 _ => {}
235 }
236 }
237 }
238 any_tiled
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn tile_matmul_default_sizes() {
248 let shape = MatMulShape {
249 m: 1024,
250 n: 1024,
251 k: 1024,
252 };
253 let plan = tile_matmul(&shape, &TilingDefaults::default());
254 assert_eq!(plan.tiles[0].tile_size, 32); assert_eq!(plan.tiles[1].tile_size, 32); assert_eq!(plan.tiles[2].tile_size, 32); }
258
259 #[test]
260 fn tile_matmul_clamps_small_dim() {
261 let shape = MatMulShape {
262 m: 16,
263 n: 1024,
264 k: 1024,
265 };
266 let plan = tile_matmul(&shape, &TilingDefaults::default());
267 assert_eq!(plan.tiles[0].tile_size, 16); assert_eq!(plan.tiles[1].tile_size, 32); }
270
271 #[test]
272 fn tile_matmul_reuse_factor() {
273 let shape = MatMulShape {
274 m: 1024,
275 n: 1024,
276 k: 1024,
277 };
278 let plan = tile_matmul(&shape, &TilingDefaults::default());
279 assert!((plan.reuse_factor - 16.0).abs() < 0.01);
281 }
282
283 #[test]
284 fn tile_conv2d_default_sizes() {
285 let shape = Conv2DShape {
286 oh: 32,
287 ow: 32,
288 kh: 3,
289 kw: 3,
290 };
291 let plan = tile_conv2d(&shape, &TilingDefaults::default());
292 assert_eq!(plan.tiles[0].tile_size, 4); assert_eq!(plan.tiles[1].tile_size, 4); }
295
296 #[test]
297 fn tile_conv2d_clamps_small_spatial() {
298 let shape = Conv2DShape {
299 oh: 2,
300 ow: 32,
301 kh: 3,
302 kw: 3,
303 };
304 let plan = tile_conv2d(&shape, &TilingDefaults::default());
305 assert_eq!(plan.tiles[0].tile_size, 2); assert_eq!(plan.tiles[1].tile_size, 4); }
308
309 #[test]
310 fn tiling_defaults_sane() {
311 let d = TilingDefaults::default();
312 assert!(d.matmul_m >= 4);
313 assert!(d.matmul_n >= 4);
314 assert!(d.matmul_k >= 4);
315 assert!(d.conv2d_oh >= 4);
316 assert!(d.conv2d_ow >= 4);
317 assert!(d.matmul_m.is_power_of_two());
319 assert!(d.matmul_n.is_power_of_two());
320 assert!(d.matmul_k.is_power_of_two());
321 assert!(d.conv2d_oh.is_power_of_two());
322 assert!(d.conv2d_ow.is_power_of_two());
323 }
324
325 #[test]
326 fn tiling_plan_display() {
327 let plan = TilingPlan {
328 op_name: "matmul_0".into(),
329 tiles: vec![
330 TileConfig {
331 dim_name: "M".into(),
332 tile_size: 32,
333 },
334 TileConfig {
335 dim_name: "N".into(),
336 tile_size: 32,
337 },
338 ],
339 reuse_factor: 16.0,
340 };
341 let s = format!("{plan}");
342 assert!(s.contains("matmul_0"));
343 assert!(s.contains("M=32"));
344 assert!(s.contains("N=32"));
345 assert!(s.contains("reuse=16.00"));
346 }
347
348 #[test]
349 fn pass_on_matmul_module() {
350 let mut module = Module::default();
354 let pass = TilingPass::default();
355 let changed = pass.run(&mut module);
356 assert!(!changed);
358 }
359
360 #[test]
361 fn pass_noop_on_elementwise() {
362 let mut module = Module::default();
364 let pass = TilingPass::default();
365 let changed = pass.run(&mut module);
366 assert!(!changed);
367 }
368
369 #[test]
370 fn performance_test() {
371 let shape = MatMulShape {
373 m: 2048,
374 n: 2048,
375 k: 2048,
376 };
377 let plan = tile_matmul(&shape, &TilingDefaults::default());
378 assert!(plan.reuse_factor > 1.0);
379 }
380}