potrf_cta_device.hh
Go to the documentation of this file.
1#pragma once
2
3// The native batched POTRF CTA kernel: one matrix is staged whole into local
4// memory and factorised by a right-looking blocked recurrence, touching global
5// memory exactly twice. evidence: docs/perf/potrf.md
6//
7// Three invariants a maintainer can silently break:
8// * Phase barriers follow `Scope`, which potrf_cta_launch_params (potrf_cta.cc)
9// derives; the wrong scope races (P1)->(P2)->(P3) into a plausible wrong
10// factor rather than a crash.
11// * Every phase barrier sits at the top level, outside the `if` guarding its
12// phase, and failure is a predicated skip, never a `break`, so both paths
13// execute the same barrier count.
14// * d[NB], x[NB] and acc[TS][TS] must stay in registers: never a parameter,
15// never a dynamic index (which silently relocates them to local memory).
16// evidence: docs/perf/potrf.md#register-gate
17
18#include <src/sycl/device_scalar.hh>
19#include <src/sycl/kernel_attrs.hh>
20
21#include <sycl/sycl.hpp>
22
23#include <cstddef>
24#include <cstdint>
25
27
28enum class PotrfScope { SubGroup, WorkGroup };
29
30template <PotrfScope SC>
31inline void potrf_phase_barrier(const sycl::nd_item<1>& it) {
32 if constexpr (SC == PotrfScope::SubGroup) {
33 sycl::group_barrier(it.get_sub_group());
34 } else {
35 sycl::group_barrier(it.get_group());
36 }
37}
38
39// (P1) -- the ib x ib diagonal block on ONE sub-group; lane r owns row j+r.
40template <typename D, typename R, int NB>
41inline void potrf_diag_block_subgroup(const sycl::sub_group& sg,
42 D* __restrict S, int lda,
43 int j, int ib,
44 R* __restrict diag,
45 int* __restrict fail) {
46 const int lane = static_cast<int>(sg.get_local_linear_id());
47
48 // Out-of-range entries are zeroed, not left indeterminate: lanes ib..31 still
49 // take part in every select_from_group below.
50 D d[NB];
51BATCHLAS_UNROLL_FULL
52 for (int c = 0; c < NB; ++c) {
53 d[c] = (lane < ib && c < ib && c <= lane)
54 ? S[(j + lane) + static_cast<std::ptrdiff_t>(j + c) * lda]
55 : D{};
56 }
57
58 bool alive = true;
59BATCHLAS_UNROLL_FULL
60 for (int k = 0; k < NB; ++k) {
61 // The pivot is lane k's register d[k], not S(j+k, j+k), which still holds
62 // the ORIGINAL value: reading the tile gives wrong columns and `info == 0`
63 // for any matrix failing at a leading minor > 1 (InfoIndexIsExact).
64 const R akk = sycl::select_from_group(sg, sycl_device::dev_real(d[k]),
65 static_cast<uint32_t>(k));
66
67 bool active = alive && (k < ib);
68
69 // `!(akk > 0)`, not `akk <= 0`, so NaN is rejected too, as LAPACK does.
70 if (active && !(akk > R(0))) {
71 if (lane == 0) *fail = j + k + 1; // 1-based GLOBAL column
72 active = false;
73 alive = false; // sticky: FIRST FAILURE WINS
74 }
75
76 const R dkk = active ? sycl::sqrt(akk) : R(1);
77 const R r = R(1) / dkk; // NOT rsqrt: rsqrt.approx is not the reference
78
79 if (active) {
80 if (lane == k) {
81 d[k] = sycl_device::dev_from_real<D>(dkk);
82 } else if (lane > k && lane < ib) {
83 d[k] = sycl_device::dev_mul_real(d[k], r);
84 }
85
86 // `lane < ib` is load-bearing: unguarded, lanes ib..31 clobber the A21
87 // panel (P2) reads, and on the ragged last panel a neighbouring matrix.
88 if (lane < ib && lane >= k) {
89 S[(j + lane) + static_cast<std::ptrdiff_t>(j + k) * lda] = d[k];
90 }
91 if (lane == 0) diag[k] = dkk;
92 }
93
94 // B1'. ALWAYS the sub-group and unconditional: (P1) may run inside
95 // `if (sg_id == 0)`, where a work-group barrier would diverge.
96 sycl::group_barrier(sg);
97
98 // No WAR hazard, hence no second barrier: step k+1 publishes a later column.
99 if (active) {
100BATCHLAS_UNROLL_FULL
101 for (int c = k + 1; c < NB; ++c) {
102 if (c < ib && lane < ib && lane >= c) {
103 d[c] = sycl_device::dev_sub(
104 d[c],
105 sycl_device::dev_mul(
106 d[k],
107 sycl_device::dev_conj(
108 S[(j + c) + static_cast<std::ptrdiff_t>(j + k) * lda])));
109 }
110 }
111 }
112 }
113}
114
115// (P2) -- the panel solve L21 = A21 * L11^-H; rows are independent, no barrier.
116template <typename D, typename R, int NB>
117inline void potrf_panel_solve_rows(int tid, int L,
118 D* __restrict S, int lda,
119 int j, int ib, int m2,
120 const R* __restrict diag) {
121 for (int row = tid; row < m2; row += L) {
122 const int i = j + ib + row;
123
124 D x[NB];
125BATCHLAS_UNROLL_FULL
126 for (int c = 0; c < NB; ++c) {
127 x[c] = (c < ib) ? S[i + static_cast<std::ptrdiff_t>(j + c) * lda] : D{};
128 }
129
130BATCHLAS_UNROLL_FULL
131 for (int c = 0; c < NB; ++c) {
132 if (c < ib) {
133 D s = x[c];
134BATCHLAS_UNROLL_FULL
135 for (int p = 0; p < NB; ++p) {
136 if (p < c) {
137 s = sycl_device::dev_sub(
138 s,
139 sycl_device::dev_mul(
140 x[p],
141 sycl_device::dev_conj(
142 S[(j + c) + static_cast<std::ptrdiff_t>(j + p) * lda])));
143 }
144 }
145 // A DIVIDE, as reference ?trsm divides; (P1) scales by a
146 // reciprocal because ?potf2 does.
147 x[c] = sycl_device::dev_div_real(s, diag[c]);
148 }
149 }
150
151BATCHLAS_UNROLL_FULL
152 for (int c = 0; c < NB; ++c) {
153 if (c < ib) S[i + static_cast<std::ptrdiff_t>(j + c) * lda] = x[c];
154 }
155 }
156}
157
158// (P3) -- the trailing update A22 -= L21 L21^H in TS x TS register tiles, the
159// triangle trimmed at TILE granularity by `ra >= cb`. `off` is the Rt0-based
160// prefix table, rescaled here by `dR = Rt0 - Rt`. No barrier.
161template <typename D, typename R, int TS>
162inline void potrf_trailing_tiles(int tid, int L,
163 D* __restrict S, int lda,
164 int j, int ib, int m2,
165 int Rt, int dR,
166 const int* __restrict off) {
167 const int Ntiles = Rt * (Rt + 1) / 2;
168 const int base = j + ib;
169
170 for (int t = tid; t < Ntiles; t += L) {
171 int lo = 0, hi = Rt - 1;
172 while (lo < hi) {
173 const int mid = (lo + hi + 1) >> 1;
174 if (off[mid] - mid * dR <= t) {
175 lo = mid;
176 } else {
177 hi = mid - 1;
178 }
179 }
180 const int ct = lo;
181 const int rt = ct + (t - (off[ct] - ct * dR));
182 const int r0 = rt * TS;
183 const int c0 = ct * TS;
184
185 D acc[TS][TS];
186BATCHLAS_UNROLL_FULL
187 for (int a = 0; a < TS; ++a) {
188BATCHLAS_UNROLL_FULL
189 for (int b = 0; b < TS; ++b) acc[a][b] = D{};
190 }
191
192 for (int k = 0; k < ib; ++k) {
193 D va[TS], vb[TS];
194BATCHLAS_UNROLL_FULL
195 for (int a = 0; a < TS; ++a) {
196 va[a] = (r0 + a < m2)
197 ? S[(base + r0 + a) + static_cast<std::ptrdiff_t>(j + k) * lda]
198 : D{};
199 }
200BATCHLAS_UNROLL_FULL
201 for (int b = 0; b < TS; ++b) {
202 vb[b] = (c0 + b < m2)
203 ? S[(base + c0 + b) + static_cast<std::ptrdiff_t>(j + k) * lda]
204 : D{};
205 }
206BATCHLAS_UNROLL_FULL
207 for (int a = 0; a < TS; ++a) {
208BATCHLAS_UNROLL_FULL
209 for (int b = 0; b < TS; ++b) {
210 sycl_device::fma_acc(acc[a][b], va[a], sycl_device::dev_conj(vb[b]));
211 }
212 }
213 }
214
215BATCHLAS_UNROLL_FULL
216 for (int a = 0; a < TS; ++a) {
217BATCHLAS_UNROLL_FULL
218 for (int b = 0; b < TS; ++b) {
219 const int ra = r0 + a;
220 const int cb = c0 + b;
221 if (ra < m2 && cb < m2 && ra >= cb) {
222 D v = sycl_device::dev_sub(
223 S[(base + ra) + static_cast<std::ptrdiff_t>(base + cb) * lda],
224 acc[a][b]);
225 // Force the Hermitian diagonal real: dead today ((P1) rewrites
226 // every output diagonal), live if anything ever reads one here.
227 if constexpr (sycl_device::dev_is_complex_v<D>) {
228 if (ra == cb) {
229 v = sycl_device::dev_from_real<D>(sycl_device::dev_real(v));
230 }
231 }
232 S[(base + ra) + static_cast<std::ptrdiff_t>(base + cb) * lda] = v;
233 }
234 }
235 }
236 }
237}
238
239// The whole body for ONE matrix; S, diag, fail, off and Ag are already offset to
240// it. `upper` is a LOAD/STORE TRANSFORM, not a second algorithm: A = U^H U is the
241// same recurrence on S(i,c) = conj(A(c,i)), so Uplo costs no extra instantiation.
242template <typename D, typename R, int NB, int TS, PotrfScope SC>
243inline void potrf_cta_body(const sycl::nd_item<1>& it,
244 const sycl::sub_group& sg,
245 int tid, int L, bool p1_active,
246 D* __restrict S, int lda,
247 R* __restrict diag,
248 int* __restrict fail,
249 int* __restrict off,
250 D* __restrict Ag, int ldg,
251 int n, bool upper) {
252 const int m2_0 = (n > NB) ? (n - NB) : 0;
253 const int Rt0 = (m2_0 + TS - 1) / TS;
254
255 // One fused load writes every element of the lda x n tile exactly once, pad
256 // rows included -- which is why (P3) may read through its `< m2` guards.
257 for (int c = 0; c < n; ++c) {
258 for (int i = tid; i < lda; i += L) {
259 D v{};
260 if (i < n && i >= c) {
261 v = upper ? sycl_device::dev_conj(Ag[c + static_cast<std::ptrdiff_t>(i) * ldg])
262 : Ag[i + static_cast<std::ptrdiff_t>(c) * ldg];
263 // Diagonal loads as (real(A(c,c)), 0), per LAPACK/cuSOLVER.
264 if (i == c) {
265 v = sycl_device::dev_from_real<D>(sycl_device::dev_real(v));
266 }
267 }
268 S[i + static_cast<std::ptrdiff_t>(c) * lda] = v;
269 }
270 }
271 for (int c = tid; c < NB; c += L) diag[c] = R(0);
272 // off[] is (P3)'s tile-index prefix table, published once at B0 and NEVER
273 // rewritten per panel -- hence (P3)'s dR rescale. off[Rt0] is its sentinel.
274 for (int c = tid; c <= Rt0; c += L) off[c] = c * Rt0 - ((c * (c - 1)) >> 1);
275 if (tid == 0) *fail = 0;
276
277 potrf_phase_barrier<SC>(it); // B0
278
279 bool ok = true;
280 for (int j = 0; j < n; j += NB) {
281 const int ib = (n - j < NB) ? (n - j) : NB;
282 const int m2 = n - j - ib;
283
284 if (ok && p1_active) {
285 potrf_diag_block_subgroup<D, R, NB>(sg, S, lda, j, ib, diag, fail);
286 }
287 potrf_phase_barrier<SC>(it); // B1
288 ok = (*fail == 0);
289
290 if (ok && m2 > 0) {
291 potrf_panel_solve_rows<D, R, NB>(tid, L, S, lda, j, ib, m2, diag);
292 }
293 potrf_phase_barrier<SC>(it); // B2
294
295 if (ok && m2 > 0) {
296 const int Rt = (m2 + TS - 1) / TS;
297 potrf_trailing_tiles<D, R, TS>(tid, L, S, lda, j, ib, m2, Rt, Rt0 - Rt, off);
298 }
299 potrf_phase_barrier<SC>(it); // B3
300 }
301
302 // `i >= c` here means the other triangle is not WRITTEN; the same guard on
303 // the LOAD means it is not READ, which ortho.cc depends on.
304 for (int c = 0; c < n; ++c) {
305 for (int i = tid; i < n; i += L) {
306 if (i >= c) {
307 const D v = S[i + static_cast<std::ptrdiff_t>(c) * lda];
308 if (upper) {
309 Ag[c + static_cast<std::ptrdiff_t>(i) * ldg] = sycl_device::dev_conj(v);
310 } else {
311 Ag[i + static_cast<std::ptrdiff_t>(c) * ldg] = v;
312 }
313 }
314 }
315 }
316}
317
318} // namespace batchlas::potrf_native
void potrf_phase_barrier(const sycl::nd_item< 1 > &it)
void potrf_cta_body(const sycl::nd_item< 1 > &it, const sycl::sub_group &sg, int tid, int L, bool p1_active, D *__restrict S, int lda, R *__restrict diag, int *__restrict fail, int *__restrict off, D *__restrict Ag, int ldg, int n, bool upper)
void potrf_panel_solve_rows(int tid, int L, D *__restrict S, int lda, int j, int ib, int m2, const R *__restrict diag)
void potrf_trailing_tiles(int tid, int L, D *__restrict S, int lda, int j, int ib, int m2, int Rt, int dR, const int *__restrict off)
void potrf_diag_block_subgroup(const sycl::sub_group &sg, D *__restrict S, int lda, int j, int ib, R *__restrict diag, int *__restrict fail)