1use super::spirv::{
9 FUNCTION_CONTROL_NONE, OP_F_ADD, OP_F_DIV, OP_F_MUL, OP_F_SUB, OP_I_ADD, OP_I_MUL, OP_U_DIV,
10 OP_U_LESS_THAN, OP_U_MOD, OPENCL_EXP, OPENCL_FMAX, STORAGE_CLASS_FUNCTION, SpvModule,
11 emit_preamble, load_gid_x,
12};
13
14const OP_I_SUB: u32 = 130;
16const OP_F_ORD_GT: u32 = 188;
18
19#[allow(clippy::too_many_arguments)]
35pub fn conv2d_spirv(
36 n: u32,
37 c_in: u32,
38 h_in: u32,
39 w_in: u32,
40 k_out: u32,
41 fh: u32,
42 fw: u32,
43 oh: u32,
44 ow: u32,
45 stride_h: u32,
46 stride_w: u32,
47 pad_h: u32,
48 pad_w: u32,
49) -> Vec<u32> {
50 let mut m = SpvModule::new();
51 let b = emit_preamble(&mut m, "main");
52
53 let fn_ty = m.alloc_id();
54 let p_input = m.alloc_id();
55 let p_filter = m.alloc_id();
56 let p_output = m.alloc_id();
57
58 m.emit_type_function(
60 fn_ty,
61 b.ty_void,
62 &[
63 b.ty_ptr_cross_float,
64 b.ty_ptr_cross_float,
65 b.ty_ptr_cross_float,
66 ],
67 );
68
69 let c_n = m.alloc_id();
71 m.emit_constant_u32(b.ty_uint, c_n, n);
72 let c_c_in = m.alloc_id();
73 m.emit_constant_u32(b.ty_uint, c_c_in, c_in);
74 let c_h_in = m.alloc_id();
75 m.emit_constant_u32(b.ty_uint, c_h_in, h_in);
76 let c_w_in = m.alloc_id();
77 m.emit_constant_u32(b.ty_uint, c_w_in, w_in);
78 let c_k_out = m.alloc_id();
79 m.emit_constant_u32(b.ty_uint, c_k_out, k_out);
80 let c_fh = m.alloc_id();
81 m.emit_constant_u32(b.ty_uint, c_fh, fh);
82 let c_fw = m.alloc_id();
83 m.emit_constant_u32(b.ty_uint, c_fw, fw);
84 let c_oh = m.alloc_id();
85 m.emit_constant_u32(b.ty_uint, c_oh, oh);
86 let c_ow = m.alloc_id();
87 m.emit_constant_u32(b.ty_uint, c_ow, ow);
88 let c_stride_h = m.alloc_id();
89 m.emit_constant_u32(b.ty_uint, c_stride_h, stride_h);
90 let c_stride_w = m.alloc_id();
91 m.emit_constant_u32(b.ty_uint, c_stride_w, stride_w);
92 let c_pad_h = m.alloc_id();
93 m.emit_constant_u32(b.ty_uint, c_pad_h, pad_h);
94 let c_pad_w = m.alloc_id();
95 m.emit_constant_u32(b.ty_uint, c_pad_w, pad_w);
96
97 let label_entry = m.alloc_id();
99 let label_body = m.alloc_id();
100 let label_merge = m.alloc_id();
101
102 m.emit_function(b.ty_void, b.main_fn, FUNCTION_CONTROL_NONE, fn_ty);
104 m.emit_function_parameter(b.ty_ptr_cross_float, p_input);
105 m.emit_function_parameter(b.ty_ptr_cross_float, p_filter);
106 m.emit_function_parameter(b.ty_ptr_cross_float, p_output);
107 m.emit_label(label_entry);
108
109 let gid = load_gid_x(&mut m, &b);
110
111 let t1 = m.alloc_id();
113 m.emit(OP_I_MUL, &[b.ty_uint, t1, c_n, c_k_out]);
114 let t2 = m.alloc_id();
115 m.emit(OP_I_MUL, &[b.ty_uint, t2, t1, c_oh]);
116 let total = m.alloc_id();
117 m.emit(OP_I_MUL, &[b.ty_uint, total, t2, c_ow]);
118
119 let cond = m.alloc_id();
120 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, total]);
121 m.emit_selection_merge(label_merge);
122 m.emit_branch_conditional(cond, label_body, label_merge);
123
124 m.emit_label(label_body);
125
126 let ox = m.alloc_id();
128 m.emit(OP_U_MOD, &[b.ty_uint, ox, gid, c_ow]);
129 let tmp1 = m.alloc_id();
130 m.emit(OP_U_DIV, &[b.ty_uint, tmp1, gid, c_ow]);
131 let oy = m.alloc_id();
132 m.emit(OP_U_MOD, &[b.ty_uint, oy, tmp1, c_oh]);
133 let tmp2 = m.alloc_id();
134 m.emit(OP_U_DIV, &[b.ty_uint, tmp2, tmp1, c_oh]);
135 let kf = m.alloc_id();
136 m.emit(OP_U_MOD, &[b.ty_uint, kf, tmp2, c_k_out]);
137 let b_idx = m.alloc_id();
138 m.emit(OP_U_DIV, &[b.ty_uint, b_idx, tmp2, c_k_out]);
139
140 let var_acc = m.alloc_id();
142 m.emit_variable(b.ty_ptr_func_float, var_acc, STORAGE_CLASS_FUNCTION);
143 m.emit_store(var_acc, b.c_float_0);
144
145 let flat_total_id = m.alloc_id();
147 let flat_t1 = m.alloc_id();
148 m.emit(OP_I_MUL, &[b.ty_uint, flat_t1, c_c_in, c_fh]);
149 m.emit(OP_I_MUL, &[b.ty_uint, flat_total_id, flat_t1, c_fw]);
150
151 let var_flat = m.alloc_id();
152 m.emit_variable(b.ty_ptr_func_uint, var_flat, STORAGE_CLASS_FUNCTION);
153 m.emit_store(var_flat, b.c_uint_0);
154
155 let lbl_loop_hdr = m.alloc_id();
156 let lbl_loop_body = m.alloc_id();
157 let lbl_loop_cont = m.alloc_id();
158 let lbl_loop_merge = m.alloc_id();
159
160 m.emit_branch(lbl_loop_hdr);
161
162 m.emit_label(lbl_loop_hdr);
164 let flat_val = m.alloc_id();
165 m.emit_load(b.ty_uint, flat_val, var_flat);
166 let loop_cond = m.alloc_id();
167 m.emit(
168 OP_U_LESS_THAN,
169 &[b.ty_bool, loop_cond, flat_val, flat_total_id],
170 );
171 m.emit_loop_merge(lbl_loop_merge, lbl_loop_cont);
172 m.emit_branch_conditional(loop_cond, lbl_loop_body, lbl_loop_merge);
173
174 m.emit_label(lbl_loop_body);
176
177 let fx = m.alloc_id();
179 m.emit(OP_U_MOD, &[b.ty_uint, fx, flat_val, c_fw]);
180 let ftmp1 = m.alloc_id();
181 m.emit(OP_U_DIV, &[b.ty_uint, ftmp1, flat_val, c_fw]);
182 let fy = m.alloc_id();
183 m.emit(OP_U_MOD, &[b.ty_uint, fy, ftmp1, c_fh]);
184 let ci = m.alloc_id();
185 m.emit(OP_U_DIV, &[b.ty_uint, ci, ftmp1, c_fh]);
186
187 let oy_sh = m.alloc_id();
189 m.emit(OP_I_MUL, &[b.ty_uint, oy_sh, oy, c_stride_h]);
190 let iy_raw = m.alloc_id();
191 m.emit(OP_I_ADD, &[b.ty_uint, iy_raw, oy_sh, fy]);
192 let ox_sw = m.alloc_id();
193 m.emit(OP_I_MUL, &[b.ty_uint, ox_sw, ox, c_stride_w]);
194 let ix_raw = m.alloc_id();
195 m.emit(OP_I_ADD, &[b.ty_uint, ix_raw, ox_sw, fx]);
196
197 let lbl_skip = m.alloc_id();
200
201 let iy_lt_pad = m.alloc_id();
202 m.emit(OP_U_LESS_THAN, &[b.ty_bool, iy_lt_pad, iy_raw, c_pad_h]);
203 let lbl_iy_ok = m.alloc_id();
204 m.emit_selection_merge(lbl_skip);
205 m.emit_branch_conditional(iy_lt_pad, lbl_skip, lbl_iy_ok);
206
207 m.emit_label(lbl_iy_ok);
208 let iy_real = m.alloc_id();
209 m.emit(OP_I_SUB, &[b.ty_uint, iy_real, iy_raw, c_pad_h]);
210 let iy_in_bounds = m.alloc_id();
211 m.emit(OP_U_LESS_THAN, &[b.ty_bool, iy_in_bounds, iy_real, c_h_in]);
212 let lbl_ix_check = m.alloc_id();
213 m.emit_selection_merge(lbl_skip);
214 m.emit_branch_conditional(iy_in_bounds, lbl_ix_check, lbl_skip);
215
216 m.emit_label(lbl_ix_check);
217 let ix_lt_pad = m.alloc_id();
218 m.emit(OP_U_LESS_THAN, &[b.ty_bool, ix_lt_pad, ix_raw, c_pad_w]);
219 let lbl_ix_ok = m.alloc_id();
220 m.emit_selection_merge(lbl_skip);
221 m.emit_branch_conditional(ix_lt_pad, lbl_skip, lbl_ix_ok);
222
223 m.emit_label(lbl_ix_ok);
224 let ix_real = m.alloc_id();
225 m.emit(OP_I_SUB, &[b.ty_uint, ix_real, ix_raw, c_pad_w]);
226 let ix_in_bounds = m.alloc_id();
227 m.emit(OP_U_LESS_THAN, &[b.ty_bool, ix_in_bounds, ix_real, c_w_in]);
228 let lbl_accum = m.alloc_id();
229 m.emit_selection_merge(lbl_skip);
230 m.emit_branch_conditional(ix_in_bounds, lbl_accum, lbl_skip);
231
232 m.emit_label(lbl_accum);
233
234 let in1 = m.alloc_id();
236 m.emit(OP_I_MUL, &[b.ty_uint, in1, b_idx, c_c_in]);
237 let in2 = m.alloc_id();
238 m.emit(OP_I_ADD, &[b.ty_uint, in2, in1, ci]);
239 let in3 = m.alloc_id();
240 m.emit(OP_I_MUL, &[b.ty_uint, in3, in2, c_h_in]);
241 let in4 = m.alloc_id();
242 m.emit(OP_I_ADD, &[b.ty_uint, in4, in3, iy_real]);
243 let in5 = m.alloc_id();
244 m.emit(OP_I_MUL, &[b.ty_uint, in5, in4, c_w_in]);
245 let in_idx = m.alloc_id();
246 m.emit(OP_I_ADD, &[b.ty_uint, in_idx, in5, ix_real]);
247
248 let inp_ptr = m.alloc_id();
249 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, inp_ptr, p_input, in_idx);
250 let inp_val = m.alloc_id();
251 m.emit_load(b.ty_float, inp_val, inp_ptr);
252
253 let f1 = m.alloc_id();
255 m.emit(OP_I_MUL, &[b.ty_uint, f1, kf, c_c_in]);
256 let f2 = m.alloc_id();
257 m.emit(OP_I_ADD, &[b.ty_uint, f2, f1, ci]);
258 let f3 = m.alloc_id();
259 m.emit(OP_I_MUL, &[b.ty_uint, f3, f2, c_fh]);
260 let f4 = m.alloc_id();
261 m.emit(OP_I_ADD, &[b.ty_uint, f4, f3, fy]);
262 let f5 = m.alloc_id();
263 m.emit(OP_I_MUL, &[b.ty_uint, f5, f4, c_fw]);
264 let flt_idx = m.alloc_id();
265 m.emit(OP_I_ADD, &[b.ty_uint, flt_idx, f5, fx]);
266
267 let flt_ptr = m.alloc_id();
268 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, flt_ptr, p_filter, flt_idx);
269 let flt_val = m.alloc_id();
270 m.emit_load(b.ty_float, flt_val, flt_ptr);
271
272 let prod = m.alloc_id();
274 m.emit(OP_F_MUL, &[b.ty_float, prod, inp_val, flt_val]);
275 let old_acc = m.alloc_id();
276 m.emit_load(b.ty_float, old_acc, var_acc);
277 let new_acc = m.alloc_id();
278 m.emit(OP_F_ADD, &[b.ty_float, new_acc, old_acc, prod]);
279 m.emit_store(var_acc, new_acc);
280
281 m.emit_branch(lbl_skip);
282
283 m.emit_label(lbl_skip);
284 m.emit_branch(lbl_loop_cont);
285
286 m.emit_label(lbl_loop_cont);
288 let flat_inc = m.alloc_id();
289 m.emit(OP_I_ADD, &[b.ty_uint, flat_inc, flat_val, b.c_uint_1]);
290 m.emit_store(var_flat, flat_inc);
291 m.emit_branch(lbl_loop_hdr);
292
293 m.emit_label(lbl_loop_merge);
295
296 let final_acc = m.alloc_id();
297 m.emit_load(b.ty_float, final_acc, var_acc);
298
299 let out_ptr = m.alloc_id();
300 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, out_ptr, p_output, gid);
301 m.emit_store(out_ptr, final_acc);
302
303 m.emit_branch(label_merge);
304
305 m.emit_label(label_merge);
306 m.emit_return();
307 m.emit_function_end();
308
309 m.finalize()
310}
311
312#[allow(clippy::too_many_arguments)]
329pub fn attention_spirv(
330 batch_heads: u32,
331 seq_q: u32,
332 seq_kv: u32,
333 head_dim: u32,
334 scale: f32,
335 causal: bool,
336) -> Vec<u32> {
337 let mut m = SpvModule::new();
338 let b = emit_preamble(&mut m, "main");
339
340 let fn_ty = m.alloc_id();
341 let p_q = m.alloc_id();
342 let p_k = m.alloc_id();
343 let p_v = m.alloc_id();
344 let p_o = m.alloc_id();
345
346 m.emit_type_function(
348 fn_ty,
349 b.ty_void,
350 &[
351 b.ty_ptr_cross_float,
352 b.ty_ptr_cross_float,
353 b.ty_ptr_cross_float,
354 b.ty_ptr_cross_float,
355 ],
356 );
357
358 let c_batch_heads = m.alloc_id();
360 m.emit_constant_u32(b.ty_uint, c_batch_heads, batch_heads);
361 let c_seq_q = m.alloc_id();
362 m.emit_constant_u32(b.ty_uint, c_seq_q, seq_q);
363 let c_seq_kv = m.alloc_id();
364 m.emit_constant_u32(b.ty_uint, c_seq_kv, seq_kv);
365 let c_head_dim = m.alloc_id();
366 m.emit_constant_u32(b.ty_uint, c_head_dim, head_dim);
367 let c_scale = m.alloc_id();
368 m.emit_constant_f32(b.ty_float, c_scale, scale);
369 let c_neg_inf = m.alloc_id();
370 m.emit_constant_f32(b.ty_float, c_neg_inf, f32::NEG_INFINITY);
371 let c_skv_hd = m.alloc_id();
373 m.emit_constant_u32(b.ty_uint, c_skv_hd, seq_kv * head_dim);
374
375 let label_entry = m.alloc_id();
377 let label_body = m.alloc_id();
378 let label_merge = m.alloc_id();
379
380 m.emit_function(b.ty_void, b.main_fn, FUNCTION_CONTROL_NONE, fn_ty);
381 m.emit_function_parameter(b.ty_ptr_cross_float, p_q);
382 m.emit_function_parameter(b.ty_ptr_cross_float, p_k);
383 m.emit_function_parameter(b.ty_ptr_cross_float, p_v);
384 m.emit_function_parameter(b.ty_ptr_cross_float, p_o);
385 m.emit_label(label_entry);
386
387 let gid = load_gid_x(&mut m, &b);
388
389 let total = m.alloc_id();
391 m.emit(OP_I_MUL, &[b.ty_uint, total, c_batch_heads, c_seq_q]);
392
393 let cond = m.alloc_id();
394 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, gid, total]);
395 m.emit_selection_merge(label_merge);
396 m.emit_branch_conditional(cond, label_body, label_merge);
397
398 m.emit_label(label_body);
399
400 let bh = m.alloc_id();
402 m.emit(OP_U_DIV, &[b.ty_uint, bh, gid, c_seq_q]);
403 let sq = m.alloc_id();
404 m.emit(OP_U_MOD, &[b.ty_uint, sq, gid, c_seq_q]);
405
406 let q_base = m.alloc_id();
408 m.emit(OP_I_MUL, &[b.ty_uint, q_base, gid, c_head_dim]);
409 let kv_base = m.alloc_id();
411 m.emit(OP_I_MUL, &[b.ty_uint, kv_base, bh, c_skv_hd]);
412
413 let var_max = m.alloc_id();
415 m.emit_variable(b.ty_ptr_func_float, var_max, STORAGE_CLASS_FUNCTION);
416 m.emit_store(var_max, c_neg_inf);
417
418 emit_score_pass(
420 &mut m, &b, causal, sq, c_seq_kv, c_head_dim, c_scale, q_base, kv_base, p_q, p_k, var_max,
421 true, None, None, p_v, p_o,
422 );
423
424 let final_max = m.alloc_id();
425 m.emit_load(b.ty_float, final_max, var_max);
426
427 let var_dz = m.alloc_id();
432 m.emit_variable(b.ty_ptr_func_uint, var_dz, STORAGE_CLASS_FUNCTION);
433 m.emit_store(var_dz, b.c_uint_0);
434
435 let lbl_dz_hdr = m.alloc_id();
436 let lbl_dz_body = m.alloc_id();
437 let lbl_dz_cont = m.alloc_id();
438 let lbl_dz_merge = m.alloc_id();
439
440 m.emit_branch(lbl_dz_hdr);
441
442 m.emit_label(lbl_dz_hdr);
443 let dz_val = m.alloc_id();
444 m.emit_load(b.ty_uint, dz_val, var_dz);
445 let dz_cond = m.alloc_id();
446 m.emit(OP_U_LESS_THAN, &[b.ty_bool, dz_cond, dz_val, c_head_dim]);
447 m.emit_loop_merge(lbl_dz_merge, lbl_dz_cont);
448 m.emit_branch_conditional(dz_cond, lbl_dz_body, lbl_dz_merge);
449
450 m.emit_label(lbl_dz_body);
451 let oz_idx = m.alloc_id();
452 m.emit(OP_I_ADD, &[b.ty_uint, oz_idx, q_base, dz_val]);
453 let oz_ptr = m.alloc_id();
454 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, oz_ptr, p_o, oz_idx);
455 m.emit_store(oz_ptr, b.c_float_0);
456 m.emit_branch(lbl_dz_cont);
457
458 m.emit_label(lbl_dz_cont);
459 let dz_inc = m.alloc_id();
460 m.emit(OP_I_ADD, &[b.ty_uint, dz_inc, dz_val, b.c_uint_1]);
461 m.emit_store(var_dz, dz_inc);
462 m.emit_branch(lbl_dz_hdr);
463
464 m.emit_label(lbl_dz_merge);
465
466 let var_sum_exp = m.alloc_id();
468 m.emit_variable(b.ty_ptr_func_float, var_sum_exp, STORAGE_CLASS_FUNCTION);
469 m.emit_store(var_sum_exp, b.c_float_0);
470
471 emit_score_pass(
472 &mut m,
473 &b,
474 causal,
475 sq,
476 c_seq_kv,
477 c_head_dim,
478 c_scale,
479 q_base,
480 kv_base,
481 p_q,
482 p_k,
483 var_sum_exp,
484 false,
485 Some(final_max),
486 Some(p_o),
487 p_v,
488 p_o,
489 );
490
491 let sum_final = m.alloc_id();
493 m.emit_load(b.ty_float, sum_final, var_sum_exp);
494
495 let sum_gt_zero = m.alloc_id();
496 m.emit(
497 OP_F_ORD_GT,
498 &[b.ty_bool, sum_gt_zero, sum_final, b.c_float_0],
499 );
500
501 let lbl_norm = m.alloc_id();
502 let lbl_norm_merge = m.alloc_id();
503 m.emit_selection_merge(lbl_norm_merge);
504 m.emit_branch_conditional(sum_gt_zero, lbl_norm, lbl_norm_merge);
505
506 m.emit_label(lbl_norm);
507
508 let var_d4 = m.alloc_id();
510 m.emit_variable(b.ty_ptr_func_uint, var_d4, STORAGE_CLASS_FUNCTION);
511 m.emit_store(var_d4, b.c_uint_0);
512
513 let lbl_d4_hdr = m.alloc_id();
514 let lbl_d4_body = m.alloc_id();
515 let lbl_d4_cont = m.alloc_id();
516 let lbl_d4_merge = m.alloc_id();
517
518 m.emit_branch(lbl_d4_hdr);
519
520 m.emit_label(lbl_d4_hdr);
521 let d4_val = m.alloc_id();
522 m.emit_load(b.ty_uint, d4_val, var_d4);
523 let d4_cond = m.alloc_id();
524 m.emit(OP_U_LESS_THAN, &[b.ty_bool, d4_cond, d4_val, c_head_dim]);
525 m.emit_loop_merge(lbl_d4_merge, lbl_d4_cont);
526 m.emit_branch_conditional(d4_cond, lbl_d4_body, lbl_d4_merge);
527
528 m.emit_label(lbl_d4_body);
529 let o4_idx = m.alloc_id();
530 m.emit(OP_I_ADD, &[b.ty_uint, o4_idx, q_base, d4_val]);
531 let o4_ptr = m.alloc_id();
532 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, o4_ptr, p_o, o4_idx);
533 let o4_val = m.alloc_id();
534 m.emit_load(b.ty_float, o4_val, o4_ptr);
535 let o4_normed = m.alloc_id();
536 m.emit(OP_F_DIV, &[b.ty_float, o4_normed, o4_val, sum_final]);
537 m.emit_store(o4_ptr, o4_normed);
538
539 m.emit_branch(lbl_d4_cont);
540 m.emit_label(lbl_d4_cont);
541 let d4_inc = m.alloc_id();
542 m.emit(OP_I_ADD, &[b.ty_uint, d4_inc, d4_val, b.c_uint_1]);
543 m.emit_store(var_d4, d4_inc);
544 m.emit_branch(lbl_d4_hdr);
545
546 m.emit_label(lbl_d4_merge);
547
548 m.emit_branch(lbl_norm_merge);
549 m.emit_label(lbl_norm_merge);
550
551 m.emit_branch(label_merge);
552
553 m.emit_label(label_merge);
554 m.emit_return();
555 m.emit_function_end();
556
557 m.finalize()
558}
559
560#[allow(clippy::too_many_arguments)]
566fn emit_score_pass(
567 m: &mut SpvModule,
568 b: &super::spirv::BaseIds,
569 causal: bool,
570 sq: u32,
571 c_seq_kv: u32,
572 c_head_dim: u32,
573 c_scale: u32,
574 q_base: u32,
575 kv_base: u32,
576 p_q: u32,
577 p_k: u32,
578 accum_var: u32,
579 is_max_pass: bool,
580 max_val: Option<u32>,
581 o_buf: Option<u32>,
582 p_v: u32,
583 _p_o_unused: u32,
584) {
585 let var_sk = m.alloc_id();
586 m.emit_variable(b.ty_ptr_func_uint, var_sk, STORAGE_CLASS_FUNCTION);
587 m.emit_store(var_sk, b.c_uint_0);
588
589 let lbl_hdr = m.alloc_id();
590 let lbl_body = m.alloc_id();
591 let lbl_cont = m.alloc_id();
592 let lbl_merge = m.alloc_id();
593
594 m.emit_branch(lbl_hdr);
595
596 m.emit_label(lbl_hdr);
597 let sk_val = m.alloc_id();
598 m.emit_load(b.ty_uint, sk_val, var_sk);
599 let cond = m.alloc_id();
600 m.emit(OP_U_LESS_THAN, &[b.ty_bool, cond, sk_val, c_seq_kv]);
601 m.emit_loop_merge(lbl_merge, lbl_cont);
602 m.emit_branch_conditional(cond, lbl_body, lbl_merge);
603
604 m.emit_label(lbl_body);
605
606 let lbl_compute = m.alloc_id();
607 let lbl_skip = m.alloc_id();
608 if causal {
609 let sk_gt_sq = m.alloc_id();
610 m.emit(OP_U_LESS_THAN, &[b.ty_bool, sk_gt_sq, sq, sk_val]);
611 m.emit_selection_merge(lbl_skip);
612 m.emit_branch_conditional(sk_gt_sq, lbl_skip, lbl_compute);
613 } else {
614 m.emit_branch(lbl_compute);
615 }
616
617 m.emit_label(lbl_compute);
618
619 let sk_hd = m.alloc_id();
621 m.emit(OP_I_MUL, &[b.ty_uint, sk_hd, sk_val, c_head_dim]);
622 let k_off = m.alloc_id();
623 m.emit(OP_I_ADD, &[b.ty_uint, k_off, kv_base, sk_hd]);
624
625 let var_d = m.alloc_id();
627 m.emit_variable(b.ty_ptr_func_uint, var_d, STORAGE_CLASS_FUNCTION);
628 m.emit_store(var_d, b.c_uint_0);
629 let var_dot = m.alloc_id();
630 m.emit_variable(b.ty_ptr_func_float, var_dot, STORAGE_CLASS_FUNCTION);
631 m.emit_store(var_dot, b.c_float_0);
632
633 let lbl_d_hdr = m.alloc_id();
634 let lbl_d_body = m.alloc_id();
635 let lbl_d_cont = m.alloc_id();
636 let lbl_d_merge = m.alloc_id();
637
638 m.emit_branch(lbl_d_hdr);
639
640 m.emit_label(lbl_d_hdr);
641 let d_val = m.alloc_id();
642 m.emit_load(b.ty_uint, d_val, var_d);
643 let d_cond = m.alloc_id();
644 m.emit(OP_U_LESS_THAN, &[b.ty_bool, d_cond, d_val, c_head_dim]);
645 m.emit_loop_merge(lbl_d_merge, lbl_d_cont);
646 m.emit_branch_conditional(d_cond, lbl_d_body, lbl_d_merge);
647
648 m.emit_label(lbl_d_body);
649 let q_idx = m.alloc_id();
650 m.emit(OP_I_ADD, &[b.ty_uint, q_idx, q_base, d_val]);
651 let q_ptr = m.alloc_id();
652 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, q_ptr, p_q, q_idx);
653 let q_val = m.alloc_id();
654 m.emit_load(b.ty_float, q_val, q_ptr);
655 let k_idx = m.alloc_id();
656 m.emit(OP_I_ADD, &[b.ty_uint, k_idx, k_off, d_val]);
657 let k_ptr = m.alloc_id();
658 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, k_ptr, p_k, k_idx);
659 let k_val = m.alloc_id();
660 m.emit_load(b.ty_float, k_val, k_ptr);
661
662 let qk_prod = m.alloc_id();
663 m.emit(OP_F_MUL, &[b.ty_float, qk_prod, q_val, k_val]);
664 let old_dot = m.alloc_id();
665 m.emit_load(b.ty_float, old_dot, var_dot);
666 let new_dot = m.alloc_id();
667 m.emit(OP_F_ADD, &[b.ty_float, new_dot, old_dot, qk_prod]);
668 m.emit_store(var_dot, new_dot);
669
670 m.emit_branch(lbl_d_cont);
671 m.emit_label(lbl_d_cont);
672 let d_inc = m.alloc_id();
673 m.emit(OP_I_ADD, &[b.ty_uint, d_inc, d_val, b.c_uint_1]);
674 m.emit_store(var_d, d_inc);
675 m.emit_branch(lbl_d_hdr);
676
677 m.emit_label(lbl_d_merge);
678
679 let dot_final = m.alloc_id();
681 m.emit_load(b.ty_float, dot_final, var_dot);
682 let score = m.alloc_id();
683 m.emit(OP_F_MUL, &[b.ty_float, score, dot_final, c_scale]);
684
685 if is_max_pass {
686 let old_acc = m.alloc_id();
688 m.emit_load(b.ty_float, old_acc, accum_var);
689 let new_acc = m.alloc_id();
690 m.emit_opencl_ext(
691 b.opencl_ext,
692 b.ty_float,
693 new_acc,
694 OPENCL_FMAX,
695 &[old_acc, score],
696 );
697 m.emit_store(accum_var, new_acc);
698 } else {
699 let max_id = max_val.unwrap_or(b.c_float_0);
701 let score_shifted = m.alloc_id();
702 m.emit(OP_F_SUB, &[b.ty_float, score_shifted, score, max_id]);
703 let w = m.alloc_id();
704 m.emit_opencl_ext(b.opencl_ext, b.ty_float, w, OPENCL_EXP, &[score_shifted]);
705
706 let old_sum = m.alloc_id();
708 m.emit_load(b.ty_float, old_sum, accum_var);
709 let new_sum = m.alloc_id();
710 m.emit(OP_F_ADD, &[b.ty_float, new_sum, old_sum, w]);
711 m.emit_store(accum_var, new_sum);
712
713 if let Some(o_buf_id) = o_buf {
715 let v_off = m.alloc_id();
716 m.emit(OP_I_ADD, &[b.ty_uint, v_off, kv_base, sk_hd]);
717
718 let var_d3 = m.alloc_id();
719 m.emit_variable(b.ty_ptr_func_uint, var_d3, STORAGE_CLASS_FUNCTION);
720 m.emit_store(var_d3, b.c_uint_0);
721
722 let lbl_d3_hdr = m.alloc_id();
723 let lbl_d3_body = m.alloc_id();
724 let lbl_d3_cont = m.alloc_id();
725 let lbl_d3_merge = m.alloc_id();
726
727 m.emit_branch(lbl_d3_hdr);
728
729 m.emit_label(lbl_d3_hdr);
730 let d3_val = m.alloc_id();
731 m.emit_load(b.ty_uint, d3_val, var_d3);
732 let d3_cond = m.alloc_id();
733 m.emit(OP_U_LESS_THAN, &[b.ty_bool, d3_cond, d3_val, c_head_dim]);
734 m.emit_loop_merge(lbl_d3_merge, lbl_d3_cont);
735 m.emit_branch_conditional(d3_cond, lbl_d3_body, lbl_d3_merge);
736
737 m.emit_label(lbl_d3_body);
738 let v_idx = m.alloc_id();
739 m.emit(OP_I_ADD, &[b.ty_uint, v_idx, v_off, d3_val]);
740 let v_ptr = m.alloc_id();
741 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, v_ptr, p_v, v_idx);
742 let v_val = m.alloc_id();
743 m.emit_load(b.ty_float, v_val, v_ptr);
744 let wv = m.alloc_id();
745 m.emit(OP_F_MUL, &[b.ty_float, wv, w, v_val]);
746
747 let o_idx = m.alloc_id();
748 m.emit(OP_I_ADD, &[b.ty_uint, o_idx, q_base, d3_val]);
749 let o_ptr = m.alloc_id();
750 m.emit_in_bounds_ptr_access_chain(b.ty_ptr_cross_float, o_ptr, o_buf_id, o_idx);
751 let o_old = m.alloc_id();
752 m.emit_load(b.ty_float, o_old, o_ptr);
753 let o_new = m.alloc_id();
754 m.emit(OP_F_ADD, &[b.ty_float, o_new, o_old, wv]);
755 m.emit_store(o_ptr, o_new);
756
757 m.emit_branch(lbl_d3_cont);
758 m.emit_label(lbl_d3_cont);
759 let d3_inc = m.alloc_id();
760 m.emit(OP_I_ADD, &[b.ty_uint, d3_inc, d3_val, b.c_uint_1]);
761 m.emit_store(var_d3, d3_inc);
762 m.emit_branch(lbl_d3_hdr);
763
764 m.emit_label(lbl_d3_merge);
765 }
766 }
767
768 m.emit_branch(lbl_skip);
769 m.emit_label(lbl_skip);
770 m.emit_branch(lbl_cont);
771
772 m.emit_label(lbl_cont);
773 let sk_inc = m.alloc_id();
774 m.emit(OP_I_ADD, &[b.ty_uint, sk_inc, sk_val, b.c_uint_1]);
775 m.emit_store(var_sk, sk_inc);
776 m.emit_branch(lbl_hdr);
777
778 m.emit_label(lbl_merge);
779}
780
781#[cfg(test)]
784mod tests {
785 use super::*;
786 use crate::spirv::SPIRV_MAGIC;
787
788 fn check_valid_spirv(words: &[u32]) {
789 assert!(words.len() >= 5, "too short for SPIR-V header");
790 assert_eq!(words[0], SPIRV_MAGIC, "bad magic");
791 assert!(words[3] > 0, "ID bound must be > 0");
792 assert_eq!(words[4], 0, "schema must be 0");
793 }
794
795 #[test]
796 fn conv2d_spirv_valid() {
797 let words = conv2d_spirv(1, 3, 8, 8, 16, 3, 3, 6, 6, 1, 1, 0, 0);
798 check_valid_spirv(&words);
799 }
800
801 #[test]
802 fn conv2d_spirv_with_padding() {
803 let words = conv2d_spirv(2, 1, 5, 5, 4, 3, 3, 5, 5, 1, 1, 1, 1);
804 check_valid_spirv(&words);
805 }
806
807 #[test]
808 fn conv2d_spirv_1x1() {
809 let words = conv2d_spirv(1, 3, 4, 4, 8, 1, 1, 4, 4, 1, 1, 0, 0);
810 check_valid_spirv(&words);
811 }
812
813 #[test]
814 fn attention_spirv_valid() {
815 let words = attention_spirv(2, 4, 4, 8, 0.125, false);
816 check_valid_spirv(&words);
817 }
818
819 #[test]
820 fn attention_spirv_causal() {
821 let words = attention_spirv(1, 8, 8, 16, 0.25, true);
822 check_valid_spirv(&words);
823 }
824
825 #[test]
826 fn attention_spirv_magic_number() {
827 let words = attention_spirv(1, 4, 4, 8, 0.125, false);
828 assert_eq!(words[0], 0x07230203);
829 }
830
831 #[test]
832 fn conv2d_spirv_magic_number() {
833 let words = conv2d_spirv(1, 1, 4, 4, 1, 1, 1, 4, 4, 1, 1, 0, 0);
834 assert_eq!(words[0], 0x07230203);
835 }
836}