1use std::fmt;
7
8use nxpu_ir::{Module, Scalar, ScalarKind};
9
10use crate::Pass;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct VectorWidth {
15 pub scalar: Scalar,
17 pub lanes: u32,
19 pub register_bits: u32,
21}
22
23impl VectorWidth {
24 pub fn for_register_width(scalar: Scalar, register_bits: u32) -> Self {
26 let scalar_bits = scalar_width_bits(scalar);
27 let lanes = (register_bits).checked_div(scalar_bits).unwrap_or(1);
28 Self {
29 scalar,
30 lanes,
31 register_bits,
32 }
33 }
34}
35
36impl fmt::Display for VectorWidth {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 write!(
39 f,
40 "{:?}x{} ({}b reg)",
41 self.scalar, self.lanes, self.register_bits
42 )
43 }
44}
45
46#[derive(Debug, Clone)]
48pub struct VectorizationHint {
49 pub op_name: String,
51 pub dim_name: String,
53 pub vector_width: VectorWidth,
55 pub is_reduction: bool,
57 pub is_contiguous: bool,
59}
60
61impl fmt::Display for VectorizationHint {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let red = if self.is_reduction {
64 " [reduction]"
65 } else {
66 ""
67 };
68 let contig = if self.is_contiguous {
69 " [contiguous]"
70 } else {
71 ""
72 };
73 write!(
74 f,
75 "{}.{}: {}{red}{contig}",
76 self.op_name, self.dim_name, self.vector_width
77 )
78 }
79}
80
81pub fn analyze_vectorization(
86 pattern: &nxpu_analysis::KernelPattern,
87 register_bits: u32,
88) -> Vec<VectorizationHint> {
89 let scalar = Scalar::F32; let vw = VectorWidth::for_register_width(scalar, register_bits);
91
92 match pattern {
93 nxpu_analysis::KernelPattern::MatMul { shape, .. } => {
94 vec![
95 VectorizationHint {
96 op_name: "MatMul".into(),
97 dim_name: shape.n.clone(),
98 vector_width: vw.clone(),
99 is_reduction: false,
100 is_contiguous: true,
101 },
102 VectorizationHint {
103 op_name: "MatMul".into(),
104 dim_name: shape.k.clone(),
105 vector_width: vw,
106 is_reduction: true,
107 is_contiguous: false,
108 },
109 ]
110 }
111 nxpu_analysis::KernelPattern::ElementWise { dim_name, .. } => {
112 vec![VectorizationHint {
113 op_name: "ElementWise".into(),
114 dim_name: dim_name.clone(),
115 vector_width: vw,
116 is_reduction: false,
117 is_contiguous: true,
118 }]
119 }
120 nxpu_analysis::KernelPattern::Activation { dim_name, .. } => {
121 vec![VectorizationHint {
122 op_name: "Activation".into(),
123 dim_name: dim_name.clone(),
124 vector_width: vw,
125 is_reduction: false,
126 is_contiguous: true,
127 }]
128 }
129 nxpu_analysis::KernelPattern::Conv2D { shape, .. } => {
130 vec![VectorizationHint {
131 op_name: "Conv2D".into(),
132 dim_name: shape.width.clone(),
133 vector_width: vw,
134 is_reduction: false,
135 is_contiguous: true,
136 }]
137 }
138 nxpu_analysis::KernelPattern::Reduce { op, axis, .. } => {
139 vec![VectorizationHint {
140 op_name: op.op_name().to_string(),
141 dim_name: format!("axis_{axis}"),
142 vector_width: vw,
143 is_reduction: true,
144 is_contiguous: true,
145 }]
146 }
147 _ => Vec::new(),
148 }
149}
150
151#[derive(Debug)]
155pub struct VectorizationPass {
156 register_bits: u32,
157}
158
159impl VectorizationPass {
160 pub fn new(register_bits: u32) -> Self {
162 Self { register_bits }
163 }
164}
165
166impl Default for VectorizationPass {
167 fn default() -> Self {
168 Self::new(128)
169 }
170}
171
172impl Pass for VectorizationPass {
173 fn name(&self) -> &str {
174 "vectorize"
175 }
176
177 fn run(&self, module: &mut Module) -> bool {
178 let mut any = false;
179 for i in 0..module.entry_points.len() {
180 if let Ok(pattern) = nxpu_analysis::classify_entry_point(module, i) {
181 let hints = analyze_vectorization(&pattern, self.register_bits);
182 if !hints.is_empty() {
183 any = true;
184 }
185 }
186 }
187 any
188 }
189}
190
191fn scalar_width_bits(scalar: Scalar) -> u32 {
193 match scalar.kind {
194 ScalarKind::Float | ScalarKind::BFloat | ScalarKind::Sint | ScalarKind::Uint => {
195 scalar.width as u32 * 8
196 }
197 ScalarKind::Bool => 8,
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use nxpu_analysis::{
205 ActivationOp, Conv2DShape, ElementWiseOp, KernelPattern, MatMulShape, ReduceOp,
206 TensorBinding, TensorRole,
207 };
208 use nxpu_ir::{
209 AddressSpace, Arena, GlobalVariable, Scalar, StorageAccess, Type, TypeInner, UniqueArena,
210 };
211
212 fn dummy_binding(name: &str, role: TensorRole) -> TensorBinding {
213 let mut types = UniqueArena::new();
214 let ty = types.insert(Type {
215 name: None,
216 inner: TypeInner::Scalar(Scalar::F32),
217 });
218 let mut arena = Arena::new();
219 let handle = arena.append(GlobalVariable {
220 name: Some(name.into()),
221 space: AddressSpace::Storage {
222 access: StorageAccess::LOAD,
223 },
224 binding: None,
225 ty,
226 init: None,
227 layout: None,
228 });
229 TensorBinding {
230 handle,
231 name: name.into(),
232 elem_type: 1, role,
234 }
235 }
236
237 #[test]
238 fn vector_width_f32_128() {
239 let vw = VectorWidth::for_register_width(Scalar::F32, 128);
240 assert_eq!(vw.lanes, 4);
241 }
242
243 #[test]
244 fn vector_width_f16_128() {
245 let vw = VectorWidth::for_register_width(Scalar::F16, 128);
246 assert_eq!(vw.lanes, 8);
247 }
248
249 #[test]
250 fn vector_width_int8_128() {
251 let scalar = Scalar {
252 kind: ScalarKind::Sint,
253 width: 1,
254 };
255 let vw = VectorWidth::for_register_width(scalar, 128);
256 assert_eq!(vw.lanes, 16);
257 }
258
259 #[test]
260 fn vector_width_f32_256() {
261 let vw = VectorWidth::for_register_width(Scalar::F32, 256);
262 assert_eq!(vw.lanes, 8);
263 }
264
265 #[test]
266 fn vectorize_matmul() {
267 let pattern = KernelPattern::MatMul {
268 inputs: [
269 dummy_binding("A", TensorRole::Input),
270 dummy_binding("B", TensorRole::Input),
271 ],
272 output: dummy_binding("C", TensorRole::Output),
273 shape: MatMulShape {
274 m: "M".into(),
275 n: "N".into(),
276 k: "K".into(),
277 },
278 };
279 let hints = analyze_vectorization(&pattern, 128);
280 assert_eq!(hints.len(), 2);
281 assert_eq!(hints[0].dim_name, "N");
283 assert!(hints[0].is_contiguous);
284 assert!(!hints[0].is_reduction);
285 assert_eq!(hints[1].dim_name, "K");
287 assert!(hints[1].is_reduction);
288 }
289
290 #[test]
291 fn vectorize_elementwise() {
292 let pattern = KernelPattern::ElementWise {
293 op: ElementWiseOp::Add,
294 inputs: [
295 dummy_binding("A", TensorRole::Input),
296 dummy_binding("B", TensorRole::Input),
297 ],
298 output: dummy_binding("C", TensorRole::Output),
299 dim_name: "N".into(),
300 };
301 let hints = analyze_vectorization(&pattern, 128);
302 assert_eq!(hints.len(), 1);
303 assert_eq!(hints[0].dim_name, "N");
304 assert!(hints[0].is_contiguous);
305 }
306
307 #[test]
308 fn vectorize_conv2d() {
309 let pattern = KernelPattern::Conv2D {
310 input: dummy_binding("input", TensorRole::Input),
311 weight: dummy_binding("weight", TensorRole::Input),
312 output: dummy_binding("output", TensorRole::Output),
313 shape: Conv2DShape {
314 batch: "1".into(),
315 channels_in: "3".into(),
316 channels_out: "16".into(),
317 height: "32".into(),
318 width: "W".into(),
319 kernel_h: "3".into(),
320 kernel_w: "3".into(),
321 kernel_h_val: 3,
322 kernel_w_val: 3,
323 stride_h: 1,
324 stride_w: 1,
325 pad_h: 1,
326 pad_w: 1,
327 groups: 1,
328 dilation_h: 1,
329 dilation_w: 1,
330 },
331 bias: None,
332 activation: None,
333 };
334 let hints = analyze_vectorization(&pattern, 128);
335 assert_eq!(hints.len(), 1);
336 assert_eq!(hints[0].dim_name, "W");
337 }
338
339 #[test]
340 fn vectorize_reduction() {
341 let pattern = KernelPattern::Reduce {
342 op: ReduceOp::Sum,
343 input: dummy_binding("input", TensorRole::Input),
344 output: dummy_binding("output", TensorRole::Output),
345 axis: 1,
346 };
347 let hints = analyze_vectorization(&pattern, 128);
348 assert_eq!(hints.len(), 1);
349 assert!(hints[0].is_reduction);
350 }
351
352 #[test]
353 fn vectorize_activation() {
354 let pattern = KernelPattern::Activation {
355 op: ActivationOp::Relu,
356 input: dummy_binding("input", TensorRole::Input),
357 output: dummy_binding("output", TensorRole::Output),
358 dim_name: "N".into(),
359 };
360 let hints = analyze_vectorization(&pattern, 128);
361 assert_eq!(hints.len(), 1);
362 assert!(hints[0].is_contiguous);
363 assert!(!hints[0].is_reduction);
364 }
365
366 #[test]
367 fn pass_on_module() {
368 let mut module = Module::default();
370 let pass = VectorizationPass::default();
371 let changed = pass.run(&mut module);
372 assert!(!changed);
373 }
374}