NumericAL
A header-only C++20 lexer built on REAL
Loading...
Searching...
No Matches
amx.hpp
Go to the documentation of this file.
1
22#ifndef NUMERICAL_AMX_HPP
23#define NUMERICAL_AMX_HPP
24
25#include <algorithm>
26#include <array>
27#include <cstring>
28#include <atomic>
29#include <cmath>
30#include <cstddef>
31#include <cstdint>
32#include <initializer_list>
33
34#include "simd.hpp"
35
36#if defined(__APPLE__)
37# include <TargetConditionals.h>
38#endif
39#if defined(__APPLE__) && defined(__aarch64__) && TARGET_OS_OSX && !defined(NUMERICAL_NO_AMX)
40# define NUMERICAL_AMX_COMPILED // the AMX path is compiled in (tested with #ifdef only)
41# include <sys/sysctl.h>
42#endif
43
45
46#ifdef NUMERICAL_AMX_COMPILED
48 enum class amx_op : unsigned
49 {
50 ldx = 0,
51 ldy = 1,
52 ldz = 4,
53 stz = 5,
54 fma64 = 10,
55 fma32 = 12,
56 };
57
63 template <amx_op Op>
64 inline void amx(std::uint64_t operand) noexcept
65 {
66 __asm__ volatile (".word (0x201000 + (%0 << 5) + 0%1 - ((0%1 >> 4) * 6))" // NOLINT(hicpp-no-assembler): no compiler emits the AMX
67 :
68 : "i" (static_cast<unsigned>(Op)), "r" (operand)
69 : "memory");
70 }
71
76 template <bool Off>
77 inline void amx_switch() noexcept
78 {
79 __asm__ volatile ("nop\nnop\nnop\n.word (0x201000 + (17 << 5) + %0)" // NOLINT(hicpp-no-assembler): no compiler emits the AMX
80 :
81 : "i" (static_cast<unsigned>(Off))
82 : "memory");
83 }
84
86 class amx_session
87 {
88 public:
89
90 amx_session() noexcept
91 {
92 amx_switch<false>();
93 }
94
95 amx_session(const amx_session&) = delete;
96 amx_session& operator=(const amx_session&) = delete;
97 amx_session(amx_session&&) = delete;
98 amx_session& operator=(amx_session&&) = delete;
99
100 ~amx_session()
101 {
102 amx_switch<true>();
103 }
104 };
105
107 [[nodiscard]] inline std::uint64_t amx_address(const void * at,
108 std::uint64_t index) noexcept
109 {
110 return (index << 56U) | reinterpret_cast<std::uintptr_t>(at); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the instruction takes the address as an integer
111 }
112
114 inline constexpr std::size_t amx_dot_group {8};
115
124 inline void amx_dot_rows_f64(const double* m,
125 std::size_t ld,
126 std::size_t rows,
127 const double* x,
128 std::size_t count,
129 double* out) noexcept
130 {
131 alignas(64) static constexpr std::array<double, 8> zero {};
132 alignas(64) std::array<std::array<double, 8>, amx_dot_group> lanes {};
133 const std::size_t whole {count - (count % 8)};
134 for (std::size_t r0 {0}; r0 < rows; r0 += amx_dot_group) {
135 const std::size_t group {std::min(amx_dot_group, rows - r0)};
136 for (std::size_t r {0}; r < group; ++r) {
138 }
139 for (std::size_t j {0}; j < whole; j += 8) {
140 amx<amx_op::ldx>(amx_address(x + j, 0));
141 for (std::size_t r {0}; r < group; ++r) {
142 amx<amx_op::ldy>(amx_address(m + ((r0 + r) * ld) + j, r));
143 amx<amx_op::fma64>((std::uint64_t {1} << 63U) | (std::uint64_t {r} << 20U) | (std::uint64_t {r} *64U)); // vector mode: Z row r += X0 ∘ Y r
144 }
145 }
146 for (std::size_t r {0}; r < group; ++r) { // every row stored before any is read: a read right behind its store waited on it
147 amx<amx_op::stz>(amx_address(lanes[r].data(), r));
148 }
149 for (std::size_t r {0}; r < group; ++r) {
150 for (std::size_t j {whole}; j < count; ++j) {
151 lanes[r][j % 8] = std::fma(m[((r0 + r) * ld) + j], x[j], lanes[r][j % 8]);
152 }
153 double sum {lanes[r][0]};
154 for (std::size_t l {1}; l < 8; ++l) {
155 sum += lanes[r][l];
156 }
157 out[r0 + r] = sum;
158 }
159 }
160 }
161
163 inline constexpr std::size_t amx_mr {32};
164
166 inline constexpr std::size_t amx_nr {16};
167
172 inline constexpr std::size_t amx_least_extent {16};
173
177 inline constexpr std::size_t amx_least_work {std::size_t {1} << 16U};
178
180 [[nodiscard]] constexpr bool amx_suits(std::size_t m,
181 std::size_t n,
182 std::size_t k) noexcept
183 {
184 return (m >= amx_least_extent && n >= amx_least_extent) || m * n * k >= amx_least_work;
185 }
186
188 inline constexpr std::size_t amx_f32_side {32};
189
191 inline constexpr std::size_t amx_f32_least_width {28};
192
202 [[nodiscard]] constexpr bool amx_suits_f32(std::size_t m,
203 std::size_t n,
204 std::size_t k) noexcept
205 {
206 const bool whole_tiles {n % amx_f32_side == 0};
207 const bool wide {m >= amx_least_extent && n >= amx_f32_least_width && m * n * k >= amx_least_work};
208 const bool tall_and_deep {m >= 64 && k >= 64};
209 return amx_suits(m, n, k) && (whole_tiles || wide || tall_and_deep);
210 }
211
215 inline constexpr std::size_t amx_in_place_stride {512};
216
227 [[gnu::noinline, gnu::aligned(64)]] inline void amx_depth_f64(std::size_t kc,
228 const double* ap,
229 const double* bp,
230 std::size_t ldb) noexcept
231 {
232 for (std::size_t p {0}; p < kc; ++p) {
233 const double* const a {ap + (p * amx_mr)};
234 const double* const b {bp + (p * ldb)};
235 for (std::uint64_t ya {0}; ya < 4; ++ya) {
236 amx<amx_op::ldy>(amx_address(a + (ya * 8), ya));
237 }
239 amx<amx_op::ldx>(amx_address(b + 8, 1));
240 for (std::uint64_t ya {0}; ya < 4; ++ya) {
241 for (std::uint64_t xb {0}; xb < 2; ++xb) {
242 // Matrix mode: Z row (2·ya + xb), X register xb (64 bytes each), Y register ya.
243 amx<amx_op::fma64>((((ya * 2) + xb) << 20U) | ((xb * 64) << 10U) | (ya * 64));
244 }
245 }
246 }
247 }
248
263 inline void amx_full_tile_f64(std::size_t kc,
264 const double* ap,
265 const double* bp,
266 std::size_t ldb,
267 double* tile,
268 std::size_t ld) noexcept
269 {
270 for (std::uint64_t ya {0}; ya < 4; ++ya) {
271 for (std::uint64_t xb {0}; xb < 2; ++xb) {
272 for (std::uint64_t j {0}; j < 8; ++j) {
273 amx<amx_op::ldz>(amx_address(tile + (((ya * 8) + j) * ld) + (xb * 8), (j * 8) + (ya * 2) + xb));
274 }
275 }
276 }
278 for (std::uint64_t ya {0}; ya < 4; ++ya) {
279 for (std::uint64_t xb {0}; xb < 2; ++xb) {
280 for (std::uint64_t j {0}; j < 8; ++j) {
281 amx<amx_op::stz>(amx_address(tile + (((ya * 8) + j) * ld) + (xb * 8), (j * 8) + (ya * 2) + xb));
282 }
283 }
284 }
285 }
286
289 [[gnu::noinline, gnu::aligned(64)]] inline void amx_depth_f32(std::size_t kc,
290 const float* ap,
291 const float* bp,
292 std::size_t ldb) noexcept
293 {
294 for (std::size_t p {0}; p < kc; ++p) {
295 const float* const a {ap + (p * amx_f32_side)};
296 const float* const b {bp + (p * ldb)};
298 amx<amx_op::ldy>(amx_address(a + 16, 1));
300 amx<amx_op::ldx>(amx_address(b + 16, 1));
301 for (std::uint64_t ya {0}; ya < 2; ++ya) {
302 for (std::uint64_t xb {0}; xb < 2; ++xb) {
303 amx<amx_op::fma32>((((ya * 2) + xb) << 20U) | ((xb * 64) << 10U) | (ya * 64));
304 }
305 }
306 }
307 }
308
310 inline void amx_depth(std::size_t kc,
311 const double* ap,
312 const double* bp,
313 std::size_t ldb) noexcept
314 {
316 }
317
319 inline void amx_depth(std::size_t kc,
320 const float* ap,
321 const float* bp,
322 std::size_t ldb) noexcept
323 {
325 }
326
328 template <typename T>
329 inline constexpr amx_op amx_multiply_add {amx_op::fma32};
330
332 template <>
333 inline constexpr amx_op amx_multiply_add<double> {amx_op::fma64};
334
342 template <typename T,
343 std::uint64_t Bands,
344 std::uint64_t Halves>
345 [[gnu::noinline, gnu::aligned(64)]] void amx_depth_part(std::size_t kc,
346 const T* ap,
347 const T* bp,
348 std::size_t ldb) noexcept
349 {
350 constexpr std::size_t lanes {64 / sizeof(T)}; // a register's elements
351 for (std::size_t p {0}; p < kc; ++p) {
352 const T* const a {ap + (p * amx_mr)};
353 const T* const b {bp + (p * ldb)};
354 for (std::uint64_t ya {0}; ya < Bands; ++ya) {
356 }
357 for (std::uint64_t xb {0}; xb < Halves; ++xb) {
359 }
360 for (std::uint64_t ya {0}; ya < Bands; ++ya) {
361 for (std::uint64_t xb {0}; xb < Halves; ++xb) {
362 amx<amx_multiply_add<T>>((((ya * 2) + xb) << 20U) | ((xb * 64) << 10U) | (ya * 64));
363 }
364 }
365 }
366 }
367
369 template <typename T,
370 std::uint64_t Bands>
371 void amx_depth_part_halves(std::size_t kc,
372 const T* ap,
373 const T* bp,
374 std::size_t ldb,
375 std::uint64_t halves) noexcept
376 {
377 if (halves == 1) {
379 }
380 else {
382 }
383 }
384
387 inline void amx_depth_part(std::size_t kc,
388 const double* ap,
389 const double* bp,
390 std::size_t ldb,
391 std::uint64_t bands,
392 std::uint64_t halves) noexcept
393 {
394 if (bands == 1) {
396 }
397 else if (bands == 2) {
399 }
400 else if (bands == 3) {
402 }
403 else { // every band: a partial tile, so one half (the whole tile goes to amx_depth)
405 }
406 }
407
409 inline void amx_depth_part(std::size_t kc,
410 const float* ap,
411 const float* bp,
412 std::size_t ldb,
413 std::uint64_t bands,
414 std::uint64_t halves) noexcept
415 {
416 if (bands == 1) {
418 }
419 else { // every band: a partial tile, so one half (the whole tile goes to amx_depth)
421 }
422 }
423
439 template <typename T>
440 void amx_partial_tile(std::size_t kc,
441 const T* ap,
442 const T* bp,
443 std::size_t ldb,
444 T* c,
445 std::size_t ldc,
446 std::size_t rows,
447 std::size_t cols) noexcept
448 {
449 static_assert(amx_f32_side == amx_mr, "both tiles have amx_mr rows");
450 constexpr std::size_t lanes {64 / sizeof(T)}; // a half row: one Z row
451 constexpr std::uint64_t bands {amx_mr / lanes}; // accumulators down the tile
452 alignas(64) std::array<T, amx_mr * lanes> cut {}; // the half row the last column cuts, per row
453 const std::uint64_t whole_halves {cols / lanes}; // halves wholly inside C
454 const std::size_t cut_width {cols % lanes}; // the cut half's elements inside C
455 const auto each_half = [&](auto&& at) {
456 for (std::uint64_t ya {0}; ya < bands; ++ya) {
457 for (std::uint64_t j {0}; j < lanes; ++j) {
458 const std::size_t row {(ya * lanes) + j};
459 if (row >= rows) {
460 continue;
461 }
462 for (std::uint64_t xb {0}; xb < 2; ++xb) {
463 const std::uint64_t z {(j * 2 * bands) + (ya * 2) + xb};
464 if (xb < whole_halves) {
465 at(c + (row * ldc) + (xb * lanes), z);
466 }
467 else if (xb == whole_halves && cut_width != 0) {
468 at(cut.data() + (row * lanes), z);
469 }
470 }
471 }
472 }
473 };
474 if (cut_width != 0) {
475 for (std::size_t row {0}; row < rows; ++row) {
476 for (std::size_t e {0}; e < cut_width; ++e) {
477 cut[(row * lanes) + e] = c[(row * ldc) + (whole_halves * lanes) + e];
478 }
479 }
480 }
481 each_half([](T* half, std::uint64_t z) { amx<amx_op::ldz>(amx_address(half, z)); });
482 const std::uint64_t used_bands {(rows + lanes - 1) / lanes}; // the accumulators C's rows fill
483 const std::uint64_t used_halves {std::min<std::uint64_t>(2, (cols + lanes - 1) / lanes)};
484 if (used_bands == bands && used_halves == 2) {
485 amx_depth(kc, ap, bp, ldb);
486 }
487 else {
489 }
490 each_half([](T* half, std::uint64_t z) { amx<amx_op::stz>(amx_address(half, z)); });
491 if (cut_width != 0) {
492 for (std::size_t row {0}; row < rows; ++row) {
493 for (std::size_t e {0}; e < cut_width; ++e) {
494 c[(row * ldc) + (whole_halves * lanes) + e] = cut[(row * lanes) + e];
495 }
496 }
497 }
498 }
499
502 inline void amx_full_tile_f32(std::size_t kc,
503 const float* ap,
504 const float* bp,
505 std::size_t ldb,
506 float* tile,
507 std::size_t ld) noexcept
508 {
509 for (std::uint64_t ya {0}; ya < 2; ++ya) {
510 for (std::uint64_t xb {0}; xb < 2; ++xb) {
511 for (std::uint64_t j {0}; j < 16; ++j) {
512 amx<amx_op::ldz>(amx_address(tile + (((ya * 16) + j) * ld) + (xb * 16), (j * 4) + (ya * 2) + xb));
513 }
514 }
515 }
517 for (std::uint64_t ya {0}; ya < 2; ++ya) {
518 for (std::uint64_t xb {0}; xb < 2; ++xb) {
519 for (std::uint64_t j {0}; j < 16; ++j) {
520 amx<amx_op::stz>(amx_address(tile + (((ya * 16) + j) * ld) + (xb * 16), (j * 4) + (ya * 2) + xb));
521 }
522 }
523 }
524 }
525
532 inline void amx_tile_f32(std::size_t kc,
533 const float* ap,
534 const float* bp,
535 std::size_t ldb,
536 float* c,
537 std::size_t ldc,
538 std::size_t rows,
539 std::size_t cols) noexcept
540 {
541 if (rows == amx_f32_side && cols == amx_f32_side) {
542 amx_full_tile_f32(kc, ap, bp, ldb, c, ldc);
543 return;
544 }
545 amx_partial_tile(kc, ap, bp, ldb, c, ldc, rows, cols);
546 }
547
560 inline void amx_tile_f64(std::size_t kc,
561 const double* ap,
562 const double* bp,
563 std::size_t ldb,
564 double* c,
565 std::size_t ldc,
566 std::size_t rows,
567 std::size_t cols) noexcept
568 {
569 if ((rows < amx_mr || cols < amx_nr) && (cols < 8 || cols % 8 == 0)) {
570 amx_partial_tile(kc, ap, bp, ldb, c, ldc, rows, cols);
571 return;
572 }
573 through_edge<amx_mr, amx_nr>(c, ldc, rows, cols, [&](double* tile, std::size_t ld) noexcept { amx_full_tile_f64(kc, ap, bp, ldb, tile, ld); });
574 }
575
579 inline constexpr std::size_t amx_stream_least {2048};
580
589 inline void amx_pack_a(const double* a,
590 std::size_t lda,
591 std::size_t rows,
592 std::size_t kc,
593 double* out) noexcept
594 {
595 using pair = double __attribute__((vector_size(16)));
596 std::size_t ir {0};
597 for (; rows * kc >= amx_stream_least && ir + amx_mr <= rows; ir += amx_mr) {
598 double* const sliver {out + (ir * kc)};
599 std::size_t p {0};
600 for (; p + 2 <= kc; p += 2) { // two columns of four rows a step: the rows' pairs read whole, their lanes swapped
601 for (std::size_t i {0}; i < amx_mr; i += 4) {
602 std::array<pair, 4> row {};
603 for (std::size_t r {0}; r < 4; ++r) {
604 std::memcpy(&row.at(r), a + ((ir + i + r) * lda) + p, sizeof(pair));
605 }
606 const pair here_low {__builtin_shufflevector(row[0], row[1], 0, 2)};
607 const pair here_high {__builtin_shufflevector(row[2], row[3], 0, 2)};
608 const pair next_low {__builtin_shufflevector(row[0], row[1], 1, 3)};
609 const pair next_high {__builtin_shufflevector(row[2], row[3], 1, 3)};
610 __asm__ volatile ("stnp %q0, %q1, [%2]" // NOLINT(hicpp-no-assembler): as below, four elements at once
611 :
612 : "w" (here_low), "w" (here_high), "r" (sliver + (p * amx_mr) + i)
613 : "memory");
614 __asm__ volatile ("stnp %q0, %q1, [%2]" // NOLINT(hicpp-no-assembler): as below, four elements at once
615 :
616 : "w" (next_low), "w" (next_high), "r" (sliver + ((p + 1) * amx_mr) + i)
617 : "memory");
618 }
619 }
620 for (; p < kc; ++p) {
621 for (std::size_t i {0}; i < amx_mr; i += 2) {
622 const double first {a[((ir + i) * lda) + p]};
623 const double second {a[((ir + i + 1) * lda) + p]};
624 __asm__ volatile ("stnp %d0, %d1, [%2]" // NOLINT(hicpp-no-assembler): no builtin emits a pair past the L1 under both compilers
625 :
626 : "w" (first), "w" (second), "r" (sliver + (p * amx_mr) + i)
627 : "memory");
628 }
629 }
630 }
631 for (; ir + amx_mr <= rows; ir += amx_mr) { // whole slivers below the threshold, the loop free of the padding's test
632 double* const sliver {out + (ir * kc)};
633 std::size_t p {0};
634 for (; p + 2 <= kc; p += 2) { // as above, by ordinary stores
635 for (std::size_t i {0}; i < amx_mr; i += 2) {
636 pair first;
637 pair second;
638 std::memcpy(&first, a + ((ir + i) * lda) + p, sizeof(pair));
639 std::memcpy(&second, a + ((ir + i + 1) * lda) + p, sizeof(pair));
640 const pair here {__builtin_shufflevector(first, second, 0, 2)};
641 const pair next {__builtin_shufflevector(first, second, 1, 3)};
642 std::memcpy(sliver + (p * amx_mr) + i, &here, sizeof(pair));
643 std::memcpy(sliver + ((p + 1) * amx_mr) + i, &next, sizeof(pair));
644 }
645 }
646 for (; p < kc; ++p) {
647 for (std::size_t i {0}; i < amx_mr; ++i) {
648 sliver[(p * amx_mr) + i] = a[((ir + i) * lda) + p];
649 }
650 }
651 }
652 if (ir < rows) {
653 double* const sliver {out + (ir * kc)};
654 for (std::size_t p {0}; p < kc; ++p) {
655 for (std::size_t i {0}; i < amx_mr; ++i) {
656 sliver[(p * amx_mr) + i] = ir + i < rows ? a[((ir + i) * lda) + p] : 0.0;
657 }
658 }
659 }
660 }
661
668 inline void amx_pack_a_f32(const float* a,
669 std::size_t lda,
670 std::size_t rows,
671 std::size_t kc,
672 float* out) noexcept
673 {
674 using quad = float __attribute__((vector_size(16)));
675 constexpr std::size_t stream_least {amx_stream_least * (sizeof(double) / sizeof(float))};
676 // Four rows' four elements as four columns.
677 const auto transposed = [](const quad& r0, const quad& r1, const quad& r2, const quad& r3) noexcept {
678 const quad low01 {__builtin_shufflevector(r0, r1, 0, 4, 1, 5)};
679 const quad high01 {__builtin_shufflevector(r0, r1, 2, 6, 3, 7)};
680 const quad low23 {__builtin_shufflevector(r2, r3, 0, 4, 1, 5)};
681 const quad high23 {__builtin_shufflevector(r2, r3, 2, 6, 3, 7)};
682 return std::array<quad, 4> {__builtin_shufflevector(low01, low23, 0, 1, 4, 5), __builtin_shufflevector(low01, low23, 2, 3, 6, 7),
684 };
685 // A column's eight rows, past the L1.
686 const auto store = [](const quad& upper, const quad& lower, float* at) noexcept {
687 __asm__ volatile ("stnp %q0, %q1, [%2]" // NOLINT(hicpp-no-assembler): as amx_pack_a, eight elements at once
688 :
689 : "w" (upper), "w" (lower), "r" (at)
690 : "memory");
691 };
692 std::size_t ir {0};
693 for (; rows * kc >= stream_least && ir + amx_f32_side <= rows; ir += amx_f32_side) {
694 float* const sliver {out + (ir * kc)};
695 std::size_t p {0};
696 for (; p + 4 <= kc; p += 4) { // four columns of eight rows a step: two blocks of 4 × 4 transposed
697 for (std::size_t i {0}; i < amx_f32_side; i += 8) {
698 std::array<quad, 8> row {};
699 for (std::size_t r {0}; r < 8; ++r) {
700 std::memcpy(&row.at(r), a + ((ir + i + r) * lda) + p, sizeof(quad));
701 }
702 const std::array<quad, 4> upper {transposed(row[0], row[1], row[2], row[3])};
703 const std::array<quad, 4> lower {transposed(row[4], row[5], row[6], row[7])};
704 float* const at {sliver + (p * amx_f32_side) + i};
705 store(upper[0], lower[0], at);
706 store(upper[1], lower[1], at + amx_f32_side);
707 store(upper[2], lower[2], at + (2 * amx_f32_side));
708 store(upper[3], lower[3], at + (3 * amx_f32_side));
709 }
710 }
711 for (; p < kc; ++p) {
712 for (std::size_t i {0}; i < amx_f32_side; i += 2) {
713 const float first {a[((ir + i) * lda) + p]};
714 const float second {a[((ir + i + 1) * lda) + p]};
715 __asm__ volatile ("stnp %s0, %s1, [%2]" // NOLINT(hicpp-no-assembler): as amx_pack_a, a pair past the L1
716 :
717 : "w" (first), "w" (second), "r" (sliver + (p * amx_f32_side) + i)
718 : "memory");
719 }
720 }
721 }
722 for (; ir < rows; ir += amx_f32_side) {
723 float* const sliver {out + (ir * kc)};
724 const std::size_t height {std::min(amx_f32_side, rows - ir)};
725 for (std::size_t p {0}; p < kc; ++p) {
726 for (std::size_t i {0}; i < amx_f32_side; ++i) {
727 sliver[(p * amx_f32_side) + i] = i < height ? a[((ir + i) * lda) + p] : 0.0F;
728 }
729 }
730 }
731 }
732
734 [[nodiscard]] constexpr bool amx_family_known(std::uint32_t family) noexcept
735 {
736 switch (family) {
737 case 0x1b588bb3U: // FIRESTORM_ICESTORM: M1, M1 Pro, Max, Ultra
738 case 0xda33d83dU: // BLIZZARD_AVALANCHE: M2 family
739 case 0xfa33415eU: // IBIZA: M3
740 case 0x5f4dea93U: // LOBOS: M3 Pro
741 case 0x72015832U: // PALMA: M3 Max
742 case 0x6f5129acU: // DONAN: M4
743 case 0x17d5b93aU: // BRAVA: M4 Pro, Max
744 return true;
745 default:
746 return false;
747 }
748 }
749
751 [[nodiscard]] inline std::uint32_t cpu_family() noexcept
752 {
753 std::uint32_t family {0};
754 std::size_t size {sizeof family};
755 (void)sysctlbyname("hw.cpufamily", &family, &size, nullptr, 0); // on failure family stays 0
756 return family;
757 }
758
763 [[nodiscard]] inline bool amx_dot_self_test() noexcept
764 {
765 constexpr std::size_t rows {11};
766 constexpr std::size_t count {21};
767 std::array<double, rows * count> m {};
768 std::array<double, count> x {};
769 std::array<double, rows> got {};
770 std::array<double, rows> want {};
771 for (std::size_t i {0}; i < m.size(); ++i) {
772 m[i] = std::ldexp(static_cast<double>((i * 37) % 101) - 50.0, -static_cast<int>(i % 9));
773 }
774 for (std::size_t j {0}; j < count; ++j) {
775 x[j] = 1.0 / static_cast<double>((j % 13) + 3);
776 }
777 for (std::size_t r {0}; r < rows; ++r) {
778 std::array<double, 8> lane {};
779 for (std::size_t j {0}; j < count; ++j) {
780 lane[j % 8] = std::fma(m[(r * count) + j], x[j], lane[j % 8]);
781 }
782 double sum {lane[0]};
783 for (std::size_t l {1}; l < 8; ++l) {
784 sum += lane[l];
785 }
786 want[r] = sum;
787 }
788 {
789 const amx_session on {};
790 amx_dot_rows_f64(m.data(), count, rows, x.data(), count, got.data());
791 }
792 return got == want;
793 }
794
797 [[nodiscard]] inline bool amx_f32_self_test() noexcept
798 {
799 constexpr std::size_t depth {7};
800 constexpr std::size_t side {amx_f32_side};
801 std::array<float, depth * side> a {};
802 std::array<float, depth * side> b {};
803 std::array<float, side * side> c {};
804 std::array<float, side * side> want {};
805 for (std::size_t i {0}; i < depth * side; ++i) {
806 a[i] = std::ldexp(static_cast<float>((i * 37) % 101) - 50.0F, -static_cast<int>(i % 9));
807 b[i] = 1.0F / static_cast<float>((i % 13) + 3);
808 }
809 for (std::size_t i {0}; i < side * side; ++i) {
810 c[i] = static_cast<float>(i % 17) * 0.1F;
811 want[i] = c[i];
812 }
813 for (const std::size_t rows : {side, std::size_t {19}}) {
814 const std::size_t cols {rows == side ? side : std::size_t {11}};
815 for (std::size_t i {0}; i < rows; ++i) {
816 for (std::size_t j {0}; j < cols; ++j) {
817 for (std::size_t p {0}; p < depth; ++p) {
818 want[(i * side) + j] = std::fma(a[(p * side) + i], b[(p * side) + j], want[(i * side) + j]);
819 }
820 }
821 }
822 {
823 const amx_session on {};
824 amx_tile_f32(depth, a.data(), b.data(), side, c.data(), side, rows, cols);
825 }
826 }
827 return c == want;
828 }
829
836 [[nodiscard]] inline bool amx_self_test() noexcept
837 {
838 constexpr std::size_t depth {7};
839 std::array<double, depth * amx_mr> a {};
840 std::array<double, depth * amx_nr> b {};
841 std::array<double, amx_mr * amx_nr> c {};
842 std::array<double, amx_mr * amx_nr> want {};
843 for (std::size_t i {0}; i < depth * amx_mr; ++i) {
844 a[i] = std::ldexp(static_cast<double>((i * 37) % 101) - 50.0, -static_cast<int>(i % 9));
845 }
846 for (std::size_t i {0}; i < depth * amx_nr; ++i) {
847 b[i] = 1.0 / static_cast<double>((i % 13) + 3);
848 }
849 for (std::size_t i {0}; i < amx_mr * amx_nr; ++i) {
850 c[i] = static_cast<double>(i % 17) * 0.1;
851 want[i] = c[i];
852 }
853 for (const std::size_t rows : {amx_mr, std::size_t {19}}) {
854 const std::size_t cols {rows == amx_mr ? amx_nr : std::size_t {11}};
855 for (std::size_t i {0}; i < rows; ++i) {
856 for (std::size_t j {0}; j < cols; ++j) {
857 for (std::size_t p {0}; p < depth; ++p) {
858 want[(i * amx_nr) + j] = std::fma(a[(p * amx_mr) + i], b[(p * amx_nr) + j], want[(i * amx_nr) + j]);
859 }
860 }
861 }
862 {
863 const amx_session on {};
864 amx_tile_f64(depth, a.data(), b.data(), amx_nr, c.data(), amx_nr, rows, cols);
865 }
866 }
867 const std::array<bool, 3> passed {c == want, amx_f32_self_test(), amx_dot_self_test()};
868 return std::count(passed.begin(), passed.end(), true) == 3; // no branch on a check that only a fault fails
869 }
870
872 [[nodiscard]] inline bool running_virtualised() noexcept
873 {
874 int present {0};
875 std::size_t size {sizeof present};
876 (void)sysctlbyname("kern.hv_vmm_present", &present, &size, nullptr, 0); // on failure present stays 0
877 return present != 0;
878 }
879
881 [[nodiscard]] inline std::atomic<bool>& amx_probed() noexcept
882 {
883 static std::atomic<bool> probed {false};
884 return probed;
885 }
886
889 [[nodiscard]] inline bool amx_usable_on(std::uint32_t family,
890 bool virtualised) noexcept
891 {
893 return false;
894 }
895 amx_probed().store(true);
896 return amx_self_test();
897 }
898
899#endif
900
902 [[nodiscard]] inline bool amx_usable() noexcept
903 {
904#ifdef NUMERICAL_AMX_COMPILED
905 static const bool usable {amx_usable_on(cpu_family(), running_virtualised())};
906 return usable;
907#else
908 return false;
909#endif
910 }
911} // namespace numerical::detail
912
913#endif // NUMERICAL_AMX_HPP
bool amx_usable() noexcept
Whether this chip's AMX can be used: known family and a passing self-test, decided once.
Definition amx.hpp:902
T sum(const tensor< T > &t)
The sum of every element, by pairwise halving (0 for an empty tensor).
The native vector of float and double the kernels hold their lanes in (GNU vector extensions,...