-
Notifications
You must be signed in to change notification settings - Fork 120
Expand file tree
/
Copy pathdasllama_math.das
More file actions
4165 lines (3811 loc) · 209 KB
/
Copy pathdasllama_math.das
File metadata and controls
4165 lines (3811 loc) · 209 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
options gen2
options _comment_hygiene = true
module dasllama_math shared public
require math
require daslib/fio // has_env_variable: DAS_JOBQUE_TEAM_RANK_GATE suppresses the profile's team_rank_gate knob
require dasllama/dasllama_env // env_flag/env_int64: the shared typed readers + the knob registry
require daslib/jobque_boost public // matmul's threaded path expands parallel_for; consumers need its symbols
require daslib/jobque_profile // named trace categories + unit markers (set_trace_tags registers them)
require dasllama/dasllama_par // maybe_parallel_for: thread-or-inline a kernel body on a runtime size flag
require dasllama/dasllama_tune // [tuned]: reconstitute dot from dot_template with per-box loop hints
require dasllama/dasllama_gemm_schema // kq_qsb/kq_ssb — the fmt-id kq plane strides
require llvm/daslib/f16_cvt // f16<->f32 converts: JIT half intrinsics on aarch64 / x64+F16C, exact bit-twiddle elsewhere
// dasLLAMA numeric primitives — naive fp32 baseline (curriculum step 1).
// Correctness first; optimization (blocking, float4, threading) comes later.
//
// Every array param is `array<float> | #` (owning array OR borrowed view), so weights
// can be passed zero-copy as views into one big model blob and outputs can be written
// straight into a view (e.g. the KV cache). Dims/indices are int64 for large models.
//! Dot product of two n-length float buffers, auto-vectorized by the LLVM JIT
//! (width-8, no bounds checks, a/b asserted non-aliasing). Beats hand-rolled float4 ~1.8x.
//! `dot_template` is the single source of truth (hot loop marked `[tune=1]`); the emitted
//! `dot` below reconstitutes it via `[tuned]` with the per-box permutation — default `vec8_u2`
//! is the original `[vectorize, vectorize_width=8, unroll_count=2]`, so this is bit-identical.
[hint(unsafe_range_check, noalias = a, noalias = b)]
def template dot_template(a, b : float const ?; n : int64) : float {
var acc = 0.0
for [tune = 1] (j in range64(n)) {
acc = mad(unsafe(a[j]), unsafe(b[j]), acc) // fused multiply-add (vectorizes to fmla)
}
return acc
}
[hint(unsafe_range_check, noalias = a, noalias = b), tuned]
def dot(a, b : float const?; n : int64) : float {}
// The elementwise kernels below share dot's tuning rig: each `<name>_template` holds the `[tune=1]`
// hot loop and `[tuned]` reconstitutes `<name>` with the per-box permutation from the app tune
// sidecar — default `vec8_u2` is bit-identical until the profiler picks a different perm.
//! AXPY: d[j] += scale * s[j] over n elements, auto-vectorized. Used single-threaded AND as the
//! V-accumulate in threaded prefill attention — `d`/`s` never overlap, so noalias holds inside
//! fork workers too (a captured `var d : float?` stays a writable pointer — capture freezes the binding, not the pointee).
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template axpy_template(var d : float ?; s : float const ?; scale : float; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = mad(scale, s[j], d[j])
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def axpy(var d : float?; s : float const?; scale : float; n : int64) {}
// The f16 KV-codec kernels: the cache side is IEEE binary16 held in uint16, converted per element
// inside the hot loop (ggml's lesson: convert the O(head_size) side in-loop, keep the bandwidth
// win — f16_to_f32 lowers to vcvtph2ps/fcvtl inside the vectorized loop under the JIT).
//! Dot of an f32 query row against an f16 (binary16-in-uint16) cache row.
[hint(unsafe_range_check, noalias = a, noalias = b)]
def template dot_f16_template(a : float const ?; b : uint16 const ?; n : int64) : float {
var acc = 0.0
for [tune = 1] (j in range64(n)) {
acc = mad(unsafe(a[j]), f16_to_f32(uint(unsafe(b[j]))), acc)
}
return acc
}
[hint(unsafe_range_check, noalias = a, noalias = b), tuned]
def dot_f16(a : float const?; b : uint16 const?; n : int64) : float {}
//! AXPY from an f16 cache row: d[j] += scale * f16(s[j]) — the V-accumulate under the f16 codec.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template axpy_f16_template(var d : float ?; s : uint16 const ?; scale : float; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = mad(scale, f16_to_f32(uint(s[j])), d[j])
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def axpy_f16(var d : float?; s : uint16 const?; scale : float; n : int64) {}
//! Dot of an f32 row against a bfloat16 (bf16-in-uint16) row. The widen is an exact high-half
//! bit shift, so dots are bit-identical to pre-expanding the weight to fp32 at half the bandwidth
//! (llama.cpp's AVX2 ggml_vec_dot_bf16 also truncates the ACTIVATION to bf16; ours stays f32).
[hint(unsafe_range_check, noalias = a, noalias = b)]
def template dot_bf16_template(a : float const ?; b : uint16 const ?; n : int64) : float {
var acc = 0.0
for [tune = 1] (j in range64(n)) {
acc = mad(unsafe(a[j]), unsafe(reinterpret<float>(uint(unsafe(b[j])) << 16u)), acc)
}
return acc
}
[hint(unsafe_range_check, noalias = a, noalias = b), tuned]
def dot_bf16(a : float const?; b : uint16 const?; n : int64) : float {}
//! f32 -> f16 row convert (the codec store). Clamps to ±65504 so inf can NEVER enter the cache —
//! O(kv_dim) per token, free next to the reads.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template cvt_f32_to_f16_template(var d : uint16 ?; s : float const ?; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = uint16(f32_to_f16(clamp(s[j], -65504.0, 65504.0)))
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def cvt_f32_to_f16(var d : uint16?; s : float const?; n : int64) {}
//! f16 -> f32 row convert (bulk dequant into f32 scratch).
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template cvt_f16_to_f32_template(var d : float ?; s : uint16 const ?; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = f16_to_f32(uint(s[j]))
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def cvt_f16_to_f32(var d : float?; s : uint16 const?; n : int64) {}
// The q8_0 KV-codec kernels: cache side is ggml block_q8_0 — 32 int8 quants sharing one f16 scale,
// INTERLEAVED {f16 d; int8 qs[32]} = 34 bytes/block (llama.cpp -ctk/-ctv layout, so oracle
// cross-checks stay honest). Rows are whole blocks (kv_dim % 32 == 0); n always a multiple of 32.
//! Dot of an f32 query row against a q8_0 cache row: the block scale factors out —
//! d * Σ a[j]·q[j] per 32-block (the prefill read; decode uses the int8×int8 twin below).
[hint(unsafe_range_check, noalias = a, noalias = b)]
def template dot_q8kv_template(a : float const ?; b : uint8 const ?; n : int64) : float {
var acc = 0.0
unsafe {
let b16 = reinterpret<uint16 const?>(b)
let bq = reinterpret<int8 const?>(b)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 34l + 2l
var facc = 0.0
for [tune = 1] (k in range64(32l)) {
facc = mad(a[base + k], float(bq[qb + k]), facc)
}
acc = mad(facc, f16_to_f32(uint(b16[bi * 17l])), acc)
}
}
return acc
}
[hint(unsafe_range_check, noalias = a, noalias = b), tuned]
def dot_q8kv(a : float const?; b : uint8 const?; n : int64) : float {}
//! THE q8_0 decode hot path: a Q8-quantized query (int8 quants + f32 scales per 32) against a
//! q8_0 cache row. Inner loop is pure int8·int8 multiply-accumulate — the same SDOT/VNNI shape
//! as dot_q8q8 — with both block scales multiplying in once per 32-block.
[hint(unsafe_range_check, noalias = q, noalias = qs, noalias = b)]
def template dot_q8q8kv_template(q : int8 const ?; qs : float const ?; b : uint8 const ?; n : int64) : float {
var acc = 0.0
unsafe {
let b16 = reinterpret<uint16 const?>(b)
let bq = reinterpret<int8 const?>(b)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 34l + 2l
var iacc = 0
for [tune = 1] (k in range64(32l)) {
iacc += int(q[base + k]) * int(bq[qb + k])
}
acc += float(iacc) * qs[bi] * f16_to_f32(uint(b16[bi * 17l]))
}
}
return acc
}
[hint(unsafe_range_check, noalias = q, noalias = qs, noalias = b), tuned(fallback = "vec16")]
def dot_q8q8kv(q : int8 const?; qs : float const?; b : uint8 const?; n : int64) : float {}
//! AXPY from a q8_0 cache row: d[j] += scale * (q[j] * d_block) — the V-accumulate under the
//! q8_0 codec; scale * d_block hoists per 32-block.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template axpy_q8kv_template(var d : float ?; s : uint8 const ?; scale : float; n : int64) : void {
unsafe {
let s16 = reinterpret<uint16 const?>(s)
let sq = reinterpret<int8 const?>(s)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 34l + 2l
let c = scale * f16_to_f32(uint(s16[bi * 17l]))
for [tune = 1] (k in range64(32l)) {
d[base + k] = mad(c, float(sq[qb + k]), d[base + k])
}
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def axpy_q8kv(var d : float?; s : uint8 const?; scale : float; n : int64) {}
//! q8_0 -> f32 row convert (the flash pack's bulk dequant): d[j] = q[j] * d_block.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template cvt_q8kv_to_f32_template(var d : float ?; s : uint8 const ?; n : int64) : void {
unsafe {
let s16 = reinterpret<uint16 const?>(s)
let sq = reinterpret<int8 const?>(s)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 34l + 2l
let db = f16_to_f32(uint(s16[bi * 17l]))
for [tune = 1] (k in range64(32l)) {
d[base + k] = float(sq[qb + k]) * db
}
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def cvt_q8kv_to_f32(var d : float?; s : uint8 const?; n : int64) {}
//! Quantize n f32s (whole 32-blocks) into q8_0 blocks at dst — the codec store, ggml's exact
//! quantize_row_q8_0 semantics: per block d = amax/127 as f16, quants round with UN-rounded f32
//! 1/d — idempotent (re-quantizing a dequantized row is byte-identical, max element hits ±127).
[hint(unsafe_range_check, noalias = dst, noalias = src)]
def template quantize_q8kv_row_template(var dst : uint8 ?; src : float const ?; n : int64) : void {
unsafe {
let p4 = reinterpret<float4 const?>(src)
var d16 = reinterpret<uint16?>(dst)
var dq = reinterpret<int8?>(dst)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let f4 = base / 4l
var m0 = abs(p4[f4])
var m1 = abs(p4[f4 + 1l])
for (k in range64(1l, 4l)) {
m0 = max(m0, abs(p4[f4 + 2l * k]))
m1 = max(m1, abs(p4[f4 + 2l * k + 1l]))
}
let m = max(m0, m1)
let amax = max(max(m.x, m.y), max(m.z, m.w))
let d = amax / 127.0
let id = d != 0.0 ? 1.0 / d : 0.0
d16[bi * 17l] = uint16(f32_to_f16(d))
let qb = bi * 34l + 2l
for [tune = 1] (i in range64(32l)) {
dq[qb + i] = int8(round(src[base + i] * id))
}
}
}
}
[hint(unsafe_range_check, noalias = dst, noalias = src), tuned(fallback = "plain")]
def quantize_q8kv_row(var dst : uint8?; src : float const?; n : int64) {}
// The tq4 KV-codec kernels: TurboQuant-style rotated 4-bit cache — a seeded-sign FWHT basis
// change at the store/query seams (dasllama_common) over a q4_0-shaped block: 32 4-bit quants
// sharing one f16 scale, {f16 d; uint8 qs[16]} = 18B/block. Rows whole blocks; head_size a power of two for the FWHT.
//! In-place per-head rotation: v *= signs, then the orthonormal FWHT (n a power of two).
//! ⟨Hx, Hy⟩ = ⟨x, y⟩, so scores/AV run entirely in the rotated basis.
def fwht_signs_row(var v : float?; signs : float const?; n : int64) {
unsafe {
for (i in range64(n)) {
v[i] *= signs[i]
}
fwht_core(v, n)
}
}
//! Inverse of fwht_signs_row: FWHT first (self-inverse), then the signs.
def fwht_unsign_row(var v : float?; signs : float const?; n : int64) {
unsafe {
fwht_core(v, n)
for (i in range64(n)) {
v[i] *= signs[i]
}
}
}
def private fwht_core(var v : float?; n : int64) {
unsafe {
var h = 1l
while (h < n) {
var i = 0l
while (i < n) {
for (j in range64(i, i + h)) {
let a = v[j]
let b = v[j + h]
v[j] = a + b
v[j + h] = a - b
}
i += h * 2l
}
h *= 2l
}
let inv = 1.0 / sqrt(float(n))
for (i in range64(n)) {
v[i] *= inv
}
}
}
//! Fill the tq4 rotation sign vector (±1), deterministic — sessions and pools sharing cache
//! bytes MUST agree, so the seed is a fixed constant and layers with a smaller head_size use
//! a prefix of the same vector.
def tq4_fill_signs(var dst : array<float>; n : int64) {
dst |> resize(int(n))
var s = 0xC0FFEEu
for (x in dst) {
s = s * 1664525u + 1013904223u
x = (s & 0x10000u) != 0u ? -1.0 : 1.0
}
}
//! THE tq4 decode hot path: a Q8-quantized (rotated) query against a tq4 cache row — int8×int4
//! multiply-accumulate per 32-block, both block scales in once. The nibble unpack is two MACs
//! per cache byte.
[hint(unsafe_range_check, noalias = q, noalias = qs, noalias = b)]
def template dot_q8tq4kv_template(q : int8 const ?; qs : float const ?; b : uint8 const ?; n : int64) : float {
var acc = 0.0
unsafe {
let b16 = reinterpret<uint16 const?>(b)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 18l + 2l
var iacc = 0
for [tune = 1] (k in range64(16l)) {
let by = int(b[qb + k])
iacc += int(q[base + 2l * k]) * ((by & 15) - 8)
iacc += int(q[base + 2l * k + 1l]) * ((by >> 4) - 8)
}
acc += float(iacc) * qs[bi] * f16_to_f32(uint(b16[bi * 9l]))
}
}
return acc
}
[hint(unsafe_range_check, noalias = q, noalias = qs, noalias = b), tuned(fallback = "vec16")]
def dot_q8tq4kv(q : int8 const?; qs : float const?; b : uint8 const?; n : int64) : float {}
//! AXPY from a tq4 cache row: d[j] += scale * ((nib - 8) * d_block) — the V-accumulate under
//! the tq4 codec (in the rotated basis; the driver un-rotates the head's output once).
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template axpy_tq4kv_template(var d : float ?; s : uint8 const ?; scale : float; n : int64) : void {
unsafe {
let s16 = reinterpret<uint16 const?>(s)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 18l + 2l
let c = scale * f16_to_f32(uint(s16[bi * 9l]))
for [tune = 1] (k in range64(16l)) {
let by = int(s[qb + k])
d[base + 2l * k] = mad(c, float((by & 15) - 8), d[base + 2l * k])
d[base + 2l * k + 1l] = mad(c, float((by >> 4) - 8), d[base + 2l * k + 1l])
}
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def axpy_tq4kv(var d : float?; s : uint8 const?; scale : float; n : int64) {}
//! tq4 -> f32 row convert (the flash pack's bulk dequant): d[j] = (nib - 8) * d_block.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template cvt_tq4kv_to_f32_template(var d : float ?; s : uint8 const ?; n : int64) : void {
unsafe {
let s16 = reinterpret<uint16 const?>(s)
for (bi in range64(n / 32l)) {
let base = bi * 32l
let qb = bi * 18l + 2l
let db = f16_to_f32(uint(s16[bi * 9l]))
for [tune = 1] (k in range64(16l)) {
let by = int(s[qb + k])
d[base + 2l * k] = float((by & 15) - 8) * db
d[base + 2l * k + 1l] = float((by >> 4) - 8) * db
}
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def cvt_tq4kv_to_f32(var d : float?; s : uint8 const?; n : int64) {}
//! Quantize n (rotated) f32s into tq4 blocks at dst — the codec store. ggml quantize_row_q4_0
//! semantics: d = signed-max / -8 stored as f16, quants truncate x*id + 8.5 (never negative:
//! |x*id| <= 8 by construction), capped at 15.
[hint(unsafe_range_check, noalias = dst, noalias = src)]
def template quantize_tq4kv_row_template(var dst : uint8 ?; src : float const ?; n : int64) : void {
unsafe {
var d16 = reinterpret<uint16?>(dst)
for (bi in range64(n / 32l)) {
let base = bi * 32l
var amax = 0.0
var mval = 0.0
for (i in range64(32l)) {
let v = src[base + i]
if (abs(v) > amax) {
amax = abs(v)
mval = v
}
}
let d = mval / -8.0
let id = d != 0.0 ? 1.0 / d : 0.0
d16[bi * 9l] = uint16(f32_to_f16(d))
let qb = bi * 18l + 2l
for [tune = 1] (k in range64(16l)) {
let q0 = min(15, int(src[base + 2l * k] * id + 8.5))
let q1 = min(15, int(src[base + 2l * k + 1l] * id + 8.5))
dst[qb + k] = uint8(q0 | (q1 << 4))
}
}
}
}
[hint(unsafe_range_check, noalias = dst, noalias = src), tuned(fallback = "plain")]
def quantize_tq4kv_row(var dst : uint8?; src : float const?; n : int64) {}
//! d[j] += s[j], width-8 vectorized (the residual-stream update). The plain `for k; a[k]+=b[k]`
//! over bounds-checked arrays does NOT vectorize under the JIT — this pointer form is ~5x faster.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template add_inplace_template(var d : float ?; s : float const ?; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] += s[j]
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def add_inplace(var d : float?; s : float const?; n : int64) {}
//! d[j] = (d[j] + s[j]) * scale in ONE pass — the residual add fused with the per-layer output
//! scale (gemma4 layer_out_scale, the PLE 1/sqrt(2) fold). Bit-identical to add_inplace followed
//! by scale_inplace (same two fp ops per element, same order) at one RMW walk instead of two.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template add_scale_inplace_template(var d : float ?; s : float const ?; scale : float; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = (d[j] + s[j]) * scale
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def add_scale_inplace(var d : float?; s : float const?; scale : float; n : int64) {}
//! d[j] *= s[j], width-8 vectorized — the SwiGLU/GeGLU gate multiply (act(W1·x) * (W3·x)).
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template mul_inplace_template(var d : float ?; s : float const ?; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] *= s[j]
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def mul_inplace(var d : float?; s : float const?; n : int64) {}
//! d[j] *= scale (broadcast scalar), width-8 vectorized — ggml_vec_scale_f32. Flash attention
//! multiplies the whole Q×KV score tile by 1/sqrt(head_size) in one sweep, replacing the old scalar
//! per-element multiply buried in the mask branch.
[hint(unsafe_range_check, noalias = d)]
def template scale_inplace_template(var d : float ?; scale : float; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] *= scale
}
}
}
[hint(unsafe_range_check, noalias = d), tuned]
def scale_inplace(var d : float?; scale : float; n : int64) {}
//! d[j] = s[j], width-8 vectorized contiguous copy (the KV-cache store). Same reason as add_inplace:
//! a bounds-checked array copy does NOT vectorize under the JIT; this pointer form does. d (cache) and
//! s (the k_b/v_b scratch) are always distinct arrays, so noalias is correct and free.
[hint(unsafe_range_check, noalias = d, noalias = s)]
def template copy_floats_template(var d : float ?; s : float const ?; n : int64) : void {
for [tune = 1] (j in range64(n)) {
unsafe {
d[j] = s[j]
}
}
}
[hint(unsafe_range_check, noalias = d, noalias = s), tuned]
def copy_floats(var d : float?; s : float const?; n : int64) {}
// Spin-before-park window for the jobque workers (see setup_dasllama_jobque). Runtime knob: the
// useful window tracks the box's serial-gap lengths, not the kernel. 30ms covers the between-call
// gaps of a decode/prefill loop, keeping workers hot through a whole token instead of parking.
var g_jobque_spin_us = 30000l
def public set_jobque_spin_us(v : int64) { g_jobque_spin_us = max(v, 0l) }
def public get_jobque_spin_us() : int64 { return g_jobque_spin_us }
// Join-poll level for fork/join joins (see setup_dasllama_jobque) — ggml's --poll denomination:
// level*1024*128 relax-rounds on the joining thread before it parks (0 = park immediately).
// 50 is ggml's shipped default; the poll burns only the calling thread and is bounded (~100ms).
var g_jobque_join_poll = 50l
def public set_jobque_join_poll(v : int64) { g_jobque_join_poll = max(v, 0l) }
def public get_jobque_join_poll() : int64 { return g_jobque_join_poll }
// Worker-pool limit (JobQue::setWorkerLimit — workers above the limit sleep dormant): the
// tiny-model dial. An inline-dominated decode token runs FASTER with fewer awake workers (zen2
// 135M: T=8 176 t/s vs T=48 149 — T-1 pollers interfere with main-thread weight streaming). 0 = no limit.
var g_dispatch_worker_limit = 0
def public set_dispatch_worker_limit(v : int) { g_dispatch_worker_limit = max(v, 0) }
def public get_dispatch_worker_limit() : int { return g_dispatch_worker_limit }
// Per-op team rank gate (JobQue::setTeamRankGate — each team op admits only as many workers as
// its widest stage published chunks). Server-core boxes want it ON (135M T=48: +10% zen2/+48% SPR,
// >=1B neutral); heterogeneous P/E (M1) keep it off. Tri-state: -1 = que default, 0/1 = force.
var g_team_rank_gate = -1
def public set_team_rank_gate(v : int) { g_team_rank_gate = clamp(v, -1, 1) }
def public get_team_rank_gate() : int { return g_team_rank_gate }
// Trace op tags (jobque_trace_tag): stamped before dispatches so the lane-timeline viewer colors
// by OP rather than stage-count. Gated — set_trace_tags(true) rides with jobque_trace_start in
// the profiling harness; production dispatches never pay the extern call.
var g_trace_tags = false
def public set_trace_tags(on : bool) {
g_trace_tags = on
if (on && is_job_que_available()) { // registries live on the que; stay que-independent like trace_tag
register_trace_categories()
}
}
let public TRACE_TAG_ATTN_CHAIN = 1
let public TRACE_TAG_ATTN_STD = 2
let public TRACE_TAG_FFN_CHAIN = 3
let public TRACE_TAG_FFN_STD = 4
let public TRACE_TAG_CLS = 5
let public TRACE_TAG_MOE = 6
let public TRACE_TAG_SHEXP = 7
let public TRACE_TAG_PLE = 8
let public TRACE_TAG_ROUTE = 9 // MoE router GEMM + select/bucket (prefill section split)
let public TRACE_TAG_PARK = 10 // MoE park/reduce bookkeeping around the expert GEMMs
let public TRACE_TAG_DN = 11 // Gated DeltaNet layers (qwen35 hybrid) — separates dn from gated attn
def public trace_tag(t : int) {
if (g_trace_tags) {
jobque_trace_tag(t)
}
}
def public trace_marker(name : string; arg : int) {
// instant unit boundary ("token", "layer", "step") on the caller lane; same gate as trace_tag
if (g_trace_tags && is_job_que_available()) {
profile_marker(name, arg)
}
}
// names/colors match the lane-timeline viewer's dark-theme palette, so saved traces carry
// the exact legend the artifact hardcoded
def private register_trace_categories() {
profile_category(TRACE_TAG_ATTN_CHAIN, "attn chain", 0x5E96DEu)
profile_category(TRACE_TAG_ATTN_STD, "attn std", 0x5E96DEu)
profile_category(TRACE_TAG_FFN_CHAIN, "ffn chain", 0x43B08Au)
profile_category(TRACE_TAG_FFN_STD, "ffn per-op", 0x43B08Au)
profile_category(TRACE_TAG_CLS, "classifier", 0xAC82E4u)
profile_category(TRACE_TAG_MOE, "moe experts", 0xE08563u)
profile_category(TRACE_TAG_SHEXP, "shexp", 0xC39536u)
profile_category(TRACE_TAG_PLE, "ple", 0xC39536u)
profile_category(TRACE_TAG_ROUTE, "router", 0x4FC3D0u)
profile_category(TRACE_TAG_PARK, "park/reduce", 0xD678B4u)
}
//! Configure the job queue for dasLLAMA's pure fork/join matmul dispatch: pooled fork contexts,
//! batched dispatch, spin-before-park window, join-poll level. Call INSIDE `with_job_que` (or
//! after `create_job_que`) — the queue must exist. Engine spelling of `setup_dasllama_jobque`.
def setup_dasllama_jobque_() {
set_jobque_fork_pool(true, true)
set_jobque_batch_dispatch(true)
// team dispatch (#3368) as the runner default: fifo fork/join costs ~10us PER JOB, so a
// 96-lane dispatch is ~1ms x ~160 dispatches/decoded token — EPYC 9654 measured Llama-1B
// decode 7.6 -> 43 t/s (5.7x) and prefill-512 2x from this one switch (history/dasLLAMA/epyc9654_measurements.md)
set_jobque_team_mode(true)
set_jobque_worker_spin(int(min(g_jobque_spin_us, 2147483647l)))
set_jobque_join_spin(int(min(g_jobque_join_poll, 2147483647l)))
set_jobque_worker_limit(g_dispatch_worker_limit > 0 ? g_dispatch_worker_limit : -1) // -1 = unlimited
if (g_team_rank_gate >= 0 && !has_env_variable("DAS_JOBQUE_TEAM_RANK_GATE")) {
set_jobque_team_rank_gate(g_team_rank_gate != 0)
}
// The dispatch caller is load-bearing in team mode — only it publishes chains, so a
// descheduled caller idles every lane (4B Q4 32L trace: 13 all-lane holes/token, 1.7% worst).
// Que creation ran this thread at High; claim the top notch. DASLLAMA_CALLER_PRIO=<-2..2> is the A/B rail.
set_current_thread_priority(clamp(env_int("DASLLAMA_CALLER_PRIO", 2), -2, 2))
}
// dasLLAMA matmuls are lockstep linear algebra: SMT siblings share the FMA/load ports, so the
// default jobque gets physical cores - 1 workers (ggml's -t default; EPYC 9654 confirmed 192 SMT
// lanes lose to 96). Cap min()s with the stock rule; DAS_JOBQUE_THREADS overrides.
[init]
def dasllama_jobque_threads_cap() {
set_jobque_threads_cap(max(get_total_hw_cores() - 1, 1))
}
// Chunks-per-hw-job for the quantized matmuls' row split (the fp32 kernels use 1). Q8's
// dequant-in-dot is compute-bound and balances better at 2 chunks/job; Q4's heavy nibble
// unpack at 4 (both measured M1 Max). Per-box knobs; clamped to >= 1.
var g_q8_chunks_per_job = 2
var g_q4_chunks_per_job = 4
def public set_q8_chunks_per_job(v : int) { g_q8_chunks_per_job = max(v, 1) }
def public get_q8_chunks_per_job() : int { return g_q8_chunks_per_job }
def public set_q4_chunks_per_job(v : int) { g_q4_chunks_per_job = max(v, 1) }
def public get_q4_chunks_per_job() : int { return g_q4_chunks_per_job }
// Oversplit factor for the quantized BATCH matmuls (prefill GEMM): chunks = hw_jobs * this.
// More chunks than workers lets awake workers pop a late-woken worker's remaining chunks off the
// fifo (3990X @32 workers: fork/join bill -41% -> -15% at x4; x8 is past the per-chunk-overhead knee).
var g_q8_batch_chunks_per_job = 4
def public set_q8_batch_chunks_per_job(v : int) { g_q8_batch_chunks_per_job = max(v, 1) }
def public get_q8_batch_chunks_per_job() : int { return g_q8_batch_chunks_per_job }
// Dispatch lanes for a fork/join chunk split: worker threads PLUS the calling thread (team mode
// by design). Sizing chunk counts to workers alone leaves the last wave short (7 workers x4 = 28
// chunks/8 lanes = 87.5%); sizing to lanes took decode GEMV ~109 -> ~118 GB/s on M1 Max.
var g_dispatch_caller_lane = true
def public set_dispatch_caller_lane(v : bool) { g_dispatch_caller_lane = v }
def public get_dispatch_caller_lane() : bool { return g_dispatch_caller_lane }
def public get_dispatch_lanes() : int {
// Que-less process: the calling thread is the only lane, so every shaper answers "inline".
// (The work-proportional shapers evaluate eagerly at the dispatch sites — the old bool-gated
// form only read this in its parallel arm, so que-less small-matmul callers never noticed.)
if (!is_job_que_available()) {
return 1
}
// the live pool: dormant over-limit workers (set_jobque_worker_limit) never serve chunks,
// so the shapers must size waves to the awake workers only
let live = min(get_total_hw_jobs(), get_jobque_worker_limit())
return live + (g_dispatch_caller_lane ? 1 : 0)
}
def public get_dispatch_slot_bound() : int {
// exclusive upper bound on the slot maybe_parallel_for_indexed hands its block — size
// per-slot scratch to this. Caller participates as the LAST slot; a worker limit only shrinks
// the live set, so dormant workers keep their high slot numbers (bound stays pool-sized).
return is_job_que_available() ? get_total_hw_jobs() + 1 : 1
}
// Min grain for matmul row splits (ggml-cpu.c mul_mat chunk_size = 16): without it a fixed
// lanes×oversplit chunk count shatters a small matmul into sub-grain work items on many-core
// boxes (EPYC 9654: kv 2048x512 GEMM 2.5x slower at 192 lanes than at 48). 0 disables.
var g_matmul_min_chunk_rows = 16
def public set_matmul_min_chunk_rows(v : int) { g_matmul_min_chunk_rows = max(v, 0) }
def public get_matmul_min_chunk_rows() : int { return g_matmul_min_chunk_rows }
// The dispatch invariant that killed the prefill regression: a chunk count > lanes MUST be a whole
// multiple of dispatch lanes, so no wave is ragged (a partial final wave makes the join wait on
// stragglers). Fires in debug/test builds so a future chunk-math change can't silently reintroduce it.
def private assert_wave_aligned(chunks, lanes : int) {
assert(chunks <= lanes || chunks % lanes == 0, "matmul dispatch chunk count must be a whole number of dispatch-lane waves")
}
// ===== work-proportional dispatch (lanes sized to the matmul, not the box) =====
// MACs of work that justify one dispatch chunk: lanes_for_work invites one lane per this much
// work (grain floors still keep tiny matmuls inline). Default 1 = dispatch everything the grains
// admit — the old 1M default left every per-layer attn matmul of a <=4B model single-lane (-5..-17% tok/s).
var g_target_chunk_work = 1l
def public set_target_chunk_work(v : int64) { g_target_chunk_work = max(v, 1l) }
def public get_target_chunk_work() : int64 { return g_target_chunk_work }
// Per-regime lane caps (0 = uncapped). GEMV rides memory bandwidth — past the box's DRAM knee
// extra lanes just queue at the memory door (team_probe's streaming ladder finds the knee).
// Batch (GEMM) is compute-bound and caps at the overhead knee instead.
var g_gemv_lane_cap = 0
def public set_gemv_lane_cap(v : int) { g_gemv_lane_cap = max(v, 0) }
def public get_gemv_lane_cap() : int { return g_gemv_lane_cap }
var g_batch_lane_cap = 0
def public set_batch_lane_cap(v : int) { g_batch_lane_cap = max(v, 0) }
def public get_batch_lane_cap() : int { return g_batch_lane_cap }
//! Dispatch lanes proportional to work: clamp(work / target_chunk_work, 1,
//! min(get_dispatch_lanes(), lane_cap)). 1 means "not worth a dispatch" — the matmul_chunks
//! shapers return 1 and the num_jobs form of maybe_parallel_for runs inline. lane_cap 0 = uncapped.
def public lanes_for_work(work : int64; lane_cap : int) : int {
let lanes = max(get_dispatch_lanes(), 1)
let cap = lane_cap > 0 ? min(lanes, lane_cap) : lanes
return int(clamp(work / g_target_chunk_work, 1l, int64(cap)))
}
//! Work-aware chunk count for a batch (GEMM) row split: L = lanes_for_work(work, batch cap);
//! 1 => inline; else chunks rounded DOWN to a whole number of L-waves so no ragged final wave
//! makes the join wait on stragglers. `rows` is the split range's total row count; `work` = MAC count.
def public matmul_chunks(rows : int; oversplit : int; work : int64) : int {
let L = lanes_for_work(work, g_batch_lane_cap)
if (L <= 1) {
return 1
}
let want = L * max(oversplit, 1)
if (g_matmul_min_chunk_rows <= 0) {
return want
}
let cap = clamp(rows / g_matmul_min_chunk_rows, 1, want) // grain-limited ceiling (<= want = L × oversplit)
// the grain may only limit OVERSPLIT — never cut chunks below the admitted lanes (underfilling
// L idles lanes for the whole dispatch, always worse than sub-grain chunks; rows < L is the
// only natural floor)
if (cap < L) {
let chunks = clamp(rows, 1, L)
assert_wave_aligned(chunks, L)
return chunks
}
let chunks = (cap / L) * L // whole L-waves within the grain cap
assert_wave_aligned(chunks, L)
return chunks
}
// ===== 2-D batch chunk grid (row-units × token-blocks) — the chunk-starved-shape remedy =====
// The per-box 2-D grid pin (box_profile "batch_grid_2d"): 0 = off (1-D always); 1/2 arm the
// per-dispatch auto-gate — 2-D engages only when the 1-D grain-capped chunk count can't fill one
// wave (a starved shape idles lanes); big GEMMs keep the weight-stationary 1-D walk regardless.
var g_batch_grid_2d = 0
def public set_batch_grid_2d(v : int) { g_batch_grid_2d = clamp(v, 0, 2) }
def public get_batch_grid_2d() : int { return g_batch_grid_2d }
def private gcd_int(a, b : int) : int {
var x = max(a, 1)
var y = max(b, 1)
while (y != 0) {
let t = y
y = x % y
x = t
}
return x
}
//! Chunk grid for the token-blocked batch GEMM: (row chunks, token chunks). y == 1 => stay 1-D.
//! With batch_grid_2d armed, a starved shape (1-D chunks < admitted lanes) goes 2-D: the token
//! axis supplies missing parallelism at the same row grain (cells decode rows-fastest, sharing LLC).
def public matmul_grid(units : int; unit_rows : int64; oversplit : int; work : int64; ntok : int64; tokq : int) : int2 {
let rows = units * int(unit_rows)
let c1 = matmul_chunks(rows, oversplit, work)
if (g_batch_grid_2d == 0 || c1 <= 1) {
return int2(c1, 1)
}
let L = lanes_for_work(work, g_batch_lane_cap)
let ntile = int(ntok / int64(tokq))
// grain-starved = the 1-D count only reaches L by going sub-grain (matmul_chunks fills to L);
// the token axis supplies the missing parallelism at FULL row grain instead — weight rows
// stream in grain-sized spans, re-read once per token cell
let gcap = g_matmul_min_chunk_rows > 0 ? clamp(rows / g_matmul_min_chunk_rows, 1, c1) : c1
if (gcap >= L || ntile < 2) { // not grain-starved, or no token axis to split
return int2(c1, 1)
}
let rc = clamp(gcap, 1, max(units, 1)) // row cells finer than one unit are empty claims
var ntc = 1
if (g_batch_grid_2d == 1) {
ntc = min((int(ntok) + 15) / 16, ntile)
} else {
// smallest whole-wave ntc covering the oversplit target; alignment that can't fit the
// available tiles stays 1-D (a clamped ntc would be unaligned = strategy 1 in disguise)
let step = L / gcd_int(rc, L)
let need = (L * max(oversplit, 1) + rc - 1) / rc
ntc = ((need + step - 1) / step) * step
if (ntc > ntile) {
return int2(c1, 1)
}
}
return ntc > 1 ? int2(rc, ntc) : int2(c1, 1)
}
// Coarser grain for GEMV-shaped splits (ggml mul_mat: chunk_size 16 -> 64 when nr0==1||nr1==1):
// a decode matvec row is far less work than a GEMM row reused across ntok tokens (EPYC 9654:
// grain 64 = +13-15% decode at default threads). 0 disables.
var g_matmul_min_chunk_rows_gemv = 64
def public set_matmul_min_chunk_rows_gemv(v : int) { g_matmul_min_chunk_rows_gemv = max(v, 0) }
def public get_matmul_min_chunk_rows_gemv() : int { return g_matmul_min_chunk_rows_gemv }
// GEMV tail: the one-chunk-per-lane equal split leaves the join waiting out the SLOWEST lane's
// whole chunk (median lane idles 14-24% of every GEMV stage, per-lane trace 3990X). >1 publishes
// wave-aligned chunks per lane so finished lanes keep serving; default 8; 1 = historical equal split.
var g_gemv_chunks_per_lane = 8
def public set_gemv_chunks_per_lane(v : int) { g_gemv_chunks_per_lane = max(v, 1) }
def public get_gemv_chunks_per_lane() : int { return g_gemv_chunks_per_lane }
// Wave fill: when the grain-derived count lands below admitted lanes, round UP to one full wave —
// underfilling always idles more than going sub-grain (30B decode router: 128 rows/grain 64 = 2
// chunks on 16 lanes, 14 idle, ~3.3% of token). rows < lanes is the only natural floor.
var g_gemv_wave_fill = true
def public set_gemv_wave_fill(v : bool) { g_gemv_wave_fill = v }
def public get_gemv_wave_fill() : bool { return g_gemv_wave_fill }
//! matmul_chunks for GEMV-shaped dispatches (single-token mm/group3/groupn): L = lanes_for_work
//! (work, gemv cap); 1 => inline; else the coarser GEMV grain, capped at ONE chunk per lane (a
//! decode chunk is one bandwidth stream — EPYC T=24: last lane's +1 chunk = 37% of op wall).
def public matmul_chunks_gemv(rows : int; oversplit : int; work : int64) : int {
let L = lanes_for_work(work, g_gemv_lane_cap)
if (L <= 1) {
return 1
}
if (g_matmul_min_chunk_rows_gemv <= 0) {
return L * max(oversplit, 1)
}
if (g_gemv_chunks_per_lane > 1) {
let waves = min(rows / (g_matmul_min_chunk_rows_gemv * L), g_gemv_chunks_per_lane)
if (waves >= 1) {
let chunks = waves * L
assert_wave_aligned(chunks, L)
return chunks
}
// fewer than one grain-sized chunk per lane — the grain-limited split below applies
}
let n = clamp(rows / g_matmul_min_chunk_rows_gemv, 1, L)
let chunks = (g_gemv_wave_fill && n < L) ? clamp(rows, 1, L) : n
assert_wave_aligned(chunks, L)
return chunks
}
//! LPT (longest-processing-time-first) claim order for a region-list dispatch with known unequal
//! per-region costs: sort (id, cost) biggest-cost first (ties: lowest id) so the self-serve chunk
//! queue front-loads expensive regions and drains cheap ones last. Use whenever cost is known at build time.
def public lpt_order(var ord : array<int2>) {
ord |> sort() $(a, b) => a.y != b.y ? a.y > b.y : a.x < b.x
}
//! Chunk count for a fused-chain stage over `groups` 32-row quant groups: the GEMV shaper's count,
//! except fine-grain mode (gemv_chunks_per_lane > 1) splits at GROUP granularity (capped at
//! lanes×fine) so a ragged last wave costs one 32-row chunk, not a whole lane's share (~25% join tail fix, 3990X).
def public chain_stage_chunks(groups : int; rows : int; work : int64) : int {
if (g_gemv_chunks_per_lane > 1) {
let L = lanes_for_work(work, g_gemv_lane_cap)
if (L > 1) {
return min(groups, L * g_gemv_chunks_per_lane)
}
}
return matmul_chunks_gemv(rows, 1, work)
}
//! y = W·x — matrix-vector product, the dominant LLM compute kernel. W is row-major [d x n],
//! x is [n], y is [d]; each y[i] is one dot of W's row i with x (matches llama2.c: n=input, d=output).
//! Large matmuls thread output rows (requires an enclosing with_job_que); small stay single-thread.
def matmul(var y : array<float>; w : array<float> | #; x : array<float>; n, d : int64) {
unsafe {
var yp = addr(y[0]) // var: stays a writable pointer when captured
let wp = addr<float const?>(w[0]) // strip the `#` if w is a view
let xp = addr(x[0])
maybe_parallel_for(0, int(d), matmul_chunks_gemv(int(d), 1, n * d)) $(rb, re) {
unsafe {
for (i in range(rb, re)) {
yp[i] = dot(wp + int64(i) * n, xp, n)
}
}
}
}
}
//! Blob+offset form: the weight is rows [d x n] at element offset `woff` inside `w` — no
//! array_view (the view's block invoke is an LLVM optimization boundary; the kernel only ever
//! wanted the base pointer).
def matmul(var y : array<float>; w : array<float>; woff : int64; x : array<float>; n, d : int64) {
unsafe {
var yp = addr(y[0])
let wp = addr<float const?>(w[woff])
let xp = addr(x[0])
maybe_parallel_for(0, int(d), matmul_chunks_gemv(int(d), 1, n * d)) $(rb, re) {
unsafe {
for (i in range(rb, re)) {
yp[i] = dot(wp + int64(i) * n, xp, n)
}
}
}
}
}
//! Batched fp32 GEMM for prefill: Y [ntok x d] = X [ntok x n]·Wᵀ, W row-major [d x n]. Both
//! token-major. Weight-stationary nest (rows outer) — each weight row streams from DRAM once,
//! reused across all ntok tokens (vs ntok× matmul re-reading W). Bit-for-bit ntok× matmul.
def matmul_batch(var y : array<float>; w : array<float> | #; x : array<float>; n, d, ntok : int64) {
unsafe {
matmul_batch_core(addr(y[0]), addr<float const?>(w[0]), addr<float const?>(x[0]), n, d, ntok)
}
}
// The fp32 batch GEMM core: 1-D row split, except ROW-STARVED shapes (MoE router: d=n_expert=128,
// grain 16 => 8 chunks idling half a 16-lane box) split tokens too — a flat (row-chunk × token-block)
// domain. Disjoint stores, same dot per (row, token) — bit-identical either way.
def private matmul_batch_core(var yp : float?; wp : float const?; xp : float const?; n, d, ntok : int64) {
var myp = yp
let rch = matmul_chunks(int(d), get_q8_batch_chunks_per_job(), n * d * ntok)
let L = lanes_for_work(n * d * ntok, g_batch_lane_cap)
let tb = (rch < L && ntok >= 128l) ? min(int64((2 * L + rch - 1) / rch), ntok / 64l) : 1l
if (tb <= 1l) {
maybe_parallel_for(0, int(d), rch) $(rb, re) {
unsafe {
for (i in range(rb, re)) {
let wrow = wp + int64(i) * n
for (tk in range64(ntok)) {
myp[tk * d + int64(i)] = dot(wrow, xp + tk * n, n)
}
}
}
}
return
}
let rows_per = (d + int64(rch) - 1l) / int64(rch)
let tok_per = (ntok + tb - 1l) / tb
maybe_parallel_for(0, int(int64(rch) * tb), int(int64(rch) * tb)) $(cb, ce) {
unsafe {
for (c in range(cb, ce)) {
let rc = int64(c) / tb
let tc = int64(c) % tb
let r1 = min(rc * rows_per + rows_per, d)
let t1 = min(tc * tok_per + tok_per, ntok)
for (i in range64(rc * rows_per, r1)) {
let wrow = wp + i * n
for (tk in range64(tc * tok_per, t1)) {
myp[tk * d + i] = dot(wrow, xp + tk * n, n)
}
}
}
}
}
}
// ===== float-plane batch override (the "+AMX"/accelerate tier) =====
// fp32/bf16 BATCH slots only (quant stays NEON — crossover verdict in the accelerate driver
// header). false = fall through to the portable body; off by default (raw tier bit-unchanged).
typedef FpBatchOverrideFn = function<(var yp : float?; wp : float const?; xp : float const?; n : int64; d : int64; ntok : int64) : bool>
typedef Bf16BatchOverrideFn = function<(var yp : float?; wp : uint16 const?; xp : float const?; n : int64; d : int64; ntok : int64) : bool>
[unused_argument(yp, wp, xp, n, d, ntok)]
def private fp_batch_override_unset(var yp : float?; wp : float const?; xp : float const?; n, d, ntok : int64) : bool => false
[unused_argument(yp, wp, xp, n, d, ntok)]
def private bf16_batch_override_unset(var yp : float?; wp : uint16 const?; xp : float const?; n, d, ntok : int64) : bool => false
var private g_mm_fp_batch_override : FpBatchOverrideFn = @@fp_batch_override_unset
var private g_mm_bf16_batch_override : Bf16BatchOverrideFn = @@bf16_batch_override_unset
var private g_fp_override_active = false
var private g_fp_override_min_ntok = 32l
//! Install the float-plane batch override (the accelerate driver). min_ntok gates the donor to
//! batch shapes; 1 arms it for decode GEMV too (the M4/M5-SME applicability probe lane).
def set_float_batch_override(fp : FpBatchOverrideFn; bf16 : Bf16BatchOverrideFn; min_ntok : int64) {
g_mm_fp_batch_override = fp
g_mm_bf16_batch_override = bf16
g_fp_override_min_ntok = min_ntok
g_fp_override_active = true
}
def clear_float_batch_override() {
g_mm_fp_batch_override = @@fp_batch_override_unset
g_mm_bf16_batch_override = @@bf16_batch_override_unset
g_fp_override_active = false
}
def float_batch_override_active() : bool => g_fp_override_active
//! Blob+offset form of matmul_batch (see the matmul woff overload).
def matmul_batch(var y : array<float>; w : array<float>; woff : int64; x : array<float>; n, d, ntok : int64) {
unsafe {
var yp = addr(y[0])
let wp = addr<float const?>(w[woff])
let xp = addr<float const?>(x[0])
if (g_fp_override_active && ntok >= g_fp_override_min_ntok && invoke(g_mm_fp_batch_override, yp, wp, xp, n, d, ntok)) {
return
}
matmul_batch_core(yp, wp, xp, n, d, ntok)
}
}
//! Batched matvec against a native-BF16 weight [d x n] at halfword offset woff: Y [ntok x d]
//! token-major. Bit-for-bit matmul_batch's fp32 expand at half the weight read (dot_bf16's widen
//! is exact). Decode (ntok == 1) takes the GEMV chunk shaper.
def matmul_bf16_batch(var y : array<float>; w : array<uint16>; woff : int64; x : array<float>; n, d, ntok : int64) {
unsafe {
var yp = addr(y[0])
let wp = addr<uint16 const?>(w[woff])
let xp = addr<float const?>(x[0])
if (g_fp_override_active && ntok >= g_fp_override_min_ntok && invoke(g_mm_bf16_batch_override, yp, wp, xp, n, d, ntok)) {
return
}
let nch = ntok == 1l ? matmul_chunks_gemv(int(d), 1, n * d) : matmul_chunks(int(d), get_q8_batch_chunks_per_job(), n * d * ntok)
maybe_parallel_for(0, int(d), nch) $(rb, re) {
unsafe {
for (i in range(rb, re)) {
let wrow = wp + int64(i) * n
for (tk in range64(ntok)) {
yp[tk * d + int64(i)] = dot_bf16(xp + tk * n, wrow, n)
}
}
}
}
}
}
// ===== fp32 micro-GEMM: C[M x N] += A[M x K] * B[K x N] (B's N contiguous = SIMD dim) =====
// Broadcast-A FMA register-tile form (zero horizontal reduce) that makes flash attention's QKᵀ
// and P·V fast; single-thread, pointer-based (fork-callable). Tolerance-equal to scalar (FMA reorders low bits).
// Scalar C += A·B over rows [i0,i1) × cols [j0,j1) — the L-shaped remainder the 4×16 tile leaves.
[hint(noalias = cp, noalias = ap, noalias = bp)]
def private gemm_f32_region(var cp : float?; ap, bp : float const?; i0, i1, j0, j1, K, N : int64) {
unsafe {
for (i in range64(i0, i1)) {
let crow = i * N
for (kk in range64(K)) {
let a = ap[i * K + kk]
let brow = kk * N
for (j in range64(j0, j1)) {
cp[crow + j] += a * bp[brow + j]
}
}
}
}
}
// 4-row × 16-col tile: 16 float4 accumulators, broadcast-A FMA, no horizontal reduce. C += (cols
// [j0,j0+16) of rows [i0,i0+4)). N is the row stride of C/B; K the row stride / inner dim of A/B.
// Template public (not private): the tuner harness's cross-module [dasllama_grid] scan must see it.
[hint(noalias = cp, noalias = ap, noalias = bp)]
def template gemm_f32_uk_4x16_template(var cp : float ?; ap, bp : float const ?; i0, j0, K, N : int64) : void {
unsafe {
let o0 = (i0 + 0l) * N + j0; let o1 = (i0 + 1l) * N + j0