1const OP_TYPE_COOPERATIVE_MATRIX_KHR: u32 = 4456;
22const OP_COOPERATIVE_MATRIX_LOAD_KHR: u32 = 4457;
24const OP_COOPERATIVE_MATRIX_STORE_KHR: u32 = 4458;
26const OP_COOPERATIVE_MATRIX_MUL_ADD_KHR: u32 = 4459;
28const CAPABILITY_COOPERATIVE_MATRIX_KHR: u32 = 6022;
32const CAPABILITY_SHADER: u32 = 1;
34const CAPABILITY_FLOAT16: u32 = 9;
36const ADDRESSING_MODEL_LOGICAL: u32 = 0;
39const MEMORY_MODEL_GLSL450: u32 = 1;
40
41const EXECUTION_MODEL_GLCOMPUTE: u32 = 5;
44const EXECUTION_MODE_LOCAL_SIZE: u32 = 17;
45
46const STORAGE_CLASS_STORAGE_BUFFER: u32 = 12;
49const STORAGE_CLASS_INPUT: u32 = 1;
50
51const DECORATION_DESCRIPTOR_SET: u32 = 34;
54const DECORATION_BINDING: u32 = 33;
55const DECORATION_BLOCK: u32 = 2;
56const DECORATION_BUILTIN: u32 = 11;
57const DECORATION_NON_WRITABLE: u32 = 24;
58const BUILTIN_WORKGROUP_ID: u32 = 26;
59
60const OP_EXTENSION: u32 = 10;
63const OP_CAPABILITY: u32 = 17;
64const OP_MEMORY_MODEL: u32 = 14;
65const OP_ENTRY_POINT: u32 = 15;
66const OP_EXECUTION_MODE: u32 = 16;
67const OP_DECORATE: u32 = 71;
68const OP_MEMBER_DECORATE: u32 = 72;
69const OP_TYPE_VOID: u32 = 19;
70const OP_TYPE_INT: u32 = 21;
71const OP_TYPE_FLOAT: u32 = 22;
72const OP_TYPE_POINTER: u32 = 32;
73const OP_TYPE_FUNCTION: u32 = 33;
74const OP_TYPE_STRUCT: u32 = 30;
75const OP_TYPE_RUNTIME_ARRAY: u32 = 29;
76const OP_CONSTANT: u32 = 43;
77const OP_FUNCTION: u32 = 54;
78const OP_FUNCTION_END: u32 = 56;
79const OP_VARIABLE: u32 = 59;
80const OP_LOAD: u32 = 61;
81const OP_ACCESS_CHAIN: u32 = 65;
82const OP_IN_BOUNDS_ACCESS_CHAIN: u32 = 66;
83const OP_LABEL: u32 = 248;
84const OP_RETURN: u32 = 253;
85const OP_COMPOSITE_EXTRACT: u32 = 81;
86const OP_I_MUL: u32 = 132;
87const OP_I_ADD: u32 = 128;
88
89const SCOPE_SUBGROUP: u32 = 3;
98
99const COOPERATIVE_MATRIX_OPERANDS_NONE: u32 = 0;
103
104const SPIRV_MAGIC: u32 = 0x07230203;
107const SPIRV_VERSION_1_6: u32 = 0x0001_0600;
109const SPIRV_GENERATOR: u32 = 0x000D_0003; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct XmxTileConfig {
122 pub m: u32,
124 pub n: u32,
126 pub k: u32,
128}
129
130impl XmxTileConfig {
131 pub const XE_HPC_FP16: Self = Self { m: 8, n: 16, k: 16 };
133
134 pub const XE_DEFAULT: Self = Self { m: 8, n: 16, k: 16 };
136
137 pub fn accum_elements(&self) -> u32 {
139 self.m * self.n
140 }
141}
142
143impl Default for XmxTileConfig {
144 fn default() -> Self {
145 Self::XE_HPC_FP16
146 }
147}
148
149struct XmxSpvModule {
152 words: Vec<u32>,
153 id_bound: u32,
154}
155
156impl XmxSpvModule {
157 fn new() -> Self {
158 let words = vec![SPIRV_MAGIC, SPIRV_VERSION_1_6, SPIRV_GENERATOR, 0, 0];
159 Self { words, id_bound: 1 }
160 }
161
162 fn alloc_id(&mut self) -> u32 {
163 let id = self.id_bound;
164 self.id_bound += 1;
165 id
166 }
167
168 fn emit(&mut self, opcode: u32, operands: &[u32]) {
169 let word_count = (1 + operands.len()) as u32;
170 self.words.push((word_count << 16) | opcode);
171 self.words.extend_from_slice(operands);
172 }
173
174 fn string_words(s: &str) -> Vec<u32> {
175 let bytes = s.as_bytes();
176 let padded_len = (bytes.len() + 4) & !3;
177 let mut out = vec![0u32; padded_len / 4];
178 for (i, &b) in bytes.iter().enumerate() {
179 out[i / 4] |= (b as u32) << ((i % 4) * 8);
180 }
181 out
182 }
183
184 fn finalize(mut self) -> Vec<u32> {
185 self.words[3] = self.id_bound;
186 self.words
187 }
188
189 fn emit_capability(&mut self, cap: u32) {
192 self.emit(OP_CAPABILITY, &[cap]);
193 }
194
195 fn emit_extension(&mut self, name: &str) {
196 let mut ops = Self::string_words(name);
197 let word_count = (1 + ops.len()) as u32;
199 self.words.push((word_count << 16) | OP_EXTENSION);
200 self.words.append(&mut ops);
201 }
202
203 fn emit_memory_model(&mut self, addr: u32, model: u32) {
204 self.emit(OP_MEMORY_MODEL, &[addr, model]);
205 }
206
207 fn emit_entry_point(&mut self, model: u32, func_id: u32, name: &str, interfaces: &[u32]) {
208 let mut ops = vec![model, func_id];
209 ops.extend(Self::string_words(name));
210 ops.extend_from_slice(interfaces);
211 self.emit(OP_ENTRY_POINT, &ops);
212 }
213
214 fn emit_execution_mode_local_size(&mut self, func_id: u32, x: u32, y: u32, z: u32) {
215 self.emit(
216 OP_EXECUTION_MODE,
217 &[func_id, EXECUTION_MODE_LOCAL_SIZE, x, y, z],
218 );
219 }
220
221 fn emit_decorate(&mut self, target: u32, decoration: u32, extra: &[u32]) {
222 let mut ops = vec![target, decoration];
223 ops.extend_from_slice(extra);
224 self.emit(OP_DECORATE, &ops);
225 }
226
227 fn emit_member_decorate(
228 &mut self,
229 struct_id: u32,
230 member: u32,
231 decoration: u32,
232 extra: &[u32],
233 ) {
234 let mut ops = vec![struct_id, member, decoration];
235 ops.extend_from_slice(extra);
236 self.emit(OP_MEMBER_DECORATE, &ops);
237 }
238
239 fn emit_type_void(&mut self, id: u32) {
240 self.emit(OP_TYPE_VOID, &[id]);
241 }
242 fn emit_type_int(&mut self, id: u32, width: u32, sign: u32) {
243 self.emit(OP_TYPE_INT, &[id, width, sign]);
244 }
245 fn emit_type_float(&mut self, id: u32, width: u32) {
246 self.emit(OP_TYPE_FLOAT, &[id, width]);
247 }
248 fn emit_type_ptr(&mut self, id: u32, sc: u32, pointee: u32) {
249 self.emit(OP_TYPE_POINTER, &[id, sc, pointee]);
250 }
251 fn emit_type_fn(&mut self, id: u32, ret: u32, params: &[u32]) {
252 let mut ops = vec![id, ret];
253 ops.extend_from_slice(params);
254 self.emit(OP_TYPE_FUNCTION, &ops);
255 }
256 fn emit_type_struct(&mut self, id: u32, members: &[u32]) {
257 let mut ops = vec![id];
258 ops.extend_from_slice(members);
259 self.emit(OP_TYPE_STRUCT, &ops);
260 }
261 fn emit_type_runtime_array(&mut self, id: u32, elem: u32) {
262 self.emit(OP_TYPE_RUNTIME_ARRAY, &[id, elem]);
263 }
264
265 fn emit_const_u32(&mut self, ty: u32, id: u32, val: u32) {
266 self.emit(OP_CONSTANT, &[ty, id, val]);
267 }
268 fn emit_variable(&mut self, ty: u32, id: u32, sc: u32) {
269 self.emit(OP_VARIABLE, &[ty, id, sc]);
270 }
271 fn emit_load(&mut self, ty: u32, id: u32, ptr: u32) {
272 self.emit(OP_LOAD, &[ty, id, ptr]);
273 }
274 fn emit_label(&mut self, id: u32) {
275 self.emit(OP_LABEL, &[id]);
276 }
277 fn emit_return(&mut self) {
278 self.emit(OP_RETURN, &[]);
279 }
280 fn emit_function_end(&mut self) {
281 self.emit(OP_FUNCTION_END, &[]);
282 }
283 fn emit_function(&mut self, ret_ty: u32, id: u32, ctrl: u32, fn_ty: u32) {
284 self.emit(OP_FUNCTION, &[ret_ty, id, ctrl, fn_ty]);
285 }
286 fn emit_i_add(&mut self, ty: u32, id: u32, a: u32, b: u32) {
287 self.emit(OP_I_ADD, &[ty, id, a, b]);
288 }
289 fn emit_i_mul(&mut self, ty: u32, id: u32, a: u32, b: u32) {
290 self.emit(OP_I_MUL, &[ty, id, a, b]);
291 }
292 fn emit_composite_extract(&mut self, ty: u32, id: u32, composite: u32, idx: u32) {
293 self.emit(OP_COMPOSITE_EXTRACT, &[ty, id, composite, idx]);
294 }
295
296 fn emit_access_chain(&mut self, ty: u32, id: u32, base: u32, indices: &[u32]) {
297 let mut ops = vec![ty, id, base];
298 ops.extend_from_slice(indices);
299 self.emit(OP_ACCESS_CHAIN, &ops);
300 }
301
302 fn emit_in_bounds_access_chain(&mut self, ty: u32, id: u32, base: u32, indices: &[u32]) {
303 let mut ops = vec![ty, id, base];
304 ops.extend_from_slice(indices);
305 self.emit(OP_IN_BOUNDS_ACCESS_CHAIN, &ops);
306 }
307
308 fn emit_type_cooperative_matrix(
312 &mut self,
313 id: u32,
314 component_type: u32,
315 scope: u32,
316 rows: u32,
317 cols: u32,
318 matrix_use: u32,
319 ) {
320 self.emit(
321 OP_TYPE_COOPERATIVE_MATRIX_KHR,
322 &[id, component_type, scope, rows, cols, matrix_use],
323 );
324 }
325
326 fn emit_coop_matrix_load(
328 &mut self,
329 result_ty: u32,
330 result: u32,
331 pointer: u32,
332 layout: u32,
333 stride: u32,
334 ) {
335 self.emit(
336 OP_COOPERATIVE_MATRIX_LOAD_KHR,
337 &[
338 result_ty,
339 result,
340 pointer,
341 layout,
342 stride,
343 COOPERATIVE_MATRIX_OPERANDS_NONE,
344 ],
345 );
346 }
347
348 fn emit_coop_matrix_store(&mut self, pointer: u32, object: u32, layout: u32, stride: u32) {
350 self.emit(
351 OP_COOPERATIVE_MATRIX_STORE_KHR,
352 &[
353 pointer,
354 object,
355 layout,
356 stride,
357 COOPERATIVE_MATRIX_OPERANDS_NONE,
358 ],
359 );
360 }
361
362 fn emit_coop_matrix_muladd(
364 &mut self,
365 result_ty: u32,
366 result: u32,
367 a: u32,
368 b: u32,
369 c: u32,
370 operands: u32,
371 ) {
372 self.emit(
373 OP_COOPERATIVE_MATRIX_MUL_ADD_KHR,
374 &[result_ty, result, a, b, c, operands],
375 );
376 }
377}
378
379pub fn gemm_xmx_spirv(tile: XmxTileConfig, wg_x: u32, wg_y: u32) -> Vec<u32> {
412 let mut m = XmxSpvModule::new();
413
414 m.emit_capability(CAPABILITY_SHADER);
416 m.emit_capability(CAPABILITY_COOPERATIVE_MATRIX_KHR);
417
418 m.emit_extension("SPV_KHR_cooperative_matrix");
420
421 m.emit_memory_model(ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450);
423
424 let ty_void = m.alloc_id();
426 let ty_u32 = m.alloc_id();
427 let ty_f32 = m.alloc_id();
428
429 let ty_rt_f32 = m.alloc_id(); let ty_rt_u32 = m.alloc_id(); let ty_sb_f32 = m.alloc_id(); let ty_sb_u32 = m.alloc_id(); let ty_ptr_sb_f32 = m.alloc_id();
435 let ty_ptr_sb_u32 = m.alloc_id();
436 let ty_ptr_f32_sb = m.alloc_id();
437 let ty_ptr_u32_sb = m.alloc_id();
438
439 let ty_cmat_a = m.alloc_id(); let ty_cmat_b = m.alloc_id(); let ty_cmat_c = m.alloc_id(); let ty_fn_void = m.alloc_id();
446
447 let ty_v3u32 = m.alloc_id();
449 let ty_ptr_in_v3u32 = m.alloc_id();
450
451 let c0 = m.alloc_id();
455 let c1 = m.alloc_id();
456 let c2 = m.alloc_id();
457 let c_scope = m.alloc_id();
458 let c_tile_m = m.alloc_id();
459 let c_tile_n = m.alloc_id();
460 let c_tile_k = m.alloc_id();
461
462 let var_a = m.alloc_id();
464 let var_b = m.alloc_id();
465 let var_c = m.alloc_id();
466 let var_dim = m.alloc_id();
467
468 let var_wg_id = m.alloc_id();
470
471 let fn_main = m.alloc_id();
473 let lbl_entry = m.alloc_id();
474
475 m.emit_entry_point(
477 EXECUTION_MODEL_GLCOMPUTE,
478 fn_main,
479 "gemm_xmx_f32",
480 &[var_a, var_b, var_c, var_dim, var_wg_id],
481 );
482 m.emit_execution_mode_local_size(fn_main, wg_x, wg_y, 1);
483
484 m.emit_decorate(ty_rt_f32, 6 , &[4]);
486 m.emit_decorate(ty_rt_u32, 6 , &[4]);
487
488 m.emit_decorate(ty_sb_f32, DECORATION_BLOCK, &[]);
489 m.emit_decorate(ty_sb_u32, DECORATION_BLOCK, &[]);
490
491 m.emit_member_decorate(ty_sb_f32, 0, 35 , &[0]);
492 m.emit_member_decorate(ty_sb_u32, 0, 35 , &[0]);
493
494 m.emit_decorate(var_a, DECORATION_DESCRIPTOR_SET, &[0]);
495 m.emit_decorate(var_a, DECORATION_BINDING, &[0]);
496 m.emit_decorate(var_a, DECORATION_NON_WRITABLE, &[]);
497 m.emit_decorate(var_b, DECORATION_DESCRIPTOR_SET, &[0]);
498 m.emit_decorate(var_b, DECORATION_BINDING, &[1]);
499 m.emit_decorate(var_b, DECORATION_NON_WRITABLE, &[]);
500 m.emit_decorate(var_c, DECORATION_DESCRIPTOR_SET, &[0]);
501 m.emit_decorate(var_c, DECORATION_BINDING, &[2]);
502 m.emit_decorate(var_dim, DECORATION_DESCRIPTOR_SET, &[0]);
503 m.emit_decorate(var_dim, DECORATION_BINDING, &[3]);
504 m.emit_decorate(var_dim, DECORATION_NON_WRITABLE, &[]);
505 m.emit_decorate(var_wg_id, DECORATION_BUILTIN, &[BUILTIN_WORKGROUP_ID]);
506
507 m.emit_type_void(ty_void);
509 m.emit_type_int(ty_u32, 32, 0);
510 m.emit_type_float(ty_f32, 32);
511
512 m.emit_type_runtime_array(ty_rt_f32, ty_f32);
513 m.emit_type_runtime_array(ty_rt_u32, ty_u32);
514 m.emit_type_struct(ty_sb_f32, &[ty_rt_f32]);
515 m.emit_type_struct(ty_sb_u32, &[ty_rt_u32]);
516 m.emit_type_ptr(ty_ptr_sb_f32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f32);
517 m.emit_type_ptr(ty_ptr_sb_u32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_u32);
518 m.emit_type_ptr(ty_ptr_f32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f32);
519 m.emit_type_ptr(ty_ptr_u32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_u32);
520
521 m.emit_const_u32(ty_u32, c0, 0);
523 m.emit_const_u32(ty_u32, c1, 1);
524 m.emit_const_u32(ty_u32, c2, 2);
525 m.emit_const_u32(ty_u32, c_scope, SCOPE_SUBGROUP);
526 m.emit_const_u32(ty_u32, c_tile_m, tile.m);
527 m.emit_const_u32(ty_u32, c_tile_n, tile.n);
528 m.emit_const_u32(ty_u32, c_tile_k, tile.k);
529
530 m.emit_type_cooperative_matrix(ty_cmat_a, ty_f32, c_scope, c_tile_m, c_tile_k, c0);
533 m.emit_type_cooperative_matrix(ty_cmat_b, ty_f32, c_scope, c_tile_k, c_tile_n, c1);
534 m.emit_type_cooperative_matrix(ty_cmat_c, ty_f32, c_scope, c_tile_m, c_tile_n, c2);
535
536 let ty_v3u32_actual = ty_v3u32;
538 m.emit(30 , &[ty_v3u32_actual, ty_u32, 3]);
539 m.emit_type_ptr(ty_ptr_in_v3u32, STORAGE_CLASS_INPUT, ty_v3u32_actual);
540
541 m.emit_type_fn(ty_fn_void, ty_void, &[]);
542
543 m.emit_variable(ty_ptr_sb_f32, var_a, STORAGE_CLASS_STORAGE_BUFFER);
545 m.emit_variable(ty_ptr_sb_f32, var_b, STORAGE_CLASS_STORAGE_BUFFER);
546 m.emit_variable(ty_ptr_sb_f32, var_c, STORAGE_CLASS_STORAGE_BUFFER);
547 m.emit_variable(ty_ptr_sb_u32, var_dim, STORAGE_CLASS_STORAGE_BUFFER);
548 m.emit_variable(ty_ptr_in_v3u32, var_wg_id, STORAGE_CLASS_INPUT);
549
550 m.emit_function(ty_void, fn_main, 0, ty_fn_void);
552 m.emit_label(lbl_entry);
553
554 let wg_id = m.alloc_id();
556 m.emit_load(ty_v3u32_actual, wg_id, var_wg_id);
557
558 let wg_col = m.alloc_id();
560 let wg_row = m.alloc_id();
561 m.emit_composite_extract(ty_u32, wg_col, wg_id, 0);
562 m.emit_composite_extract(ty_u32, wg_row, wg_id, 1);
563
564 let ptr_m = m.alloc_id();
566 let ptr_n = m.alloc_id();
567 let ptr_k = m.alloc_id();
568 let dim_m = m.alloc_id();
569 let dim_n = m.alloc_id();
570 let dim_k = m.alloc_id();
571 m.emit_access_chain(ty_ptr_u32_sb, ptr_m, var_dim, &[c0, c0]);
572 m.emit_access_chain(ty_ptr_u32_sb, ptr_n, var_dim, &[c0, c1]);
573 m.emit_access_chain(ty_ptr_u32_sb, ptr_k, var_dim, &[c0, c2]);
574 m.emit_load(ty_u32, dim_m, ptr_m);
575 m.emit_load(ty_u32, dim_n, ptr_n);
576 m.emit_load(ty_u32, dim_k, ptr_k);
577
578 let row_base = m.alloc_id();
580 let col_base = m.alloc_id();
581 m.emit_i_mul(ty_u32, row_base, wg_row, c_tile_m);
582 m.emit_i_mul(ty_u32, col_base, wg_col, c_tile_n);
583
584 let c_row_stride = dim_n; let c_base_flat = m.alloc_id();
588 let c_base_tmp = m.alloc_id();
589 m.emit_i_mul(ty_u32, c_base_tmp, row_base, c_row_stride);
590 m.emit_i_add(ty_u32, c_base_flat, c_base_tmp, col_base);
591 let ptr_c_tile = m.alloc_id();
592 m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_c_tile, var_c, &[c0, c_base_flat]);
593
594 let mat_c_init = m.alloc_id();
595 m.emit_coop_matrix_load(ty_cmat_c, mat_c_init, ptr_c_tile, c0, c_row_stride);
597
598 let mat_acc_after = {
603 let a_base_flat = m.alloc_id();
613 m.emit_i_mul(ty_u32, a_base_flat, row_base, dim_k);
614 let ptr_a_tile = m.alloc_id();
615 m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_a_tile, var_a, &[c0, a_base_flat]);
616 let mat_a = m.alloc_id();
617 m.emit_coop_matrix_load(ty_cmat_a, mat_a, ptr_a_tile, c0, dim_k);
618
619 let ptr_b_tile = m.alloc_id();
621 m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_b_tile, var_b, &[c0, col_base]);
622 let mat_b = m.alloc_id();
623 m.emit_coop_matrix_load(ty_cmat_b, mat_b, ptr_b_tile, c0, dim_n);
624
625 let mat_tmp = m.alloc_id();
627 m.emit_coop_matrix_muladd(
628 ty_cmat_c,
629 mat_tmp,
630 mat_a,
631 mat_b,
632 mat_c_init,
633 COOPERATIVE_MATRIX_OPERANDS_NONE,
634 );
635 mat_tmp
636 };
637
638 m.emit_coop_matrix_store(ptr_c_tile, mat_acc_after, c0, c_row_stride);
640
641 m.emit_return();
642 m.emit_function_end();
643
644 m.finalize()
645}
646
647pub fn gemm_xmx_f16_spirv(tile: XmxTileConfig, wg_x: u32, wg_y: u32) -> Vec<u32> {
658 let mut m = XmxSpvModule::new();
659
660 m.emit_capability(CAPABILITY_SHADER);
662 m.emit_capability(CAPABILITY_FLOAT16);
663 m.emit_capability(CAPABILITY_COOPERATIVE_MATRIX_KHR);
664 m.emit_extension("SPV_KHR_cooperative_matrix");
665 m.emit_memory_model(ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450);
666
667 let ty_void = m.alloc_id();
669 let ty_u32 = m.alloc_id();
670 let ty_f16 = m.alloc_id();
671 let ty_f32 = m.alloc_id();
672
673 let ty_rt_f16 = m.alloc_id();
674 let ty_rt_f32 = m.alloc_id();
675 let ty_rt_u32 = m.alloc_id();
676 let ty_sb_f16 = m.alloc_id();
677 let ty_sb_f32 = m.alloc_id();
678 let ty_sb_u32 = m.alloc_id();
679 let ty_ptr_sb_f16 = m.alloc_id();
680 let ty_ptr_sb_f32 = m.alloc_id();
681 let ty_ptr_sb_u32 = m.alloc_id();
682 let ty_ptr_f16_sb = m.alloc_id();
683 let ty_ptr_f32_sb = m.alloc_id();
684 let ty_ptr_u32_sb = m.alloc_id();
685
686 let ty_cmat_a = m.alloc_id();
688 let ty_cmat_b = m.alloc_id();
689 let ty_cmat_c = m.alloc_id();
690
691 let ty_v3u32 = m.alloc_id();
692 let ty_ptr_in_v3u32 = m.alloc_id();
693 let ty_fn_void = m.alloc_id();
694
695 let var_a = m.alloc_id();
697 let var_b = m.alloc_id();
698 let var_c = m.alloc_id();
699 let var_dim = m.alloc_id();
700 let var_wg = m.alloc_id();
701 let fn_main = m.alloc_id();
702 let lbl = m.alloc_id();
703
704 m.emit_entry_point(
706 EXECUTION_MODEL_GLCOMPUTE,
707 fn_main,
708 "gemm_xmx_f16",
709 &[var_a, var_b, var_c, var_dim, var_wg],
710 );
711 m.emit_execution_mode_local_size(fn_main, wg_x, wg_y, 1);
712
713 m.emit_decorate(ty_rt_f16, 6, &[2]); m.emit_decorate(ty_rt_f32, 6, &[4]);
716 m.emit_decorate(ty_rt_u32, 6, &[4]);
717 m.emit_decorate(ty_sb_f16, DECORATION_BLOCK, &[]);
718 m.emit_decorate(ty_sb_f32, DECORATION_BLOCK, &[]);
719 m.emit_decorate(ty_sb_u32, DECORATION_BLOCK, &[]);
720 m.emit_member_decorate(ty_sb_f16, 0, 35, &[0]);
721 m.emit_member_decorate(ty_sb_f32, 0, 35, &[0]);
722 m.emit_member_decorate(ty_sb_u32, 0, 35, &[0]);
723 for (var, set, binding, writable) in [
724 (var_a, 0u32, 0u32, false),
725 (var_b, 0, 1, false),
726 (var_c, 0, 2, true),
727 (var_dim, 0, 3, false),
728 ] {
729 m.emit_decorate(var, DECORATION_DESCRIPTOR_SET, &[set]);
730 m.emit_decorate(var, DECORATION_BINDING, &[binding]);
731 if !writable {
732 m.emit_decorate(var, DECORATION_NON_WRITABLE, &[]);
733 }
734 }
735 m.emit_decorate(var_wg, DECORATION_BUILTIN, &[BUILTIN_WORKGROUP_ID]);
736
737 m.emit_type_void(ty_void);
739 m.emit_type_int(ty_u32, 32, 0);
740 m.emit_type_float(ty_f16, 16);
741 m.emit_type_float(ty_f32, 32);
742 m.emit_type_runtime_array(ty_rt_f16, ty_f16);
743 m.emit_type_runtime_array(ty_rt_f32, ty_f32);
744 m.emit_type_runtime_array(ty_rt_u32, ty_u32);
745 m.emit_type_struct(ty_sb_f16, &[ty_rt_f16]);
746 m.emit_type_struct(ty_sb_f32, &[ty_rt_f32]);
747 m.emit_type_struct(ty_sb_u32, &[ty_rt_u32]);
748 m.emit_type_ptr(ty_ptr_sb_f16, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f16);
749 m.emit_type_ptr(ty_ptr_sb_f32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_f32);
750 m.emit_type_ptr(ty_ptr_sb_u32, STORAGE_CLASS_STORAGE_BUFFER, ty_sb_u32);
751 m.emit_type_ptr(ty_ptr_f16_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f16);
752 m.emit_type_ptr(ty_ptr_f32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_f32);
753 m.emit_type_ptr(ty_ptr_u32_sb, STORAGE_CLASS_STORAGE_BUFFER, ty_u32);
754 let c0 = m.alloc_id();
757 let c1 = m.alloc_id();
758 let c2 = m.alloc_id();
759 let c_scope = m.alloc_id();
760 let c_tm = m.alloc_id();
761 let c_tn = m.alloc_id();
762 let c_tk = m.alloc_id();
763 m.emit_const_u32(ty_u32, c0, 0);
764 m.emit_const_u32(ty_u32, c1, 1);
765 m.emit_const_u32(ty_u32, c2, 2);
766 m.emit_const_u32(ty_u32, c_scope, SCOPE_SUBGROUP);
767 m.emit_const_u32(ty_u32, c_tm, tile.m);
768 m.emit_const_u32(ty_u32, c_tn, tile.n);
769 m.emit_const_u32(ty_u32, c_tk, tile.k);
770
771 m.emit_type_cooperative_matrix(ty_cmat_a, ty_f16, c_scope, c_tm, c_tk, c0);
773 m.emit_type_cooperative_matrix(ty_cmat_b, ty_f16, c_scope, c_tk, c_tn, c1);
774 m.emit_type_cooperative_matrix(ty_cmat_c, ty_f32, c_scope, c_tm, c_tn, c2);
775 m.emit(30, &[ty_v3u32, ty_u32, 3]); m.emit_type_ptr(ty_ptr_in_v3u32, STORAGE_CLASS_INPUT, ty_v3u32);
777 m.emit_type_fn(ty_fn_void, ty_void, &[]);
778
779 m.emit_variable(ty_ptr_sb_f16, var_a, STORAGE_CLASS_STORAGE_BUFFER);
781 m.emit_variable(ty_ptr_sb_f16, var_b, STORAGE_CLASS_STORAGE_BUFFER);
782 m.emit_variable(ty_ptr_sb_f32, var_c, STORAGE_CLASS_STORAGE_BUFFER);
783 m.emit_variable(ty_ptr_sb_u32, var_dim, STORAGE_CLASS_STORAGE_BUFFER);
784 m.emit_variable(ty_ptr_in_v3u32, var_wg, STORAGE_CLASS_INPUT);
785
786 m.emit_function(ty_void, fn_main, 0, ty_fn_void);
788 m.emit_label(lbl);
789
790 let wg_id = m.alloc_id();
791 m.emit_load(ty_v3u32, wg_id, var_wg);
792 let wg_col = m.alloc_id();
793 m.emit_composite_extract(ty_u32, wg_col, wg_id, 0);
794 let wg_row = m.alloc_id();
795 m.emit_composite_extract(ty_u32, wg_row, wg_id, 1);
796
797 let ptr_m = m.alloc_id();
798 m.emit_access_chain(ty_ptr_u32_sb, ptr_m, var_dim, &[c0, c0]);
799 let ptr_n = m.alloc_id();
800 m.emit_access_chain(ty_ptr_u32_sb, ptr_n, var_dim, &[c0, c1]);
801 let ptr_k = m.alloc_id();
802 m.emit_access_chain(ty_ptr_u32_sb, ptr_k, var_dim, &[c0, c2]);
803 let dim_m = m.alloc_id();
804 m.emit_load(ty_u32, dim_m, ptr_m);
805 let dim_n = m.alloc_id();
806 m.emit_load(ty_u32, dim_n, ptr_n);
807 let dim_k = m.alloc_id();
808 m.emit_load(ty_u32, dim_k, ptr_k);
809
810 let row_base = m.alloc_id();
811 m.emit_i_mul(ty_u32, row_base, wg_row, c_tm);
812 let col_base = m.alloc_id();
813 m.emit_i_mul(ty_u32, col_base, wg_col, c_tn);
814
815 let c_base_tmp = m.alloc_id();
817 m.emit_i_mul(ty_u32, c_base_tmp, row_base, dim_n);
818 let c_base_flat = m.alloc_id();
819 m.emit_i_add(ty_u32, c_base_flat, c_base_tmp, col_base);
820 let ptr_c_tile = m.alloc_id();
821 m.emit_in_bounds_access_chain(ty_ptr_f32_sb, ptr_c_tile, var_c, &[c0, c_base_flat]);
822 let mat_c_init = m.alloc_id();
823 m.emit_coop_matrix_load(ty_cmat_c, mat_c_init, ptr_c_tile, c0, dim_n);
825
826 let a_base = m.alloc_id();
828 m.emit_i_mul(ty_u32, a_base, row_base, dim_k);
829 let ptr_a = m.alloc_id();
830 m.emit_in_bounds_access_chain(ty_ptr_f16_sb, ptr_a, var_a, &[c0, a_base]);
831 let mat_a = m.alloc_id();
832 m.emit_coop_matrix_load(ty_cmat_a, mat_a, ptr_a, c0, dim_k);
833
834 let ptr_b = m.alloc_id();
836 m.emit_in_bounds_access_chain(ty_ptr_f16_sb, ptr_b, var_b, &[c0, col_base]);
837 let mat_b = m.alloc_id();
838 m.emit_coop_matrix_load(ty_cmat_b, mat_b, ptr_b, c0, dim_n);
839
840 let mat_out = m.alloc_id();
842 m.emit_coop_matrix_muladd(
843 ty_cmat_c,
844 mat_out,
845 mat_a,
846 mat_b,
847 mat_c_init,
848 COOPERATIVE_MATRIX_OPERANDS_NONE,
849 );
850
851 m.emit_coop_matrix_store(ptr_c_tile, mat_out, c0, dim_n);
853
854 m.emit_return();
855 m.emit_function_end();
856 m.finalize()
857}
858
859pub fn matmul_xmx_bf16_spirv(
878 tile: XmxTileConfig,
879 wg_x: u32,
880 wg_y: u32,
881) -> crate::error::LevelZeroResult<Vec<u32>> {
882 let _ = (tile, wg_x, wg_y);
883 Err(crate::error::LevelZeroError::Unsupported(
884 "BF16 XMX GEMM (matmul_xmx_bf16_spirv) is not yet implemented: it requires a genuine \
885 SPV_KHR_bfloat16 cooperative-matrix element type; reusing the FP16 kernel would \
886 reinterpret BF16 bits as IEEE binary16 and silently corrupt every result"
887 .into(),
888 ))
889}
890
891pub fn device_supports_xmx(device_name: &str) -> bool {
903 let name = device_name.to_ascii_lowercase();
904 name.contains("arc")
906 || name.contains("data center gpu max")
908 || name.contains("ponte vecchio")
909 || name.contains("max 1")
910 || name.contains("max 12")
911 || name.contains("iris xe")
913 || name.contains("uhd graphics")
914}
915
916pub fn best_xmx_tile(device_name: &str) -> XmxTileConfig {
920 let name = device_name.to_ascii_lowercase();
921 if name.contains("max") || name.contains("ponte vecchio") {
922 XmxTileConfig { m: 8, n: 32, k: 16 }
924 } else if name.contains("arc") || name.contains("iris xe") {
925 XmxTileConfig::XE_HPC_FP16
926 } else {
927 XmxTileConfig::XE_DEFAULT
928 }
929}
930
931#[cfg(test)]
934mod tests {
935 use super::*;
936
937 #[test]
938 fn gemm_xmx_spirv_starts_with_magic() {
939 let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
940 assert!(!words.is_empty(), "output must not be empty");
941 assert_eq!(words[0], 0x07230203, "first word must be SPIR-V magic");
942 }
943
944 #[test]
945 fn gemm_xmx_spirv_version_1_6() {
946 let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
947 assert_eq!(words[1], 0x0001_0600, "version must be SPIR-V 1.6");
948 }
949
950 #[test]
951 fn gemm_xmx_spirv_id_bound_nonzero() {
952 let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
953 assert!(words[3] > 0, "ID bound must be > 0");
954 }
955
956 #[test]
957 fn gemm_xmx_f16_produces_valid_header() {
958 let words = gemm_xmx_f16_spirv(XmxTileConfig::XE_HPC_FP16, 16, 16);
959 assert_eq!(words[0], SPIRV_MAGIC);
960 assert_eq!(words[1], SPIRV_VERSION_1_6);
961 assert!(words.len() > 20, "module must have non-trivial content");
962 }
963
964 #[test]
965 fn matmul_xmx_bf16_returns_unsupported() {
966 let result = matmul_xmx_bf16_spirv(XmxTileConfig::default(), 16, 16);
970 assert!(matches!(
971 result,
972 Err(crate::error::LevelZeroError::Unsupported(_))
973 ));
974 }
975
976 #[test]
977 fn xmx_tile_accum_elements() {
978 let tile = XmxTileConfig { m: 8, n: 16, k: 16 };
979 assert_eq!(tile.accum_elements(), 128);
980 }
981
982 #[test]
983 fn device_supports_xmx_arc() {
984 assert!(device_supports_xmx("Intel Arc A770 Graphics"));
985 assert!(device_supports_xmx("Intel Data Center GPU Max 1550"));
986 assert!(!device_supports_xmx("AMD Radeon RX 7900 XTX"));
987 }
988
989 #[test]
990 fn best_xmx_tile_xe_hpc() {
991 let tile = best_xmx_tile("Intel Data Center GPU Max 1550");
992 assert_eq!(tile.m, 8);
993 assert_eq!(tile.n, 32);
994 }
995
996 #[test]
997 fn different_tile_sizes_produce_different_binaries() {
998 let a = gemm_xmx_spirv(XmxTileConfig { m: 8, n: 16, k: 16 }, 16, 16);
999 let b = gemm_xmx_spirv(XmxTileConfig { m: 8, n: 32, k: 16 }, 16, 16);
1000 assert_ne!(
1001 a, b,
1002 "different tile configurations must yield distinct SPIR-V"
1003 );
1004 }
1005
1006 #[test]
1007 fn gemm_xmx_spirv_contains_cooperative_matrix_opcode() {
1008 let words = gemm_xmx_spirv(XmxTileConfig::default(), 16, 16);
1009 let has_cmat = words
1011 .iter()
1012 .any(|&w| (w & 0xFFFF) == OP_TYPE_COOPERATIVE_MATRIX_KHR);
1013 assert!(has_cmat, "module must declare OpTypeCooperativeMatrixKHR");
1014 }
1015
1016 #[test]
1017 fn gemm_xmx_f16_contains_float16_type() {
1018 let words = gemm_xmx_f16_spirv(XmxTileConfig::XE_HPC_FP16, 16, 16);
1019 let has_f16 = words.windows(3).any(|w| {
1021 (w[0] & 0xFFFF) == 22 && w[2] == 16 });
1023 assert!(has_f16, "FP16 module must declare 16-bit float type");
1024 }
1025}