geqrf_panel_reg_device.hh
Go to the documentation of this file.
1#pragma once
2
3// The REGISTER panel leaf: one panel ROW per work-item, the m x N panel in registers
4// between one coalesced load and one store. LarfgScalars/geqrf_larfg_scalars are reused VERBATIM,
5// because tau's real-beta convention is a contract ormqr/orgqr/ormbr/sy2sb read.
6// evidence: docs/perf/qr.md#the-register-panel-leaf-wp6--p5
7
9
10#include <src/sycl/device_scalar.hh>
11#include <src/util/resident_capacity.hh>
12
13#include <sycl/sycl.hpp>
14
15#include <cstddef>
16#include <cstdint>
17
18namespace batchlas::geqrf_native {
19
20// THE ONE source of this leaf's width and register cost. cols 0 = ABSENT; a non-zero cols MUST
21// equal geqrf_nb_for_type<T>(), which geqrf_blocked.cc static_asserts.
22// evidence: docs/perf/qr.md#the-register-panel-leaf-width-and-height-table
23template <typename D> struct GeqrfPanelRegPlan {
24 static constexpr int cols = 0;
25 static constexpr int probed_cols = 0;
26 static constexpr int probed_regs = 0;
27 static constexpr int launch_max_rows = 0;
28};
29template <> struct GeqrfPanelRegPlan<float> {
30 static constexpr int cols = 0; // 32 costs 39.6 s of ptxas; set it to 32 to re-enable
31 static constexpr int probed_cols = 32;
32 static constexpr int probed_regs = 168;
33 static constexpr int launch_max_rows = 0; // must be MEASURED before cols goes non-zero
34};
35template <> struct GeqrfPanelRegPlan<double> {
36 static constexpr int cols = 16;
37 static constexpr int probed_cols = 16;
38 static constexpr int probed_regs = 136;
39 // MEASURED on the SHIPPED library, not what probed_regs predicts. evidence: below.
40 static constexpr int launch_max_rows = 384;
41};
42template <> struct GeqrfPanelRegPlan<sycl_device::Cx<float>> {
43 static constexpr int cols = 0; // 32 costs 46.0 s of ptxas
44 static constexpr int probed_cols = 32;
45 static constexpr int probed_regs = 180;
46 static constexpr int launch_max_rows = 0;
47};
48template <> struct GeqrfPanelRegPlan<sycl_device::Cx<double>> {
49 // 60.5 s of ptxas, and clamped at the 255-register ceiling: one block per SM.
50 static constexpr int cols = 0;
51 static constexpr int probed_cols = 32;
52 static constexpr int probed_regs = 255;
53 static constexpr int launch_max_rows = 0;
54};
55
56// A MODEL, for cells with no probe row only -- measured wrong in BOTH directions here.
57// evidence: docs/perf/qr.md#the-register-panel-leaf-width-and-height-table
58inline constexpr int kGeqrfPanelRegOverhead = 96;
59inline constexpr int kGeqrfPanelRegMargin = 16;
60
61// A row probed at a width the plan no longer uses reads as measured but describes another kernel.
62template <typename D>
67
68constexpr int geqrf_panel_reg_words(std::size_t scalar_bytes) {
69 return static_cast<int>(scalar_bytes / sizeof(float));
70}
71
72// A PROBED row needs no margin; the margin is the model's, because the model is an estimate.
73template <typename D>
74constexpr int geqrf_panel_reg_regs() {
75 constexpr int probed = GeqrfPanelRegPlan<D>::probed_regs;
76 if constexpr (probed > 0) {
77 return probed;
78 } else {
81 }
82}
83
84inline constexpr int kGeqrfPanelRegMaxWg = 1024;
85
86// THE HARD GATE as a HEIGHT: warps overflowing a register SUB-PARTITION abort the launch. THE
87// ARITHMETIC DOES NOT DECIDE IT -- sm89_max_work_group divides a PROBED count, optimistic against
88// what the library allocates, so the MEASURED launch_max_rows stays in the min() and wins.
89// evidence: docs/perf/qr.md#the-height-ceiling-the-aot-probe-got-wrong
90template <typename D>
91constexpr int geqrf_panel_reg_max_rows() {
92 if constexpr (GeqrfPanelRegPlan<D>::cols < 1) {
93 return 0;
94 } else {
96 "a scalar with a register panel leaf must carry a MEASURED height ceiling");
97 const int by_regs = resident::sm89_max_work_group(geqrf_panel_reg_regs<D>());
98 const int capped = (by_regs < kGeqrfPanelRegMaxWg) ? by_regs : kGeqrfPanelRegMaxWg;
99 const int measured = GeqrfPanelRegPlan<D>::launch_max_rows;
100 const int lo = (measured < capped) ? measured : capped;
101 return (lo / 32) * 32;
102 }
103}
104
105// THE POLICY HEIGHT, a different fact from the hard ceiling: the tallest panel at which this
106// leaf is the right CHOICE. evidence: docs/perf/qr.md#the-panel-height-window
107inline constexpr int kGeqrfPanelRegPolicyRows = 128;
108
109constexpr int geqrf_panel_reg_wg(int m) {
110 const int w = ((m + 31) / 32) * 32;
111 return (w < 32) ? 32 : w;
112}
113
114// Two real slots and N scalar slots per sub-group, plus alpha. The reductions are butterflies and
115// NEVER reduce_over_group, whose static shared opens the hole. evidence: docs/perf/qr.md#the-48-kib-launch-hole
116constexpr std::size_t geqrf_panel_reg_real_slots(int wg) {
117 return 2u * static_cast<std::size_t>(wg / 32);
118}
119constexpr std::size_t geqrf_panel_reg_scalar_slots(int wg, int cols) {
120 return 1u + static_cast<std::size_t>(wg / 32) * static_cast<std::size_t>(cols);
121}
122
123// LAPACK ?GEQR2 on an m x n panel with n <= N, in place, one row per work-item. CONTRACT the
124// launcher holds: m <= work-group size, n <= N, kmax <= min(m, n), all launch-uniform. Rows at or
125// above m carry zeros and suppress their store but still execute every barrier -- an early return
126// is not work-group-uniform. Three barriers per column, none double-buffered: column j's writes
127// all follow column j-1's last barrier.
128template <typename D, int N, typename Real>
129inline void geqr2_panel_reg_device(sycl::nd_item<1> it, D* a, int ld, int m, int n, int kmax,
130 D* tau_ptr, Real* sred, D* sw) {
131 using R = real_of<D>;
132 static_assert(sizeof(R) == sizeof(Real), "the real slot array must match the scalar's real");
133
134 const auto g = it.get_group();
135 const auto sg = it.get_sub_group();
136 const int r = static_cast<int>(it.get_local_linear_id());
137 const int lane = static_cast<int>(sg.get_local_linear_id());
138 const int sgid = static_cast<int>(sg.get_group_linear_id());
139 const int nsg = static_cast<int>(sg.get_group_linear_range());
140 const bool live = (r < m);
141
142 // unroll(full) on EVERY loop whose index reaches rA, or rA[j] is a DYNAMIC index and the
143 // array leaves the register file. evidence: docs/perf/qr.md#the-stack-frame-is-the-gate-for-this-kernel-not-the-spill-counter
144 D rA[N];
145#pragma clang loop unroll(full)
146 for (int k = 0; k < N; ++k) {
147 rA[k] = (live && k < n) ? a[static_cast<std::ptrdiff_t>(r) +
148 static_cast<std::ptrdiff_t>(k) * ld]
149 : dev_zero<D>();
150 }
151
152#pragma clang loop unroll(full)
153 for (int j = 0; j < N; ++j) {
154 if (j >= kmax) continue; // launch-uniform: cannot desync the barriers below
155
156 R part = (live && r >= j) ? dev_absmax<D>(rA[j]) : R(0);
157 part = geqrf_sg_max<R>(sg, part);
158 if (lane == 0) sred[sgid] = part;
159 if (r == j) sw[0] = rA[j];
160 sycl::group_barrier(g); // B1
161
162 // Same slots in the same order on every item, so h below is work-group-uniform.
163 R smax = sred[0];
164 for (int i = 1; i < nsg; ++i) smax = sycl::fmax(smax, sred[i]);
165 const D alpha = sw[0];
166
167 R ssq_part = R(0);
168 if (live && r > j && smax > R(0)) ssq_part = dev_abs2_scaled<D>(rA[j], smax);
169 ssq_part = geqrf_sg_sum<R>(sg, ssq_part);
170 if (lane == 0) sred[nsg + sgid] = ssq_part;
171 sycl::group_barrier(g); // B2
172
173 R ssq = R(0);
174 for (int i = 0; i < nsg; ++i) ssq += sred[nsg + i];
175
176 const LarfgScalars<D> h = geqrf_larfg_scalars<D>(alpha, smax, ssq);
177
178 // NO `if (h.identity) continue;`: inert arithmetically, and omitting it keeps the
179 // barrier sequence uniform. tau is a defined output either way.
180 if (r == j) {
181 rA[j] = h.beta;
182 tau_ptr[j] = h.tau;
183 } else if (live && r > j) {
184 rA[j] = h.use_mul ? sycl_device::dev_mul(rA[j], h.vfactor)
185 : sycl_device::dev_div(rA[j], h.vfactor);
186 }
187
188 // One select: zeroes eliminated and dead rows, supplies the implicit v(j) = 1. Dropping
189 // the `r < j` term lets an eliminated row re-enter the reflector.
190 const D v = (!live || r < j)
191 ? dev_zero<D>()
192 : ((r == j) ? sycl_device::dev_one<D>() : rA[j]);
193 const D cv = sycl_device::dev_conj(v);
194 // CONJ(tau), zgeqr2's convention. evidence: docs/perf/qr.md#a-residual-test-cannot-guard-a-convention
195 const D ctau = sycl_device::dev_conj(h.tau);
196
197#pragma clang loop unroll(full)
198 for (int k = 0; k < N; ++k) {
199 if (k <= j || k >= n) continue; // uniform, or lanes drop out of the butterfly
200 const D pw = geqrf_sg_sum<D>(sg, sycl_device::dev_mul(cv, rA[k]));
201 if (lane == 0) sw[1 + sgid * N + k] = pw;
202 }
203 sycl::group_barrier(g); // B3
204
205#pragma clang loop unroll(full)
206 for (int k = 0; k < N; ++k) {
207 if (k <= j || k >= n) continue;
208 D w = dev_zero<D>();
209 for (int i = 0; i < nsg; ++i) w = dev_add<D>(w, sw[1 + i * N + k]);
210 rA[k] = sycl_device::dev_sub(
211 rA[k], sycl_device::dev_mul(sycl_device::dev_mul(ctau, w), v));
212 }
213 }
214
215#pragma clang loop unroll(full)
216 for (int k = 0; k < N; ++k) {
217 if (live && k < n) {
218 a[static_cast<std::ptrdiff_t>(r) + static_cast<std::ptrdiff_t>(k) * ld] = rA[k];
219 }
220 }
221}
222
223} // namespace batchlas::geqrf_native
constexpr int geqrf_panel_reg_max_rows()
void geqr2_panel_reg_device(sycl::nd_item< 1 > it, D *a, int ld, int m, int n, int kmax, D *tau_ptr, Real *sred, D *sw)
LarfgScalars< D > geqrf_larfg_scalars(D alpha, real_of< D > s, real_of< D > ssq)
D geqrf_sg_sum(const sycl::sub_group &sg, D v)
constexpr int geqrf_panel_reg_words(std::size_t scalar_bytes)
R geqrf_sg_max(const sycl::sub_group &sg, R v)
real_of< D > dev_absmax(D a)
constexpr std::size_t geqrf_panel_reg_scalar_slots(int wg, int cols)
constexpr bool geqrf_panel_reg_row_is_current()
constexpr std::size_t geqrf_panel_reg_real_slots(int wg)
constexpr int geqrf_panel_reg_wg(int m)
typename RealOf< D >::type real_of
real_of< D > dev_abs2_scaled(D a, real_of< D > s)