smaulbrain-core 3.0.0__tar.gz

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.
Files changed (34) hide show
  1. smaulbrain_core-3.0.0/LICENSE +21 -0
  2. smaulbrain_core-3.0.0/PKG-INFO +29 -0
  3. smaulbrain_core-3.0.0/pyproject.toml +59 -0
  4. smaulbrain_core-3.0.0/setup.cfg +4 -0
  5. smaulbrain_core-3.0.0/smaulbrain/__init__.py +3 -0
  6. smaulbrain_core-3.0.0/smaulbrain/_native_opt.py +13 -0
  7. smaulbrain_core-3.0.0/smaulbrain/_native_opt_numpy.py +92 -0
  8. smaulbrain_core-3.0.0/smaulbrain/_optimizer_kernels.cpp +265 -0
  9. smaulbrain_core-3.0.0/smaulbrain/_quant_native.py +567 -0
  10. smaulbrain_core-3.0.0/smaulbrain/_ternary_kernels.cpp +861 -0
  11. smaulbrain_core-3.0.0/smaulbrain/checkpoint.py +122 -0
  12. smaulbrain_core-3.0.0/smaulbrain/cli.py +56 -0
  13. smaulbrain_core-3.0.0/smaulbrain/config.py +197 -0
  14. smaulbrain_core-3.0.0/smaulbrain/cpu.py +43 -0
  15. smaulbrain_core-3.0.0/smaulbrain/data.py +73 -0
  16. smaulbrain_core-3.0.0/smaulbrain/growth.py +224 -0
  17. smaulbrain_core-3.0.0/smaulbrain/inference.py +46 -0
  18. smaulbrain_core-3.0.0/smaulbrain/kernels/__init__.py +94 -0
  19. smaulbrain_core-3.0.0/smaulbrain/kernels/cpu_kernels.cpp +90 -0
  20. smaulbrain_core-3.0.0/smaulbrain/math_ref.py +115 -0
  21. smaulbrain_core-3.0.0/smaulbrain/model.py +255 -0
  22. smaulbrain_core-3.0.0/smaulbrain/moe.py +74 -0
  23. smaulbrain_core-3.0.0/smaulbrain/ops.py +85 -0
  24. smaulbrain_core-3.0.0/smaulbrain/paging.py +98 -0
  25. smaulbrain_core-3.0.0/smaulbrain/prune_cmd.py +87 -0
  26. smaulbrain_core-3.0.0/smaulbrain/quant.py +142 -0
  27. smaulbrain_core-3.0.0/smaulbrain/smaulopt.py +70 -0
  28. smaulbrain_core-3.0.0/smaulbrain/train_loop.py +366 -0
  29. smaulbrain_core-3.0.0/smaulbrain/vocab.py +35 -0
  30. smaulbrain_core-3.0.0/smaulbrain_core.egg-info/PKG-INFO +29 -0
  31. smaulbrain_core-3.0.0/smaulbrain_core.egg-info/SOURCES.txt +32 -0
  32. smaulbrain_core-3.0.0/smaulbrain_core.egg-info/dependency_links.txt +1 -0
  33. smaulbrain_core-3.0.0/smaulbrain_core.egg-info/requires.txt +5 -0
  34. smaulbrain_core-3.0.0/smaulbrain_core.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NativeMc
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,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: smaulbrain-core
3
+ Version: 3.0.0
4
+ Summary: Byte-level recurrent language model with mixture-of-experts, ternary/FP8 quantization, and a factored optimizer, on PyTorch.
5
+ Author: Yash and contributors to SmaulBRAIN
6
+ License-Expression: LicenseRef-SmaulBRAIN
7
+ Project-URL: Homepage, https://github.com/Native-Neo/SmaulBRAIN
8
+ Project-URL: Repository, https://github.com/Native-Neo/SmaulBRAIN
9
+ Project-URL: Issues, https://github.com/Native-Neo/SmaulBRAIN/issues
10
+ Keywords: language-model,mixture-of-experts,moe,quantization,ternary,pytorch,byte-level
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: torch>=2.6
26
+ Requires-Dist: numpy>=1.24
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8; extra == "test"
29
+ Dynamic: license-file
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "smaulbrain-core"
7
+ dynamic = ["version"]
8
+ description = "Byte-level recurrent language model with mixture-of-experts, ternary/FP8 quantization, and a factored optimizer, on PyTorch."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "LicenseRef-SmaulBRAIN"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Yash and contributors to SmaulBRAIN" }]
14
+ keywords = [
15
+ "language-model",
16
+ "mixture-of-experts",
17
+ "moe",
18
+ "quantization",
19
+ "ternary",
20
+ "pytorch",
21
+ "byte-level",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Intended Audience :: Science/Research",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.9",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
35
+ ]
36
+ dependencies = [
37
+ "torch>=2.6",
38
+ "numpy>=1.24",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ test = ["pytest>=8"]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/Native-Neo/SmaulBRAIN"
46
+ Repository = "https://github.com/Native-Neo/SmaulBRAIN"
47
+ Issues = "https://github.com/Native-Neo/SmaulBRAIN/issues"
48
+
49
+ [tool.setuptools]
50
+ packages = ["smaulbrain", "smaulbrain.kernels"]
51
+ py-modules = []
52
+ include-package-data = false
53
+
54
+ [tool.setuptools.dynamic]
55
+ version = { attr = "smaulbrain.__version__" }
56
+
57
+ [tool.setuptools.package-data]
58
+ smaulbrain = ["*.cpp"]
59
+ "smaulbrain.kernels" = ["*.cpp"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """SmaulBRAIN V2 package."""
2
+ __version__ = "3.0.0"
3
+ CHECKPOINT_VERSION = "smaulbrain-pytorch-v1:1"
@@ -0,0 +1,13 @@
1
+ """Optional CPU C++ fused SmaulOPT update reached through Torch tensor views."""
2
+ import torch
3
+ from . import _native_opt_numpy as _backend
4
+
5
+
6
+ def factored_step(opt,w,g,st,collect_norms):
7
+ if w.device.type != "cpu" or not w.is_contiguous() or w.ndim != 2 or w.numel() < 1024:
8
+ return None
9
+ if any(not isinstance(st[k],torch.Tensor) or st[k].device.type != "cpu" or not st[k].is_contiguous() for k in ("m","R","C")):
10
+ return None
11
+ if st["m"].shape != w.shape or st["R"].shape != (w.shape[0],) or st["C"].shape != (w.shape[1],): return None
12
+ return _backend.factored_step(opt,w.detach().numpy(),g.detach().contiguous().numpy(),
13
+ {k:st[k].detach().numpy() for k in ("m","R","C")},collect_norms)
@@ -0,0 +1,92 @@
1
+ """Optional native fused SmaulOPT update (see _optimizer_kernels.cpp).
2
+
3
+ Self-contained: builds its own cached shared object and never raises. If no
4
+ compiler is available, SMAULBRAIN_NATIVE_OPT=0 is set, or a tensor does not
5
+ qualify (dtype/layout), callers fall back to the NumPy reference path.
6
+ """
7
+ import ctypes
8
+ import hashlib
9
+ import os
10
+ import shutil
11
+ import subprocess
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+
16
+ from . import cpu as _cpu
17
+
18
+ _LIB = None
19
+ _TRIED = False
20
+
21
+
22
+ def _load():
23
+ global _LIB, _TRIED
24
+ if _LIB is not None or _TRIED:
25
+ return _LIB
26
+ _TRIED = True
27
+ if os.environ.get("SMAULBRAIN_NATIVE_OPT", "1") == "0":
28
+ return None
29
+ try:
30
+ source = Path(__file__).with_name("_optimizer_kernels.cpp")
31
+ compiler = shutil.which(os.environ.get("CXX", "g++")) or shutil.which("c++")
32
+ if compiler is None or not source.is_file():
33
+ return None
34
+ has_avx = bool(_cpu.detect().get("has_avx", False))
35
+ variant = "-mavx" if has_avx else "-msse2"
36
+ flags = ["-O3", "-std=c++17", "-shared", "-fPIC", "-fno-fast-math",
37
+ "-ffp-contract=off", "-pthread", variant]
38
+ digest = hashlib.sha256(source.read_bytes() + compiler.encode()
39
+ + " ".join(flags).encode()).hexdigest()[:20]
40
+ cache = Path(os.environ.get("XDG_CACHE_HOME", Path.home() / ".cache")) / "smaulbrain"
41
+ cache.mkdir(parents=True, exist_ok=True)
42
+ target = cache / f"opt-{variant[1:]}-{digest}.so"
43
+ if not target.is_file():
44
+ tmp = cache / f"opt-{variant[1:]}-{digest}-{os.getpid()}.tmp.so"
45
+ res = subprocess.run([compiler, *flags, str(source), "-o", str(tmp)],
46
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
47
+ text=True, timeout=90)
48
+ if res.returncode != 0:
49
+ return None
50
+ os.replace(tmp, target)
51
+ lib = ctypes.CDLL(str(target))
52
+ fp = ctypes.POINTER(ctypes.c_float)
53
+ f = ctypes.c_float
54
+ lib.smaulbrain_smaulopt_factored.argtypes = [
55
+ fp, fp, fp, fp, fp, ctypes.c_size_t, ctypes.c_size_t,
56
+ f, f, f, f, f, f, f, ctypes.c_int, ctypes.POINTER(ctypes.c_double)]
57
+ lib.smaulbrain_smaulopt_factored.restype = ctypes.c_int
58
+ _LIB = lib
59
+ except Exception:
60
+ _LIB = None
61
+ return _LIB
62
+
63
+
64
+ def _ok(a):
65
+ return (isinstance(a, np.ndarray) and a.dtype == np.float32
66
+ and a.flags.c_contiguous and a.flags.writeable)
67
+
68
+
69
+ def factored_step(opt, w, g, st, collect_norms):
70
+ """Run the fused update in place. Returns the update RMS (or 0.0) on
71
+ success, or None if the caller must use the NumPy path."""
72
+ lib = _load()
73
+ if lib is None or w.ndim != 2 or w.size < 1024:
74
+ return None
75
+ m, R, C = st["m"], st["R"], st["C"]
76
+ if not (_ok(w) and _ok(m) and _ok(R) and _ok(C)
77
+ and m.shape == w.shape and R.shape == (w.shape[0],) and C.shape == (w.shape[1],)):
78
+ return None
79
+ gf = np.ascontiguousarray(g, dtype=np.float32)
80
+ if gf.shape != w.shape:
81
+ return None
82
+ f32 = np.float32
83
+ p = lambda a: a.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
84
+ sq = ctypes.c_double(0.0)
85
+ rc = lib.smaulbrain_smaulopt_factored(
86
+ p(w), p(m), p(gf), p(R), p(C), w.shape[0], w.shape[1],
87
+ f32(opt.beta1), f32(1.0 - opt.beta1), f32(opt.beta2), f32(1.0 - opt.beta2),
88
+ f32(opt.eps), f32(opt.lr), f32(opt.lr * opt.weight_decay) if opt.weight_decay else f32(0.0),
89
+ 1 if collect_norms else 0, ctypes.byref(sq))
90
+ if rc != 0:
91
+ return None
92
+ return float(np.sqrt(sq.value / w.size)) if collect_norms else 0.0
@@ -0,0 +1,265 @@
1
+ // Fused SmaulOPT update for factored (2-D) parameters.
2
+ //
3
+ // Mirrors smaulopt.SmaulOPT.step() for a matrix W (rows x cols):
4
+ // m <- b1*m + (1-b1)*g
5
+ // R <- b2*R + (1-b2)*mean_j(g^2), C <- b2*C + (1-b2)*mean_i(g^2)
6
+ // upd = m / (sqrt(max(R_i*C_j/mean(R), 0)) + eps)
7
+ // W <- W - (lr*wd)*W ; W <- W - lr*upd
8
+ // in two streaming passes over the matrix (the NumPy path makes ~10).
9
+ // All per-element arithmetic is IEEE float32 in the same operation order as
10
+ // the NumPy path (no FMA, no fast-math); only the row/column means differ,
11
+ // they accumulate in float64. Reductions use fixed 16-row blocks combined in
12
+ // block order, so results do not depend on the thread count.
13
+ #include <algorithm>
14
+ #include <atomic>
15
+ #include <cmath>
16
+ #include <condition_variable>
17
+ #include <cstddef>
18
+ #include <cstdint>
19
+ #include <cstdlib>
20
+ #include <functional>
21
+ #include <mutex>
22
+ #include <thread>
23
+ #include <vector>
24
+ #if defined(__AVX__)
25
+ #include <immintrin.h>
26
+ #endif
27
+
28
+ namespace {
29
+ constexpr std::size_t kBlockRows = 16;
30
+
31
+ // Persistent worker pool (same pattern as _ternary_kernels.cpp). Creating
32
+ // threads per optimizer call costs more than the update itself at matrix
33
+ // sizes this project targets, so workers are started once per process. Worker
34
+ // count = SMAULBRAIN_THREADS if set (>=1), else hardware_concurrency capped at
35
+ // 16; SMAULBRAIN_THREADS=1 disables threading.
36
+ class Pool {
37
+ public:
38
+ // Intentionally leaked: detached workers must outlive static destruction.
39
+ static Pool& get() { static Pool* p = new Pool(); return *p; }
40
+ unsigned size() const noexcept { return nthreads_; }
41
+
42
+ void run(std::size_t n_items, const std::function<void(std::size_t)>& fn) {
43
+ if (n_items == 0) return;
44
+ if (nthreads_ <= 1 || n_items == 1) {
45
+ for (std::size_t i = 0; i < n_items; ++i) fn(i);
46
+ return;
47
+ }
48
+ std::lock_guard<std::mutex> run_lock(run_mu_);
49
+ fn_ = &fn;
50
+ n_items_ = n_items;
51
+ next_.store(0, std::memory_order_relaxed);
52
+ const unsigned helpers = static_cast<unsigned>(
53
+ std::min<std::size_t>(nthreads_ - 1, n_items - 1));
54
+ remaining_.store(helpers, std::memory_order_relaxed);
55
+ wanted_.store(helpers, std::memory_order_relaxed);
56
+ {
57
+ std::lock_guard<std::mutex> lk(mu_);
58
+ generation_.fetch_add(1, std::memory_order_release);
59
+ }
60
+ cv_.notify_all();
61
+ work();
62
+ while (remaining_.load(std::memory_order_acquire) != 0) {
63
+ for (int k = 0; k < 64; ++k) cpu_relax();
64
+ if (remaining_.load(std::memory_order_acquire) == 0) break;
65
+ std::this_thread::yield();
66
+ }
67
+ }
68
+
69
+ private:
70
+ Pool() {
71
+ unsigned n = std::thread::hardware_concurrency();
72
+ if (n == 0) n = 1;
73
+ if (const char* e = std::getenv("SMAULBRAIN_THREADS")) {
74
+ const long v = std::strtol(e, nullptr, 10);
75
+ if (v >= 1) n = static_cast<unsigned>(v);
76
+ }
77
+ nthreads_ = std::min(n, 16u);
78
+ for (unsigned i = 1; i < nthreads_; ++i)
79
+ workers_.emplace_back([this] { loop(); });
80
+ for (auto& t : workers_) t.detach(); // process exit must not block on workers
81
+ }
82
+ static inline void cpu_relax() noexcept {
83
+ #if defined(__SSE2__)
84
+ _mm_pause();
85
+ #endif
86
+ }
87
+ void work() {
88
+ for (;;) {
89
+ const std::size_t i = next_.fetch_add(1, std::memory_order_relaxed);
90
+ if (i >= n_items_) break;
91
+ (*fn_)(i);
92
+ }
93
+ }
94
+ void loop() {
95
+ unsigned seen = 0;
96
+ for (;;) {
97
+ // Spin briefly for the next job, then sleep on the condvar.
98
+ bool got = false;
99
+ for (int k = 0; k < 20000; ++k) {
100
+ if (generation_.load(std::memory_order_acquire) != seen) { got = true; break; }
101
+ cpu_relax();
102
+ }
103
+ if (!got) {
104
+ std::unique_lock<std::mutex> lk(mu_);
105
+ cv_.wait(lk, [&] { return generation_.load(std::memory_order_acquire) != seen; });
106
+ }
107
+ seen = generation_.load(std::memory_order_acquire);
108
+ // Only `wanted_` helpers participate in each job.
109
+ unsigned w = wanted_.load(std::memory_order_relaxed);
110
+ while (w > 0 && !wanted_.compare_exchange_weak(w, w - 1, std::memory_order_acq_rel)) {}
111
+ if (w == 0) continue;
112
+ work();
113
+ remaining_.fetch_sub(1, std::memory_order_acq_rel);
114
+ }
115
+ }
116
+ unsigned nthreads_ = 1;
117
+ std::vector<std::thread> workers_;
118
+ std::mutex mu_, run_mu_;
119
+ std::condition_variable cv_;
120
+ std::atomic<unsigned> generation_{0}, remaining_{0}, wanted_{0};
121
+ std::atomic<std::size_t> next_{0};
122
+ const std::function<void(std::size_t)>* fn_ = nullptr;
123
+ std::size_t n_items_ = 0;
124
+ };
125
+
126
+ // Dynamic work distribution over [0, nblocks). Callers must make each block's
127
+ // output independent of which thread runs it, and combine per-block results in
128
+ // block order (as the passes below do), so results stay deterministic.
129
+ template <class F>
130
+ void parallel_blocks(std::size_t nblocks, bool allow_threads, F&& fn) {
131
+ Pool& pool = Pool::get();
132
+ if (!allow_threads || nblocks == 0 || pool.size() <= 1) {
133
+ for (std::size_t b = 0; b < nblocks; ++b) fn(b);
134
+ return;
135
+ }
136
+ std::function<void(std::size_t)> job = [&fn](std::size_t b) { fn(b); };
137
+ pool.run(nblocks, job);
138
+ }
139
+ } // namespace
140
+
141
+ extern "C" int smaulbrain_smaulopt_factored(
142
+ float* w, float* m, const float* g, float* R, float* C,
143
+ std::size_t rows, std::size_t cols,
144
+ float beta1, float omb1, float beta2, float omb2, float eps, float lr,
145
+ float decay, int want_norm, double* upd_sq_sum) noexcept {
146
+ if (!w || !m || !g || !R || !C || rows == 0 || cols == 0) return 1;
147
+ try {
148
+ const std::size_t nblocks = (rows + kBlockRows - 1) / kBlockRows;
149
+ const bool threaded = rows * cols >= (std::size_t{1} << 18);
150
+
151
+ // ---- pass 1: moment update + g^2 row/column statistics -------------
152
+ std::vector<double> row_sum(rows, 0.0);
153
+ std::vector<float> col_part(nblocks * cols, 0.0f);
154
+ parallel_blocks(nblocks, threaded, [&](std::size_t blk) {
155
+ const std::size_t i0 = blk * kBlockRows, i1 = std::min(rows, i0 + kBlockRows);
156
+ float* cp = col_part.data() + blk * cols;
157
+ for (std::size_t i = i0; i < i1; ++i) {
158
+ const float* gr = g + i * cols;
159
+ float* mr = m + i * cols;
160
+ double rs = 0.0;
161
+ std::size_t j = 0;
162
+ #if defined(__AVX__)
163
+ const __m256 b1 = _mm256_set1_ps(beta1), o1 = _mm256_set1_ps(omb1);
164
+ __m256d acc = _mm256_setzero_pd();
165
+ for (; j + 8 <= cols; j += 8) {
166
+ const __m256 gv = _mm256_loadu_ps(gr + j);
167
+ const __m256 mv = _mm256_add_ps(_mm256_mul_ps(_mm256_loadu_ps(mr + j), b1),
168
+ _mm256_mul_ps(gv, o1));
169
+ _mm256_storeu_ps(mr + j, mv);
170
+ const __m256 g2 = _mm256_mul_ps(gv, gv);
171
+ _mm256_storeu_ps(cp + j, _mm256_add_ps(_mm256_loadu_ps(cp + j), g2));
172
+ acc = _mm256_add_pd(acc, _mm256_cvtps_pd(_mm256_castps256_ps128(g2)));
173
+ acc = _mm256_add_pd(acc, _mm256_cvtps_pd(_mm256_extractf128_ps(g2, 1)));
174
+ }
175
+ alignas(32) double lanes[4];
176
+ _mm256_store_pd(lanes, acc);
177
+ rs = (lanes[0] + lanes[1]) + (lanes[2] + lanes[3]);
178
+ #endif
179
+ for (; j < cols; ++j) {
180
+ const float gv = gr[j];
181
+ mr[j] = mr[j] * beta1 + gv * omb1;
182
+ const float g2 = gv * gv;
183
+ cp[j] += g2;
184
+ rs += static_cast<double>(g2);
185
+ }
186
+ row_sum[i] = rs;
187
+ }
188
+ });
189
+
190
+ // ---- statistics update (tiny) --------------------------------------
191
+ std::vector<double> col_sum(cols, 0.0);
192
+ for (std::size_t blk = 0; blk < nblocks; ++blk) {
193
+ const float* cp = col_part.data() + blk * cols;
194
+ for (std::size_t j = 0; j < cols; ++j) col_sum[j] += static_cast<double>(cp[j]);
195
+ }
196
+ double r_total = 0.0;
197
+ for (std::size_t i = 0; i < rows; ++i) {
198
+ const float row_mean = static_cast<float>(row_sum[i] / static_cast<double>(cols));
199
+ R[i] = R[i] * beta2 + row_mean * omb2;
200
+ r_total += static_cast<double>(R[i]);
201
+ }
202
+ for (std::size_t j = 0; j < cols; ++j) {
203
+ const float col_mean = static_cast<float>(col_sum[j] / static_cast<double>(rows));
204
+ C[j] = C[j] * beta2 + col_mean * omb2;
205
+ }
206
+ const double mean_r = r_total / static_cast<double>(rows);
207
+ const float denom = static_cast<float>(mean_r > 1e-30 ? mean_r : 1e-30);
208
+
209
+ // ---- pass 2: update + weight decay (+ optional update norm) --------
210
+ std::vector<double> blk_sq(nblocks, 0.0);
211
+ parallel_blocks(nblocks, threaded, [&](std::size_t blk) {
212
+ const std::size_t i0 = blk * kBlockRows, i1 = std::min(rows, i0 + kBlockRows);
213
+ double sq = 0.0;
214
+ for (std::size_t i = i0; i < i1; ++i) {
215
+ float* wr = w + i * cols;
216
+ const float* mr = m + i * cols;
217
+ const float ri = R[i];
218
+ std::size_t j = 0;
219
+ #if defined(__AVX__)
220
+ const __m256 rv = _mm256_set1_ps(ri), dv = _mm256_set1_ps(denom);
221
+ const __m256 ev = _mm256_set1_ps(eps), lv = _mm256_set1_ps(lr);
222
+ const __m256 cv = _mm256_set1_ps(decay), zero = _mm256_setzero_ps();
223
+ __m256d acc = _mm256_setzero_pd();
224
+ for (; j + 8 <= cols; j += 8) {
225
+ __m256 v = _mm256_mul_ps(rv, _mm256_loadu_ps(C + j));
226
+ v = _mm256_div_ps(v, dv);
227
+ v = _mm256_max_ps(zero, v); // NaN-propagating like np.maximum
228
+ v = _mm256_add_ps(_mm256_sqrt_ps(v), ev);
229
+ const __m256 u = _mm256_div_ps(_mm256_loadu_ps(mr + j), v);
230
+ if (want_norm) {
231
+ const __m256 u2 = _mm256_mul_ps(u, u);
232
+ acc = _mm256_add_pd(acc, _mm256_cvtps_pd(_mm256_castps256_ps128(u2)));
233
+ acc = _mm256_add_pd(acc, _mm256_cvtps_pd(_mm256_extractf128_ps(u2, 1)));
234
+ }
235
+ __m256 wv = _mm256_loadu_ps(wr + j);
236
+ if (decay != 0.0f) wv = _mm256_sub_ps(wv, _mm256_mul_ps(cv, wv));
237
+ wv = _mm256_sub_ps(wv, _mm256_mul_ps(u, lv));
238
+ _mm256_storeu_ps(wr + j, wv);
239
+ }
240
+ alignas(32) double lanes[4];
241
+ _mm256_store_pd(lanes, acc);
242
+ sq += (lanes[0] + lanes[1]) + (lanes[2] + lanes[3]);
243
+ #endif
244
+ for (; j < cols; ++j) {
245
+ float v = ri * C[j];
246
+ v = v / denom;
247
+ v = (v != v) ? v : (v > 0.0f ? v : 0.0f);
248
+ v = std::sqrt(v) + eps;
249
+ const float u = mr[j] / v;
250
+ if (want_norm) sq += static_cast<double>(u * u);
251
+ float wv = wr[j];
252
+ if (decay != 0.0f) wv = wv - decay * wv;
253
+ wr[j] = wv - u * lr;
254
+ }
255
+ }
256
+ blk_sq[blk] = sq;
257
+ });
258
+ double total = 0.0;
259
+ for (double v : blk_sq) total += v;
260
+ if (upd_sq_sum) *upd_sq_sum = total;
261
+ return 0;
262
+ } catch (...) {
263
+ return 2;
264
+ }
265
+ }