RDKit
Open-source cheminformatics and machine learning.
Loading...
Searching...
No Matches
BFGSOpt_SVE.h
Go to the documentation of this file.
1#ifndef RDKIT_NUMERICS_OPTIMIZER_BFGSOPT_SVE_H
2#define RDKIT_NUMERICS_OPTIMIZER_BFGSOPT_SVE_H
3
4#if defined(__linux__) && defined(__aarch64__)
5#include <sys/auxv.h>
6#include <asm/hwcap.h>
7#if defined(__has_include)
8#if __has_include(<arm_sve.h>)
9#include <arm_sve.h>
10#define RDK_SVE_AVAILABLE 1
11#endif
12#endif
13#endif
14
15namespace BFGSOpt {
16static bool cpuHasSVE() {
17#if defined(__linux__) && defined(__aarch64__) && defined(RDK_SVE_AVAILABLE)
18 static const bool result = (getauxval(AT_HWCAP) & HWCAP_SVE) != 0;
19 return result;
20#else
21 return false;
22#endif
23}
24
25#ifdef RDK_SVE_AVAILABLE
26
27// ---------------------------------------------------------------------------
28// SVE kernel: initialise search direction xi = -grad and accumulate ||pos||^2
29// ---------------------------------------------------------------------------
30__attribute__((target("+sve"))) static void sveInitXiAndSum(unsigned int dim,
31 const double *grad,
32 double *xi,
33 const double *pos,
34 double *outSum) {
35 svfloat64_t acc = svdup_f64(0.0);
36 uint64_t i = 0;
37 while (i < dim) {
38 svbool_t pg = svwhilelt_b64(i, (uint64_t)dim);
39 svfloat64_t g = svld1_f64(pg, grad + i);
40 svfloat64_t p = svld1_f64(pg, pos + i);
41 // Negate grad in-place during store — avoids a separate negation pass
42 svst1_f64(pg, xi + i, svneg_f64_m(svdup_f64(0.0), pg, g));
43 // Fused multiply-add accumulates p[i]^2 without intermediate stores
44 acc = svmla_f64_m(pg, acc, p, p);
45 i += svcntd();
46 }
47 // Horizontal reduction collapses all vector lanes to a single scalar sum
48 *outSum = svaddv_f64(svptrue_b64(), acc);
49}
50
51// ---------------------------------------------------------------------------
52// SVE kernel: compute hessDGrad = invHessian * dGrad, then accumulate the
53// four dot-product scalars (fac, fae, sumDGrad, sumXi) needed for the BFGS
54// rank-1 update.
55//
56// The inner matrix-vector product uses SVE gather/FMA to handle arbitrary dim
57// without scalar remainder loops. Each outer row is streamed once, keeping
58// cache pressure proportional to dim rather than dim^2. The four scalar
59// accumulators are computed in a single fused pass over the result vector,
60// saving two additional O(dim) traversals compared to separate dot-product
61// calls.
62// ---------------------------------------------------------------------------
63__attribute__((target("+sve"))) static void sveHessianVecMul(
64 unsigned int dim, const double *invHessian, const double *dGrad,
65 double *hessDGrad, const double *xi, double *outFac, double *outFae,
66 double *outSumDGrad, double *outSumXi) {
67 // Phase 1: matrix-vector multiply invHessian * dGrad -> hessDGrad
68 // Fully vectorised over both i and j.
69 for (unsigned int i = 0; i < dim; i++) {
70 const double *row = invHessian + i * dim;
71 svfloat64_t acc = svdup_f64(0.0);
72 uint64_t j = 0;
73 while (j < dim) {
74 svbool_t pg = svwhilelt_b64(j, (uint64_t)dim);
75 acc = svmla_f64_m(pg, acc, svld1_f64(pg, row + j),
76 svld1_f64(pg, dGrad + j));
77 j += svcntd();
78 }
79 hessDGrad[i] = svaddv_f64(svptrue_b64(), acc);
80 }
81
82 svfloat64_t vFac = svdup_f64(0.0), vFae = svdup_f64(0.0);
83 svfloat64_t vSDG = svdup_f64(0.0), vSXi = svdup_f64(0.0);
84 uint64_t i = 0;
85 while (i < dim) {
86 svbool_t pg = svwhilelt_b64(i, (uint64_t)dim);
87 svfloat64_t vdg = svld1_f64(pg, dGrad + i);
88 svfloat64_t vxi = svld1_f64(pg, xi + i);
89 svfloat64_t vhd = svld1_f64(pg, hessDGrad + i);
90
91 vFac = svmla_f64_m(pg, vFac, vdg, vxi);
92 vFae = svmla_f64_m(pg, vFae, vdg, vhd);
93 vSDG = svmla_f64_m(pg, vSDG, vdg, vdg);
94 vSXi = svmla_f64_m(pg, vSXi, vxi, vxi);
95 i += svcntd();
96 }
97 *outFac = svaddv_f64(svptrue_b64(), vFac);
98 *outFae = svaddv_f64(svptrue_b64(), vFae);
99 *outSumDGrad = svaddv_f64(svptrue_b64(), vSDG);
100 *outSumXi = svaddv_f64(svptrue_b64(), vSXi);
101}
102
103// ---------------------------------------------------------------------------
104// SVE kernel: symmetric rank-1 update of the inverse Hessian approximation.
105//
106// The BFGS update formula adds three outer-product terms to invHessian.
107// Exploiting symmetry (only the upper triangle is computed; the lower is
108// mirrored afterwards) halves the number of FLOPs and memory writes versus a
109// naive full-matrix update. The SVE FMA instructions (svmla / svmls) fuse
110// multiply and add into a single pipeline stage, reducing instruction count
111// and register pressure compared to separate multiply + add sequences.
112// ---------------------------------------------------------------------------
113__attribute__((target("+sve"))) static void sveHessianRank1Update(
114 unsigned int dim, double *invHessian, const double *xi,
115 const double *hessDGrad, const double *dGrad, double fac, double fad,
116 double fae) {
117 for (unsigned int i = 0; i < dim; i++) {
118 // Broadcast scalar multipliers once per row to avoid redundant computation
119 svfloat64_t vpxi = svdup_f64(fac * xi[i]);
120 svfloat64_t vhdgi = svdup_f64(fad * hessDGrad[i]);
121 svfloat64_t vdgi = svdup_f64(fae * dGrad[i]);
122 double *row = invHessian + i * dim;
123 // Start from column i to process only the upper triangle
124 uint64_t j = i;
125 while (j < dim) {
126 svbool_t pg = svwhilelt_b64(j, (uint64_t)dim);
127 svfloat64_t vxj = svld1_f64(pg, xi + j);
128 svfloat64_t vhdgj = svld1_f64(pg, hessDGrad + j);
129 svfloat64_t vdgj = svld1_f64(pg, dGrad + j);
130 svfloat64_t vh = svld1_f64(pg, row + j);
131 // Three fused multiply-adds apply all three BFGS update terms at once
132 vh = svmla_f64_m(pg, vh, vpxi, vxj);
133 vh = svmls_f64_m(pg, vh, vhdgi, vhdgj);
134 vh = svmla_f64_m(pg, vh, vdgi, vdgj);
135 svst1_f64(pg, row + j, vh);
136 j += svcntd();
137 }
138 // Mirror upper triangle to lower triangle to maintain symmetry;
139 // scalar loop is cheap (dim - i iterations) relative to the SVE inner loop
140 for (unsigned int j2 = i + 1; j2 < dim; j2++) {
141 invHessian[j2 * dim + i] = invHessian[i * dim + j2];
142 }
143 }
144}
145
146// ---------------------------------------------------------------------------
147// SVE kernel: compute new search direction xi = -(invHessian * grad)
148//
149// SVE implementation accelerates the matrix-vector multiply using vectorized
150// FMA across columns, then negates the accumulated dot product at the scalar
151// level (single negation per row) rather than applying a separate vector
152// negation pass. This keeps the loop body to one SVE FMA instruction
153// per iteration, maximising throughput on in-order SVE pipelines.
154// ---------------------------------------------------------------------------
155__attribute__((target("+sve"))) static void sveHessianVecMulNeg(
156 unsigned int dim, const double *invHessian, const double *grad,
157 double *xi) {
158 for (unsigned int i = 0; i < dim; i++) {
159 const double *row = invHessian + i * dim;
160 svfloat64_t acc = svdup_f64(0.0);
161 uint64_t j = 0;
162 while (j < dim) {
163 svbool_t pg = svwhilelt_b64(j, (uint64_t)dim);
164 svfloat64_t h = svld1_f64(pg, row + j);
165 svfloat64_t g = svld1_f64(pg, grad + j);
166 acc = svmla_f64_m(pg, acc, h, g);
167 j += svcntd();
168 }
169 // Negate the scalar result once per row rather than vectorising the
170 // negation, keeping the store path simple and avoiding an extra SVE pass
171 xi[i] = -svaddv_f64(svptrue_b64(), acc);
172 }
173}
174
175#endif
176
177} // namespace BFGSOpt
178
179#endif // RDKIT_NUMERICS_OPTIMIZER_BFGSOPT_SVE_H
static bool cpuHasSVE()
Definition BFGSOpt_SVE.h:16