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.
- smaulbrain_core-3.0.0/LICENSE +21 -0
- smaulbrain_core-3.0.0/PKG-INFO +29 -0
- smaulbrain_core-3.0.0/pyproject.toml +59 -0
- smaulbrain_core-3.0.0/setup.cfg +4 -0
- smaulbrain_core-3.0.0/smaulbrain/__init__.py +3 -0
- smaulbrain_core-3.0.0/smaulbrain/_native_opt.py +13 -0
- smaulbrain_core-3.0.0/smaulbrain/_native_opt_numpy.py +92 -0
- smaulbrain_core-3.0.0/smaulbrain/_optimizer_kernels.cpp +265 -0
- smaulbrain_core-3.0.0/smaulbrain/_quant_native.py +567 -0
- smaulbrain_core-3.0.0/smaulbrain/_ternary_kernels.cpp +861 -0
- smaulbrain_core-3.0.0/smaulbrain/checkpoint.py +122 -0
- smaulbrain_core-3.0.0/smaulbrain/cli.py +56 -0
- smaulbrain_core-3.0.0/smaulbrain/config.py +197 -0
- smaulbrain_core-3.0.0/smaulbrain/cpu.py +43 -0
- smaulbrain_core-3.0.0/smaulbrain/data.py +73 -0
- smaulbrain_core-3.0.0/smaulbrain/growth.py +224 -0
- smaulbrain_core-3.0.0/smaulbrain/inference.py +46 -0
- smaulbrain_core-3.0.0/smaulbrain/kernels/__init__.py +94 -0
- smaulbrain_core-3.0.0/smaulbrain/kernels/cpu_kernels.cpp +90 -0
- smaulbrain_core-3.0.0/smaulbrain/math_ref.py +115 -0
- smaulbrain_core-3.0.0/smaulbrain/model.py +255 -0
- smaulbrain_core-3.0.0/smaulbrain/moe.py +74 -0
- smaulbrain_core-3.0.0/smaulbrain/ops.py +85 -0
- smaulbrain_core-3.0.0/smaulbrain/paging.py +98 -0
- smaulbrain_core-3.0.0/smaulbrain/prune_cmd.py +87 -0
- smaulbrain_core-3.0.0/smaulbrain/quant.py +142 -0
- smaulbrain_core-3.0.0/smaulbrain/smaulopt.py +70 -0
- smaulbrain_core-3.0.0/smaulbrain/train_loop.py +366 -0
- smaulbrain_core-3.0.0/smaulbrain/vocab.py +35 -0
- smaulbrain_core-3.0.0/smaulbrain_core.egg-info/PKG-INFO +29 -0
- smaulbrain_core-3.0.0/smaulbrain_core.egg-info/SOURCES.txt +32 -0
- smaulbrain_core-3.0.0/smaulbrain_core.egg-info/dependency_links.txt +1 -0
- smaulbrain_core-3.0.0/smaulbrain_core.egg-info/requires.txt +5 -0
- 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,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
|
+
}
|