potrf_lpanel_device.hh
Go to the documentation of this file.
1#pragma once
2
3// The native batched POTRF LPANEL kernel: LEFT-LOOKING, thread-per-row. Local memory holds
4// one `n x NB` panel, not the matrix; the columns to its left are re-read from global memory.
5// Four invariants a maintainer can silently break. (1) EVERY barrier is a WORK-GROUP barrier
6// at the top level, outside the `if` guarding its phase, and failure is a predicated skip,
7// never a `break` -- the schedule is then a function of (n, NB) alone, which is what makes
8// packing G matrices into one work-group legal, the opposite of potrf_cta_device.hh. (2) B6 is
9// not optional: the next panel's left-looking read is a GLOBAL read of the columns the store
10// just wrote, by different work-items. (3) Every global read and every store is guarded by
11// `row >= column`, so the other triangle is neither read nor written; ortho.cc depends on it.
12// (4) rp[], rS[] and rA[] must stay in registers -- never a parameter, never a dynamic index.
13// evidence: docs/perf/potrf.md#the-lpanel-tier
14
15#include <src/sycl/device_scalar.hh>
16#include <src/sycl/kernel_attrs.hh>
17
18#include <sycl/sycl.hpp>
19
20#include <cstddef>
21#include <cstdint>
22
23namespace batchlas::potrf_native {
24
25// The whole body for ONE matrix, Uplo::Lower; sA, sB, fail and Ag are already offset to it.
26// `L >= n` lanes: lane `r` owns row `r` for the whole call, because rp/rS/rA must survive the
27// k loop. `store` is false for a packed group's dead slots, which still run the WHOLE body --
28// the barrier count is work-group uniform -- with only the global stores suppressed.
29template <typename D, typename R, int NB>
30inline void potrf_lpanel_body(const sycl::nd_item<1>& it,
31 int tid, int L, bool store,
32 D* __restrict sA, int slda,
33 D* __restrict sB,
34 int* __restrict fail,
35 D* __restrict Ag, int ldg,
36 int n) {
37 // Local-space views (generic cfloat reads were LD.E). evidence: docs/perf/blackwell.md#potrf-lpanel-vector-sb
38 const auto sAl = sycl::address_space_cast<sycl::access::address_space::local_space,
39 sycl::access::decorated::no>(sA);
40 const auto sBl = sycl::address_space_cast<sycl::access::address_space::local_space,
41 sycl::access::decorated::no>(sB);
42 if (tid == 0) *fail = 0;
43 sycl::group_barrier(it.get_group()); // B0
44
45 const int row = tid;
46 bool ok = true; // matrix-uniform: every lane derives it from the same local pivot
47
48 for (int j = 0; j < n; j += NB) {
49 const int ib = (n - j < NB) ? (n - j) : NB;
50 const bool panel_ok = ok;
51 const bool live = panel_ok && (row >= j) && (row < n);
52
53 // (1) prefetch; `row >= j + i` keeps the strictly upper diagonal block unread.
54 D rp[NB];
55 D rS[NB];
56BATCHLAS_UNROLL_FULL
57 for (int i = 0; i < NB; ++i) {
58 D v{};
59 if (live && i < ib && row >= j + i) {
60 v = Ag[row + static_cast<std::ptrdiff_t>(j + i) * ldg];
61 // A Hermitian diagonal's imaginary part is contractually ignored.
62 if (row == j + i) v = sycl_device::dev_from_real<D>(sycl_device::dev_real(v));
63 }
64 rp[i] = v;
65 rS[i] = D{};
66 }
67
68 // (2) rS(row,i) = sum_{k<j} L(row,k)*conj(L(j+i,k)); (4) factors the result in local.
69 for (int k = 0; k < j; k += NB) {
70 const int kb = ((j - k) < NB) ? (j - k) : NB;
71
72 // Out-of-range entries are ZEROED, not skipped: the FMA loop below is then a
73 // fully unrolled NB x NB with no inner bound test.
74 for (int e = tid; e < NB * NB; e += L) {
75 const int i = e % NB;
76 const int kk = e / NB;
77 D v{};
78 if (i < ib && kk < kb) {
79 v = sycl_device::dev_conj(
80 Ag[(j + i) + static_cast<std::ptrdiff_t>(k + kk) * ldg]);
81 }
82 sBl[i + kk * NB] = v;
83 }
84 sycl::group_barrier(it.get_group()); // B1
85
86 if (live) {
87 D rA[NB];
88BATCHLAS_UNROLL_FULL
89 for (int kk = 0; kk < NB; ++kk) {
90 rA[kk] = (kk < kb)
91 ? Ag[row + static_cast<std::ptrdiff_t>(k + kk) * ldg]
92 : D{};
93 }
94 // Lane-uniform sB column as 16-byte local vectors; each rS[i] still sums kk
95 // in ascending order. evidence: docs/perf/blackwell.md#potrf-lpanel-vector-sb
96 using V = sycl::vec<float, 4>;
97 constexpr int VW = 16 / static_cast<int>(sizeof(D));
98 static_assert((NB * sizeof(D)) % 16 == 0, "sB column must be whole vectors");
99 auto sBv = sycl::address_space_cast<sycl::access::address_space::local_space,
100 sycl::access::decorated::no>(
101 reinterpret_cast<V*>(sB));
102BATCHLAS_UNROLL_FULL
103 for (int kk = 0; kk < NB; ++kk) {
104 D col[NB];
105BATCHLAS_UNROLL_FULL
106 for (int v = 0; v < NB / VW; ++v) {
107 const V x = sBv[(kk * NB) / VW + v];
108 __builtin_memcpy(&col[v * VW], &x, 16);
109 }
110BATCHLAS_UNROLL_FULL
111 for (int i = 0; i < NB; ++i) {
112 sycl_device::fma_acc(rS[i], rA[kk], col[i]);
113 }
114 }
115 }
116 sycl::group_barrier(it.get_group()); // B2 -- WAR on sB
117 }
118
119 // (3) publish; the strictly upper diagonal block is ZEROED, not left indeterminate.
120 if (live) {
121BATCHLAS_UNROLL_FULL
122 for (int i = 0; i < NB; ++i) {
123 if (i < ib) {
124 D v{};
125 if (row >= j + i) {
126 v = sycl_device::dev_sub(rp[i], rS[i]);
127 // fma_acc(x, conj(x)) leaves a residue in the imaginary part.
128 if (row == j + i) {
129 v = sycl_device::dev_from_real<D>(sycl_device::dev_real(v));
130 }
131 }
132 sAl[row + static_cast<std::ptrdiff_t>(i) * slda] = v;
133 }
134 }
135 }
136 sycl::group_barrier(it.get_group()); // B3
137
138 for (int i = 0; i < ib; ++i) {
139 const R d = sycl_device::dev_real(sAl[(j + i) + static_cast<std::ptrdiff_t>(i) * slda]);
140
141 // `!(d > 0)`, not `d <= 0`, so NaN is rejected too, as LAPACK does; every lane
142 // reads the same local cell, so `bad` needs no broadcast.
143 const bool bad = ok && !(d > R(0));
144 if (bad) {
145 ok = false; // sticky: FIRST FAILURE WINS
146 if (tid == 0) *fail = j + i + 1; // 1-based GLOBAL column
147 }
148
149 // WAR: every lane READ this cell just above, lane j+i overwrites it below.
150 sycl::group_barrier(it.get_group()); // B3b, evidence: docs/perf/potrf.md#the-pivot-cell-war-race
151
152 if (ok) {
153 const R dkk = sycl::sqrt(d);
154 const R rs = R(1) / dkk; // NOT rsqrt: rsqrt.approx is not the reference
155 if (row == j + i) {
156 sAl[row + static_cast<std::ptrdiff_t>(i) * slda] =
157 sycl_device::dev_from_real<D>(dkk);
158 } else if (row > j + i && row < n) {
159 sAl[row + static_cast<std::ptrdiff_t>(i) * slda] = sycl_device::dev_mul_real(
160 sAl[row + static_cast<std::ptrdiff_t>(i) * slda], rs);
161 }
162 }
163 sycl::group_barrier(it.get_group()); // B4
164
165 if (ok && row > j + i && row < n) {
166 const D lri = sAl[row + static_cast<std::ptrdiff_t>(i) * slda];
167 for (int c = i + 1; c < ib; ++c) {
168 const D ljc = sAl[(j + c) + static_cast<std::ptrdiff_t>(i) * slda];
169 sAl[row + static_cast<std::ptrdiff_t>(c) * slda] = sycl_device::dev_sub(
170 sAl[row + static_cast<std::ptrdiff_t>(c) * slda],
171 sycl_device::dev_mul(lri, sycl_device::dev_conj(ljc)));
172 }
173 }
174 sycl::group_barrier(it.get_group()); // B5
175 }
176
177 // (5) store. Guarded by the panel's ENTRY state, not by `ok`: a panel that failed
178 // part-way still holds finite numbers.
179 if (store && panel_ok && row < n) {
180 for (int i = 0; i < ib; ++i) {
181 if (row >= j + i) {
182 Ag[row + static_cast<std::ptrdiff_t>(j + i) * ldg] =
183 sAl[row + static_cast<std::ptrdiff_t>(i) * slda];
184 }
185 }
186 }
187 sycl::group_barrier(it.get_group()); // B6 -- the next panel READS these columns
188 }
189}
190
191} // namespace batchlas::potrf_native
void potrf_lpanel_body(const sycl::nd_item< 1 > &it, int tid, int L, bool store, D *__restrict sA, int slda, D *__restrict sB, int *__restrict fail, D *__restrict Ag, int ldg, int n)