getrf_cta_device.hh
Go to the documentation of this file.
1#pragma once
2
3// All of the native batched GETRF panel's device code. FOUR spellings here are wrong
4// answers or launch failures if changed -- the butterfly pivot search, the cabs1 pivot
5// metric, info's exact-zero/1-based/first-failure rule, and the top-of-loop barriers.
6// evidence: docs/perf/lu.md#the-shape-of-the-getrf-device-code
7
8#include <src/sycl/device_scalar.hh>
9
10#include <sycl/sycl.hpp>
11
12#include <cstddef>
13#include <cstdint>
14
16
17using batchlas::sycl_device::Cx;
18
19template <typename D> struct RealOf { using type = D; };
20template <typename R> struct RealOf<Cx<R>> { using type = R; };
21template <typename D> using real_of = typename RealOf<D>::type;
22
23template <typename D>
24inline D lu_zero() {
25 if constexpr (batchlas::sycl_device::dev_is_complex_v<D>) {
26 return D{real_of<D>(0), real_of<D>(0)};
27 } else {
28 return D(0);
29 }
30}
31
32// LAPACK's cabs1: |Re| + |Im|; deliberately not the modulus (see header).
33template <typename D>
34inline real_of<D> lu_cabs1(D a) {
35 if constexpr (batchlas::sycl_device::dev_is_complex_v<D>) {
36 return sycl::fabs(a.re) + sycl::fabs(a.im);
37 } else {
38 return sycl::fabs(a);
39 }
40}
41
42template <typename D>
44 D* p;
45 int ld;
46 D& at(int r, int c) const {
47 return p[static_cast<std::ptrdiff_t>(r) + static_cast<std::ptrdiff_t>(c) * ld];
48 }
49};
50
51// Own base per matrix (G share one accessor), and the launcher pads ld ODD against a
52// bank conflict. evidence: docs/perf/lu.md#the-shape-of-the-getrf-device-code
53template <typename D> using LuRawTile = LuGlobalTile<D>;
54
55// CONSTANT, not a function of wg width: the capacity query, the fit predicate and the
56// launcher must agree on the SLM footprint.
57inline constexpr int kLuRedSlots = 32;
58
59// Which group the panel's phase barriers synchronise. SubGroup is what makes G-packing
60// legal: the G sub-groups sharing a work-group then never synchronise with each other.
61// Choosing WorkGroup for a packed launch is a RACE by construction -- wrong answers, not
62// a launch failure -- so the launcher derives the two together and asserts the pairing.
63enum class LuScope { SubGroup, WorkGroup };
64
65// LAPACK ?GETF2 on an m x n tile, in place, for `kmax` (not min(m, n)) steps.
66// `piv_base` is the panel's first GLOBAL row index, so ipiv and info come out global
67// and 1-based with no fix-up pass. `info_item` is READ as well as written
68// (first-failure-wins across panels), so both launchers zero it beforehand.
69template <typename D, LuScope SC, typename Tile, typename ValAcc, typename IdxAcc>
70inline void getf2_panel_device(sycl::nd_item<1> it, Tile A, int m, int n, int kmax,
71 int* piv_item, int piv_base, int32_t* info_item,
72 ValAcc rval, IdxAcc ridx) {
73 using R = real_of<D>;
74
75 const auto g = it.get_group();
76 const auto sg = it.get_sub_group();
77 const int lane = static_cast<int>(sg.get_local_linear_id());
78 const int nlanes = static_cast<int>(sg.get_local_linear_range());
79 const int team = static_cast<int>(sg.get_group_linear_id());
80 const int nteams = static_cast<int>(sg.get_group_linear_range());
81
82 // Under SubGroup scope the work-group holds several independent matrices, so the
83 // item's own lane index and the sub-group width -- not the work-group's -- are what
84 // stride this matrix's rows.
85 const int wg = (SC == LuScope::SubGroup) ? nlanes
86 : static_cast<int>(it.get_local_range(0));
87 const int tid = (SC == LuScope::SubGroup) ? lane
88 : static_cast<int>(it.get_local_linear_id());
89
90 // The phase barriers, at the scope this instantiation owns.
91 const auto phase_barrier = [&]() {
92 if constexpr (SC == LuScope::SubGroup) {
93 sycl::group_barrier(sg);
94 } else {
95 sycl::group_barrier(g);
96 }
97 };
98
99 int32_t info_local = (tid == 0) ? *info_item : 0;
100
101 for (int k = 0; k < kmax; ++k) {
102 R bv = R(-1);
103 int bi = m; // "no candidate" -- never wins a tie
104 for (int i = k + tid; i < m; i += wg) {
105 const R v = lu_cabs1<D>(A.at(i, k));
106 if (v > bv) { bv = v; bi = i; }
107 }
108
109 for (uint32_t off = static_cast<uint32_t>(nlanes) / 2; off > 0; off >>= 1) {
110 const R ov = sycl::permute_group_by_xor(sg, bv, off);
111 const int oi = sycl::permute_group_by_xor(sg, bi, off);
112 if (ov > bv || (ov == bv && oi < bi)) { bv = ov; bi = oi; }
113 }
114 R fv;
115 int p;
116 if constexpr (SC == LuScope::SubGroup) {
117 // The butterfly above already spans every lane that scanned this column, so
118 // there is nothing to combine and no B1: the SLM slots and the cross-team
119 // scan exist only to join several sub-groups working on ONE matrix.
120 fv = bv;
121 p = bi;
122 static_cast<void>(rval);
123 static_cast<void>(ridx);
124 } else {
125 if (lane == 0) {
126 rval[static_cast<std::size_t>(team)] = bv;
127 ridx[static_cast<std::size_t>(team)] = bi;
128 }
129 sycl::group_barrier(g); // B1
130
131 fv = R(-1);
132 p = m;
133 for (int t = 0; t < nteams; ++t) {
134 const R v = rval[static_cast<std::size_t>(t)];
135 const int ii = ridx[static_cast<std::size_t>(t)];
136 if (v > fv || (v == fv && ii < p)) { fv = v; p = ii; }
137 }
138 }
139 static_cast<void>(fv);
140 if (p >= m) p = k; // unreachable: k < m, so column k always has a row
141
142 if (tid == 0) {
143 piv_item[k] = piv_base + p + 1; // GLOBAL, 1-BASED, LAPACK ipiv
144 }
145
146 // Across ALL n columns, including the finished L to the left of k:
147 // exchanging only columns >= k is the classic silently-wrong LU.
148 if (p != k) {
149 for (int c = tid; c < n; c += wg) {
150 D& x = A.at(k, c);
151 D& y = A.at(p, c);
152 const D t = x;
153 x = y;
154 y = t;
155 }
156 }
157 phase_barrier(); // B2
158
159 const D d = A.at(k, k);
160 if (batchlas::sycl_device::dev_is_zero(d)) {
161 // EXACT zero, no epsilon: the column is left alone, keeping the item finite.
162 if (tid == 0 && info_local == 0) {
163 info_local = static_cast<int32_t>(piv_base + k + 1);
164 }
165 } else {
166 const D r = batchlas::sycl_device::dev_recip(d);
167 const bool use_mul = batchlas::sycl_device::dev_isfinite(r) &&
168 !batchlas::sycl_device::dev_is_zero(r);
169 if (use_mul) {
170 for (int i = k + 1 + tid; i < m; i += wg) {
171 A.at(i, k) = batchlas::sycl_device::dev_mul(A.at(i, k), r);
172 }
173 } else {
174 // ?GETF2's small-pivot arm: the reciprocal overflowed, so divide.
175 for (int i = k + 1 + tid; i < m; i += wg) {
176 A.at(i, k) = batchlas::sycl_device::dev_div(A.at(i, k), d);
177 }
178 }
179 }
180 phase_barrier(); // B3
181
182 // Flattened so the work-group saturates when one extent is short; the two
183 // runtime divisions beat a power-of-two split (docs/perf/lu.md#lu-negative-results).
184 const int mm = m - k - 1;
185 const int nn = n - k - 1;
186 if (mm > 0 && nn > 0) {
187 const int total = mm * nn;
188 for (int e = tid; e < total; e += wg) {
189 const int i = k + 1 + (e % mm);
190 const int j = k + 1 + (e / mm);
191 A.at(i, j) = batchlas::sycl_device::dev_sub(
192 A.at(i, j),
193 batchlas::sycl_device::dev_mul(A.at(i, k), A.at(k, j)));
194 }
195 }
196 phase_barrier(); // B4
197 }
198
199 if (tid == 0) {
200 *info_item = info_local;
201 }
202}
203
204} // namespace batchlas::getrf_native
void getf2_panel_device(sycl::nd_item< 1 > it, Tile A, int m, int n, int kmax, int *piv_item, int piv_base, int32_t *info_item, ValAcc rval, IdxAcc ridx)
LuGlobalTile< D > LuRawTile
typename RealOf< D >::type real_of
real_of< D > lu_cabs1(D a)