Repository navigation
Expand file tree
/
Copy pathOpenBlasBackend.cs
More file actions
206 lines (164 loc) · 10.7 KB
/
Copy pathOpenBlasBackend.cs
File metadata and controls
206 lines (164 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
using System;
using NumSharp;
using NumSharp.Backends;
namespace NumSharp.Interop.OpenBLAS
{
/// <summary>
/// The <see cref="IBlasBackend"/> this package installs: NumSharp's matrix products computed
/// by an external CBLAS library, through a route-for-route port of NumPy's two
/// matrix-product dispatchers.
/// </summary>
/// <remarks>
/// <para>
/// It answers for <c>float32</c>, <c>float64</c> and <c>complex128</c> (the dtypes NumPy itself
/// routes through cblas), and returns false for everything else so the engine keeps computing
/// it with its own managed kernels — integer and bool products do not need a BLAS anyway
/// (modular integer addition is associative, so summation order cannot change the result;
/// complex float accumulation is NOT associative and genuinely needs <c>zgemm</c>/<c>zdotu</c>
/// for byte-parity). complex128 is served only when the loaded library exports the complex
/// products; a bare real-only CBLAS declines it back to the managed complex kernel.
/// </para>
/// <para>
/// <b>Why the two entry points are ported separately.</b> <c>np.dot</c> and <c>np.matmul</c>
/// are not the same C code in NumPy: <c>cblas_matrixproduct</c> (+ the N-D <c>dotfunc</c>
/// tail) versus the <c>@TYPE@_matmul</c> gufunc. They agree bit-for-bit on nearly every
/// input, but pick different routes when an operand is not blasable — a stride-2 matrix
/// times a vector gets gemv-on-a-copy from one and the portable loop from the other, and
/// 278/300 elements differ. A backend claiming bit-parity must reproduce that split, which
/// is why <see cref="IBlasBackend"/> has both <see cref="TryDot"/> and
/// <see cref="TryMatMul2D"/> rather than one matrix-product method.
/// </para>
/// </remarks>
public sealed class OpenBlasBackend : IBlasBackend, ISlidingDotBackend
{
/// <inheritdoc/>
public string Info
{
get
{
if (!OpenBlasNative.IsLoaded)
return "<no CBLAS library loaded>";
var config = OpenBlasNative.GetConfig();
return $"{OpenBlasNative.LibraryPath} [symbols {OpenBlasNative.SymbolScheme}, " +
$"{(OpenBlasNative.IsIlp64 ? "ILP64" : "LP64")}, threads {OpenBlasNative.GetNumThreads()}]" +
(config == null ? string.Empty : " " + config);
}
}
/// <inheritdoc/>
public bool TryDot(NDArray left, NDArray right, out NDArray result)
=> OpenBlasEngine.TryDot(left, right, out result);
/// <inheritdoc/>
public bool TryMatMul2D(NDArray left, NDArray right, NDArray result)
=> OpenBlasEngine.TryMatmul2D(left, right, result);
/// <inheritdoc/>
public bool TryMatMulBatched(NDArray left, NDArray right, NDArray result)
=> OpenBlasEngine.TryMatmulBatched(left, right, result);
#region Products (CBLAS inner / vdot / vecdot / matvec / vecmat)
// The rest of what NumPy sends to cblas beyond dot/matmul. Each is a route-for-route port of
// NumPy 2.4.2's own dispatcher (cblasfuncs.c / matmul.c.src / vdot.c), so where the managed
// composition reassociates the sum differently — vecdot's Multiply+ReduceAdd, complex vdot's
// conj-then-dotu, complex vecmat's conj-then-gemv — this computes the byte-identical answer.
// They answer for float32/float64/complex128 (complex128 only when the complex products are
// loaded) and decline every other dtype, keeping the managed composition as the fallback.
/// <inheritdoc/>
public bool TryInner(NDArray a, NDArray b, out NDArray result)
=> OpenBlasEngine.TryInner(a, b, out result);
/// <inheritdoc/>
public bool TryVdot(NDArray a, NDArray b, out NDArray result)
=> OpenBlasEngine.TryVdot(a, b, out result);
/// <inheritdoc/>
public bool TryVecdot(NDArray x1, NDArray x2, out NDArray result)
=> OpenBlasEngine.TryVecdot(x1, x2, out result);
/// <inheritdoc/>
public bool TryMatvec(NDArray x1, NDArray x2, out NDArray result)
=> OpenBlasEngine.TryMatvec(x1, x2, out result);
/// <inheritdoc/>
public bool TryVecmat(NDArray x1, NDArray x2, out NDArray result)
=> OpenBlasEngine.TryVecmat(x1, x2, out result);
#endregion
#region LU factorisations (LAPACK getrf / gesv)
// These answer only when the loaded library also exports LAPACK (a full OpenBLAS does; a bare
// reference CBLAS does not). A miss returns false and the engine raises its "needs a LAPACK
// backend" — the same outcome as no backend at all. float32 operands are computed in double
// and cast back, exactly as NumPy's linalg does; every other dtype falls through.
/// <inheritdoc/>
public bool TryInv(NDArray a, out NDArray result)
=> OpenBlasEngine.TryInv(a, out result);
/// <inheritdoc/>
public bool TrySolve(NDArray a, NDArray b, bool oneDimensionalRhs, out NDArray result)
=> OpenBlasEngine.TrySolve(a, b, oneDimensionalRhs, out result);
/// <inheritdoc/>
public bool TryDet(NDArray a, out NDArray result)
=> OpenBlasEngine.TryDet(a, out result);
/// <inheritdoc/>
public bool TrySlogdet(NDArray a, out NDArray sign, out NDArray logabsdet)
=> OpenBlasEngine.TrySlogdet(a, out sign, out logabsdet);
#endregion
#region Cholesky / QR factorisations (LAPACK potrf / geqrf / orgqr)
// Same story as the LU family: they answer only when the loaded library exports LAPACK, compute
// float32 in double and cast back exactly as NumPy does, and decline every other dtype so the
// engine raises its "needs a LAPACK backend". A not-positive-definite / bad-argument matrix is a
// genuine LinAlgError from the routine (the operand WAS served), not a decline — it propagates.
/// <inheritdoc/>
public bool TryCholesky(NDArray a, bool upper, out NDArray result)
=> OpenBlasEngine.TryCholesky(a, upper, out result);
/// <inheritdoc/>
public bool TryQr(NDArray a, string mode, out NDArray q, out NDArray r)
=> OpenBlasEngine.TryQr(a, mode, out q, out r);
#endregion
#region SVD / least-squares factorisations (LAPACK gesdd / gelsd)
// Same story again: they answer only when the loaded library exports LAPACK, compute float32 in
// double and cast back exactly as NumPy does, and decline every other dtype so the engine raises
// its "needs a LAPACK backend". A non-convergence is a genuine LinAlgError from the routine (the
// operand WAS served), not a decline — it propagates. TrySvd is also what turns on pinv,
// matrix_rank, cond and the spectral/nuclear matrix norms, which reconstruct on top of it.
/// <inheritdoc/>
public bool TrySvd(NDArray a, bool fullMatrices, bool computeUv, out NDArray u, out NDArray s, out NDArray vh)
=> OpenBlasEngine.TrySvd(a, fullMatrices, computeUv, out u, out s, out vh);
/// <inheritdoc/>
public bool TryLstsq(NDArray a, NDArray b, double rcond,
out NDArray solution, out NDArray residuals, out NDArray rank, out NDArray singularValues)
=> OpenBlasEngine.TryLstsq(a, b, rcond, out solution, out residuals, out rank, out singularValues);
#endregion
#region Eigen factorisations (LAPACK syevd / heevd / geev)
// Same story as the other LAPACK families: they answer only when the loaded library exports
// LAPACK, compute float32 in double and cast back exactly as NumPy does, and decline every other
// dtype so the engine raises its "needs a LAPACK backend". A non-convergent element is a genuine
// LinAlgError from the routine (the operand WAS served), not a decline — it propagates. TryEig
// always hands back complex128; the np.linalg.eig wrapper collapses it to a real result when the
// imaginary parts all vanish, exactly as NumPy's Python layer does.
/// <inheritdoc/>
public bool TryEigh(NDArray a, char uplo, bool computeVectors, out NDArray eigenvalues, out NDArray eigenvectors)
=> OpenBlasEngine.TryEigh(a, uplo, computeVectors, out eigenvalues, out eigenvectors);
/// <inheritdoc/>
public bool TryEig(NDArray a, bool computeVectors, out NDArray eigenvalues, out NDArray eigenvectors)
=> OpenBlasEngine.TryEig(a, computeVectors, out eigenvalues, out eigenvectors);
#endregion
#region Sliding dot (np.correlate / np.convolve) — the byte-parity dotfunc
// np.correlate / np.convolve reduce every ramp position, and the middle whenever NumPy's
// small_correlate declines (a real kernel longer than 11, or any complex kernel), with NumPy's
// per-dtype dotfunc — the same double-accumulated chunked cblas ?dot (?dotu for complex) the
// product family uses. Exposing it through the ISlidingDotBackend seam is what lets the sliding
// kernels match NumPy byte-for-byte on the long float32/float64 and complex128 kernels the
// managed reduction reorders; short real kernels stay on the managed path (already byte-exact),
// and this declines every non-cblas dtype so those stay managed too.
/// <inheritdoc/>
bool ISlidingDotBackend.SupportsDot(NPTypeCode dtype)
=> OpenBlasEngine.SupportsSlidingDot(dtype);
/// <inheritdoc/>
unsafe void ISlidingDotBackend.Dot(NPTypeCode dtype,
void* a, long strideA, void* b, long strideB, void* result, long count)
=> OpenBlasEngine.SlidingDot(dtype, a, strideA, b, strideB, result, count);
/// <inheritdoc/>
// Override the default per-position fallback: switch on the dtype ONCE and run the whole
// fully-overlapping middle through the tight native ?dot loop, so np.correlate/np.convolve
// pay the interface dispatch once for the region instead of once per output. Bit-identical
// to the per-position path (same DoubleBlas.Dot / NumPy @name@_dot); only the dispatch moves.
unsafe void ISlidingDotBackend.DotBatch(NPTypeCode dtype,
void* a, void* b, void* result, long count, long n2)
=> OpenBlasEngine.SlidingDotBatch(dtype, a, b, result, count, n2);
#endregion
/// <summary>The loaded library's own description, for diagnostics.</summary>
public override string ToString() => "OpenBlasBackend " + Info;
}
}