Skip to content

Commit 476d2f8

Browse files
committed
Introduced OZAKI_SB
1 parent b6b52e4 commit 476d2f8

3 files changed

Lines changed: 152 additions & 6 deletions

File tree

samples/ozaki/kernels/ozaki1_int8.cl

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* BK - K-unroll factor for DPAS (32 for int8)
2828
* KU - K-loop unroll depth (2)
2929
* NSLICES - number of mantissa slices
30+
* OZAKI_SB - slice-block width for the pair loop (1 = unblocked)
3031
* MANT_BITS - mantissa bit count (52 for fp64, 23 for fp32)
3132
* BIAS_PLUS_MANT - exponent bias + mantissa bits
3233
* USE_DOUBLE - if 1, fp64 accumulation; otherwise fp32
@@ -48,6 +49,17 @@
4849
#if !defined(NSLICES)
4950
# define NSLICES 8
5051
#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
5163
#if !defined(MANT_BITS)
5264
# define MANT_BITS 52
5365
#endif
@@ -78,6 +90,19 @@
7890
# define OZAKI_USE_OCL_KLOOP
7991
#endif
8092

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+
81106
/**
82107
* Bounds checks are OFF by default for performance.
83108
* Set -DOZAKI_BOUNDS=1 to enable per-element M/N guards
@@ -203,6 +228,49 @@
203228
} while (0)
204229
#endif
205230

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+
206274
/* K-loop prefetch: opt-in via OZAKI_PREFETCH=1 (default off on PVC). */
207275
#if defined(OZAKI_PREFETCH) && (0 < OZAKI_PREFETCH)
208276
# 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(
599667
}
600668
#endif
601669

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
602713
for (sa = 0; sa < (SINT)NSLICES && (int)sa <= OZAKI_CUTOFF; ++sa) {
603714
const int high_sa = MANT_BITS - (7 * (int)sa);
604715
const int low_bit_sa = MAX(0, high_sa - 6);
@@ -760,6 +871,7 @@ kernel void gemm_fused(
760871
#endif
761872
}
762873
}
874+
#endif /* OZAKI_SLICE_BLOCKED */
763875

764876
#if !defined(OZAKI_USE_OCL_KLOOP) && !(defined(NV_MMA) && (NV_MMA))
765877
/* Final write: register C -> global C */

samples/ozaki/ozaki_opencl.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
432432
char build_options[128];
433433
const int mant_bits = use_double ? 52 : 23;
434434
const int bias_plus_mant = use_double ? 1075 : 150;
435-
int rtm = 0, rtn = 0, rtn_req = 0, ku_req, biggrf, hier;
435+
int rtm = 0, rtn = 0, rtm_req = 0, rtn_req = 0, ku_req, biggrf, hier;
436436
size_t max_wgs;
437437
int v;
438438
{
@@ -461,7 +461,10 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
461461
max_wgs = (0 != biggrf) ? devinfo->wgsize[0] / 2 : devinfo->wgsize[0];
462462
/* Read optional user overrides for register tiling factors. */
463463
env = getenv("OZAKI_RTM");
464-
if (NULL != env && 0 < atoi(env)) rtm = atoi(env);
464+
if (NULL != env && 0 < atoi(env)) {
465+
rtm = atoi(env);
466+
rtm_req = rtm; /* explicit request applies to both schemes */
467+
}
465468
env = getenv("OZAKI_RTN");
466469
if (NULL != env && 0 < atoi(env)) {
467470
rtn = atoi(env);
@@ -503,9 +506,31 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
503506
*/
504507
env = getenv("OZAKI_NPANEL");
505508
ctx->npanel = (NULL != env) ? atoi(env) : 0;
509+
/**
510+
* Scheme-1 slice blocking (1 = unblocked). The kernel shares each loaded
511+
* A/B fragment across all pairs of an OZAKI_SB-wide slice block, trading
512+
* OZAKI_SB^2 concurrent accumulator sets for an OZAKI_SB-fold cut in load
513+
* messages.
514+
*
515+
* Opt-in: blocking only pays where the halved RTM it requires still leaves
516+
* registers to spare, which on PVC means 256-GRF (SB=2 measured -18% at
517+
* n=1024 and -5% at 6144 there). Under the default GRF128 the row tiling
518+
* is already 2, so the doubled accumulator set spills instead -- 3.3x
519+
* slower at n=6144 -- and fp32 lacks the pair redundancy to recover even
520+
* with registers available (+1.5% at n=4096, nslices=4).
521+
*/
522+
env = getenv("OZAKI_SB");
523+
{ const int sb = (NULL != env && 0 < atoi(env)) ? atoi(env) : 1;
524+
/* The kernel indexes whole blocks, so OZAKI_SB must divide NSLICES. */
525+
ctx->sb = (1 < sb && sb <= nslices && 0 == (nslices % sb)) ? sb : 1;
526+
if (1 < sb && ctx->sb != sb && 0 != verbosity) {
527+
fprintf(stderr, "INFO OZAKI: OZAKI_SB=%d does not divide nslices=%d -- ignored\n", sb, nslices);
528+
}
529+
}
506530
if (0 == rtm) {
507531
if (0 != devinfo->intel && 0 != gpu) {
508-
rtm = (0 != biggrf) ? 4 : 2;
532+
/* Slice blocking holds SB^2 accumulator sets, so it halves RTM. */
533+
rtm = (0 != biggrf) ? (1 < ctx->sb ? 2 : 4) : 2;
509534
}
510535
else if (0 != ctx->nv_mma && 0 != gpu) {
511536
rtm = 2;
@@ -650,10 +675,10 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
650675
" -DNSLICES=%d -DUSE_DOUBLE=%d"
651676
" -DMANT_BITS=%d -DBIAS_PLUS_MANT=%d"
652677
" -DBM_PRE=%d -DBN_PRE=%d -DBK_PRE=%d"
653-
" -DRTM=%d -DRTN=%d"
678+
" -DRTM=%d -DRTN=%d -DOZAKI_SB=%d"
654679
" -DOZAKI_SQ=%d -DCONSTANT=global",
655680
bk_pre, ctx->ku, ctx->rc, sg, (int)devinfo->intel, nv,
656-
nslices, use_double, mant_bits, bias_plus_mant, bm_pre, bn_pre, bk_pre, rtm, rtn, sq_jit);
681+
nslices, use_double, mant_bits, bias_plus_mant, bm_pre, bn_pre, bk_pre, rtm, rtn, ctx->sb, sq_jit);
657682
if (0 != ctx->nv_mma) {
658683
goff += (size_t)LIBXS_SNPRINTF(build_params + goff, sizeof(build_params) - goff, " -DNV_MMA=1");
659684
}
@@ -737,7 +762,14 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
737762
/* Fractional CRT is double-only: without fp64 fall back to Garner. */
738763
const int fraccrt = (0 == has_fp64) ? 0 : ((1 == fraccrt_req || 2 == fraccrt_req) ? fraccrt_req : 0);
739764
const int crt_hier = (1 == fraccrt) ? 0 : (0 != ctx->hier || 3 == kind || 2 == fraccrt);
740-
const int crt_rtm = (0 != crt_hier && 0 != biggrf && 0 == ctx->hier) ? LIBXS_MAX(rtm / 2, 1) : rtm;
765+
/**
766+
* Scheme 2 has no pair loop, so slice blocking never applies to it and it
767+
* must not inherit the halved RTM that blocking imposes on Scheme 1.
768+
* rtm_crt_base is the row tiling Scheme 1 would have used unblocked.
769+
*/
770+
const int rtm_crt_base = (1 < ctx->sb && 0 == rtm_req) ? rtm * 2 : rtm;
771+
const int crt_rtm =
772+
(0 != crt_hier && 0 != biggrf && 0 == ctx->hier) ? LIBXS_MAX(rtm_crt_base / 2, 1) : rtm_crt_base;
741773
/**
742774
* MMA gives a sub-tile 16 rows but only 8 columns, so reaching a square
743775
* register tile needs twice the column tiling. Scheme 2 measured +36% at
@@ -1009,6 +1041,7 @@ int ozaki_init(ozaki_context_t* ctx, int tm, int tn, int use_double, int kind, i
10091041
if (ctx->crt_rtm != ctx->rtm) ozaki_print_opt(stderr, "crt_rtm", ctx->crt_rtm);
10101042
ozaki_print_opt(stderr, "rtn", ctx->rtn);
10111043
if (ctx->crt_rtn != ctx->rtn) ozaki_print_opt(stderr, "crt_rtn", ctx->crt_rtn);
1044+
if (1 < ctx->sb) ozaki_print_opt(stderr, "sb", ctx->sb);
10121045
if (0 != devinfo->intel) {
10131046
const int crt_grf128 = (0 != ctx->crt_rtm && ctx->crt_rtm < ctx->rtm);
10141047
ozaki_print_opt(stderr, "grf", ctx->biggrf ? 256 : 128);

samples/ozaki/ozaki_opencl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ typedef struct ozaki_context_t {
187187
*/
188188
int rtm, rtn, crt_rtm, crt_rtn;
189189
int ku; /* K-loop unroll factor (compiled into kernel) */
190+
int sb; /* Scheme-1 slice-block width for the pair loop (1 = unblocked) */
190191
int rc; /* DPAS repeat count: 8 (default) or 4 (split) */
191192
int nv_mma; /* NV MMA path enabled (m16n8k32, SG=32) */
192193
int pb; /* CRT prime batching factor (compiled into kernel) */

0 commit comments

Comments
 (0)