|
27 | 27 | * BK - K-unroll factor for DPAS (32 for int8) |
28 | 28 | * KU - K-loop unroll depth (2) |
29 | 29 | * NSLICES - number of mantissa slices |
| 30 | + * OZAKI_SB - slice-block width for the pair loop (1 = unblocked) |
30 | 31 | * MANT_BITS - mantissa bit count (52 for fp64, 23 for fp32) |
31 | 32 | * BIAS_PLUS_MANT - exponent bias + mantissa bits |
32 | 33 | * USE_DOUBLE - if 1, fp64 accumulation; otherwise fp32 |
|
48 | 49 | #if !defined(NSLICES) |
49 | 50 | # define NSLICES 8 |
50 | 51 | #endif |
| 52 | +/** |
| 53 | + * Slice blocking for the pair loop. The unblocked nest re-streams slice sa's |
| 54 | + * A panel once per sb, so p slices cost p*p panel loads where p distinct |
| 55 | + * panels exist. Blocking sa and sb by OZAKI_SB hoists the fragment loads out |
| 56 | + * of the pair dimension: loads drop by OZAKI_SB at the cost of OZAKI_SB^2 |
| 57 | + * accumulator sets held concurrently. Requires the split load/compute macros, |
| 58 | + * so it is Intel-only; other paths keep the unblocked nest. |
| 59 | + */ |
| 60 | +#if !defined(OZAKI_SB) |
| 61 | +# define OZAKI_SB 1 |
| 62 | +#endif |
51 | 63 | #if !defined(MANT_BITS) |
52 | 64 | # define MANT_BITS 52 |
53 | 65 | #endif |
|
78 | 90 | # define OZAKI_USE_OCL_KLOOP |
79 | 91 | #endif |
80 | 92 |
|
| 93 | +/** |
| 94 | + * Slice blocking needs the split load/compute macros (Intel DPAS path) and the |
| 95 | + * square traversal: under OZAKI_SQ every ordered pair is its own block entry, |
| 96 | + * whereas the triangular nest folds a transposed product into each off-diagonal |
| 97 | + * pair, which a shared-fragment block cannot express. |
| 98 | + */ |
| 99 | +#if (1 < OZAKI_SB) && defined(INTEL) && (2 <= INTEL) && (RTM >= 2) && (RTN >= 2) && OZAKI_SQ |
| 100 | +# if 0 != (NSLICES % OZAKI_SB) |
| 101 | +# error OZAKI_SB must divide NSLICES |
| 102 | +# endif |
| 103 | +# define OZAKI_SLICE_BLOCKED |
| 104 | +#endif |
| 105 | + |
81 | 106 | /** |
82 | 107 | * Bounds checks are OFF by default for performance. |
83 | 108 | * Set -DOZAKI_BOUNDS=1 to enable per-element M/N guards |
|
203 | 228 | } while (0) |
204 | 229 | #endif |
205 | 230 |
|
| 231 | +#if defined(OZAKI_SLICE_BLOCKED) |
| 232 | +/** |
| 233 | + * Slice-blocked K-loop: one K-step loads the A fragments of NA slices and the |
| 234 | + * B fragments of NB slices, then issues every (ia, ib) sub-tile product from |
| 235 | + * registers. Load messages per K-step drop from 2*NA*NB (unblocked, one pair |
| 236 | + * at a time) to NA+NB. |
| 237 | + * |
| 238 | + * AS_BASE/BS_BASE are the slice-0 panels, A_STRIDE/B_STRIDE the per-slice |
| 239 | + * strides, SA0/SB0 the first slice of each block. ACC holds NA*NB |
| 240 | + * accumulator groups of RTM*RTN each, indexed (ia * NB + ib). |
| 241 | + */ |
| 242 | +# define OZAKI_KSTEP_BLOCKED(AS_BASE, BS_BASE, A_STRIDE, B_STRIDE, SA0, SB0, K_PAD_, N_PAD_, M_, MI, NJ, KOFF, ACC) \ |
| 243 | + do { \ |
| 244 | + ushort8 a_bk_[OZAKI_SB][RTM]; \ |
| 245 | + uint8 b_bk_[OZAKI_SB][RTN]; \ |
| 246 | + int ia_bk_, ib_bk_; \ |
| 247 | + UNROLL_FORCE(OZAKI_SB) for (ia_bk_ = 0; ia_bk_ < OZAKI_SB; ++ia_bk_) \ |
| 248 | + { \ |
| 249 | + OZAKI_LOAD_A_TILED((AS_BASE) + (long)((SA0) + ia_bk_) * (A_STRIDE), K_PAD_, M_, MI, KOFF, a_bk_[ia_bk_]); \ |
| 250 | + } \ |
| 251 | + UNROLL_FORCE(OZAKI_SB) for (ib_bk_ = 0; ib_bk_ < OZAKI_SB; ++ib_bk_) \ |
| 252 | + { \ |
| 253 | + OZAKI_LOAD_B_TILED((BS_BASE) + (long)((SB0) + ib_bk_) * (B_STRIDE), N_PAD_, K_PAD_, NJ, KOFF, b_bk_[ib_bk_]); \ |
| 254 | + } \ |
| 255 | + UNROLL_FORCE(OZAKI_SB) for (ia_bk_ = 0; ia_bk_ < OZAKI_SB; ++ia_bk_) \ |
| 256 | + { \ |
| 257 | + UNROLL_FORCE(OZAKI_SB) for (ib_bk_ = 0; ib_bk_ < OZAKI_SB; ++ib_bk_) \ |
| 258 | + { \ |
| 259 | + OZAKI_COMPUTE_TILED(a_bk_[ia_bk_], b_bk_[ib_bk_], (ACC) + (ia_bk_ * OZAKI_SB + ib_bk_) * RTM * RTN); \ |
| 260 | + } \ |
| 261 | + } \ |
| 262 | + } while (0) |
| 263 | + |
| 264 | +# define OZAKI_KLOOP_BLOCKED(AS_BASE, BS_BASE, A_STRIDE, B_STRIDE, SA0, SB0, K_PAD_, N_PAD_, M_, MI, NJ, ACC) \ |
| 265 | + do { \ |
| 266 | + int k_b_; \ |
| 267 | + for (k_b_ = 0; k_b_ < (K_PAD_); k_b_ += BK) { \ |
| 268 | + OZAKI_KSTEP_BLOCKED(AS_BASE, BS_BASE, A_STRIDE, B_STRIDE, SA0, SB0, \ |
| 269 | + K_PAD_, N_PAD_, M_, MI, NJ, k_b_, ACC); \ |
| 270 | + } \ |
| 271 | + } while (0) |
| 272 | +#endif /* OZAKI_SLICE_BLOCKED */ |
| 273 | + |
206 | 274 | /* K-loop prefetch: opt-in via OZAKI_PREFETCH=1 (default off on PVC). */ |
207 | 275 | #if defined(OZAKI_PREFETCH) && (0 < OZAKI_PREFETCH) |
208 | 276 | # define OZAKI_KLOOP_PREFETCH(AS, BS, K, N, M, KOFF, MI, NJ) OZAKI_PREFETCH_TILED(AS, BS, K, N, M, KOFF, MI, NJ) |
@@ -599,6 +667,49 @@ kernel void gemm_fused( |
599 | 667 | } |
600 | 668 | #endif |
601 | 669 |
|
| 670 | +#if defined(OZAKI_SLICE_BLOCKED) |
| 671 | + /** |
| 672 | + * Slice-blocked pair loop. Each (sa0, sb0) block covers up to OZAKI_SB |
| 673 | + * consecutive slices per side and shares the loaded fragments across all |
| 674 | + * pairs inside it. The block bounds are compile-time constants, so the |
| 675 | + * cutoff predication below folds away entirely; only the tail blocks (where |
| 676 | + * NSLICES or the cutoff truncates the block) retain a narrower extent. |
| 677 | + * |
| 678 | + * Square traversal only: with OZAKI_SQ the mirror pair is a separate (sa, sb) |
| 679 | + * block entry, so no in-block transpose term is needed. |
| 680 | + */ |
| 681 | + for (sa = 0; sa < (SINT)NSLICES && (int)sa <= OZAKI_CUTOFF; sa += OZAKI_SB) { |
| 682 | + SINT sb0; |
| 683 | + for (sb0 = 0; sb0 < (SINT)NSLICES; sb0 += OZAKI_SB) { |
| 684 | + OZAKI_ACC_T c_blk[OZAKI_SB * OZAKI_SB * RTM * RTN]; |
| 685 | + int ia, ib; |
| 686 | + UNROLL_FORCE(OZAKI_SB * OZAKI_SB * RTM * RTN) |
| 687 | + for (ia = 0; ia < OZAKI_SB * OZAKI_SB * RTM * RTN; ++ia) c_blk[ia] = OZAKI_ACC_ZERO; |
| 688 | + OZAKI_KLOOP_BLOCKED(as_base, bs_base, a_stride, b_stride, sa, sb0, K_pad, N_pad, M, mi_base, nj_base, c_blk); |
| 689 | + /** |
| 690 | + * Every slice of the block is loaded and multiplied unconditionally -- |
| 691 | + * OZAKI_SB divides NSLICES, so the indices stay in range -- and pairs |
| 692 | + * past the cutoff are simply not flushed. Their products are dead work |
| 693 | + * on the tail blocks only, which is why OZAKI_SB stays small. |
| 694 | + */ |
| 695 | + UNROLL_FORCE(OZAKI_SB) for (ia = 0; ia < OZAKI_SB; ++ia) |
| 696 | + { |
| 697 | + const int high_a = MANT_BITS - (7 * ((int)sa + ia)); |
| 698 | + const int low_a = MAX(0, high_a - 6); |
| 699 | + UNROLL_FORCE(OZAKI_SB) for (ib = 0; ib < OZAKI_SB; ++ib) |
| 700 | + { |
| 701 | + const int high_b = MANT_BITS - (7 * ((int)sb0 + ib)); |
| 702 | + const int low_b = MAX(0, high_b - 6); |
| 703 | + if ((int)sa + ia + (int)sb0 + ib <= OZAKI_CUTOFF) { |
| 704 | + const real_t pscale = OZAKI_ALPHA_MUL(alpha, EXP2I(low_a + low_b - 2 * MANT_BITS)); |
| 705 | + OZAKI_SCALE_FLUSH(c_blk + (ia * OZAKI_SB + ib) * RTM * RTN, c, ldc, ea_cache, eb_cache, mi_base, nj_base, sg_lid, |
| 706 | + M, N, pscale); |
| 707 | + } |
| 708 | + } |
| 709 | + } |
| 710 | + } |
| 711 | + } |
| 712 | +#else |
602 | 713 | for (sa = 0; sa < (SINT)NSLICES && (int)sa <= OZAKI_CUTOFF; ++sa) { |
603 | 714 | const int high_sa = MANT_BITS - (7 * (int)sa); |
604 | 715 | const int low_bit_sa = MAX(0, high_sa - 6); |
@@ -760,6 +871,7 @@ kernel void gemm_fused( |
760 | 871 | #endif |
761 | 872 | } |
762 | 873 | } |
| 874 | +#endif /* OZAKI_SLICE_BLOCKED */ |
763 | 875 |
|
764 | 876 | #if !defined(OZAKI_USE_OCL_KLOOP) && !(defined(NV_MMA) && (NV_MMA)) |
765 | 877 | /* Final write: register C -> global C */ |
|
0 commit comments