1use crate::spirv::{
20 EXECUTION_MODEL_KERNEL, FUNCTION_CONTROL_NONE, OP_COMPOSITE_EXTRACT, OP_F_ADD, OP_F_MUL,
21 OP_I_ADD, OP_I_MUL, SpvModule,
22};
23
24const OP_SUBGROUP_BLOCK_READ_INTEL: u32 = 5575;
28const OP_SUBGROUP_BLOCK_WRITE_INTEL: u32 = 5576;
30
31const CAPABILITY_ADDRESSES: u32 = 4;
34const CAPABILITY_KERNEL: u32 = 6;
35const CAPABILITY_SUBGROUP_SHUFFLE_INTEL: u32 = 5568;
37const CAPABILITY_SUBGROUP_BUFFER_BLOCK_IO_INTEL: u32 = 5569;
39
40const ADDRESSING_MODEL_PHYSICAL64: u32 = 2;
43const MEMORY_MODEL_OPENCL: u32 = 2;
44
45const DECORATION_BUILTIN: u32 = 11;
48const BUILTIN_GLOBAL_INVOCATION_ID: u32 = 28;
49
50const STORAGE_CLASS_INPUT: u32 = 1;
53const STORAGE_CLASS_CROSS_WORKGROUP: u32 = 5;
54
55const WORKGROUP_SIZE: u32 = 16;
58
59pub fn esimd_block_copy_spirv() -> Vec<u32> {
72 let mut m = SpvModule::new();
73
74 m.emit_capability(CAPABILITY_KERNEL);
76 m.emit_capability(CAPABILITY_ADDRESSES);
77 m.emit_capability(CAPABILITY_SUBGROUP_SHUFFLE_INTEL);
78 m.emit_capability(CAPABILITY_SUBGROUP_BUFFER_BLOCK_IO_INTEL);
79
80 m.emit_extension("SPV_INTEL_subgroups");
82
83 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
85
86 let ty_void = m.alloc_id();
88 let ty_uint = m.alloc_id();
89 let ty_float = m.alloc_id();
90 let ty_v3uint = m.alloc_id();
91 let ty_ptr_input_v3uint = m.alloc_id();
92 let ty_ptr_cross_float = m.alloc_id();
93 let ty_fn = m.alloc_id();
94
95 let var_gid = m.alloc_id();
97 let main_fn = m.alloc_id();
98 let p_input = m.alloc_id();
99 let p_output = m.alloc_id();
100 let label = m.alloc_id();
101
102 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
103
104 m.emit_type_void(ty_void);
105 m.emit_type_int(ty_uint, 32, 0);
106 m.emit_type_float(ty_float, 32);
107 m.emit_type_vector(ty_v3uint, ty_uint, 3);
108 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
109 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
110 m.emit_type_function(ty_fn, ty_void, &[ty_ptr_cross_float, ty_ptr_cross_float]);
111
112 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
113
114 m.emit_entry_point(
115 EXECUTION_MODEL_KERNEL,
116 main_fn,
117 "esimd_block_copy",
118 &[var_gid],
119 );
120 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
121
122 m.emit_function(ty_void, main_fn, FUNCTION_CONTROL_NONE, ty_fn);
123 m.emit_function_parameter(ty_ptr_cross_float, p_input);
124 m.emit_function_parameter(ty_ptr_cross_float, p_output);
125 m.emit_label(label);
126
127 let gid_val = m.alloc_id();
129 m.emit_load(ty_v3uint, gid_val, var_gid);
130 let gid_x = m.alloc_id();
131 m.emit(OP_COMPOSITE_EXTRACT, &[ty_uint, gid_x, gid_val, 0]);
132
133 let in_ptr = m.alloc_id();
135 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, in_ptr, p_input, gid_x);
136 let block_val = m.alloc_id();
138 m.emit(OP_SUBGROUP_BLOCK_READ_INTEL, &[ty_float, block_val, in_ptr]);
139
140 let out_ptr = m.alloc_id();
142 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, out_ptr, p_output, gid_x);
143 m.emit(OP_SUBGROUP_BLOCK_WRITE_INTEL, &[out_ptr, block_val]);
144
145 m.emit_return();
146 m.emit_function_end();
147
148 m.finalize()
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub struct DpasTileConfig {
159 pub m: u32,
161 pub n: u32,
163 pub k: u32,
165}
166
167impl DpasTileConfig {
168 pub const DG2_BF16: Self = Self { m: 8, n: 8, k: 16 };
170 pub const DG2_INT8: Self = Self { m: 8, n: 8, k: 32 };
172
173 #[must_use]
175 pub fn mac_lanes(&self) -> u32 {
176 self.m * self.n
177 }
178}
179
180pub fn dpas_gemm_spirv(tile: DpasTileConfig) -> Vec<u32> {
194 let mut m = SpvModule::new();
195
196 m.emit_capability(CAPABILITY_KERNEL);
198 m.emit_capability(CAPABILITY_ADDRESSES);
199 m.emit_capability(CAPABILITY_SUBGROUP_SHUFFLE_INTEL);
200 m.emit_capability(CAPABILITY_SUBGROUP_BUFFER_BLOCK_IO_INTEL);
201
202 m.emit_extension("SPV_INTEL_subgroups");
204
205 m.emit_memory_model(ADDRESSING_MODEL_PHYSICAL64, MEMORY_MODEL_OPENCL);
207
208 let ty_void = m.alloc_id();
210 let ty_uint = m.alloc_id();
211 let ty_float = m.alloc_id();
212 let ty_v3uint = m.alloc_id();
213 let ty_ptr_input_v3uint = m.alloc_id();
214 let ty_ptr_cross_float = m.alloc_id();
215 let ty_fn = m.alloc_id();
216
217 let c_k = m.alloc_id();
219 let c_tile_m = m.alloc_id();
220 let c_tile_n = m.alloc_id();
221
222 let var_gid = m.alloc_id();
223 let main_fn = m.alloc_id();
224 let p_a = m.alloc_id();
225 let p_b = m.alloc_id();
226 let p_c = m.alloc_id();
227 let p_k = m.alloc_id();
228 let label = m.alloc_id();
229
230 m.emit_decorate(var_gid, DECORATION_BUILTIN, &[BUILTIN_GLOBAL_INVOCATION_ID]);
231
232 m.emit_type_void(ty_void);
233 m.emit_type_int(ty_uint, 32, 0);
234 m.emit_type_float(ty_float, 32);
235 m.emit_type_vector(ty_v3uint, ty_uint, 3);
236 m.emit_type_pointer(ty_ptr_input_v3uint, STORAGE_CLASS_INPUT, ty_v3uint);
237 m.emit_type_pointer(ty_ptr_cross_float, STORAGE_CLASS_CROSS_WORKGROUP, ty_float);
238 m.emit_type_function(
239 ty_fn,
240 ty_void,
241 &[
242 ty_ptr_cross_float,
243 ty_ptr_cross_float,
244 ty_ptr_cross_float,
245 ty_uint,
246 ],
247 );
248
249 m.emit_constant_u32(ty_uint, c_k, tile.k);
250 m.emit_constant_u32(ty_uint, c_tile_m, tile.m);
251 m.emit_constant_u32(ty_uint, c_tile_n, tile.n);
252
253 m.emit_variable(ty_ptr_input_v3uint, var_gid, STORAGE_CLASS_INPUT);
254
255 m.emit_entry_point(EXECUTION_MODEL_KERNEL, main_fn, "dpas_gemm", &[var_gid]);
256 m.emit_execution_mode_local_size(main_fn, WORKGROUP_SIZE, 1, 1);
257
258 m.emit_function(ty_void, main_fn, FUNCTION_CONTROL_NONE, ty_fn);
259 m.emit_function_parameter(ty_ptr_cross_float, p_a);
260 m.emit_function_parameter(ty_ptr_cross_float, p_b);
261 m.emit_function_parameter(ty_ptr_cross_float, p_c);
262 m.emit_function_parameter(ty_uint, p_k);
263 m.emit_label(label);
264
265 let gid_val = m.alloc_id();
267 m.emit_load(ty_v3uint, gid_val, var_gid);
268 let lane = m.alloc_id();
269 m.emit(OP_COMPOSITE_EXTRACT, &[ty_uint, lane, gid_val, 0]);
270
271 let a_ptr = m.alloc_id();
273 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, a_ptr, p_a, lane);
274 let a_blk = m.alloc_id();
275 m.emit(OP_SUBGROUP_BLOCK_READ_INTEL, &[ty_float, a_blk, a_ptr]);
276
277 let b_ptr = m.alloc_id();
278 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, b_ptr, p_b, lane);
279 let b_blk = m.alloc_id();
280 m.emit(OP_SUBGROUP_BLOCK_READ_INTEL, &[ty_float, b_blk, b_ptr]);
281
282 let prod = m.alloc_id();
284 m.emit(OP_F_MUL, &[ty_float, prod, a_blk, b_blk]);
285
286 let tile_area = m.alloc_id();
291 m.emit(OP_I_MUL, &[ty_uint, tile_area, c_tile_m, c_tile_n]);
292 let tile_volume = m.alloc_id();
293 m.emit(OP_I_MUL, &[ty_uint, tile_volume, tile_area, c_k]);
294 let depth_span = m.alloc_id();
296 m.emit(OP_I_MUL, &[ty_uint, depth_span, p_k, tile_volume]);
297 let out_idx = m.alloc_id();
298 m.emit(OP_I_ADD, &[ty_uint, out_idx, lane, depth_span]);
299
300 let acc = m.alloc_id();
302 m.emit(OP_F_ADD, &[ty_float, acc, prod, a_blk]);
303 let c_ptr = m.alloc_id();
304 m.emit_in_bounds_ptr_access_chain(ty_ptr_cross_float, c_ptr, p_c, out_idx);
305 m.emit(OP_SUBGROUP_BLOCK_WRITE_INTEL, &[c_ptr, acc]);
306
307 m.emit_return();
308 m.emit_function_end();
309
310 m.finalize()
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub enum Fp8Format {
318 E4m3,
320 E5m2,
322}
323
324impl Fp8Format {
325 #[must_use]
327 pub fn exponent_bits(self) -> u32 {
328 match self {
329 Fp8Format::E4m3 => 4,
330 Fp8Format::E5m2 => 5,
331 }
332 }
333
334 #[must_use]
336 pub fn mantissa_bits(self) -> u32 {
337 match self {
338 Fp8Format::E4m3 => 3,
339 Fp8Format::E5m2 => 2,
340 }
341 }
342
343 #[must_use]
345 pub fn intel_name(self) -> &'static str {
346 match self {
347 Fp8Format::E4m3 => "HF8",
348 Fp8Format::E5m2 => "BF8",
349 }
350 }
351}
352
353const OP_CAPABILITY: u32 = 17;
355const OP_EXTENSION: u32 = 10;
356const OP_MEMORY_MODEL: u32 = 14;
357const OP_ENTRY_POINT: u32 = 15;
358const OP_EXECUTION_MODE: u32 = 16;
359const OP_TYPE_VOID: u32 = 19;
360const OP_TYPE_INT: u32 = 21;
361const OP_TYPE_FLOAT: u32 = 22;
362const OP_TYPE_FUNCTION: u32 = 33;
363const OP_CONSTANT: u32 = 43;
364const OP_FUNCTION: u32 = 54;
365const OP_FUNCTION_END: u32 = 56;
366const OP_LABEL: u32 = 248;
367const OP_RETURN: u32 = 253;
368const OP_TYPE_COOPERATIVE_MATRIX_KHR: u32 = 4456;
369
370const SPIRV_MAGIC: u32 = 0x0723_0203;
371const SPIRV_VERSION_1_6: u32 = 0x0001_0600;
372const SPIRV_GENERATOR: u32 = 0x000D_0004; const CAPABILITY_SHADER: u32 = 1;
375const CAPABILITY_COOPERATIVE_MATRIX_KHR: u32 = 6022;
376const CAPABILITY_FLOAT8_EXT: u32 = 6212;
378
379const FLOAT8_E4M3_EXT: u32 = 4214;
381const FLOAT8_E5M2_EXT: u32 = 4215;
383
384const ADDRESSING_MODEL_LOGICAL: u32 = 0;
385const MEMORY_MODEL_GLSL450: u32 = 1;
386const EXECUTION_MODEL_GLCOMPUTE: u32 = 5;
387const EXECUTION_MODE_LOCAL_SIZE: u32 = 17;
388const SCOPE_SUBGROUP: u32 = 3;
389const MATRIX_USE_A: u32 = 0;
390const MATRIX_USE_B: u32 = 1;
391const MATRIX_USE_ACCUMULATOR: u32 = 2;
392
393fn push_inst(words: &mut Vec<u32>, opcode: u32, operands: &[u32]) {
395 let word_count = (1 + operands.len()) as u32;
396 words.push((word_count << 16) | opcode);
397 words.extend_from_slice(operands);
398}
399
400fn string_words(s: &str) -> Vec<u32> {
402 let bytes = s.as_bytes();
403 let padded_len = (bytes.len() + 4) & !3;
404 let mut out = vec![0u32; padded_len / 4];
405 for (i, &b) in bytes.iter().enumerate() {
406 out[i / 4] |= u32::from(b) << ((i % 4) * 8);
407 }
408 out
409}
410
411pub fn gemm_fp8_coop_matrix_spirv(
423 format: Fp8Format,
424 tile_m: u32,
425 tile_n: u32,
426 tile_k: u32,
427) -> Vec<u32> {
428 let mut words = vec![SPIRV_MAGIC, SPIRV_VERSION_1_6, SPIRV_GENERATOR, 0, 0];
429 let mut next_id = 1u32;
430 let mut alloc = || {
431 let id = next_id;
432 next_id += 1;
433 id
434 };
435
436 push_inst(&mut words, OP_CAPABILITY, &[CAPABILITY_SHADER]);
438 push_inst(
439 &mut words,
440 OP_CAPABILITY,
441 &[CAPABILITY_COOPERATIVE_MATRIX_KHR],
442 );
443 push_inst(&mut words, OP_CAPABILITY, &[CAPABILITY_FLOAT8_EXT]);
444
445 {
447 let ext = string_words("SPV_KHR_cooperative_matrix");
448 push_inst(&mut words, OP_EXTENSION, &ext);
449 }
450 {
451 let ext = string_words("SPV_EXT_float8");
452 push_inst(&mut words, OP_EXTENSION, &ext);
453 }
454
455 push_inst(
457 &mut words,
458 OP_MEMORY_MODEL,
459 &[ADDRESSING_MODEL_LOGICAL, MEMORY_MODEL_GLSL450],
460 );
461
462 let ty_void = alloc();
464 let ty_u32 = alloc();
465 let ty_f8 = alloc(); let ty_f32 = alloc(); let ty_cmat_a = alloc();
468 let ty_cmat_b = alloc();
469 let ty_cmat_c = alloc();
470 let ty_fn = alloc();
471 let c_m = alloc();
472 let c_n = alloc();
473 let c_k = alloc();
474 let main_fn = alloc();
475 let label = alloc();
476
477 push_inst(&mut words, OP_TYPE_VOID, &[ty_void]);
478 push_inst(&mut words, OP_TYPE_INT, &[ty_u32, 32, 0]);
479 let fp8_encoding = match format {
484 Fp8Format::E4m3 => FLOAT8_E4M3_EXT,
485 Fp8Format::E5m2 => FLOAT8_E5M2_EXT,
486 };
487 push_inst(&mut words, OP_TYPE_FLOAT, &[ty_f8, 8, fp8_encoding]);
488 push_inst(&mut words, OP_TYPE_FLOAT, &[ty_f32, 32]);
489
490 push_inst(&mut words, OP_CONSTANT, &[ty_u32, c_m, tile_m]);
491 push_inst(&mut words, OP_CONSTANT, &[ty_u32, c_n, tile_n]);
492 push_inst(&mut words, OP_CONSTANT, &[ty_u32, c_k, tile_k]);
493
494 push_inst(
496 &mut words,
497 OP_TYPE_COOPERATIVE_MATRIX_KHR,
498 &[ty_cmat_a, ty_f8, SCOPE_SUBGROUP, c_m, c_k, MATRIX_USE_A],
499 );
500 push_inst(
501 &mut words,
502 OP_TYPE_COOPERATIVE_MATRIX_KHR,
503 &[ty_cmat_b, ty_f8, SCOPE_SUBGROUP, c_k, c_n, MATRIX_USE_B],
504 );
505 push_inst(
506 &mut words,
507 OP_TYPE_COOPERATIVE_MATRIX_KHR,
508 &[
509 ty_cmat_c,
510 ty_f32,
511 SCOPE_SUBGROUP,
512 c_m,
513 c_n,
514 MATRIX_USE_ACCUMULATOR,
515 ],
516 );
517
518 push_inst(&mut words, OP_TYPE_FUNCTION, &[ty_fn, ty_void]);
519
520 {
522 let mut ops = vec![EXECUTION_MODEL_GLCOMPUTE, main_fn];
523 ops.extend(string_words("gemm_fp8"));
524 push_inst(&mut words, OP_ENTRY_POINT, &ops);
525 }
526 push_inst(
527 &mut words,
528 OP_EXECUTION_MODE,
529 &[main_fn, EXECUTION_MODE_LOCAL_SIZE, 16, 1, 1],
530 );
531
532 push_inst(&mut words, OP_FUNCTION, &[ty_void, main_fn, 0, ty_fn]);
534 push_inst(&mut words, OP_LABEL, &[label]);
535 push_inst(&mut words, OP_RETURN, &[]);
536 push_inst(&mut words, OP_FUNCTION_END, &[]);
537
538 words[3] = next_id;
539 words
540}
541
542#[cfg(test)]
545mod tests {
546 use super::*;
547 use crate::spirv::{OP_CAPABILITY, OP_EXTENSION, SPIRV_MAGIC};
548
549 fn decode(words: &[u32]) -> Vec<(u32, Vec<u32>)> {
552 let mut out = Vec::new();
553 let mut i = 5;
554 while i < words.len() {
555 let wc = (words[i] >> 16) as usize;
556 let op = words[i] & 0xffff;
557 if wc == 0 || i + wc > words.len() {
558 break;
559 }
560 out.push((op, words[i + 1..i + wc].to_vec()));
561 i += wc;
562 }
563 out
564 }
565
566 fn header_ok(words: &[u32]) {
567 assert!(words.len() >= 6);
568 assert_eq!(words[0], SPIRV_MAGIC, "bad magic");
569 assert!(words[3] > 0, "id bound must be > 0");
570 assert_eq!(words[4], 0, "schema must be 0");
571 }
572
573 fn ext_string(operands: &[u32]) -> String {
575 let mut bytes = Vec::new();
576 for w in operands {
577 for shift in 0..4 {
578 let b = ((w >> (shift * 8)) & 0xff) as u8;
579 if b == 0 {
580 return String::from_utf8_lossy(&bytes).into_owned();
581 }
582 bytes.push(b);
583 }
584 }
585 String::from_utf8_lossy(&bytes).into_owned()
586 }
587
588 #[test]
589 fn block_copy_header_and_alignment() {
590 let words = esimd_block_copy_spirv();
591 header_ok(&words);
592 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
593 assert_eq!(bytes.len() % 4, 0);
594 }
595
596 #[test]
597 fn block_copy_declares_intel_subgroups_extension() {
598 let words = esimd_block_copy_spirv();
599 let insts = decode(&words);
600 let has_ext = insts
601 .iter()
602 .filter(|(op, _)| *op == OP_EXTENSION)
603 .any(|(_, ops)| ext_string(ops) == "SPV_INTEL_subgroups");
604 assert!(has_ext, "must declare SPV_INTEL_subgroups");
605 }
606
607 #[test]
608 fn block_copy_declares_block_io_capability() {
609 let words = esimd_block_copy_spirv();
610 let insts = decode(&words);
611 let caps: Vec<u32> = insts
612 .iter()
613 .filter(|(op, _)| *op == OP_CAPABILITY)
614 .map(|(_, ops)| ops[0])
615 .collect();
616 assert!(caps.contains(&CAPABILITY_SUBGROUP_BUFFER_BLOCK_IO_INTEL));
617 assert!(caps.contains(&CAPABILITY_KERNEL));
618 }
619
620 #[test]
621 fn block_copy_emits_block_read_and_write() {
622 let words = esimd_block_copy_spirv();
623 let insts = decode(&words);
624 assert!(
625 insts
626 .iter()
627 .any(|(op, _)| *op == OP_SUBGROUP_BLOCK_READ_INTEL),
628 "missing OpSubgroupBlockReadINTEL"
629 );
630 assert!(
631 insts
632 .iter()
633 .any(|(op, _)| *op == OP_SUBGROUP_BLOCK_WRITE_INTEL),
634 "missing OpSubgroupBlockWriteINTEL"
635 );
636 }
637
638 #[test]
639 fn dpas_tile_config_constants() {
640 assert_eq!(DpasTileConfig::DG2_BF16.mac_lanes(), 64);
641 assert_eq!(DpasTileConfig::DG2_INT8.k, 32);
642 }
643
644 #[test]
645 fn dpas_gemm_header_and_block_ops() {
646 let words = dpas_gemm_spirv(DpasTileConfig::DG2_BF16);
647 header_ok(&words);
648 let insts = decode(&words);
649 assert!(
650 insts
651 .iter()
652 .any(|(op, _)| *op == OP_SUBGROUP_BLOCK_READ_INTEL)
653 );
654 assert!(
655 insts
656 .iter()
657 .any(|(op, _)| *op == OP_SUBGROUP_BLOCK_WRITE_INTEL)
658 );
659 let has_k_const = insts
661 .iter()
662 .any(|(op, ops)| *op == crate::spirv::OP_CONSTANT && ops.last() == Some(&16));
663 assert!(has_k_const, "systolic depth constant 16 must be baked");
664 }
665
666 #[test]
667 fn dpas_gemm_distinct_tiles_distinct_binaries() {
668 let bf16 = dpas_gemm_spirv(DpasTileConfig::DG2_BF16);
669 let int8 = dpas_gemm_spirv(DpasTileConfig::DG2_INT8);
670 assert_ne!(bf16, int8, "different k must change the baked constant");
671 }
672
673 #[test]
674 fn fp8_format_fields() {
675 assert_eq!(Fp8Format::E4m3.exponent_bits(), 4);
676 assert_eq!(Fp8Format::E4m3.mantissa_bits(), 3);
677 assert_eq!(Fp8Format::E4m3.intel_name(), "HF8");
678 assert_eq!(Fp8Format::E5m2.exponent_bits(), 5);
679 assert_eq!(Fp8Format::E5m2.intel_name(), "BF8");
680 }
681
682 #[test]
683 fn fp8_coop_matrix_header_and_capabilities() {
684 let words = gemm_fp8_coop_matrix_spirv(Fp8Format::E4m3, 8, 16, 16);
685 header_ok(&words);
686 assert_eq!(
687 words[1], SPIRV_VERSION_1_6,
688 "FP8 coop-matrix needs SPIR-V 1.6"
689 );
690 let insts = decode(&words);
691 let caps: Vec<u32> = insts
692 .iter()
693 .filter(|(op, _)| *op == OP_CAPABILITY)
694 .map(|(_, ops)| ops[0])
695 .collect();
696 assert!(
697 caps.contains(&CAPABILITY_FLOAT8_EXT),
698 "Float8EXT capability required"
699 );
700 assert!(caps.contains(&CAPABILITY_COOPERATIVE_MATRIX_KHR));
701 }
702
703 #[test]
704 fn fp8_coop_matrix_declares_extensions_and_8bit_float() {
705 let words = gemm_fp8_coop_matrix_spirv(Fp8Format::E5m2, 8, 16, 16);
706 let insts = decode(&words);
707 let exts: Vec<String> = insts
709 .iter()
710 .filter(|(op, _)| *op == OP_EXTENSION)
711 .map(|(_, ops)| ext_string(ops))
712 .collect();
713 assert!(exts.iter().any(|e| e == "SPV_KHR_cooperative_matrix"));
714 assert!(exts.iter().any(|e| e == "SPV_EXT_float8"));
715
716 let has_f8 = insts
718 .iter()
719 .any(|(op, ops)| *op == OP_TYPE_FLOAT && ops.get(1) == Some(&8));
720 assert!(has_f8, "8-bit OpTypeFloat must be declared");
721 let cmat_count = insts
723 .iter()
724 .filter(|(op, _)| *op == OP_TYPE_COOPERATIVE_MATRIX_KHR)
725 .count();
726 assert_eq!(cmat_count, 3);
727 }
728
729 #[test]
730 fn fp8_coop_matrix_word_aligned() {
731 let words = gemm_fp8_coop_matrix_spirv(Fp8Format::E4m3, 8, 32, 16);
732 let bytes: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
733 assert_eq!(bytes.len() % 4, 0);
734 }
735
736 #[test]
737 fn fp8_e4m3_and_e5m2_produce_distinct_binaries() {
738 let e4m3 = gemm_fp8_coop_matrix_spirv(Fp8Format::E4m3, 8, 16, 16);
739 let e5m2 = gemm_fp8_coop_matrix_spirv(Fp8Format::E5m2, 8, 16, 16);
740 assert_ne!(
741 e4m3, e5m2,
742 "the two FP8 encodings must yield distinct SPIR-V modules"
743 );
744 }
745
746 #[test]
747 fn fp8_type_carries_encoding_operand() {
748 let e4m3 = decode(&gemm_fp8_coop_matrix_spirv(Fp8Format::E4m3, 8, 16, 16));
751 let e5m2 = decode(&gemm_fp8_coop_matrix_spirv(Fp8Format::E5m2, 8, 16, 16));
752 let enc = |insts: &[(u32, Vec<u32>)]| -> Option<u32> {
753 insts
754 .iter()
755 .find(|(op, ops)| *op == OP_TYPE_FLOAT && ops.get(1) == Some(&8))
756 .and_then(|(_, ops)| ops.get(2).copied())
757 };
758 assert_eq!(enc(&e4m3), Some(FLOAT8_E4M3_EXT));
759 assert_eq!(enc(&e5m2), Some(FLOAT8_E5M2_EXT));
760 }
761}