getrf_panel_reg_device.hh
Go to the documentation of this file.
1#pragma once
2
3// The register-resident GETRF panel leaf: one work-group per m x ncols panel, item `tid`
4// owning row `tid` of a compile-time `D rA[NB]`, pivoting by LAZY RELABEL. Same contract
5// as getf2_panel_device. THREE SPELLINGS ARE SILENT WRONG ANSWERS OR SILENT SPILLS: the
6// `j` loop's COMPILE-TIME bound with `continue` (never `break`); publishing the pivot row
7// AFTER the relabel, by the item whose `act` is therefore false; and selecting a complex
8// scalar by if/else and never `?:`. evidence: docs/perf/lu.md#the-register-panel-leaf
9
11
12#include <src/sycl/device_scalar.hh>
13#include <src/sycl/kernel_attrs.hh>
14
15#include <sycl/sycl.hpp>
16
17#include <cstddef>
18#include <cstdint>
19
20namespace batchlas::getrf_native {
21
22// `piv_item` carries the panel's piv_base; `kmax` = min(m, ncols) must be KERNEL-UNIFORM,
23// because the `continue` guards below skip work-group barriers. `always_inline` is NOT a
24// hint: without it the ABI call passes the accessors through a 200-byte stack depot.
25// evidence: docs/perf/lu.md#the-register-panel-leaf-register-probe
26template <typename D, int NB, typename SxAcc, typename ValAcc, typename IdxAcc>
27[[gnu::always_inline]] inline void getf2_panel_reg_device(sycl::nd_item<1> it,
28 D* a, int ld,
29 int m, int ncols, int kmax,
30 int* piv_item, int piv_base,
31 int32_t* info_item,
32 SxAcc sx, ValAcc rval,
33 IdxAcc ridx) {
34 using R = real_of<D>;
35 namespace sd = ::batchlas::sycl_device;
36
37 const auto g = it.get_group();
38 const auto sg = it.get_sub_group();
39 const int tid = static_cast<int>(it.get_local_linear_id());
40 const int lane = static_cast<int>(sg.get_local_linear_id());
41 const int nlanes = static_cast<int>(sg.get_local_linear_range());
42 const int team = static_cast<int>(sg.get_group_linear_id());
43 const int nteams = static_cast<int>(sg.get_group_linear_range());
44
45 // CLAMP, never return: a pad item is still in every shuffle mask and every barrier.
46 const bool live = (tid < m);
47 const std::ptrdiff_t ldp = static_cast<std::ptrdiff_t>(ld);
48
49 D rA[NB]; // top level, never a parameter: tiny_device.hh invariant 1
50BATCHLAS_UNROLL_FULL
51 for (int c = 0; c < NB; ++c) {
52 D v = lu_zero<D>(); // a STATEMENT, not `?:`: see spelling 3
53 if (live && c < ncols) v = a[tid + static_cast<std::ptrdiff_t>(c) * ldp];
54 rA[c] = v;
55 }
56
57 int rowid = live ? tid : -1; // -1: never a candidate, never == j, never > j
58 int32_t info_local = (tid == 0) ? *info_item : 0;
59
60BATCHLAS_UNROLL_FULL
61 for (int j = 0; j < NB; ++j) {
62 if (j >= kmax) continue; // see spelling 1 at the top of this file
63
64 // --- 1. argmax over logical rows >= j; a NaN magnitude is MAPPED to -1 or lanes
65 // disagree about the winner. evidence: docs/perf/lu.md#pad-rows-and-the-argmax-corrected
66 const R mag = lu_cabs1<D>(rA[j]);
67 const bool cand = (rowid >= j);
68 R bv = (cand && (mag == mag)) ? mag : R(-1);
69 int bi = cand ? rowid : m; // `m` loses every tie to a real row
70 for (uint32_t off = static_cast<uint32_t>(nlanes) / 2u; off > 0u; off >>= 1) {
71 const R ov = sycl::permute_group_by_xor(sg, bv, off);
72 const int oi = sycl::permute_group_by_xor(sg, bi, off);
73 if (ov > bv || (ov == bv && oi < bi)) { bv = ov; bi = oi; }
74 }
75 if (lane == 0) {
76 rval[static_cast<std::size_t>(team)] = bv;
77 ridx[static_cast<std::size_t>(team)] = bi;
78 }
79 sycl::group_barrier(g); // B1
80
81 R fv = R(-1);
82 int p = m;
83 for (int t = 0; t < nteams; ++t) {
84 const R v = rval[static_cast<std::size_t>(t)];
85 const int ii = ridx[static_cast<std::size_t>(t)];
86 if (v > fv || (v == fv && ii < p)) { fv = v; p = ii; }
87 }
88 static_cast<void>(fv);
89 if (p >= m) p = j; // unreachable: j < kmax <= m, so logical row j exists
90
91 if (tid == 0) piv_item[j] = piv_base + p + 1; // GLOBAL, 1-BASED, LAPACK ipiv
92
93 // --- 2. the lazy swap: two labels change, no register moves work-item.
94 if (rowid == p) rowid = j;
95 else if (rowid == j) rowid = p;
96
97 // --- 3. publish the pivot row; exactly one item now carries rowid == j.
98 if (rowid == j) {
99BATCHLAS_UNROLL_FULL
100 for (int k = j; k < NB; ++k) sx[static_cast<std::size_t>(k)] = rA[k];
101 }
102 sycl::group_barrier(g); // B2
103
104 const D d = sx[static_cast<std::size_t>(j)];
105 const bool zero = sd::dev_is_zero(d); // EXACT zero, no epsilon
106 if (zero && tid == 0 && info_local == 0) {
107 info_local = static_cast<int32_t>(piv_base + j + 1);
108 }
109 const D rc = sd::dev_recip(d);
110 const bool use_mul = !zero && sd::dev_isfinite(rc) && !sd::dev_is_zero(rc);
111 const bool act = (rowid > j);
112 if (act) {
113 // --- 4. scale. An if/else STATEMENT, never `?:` -- see spelling 3.
114 if (use_mul) rA[j] = sd::dev_mul(rA[j], rc);
115 else if (!zero) rA[j] = sd::dev_div(rA[j], d); // ?GETF2's sfmin arm
116 }
117
118 // `k = j + 1` is a COMPILE-TIME lower bound once j is unrolled.
119 // evidence: docs/perf/lu.md#why-the-rank-1-update-starts-at-k--j--1
120BATCHLAS_UNROLL_FULL
121 for (int k = j + 1; k < NB; ++k) {
122 if (k >= ncols) continue;
123 const D u = sx[static_cast<std::size_t>(k)];
124 // Unguarded by `zero`: a zero pivot WAS the argmax, so every multiplier is 0.
125 if (act) rA[k] = sd::dev_sub(rA[k], sd::dev_mul(rA[j], u));
126 }
127 // No third barrier. evidence: docs/perf/lu.md#two-barriers-per-column
128 }
129
130 // --- 5. store to the RELABELLED row: rowid is where this data belongs in P A.
131 if (live) {
132BATCHLAS_UNROLL_FULL
133 for (int k = 0; k < NB; ++k) {
134 if (k >= ncols) continue;
135 a[rowid + static_cast<std::ptrdiff_t>(k) * ldp] = rA[k];
136 }
137 }
138 if (tid == 0) *info_item = info_local;
139}
140
141} // namespace batchlas::getrf_native
typename RealOf< D >::type real_of
real_of< D > lu_cabs1(D a)
void getf2_panel_reg_device(sycl::nd_item< 1 > it, D *a, int ld, int m, int ncols, int kmax, int *piv_item, int piv_base, int32_t *info_item, SxAcc sx, ValAcc rval, IdxAcc ridx)