spur-math 0.3.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
spur_math/__init__.py ADDED
@@ -0,0 +1,141 @@
1
+ import ctypes
2
+ import os
3
+
4
+ import numpy as np
5
+
6
+ _dll_path = os.path.join(os.path.dirname(__file__), "spur_kernels.dll")
7
+ if not os.path.exists(_dll_path):
8
+ _dll_path = os.path.join(os.path.dirname(__file__), "libspur_kernels.so")
9
+ if not os.path.exists(_dll_path):
10
+ raise ImportError(
11
+ "spur_kernels DLL/SO introuvable a cote du package. "
12
+ "Compilez-le: gcc -O3 -march=native -mavx2 -mfma -fopenmp -shared "
13
+ "-o spur_math/spur_kernels.dll src/spur_kernels.c"
14
+ )
15
+
16
+ _dll = ctypes.CDLL(os.path.abspath(_dll_path))
17
+
18
+ # refus propre (au lieu de SIGILL) sur CPU sans AVX2+FMA
19
+ _dll.spur_cpu_ok.restype = ctypes.c_int
20
+ if not _dll.spur_cpu_ok():
21
+ raise ImportError(
22
+ "spur_math : ce paquet requiert un CPU avec AVX2 et FMA "
23
+ "(Intel Haswell 2013+, AMD Zen 2017+)."
24
+ )
25
+
26
+
27
+ def _bind(name):
28
+ f = getattr(_dll, name)
29
+ f.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double),
30
+ ctypes.c_longlong]
31
+ f.restype = None
32
+ return f
33
+
34
+
35
+ _batch_gelu = _bind("spur_batch_gelu")
36
+ _batch_erf = _bind("spur_batch_erf")
37
+ _batch_tanh = _bind("spur_batch_tanh")
38
+
39
+
40
+ def _bind_mm(name, with_bias=False):
41
+ f = getattr(_dll, name)
42
+ if with_bias:
43
+ f.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double),
44
+ ctypes.c_void_p, ctypes.POINTER(ctypes.c_double),
45
+ ctypes.c_longlong, ctypes.c_longlong, ctypes.c_longlong]
46
+ else:
47
+ f.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double),
48
+ ctypes.POINTER(ctypes.c_double),
49
+ ctypes.c_longlong, ctypes.c_longlong, ctypes.c_longlong]
50
+ f.restype = None
51
+ return f
52
+
53
+
54
+ def _bind_bw(name):
55
+ f = getattr(_dll, name)
56
+ f.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double),
57
+ ctypes.POINTER(ctypes.c_double), ctypes.c_longlong]
58
+ f.restype = None
59
+ return f
60
+
61
+
62
+ _matmul_nt = _bind_mm("spur_matmul_nt")
63
+ _matmul_nt_gelu = _bind_mm("spur_matmul_nt_gelu", with_bias=True)
64
+ _gelu_backward = _bind_bw("spur_batch_gelu_backward")
65
+
66
+
67
+ def matmul_nt(a, b):
68
+ """C = A . B^T. a: (m,k), b: (n,k) -> c: (m,n). Convention BLAS NT."""
69
+ a = np.ascontiguousarray(a, dtype=np.float64)
70
+ b = np.ascontiguousarray(b, dtype=np.float64)
71
+ m, k = a.shape
72
+ n = b.shape[0]
73
+ c = np.zeros((m, n))
74
+ _matmul_nt(a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
75
+ b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
76
+ c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
77
+ m, k, n)
78
+ return c
79
+
80
+
81
+ def matmul_nt_gelu(a, b, bias=None):
82
+ """C = gelu(A . B^T + bias) fusionne. a:(m,k) b:(n,k) bias:(n,) -> (m,n).
83
+ bias=None accepte (retro-compatible)."""
84
+ a = np.ascontiguousarray(a, dtype=np.float64)
85
+ b = np.ascontiguousarray(b, dtype=np.float64)
86
+ m, k = a.shape
87
+ n = b.shape[0]
88
+ c = np.zeros((m, n))
89
+ bp = None
90
+ if bias is not None:
91
+ bias = np.ascontiguousarray(bias, dtype=np.float64)
92
+ assert bias.shape == (n,), f"bias attendu ({n},), recu {bias.shape}"
93
+ bp = bias.ctypes.data_as(ctypes.c_void_p)
94
+ _matmul_nt_gelu(a.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
95
+ b.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
96
+ bp,
97
+ c.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
98
+ m, k, n)
99
+ return c
100
+
101
+
102
+ def gelu_backward(dY, x):
103
+ """dX = dY * gelu'(x) — gradient exact de la gelu approximee (training)."""
104
+ dYc = np.ascontiguousarray(dY, dtype=np.float64)
105
+ xc = np.ascontiguousarray(x, dtype=np.float64)
106
+ out = np.zeros_like(xc)
107
+ _gelu_backward(dYc.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
108
+ xc.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
109
+ out.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
110
+ xc.size)
111
+ return out
112
+
113
+
114
+ def gelu(x):
115
+ """GELU approximatif AVX2. Erreur max 0.079 sur [-2, 2], sature proprement au-dela."""
116
+ xc = np.ascontiguousarray(x, dtype=np.float64)
117
+ out = np.zeros_like(xc)
118
+ fn = _batch_gelu
119
+ fn(xc.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
120
+ out.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), xc.size)
121
+ return out
122
+
123
+
124
+ def erf(x):
125
+ """erf approximatif AVX2. Erreur max 0.011 sur [-2, 2]."""
126
+ xc = np.ascontiguousarray(x, dtype=np.float64)
127
+ out = np.zeros_like(xc)
128
+ fn = _batch_erf
129
+ fn(xc.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
130
+ out.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), xc.size)
131
+ return out
132
+
133
+
134
+ def tanh(x):
135
+ """tanh approximatif AVX2. Erreur max 0.008 sur [-3, 3]."""
136
+ xc = np.ascontiguousarray(x, dtype=np.float64)
137
+ out = np.zeros_like(xc)
138
+ fn = _batch_tanh
139
+ fn(xc.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
140
+ out.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), xc.size)
141
+ return out
spur_math/_jit.py ADDED
@@ -0,0 +1,56 @@
1
+ """Bindings JIT (map kernel multi-entrees) — Windows x64 uniquement.
2
+
3
+ Compile le DLL si absent :
4
+ gcc -O3 -mavx2 -mfma -shared -o spur_math/spur_jit.dll src/spur.c -lm
5
+ """
6
+ import ctypes
7
+ import os
8
+
9
+ import numpy as np
10
+
11
+ _HERE = os.path.dirname(os.path.abspath(__file__))
12
+ _dll_path = os.path.join(_HERE, "spur_jit.dll")
13
+ if not os.path.exists(_dll_path):
14
+ raise ImportError(
15
+ "spur_jit.dll introuvable (JIT Windows x64). "
16
+ "gcc -O2 -mavx2 -mfma -shared -o spur_math/spur_jit.dll src/spur.c -lm"
17
+ )
18
+
19
+ _dll = ctypes.CDLL(_dll_path)
20
+
21
+
22
+ class SpurIns(ctypes.Structure):
23
+ _fields_ = [("op", ctypes.c_short), ("a", ctypes.c_short),
24
+ ("b", ctypes.c_short), ("dst", ctypes.c_short),
25
+ ("imm", ctypes.c_double)]
26
+
27
+
28
+ # opcodes (include/spur.h)
29
+ MOVI, ADDI, MULI, ADD, SUB, MUL, SUBI, BNZ, ACC = range(9)
30
+ GELU, ERF, TANH, LSE2, TANHA, TANHS, ERFA, ACCLSE, HALT = range(9, 18)
31
+ MP_LD, MP_ST, SIGMOID = 18, 19, 20
32
+
33
+ _dll.spur_map_build.restype = ctypes.c_int
34
+ _dll.spur_map_build.argtypes = [ctypes.POINTER(SpurIns), ctypes.c_int]
35
+ _dll.spur_map_exec.restype = None
36
+ _dll.spur_map_exec.argtypes = [ctypes.c_int] + [ctypes.c_void_p] * 3 + [
37
+ ctypes.c_longlong]
38
+
39
+
40
+ def compile_map(prog):
41
+ """prog : liste de tuples (op,a,b,dst,imm) -> handle JIT."""
42
+ arr = (SpurIns * len(prog))()
43
+ for i, ins in enumerate(prog):
44
+ arr[i] = SpurIns(*ins)
45
+ h = _dll.spur_map_build(arr, len(prog))
46
+ if h < 0:
47
+ raise RuntimeError(f"spur_map_build a echoue ({h})")
48
+ return h
49
+
50
+
51
+ def run_map(handle, in0, in1, out):
52
+ """out[i] = F(in0[i], in1[i]) element-wise. in1 peut etre in0."""
53
+ n = out.size
54
+ _dll.spur_map_exec(handle,
55
+ in0.ctypes.data, in1.ctypes.data,
56
+ out.ctypes.data, n)
Binary file
Binary file
Binary file
spur_math/spur_jit.dll ADDED
Binary file
Binary file
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: spur-math
3
+ Version: 0.3.0
4
+ Summary: Accelerated AVX2 math kernels with certified SPEAR polynomials (gelu, erf, tanh), tiled matmul and fused FFN layer with backprop
5
+ Author: bahira
6
+ License: MIT
7
+ Keywords: simd,avx2,math,erf,tanh,gelu
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: C
10
+ Classifier: Topic :: Scientific/Engineering
11
+ Classifier: Intended Audience :: Science/Research
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ # SpearVM
18
+
19
+ **Accélérateur mathématique SIMD** — noyaux transcendantals vectorisés AVX2,
20
+ précision certifiée, appelables depuis Python/C.
21
+
22
+ ## Speedups mesurés (4M éléments, OpenMP multi-cœur)
23
+
24
+ | Noyau | Natif libm | AVX2 SPEAR | **Speedup** |
25
+ |---|---|---|---|
26
+ | GELU | 120 ms | 8 ms | **×15.0** |
27
+ | ERF | 306 ms | 9 ms | **×34.0** |
28
+ | TANH | 234 ms | 9 ms | **×26.0** |
29
+
30
+ ## Matmul (bench_mm)
31
+
32
+ `examples/bench_mm.c` — C = A·B double, validation vs naif incluse.
33
+
34
+ | n | naif ikj | dot scalaire | AVX2 dot | AVX4 bloc + OMP |
35
+ |---|---|---|---|---|
36
+ | 128 | 2.0 ms (2.1 GF) | ×0.12 | ×0.45 | ×0.73 (série) |
37
+ | 256 | 23 ms (1.5 GF) | ×0.24 | ×1.02 | **×1.61** |
38
+ | 512 | 198 ms (1.4 GF) | ×0.31 | ×0.37 | **×2.56** (3.5 GFLOPS) |
39
+
40
+ - err ≤ 1.6e-14 sur toutes les variantes
41
+ - blocage registres 4 lignes : chaque ligne de Bᵀ sert 4 rangs de A, 4 chaînes FMA indépendantes
42
+ - OpenMP activé seulement si n ≥ 192 ; lancer avec `OMP_WAIT_POLICY=ACTIVE`
43
+ - timings VM bruités (±2× entre runs), gains observés jusqu'à ×12 à n=256 machine au repos
44
+
45
+ ## Matmul API (v0.2+)
46
+
47
+ `sm.matmul_nt(A, B)` — C = A·Bᵀ, convention BLAS NT, double précision.
48
+
49
+ - **Tuillage cache** KC×NC (512 Ko/tuile en L2) + blocage registres 4 lignes,
50
+ 4 chaînes FMA indépendantes, OpenMP
51
+ - err ≤ 1.4e-13 vs numpy sur toutes tailles (512→2048), y compris queues m%4
52
+ - `matmul_nt_gelu` : activation fusionnée — la gelu est non linéaire donc k
53
+ n'est pas coupé, seulement le blocage colonnes
54
+
55
+ | Cas | SpearVM | numpy BLAS |
56
+ |---|---|---|
57
+ | carré 512 | 24.5 GFLOPS | 48.2 GFLOPS (×0.51) |
58
+ | carré 1024 | 13.9 GFLOPS | 18.0 GFLOPS (×0.77) |
59
+ | carré 2048 | 10.3 GFLOPS | 23.5 GFLOPS (×0.44) |
60
+ | **FFN 1024×768×3072 fusionné gelu** | **353 ms** | **858 ms (×2.43)** |
61
+
62
+ Lecture honnête : en GEMM carré pur, OpenBLAS/MKL reste devant (packing AVX,
63
+ microkernels plus larges). Là où SpearVM gagne, c'est le **pipeline fusionné**
64
+ — un passage au lieu de deux, zéro buffer intermédiaire — et l'intégration
65
+ NT sans copies.
66
+
67
+ ## Pipeline NN end-to-end (bench_nn)
68
+
69
+ `Y = gelu(X · Wᵀ)` — pattern FFN transformer. M=1024, K=768, N=3072 (4.8 GFLOP).
70
+
71
+ | Implémentation | Total | Détail |
72
+ |---|---|---|
73
+ | **SpearVM** (`matmul_nt_gelu`, tuilé) | **353 ms (×2.43 vs numpy)** | 13.7 GFLOPS équivalents, err 0.0797 |
74
+
75
+ - err max vs référence exacte : **0.0797** (= contrat datasheet gelu ≤0.079)
76
+ - la forme linéarisée sature proprement hors domaine : err bornée ~0.002·|x| jusqu'à ±25
77
+ - validation : `bin/bench_nn.exe` puis `python examples/check_nn.py`
78
+
79
+ ```bash
80
+ gcc -O3 -mavx2 -mfma -fopenmp examples/bench_nn.c src/spur_kernels.c -o bench_nn -lm
81
+ OMP_WAIT_POLICY=ACTIVE ./bench_nn && python examples/check_nn.py
82
+ ```
83
+
84
+ ## Précision (datasheet)
85
+
86
+ Chaque noyau documente son erreur max vs IEEE :
87
+ - gelu : ≤ 0.079 sur [-2, 2]
88
+ - erf : ≤ 0.011 sur [-2, 2]
89
+ - tanh : ≤ 0.009 sur [-3, 3]
90
+ - lse2 : hard-max (écart ≤ ln 2)
91
+
92
+ ## Build
93
+
94
+ ```bash
95
+ make # DLL + tests
96
+ gcc -O3 -mavx2 -mfma -fopenmp src/spur_kernels.c -shared -o spur_kernels.dll
97
+ ```
98
+
99
+ ## Installation Python
100
+
101
+ ```bash
102
+ pip install spur-math # apres publication PyPI
103
+ ```
104
+
105
+ Ou depuis les sources (compile la DLL puis installe) :
106
+
107
+ ```bash
108
+ python build_package.py --install
109
+ ```
110
+
111
+ > Note : le wheel actuel embarque un binaire Windows x64. Sur Linux/macOS,
112
+ > compilez le `.so` vous-meme avec la ligne gcc ci-dessus (le package affiche
113
+ > la commande exacte si la lib manque).
114
+
115
+ def gelu(x):
116
+ """GELU approximatif AVX2. Erreur max 0.079 sur [-2, 2], sature proprement au-dela."""
117
+ ```
118
+
119
+ ## Training (backprop)
120
+
121
+ ```python
122
+ import spur_math as sm
123
+
124
+ # forward fusionne
125
+ Y = sm.matmul_nt_gelu(X, W) # Y = gelu(X . W^T), un seul passage
126
+
127
+ # backward — les gradients passent par le meme matmul
128
+ T = sm.matmul_nt(X, W)
129
+ dY = dLdY * sm.gelu_backward(np.ones_like(T), T) # ou directement gelu_backward(dLdY, T)
130
+ dW = sm.matmul_nt(dY.T, X.T)
131
+ dX = sm.matmul_nt(dY, W.T)
132
+ ```
133
+
134
+ Gradients verifies par differences finies (err ~1e-10) et SGD convergeant
135
+ (loss /87 en 300 pas, `tests/test_train.py`).
136
+
137
+ ## Usage Python
138
+
139
+ ```python
140
+ import ctypes
141
+ lib = ctypes.CDLL("spur_kernels.dll")
142
+ # voir examples/bench_kernels.c pour l'API C complète
143
+ ```
144
+
145
+ ## Architecture
146
+
147
+ ```
148
+ src/spur_kernels.c Kernels AVX2 vectorisés + OpenMP
149
+ include/spur.h API publique
150
+ examples/ Benchmarks et démos
151
+ tests/ Tests de correction
152
+ ```
153
+
154
+ ## Limitations
155
+
156
+ - Windows x64 uniquement
157
+ - Précision approximative (±0.01–0.09 selon noyau)
158
+ - Pas de support GPU
159
+
160
+ ## Licence
161
+
162
+ MIT
@@ -0,0 +1,12 @@
1
+ spur_math/__init__.py,sha256=tfAJjQtV4dOc_4XCvBnHKHdgrRFeHITdxVxSMRVo7S0,4930
2
+ spur_math/_jit.py,sha256=SyeDauWaiYgCNDMzwnFyWSlVi488saEBZs8GlzA7UrI,1771
3
+ spur_math/libgcc_s_seh-1.dll,sha256=zzccarovcbK5Xrv6yFKOyczdCC7-8susSY1W_7K7djw,140800
4
+ spur_math/libgomp-1.dll,sha256=Ix2TY0cuPdSq1F8LC806TASqH-2iIp_MywWfka3sG0k,293376
5
+ spur_math/libwinpthread-1.dll,sha256=eILRsJNxTM369OB4moF3kgorgSYJlJ3Lz3k3xWGBMtg,57344
6
+ spur_math/spur_jit.dll,sha256=cyRgUC5VSF_YzC_1OUesgouhU3bU7myg1w4Fk7fxMy8,46492
7
+ spur_math/spur_kernels.dll,sha256=T4qEOqvgfmj9GZtJ0bSSOuKFkbuTqCFiE0329x1-I1Q,127615
8
+ spur_math-0.3.0.dist-info/licenses/LICENSE,sha256=t6YJ-fG4s6c1WhKowcStyC3o09nuh_qrZ9D_OHsadSs,1063
9
+ spur_math-0.3.0.dist-info/METADATA,sha256=1Jym1oRUVfwEzHiLulwdXClOJAF-qM8Uhhu9V2LoHws,5273
10
+ spur_math-0.3.0.dist-info/WHEEL,sha256=q4eftmEBiRKj4YbvW-aDc9_rQBeao0lG0ucL2-UEsWo,102
11
+ spur_math-0.3.0.dist-info/top_level.txt,sha256=qcKzGgW-YrGvEUr5DGUbvPRGdnUszBHk4H5XVtq9Joo,10
12
+ spur_math-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+
5
+ Tag: py3-none-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 bahira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ spur_math