sync(v1.6.0 / v1.0.9): synchronize full luminamoon monorepo, LSO-Eval v1.1.1 results, and LuminaV v1.6.0 bundle
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +7 -7
- .github/dependabot.yml +37 -37
- .github/workflows/ci.yml +308 -308
- .github/workflows/docs.yml +83 -83
- .github/workflows/hf_sync.yml +49 -49
- .github/workflows/security.yml +91 -91
- .gitignore +54 -54
- CHANGELOG.md +430 -430
- Cargo.lock +1218 -1218
- Cargo.toml +39 -39
- LICENSE +13 -13
- README.md +334 -334
- SECURITY.md +50 -50
- benchmarks/bench_data_processing.py +411 -411
- benchmarks/bench_luminav_vision_modes.py +398 -398
- changelogs/LUMINAV_ALGORITHM_CHANGELOG.md +430 -430
- changelogs/README.md +25 -25
- changelogs/SOFTWARE_CHANGELOG.md +261 -261
- config.json +122 -122
- data/Cargo.toml +24 -24
- data/README.md +206 -206
- data/docs/README.md +62 -62
- data/docs/library_audit/01_rust_toolchain_simd_and_mmap.md +56 -56
- data/docs/library_audit/02_python_pyo3_abi3t_and_maturin.md +53 -53
- data/docs/library_audit/03_parquet_arrow_and_http206_streaming.md +48 -48
- data/docs/library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md +49 -49
- data/docs/library_audit/05_universal_formats_compression_and_diagnostics.md +87 -87
- data/docs/library_audit/README.md +30 -30
- data/src/constants.rs +21 -21
- data/src/coord.rs +39 -39
- data/src/dedup.rs +112 -112
- data/src/error.rs +166 -166
- data/src/lib.rs +15 -15
- data/src/metadata.rs +264 -264
- data/src/minhash.rs +186 -186
- data/src/postprocess.rs +135 -135
- data/src/py_bridge.rs +839 -839
- data/src/quality.rs +169 -169
- data/src/serialize.rs +268 -268
- data/src/simd_cpu.rs +313 -313
- data/src/string_tensor.rs +269 -269
- data/src/tests.rs +384 -384
- docs/README.md +101 -101
- docs/dataloader/README.md +69 -69
- docs/dataloader/coord_and_metadata/README.md +70 -70
- docs/dataloader/dedup/README.md +66 -66
- docs/dataloader/multimodal_and_hf_hub/README.md +38 -38
- docs/dataloader/preram/README.md +37 -37
- docs/dataloader/python_ffi_and_runtime/README.md +29 -29
- docs/dataloader/simd_and_postprocess/README.md +62 -62
.env.example
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
# GitHub API Personal Access Tokens
|
| 2 |
-
GITHUB_TOKEN=
|
| 3 |
-
GITHUB_TOKEN_2=
|
| 4 |
-
|
| 5 |
-
# Distribution & Registry Deployment Tokens
|
| 6 |
-
HF_TOKEN=
|
| 7 |
-
PYPI_TOKEN=
|
|
|
|
| 1 |
+
# GitHub API Personal Access Tokens
|
| 2 |
+
GITHUB_TOKEN=
|
| 3 |
+
GITHUB_TOKEN_2=
|
| 4 |
+
|
| 5 |
+
# Distribution & Registry Deployment Tokens
|
| 6 |
+
HF_TOKEN=
|
| 7 |
+
PYPI_TOKEN=
|
.github/dependabot.yml
CHANGED
|
@@ -1,37 +1,37 @@
|
|
| 1 |
-
version: 2
|
| 2 |
-
updates:
|
| 3 |
-
- package-ecosystem: "cargo"
|
| 4 |
-
directory: "/"
|
| 5 |
-
target-branch: "development"
|
| 6 |
-
schedule:
|
| 7 |
-
interval: "weekly"
|
| 8 |
-
day: "monday"
|
| 9 |
-
open-pull-requests-limit: 0
|
| 10 |
-
labels:
|
| 11 |
-
- "dependencies"
|
| 12 |
-
- "rust"
|
| 13 |
-
- "security"
|
| 14 |
-
|
| 15 |
-
- package-ecosystem: "pip"
|
| 16 |
-
directory: "/"
|
| 17 |
-
target-branch: "development"
|
| 18 |
-
schedule:
|
| 19 |
-
interval: "weekly"
|
| 20 |
-
day: "monday"
|
| 21 |
-
open-pull-requests-limit: 0
|
| 22 |
-
labels:
|
| 23 |
-
- "dependencies"
|
| 24 |
-
- "python"
|
| 25 |
-
- "security"
|
| 26 |
-
|
| 27 |
-
- package-ecosystem: "github-actions"
|
| 28 |
-
directory: "/"
|
| 29 |
-
target-branch: "development"
|
| 30 |
-
schedule:
|
| 31 |
-
interval: "weekly"
|
| 32 |
-
day: "monday"
|
| 33 |
-
open-pull-requests-limit: 0
|
| 34 |
-
labels:
|
| 35 |
-
- "dependencies"
|
| 36 |
-
- "github-actions"
|
| 37 |
-
- "security"
|
|
|
|
| 1 |
+
version: 2
|
| 2 |
+
updates:
|
| 3 |
+
- package-ecosystem: "cargo"
|
| 4 |
+
directory: "/"
|
| 5 |
+
target-branch: "development"
|
| 6 |
+
schedule:
|
| 7 |
+
interval: "weekly"
|
| 8 |
+
day: "monday"
|
| 9 |
+
open-pull-requests-limit: 0
|
| 10 |
+
labels:
|
| 11 |
+
- "dependencies"
|
| 12 |
+
- "rust"
|
| 13 |
+
- "security"
|
| 14 |
+
|
| 15 |
+
- package-ecosystem: "pip"
|
| 16 |
+
directory: "/"
|
| 17 |
+
target-branch: "development"
|
| 18 |
+
schedule:
|
| 19 |
+
interval: "weekly"
|
| 20 |
+
day: "monday"
|
| 21 |
+
open-pull-requests-limit: 0
|
| 22 |
+
labels:
|
| 23 |
+
- "dependencies"
|
| 24 |
+
- "python"
|
| 25 |
+
- "security"
|
| 26 |
+
|
| 27 |
+
- package-ecosystem: "github-actions"
|
| 28 |
+
directory: "/"
|
| 29 |
+
target-branch: "development"
|
| 30 |
+
schedule:
|
| 31 |
+
interval: "weekly"
|
| 32 |
+
day: "monday"
|
| 33 |
+
open-pull-requests-limit: 0
|
| 34 |
+
labels:
|
| 35 |
+
- "dependencies"
|
| 36 |
+
- "github-actions"
|
| 37 |
+
- "security"
|
.github/workflows/ci.yml
CHANGED
|
@@ -1,309 +1,309 @@
|
|
| 1 |
-
name: CI/CD Pipeline
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches: [ "main", "master" ]
|
| 6 |
-
tags: [ "v*" ]
|
| 7 |
-
pull_request:
|
| 8 |
-
branches: [ "main", "master", "development" ]
|
| 9 |
-
release:
|
| 10 |
-
types: [published]
|
| 11 |
-
workflow_dispatch:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
concurrency:
|
| 15 |
-
group: ${{ github.workflow }}-${{ github.ref }}
|
| 16 |
-
cancel-in-progress: true
|
| 17 |
-
|
| 18 |
-
permissions:
|
| 19 |
-
contents: read
|
| 20 |
-
|
| 21 |
-
jobs:
|
| 22 |
-
rust-quality:
|
| 23 |
-
name: Rust Quality & Parity (${{ matrix.os }})
|
| 24 |
-
if: github.event_name != 'release'
|
| 25 |
-
runs-on: ${{ matrix.os }}
|
| 26 |
-
strategy:
|
| 27 |
-
fail-fast: false
|
| 28 |
-
matrix:
|
| 29 |
-
os: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.base_ref == 'main') && fromJSON('["ubuntu-latest", "windows-latest", "macos-latest"]') || fromJSON('["ubuntu-latest"]') }}
|
| 30 |
-
steps:
|
| 31 |
-
- name: Checkout repository
|
| 32 |
-
uses: actions/checkout@v7
|
| 33 |
-
|
| 34 |
-
- name: Set up Python
|
| 35 |
-
uses: actions/setup-python@v7
|
| 36 |
-
with:
|
| 37 |
-
python-version: "3.12"
|
| 38 |
-
|
| 39 |
-
- name: Set up Rust toolchain
|
| 40 |
-
uses: dtolnay/rust-toolchain@stable
|
| 41 |
-
with:
|
| 42 |
-
components: clippy, rustfmt
|
| 43 |
-
|
| 44 |
-
- name: Rust Cache
|
| 45 |
-
uses: Swatinem/rust-cache@v2
|
| 46 |
-
|
| 47 |
-
- name: Verify Formatting (cargo fmt)
|
| 48 |
-
if: matrix.os == 'ubuntu-latest'
|
| 49 |
-
run: cargo fmt --check
|
| 50 |
-
|
| 51 |
-
- name: Cargo Clippy Lints
|
| 52 |
-
run: cargo clippy --workspace --all-targets -- -D warnings
|
| 53 |
-
|
| 54 |
-
- name: Cargo Unit & Parity Tests
|
| 55 |
-
run: cargo test --workspace --verbose
|
| 56 |
-
|
| 57 |
-
type-check:
|
| 58 |
-
name: Python Static Typing (Pyright)
|
| 59 |
-
if: github.event_name != 'release'
|
| 60 |
-
runs-on: ubuntu-latest
|
| 61 |
-
steps:
|
| 62 |
-
- name: Checkout repository
|
| 63 |
-
uses: actions/checkout@v7
|
| 64 |
-
|
| 65 |
-
- name: Set up Python 3.14
|
| 66 |
-
uses: actions/setup-python@v7
|
| 67 |
-
with:
|
| 68 |
-
python-version: "3.14"
|
| 69 |
-
|
| 70 |
-
- name: Set up Node.js
|
| 71 |
-
uses: actions/setup-node@v7
|
| 72 |
-
with:
|
| 73 |
-
node-version: "24"
|
| 74 |
-
|
| 75 |
-
- name: Install Pyright Dependencies
|
| 76 |
-
run: |
|
| 77 |
-
python -m pip install --upgrade pip
|
| 78 |
-
pip install "numpy>=2.0.0"
|
| 79 |
-
|
| 80 |
-
- name: Run Pyright
|
| 81 |
-
run: npx pyright python/luminamoon
|
| 82 |
-
|
| 83 |
-
- name: Verify Universal Fallback Wheel Builder
|
| 84 |
-
run: |
|
| 85 |
-
python tools/build_universal_wheel.py --out dist
|
| 86 |
-
python -c "import zipfile, sys, tempfile; td = tempfile.TemporaryDirectory(); import glob; whl = glob.glob('dist/luminamoon-*-py3-none-any.whl')[0]; z = zipfile.ZipFile(whl); z.extractall(td.name); sys.path.insert(0, td.name); import luminamoon.optimizer.luminav as lum; import numpy as np; p = np.zeros(5, dtype=np.float32); opt = lum.LuminaV([p], lr=1e-3); opt.step(grads=[np.ones(5, dtype=np.float32)]); assert opt.state[p]['step'] == 1; print('Universal fallback verified successfully')"
|
| 87 |
-
|
| 88 |
-
python-matrix:
|
| 89 |
-
name: Python Tests (${{ matrix.os }}, Py${{ matrix.python-version }})
|
| 90 |
-
if: github.event_name != 'release'
|
| 91 |
-
needs: [rust-quality]
|
| 92 |
-
runs-on: ${{ matrix.os }}
|
| 93 |
-
strategy:
|
| 94 |
-
fail-fast: false
|
| 95 |
-
matrix:
|
| 96 |
-
os: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest", "windows-latest", "macos-latest"]') || ((github.ref == 'refs/heads/main' || github.base_ref == 'main') && fromJSON('["ubuntu-latest", "windows-latest"]') || fromJSON('["ubuntu-latest"]')) }}
|
| 97 |
-
python-version: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && fromJSON('["3.10", "3.11", "3.12", "3.13", "3.14"]') || fromJSON('["3.14"]') }}
|
| 98 |
-
steps:
|
| 99 |
-
- name: Checkout repository
|
| 100 |
-
uses: actions/checkout@v7
|
| 101 |
-
|
| 102 |
-
- name: Set up Rust toolchain
|
| 103 |
-
uses: dtolnay/rust-toolchain@stable
|
| 104 |
-
|
| 105 |
-
- name: Rust Cache
|
| 106 |
-
uses: Swatinem/rust-cache@v2
|
| 107 |
-
|
| 108 |
-
- name: Set up Python
|
| 109 |
-
uses: actions/setup-python@v7
|
| 110 |
-
with:
|
| 111 |
-
python-version: ${{ matrix.python-version }}
|
| 112 |
-
allow-prereleases: true
|
| 113 |
-
cache: "pip"
|
| 114 |
-
|
| 115 |
-
- name: Upgrade Pip & Install Build Tools
|
| 116 |
-
run: |
|
| 117 |
-
python -m pip install --upgrade pip
|
| 118 |
-
pip install maturin "numpy>=2.0.0" pytest
|
| 119 |
-
|
| 120 |
-
- name: Build & Install LuminaMoon (Native CPython Extension)
|
| 121 |
-
run: python tools/build_wheel.py --install
|
| 122 |
-
|
| 123 |
-
- name: Run Standalone NumPy Verification
|
| 124 |
-
run: python optimizer/tests/test_standalone_numpy.py
|
| 125 |
-
|
| 126 |
-
- name: Run Data Modular Verification
|
| 127 |
-
run: python -B tests/test_data_modular.py
|
| 128 |
-
|
| 129 |
-
- name: Run Adversarial String Verification
|
| 130 |
-
run: python -B tests/test_gpu_vs_cpu_strings.py
|
| 131 |
-
|
| 132 |
-
- name: Install Optional PyTorch Dependency
|
| 133 |
-
id: install-torch
|
| 134 |
-
run: python -c "import os, subprocess, sys; r = subprocess.run([sys.executable, '-m', 'pip', 'install', 'torch>=2.0.0']); open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8').write(f'installed={str(r.returncode == 0).lower()}\n'); print('PyTorch installed:', r.returncode == 0)"
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
- name: Run PyTorch End-to-End Training Tests
|
| 138 |
-
if: steps.install-torch.outputs.installed == 'true'
|
| 139 |
-
run: python optimizer/tests/test_end_to_end_training.py
|
| 140 |
-
|
| 141 |
-
- name: Run LSO-Eval Vision (V-Eval) Protocol Verification
|
| 142 |
-
if: steps.install-torch.outputs.installed == 'true'
|
| 143 |
-
run: python eval/v_eval_suite.py --verify-architecture
|
| 144 |
-
|
| 145 |
-
- name: Verify Universal Dynamic Fallback
|
| 146 |
-
run: |
|
| 147 |
-
python -c "import luminamoon.optimizer.luminav as lum; lum._luminamoon_rs = None; import numpy as np; p = np.zeros(10, dtype=np.float32); opt = lum.LuminaV([p], lr=1e-3); opt.step(grads=[np.ones(10, dtype=np.float32)]); assert opt.state[p]['step'] == 1; print('Dynamic fallback verified successfully')"
|
| 148 |
-
|
| 149 |
-
free-threaded:
|
| 150 |
-
name: Free-Threaded No-GIL (${{ matrix.python-version }})
|
| 151 |
-
if: github.event_name != 'release'
|
| 152 |
-
needs: [rust-quality]
|
| 153 |
-
runs-on: ubuntu-latest
|
| 154 |
-
continue-on-error: true
|
| 155 |
-
strategy:
|
| 156 |
-
fail-fast: false
|
| 157 |
-
matrix:
|
| 158 |
-
python-version: ["3.14t", "3.15t"]
|
| 159 |
-
steps:
|
| 160 |
-
- name: Checkout repository
|
| 161 |
-
uses: actions/checkout@v7
|
| 162 |
-
|
| 163 |
-
- name: Set up Rust toolchain
|
| 164 |
-
uses: dtolnay/rust-toolchain@stable
|
| 165 |
-
|
| 166 |
-
- name: Rust Cache
|
| 167 |
-
uses: Swatinem/rust-cache@v2
|
| 168 |
-
|
| 169 |
-
- name: Set up Python (Free-Threaded)
|
| 170 |
-
uses: actions/setup-python@v7
|
| 171 |
-
with:
|
| 172 |
-
python-version: ${{ matrix.python-version }}
|
| 173 |
-
allow-prereleases: true
|
| 174 |
-
|
| 175 |
-
- name: Install Maturin
|
| 176 |
-
run: |
|
| 177 |
-
python -m pip install --upgrade pip
|
| 178 |
-
pip install maturin
|
| 179 |
-
|
| 180 |
-
- name: Verify abi3t Wheel Compilation & Import
|
| 181 |
-
run: |
|
| 182 |
-
maturin build --release -i python --out dist
|
| 183 |
-
pip install dist/*.whl
|
| 184 |
-
python -c "import luminamoon.optimizer._luminamoon_optimizer_rs, sys; assert not sys._is_gil_enabled(), 'GIL must remain disabled'; print('Free-threaded abi3t extension verified successfully')"
|
| 185 |
-
|
| 186 |
-
release-build:
|
| 187 |
-
name: Distribution Wheel Build (${{ matrix.os }})
|
| 188 |
-
needs: [python-matrix, type-check]
|
| 189 |
-
if: (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && github.event_name != 'release'
|
| 190 |
-
runs-on: ${{ matrix.os }}
|
| 191 |
-
strategy:
|
| 192 |
-
matrix:
|
| 193 |
-
os: [ubuntu-latest, windows-latest, macos-latest]
|
| 194 |
-
steps:
|
| 195 |
-
- name: Checkout repository
|
| 196 |
-
uses: actions/checkout@v7
|
| 197 |
-
|
| 198 |
-
- name: Set up Python for Build Scripts
|
| 199 |
-
if: matrix.os == 'ubuntu-latest' || startsWith(github.ref, 'refs/tags/v')
|
| 200 |
-
uses: actions/setup-python@v7
|
| 201 |
-
with:
|
| 202 |
-
python-version: "3.14"
|
| 203 |
-
|
| 204 |
-
- name: Sync version from Git Tag
|
| 205 |
-
if: startsWith(github.ref, 'refs/tags/v')
|
| 206 |
-
run: python tools/sync_version.py "${{ github.ref_name }}"
|
| 207 |
-
|
| 208 |
-
- name: Build Universal Pure-Python Fallback Wheel (py3-none-any)
|
| 209 |
-
if: matrix.os == 'ubuntu-latest'
|
| 210 |
-
run: python tools/build_universal_wheel.py --out dist
|
| 211 |
-
|
| 212 |
-
- name: Set up Rust toolchain
|
| 213 |
-
uses: dtolnay/rust-toolchain@stable
|
| 214 |
-
|
| 215 |
-
- name: Build sdist (Source Distribution)
|
| 216 |
-
if: matrix.os == 'ubuntu-latest'
|
| 217 |
-
uses: PyO3/maturin-action@v1
|
| 218 |
-
with:
|
| 219 |
-
command: sdist
|
| 220 |
-
args: --out dist
|
| 221 |
-
|
| 222 |
-
- name: Build Wheel via Maturin
|
| 223 |
-
uses: PyO3/maturin-action@v1
|
| 224 |
-
with:
|
| 225 |
-
command: build
|
| 226 |
-
args: --release --out dist
|
| 227 |
-
manylinux: auto
|
| 228 |
-
|
| 229 |
-
- name: Upload Artifacts
|
| 230 |
-
uses: actions/upload-artifact@v7
|
| 231 |
-
with:
|
| 232 |
-
name: wheels-${{ matrix.os }}
|
| 233 |
-
path: dist/*
|
| 234 |
-
|
| 235 |
-
draft-release:
|
| 236 |
-
name: Create Draft GitHub Release
|
| 237 |
-
needs: [release-build]
|
| 238 |
-
if: startsWith(github.ref, 'refs/tags/v') && github.event_name != 'release'
|
| 239 |
-
runs-on: ubuntu-latest
|
| 240 |
-
permissions:
|
| 241 |
-
contents: write
|
| 242 |
-
steps:
|
| 243 |
-
- name: Checkout repository
|
| 244 |
-
uses: actions/checkout@v7
|
| 245 |
-
|
| 246 |
-
- name: Download all built wheels
|
| 247 |
-
uses: actions/download-artifact@v8
|
| 248 |
-
with:
|
| 249 |
-
pattern: wheels-*
|
| 250 |
-
merge-multiple: true
|
| 251 |
-
path: dist
|
| 252 |
-
|
| 253 |
-
- name: Extract Structured Release Notes from Software Changelog
|
| 254 |
-
env:
|
| 255 |
-
RELEASE_TAG: ${{ github.ref_name }}
|
| 256 |
-
run: |
|
| 257 |
-
python3 -c '
|
| 258 |
-
import os, pathlib, re
|
| 259 |
-
tag = os.environ["RELEASE_TAG"].lstrip("v")
|
| 260 |
-
text = pathlib.Path("changelogs/SOFTWARE_CHANGELOG.md").read_text(encoding="utf-8")
|
| 261 |
-
pattern = rf"^## \[{re.escape(tag)}\].*?(?=^## \[|\Z)"
|
| 262 |
-
match = re.search(pattern, text, flags=re.MULTILINE | re.DOTALL)
|
| 263 |
-
body = re.sub(r"\n+---\s*$", "", match.group(0).strip()) if match else f"## [{tag}]\n\nRelease of Loomy (`luminamoon`) v{tag}."
|
| 264 |
-
header = (
|
| 265 |
-
f"{body}\n\n---\n\n"
|
| 266 |
-
"## Installation\n\n"
|
| 267 |
-
f"```bash\npip install --upgrade luminamoon=={tag}\n```\n"
|
| 268 |
-
)
|
| 269 |
-
pathlib.Path("RELEASE_NOTES.md").write_text(header, encoding="utf-8")
|
| 270 |
-
'
|
| 271 |
-
|
| 272 |
-
- name: Create Draft GitHub Release
|
| 273 |
-
uses: softprops/action-gh-release@v2
|
| 274 |
-
with:
|
| 275 |
-
name: "${{ github.ref_name }}"
|
| 276 |
-
body_path: RELEASE_NOTES.md
|
| 277 |
-
files: dist/*
|
| 278 |
-
draft: true
|
| 279 |
-
prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
|
| 280 |
-
make_latest: ${{ !contains(github.ref_name, 'a') && !contains(github.ref_name, 'b') && !contains(github.ref_name, 'rc') }}
|
| 281 |
-
generate_release_notes: true
|
| 282 |
-
|
| 283 |
-
publish-pypi:
|
| 284 |
-
name: Publish Wheels to PyPI
|
| 285 |
-
needs: [draft-release]
|
| 286 |
-
if: always() && (github.event_name == 'release' && github.event.action == 'published')
|
| 287 |
-
runs-on: ubuntu-latest
|
| 288 |
-
permissions:
|
| 289 |
-
contents: read
|
| 290 |
-
id-token: write
|
| 291 |
-
steps:
|
| 292 |
-
- name: Checkout repository
|
| 293 |
-
uses: actions/checkout@v7
|
| 294 |
-
|
| 295 |
-
- name: Download release assets via GitHub CLI
|
| 296 |
-
env:
|
| 297 |
-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 298 |
-
RELEASE_TAG: ${{ github.event.release.tag_name || github.ref_name }}
|
| 299 |
-
run: |
|
| 300 |
-
mkdir -p dist
|
| 301 |
-
gh release download "$RELEASE_TAG" --dir dist --pattern "*.whl" --pattern "*.tar.gz"
|
| 302 |
-
ls -la dist
|
| 303 |
-
|
| 304 |
-
- name: Publish Distribution to PyPI
|
| 305 |
-
uses: pypa/gh-action-pypi-publish@release/v1
|
| 306 |
-
with:
|
| 307 |
-
password: ${{ secrets.PYPI_API_TOKEN }}
|
| 308 |
-
packages-dir: dist/
|
| 309 |
skip-existing: true
|
|
|
|
| 1 |
+
name: CI/CD Pipeline
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main", "master" ]
|
| 6 |
+
tags: [ "v*" ]
|
| 7 |
+
pull_request:
|
| 8 |
+
branches: [ "main", "master", "development" ]
|
| 9 |
+
release:
|
| 10 |
+
types: [published]
|
| 11 |
+
workflow_dispatch:
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
concurrency:
|
| 15 |
+
group: ${{ github.workflow }}-${{ github.ref }}
|
| 16 |
+
cancel-in-progress: true
|
| 17 |
+
|
| 18 |
+
permissions:
|
| 19 |
+
contents: read
|
| 20 |
+
|
| 21 |
+
jobs:
|
| 22 |
+
rust-quality:
|
| 23 |
+
name: Rust Quality & Parity (${{ matrix.os }})
|
| 24 |
+
if: github.event_name != 'release'
|
| 25 |
+
runs-on: ${{ matrix.os }}
|
| 26 |
+
strategy:
|
| 27 |
+
fail-fast: false
|
| 28 |
+
matrix:
|
| 29 |
+
os: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.base_ref == 'main') && fromJSON('["ubuntu-latest", "windows-latest", "macos-latest"]') || fromJSON('["ubuntu-latest"]') }}
|
| 30 |
+
steps:
|
| 31 |
+
- name: Checkout repository
|
| 32 |
+
uses: actions/checkout@v7
|
| 33 |
+
|
| 34 |
+
- name: Set up Python
|
| 35 |
+
uses: actions/setup-python@v7
|
| 36 |
+
with:
|
| 37 |
+
python-version: "3.12"
|
| 38 |
+
|
| 39 |
+
- name: Set up Rust toolchain
|
| 40 |
+
uses: dtolnay/rust-toolchain@stable
|
| 41 |
+
with:
|
| 42 |
+
components: clippy, rustfmt
|
| 43 |
+
|
| 44 |
+
- name: Rust Cache
|
| 45 |
+
uses: Swatinem/rust-cache@v2
|
| 46 |
+
|
| 47 |
+
- name: Verify Formatting (cargo fmt)
|
| 48 |
+
if: matrix.os == 'ubuntu-latest'
|
| 49 |
+
run: cargo fmt --check
|
| 50 |
+
|
| 51 |
+
- name: Cargo Clippy Lints
|
| 52 |
+
run: cargo clippy --workspace --all-targets -- -D warnings
|
| 53 |
+
|
| 54 |
+
- name: Cargo Unit & Parity Tests
|
| 55 |
+
run: cargo test --workspace --verbose
|
| 56 |
+
|
| 57 |
+
type-check:
|
| 58 |
+
name: Python Static Typing (Pyright)
|
| 59 |
+
if: github.event_name != 'release'
|
| 60 |
+
runs-on: ubuntu-latest
|
| 61 |
+
steps:
|
| 62 |
+
- name: Checkout repository
|
| 63 |
+
uses: actions/checkout@v7
|
| 64 |
+
|
| 65 |
+
- name: Set up Python 3.14
|
| 66 |
+
uses: actions/setup-python@v7
|
| 67 |
+
with:
|
| 68 |
+
python-version: "3.14"
|
| 69 |
+
|
| 70 |
+
- name: Set up Node.js
|
| 71 |
+
uses: actions/setup-node@v7
|
| 72 |
+
with:
|
| 73 |
+
node-version: "24"
|
| 74 |
+
|
| 75 |
+
- name: Install Pyright Dependencies
|
| 76 |
+
run: |
|
| 77 |
+
python -m pip install --upgrade pip
|
| 78 |
+
pip install "numpy>=2.0.0"
|
| 79 |
+
|
| 80 |
+
- name: Run Pyright
|
| 81 |
+
run: npx pyright python/luminamoon
|
| 82 |
+
|
| 83 |
+
- name: Verify Universal Fallback Wheel Builder
|
| 84 |
+
run: |
|
| 85 |
+
python tools/build_universal_wheel.py --out dist
|
| 86 |
+
python -c "import zipfile, sys, tempfile; td = tempfile.TemporaryDirectory(); import glob; whl = glob.glob('dist/luminamoon-*-py3-none-any.whl')[0]; z = zipfile.ZipFile(whl); z.extractall(td.name); sys.path.insert(0, td.name); import luminamoon.optimizer.luminav as lum; import numpy as np; p = np.zeros(5, dtype=np.float32); opt = lum.LuminaV([p], lr=1e-3); opt.step(grads=[np.ones(5, dtype=np.float32)]); assert opt.state[p]['step'] == 1; print('Universal fallback verified successfully')"
|
| 87 |
+
|
| 88 |
+
python-matrix:
|
| 89 |
+
name: Python Tests (${{ matrix.os }}, Py${{ matrix.python-version }})
|
| 90 |
+
if: github.event_name != 'release'
|
| 91 |
+
needs: [rust-quality]
|
| 92 |
+
runs-on: ${{ matrix.os }}
|
| 93 |
+
strategy:
|
| 94 |
+
fail-fast: false
|
| 95 |
+
matrix:
|
| 96 |
+
os: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest", "windows-latest", "macos-latest"]') || ((github.ref == 'refs/heads/main' || github.base_ref == 'main') && fromJSON('["ubuntu-latest", "windows-latest"]') || fromJSON('["ubuntu-latest"]')) }}
|
| 97 |
+
python-version: ${{ (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && fromJSON('["3.10", "3.11", "3.12", "3.13", "3.14"]') || fromJSON('["3.14"]') }}
|
| 98 |
+
steps:
|
| 99 |
+
- name: Checkout repository
|
| 100 |
+
uses: actions/checkout@v7
|
| 101 |
+
|
| 102 |
+
- name: Set up Rust toolchain
|
| 103 |
+
uses: dtolnay/rust-toolchain@stable
|
| 104 |
+
|
| 105 |
+
- name: Rust Cache
|
| 106 |
+
uses: Swatinem/rust-cache@v2
|
| 107 |
+
|
| 108 |
+
- name: Set up Python
|
| 109 |
+
uses: actions/setup-python@v7
|
| 110 |
+
with:
|
| 111 |
+
python-version: ${{ matrix.python-version }}
|
| 112 |
+
allow-prereleases: true
|
| 113 |
+
cache: "pip"
|
| 114 |
+
|
| 115 |
+
- name: Upgrade Pip & Install Build Tools
|
| 116 |
+
run: |
|
| 117 |
+
python -m pip install --upgrade pip
|
| 118 |
+
pip install maturin "numpy>=2.0.0" pytest
|
| 119 |
+
|
| 120 |
+
- name: Build & Install LuminaMoon (Native CPython Extension)
|
| 121 |
+
run: python tools/build_wheel.py --install
|
| 122 |
+
|
| 123 |
+
- name: Run Standalone NumPy Verification
|
| 124 |
+
run: python optimizer/tests/test_standalone_numpy.py
|
| 125 |
+
|
| 126 |
+
- name: Run Data Modular Verification
|
| 127 |
+
run: python -B tests/test_data_modular.py
|
| 128 |
+
|
| 129 |
+
- name: Run Adversarial String Verification
|
| 130 |
+
run: python -B tests/test_gpu_vs_cpu_strings.py
|
| 131 |
+
|
| 132 |
+
- name: Install Optional PyTorch Dependency
|
| 133 |
+
id: install-torch
|
| 134 |
+
run: python -c "import os, subprocess, sys; r = subprocess.run([sys.executable, '-m', 'pip', 'install', 'torch>=2.0.0']); open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8').write(f'installed={str(r.returncode == 0).lower()}\n'); print('PyTorch installed:', r.returncode == 0)"
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
- name: Run PyTorch End-to-End Training Tests
|
| 138 |
+
if: steps.install-torch.outputs.installed == 'true'
|
| 139 |
+
run: python optimizer/tests/test_end_to_end_training.py
|
| 140 |
+
|
| 141 |
+
- name: Run LSO-Eval Vision (V-Eval) Protocol Verification
|
| 142 |
+
if: steps.install-torch.outputs.installed == 'true'
|
| 143 |
+
run: python eval/v_eval_suite.py --verify-architecture
|
| 144 |
+
|
| 145 |
+
- name: Verify Universal Dynamic Fallback
|
| 146 |
+
run: |
|
| 147 |
+
python -c "import luminamoon.optimizer.luminav as lum; lum._luminamoon_rs = None; import numpy as np; p = np.zeros(10, dtype=np.float32); opt = lum.LuminaV([p], lr=1e-3); opt.step(grads=[np.ones(10, dtype=np.float32)]); assert opt.state[p]['step'] == 1; print('Dynamic fallback verified successfully')"
|
| 148 |
+
|
| 149 |
+
free-threaded:
|
| 150 |
+
name: Free-Threaded No-GIL (${{ matrix.python-version }})
|
| 151 |
+
if: github.event_name != 'release'
|
| 152 |
+
needs: [rust-quality]
|
| 153 |
+
runs-on: ubuntu-latest
|
| 154 |
+
continue-on-error: true
|
| 155 |
+
strategy:
|
| 156 |
+
fail-fast: false
|
| 157 |
+
matrix:
|
| 158 |
+
python-version: ["3.14t", "3.15t"]
|
| 159 |
+
steps:
|
| 160 |
+
- name: Checkout repository
|
| 161 |
+
uses: actions/checkout@v7
|
| 162 |
+
|
| 163 |
+
- name: Set up Rust toolchain
|
| 164 |
+
uses: dtolnay/rust-toolchain@stable
|
| 165 |
+
|
| 166 |
+
- name: Rust Cache
|
| 167 |
+
uses: Swatinem/rust-cache@v2
|
| 168 |
+
|
| 169 |
+
- name: Set up Python (Free-Threaded)
|
| 170 |
+
uses: actions/setup-python@v7
|
| 171 |
+
with:
|
| 172 |
+
python-version: ${{ matrix.python-version }}
|
| 173 |
+
allow-prereleases: true
|
| 174 |
+
|
| 175 |
+
- name: Install Maturin
|
| 176 |
+
run: |
|
| 177 |
+
python -m pip install --upgrade pip
|
| 178 |
+
pip install maturin
|
| 179 |
+
|
| 180 |
+
- name: Verify abi3t Wheel Compilation & Import
|
| 181 |
+
run: |
|
| 182 |
+
maturin build --release -i python --out dist
|
| 183 |
+
pip install dist/*.whl
|
| 184 |
+
python -c "import luminamoon.optimizer._luminamoon_optimizer_rs, sys; assert not sys._is_gil_enabled(), 'GIL must remain disabled'; print('Free-threaded abi3t extension verified successfully')"
|
| 185 |
+
|
| 186 |
+
release-build:
|
| 187 |
+
name: Distribution Wheel Build (${{ matrix.os }})
|
| 188 |
+
needs: [python-matrix, type-check]
|
| 189 |
+
if: (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && github.event_name != 'release'
|
| 190 |
+
runs-on: ${{ matrix.os }}
|
| 191 |
+
strategy:
|
| 192 |
+
matrix:
|
| 193 |
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
| 194 |
+
steps:
|
| 195 |
+
- name: Checkout repository
|
| 196 |
+
uses: actions/checkout@v7
|
| 197 |
+
|
| 198 |
+
- name: Set up Python for Build Scripts
|
| 199 |
+
if: matrix.os == 'ubuntu-latest' || startsWith(github.ref, 'refs/tags/v')
|
| 200 |
+
uses: actions/setup-python@v7
|
| 201 |
+
with:
|
| 202 |
+
python-version: "3.14"
|
| 203 |
+
|
| 204 |
+
- name: Sync version from Git Tag
|
| 205 |
+
if: startsWith(github.ref, 'refs/tags/v')
|
| 206 |
+
run: python tools/sync_version.py "${{ github.ref_name }}"
|
| 207 |
+
|
| 208 |
+
- name: Build Universal Pure-Python Fallback Wheel (py3-none-any)
|
| 209 |
+
if: matrix.os == 'ubuntu-latest'
|
| 210 |
+
run: python tools/build_universal_wheel.py --out dist
|
| 211 |
+
|
| 212 |
+
- name: Set up Rust toolchain
|
| 213 |
+
uses: dtolnay/rust-toolchain@stable
|
| 214 |
+
|
| 215 |
+
- name: Build sdist (Source Distribution)
|
| 216 |
+
if: matrix.os == 'ubuntu-latest'
|
| 217 |
+
uses: PyO3/maturin-action@v1
|
| 218 |
+
with:
|
| 219 |
+
command: sdist
|
| 220 |
+
args: --out dist
|
| 221 |
+
|
| 222 |
+
- name: Build Wheel via Maturin
|
| 223 |
+
uses: PyO3/maturin-action@v1
|
| 224 |
+
with:
|
| 225 |
+
command: build
|
| 226 |
+
args: --release --out dist
|
| 227 |
+
manylinux: auto
|
| 228 |
+
|
| 229 |
+
- name: Upload Artifacts
|
| 230 |
+
uses: actions/upload-artifact@v7
|
| 231 |
+
with:
|
| 232 |
+
name: wheels-${{ matrix.os }}
|
| 233 |
+
path: dist/*
|
| 234 |
+
|
| 235 |
+
draft-release:
|
| 236 |
+
name: Create Draft GitHub Release
|
| 237 |
+
needs: [release-build]
|
| 238 |
+
if: startsWith(github.ref, 'refs/tags/v') && github.event_name != 'release'
|
| 239 |
+
runs-on: ubuntu-latest
|
| 240 |
+
permissions:
|
| 241 |
+
contents: write
|
| 242 |
+
steps:
|
| 243 |
+
- name: Checkout repository
|
| 244 |
+
uses: actions/checkout@v7
|
| 245 |
+
|
| 246 |
+
- name: Download all built wheels
|
| 247 |
+
uses: actions/download-artifact@v8
|
| 248 |
+
with:
|
| 249 |
+
pattern: wheels-*
|
| 250 |
+
merge-multiple: true
|
| 251 |
+
path: dist
|
| 252 |
+
|
| 253 |
+
- name: Extract Structured Release Notes from Software Changelog
|
| 254 |
+
env:
|
| 255 |
+
RELEASE_TAG: ${{ github.ref_name }}
|
| 256 |
+
run: |
|
| 257 |
+
python3 -c '
|
| 258 |
+
import os, pathlib, re
|
| 259 |
+
tag = os.environ["RELEASE_TAG"].lstrip("v")
|
| 260 |
+
text = pathlib.Path("changelogs/SOFTWARE_CHANGELOG.md").read_text(encoding="utf-8")
|
| 261 |
+
pattern = rf"^## \[{re.escape(tag)}\].*?(?=^## \[|\Z)"
|
| 262 |
+
match = re.search(pattern, text, flags=re.MULTILINE | re.DOTALL)
|
| 263 |
+
body = re.sub(r"\n+---\s*$", "", match.group(0).strip()) if match else f"## [{tag}]\n\nRelease of Loomy (`luminamoon`) v{tag}."
|
| 264 |
+
header = (
|
| 265 |
+
f"{body}\n\n---\n\n"
|
| 266 |
+
"## Installation\n\n"
|
| 267 |
+
f"```bash\npip install --upgrade luminamoon=={tag}\n```\n"
|
| 268 |
+
)
|
| 269 |
+
pathlib.Path("RELEASE_NOTES.md").write_text(header, encoding="utf-8")
|
| 270 |
+
'
|
| 271 |
+
|
| 272 |
+
- name: Create Draft GitHub Release
|
| 273 |
+
uses: softprops/action-gh-release@v2
|
| 274 |
+
with:
|
| 275 |
+
name: "${{ github.ref_name }}"
|
| 276 |
+
body_path: RELEASE_NOTES.md
|
| 277 |
+
files: dist/*
|
| 278 |
+
draft: true
|
| 279 |
+
prerelease: ${{ contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'rc') }}
|
| 280 |
+
make_latest: ${{ !contains(github.ref_name, 'a') && !contains(github.ref_name, 'b') && !contains(github.ref_name, 'rc') }}
|
| 281 |
+
generate_release_notes: true
|
| 282 |
+
|
| 283 |
+
publish-pypi:
|
| 284 |
+
name: Publish Wheels to PyPI
|
| 285 |
+
needs: [draft-release]
|
| 286 |
+
if: always() && (github.event_name == 'release' && github.event.action == 'published')
|
| 287 |
+
runs-on: ubuntu-latest
|
| 288 |
+
permissions:
|
| 289 |
+
contents: read
|
| 290 |
+
id-token: write
|
| 291 |
+
steps:
|
| 292 |
+
- name: Checkout repository
|
| 293 |
+
uses: actions/checkout@v7
|
| 294 |
+
|
| 295 |
+
- name: Download release assets via GitHub CLI
|
| 296 |
+
env:
|
| 297 |
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 298 |
+
RELEASE_TAG: ${{ github.event.release.tag_name || github.ref_name }}
|
| 299 |
+
run: |
|
| 300 |
+
mkdir -p dist
|
| 301 |
+
gh release download "$RELEASE_TAG" --dir dist --pattern "*.whl" --pattern "*.tar.gz"
|
| 302 |
+
ls -la dist
|
| 303 |
+
|
| 304 |
+
- name: Publish Distribution to PyPI
|
| 305 |
+
uses: pypa/gh-action-pypi-publish@release/v1
|
| 306 |
+
with:
|
| 307 |
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
| 308 |
+
packages-dir: dist/
|
| 309 |
skip-existing: true
|
.github/workflows/docs.yml
CHANGED
|
@@ -1,83 +1,83 @@
|
|
| 1 |
-
name: Documentation Portal (GitHub Pages)
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches: [ "main" ]
|
| 6 |
-
pull_request:
|
| 7 |
-
branches: [ "main", "development" ]
|
| 8 |
-
paths:
|
| 9 |
-
- "docs/**"
|
| 10 |
-
- "mkdocs.yml"
|
| 11 |
-
- "README.md"
|
| 12 |
-
- "**/README.md"
|
| 13 |
-
- "changelogs/**"
|
| 14 |
-
- "SECURITY.md"
|
| 15 |
-
- "tools/prepare_mkdocs.py"
|
| 16 |
-
- "tools/prune_deployments.py"
|
| 17 |
-
- ".github/workflows/docs.yml"
|
| 18 |
-
workflow_dispatch:
|
| 19 |
-
|
| 20 |
-
permissions:
|
| 21 |
-
contents: read
|
| 22 |
-
pages: write
|
| 23 |
-
id-token: write
|
| 24 |
-
deployments: write
|
| 25 |
-
|
| 26 |
-
concurrency:
|
| 27 |
-
group: "pages-${{ github.ref }}"
|
| 28 |
-
cancel-in-progress: true
|
| 29 |
-
|
| 30 |
-
jobs:
|
| 31 |
-
build:
|
| 32 |
-
name: Build MkDocs Material Site
|
| 33 |
-
runs-on: ubuntu-latest
|
| 34 |
-
steps:
|
| 35 |
-
- name: Checkout repository
|
| 36 |
-
uses: actions/checkout@v7
|
| 37 |
-
|
| 38 |
-
- name: Set up Python
|
| 39 |
-
uses: actions/setup-python@v7
|
| 40 |
-
with:
|
| 41 |
-
python-version: "3.14"
|
| 42 |
-
cache: "pip"
|
| 43 |
-
|
| 44 |
-
- name: Install MkDocs Material
|
| 45 |
-
run: |
|
| 46 |
-
python -m pip install --upgrade pip
|
| 47 |
-
pip install "mkdocs-material>=9.5.0"
|
| 48 |
-
|
| 49 |
-
- name: Prepare Documentation Stage & Normalize Math/Links
|
| 50 |
-
run: python tools/prepare_mkdocs.py
|
| 51 |
-
|
| 52 |
-
- name: Build Static Documentation Site
|
| 53 |
-
run: mkdocs build
|
| 54 |
-
|
| 55 |
-
- name: Upload GitHub Pages Artifact
|
| 56 |
-
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
| 57 |
-
uses: actions/upload-pages-artifact@v5
|
| 58 |
-
with:
|
| 59 |
-
path: site
|
| 60 |
-
|
| 61 |
-
deploy:
|
| 62 |
-
name: Deploy to GitHub Pages
|
| 63 |
-
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
| 64 |
-
needs: build
|
| 65 |
-
runs-on: ubuntu-latest
|
| 66 |
-
environment:
|
| 67 |
-
name: github-pages
|
| 68 |
-
url: ${{ steps.deployment.outputs.page_url }}
|
| 69 |
-
steps:
|
| 70 |
-
- name: Deploy to GitHub Pages
|
| 71 |
-
id: deployment
|
| 72 |
-
uses: actions/deploy-pages@v5
|
| 73 |
-
|
| 74 |
-
- name: Checkout repository for tools
|
| 75 |
-
if: success()
|
| 76 |
-
uses: actions/checkout@v7
|
| 77 |
-
|
| 78 |
-
- name: Prune Older Deployments (Retention Max 3)
|
| 79 |
-
if: success()
|
| 80 |
-
env:
|
| 81 |
-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 82 |
-
GH_REPO: ${{ github.repository }}
|
| 83 |
-
run: python tools/prune_deployments.py --retention 3
|
|
|
|
| 1 |
+
name: Documentation Portal (GitHub Pages)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main" ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ "main", "development" ]
|
| 8 |
+
paths:
|
| 9 |
+
- "docs/**"
|
| 10 |
+
- "mkdocs.yml"
|
| 11 |
+
- "README.md"
|
| 12 |
+
- "**/README.md"
|
| 13 |
+
- "changelogs/**"
|
| 14 |
+
- "SECURITY.md"
|
| 15 |
+
- "tools/prepare_mkdocs.py"
|
| 16 |
+
- "tools/prune_deployments.py"
|
| 17 |
+
- ".github/workflows/docs.yml"
|
| 18 |
+
workflow_dispatch:
|
| 19 |
+
|
| 20 |
+
permissions:
|
| 21 |
+
contents: read
|
| 22 |
+
pages: write
|
| 23 |
+
id-token: write
|
| 24 |
+
deployments: write
|
| 25 |
+
|
| 26 |
+
concurrency:
|
| 27 |
+
group: "pages-${{ github.ref }}"
|
| 28 |
+
cancel-in-progress: true
|
| 29 |
+
|
| 30 |
+
jobs:
|
| 31 |
+
build:
|
| 32 |
+
name: Build MkDocs Material Site
|
| 33 |
+
runs-on: ubuntu-latest
|
| 34 |
+
steps:
|
| 35 |
+
- name: Checkout repository
|
| 36 |
+
uses: actions/checkout@v7
|
| 37 |
+
|
| 38 |
+
- name: Set up Python
|
| 39 |
+
uses: actions/setup-python@v7
|
| 40 |
+
with:
|
| 41 |
+
python-version: "3.14"
|
| 42 |
+
cache: "pip"
|
| 43 |
+
|
| 44 |
+
- name: Install MkDocs Material
|
| 45 |
+
run: |
|
| 46 |
+
python -m pip install --upgrade pip
|
| 47 |
+
pip install "mkdocs-material>=9.5.0"
|
| 48 |
+
|
| 49 |
+
- name: Prepare Documentation Stage & Normalize Math/Links
|
| 50 |
+
run: python tools/prepare_mkdocs.py
|
| 51 |
+
|
| 52 |
+
- name: Build Static Documentation Site
|
| 53 |
+
run: mkdocs build
|
| 54 |
+
|
| 55 |
+
- name: Upload GitHub Pages Artifact
|
| 56 |
+
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
| 57 |
+
uses: actions/upload-pages-artifact@v5
|
| 58 |
+
with:
|
| 59 |
+
path: site
|
| 60 |
+
|
| 61 |
+
deploy:
|
| 62 |
+
name: Deploy to GitHub Pages
|
| 63 |
+
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
| 64 |
+
needs: build
|
| 65 |
+
runs-on: ubuntu-latest
|
| 66 |
+
environment:
|
| 67 |
+
name: github-pages
|
| 68 |
+
url: ${{ steps.deployment.outputs.page_url }}
|
| 69 |
+
steps:
|
| 70 |
+
- name: Deploy to GitHub Pages
|
| 71 |
+
id: deployment
|
| 72 |
+
uses: actions/deploy-pages@v5
|
| 73 |
+
|
| 74 |
+
- name: Checkout repository for tools
|
| 75 |
+
if: success()
|
| 76 |
+
uses: actions/checkout@v7
|
| 77 |
+
|
| 78 |
+
- name: Prune Older Deployments (Retention Max 3)
|
| 79 |
+
if: success()
|
| 80 |
+
env:
|
| 81 |
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
| 82 |
+
GH_REPO: ${{ github.repository }}
|
| 83 |
+
run: python tools/prune_deployments.py --retention 3
|
.github/workflows/hf_sync.yml
CHANGED
|
@@ -1,49 +1,49 @@
|
|
| 1 |
-
name: Hugging Face Hub Auto-Sync
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches: [ "main" ]
|
| 6 |
-
tags: [ "v*" ]
|
| 7 |
-
pull_request:
|
| 8 |
-
branches: [ "main" ]
|
| 9 |
-
release:
|
| 10 |
-
types: [published]
|
| 11 |
-
workflow_dispatch:
|
| 12 |
-
|
| 13 |
-
concurrency:
|
| 14 |
-
group: "hf-sync-${{ github.ref }}"
|
| 15 |
-
cancel-in-progress: true
|
| 16 |
-
|
| 17 |
-
permissions:
|
| 18 |
-
contents: read
|
| 19 |
-
|
| 20 |
-
jobs:
|
| 21 |
-
sync-huggingface:
|
| 22 |
-
name: Stage & Sync Hugging Face Repositories
|
| 23 |
-
runs-on: ubuntu-latest
|
| 24 |
-
steps:
|
| 25 |
-
- name: Checkout repository
|
| 26 |
-
uses: actions/checkout@v7
|
| 27 |
-
|
| 28 |
-
- name: Set up Python
|
| 29 |
-
uses: actions/setup-python@v7
|
| 30 |
-
with:
|
| 31 |
-
python-version: "3.14"
|
| 32 |
-
cache: "pip"
|
| 33 |
-
|
| 34 |
-
- name: Install Dependencies
|
| 35 |
-
run: |
|
| 36 |
-
python -m pip install --upgrade pip
|
| 37 |
-
pip install "huggingface_hub>=0.25.0" --extra-index-url https://download.pytorch.org/whl/cpu torch
|
| 38 |
-
|
| 39 |
-
- name: Stage & Verify Hugging Face Bundles (PR Check)
|
| 40 |
-
if: github.event_name == 'pull_request'
|
| 41 |
-
run: python tools/sync_huggingface.py
|
| 42 |
-
|
| 43 |
-
- name: Stage, Verify & Push to Hugging Face Hub (main / release)
|
| 44 |
-
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
| 45 |
-
env:
|
| 46 |
-
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 47 |
-
HF_ORG: ${{ secrets.HF_ORG }}
|
| 48 |
-
HF_USERNAME: ${{ secrets.HF_USERNAME }}
|
| 49 |
-
run: python tools/sync_huggingface.py --push
|
|
|
|
| 1 |
+
name: Hugging Face Hub Auto-Sync
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main" ]
|
| 6 |
+
tags: [ "v*" ]
|
| 7 |
+
pull_request:
|
| 8 |
+
branches: [ "main" ]
|
| 9 |
+
release:
|
| 10 |
+
types: [published]
|
| 11 |
+
workflow_dispatch:
|
| 12 |
+
|
| 13 |
+
concurrency:
|
| 14 |
+
group: "hf-sync-${{ github.ref }}"
|
| 15 |
+
cancel-in-progress: true
|
| 16 |
+
|
| 17 |
+
permissions:
|
| 18 |
+
contents: read
|
| 19 |
+
|
| 20 |
+
jobs:
|
| 21 |
+
sync-huggingface:
|
| 22 |
+
name: Stage & Sync Hugging Face Repositories
|
| 23 |
+
runs-on: ubuntu-latest
|
| 24 |
+
steps:
|
| 25 |
+
- name: Checkout repository
|
| 26 |
+
uses: actions/checkout@v7
|
| 27 |
+
|
| 28 |
+
- name: Set up Python
|
| 29 |
+
uses: actions/setup-python@v7
|
| 30 |
+
with:
|
| 31 |
+
python-version: "3.14"
|
| 32 |
+
cache: "pip"
|
| 33 |
+
|
| 34 |
+
- name: Install Dependencies
|
| 35 |
+
run: |
|
| 36 |
+
python -m pip install --upgrade pip
|
| 37 |
+
pip install "huggingface_hub>=0.25.0" --extra-index-url https://download.pytorch.org/whl/cpu torch
|
| 38 |
+
|
| 39 |
+
- name: Stage & Verify Hugging Face Bundles (PR Check)
|
| 40 |
+
if: github.event_name == 'pull_request'
|
| 41 |
+
run: python tools/sync_huggingface.py
|
| 42 |
+
|
| 43 |
+
- name: Stage, Verify & Push to Hugging Face Hub (main / release)
|
| 44 |
+
if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release' || github.event_name == 'workflow_dispatch')
|
| 45 |
+
env:
|
| 46 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 47 |
+
HF_ORG: ${{ secrets.HF_ORG }}
|
| 48 |
+
HF_USERNAME: ${{ secrets.HF_USERNAME }}
|
| 49 |
+
run: python tools/sync_huggingface.py --push
|
.github/workflows/security.yml
CHANGED
|
@@ -1,91 +1,91 @@
|
|
| 1 |
-
name: Security & Vulnerability Audit (CVE / CWE / SAST)
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
branches: [ "main" ]
|
| 6 |
-
pull_request:
|
| 7 |
-
branches: [ "main", "development" ]
|
| 8 |
-
schedule:
|
| 9 |
-
- cron: "0 3 * * 1"
|
| 10 |
-
workflow_dispatch:
|
| 11 |
-
|
| 12 |
-
permissions:
|
| 13 |
-
contents: read
|
| 14 |
-
|
| 15 |
-
concurrency:
|
| 16 |
-
group: "security-${{ github.ref }}"
|
| 17 |
-
cancel-in-progress: true
|
| 18 |
-
|
| 19 |
-
jobs:
|
| 20 |
-
codeql-cwe-sast:
|
| 21 |
-
name: CodeQL SAST & CWE Analysis (${{ matrix.language }})
|
| 22 |
-
runs-on: ubuntu-latest
|
| 23 |
-
permissions:
|
| 24 |
-
actions: read
|
| 25 |
-
contents: read
|
| 26 |
-
security-events: write
|
| 27 |
-
strategy:
|
| 28 |
-
fail-fast: false
|
| 29 |
-
matrix:
|
| 30 |
-
include:
|
| 31 |
-
- language: python
|
| 32 |
-
build-mode: none
|
| 33 |
-
- language: actions
|
| 34 |
-
build-mode: none
|
| 35 |
-
steps:
|
| 36 |
-
- name: Checkout repository
|
| 37 |
-
uses: actions/checkout@v7
|
| 38 |
-
|
| 39 |
-
- name: Initialize CodeQL (CWE Static Analyzer)
|
| 40 |
-
uses: github/codeql-action/init@v4
|
| 41 |
-
with:
|
| 42 |
-
languages: ${{ matrix.language }}
|
| 43 |
-
build-mode: ${{ matrix.build-mode }}
|
| 44 |
-
|
| 45 |
-
- name: Perform CodeQL CWE Analysis & Upload SARIF
|
| 46 |
-
uses: github/codeql-action/analyze@v4
|
| 47 |
-
with:
|
| 48 |
-
category: "/language:${{ matrix.language }}"
|
| 49 |
-
|
| 50 |
-
supply-chain-cve-audit:
|
| 51 |
-
name: RustSec, OSV & PyPA CVE Supply-Chain Audit
|
| 52 |
-
runs-on: ubuntu-latest
|
| 53 |
-
steps:
|
| 54 |
-
- name: Checkout repository
|
| 55 |
-
uses: actions/checkout@v7
|
| 56 |
-
|
| 57 |
-
- name: Set up Python
|
| 58 |
-
uses: actions/setup-python@v7
|
| 59 |
-
with:
|
| 60 |
-
python-version: "3.14"
|
| 61 |
-
cache: "pip"
|
| 62 |
-
|
| 63 |
-
- name: Install PyPA pip-audit & Build Tools
|
| 64 |
-
run: |
|
| 65 |
-
python -m pip install --upgrade pip
|
| 66 |
-
pip install "pip-audit>=2.7.0"
|
| 67 |
-
|
| 68 |
-
- name: Audit Cargo.lock (RustSec/OSV/CVE) & Repository Secret Hygiene
|
| 69 |
-
run: python tools/security_audit.py
|
| 70 |
-
|
| 71 |
-
- name: Audit Python Environment via PyPA pip-audit (OSV / PYSEC / CVE)
|
| 72 |
-
run: |
|
| 73 |
-
python tools/build_universal_wheel.py --out dist
|
| 74 |
-
pip install dist/luminamoon-*-py3-none-any.whl
|
| 75 |
-
pip-audit --desc on
|
| 76 |
-
|
| 77 |
-
dependency-review:
|
| 78 |
-
name: PR Dependency & License Vulnerability Gate
|
| 79 |
-
if: github.event_name == 'pull_request'
|
| 80 |
-
runs-on: ubuntu-latest
|
| 81 |
-
permissions:
|
| 82 |
-
contents: read
|
| 83 |
-
pull-requests: read
|
| 84 |
-
steps:
|
| 85 |
-
- name: Checkout repository
|
| 86 |
-
uses: actions/checkout@v7
|
| 87 |
-
|
| 88 |
-
- name: Dependency Review (CVE / GHSA Severity Gate)
|
| 89 |
-
uses: actions/dependency-review-action@v5
|
| 90 |
-
with:
|
| 91 |
-
fail-on-severity: moderate
|
|
|
|
| 1 |
+
name: Security & Vulnerability Audit (CVE / CWE / SAST)
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main" ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ "main", "development" ]
|
| 8 |
+
schedule:
|
| 9 |
+
- cron: "0 3 * * 1"
|
| 10 |
+
workflow_dispatch:
|
| 11 |
+
|
| 12 |
+
permissions:
|
| 13 |
+
contents: read
|
| 14 |
+
|
| 15 |
+
concurrency:
|
| 16 |
+
group: "security-${{ github.ref }}"
|
| 17 |
+
cancel-in-progress: true
|
| 18 |
+
|
| 19 |
+
jobs:
|
| 20 |
+
codeql-cwe-sast:
|
| 21 |
+
name: CodeQL SAST & CWE Analysis (${{ matrix.language }})
|
| 22 |
+
runs-on: ubuntu-latest
|
| 23 |
+
permissions:
|
| 24 |
+
actions: read
|
| 25 |
+
contents: read
|
| 26 |
+
security-events: write
|
| 27 |
+
strategy:
|
| 28 |
+
fail-fast: false
|
| 29 |
+
matrix:
|
| 30 |
+
include:
|
| 31 |
+
- language: python
|
| 32 |
+
build-mode: none
|
| 33 |
+
- language: actions
|
| 34 |
+
build-mode: none
|
| 35 |
+
steps:
|
| 36 |
+
- name: Checkout repository
|
| 37 |
+
uses: actions/checkout@v7
|
| 38 |
+
|
| 39 |
+
- name: Initialize CodeQL (CWE Static Analyzer)
|
| 40 |
+
uses: github/codeql-action/init@v4
|
| 41 |
+
with:
|
| 42 |
+
languages: ${{ matrix.language }}
|
| 43 |
+
build-mode: ${{ matrix.build-mode }}
|
| 44 |
+
|
| 45 |
+
- name: Perform CodeQL CWE Analysis & Upload SARIF
|
| 46 |
+
uses: github/codeql-action/analyze@v4
|
| 47 |
+
with:
|
| 48 |
+
category: "/language:${{ matrix.language }}"
|
| 49 |
+
|
| 50 |
+
supply-chain-cve-audit:
|
| 51 |
+
name: RustSec, OSV & PyPA CVE Supply-Chain Audit
|
| 52 |
+
runs-on: ubuntu-latest
|
| 53 |
+
steps:
|
| 54 |
+
- name: Checkout repository
|
| 55 |
+
uses: actions/checkout@v7
|
| 56 |
+
|
| 57 |
+
- name: Set up Python
|
| 58 |
+
uses: actions/setup-python@v7
|
| 59 |
+
with:
|
| 60 |
+
python-version: "3.14"
|
| 61 |
+
cache: "pip"
|
| 62 |
+
|
| 63 |
+
- name: Install PyPA pip-audit & Build Tools
|
| 64 |
+
run: |
|
| 65 |
+
python -m pip install --upgrade pip
|
| 66 |
+
pip install "pip-audit>=2.7.0"
|
| 67 |
+
|
| 68 |
+
- name: Audit Cargo.lock (RustSec/OSV/CVE) & Repository Secret Hygiene
|
| 69 |
+
run: python tools/security_audit.py
|
| 70 |
+
|
| 71 |
+
- name: Audit Python Environment via PyPA pip-audit (OSV / PYSEC / CVE)
|
| 72 |
+
run: |
|
| 73 |
+
python tools/build_universal_wheel.py --out dist
|
| 74 |
+
pip install dist/luminamoon-*-py3-none-any.whl
|
| 75 |
+
pip-audit --desc on
|
| 76 |
+
|
| 77 |
+
dependency-review:
|
| 78 |
+
name: PR Dependency & License Vulnerability Gate
|
| 79 |
+
if: github.event_name == 'pull_request'
|
| 80 |
+
runs-on: ubuntu-latest
|
| 81 |
+
permissions:
|
| 82 |
+
contents: read
|
| 83 |
+
pull-requests: read
|
| 84 |
+
steps:
|
| 85 |
+
- name: Checkout repository
|
| 86 |
+
uses: actions/checkout@v7
|
| 87 |
+
|
| 88 |
+
- name: Dependency Review (CVE / GHSA Severity Gate)
|
| 89 |
+
uses: actions/dependency-review-action@v5
|
| 90 |
+
with:
|
| 91 |
+
fail-on-severity: moderate
|
.gitignore
CHANGED
|
@@ -1,54 +1,54 @@
|
|
| 1 |
-
/target/
|
| 2 |
-
/optimizer/target/
|
| 3 |
-
**/target/
|
| 4 |
-
__pycache__/
|
| 5 |
-
*.py[cod]
|
| 6 |
-
*$py.class
|
| 7 |
-
*.so
|
| 8 |
-
*.pyd
|
| 9 |
-
*.dll
|
| 10 |
-
*.dylib
|
| 11 |
-
*.exe
|
| 12 |
-
*.lib
|
| 13 |
-
*.a
|
| 14 |
-
*.o
|
| 15 |
-
*.obj
|
| 16 |
-
*.pdb
|
| 17 |
-
*.exp
|
| 18 |
-
.pytest_cache/
|
| 19 |
-
.ruff_cache/
|
| 20 |
-
.mypy_cache/
|
| 21 |
-
build/
|
| 22 |
-
dist/
|
| 23 |
-
wheels/
|
| 24 |
-
*.whl
|
| 25 |
-
*.egg-info/
|
| 26 |
-
.venv/
|
| 27 |
-
venv/
|
| 28 |
-
.env
|
| 29 |
-
.env.*
|
| 30 |
-
!.env.example
|
| 31 |
-
.DS_Store
|
| 32 |
-
Thumbs.db
|
| 33 |
-
*.log
|
| 34 |
-
.coverage
|
| 35 |
-
htmlcov/
|
| 36 |
-
.benchmarks/
|
| 37 |
-
.vscode/
|
| 38 |
-
.idea/
|
| 39 |
-
optimizer/tools/aot_compiler/glslang/
|
| 40 |
-
.luminamoon_cache/
|
| 41 |
-
*.meta
|
| 42 |
-
*.meta.dedup
|
| 43 |
-
!eval/artifacts/**/*.meta
|
| 44 |
-
!eval/artifacts/**/*.meta.dedup
|
| 45 |
-
x.py
|
| 46 |
-
logs/
|
| 47 |
-
site/
|
| 48 |
-
docs/overview.md
|
| 49 |
-
docs/security.md
|
| 50 |
-
docs/lso_eval.md
|
| 51 |
-
docs/optimize/standalone_guide.md
|
| 52 |
-
docs/dataloader/standalone_guide.md
|
| 53 |
-
docs/changelogs/
|
| 54 |
-
scratch/
|
|
|
|
| 1 |
+
/target/
|
| 2 |
+
/optimizer/target/
|
| 3 |
+
**/target/
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.py[cod]
|
| 6 |
+
*$py.class
|
| 7 |
+
*.so
|
| 8 |
+
*.pyd
|
| 9 |
+
*.dll
|
| 10 |
+
*.dylib
|
| 11 |
+
*.exe
|
| 12 |
+
*.lib
|
| 13 |
+
*.a
|
| 14 |
+
*.o
|
| 15 |
+
*.obj
|
| 16 |
+
*.pdb
|
| 17 |
+
*.exp
|
| 18 |
+
.pytest_cache/
|
| 19 |
+
.ruff_cache/
|
| 20 |
+
.mypy_cache/
|
| 21 |
+
build/
|
| 22 |
+
dist/
|
| 23 |
+
wheels/
|
| 24 |
+
*.whl
|
| 25 |
+
*.egg-info/
|
| 26 |
+
.venv/
|
| 27 |
+
venv/
|
| 28 |
+
.env
|
| 29 |
+
.env.*
|
| 30 |
+
!.env.example
|
| 31 |
+
.DS_Store
|
| 32 |
+
Thumbs.db
|
| 33 |
+
*.log
|
| 34 |
+
.coverage
|
| 35 |
+
htmlcov/
|
| 36 |
+
.benchmarks/
|
| 37 |
+
.vscode/
|
| 38 |
+
.idea/
|
| 39 |
+
optimizer/tools/aot_compiler/glslang/
|
| 40 |
+
.luminamoon_cache/
|
| 41 |
+
*.meta
|
| 42 |
+
*.meta.dedup
|
| 43 |
+
!eval/artifacts/**/*.meta
|
| 44 |
+
!eval/artifacts/**/*.meta.dedup
|
| 45 |
+
x.py
|
| 46 |
+
logs/
|
| 47 |
+
site/
|
| 48 |
+
docs/overview.md
|
| 49 |
+
docs/security.md
|
| 50 |
+
docs/lso_eval.md
|
| 51 |
+
docs/optimize/standalone_guide.md
|
| 52 |
+
docs/dataloader/standalone_guide.md
|
| 53 |
+
docs/changelogs/
|
| 54 |
+
scratch/
|
CHANGELOG.md
CHANGED
|
@@ -1,430 +1,430 @@
|
|
| 1 |
-
# LuminaV Optimizer Algorithm Changelog
|
| 2 |
-
|
| 3 |
-
All notable changes to the LuminaV Optimizer algorithm are documented in this file.
|
| 4 |
-
|
| 5 |
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
-
|
| 7 |
-
* [Software Package Changelog (`luminamoon`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/SOFTWARE_CHANGELOG.md)
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
## [1.6.0] - 2026-11-03
|
| 12 |
-
|
| 13 |
-
### Added
|
| 14 |
-
- **T-Family (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 15 |
-
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$, computing the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounding the lookahead update $\tilde{m}_t$ via the **Triangle Inequality Envelope** $\mathcal{E}_t^{(\text{T2})}$:
|
| 16 |
-
|
| 17 |
-
$\displaystyle d_t = g_t - m _ {t-1}, \qquad v_t = \beta_2 v _ {t-1} + (1 - \beta_2) d_t^2$
|
| 18 |
-
|
| 19 |
-
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \qquad w_1 = \beta_1^2, \qquad w_2 = 1 - \beta_1^2$
|
| 20 |
-
|
| 21 |
-
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 22 |
-
|
| 23 |
-
By the Triangle Inequality ($|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$), $|u_t^{(\text{T2})}| < 1$ is guaranteed without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, and $0.5\lambda \sigma_t$ provides belief-variance damping.
|
| 24 |
-
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Combines the single-buffer **Triangle-Defect** denominator $\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ (using residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below, reducing optimizer state memory by 50% via the Triangle Inequality.
|
| 25 |
-
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas:
|
| 26 |
-
|
| 27 |
-
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 28 |
-
* Keeps 100% backward compatibility and identical outputs for existing modes (`buffer=1`, `buffer=2`, and `version="1.5.5"`).
|
| 29 |
-
|
| 30 |
-
---
|
| 31 |
-
|
| 32 |
-
## [1.5.5] - 2026-10-29
|
| 33 |
-
|
| 34 |
-
### Added
|
| 35 |
-
- **Contiguous Bucket Fusion (`fuse_tensors: bool = False`)**:
|
| 36 |
-
* Added opt-in contiguous parameter and state coalescing (`_build_contiguous_buckets`) that groups parameters within each `(param_group, device, dtype)` domain $\mathcal{B}_{g,d,\tau}$ into contiguous 1D storage (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) and rebinds `p.data`, `p.grad`, and `self.state[p]` entries as zero-copy views (`flat_*[offset : offset + n].view(p.shape)`).
|
| 37 |
-
* Eliminates per-tensor CPU-to-GPU kernel launch latency on fragmented vision and edge architectures by executing an entire bucket in a single fused kernel dispatch while computing `cautious_scale` and `radial` trust bounds across the bucket domain $\mathcal{B}_{g,d,\tau}$.
|
| 38 |
-
* Added in-place `zero_grad()` view preservation (`flat_g.zero_()`) and full `state_dict()` / `load_state_dict()` checkpoint compatibility without leaking temporary buffer keys (LSO-Eval Stage II compliant).
|
| 39 |
-
- **Triton JIT Pre-Warmup (`prewarm_jit: bool = True`)**:
|
| 40 |
-
* Added automatic initialization-time Triton JIT compilation (`_prewarm_all_configs`) using 64-element dummy buffers (`128–256 bytes` VRAM) with `lr=0.0` and `weight_decay=0.0` across both Step 1 (`pass1` + `pass2`) and Step 2+ (`fused_single_pass` + `post_step_ema`) paths with target `BLOCK_SIZE` specialization (`1024` or `2048`), eliminating first-step compilation stalls.
|
| 41 |
-
- **Bounded Bucket Chunking & Sequential In-Place Migration (`bucket_cap_mb: float = 500.0`)**:
|
| 42 |
-
* Bound contiguous bucket coalescing by an upper memory capacity ceiling $C_{\text{cap}} = 500\text{ MB}$, partitioning model parameters into ordered contiguous chunks $\mathcal{B}^{(1)}, \mathcal{B}^{(2)}, \dots, \mathcal{B}^{(K)}$ such that $\sum_{p \in \mathcal{B}^{(k)}} \text{bytes}(p) \le C_{\text{cap}}$ (with giant single layers $> C_{\text{cap}}$ isolated into individual buckets).
|
| 43 |
-
* Enforced sequential in-place migration with immediate parameter storage recycling (`del old_data`, `del old_grad`) and garbage collection, constraining peak transient memory allocation spikes during optimizer initialization to at most $\min(\text{model bytes}, C _ {\text{cap}})$.
|
| 44 |
-
|
| 45 |
-
---
|
| 46 |
-
|
| 47 |
-
## [1.5.4] - 2026-10-29
|
| 48 |
-
|
| 49 |
-
### Added
|
| 50 |
-
- **In-Container Native Bitwise-BF16 State Promotion (`dtype_mode == 2`, `master_weights="none"`)**:
|
| 51 |
-
* Promoted 16-bit optimizer state buffers ($m_t$ via stochastic rounding `f32_to_bf16_bits_sr` and $v_t$ via round-to-nearest-even `f32_to_bf16_bits_rne`) to native Bitwise-BF16 stored inside the existing 16-bit container during pure `FP16` execution, expanding state exponent dynamic range to 8 bits ($1.18 \times 10^{-38}$) with zero additional VRAM overhead.
|
| 52 |
-
|
| 53 |
-
### Changed
|
| 54 |
-
- **Unified Denominator Stability Constant ($\epsilon_{\text{eff}} = \epsilon = 10^{-8}$)**:
|
| 55 |
-
* Unified the effective denominator constant $\epsilon_{\text{eff}} = \epsilon = 10^{-8}$ across all precision modes and hardware backends (replacing the legacy `FP16` floor clamp $\max(\epsilon, 10^{-4})$ introduced in `1.1.5`), preventing update velocity attenuation and late-stage convergence loss resets when $\sqrt{v_t} \ll 10^{-4}$.
|
| 56 |
-
|
| 57 |
-
### Removed
|
| 58 |
-
- **Centered Defect Compensation (`centered_defect`, $\mu_d$)**:
|
| 59 |
-
* Removed the `centered_defect` hyperparameter and the global defect mean reduction $\mu_d = \frac{1}{N}\sum_{i=1}^N (g_{t,i} - m_{t-1,i})$ across both Dual-Buffer Convex Lookahead Momentum (`buffer=2` / CLM) and Single-Buffer Triangle-Defect Convex Lookahead Momentum (`buffer=1` / TD-CLM).
|
| 60 |
-
* Simplified the instantaneous gradient-momentum residual to purely local coordinate-wise differences:
|
| 61 |
-
|
| 62 |
-
$\displaystyle d_t^{(\text{B1})} = g_t - m_{t-1}, \qquad d_t^{(\text{B2})} = g_t - m_t$
|
| 63 |
-
|
| 64 |
-
* Eliminated the pre-step global reduction pass (`_lumina_diff_sum_kernel` in Triton JIT/AOT, `diff_sum.comp` in Vulkan SPIR-V, `reduce_diff_sum_simd` in x86\_64 AVX-512/AVX2, and slot 5 of the 6-element `ScratchBuffer`), removing cross-lane/cross-rank reduction latency while retaining the 6-element physical `ScratchBuffer` layout (`[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale, _reserved]`, 5 active slots + 1 reserved slot for ABI alignment).
|
| 65 |
-
|
| 66 |
-
---
|
| 67 |
-
|
| 68 |
-
## [1.5.3] - 2026-10-20
|
| 69 |
-
|
| 70 |
-
No versions were published between 1.3.3 and 1.5.3. This release notes all changes made since version 1.3.3.
|
| 71 |
-
|
| 72 |
-
### Added
|
| 73 |
-
- **Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM, `buffer=1`)**:
|
| 74 |
-
* Replaced the legacy 3-pass RMS single-buffer update with a single-pass formulation:
|
| 75 |
-
|
| 76 |
-
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \quad w_1 = \beta_1^2, \quad w_2 = 1 - \beta_1^2$
|
| 77 |
-
|
| 78 |
-
$\displaystyle \mathcal{E} _ t = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t^{(\text{B1})}| + \epsilon _ {\text{eff}}$
|
| 79 |
-
|
| 80 |
-
$\displaystyle u_t^{(\text{B1})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t} \in (-1, 1)$
|
| 81 |
-
|
| 82 |
-
* In 1.3.3, single-buffer mode required three sequential GPU kernel launches (`_lumina_v1_pass1_kernel`, `_lumina_v1_pass2_kernel`, `_lumina_v1_pass3_update_kernel`) and global atomic reductions across thread blocks to compute tensor-wide root-mean-square ($\text{rms} = \sqrt{\frac{1}{N}\sum \tilde{m} _ i^2 + \epsilon}$).
|
| 83 |
-
* TD-CLM replaces the tensor-wide reduction with coordinate-wise envelope bounding via the Triangle Inequality Envelope $\mathcal{E} _ t$. This reduces single-buffer execution to a single GPU pass and eliminates all $\tanh$ evaluations in the momentum path.
|
| 84 |
-
* Calibrated the defect penalty weight $\lambda$ to the symmetric spike-reversal equilibrium:
|
| 85 |
-
|
| 86 |
-
$\displaystyle \lambda^* = \frac{2(1 - \beta_1^2)^2}{4\beta_1^2 - 3}$
|
| 87 |
-
|
| 88 |
-
For the default $\beta_1 = 0.9$, $\lambda^* \approx 0.3008$, setting the default parameter to `lam=0.3`.
|
| 89 |
-
|
| 90 |
-
- **Padé [13/6] Rational Approximation (`tanh_pade_f32`)**:
|
| 91 |
-
* Implemented a rational Padé [13/6] polynomial for hyperbolic tangent calculations:
|
| 92 |
-
|
| 93 |
-
$\displaystyle \tanh _ {\text{Pade}}(x) = \frac{x(1 + c_1 x^2 + c_2 x^4 + c_3 x^6 + c_4 x^8 + c_5 x^{10} + c_6 x^{12})}{1 + d_1 x^2 + d_2 x^4 + d_3 x^6}$
|
| 94 |
-
|
| 95 |
-
* Evaluated over the clamped domain $x \in [-7.9988117, 7.9988117]$ with asymptotic saturation to $\pm 1.0$.
|
| 96 |
-
* In 1.3.3, `_triton_tanh_fast` used hardware-dependent SFU instructions (`tl.math.tanh`) or analytical sigmoid fallbacks (`2 / (1 + exp(-2x)) - 1`). Different GPU architectures (e.g. Turing, Ampere, Hopper) evaluate SFU transcendental functions with slightly different mantissa rounding, leading to bitwise divergence during distributed training.
|
| 97 |
-
* Padé [13/6] provides uniform precision with $\lVert \tanh _ {\text{Pade}}(x) - \tanh(x) \rVert _ \infty < 1.5 \times 10^{-6}$ across CPU Scalar, AVX2, AVX-512, Vulkan SPIR-V, and Triton GPU kernels.
|
| 98 |
-
|
| 99 |
-
- **Centered Innovation Defect (`centered_defect`)**:
|
| 100 |
-
* Added batch-mean centering for gradient innovation tracking (`centered_defect=False` by default):
|
| 101 |
-
|
| 102 |
-
$\displaystyle \mu_d = \frac{1}{N}\sum _ {i=1}^N (g _ {t,i} - m _ {t-1,i})$
|
| 103 |
-
|
| 104 |
-
$\displaystyle d_t^{(\text{B1})} = (g_t - m _ {t-1}) - \mu_d$
|
| 105 |
-
|
| 106 |
-
$\displaystyle d_t^{(\text{B2})} = (g_t - m_t) - \beta_1 \mu_d$
|
| 107 |
-
|
| 108 |
-
* In 1.3.3, uncentered innovation $g_t - m_t$ was used directly. On layers where gradients have a non-zero mean (such as normalization scales, biases, and token embeddings), the raw difference caused systematic drift in the second-moment accumulator. Centering eliminates this mean shift.
|
| 109 |
-
|
| 110 |
-
- **Subnormal-Safe IEEE-754 FP16 Stochastic Rounding**:
|
| 111 |
-
* Added `f32_to_f16_bits_rne` to handle IEEE-754 subnormal numbers ($|x| < 2^{-14}$) during FP16 stochastic rounding.
|
| 112 |
-
* In 1.3.3, stochastic rounding added 13-bit random noise and masked with `& -8192`. When a floating-point value entered the subnormal range, bitwise addition directly corrupted the exponent field (bit 10), causing values to flip between normal and subnormal representations or trigger hardware flush-to-zero traps.
|
| 113 |
-
* The updated routine checks for zero exponent, shifts the significand into the subnormal format, and applies round-to-nearest-even tie-breaking before bit truncation.
|
| 114 |
-
|
| 115 |
-
- **Multi-Backend Execution Engine**:
|
| 116 |
-
* Replaced the Python-only runtime with a multi-backend execution layer:
|
| 117 |
-
* **Rust SIMD (`luminav-simd`)**: AVX-512 (512-bit registers), AVX2 with FMA, portable scalar fallback, and Rayon parallel chunk scheduling (`ELEM_CHUNK_SIZE = 2,097,152`).
|
| 118 |
-
* **Vulkan UMA (`luminav-vulkan`)**: SPIR-V compute pipeline for cross-vendor GPUs (AMD, Intel, Apple Silicon via MoltenVK, and NVIDIA) with zero-copy host-visible memory buffers.
|
| 119 |
-
* **Triton AOT (`luminav-triton-aot`)**: C-ABI wrapper dispatching ahead-of-time precompiled kernels for 84 GPU architectures (Blackwell SM100/SM120, Hopper SM90, Ada SM89, Ampere SM80/SM86, Turing SM75, and AMD ROCm gfx90a/gfx942/gfx1100).
|
| 120 |
-
* **Standalone Runtime (`luminamoon.optimizer.standalone`)**: Pure NumPy and C-ABI execution that runs without requiring PyTorch.
|
| 121 |
-
|
| 122 |
-
### Changed
|
| 123 |
-
- **Parameter `alpha_ss`**: Removed. In 1.3.3, `alpha_ss=0.5` was used for soft-sign scaling $z / (1 + \alpha _ {\text{ss}} |z|)$ in the legacy single-buffer path. In 1.5.3, TD-CLM envelope normalization bounds updates directly to $(-1, 1)$, making soft-sign obsolete.
|
| 124 |
-
- **Parameter `lam`**: Added with default `0.3`. Governs the defect penalty weight in TD-CLM.
|
| 125 |
-
- **Parameter `centered_defect`**: Added with default `False`. Enables batch-mean centering of the innovation defect.
|
| 126 |
-
- **Parameter `bound_type`**: Default changed from `"radial"` (1.3.3) to `"coordinate"` (1.5.3). Coordinate-wise hyperbolic bounding $(B_i \tanh(\Delta \theta_i / B_i))$ evaluates locally on each parameter without computing a tensor-wide norm reduction.
|
| 127 |
-
- **Parameter `bound_ratio`**: Default changed from `0.03` to `0.05` to provide better update headroom on large model architectures.
|
| 128 |
-
- **Parameter `ema_decay`**: Default changed from static `0.8` to `"auto"`. Resolves to `0.80` for Dual-Buffer mode (`buffer=2`) and `0.95` for Single-Buffer mode (`buffer=1`).
|
| 129 |
-
- **Scratch Buffer**: Expanded from 5 floats `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` to 6 floats, adding `diff_sum` at index 5 to accumulate batch-mean defect statistics.
|
| 130 |
-
- **Cautious Scaling Floor**: Enforced `cautious_clamp_min=0.5` across all backends to cap maximum cautious energy scaling at $2.0\times$ ($2^1$).
|
| 131 |
-
|
| 132 |
-
### Fixed
|
| 133 |
-
- **Hardware Mantissa Divergence**: Fixed bitwise output differences across GPU families by replacing hardware SFU `tl.math.tanh` with Padé [13/6] rational approximation.
|
| 134 |
-
- **FP16 Subnormal Bit Corruption**: Fixed exponent corruption in subnormal FP16 stochastic rounding by separating exponent extraction from significand shift logic.
|
| 135 |
-
- **Single-Buffer Kernel Launch Latency**: Reduced single-buffer kernel dispatches from 3 passes to 1 pass, eliminating host-side synchronization bubbles and intermediate L2 atomic reduction overhead.
|
| 136 |
-
- **Non-Zero Mean Gradient Drift**: Resolved directional accumulator drift on bias and normalization layers via `centered_defect`.
|
| 137 |
-
|
| 138 |
-
### Parameter Comparison (1.3.3 vs 1.5.3)
|
| 139 |
-
|
| 140 |
-
| Parameter | Version 1.3.3 | Version 1.5.3 | Technical Notes |
|
| 141 |
-
| :--- | :--- | :--- | :--- |
|
| 142 |
-
| `params` | `params` | `params` | Iterable of parameters or parameter groups. |
|
| 143 |
-
| `lr` | `8e-4` | `8e-4` | Base learning rate. |
|
| 144 |
-
| `betas` | `(0.9, 0.999)` | `(0.9, 0.999)` | First and second moment exponential decay rates. |
|
| 145 |
-
| `eps` | `1e-8` | `1e-8` | Numerical stability floor. |
|
| 146 |
-
| `weight_decay` | `8e-2` | `8e-2` | Decoupled weight decay rate. |
|
| 147 |
-
| `tau` | `0.8` | `0.8` | Noise dispersion scale factor. |
|
| 148 |
-
| `alpha_ss` | `0.5` | *(removed)* | Removed; superseded by TD-CLM envelope normalization. |
|
| 149 |
-
| `lam` | *(none)* | `0.3` | Defect penalty weight in TD-CLM (calibrated to $\lambda^* \approx 0.3008$). |
|
| 150 |
-
| `cautious` | `True` | `True` | Directional verification gate ($u_i g_i > 0$). |
|
| 151 |
-
| `cautious_clamp_min` | `0.5` | `0.5` | Maximum cautious scale cap ($2.0\times$). |
|
| 152 |
-
| `buffer` | `2` | `2` | `1` = Single-Buffer (TD-CLM), `2` = Dual-Buffer (CLM). |
|
| 153 |
-
| `centered_defect` | *(none)* | `False` | Enables subtraction of batch-mean defect $\mu_d$. |
|
| 154 |
-
| `stochastic_rounding` | `True` | `True` | Bitwise stochastic rounding on FP16/BF16 weights. |
|
| 155 |
-
| `bound` | `True` | `True` | Enables update trust-region bounding. |
|
| 156 |
-
| `bound_type` | `"radial"` | `"coordinate"` | Default changed to coordinate-wise bounding (zero reduction overhead). |
|
| 157 |
-
| `bound_ratio` | `0.03` | `0.05` | Maximum step magnitude relative to parameter magnitude. |
|
| 158 |
-
| `master_weights` | `False` | `"none"` | `"none"`, `"semi"`, or `"full"`. |
|
| 159 |
-
| `execution` | `"auto"` | `"auto"` | Dynamic routing: SIMD, Vulkan, Triton, or Foreach. |
|
| 160 |
-
| `fused_single_pass` | `False` | `True` | Enables single-pass streaming with EMA scaling. |
|
| 161 |
-
| `ema_decay` | `0.8` | `"auto"` | Automatically sets `0.80` for dual-buffer, `0.95` for single-buffer. |
|
| 162 |
-
| `fsdp_sync` | `False` | `False` | Cross-rank collective all-reduce synchronization. |
|
| 163 |
-
| `seed` | `1337` | `1337` | PRNG seed for deterministic stochastic rounding. |
|
| 164 |
-
|
| 165 |
-
---
|
| 166 |
-
|
| 167 |
-
## [1.3.3] - 2026-10-06
|
| 168 |
-
|
| 169 |
-
### Fixed
|
| 170 |
-
* **Triton AST Compiler Early-Return Inlining Error:**
|
| 171 |
-
* Resolved fatal MLIR/CFG code generation failure (`at 35:4: _store_state_buffer(...) ^`) triggered by mid-branch `return` statements in `@triton.jit` helper functions (`_store_param` and `_store_state_buffer`).
|
| 172 |
-
* Refactored helper control flow into structured, single-exit `if ... elif ... else` branches, ensuring compliant JIT function inlining across all kernel entry points.
|
| 173 |
-
* **Bitcast Width Mismatch (Int64 Promotion in Stochastic Rounding):**
|
| 174 |
-
* Fixed implicit 64-bit integer promotion caused by Python integer literals (`0x9E3779B9`, `~0x1FFF`, `~0xFFFF`) inside Triton bitwise operations.
|
| 175 |
-
* Enforced strict 32-bit signed arithmetic via two's complement constants (`-1640531527`, `-2048173461`, `-1028477387`) with explicit `.to(tl.int32)` coercion, and standardized bitwise masks (`-65536` for BF16, `-8192` for FP16), ensuring type-safe 32-bit bitcasting (`tl.int32` to `tl.float32`).
|
| 176 |
-
* **JIT Kernel Call Keyword Arguments:**
|
| 177 |
-
* Replaced internal keyword argument invocations (`use_sr=False`) with standard positional boolean parameters inside Triton JIT kernels to eliminate compiler argument binding warnings across Triton runtime versions.
|
| 178 |
-
|
| 179 |
-
### Improved
|
| 180 |
-
* **Throttled Fallback Telemetry & Console De-Noising:**
|
| 181 |
-
* Added `self._warned_triton_failure` state tracking to prevent multi-line compiler AST dumps from flooding the console across every parameter tensor in a parameter group.
|
| 182 |
-
* Formatted fallback notifications into a single concise one-line `WARNING` log on initial encounter, routing full tracebacks to `DEBUG` level.
|
| 183 |
-
|
| 184 |
-
### Verified
|
| 185 |
-
* **End-to-End JIT Execution on NVIDIA Architectures (Tesla T4):**
|
| 186 |
-
* Validated JIT execution across both Dual-Pass (`fused_single_pass=False`) and Fused Single-Pass (`fused_single_pass=True`) modes in native FP16.
|
| 187 |
-
* Confirmed non-zero weight update dynamics ($|\Delta W| > 0$) without gradient underflow or weight freezing in pure master-free training for Version 1.3.3.
|
| 188 |
-
|
| 189 |
-
---
|
| 190 |
-
|
| 191 |
-
## [1.3.2] - 2026-10-06
|
| 192 |
-
|
| 193 |
-
### Added
|
| 194 |
-
- **Distributed FSDP Collective Synchronization (`fsdp_sync`)**:
|
| 195 |
-
- Introduced the `fsdp_sync: bool = False` configuration flag on optimizer initialization, backed by automated runtime guarding via `torch.distributed.is_initialized()`.
|
| 196 |
-
- Integrated cross-GPU collective communications (`torch.distributed.all_reduce(..., op=torch.distributed.ReduceOp.SUM)`) for global reduction metrics (`mask_sum`, `u_sq_sum`, and `p_sq_sum`) across Triton kernels, single-step execution, and vectorized foreach loops.
|
| 197 |
-
- Maintains bit-exact scalar consistency for `m_bar`, `cautious_scale`, and `bound_scale` across distributed parameter shards during large-scale pretraining under PyTorch FSDP (Fully Sharded Data Parallel) and DeepSpeed ZeRO-3.
|
| 198 |
-
- **Dedicated Low-Precision Stochastic Quantization Routine (`_sr_quantize`)**:
|
| 199 |
-
- Factored out bitwise stochastic rounding into a dedicated, modular helper function: `_sr_quantize(target, val_f32, use_sr, generator)`.
|
| 200 |
-
- Enabled native stochastic rounding on half-precision momentum states (`exp_avg` in BF16/FP16) across all PyTorch CPU/CUDA fallback paths (`_single_step_v2`, `_foreach_step_v2`, `_single_step_v1`, and `_foreach_step_v1`), systematically eliminating gradient stagnation induced by fractional bit truncation (LSB underflow).
|
| 201 |
-
- **Persistent Contiguous Buffer Caching**:
|
| 202 |
-
- Introduced persistent state cache tensors within `self.state[p]` (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, and `mp_contig`) to stage strided or sliced parameters before kernel launch.
|
| 203 |
-
- Eliminates dynamic memory allocations (`.contiguous()`) during iterative training loops, preserving PyTorch VRAM caching allocator stability and preventing CUDA Graph capture invalidation.
|
| 204 |
-
- **Zero-Copy Direct Tensor Aliasing for FP32 Master Weights**:
|
| 205 |
-
- When parameter tensors are natively `torch.float32`, `state["master_param"]` now directly aliases the parameter tensor pointer (`state["master_param"] = p`), avoiding additional master weight VRAM overhead for FP32 layers (0 bytes auxiliary memory footprint).
|
| 206 |
-
- **Checkpoint Sanitizer via State Dict Filter (`state_dict`)**:
|
| 207 |
-
- Overrode `state_dict()` to automatically purge temporary contiguous cache buffers (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, `mp_contig`), ensuring on-disk checkpoint payloads remain clean, compact, and fully backward-compatible.
|
| 208 |
-
- **Defensive Device Pipeline Flush on Kernel Fault (`torch.cuda.synchronize`)**:
|
| 209 |
-
- Embedded explicit hardware device synchronization (`torch.cuda.synchronize(p.device)`) within Triton exception recovery handlers prior to PyTorch fallback execution, preventing asynchronous CUDA error cascades into subsequent tensor operations.
|
| 210 |
-
|
| 211 |
-
### Changed
|
| 212 |
-
- **High-Entropy Bit-Avalanche PRNG Hash Function (`_prng`)**:
|
| 213 |
-
- Replaced the standard Linear Congruential Generator (LCG) with a MurmurHash3 / SplitMix32 integer bit-avalanche hashing routine:
|
| 214 |
-
```python
|
| 215 |
-
z = idx + seed + 0x9E3779B9
|
| 216 |
-
z = (z ^ (z >> 16)) * 0x85EBCA6B
|
| 217 |
-
z = (z ^ (z >> 13)) * 0xC2B2AE35
|
| 218 |
-
z = z ^ (z >> 16)
|
| 219 |
-
```
|
| 220 |
-
- Delivers uniform pseudo-random noise distributions with zero low-order bit periodicity, significantly refining stochastic rounding fidelity on BF16/FP16 parameters.
|
| 221 |
-
- **Memory-Contiguous Scratch Buffer Realignment for v1 Triton Kernels**:
|
| 222 |
-
- Re-indexed reduction buffer offsets in Triton v1 kernels:
|
| 223 |
-
- Pass 1 metrics (`[rms_sum, p_sq_sum]`) mapped to contiguous indices `[0, 1]`.
|
| 224 |
-
- Pass 2 metrics (`[mask_sum, u_sq_sum]`) mapped to contiguous indices `[2, 3]`.
|
| 225 |
-
- Enables vectorized contiguous slice all-reduces (`scratch_param[:2]` and `scratch_param[2:4]`) under FSDP without requiring non-contiguous memory gather/scatter overhead.
|
| 226 |
-
- **Multi-GPU Device Index Resolution in RNG Dispatcher (`_get_generator`)**:
|
| 227 |
-
- Fixed device index resolution when `device` is passed as a generic `torch.device("cuda")` without an explicit index by defaulting to `torch.cuda.current_device()`, avoiding cross-GPU generator key collisions during multi-GPU distributed runs.
|
| 228 |
-
- **Conditional Atomic Reduction Pruning in v2 Pass 1 Kernel**:
|
| 229 |
-
- Guarded `mask_sum` atomic accumulation in `_lumina_v2_pass1_kernel` with `if cautious:`, bypassing CTA reductions and L2 atomic additions when `cautious=False`.
|
| 230 |
-
- **Expanded Master Weight Precision Configuration Aliases**:
|
| 231 |
-
- Added the `"half"` alias for `"semi"` mode alongside `"fp32"` and `"masterweight"` aliases for `"full"` mode.
|
| 232 |
-
- **Metadata & Documentation Header Update**:
|
| 233 |
-
- Bumped specification version to `1.3.2`.
|
| 234 |
-
|
| 235 |
-
### Fixed
|
| 236 |
-
- **Singular Zero-Division & Step-Zero Bias Correction (`safe_base_step`)**:
|
| 237 |
-
- Introduced `safe_base_step = max(base_step, 1)` and `bc2_base = max(1.0 - (beta2 ** safe_base_step), 1e-15)` across Triton, single-step, and foreach loops.
|
| 238 |
-
- Fully resolves potential `ZeroDivisionError` and NaN blowups in `c1` / `c2` computations when steps evaluate at `step == 0`.
|
| 239 |
-
- **Numerical Overflow Clamp in Sigmoid-Based Tanh Fallback**:
|
| 240 |
-
- Applied register clamp bounds `x_clamped = tl.maximum(tl.minimum(x, 20.0), -20.0)` in analytical fallback `_triton_tanh_fast`, preventing `tl.exp(-2.0 * x)` from overflowing to `inf` or `nan` on devices lacking hardware SFU tanh instructions.
|
| 241 |
-
- **Missing Argument Forwarding in PyTorch Single-Step v1**:
|
| 242 |
-
- Corrected `_single_step_v1` method signature and call-sites to accept and propagate `scratches`, eliminating runtime `TypeError` during fallback invocation.
|
| 243 |
-
- **Multi-Rank Synchronization Skew in Sharded Parameter Bounding**:
|
| 244 |
-
- Aligned tensor norms and radial bounding factors across sharded ranks via synchronized global scalar reductions, preventing parameter update drift across distributed ranks.
|
| 245 |
-
|
| 246 |
-
### Performance & Engine Refactoring
|
| 247 |
-
- **Zero Dynamic VRAM Allocation on Strided / Sliced Tensors**:
|
| 248 |
-
- Persistent pre-allocated contiguous staging buffers avoid PyTorch caching allocator churn and eliminate CUDA Graph memory address invalidations.
|
| 249 |
-
- Preserves deterministic memory footprints across long pre-training runs.
|
| 250 |
-
- **Optimized Distributed Collective Bandwidth**:
|
| 251 |
-
- Grouping scalar reductions into contiguous vector slices compresses distributed synchronization overhead into a single collective kernel launch per pass.
|
| 252 |
-
|
| 253 |
-
---
|
| 254 |
-
|
| 255 |
-
## [1.3.0] - 2026-09-23
|
| 256 |
-
|
| 257 |
-
### Added
|
| 258 |
-
- **Fused Single-Pass Kernel with Post-Step EMA (`fused_single_pass`, `ema_decay`)**:
|
| 259 |
-
- Introduced `_lumina_v2_fused_single_pass_kernel` coupled with an asynchronous CTA micro-kernel `_lumina_v2_post_step_ema_kernel[(1,)]`.
|
| 260 |
-
- When `fused_single_pass=True`, avoids two-pass global reductions by leveraging running Exponential Moving Average (EMA) estimates of `cautious_scale` ($1 / \bar{M}$) and `bound_scale` ($\tanh(r) / r$).
|
| 261 |
-
- Unifies gradient filtering, momentum accumulation, variance dispersion, cautious masking, radial bounding, decoupled weight decay, stochastic rounding, and block partial reductions into a **single unified VRAM-resident pass**, reducing global GPU memory bandwidth traffic per optimizer step.
|
| 262 |
-
- **Hybrid Master Weight Precision Architecture (`master_weights`)**:
|
| 263 |
-
- Added multi-tier master weight flexibility via `master_weights: Union[bool, str] = False`:
|
| 264 |
-
- `"none"` / `False` *(Default)*: Pure master-free low-precision training leveraging on-chip bitwise stochastic rounding directly on native half-precision weights.
|
| 265 |
-
- `"semi"` / `"hybrid"` / `"half"`: 32-bit FP32 master parameters (`state["master_param"]`) coupled with 16-bit low-memory optimizer states (`exp_avg`, `exp_avg_sq` in BF16/FP16), saving ~50% optimizer state VRAM compared to traditional FP32 setups while maintaining full FP32 weight accumulation accuracy.
|
| 266 |
-
- `"full"` / `"fp32"` / `"masterweight"`: Traditional full FP32 master parameters with full FP32 optimizer states for standard baseline comparisons.
|
| 267 |
-
- **In-Kernel In-Flight NaN / Inf Gradient Sanitization**:
|
| 268 |
-
- Embedded register-level defensive sanitization across all Triton GPU kernels: `is_finite_g = (g == g) & (g < 1e38) & (g > -1e38); g = tl.where(is_finite_g, g, 0.0)`.
|
| 269 |
-
- Replaces non-finite gradients and subnormal corruptions with zero in registers before they can contaminate momentum (`exp_avg`) or variance (`exp_avg_sq`) state buffers.
|
| 270 |
-
- **Low-Precision State Buffer Serialization (`_store_state_buffer`)**:
|
| 271 |
-
- Added a dedicated Triton state serialization routine supporting FP32, BF16, and FP16 state representations with optional bitwise stochastic rounding applied directly to momentum buffers.
|
| 272 |
-
- **Multi-Device & Cross-Accelerator Hardware Context Isolation (`_get_device_context`)**:
|
| 273 |
-
- Unified device stream scoping supporting NVIDIA CUDA (`torch.cuda`), Intel XPU (`torch.xpu`), and modern PyTorch 2.x unified accelerator interfaces (`torch.accelerator.device`), eliminating multi-GPU stream hijacking and cross-device race conditions.
|
| 274 |
-
- **Strict Determinism Auto-Routing**:
|
| 275 |
-
- Automatically queries `torch.are_deterministic_algorithms_enabled()`. When enabled under `execution="auto"`, dynamically routes execution to C++ vectorized `foreach` to preserve bitwise cross-run reproducibility, circumventing non-deterministic floating-point hardware atomic reordering.
|
| 276 |
-
- **Granular Fault Recovery with State Guarding (`completed_indices`)**:
|
| 277 |
-
- Implemented per-parameter completion tracking in `_dispatch_triton`. If a kernel encounters an unexpected runtime fault, completed parameters are preserved while uncompleted ones fall back cleanly to `foreach`.
|
| 278 |
-
- Added `_single_step_v2_apply_param` and `_single_step_v1_apply_param` to safely finalize parameter updates if Pass 1 had already committed state transitions, strictly preventing double-update state corruption.
|
| 279 |
-
|
| 280 |
-
### Changed
|
| 281 |
-
- **Per-Parameter Persistent Scratch Tensors (Elimination of Global Slicing)**:
|
| 282 |
-
- Replaced the global dynamic slice allocator (`self._scratch_tensors[dev][:count]`) with isolated per-parameter persistent state allocations (`state["scratch"] = torch.zeros((5,), device=p.device, dtype=torch.float32)`).
|
| 283 |
-
- Memory pointers for `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` remain completely invariant throughout the entire training lifecycle, preserving pointer stability for **CUDA Graph Capture** (`torch.accelerator.Graph`) and TorchInductor AOT compilation without memory aliasing.
|
| 284 |
-
- **Analytically Exact Nesterov Bias Correction ($\beta_1^{\text{step} + 1}$)**:
|
| 285 |
-
- Corrected first-moment bias correction from the classical Adam formulation ($1.0 - \beta_1^{\text{step}}$) to the exact analytical expectation of the Nesterov blended momentum: `bc1_nes = 1.0 - beta1^(step + 1)`. Removes residual momentum bias during early warmup iterations.
|
| 286 |
-
- **Adaptive CTA Tile Sizing and Dynamic Thread Warp Scaling**:
|
| 287 |
-
- Introduced hardware-aware CTA tile scaling based on parameter volume: parameters with $\ge 2,097,152$ elements (2M+) dynamically configure `BLOCK_SIZE = 2048` and `num_warps = 8` (versus `BLOCK_SIZE = 1024`, `num_warps = 4` for smaller tensors).
|
| 288 |
-
- Cuts total thread-block grid counts and L2 cache atomic addition pressure by 50% on large MLP, Attention Projection, and Vocabulary layers.
|
| 289 |
-
- **Hardware-Native Tanh Instruction Dispatch (`tl.math.tanh` / `tl.tanh`)**:
|
| 290 |
-
- Upgraded `_triton_tanh_fast` to dynamically inspect compiler capabilities, binding directly to native PTX Special Function Unit (SFU) instructions (`tanh.approx.f32`) on modern Triton compilers while retaining analytical sigmoid fallbacks for legacy environments.
|
| 291 |
-
- **PRNG Decorrelation between Momentum and Parameter Updates**:
|
| 292 |
-
- Separated RNG seed streams using bitwise alternating mask shifts: `seed_p = (seed + 0x55555555) & 0x7FFFFFFF`, mathematically eliminating cross-coupling noise correlation between state buffer rounding and parameter update rounding.
|
| 293 |
-
- **Bumped specification version metadata to 1.3.0** across `luminav.py`.
|
| 294 |
-
|
| 295 |
-
### Fixed
|
| 296 |
-
- **Floating-Point Cancellation in Radial Bounding ($r \to 0$)**:
|
| 297 |
-
- Fixed precision jitter and roundoff degradation near zero by adding an asymptotic Taylor-series guard across Triton and PyTorch bounding engines: `torch.where(r < 1e-4, 1.0, tanh(r) / r)`.
|
| 298 |
-
- **Artificial Denominator Dilation in Dispersion Scale ($\sigma$)**:
|
| 299 |
-
- Replaced additive epsilon inflation (`sigma + 1e-6`) with a non-distorting floor clamp (`tl.maximum(sigma, 1e-12)`), preventing artificial dampening of subtle gradient signals.
|
| 300 |
-
- **Singular Zero-Division in Parameter Norm Clamping**:
|
| 301 |
-
- Replaced `norm_p + 1e-6` with `torch.clamp_min(norm_p, 1.0)` / `tl.maximum(norm_p, 1.0)`, maintaining numerical stability even when parameters are initialized near zero.
|
| 302 |
-
- **Transient Memory Pool Spikes during PyTorch Fallback Stochastic Rounding**:
|
| 303 |
-
- Refactored `_sr_update` to stream stochastic noise generation in bounded chunks of 1,048,576 elements (~4 MB chunk size), eliminating multi-hundred megabyte temporary int32 tensor spikes during fallback execution on large parameter matrices.
|
| 304 |
-
- **Non-Contiguous Buffer Safety & Strided Memory Copy**:
|
| 305 |
-
- Added strict contiguous layout verification for parameters, states, and master weights with in-place synchronization (`p.copy_(p_contig)`), guarded by a one-time developer warning.
|
| 306 |
-
|
| 307 |
-
### Performance & Engine Refactoring
|
| 308 |
-
- **~50% Global Memory Bandwidth Reduction via Fused Single-Pass Mode**:
|
| 309 |
-
- By unifying parameter loading, gradient evaluation, state transformation, and bounding into one GPU pass under `fused_single_pass=True`, VRAM read/write cycles are halved for cautious and radially bounded steps.
|
| 310 |
-
- **Sub-Microsecond L2 Hardware Atomic Reduction Architecture**:
|
| 311 |
-
- Formally documented the hybrid Intra-Block Tree Reduction + Inter-Block Hardware Atomic pattern. With `BLOCK_SIZE=2048`, a 10M-parameter tensor issues only 4,883 atomic additions directly into GPU L2 cache crossbars, executing in < 0.5 µs and strictly outperforming multi-tier cascaded reduction micro-kernels by eliminating 3–5 µs driver dispatch bubbles.
|
| 312 |
-
- **Static Buffer Address Invariance for CUDA Graphs & TorchInductor AOT**:
|
| 313 |
-
- Persistent 5-slot state scratch buffers eliminate all dynamic slicing and memory reallocations during optimization loops, preserving pointer address invariance for CUDA Graph replay.
|
| 314 |
-
|
| 315 |
-
---
|
| 316 |
-
|
| 317 |
-
## [1.2.1] - 2026-09-17
|
| 318 |
-
|
| 319 |
-
### Added
|
| 320 |
-
- **In-Kernel GPU Parameter Norm Reduction (`p_sq_sum_ptr`)**: Fused parameter Euclidean norm squared $\sum(p_i^2)$ calculation directly into Pass 1 Triton reduction kernels (`_lumina_v2_pass1_kernel` and `_lumina_v1_pass1_kernel`). Pass 2 and Pass 3 now load and resolve `norm_p` (`tl.sqrt(tl.maximum(p_sq_sum, 0.0))`) entirely inside GPU SRAM/registers without host intervention.
|
| 321 |
-
- **Stateless CPU Integer Multiplicative PRNG Hashing**: Replaced dynamic GPU RNG tensor allocation with a pure CPU integer hash using Knuth's 32-bit golden ratio constant: `(step * 0x9E3779B9 + i * 10007) & 0x7FFFFFFF`. Generates uncorrelated 31-bit integer seeds per layer with zero GPU kernel launch overhead and zero PCIe host-device latency.
|
| 322 |
-
- **Isolated Generator Cache for Fallback Engine (`self._generators`)**: Added an internal per-device `torch.Generator` cache, ensuring stochastic rounding in `_sr_update` operates in complete isolation from PyTorch's default CUDA generator state.
|
| 323 |
-
|
| 324 |
-
### Changed
|
| 325 |
-
- **Consolidated Scratch Buffer Zeroing**: Removed redundant per-parameter slice zeroing inside parameter loops. Buffer zeroing is now batched into a single consolidated `scratch.zero_()` call per step inside `_get_scratch_buffer`.
|
| 326 |
-
- **Scratch Buffer Stride Expansion**:
|
| 327 |
-
- `buffer=2`: Expanded stride from 2×N to 3×N elements per parameter to host `[mask_sum, u_sq_sum, p_sq_sum]`.
|
| 328 |
-
- `buffer=1`: Expanded stride from 3×N to 4×N elements per parameter to host `[rms_sum, mask_sum, u_sq_sum, p_sq_sum]`.
|
| 329 |
-
- **Bumped specification version metadata to 1.2.1**.
|
| 330 |
-
|
| 331 |
-
### Fixed
|
| 332 |
-
- **Host-Device Synchronization & Pipeline Stalls (`.item()` Bottleneck)**: Removed `norm_p = float(p_contig.float().norm().item())` and `seed = torch.randint(...).item()` from Triton step pipelines. Eliminated 200–400 synchronous PCIe roundtrips and GPU pipeline stalls per step on deep architectures (e.g., 28-layer Transformers), restoring full asynchronous GPU compute utilization.
|
| 333 |
-
- **CUDA Graphs & `torch.compile` Graph Breaks**: Fixed fatal graph capture crashes and TorchDynamo graph breaks caused by blocking host-device synchronization during radial bounding and seed generation. The optimizer step is now compatible with CUDA Graphs (`torch.accelerator.Graph`) and TorchInductor compilation.
|
| 334 |
-
- **Transient VRAM Allocator Thrashing**: Eliminated repeated temporary FP32 parameter cloning (`p_contig.float()`) during norm evaluation, preventing memory pool fragmentation in tight VRAM regimes.
|
| 335 |
-
- **Global RNG Pollution & Determinism Compliance**: Fixed global RNG state mutation in fallback stochastic rounding. Solved reproducibility drift in upstream Dropout, DataLoader shuffling, and RLHF reward modeling, ensuring strict adherence to PyTorch 2.10+ `torch.use_deterministic_algorithms(True)`.
|
| 336 |
-
|
| 337 |
-
### Performance & Engine Refactoring
|
| 338 |
-
- **Batch Kernel Launch Overhead Elimination**: Removed up to 400–800 individual `cudaMemsetAsync` micro-calls per step previously triggered by per-slice `.zero_()` invocations on 1-element scratch tensors.
|
| 339 |
-
- **Single-Pass Bypass for Coordinate Bounding**: Streamlined `_triton_step_v2` to immediately bypass two-pass reductions and invoke `_lumina_v2_single_pass_kernel` when `cautious=False` and coordinate bounding (`bound_mode == 2`) is selected.
|
| 340 |
-
|
| 341 |
-
---
|
| 342 |
-
|
| 343 |
-
## [1.2.0] - 2026-09-17
|
| 344 |
-
|
| 345 |
-
### Added
|
| 346 |
-
- **Automatic Dynamic FP16 Cliff Governor:** Built-in asymptotic boundary governor (`_triton_tanh_fast` in GPU register and PyTorch fallback) that automatically activates when `p.dtype == torch.float16`. Prevents unclipped steps from pushing parameters, activations, and residual streams into the IEEE-754 FP16 overflow cliff (> 65,504). Remains completely dormant (zero overhead) on `bfloat16` (TPU / Ampere+) and `float32`.
|
| 347 |
-
- **Smooth Asymptotic Parameter Bounding (`bound`, `bound_type`, `bound_ratio`):** Added non-intrusive step bounding with two mathematically continuous formulations:
|
| 348 |
-
- `"radial"`: Direction-Preserving Radial Squashing (`update * tanh(r) / r`) preserving gradient direction (Cosine Similarity = 1.000).
|
| 349 |
-
- `"coordinate"`: Dual-Hyperbolic Coordinate Squashing for fine-grained per-element damping.
|
| 350 |
-
- Configurable via constructor: `bound: bool = True`, `bound_type: str = "radial"`, `bound_ratio: float = 0.03`.
|
| 351 |
-
|
| 352 |
-
### Changed
|
| 353 |
-
- **Increased Default `cautious_clamp_min` from `0.2` to `0.5`:** Enforces an exact power-of-two maximum energy scaling factor (2.00x, $2^1$). Eliminates floating-point mantissa roundoff distortion in IEEE-754 hardware.
|
| 354 |
-
- Bumped specification version metadata to `1.2.0`.
|
| 355 |
-
|
| 356 |
-
### Fixed
|
| 357 |
-
* **FP16 Step Divergence & NaN Overflow at High Learning Rates:** Solved step divergence and subsequent NaN crashes when training deep 28-layer Transformer architectures (e.g., Qwen3-0.6B) in pure FP16 at nominal learning rates (`lr = 8e-4`).
|
| 358 |
-
* **Sparse Embedding & LM-Head Amplification:** Mitigated 5x over-amplification on sparse vocabulary gradients where active token counts are much smaller than total tensor parameters ($N \gg N_{\text{active}}$).
|
| 359 |
-
* **Verified stable 1,000-step pretraining from scratch on Qwen3-0.6B** (seq length 512, pure FP16, `lr = 8e-4`) on Nvidia Tesla T4 with zero gradient clipping and zero LR scheduler (loss dropped from 12.09 down to ~5.7 with zero NaNs).
|
| 360 |
-
* **Weight Decay Numerical Parity on Fallback Engine:** Resolved a silent precision-stagnation issue in `_single_step` and `_foreach_step`. Previously, decoupled weight decay (`p.mul_(1.0 - lr * weight_decay)`) was executed directly on native FP16/BF16 tensors prior to `_sr_update`. Due to IEEE-754 Round-to-Nearest (RTN) limits, `(1 - lr * weight_decay) ≈ 0.999936` rounded back to `1.0`, causing weight decay to freeze on half-precision parameters. Weight decay is now unified into FP32 registers inside `_sr_update` alongside stochastic rounding, matching the numerical behavior with the Triton kernel.
|
| 361 |
-
* **Fallback Foreach Indentation & Scope Alignment:** Corrected tensor iteration scope and indentation for `torch._foreach_add_` in non-SR fallback branches.
|
| 362 |
-
* **In-Place GPU Clamping in `_apply_bound`:** Replaced dynamic GPU scalar allocations (`torch.tensor(1.5e-4, device=p.device)`) with native `torch.clamp_min`, eliminating transient CUDA malloc spikes per update step.
|
| 363 |
-
|
| 364 |
-
### Performance & Engine Refactoring
|
| 365 |
-
* **Unified Bound Execution Pipeline:** Added an immediate short-circuit guard (`if lr == 0.0: return update`) and streamlined the execution flow: intermediate updates are scaled in FP32 (`step_val = lr * u_float`), passed through radial/coordinate bounding, chained cleanly into the FP16 Cliff Governor, and returned in a single normalized step.
|
| 366 |
-
|
| 367 |
-
---
|
| 368 |
-
|
| 369 |
-
## [1.1.5] - 2026-09-15
|
| 370 |
-
|
| 371 |
-
### Added
|
| 372 |
-
- **Native Hardware Capability Detection (`_supports_native_bf16`):** Added automatic device capability detection via `torch.cuda.is_bf16_supported()` to identify Ampere SM80+ architectures and streamline native execution paths.
|
| 373 |
-
- **Hugging Face Repository Reference:** Added official Hugging Face repository link in module docstrings.
|
| 374 |
-
|
| 375 |
-
### Changed
|
| 376 |
-
- **Dynamic Epsilon Clamping for FP16:** Dynamically floors effective epsilon to `max(eps, 1e-4)` when parameters or states are in `torch.float16`. Prevents bias-corrected scale factor `c2` from underflowing into IEEE-754 subnormal/zero limits on step 1.
|
| 377 |
-
- Bumped specification version to `1.1.5`.
|
| 378 |
-
|
| 379 |
-
### Fixed
|
| 380 |
-
- **FP16 Step-1 `NaN` from Epsilon Underflow:** Fixed `0.0 / 0.0 = NaN` division occurring on initial training steps when parameters receive zero gradients (e.g., unselected vocabulary embeddings, padding tokens, or dropout paths).
|
| 381 |
-
- **Second-Moment Variance Saturation (`+inf`):** Added safe upper-bound register clamping (`tl.minimum(v_new, 65000.0)` in Triton kernels and `.clamp_(0.0, 65000.0)` in PyTorch C++ foreach/single loops). Prevents unclipped initial pretraining gradient surges ($|g| > 256$) from overflowing the FP16 maximum dynamic range (65,504) into irreversible `+inf`.
|
| 382 |
-
- **Zero-Sigma Division Guard:** Injected explicit `sigma + 1e-6` denominator guards across all Triton kernel passes and PyTorch update steps to ensure numerical safety during cold-start iterations.
|
| 383 |
-
- Verified stable 100-step pretraining from scratch on `SmolLM-135M` using Wikimedia Wikipedia in pure FP16 on Nvidia Tesla T4 GPU with zero gradient clipping.
|
| 384 |
-
|
| 385 |
-
---
|
| 386 |
-
|
| 387 |
-
## [1.1.0] - 2026-09-11
|
| 388 |
-
|
| 389 |
-
### Added
|
| 390 |
-
- **Bitwise Stochastic Rounding (SR):** Integrated on-chip PRNG integer bit-manipulation hash in Triton kernels and vectorized PyTorch fallback for native FP16/BF16 training without master weights.
|
| 391 |
-
- Unified Triton store routine introducing compile-time branching (`dtype_mode` / `use_sr: tl.constexpr`) with explicit safe casting for FP16 and BF16 pointers.
|
| 392 |
-
- Vectorized fallback improvements utilizing C++ multi-tensor operations when stochastic rounding is disabled or tensors are FP32.
|
| 393 |
-
- State buffer contiguity validation (`exp_avg_contig`, `exp_avg_sq_contig`) ensuring safe linear memory addressing before kernel dispatch.
|
| 394 |
-
- Declarative configuration file (`config.json`) defining hyperparameters and optimizer presets.
|
| 395 |
-
|
| 396 |
-
### Changed
|
| 397 |
-
- Refactored PRNG integer arithmetic to strictly stay within signed 32-bit integer limits ($< 2^{31} - 1$), eliminating JIT compilation failures on newer Triton releases.
|
| 398 |
-
- State buffers (`exp_avg`, `exp_avg_sq`) now initialize with strict contiguous memory format.
|
| 399 |
-
|
| 400 |
-
### Fixed
|
| 401 |
-
- Fixed `ValueError: Scalar out of range for type int32` and Triton LLVM compile errors during unsigned 32-bit bitmask operations.
|
| 402 |
-
- Resolved kernel fallback hangs and compilation overhead on CUDA devices.
|
| 403 |
-
|
| 404 |
-
---
|
| 405 |
-
|
| 406 |
-
## [1.0.2] - 2026-09-08
|
| 407 |
-
|
| 408 |
-
### Added
|
| 409 |
-
- Official Apache-2.0 license notice and copyright headers.
|
| 410 |
-
- Standardized package exports (`__all__`, `__version__`, `__author__`, `__license__`).
|
| 411 |
-
|
| 412 |
-
---
|
| 413 |
-
|
| 414 |
-
## [1.0.1] - 2026-08-31
|
| 415 |
-
|
| 416 |
-
### Fixed
|
| 417 |
-
- **Memory Synchronization Fix (`423d171`):** Fixed a critical bug where non-contiguous parameter tensors (e.g., transposed linear layers or sliced weights) were updated on temporary contiguous copies without reflecting changes back to model weights. Added `p.copy_(p_contig)` after Triton execution.
|
| 418 |
-
|
| 419 |
-
---
|
| 420 |
-
|
| 421 |
-
## [1.0.0] - 2026-08-30
|
| 422 |
-
|
| 423 |
-
### Added
|
| 424 |
-
- Initial release of the LuminaV optimizer.
|
| 425 |
-
- **Zero Master-Weight Copies:** Direct parameter updates in native half-precision without allocating 4-byte FP32 master weights.
|
| 426 |
-
- **Hyperbolic Tangent (`tanh`) Bounding Envelope:** Projects normalized momentum ratio into `(-1.0, 1.0)` to eliminate exploding gradient shocks.
|
| 427 |
-
- **Directional Alignment Gate:** Cautious update masking that zeroes out momentum steps opposing mini-batch gradient directions.
|
| 428 |
-
- **Central Innovation Variance:** Noise dispersion tracking $(g_t - m_t)^2$ decoupling mini-batch turbulence from directional descent.
|
| 429 |
-
- Dual-buffer mode (`buffer=2`, LuminaV-2B) and single-buffer mode (`buffer=1`, LuminaV-1).
|
| 430 |
-
- Custom fused OpenAI Triton kernels with automated fallback execution.
|
|
|
|
| 1 |
+
# LuminaV Optimizer Algorithm Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to the LuminaV Optimizer algorithm are documented in this file.
|
| 4 |
+
|
| 5 |
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
+
|
| 7 |
+
* [Software Package Changelog (`luminamoon`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/SOFTWARE_CHANGELOG.md)
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## [1.6.0] - 2026-11-03
|
| 12 |
+
|
| 13 |
+
### Added
|
| 14 |
+
- **T-Family (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 15 |
+
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$, computing the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounding the lookahead update $\tilde{m}_t$ via the **Triangle Inequality Envelope** $\mathcal{E}_t^{(\text{T2})}$:
|
| 16 |
+
|
| 17 |
+
$\displaystyle d_t = g_t - m _ {t-1}, \qquad v_t = \beta_2 v _ {t-1} + (1 - \beta_2) d_t^2$
|
| 18 |
+
|
| 19 |
+
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \qquad w_1 = \beta_1^2, \qquad w_2 = 1 - \beta_1^2$
|
| 20 |
+
|
| 21 |
+
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 22 |
+
|
| 23 |
+
By the Triangle Inequality ($|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$), $|u_t^{(\text{T2})}| < 1$ is guaranteed without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, and $0.5\lambda \sigma_t$ provides belief-variance damping.
|
| 24 |
+
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Combines the single-buffer **Triangle-Defect** denominator $\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ (using residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below, reducing optimizer state memory by 50% via the Triangle Inequality.
|
| 25 |
+
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas:
|
| 26 |
+
|
| 27 |
+
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 28 |
+
* Keeps 100% backward compatibility and identical outputs for existing modes (`buffer=1`, `buffer=2`, and `version="1.5.5"`).
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## [1.5.5] - 2026-10-29
|
| 33 |
+
|
| 34 |
+
### Added
|
| 35 |
+
- **Contiguous Bucket Fusion (`fuse_tensors: bool = False`)**:
|
| 36 |
+
* Added opt-in contiguous parameter and state coalescing (`_build_contiguous_buckets`) that groups parameters within each `(param_group, device, dtype)` domain $\mathcal{B}_{g,d,\tau}$ into contiguous 1D storage (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) and rebinds `p.data`, `p.grad`, and `self.state[p]` entries as zero-copy views (`flat_*[offset : offset + n].view(p.shape)`).
|
| 37 |
+
* Eliminates per-tensor CPU-to-GPU kernel launch latency on fragmented vision and edge architectures by executing an entire bucket in a single fused kernel dispatch while computing `cautious_scale` and `radial` trust bounds across the bucket domain $\mathcal{B}_{g,d,\tau}$.
|
| 38 |
+
* Added in-place `zero_grad()` view preservation (`flat_g.zero_()`) and full `state_dict()` / `load_state_dict()` checkpoint compatibility without leaking temporary buffer keys (LSO-Eval Stage II compliant).
|
| 39 |
+
- **Triton JIT Pre-Warmup (`prewarm_jit: bool = True`)**:
|
| 40 |
+
* Added automatic initialization-time Triton JIT compilation (`_prewarm_all_configs`) using 64-element dummy buffers (`128–256 bytes` VRAM) with `lr=0.0` and `weight_decay=0.0` across both Step 1 (`pass1` + `pass2`) and Step 2+ (`fused_single_pass` + `post_step_ema`) paths with target `BLOCK_SIZE` specialization (`1024` or `2048`), eliminating first-step compilation stalls.
|
| 41 |
+
- **Bounded Bucket Chunking & Sequential In-Place Migration (`bucket_cap_mb: float = 500.0`)**:
|
| 42 |
+
* Bound contiguous bucket coalescing by an upper memory capacity ceiling $C_{\text{cap}} = 500\text{ MB}$, partitioning model parameters into ordered contiguous chunks $\mathcal{B}^{(1)}, \mathcal{B}^{(2)}, \dots, \mathcal{B}^{(K)}$ such that $\sum_{p \in \mathcal{B}^{(k)}} \text{bytes}(p) \le C_{\text{cap}}$ (with giant single layers $> C_{\text{cap}}$ isolated into individual buckets).
|
| 43 |
+
* Enforced sequential in-place migration with immediate parameter storage recycling (`del old_data`, `del old_grad`) and garbage collection, constraining peak transient memory allocation spikes during optimizer initialization to at most $\min(\text{model bytes}, C _ {\text{cap}})$.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## [1.5.4] - 2026-10-29
|
| 48 |
+
|
| 49 |
+
### Added
|
| 50 |
+
- **In-Container Native Bitwise-BF16 State Promotion (`dtype_mode == 2`, `master_weights="none"`)**:
|
| 51 |
+
* Promoted 16-bit optimizer state buffers ($m_t$ via stochastic rounding `f32_to_bf16_bits_sr` and $v_t$ via round-to-nearest-even `f32_to_bf16_bits_rne`) to native Bitwise-BF16 stored inside the existing 16-bit container during pure `FP16` execution, expanding state exponent dynamic range to 8 bits ($1.18 \times 10^{-38}$) with zero additional VRAM overhead.
|
| 52 |
+
|
| 53 |
+
### Changed
|
| 54 |
+
- **Unified Denominator Stability Constant ($\epsilon_{\text{eff}} = \epsilon = 10^{-8}$)**:
|
| 55 |
+
* Unified the effective denominator constant $\epsilon_{\text{eff}} = \epsilon = 10^{-8}$ across all precision modes and hardware backends (replacing the legacy `FP16` floor clamp $\max(\epsilon, 10^{-4})$ introduced in `1.1.5`), preventing update velocity attenuation and late-stage convergence loss resets when $\sqrt{v_t} \ll 10^{-4}$.
|
| 56 |
+
|
| 57 |
+
### Removed
|
| 58 |
+
- **Centered Defect Compensation (`centered_defect`, $\mu_d$)**:
|
| 59 |
+
* Removed the `centered_defect` hyperparameter and the global defect mean reduction $\mu_d = \frac{1}{N}\sum_{i=1}^N (g_{t,i} - m_{t-1,i})$ across both Dual-Buffer Convex Lookahead Momentum (`buffer=2` / CLM) and Single-Buffer Triangle-Defect Convex Lookahead Momentum (`buffer=1` / TD-CLM).
|
| 60 |
+
* Simplified the instantaneous gradient-momentum residual to purely local coordinate-wise differences:
|
| 61 |
+
|
| 62 |
+
$\displaystyle d_t^{(\text{B1})} = g_t - m_{t-1}, \qquad d_t^{(\text{B2})} = g_t - m_t$
|
| 63 |
+
|
| 64 |
+
* Eliminated the pre-step global reduction pass (`_lumina_diff_sum_kernel` in Triton JIT/AOT, `diff_sum.comp` in Vulkan SPIR-V, `reduce_diff_sum_simd` in x86\_64 AVX-512/AVX2, and slot 5 of the 6-element `ScratchBuffer`), removing cross-lane/cross-rank reduction latency while retaining the 6-element physical `ScratchBuffer` layout (`[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale, _reserved]`, 5 active slots + 1 reserved slot for ABI alignment).
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## [1.5.3] - 2026-10-20
|
| 69 |
+
|
| 70 |
+
No versions were published between 1.3.3 and 1.5.3. This release notes all changes made since version 1.3.3.
|
| 71 |
+
|
| 72 |
+
### Added
|
| 73 |
+
- **Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM, `buffer=1`)**:
|
| 74 |
+
* Replaced the legacy 3-pass RMS single-buffer update with a single-pass formulation:
|
| 75 |
+
|
| 76 |
+
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \quad w_1 = \beta_1^2, \quad w_2 = 1 - \beta_1^2$
|
| 77 |
+
|
| 78 |
+
$\displaystyle \mathcal{E} _ t = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t^{(\text{B1})}| + \epsilon _ {\text{eff}}$
|
| 79 |
+
|
| 80 |
+
$\displaystyle u_t^{(\text{B1})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t} \in (-1, 1)$
|
| 81 |
+
|
| 82 |
+
* In 1.3.3, single-buffer mode required three sequential GPU kernel launches (`_lumina_v1_pass1_kernel`, `_lumina_v1_pass2_kernel`, `_lumina_v1_pass3_update_kernel`) and global atomic reductions across thread blocks to compute tensor-wide root-mean-square ($\text{rms} = \sqrt{\frac{1}{N}\sum \tilde{m} _ i^2 + \epsilon}$).
|
| 83 |
+
* TD-CLM replaces the tensor-wide reduction with coordinate-wise envelope bounding via the Triangle Inequality Envelope $\mathcal{E} _ t$. This reduces single-buffer execution to a single GPU pass and eliminates all $\tanh$ evaluations in the momentum path.
|
| 84 |
+
* Calibrated the defect penalty weight $\lambda$ to the symmetric spike-reversal equilibrium:
|
| 85 |
+
|
| 86 |
+
$\displaystyle \lambda^* = \frac{2(1 - \beta_1^2)^2}{4\beta_1^2 - 3}$
|
| 87 |
+
|
| 88 |
+
For the default $\beta_1 = 0.9$, $\lambda^* \approx 0.3008$, setting the default parameter to `lam=0.3`.
|
| 89 |
+
|
| 90 |
+
- **Padé [13/6] Rational Approximation (`tanh_pade_f32`)**:
|
| 91 |
+
* Implemented a rational Padé [13/6] polynomial for hyperbolic tangent calculations:
|
| 92 |
+
|
| 93 |
+
$\displaystyle \tanh _ {\text{Pade}}(x) = \frac{x(1 + c_1 x^2 + c_2 x^4 + c_3 x^6 + c_4 x^8 + c_5 x^{10} + c_6 x^{12})}{1 + d_1 x^2 + d_2 x^4 + d_3 x^6}$
|
| 94 |
+
|
| 95 |
+
* Evaluated over the clamped domain $x \in [-7.9988117, 7.9988117]$ with asymptotic saturation to $\pm 1.0$.
|
| 96 |
+
* In 1.3.3, `_triton_tanh_fast` used hardware-dependent SFU instructions (`tl.math.tanh`) or analytical sigmoid fallbacks (`2 / (1 + exp(-2x)) - 1`). Different GPU architectures (e.g. Turing, Ampere, Hopper) evaluate SFU transcendental functions with slightly different mantissa rounding, leading to bitwise divergence during distributed training.
|
| 97 |
+
* Padé [13/6] provides uniform precision with $\lVert \tanh _ {\text{Pade}}(x) - \tanh(x) \rVert _ \infty < 1.5 \times 10^{-6}$ across CPU Scalar, AVX2, AVX-512, Vulkan SPIR-V, and Triton GPU kernels.
|
| 98 |
+
|
| 99 |
+
- **Centered Innovation Defect (`centered_defect`)**:
|
| 100 |
+
* Added batch-mean centering for gradient innovation tracking (`centered_defect=False` by default):
|
| 101 |
+
|
| 102 |
+
$\displaystyle \mu_d = \frac{1}{N}\sum _ {i=1}^N (g _ {t,i} - m _ {t-1,i})$
|
| 103 |
+
|
| 104 |
+
$\displaystyle d_t^{(\text{B1})} = (g_t - m _ {t-1}) - \mu_d$
|
| 105 |
+
|
| 106 |
+
$\displaystyle d_t^{(\text{B2})} = (g_t - m_t) - \beta_1 \mu_d$
|
| 107 |
+
|
| 108 |
+
* In 1.3.3, uncentered innovation $g_t - m_t$ was used directly. On layers where gradients have a non-zero mean (such as normalization scales, biases, and token embeddings), the raw difference caused systematic drift in the second-moment accumulator. Centering eliminates this mean shift.
|
| 109 |
+
|
| 110 |
+
- **Subnormal-Safe IEEE-754 FP16 Stochastic Rounding**:
|
| 111 |
+
* Added `f32_to_f16_bits_rne` to handle IEEE-754 subnormal numbers ($|x| < 2^{-14}$) during FP16 stochastic rounding.
|
| 112 |
+
* In 1.3.3, stochastic rounding added 13-bit random noise and masked with `& -8192`. When a floating-point value entered the subnormal range, bitwise addition directly corrupted the exponent field (bit 10), causing values to flip between normal and subnormal representations or trigger hardware flush-to-zero traps.
|
| 113 |
+
* The updated routine checks for zero exponent, shifts the significand into the subnormal format, and applies round-to-nearest-even tie-breaking before bit truncation.
|
| 114 |
+
|
| 115 |
+
- **Multi-Backend Execution Engine**:
|
| 116 |
+
* Replaced the Python-only runtime with a multi-backend execution layer:
|
| 117 |
+
* **Rust SIMD (`luminav-simd`)**: AVX-512 (512-bit registers), AVX2 with FMA, portable scalar fallback, and Rayon parallel chunk scheduling (`ELEM_CHUNK_SIZE = 2,097,152`).
|
| 118 |
+
* **Vulkan UMA (`luminav-vulkan`)**: SPIR-V compute pipeline for cross-vendor GPUs (AMD, Intel, Apple Silicon via MoltenVK, and NVIDIA) with zero-copy host-visible memory buffers.
|
| 119 |
+
* **Triton AOT (`luminav-triton-aot`)**: C-ABI wrapper dispatching ahead-of-time precompiled kernels for 84 GPU architectures (Blackwell SM100/SM120, Hopper SM90, Ada SM89, Ampere SM80/SM86, Turing SM75, and AMD ROCm gfx90a/gfx942/gfx1100).
|
| 120 |
+
* **Standalone Runtime (`luminamoon.optimizer.standalone`)**: Pure NumPy and C-ABI execution that runs without requiring PyTorch.
|
| 121 |
+
|
| 122 |
+
### Changed
|
| 123 |
+
- **Parameter `alpha_ss`**: Removed. In 1.3.3, `alpha_ss=0.5` was used for soft-sign scaling $z / (1 + \alpha _ {\text{ss}} |z|)$ in the legacy single-buffer path. In 1.5.3, TD-CLM envelope normalization bounds updates directly to $(-1, 1)$, making soft-sign obsolete.
|
| 124 |
+
- **Parameter `lam`**: Added with default `0.3`. Governs the defect penalty weight in TD-CLM.
|
| 125 |
+
- **Parameter `centered_defect`**: Added with default `False`. Enables batch-mean centering of the innovation defect.
|
| 126 |
+
- **Parameter `bound_type`**: Default changed from `"radial"` (1.3.3) to `"coordinate"` (1.5.3). Coordinate-wise hyperbolic bounding $(B_i \tanh(\Delta \theta_i / B_i))$ evaluates locally on each parameter without computing a tensor-wide norm reduction.
|
| 127 |
+
- **Parameter `bound_ratio`**: Default changed from `0.03` to `0.05` to provide better update headroom on large model architectures.
|
| 128 |
+
- **Parameter `ema_decay`**: Default changed from static `0.8` to `"auto"`. Resolves to `0.80` for Dual-Buffer mode (`buffer=2`) and `0.95` for Single-Buffer mode (`buffer=1`).
|
| 129 |
+
- **Scratch Buffer**: Expanded from 5 floats `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` to 6 floats, adding `diff_sum` at index 5 to accumulate batch-mean defect statistics.
|
| 130 |
+
- **Cautious Scaling Floor**: Enforced `cautious_clamp_min=0.5` across all backends to cap maximum cautious energy scaling at $2.0\times$ ($2^1$).
|
| 131 |
+
|
| 132 |
+
### Fixed
|
| 133 |
+
- **Hardware Mantissa Divergence**: Fixed bitwise output differences across GPU families by replacing hardware SFU `tl.math.tanh` with Padé [13/6] rational approximation.
|
| 134 |
+
- **FP16 Subnormal Bit Corruption**: Fixed exponent corruption in subnormal FP16 stochastic rounding by separating exponent extraction from significand shift logic.
|
| 135 |
+
- **Single-Buffer Kernel Launch Latency**: Reduced single-buffer kernel dispatches from 3 passes to 1 pass, eliminating host-side synchronization bubbles and intermediate L2 atomic reduction overhead.
|
| 136 |
+
- **Non-Zero Mean Gradient Drift**: Resolved directional accumulator drift on bias and normalization layers via `centered_defect`.
|
| 137 |
+
|
| 138 |
+
### Parameter Comparison (1.3.3 vs 1.5.3)
|
| 139 |
+
|
| 140 |
+
| Parameter | Version 1.3.3 | Version 1.5.3 | Technical Notes |
|
| 141 |
+
| :--- | :--- | :--- | :--- |
|
| 142 |
+
| `params` | `params` | `params` | Iterable of parameters or parameter groups. |
|
| 143 |
+
| `lr` | `8e-4` | `8e-4` | Base learning rate. |
|
| 144 |
+
| `betas` | `(0.9, 0.999)` | `(0.9, 0.999)` | First and second moment exponential decay rates. |
|
| 145 |
+
| `eps` | `1e-8` | `1e-8` | Numerical stability floor. |
|
| 146 |
+
| `weight_decay` | `8e-2` | `8e-2` | Decoupled weight decay rate. |
|
| 147 |
+
| `tau` | `0.8` | `0.8` | Noise dispersion scale factor. |
|
| 148 |
+
| `alpha_ss` | `0.5` | *(removed)* | Removed; superseded by TD-CLM envelope normalization. |
|
| 149 |
+
| `lam` | *(none)* | `0.3` | Defect penalty weight in TD-CLM (calibrated to $\lambda^* \approx 0.3008$). |
|
| 150 |
+
| `cautious` | `True` | `True` | Directional verification gate ($u_i g_i > 0$). |
|
| 151 |
+
| `cautious_clamp_min` | `0.5` | `0.5` | Maximum cautious scale cap ($2.0\times$). |
|
| 152 |
+
| `buffer` | `2` | `2` | `1` = Single-Buffer (TD-CLM), `2` = Dual-Buffer (CLM). |
|
| 153 |
+
| `centered_defect` | *(none)* | `False` | Enables subtraction of batch-mean defect $\mu_d$. |
|
| 154 |
+
| `stochastic_rounding` | `True` | `True` | Bitwise stochastic rounding on FP16/BF16 weights. |
|
| 155 |
+
| `bound` | `True` | `True` | Enables update trust-region bounding. |
|
| 156 |
+
| `bound_type` | `"radial"` | `"coordinate"` | Default changed to coordinate-wise bounding (zero reduction overhead). |
|
| 157 |
+
| `bound_ratio` | `0.03` | `0.05` | Maximum step magnitude relative to parameter magnitude. |
|
| 158 |
+
| `master_weights` | `False` | `"none"` | `"none"`, `"semi"`, or `"full"`. |
|
| 159 |
+
| `execution` | `"auto"` | `"auto"` | Dynamic routing: SIMD, Vulkan, Triton, or Foreach. |
|
| 160 |
+
| `fused_single_pass` | `False` | `True` | Enables single-pass streaming with EMA scaling. |
|
| 161 |
+
| `ema_decay` | `0.8` | `"auto"` | Automatically sets `0.80` for dual-buffer, `0.95` for single-buffer. |
|
| 162 |
+
| `fsdp_sync` | `False` | `False` | Cross-rank collective all-reduce synchronization. |
|
| 163 |
+
| `seed` | `1337` | `1337` | PRNG seed for deterministic stochastic rounding. |
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
## [1.3.3] - 2026-10-06
|
| 168 |
+
|
| 169 |
+
### Fixed
|
| 170 |
+
* **Triton AST Compiler Early-Return Inlining Error:**
|
| 171 |
+
* Resolved fatal MLIR/CFG code generation failure (`at 35:4: _store_state_buffer(...) ^`) triggered by mid-branch `return` statements in `@triton.jit` helper functions (`_store_param` and `_store_state_buffer`).
|
| 172 |
+
* Refactored helper control flow into structured, single-exit `if ... elif ... else` branches, ensuring compliant JIT function inlining across all kernel entry points.
|
| 173 |
+
* **Bitcast Width Mismatch (Int64 Promotion in Stochastic Rounding):**
|
| 174 |
+
* Fixed implicit 64-bit integer promotion caused by Python integer literals (`0x9E3779B9`, `~0x1FFF`, `~0xFFFF`) inside Triton bitwise operations.
|
| 175 |
+
* Enforced strict 32-bit signed arithmetic via two's complement constants (`-1640531527`, `-2048173461`, `-1028477387`) with explicit `.to(tl.int32)` coercion, and standardized bitwise masks (`-65536` for BF16, `-8192` for FP16), ensuring type-safe 32-bit bitcasting (`tl.int32` to `tl.float32`).
|
| 176 |
+
* **JIT Kernel Call Keyword Arguments:**
|
| 177 |
+
* Replaced internal keyword argument invocations (`use_sr=False`) with standard positional boolean parameters inside Triton JIT kernels to eliminate compiler argument binding warnings across Triton runtime versions.
|
| 178 |
+
|
| 179 |
+
### Improved
|
| 180 |
+
* **Throttled Fallback Telemetry & Console De-Noising:**
|
| 181 |
+
* Added `self._warned_triton_failure` state tracking to prevent multi-line compiler AST dumps from flooding the console across every parameter tensor in a parameter group.
|
| 182 |
+
* Formatted fallback notifications into a single concise one-line `WARNING` log on initial encounter, routing full tracebacks to `DEBUG` level.
|
| 183 |
+
|
| 184 |
+
### Verified
|
| 185 |
+
* **End-to-End JIT Execution on NVIDIA Architectures (Tesla T4):**
|
| 186 |
+
* Validated JIT execution across both Dual-Pass (`fused_single_pass=False`) and Fused Single-Pass (`fused_single_pass=True`) modes in native FP16.
|
| 187 |
+
* Confirmed non-zero weight update dynamics ($|\Delta W| > 0$) without gradient underflow or weight freezing in pure master-free training for Version 1.3.3.
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## [1.3.2] - 2026-10-06
|
| 192 |
+
|
| 193 |
+
### Added
|
| 194 |
+
- **Distributed FSDP Collective Synchronization (`fsdp_sync`)**:
|
| 195 |
+
- Introduced the `fsdp_sync: bool = False` configuration flag on optimizer initialization, backed by automated runtime guarding via `torch.distributed.is_initialized()`.
|
| 196 |
+
- Integrated cross-GPU collective communications (`torch.distributed.all_reduce(..., op=torch.distributed.ReduceOp.SUM)`) for global reduction metrics (`mask_sum`, `u_sq_sum`, and `p_sq_sum`) across Triton kernels, single-step execution, and vectorized foreach loops.
|
| 197 |
+
- Maintains bit-exact scalar consistency for `m_bar`, `cautious_scale`, and `bound_scale` across distributed parameter shards during large-scale pretraining under PyTorch FSDP (Fully Sharded Data Parallel) and DeepSpeed ZeRO-3.
|
| 198 |
+
- **Dedicated Low-Precision Stochastic Quantization Routine (`_sr_quantize`)**:
|
| 199 |
+
- Factored out bitwise stochastic rounding into a dedicated, modular helper function: `_sr_quantize(target, val_f32, use_sr, generator)`.
|
| 200 |
+
- Enabled native stochastic rounding on half-precision momentum states (`exp_avg` in BF16/FP16) across all PyTorch CPU/CUDA fallback paths (`_single_step_v2`, `_foreach_step_v2`, `_single_step_v1`, and `_foreach_step_v1`), systematically eliminating gradient stagnation induced by fractional bit truncation (LSB underflow).
|
| 201 |
+
- **Persistent Contiguous Buffer Caching**:
|
| 202 |
+
- Introduced persistent state cache tensors within `self.state[p]` (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, and `mp_contig`) to stage strided or sliced parameters before kernel launch.
|
| 203 |
+
- Eliminates dynamic memory allocations (`.contiguous()`) during iterative training loops, preserving PyTorch VRAM caching allocator stability and preventing CUDA Graph capture invalidation.
|
| 204 |
+
- **Zero-Copy Direct Tensor Aliasing for FP32 Master Weights**:
|
| 205 |
+
- When parameter tensors are natively `torch.float32`, `state["master_param"]` now directly aliases the parameter tensor pointer (`state["master_param"] = p`), avoiding additional master weight VRAM overhead for FP32 layers (0 bytes auxiliary memory footprint).
|
| 206 |
+
- **Checkpoint Sanitizer via State Dict Filter (`state_dict`)**:
|
| 207 |
+
- Overrode `state_dict()` to automatically purge temporary contiguous cache buffers (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, `mp_contig`), ensuring on-disk checkpoint payloads remain clean, compact, and fully backward-compatible.
|
| 208 |
+
- **Defensive Device Pipeline Flush on Kernel Fault (`torch.cuda.synchronize`)**:
|
| 209 |
+
- Embedded explicit hardware device synchronization (`torch.cuda.synchronize(p.device)`) within Triton exception recovery handlers prior to PyTorch fallback execution, preventing asynchronous CUDA error cascades into subsequent tensor operations.
|
| 210 |
+
|
| 211 |
+
### Changed
|
| 212 |
+
- **High-Entropy Bit-Avalanche PRNG Hash Function (`_prng`)**:
|
| 213 |
+
- Replaced the standard Linear Congruential Generator (LCG) with a MurmurHash3 / SplitMix32 integer bit-avalanche hashing routine:
|
| 214 |
+
```python
|
| 215 |
+
z = idx + seed + 0x9E3779B9
|
| 216 |
+
z = (z ^ (z >> 16)) * 0x85EBCA6B
|
| 217 |
+
z = (z ^ (z >> 13)) * 0xC2B2AE35
|
| 218 |
+
z = z ^ (z >> 16)
|
| 219 |
+
```
|
| 220 |
+
- Delivers uniform pseudo-random noise distributions with zero low-order bit periodicity, significantly refining stochastic rounding fidelity on BF16/FP16 parameters.
|
| 221 |
+
- **Memory-Contiguous Scratch Buffer Realignment for v1 Triton Kernels**:
|
| 222 |
+
- Re-indexed reduction buffer offsets in Triton v1 kernels:
|
| 223 |
+
- Pass 1 metrics (`[rms_sum, p_sq_sum]`) mapped to contiguous indices `[0, 1]`.
|
| 224 |
+
- Pass 2 metrics (`[mask_sum, u_sq_sum]`) mapped to contiguous indices `[2, 3]`.
|
| 225 |
+
- Enables vectorized contiguous slice all-reduces (`scratch_param[:2]` and `scratch_param[2:4]`) under FSDP without requiring non-contiguous memory gather/scatter overhead.
|
| 226 |
+
- **Multi-GPU Device Index Resolution in RNG Dispatcher (`_get_generator`)**:
|
| 227 |
+
- Fixed device index resolution when `device` is passed as a generic `torch.device("cuda")` without an explicit index by defaulting to `torch.cuda.current_device()`, avoiding cross-GPU generator key collisions during multi-GPU distributed runs.
|
| 228 |
+
- **Conditional Atomic Reduction Pruning in v2 Pass 1 Kernel**:
|
| 229 |
+
- Guarded `mask_sum` atomic accumulation in `_lumina_v2_pass1_kernel` with `if cautious:`, bypassing CTA reductions and L2 atomic additions when `cautious=False`.
|
| 230 |
+
- **Expanded Master Weight Precision Configuration Aliases**:
|
| 231 |
+
- Added the `"half"` alias for `"semi"` mode alongside `"fp32"` and `"masterweight"` aliases for `"full"` mode.
|
| 232 |
+
- **Metadata & Documentation Header Update**:
|
| 233 |
+
- Bumped specification version to `1.3.2`.
|
| 234 |
+
|
| 235 |
+
### Fixed
|
| 236 |
+
- **Singular Zero-Division & Step-Zero Bias Correction (`safe_base_step`)**:
|
| 237 |
+
- Introduced `safe_base_step = max(base_step, 1)` and `bc2_base = max(1.0 - (beta2 ** safe_base_step), 1e-15)` across Triton, single-step, and foreach loops.
|
| 238 |
+
- Fully resolves potential `ZeroDivisionError` and NaN blowups in `c1` / `c2` computations when steps evaluate at `step == 0`.
|
| 239 |
+
- **Numerical Overflow Clamp in Sigmoid-Based Tanh Fallback**:
|
| 240 |
+
- Applied register clamp bounds `x_clamped = tl.maximum(tl.minimum(x, 20.0), -20.0)` in analytical fallback `_triton_tanh_fast`, preventing `tl.exp(-2.0 * x)` from overflowing to `inf` or `nan` on devices lacking hardware SFU tanh instructions.
|
| 241 |
+
- **Missing Argument Forwarding in PyTorch Single-Step v1**:
|
| 242 |
+
- Corrected `_single_step_v1` method signature and call-sites to accept and propagate `scratches`, eliminating runtime `TypeError` during fallback invocation.
|
| 243 |
+
- **Multi-Rank Synchronization Skew in Sharded Parameter Bounding**:
|
| 244 |
+
- Aligned tensor norms and radial bounding factors across sharded ranks via synchronized global scalar reductions, preventing parameter update drift across distributed ranks.
|
| 245 |
+
|
| 246 |
+
### Performance & Engine Refactoring
|
| 247 |
+
- **Zero Dynamic VRAM Allocation on Strided / Sliced Tensors**:
|
| 248 |
+
- Persistent pre-allocated contiguous staging buffers avoid PyTorch caching allocator churn and eliminate CUDA Graph memory address invalidations.
|
| 249 |
+
- Preserves deterministic memory footprints across long pre-training runs.
|
| 250 |
+
- **Optimized Distributed Collective Bandwidth**:
|
| 251 |
+
- Grouping scalar reductions into contiguous vector slices compresses distributed synchronization overhead into a single collective kernel launch per pass.
|
| 252 |
+
|
| 253 |
+
---
|
| 254 |
+
|
| 255 |
+
## [1.3.0] - 2026-09-23
|
| 256 |
+
|
| 257 |
+
### Added
|
| 258 |
+
- **Fused Single-Pass Kernel with Post-Step EMA (`fused_single_pass`, `ema_decay`)**:
|
| 259 |
+
- Introduced `_lumina_v2_fused_single_pass_kernel` coupled with an asynchronous CTA micro-kernel `_lumina_v2_post_step_ema_kernel[(1,)]`.
|
| 260 |
+
- When `fused_single_pass=True`, avoids two-pass global reductions by leveraging running Exponential Moving Average (EMA) estimates of `cautious_scale` ($1 / \bar{M}$) and `bound_scale` ($\tanh(r) / r$).
|
| 261 |
+
- Unifies gradient filtering, momentum accumulation, variance dispersion, cautious masking, radial bounding, decoupled weight decay, stochastic rounding, and block partial reductions into a **single unified VRAM-resident pass**, reducing global GPU memory bandwidth traffic per optimizer step.
|
| 262 |
+
- **Hybrid Master Weight Precision Architecture (`master_weights`)**:
|
| 263 |
+
- Added multi-tier master weight flexibility via `master_weights: Union[bool, str] = False`:
|
| 264 |
+
- `"none"` / `False` *(Default)*: Pure master-free low-precision training leveraging on-chip bitwise stochastic rounding directly on native half-precision weights.
|
| 265 |
+
- `"semi"` / `"hybrid"` / `"half"`: 32-bit FP32 master parameters (`state["master_param"]`) coupled with 16-bit low-memory optimizer states (`exp_avg`, `exp_avg_sq` in BF16/FP16), saving ~50% optimizer state VRAM compared to traditional FP32 setups while maintaining full FP32 weight accumulation accuracy.
|
| 266 |
+
- `"full"` / `"fp32"` / `"masterweight"`: Traditional full FP32 master parameters with full FP32 optimizer states for standard baseline comparisons.
|
| 267 |
+
- **In-Kernel In-Flight NaN / Inf Gradient Sanitization**:
|
| 268 |
+
- Embedded register-level defensive sanitization across all Triton GPU kernels: `is_finite_g = (g == g) & (g < 1e38) & (g > -1e38); g = tl.where(is_finite_g, g, 0.0)`.
|
| 269 |
+
- Replaces non-finite gradients and subnormal corruptions with zero in registers before they can contaminate momentum (`exp_avg`) or variance (`exp_avg_sq`) state buffers.
|
| 270 |
+
- **Low-Precision State Buffer Serialization (`_store_state_buffer`)**:
|
| 271 |
+
- Added a dedicated Triton state serialization routine supporting FP32, BF16, and FP16 state representations with optional bitwise stochastic rounding applied directly to momentum buffers.
|
| 272 |
+
- **Multi-Device & Cross-Accelerator Hardware Context Isolation (`_get_device_context`)**:
|
| 273 |
+
- Unified device stream scoping supporting NVIDIA CUDA (`torch.cuda`), Intel XPU (`torch.xpu`), and modern PyTorch 2.x unified accelerator interfaces (`torch.accelerator.device`), eliminating multi-GPU stream hijacking and cross-device race conditions.
|
| 274 |
+
- **Strict Determinism Auto-Routing**:
|
| 275 |
+
- Automatically queries `torch.are_deterministic_algorithms_enabled()`. When enabled under `execution="auto"`, dynamically routes execution to C++ vectorized `foreach` to preserve bitwise cross-run reproducibility, circumventing non-deterministic floating-point hardware atomic reordering.
|
| 276 |
+
- **Granular Fault Recovery with State Guarding (`completed_indices`)**:
|
| 277 |
+
- Implemented per-parameter completion tracking in `_dispatch_triton`. If a kernel encounters an unexpected runtime fault, completed parameters are preserved while uncompleted ones fall back cleanly to `foreach`.
|
| 278 |
+
- Added `_single_step_v2_apply_param` and `_single_step_v1_apply_param` to safely finalize parameter updates if Pass 1 had already committed state transitions, strictly preventing double-update state corruption.
|
| 279 |
+
|
| 280 |
+
### Changed
|
| 281 |
+
- **Per-Parameter Persistent Scratch Tensors (Elimination of Global Slicing)**:
|
| 282 |
+
- Replaced the global dynamic slice allocator (`self._scratch_tensors[dev][:count]`) with isolated per-parameter persistent state allocations (`state["scratch"] = torch.zeros((5,), device=p.device, dtype=torch.float32)`).
|
| 283 |
+
- Memory pointers for `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` remain completely invariant throughout the entire training lifecycle, preserving pointer stability for **CUDA Graph Capture** (`torch.accelerator.Graph`) and TorchInductor AOT compilation without memory aliasing.
|
| 284 |
+
- **Analytically Exact Nesterov Bias Correction ($\beta_1^{\text{step} + 1}$)**:
|
| 285 |
+
- Corrected first-moment bias correction from the classical Adam formulation ($1.0 - \beta_1^{\text{step}}$) to the exact analytical expectation of the Nesterov blended momentum: `bc1_nes = 1.0 - beta1^(step + 1)`. Removes residual momentum bias during early warmup iterations.
|
| 286 |
+
- **Adaptive CTA Tile Sizing and Dynamic Thread Warp Scaling**:
|
| 287 |
+
- Introduced hardware-aware CTA tile scaling based on parameter volume: parameters with $\ge 2,097,152$ elements (2M+) dynamically configure `BLOCK_SIZE = 2048` and `num_warps = 8` (versus `BLOCK_SIZE = 1024`, `num_warps = 4` for smaller tensors).
|
| 288 |
+
- Cuts total thread-block grid counts and L2 cache atomic addition pressure by 50% on large MLP, Attention Projection, and Vocabulary layers.
|
| 289 |
+
- **Hardware-Native Tanh Instruction Dispatch (`tl.math.tanh` / `tl.tanh`)**:
|
| 290 |
+
- Upgraded `_triton_tanh_fast` to dynamically inspect compiler capabilities, binding directly to native PTX Special Function Unit (SFU) instructions (`tanh.approx.f32`) on modern Triton compilers while retaining analytical sigmoid fallbacks for legacy environments.
|
| 291 |
+
- **PRNG Decorrelation between Momentum and Parameter Updates**:
|
| 292 |
+
- Separated RNG seed streams using bitwise alternating mask shifts: `seed_p = (seed + 0x55555555) & 0x7FFFFFFF`, mathematically eliminating cross-coupling noise correlation between state buffer rounding and parameter update rounding.
|
| 293 |
+
- **Bumped specification version metadata to 1.3.0** across `luminav.py`.
|
| 294 |
+
|
| 295 |
+
### Fixed
|
| 296 |
+
- **Floating-Point Cancellation in Radial Bounding ($r \to 0$)**:
|
| 297 |
+
- Fixed precision jitter and roundoff degradation near zero by adding an asymptotic Taylor-series guard across Triton and PyTorch bounding engines: `torch.where(r < 1e-4, 1.0, tanh(r) / r)`.
|
| 298 |
+
- **Artificial Denominator Dilation in Dispersion Scale ($\sigma$)**:
|
| 299 |
+
- Replaced additive epsilon inflation (`sigma + 1e-6`) with a non-distorting floor clamp (`tl.maximum(sigma, 1e-12)`), preventing artificial dampening of subtle gradient signals.
|
| 300 |
+
- **Singular Zero-Division in Parameter Norm Clamping**:
|
| 301 |
+
- Replaced `norm_p + 1e-6` with `torch.clamp_min(norm_p, 1.0)` / `tl.maximum(norm_p, 1.0)`, maintaining numerical stability even when parameters are initialized near zero.
|
| 302 |
+
- **Transient Memory Pool Spikes during PyTorch Fallback Stochastic Rounding**:
|
| 303 |
+
- Refactored `_sr_update` to stream stochastic noise generation in bounded chunks of 1,048,576 elements (~4 MB chunk size), eliminating multi-hundred megabyte temporary int32 tensor spikes during fallback execution on large parameter matrices.
|
| 304 |
+
- **Non-Contiguous Buffer Safety & Strided Memory Copy**:
|
| 305 |
+
- Added strict contiguous layout verification for parameters, states, and master weights with in-place synchronization (`p.copy_(p_contig)`), guarded by a one-time developer warning.
|
| 306 |
+
|
| 307 |
+
### Performance & Engine Refactoring
|
| 308 |
+
- **~50% Global Memory Bandwidth Reduction via Fused Single-Pass Mode**:
|
| 309 |
+
- By unifying parameter loading, gradient evaluation, state transformation, and bounding into one GPU pass under `fused_single_pass=True`, VRAM read/write cycles are halved for cautious and radially bounded steps.
|
| 310 |
+
- **Sub-Microsecond L2 Hardware Atomic Reduction Architecture**:
|
| 311 |
+
- Formally documented the hybrid Intra-Block Tree Reduction + Inter-Block Hardware Atomic pattern. With `BLOCK_SIZE=2048`, a 10M-parameter tensor issues only 4,883 atomic additions directly into GPU L2 cache crossbars, executing in < 0.5 µs and strictly outperforming multi-tier cascaded reduction micro-kernels by eliminating 3–5 µs driver dispatch bubbles.
|
| 312 |
+
- **Static Buffer Address Invariance for CUDA Graphs & TorchInductor AOT**:
|
| 313 |
+
- Persistent 5-slot state scratch buffers eliminate all dynamic slicing and memory reallocations during optimization loops, preserving pointer address invariance for CUDA Graph replay.
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## [1.2.1] - 2026-09-17
|
| 318 |
+
|
| 319 |
+
### Added
|
| 320 |
+
- **In-Kernel GPU Parameter Norm Reduction (`p_sq_sum_ptr`)**: Fused parameter Euclidean norm squared $\sum(p_i^2)$ calculation directly into Pass 1 Triton reduction kernels (`_lumina_v2_pass1_kernel` and `_lumina_v1_pass1_kernel`). Pass 2 and Pass 3 now load and resolve `norm_p` (`tl.sqrt(tl.maximum(p_sq_sum, 0.0))`) entirely inside GPU SRAM/registers without host intervention.
|
| 321 |
+
- **Stateless CPU Integer Multiplicative PRNG Hashing**: Replaced dynamic GPU RNG tensor allocation with a pure CPU integer hash using Knuth's 32-bit golden ratio constant: `(step * 0x9E3779B9 + i * 10007) & 0x7FFFFFFF`. Generates uncorrelated 31-bit integer seeds per layer with zero GPU kernel launch overhead and zero PCIe host-device latency.
|
| 322 |
+
- **Isolated Generator Cache for Fallback Engine (`self._generators`)**: Added an internal per-device `torch.Generator` cache, ensuring stochastic rounding in `_sr_update` operates in complete isolation from PyTorch's default CUDA generator state.
|
| 323 |
+
|
| 324 |
+
### Changed
|
| 325 |
+
- **Consolidated Scratch Buffer Zeroing**: Removed redundant per-parameter slice zeroing inside parameter loops. Buffer zeroing is now batched into a single consolidated `scratch.zero_()` call per step inside `_get_scratch_buffer`.
|
| 326 |
+
- **Scratch Buffer Stride Expansion**:
|
| 327 |
+
- `buffer=2`: Expanded stride from 2×N to 3×N elements per parameter to host `[mask_sum, u_sq_sum, p_sq_sum]`.
|
| 328 |
+
- `buffer=1`: Expanded stride from 3×N to 4×N elements per parameter to host `[rms_sum, mask_sum, u_sq_sum, p_sq_sum]`.
|
| 329 |
+
- **Bumped specification version metadata to 1.2.1**.
|
| 330 |
+
|
| 331 |
+
### Fixed
|
| 332 |
+
- **Host-Device Synchronization & Pipeline Stalls (`.item()` Bottleneck)**: Removed `norm_p = float(p_contig.float().norm().item())` and `seed = torch.randint(...).item()` from Triton step pipelines. Eliminated 200–400 synchronous PCIe roundtrips and GPU pipeline stalls per step on deep architectures (e.g., 28-layer Transformers), restoring full asynchronous GPU compute utilization.
|
| 333 |
+
- **CUDA Graphs & `torch.compile` Graph Breaks**: Fixed fatal graph capture crashes and TorchDynamo graph breaks caused by blocking host-device synchronization during radial bounding and seed generation. The optimizer step is now compatible with CUDA Graphs (`torch.accelerator.Graph`) and TorchInductor compilation.
|
| 334 |
+
- **Transient VRAM Allocator Thrashing**: Eliminated repeated temporary FP32 parameter cloning (`p_contig.float()`) during norm evaluation, preventing memory pool fragmentation in tight VRAM regimes.
|
| 335 |
+
- **Global RNG Pollution & Determinism Compliance**: Fixed global RNG state mutation in fallback stochastic rounding. Solved reproducibility drift in upstream Dropout, DataLoader shuffling, and RLHF reward modeling, ensuring strict adherence to PyTorch 2.10+ `torch.use_deterministic_algorithms(True)`.
|
| 336 |
+
|
| 337 |
+
### Performance & Engine Refactoring
|
| 338 |
+
- **Batch Kernel Launch Overhead Elimination**: Removed up to 400–800 individual `cudaMemsetAsync` micro-calls per step previously triggered by per-slice `.zero_()` invocations on 1-element scratch tensors.
|
| 339 |
+
- **Single-Pass Bypass for Coordinate Bounding**: Streamlined `_triton_step_v2` to immediately bypass two-pass reductions and invoke `_lumina_v2_single_pass_kernel` when `cautious=False` and coordinate bounding (`bound_mode == 2`) is selected.
|
| 340 |
+
|
| 341 |
+
---
|
| 342 |
+
|
| 343 |
+
## [1.2.0] - 2026-09-17
|
| 344 |
+
|
| 345 |
+
### Added
|
| 346 |
+
- **Automatic Dynamic FP16 Cliff Governor:** Built-in asymptotic boundary governor (`_triton_tanh_fast` in GPU register and PyTorch fallback) that automatically activates when `p.dtype == torch.float16`. Prevents unclipped steps from pushing parameters, activations, and residual streams into the IEEE-754 FP16 overflow cliff (> 65,504). Remains completely dormant (zero overhead) on `bfloat16` (TPU / Ampere+) and `float32`.
|
| 347 |
+
- **Smooth Asymptotic Parameter Bounding (`bound`, `bound_type`, `bound_ratio`):** Added non-intrusive step bounding with two mathematically continuous formulations:
|
| 348 |
+
- `"radial"`: Direction-Preserving Radial Squashing (`update * tanh(r) / r`) preserving gradient direction (Cosine Similarity = 1.000).
|
| 349 |
+
- `"coordinate"`: Dual-Hyperbolic Coordinate Squashing for fine-grained per-element damping.
|
| 350 |
+
- Configurable via constructor: `bound: bool = True`, `bound_type: str = "radial"`, `bound_ratio: float = 0.03`.
|
| 351 |
+
|
| 352 |
+
### Changed
|
| 353 |
+
- **Increased Default `cautious_clamp_min` from `0.2` to `0.5`:** Enforces an exact power-of-two maximum energy scaling factor (2.00x, $2^1$). Eliminates floating-point mantissa roundoff distortion in IEEE-754 hardware.
|
| 354 |
+
- Bumped specification version metadata to `1.2.0`.
|
| 355 |
+
|
| 356 |
+
### Fixed
|
| 357 |
+
* **FP16 Step Divergence & NaN Overflow at High Learning Rates:** Solved step divergence and subsequent NaN crashes when training deep 28-layer Transformer architectures (e.g., Qwen3-0.6B) in pure FP16 at nominal learning rates (`lr = 8e-4`).
|
| 358 |
+
* **Sparse Embedding & LM-Head Amplification:** Mitigated 5x over-amplification on sparse vocabulary gradients where active token counts are much smaller than total tensor parameters ($N \gg N_{\text{active}}$).
|
| 359 |
+
* **Verified stable 1,000-step pretraining from scratch on Qwen3-0.6B** (seq length 512, pure FP16, `lr = 8e-4`) on Nvidia Tesla T4 with zero gradient clipping and zero LR scheduler (loss dropped from 12.09 down to ~5.7 with zero NaNs).
|
| 360 |
+
* **Weight Decay Numerical Parity on Fallback Engine:** Resolved a silent precision-stagnation issue in `_single_step` and `_foreach_step`. Previously, decoupled weight decay (`p.mul_(1.0 - lr * weight_decay)`) was executed directly on native FP16/BF16 tensors prior to `_sr_update`. Due to IEEE-754 Round-to-Nearest (RTN) limits, `(1 - lr * weight_decay) ≈ 0.999936` rounded back to `1.0`, causing weight decay to freeze on half-precision parameters. Weight decay is now unified into FP32 registers inside `_sr_update` alongside stochastic rounding, matching the numerical behavior with the Triton kernel.
|
| 361 |
+
* **Fallback Foreach Indentation & Scope Alignment:** Corrected tensor iteration scope and indentation for `torch._foreach_add_` in non-SR fallback branches.
|
| 362 |
+
* **In-Place GPU Clamping in `_apply_bound`:** Replaced dynamic GPU scalar allocations (`torch.tensor(1.5e-4, device=p.device)`) with native `torch.clamp_min`, eliminating transient CUDA malloc spikes per update step.
|
| 363 |
+
|
| 364 |
+
### Performance & Engine Refactoring
|
| 365 |
+
* **Unified Bound Execution Pipeline:** Added an immediate short-circuit guard (`if lr == 0.0: return update`) and streamlined the execution flow: intermediate updates are scaled in FP32 (`step_val = lr * u_float`), passed through radial/coordinate bounding, chained cleanly into the FP16 Cliff Governor, and returned in a single normalized step.
|
| 366 |
+
|
| 367 |
+
---
|
| 368 |
+
|
| 369 |
+
## [1.1.5] - 2026-09-15
|
| 370 |
+
|
| 371 |
+
### Added
|
| 372 |
+
- **Native Hardware Capability Detection (`_supports_native_bf16`):** Added automatic device capability detection via `torch.cuda.is_bf16_supported()` to identify Ampere SM80+ architectures and streamline native execution paths.
|
| 373 |
+
- **Hugging Face Repository Reference:** Added official Hugging Face repository link in module docstrings.
|
| 374 |
+
|
| 375 |
+
### Changed
|
| 376 |
+
- **Dynamic Epsilon Clamping for FP16:** Dynamically floors effective epsilon to `max(eps, 1e-4)` when parameters or states are in `torch.float16`. Prevents bias-corrected scale factor `c2` from underflowing into IEEE-754 subnormal/zero limits on step 1.
|
| 377 |
+
- Bumped specification version to `1.1.5`.
|
| 378 |
+
|
| 379 |
+
### Fixed
|
| 380 |
+
- **FP16 Step-1 `NaN` from Epsilon Underflow:** Fixed `0.0 / 0.0 = NaN` division occurring on initial training steps when parameters receive zero gradients (e.g., unselected vocabulary embeddings, padding tokens, or dropout paths).
|
| 381 |
+
- **Second-Moment Variance Saturation (`+inf`):** Added safe upper-bound register clamping (`tl.minimum(v_new, 65000.0)` in Triton kernels and `.clamp_(0.0, 65000.0)` in PyTorch C++ foreach/single loops). Prevents unclipped initial pretraining gradient surges ($|g| > 256$) from overflowing the FP16 maximum dynamic range (65,504) into irreversible `+inf`.
|
| 382 |
+
- **Zero-Sigma Division Guard:** Injected explicit `sigma + 1e-6` denominator guards across all Triton kernel passes and PyTorch update steps to ensure numerical safety during cold-start iterations.
|
| 383 |
+
- Verified stable 100-step pretraining from scratch on `SmolLM-135M` using Wikimedia Wikipedia in pure FP16 on Nvidia Tesla T4 GPU with zero gradient clipping.
|
| 384 |
+
|
| 385 |
+
---
|
| 386 |
+
|
| 387 |
+
## [1.1.0] - 2026-09-11
|
| 388 |
+
|
| 389 |
+
### Added
|
| 390 |
+
- **Bitwise Stochastic Rounding (SR):** Integrated on-chip PRNG integer bit-manipulation hash in Triton kernels and vectorized PyTorch fallback for native FP16/BF16 training without master weights.
|
| 391 |
+
- Unified Triton store routine introducing compile-time branching (`dtype_mode` / `use_sr: tl.constexpr`) with explicit safe casting for FP16 and BF16 pointers.
|
| 392 |
+
- Vectorized fallback improvements utilizing C++ multi-tensor operations when stochastic rounding is disabled or tensors are FP32.
|
| 393 |
+
- State buffer contiguity validation (`exp_avg_contig`, `exp_avg_sq_contig`) ensuring safe linear memory addressing before kernel dispatch.
|
| 394 |
+
- Declarative configuration file (`config.json`) defining hyperparameters and optimizer presets.
|
| 395 |
+
|
| 396 |
+
### Changed
|
| 397 |
+
- Refactored PRNG integer arithmetic to strictly stay within signed 32-bit integer limits ($< 2^{31} - 1$), eliminating JIT compilation failures on newer Triton releases.
|
| 398 |
+
- State buffers (`exp_avg`, `exp_avg_sq`) now initialize with strict contiguous memory format.
|
| 399 |
+
|
| 400 |
+
### Fixed
|
| 401 |
+
- Fixed `ValueError: Scalar out of range for type int32` and Triton LLVM compile errors during unsigned 32-bit bitmask operations.
|
| 402 |
+
- Resolved kernel fallback hangs and compilation overhead on CUDA devices.
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
## [1.0.2] - 2026-09-08
|
| 407 |
+
|
| 408 |
+
### Added
|
| 409 |
+
- Official Apache-2.0 license notice and copyright headers.
|
| 410 |
+
- Standardized package exports (`__all__`, `__version__`, `__author__`, `__license__`).
|
| 411 |
+
|
| 412 |
+
---
|
| 413 |
+
|
| 414 |
+
## [1.0.1] - 2026-08-31
|
| 415 |
+
|
| 416 |
+
### Fixed
|
| 417 |
+
- **Memory Synchronization Fix (`423d171`):** Fixed a critical bug where non-contiguous parameter tensors (e.g., transposed linear layers or sliced weights) were updated on temporary contiguous copies without reflecting changes back to model weights. Added `p.copy_(p_contig)` after Triton execution.
|
| 418 |
+
|
| 419 |
+
---
|
| 420 |
+
|
| 421 |
+
## [1.0.0] - 2026-08-30
|
| 422 |
+
|
| 423 |
+
### Added
|
| 424 |
+
- Initial release of the LuminaV optimizer.
|
| 425 |
+
- **Zero Master-Weight Copies:** Direct parameter updates in native half-precision without allocating 4-byte FP32 master weights.
|
| 426 |
+
- **Hyperbolic Tangent (`tanh`) Bounding Envelope:** Projects normalized momentum ratio into `(-1.0, 1.0)` to eliminate exploding gradient shocks.
|
| 427 |
+
- **Directional Alignment Gate:** Cautious update masking that zeroes out momentum steps opposing mini-batch gradient directions.
|
| 428 |
+
- **Central Innovation Variance:** Noise dispersion tracking $(g_t - m_t)^2$ decoupling mini-batch turbulence from directional descent.
|
| 429 |
+
- Dual-buffer mode (`buffer=2`, LuminaV-2B) and single-buffer mode (`buffer=1`, LuminaV-1).
|
| 430 |
+
- Custom fused OpenAI Triton kernels with automated fallback execution.
|
Cargo.lock
CHANGED
|
@@ -1,1218 +1,1218 @@
|
|
| 1 |
-
# This file is automatically @generated by Cargo.
|
| 2 |
-
# It is not intended for manual editing.
|
| 3 |
-
version = 4
|
| 4 |
-
|
| 5 |
-
[[package]]
|
| 6 |
-
name = "ahash"
|
| 7 |
-
version = "0.8.12"
|
| 8 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 9 |
-
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
| 10 |
-
dependencies = [
|
| 11 |
-
"cfg-if",
|
| 12 |
-
"const-random",
|
| 13 |
-
"getrandom 0.3.4",
|
| 14 |
-
"once_cell",
|
| 15 |
-
"version_check",
|
| 16 |
-
"zerocopy",
|
| 17 |
-
]
|
| 18 |
-
|
| 19 |
-
[[package]]
|
| 20 |
-
name = "alloc-no-stdlib"
|
| 21 |
-
version = "3.0.0"
|
| 22 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 23 |
-
checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
|
| 24 |
-
|
| 25 |
-
[[package]]
|
| 26 |
-
name = "alloc-stdlib"
|
| 27 |
-
version = "0.3.0"
|
| 28 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 29 |
-
checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
|
| 30 |
-
dependencies = [
|
| 31 |
-
"alloc-no-stdlib",
|
| 32 |
-
]
|
| 33 |
-
|
| 34 |
-
[[package]]
|
| 35 |
-
name = "allocator-api2"
|
| 36 |
-
version = "0.2.21"
|
| 37 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 38 |
-
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
| 39 |
-
|
| 40 |
-
[[package]]
|
| 41 |
-
name = "android_system_properties"
|
| 42 |
-
version = "0.1.6"
|
| 43 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 44 |
-
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
| 45 |
-
dependencies = [
|
| 46 |
-
"libc",
|
| 47 |
-
]
|
| 48 |
-
|
| 49 |
-
[[package]]
|
| 50 |
-
name = "arrow-array"
|
| 51 |
-
version = "60.1.0"
|
| 52 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 53 |
-
checksum = "0f3d22a41d3df78b71df0b685af219ae3a94188ab98d6fb29c958eb55c3247f4"
|
| 54 |
-
dependencies = [
|
| 55 |
-
"ahash",
|
| 56 |
-
"arrow-buffer",
|
| 57 |
-
"arrow-data",
|
| 58 |
-
"arrow-schema",
|
| 59 |
-
"chrono",
|
| 60 |
-
"half",
|
| 61 |
-
"hashbrown",
|
| 62 |
-
"num-complex",
|
| 63 |
-
"num-integer",
|
| 64 |
-
"num-traits",
|
| 65 |
-
]
|
| 66 |
-
|
| 67 |
-
[[package]]
|
| 68 |
-
name = "arrow-buffer"
|
| 69 |
-
version = "60.1.0"
|
| 70 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 71 |
-
checksum = "7bd46a530bc1a8e763102fc36c1dc0f51f3ffa150c78e7f5658ccb54b1458ade"
|
| 72 |
-
dependencies = [
|
| 73 |
-
"bytes",
|
| 74 |
-
"half",
|
| 75 |
-
"num-bigint",
|
| 76 |
-
"num-traits",
|
| 77 |
-
]
|
| 78 |
-
|
| 79 |
-
[[package]]
|
| 80 |
-
name = "arrow-cmp"
|
| 81 |
-
version = "60.1.0"
|
| 82 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 83 |
-
checksum = "1913e57a64fbf2c4502fa6d54b547d6a1ae59e3146cbc15321d47468c937abd1"
|
| 84 |
-
dependencies = [
|
| 85 |
-
"arrow-array",
|
| 86 |
-
"arrow-buffer",
|
| 87 |
-
"arrow-schema",
|
| 88 |
-
]
|
| 89 |
-
|
| 90 |
-
[[package]]
|
| 91 |
-
name = "arrow-data"
|
| 92 |
-
version = "60.1.0"
|
| 93 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 94 |
-
checksum = "415b7fbc4cf56b080bb5677a4ae74211b5c79e571ed37a1d547633ec07f3b43b"
|
| 95 |
-
dependencies = [
|
| 96 |
-
"arrow-buffer",
|
| 97 |
-
"arrow-schema",
|
| 98 |
-
"half",
|
| 99 |
-
"num-integer",
|
| 100 |
-
"num-traits",
|
| 101 |
-
]
|
| 102 |
-
|
| 103 |
-
[[package]]
|
| 104 |
-
name = "arrow-ipc"
|
| 105 |
-
version = "60.1.0"
|
| 106 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 107 |
-
checksum = "45f3e766bccf5a0cb57a5de52a4cba450dea5171547c2cb5939b486d08887cba"
|
| 108 |
-
dependencies = [
|
| 109 |
-
"arrow-array",
|
| 110 |
-
"arrow-buffer",
|
| 111 |
-
"arrow-data",
|
| 112 |
-
"arrow-schema",
|
| 113 |
-
"arrow-select",
|
| 114 |
-
"flatbuffers",
|
| 115 |
-
]
|
| 116 |
-
|
| 117 |
-
[[package]]
|
| 118 |
-
name = "arrow-schema"
|
| 119 |
-
version = "60.1.0"
|
| 120 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 121 |
-
checksum = "de2c8e714b466404cdc1189e89f892426efea614d938e16c15518c340cd43ba3"
|
| 122 |
-
|
| 123 |
-
[[package]]
|
| 124 |
-
name = "arrow-select"
|
| 125 |
-
version = "60.1.0"
|
| 126 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 127 |
-
checksum = "a5af3f6656c5f2c6944f93d9a357a7f204c93d5f349385a44cf46c4a26fbcf30"
|
| 128 |
-
dependencies = [
|
| 129 |
-
"ahash",
|
| 130 |
-
"arrow-array",
|
| 131 |
-
"arrow-buffer",
|
| 132 |
-
"arrow-cmp",
|
| 133 |
-
"arrow-data",
|
| 134 |
-
"arrow-schema",
|
| 135 |
-
"num-traits",
|
| 136 |
-
]
|
| 137 |
-
|
| 138 |
-
[[package]]
|
| 139 |
-
name = "ash"
|
| 140 |
-
version = "0.38.0+1.3.281"
|
| 141 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 142 |
-
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
| 143 |
-
dependencies = [
|
| 144 |
-
"libloading 0.8.9",
|
| 145 |
-
]
|
| 146 |
-
|
| 147 |
-
[[package]]
|
| 148 |
-
name = "autocfg"
|
| 149 |
-
version = "1.5.1"
|
| 150 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 151 |
-
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
| 152 |
-
|
| 153 |
-
[[package]]
|
| 154 |
-
name = "base64"
|
| 155 |
-
version = "0.23.1"
|
| 156 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 157 |
-
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
| 158 |
-
|
| 159 |
-
[[package]]
|
| 160 |
-
name = "bitflags"
|
| 161 |
-
version = "2.13.2"
|
| 162 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 163 |
-
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
| 164 |
-
|
| 165 |
-
[[package]]
|
| 166 |
-
name = "brotli"
|
| 167 |
-
version = "9.0.0"
|
| 168 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 169 |
-
checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
|
| 170 |
-
dependencies = [
|
| 171 |
-
"alloc-no-stdlib",
|
| 172 |
-
"alloc-stdlib",
|
| 173 |
-
"brotli-decompressor",
|
| 174 |
-
]
|
| 175 |
-
|
| 176 |
-
[[package]]
|
| 177 |
-
name = "brotli-decompressor"
|
| 178 |
-
version = "6.0.1"
|
| 179 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 180 |
-
checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
|
| 181 |
-
dependencies = [
|
| 182 |
-
"alloc-no-stdlib",
|
| 183 |
-
"alloc-stdlib",
|
| 184 |
-
]
|
| 185 |
-
|
| 186 |
-
[[package]]
|
| 187 |
-
name = "bumpalo"
|
| 188 |
-
version = "3.20.3"
|
| 189 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 190 |
-
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
| 191 |
-
|
| 192 |
-
[[package]]
|
| 193 |
-
name = "bytes"
|
| 194 |
-
version = "1.12.1"
|
| 195 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 196 |
-
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
| 197 |
-
|
| 198 |
-
[[package]]
|
| 199 |
-
name = "cc"
|
| 200 |
-
version = "1.8.0"
|
| 201 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 202 |
-
checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838"
|
| 203 |
-
dependencies = [
|
| 204 |
-
"find-msvc-tools",
|
| 205 |
-
"jobserver",
|
| 206 |
-
"libc",
|
| 207 |
-
"shlex",
|
| 208 |
-
]
|
| 209 |
-
|
| 210 |
-
[[package]]
|
| 211 |
-
name = "cfg-if"
|
| 212 |
-
version = "1.0.5"
|
| 213 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 214 |
-
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
| 215 |
-
|
| 216 |
-
[[package]]
|
| 217 |
-
name = "chrono"
|
| 218 |
-
version = "0.4.45"
|
| 219 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 220 |
-
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
| 221 |
-
dependencies = [
|
| 222 |
-
"iana-time-zone",
|
| 223 |
-
"num-traits",
|
| 224 |
-
"windows-link",
|
| 225 |
-
]
|
| 226 |
-
|
| 227 |
-
[[package]]
|
| 228 |
-
name = "const-random"
|
| 229 |
-
version = "0.1.18"
|
| 230 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 231 |
-
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
| 232 |
-
dependencies = [
|
| 233 |
-
"const-random-macro",
|
| 234 |
-
]
|
| 235 |
-
|
| 236 |
-
[[package]]
|
| 237 |
-
name = "const-random-macro"
|
| 238 |
-
version = "0.1.16"
|
| 239 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 240 |
-
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
| 241 |
-
dependencies = [
|
| 242 |
-
"getrandom 0.2.17",
|
| 243 |
-
"once_cell",
|
| 244 |
-
"tiny-keccak",
|
| 245 |
-
]
|
| 246 |
-
|
| 247 |
-
[[package]]
|
| 248 |
-
name = "core-foundation-sys"
|
| 249 |
-
version = "0.8.7"
|
| 250 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 251 |
-
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
| 252 |
-
|
| 253 |
-
[[package]]
|
| 254 |
-
name = "crc32fast"
|
| 255 |
-
version = "1.5.2"
|
| 256 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 257 |
-
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
| 258 |
-
dependencies = [
|
| 259 |
-
"cfg-if",
|
| 260 |
-
]
|
| 261 |
-
|
| 262 |
-
[[package]]
|
| 263 |
-
name = "crossbeam-deque"
|
| 264 |
-
version = "0.8.8"
|
| 265 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 266 |
-
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
| 267 |
-
dependencies = [
|
| 268 |
-
"crossbeam-epoch",
|
| 269 |
-
"crossbeam-utils",
|
| 270 |
-
]
|
| 271 |
-
|
| 272 |
-
[[package]]
|
| 273 |
-
name = "crossbeam-epoch"
|
| 274 |
-
version = "0.9.21"
|
| 275 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 276 |
-
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
| 277 |
-
dependencies = [
|
| 278 |
-
"crossbeam-utils",
|
| 279 |
-
]
|
| 280 |
-
|
| 281 |
-
[[package]]
|
| 282 |
-
name = "crossbeam-utils"
|
| 283 |
-
version = "0.8.23"
|
| 284 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 285 |
-
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
| 286 |
-
|
| 287 |
-
[[package]]
|
| 288 |
-
name = "crunchy"
|
| 289 |
-
version = "0.2.4"
|
| 290 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 291 |
-
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
| 292 |
-
|
| 293 |
-
[[package]]
|
| 294 |
-
name = "either"
|
| 295 |
-
version = "1.19.0"
|
| 296 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 297 |
-
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
| 298 |
-
|
| 299 |
-
[[package]]
|
| 300 |
-
name = "equivalent"
|
| 301 |
-
version = "1.0.3"
|
| 302 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 303 |
-
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
| 304 |
-
|
| 305 |
-
[[package]]
|
| 306 |
-
name = "find-msvc-tools"
|
| 307 |
-
version = "0.1.14"
|
| 308 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 309 |
-
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
| 310 |
-
|
| 311 |
-
[[package]]
|
| 312 |
-
name = "flatbuffers"
|
| 313 |
-
version = "25.12.19"
|
| 314 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 315 |
-
checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3"
|
| 316 |
-
dependencies = [
|
| 317 |
-
"bitflags",
|
| 318 |
-
"rustc_version",
|
| 319 |
-
]
|
| 320 |
-
|
| 321 |
-
[[package]]
|
| 322 |
-
name = "flate2"
|
| 323 |
-
version = "1.1.10"
|
| 324 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 325 |
-
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
| 326 |
-
dependencies = [
|
| 327 |
-
"zlib-rs",
|
| 328 |
-
]
|
| 329 |
-
|
| 330 |
-
[[package]]
|
| 331 |
-
name = "foldhash"
|
| 332 |
-
version = "0.2.0"
|
| 333 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 334 |
-
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
| 335 |
-
|
| 336 |
-
[[package]]
|
| 337 |
-
name = "futures-core"
|
| 338 |
-
version = "0.3.34"
|
| 339 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 340 |
-
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
| 341 |
-
|
| 342 |
-
[[package]]
|
| 343 |
-
name = "futures-task"
|
| 344 |
-
version = "0.3.34"
|
| 345 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 346 |
-
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
| 347 |
-
|
| 348 |
-
[[package]]
|
| 349 |
-
name = "futures-util"
|
| 350 |
-
version = "0.3.34"
|
| 351 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 352 |
-
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
| 353 |
-
dependencies = [
|
| 354 |
-
"futures-core",
|
| 355 |
-
"futures-task",
|
| 356 |
-
"pin-project-lite",
|
| 357 |
-
"slab",
|
| 358 |
-
]
|
| 359 |
-
|
| 360 |
-
[[package]]
|
| 361 |
-
name = "getrandom"
|
| 362 |
-
version = "0.2.17"
|
| 363 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 364 |
-
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
| 365 |
-
dependencies = [
|
| 366 |
-
"cfg-if",
|
| 367 |
-
"libc",
|
| 368 |
-
"wasi",
|
| 369 |
-
]
|
| 370 |
-
|
| 371 |
-
[[package]]
|
| 372 |
-
name = "getrandom"
|
| 373 |
-
version = "0.3.4"
|
| 374 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 375 |
-
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
| 376 |
-
dependencies = [
|
| 377 |
-
"cfg-if",
|
| 378 |
-
"libc",
|
| 379 |
-
"r-efi 5.3.0",
|
| 380 |
-
"wasip2",
|
| 381 |
-
]
|
| 382 |
-
|
| 383 |
-
[[package]]
|
| 384 |
-
name = "getrandom"
|
| 385 |
-
version = "0.4.3"
|
| 386 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 387 |
-
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
| 388 |
-
dependencies = [
|
| 389 |
-
"cfg-if",
|
| 390 |
-
"libc",
|
| 391 |
-
"r-efi 6.0.0",
|
| 392 |
-
]
|
| 393 |
-
|
| 394 |
-
[[package]]
|
| 395 |
-
name = "half"
|
| 396 |
-
version = "2.7.1"
|
| 397 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 398 |
-
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
| 399 |
-
dependencies = [
|
| 400 |
-
"cfg-if",
|
| 401 |
-
"crunchy",
|
| 402 |
-
"num-traits",
|
| 403 |
-
"zerocopy",
|
| 404 |
-
]
|
| 405 |
-
|
| 406 |
-
[[package]]
|
| 407 |
-
name = "hashbrown"
|
| 408 |
-
version = "0.17.1"
|
| 409 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 410 |
-
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
| 411 |
-
dependencies = [
|
| 412 |
-
"allocator-api2",
|
| 413 |
-
"equivalent",
|
| 414 |
-
"foldhash",
|
| 415 |
-
]
|
| 416 |
-
|
| 417 |
-
[[package]]
|
| 418 |
-
name = "heck"
|
| 419 |
-
version = "0.5.0"
|
| 420 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 421 |
-
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
| 422 |
-
|
| 423 |
-
[[package]]
|
| 424 |
-
name = "iana-time-zone"
|
| 425 |
-
version = "0.1.65"
|
| 426 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 427 |
-
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
| 428 |
-
dependencies = [
|
| 429 |
-
"android_system_properties",
|
| 430 |
-
"core-foundation-sys",
|
| 431 |
-
"iana-time-zone-haiku",
|
| 432 |
-
"js-sys",
|
| 433 |
-
"log",
|
| 434 |
-
"wasm-bindgen",
|
| 435 |
-
"windows-core",
|
| 436 |
-
]
|
| 437 |
-
|
| 438 |
-
[[package]]
|
| 439 |
-
name = "iana-time-zone-haiku"
|
| 440 |
-
version = "0.1.2"
|
| 441 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 442 |
-
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
| 443 |
-
dependencies = [
|
| 444 |
-
"cc",
|
| 445 |
-
]
|
| 446 |
-
|
| 447 |
-
[[package]]
|
| 448 |
-
name = "itoa"
|
| 449 |
-
version = "1.0.18"
|
| 450 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 451 |
-
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
| 452 |
-
|
| 453 |
-
[[package]]
|
| 454 |
-
name = "jobserver"
|
| 455 |
-
version = "0.1.35"
|
| 456 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 457 |
-
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
| 458 |
-
dependencies = [
|
| 459 |
-
"getrandom 0.4.3",
|
| 460 |
-
"libc",
|
| 461 |
-
]
|
| 462 |
-
|
| 463 |
-
[[package]]
|
| 464 |
-
name = "js-sys"
|
| 465 |
-
version = "0.3.106"
|
| 466 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 467 |
-
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
| 468 |
-
dependencies = [
|
| 469 |
-
"cfg-if",
|
| 470 |
-
"futures-util",
|
| 471 |
-
"wasm-bindgen",
|
| 472 |
-
]
|
| 473 |
-
|
| 474 |
-
[[package]]
|
| 475 |
-
name = "libc"
|
| 476 |
-
version = "0.2.191"
|
| 477 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 478 |
-
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
| 479 |
-
|
| 480 |
-
[[package]]
|
| 481 |
-
name = "libloading"
|
| 482 |
-
version = "0.8.9"
|
| 483 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 484 |
-
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
| 485 |
-
dependencies = [
|
| 486 |
-
"cfg-if",
|
| 487 |
-
"windows-link",
|
| 488 |
-
]
|
| 489 |
-
|
| 490 |
-
[[package]]
|
| 491 |
-
name = "libloading"
|
| 492 |
-
version = "0.9.0"
|
| 493 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 494 |
-
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
| 495 |
-
dependencies = [
|
| 496 |
-
"cfg-if",
|
| 497 |
-
"windows-link",
|
| 498 |
-
]
|
| 499 |
-
|
| 500 |
-
[[package]]
|
| 501 |
-
name = "libm"
|
| 502 |
-
version = "0.2.16"
|
| 503 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 504 |
-
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
| 505 |
-
|
| 506 |
-
[[package]]
|
| 507 |
-
name = "lock_api"
|
| 508 |
-
version = "0.4.14"
|
| 509 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 510 |
-
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
| 511 |
-
dependencies = [
|
| 512 |
-
"scopeguard",
|
| 513 |
-
]
|
| 514 |
-
|
| 515 |
-
[[package]]
|
| 516 |
-
name = "log"
|
| 517 |
-
version = "0.4.34"
|
| 518 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 519 |
-
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
| 520 |
-
|
| 521 |
-
[[package]]
|
| 522 |
-
name = "luminamoon-dataloader"
|
| 523 |
-
version = "1.1.0-post.3"
|
| 524 |
-
dependencies = [
|
| 525 |
-
"arrow-array",
|
| 526 |
-
"arrow-ipc",
|
| 527 |
-
"arrow-schema",
|
| 528 |
-
"crc32fast",
|
| 529 |
-
"hashbrown",
|
| 530 |
-
"parking_lot",
|
| 531 |
-
"parquet",
|
| 532 |
-
"pyo3",
|
| 533 |
-
"rayon",
|
| 534 |
-
"serde",
|
| 535 |
-
"serde_json",
|
| 536 |
-
"thiserror",
|
| 537 |
-
"xxhash-rust",
|
| 538 |
-
]
|
| 539 |
-
|
| 540 |
-
[[package]]
|
| 541 |
-
name = "luminamoon-optimizer"
|
| 542 |
-
version = "1.1.0-post.3"
|
| 543 |
-
dependencies = [
|
| 544 |
-
"luminamoon-dataloader",
|
| 545 |
-
"luminav-core",
|
| 546 |
-
"luminav-dispatch",
|
| 547 |
-
"luminav-simd",
|
| 548 |
-
"luminav-triton-aot",
|
| 549 |
-
"luminav-vulkan",
|
| 550 |
-
"pyo3",
|
| 551 |
-
"rayon",
|
| 552 |
-
"thiserror",
|
| 553 |
-
]
|
| 554 |
-
|
| 555 |
-
[[package]]
|
| 556 |
-
name = "luminav-core"
|
| 557 |
-
version = "1.1.0-post.3"
|
| 558 |
-
dependencies = [
|
| 559 |
-
"thiserror",
|
| 560 |
-
]
|
| 561 |
-
|
| 562 |
-
[[package]]
|
| 563 |
-
name = "luminav-dispatch"
|
| 564 |
-
version = "1.1.0-post.3"
|
| 565 |
-
dependencies = [
|
| 566 |
-
"luminav-core",
|
| 567 |
-
"luminav-simd",
|
| 568 |
-
"luminav-triton-aot",
|
| 569 |
-
"luminav-vulkan",
|
| 570 |
-
"thiserror",
|
| 571 |
-
]
|
| 572 |
-
|
| 573 |
-
[[package]]
|
| 574 |
-
name = "luminav-simd"
|
| 575 |
-
version = "1.1.0-post.3"
|
| 576 |
-
dependencies = [
|
| 577 |
-
"luminav-core",
|
| 578 |
-
"rayon",
|
| 579 |
-
]
|
| 580 |
-
|
| 581 |
-
[[package]]
|
| 582 |
-
name = "luminav-triton-aot"
|
| 583 |
-
version = "1.1.0-post.3"
|
| 584 |
-
dependencies = [
|
| 585 |
-
"libloading 0.9.0",
|
| 586 |
-
"luminav-core",
|
| 587 |
-
"serde",
|
| 588 |
-
"serde_json",
|
| 589 |
-
"thiserror",
|
| 590 |
-
]
|
| 591 |
-
|
| 592 |
-
[[package]]
|
| 593 |
-
name = "luminav-vulkan"
|
| 594 |
-
version = "1.1.0-post.3"
|
| 595 |
-
dependencies = [
|
| 596 |
-
"ash",
|
| 597 |
-
"luminav-core",
|
| 598 |
-
"thiserror",
|
| 599 |
-
]
|
| 600 |
-
|
| 601 |
-
[[package]]
|
| 602 |
-
name = "lz4_flex"
|
| 603 |
-
version = "0.14.0"
|
| 604 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 605 |
-
checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226"
|
| 606 |
-
dependencies = [
|
| 607 |
-
"twox-hash",
|
| 608 |
-
]
|
| 609 |
-
|
| 610 |
-
[[package]]
|
| 611 |
-
name = "memchr"
|
| 612 |
-
version = "2.8.3"
|
| 613 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 614 |
-
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
| 615 |
-
|
| 616 |
-
[[package]]
|
| 617 |
-
name = "num-bigint"
|
| 618 |
-
version = "0.5.1"
|
| 619 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 620 |
-
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
|
| 621 |
-
dependencies = [
|
| 622 |
-
"num-integer",
|
| 623 |
-
"num-traits",
|
| 624 |
-
]
|
| 625 |
-
|
| 626 |
-
[[package]]
|
| 627 |
-
name = "num-complex"
|
| 628 |
-
version = "0.4.6"
|
| 629 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 630 |
-
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
| 631 |
-
dependencies = [
|
| 632 |
-
"num-traits",
|
| 633 |
-
]
|
| 634 |
-
|
| 635 |
-
[[package]]
|
| 636 |
-
name = "num-integer"
|
| 637 |
-
version = "0.1.47"
|
| 638 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 639 |
-
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
| 640 |
-
dependencies = [
|
| 641 |
-
"num-traits",
|
| 642 |
-
]
|
| 643 |
-
|
| 644 |
-
[[package]]
|
| 645 |
-
name = "num-traits"
|
| 646 |
-
version = "0.2.19"
|
| 647 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 648 |
-
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
| 649 |
-
dependencies = [
|
| 650 |
-
"autocfg",
|
| 651 |
-
"libm",
|
| 652 |
-
]
|
| 653 |
-
|
| 654 |
-
[[package]]
|
| 655 |
-
name = "once_cell"
|
| 656 |
-
version = "1.21.4"
|
| 657 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 658 |
-
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
| 659 |
-
|
| 660 |
-
[[package]]
|
| 661 |
-
name = "parking_lot"
|
| 662 |
-
version = "0.12.5"
|
| 663 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 664 |
-
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
| 665 |
-
dependencies = [
|
| 666 |
-
"lock_api",
|
| 667 |
-
"parking_lot_core",
|
| 668 |
-
]
|
| 669 |
-
|
| 670 |
-
[[package]]
|
| 671 |
-
name = "parking_lot_core"
|
| 672 |
-
version = "0.9.12"
|
| 673 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 674 |
-
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
| 675 |
-
dependencies = [
|
| 676 |
-
"cfg-if",
|
| 677 |
-
"libc",
|
| 678 |
-
"redox_syscall",
|
| 679 |
-
"smallvec",
|
| 680 |
-
"windows-link",
|
| 681 |
-
]
|
| 682 |
-
|
| 683 |
-
[[package]]
|
| 684 |
-
name = "parquet"
|
| 685 |
-
version = "60.1.0"
|
| 686 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 687 |
-
checksum = "806eaa0f1c0436dcb43bee8e6a82bf2efff90521a60172f8281cb9ef577b43a4"
|
| 688 |
-
dependencies = [
|
| 689 |
-
"ahash",
|
| 690 |
-
"arrow-array",
|
| 691 |
-
"arrow-buffer",
|
| 692 |
-
"arrow-data",
|
| 693 |
-
"arrow-ipc",
|
| 694 |
-
"arrow-schema",
|
| 695 |
-
"arrow-select",
|
| 696 |
-
"base64",
|
| 697 |
-
"brotli",
|
| 698 |
-
"bytes",
|
| 699 |
-
"chrono",
|
| 700 |
-
"flate2",
|
| 701 |
-
"half",
|
| 702 |
-
"hashbrown",
|
| 703 |
-
"lz4_flex",
|
| 704 |
-
"num-bigint",
|
| 705 |
-
"num-integer",
|
| 706 |
-
"num-traits",
|
| 707 |
-
"seq-macro",
|
| 708 |
-
"simdutf8",
|
| 709 |
-
"snap",
|
| 710 |
-
"twox-hash",
|
| 711 |
-
"zstd",
|
| 712 |
-
]
|
| 713 |
-
|
| 714 |
-
[[package]]
|
| 715 |
-
name = "pin-project-lite"
|
| 716 |
-
version = "0.2.17"
|
| 717 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 718 |
-
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
| 719 |
-
|
| 720 |
-
[[package]]
|
| 721 |
-
name = "pkg-config"
|
| 722 |
-
version = "0.3.34"
|
| 723 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 724 |
-
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
| 725 |
-
|
| 726 |
-
[[package]]
|
| 727 |
-
name = "portable-atomic"
|
| 728 |
-
version = "1.15.0"
|
| 729 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 730 |
-
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
| 731 |
-
|
| 732 |
-
[[package]]
|
| 733 |
-
name = "proc-macro2"
|
| 734 |
-
version = "1.0.107"
|
| 735 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 736 |
-
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
| 737 |
-
dependencies = [
|
| 738 |
-
"unicode-ident",
|
| 739 |
-
]
|
| 740 |
-
|
| 741 |
-
[[package]]
|
| 742 |
-
name = "pyo3"
|
| 743 |
-
version = "0.30.1"
|
| 744 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 745 |
-
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
| 746 |
-
dependencies = [
|
| 747 |
-
"libc",
|
| 748 |
-
"once_cell",
|
| 749 |
-
"portable-atomic",
|
| 750 |
-
"pyo3-build-config",
|
| 751 |
-
"pyo3-ffi",
|
| 752 |
-
"pyo3-macros",
|
| 753 |
-
]
|
| 754 |
-
|
| 755 |
-
[[package]]
|
| 756 |
-
name = "pyo3-build-config"
|
| 757 |
-
version = "0.30.1"
|
| 758 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 759 |
-
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
| 760 |
-
dependencies = [
|
| 761 |
-
"target-lexicon",
|
| 762 |
-
]
|
| 763 |
-
|
| 764 |
-
[[package]]
|
| 765 |
-
name = "pyo3-ffi"
|
| 766 |
-
version = "0.30.1"
|
| 767 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 768 |
-
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
| 769 |
-
dependencies = [
|
| 770 |
-
"libc",
|
| 771 |
-
"pyo3-build-config",
|
| 772 |
-
]
|
| 773 |
-
|
| 774 |
-
[[package]]
|
| 775 |
-
name = "pyo3-macros"
|
| 776 |
-
version = "0.30.1"
|
| 777 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 778 |
-
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
| 779 |
-
dependencies = [
|
| 780 |
-
"proc-macro2",
|
| 781 |
-
"pyo3-macros-backend",
|
| 782 |
-
"quote",
|
| 783 |
-
"syn 3.0.9",
|
| 784 |
-
]
|
| 785 |
-
|
| 786 |
-
[[package]]
|
| 787 |
-
name = "pyo3-macros-backend"
|
| 788 |
-
version = "0.30.1"
|
| 789 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 790 |
-
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
| 791 |
-
dependencies = [
|
| 792 |
-
"heck",
|
| 793 |
-
"proc-macro2",
|
| 794 |
-
"quote",
|
| 795 |
-
"syn 3.0.9",
|
| 796 |
-
]
|
| 797 |
-
|
| 798 |
-
[[package]]
|
| 799 |
-
name = "quote"
|
| 800 |
-
version = "1.0.47"
|
| 801 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 802 |
-
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
| 803 |
-
dependencies = [
|
| 804 |
-
"proc-macro2",
|
| 805 |
-
]
|
| 806 |
-
|
| 807 |
-
[[package]]
|
| 808 |
-
name = "r-efi"
|
| 809 |
-
version = "5.3.0"
|
| 810 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 811 |
-
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
| 812 |
-
|
| 813 |
-
[[package]]
|
| 814 |
-
name = "r-efi"
|
| 815 |
-
version = "6.0.0"
|
| 816 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 817 |
-
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
| 818 |
-
|
| 819 |
-
[[package]]
|
| 820 |
-
name = "rayon"
|
| 821 |
-
version = "1.12.0"
|
| 822 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 823 |
-
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
| 824 |
-
dependencies = [
|
| 825 |
-
"either",
|
| 826 |
-
"rayon-core",
|
| 827 |
-
]
|
| 828 |
-
|
| 829 |
-
[[package]]
|
| 830 |
-
name = "rayon-core"
|
| 831 |
-
version = "1.13.0"
|
| 832 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 833 |
-
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
| 834 |
-
dependencies = [
|
| 835 |
-
"crossbeam-deque",
|
| 836 |
-
"crossbeam-utils",
|
| 837 |
-
]
|
| 838 |
-
|
| 839 |
-
[[package]]
|
| 840 |
-
name = "redox_syscall"
|
| 841 |
-
version = "0.5.18"
|
| 842 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 843 |
-
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
| 844 |
-
dependencies = [
|
| 845 |
-
"bitflags",
|
| 846 |
-
]
|
| 847 |
-
|
| 848 |
-
[[package]]
|
| 849 |
-
name = "rustc_version"
|
| 850 |
-
version = "0.4.1"
|
| 851 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 852 |
-
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
| 853 |
-
dependencies = [
|
| 854 |
-
"semver",
|
| 855 |
-
]
|
| 856 |
-
|
| 857 |
-
[[package]]
|
| 858 |
-
name = "rustversion"
|
| 859 |
-
version = "1.0.23"
|
| 860 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 861 |
-
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
| 862 |
-
|
| 863 |
-
[[package]]
|
| 864 |
-
name = "scopeguard"
|
| 865 |
-
version = "1.2.0"
|
| 866 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 867 |
-
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
| 868 |
-
|
| 869 |
-
[[package]]
|
| 870 |
-
name = "semver"
|
| 871 |
-
version = "1.0.28"
|
| 872 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 873 |
-
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
| 874 |
-
|
| 875 |
-
[[package]]
|
| 876 |
-
name = "seq-macro"
|
| 877 |
-
version = "0.3.6"
|
| 878 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 879 |
-
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
|
| 880 |
-
|
| 881 |
-
[[package]]
|
| 882 |
-
name = "serde"
|
| 883 |
-
version = "1.0.229"
|
| 884 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 885 |
-
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
| 886 |
-
dependencies = [
|
| 887 |
-
"serde_core",
|
| 888 |
-
"serde_derive",
|
| 889 |
-
]
|
| 890 |
-
|
| 891 |
-
[[package]]
|
| 892 |
-
name = "serde_core"
|
| 893 |
-
version = "1.0.229"
|
| 894 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 895 |
-
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
| 896 |
-
dependencies = [
|
| 897 |
-
"serde_derive",
|
| 898 |
-
]
|
| 899 |
-
|
| 900 |
-
[[package]]
|
| 901 |
-
name = "serde_derive"
|
| 902 |
-
version = "1.0.229"
|
| 903 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 904 |
-
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
| 905 |
-
dependencies = [
|
| 906 |
-
"proc-macro2",
|
| 907 |
-
"quote",
|
| 908 |
-
"syn 3.0.9",
|
| 909 |
-
]
|
| 910 |
-
|
| 911 |
-
[[package]]
|
| 912 |
-
name = "serde_json"
|
| 913 |
-
version = "1.0.154"
|
| 914 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 915 |
-
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
| 916 |
-
dependencies = [
|
| 917 |
-
"itoa",
|
| 918 |
-
"memchr",
|
| 919 |
-
"serde",
|
| 920 |
-
"serde_core",
|
| 921 |
-
"zmij",
|
| 922 |
-
]
|
| 923 |
-
|
| 924 |
-
[[package]]
|
| 925 |
-
name = "shlex"
|
| 926 |
-
version = "2.0.1"
|
| 927 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 928 |
-
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
| 929 |
-
|
| 930 |
-
[[package]]
|
| 931 |
-
name = "simdutf8"
|
| 932 |
-
version = "0.1.5"
|
| 933 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 934 |
-
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
| 935 |
-
|
| 936 |
-
[[package]]
|
| 937 |
-
name = "slab"
|
| 938 |
-
version = "0.4.12"
|
| 939 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 940 |
-
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
| 941 |
-
|
| 942 |
-
[[package]]
|
| 943 |
-
name = "smallvec"
|
| 944 |
-
version = "1.16.3"
|
| 945 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 946 |
-
checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
|
| 947 |
-
|
| 948 |
-
[[package]]
|
| 949 |
-
name = "snap"
|
| 950 |
-
version = "1.1.2"
|
| 951 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 952 |
-
checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886"
|
| 953 |
-
|
| 954 |
-
[[package]]
|
| 955 |
-
name = "syn"
|
| 956 |
-
version = "2.0.119"
|
| 957 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 958 |
-
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
| 959 |
-
dependencies = [
|
| 960 |
-
"proc-macro2",
|
| 961 |
-
"quote",
|
| 962 |
-
"unicode-ident",
|
| 963 |
-
]
|
| 964 |
-
|
| 965 |
-
[[package]]
|
| 966 |
-
name = "syn"
|
| 967 |
-
version = "3.0.9"
|
| 968 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 969 |
-
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
| 970 |
-
dependencies = [
|
| 971 |
-
"proc-macro2",
|
| 972 |
-
"quote",
|
| 973 |
-
"unicode-ident",
|
| 974 |
-
]
|
| 975 |
-
|
| 976 |
-
[[package]]
|
| 977 |
-
name = "target-lexicon"
|
| 978 |
-
version = "0.13.5"
|
| 979 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 980 |
-
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
| 981 |
-
|
| 982 |
-
[[package]]
|
| 983 |
-
name = "thiserror"
|
| 984 |
-
version = "2.0.21"
|
| 985 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 986 |
-
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
| 987 |
-
dependencies = [
|
| 988 |
-
"thiserror-impl",
|
| 989 |
-
]
|
| 990 |
-
|
| 991 |
-
[[package]]
|
| 992 |
-
name = "thiserror-impl"
|
| 993 |
-
version = "2.0.21"
|
| 994 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 995 |
-
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
| 996 |
-
dependencies = [
|
| 997 |
-
"proc-macro2",
|
| 998 |
-
"quote",
|
| 999 |
-
"syn 3.0.9",
|
| 1000 |
-
]
|
| 1001 |
-
|
| 1002 |
-
[[package]]
|
| 1003 |
-
name = "tiny-keccak"
|
| 1004 |
-
version = "2.0.2"
|
| 1005 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1006 |
-
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
| 1007 |
-
dependencies = [
|
| 1008 |
-
"crunchy",
|
| 1009 |
-
]
|
| 1010 |
-
|
| 1011 |
-
[[package]]
|
| 1012 |
-
name = "twox-hash"
|
| 1013 |
-
version = "2.1.5"
|
| 1014 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1015 |
-
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
|
| 1016 |
-
|
| 1017 |
-
[[package]]
|
| 1018 |
-
name = "unicode-ident"
|
| 1019 |
-
version = "1.0.27"
|
| 1020 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1021 |
-
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
| 1022 |
-
|
| 1023 |
-
[[package]]
|
| 1024 |
-
name = "version_check"
|
| 1025 |
-
version = "0.9.5"
|
| 1026 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1027 |
-
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
| 1028 |
-
|
| 1029 |
-
[[package]]
|
| 1030 |
-
name = "wasi"
|
| 1031 |
-
version = "0.11.1+wasi-snapshot-preview1"
|
| 1032 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1033 |
-
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
| 1034 |
-
|
| 1035 |
-
[[package]]
|
| 1036 |
-
name = "wasip2"
|
| 1037 |
-
version = "1.0.4+wasi-0.2.12"
|
| 1038 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1039 |
-
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
| 1040 |
-
dependencies = [
|
| 1041 |
-
"wit-bindgen",
|
| 1042 |
-
]
|
| 1043 |
-
|
| 1044 |
-
[[package]]
|
| 1045 |
-
name = "wasm-bindgen"
|
| 1046 |
-
version = "0.2.129"
|
| 1047 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1048 |
-
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
| 1049 |
-
dependencies = [
|
| 1050 |
-
"cfg-if",
|
| 1051 |
-
"once_cell",
|
| 1052 |
-
"rustversion",
|
| 1053 |
-
"wasm-bindgen-macro",
|
| 1054 |
-
"wasm-bindgen-shared",
|
| 1055 |
-
]
|
| 1056 |
-
|
| 1057 |
-
[[package]]
|
| 1058 |
-
name = "wasm-bindgen-macro"
|
| 1059 |
-
version = "0.2.129"
|
| 1060 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1061 |
-
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
| 1062 |
-
dependencies = [
|
| 1063 |
-
"quote",
|
| 1064 |
-
"wasm-bindgen-macro-support",
|
| 1065 |
-
]
|
| 1066 |
-
|
| 1067 |
-
[[package]]
|
| 1068 |
-
name = "wasm-bindgen-macro-support"
|
| 1069 |
-
version = "0.2.129"
|
| 1070 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1071 |
-
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
| 1072 |
-
dependencies = [
|
| 1073 |
-
"bumpalo",
|
| 1074 |
-
"proc-macro2",
|
| 1075 |
-
"quote",
|
| 1076 |
-
"syn 3.0.9",
|
| 1077 |
-
"wasm-bindgen-shared",
|
| 1078 |
-
]
|
| 1079 |
-
|
| 1080 |
-
[[package]]
|
| 1081 |
-
name = "wasm-bindgen-shared"
|
| 1082 |
-
version = "0.2.129"
|
| 1083 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1084 |
-
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
| 1085 |
-
dependencies = [
|
| 1086 |
-
"unicode-ident",
|
| 1087 |
-
]
|
| 1088 |
-
|
| 1089 |
-
[[package]]
|
| 1090 |
-
name = "windows-core"
|
| 1091 |
-
version = "0.62.2"
|
| 1092 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1093 |
-
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
| 1094 |
-
dependencies = [
|
| 1095 |
-
"windows-implement",
|
| 1096 |
-
"windows-interface",
|
| 1097 |
-
"windows-link",
|
| 1098 |
-
"windows-result",
|
| 1099 |
-
"windows-strings",
|
| 1100 |
-
]
|
| 1101 |
-
|
| 1102 |
-
[[package]]
|
| 1103 |
-
name = "windows-implement"
|
| 1104 |
-
version = "0.60.2"
|
| 1105 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1106 |
-
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
| 1107 |
-
dependencies = [
|
| 1108 |
-
"proc-macro2",
|
| 1109 |
-
"quote",
|
| 1110 |
-
"syn 2.0.119",
|
| 1111 |
-
]
|
| 1112 |
-
|
| 1113 |
-
[[package]]
|
| 1114 |
-
name = "windows-interface"
|
| 1115 |
-
version = "0.59.3"
|
| 1116 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1117 |
-
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
| 1118 |
-
dependencies = [
|
| 1119 |
-
"proc-macro2",
|
| 1120 |
-
"quote",
|
| 1121 |
-
"syn 2.0.119",
|
| 1122 |
-
]
|
| 1123 |
-
|
| 1124 |
-
[[package]]
|
| 1125 |
-
name = "windows-link"
|
| 1126 |
-
version = "0.2.1"
|
| 1127 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1128 |
-
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
| 1129 |
-
|
| 1130 |
-
[[package]]
|
| 1131 |
-
name = "windows-result"
|
| 1132 |
-
version = "0.4.1"
|
| 1133 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1134 |
-
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
| 1135 |
-
dependencies = [
|
| 1136 |
-
"windows-link",
|
| 1137 |
-
]
|
| 1138 |
-
|
| 1139 |
-
[[package]]
|
| 1140 |
-
name = "windows-strings"
|
| 1141 |
-
version = "0.5.1"
|
| 1142 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1143 |
-
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
| 1144 |
-
dependencies = [
|
| 1145 |
-
"windows-link",
|
| 1146 |
-
]
|
| 1147 |
-
|
| 1148 |
-
[[package]]
|
| 1149 |
-
name = "wit-bindgen"
|
| 1150 |
-
version = "0.57.1"
|
| 1151 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1152 |
-
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
| 1153 |
-
|
| 1154 |
-
[[package]]
|
| 1155 |
-
name = "xxhash-rust"
|
| 1156 |
-
version = "0.8.19"
|
| 1157 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1158 |
-
checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7"
|
| 1159 |
-
|
| 1160 |
-
[[package]]
|
| 1161 |
-
name = "zerocopy"
|
| 1162 |
-
version = "0.8.63"
|
| 1163 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1164 |
-
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
| 1165 |
-
dependencies = [
|
| 1166 |
-
"zerocopy-derive",
|
| 1167 |
-
]
|
| 1168 |
-
|
| 1169 |
-
[[package]]
|
| 1170 |
-
name = "zerocopy-derive"
|
| 1171 |
-
version = "0.8.63"
|
| 1172 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1173 |
-
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
| 1174 |
-
dependencies = [
|
| 1175 |
-
"proc-macro2",
|
| 1176 |
-
"quote",
|
| 1177 |
-
"syn 2.0.119",
|
| 1178 |
-
]
|
| 1179 |
-
|
| 1180 |
-
[[package]]
|
| 1181 |
-
name = "zlib-rs"
|
| 1182 |
-
version = "0.6.8"
|
| 1183 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1184 |
-
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
| 1185 |
-
|
| 1186 |
-
[[package]]
|
| 1187 |
-
name = "zmij"
|
| 1188 |
-
version = "1.0.23"
|
| 1189 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1190 |
-
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
| 1191 |
-
|
| 1192 |
-
[[package]]
|
| 1193 |
-
name = "zstd"
|
| 1194 |
-
version = "0.14.3"
|
| 1195 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1196 |
-
checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
|
| 1197 |
-
dependencies = [
|
| 1198 |
-
"zstd-safe",
|
| 1199 |
-
]
|
| 1200 |
-
|
| 1201 |
-
[[package]]
|
| 1202 |
-
name = "zstd-safe"
|
| 1203 |
-
version = "8.2.0"
|
| 1204 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1205 |
-
checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
|
| 1206 |
-
dependencies = [
|
| 1207 |
-
"zstd-sys",
|
| 1208 |
-
]
|
| 1209 |
-
|
| 1210 |
-
[[package]]
|
| 1211 |
-
name = "zstd-sys"
|
| 1212 |
-
version = "2.1.2+zstd.1.5.7"
|
| 1213 |
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1214 |
-
checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
|
| 1215 |
-
dependencies = [
|
| 1216 |
-
"cc",
|
| 1217 |
-
"pkg-config",
|
| 1218 |
-
]
|
|
|
|
| 1 |
+
# This file is automatically @generated by Cargo.
|
| 2 |
+
# It is not intended for manual editing.
|
| 3 |
+
version = 4
|
| 4 |
+
|
| 5 |
+
[[package]]
|
| 6 |
+
name = "ahash"
|
| 7 |
+
version = "0.8.12"
|
| 8 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 9 |
+
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"cfg-if",
|
| 12 |
+
"const-random",
|
| 13 |
+
"getrandom 0.3.4",
|
| 14 |
+
"once_cell",
|
| 15 |
+
"version_check",
|
| 16 |
+
"zerocopy",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
[[package]]
|
| 20 |
+
name = "alloc-no-stdlib"
|
| 21 |
+
version = "3.0.0"
|
| 22 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 23 |
+
checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a"
|
| 24 |
+
|
| 25 |
+
[[package]]
|
| 26 |
+
name = "alloc-stdlib"
|
| 27 |
+
version = "0.3.0"
|
| 28 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 29 |
+
checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1"
|
| 30 |
+
dependencies = [
|
| 31 |
+
"alloc-no-stdlib",
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
[[package]]
|
| 35 |
+
name = "allocator-api2"
|
| 36 |
+
version = "0.2.21"
|
| 37 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 38 |
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
| 39 |
+
|
| 40 |
+
[[package]]
|
| 41 |
+
name = "android_system_properties"
|
| 42 |
+
version = "0.1.6"
|
| 43 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 44 |
+
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
| 45 |
+
dependencies = [
|
| 46 |
+
"libc",
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
[[package]]
|
| 50 |
+
name = "arrow-array"
|
| 51 |
+
version = "60.1.0"
|
| 52 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 53 |
+
checksum = "0f3d22a41d3df78b71df0b685af219ae3a94188ab98d6fb29c958eb55c3247f4"
|
| 54 |
+
dependencies = [
|
| 55 |
+
"ahash",
|
| 56 |
+
"arrow-buffer",
|
| 57 |
+
"arrow-data",
|
| 58 |
+
"arrow-schema",
|
| 59 |
+
"chrono",
|
| 60 |
+
"half",
|
| 61 |
+
"hashbrown",
|
| 62 |
+
"num-complex",
|
| 63 |
+
"num-integer",
|
| 64 |
+
"num-traits",
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
[[package]]
|
| 68 |
+
name = "arrow-buffer"
|
| 69 |
+
version = "60.1.0"
|
| 70 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 71 |
+
checksum = "7bd46a530bc1a8e763102fc36c1dc0f51f3ffa150c78e7f5658ccb54b1458ade"
|
| 72 |
+
dependencies = [
|
| 73 |
+
"bytes",
|
| 74 |
+
"half",
|
| 75 |
+
"num-bigint",
|
| 76 |
+
"num-traits",
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
[[package]]
|
| 80 |
+
name = "arrow-cmp"
|
| 81 |
+
version = "60.1.0"
|
| 82 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 83 |
+
checksum = "1913e57a64fbf2c4502fa6d54b547d6a1ae59e3146cbc15321d47468c937abd1"
|
| 84 |
+
dependencies = [
|
| 85 |
+
"arrow-array",
|
| 86 |
+
"arrow-buffer",
|
| 87 |
+
"arrow-schema",
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
[[package]]
|
| 91 |
+
name = "arrow-data"
|
| 92 |
+
version = "60.1.0"
|
| 93 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 94 |
+
checksum = "415b7fbc4cf56b080bb5677a4ae74211b5c79e571ed37a1d547633ec07f3b43b"
|
| 95 |
+
dependencies = [
|
| 96 |
+
"arrow-buffer",
|
| 97 |
+
"arrow-schema",
|
| 98 |
+
"half",
|
| 99 |
+
"num-integer",
|
| 100 |
+
"num-traits",
|
| 101 |
+
]
|
| 102 |
+
|
| 103 |
+
[[package]]
|
| 104 |
+
name = "arrow-ipc"
|
| 105 |
+
version = "60.1.0"
|
| 106 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 107 |
+
checksum = "45f3e766bccf5a0cb57a5de52a4cba450dea5171547c2cb5939b486d08887cba"
|
| 108 |
+
dependencies = [
|
| 109 |
+
"arrow-array",
|
| 110 |
+
"arrow-buffer",
|
| 111 |
+
"arrow-data",
|
| 112 |
+
"arrow-schema",
|
| 113 |
+
"arrow-select",
|
| 114 |
+
"flatbuffers",
|
| 115 |
+
]
|
| 116 |
+
|
| 117 |
+
[[package]]
|
| 118 |
+
name = "arrow-schema"
|
| 119 |
+
version = "60.1.0"
|
| 120 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 121 |
+
checksum = "de2c8e714b466404cdc1189e89f892426efea614d938e16c15518c340cd43ba3"
|
| 122 |
+
|
| 123 |
+
[[package]]
|
| 124 |
+
name = "arrow-select"
|
| 125 |
+
version = "60.1.0"
|
| 126 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 127 |
+
checksum = "a5af3f6656c5f2c6944f93d9a357a7f204c93d5f349385a44cf46c4a26fbcf30"
|
| 128 |
+
dependencies = [
|
| 129 |
+
"ahash",
|
| 130 |
+
"arrow-array",
|
| 131 |
+
"arrow-buffer",
|
| 132 |
+
"arrow-cmp",
|
| 133 |
+
"arrow-data",
|
| 134 |
+
"arrow-schema",
|
| 135 |
+
"num-traits",
|
| 136 |
+
]
|
| 137 |
+
|
| 138 |
+
[[package]]
|
| 139 |
+
name = "ash"
|
| 140 |
+
version = "0.38.0+1.3.281"
|
| 141 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 142 |
+
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
| 143 |
+
dependencies = [
|
| 144 |
+
"libloading 0.8.9",
|
| 145 |
+
]
|
| 146 |
+
|
| 147 |
+
[[package]]
|
| 148 |
+
name = "autocfg"
|
| 149 |
+
version = "1.5.1"
|
| 150 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 151 |
+
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
| 152 |
+
|
| 153 |
+
[[package]]
|
| 154 |
+
name = "base64"
|
| 155 |
+
version = "0.23.1"
|
| 156 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 157 |
+
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
| 158 |
+
|
| 159 |
+
[[package]]
|
| 160 |
+
name = "bitflags"
|
| 161 |
+
version = "2.13.2"
|
| 162 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 163 |
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
| 164 |
+
|
| 165 |
+
[[package]]
|
| 166 |
+
name = "brotli"
|
| 167 |
+
version = "9.0.0"
|
| 168 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 169 |
+
checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b"
|
| 170 |
+
dependencies = [
|
| 171 |
+
"alloc-no-stdlib",
|
| 172 |
+
"alloc-stdlib",
|
| 173 |
+
"brotli-decompressor",
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
+
[[package]]
|
| 177 |
+
name = "brotli-decompressor"
|
| 178 |
+
version = "6.0.1"
|
| 179 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 180 |
+
checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7"
|
| 181 |
+
dependencies = [
|
| 182 |
+
"alloc-no-stdlib",
|
| 183 |
+
"alloc-stdlib",
|
| 184 |
+
]
|
| 185 |
+
|
| 186 |
+
[[package]]
|
| 187 |
+
name = "bumpalo"
|
| 188 |
+
version = "3.20.3"
|
| 189 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 190 |
+
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
| 191 |
+
|
| 192 |
+
[[package]]
|
| 193 |
+
name = "bytes"
|
| 194 |
+
version = "1.12.1"
|
| 195 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 196 |
+
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
| 197 |
+
|
| 198 |
+
[[package]]
|
| 199 |
+
name = "cc"
|
| 200 |
+
version = "1.8.0"
|
| 201 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 202 |
+
checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838"
|
| 203 |
+
dependencies = [
|
| 204 |
+
"find-msvc-tools",
|
| 205 |
+
"jobserver",
|
| 206 |
+
"libc",
|
| 207 |
+
"shlex",
|
| 208 |
+
]
|
| 209 |
+
|
| 210 |
+
[[package]]
|
| 211 |
+
name = "cfg-if"
|
| 212 |
+
version = "1.0.5"
|
| 213 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 214 |
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
| 215 |
+
|
| 216 |
+
[[package]]
|
| 217 |
+
name = "chrono"
|
| 218 |
+
version = "0.4.45"
|
| 219 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 220 |
+
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
| 221 |
+
dependencies = [
|
| 222 |
+
"iana-time-zone",
|
| 223 |
+
"num-traits",
|
| 224 |
+
"windows-link",
|
| 225 |
+
]
|
| 226 |
+
|
| 227 |
+
[[package]]
|
| 228 |
+
name = "const-random"
|
| 229 |
+
version = "0.1.18"
|
| 230 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 231 |
+
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
| 232 |
+
dependencies = [
|
| 233 |
+
"const-random-macro",
|
| 234 |
+
]
|
| 235 |
+
|
| 236 |
+
[[package]]
|
| 237 |
+
name = "const-random-macro"
|
| 238 |
+
version = "0.1.16"
|
| 239 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 240 |
+
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
| 241 |
+
dependencies = [
|
| 242 |
+
"getrandom 0.2.17",
|
| 243 |
+
"once_cell",
|
| 244 |
+
"tiny-keccak",
|
| 245 |
+
]
|
| 246 |
+
|
| 247 |
+
[[package]]
|
| 248 |
+
name = "core-foundation-sys"
|
| 249 |
+
version = "0.8.7"
|
| 250 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 251 |
+
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
| 252 |
+
|
| 253 |
+
[[package]]
|
| 254 |
+
name = "crc32fast"
|
| 255 |
+
version = "1.5.2"
|
| 256 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 257 |
+
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
| 258 |
+
dependencies = [
|
| 259 |
+
"cfg-if",
|
| 260 |
+
]
|
| 261 |
+
|
| 262 |
+
[[package]]
|
| 263 |
+
name = "crossbeam-deque"
|
| 264 |
+
version = "0.8.8"
|
| 265 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 266 |
+
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
|
| 267 |
+
dependencies = [
|
| 268 |
+
"crossbeam-epoch",
|
| 269 |
+
"crossbeam-utils",
|
| 270 |
+
]
|
| 271 |
+
|
| 272 |
+
[[package]]
|
| 273 |
+
name = "crossbeam-epoch"
|
| 274 |
+
version = "0.9.21"
|
| 275 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 276 |
+
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
|
| 277 |
+
dependencies = [
|
| 278 |
+
"crossbeam-utils",
|
| 279 |
+
]
|
| 280 |
+
|
| 281 |
+
[[package]]
|
| 282 |
+
name = "crossbeam-utils"
|
| 283 |
+
version = "0.8.23"
|
| 284 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 285 |
+
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
| 286 |
+
|
| 287 |
+
[[package]]
|
| 288 |
+
name = "crunchy"
|
| 289 |
+
version = "0.2.4"
|
| 290 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 291 |
+
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
| 292 |
+
|
| 293 |
+
[[package]]
|
| 294 |
+
name = "either"
|
| 295 |
+
version = "1.19.0"
|
| 296 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 297 |
+
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
| 298 |
+
|
| 299 |
+
[[package]]
|
| 300 |
+
name = "equivalent"
|
| 301 |
+
version = "1.0.3"
|
| 302 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 303 |
+
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
| 304 |
+
|
| 305 |
+
[[package]]
|
| 306 |
+
name = "find-msvc-tools"
|
| 307 |
+
version = "0.1.14"
|
| 308 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 309 |
+
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
| 310 |
+
|
| 311 |
+
[[package]]
|
| 312 |
+
name = "flatbuffers"
|
| 313 |
+
version = "25.12.19"
|
| 314 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 315 |
+
checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3"
|
| 316 |
+
dependencies = [
|
| 317 |
+
"bitflags",
|
| 318 |
+
"rustc_version",
|
| 319 |
+
]
|
| 320 |
+
|
| 321 |
+
[[package]]
|
| 322 |
+
name = "flate2"
|
| 323 |
+
version = "1.1.10"
|
| 324 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 325 |
+
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
| 326 |
+
dependencies = [
|
| 327 |
+
"zlib-rs",
|
| 328 |
+
]
|
| 329 |
+
|
| 330 |
+
[[package]]
|
| 331 |
+
name = "foldhash"
|
| 332 |
+
version = "0.2.0"
|
| 333 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 334 |
+
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
| 335 |
+
|
| 336 |
+
[[package]]
|
| 337 |
+
name = "futures-core"
|
| 338 |
+
version = "0.3.34"
|
| 339 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 340 |
+
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
| 341 |
+
|
| 342 |
+
[[package]]
|
| 343 |
+
name = "futures-task"
|
| 344 |
+
version = "0.3.34"
|
| 345 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 346 |
+
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
| 347 |
+
|
| 348 |
+
[[package]]
|
| 349 |
+
name = "futures-util"
|
| 350 |
+
version = "0.3.34"
|
| 351 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 352 |
+
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
| 353 |
+
dependencies = [
|
| 354 |
+
"futures-core",
|
| 355 |
+
"futures-task",
|
| 356 |
+
"pin-project-lite",
|
| 357 |
+
"slab",
|
| 358 |
+
]
|
| 359 |
+
|
| 360 |
+
[[package]]
|
| 361 |
+
name = "getrandom"
|
| 362 |
+
version = "0.2.17"
|
| 363 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 364 |
+
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
| 365 |
+
dependencies = [
|
| 366 |
+
"cfg-if",
|
| 367 |
+
"libc",
|
| 368 |
+
"wasi",
|
| 369 |
+
]
|
| 370 |
+
|
| 371 |
+
[[package]]
|
| 372 |
+
name = "getrandom"
|
| 373 |
+
version = "0.3.4"
|
| 374 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 375 |
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
| 376 |
+
dependencies = [
|
| 377 |
+
"cfg-if",
|
| 378 |
+
"libc",
|
| 379 |
+
"r-efi 5.3.0",
|
| 380 |
+
"wasip2",
|
| 381 |
+
]
|
| 382 |
+
|
| 383 |
+
[[package]]
|
| 384 |
+
name = "getrandom"
|
| 385 |
+
version = "0.4.3"
|
| 386 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 387 |
+
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
| 388 |
+
dependencies = [
|
| 389 |
+
"cfg-if",
|
| 390 |
+
"libc",
|
| 391 |
+
"r-efi 6.0.0",
|
| 392 |
+
]
|
| 393 |
+
|
| 394 |
+
[[package]]
|
| 395 |
+
name = "half"
|
| 396 |
+
version = "2.7.1"
|
| 397 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 398 |
+
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
| 399 |
+
dependencies = [
|
| 400 |
+
"cfg-if",
|
| 401 |
+
"crunchy",
|
| 402 |
+
"num-traits",
|
| 403 |
+
"zerocopy",
|
| 404 |
+
]
|
| 405 |
+
|
| 406 |
+
[[package]]
|
| 407 |
+
name = "hashbrown"
|
| 408 |
+
version = "0.17.1"
|
| 409 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 410 |
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
| 411 |
+
dependencies = [
|
| 412 |
+
"allocator-api2",
|
| 413 |
+
"equivalent",
|
| 414 |
+
"foldhash",
|
| 415 |
+
]
|
| 416 |
+
|
| 417 |
+
[[package]]
|
| 418 |
+
name = "heck"
|
| 419 |
+
version = "0.5.0"
|
| 420 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 421 |
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
| 422 |
+
|
| 423 |
+
[[package]]
|
| 424 |
+
name = "iana-time-zone"
|
| 425 |
+
version = "0.1.65"
|
| 426 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 427 |
+
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
| 428 |
+
dependencies = [
|
| 429 |
+
"android_system_properties",
|
| 430 |
+
"core-foundation-sys",
|
| 431 |
+
"iana-time-zone-haiku",
|
| 432 |
+
"js-sys",
|
| 433 |
+
"log",
|
| 434 |
+
"wasm-bindgen",
|
| 435 |
+
"windows-core",
|
| 436 |
+
]
|
| 437 |
+
|
| 438 |
+
[[package]]
|
| 439 |
+
name = "iana-time-zone-haiku"
|
| 440 |
+
version = "0.1.2"
|
| 441 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 442 |
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
| 443 |
+
dependencies = [
|
| 444 |
+
"cc",
|
| 445 |
+
]
|
| 446 |
+
|
| 447 |
+
[[package]]
|
| 448 |
+
name = "itoa"
|
| 449 |
+
version = "1.0.18"
|
| 450 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 451 |
+
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
| 452 |
+
|
| 453 |
+
[[package]]
|
| 454 |
+
name = "jobserver"
|
| 455 |
+
version = "0.1.35"
|
| 456 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 457 |
+
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
| 458 |
+
dependencies = [
|
| 459 |
+
"getrandom 0.4.3",
|
| 460 |
+
"libc",
|
| 461 |
+
]
|
| 462 |
+
|
| 463 |
+
[[package]]
|
| 464 |
+
name = "js-sys"
|
| 465 |
+
version = "0.3.106"
|
| 466 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 467 |
+
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
| 468 |
+
dependencies = [
|
| 469 |
+
"cfg-if",
|
| 470 |
+
"futures-util",
|
| 471 |
+
"wasm-bindgen",
|
| 472 |
+
]
|
| 473 |
+
|
| 474 |
+
[[package]]
|
| 475 |
+
name = "libc"
|
| 476 |
+
version = "0.2.191"
|
| 477 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 478 |
+
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
| 479 |
+
|
| 480 |
+
[[package]]
|
| 481 |
+
name = "libloading"
|
| 482 |
+
version = "0.8.9"
|
| 483 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 484 |
+
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
| 485 |
+
dependencies = [
|
| 486 |
+
"cfg-if",
|
| 487 |
+
"windows-link",
|
| 488 |
+
]
|
| 489 |
+
|
| 490 |
+
[[package]]
|
| 491 |
+
name = "libloading"
|
| 492 |
+
version = "0.9.0"
|
| 493 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 494 |
+
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
| 495 |
+
dependencies = [
|
| 496 |
+
"cfg-if",
|
| 497 |
+
"windows-link",
|
| 498 |
+
]
|
| 499 |
+
|
| 500 |
+
[[package]]
|
| 501 |
+
name = "libm"
|
| 502 |
+
version = "0.2.16"
|
| 503 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 504 |
+
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
| 505 |
+
|
| 506 |
+
[[package]]
|
| 507 |
+
name = "lock_api"
|
| 508 |
+
version = "0.4.14"
|
| 509 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 510 |
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
| 511 |
+
dependencies = [
|
| 512 |
+
"scopeguard",
|
| 513 |
+
]
|
| 514 |
+
|
| 515 |
+
[[package]]
|
| 516 |
+
name = "log"
|
| 517 |
+
version = "0.4.34"
|
| 518 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 519 |
+
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
| 520 |
+
|
| 521 |
+
[[package]]
|
| 522 |
+
name = "luminamoon-dataloader"
|
| 523 |
+
version = "1.1.0-post.3"
|
| 524 |
+
dependencies = [
|
| 525 |
+
"arrow-array",
|
| 526 |
+
"arrow-ipc",
|
| 527 |
+
"arrow-schema",
|
| 528 |
+
"crc32fast",
|
| 529 |
+
"hashbrown",
|
| 530 |
+
"parking_lot",
|
| 531 |
+
"parquet",
|
| 532 |
+
"pyo3",
|
| 533 |
+
"rayon",
|
| 534 |
+
"serde",
|
| 535 |
+
"serde_json",
|
| 536 |
+
"thiserror",
|
| 537 |
+
"xxhash-rust",
|
| 538 |
+
]
|
| 539 |
+
|
| 540 |
+
[[package]]
|
| 541 |
+
name = "luminamoon-optimizer"
|
| 542 |
+
version = "1.1.0-post.3"
|
| 543 |
+
dependencies = [
|
| 544 |
+
"luminamoon-dataloader",
|
| 545 |
+
"luminav-core",
|
| 546 |
+
"luminav-dispatch",
|
| 547 |
+
"luminav-simd",
|
| 548 |
+
"luminav-triton-aot",
|
| 549 |
+
"luminav-vulkan",
|
| 550 |
+
"pyo3",
|
| 551 |
+
"rayon",
|
| 552 |
+
"thiserror",
|
| 553 |
+
]
|
| 554 |
+
|
| 555 |
+
[[package]]
|
| 556 |
+
name = "luminav-core"
|
| 557 |
+
version = "1.1.0-post.3"
|
| 558 |
+
dependencies = [
|
| 559 |
+
"thiserror",
|
| 560 |
+
]
|
| 561 |
+
|
| 562 |
+
[[package]]
|
| 563 |
+
name = "luminav-dispatch"
|
| 564 |
+
version = "1.1.0-post.3"
|
| 565 |
+
dependencies = [
|
| 566 |
+
"luminav-core",
|
| 567 |
+
"luminav-simd",
|
| 568 |
+
"luminav-triton-aot",
|
| 569 |
+
"luminav-vulkan",
|
| 570 |
+
"thiserror",
|
| 571 |
+
]
|
| 572 |
+
|
| 573 |
+
[[package]]
|
| 574 |
+
name = "luminav-simd"
|
| 575 |
+
version = "1.1.0-post.3"
|
| 576 |
+
dependencies = [
|
| 577 |
+
"luminav-core",
|
| 578 |
+
"rayon",
|
| 579 |
+
]
|
| 580 |
+
|
| 581 |
+
[[package]]
|
| 582 |
+
name = "luminav-triton-aot"
|
| 583 |
+
version = "1.1.0-post.3"
|
| 584 |
+
dependencies = [
|
| 585 |
+
"libloading 0.9.0",
|
| 586 |
+
"luminav-core",
|
| 587 |
+
"serde",
|
| 588 |
+
"serde_json",
|
| 589 |
+
"thiserror",
|
| 590 |
+
]
|
| 591 |
+
|
| 592 |
+
[[package]]
|
| 593 |
+
name = "luminav-vulkan"
|
| 594 |
+
version = "1.1.0-post.3"
|
| 595 |
+
dependencies = [
|
| 596 |
+
"ash",
|
| 597 |
+
"luminav-core",
|
| 598 |
+
"thiserror",
|
| 599 |
+
]
|
| 600 |
+
|
| 601 |
+
[[package]]
|
| 602 |
+
name = "lz4_flex"
|
| 603 |
+
version = "0.14.0"
|
| 604 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 605 |
+
checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226"
|
| 606 |
+
dependencies = [
|
| 607 |
+
"twox-hash",
|
| 608 |
+
]
|
| 609 |
+
|
| 610 |
+
[[package]]
|
| 611 |
+
name = "memchr"
|
| 612 |
+
version = "2.8.3"
|
| 613 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 614 |
+
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
| 615 |
+
|
| 616 |
+
[[package]]
|
| 617 |
+
name = "num-bigint"
|
| 618 |
+
version = "0.5.1"
|
| 619 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 620 |
+
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
|
| 621 |
+
dependencies = [
|
| 622 |
+
"num-integer",
|
| 623 |
+
"num-traits",
|
| 624 |
+
]
|
| 625 |
+
|
| 626 |
+
[[package]]
|
| 627 |
+
name = "num-complex"
|
| 628 |
+
version = "0.4.6"
|
| 629 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 630 |
+
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
| 631 |
+
dependencies = [
|
| 632 |
+
"num-traits",
|
| 633 |
+
]
|
| 634 |
+
|
| 635 |
+
[[package]]
|
| 636 |
+
name = "num-integer"
|
| 637 |
+
version = "0.1.47"
|
| 638 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 639 |
+
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
| 640 |
+
dependencies = [
|
| 641 |
+
"num-traits",
|
| 642 |
+
]
|
| 643 |
+
|
| 644 |
+
[[package]]
|
| 645 |
+
name = "num-traits"
|
| 646 |
+
version = "0.2.19"
|
| 647 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 648 |
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
| 649 |
+
dependencies = [
|
| 650 |
+
"autocfg",
|
| 651 |
+
"libm",
|
| 652 |
+
]
|
| 653 |
+
|
| 654 |
+
[[package]]
|
| 655 |
+
name = "once_cell"
|
| 656 |
+
version = "1.21.4"
|
| 657 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 658 |
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
| 659 |
+
|
| 660 |
+
[[package]]
|
| 661 |
+
name = "parking_lot"
|
| 662 |
+
version = "0.12.5"
|
| 663 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 664 |
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
| 665 |
+
dependencies = [
|
| 666 |
+
"lock_api",
|
| 667 |
+
"parking_lot_core",
|
| 668 |
+
]
|
| 669 |
+
|
| 670 |
+
[[package]]
|
| 671 |
+
name = "parking_lot_core"
|
| 672 |
+
version = "0.9.12"
|
| 673 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 674 |
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
| 675 |
+
dependencies = [
|
| 676 |
+
"cfg-if",
|
| 677 |
+
"libc",
|
| 678 |
+
"redox_syscall",
|
| 679 |
+
"smallvec",
|
| 680 |
+
"windows-link",
|
| 681 |
+
]
|
| 682 |
+
|
| 683 |
+
[[package]]
|
| 684 |
+
name = "parquet"
|
| 685 |
+
version = "60.1.0"
|
| 686 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 687 |
+
checksum = "806eaa0f1c0436dcb43bee8e6a82bf2efff90521a60172f8281cb9ef577b43a4"
|
| 688 |
+
dependencies = [
|
| 689 |
+
"ahash",
|
| 690 |
+
"arrow-array",
|
| 691 |
+
"arrow-buffer",
|
| 692 |
+
"arrow-data",
|
| 693 |
+
"arrow-ipc",
|
| 694 |
+
"arrow-schema",
|
| 695 |
+
"arrow-select",
|
| 696 |
+
"base64",
|
| 697 |
+
"brotli",
|
| 698 |
+
"bytes",
|
| 699 |
+
"chrono",
|
| 700 |
+
"flate2",
|
| 701 |
+
"half",
|
| 702 |
+
"hashbrown",
|
| 703 |
+
"lz4_flex",
|
| 704 |
+
"num-bigint",
|
| 705 |
+
"num-integer",
|
| 706 |
+
"num-traits",
|
| 707 |
+
"seq-macro",
|
| 708 |
+
"simdutf8",
|
| 709 |
+
"snap",
|
| 710 |
+
"twox-hash",
|
| 711 |
+
"zstd",
|
| 712 |
+
]
|
| 713 |
+
|
| 714 |
+
[[package]]
|
| 715 |
+
name = "pin-project-lite"
|
| 716 |
+
version = "0.2.17"
|
| 717 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 718 |
+
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
| 719 |
+
|
| 720 |
+
[[package]]
|
| 721 |
+
name = "pkg-config"
|
| 722 |
+
version = "0.3.34"
|
| 723 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 724 |
+
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
| 725 |
+
|
| 726 |
+
[[package]]
|
| 727 |
+
name = "portable-atomic"
|
| 728 |
+
version = "1.15.0"
|
| 729 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 730 |
+
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
| 731 |
+
|
| 732 |
+
[[package]]
|
| 733 |
+
name = "proc-macro2"
|
| 734 |
+
version = "1.0.107"
|
| 735 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 736 |
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
| 737 |
+
dependencies = [
|
| 738 |
+
"unicode-ident",
|
| 739 |
+
]
|
| 740 |
+
|
| 741 |
+
[[package]]
|
| 742 |
+
name = "pyo3"
|
| 743 |
+
version = "0.30.1"
|
| 744 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 745 |
+
checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
|
| 746 |
+
dependencies = [
|
| 747 |
+
"libc",
|
| 748 |
+
"once_cell",
|
| 749 |
+
"portable-atomic",
|
| 750 |
+
"pyo3-build-config",
|
| 751 |
+
"pyo3-ffi",
|
| 752 |
+
"pyo3-macros",
|
| 753 |
+
]
|
| 754 |
+
|
| 755 |
+
[[package]]
|
| 756 |
+
name = "pyo3-build-config"
|
| 757 |
+
version = "0.30.1"
|
| 758 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 759 |
+
checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
|
| 760 |
+
dependencies = [
|
| 761 |
+
"target-lexicon",
|
| 762 |
+
]
|
| 763 |
+
|
| 764 |
+
[[package]]
|
| 765 |
+
name = "pyo3-ffi"
|
| 766 |
+
version = "0.30.1"
|
| 767 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 768 |
+
checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
|
| 769 |
+
dependencies = [
|
| 770 |
+
"libc",
|
| 771 |
+
"pyo3-build-config",
|
| 772 |
+
]
|
| 773 |
+
|
| 774 |
+
[[package]]
|
| 775 |
+
name = "pyo3-macros"
|
| 776 |
+
version = "0.30.1"
|
| 777 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 778 |
+
checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
|
| 779 |
+
dependencies = [
|
| 780 |
+
"proc-macro2",
|
| 781 |
+
"pyo3-macros-backend",
|
| 782 |
+
"quote",
|
| 783 |
+
"syn 3.0.9",
|
| 784 |
+
]
|
| 785 |
+
|
| 786 |
+
[[package]]
|
| 787 |
+
name = "pyo3-macros-backend"
|
| 788 |
+
version = "0.30.1"
|
| 789 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 790 |
+
checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
|
| 791 |
+
dependencies = [
|
| 792 |
+
"heck",
|
| 793 |
+
"proc-macro2",
|
| 794 |
+
"quote",
|
| 795 |
+
"syn 3.0.9",
|
| 796 |
+
]
|
| 797 |
+
|
| 798 |
+
[[package]]
|
| 799 |
+
name = "quote"
|
| 800 |
+
version = "1.0.47"
|
| 801 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 802 |
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
| 803 |
+
dependencies = [
|
| 804 |
+
"proc-macro2",
|
| 805 |
+
]
|
| 806 |
+
|
| 807 |
+
[[package]]
|
| 808 |
+
name = "r-efi"
|
| 809 |
+
version = "5.3.0"
|
| 810 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 811 |
+
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
| 812 |
+
|
| 813 |
+
[[package]]
|
| 814 |
+
name = "r-efi"
|
| 815 |
+
version = "6.0.0"
|
| 816 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 817 |
+
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
| 818 |
+
|
| 819 |
+
[[package]]
|
| 820 |
+
name = "rayon"
|
| 821 |
+
version = "1.12.0"
|
| 822 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 823 |
+
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
| 824 |
+
dependencies = [
|
| 825 |
+
"either",
|
| 826 |
+
"rayon-core",
|
| 827 |
+
]
|
| 828 |
+
|
| 829 |
+
[[package]]
|
| 830 |
+
name = "rayon-core"
|
| 831 |
+
version = "1.13.0"
|
| 832 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 833 |
+
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
| 834 |
+
dependencies = [
|
| 835 |
+
"crossbeam-deque",
|
| 836 |
+
"crossbeam-utils",
|
| 837 |
+
]
|
| 838 |
+
|
| 839 |
+
[[package]]
|
| 840 |
+
name = "redox_syscall"
|
| 841 |
+
version = "0.5.18"
|
| 842 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 843 |
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
| 844 |
+
dependencies = [
|
| 845 |
+
"bitflags",
|
| 846 |
+
]
|
| 847 |
+
|
| 848 |
+
[[package]]
|
| 849 |
+
name = "rustc_version"
|
| 850 |
+
version = "0.4.1"
|
| 851 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 852 |
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
| 853 |
+
dependencies = [
|
| 854 |
+
"semver",
|
| 855 |
+
]
|
| 856 |
+
|
| 857 |
+
[[package]]
|
| 858 |
+
name = "rustversion"
|
| 859 |
+
version = "1.0.23"
|
| 860 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 861 |
+
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
| 862 |
+
|
| 863 |
+
[[package]]
|
| 864 |
+
name = "scopeguard"
|
| 865 |
+
version = "1.2.0"
|
| 866 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 867 |
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
| 868 |
+
|
| 869 |
+
[[package]]
|
| 870 |
+
name = "semver"
|
| 871 |
+
version = "1.0.28"
|
| 872 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 873 |
+
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
| 874 |
+
|
| 875 |
+
[[package]]
|
| 876 |
+
name = "seq-macro"
|
| 877 |
+
version = "0.3.6"
|
| 878 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 879 |
+
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
|
| 880 |
+
|
| 881 |
+
[[package]]
|
| 882 |
+
name = "serde"
|
| 883 |
+
version = "1.0.229"
|
| 884 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 885 |
+
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
| 886 |
+
dependencies = [
|
| 887 |
+
"serde_core",
|
| 888 |
+
"serde_derive",
|
| 889 |
+
]
|
| 890 |
+
|
| 891 |
+
[[package]]
|
| 892 |
+
name = "serde_core"
|
| 893 |
+
version = "1.0.229"
|
| 894 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 895 |
+
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
| 896 |
+
dependencies = [
|
| 897 |
+
"serde_derive",
|
| 898 |
+
]
|
| 899 |
+
|
| 900 |
+
[[package]]
|
| 901 |
+
name = "serde_derive"
|
| 902 |
+
version = "1.0.229"
|
| 903 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 904 |
+
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
| 905 |
+
dependencies = [
|
| 906 |
+
"proc-macro2",
|
| 907 |
+
"quote",
|
| 908 |
+
"syn 3.0.9",
|
| 909 |
+
]
|
| 910 |
+
|
| 911 |
+
[[package]]
|
| 912 |
+
name = "serde_json"
|
| 913 |
+
version = "1.0.154"
|
| 914 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 915 |
+
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
| 916 |
+
dependencies = [
|
| 917 |
+
"itoa",
|
| 918 |
+
"memchr",
|
| 919 |
+
"serde",
|
| 920 |
+
"serde_core",
|
| 921 |
+
"zmij",
|
| 922 |
+
]
|
| 923 |
+
|
| 924 |
+
[[package]]
|
| 925 |
+
name = "shlex"
|
| 926 |
+
version = "2.0.1"
|
| 927 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 928 |
+
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
| 929 |
+
|
| 930 |
+
[[package]]
|
| 931 |
+
name = "simdutf8"
|
| 932 |
+
version = "0.1.5"
|
| 933 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 934 |
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
| 935 |
+
|
| 936 |
+
[[package]]
|
| 937 |
+
name = "slab"
|
| 938 |
+
version = "0.4.12"
|
| 939 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 940 |
+
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
| 941 |
+
|
| 942 |
+
[[package]]
|
| 943 |
+
name = "smallvec"
|
| 944 |
+
version = "1.16.3"
|
| 945 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 946 |
+
checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b"
|
| 947 |
+
|
| 948 |
+
[[package]]
|
| 949 |
+
name = "snap"
|
| 950 |
+
version = "1.1.2"
|
| 951 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 952 |
+
checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886"
|
| 953 |
+
|
| 954 |
+
[[package]]
|
| 955 |
+
name = "syn"
|
| 956 |
+
version = "2.0.119"
|
| 957 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 958 |
+
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
| 959 |
+
dependencies = [
|
| 960 |
+
"proc-macro2",
|
| 961 |
+
"quote",
|
| 962 |
+
"unicode-ident",
|
| 963 |
+
]
|
| 964 |
+
|
| 965 |
+
[[package]]
|
| 966 |
+
name = "syn"
|
| 967 |
+
version = "3.0.9"
|
| 968 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 969 |
+
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
| 970 |
+
dependencies = [
|
| 971 |
+
"proc-macro2",
|
| 972 |
+
"quote",
|
| 973 |
+
"unicode-ident",
|
| 974 |
+
]
|
| 975 |
+
|
| 976 |
+
[[package]]
|
| 977 |
+
name = "target-lexicon"
|
| 978 |
+
version = "0.13.5"
|
| 979 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 980 |
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
| 981 |
+
|
| 982 |
+
[[package]]
|
| 983 |
+
name = "thiserror"
|
| 984 |
+
version = "2.0.21"
|
| 985 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 986 |
+
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
|
| 987 |
+
dependencies = [
|
| 988 |
+
"thiserror-impl",
|
| 989 |
+
]
|
| 990 |
+
|
| 991 |
+
[[package]]
|
| 992 |
+
name = "thiserror-impl"
|
| 993 |
+
version = "2.0.21"
|
| 994 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 995 |
+
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
|
| 996 |
+
dependencies = [
|
| 997 |
+
"proc-macro2",
|
| 998 |
+
"quote",
|
| 999 |
+
"syn 3.0.9",
|
| 1000 |
+
]
|
| 1001 |
+
|
| 1002 |
+
[[package]]
|
| 1003 |
+
name = "tiny-keccak"
|
| 1004 |
+
version = "2.0.2"
|
| 1005 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1006 |
+
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
| 1007 |
+
dependencies = [
|
| 1008 |
+
"crunchy",
|
| 1009 |
+
]
|
| 1010 |
+
|
| 1011 |
+
[[package]]
|
| 1012 |
+
name = "twox-hash"
|
| 1013 |
+
version = "2.1.5"
|
| 1014 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1015 |
+
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
|
| 1016 |
+
|
| 1017 |
+
[[package]]
|
| 1018 |
+
name = "unicode-ident"
|
| 1019 |
+
version = "1.0.27"
|
| 1020 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1021 |
+
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
| 1022 |
+
|
| 1023 |
+
[[package]]
|
| 1024 |
+
name = "version_check"
|
| 1025 |
+
version = "0.9.5"
|
| 1026 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1027 |
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
| 1028 |
+
|
| 1029 |
+
[[package]]
|
| 1030 |
+
name = "wasi"
|
| 1031 |
+
version = "0.11.1+wasi-snapshot-preview1"
|
| 1032 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1033 |
+
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
| 1034 |
+
|
| 1035 |
+
[[package]]
|
| 1036 |
+
name = "wasip2"
|
| 1037 |
+
version = "1.0.4+wasi-0.2.12"
|
| 1038 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1039 |
+
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
| 1040 |
+
dependencies = [
|
| 1041 |
+
"wit-bindgen",
|
| 1042 |
+
]
|
| 1043 |
+
|
| 1044 |
+
[[package]]
|
| 1045 |
+
name = "wasm-bindgen"
|
| 1046 |
+
version = "0.2.129"
|
| 1047 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1048 |
+
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
| 1049 |
+
dependencies = [
|
| 1050 |
+
"cfg-if",
|
| 1051 |
+
"once_cell",
|
| 1052 |
+
"rustversion",
|
| 1053 |
+
"wasm-bindgen-macro",
|
| 1054 |
+
"wasm-bindgen-shared",
|
| 1055 |
+
]
|
| 1056 |
+
|
| 1057 |
+
[[package]]
|
| 1058 |
+
name = "wasm-bindgen-macro"
|
| 1059 |
+
version = "0.2.129"
|
| 1060 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1061 |
+
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
| 1062 |
+
dependencies = [
|
| 1063 |
+
"quote",
|
| 1064 |
+
"wasm-bindgen-macro-support",
|
| 1065 |
+
]
|
| 1066 |
+
|
| 1067 |
+
[[package]]
|
| 1068 |
+
name = "wasm-bindgen-macro-support"
|
| 1069 |
+
version = "0.2.129"
|
| 1070 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1071 |
+
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
| 1072 |
+
dependencies = [
|
| 1073 |
+
"bumpalo",
|
| 1074 |
+
"proc-macro2",
|
| 1075 |
+
"quote",
|
| 1076 |
+
"syn 3.0.9",
|
| 1077 |
+
"wasm-bindgen-shared",
|
| 1078 |
+
]
|
| 1079 |
+
|
| 1080 |
+
[[package]]
|
| 1081 |
+
name = "wasm-bindgen-shared"
|
| 1082 |
+
version = "0.2.129"
|
| 1083 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1084 |
+
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
| 1085 |
+
dependencies = [
|
| 1086 |
+
"unicode-ident",
|
| 1087 |
+
]
|
| 1088 |
+
|
| 1089 |
+
[[package]]
|
| 1090 |
+
name = "windows-core"
|
| 1091 |
+
version = "0.62.2"
|
| 1092 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1093 |
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
| 1094 |
+
dependencies = [
|
| 1095 |
+
"windows-implement",
|
| 1096 |
+
"windows-interface",
|
| 1097 |
+
"windows-link",
|
| 1098 |
+
"windows-result",
|
| 1099 |
+
"windows-strings",
|
| 1100 |
+
]
|
| 1101 |
+
|
| 1102 |
+
[[package]]
|
| 1103 |
+
name = "windows-implement"
|
| 1104 |
+
version = "0.60.2"
|
| 1105 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1106 |
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
| 1107 |
+
dependencies = [
|
| 1108 |
+
"proc-macro2",
|
| 1109 |
+
"quote",
|
| 1110 |
+
"syn 2.0.119",
|
| 1111 |
+
]
|
| 1112 |
+
|
| 1113 |
+
[[package]]
|
| 1114 |
+
name = "windows-interface"
|
| 1115 |
+
version = "0.59.3"
|
| 1116 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1117 |
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
| 1118 |
+
dependencies = [
|
| 1119 |
+
"proc-macro2",
|
| 1120 |
+
"quote",
|
| 1121 |
+
"syn 2.0.119",
|
| 1122 |
+
]
|
| 1123 |
+
|
| 1124 |
+
[[package]]
|
| 1125 |
+
name = "windows-link"
|
| 1126 |
+
version = "0.2.1"
|
| 1127 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1128 |
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
| 1129 |
+
|
| 1130 |
+
[[package]]
|
| 1131 |
+
name = "windows-result"
|
| 1132 |
+
version = "0.4.1"
|
| 1133 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1134 |
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
| 1135 |
+
dependencies = [
|
| 1136 |
+
"windows-link",
|
| 1137 |
+
]
|
| 1138 |
+
|
| 1139 |
+
[[package]]
|
| 1140 |
+
name = "windows-strings"
|
| 1141 |
+
version = "0.5.1"
|
| 1142 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1143 |
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
| 1144 |
+
dependencies = [
|
| 1145 |
+
"windows-link",
|
| 1146 |
+
]
|
| 1147 |
+
|
| 1148 |
+
[[package]]
|
| 1149 |
+
name = "wit-bindgen"
|
| 1150 |
+
version = "0.57.1"
|
| 1151 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1152 |
+
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
| 1153 |
+
|
| 1154 |
+
[[package]]
|
| 1155 |
+
name = "xxhash-rust"
|
| 1156 |
+
version = "0.8.19"
|
| 1157 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1158 |
+
checksum = "550a2b930b62486a393c52d5c3b84bff264b28aa437ed64694d31e93b1757af7"
|
| 1159 |
+
|
| 1160 |
+
[[package]]
|
| 1161 |
+
name = "zerocopy"
|
| 1162 |
+
version = "0.8.63"
|
| 1163 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1164 |
+
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
| 1165 |
+
dependencies = [
|
| 1166 |
+
"zerocopy-derive",
|
| 1167 |
+
]
|
| 1168 |
+
|
| 1169 |
+
[[package]]
|
| 1170 |
+
name = "zerocopy-derive"
|
| 1171 |
+
version = "0.8.63"
|
| 1172 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1173 |
+
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
| 1174 |
+
dependencies = [
|
| 1175 |
+
"proc-macro2",
|
| 1176 |
+
"quote",
|
| 1177 |
+
"syn 2.0.119",
|
| 1178 |
+
]
|
| 1179 |
+
|
| 1180 |
+
[[package]]
|
| 1181 |
+
name = "zlib-rs"
|
| 1182 |
+
version = "0.6.8"
|
| 1183 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1184 |
+
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
| 1185 |
+
|
| 1186 |
+
[[package]]
|
| 1187 |
+
name = "zmij"
|
| 1188 |
+
version = "1.0.23"
|
| 1189 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1190 |
+
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
| 1191 |
+
|
| 1192 |
+
[[package]]
|
| 1193 |
+
name = "zstd"
|
| 1194 |
+
version = "0.14.3"
|
| 1195 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1196 |
+
checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70"
|
| 1197 |
+
dependencies = [
|
| 1198 |
+
"zstd-safe",
|
| 1199 |
+
]
|
| 1200 |
+
|
| 1201 |
+
[[package]]
|
| 1202 |
+
name = "zstd-safe"
|
| 1203 |
+
version = "8.2.0"
|
| 1204 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1205 |
+
checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b"
|
| 1206 |
+
dependencies = [
|
| 1207 |
+
"zstd-sys",
|
| 1208 |
+
]
|
| 1209 |
+
|
| 1210 |
+
[[package]]
|
| 1211 |
+
name = "zstd-sys"
|
| 1212 |
+
version = "2.1.2+zstd.1.5.7"
|
| 1213 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1214 |
+
checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3"
|
| 1215 |
+
dependencies = [
|
| 1216 |
+
"cc",
|
| 1217 |
+
"pkg-config",
|
| 1218 |
+
]
|
Cargo.toml
CHANGED
|
@@ -1,39 +1,39 @@
|
|
| 1 |
-
[workspace]
|
| 2 |
-
resolver = "3"
|
| 3 |
-
members = [
|
| 4 |
-
"optimizer",
|
| 5 |
-
"optimizer/crates/*",
|
| 6 |
-
"data",
|
| 7 |
-
]
|
| 8 |
-
|
| 9 |
-
[workspace.package]
|
| 10 |
-
version = "1.1.0-post.3"
|
| 11 |
-
edition = "2024"
|
| 12 |
-
license = "Apache-2.0"
|
| 13 |
-
authors = ["Silver Moon <leafmoon.idn@gmail.com>", "Lumina Moon (cloverx-id) Contributors"]
|
| 14 |
-
repository = "https://github.com/cloverx-id/luminamoon"
|
| 15 |
-
homepage = "https://github.com/cloverx-id/luminamoon"
|
| 16 |
-
|
| 17 |
-
[workspace.dependencies]
|
| 18 |
-
pyo3 = { version = "0.30.1", features = ["abi3-py310", "abi3t"] }
|
| 19 |
-
ash = "0.38.0"
|
| 20 |
-
libloading = "0.9.0"
|
| 21 |
-
rayon = "1.12.0"
|
| 22 |
-
thiserror = "2.0.21"
|
| 23 |
-
serde = { version = "1.0.229", features = ["derive"] }
|
| 24 |
-
serde_json = "1.0.154"
|
| 25 |
-
parquet = "60.1.0"
|
| 26 |
-
arrow-array = "60.1.0"
|
| 27 |
-
arrow-schema = "60.1.0"
|
| 28 |
-
arrow-ipc = "60.1.0"
|
| 29 |
-
xxhash-rust = { version = "0.8.19", features = ["xxh3"] }
|
| 30 |
-
crc32fast = "1.5.2"
|
| 31 |
-
hashbrown = "0.17.1"
|
| 32 |
-
parking_lot = "0.12.5"
|
| 33 |
-
|
| 34 |
-
[profile.release]
|
| 35 |
-
opt-level = 3
|
| 36 |
-
lto = "fat"
|
| 37 |
-
codegen-units = 16
|
| 38 |
-
panic = "abort"
|
| 39 |
-
strip = true
|
|
|
|
| 1 |
+
[workspace]
|
| 2 |
+
resolver = "3"
|
| 3 |
+
members = [
|
| 4 |
+
"optimizer",
|
| 5 |
+
"optimizer/crates/*",
|
| 6 |
+
"data",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
[workspace.package]
|
| 10 |
+
version = "1.1.0-post.3"
|
| 11 |
+
edition = "2024"
|
| 12 |
+
license = "Apache-2.0"
|
| 13 |
+
authors = ["Silver Moon <leafmoon.idn@gmail.com>", "Lumina Moon (cloverx-id) Contributors"]
|
| 14 |
+
repository = "https://github.com/cloverx-id/luminamoon"
|
| 15 |
+
homepage = "https://github.com/cloverx-id/luminamoon"
|
| 16 |
+
|
| 17 |
+
[workspace.dependencies]
|
| 18 |
+
pyo3 = { version = "0.30.1", features = ["abi3-py310", "abi3t"] }
|
| 19 |
+
ash = "0.38.0"
|
| 20 |
+
libloading = "0.9.0"
|
| 21 |
+
rayon = "1.12.0"
|
| 22 |
+
thiserror = "2.0.21"
|
| 23 |
+
serde = { version = "1.0.229", features = ["derive"] }
|
| 24 |
+
serde_json = "1.0.154"
|
| 25 |
+
parquet = "60.1.0"
|
| 26 |
+
arrow-array = "60.1.0"
|
| 27 |
+
arrow-schema = "60.1.0"
|
| 28 |
+
arrow-ipc = "60.1.0"
|
| 29 |
+
xxhash-rust = { version = "0.8.19", features = ["xxh3"] }
|
| 30 |
+
crc32fast = "1.5.2"
|
| 31 |
+
hashbrown = "0.17.1"
|
| 32 |
+
parking_lot = "0.12.5"
|
| 33 |
+
|
| 34 |
+
[profile.release]
|
| 35 |
+
opt-level = 3
|
| 36 |
+
lto = "fat"
|
| 37 |
+
codegen-units = 16
|
| 38 |
+
panic = "abort"
|
| 39 |
+
strip = true
|
LICENSE
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
-
Copyright 2026 Silver Moon (cloverxion) and Lumina Moon (cloverx-id) Contributors
|
| 2 |
-
|
| 3 |
-
Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
-
you may not use this file except in compliance with the License.
|
| 5 |
-
You may obtain a copy of the License at
|
| 6 |
-
|
| 7 |
-
https://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
-
|
| 9 |
-
Unless required by applicable law or agreed to in writing, software
|
| 10 |
-
distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
-
See the License for the specific language governing permissions and
|
| 13 |
-
limitations under the License.
|
|
|
|
| 1 |
+
Copyright 2026 Silver Moon (cloverxion) and Lumina Moon (cloverx-id) Contributors
|
| 2 |
+
|
| 3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
you may not use this file except in compliance with the License.
|
| 5 |
+
You may obtain a copy of the License at
|
| 6 |
+
|
| 7 |
+
https://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
|
| 9 |
+
Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
See the License for the specific language governing permissions and
|
| 13 |
+
limitations under the License.
|
README.md
CHANGED
|
@@ -1,334 +1,334 @@
|
|
| 1 |
-
---
|
| 2 |
-
license: apache-2.0
|
| 3 |
-
library_name: pytorch
|
| 4 |
-
language:
|
| 5 |
-
- en
|
| 6 |
-
tags:
|
| 7 |
-
- pytorch
|
| 8 |
-
- optimizer
|
| 9 |
-
- custom-optimizer
|
| 10 |
-
- luminamoon
|
| 11 |
-
- luminav
|
| 12 |
-
- clm
|
| 13 |
-
- td-clm
|
| 14 |
-
- lso-eval
|
| 15 |
-
- rust
|
| 16 |
-
- simd
|
| 17 |
-
- avx512
|
| 18 |
-
- vulkan
|
| 19 |
-
- triton
|
| 20 |
-
- memory-efficient
|
| 21 |
-
- stochastic-rounding
|
| 22 |
-
- low-vram
|
| 23 |
-
- bf16
|
| 24 |
-
- fp16
|
| 25 |
-
- cuda
|
| 26 |
-
- rocm
|
| 27 |
-
- xpu
|
| 28 |
-
- llm
|
| 29 |
-
- transformer
|
| 30 |
-
- deep-learning
|
| 31 |
-
- research
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
# Loomy
|
| 35 |
-
|
| 36 |
-
<p align="left">
|
| 37 |
-
<a href="https://doi.org/10.5281/zenodo.23973326"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.23973326.svg" alt="DOI"></a>
|
| 38 |
-
<a href="https://pypi.org/project/luminamoon/"><img src="https://img.shields.io/pypi/v/luminamoon.svg?color=4f46e5" alt="PyPI version"></a>
|
| 39 |
-
<a href="https://github.com/cloverx-id/luminamoon/actions/workflows/ci.yml"><img src="https://github.com/cloverx-id/luminamoon/actions/workflows/ci.yml/badge.svg" alt="CI Status"></a>
|
| 40 |
-
<a href="eval/README.md"><img src="https://img.shields.io/badge/LSO--Eval-Verified-10b981" alt="LSO-Eval"></a>
|
| 41 |
-
<a href="eval/LSO_Eval.pdf"><img src="https://img.shields.io/badge/Eval_Docs-PDF-b31b1b" alt="Eval Docs"></a>
|
| 42 |
-
<a href="https://cloverx-id.github.io/luminamoon/"><img src="https://img.shields.io/badge/docs-GitHub_Pages-06b6d4" alt="Documentation"></a>
|
| 43 |
-
<a href="https://pypi.org/project/luminamoon/"><img src="https://img.shields.io/badge/python-3.10%20%E2%86%92%203.15t%20(free--threaded)-10b981" alt="Python Versions"></a>
|
| 44 |
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
|
| 45 |
-
</p>
|
| 46 |
-
|
| 47 |
-
**Loomy (`luminamoon`)** provides low-precision numerical optimization, rational neural network layers, and dataset text preprocessing utilities developed by **Silver Moon ([@cloverxion](https://github.com/cloverxion))** and **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**.
|
| 48 |
-
|
| 49 |
-
Built with a modular Rust and Python architecture, `luminamoon` provides components for CPU/GPU parameter updates and text dataset preprocessing:
|
| 50 |
-
|
| 51 |
-
> **LSO-Eval Evaluation & Verification Notebook**: [**Open in Google Colab**](https://colab.research.google.com/drive/1UepDza5Wm3KRNooCA2Bn69GIeKB21a4a?usp=sharing) — *Stage II–IV benchmarks executed on `LuminaV v1.5.3` under native `torch.bfloat16` (`centered_defect=False`).*
|
| 52 |
-
|
| 53 |
-
## Projects & Components
|
| 54 |
-
|
| 55 |
-
| Project / Component | Path | Status | Description | Documentation |
|
| 56 |
-
| :--- | :--- | :--- | :--- | :--- |
|
| 57 |
-
| **LuminaV Optimizer** | [`optimizer/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/optimizer/README.md) | `Active` (Math `v1.6.0`) | LuminaV v1.6.0 optimizer implementation with CPU SIMD (AVX-512/AVX2), Vulkan UMA, Triton AOT/JIT kernels, Triangle Family (`buffer="T1"`, `"T2"`), optional contiguous bucket fusion, and Triton JIT pre-warmup. | [Optimizer Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/optimizer/README.md) |
|
| 58 |
-
| **LuminaMoon Data Engine** | [`data/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md) | `Active` (`v1.1.0`) | Modular text preprocessing library: AVX-512/AVX2/NEON SIMD text normalization, dual-layout StringTensor (CPU StringView & GPU contiguous buffers), Shannon entropy filtering, MinHash/XXH3-64 deduplication, PII masking, and 128B Ping-Pong checkpoint recovery. | [Data Engine Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md) |
|
| 59 |
-
| **LSO-Eval** | [`eval/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md) | `Active` | Algorithm-agnostic optimizer evaluation & verification: Z3 SMT solver checks, 2D trajectory benchmarks, numerical parity tests, and Two-Class 4B LLM & Vision evaluation. | [LSO-Eval Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md) |
|
| 60 |
-
| **Changelogs** | [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md) | `Active` | Version histories separating Software Package releases (`v1.0.0`–`v1.1.0`) and LuminaV Algorithm Math versions (`v1.0.0`–`v1.6.0`). | [Changelogs](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md) |
|
| 61 |
-
| **Documentation** | [`docs/`](https://cloverx-id.github.io/luminamoon/) | `Active` | Documentation index, developer guides (`docs/optimize/`, `docs/dataloader/`), and dependency version notes. | [Docs Index](https://cloverx-id.github.io/luminamoon/) |
|
| 62 |
-
| **Ra-Family Architecture** | [`python/luminamoon/nn/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/tree/main/python/luminamoon/nn) | `Active` (`v1.1.0`) | Rational, tanh-free & square-root-free neural components: RaNorm, RaBatchNorm2d (with Conv2d weight folding), RaTanh, RaGELU, PosRaGLU (bounded gating in `(0, 1)`), and RaAttention with Cayley-Padé rational softmax. Triton GPU and Rust AVX2/AVX-512 SIMD acceleration. | [Ra-Family Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/nn/README.md) |
|
| 63 |
-
| **Python Package** | [`python/luminamoon/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/python/luminamoon/__init__.py) | `Active` (`v1.1.0`) | Unified free-threaded (`abi3t`) Python namespace exposing LuminaV Optimizer, DataStreamLoader, Ra-Family neural architecture, and PyTorch/NumPy/HuggingFace integration. | [API Reference](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/03_configuration_api.md) |
|
| 64 |
-
| **Theoretical References** | [`references/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) | `Active` | Mathematical formulas, component mapping, literature comparisons, and BibTeX citations for LuminaV, Ra-Family, and Data Engine. | [References Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) |
|
| 65 |
-
|
| 66 |
-
## Quickstart
|
| 67 |
-
|
| 68 |
-
### Installation
|
| 69 |
-
|
| 70 |
-
Install the compiled package wheel from PyPI:
|
| 71 |
-
|
| 72 |
-
```bash
|
| 73 |
-
pip install luminamoon
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
Or build from source using Maturin and the Rust 2024 toolchain:
|
| 77 |
-
|
| 78 |
-
```bash
|
| 79 |
-
git clone https://github.com/cloverx-id/luminamoon.git
|
| 80 |
-
cd luminamoon
|
| 81 |
-
pip install maturin
|
| 82 |
-
maturin develop --release
|
| 83 |
-
```
|
| 84 |
-
|
| 85 |
-
### Basic Usage (LuminaV Optimizer)
|
| 86 |
-
|
| 87 |
-
```python
|
| 88 |
-
import numpy as np
|
| 89 |
-
from luminamoon import LuminaV
|
| 90 |
-
|
| 91 |
-
# Initialize parameters
|
| 92 |
-
weights = np.random.randn(64, 10).astype(np.float32)
|
| 93 |
-
biases = np.zeros(10, dtype=np.float32)
|
| 94 |
-
|
| 95 |
-
# Instantiate LuminaV optimizer (uses default hyperparameters)
|
| 96 |
-
optimizer = LuminaV([weights, biases], lr=8e-4, weight_decay=0.08, buffer=2)
|
| 97 |
-
|
| 98 |
-
# Execute optimization step with explicit gradients
|
| 99 |
-
grad_w = np.random.randn(64, 10).astype(np.float32)
|
| 100 |
-
grad_b = np.random.randn(10, dtype=np.float32)
|
| 101 |
-
optimizer.step(grads=[grad_w, grad_b])
|
| 102 |
-
```
|
| 103 |
-
|
| 104 |
-
For PyTorch deep learning workflows, see the [PyTorch Integration Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/01_getting_started.md#quickstart-pytorch-integration).
|
| 105 |
-
|
| 106 |
-
### LuminaV Hyperparameter Quick Reference
|
| 107 |
-
|
| 108 |
-
All default values below are exposed in IDE autocompletion and `LuminaV.__init__` docstrings:
|
| 109 |
-
|
| 110 |
-
| Parameter | Type | Default | Description |
|
| 111 |
-
| :--- | :--- | :--- | :--- |
|
| 112 |
-
| `lr` | `float` | `8e-4` (`0.0008`) | Learning rate (`η`). |
|
| 113 |
-
| `betas` | `Tuple[float, float]` | `(0.9, 0.999)` | Exponential moving average factors `(β₁, β₂)` for first-moment momentum and second-moment variance. |
|
| 114 |
-
| `eps` | `float` | `1e-8` | Numerical stability constant (`ε`) in the adaptive denominator. |
|
| 115 |
-
| `weight_decay` | `float` | `0.08` (`8e-2`) | Decoupled weight decay coefficient. |
|
| 116 |
-
| `tau` | `float` | `0.8` | Denominator temperature scaling factor (`τ`) for 2-buffer variance damping. |
|
| 117 |
-
| `lam` | `float` | `0.3` | Triangle-defect penalty weight (`λ`) on `\|d_t\| = \|g_t - m_{t-1}\|` in 1-Buffer TD-CLM (`buffer=1`) and Triangle Family (`buffer="T1"`, `"T2"`). |
|
| 118 |
-
| `cautious` | `bool` | `True` | Enables sign-aligned cautious update masking and lagged EMA scaling. |
|
| 119 |
-
| `cautious_clamp_min` | `float` | `0.5` | Lower bound in `(0.0, 1.0]` for cautious alignment scaling. |
|
| 120 |
-
| `buffer` | `int \| str` | `2` | State mode: `2` (`"dual"`, `"clm"`) for 2-Buffer CLM, `1` (`"single"`, `"td-clm"`, `"tdclm"`) for 1-Buffer TD-CLM, `"T2"` for 2-Buffer Triangle Family (`tanh`-free), or `"T1"` for 1-Buffer Triangle Family (`tanh`-free). |
|
| 121 |
-
| `stochastic_rounding` | `bool` | `True` | Applies stateless 32-bit MurmurHash3 stochastic rounding when writing to `bfloat16` / `float16` weights. |
|
| 122 |
-
| `bound` | `bool` | `True` | Enables adaptive update bounding. |
|
| 123 |
-
| `bound_type` | `str \| int` | `"coordinate"` | Clipping mode: `"coordinate"` (element-wise clip) or `"radial"` (global norm ratio). |
|
| 124 |
-
| `bound_ratio` | `float` | `0.05` | Maximum update magnitude relative to parameter scale. |
|
| 125 |
-
| `master_weights` | `str \| bool` | `"none"` | Mixed-precision master weight mode for `bfloat16`/`float16`: `"none"` (`False`), `"semi"`, or `"full"` (`True`). |
|
| 126 |
-
| `execution` | `str \| int` | `"auto"` | Hardware backend: `"auto"`, `"triton"`, `"vulkan"`, `"simd"`, or `"scalar"`. |
|
| 127 |
-
| `fused_single_pass` | `bool` | `True` | Fuses parameter update and scalar reduction statistics into a single kernel pass. |
|
| 128 |
-
| `fuse_tensors` | `bool` | `False` | Coalesces parameters into contiguous 1D memory buffers to reduce kernel launch latency on fragmented architectures (e.g. Vision models / ViT). Not recommended for large LLMs or distributed FSDP training. |
|
| 129 |
-
| `bucket_cap_mb` | `float` | `500.0` | Maximum memory capacity per contiguous bucket chunk in megabytes when `fuse_tensors=True`. Partitions large models into bounded chunks. Emits a `UserWarning` if explicitly specified when `fuse_tensors=False`. |
|
| 130 |
-
| `prewarm_jit` | `bool` | `True` | Pre-compiles Triton GPU kernels on 64-element dummy buffers (`lr=0.0`) during initialization to avoid first-step JIT compilation delay. |
|
| 131 |
-
| `ema_decay` | `str \| float` | `"auto"` | EMA decay for lagged cautious/radial scalars (`"auto"` resolves to `0.80` for `buffer=2`/`"T2"` and `0.95` for `buffer=1`/`"T1"`). |
|
| 132 |
-
| `fsdp_sync` | `bool` | `False` | Synchronizes global reduction scratch buffers across `torch.distributed` FSDP shards. |
|
| 133 |
-
| `seed` | `int` | `1337` | Deterministic PRNG seed for stateless stochastic rounding. |
|
| 134 |
-
| `version` | `Optional[str]` | `"1.6.0"` | Selects the active LuminaV mathematical specification version. |
|
| 135 |
-
|
| 136 |
-
### Software vs. Algorithm Versioning (`v1.1.0` / Math `v1.6.0`)
|
| 137 |
-
|
| 138 |
-
`luminamoon` maintains two distinct, decoupled version lineages (documented in [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md)):
|
| 139 |
-
|
| 140 |
-
1. **Software Package Version (`v1.1.0`)**:
|
| 141 |
-
- Tracks the monorepo distribution, CPython FFI bindings, Rust SIMD crates, neural architecture (`luminamoon.nn`), and Data Engine utilities on PyPI (`pip install luminamoon`).
|
| 142 |
-
- To reproduce or execute older software package releases in Python, install the desired release directly via standard package management (`pip install luminamoon==1.0.9`) or check out official git release tags.
|
| 143 |
-
- See [`changelogs/SOFTWARE_CHANGELOG.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/SOFTWARE_CHANGELOG.md).
|
| 144 |
-
|
| 145 |
-
2. **LuminaV Algorithm Math Version (`v1.6.0`)**:
|
| 146 |
-
- Tracks the mathematical optimization formulas, convergence envelopes, and hardware kernels of the **LuminaV Optimizer** independently from pip package updates.
|
| 147 |
-
- Four mathematical versions are supported: `1.6.0` (default with Triangle Family `T1`/`T2` and Padé rational bounds), `1.5.5` (bucket fusion and Triton pre-warmup), `1.5.4` (Bitwise-BF16 state promotion), and `1.5.3` (single-buffer TD-CLM).
|
| 148 |
-
- Features **strict capability enforcement**: locking to an older mathematical version (such as `version="1.5.4"`) clearly rejects features introduced in later versions (such as `buffer="T2"` or `fuse_tensors=True`), helping ensure exact reproducibility for benchmarks and publications.
|
| 149 |
-
- See [`changelogs/LUMINAV_ALGORITHM_CHANGELOG.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/CHANGELOG.md).
|
| 150 |
-
|
| 151 |
-
```python
|
| 152 |
-
import luminamoon
|
| 153 |
-
from luminamoon import LuminaV
|
| 154 |
-
import luminamoon.data as lmd
|
| 155 |
-
|
| 156 |
-
# 1. Inspect Software Package Version vs. Algorithm Math Version
|
| 157 |
-
print(luminamoon.__version__) # "1.1.0" (Software package release)
|
| 158 |
-
print(lmd.get_version()) # "1.1.0" (Data Engine package module)
|
| 159 |
-
print(LuminaV.ALGORITHM_MATH) # "1.6.0" (Algorithm mathematical version)
|
| 160 |
-
print(LuminaV.ALGORITHM_SPEC) # "1.6.0" (Backward-compatible alias)
|
| 161 |
-
print(LuminaV.getVersion()) # "1.6.0"
|
| 162 |
-
|
| 163 |
-
# 2. Inspect capability matrix of any supported specification
|
| 164 |
-
caps = LuminaV.get_capabilities("1.5.5")
|
| 165 |
-
print(caps["allowed_buffers"]) # (1, 2)
|
| 166 |
-
print(caps["supports_triangle_family"]) # False (introduced in 1.6.0)
|
| 167 |
-
|
| 168 |
-
# 3. Lock algorithm version for scientific checkpoint reproducibility
|
| 169 |
-
opt = LuminaV([weights, biases], lr=8e-4, version="1.5.5")
|
| 170 |
-
opt.setVersion("1.5.5") # Instance-level version lock
|
| 171 |
-
LuminaV.setVersion("1.5.5") # Class-level default specification
|
| 172 |
-
```
|
| 173 |
-
|
| 174 |
-
### Basic Usage (LuminaMoon Data Engine: Fused Text Preprocessing Pipeline)
|
| 175 |
-
|
| 176 |
-
```python
|
| 177 |
-
from luminamoon.data import FusedPipeline, StringTensor, create_resumable_collate, ResumablePipeline
|
| 178 |
-
|
| 179 |
-
# 1. Fused single-pass text preprocessing in Rust
|
| 180 |
-
pipeline = FusedPipeline(
|
| 181 |
-
trim_whitespace=True,
|
| 182 |
-
normalize_newlines=True, # AVX-512 / AVX2 / NEON SIMD
|
| 183 |
-
min_chars=10,
|
| 184 |
-
min_entropy=2.0,
|
| 185 |
-
)
|
| 186 |
-
string_tensor = pipeline([" sample 1\r\n", "sample 2 with proper prose text"])
|
| 187 |
-
|
| 188 |
-
# 2. Export to Apache Arrow, Polars, and PyTorch buffers
|
| 189 |
-
polars_series = string_tensor.to_polars()
|
| 190 |
-
arrow_array = string_tensor.to_arrow()
|
| 191 |
-
offsets_tensor, payload_tensor = string_tensor.to_torch_gpu(device="cuda")
|
| 192 |
-
|
| 193 |
-
# 3. Stream dataset with background prefetching & 128B Ping-Pong checkpointing
|
| 194 |
-
pipe = ResumablePipeline(
|
| 195 |
-
source=raw_data,
|
| 196 |
-
fused_pipeline=pipeline,
|
| 197 |
-
batch_size=64,
|
| 198 |
-
prefetch_batches=4,
|
| 199 |
-
checkpoint_path="./cache/checkpoint.meta",
|
| 200 |
-
)
|
| 201 |
-
for batch in pipe:
|
| 202 |
-
# Training step...
|
| 203 |
-
pipe.commit() # Saves Dual-Slot A/B checkpoint and deduplication WAL
|
| 204 |
-
```
|
| 205 |
-
|
| 206 |
-
For detailed architecture, Shannon entropy filtering, MinHash deduplication, and PyTorch `collate_fn` adapters, see the [Data Engine Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md).
|
| 207 |
-
|
| 208 |
-
### Basic Usage (Ra-Family Rational Neural Architecture)
|
| 209 |
-
|
| 210 |
-
```python
|
| 211 |
-
import torch
|
| 212 |
-
from luminamoon.nn import RaNorm, PosRaGLU, RaAttention
|
| 213 |
-
|
| 214 |
-
# 1. Square-root-free token normalization (L1-mean)
|
| 215 |
-
norm = RaNorm(512)
|
| 216 |
-
x = torch.randn(2, 64, 512)
|
| 217 |
-
x_norm = norm(x)
|
| 218 |
-
|
| 219 |
-
# 2. Rational gated linear unit with strictly positive bounds in (0, 1)
|
| 220 |
-
mlp = PosRaGLU(512, hidden_features=2048)
|
| 221 |
-
x_ffn = mlp(x_norm)
|
| 222 |
-
|
| 223 |
-
# 3. Cayley-Padé rational multi-head attention (SFU-free softmax)
|
| 224 |
-
attn = RaAttention(dim=512, num_heads=8, qk_norm=True, causal=True)
|
| 225 |
-
out = attn(x_norm)
|
| 226 |
-
```
|
| 227 |
-
|
| 228 |
-
For complete architecture, parameter folding for Conv2d, and standalone NumPy usage, see the [Ra-Family Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/nn/README.md).
|
| 229 |
-
|
| 230 |
-
## Verification & CI/CD
|
| 231 |
-
|
| 232 |
-
All pull requests and releases are tested across Linux, Windows, and macOS:
|
| 233 |
-
|
| 234 |
-
```bash
|
| 235 |
-
# Run static analysis across the Rust workspace
|
| 236 |
-
cargo clippy --workspace --all-targets -- -D warnings
|
| 237 |
-
|
| 238 |
-
# Run Rust workspace unit and parity tests (Optimizer + Data Engine)
|
| 239 |
-
cargo test --workspace
|
| 240 |
-
|
| 241 |
-
# Run Python static type check
|
| 242 |
-
npx pyright python/luminamoon
|
| 243 |
-
|
| 244 |
-
# Run standalone NumPy, PyTorch, and Data Engine tests
|
| 245 |
-
python optimizer/tests/test_standalone_numpy.py
|
| 246 |
-
python optimizer/tests/test_end_to_end_training.py
|
| 247 |
-
python -B tests/test_data_modular.py
|
| 248 |
-
python -B tests/test_gpu_vs_cpu_strings.py
|
| 249 |
-
|
| 250 |
-
# Run LSO-Eval verification
|
| 251 |
-
python -m eval
|
| 252 |
-
```
|
| 253 |
-
|
| 254 |
-
## Developer Resources
|
| 255 |
-
|
| 256 |
-
For contributors working on **Loomy (`luminamoon`)**:
|
| 257 |
-
|
| 258 |
-
- **Changelogs (Software & Algorithm)**: [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md)
|
| 259 |
-
- **Optimizer Modular Documentation**: [`docs/optimize/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/optimize/README.md)
|
| 260 |
-
- **Data Engine Modular Documentation**: [`data/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md)
|
| 261 |
-
- **Optimizer Library Notes**: [`docs/library_audit/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/library_audit/README.md)
|
| 262 |
-
- **Data Standalone Library Notes**: [`data/docs/library_audit/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/docs/library_audit/README.md)
|
| 263 |
-
- **Data Engine Architecture & User Guide**: [`data/README.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md)
|
| 264 |
-
- **User Documentation**: [`docs/en/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/README.md)
|
| 265 |
-
|
| 266 |
-
## Theoretical References & Citation
|
| 267 |
-
|
| 268 |
-
LuminaMoon's algorithms are built upon published mathematical principles, including Maligranda's triangle inequality envelopes, rational neural networks, and decoupled optimization mechanics.
|
| 269 |
-
|
| 270 |
-
For the full component mapping, literature comparisons, and BibTeX entries, see [`references/README.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) and [`references/bibliography.bib`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/bibliography.bib).
|
| 271 |
-
|
| 272 |
-
To cite this repository in academic work:
|
| 273 |
-
|
| 274 |
-
```bibtex
|
| 275 |
-
@misc{loomy2026,
|
| 276 |
-
author = {Silver Moon and {Lumina Moon contributors}},
|
| 277 |
-
title = {Loomy: Low-Precision Numerical Optimization, Rational Neural Architecture, and Data Processing Repository},
|
| 278 |
-
year = {2026},
|
| 279 |
-
publisher = {GitHub},
|
| 280 |
-
howpublished = {\url{https://github.com/cloverx-id/luminamoon}},
|
| 281 |
-
doi = {10.5281/zenodo.23973326},
|
| 282 |
-
note = {Loomy repository}
|
| 283 |
-
}
|
| 284 |
-
```
|
| 285 |
-
|
| 286 |
-
## Authors & Maintenance
|
| 287 |
-
|
| 288 |
-
**Loomy (`luminamoon`)** is developed and maintained by:
|
| 289 |
-
|
| 290 |
-
- **Silver Moon ([@cloverxion](https://github.com/cloverxion))**
|
| 291 |
-
- **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**
|
| 292 |
-
|
| 293 |
-
## License
|
| 294 |
-
|
| 295 |
-
Licensed under the Apache License, Version 2.0. See [LICENSE](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/LICENSE) for details.
|
| 296 |
-
|
| 297 |
-
---
|
| 298 |
-
|
| 299 |
-
## LSO-Eval: Verification & Pretraining Benchmarks (`Qwen3.5-4B-Base`)
|
| 300 |
-
|
| 301 |
-
Summary of verification checks and pretraining benchmarks evaluated on **LuminaV (`v1.5.3`)** using the **LSO-Eval** suite, covering invariant checks (Z3 SMT solver and precision tests), 2D test functions, and pretraining runs on `Qwen3.5-4B-Base` in `bfloat16`:
|
| 302 |
-
|
| 303 |
-
> **Benchmark Note (`LuminaV v1.5.3`)**: Results below were evaluated on `LuminaV v1.5.3` under `torch.bfloat16` (`centered_defect=False`). In the evaluation loader, records were sampled by joint `(source, domain)` pairs; all candidate and baseline optimizers shared the same deterministic stream schedule.
|
| 304 |
-
|
| 305 |
-
### Stage IV-B: Context 2048 (20,000 Steps)
|
| 306 |
-
|
| 307 |
-
<div align="center">
|
| 308 |
-
<img src="https://huggingface.co/cloverx-id/LuminaV-Optimizer/resolve/main/eval/artifacts/v1.5.3/stage4b_lso_eval_results.png" alt="LSO-Eval Stage IV-B Qwen3.5-4B (ctx=2048)" width="100%" />
|
| 309 |
-
</div>
|
| 310 |
-
|
| 311 |
-
| Optimizer Configuration | Class | Final EMA | Val PPL | ΔPPL | VRAM<sub>peak</sub> | Max R<sub>t,l</sub> | Step / Opt Time |
|
| 312 |
-
| :--- | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 313 |
-
| **`LuminaV_Buffer2_Dual` (`8e-4`)** | Class I (Dual) | **`4.5549`** | **`89.14`** | **`-43.43`** | `43.89 GB` | `0.0189` | `297.1 / 48.31 ms` |
|
| 314 |
-
| **`LuminaV_Buffer1_TDCLM_3e4` (`3e-4`)** | Class II (Single) | `4.9173` | `165.13` | `+32.56` | **`35.97 GB`** | `0.0087` | **`283.5 / 36.99 ms`** |
|
| 315 |
-
| `AdamW` (Native BF16, `3e-4`) | Baseline | `4.7621` | `132.57` | `+0.00` | `43.89 GB` | `0.0037` | `288.0 / 41.48 ms` |
|
| 316 |
-
| `AdamW_FP32_Master` (`3e-4`) | Master Ref | `4.8116` | `124.43` | `-8.14` | `80.11 GB` | `0.0045` | `370.1 / 123.33 ms` |
|
| 317 |
-
|
| 318 |
-
### Stage IV-A: Context 512 (4,800 Steps, Grad Accum 8x)
|
| 319 |
-
|
| 320 |
-
<div align="center">
|
| 321 |
-
<img src="https://huggingface.co/cloverx-id/LuminaV-Optimizer/resolve/main/eval/artifacts/v1.5.3/stage4a_lso_eval_results.png" alt="LSO-Eval Stage IV-A Qwen3.5-4B (ctx=512)" width="100%" />
|
| 322 |
-
</div>
|
| 323 |
-
|
| 324 |
-
| Optimizer Configuration | Class | Final EMA | Val PPL | ΔPPL | VRAM<sub>peak</sub> | Max R<sub>t,l</sub> | Step / Opt Time |
|
| 325 |
-
| :--- | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 326 |
-
| **`LuminaV_Buffer2_Dual` (`8e-4`)** | Class I (Dual) | **`4.7621`** | **`141.96`** | **`-39.21`** | `36.75 GB` | `0.0047` | `841.6 / 48.37 ms` |
|
| 327 |
-
| **`LuminaV_Buffer1_TDCLM_3e4` (`3e-4`)** | Class II (Single) | `5.2569` | `195.58` | `+14.41` | **`28.81 GB`** | `0.0120` | **`829.4 / 36.77 ms`** |
|
| 328 |
-
| **`LuminaV_Buffer1_TDCLM` (`8e-4`)** | Class II (Single) | `5.2953` | `221.07` | `+39.90` | **`28.81 GB`** | `0.0191` | **`830.7 / 36.46 ms`** |
|
| 329 |
-
| `AdamW` (Native BF16, `3e-4`) | Baseline | `5.4497` | `181.17` | `+0.00` | `36.75 GB` | `0.0034` | `835.4 / 41.58 ms` |
|
| 330 |
-
| `AdamW_FP32_Master` (`3e-4`) | Master Ref | `5.0234` | `153.78` | `-27.39` | `75.36 GB` | `0.0040` | `919.5 / 123.35 ms` |
|
| 331 |
-
|
| 332 |
-
- **[LSO-Eval Formal Specification Manuscript (`eval/LSO_Eval.pdf`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/LSO_Eval.pdf)**
|
| 333 |
-
- **[Full LSO-Eval Verification Summary & Suite Guide (`eval/README.md`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md)**
|
| 334 |
-
- **[Interactive Reproduction Notebook in Google Colab](https://colab.research.google.com/drive/1UepDza5Wm3KRNooCA2Bn69GIeKB21a4a?usp=sharing)**
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
tags:
|
| 7 |
+
- pytorch
|
| 8 |
+
- optimizer
|
| 9 |
+
- custom-optimizer
|
| 10 |
+
- luminamoon
|
| 11 |
+
- luminav
|
| 12 |
+
- clm
|
| 13 |
+
- td-clm
|
| 14 |
+
- lso-eval
|
| 15 |
+
- rust
|
| 16 |
+
- simd
|
| 17 |
+
- avx512
|
| 18 |
+
- vulkan
|
| 19 |
+
- triton
|
| 20 |
+
- memory-efficient
|
| 21 |
+
- stochastic-rounding
|
| 22 |
+
- low-vram
|
| 23 |
+
- bf16
|
| 24 |
+
- fp16
|
| 25 |
+
- cuda
|
| 26 |
+
- rocm
|
| 27 |
+
- xpu
|
| 28 |
+
- llm
|
| 29 |
+
- transformer
|
| 30 |
+
- deep-learning
|
| 31 |
+
- research
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
# Loomy
|
| 35 |
+
|
| 36 |
+
<p align="left">
|
| 37 |
+
<a href="https://doi.org/10.5281/zenodo.23973326"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.23973326.svg" alt="DOI"></a>
|
| 38 |
+
<a href="https://pypi.org/project/luminamoon/"><img src="https://img.shields.io/pypi/v/luminamoon.svg?color=4f46e5" alt="PyPI version"></a>
|
| 39 |
+
<a href="https://github.com/cloverx-id/luminamoon/actions/workflows/ci.yml"><img src="https://github.com/cloverx-id/luminamoon/actions/workflows/ci.yml/badge.svg" alt="CI Status"></a>
|
| 40 |
+
<a href="eval/README.md"><img src="https://img.shields.io/badge/LSO--Eval-Verified-10b981" alt="LSO-Eval"></a>
|
| 41 |
+
<a href="eval/LSO_Eval.pdf"><img src="https://img.shields.io/badge/Eval_Docs-PDF-b31b1b" alt="Eval Docs"></a>
|
| 42 |
+
<a href="https://cloverx-id.github.io/luminamoon/"><img src="https://img.shields.io/badge/docs-GitHub_Pages-06b6d4" alt="Documentation"></a>
|
| 43 |
+
<a href="https://pypi.org/project/luminamoon/"><img src="https://img.shields.io/badge/python-3.10%20%E2%86%92%203.15t%20(free--threaded)-10b981" alt="Python Versions"></a>
|
| 44 |
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
|
| 45 |
+
</p>
|
| 46 |
+
|
| 47 |
+
**Loomy (`luminamoon`)** provides low-precision numerical optimization, rational neural network layers, and dataset text preprocessing utilities developed by **Silver Moon ([@cloverxion](https://github.com/cloverxion))** and **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**.
|
| 48 |
+
|
| 49 |
+
Built with a modular Rust and Python architecture, `luminamoon` provides components for CPU/GPU parameter updates and text dataset preprocessing:
|
| 50 |
+
|
| 51 |
+
> **LSO-Eval Evaluation & Verification Notebook**: [**Open in Google Colab**](https://colab.research.google.com/drive/1UepDza5Wm3KRNooCA2Bn69GIeKB21a4a?usp=sharing) — *Stage II–IV benchmarks executed on `LuminaV v1.5.3` under native `torch.bfloat16` (`centered_defect=False`).*
|
| 52 |
+
|
| 53 |
+
## Projects & Components
|
| 54 |
+
|
| 55 |
+
| Project / Component | Path | Status | Description | Documentation |
|
| 56 |
+
| :--- | :--- | :--- | :--- | :--- |
|
| 57 |
+
| **LuminaV Optimizer** | [`optimizer/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/optimizer/README.md) | `Active` (Math `v1.6.0`) | LuminaV v1.6.0 optimizer implementation with CPU SIMD (AVX-512/AVX2), Vulkan UMA, Triton AOT/JIT kernels, Triangle Family (`buffer="T1"`, `"T2"`), optional contiguous bucket fusion, and Triton JIT pre-warmup. | [Optimizer Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/optimizer/README.md) |
|
| 58 |
+
| **LuminaMoon Data Engine** | [`data/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md) | `Active` (`v1.1.0`) | Modular text preprocessing library: AVX-512/AVX2/NEON SIMD text normalization, dual-layout StringTensor (CPU StringView & GPU contiguous buffers), Shannon entropy filtering, MinHash/XXH3-64 deduplication, PII masking, and 128B Ping-Pong checkpoint recovery. | [Data Engine Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md) |
|
| 59 |
+
| **LSO-Eval** | [`eval/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md) | `Active` | Algorithm-agnostic optimizer evaluation & verification: Z3 SMT solver checks, 2D trajectory benchmarks, numerical parity tests, and Two-Class 4B LLM & Vision evaluation. | [LSO-Eval Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md) |
|
| 60 |
+
| **Changelogs** | [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md) | `Active` | Version histories separating Software Package releases (`v1.0.0`–`v1.1.0`) and LuminaV Algorithm Math versions (`v1.0.0`–`v1.6.0`). | [Changelogs](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md) |
|
| 61 |
+
| **Documentation** | [`docs/`](https://cloverx-id.github.io/luminamoon/) | `Active` | Documentation index, developer guides (`docs/optimize/`, `docs/dataloader/`), and dependency version notes. | [Docs Index](https://cloverx-id.github.io/luminamoon/) |
|
| 62 |
+
| **Ra-Family Architecture** | [`python/luminamoon/nn/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/tree/main/python/luminamoon/nn) | `Active` (`v1.1.0`) | Rational, tanh-free & square-root-free neural components: RaNorm, RaBatchNorm2d (with Conv2d weight folding), RaTanh, RaGELU, PosRaGLU (bounded gating in `(0, 1)`), and RaAttention with Cayley-Padé rational softmax. Triton GPU and Rust AVX2/AVX-512 SIMD acceleration. | [Ra-Family Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/nn/README.md) |
|
| 63 |
+
| **Python Package** | [`python/luminamoon/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/python/luminamoon/__init__.py) | `Active` (`v1.1.0`) | Unified free-threaded (`abi3t`) Python namespace exposing LuminaV Optimizer, DataStreamLoader, Ra-Family neural architecture, and PyTorch/NumPy/HuggingFace integration. | [API Reference](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/03_configuration_api.md) |
|
| 64 |
+
| **Theoretical References** | [`references/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) | `Active` | Mathematical formulas, component mapping, literature comparisons, and BibTeX citations for LuminaV, Ra-Family, and Data Engine. | [References Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) |
|
| 65 |
+
|
| 66 |
+
## Quickstart
|
| 67 |
+
|
| 68 |
+
### Installation
|
| 69 |
+
|
| 70 |
+
Install the compiled package wheel from PyPI:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
pip install luminamoon
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Or build from source using Maturin and the Rust 2024 toolchain:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
git clone https://github.com/cloverx-id/luminamoon.git
|
| 80 |
+
cd luminamoon
|
| 81 |
+
pip install maturin
|
| 82 |
+
maturin develop --release
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Basic Usage (LuminaV Optimizer)
|
| 86 |
+
|
| 87 |
+
```python
|
| 88 |
+
import numpy as np
|
| 89 |
+
from luminamoon import LuminaV
|
| 90 |
+
|
| 91 |
+
# Initialize parameters
|
| 92 |
+
weights = np.random.randn(64, 10).astype(np.float32)
|
| 93 |
+
biases = np.zeros(10, dtype=np.float32)
|
| 94 |
+
|
| 95 |
+
# Instantiate LuminaV optimizer (uses default hyperparameters)
|
| 96 |
+
optimizer = LuminaV([weights, biases], lr=8e-4, weight_decay=0.08, buffer=2)
|
| 97 |
+
|
| 98 |
+
# Execute optimization step with explicit gradients
|
| 99 |
+
grad_w = np.random.randn(64, 10).astype(np.float32)
|
| 100 |
+
grad_b = np.random.randn(10, dtype=np.float32)
|
| 101 |
+
optimizer.step(grads=[grad_w, grad_b])
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
For PyTorch deep learning workflows, see the [PyTorch Integration Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/01_getting_started.md#quickstart-pytorch-integration).
|
| 105 |
+
|
| 106 |
+
### LuminaV Hyperparameter Quick Reference
|
| 107 |
+
|
| 108 |
+
All default values below are exposed in IDE autocompletion and `LuminaV.__init__` docstrings:
|
| 109 |
+
|
| 110 |
+
| Parameter | Type | Default | Description |
|
| 111 |
+
| :--- | :--- | :--- | :--- |
|
| 112 |
+
| `lr` | `float` | `8e-4` (`0.0008`) | Learning rate (`η`). |
|
| 113 |
+
| `betas` | `Tuple[float, float]` | `(0.9, 0.999)` | Exponential moving average factors `(β₁, β₂)` for first-moment momentum and second-moment variance. |
|
| 114 |
+
| `eps` | `float` | `1e-8` | Numerical stability constant (`ε`) in the adaptive denominator. |
|
| 115 |
+
| `weight_decay` | `float` | `0.08` (`8e-2`) | Decoupled weight decay coefficient. |
|
| 116 |
+
| `tau` | `float` | `0.8` | Denominator temperature scaling factor (`τ`) for 2-buffer variance damping. |
|
| 117 |
+
| `lam` | `float` | `0.3` | Triangle-defect penalty weight (`λ`) on `\|d_t\| = \|g_t - m_{t-1}\|` in 1-Buffer TD-CLM (`buffer=1`) and Triangle Family (`buffer="T1"`, `"T2"`). |
|
| 118 |
+
| `cautious` | `bool` | `True` | Enables sign-aligned cautious update masking and lagged EMA scaling. |
|
| 119 |
+
| `cautious_clamp_min` | `float` | `0.5` | Lower bound in `(0.0, 1.0]` for cautious alignment scaling. |
|
| 120 |
+
| `buffer` | `int \| str` | `2` | State mode: `2` (`"dual"`, `"clm"`) for 2-Buffer CLM, `1` (`"single"`, `"td-clm"`, `"tdclm"`) for 1-Buffer TD-CLM, `"T2"` for 2-Buffer Triangle Family (`tanh`-free), or `"T1"` for 1-Buffer Triangle Family (`tanh`-free). |
|
| 121 |
+
| `stochastic_rounding` | `bool` | `True` | Applies stateless 32-bit MurmurHash3 stochastic rounding when writing to `bfloat16` / `float16` weights. |
|
| 122 |
+
| `bound` | `bool` | `True` | Enables adaptive update bounding. |
|
| 123 |
+
| `bound_type` | `str \| int` | `"coordinate"` | Clipping mode: `"coordinate"` (element-wise clip) or `"radial"` (global norm ratio). |
|
| 124 |
+
| `bound_ratio` | `float` | `0.05` | Maximum update magnitude relative to parameter scale. |
|
| 125 |
+
| `master_weights` | `str \| bool` | `"none"` | Mixed-precision master weight mode for `bfloat16`/`float16`: `"none"` (`False`), `"semi"`, or `"full"` (`True`). |
|
| 126 |
+
| `execution` | `str \| int` | `"auto"` | Hardware backend: `"auto"`, `"triton"`, `"vulkan"`, `"simd"`, or `"scalar"`. |
|
| 127 |
+
| `fused_single_pass` | `bool` | `True` | Fuses parameter update and scalar reduction statistics into a single kernel pass. |
|
| 128 |
+
| `fuse_tensors` | `bool` | `False` | Coalesces parameters into contiguous 1D memory buffers to reduce kernel launch latency on fragmented architectures (e.g. Vision models / ViT). Not recommended for large LLMs or distributed FSDP training. |
|
| 129 |
+
| `bucket_cap_mb` | `float` | `500.0` | Maximum memory capacity per contiguous bucket chunk in megabytes when `fuse_tensors=True`. Partitions large models into bounded chunks. Emits a `UserWarning` if explicitly specified when `fuse_tensors=False`. |
|
| 130 |
+
| `prewarm_jit` | `bool` | `True` | Pre-compiles Triton GPU kernels on 64-element dummy buffers (`lr=0.0`) during initialization to avoid first-step JIT compilation delay. |
|
| 131 |
+
| `ema_decay` | `str \| float` | `"auto"` | EMA decay for lagged cautious/radial scalars (`"auto"` resolves to `0.80` for `buffer=2`/`"T2"` and `0.95` for `buffer=1`/`"T1"`). |
|
| 132 |
+
| `fsdp_sync` | `bool` | `False` | Synchronizes global reduction scratch buffers across `torch.distributed` FSDP shards. |
|
| 133 |
+
| `seed` | `int` | `1337` | Deterministic PRNG seed for stateless stochastic rounding. |
|
| 134 |
+
| `version` | `Optional[str]` | `"1.6.0"` | Selects the active LuminaV mathematical specification version. |
|
| 135 |
+
|
| 136 |
+
### Software vs. Algorithm Versioning (`v1.1.0` / Math `v1.6.0`)
|
| 137 |
+
|
| 138 |
+
`luminamoon` maintains two distinct, decoupled version lineages (documented in [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md)):
|
| 139 |
+
|
| 140 |
+
1. **Software Package Version (`v1.1.0`)**:
|
| 141 |
+
- Tracks the monorepo distribution, CPython FFI bindings, Rust SIMD crates, neural architecture (`luminamoon.nn`), and Data Engine utilities on PyPI (`pip install luminamoon`).
|
| 142 |
+
- To reproduce or execute older software package releases in Python, install the desired release directly via standard package management (`pip install luminamoon==1.0.9`) or check out official git release tags.
|
| 143 |
+
- See [`changelogs/SOFTWARE_CHANGELOG.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/SOFTWARE_CHANGELOG.md).
|
| 144 |
+
|
| 145 |
+
2. **LuminaV Algorithm Math Version (`v1.6.0`)**:
|
| 146 |
+
- Tracks the mathematical optimization formulas, convergence envelopes, and hardware kernels of the **LuminaV Optimizer** independently from pip package updates.
|
| 147 |
+
- Four mathematical versions are supported: `1.6.0` (default with Triangle Family `T1`/`T2` and Padé rational bounds), `1.5.5` (bucket fusion and Triton pre-warmup), `1.5.4` (Bitwise-BF16 state promotion), and `1.5.3` (single-buffer TD-CLM).
|
| 148 |
+
- Features **strict capability enforcement**: locking to an older mathematical version (such as `version="1.5.4"`) clearly rejects features introduced in later versions (such as `buffer="T2"` or `fuse_tensors=True`), helping ensure exact reproducibility for benchmarks and publications.
|
| 149 |
+
- See [`changelogs/LUMINAV_ALGORITHM_CHANGELOG.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/CHANGELOG.md).
|
| 150 |
+
|
| 151 |
+
```python
|
| 152 |
+
import luminamoon
|
| 153 |
+
from luminamoon import LuminaV
|
| 154 |
+
import luminamoon.data as lmd
|
| 155 |
+
|
| 156 |
+
# 1. Inspect Software Package Version vs. Algorithm Math Version
|
| 157 |
+
print(luminamoon.__version__) # "1.1.0" (Software package release)
|
| 158 |
+
print(lmd.get_version()) # "1.1.0" (Data Engine package module)
|
| 159 |
+
print(LuminaV.ALGORITHM_MATH) # "1.6.0" (Algorithm mathematical version)
|
| 160 |
+
print(LuminaV.ALGORITHM_SPEC) # "1.6.0" (Backward-compatible alias)
|
| 161 |
+
print(LuminaV.getVersion()) # "1.6.0"
|
| 162 |
+
|
| 163 |
+
# 2. Inspect capability matrix of any supported specification
|
| 164 |
+
caps = LuminaV.get_capabilities("1.5.5")
|
| 165 |
+
print(caps["allowed_buffers"]) # (1, 2)
|
| 166 |
+
print(caps["supports_triangle_family"]) # False (introduced in 1.6.0)
|
| 167 |
+
|
| 168 |
+
# 3. Lock algorithm version for scientific checkpoint reproducibility
|
| 169 |
+
opt = LuminaV([weights, biases], lr=8e-4, version="1.5.5")
|
| 170 |
+
opt.setVersion("1.5.5") # Instance-level version lock
|
| 171 |
+
LuminaV.setVersion("1.5.5") # Class-level default specification
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
### Basic Usage (LuminaMoon Data Engine: Fused Text Preprocessing Pipeline)
|
| 175 |
+
|
| 176 |
+
```python
|
| 177 |
+
from luminamoon.data import FusedPipeline, StringTensor, create_resumable_collate, ResumablePipeline
|
| 178 |
+
|
| 179 |
+
# 1. Fused single-pass text preprocessing in Rust
|
| 180 |
+
pipeline = FusedPipeline(
|
| 181 |
+
trim_whitespace=True,
|
| 182 |
+
normalize_newlines=True, # AVX-512 / AVX2 / NEON SIMD
|
| 183 |
+
min_chars=10,
|
| 184 |
+
min_entropy=2.0,
|
| 185 |
+
)
|
| 186 |
+
string_tensor = pipeline([" sample 1\r\n", "sample 2 with proper prose text"])
|
| 187 |
+
|
| 188 |
+
# 2. Export to Apache Arrow, Polars, and PyTorch buffers
|
| 189 |
+
polars_series = string_tensor.to_polars()
|
| 190 |
+
arrow_array = string_tensor.to_arrow()
|
| 191 |
+
offsets_tensor, payload_tensor = string_tensor.to_torch_gpu(device="cuda")
|
| 192 |
+
|
| 193 |
+
# 3. Stream dataset with background prefetching & 128B Ping-Pong checkpointing
|
| 194 |
+
pipe = ResumablePipeline(
|
| 195 |
+
source=raw_data,
|
| 196 |
+
fused_pipeline=pipeline,
|
| 197 |
+
batch_size=64,
|
| 198 |
+
prefetch_batches=4,
|
| 199 |
+
checkpoint_path="./cache/checkpoint.meta",
|
| 200 |
+
)
|
| 201 |
+
for batch in pipe:
|
| 202 |
+
# Training step...
|
| 203 |
+
pipe.commit() # Saves Dual-Slot A/B checkpoint and deduplication WAL
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
For detailed architecture, Shannon entropy filtering, MinHash deduplication, and PyTorch `collate_fn` adapters, see the [Data Engine Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md).
|
| 207 |
+
|
| 208 |
+
### Basic Usage (Ra-Family Rational Neural Architecture)
|
| 209 |
+
|
| 210 |
+
```python
|
| 211 |
+
import torch
|
| 212 |
+
from luminamoon.nn import RaNorm, PosRaGLU, RaAttention
|
| 213 |
+
|
| 214 |
+
# 1. Square-root-free token normalization (L1-mean)
|
| 215 |
+
norm = RaNorm(512)
|
| 216 |
+
x = torch.randn(2, 64, 512)
|
| 217 |
+
x_norm = norm(x)
|
| 218 |
+
|
| 219 |
+
# 2. Rational gated linear unit with strictly positive bounds in (0, 1)
|
| 220 |
+
mlp = PosRaGLU(512, hidden_features=2048)
|
| 221 |
+
x_ffn = mlp(x_norm)
|
| 222 |
+
|
| 223 |
+
# 3. Cayley-Padé rational multi-head attention (SFU-free softmax)
|
| 224 |
+
attn = RaAttention(dim=512, num_heads=8, qk_norm=True, causal=True)
|
| 225 |
+
out = attn(x_norm)
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
For complete architecture, parameter folding for Conv2d, and standalone NumPy usage, see the [Ra-Family Guide](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/nn/README.md).
|
| 229 |
+
|
| 230 |
+
## Verification & CI/CD
|
| 231 |
+
|
| 232 |
+
All pull requests and releases are tested across Linux, Windows, and macOS:
|
| 233 |
+
|
| 234 |
+
```bash
|
| 235 |
+
# Run static analysis across the Rust workspace
|
| 236 |
+
cargo clippy --workspace --all-targets -- -D warnings
|
| 237 |
+
|
| 238 |
+
# Run Rust workspace unit and parity tests (Optimizer + Data Engine)
|
| 239 |
+
cargo test --workspace
|
| 240 |
+
|
| 241 |
+
# Run Python static type check
|
| 242 |
+
npx pyright python/luminamoon
|
| 243 |
+
|
| 244 |
+
# Run standalone NumPy, PyTorch, and Data Engine tests
|
| 245 |
+
python optimizer/tests/test_standalone_numpy.py
|
| 246 |
+
python optimizer/tests/test_end_to_end_training.py
|
| 247 |
+
python -B tests/test_data_modular.py
|
| 248 |
+
python -B tests/test_gpu_vs_cpu_strings.py
|
| 249 |
+
|
| 250 |
+
# Run LSO-Eval verification
|
| 251 |
+
python -m eval
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
## Developer Resources
|
| 255 |
+
|
| 256 |
+
For contributors working on **Loomy (`luminamoon`)**:
|
| 257 |
+
|
| 258 |
+
- **Changelogs (Software & Algorithm)**: [`changelogs/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/changelogs/README.md)
|
| 259 |
+
- **Optimizer Modular Documentation**: [`docs/optimize/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/optimize/README.md)
|
| 260 |
+
- **Data Engine Modular Documentation**: [`data/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md)
|
| 261 |
+
- **Optimizer Library Notes**: [`docs/library_audit/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/library_audit/README.md)
|
| 262 |
+
- **Data Standalone Library Notes**: [`data/docs/library_audit/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/docs/library_audit/README.md)
|
| 263 |
+
- **Data Engine Architecture & User Guide**: [`data/README.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/data/README.md)
|
| 264 |
+
- **User Documentation**: [`docs/en/`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/docs/en/README.md)
|
| 265 |
+
|
| 266 |
+
## Theoretical References & Citation
|
| 267 |
+
|
| 268 |
+
LuminaMoon's algorithms are built upon published mathematical principles, including Maligranda's triangle inequality envelopes, rational neural networks, and decoupled optimization mechanics.
|
| 269 |
+
|
| 270 |
+
For the full component mapping, literature comparisons, and BibTeX entries, see [`references/README.md`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/README.md) and [`references/bibliography.bib`](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/references/bibliography.bib).
|
| 271 |
+
|
| 272 |
+
To cite this repository in academic work:
|
| 273 |
+
|
| 274 |
+
```bibtex
|
| 275 |
+
@misc{loomy2026,
|
| 276 |
+
author = {Silver Moon and {Lumina Moon contributors}},
|
| 277 |
+
title = {Loomy: Low-Precision Numerical Optimization, Rational Neural Architecture, and Data Processing Repository},
|
| 278 |
+
year = {2026},
|
| 279 |
+
publisher = {GitHub},
|
| 280 |
+
howpublished = {\url{https://github.com/cloverx-id/luminamoon}},
|
| 281 |
+
doi = {10.5281/zenodo.23973326},
|
| 282 |
+
note = {Loomy repository}
|
| 283 |
+
}
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
## Authors & Maintenance
|
| 287 |
+
|
| 288 |
+
**Loomy (`luminamoon`)** is developed and maintained by:
|
| 289 |
+
|
| 290 |
+
- **Silver Moon ([@cloverxion](https://github.com/cloverxion))**
|
| 291 |
+
- **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**
|
| 292 |
+
|
| 293 |
+
## License
|
| 294 |
+
|
| 295 |
+
Licensed under the Apache License, Version 2.0. See [LICENSE](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/LICENSE) for details.
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
## LSO-Eval: Verification & Pretraining Benchmarks (`Qwen3.5-4B-Base`)
|
| 300 |
+
|
| 301 |
+
Summary of verification checks and pretraining benchmarks evaluated on **LuminaV (`v1.5.3`)** using the **LSO-Eval** suite, covering invariant checks (Z3 SMT solver and precision tests), 2D test functions, and pretraining runs on `Qwen3.5-4B-Base` in `bfloat16`:
|
| 302 |
+
|
| 303 |
+
> **Benchmark Note (`LuminaV v1.5.3`)**: Results below were evaluated on `LuminaV v1.5.3` under `torch.bfloat16` (`centered_defect=False`). In the evaluation loader, records were sampled by joint `(source, domain)` pairs; all candidate and baseline optimizers shared the same deterministic stream schedule.
|
| 304 |
+
|
| 305 |
+
### Stage IV-B: Context 2048 (20,000 Steps)
|
| 306 |
+
|
| 307 |
+
<div align="center">
|
| 308 |
+
<img src="https://huggingface.co/cloverx-id/LuminaV-Optimizer/resolve/main/eval/artifacts/v1.5.3/stage4b_lso_eval_results.png" alt="LSO-Eval Stage IV-B Qwen3.5-4B (ctx=2048)" width="100%" />
|
| 309 |
+
</div>
|
| 310 |
+
|
| 311 |
+
| Optimizer Configuration | Class | Final EMA | Val PPL | ΔPPL | VRAM<sub>peak</sub> | Max R<sub>t,l</sub> | Step / Opt Time |
|
| 312 |
+
| :--- | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 313 |
+
| **`LuminaV_Buffer2_Dual` (`8e-4`)** | Class I (Dual) | **`4.5549`** | **`89.14`** | **`-43.43`** | `43.89 GB` | `0.0189` | `297.1 / 48.31 ms` |
|
| 314 |
+
| **`LuminaV_Buffer1_TDCLM_3e4` (`3e-4`)** | Class II (Single) | `4.9173` | `165.13` | `+32.56` | **`35.97 GB`** | `0.0087` | **`283.5 / 36.99 ms`** |
|
| 315 |
+
| `AdamW` (Native BF16, `3e-4`) | Baseline | `4.7621` | `132.57` | `+0.00` | `43.89 GB` | `0.0037` | `288.0 / 41.48 ms` |
|
| 316 |
+
| `AdamW_FP32_Master` (`3e-4`) | Master Ref | `4.8116` | `124.43` | `-8.14` | `80.11 GB` | `0.0045` | `370.1 / 123.33 ms` |
|
| 317 |
+
|
| 318 |
+
### Stage IV-A: Context 512 (4,800 Steps, Grad Accum 8x)
|
| 319 |
+
|
| 320 |
+
<div align="center">
|
| 321 |
+
<img src="https://huggingface.co/cloverx-id/LuminaV-Optimizer/resolve/main/eval/artifacts/v1.5.3/stage4a_lso_eval_results.png" alt="LSO-Eval Stage IV-A Qwen3.5-4B (ctx=512)" width="100%" />
|
| 322 |
+
</div>
|
| 323 |
+
|
| 324 |
+
| Optimizer Configuration | Class | Final EMA | Val PPL | ΔPPL | VRAM<sub>peak</sub> | Max R<sub>t,l</sub> | Step / Opt Time |
|
| 325 |
+
| :--- | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
| 326 |
+
| **`LuminaV_Buffer2_Dual` (`8e-4`)** | Class I (Dual) | **`4.7621`** | **`141.96`** | **`-39.21`** | `36.75 GB` | `0.0047` | `841.6 / 48.37 ms` |
|
| 327 |
+
| **`LuminaV_Buffer1_TDCLM_3e4` (`3e-4`)** | Class II (Single) | `5.2569` | `195.58` | `+14.41` | **`28.81 GB`** | `0.0120` | **`829.4 / 36.77 ms`** |
|
| 328 |
+
| **`LuminaV_Buffer1_TDCLM` (`8e-4`)** | Class II (Single) | `5.2953` | `221.07` | `+39.90` | **`28.81 GB`** | `0.0191` | **`830.7 / 36.46 ms`** |
|
| 329 |
+
| `AdamW` (Native BF16, `3e-4`) | Baseline | `5.4497` | `181.17` | `+0.00` | `36.75 GB` | `0.0034` | `835.4 / 41.58 ms` |
|
| 330 |
+
| `AdamW_FP32_Master` (`3e-4`) | Master Ref | `5.0234` | `153.78` | `-27.39` | `75.36 GB` | `0.0040` | `919.5 / 123.35 ms` |
|
| 331 |
+
|
| 332 |
+
- **[LSO-Eval Formal Specification Manuscript (`eval/LSO_Eval.pdf`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/LSO_Eval.pdf)**
|
| 333 |
+
- **[Full LSO-Eval Verification Summary & Suite Guide (`eval/README.md`)](https://huggingface.co/cloverx-id/LuminaV-Optimizer/blob/main/eval/README.md)**
|
| 334 |
+
- **[Interactive Reproduction Notebook in Google Colab](https://colab.research.google.com/drive/1UepDza5Wm3KRNooCA2Bn69GIeKB21a4a?usp=sharing)**
|
SECURITY.md
CHANGED
|
@@ -1,50 +1,50 @@
|
|
| 1 |
-
# Security Policy
|
| 2 |
-
|
| 3 |
-
## Supported Versions
|
| 4 |
-
|
| 5 |
-
Security updates and bug fixes are maintained for the following versions of **LuminaMoon** (`luminamoon`) and the **LuminaV Optimizer** (`luminav`):
|
| 6 |
-
|
| 7 |
-
| Component / Package | Version | Security Support Status |
|
| 8 |
-
| :--- | :--- | :--- |
|
| 9 |
-
| **`luminamoon`** (Rust and Python Package) | `>= 1.1.0` | Supported (Active) |
|
| 10 |
-
| **`luminav`** (Algorithm Math) | `>= 1.6.0` | Supported (Active) |
|
| 11 |
-
| **`luminamoon`** (Maintenance Line) | `1.0.8` – `1.0.9` | Supported (Maintenance) |
|
| 12 |
-
| **`luminav`** (Maintenance Line) | `1.5.5` | Supported (Maintenance) |
|
| 13 |
-
| **`luminamoon`** | `< 1.0.8` | Unsupported (Upgrade recommended) |
|
| 14 |
-
| **`luminav`** | `< 1.5.5` | Unsupported (Upgrade recommended) |
|
| 15 |
-
|
| 16 |
-
---
|
| 17 |
-
|
| 18 |
-
## Reporting a Vulnerability
|
| 19 |
-
|
| 20 |
-
If you discover a security vulnerability, memory-safety issue in FFI/SIMD/Vulkan/Triton boundaries, or dependency flaw:
|
| 21 |
-
|
| 22 |
-
1. **Private GitHub Security Advisory (Preferred)**:
|
| 23 |
-
Submit a confidential report through [GitHub Private Vulnerability Reporting](https://github.com/cloverx-id/luminamoon/security/advisories/new).
|
| 24 |
-
2. **Direct Contact**:
|
| 25 |
-
Email **Silver Moon** (`@cloverxion`) directly at [`leafmoon.idn@gmail.com`](mailto:leafmoon.idn@gmail.com) with the subject prefix `[SECURITY][luminamoon]`.
|
| 26 |
-
|
| 27 |
-
### Response & Disclosure Timeline
|
| 28 |
-
- **Initial Acknowledgment**: Within 48 hours of report submission.
|
| 29 |
-
- **Triage & Verification**: Within 5 business days.
|
| 30 |
-
- **Fix & Disclosure**: Critical fixes are prioritized for release on GitHub, PyPI, and Hugging Face Hub alongside a published GitHub Security Advisory (GHSA / CVE).
|
| 31 |
-
|
| 32 |
-
Please do not open public GitHub issues for undisclosed security vulnerabilities.
|
| 33 |
-
|
| 34 |
-
---
|
| 35 |
-
|
| 36 |
-
## Automated Security Checks
|
| 37 |
-
|
| 38 |
-
Every commit and pull request in `cloverx-id/luminamoon` is checked across automated workflows (`.github/workflows/security.yml`):
|
| 39 |
-
|
| 40 |
-
1. **Static Analysis (GitHub CodeQL)**:
|
| 41 |
-
Automated dataflow analysis (`github/codeql-action@v4`) across Python modules and GitHub Actions workflows to catch injection, traversal, and unsafe deserialization issues.
|
| 42 |
-
2. **Dependency & Supply-Chain Scanning (`tools/security_audit.py` & `pip-audit`)**:
|
| 43 |
-
Locked Rust crates in `Cargo.lock` and Python dependencies in `pyproject.toml` are checked against the RustSec Advisory Database, Open Source Vulnerabilities (OSV), PyPA, and the National Vulnerability Database.
|
| 44 |
-
3. **Dependency Review Gate (`actions/dependency-review-action@v5`)**:
|
| 45 |
-
Blocks pull requests that introduce dependencies with known vulnerabilities.
|
| 46 |
-
4. **Secret Scanning & Push Protection**:
|
| 47 |
-
GitHub Secret Scanning and Push Protection are active to prevent accidental credential commits.
|
| 48 |
-
5. **Memory & FFI Checks**:
|
| 49 |
-
- Pointer alignment and contiguity verification across PyO3 (`abi3-py310` / `abi3t`) and NumPy/PyTorch buffers.
|
| 50 |
-
- Minimal dependency footprint: only actively required crates are locked in `Cargo.lock` (see [`docs/library_audit/`](docs/library_audit/README.md)).
|
|
|
|
| 1 |
+
# Security Policy
|
| 2 |
+
|
| 3 |
+
## Supported Versions
|
| 4 |
+
|
| 5 |
+
Security updates and bug fixes are maintained for the following versions of **LuminaMoon** (`luminamoon`) and the **LuminaV Optimizer** (`luminav`):
|
| 6 |
+
|
| 7 |
+
| Component / Package | Version | Security Support Status |
|
| 8 |
+
| :--- | :--- | :--- |
|
| 9 |
+
| **`luminamoon`** (Rust and Python Package) | `>= 1.1.0` | Supported (Active) |
|
| 10 |
+
| **`luminav`** (Algorithm Math) | `>= 1.6.0` | Supported (Active) |
|
| 11 |
+
| **`luminamoon`** (Maintenance Line) | `1.0.8` – `1.0.9` | Supported (Maintenance) |
|
| 12 |
+
| **`luminav`** (Maintenance Line) | `1.5.5` | Supported (Maintenance) |
|
| 13 |
+
| **`luminamoon`** | `< 1.0.8` | Unsupported (Upgrade recommended) |
|
| 14 |
+
| **`luminav`** | `< 1.5.5` | Unsupported (Upgrade recommended) |
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Reporting a Vulnerability
|
| 19 |
+
|
| 20 |
+
If you discover a security vulnerability, memory-safety issue in FFI/SIMD/Vulkan/Triton boundaries, or dependency flaw:
|
| 21 |
+
|
| 22 |
+
1. **Private GitHub Security Advisory (Preferred)**:
|
| 23 |
+
Submit a confidential report through [GitHub Private Vulnerability Reporting](https://github.com/cloverx-id/luminamoon/security/advisories/new).
|
| 24 |
+
2. **Direct Contact**:
|
| 25 |
+
Email **Silver Moon** (`@cloverxion`) directly at [`leafmoon.idn@gmail.com`](mailto:leafmoon.idn@gmail.com) with the subject prefix `[SECURITY][luminamoon]`.
|
| 26 |
+
|
| 27 |
+
### Response & Disclosure Timeline
|
| 28 |
+
- **Initial Acknowledgment**: Within 48 hours of report submission.
|
| 29 |
+
- **Triage & Verification**: Within 5 business days.
|
| 30 |
+
- **Fix & Disclosure**: Critical fixes are prioritized for release on GitHub, PyPI, and Hugging Face Hub alongside a published GitHub Security Advisory (GHSA / CVE).
|
| 31 |
+
|
| 32 |
+
Please do not open public GitHub issues for undisclosed security vulnerabilities.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Automated Security Checks
|
| 37 |
+
|
| 38 |
+
Every commit and pull request in `cloverx-id/luminamoon` is checked across automated workflows (`.github/workflows/security.yml`):
|
| 39 |
+
|
| 40 |
+
1. **Static Analysis (GitHub CodeQL)**:
|
| 41 |
+
Automated dataflow analysis (`github/codeql-action@v4`) across Python modules and GitHub Actions workflows to catch injection, traversal, and unsafe deserialization issues.
|
| 42 |
+
2. **Dependency & Supply-Chain Scanning (`tools/security_audit.py` & `pip-audit`)**:
|
| 43 |
+
Locked Rust crates in `Cargo.lock` and Python dependencies in `pyproject.toml` are checked against the RustSec Advisory Database, Open Source Vulnerabilities (OSV), PyPA, and the National Vulnerability Database.
|
| 44 |
+
3. **Dependency Review Gate (`actions/dependency-review-action@v5`)**:
|
| 45 |
+
Blocks pull requests that introduce dependencies with known vulnerabilities.
|
| 46 |
+
4. **Secret Scanning & Push Protection**:
|
| 47 |
+
GitHub Secret Scanning and Push Protection are active to prevent accidental credential commits.
|
| 48 |
+
5. **Memory & FFI Checks**:
|
| 49 |
+
- Pointer alignment and contiguity verification across PyO3 (`abi3-py310` / `abi3t`) and NumPy/PyTorch buffers.
|
| 50 |
+
- Minimal dependency footprint: only actively required crates are locked in `Cargo.lock` (see [`docs/library_audit/`](docs/library_audit/README.md)).
|
benchmarks/bench_data_processing.py
CHANGED
|
@@ -1,411 +1,411 @@
|
|
| 1 |
-
"""LuminaMoon Data Processing Engine Benchmark.
|
| 2 |
-
|
| 3 |
-
Compares Native Rust SIMD Accelerator (AVX-512 / AVX2 / Rayon) against
|
| 4 |
-
Pure Python Fallbacks across micro-benchmarks and end-to-end pipelines.
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
from __future__ import annotations
|
| 8 |
-
|
| 9 |
-
import json
|
| 10 |
-
import math
|
| 11 |
-
import os
|
| 12 |
-
import random
|
| 13 |
-
import sys
|
| 14 |
-
import tempfile
|
| 15 |
-
import time
|
| 16 |
-
from dataclasses import asdict, dataclass
|
| 17 |
-
from pathlib import Path
|
| 18 |
-
from typing import Any, Callable, Dict, List, Tuple
|
| 19 |
-
|
| 20 |
-
# Ensure local python package is discoverable
|
| 21 |
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "python"))
|
| 22 |
-
|
| 23 |
-
from luminamoon.optimizer import _luminamoon_optimizer_rs as rs
|
| 24 |
-
from luminamoon.data import fallbacks as fb
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
@dataclass
|
| 28 |
-
class BenchmarkResult:
|
| 29 |
-
benchmark_name: str
|
| 30 |
-
workload_size: int
|
| 31 |
-
data_bytes: int
|
| 32 |
-
rust_duration_sec: float
|
| 33 |
-
rust_throughput_items_per_sec: float
|
| 34 |
-
rust_throughput_mb_per_sec: float
|
| 35 |
-
fallback_duration_sec: float
|
| 36 |
-
fallback_throughput_items_per_sec: float
|
| 37 |
-
fallback_throughput_mb_per_sec: float
|
| 38 |
-
speedup_ratio: float
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def generate_synthetic_dataset(num_records: int = 10000, seed: int = 42) -> Tuple[List[str], int]:
|
| 42 |
-
rng = random.Random(seed)
|
| 43 |
-
templates = [
|
| 44 |
-
" The quick brown fox jumps over the lazy dog.\r\n This is line 2 with carriage returns.\r\n",
|
| 45 |
-
"Deep learning optimization requires efficient gradient descent with momentum and adaptive learning rates.\n",
|
| 46 |
-
'{"text": "{\"key\": \"value\", \"nested\": [1, 2, 3]}", "meta": "test record"}\r\n',
|
| 47 |
-
"System security alert: detected unauthorized access attempt from 192.168.1.105 on port 443.\n",
|
| 48 |
-
"Contact user support at alice.walker@enterprise-domain.org or call +1-800-555-0199 for assistance.\r\n",
|
| 49 |
-
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", # high repetition
|
| 50 |
-
"Short text.\n",
|
| 51 |
-
"Mathematics: Pade [13/6] approximant ensures subnormal FP16 RNE preservation without overflow.\n",
|
| 52 |
-
"High symbol noise: #$%@!&*()!@#$%^&*()_+{}|:<>?~`!@#$%^&*()_+{}|:<>?~`\n",
|
| 53 |
-
"Large document block: " + ("lorem ipsum dolor sit amet " * 20) + "\r\n",
|
| 54 |
-
]
|
| 55 |
-
records = []
|
| 56 |
-
total_bytes = 0
|
| 57 |
-
for i in range(num_records):
|
| 58 |
-
base = templates[i % len(templates)]
|
| 59 |
-
noise = f" [doc_id={i}_{rng.randint(100, 999)}]"
|
| 60 |
-
rec = base + noise
|
| 61 |
-
records.append(rec)
|
| 62 |
-
total_bytes += len(rec.encode("utf-8"))
|
| 63 |
-
return records, total_bytes
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def run_timed_benchmark(
|
| 67 |
-
name: str,
|
| 68 |
-
rust_fn: Callable[[], Any],
|
| 69 |
-
fallback_fn: Callable[[], Any],
|
| 70 |
-
workload_size: int,
|
| 71 |
-
data_bytes: int,
|
| 72 |
-
warmup_runs: int = 2,
|
| 73 |
-
test_runs: int = 5,
|
| 74 |
-
) -> BenchmarkResult:
|
| 75 |
-
# Warmup Rust
|
| 76 |
-
for _ in range(warmup_runs):
|
| 77 |
-
rust_fn()
|
| 78 |
-
# Timed Rust
|
| 79 |
-
rust_times = []
|
| 80 |
-
for _ in range(test_runs):
|
| 81 |
-
t0 = time.perf_counter_ns()
|
| 82 |
-
rust_fn()
|
| 83 |
-
t1 = time.perf_counter_ns()
|
| 84 |
-
rust_times.append((t1 - t0) / 1e9)
|
| 85 |
-
rust_dur = min(rust_times) # best of N runs
|
| 86 |
-
|
| 87 |
-
# Warmup Fallback
|
| 88 |
-
for _ in range(warmup_runs):
|
| 89 |
-
fallback_fn()
|
| 90 |
-
# Timed Fallback
|
| 91 |
-
fallback_times = []
|
| 92 |
-
for _ in range(test_runs):
|
| 93 |
-
t0 = time.perf_counter_ns()
|
| 94 |
-
fallback_fn()
|
| 95 |
-
t1 = time.perf_counter_ns()
|
| 96 |
-
fallback_times.append((t1 - t0) / 1e9)
|
| 97 |
-
fallback_dur = min(fallback_times)
|
| 98 |
-
|
| 99 |
-
rust_items_per_sec = workload_size / max(rust_dur, 1e-9)
|
| 100 |
-
rust_mb_per_sec = (data_bytes / (1024 * 1024)) / max(rust_dur, 1e-9)
|
| 101 |
-
|
| 102 |
-
fallback_items_per_sec = workload_size / max(fallback_dur, 1e-9)
|
| 103 |
-
fallback_mb_per_sec = (data_bytes / (1024 * 1024)) / max(fallback_dur, 1e-9)
|
| 104 |
-
|
| 105 |
-
speedup = fallback_dur / max(rust_dur, 1e-9)
|
| 106 |
-
|
| 107 |
-
return BenchmarkResult(
|
| 108 |
-
benchmark_name=name,
|
| 109 |
-
workload_size=workload_size,
|
| 110 |
-
data_bytes=data_bytes,
|
| 111 |
-
rust_duration_sec=rust_dur,
|
| 112 |
-
rust_throughput_items_per_sec=rust_items_per_sec,
|
| 113 |
-
rust_throughput_mb_per_sec=rust_mb_per_sec,
|
| 114 |
-
fallback_duration_sec=fallback_dur,
|
| 115 |
-
fallback_throughput_items_per_sec=fallback_items_per_sec,
|
| 116 |
-
fallback_throughput_mb_per_sec=fallback_mb_per_sec,
|
| 117 |
-
speedup_ratio=speedup,
|
| 118 |
-
)
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
def benchmark_text_cleaner(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 122 |
-
def rust_call():
|
| 123 |
-
return rs.clean_batch_records(records, True, True, 10, 10000, False, None)
|
| 124 |
-
|
| 125 |
-
def fallback_call():
|
| 126 |
-
return fb.clean_batch_records(records, True, True, 10, 10000, False, None)
|
| 127 |
-
|
| 128 |
-
return run_timed_benchmark(
|
| 129 |
-
name="TextCleaner.clean_batch (CRLF + Trimming + Bounds)",
|
| 130 |
-
rust_fn=rust_call,
|
| 131 |
-
fallback_fn=fallback_call,
|
| 132 |
-
workload_size=len(records),
|
| 133 |
-
data_bytes=total_bytes,
|
| 134 |
-
)
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
def benchmark_quality_filter(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 138 |
-
rust_filter = rs.QualityFilter(
|
| 139 |
-
min_chars=10,
|
| 140 |
-
max_chars=20000,
|
| 141 |
-
min_words=3,
|
| 142 |
-
min_entropy=2.0,
|
| 143 |
-
max_repetition_ratio=0.3,
|
| 144 |
-
max_symbol_ratio=0.25,
|
| 145 |
-
)
|
| 146 |
-
fallback_filter = fb.QualityFilter(
|
| 147 |
-
min_chars=10,
|
| 148 |
-
max_chars=20000,
|
| 149 |
-
min_words=3,
|
| 150 |
-
min_entropy=2.0,
|
| 151 |
-
max_repetition_ratio=0.3,
|
| 152 |
-
max_symbol_ratio=0.25,
|
| 153 |
-
)
|
| 154 |
-
|
| 155 |
-
def rust_call():
|
| 156 |
-
return rust_filter.filter_batch(records)
|
| 157 |
-
|
| 158 |
-
def fallback_call():
|
| 159 |
-
return fallback_filter.filter_batch(records)
|
| 160 |
-
|
| 161 |
-
return run_timed_benchmark(
|
| 162 |
-
name="QualityFilter.filter_batch (Entropy + Repetition + Symbols)",
|
| 163 |
-
rust_fn=rust_call,
|
| 164 |
-
fallback_fn=fallback_call,
|
| 165 |
-
workload_size=len(records),
|
| 166 |
-
data_bytes=total_bytes,
|
| 167 |
-
)
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
def benchmark_stream_dedup(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 171 |
-
# Inject 25% duplicate records
|
| 172 |
-
dup_records = list(records)
|
| 173 |
-
for i in range(len(dup_records) // 4):
|
| 174 |
-
dup_records[i * 4] = dup_records[i * 4 + 1]
|
| 175 |
-
|
| 176 |
-
def rust_call():
|
| 177 |
-
dedup = rs.StreamDeduplicator()
|
| 178 |
-
passed = dedup.filter_batch(dup_records)
|
| 179 |
-
dedup.commit()
|
| 180 |
-
return passed
|
| 181 |
-
|
| 182 |
-
def fallback_call():
|
| 183 |
-
dedup = fb.StreamDeduplicator()
|
| 184 |
-
passed = dedup.filter_batch(dup_records)
|
| 185 |
-
dedup.commit()
|
| 186 |
-
return passed
|
| 187 |
-
|
| 188 |
-
return run_timed_benchmark(
|
| 189 |
-
name="StreamDeduplicator.filter_batch (Exact XXH3-64 Staging & Commit)",
|
| 190 |
-
rust_fn=rust_call,
|
| 191 |
-
fallback_fn=fallback_call,
|
| 192 |
-
workload_size=len(dup_records),
|
| 193 |
-
data_bytes=total_bytes,
|
| 194 |
-
)
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
def benchmark_minhash_dedup(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 198 |
-
# Use 500 documents for MinHash permutation benchmarking
|
| 199 |
-
sample_records = records[:500]
|
| 200 |
-
sample_bytes = sum(len(r.encode("utf-8")) for r in sample_records)
|
| 201 |
-
|
| 202 |
-
def rust_call():
|
| 203 |
-
dedup = rs.MinHashDeduplicator(0.8, 5, 64)
|
| 204 |
-
for r in sample_records:
|
| 205 |
-
dedup.is_unique(r)
|
| 206 |
-
return dedup.count()
|
| 207 |
-
|
| 208 |
-
def fallback_call():
|
| 209 |
-
dedup = fb.MinHashDeduplicator(0.8, 5, 64)
|
| 210 |
-
for r in sample_records:
|
| 211 |
-
dedup.is_unique(r)
|
| 212 |
-
return dedup.count()
|
| 213 |
-
|
| 214 |
-
return run_timed_benchmark(
|
| 215 |
-
name="MinHashDeduplicator.is_unique (64-Permutation MinHash Signatures)",
|
| 216 |
-
rust_fn=rust_call,
|
| 217 |
-
fallback_fn=fallback_call,
|
| 218 |
-
workload_size=len(sample_records),
|
| 219 |
-
data_bytes=sample_bytes,
|
| 220 |
-
)
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
def benchmark_state_tracker() -> BenchmarkResult:
|
| 224 |
-
with tempfile.TemporaryDirectory() as tmpdir:
|
| 225 |
-
rust_wal = Path(tmpdir) / "rust_tracker.wal"
|
| 226 |
-
fb_wal = Path(tmpdir) / "fb_tracker.wal"
|
| 227 |
-
|
| 228 |
-
rust_tracker = rs.StateTracker(str(rust_wal))
|
| 229 |
-
fallback_tracker = fb.StateTracker(str(fb_wal))
|
| 230 |
-
|
| 231 |
-
num_commits = 2000
|
| 232 |
-
|
| 233 |
-
def rust_call():
|
| 234 |
-
for i in range(num_commits):
|
| 235 |
-
rust_tracker.update(1, 1, i * 32, i * 1024, 0)
|
| 236 |
-
rust_tracker.commit()
|
| 237 |
-
|
| 238 |
-
def fallback_call():
|
| 239 |
-
for i in range(num_commits):
|
| 240 |
-
fallback_tracker.update(1, 1, i * 32, i * 1024, 0)
|
| 241 |
-
fallback_tracker.commit()
|
| 242 |
-
|
| 243 |
-
return run_timed_benchmark(
|
| 244 |
-
name="StateTracker.update & commit (128-Byte Atomic CRC32 WAL)",
|
| 245 |
-
rust_fn=rust_call,
|
| 246 |
-
fallback_fn=fallback_call,
|
| 247 |
-
workload_size=num_commits,
|
| 248 |
-
data_bytes=num_commits * 128,
|
| 249 |
-
)
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
def benchmark_string_tensor(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 253 |
-
def rust_call():
|
| 254 |
-
st = rs.StringTensor.from_list(records)
|
| 255 |
-
hashes = st.hash_all()
|
| 256 |
-
filtered = st.filter_by_length(20, 1000)
|
| 257 |
-
return len(filtered.to_list()), len(hashes)
|
| 258 |
-
|
| 259 |
-
def fallback_call():
|
| 260 |
-
st = fb.StringTensor.from_list(records)
|
| 261 |
-
hashes = st.hash_all()
|
| 262 |
-
filtered = st.filter_by_length(20, 1000)
|
| 263 |
-
return len(filtered.to_list()), len(hashes)
|
| 264 |
-
|
| 265 |
-
return run_timed_benchmark(
|
| 266 |
-
name="StringTensor (Umbra-style Layout, Hash All & Length Filter)",
|
| 267 |
-
rust_fn=rust_call,
|
| 268 |
-
fallback_fn=fallback_call,
|
| 269 |
-
workload_size=len(records),
|
| 270 |
-
data_bytes=total_bytes,
|
| 271 |
-
)
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
def benchmark_fused_pipeline(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 275 |
-
def rust_call():
|
| 276 |
-
dedup = rs.StreamDeduplicator()
|
| 277 |
-
st = rs.fused_pipeline_process_batch(
|
| 278 |
-
records=records,
|
| 279 |
-
trim_whitespace=True,
|
| 280 |
-
normalize_newlines=True,
|
| 281 |
-
min_byte_len=10,
|
| 282 |
-
max_byte_len=10000,
|
| 283 |
-
truncate_over_max=False,
|
| 284 |
-
json_extract_key=None,
|
| 285 |
-
filter_quality=True,
|
| 286 |
-
min_chars=10,
|
| 287 |
-
max_chars=10000,
|
| 288 |
-
min_words=3,
|
| 289 |
-
min_entropy=2.0,
|
| 290 |
-
max_repetition_ratio=0.3,
|
| 291 |
-
max_symbol_ratio=0.25,
|
| 292 |
-
deduplicator=dedup,
|
| 293 |
-
commit_dedup=True,
|
| 294 |
-
)
|
| 295 |
-
return st.total_bytes()
|
| 296 |
-
|
| 297 |
-
def fallback_call():
|
| 298 |
-
dedup = fb.StreamDeduplicator()
|
| 299 |
-
st = fb.fused_pipeline_process_batch(
|
| 300 |
-
records=records,
|
| 301 |
-
trim_whitespace=True,
|
| 302 |
-
normalize_newlines=True,
|
| 303 |
-
min_byte_len=10,
|
| 304 |
-
max_byte_len=10000,
|
| 305 |
-
truncate_over_max=False,
|
| 306 |
-
json_extract_key=None,
|
| 307 |
-
filter_quality=True,
|
| 308 |
-
min_chars=10,
|
| 309 |
-
max_chars=10000,
|
| 310 |
-
min_words=3,
|
| 311 |
-
min_entropy=2.0,
|
| 312 |
-
max_repetition_ratio=0.3,
|
| 313 |
-
max_symbol_ratio=0.25,
|
| 314 |
-
deduplicator=dedup,
|
| 315 |
-
commit_dedup=True,
|
| 316 |
-
)
|
| 317 |
-
return st.total_bytes()
|
| 318 |
-
|
| 319 |
-
return run_timed_benchmark(
|
| 320 |
-
name="FusedPipeline.fused_process_batch (End-to-End SIMD Single Pass)",
|
| 321 |
-
rust_fn=rust_call,
|
| 322 |
-
fallback_fn=fallback_call,
|
| 323 |
-
workload_size=len(records),
|
| 324 |
-
data_bytes=total_bytes,
|
| 325 |
-
)
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
def benchmark_serialization(records: List[str]) -> List[BenchmarkResult]:
|
| 329 |
-
cols = {
|
| 330 |
-
"id": [str(i) for i in range(len(records))],
|
| 331 |
-
"text": records,
|
| 332 |
-
"score": [str(round(math.sin(i) * 10, 4)) for i in range(len(records))],
|
| 333 |
-
}
|
| 334 |
-
total_data_bytes = sum(len(c.encode("utf-8")) for col in cols.values() for c in col)
|
| 335 |
-
|
| 336 |
-
results = []
|
| 337 |
-
for fmt in ["parquet", "arrow", "jsonl", "csv"]:
|
| 338 |
-
def rust_call(fmt=fmt):
|
| 339 |
-
return rs.serialize_table_to_bytes(cols, fmt, False)
|
| 340 |
-
|
| 341 |
-
def fallback_call(fmt=fmt):
|
| 342 |
-
return fb.serialize_table_to_bytes(cols, fmt, False)
|
| 343 |
-
|
| 344 |
-
res = run_timed_benchmark(
|
| 345 |
-
name=f"Serialization: {fmt.upper()}",
|
| 346 |
-
rust_fn=rust_call,
|
| 347 |
-
fallback_fn=fallback_call,
|
| 348 |
-
workload_size=len(records),
|
| 349 |
-
data_bytes=total_data_bytes,
|
| 350 |
-
)
|
| 351 |
-
results.append(res)
|
| 352 |
-
return results
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
def main() -> None:
|
| 356 |
-
print("=================================================================")
|
| 357 |
-
print(" LuminaMoon Data Processing Engine Benchmark Suite")
|
| 358 |
-
print(f" CPU Architecture / SIMD ISA: {rs.dataloader_cpu_isa()}")
|
| 359 |
-
print("=================================================================\n")
|
| 360 |
-
|
| 361 |
-
num_records = 10000
|
| 362 |
-
print(f"Generating synthetic workload ({num_records:,} records)...")
|
| 363 |
-
records, total_bytes = generate_synthetic_dataset(num_records=num_records)
|
| 364 |
-
print(f"Generated {len(records):,} records, {total_bytes / (1024*1024):.2f} MB payload.\n")
|
| 365 |
-
|
| 366 |
-
benchmarks = [
|
| 367 |
-
("Text Cleaner", lambda: benchmark_text_cleaner(records, total_bytes)),
|
| 368 |
-
("Quality Filter", lambda: benchmark_quality_filter(records, total_bytes)),
|
| 369 |
-
("Stream Deduplicator", lambda: benchmark_stream_dedup(records, total_bytes)),
|
| 370 |
-
("MinHash Deduplicator", lambda: benchmark_minhash_dedup(records, total_bytes)),
|
| 371 |
-
("State Tracker", lambda: benchmark_state_tracker()),
|
| 372 |
-
("String Tensor", lambda: benchmark_string_tensor(records, total_bytes)),
|
| 373 |
-
("Fused Pipeline", lambda: benchmark_fused_pipeline(records, total_bytes)),
|
| 374 |
-
]
|
| 375 |
-
|
| 376 |
-
all_results: List[BenchmarkResult] = []
|
| 377 |
-
|
| 378 |
-
for label, bench_fn in benchmarks:
|
| 379 |
-
print(f"Running benchmark: {label}...")
|
| 380 |
-
res = bench_fn()
|
| 381 |
-
all_results.append(res)
|
| 382 |
-
print(f" -> Rust: {res.rust_throughput_items_per_sec:>10,.0f} items/s ({res.rust_throughput_mb_per_sec:>8.2f} MB/s, {res.rust_duration_sec*1000:>7.2f} ms)")
|
| 383 |
-
print(f" -> Fallback: {res.fallback_throughput_items_per_sec:>10,.0f} items/s ({res.fallback_throughput_mb_per_sec:>8.2f} MB/s, {res.fallback_duration_sec*1000:>7.2f} ms)")
|
| 384 |
-
print(f" -> Speedup: {res.speedup_ratio:>7.2f}x\n")
|
| 385 |
-
|
| 386 |
-
print("Running Serialization Benchmarks...")
|
| 387 |
-
ser_results = benchmark_serialization(records[:5000])
|
| 388 |
-
for res in ser_results:
|
| 389 |
-
all_results.append(res)
|
| 390 |
-
print(f" -> {res.benchmark_name:<28}: Rust {res.rust_throughput_mb_per_sec:>8.2f} MB/s vs Fallback {res.fallback_throughput_mb_per_sec:>8.2f} MB/s (Speedup: {res.speedup_ratio:>6.2f}x)")
|
| 391 |
-
|
| 392 |
-
# Save output json
|
| 393 |
-
out_dir = Path(__file__).resolve().parent.parent / "logs"
|
| 394 |
-
out_dir.mkdir(parents=True, exist_ok=True)
|
| 395 |
-
out_file = out_dir / "benchmark_data_processing_results.json"
|
| 396 |
-
with open(out_file, "w", encoding="utf-8") as f:
|
| 397 |
-
json.dump(
|
| 398 |
-
{
|
| 399 |
-
"cpu_isa": rs.dataloader_cpu_isa(),
|
| 400 |
-
"num_records": num_records,
|
| 401 |
-
"total_mb": total_bytes / (1024 * 1024),
|
| 402 |
-
"benchmarks": [asdict(r) for r in all_results],
|
| 403 |
-
},
|
| 404 |
-
f,
|
| 405 |
-
indent=2,
|
| 406 |
-
)
|
| 407 |
-
print(f"\nSaved raw benchmark metrics to {out_file}")
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
if __name__ == "__main__":
|
| 411 |
-
main()
|
|
|
|
| 1 |
+
"""LuminaMoon Data Processing Engine Benchmark.
|
| 2 |
+
|
| 3 |
+
Compares Native Rust SIMD Accelerator (AVX-512 / AVX2 / Rayon) against
|
| 4 |
+
Pure Python Fallbacks across micro-benchmarks and end-to-end pipelines.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import json
|
| 10 |
+
import math
|
| 11 |
+
import os
|
| 12 |
+
import random
|
| 13 |
+
import sys
|
| 14 |
+
import tempfile
|
| 15 |
+
import time
|
| 16 |
+
from dataclasses import asdict, dataclass
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from typing import Any, Callable, Dict, List, Tuple
|
| 19 |
+
|
| 20 |
+
# Ensure local python package is discoverable
|
| 21 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "python"))
|
| 22 |
+
|
| 23 |
+
from luminamoon.optimizer import _luminamoon_optimizer_rs as rs
|
| 24 |
+
from luminamoon.data import fallbacks as fb
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class BenchmarkResult:
|
| 29 |
+
benchmark_name: str
|
| 30 |
+
workload_size: int
|
| 31 |
+
data_bytes: int
|
| 32 |
+
rust_duration_sec: float
|
| 33 |
+
rust_throughput_items_per_sec: float
|
| 34 |
+
rust_throughput_mb_per_sec: float
|
| 35 |
+
fallback_duration_sec: float
|
| 36 |
+
fallback_throughput_items_per_sec: float
|
| 37 |
+
fallback_throughput_mb_per_sec: float
|
| 38 |
+
speedup_ratio: float
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def generate_synthetic_dataset(num_records: int = 10000, seed: int = 42) -> Tuple[List[str], int]:
|
| 42 |
+
rng = random.Random(seed)
|
| 43 |
+
templates = [
|
| 44 |
+
" The quick brown fox jumps over the lazy dog.\r\n This is line 2 with carriage returns.\r\n",
|
| 45 |
+
"Deep learning optimization requires efficient gradient descent with momentum and adaptive learning rates.\n",
|
| 46 |
+
'{"text": "{\"key\": \"value\", \"nested\": [1, 2, 3]}", "meta": "test record"}\r\n',
|
| 47 |
+
"System security alert: detected unauthorized access attempt from 192.168.1.105 on port 443.\n",
|
| 48 |
+
"Contact user support at alice.walker@enterprise-domain.org or call +1-800-555-0199 for assistance.\r\n",
|
| 49 |
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", # high repetition
|
| 50 |
+
"Short text.\n",
|
| 51 |
+
"Mathematics: Pade [13/6] approximant ensures subnormal FP16 RNE preservation without overflow.\n",
|
| 52 |
+
"High symbol noise: #$%@!&*()!@#$%^&*()_+{}|:<>?~`!@#$%^&*()_+{}|:<>?~`\n",
|
| 53 |
+
"Large document block: " + ("lorem ipsum dolor sit amet " * 20) + "\r\n",
|
| 54 |
+
]
|
| 55 |
+
records = []
|
| 56 |
+
total_bytes = 0
|
| 57 |
+
for i in range(num_records):
|
| 58 |
+
base = templates[i % len(templates)]
|
| 59 |
+
noise = f" [doc_id={i}_{rng.randint(100, 999)}]"
|
| 60 |
+
rec = base + noise
|
| 61 |
+
records.append(rec)
|
| 62 |
+
total_bytes += len(rec.encode("utf-8"))
|
| 63 |
+
return records, total_bytes
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def run_timed_benchmark(
|
| 67 |
+
name: str,
|
| 68 |
+
rust_fn: Callable[[], Any],
|
| 69 |
+
fallback_fn: Callable[[], Any],
|
| 70 |
+
workload_size: int,
|
| 71 |
+
data_bytes: int,
|
| 72 |
+
warmup_runs: int = 2,
|
| 73 |
+
test_runs: int = 5,
|
| 74 |
+
) -> BenchmarkResult:
|
| 75 |
+
# Warmup Rust
|
| 76 |
+
for _ in range(warmup_runs):
|
| 77 |
+
rust_fn()
|
| 78 |
+
# Timed Rust
|
| 79 |
+
rust_times = []
|
| 80 |
+
for _ in range(test_runs):
|
| 81 |
+
t0 = time.perf_counter_ns()
|
| 82 |
+
rust_fn()
|
| 83 |
+
t1 = time.perf_counter_ns()
|
| 84 |
+
rust_times.append((t1 - t0) / 1e9)
|
| 85 |
+
rust_dur = min(rust_times) # best of N runs
|
| 86 |
+
|
| 87 |
+
# Warmup Fallback
|
| 88 |
+
for _ in range(warmup_runs):
|
| 89 |
+
fallback_fn()
|
| 90 |
+
# Timed Fallback
|
| 91 |
+
fallback_times = []
|
| 92 |
+
for _ in range(test_runs):
|
| 93 |
+
t0 = time.perf_counter_ns()
|
| 94 |
+
fallback_fn()
|
| 95 |
+
t1 = time.perf_counter_ns()
|
| 96 |
+
fallback_times.append((t1 - t0) / 1e9)
|
| 97 |
+
fallback_dur = min(fallback_times)
|
| 98 |
+
|
| 99 |
+
rust_items_per_sec = workload_size / max(rust_dur, 1e-9)
|
| 100 |
+
rust_mb_per_sec = (data_bytes / (1024 * 1024)) / max(rust_dur, 1e-9)
|
| 101 |
+
|
| 102 |
+
fallback_items_per_sec = workload_size / max(fallback_dur, 1e-9)
|
| 103 |
+
fallback_mb_per_sec = (data_bytes / (1024 * 1024)) / max(fallback_dur, 1e-9)
|
| 104 |
+
|
| 105 |
+
speedup = fallback_dur / max(rust_dur, 1e-9)
|
| 106 |
+
|
| 107 |
+
return BenchmarkResult(
|
| 108 |
+
benchmark_name=name,
|
| 109 |
+
workload_size=workload_size,
|
| 110 |
+
data_bytes=data_bytes,
|
| 111 |
+
rust_duration_sec=rust_dur,
|
| 112 |
+
rust_throughput_items_per_sec=rust_items_per_sec,
|
| 113 |
+
rust_throughput_mb_per_sec=rust_mb_per_sec,
|
| 114 |
+
fallback_duration_sec=fallback_dur,
|
| 115 |
+
fallback_throughput_items_per_sec=fallback_items_per_sec,
|
| 116 |
+
fallback_throughput_mb_per_sec=fallback_mb_per_sec,
|
| 117 |
+
speedup_ratio=speedup,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def benchmark_text_cleaner(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 122 |
+
def rust_call():
|
| 123 |
+
return rs.clean_batch_records(records, True, True, 10, 10000, False, None)
|
| 124 |
+
|
| 125 |
+
def fallback_call():
|
| 126 |
+
return fb.clean_batch_records(records, True, True, 10, 10000, False, None)
|
| 127 |
+
|
| 128 |
+
return run_timed_benchmark(
|
| 129 |
+
name="TextCleaner.clean_batch (CRLF + Trimming + Bounds)",
|
| 130 |
+
rust_fn=rust_call,
|
| 131 |
+
fallback_fn=fallback_call,
|
| 132 |
+
workload_size=len(records),
|
| 133 |
+
data_bytes=total_bytes,
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def benchmark_quality_filter(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 138 |
+
rust_filter = rs.QualityFilter(
|
| 139 |
+
min_chars=10,
|
| 140 |
+
max_chars=20000,
|
| 141 |
+
min_words=3,
|
| 142 |
+
min_entropy=2.0,
|
| 143 |
+
max_repetition_ratio=0.3,
|
| 144 |
+
max_symbol_ratio=0.25,
|
| 145 |
+
)
|
| 146 |
+
fallback_filter = fb.QualityFilter(
|
| 147 |
+
min_chars=10,
|
| 148 |
+
max_chars=20000,
|
| 149 |
+
min_words=3,
|
| 150 |
+
min_entropy=2.0,
|
| 151 |
+
max_repetition_ratio=0.3,
|
| 152 |
+
max_symbol_ratio=0.25,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
def rust_call():
|
| 156 |
+
return rust_filter.filter_batch(records)
|
| 157 |
+
|
| 158 |
+
def fallback_call():
|
| 159 |
+
return fallback_filter.filter_batch(records)
|
| 160 |
+
|
| 161 |
+
return run_timed_benchmark(
|
| 162 |
+
name="QualityFilter.filter_batch (Entropy + Repetition + Symbols)",
|
| 163 |
+
rust_fn=rust_call,
|
| 164 |
+
fallback_fn=fallback_call,
|
| 165 |
+
workload_size=len(records),
|
| 166 |
+
data_bytes=total_bytes,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def benchmark_stream_dedup(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 171 |
+
# Inject 25% duplicate records
|
| 172 |
+
dup_records = list(records)
|
| 173 |
+
for i in range(len(dup_records) // 4):
|
| 174 |
+
dup_records[i * 4] = dup_records[i * 4 + 1]
|
| 175 |
+
|
| 176 |
+
def rust_call():
|
| 177 |
+
dedup = rs.StreamDeduplicator()
|
| 178 |
+
passed = dedup.filter_batch(dup_records)
|
| 179 |
+
dedup.commit()
|
| 180 |
+
return passed
|
| 181 |
+
|
| 182 |
+
def fallback_call():
|
| 183 |
+
dedup = fb.StreamDeduplicator()
|
| 184 |
+
passed = dedup.filter_batch(dup_records)
|
| 185 |
+
dedup.commit()
|
| 186 |
+
return passed
|
| 187 |
+
|
| 188 |
+
return run_timed_benchmark(
|
| 189 |
+
name="StreamDeduplicator.filter_batch (Exact XXH3-64 Staging & Commit)",
|
| 190 |
+
rust_fn=rust_call,
|
| 191 |
+
fallback_fn=fallback_call,
|
| 192 |
+
workload_size=len(dup_records),
|
| 193 |
+
data_bytes=total_bytes,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def benchmark_minhash_dedup(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 198 |
+
# Use 500 documents for MinHash permutation benchmarking
|
| 199 |
+
sample_records = records[:500]
|
| 200 |
+
sample_bytes = sum(len(r.encode("utf-8")) for r in sample_records)
|
| 201 |
+
|
| 202 |
+
def rust_call():
|
| 203 |
+
dedup = rs.MinHashDeduplicator(0.8, 5, 64)
|
| 204 |
+
for r in sample_records:
|
| 205 |
+
dedup.is_unique(r)
|
| 206 |
+
return dedup.count()
|
| 207 |
+
|
| 208 |
+
def fallback_call():
|
| 209 |
+
dedup = fb.MinHashDeduplicator(0.8, 5, 64)
|
| 210 |
+
for r in sample_records:
|
| 211 |
+
dedup.is_unique(r)
|
| 212 |
+
return dedup.count()
|
| 213 |
+
|
| 214 |
+
return run_timed_benchmark(
|
| 215 |
+
name="MinHashDeduplicator.is_unique (64-Permutation MinHash Signatures)",
|
| 216 |
+
rust_fn=rust_call,
|
| 217 |
+
fallback_fn=fallback_call,
|
| 218 |
+
workload_size=len(sample_records),
|
| 219 |
+
data_bytes=sample_bytes,
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def benchmark_state_tracker() -> BenchmarkResult:
|
| 224 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 225 |
+
rust_wal = Path(tmpdir) / "rust_tracker.wal"
|
| 226 |
+
fb_wal = Path(tmpdir) / "fb_tracker.wal"
|
| 227 |
+
|
| 228 |
+
rust_tracker = rs.StateTracker(str(rust_wal))
|
| 229 |
+
fallback_tracker = fb.StateTracker(str(fb_wal))
|
| 230 |
+
|
| 231 |
+
num_commits = 2000
|
| 232 |
+
|
| 233 |
+
def rust_call():
|
| 234 |
+
for i in range(num_commits):
|
| 235 |
+
rust_tracker.update(1, 1, i * 32, i * 1024, 0)
|
| 236 |
+
rust_tracker.commit()
|
| 237 |
+
|
| 238 |
+
def fallback_call():
|
| 239 |
+
for i in range(num_commits):
|
| 240 |
+
fallback_tracker.update(1, 1, i * 32, i * 1024, 0)
|
| 241 |
+
fallback_tracker.commit()
|
| 242 |
+
|
| 243 |
+
return run_timed_benchmark(
|
| 244 |
+
name="StateTracker.update & commit (128-Byte Atomic CRC32 WAL)",
|
| 245 |
+
rust_fn=rust_call,
|
| 246 |
+
fallback_fn=fallback_call,
|
| 247 |
+
workload_size=num_commits,
|
| 248 |
+
data_bytes=num_commits * 128,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def benchmark_string_tensor(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 253 |
+
def rust_call():
|
| 254 |
+
st = rs.StringTensor.from_list(records)
|
| 255 |
+
hashes = st.hash_all()
|
| 256 |
+
filtered = st.filter_by_length(20, 1000)
|
| 257 |
+
return len(filtered.to_list()), len(hashes)
|
| 258 |
+
|
| 259 |
+
def fallback_call():
|
| 260 |
+
st = fb.StringTensor.from_list(records)
|
| 261 |
+
hashes = st.hash_all()
|
| 262 |
+
filtered = st.filter_by_length(20, 1000)
|
| 263 |
+
return len(filtered.to_list()), len(hashes)
|
| 264 |
+
|
| 265 |
+
return run_timed_benchmark(
|
| 266 |
+
name="StringTensor (Umbra-style Layout, Hash All & Length Filter)",
|
| 267 |
+
rust_fn=rust_call,
|
| 268 |
+
fallback_fn=fallback_call,
|
| 269 |
+
workload_size=len(records),
|
| 270 |
+
data_bytes=total_bytes,
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def benchmark_fused_pipeline(records: List[str], total_bytes: int) -> BenchmarkResult:
|
| 275 |
+
def rust_call():
|
| 276 |
+
dedup = rs.StreamDeduplicator()
|
| 277 |
+
st = rs.fused_pipeline_process_batch(
|
| 278 |
+
records=records,
|
| 279 |
+
trim_whitespace=True,
|
| 280 |
+
normalize_newlines=True,
|
| 281 |
+
min_byte_len=10,
|
| 282 |
+
max_byte_len=10000,
|
| 283 |
+
truncate_over_max=False,
|
| 284 |
+
json_extract_key=None,
|
| 285 |
+
filter_quality=True,
|
| 286 |
+
min_chars=10,
|
| 287 |
+
max_chars=10000,
|
| 288 |
+
min_words=3,
|
| 289 |
+
min_entropy=2.0,
|
| 290 |
+
max_repetition_ratio=0.3,
|
| 291 |
+
max_symbol_ratio=0.25,
|
| 292 |
+
deduplicator=dedup,
|
| 293 |
+
commit_dedup=True,
|
| 294 |
+
)
|
| 295 |
+
return st.total_bytes()
|
| 296 |
+
|
| 297 |
+
def fallback_call():
|
| 298 |
+
dedup = fb.StreamDeduplicator()
|
| 299 |
+
st = fb.fused_pipeline_process_batch(
|
| 300 |
+
records=records,
|
| 301 |
+
trim_whitespace=True,
|
| 302 |
+
normalize_newlines=True,
|
| 303 |
+
min_byte_len=10,
|
| 304 |
+
max_byte_len=10000,
|
| 305 |
+
truncate_over_max=False,
|
| 306 |
+
json_extract_key=None,
|
| 307 |
+
filter_quality=True,
|
| 308 |
+
min_chars=10,
|
| 309 |
+
max_chars=10000,
|
| 310 |
+
min_words=3,
|
| 311 |
+
min_entropy=2.0,
|
| 312 |
+
max_repetition_ratio=0.3,
|
| 313 |
+
max_symbol_ratio=0.25,
|
| 314 |
+
deduplicator=dedup,
|
| 315 |
+
commit_dedup=True,
|
| 316 |
+
)
|
| 317 |
+
return st.total_bytes()
|
| 318 |
+
|
| 319 |
+
return run_timed_benchmark(
|
| 320 |
+
name="FusedPipeline.fused_process_batch (End-to-End SIMD Single Pass)",
|
| 321 |
+
rust_fn=rust_call,
|
| 322 |
+
fallback_fn=fallback_call,
|
| 323 |
+
workload_size=len(records),
|
| 324 |
+
data_bytes=total_bytes,
|
| 325 |
+
)
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def benchmark_serialization(records: List[str]) -> List[BenchmarkResult]:
|
| 329 |
+
cols = {
|
| 330 |
+
"id": [str(i) for i in range(len(records))],
|
| 331 |
+
"text": records,
|
| 332 |
+
"score": [str(round(math.sin(i) * 10, 4)) for i in range(len(records))],
|
| 333 |
+
}
|
| 334 |
+
total_data_bytes = sum(len(c.encode("utf-8")) for col in cols.values() for c in col)
|
| 335 |
+
|
| 336 |
+
results = []
|
| 337 |
+
for fmt in ["parquet", "arrow", "jsonl", "csv"]:
|
| 338 |
+
def rust_call(fmt=fmt):
|
| 339 |
+
return rs.serialize_table_to_bytes(cols, fmt, False)
|
| 340 |
+
|
| 341 |
+
def fallback_call(fmt=fmt):
|
| 342 |
+
return fb.serialize_table_to_bytes(cols, fmt, False)
|
| 343 |
+
|
| 344 |
+
res = run_timed_benchmark(
|
| 345 |
+
name=f"Serialization: {fmt.upper()}",
|
| 346 |
+
rust_fn=rust_call,
|
| 347 |
+
fallback_fn=fallback_call,
|
| 348 |
+
workload_size=len(records),
|
| 349 |
+
data_bytes=total_data_bytes,
|
| 350 |
+
)
|
| 351 |
+
results.append(res)
|
| 352 |
+
return results
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def main() -> None:
|
| 356 |
+
print("=================================================================")
|
| 357 |
+
print(" LuminaMoon Data Processing Engine Benchmark Suite")
|
| 358 |
+
print(f" CPU Architecture / SIMD ISA: {rs.dataloader_cpu_isa()}")
|
| 359 |
+
print("=================================================================\n")
|
| 360 |
+
|
| 361 |
+
num_records = 10000
|
| 362 |
+
print(f"Generating synthetic workload ({num_records:,} records)...")
|
| 363 |
+
records, total_bytes = generate_synthetic_dataset(num_records=num_records)
|
| 364 |
+
print(f"Generated {len(records):,} records, {total_bytes / (1024*1024):.2f} MB payload.\n")
|
| 365 |
+
|
| 366 |
+
benchmarks = [
|
| 367 |
+
("Text Cleaner", lambda: benchmark_text_cleaner(records, total_bytes)),
|
| 368 |
+
("Quality Filter", lambda: benchmark_quality_filter(records, total_bytes)),
|
| 369 |
+
("Stream Deduplicator", lambda: benchmark_stream_dedup(records, total_bytes)),
|
| 370 |
+
("MinHash Deduplicator", lambda: benchmark_minhash_dedup(records, total_bytes)),
|
| 371 |
+
("State Tracker", lambda: benchmark_state_tracker()),
|
| 372 |
+
("String Tensor", lambda: benchmark_string_tensor(records, total_bytes)),
|
| 373 |
+
("Fused Pipeline", lambda: benchmark_fused_pipeline(records, total_bytes)),
|
| 374 |
+
]
|
| 375 |
+
|
| 376 |
+
all_results: List[BenchmarkResult] = []
|
| 377 |
+
|
| 378 |
+
for label, bench_fn in benchmarks:
|
| 379 |
+
print(f"Running benchmark: {label}...")
|
| 380 |
+
res = bench_fn()
|
| 381 |
+
all_results.append(res)
|
| 382 |
+
print(f" -> Rust: {res.rust_throughput_items_per_sec:>10,.0f} items/s ({res.rust_throughput_mb_per_sec:>8.2f} MB/s, {res.rust_duration_sec*1000:>7.2f} ms)")
|
| 383 |
+
print(f" -> Fallback: {res.fallback_throughput_items_per_sec:>10,.0f} items/s ({res.fallback_throughput_mb_per_sec:>8.2f} MB/s, {res.fallback_duration_sec*1000:>7.2f} ms)")
|
| 384 |
+
print(f" -> Speedup: {res.speedup_ratio:>7.2f}x\n")
|
| 385 |
+
|
| 386 |
+
print("Running Serialization Benchmarks...")
|
| 387 |
+
ser_results = benchmark_serialization(records[:5000])
|
| 388 |
+
for res in ser_results:
|
| 389 |
+
all_results.append(res)
|
| 390 |
+
print(f" -> {res.benchmark_name:<28}: Rust {res.rust_throughput_mb_per_sec:>8.2f} MB/s vs Fallback {res.fallback_throughput_mb_per_sec:>8.2f} MB/s (Speedup: {res.speedup_ratio:>6.2f}x)")
|
| 391 |
+
|
| 392 |
+
# Save output json
|
| 393 |
+
out_dir = Path(__file__).resolve().parent.parent / "logs"
|
| 394 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 395 |
+
out_file = out_dir / "benchmark_data_processing_results.json"
|
| 396 |
+
with open(out_file, "w", encoding="utf-8") as f:
|
| 397 |
+
json.dump(
|
| 398 |
+
{
|
| 399 |
+
"cpu_isa": rs.dataloader_cpu_isa(),
|
| 400 |
+
"num_records": num_records,
|
| 401 |
+
"total_mb": total_bytes / (1024 * 1024),
|
| 402 |
+
"benchmarks": [asdict(r) for r in all_results],
|
| 403 |
+
},
|
| 404 |
+
f,
|
| 405 |
+
indent=2,
|
| 406 |
+
)
|
| 407 |
+
print(f"\nSaved raw benchmark metrics to {out_file}")
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
if __name__ == "__main__":
|
| 411 |
+
main()
|
benchmarks/bench_luminav_vision_modes.py
CHANGED
|
@@ -1,398 +1,398 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""Benchmark and Comparative Suite for LuminaV across all execution and buffer modes.
|
| 3 |
-
|
| 4 |
-
Tests training performance on highly complex, pathological synthetic image data
|
| 5 |
-
(multi-octave fractal turbulence, non-stationary Moire/Chirp waves, chaotic manifolds)
|
| 6 |
-
using a deep convolutional vision architecture (ResNet-style ConvNet with residual blocks).
|
| 7 |
-
"""
|
| 8 |
-
|
| 9 |
-
from __future__ import annotations
|
| 10 |
-
|
| 11 |
-
import math
|
| 12 |
-
import os
|
| 13 |
-
import sys
|
| 14 |
-
import time
|
| 15 |
-
from typing import Any, Dict, List, Tuple
|
| 16 |
-
|
| 17 |
-
import numpy as np
|
| 18 |
-
import torch
|
| 19 |
-
import torch.nn as nn
|
| 20 |
-
import torch.nn.functional as F
|
| 21 |
-
|
| 22 |
-
from luminamoon import LuminaV
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
# 1. Pathological / Highly Complex Synthetic Vision Dataset Generation
|
| 26 |
-
def generate_hard_synthetic_vision_dataset(
|
| 27 |
-
num_samples: int = 256,
|
| 28 |
-
height: int = 64,
|
| 29 |
-
width: int = 64,
|
| 30 |
-
num_classes: int = 8,
|
| 31 |
-
seed: int = 2026,
|
| 32 |
-
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
-
"""Generates an extremely difficult synthetic image dataset.
|
| 34 |
-
|
| 35 |
-
Features:
|
| 36 |
-
- Channel 0: Multi-octave Fractal Brownian Motion (fBm) with non-stationary phase shifts.
|
| 37 |
-
- Channel 1: High-frequency chirp & radial Moire vortex with cross-quadrature modulation.
|
| 38 |
-
- Channel 2: Chaotic double-well reaction-diffusion attractor pattern with high spatial curvature.
|
| 39 |
-
- Target: Non-linear topological invariant coupling cross-channel gradients with label ambiguity.
|
| 40 |
-
"""
|
| 41 |
-
rng = np.random.RandomState(seed)
|
| 42 |
-
|
| 43 |
-
# Coordinate grids normalized to [-2, 2]
|
| 44 |
-
y_coords, x_coords = np.meshgrid(
|
| 45 |
-
np.linspace(-2.0, 2.0, height, dtype=np.float32),
|
| 46 |
-
np.linspace(-2.0, 2.0, width, dtype=np.float32),
|
| 47 |
-
indexing="ij",
|
| 48 |
-
)
|
| 49 |
-
r_sq = x_coords**2 + y_coords**2
|
| 50 |
-
theta = np.arctan2(y_coords, x_coords)
|
| 51 |
-
|
| 52 |
-
images = np.zeros((num_samples, 3, height, width), dtype=np.float32)
|
| 53 |
-
labels = np.zeros((num_samples,), dtype=np.int64)
|
| 54 |
-
|
| 55 |
-
for i in range(num_samples):
|
| 56 |
-
# Sample-specific chaotic random parameters
|
| 57 |
-
omega = rng.uniform(8.0, 18.0)
|
| 58 |
-
phi = rng.uniform(0.0, 2.0 * math.pi)
|
| 59 |
-
k_oct = rng.uniform(3.0, 7.0)
|
| 60 |
-
|
| 61 |
-
# Ch 0: Fractal multi-octave turbulence
|
| 62 |
-
ch0 = np.zeros((height, width), dtype=np.float32)
|
| 63 |
-
for octave in range(1, 5):
|
| 64 |
-
freq = 2.0**octave
|
| 65 |
-
amp = 1.0 / freq
|
| 66 |
-
angle = phi + octave * 0.785
|
| 67 |
-
proj = x_coords * math.cos(angle) + y_coords * math.sin(angle)
|
| 68 |
-
ch0 += amp * np.sin(freq * k_oct * proj + phi)
|
| 69 |
-
# Add high-frequency micro-checkerboard jitter
|
| 70 |
-
checker = ((np.arange(height)[:, None] + np.arange(width)[None, :]) % 2) * 2.0 - 1.0
|
| 71 |
-
ch0 += 0.25 * checker * np.cos(omega * r_sq)
|
| 72 |
-
|
| 73 |
-
# Ch 1: High-frequency Chirp / Moire radial vortex
|
| 74 |
-
ch1 = np.sin(omega * r_sq + 4.0 * theta + phi) * np.cos(k_oct * x_coords * y_coords)
|
| 75 |
-
# Cross-quadrature modulation
|
| 76 |
-
ch1 += 0.35 * np.cos(15.0 * (x_coords + y_coords) + phi)
|
| 77 |
-
|
| 78 |
-
# Ch 2: Chaotic double-well / non-linear attractor
|
| 79 |
-
poly = (x_coords**3 - 3.0 * x_coords * (y_coords**2)) * 0.5
|
| 80 |
-
ch2 = np.tanh(poly * 2.0 + np.cos(omega * y_coords))
|
| 81 |
-
ch2 += 0.3 * np.sin(20.0 * x_coords + phi) * np.sin(20.0 * y_coords)
|
| 82 |
-
|
| 83 |
-
# Combine channels and normalize
|
| 84 |
-
images[i, 0] = (ch0 - ch0.mean()) / (ch0.std() + 1e-6)
|
| 85 |
-
images[i, 1] = (ch1 - ch1.mean()) / (ch1.std() + 1e-6)
|
| 86 |
-
images[i, 2] = (ch2 - ch2.mean()) / (ch2.std() + 1e-6)
|
| 87 |
-
|
| 88 |
-
# Compute complex topological functional for target class:
|
| 89 |
-
# Cross-gradient winding energy + multi-channel nonlinear coupling
|
| 90 |
-
dx_ch1 = np.gradient(ch1, axis=1)
|
| 91 |
-
dy_ch2 = np.gradient(ch2, axis=0)
|
| 92 |
-
curl_cross = np.mean(ch0 * (dx_ch1 * dy_ch2))
|
| 93 |
-
vol = np.mean(ch0 * ch1 * ch2)
|
| 94 |
-
quad = np.mean(np.sin(r_sq * 5.0) * ch0)
|
| 95 |
-
|
| 96 |
-
# Map to discrete class [0..num_classes-1] via non-linear hash
|
| 97 |
-
hash_val = math.sin(curl_cross * 25.0 + vol * 15.0 + quad * 10.0 + phi)
|
| 98 |
-
class_idx = int(math.floor(((hash_val + 1.0) / 2.0) * num_classes)) % num_classes
|
| 99 |
-
|
| 100 |
-
# Inject 10% label ambiguity / boundary noise to test cautious clamping & regularization
|
| 101 |
-
if rng.rand() < 0.10:
|
| 102 |
-
class_idx = rng.randint(0, num_classes)
|
| 103 |
-
|
| 104 |
-
labels[i] = class_idx
|
| 105 |
-
|
| 106 |
-
return torch.from_numpy(images), torch.from_numpy(labels)
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
# 2. Deep Convolutional Vision Architecture (ResNet-Lite)
|
| 110 |
-
class ResidualConvBlock(nn.Module):
|
| 111 |
-
def __init__(self, channels: int):
|
| 112 |
-
super().__init__()
|
| 113 |
-
self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1, bias=False)
|
| 114 |
-
self.bn1 = nn.BatchNorm2d(channels)
|
| 115 |
-
self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1, bias=False)
|
| 116 |
-
self.bn2 = nn.BatchNorm2d(channels)
|
| 117 |
-
|
| 118 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 119 |
-
res = x
|
| 120 |
-
out = F.silu(self.bn1(self.conv1(x)))
|
| 121 |
-
out = self.bn2(self.conv2(out))
|
| 122 |
-
return F.silu(out + res)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
class HardVisionConvNet(nn.Module):
|
| 126 |
-
def __init__(self, in_channels: int = 3, num_classes: int = 8):
|
| 127 |
-
super().__init__()
|
| 128 |
-
# Initial stem
|
| 129 |
-
self.stem = nn.Sequential(
|
| 130 |
-
nn.Conv2d(in_channels, 32, kernel_size=3, padding=1, bias=False),
|
| 131 |
-
nn.BatchNorm2d(32),
|
| 132 |
-
nn.SiLU(),
|
| 133 |
-
)
|
| 134 |
-
# Stage 1 (32 channels)
|
| 135 |
-
self.res1 = ResidualConvBlock(32)
|
| 136 |
-
self.pool1 = nn.MaxPool2d(2) # 64 -> 32
|
| 137 |
-
|
| 138 |
-
# Stage 2 (64 channels)
|
| 139 |
-
self.trans1 = nn.Sequential(
|
| 140 |
-
nn.Conv2d(32, 64, kernel_size=3, padding=1, bias=False),
|
| 141 |
-
nn.BatchNorm2d(64),
|
| 142 |
-
nn.SiLU(),
|
| 143 |
-
)
|
| 144 |
-
self.res2 = ResidualConvBlock(64)
|
| 145 |
-
self.pool2 = nn.MaxPool2d(2) # 32 -> 16
|
| 146 |
-
|
| 147 |
-
# Stage 3 (128 channels)
|
| 148 |
-
self.trans2 = nn.Sequential(
|
| 149 |
-
nn.Conv2d(64, 128, kernel_size=3, padding=1, bias=False),
|
| 150 |
-
nn.BatchNorm2d(128),
|
| 151 |
-
nn.SiLU(),
|
| 152 |
-
)
|
| 153 |
-
self.res3 = ResidualConvBlock(128)
|
| 154 |
-
self.pool3 = nn.AdaptiveAvgPool2d((2, 2)) # 128 x 2 x 2 = 512
|
| 155 |
-
|
| 156 |
-
# Classification Head
|
| 157 |
-
self.classifier = nn.Sequential(
|
| 158 |
-
nn.Linear(512, 128),
|
| 159 |
-
nn.SiLU(),
|
| 160 |
-
nn.Dropout(0.1),
|
| 161 |
-
nn.Linear(128, num_classes),
|
| 162 |
-
)
|
| 163 |
-
|
| 164 |
-
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 165 |
-
x = self.stem(x)
|
| 166 |
-
x = self.res1(x)
|
| 167 |
-
x = self.pool1(x)
|
| 168 |
-
x = self.trans1(x)
|
| 169 |
-
x = self.res2(x)
|
| 170 |
-
x = self.pool2(x)
|
| 171 |
-
x = self.trans2(x)
|
| 172 |
-
x = self.res3(x)
|
| 173 |
-
x = self.pool3(x)
|
| 174 |
-
x = torch.flatten(x, 1)
|
| 175 |
-
return self.classifier(x)
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
# 3. Benchmark Training Routine for Single Configuration
|
| 179 |
-
def run_benchmark_trial(
|
| 180 |
-
config_name: str,
|
| 181 |
-
optimizer_fn: Any,
|
| 182 |
-
images: torch.Tensor,
|
| 183 |
-
labels: torch.Tensor,
|
| 184 |
-
batch_size: int = 64,
|
| 185 |
-
steps: int = 20,
|
| 186 |
-
seed: int = 42,
|
| 187 |
-
) -> Dict[str, Any]:
|
| 188 |
-
torch.manual_seed(seed)
|
| 189 |
-
np.random.seed(seed)
|
| 190 |
-
|
| 191 |
-
model = HardVisionConvNet(in_channels=3, num_classes=8)
|
| 192 |
-
criterion = nn.CrossEntropyLoss()
|
| 193 |
-
optimizer = optimizer_fn(model.parameters())
|
| 194 |
-
|
| 195 |
-
total_params = sum(p.numel() for p in model.parameters())
|
| 196 |
-
num_tensors = sum(1 for p in model.parameters())
|
| 197 |
-
|
| 198 |
-
losses: List[float] = []
|
| 199 |
-
step_times: List[float] = []
|
| 200 |
-
accuracies: List[float] = []
|
| 201 |
-
|
| 202 |
-
num_samples = images.shape[0]
|
| 203 |
-
|
| 204 |
-
# Pre-split into deterministic batches
|
| 205 |
-
batch_indices = [
|
| 206 |
-
slice(
|
| 207 |
-
(s * batch_size) % (num_samples - batch_size),
|
| 208 |
-
((s * batch_size) % (num_samples - batch_size)) + batch_size,
|
| 209 |
-
)
|
| 210 |
-
for s in range(steps)
|
| 211 |
-
]
|
| 212 |
-
|
| 213 |
-
total_start = time.perf_counter()
|
| 214 |
-
|
| 215 |
-
for step_idx in range(steps):
|
| 216 |
-
b_slice = batch_indices[step_idx]
|
| 217 |
-
batch_x = images[b_slice]
|
| 218 |
-
batch_y = labels[b_slice]
|
| 219 |
-
|
| 220 |
-
t0 = time.perf_counter()
|
| 221 |
-
|
| 222 |
-
optimizer.zero_grad(set_to_none=True)
|
| 223 |
-
logits = model(batch_x)
|
| 224 |
-
loss = criterion(logits, batch_y)
|
| 225 |
-
loss.backward()
|
| 226 |
-
optimizer.step()
|
| 227 |
-
|
| 228 |
-
t1 = time.perf_counter()
|
| 229 |
-
|
| 230 |
-
loss_val = float(loss.item())
|
| 231 |
-
losses.append(loss_val)
|
| 232 |
-
step_times.append((t1 - t0) * 1000.0) # ms
|
| 233 |
-
|
| 234 |
-
preds = torch.argmax(logits, dim=1)
|
| 235 |
-
acc = float((preds == batch_y).float().mean().item()) * 100.0
|
| 236 |
-
accuracies.append(acc)
|
| 237 |
-
|
| 238 |
-
total_duration_ms = (time.perf_counter() - total_start) * 1000.0
|
| 239 |
-
|
| 240 |
-
# Calculate optimizer state memory footprint
|
| 241 |
-
opt_state_bytes = 0
|
| 242 |
-
if hasattr(optimizer, "state"):
|
| 243 |
-
for p, st in optimizer.state.items():
|
| 244 |
-
if isinstance(st, dict):
|
| 245 |
-
for k, v in st.items():
|
| 246 |
-
if isinstance(v, torch.Tensor):
|
| 247 |
-
opt_state_bytes += v.numel() * v.element_size()
|
| 248 |
-
elif isinstance(v, np.ndarray):
|
| 249 |
-
opt_state_bytes += v.nbytes
|
| 250 |
-
|
| 251 |
-
initial_loss = losses[0]
|
| 252 |
-
final_loss = losses[-1]
|
| 253 |
-
pct_drop = ((initial_loss - final_loss) / initial_loss) * 100.0
|
| 254 |
-
avg_step_ms = float(np.mean(step_times))
|
| 255 |
-
|
| 256 |
-
return {
|
| 257 |
-
"name": config_name,
|
| 258 |
-
"initial_loss": initial_loss,
|
| 259 |
-
"final_loss": final_loss,
|
| 260 |
-
"loss_reduction_pct": pct_drop,
|
| 261 |
-
"final_accuracy": accuracies[-1],
|
| 262 |
-
"total_time_ms": total_duration_ms,
|
| 263 |
-
"avg_step_ms": avg_step_ms,
|
| 264 |
-
"state_mem_mb": opt_state_bytes / (1024.0 * 1024.0),
|
| 265 |
-
"total_params": total_params,
|
| 266 |
-
"num_tensors": num_tensors,
|
| 267 |
-
"losses": losses,
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
# 4. Main Suite Comparing All Modes
|
| 272 |
-
def main():
|
| 273 |
-
print("=" * 80)
|
| 274 |
-
print("LuminaV Vision Mode Benchmark & Comparative Training Suite")
|
| 275 |
-
print("=" * 80)
|
| 276 |
-
|
| 277 |
-
hw = LuminaV.detect_hardware()
|
| 278 |
-
print("[Hardware Environment]")
|
| 279 |
-
print(f" CPU SIMD Vectorization: {hw.get('simd_backend')}")
|
| 280 |
-
print(f" Vulkan GPU Acceleration: {hw.get('vulkan_available')} ({hw.get('gpu_device_name')})")
|
| 281 |
-
print(f" Triton AOT Available: {hw.get('triton_aot_available')} (arch: {hw.get('triton_arch')})")
|
| 282 |
-
print(f" Wave32 Hardware Support: {hw.get('wave32_supported')}")
|
| 283 |
-
try:
|
| 284 |
-
import triton
|
| 285 |
-
print(f" Triton Windows Package: Version {triton.__version__} ({triton.__file__})")
|
| 286 |
-
except Exception as e:
|
| 287 |
-
print(f" Triton Windows Package: Failed ({e})")
|
| 288 |
-
print("-" * 80)
|
| 289 |
-
|
| 290 |
-
print("[Dataset Generation] Synthesizing pathological hard vision images (64x64, 3ch)...")
|
| 291 |
-
images, labels = generate_hard_synthetic_vision_dataset(
|
| 292 |
-
num_samples=256, height=64, width=64, num_classes=8, seed=2026
|
| 293 |
-
)
|
| 294 |
-
print(f" Generated {images.shape[0]} images | Shape: {images.shape} | Labels: {labels.shape}")
|
| 295 |
-
print(f" Label distribution: {torch.bincount(labels).tolist()}")
|
| 296 |
-
print("-" * 80)
|
| 297 |
-
|
| 298 |
-
# Define test configurations across all LuminaV modes and architectures
|
| 299 |
-
configurations = [
|
| 300 |
-
# --- Triton Windows Backends ---
|
| 301 |
-
(
|
| 302 |
-
"LuminaV (triton, Dual-Buffer)",
|
| 303 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="triton", bound=True, cautious=True),
|
| 304 |
-
),
|
| 305 |
-
(
|
| 306 |
-
"LuminaV (triton, Single-Buffer TD-CLM)",
|
| 307 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=1, execution="triton", bound=True, cautious=True),
|
| 308 |
-
),
|
| 309 |
-
# --- Hardware Backends (Vulkan GPU, SIMD AVX-512, Scalar Core) ---
|
| 310 |
-
(
|
| 311 |
-
"LuminaV (vulkan, Dual-Buffer)",
|
| 312 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="vulkan", bound=True, cautious=True),
|
| 313 |
-
),
|
| 314 |
-
(
|
| 315 |
-
"LuminaV (simd AVX-512, Dual-Buffer)",
|
| 316 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="simd", bound=True, cautious=True),
|
| 317 |
-
),
|
| 318 |
-
(
|
| 319 |
-
"LuminaV (scalar, Dual-Buffer)",
|
| 320 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="scalar", bound=True, cautious=True),
|
| 321 |
-
),
|
| 322 |
-
# --- Buffer Architectures ---
|
| 323 |
-
(
|
| 324 |
-
"LuminaV (auto, Single-Buffer TD-CLM)",
|
| 325 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=1, execution="auto", bound=True, cautious=True),
|
| 326 |
-
),
|
| 327 |
-
(
|
| 328 |
-
"LuminaV (auto, Dual-Buffer CLM)",
|
| 329 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", bound=True, cautious=True),
|
| 330 |
-
),
|
| 331 |
-
(
|
| 332 |
-
"LuminaV (auto, T1 Single-Buffer Triangle)",
|
| 333 |
-
lambda p: LuminaV(p, lr=1e-3, buffer="T1", execution="auto", bound=True, cautious=True),
|
| 334 |
-
),
|
| 335 |
-
(
|
| 336 |
-
"LuminaV (auto, T2 Dual-Buffer Triangle)",
|
| 337 |
-
lambda p: LuminaV(p, lr=1e-3, buffer="T2", execution="auto", bound=True, cautious=True),
|
| 338 |
-
),
|
| 339 |
-
# --- Advanced Vision Features ---
|
| 340 |
-
(
|
| 341 |
-
"LuminaV (auto, fuse_tensors=True)",
|
| 342 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", fuse_tensors=True),
|
| 343 |
-
),
|
| 344 |
-
(
|
| 345 |
-
"LuminaV (auto, radial bound)",
|
| 346 |
-
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", bound=True, bound_type="radial"),
|
| 347 |
-
),
|
| 348 |
-
# --- Baseline Comparison ---
|
| 349 |
-
(
|
| 350 |
-
"Baseline AdamW (PyTorch)",
|
| 351 |
-
lambda p: torch.optim.AdamW(p, lr=1e-3, betas=(0.9, 0.999), weight_decay=0.01),
|
| 352 |
-
),
|
| 353 |
-
]
|
| 354 |
-
|
| 355 |
-
results: List[Dict[str, Any]] = []
|
| 356 |
-
|
| 357 |
-
print("[Training Phase] Benchmarking 20 optimization steps per configuration on ResNet-Lite...")
|
| 358 |
-
for idx, (name, opt_fn) in enumerate(configurations, 1):
|
| 359 |
-
print(f"\n[{idx:02d}/{len(configurations):02d}] Running trial: {name} ...", flush=True)
|
| 360 |
-
res = run_benchmark_trial(
|
| 361 |
-
name, opt_fn, images, labels, batch_size=64, steps=20, seed=42
|
| 362 |
-
)
|
| 363 |
-
results.append(res)
|
| 364 |
-
print(
|
| 365 |
-
f" Loss: {res['initial_loss']:.4f} -> {res['final_loss']:.4f} "
|
| 366 |
-
f"({res['loss_reduction_pct']:+.2f}%) | "
|
| 367 |
-
f"Acc: {res['final_accuracy']:.1f}% | "
|
| 368 |
-
f"Step: {res['avg_step_ms']:.2f} ms | "
|
| 369 |
-
f"State Mem: {res['state_mem_mb']:.2f} MB"
|
| 370 |
-
)
|
| 371 |
-
|
| 372 |
-
print("\n" + "=" * 105)
|
| 373 |
-
print("COMPREHENSIVE BENCHMARK COMPARISON TABLE")
|
| 374 |
-
print("=" * 105)
|
| 375 |
-
header = (
|
| 376 |
-
f"{'Mode Configuration':<40} | {'Init Loss':<10} | {'Final Loss':<10} | "
|
| 377 |
-
f"{'Reduction':<10} | {'Accuracy':<9} | {'Avg Step':<11} | {'State RAM':<9}"
|
| 378 |
-
)
|
| 379 |
-
print(header)
|
| 380 |
-
print("-" * 105)
|
| 381 |
-
|
| 382 |
-
for r in results:
|
| 383 |
-
row = (
|
| 384 |
-
f"{r['name']:<40} | "
|
| 385 |
-
f"{r['initial_loss']:<10.4f} | "
|
| 386 |
-
f"{r['final_loss']:<10.4f} | "
|
| 387 |
-
f"{r['loss_reduction_pct']:>8.2f}% | "
|
| 388 |
-
f"{r['final_accuracy']:>7.1f}% | "
|
| 389 |
-
f"{r['avg_step_ms']:>8.2f} ms | "
|
| 390 |
-
f"{r['state_mem_mb']:>7.2f} MB"
|
| 391 |
-
)
|
| 392 |
-
print(row)
|
| 393 |
-
|
| 394 |
-
print("=" * 105)
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
if __name__ == "__main__":
|
| 398 |
-
main()
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Benchmark and Comparative Suite for LuminaV across all execution and buffer modes.
|
| 3 |
+
|
| 4 |
+
Tests training performance on highly complex, pathological synthetic image data
|
| 5 |
+
(multi-octave fractal turbulence, non-stationary Moire/Chirp waves, chaotic manifolds)
|
| 6 |
+
using a deep convolutional vision architecture (ResNet-style ConvNet with residual blocks).
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import math
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
import time
|
| 15 |
+
from typing import Any, Dict, List, Tuple
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
import torch.nn.functional as F
|
| 21 |
+
|
| 22 |
+
from luminamoon import LuminaV
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# 1. Pathological / Highly Complex Synthetic Vision Dataset Generation
|
| 26 |
+
def generate_hard_synthetic_vision_dataset(
|
| 27 |
+
num_samples: int = 256,
|
| 28 |
+
height: int = 64,
|
| 29 |
+
width: int = 64,
|
| 30 |
+
num_classes: int = 8,
|
| 31 |
+
seed: int = 2026,
|
| 32 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 33 |
+
"""Generates an extremely difficult synthetic image dataset.
|
| 34 |
+
|
| 35 |
+
Features:
|
| 36 |
+
- Channel 0: Multi-octave Fractal Brownian Motion (fBm) with non-stationary phase shifts.
|
| 37 |
+
- Channel 1: High-frequency chirp & radial Moire vortex with cross-quadrature modulation.
|
| 38 |
+
- Channel 2: Chaotic double-well reaction-diffusion attractor pattern with high spatial curvature.
|
| 39 |
+
- Target: Non-linear topological invariant coupling cross-channel gradients with label ambiguity.
|
| 40 |
+
"""
|
| 41 |
+
rng = np.random.RandomState(seed)
|
| 42 |
+
|
| 43 |
+
# Coordinate grids normalized to [-2, 2]
|
| 44 |
+
y_coords, x_coords = np.meshgrid(
|
| 45 |
+
np.linspace(-2.0, 2.0, height, dtype=np.float32),
|
| 46 |
+
np.linspace(-2.0, 2.0, width, dtype=np.float32),
|
| 47 |
+
indexing="ij",
|
| 48 |
+
)
|
| 49 |
+
r_sq = x_coords**2 + y_coords**2
|
| 50 |
+
theta = np.arctan2(y_coords, x_coords)
|
| 51 |
+
|
| 52 |
+
images = np.zeros((num_samples, 3, height, width), dtype=np.float32)
|
| 53 |
+
labels = np.zeros((num_samples,), dtype=np.int64)
|
| 54 |
+
|
| 55 |
+
for i in range(num_samples):
|
| 56 |
+
# Sample-specific chaotic random parameters
|
| 57 |
+
omega = rng.uniform(8.0, 18.0)
|
| 58 |
+
phi = rng.uniform(0.0, 2.0 * math.pi)
|
| 59 |
+
k_oct = rng.uniform(3.0, 7.0)
|
| 60 |
+
|
| 61 |
+
# Ch 0: Fractal multi-octave turbulence
|
| 62 |
+
ch0 = np.zeros((height, width), dtype=np.float32)
|
| 63 |
+
for octave in range(1, 5):
|
| 64 |
+
freq = 2.0**octave
|
| 65 |
+
amp = 1.0 / freq
|
| 66 |
+
angle = phi + octave * 0.785
|
| 67 |
+
proj = x_coords * math.cos(angle) + y_coords * math.sin(angle)
|
| 68 |
+
ch0 += amp * np.sin(freq * k_oct * proj + phi)
|
| 69 |
+
# Add high-frequency micro-checkerboard jitter
|
| 70 |
+
checker = ((np.arange(height)[:, None] + np.arange(width)[None, :]) % 2) * 2.0 - 1.0
|
| 71 |
+
ch0 += 0.25 * checker * np.cos(omega * r_sq)
|
| 72 |
+
|
| 73 |
+
# Ch 1: High-frequency Chirp / Moire radial vortex
|
| 74 |
+
ch1 = np.sin(omega * r_sq + 4.0 * theta + phi) * np.cos(k_oct * x_coords * y_coords)
|
| 75 |
+
# Cross-quadrature modulation
|
| 76 |
+
ch1 += 0.35 * np.cos(15.0 * (x_coords + y_coords) + phi)
|
| 77 |
+
|
| 78 |
+
# Ch 2: Chaotic double-well / non-linear attractor
|
| 79 |
+
poly = (x_coords**3 - 3.0 * x_coords * (y_coords**2)) * 0.5
|
| 80 |
+
ch2 = np.tanh(poly * 2.0 + np.cos(omega * y_coords))
|
| 81 |
+
ch2 += 0.3 * np.sin(20.0 * x_coords + phi) * np.sin(20.0 * y_coords)
|
| 82 |
+
|
| 83 |
+
# Combine channels and normalize
|
| 84 |
+
images[i, 0] = (ch0 - ch0.mean()) / (ch0.std() + 1e-6)
|
| 85 |
+
images[i, 1] = (ch1 - ch1.mean()) / (ch1.std() + 1e-6)
|
| 86 |
+
images[i, 2] = (ch2 - ch2.mean()) / (ch2.std() + 1e-6)
|
| 87 |
+
|
| 88 |
+
# Compute complex topological functional for target class:
|
| 89 |
+
# Cross-gradient winding energy + multi-channel nonlinear coupling
|
| 90 |
+
dx_ch1 = np.gradient(ch1, axis=1)
|
| 91 |
+
dy_ch2 = np.gradient(ch2, axis=0)
|
| 92 |
+
curl_cross = np.mean(ch0 * (dx_ch1 * dy_ch2))
|
| 93 |
+
vol = np.mean(ch0 * ch1 * ch2)
|
| 94 |
+
quad = np.mean(np.sin(r_sq * 5.0) * ch0)
|
| 95 |
+
|
| 96 |
+
# Map to discrete class [0..num_classes-1] via non-linear hash
|
| 97 |
+
hash_val = math.sin(curl_cross * 25.0 + vol * 15.0 + quad * 10.0 + phi)
|
| 98 |
+
class_idx = int(math.floor(((hash_val + 1.0) / 2.0) * num_classes)) % num_classes
|
| 99 |
+
|
| 100 |
+
# Inject 10% label ambiguity / boundary noise to test cautious clamping & regularization
|
| 101 |
+
if rng.rand() < 0.10:
|
| 102 |
+
class_idx = rng.randint(0, num_classes)
|
| 103 |
+
|
| 104 |
+
labels[i] = class_idx
|
| 105 |
+
|
| 106 |
+
return torch.from_numpy(images), torch.from_numpy(labels)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# 2. Deep Convolutional Vision Architecture (ResNet-Lite)
|
| 110 |
+
class ResidualConvBlock(nn.Module):
|
| 111 |
+
def __init__(self, channels: int):
|
| 112 |
+
super().__init__()
|
| 113 |
+
self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1, bias=False)
|
| 114 |
+
self.bn1 = nn.BatchNorm2d(channels)
|
| 115 |
+
self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1, bias=False)
|
| 116 |
+
self.bn2 = nn.BatchNorm2d(channels)
|
| 117 |
+
|
| 118 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 119 |
+
res = x
|
| 120 |
+
out = F.silu(self.bn1(self.conv1(x)))
|
| 121 |
+
out = self.bn2(self.conv2(out))
|
| 122 |
+
return F.silu(out + res)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class HardVisionConvNet(nn.Module):
|
| 126 |
+
def __init__(self, in_channels: int = 3, num_classes: int = 8):
|
| 127 |
+
super().__init__()
|
| 128 |
+
# Initial stem
|
| 129 |
+
self.stem = nn.Sequential(
|
| 130 |
+
nn.Conv2d(in_channels, 32, kernel_size=3, padding=1, bias=False),
|
| 131 |
+
nn.BatchNorm2d(32),
|
| 132 |
+
nn.SiLU(),
|
| 133 |
+
)
|
| 134 |
+
# Stage 1 (32 channels)
|
| 135 |
+
self.res1 = ResidualConvBlock(32)
|
| 136 |
+
self.pool1 = nn.MaxPool2d(2) # 64 -> 32
|
| 137 |
+
|
| 138 |
+
# Stage 2 (64 channels)
|
| 139 |
+
self.trans1 = nn.Sequential(
|
| 140 |
+
nn.Conv2d(32, 64, kernel_size=3, padding=1, bias=False),
|
| 141 |
+
nn.BatchNorm2d(64),
|
| 142 |
+
nn.SiLU(),
|
| 143 |
+
)
|
| 144 |
+
self.res2 = ResidualConvBlock(64)
|
| 145 |
+
self.pool2 = nn.MaxPool2d(2) # 32 -> 16
|
| 146 |
+
|
| 147 |
+
# Stage 3 (128 channels)
|
| 148 |
+
self.trans2 = nn.Sequential(
|
| 149 |
+
nn.Conv2d(64, 128, kernel_size=3, padding=1, bias=False),
|
| 150 |
+
nn.BatchNorm2d(128),
|
| 151 |
+
nn.SiLU(),
|
| 152 |
+
)
|
| 153 |
+
self.res3 = ResidualConvBlock(128)
|
| 154 |
+
self.pool3 = nn.AdaptiveAvgPool2d((2, 2)) # 128 x 2 x 2 = 512
|
| 155 |
+
|
| 156 |
+
# Classification Head
|
| 157 |
+
self.classifier = nn.Sequential(
|
| 158 |
+
nn.Linear(512, 128),
|
| 159 |
+
nn.SiLU(),
|
| 160 |
+
nn.Dropout(0.1),
|
| 161 |
+
nn.Linear(128, num_classes),
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 165 |
+
x = self.stem(x)
|
| 166 |
+
x = self.res1(x)
|
| 167 |
+
x = self.pool1(x)
|
| 168 |
+
x = self.trans1(x)
|
| 169 |
+
x = self.res2(x)
|
| 170 |
+
x = self.pool2(x)
|
| 171 |
+
x = self.trans2(x)
|
| 172 |
+
x = self.res3(x)
|
| 173 |
+
x = self.pool3(x)
|
| 174 |
+
x = torch.flatten(x, 1)
|
| 175 |
+
return self.classifier(x)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
# 3. Benchmark Training Routine for Single Configuration
|
| 179 |
+
def run_benchmark_trial(
|
| 180 |
+
config_name: str,
|
| 181 |
+
optimizer_fn: Any,
|
| 182 |
+
images: torch.Tensor,
|
| 183 |
+
labels: torch.Tensor,
|
| 184 |
+
batch_size: int = 64,
|
| 185 |
+
steps: int = 20,
|
| 186 |
+
seed: int = 42,
|
| 187 |
+
) -> Dict[str, Any]:
|
| 188 |
+
torch.manual_seed(seed)
|
| 189 |
+
np.random.seed(seed)
|
| 190 |
+
|
| 191 |
+
model = HardVisionConvNet(in_channels=3, num_classes=8)
|
| 192 |
+
criterion = nn.CrossEntropyLoss()
|
| 193 |
+
optimizer = optimizer_fn(model.parameters())
|
| 194 |
+
|
| 195 |
+
total_params = sum(p.numel() for p in model.parameters())
|
| 196 |
+
num_tensors = sum(1 for p in model.parameters())
|
| 197 |
+
|
| 198 |
+
losses: List[float] = []
|
| 199 |
+
step_times: List[float] = []
|
| 200 |
+
accuracies: List[float] = []
|
| 201 |
+
|
| 202 |
+
num_samples = images.shape[0]
|
| 203 |
+
|
| 204 |
+
# Pre-split into deterministic batches
|
| 205 |
+
batch_indices = [
|
| 206 |
+
slice(
|
| 207 |
+
(s * batch_size) % (num_samples - batch_size),
|
| 208 |
+
((s * batch_size) % (num_samples - batch_size)) + batch_size,
|
| 209 |
+
)
|
| 210 |
+
for s in range(steps)
|
| 211 |
+
]
|
| 212 |
+
|
| 213 |
+
total_start = time.perf_counter()
|
| 214 |
+
|
| 215 |
+
for step_idx in range(steps):
|
| 216 |
+
b_slice = batch_indices[step_idx]
|
| 217 |
+
batch_x = images[b_slice]
|
| 218 |
+
batch_y = labels[b_slice]
|
| 219 |
+
|
| 220 |
+
t0 = time.perf_counter()
|
| 221 |
+
|
| 222 |
+
optimizer.zero_grad(set_to_none=True)
|
| 223 |
+
logits = model(batch_x)
|
| 224 |
+
loss = criterion(logits, batch_y)
|
| 225 |
+
loss.backward()
|
| 226 |
+
optimizer.step()
|
| 227 |
+
|
| 228 |
+
t1 = time.perf_counter()
|
| 229 |
+
|
| 230 |
+
loss_val = float(loss.item())
|
| 231 |
+
losses.append(loss_val)
|
| 232 |
+
step_times.append((t1 - t0) * 1000.0) # ms
|
| 233 |
+
|
| 234 |
+
preds = torch.argmax(logits, dim=1)
|
| 235 |
+
acc = float((preds == batch_y).float().mean().item()) * 100.0
|
| 236 |
+
accuracies.append(acc)
|
| 237 |
+
|
| 238 |
+
total_duration_ms = (time.perf_counter() - total_start) * 1000.0
|
| 239 |
+
|
| 240 |
+
# Calculate optimizer state memory footprint
|
| 241 |
+
opt_state_bytes = 0
|
| 242 |
+
if hasattr(optimizer, "state"):
|
| 243 |
+
for p, st in optimizer.state.items():
|
| 244 |
+
if isinstance(st, dict):
|
| 245 |
+
for k, v in st.items():
|
| 246 |
+
if isinstance(v, torch.Tensor):
|
| 247 |
+
opt_state_bytes += v.numel() * v.element_size()
|
| 248 |
+
elif isinstance(v, np.ndarray):
|
| 249 |
+
opt_state_bytes += v.nbytes
|
| 250 |
+
|
| 251 |
+
initial_loss = losses[0]
|
| 252 |
+
final_loss = losses[-1]
|
| 253 |
+
pct_drop = ((initial_loss - final_loss) / initial_loss) * 100.0
|
| 254 |
+
avg_step_ms = float(np.mean(step_times))
|
| 255 |
+
|
| 256 |
+
return {
|
| 257 |
+
"name": config_name,
|
| 258 |
+
"initial_loss": initial_loss,
|
| 259 |
+
"final_loss": final_loss,
|
| 260 |
+
"loss_reduction_pct": pct_drop,
|
| 261 |
+
"final_accuracy": accuracies[-1],
|
| 262 |
+
"total_time_ms": total_duration_ms,
|
| 263 |
+
"avg_step_ms": avg_step_ms,
|
| 264 |
+
"state_mem_mb": opt_state_bytes / (1024.0 * 1024.0),
|
| 265 |
+
"total_params": total_params,
|
| 266 |
+
"num_tensors": num_tensors,
|
| 267 |
+
"losses": losses,
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
# 4. Main Suite Comparing All Modes
|
| 272 |
+
def main():
|
| 273 |
+
print("=" * 80)
|
| 274 |
+
print("LuminaV Vision Mode Benchmark & Comparative Training Suite")
|
| 275 |
+
print("=" * 80)
|
| 276 |
+
|
| 277 |
+
hw = LuminaV.detect_hardware()
|
| 278 |
+
print("[Hardware Environment]")
|
| 279 |
+
print(f" CPU SIMD Vectorization: {hw.get('simd_backend')}")
|
| 280 |
+
print(f" Vulkan GPU Acceleration: {hw.get('vulkan_available')} ({hw.get('gpu_device_name')})")
|
| 281 |
+
print(f" Triton AOT Available: {hw.get('triton_aot_available')} (arch: {hw.get('triton_arch')})")
|
| 282 |
+
print(f" Wave32 Hardware Support: {hw.get('wave32_supported')}")
|
| 283 |
+
try:
|
| 284 |
+
import triton
|
| 285 |
+
print(f" Triton Windows Package: Version {triton.__version__} ({triton.__file__})")
|
| 286 |
+
except Exception as e:
|
| 287 |
+
print(f" Triton Windows Package: Failed ({e})")
|
| 288 |
+
print("-" * 80)
|
| 289 |
+
|
| 290 |
+
print("[Dataset Generation] Synthesizing pathological hard vision images (64x64, 3ch)...")
|
| 291 |
+
images, labels = generate_hard_synthetic_vision_dataset(
|
| 292 |
+
num_samples=256, height=64, width=64, num_classes=8, seed=2026
|
| 293 |
+
)
|
| 294 |
+
print(f" Generated {images.shape[0]} images | Shape: {images.shape} | Labels: {labels.shape}")
|
| 295 |
+
print(f" Label distribution: {torch.bincount(labels).tolist()}")
|
| 296 |
+
print("-" * 80)
|
| 297 |
+
|
| 298 |
+
# Define test configurations across all LuminaV modes and architectures
|
| 299 |
+
configurations = [
|
| 300 |
+
# --- Triton Windows Backends ---
|
| 301 |
+
(
|
| 302 |
+
"LuminaV (triton, Dual-Buffer)",
|
| 303 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="triton", bound=True, cautious=True),
|
| 304 |
+
),
|
| 305 |
+
(
|
| 306 |
+
"LuminaV (triton, Single-Buffer TD-CLM)",
|
| 307 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=1, execution="triton", bound=True, cautious=True),
|
| 308 |
+
),
|
| 309 |
+
# --- Hardware Backends (Vulkan GPU, SIMD AVX-512, Scalar Core) ---
|
| 310 |
+
(
|
| 311 |
+
"LuminaV (vulkan, Dual-Buffer)",
|
| 312 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="vulkan", bound=True, cautious=True),
|
| 313 |
+
),
|
| 314 |
+
(
|
| 315 |
+
"LuminaV (simd AVX-512, Dual-Buffer)",
|
| 316 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="simd", bound=True, cautious=True),
|
| 317 |
+
),
|
| 318 |
+
(
|
| 319 |
+
"LuminaV (scalar, Dual-Buffer)",
|
| 320 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="scalar", bound=True, cautious=True),
|
| 321 |
+
),
|
| 322 |
+
# --- Buffer Architectures ---
|
| 323 |
+
(
|
| 324 |
+
"LuminaV (auto, Single-Buffer TD-CLM)",
|
| 325 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=1, execution="auto", bound=True, cautious=True),
|
| 326 |
+
),
|
| 327 |
+
(
|
| 328 |
+
"LuminaV (auto, Dual-Buffer CLM)",
|
| 329 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", bound=True, cautious=True),
|
| 330 |
+
),
|
| 331 |
+
(
|
| 332 |
+
"LuminaV (auto, T1 Single-Buffer Triangle)",
|
| 333 |
+
lambda p: LuminaV(p, lr=1e-3, buffer="T1", execution="auto", bound=True, cautious=True),
|
| 334 |
+
),
|
| 335 |
+
(
|
| 336 |
+
"LuminaV (auto, T2 Dual-Buffer Triangle)",
|
| 337 |
+
lambda p: LuminaV(p, lr=1e-3, buffer="T2", execution="auto", bound=True, cautious=True),
|
| 338 |
+
),
|
| 339 |
+
# --- Advanced Vision Features ---
|
| 340 |
+
(
|
| 341 |
+
"LuminaV (auto, fuse_tensors=True)",
|
| 342 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", fuse_tensors=True),
|
| 343 |
+
),
|
| 344 |
+
(
|
| 345 |
+
"LuminaV (auto, radial bound)",
|
| 346 |
+
lambda p: LuminaV(p, lr=1e-3, buffer=2, execution="auto", bound=True, bound_type="radial"),
|
| 347 |
+
),
|
| 348 |
+
# --- Baseline Comparison ---
|
| 349 |
+
(
|
| 350 |
+
"Baseline AdamW (PyTorch)",
|
| 351 |
+
lambda p: torch.optim.AdamW(p, lr=1e-3, betas=(0.9, 0.999), weight_decay=0.01),
|
| 352 |
+
),
|
| 353 |
+
]
|
| 354 |
+
|
| 355 |
+
results: List[Dict[str, Any]] = []
|
| 356 |
+
|
| 357 |
+
print("[Training Phase] Benchmarking 20 optimization steps per configuration on ResNet-Lite...")
|
| 358 |
+
for idx, (name, opt_fn) in enumerate(configurations, 1):
|
| 359 |
+
print(f"\n[{idx:02d}/{len(configurations):02d}] Running trial: {name} ...", flush=True)
|
| 360 |
+
res = run_benchmark_trial(
|
| 361 |
+
name, opt_fn, images, labels, batch_size=64, steps=20, seed=42
|
| 362 |
+
)
|
| 363 |
+
results.append(res)
|
| 364 |
+
print(
|
| 365 |
+
f" Loss: {res['initial_loss']:.4f} -> {res['final_loss']:.4f} "
|
| 366 |
+
f"({res['loss_reduction_pct']:+.2f}%) | "
|
| 367 |
+
f"Acc: {res['final_accuracy']:.1f}% | "
|
| 368 |
+
f"Step: {res['avg_step_ms']:.2f} ms | "
|
| 369 |
+
f"State Mem: {res['state_mem_mb']:.2f} MB"
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
print("\n" + "=" * 105)
|
| 373 |
+
print("COMPREHENSIVE BENCHMARK COMPARISON TABLE")
|
| 374 |
+
print("=" * 105)
|
| 375 |
+
header = (
|
| 376 |
+
f"{'Mode Configuration':<40} | {'Init Loss':<10} | {'Final Loss':<10} | "
|
| 377 |
+
f"{'Reduction':<10} | {'Accuracy':<9} | {'Avg Step':<11} | {'State RAM':<9}"
|
| 378 |
+
)
|
| 379 |
+
print(header)
|
| 380 |
+
print("-" * 105)
|
| 381 |
+
|
| 382 |
+
for r in results:
|
| 383 |
+
row = (
|
| 384 |
+
f"{r['name']:<40} | "
|
| 385 |
+
f"{r['initial_loss']:<10.4f} | "
|
| 386 |
+
f"{r['final_loss']:<10.4f} | "
|
| 387 |
+
f"{r['loss_reduction_pct']:>8.2f}% | "
|
| 388 |
+
f"{r['final_accuracy']:>7.1f}% | "
|
| 389 |
+
f"{r['avg_step_ms']:>8.2f} ms | "
|
| 390 |
+
f"{r['state_mem_mb']:>7.2f} MB"
|
| 391 |
+
)
|
| 392 |
+
print(row)
|
| 393 |
+
|
| 394 |
+
print("=" * 105)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
if __name__ == "__main__":
|
| 398 |
+
main()
|
changelogs/LUMINAV_ALGORITHM_CHANGELOG.md
CHANGED
|
@@ -1,430 +1,430 @@
|
|
| 1 |
-
# LuminaV Optimizer Algorithm Changelog
|
| 2 |
-
|
| 3 |
-
All notable changes to the LuminaV Optimizer algorithm are documented in this file.
|
| 4 |
-
|
| 5 |
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
-
|
| 7 |
-
* [Software Package Changelog (`luminamoon`)](SOFTWARE_CHANGELOG.md)
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
## [1.6.0] - 2026-11-03
|
| 12 |
-
|
| 13 |
-
### Added
|
| 14 |
-
- **T-Family (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 15 |
-
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$, computing the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounding the lookahead update $\tilde{m}_t$ via the **Triangle Inequality Envelope** $\mathcal{E}_t^{(\text{T2})}$:
|
| 16 |
-
|
| 17 |
-
$\displaystyle d_t = g_t - m _ {t-1}, \qquad v_t = \beta_2 v _ {t-1} + (1 - \beta_2) d_t^2$
|
| 18 |
-
|
| 19 |
-
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \qquad w_1 = \beta_1^2, \qquad w_2 = 1 - \beta_1^2$
|
| 20 |
-
|
| 21 |
-
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 22 |
-
|
| 23 |
-
By the Triangle Inequality ($|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$), $|u_t^{(\text{T2})}| < 1$ is guaranteed without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, and $0.5\lambda \sigma_t$ provides belief-variance damping.
|
| 24 |
-
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Combines the single-buffer **Triangle-Defect** denominator $\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ (using residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below, reducing optimizer state memory by 50% via the Triangle Inequality.
|
| 25 |
-
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas:
|
| 26 |
-
|
| 27 |
-
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 28 |
-
* Keeps 100% backward compatibility and identical outputs for existing modes (`buffer=1`, `buffer=2`, and `version="1.5.5"`).
|
| 29 |
-
|
| 30 |
-
---
|
| 31 |
-
|
| 32 |
-
## [1.5.5] - 2026-10-29
|
| 33 |
-
|
| 34 |
-
### Added
|
| 35 |
-
- **Contiguous Bucket Fusion (`fuse_tensors: bool = False`)**:
|
| 36 |
-
* Added opt-in contiguous parameter and state coalescing (`_build_contiguous_buckets`) that groups parameters within each `(param_group, device, dtype)` domain $\mathcal{B}_{g,d,\tau}$ into contiguous 1D storage (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) and rebinds `p.data`, `p.grad`, and `self.state[p]` entries as zero-copy views (`flat_*[offset : offset + n].view(p.shape)`).
|
| 37 |
-
* Eliminates per-tensor CPU-to-GPU kernel launch latency on fragmented vision and edge architectures by executing an entire bucket in a single fused kernel dispatch while computing `cautious_scale` and `radial` trust bounds across the bucket domain $\mathcal{B}_{g,d,\tau}$.
|
| 38 |
-
* Added in-place `zero_grad()` view preservation (`flat_g.zero_()`) and full `state_dict()` / `load_state_dict()` checkpoint compatibility without leaking temporary buffer keys (LSO-Eval Stage II compliant).
|
| 39 |
-
- **Triton JIT Pre-Warmup (`prewarm_jit: bool = True`)**:
|
| 40 |
-
* Added automatic initialization-time Triton JIT compilation (`_prewarm_all_configs`) using 64-element dummy buffers (`128–256 bytes` VRAM) with `lr=0.0` and `weight_decay=0.0` across both Step 1 (`pass1` + `pass2`) and Step 2+ (`fused_single_pass` + `post_step_ema`) paths with target `BLOCK_SIZE` specialization (`1024` or `2048`), eliminating first-step compilation stalls.
|
| 41 |
-
- **Bounded Bucket Chunking & Sequential In-Place Migration (`bucket_cap_mb: float = 500.0`)**:
|
| 42 |
-
* Bound contiguous bucket coalescing by an upper memory capacity ceiling $C_{\text{cap}} = 500\text{ MB}$, partitioning model parameters into ordered contiguous chunks $\mathcal{B}^{(1)}, \mathcal{B}^{(2)}, \dots, \mathcal{B}^{(K)}$ such that $\sum_{p \in \mathcal{B}^{(k)}} \text{bytes}(p) \le C_{\text{cap}}$ (with giant single layers $> C_{\text{cap}}$ isolated into individual buckets).
|
| 43 |
-
* Enforced sequential in-place migration with immediate parameter storage recycling (`del old_data`, `del old_grad`) and garbage collection, constraining peak transient memory allocation spikes during optimizer initialization to at most $\min(\text{model bytes}, C _ {\text{cap}})$.
|
| 44 |
-
|
| 45 |
-
---
|
| 46 |
-
|
| 47 |
-
## [1.5.4] - 2026-10-29
|
| 48 |
-
|
| 49 |
-
### Added
|
| 50 |
-
- **In-Container Native Bitwise-BF16 State Promotion (`dtype_mode == 2`, `master_weights="none"`)**:
|
| 51 |
-
* Promoted 16-bit optimizer state buffers ($m_t$ via stochastic rounding `f32_to_bf16_bits_sr` and $v_t$ via round-to-nearest-even `f32_to_bf16_bits_rne`) to native Bitwise-BF16 stored inside the existing 16-bit container during pure `FP16` execution, expanding state exponent dynamic range to 8 bits ($1.18 \times 10^{-38}$) with zero additional VRAM overhead.
|
| 52 |
-
|
| 53 |
-
### Changed
|
| 54 |
-
- **Unified Denominator Stability Constant ($\epsilon_{\text{eff}} = \epsilon = 10^{-8}$)**:
|
| 55 |
-
* Unified the effective denominator constant $\epsilon_{\text{eff}} = \epsilon = 10^{-8}$ across all precision modes and hardware backends (replacing the legacy `FP16` floor clamp $\max(\epsilon, 10^{-4})$ introduced in `1.1.5`), preventing update velocity attenuation and late-stage convergence loss resets when $\sqrt{v_t} \ll 10^{-4}$.
|
| 56 |
-
|
| 57 |
-
### Removed
|
| 58 |
-
- **Centered Defect Compensation (`centered_defect`, $\mu_d$)**:
|
| 59 |
-
* Removed the `centered_defect` hyperparameter and the global defect mean reduction $\mu_d = \frac{1}{N}\sum_{i=1}^N (g_{t,i} - m_{t-1,i})$ across both Dual-Buffer Convex Lookahead Momentum (`buffer=2` / CLM) and Single-Buffer Triangle-Defect Convex Lookahead Momentum (`buffer=1` / TD-CLM).
|
| 60 |
-
* Simplified the instantaneous gradient-momentum residual to purely local coordinate-wise differences:
|
| 61 |
-
|
| 62 |
-
$\displaystyle d_t^{(\text{B1})} = g_t - m_{t-1}, \qquad d_t^{(\text{B2})} = g_t - m_t$
|
| 63 |
-
|
| 64 |
-
* Eliminated the pre-step global reduction pass (`_lumina_diff_sum_kernel` in Triton JIT/AOT, `diff_sum.comp` in Vulkan SPIR-V, `reduce_diff_sum_simd` in x86\_64 AVX-512/AVX2, and slot 5 of the 6-element `ScratchBuffer`), removing cross-lane/cross-rank reduction latency while retaining the 6-element physical `ScratchBuffer` layout (`[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale, _reserved]`, 5 active slots + 1 reserved slot for ABI alignment).
|
| 65 |
-
|
| 66 |
-
---
|
| 67 |
-
|
| 68 |
-
## [1.5.3] - 2026-10-20
|
| 69 |
-
|
| 70 |
-
No versions were published between 1.3.3 and 1.5.3. This release notes all changes made since version 1.3.3.
|
| 71 |
-
|
| 72 |
-
### Added
|
| 73 |
-
- **Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM, `buffer=1`)**:
|
| 74 |
-
* Replaced the legacy 3-pass RMS single-buffer update with a single-pass formulation:
|
| 75 |
-
|
| 76 |
-
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \quad w_1 = \beta_1^2, \quad w_2 = 1 - \beta_1^2$
|
| 77 |
-
|
| 78 |
-
$\displaystyle \mathcal{E} _ t = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t^{(\text{B1})}| + \epsilon _ {\text{eff}}$
|
| 79 |
-
|
| 80 |
-
$\displaystyle u_t^{(\text{B1})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t} \in (-1, 1)$
|
| 81 |
-
|
| 82 |
-
* In 1.3.3, single-buffer mode required three sequential GPU kernel launches (`_lumina_v1_pass1_kernel`, `_lumina_v1_pass2_kernel`, `_lumina_v1_pass3_update_kernel`) and global atomic reductions across thread blocks to compute tensor-wide root-mean-square ($\text{rms} = \sqrt{\frac{1}{N}\sum \tilde{m} _ i^2 + \epsilon}$).
|
| 83 |
-
* TD-CLM replaces the tensor-wide reduction with coordinate-wise envelope bounding via the Triangle Inequality Envelope $\mathcal{E} _ t$. This reduces single-buffer execution to a single GPU pass and eliminates all $\tanh$ evaluations in the momentum path.
|
| 84 |
-
* Calibrated the defect penalty weight $\lambda$ to the symmetric spike-reversal equilibrium:
|
| 85 |
-
|
| 86 |
-
$\displaystyle \lambda^* = \frac{2(1 - \beta_1^2)^2}{4\beta_1^2 - 3}$
|
| 87 |
-
|
| 88 |
-
For the default $\beta_1 = 0.9$, $\lambda^* \approx 0.3008$, setting the default parameter to `lam=0.3`.
|
| 89 |
-
|
| 90 |
-
- **Padé [13/6] Rational Approximation (`tanh_pade_f32`)**:
|
| 91 |
-
* Implemented a rational Padé [13/6] polynomial for hyperbolic tangent calculations:
|
| 92 |
-
|
| 93 |
-
$\displaystyle \tanh _ {\text{Pade}}(x) = \frac{x(1 + c_1 x^2 + c_2 x^4 + c_3 x^6 + c_4 x^8 + c_5 x^{10} + c_6 x^{12})}{1 + d_1 x^2 + d_2 x^4 + d_3 x^6}$
|
| 94 |
-
|
| 95 |
-
* Evaluated over the clamped domain $x \in [-7.9988117, 7.9988117]$ with asymptotic saturation to $\pm 1.0$.
|
| 96 |
-
* In 1.3.3, `_triton_tanh_fast` used hardware-dependent SFU instructions (`tl.math.tanh`) or analytical sigmoid fallbacks (`2 / (1 + exp(-2x)) - 1`). Different GPU architectures (e.g. Turing, Ampere, Hopper) evaluate SFU transcendental functions with slightly different mantissa rounding, leading to bitwise divergence during distributed training.
|
| 97 |
-
* Padé [13/6] provides uniform precision with $\lVert \tanh _ {\text{Pade}}(x) - \tanh(x) \rVert _ \infty < 1.5 \times 10^{-6}$ across CPU Scalar, AVX2, AVX-512, Vulkan SPIR-V, and Triton GPU kernels.
|
| 98 |
-
|
| 99 |
-
- **Centered Innovation Defect (`centered_defect`)**:
|
| 100 |
-
* Added batch-mean centering for gradient innovation tracking (`centered_defect=False` by default):
|
| 101 |
-
|
| 102 |
-
$\displaystyle \mu_d = \frac{1}{N}\sum _ {i=1}^N (g _ {t,i} - m _ {t-1,i})$
|
| 103 |
-
|
| 104 |
-
$\displaystyle d_t^{(\text{B1})} = (g_t - m _ {t-1}) - \mu_d$
|
| 105 |
-
|
| 106 |
-
$\displaystyle d_t^{(\text{B2})} = (g_t - m_t) - \beta_1 \mu_d$
|
| 107 |
-
|
| 108 |
-
* In 1.3.3, uncentered innovation $g_t - m_t$ was used directly. On layers where gradients have a non-zero mean (such as normalization scales, biases, and token embeddings), the raw difference caused systematic drift in the second-moment accumulator. Centering eliminates this mean shift.
|
| 109 |
-
|
| 110 |
-
- **Subnormal-Safe IEEE-754 FP16 Stochastic Rounding**:
|
| 111 |
-
* Added `f32_to_f16_bits_rne` to handle IEEE-754 subnormal numbers ($|x| < 2^{-14}$) during FP16 stochastic rounding.
|
| 112 |
-
* In 1.3.3, stochastic rounding added 13-bit random noise and masked with `& -8192`. When a floating-point value entered the subnormal range, bitwise addition directly corrupted the exponent field (bit 10), causing values to flip between normal and subnormal representations or trigger hardware flush-to-zero traps.
|
| 113 |
-
* The updated routine checks for zero exponent, shifts the significand into the subnormal format, and applies round-to-nearest-even tie-breaking before bit truncation.
|
| 114 |
-
|
| 115 |
-
- **Multi-Backend Execution Engine**:
|
| 116 |
-
* Replaced the Python-only runtime with a multi-backend execution layer:
|
| 117 |
-
* **Rust SIMD (`luminav-simd`)**: AVX-512 (512-bit registers), AVX2 with FMA, portable scalar fallback, and Rayon parallel chunk scheduling (`ELEM_CHUNK_SIZE = 2,097,152`).
|
| 118 |
-
* **Vulkan UMA (`luminav-vulkan`)**: SPIR-V compute pipeline for cross-vendor GPUs (AMD, Intel, Apple Silicon via MoltenVK, and NVIDIA) with zero-copy host-visible memory buffers.
|
| 119 |
-
* **Triton AOT (`luminav-triton-aot`)**: C-ABI wrapper dispatching ahead-of-time precompiled kernels for 84 GPU architectures (Blackwell SM100/SM120, Hopper SM90, Ada SM89, Ampere SM80/SM86, Turing SM75, and AMD ROCm gfx90a/gfx942/gfx1100).
|
| 120 |
-
* **Standalone Runtime (`luminamoon.optimizer.standalone`)**: Pure NumPy and C-ABI execution that runs without requiring PyTorch.
|
| 121 |
-
|
| 122 |
-
### Changed
|
| 123 |
-
- **Parameter `alpha_ss`**: Removed. In 1.3.3, `alpha_ss=0.5` was used for soft-sign scaling $z / (1 + \alpha _ {\text{ss}} |z|)$ in the legacy single-buffer path. In 1.5.3, TD-CLM envelope normalization bounds updates directly to $(-1, 1)$, making soft-sign obsolete.
|
| 124 |
-
- **Parameter `lam`**: Added with default `0.3`. Governs the defect penalty weight in TD-CLM.
|
| 125 |
-
- **Parameter `centered_defect`**: Added with default `False`. Enables batch-mean centering of the innovation defect.
|
| 126 |
-
- **Parameter `bound_type`**: Default changed from `"radial"` (1.3.3) to `"coordinate"` (1.5.3). Coordinate-wise hyperbolic bounding $(B_i \tanh(\Delta \theta_i / B_i))$ evaluates locally on each parameter without computing a tensor-wide norm reduction.
|
| 127 |
-
- **Parameter `bound_ratio`**: Default changed from `0.03` to `0.05` to provide better update headroom on large model architectures.
|
| 128 |
-
- **Parameter `ema_decay`**: Default changed from static `0.8` to `"auto"`. Resolves to `0.80` for Dual-Buffer mode (`buffer=2`) and `0.95` for Single-Buffer mode (`buffer=1`).
|
| 129 |
-
- **Scratch Buffer**: Expanded from 5 floats `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` to 6 floats, adding `diff_sum` at index 5 to accumulate batch-mean defect statistics.
|
| 130 |
-
- **Cautious Scaling Floor**: Enforced `cautious_clamp_min=0.5` across all backends to cap maximum cautious energy scaling at $2.0\times$ ($2^1$).
|
| 131 |
-
|
| 132 |
-
### Fixed
|
| 133 |
-
- **Hardware Mantissa Divergence**: Fixed bitwise output differences across GPU families by replacing hardware SFU `tl.math.tanh` with Padé [13/6] rational approximation.
|
| 134 |
-
- **FP16 Subnormal Bit Corruption**: Fixed exponent corruption in subnormal FP16 stochastic rounding by separating exponent extraction from significand shift logic.
|
| 135 |
-
- **Single-Buffer Kernel Launch Latency**: Reduced single-buffer kernel dispatches from 3 passes to 1 pass, eliminating host-side synchronization bubbles and intermediate L2 atomic reduction overhead.
|
| 136 |
-
- **Non-Zero Mean Gradient Drift**: Resolved directional accumulator drift on bias and normalization layers via `centered_defect`.
|
| 137 |
-
|
| 138 |
-
### Parameter Comparison (1.3.3 vs 1.5.3)
|
| 139 |
-
|
| 140 |
-
| Parameter | Version 1.3.3 | Version 1.5.3 | Technical Notes |
|
| 141 |
-
| :--- | :--- | :--- | :--- |
|
| 142 |
-
| `params` | `params` | `params` | Iterable of parameters or parameter groups. |
|
| 143 |
-
| `lr` | `8e-4` | `8e-4` | Base learning rate. |
|
| 144 |
-
| `betas` | `(0.9, 0.999)` | `(0.9, 0.999)` | First and second moment exponential decay rates. |
|
| 145 |
-
| `eps` | `1e-8` | `1e-8` | Numerical stability floor. |
|
| 146 |
-
| `weight_decay` | `8e-2` | `8e-2` | Decoupled weight decay rate. |
|
| 147 |
-
| `tau` | `0.8` | `0.8` | Noise dispersion scale factor. |
|
| 148 |
-
| `alpha_ss` | `0.5` | *(removed)* | Removed; superseded by TD-CLM envelope normalization. |
|
| 149 |
-
| `lam` | *(none)* | `0.3` | Defect penalty weight in TD-CLM (calibrated to $\lambda^* \approx 0.3008$). |
|
| 150 |
-
| `cautious` | `True` | `True` | Directional verification gate ($u_i g_i > 0$). |
|
| 151 |
-
| `cautious_clamp_min` | `0.5` | `0.5` | Maximum cautious scale cap ($2.0\times$). |
|
| 152 |
-
| `buffer` | `2` | `2` | `1` = Single-Buffer (TD-CLM), `2` = Dual-Buffer (CLM). |
|
| 153 |
-
| `centered_defect` | *(none)* | `False` | Enables subtraction of batch-mean defect $\mu_d$. |
|
| 154 |
-
| `stochastic_rounding` | `True` | `True` | Bitwise stochastic rounding on FP16/BF16 weights. |
|
| 155 |
-
| `bound` | `True` | `True` | Enables update trust-region bounding. |
|
| 156 |
-
| `bound_type` | `"radial"` | `"coordinate"` | Default changed to coordinate-wise bounding (zero reduction overhead). |
|
| 157 |
-
| `bound_ratio` | `0.03` | `0.05` | Maximum step magnitude relative to parameter magnitude. |
|
| 158 |
-
| `master_weights` | `False` | `"none"` | `"none"`, `"semi"`, or `"full"`. |
|
| 159 |
-
| `execution` | `"auto"` | `"auto"` | Dynamic routing: SIMD, Vulkan, Triton, or Foreach. |
|
| 160 |
-
| `fused_single_pass` | `False` | `True` | Enables single-pass streaming with EMA scaling. |
|
| 161 |
-
| `ema_decay` | `0.8` | `"auto"` | Automatically sets `0.80` for dual-buffer, `0.95` for single-buffer. |
|
| 162 |
-
| `fsdp_sync` | `False` | `False` | Cross-rank collective all-reduce synchronization. |
|
| 163 |
-
| `seed` | `1337` | `1337` | PRNG seed for deterministic stochastic rounding. |
|
| 164 |
-
|
| 165 |
-
---
|
| 166 |
-
|
| 167 |
-
## [1.3.3] - 2026-10-06
|
| 168 |
-
|
| 169 |
-
### Fixed
|
| 170 |
-
* **Triton AST Compiler Early-Return Inlining Error:**
|
| 171 |
-
* Resolved fatal MLIR/CFG code generation failure (`at 35:4: _store_state_buffer(...) ^`) triggered by mid-branch `return` statements in `@triton.jit` helper functions (`_store_param` and `_store_state_buffer`).
|
| 172 |
-
* Refactored helper control flow into structured, single-exit `if ... elif ... else` branches, ensuring compliant JIT function inlining across all kernel entry points.
|
| 173 |
-
* **Bitcast Width Mismatch (Int64 Promotion in Stochastic Rounding):**
|
| 174 |
-
* Fixed implicit 64-bit integer promotion caused by Python integer literals (`0x9E3779B9`, `~0x1FFF`, `~0xFFFF`) inside Triton bitwise operations.
|
| 175 |
-
* Enforced strict 32-bit signed arithmetic via two's complement constants (`-1640531527`, `-2048173461`, `-1028477387`) with explicit `.to(tl.int32)` coercion, and standardized bitwise masks (`-65536` for BF16, `-8192` for FP16), ensuring type-safe 32-bit bitcasting (`tl.int32` to `tl.float32`).
|
| 176 |
-
* **JIT Kernel Call Keyword Arguments:**
|
| 177 |
-
* Replaced internal keyword argument invocations (`use_sr=False`) with standard positional boolean parameters inside Triton JIT kernels to eliminate compiler argument binding warnings across Triton runtime versions.
|
| 178 |
-
|
| 179 |
-
### Improved
|
| 180 |
-
* **Throttled Fallback Telemetry & Console De-Noising:**
|
| 181 |
-
* Added `self._warned_triton_failure` state tracking to prevent multi-line compiler AST dumps from flooding the console across every parameter tensor in a parameter group.
|
| 182 |
-
* Formatted fallback notifications into a single concise one-line `WARNING` log on initial encounter, routing full tracebacks to `DEBUG` level.
|
| 183 |
-
|
| 184 |
-
### Verified
|
| 185 |
-
* **End-to-End JIT Execution on NVIDIA Architectures (Tesla T4):**
|
| 186 |
-
* Validated JIT execution across both Dual-Pass (`fused_single_pass=False`) and Fused Single-Pass (`fused_single_pass=True`) modes in native FP16.
|
| 187 |
-
* Confirmed non-zero weight update dynamics ($|\Delta W| > 0$) without gradient underflow or weight freezing in pure master-free training for Version 1.3.3.
|
| 188 |
-
|
| 189 |
-
---
|
| 190 |
-
|
| 191 |
-
## [1.3.2] - 2026-10-06
|
| 192 |
-
|
| 193 |
-
### Added
|
| 194 |
-
- **Distributed FSDP Collective Synchronization (`fsdp_sync`)**:
|
| 195 |
-
- Introduced the `fsdp_sync: bool = False` configuration flag on optimizer initialization, backed by automated runtime guarding via `torch.distributed.is_initialized()`.
|
| 196 |
-
- Integrated cross-GPU collective communications (`torch.distributed.all_reduce(..., op=torch.distributed.ReduceOp.SUM)`) for global reduction metrics (`mask_sum`, `u_sq_sum`, and `p_sq_sum`) across Triton kernels, single-step execution, and vectorized foreach loops.
|
| 197 |
-
- Maintains bit-exact scalar consistency for `m_bar`, `cautious_scale`, and `bound_scale` across distributed parameter shards during large-scale pretraining under PyTorch FSDP (Fully Sharded Data Parallel) and DeepSpeed ZeRO-3.
|
| 198 |
-
- **Dedicated Low-Precision Stochastic Quantization Routine (`_sr_quantize`)**:
|
| 199 |
-
- Factored out bitwise stochastic rounding into a dedicated, modular helper function: `_sr_quantize(target, val_f32, use_sr, generator)`.
|
| 200 |
-
- Enabled native stochastic rounding on half-precision momentum states (`exp_avg` in BF16/FP16) across all PyTorch CPU/CUDA fallback paths (`_single_step_v2`, `_foreach_step_v2`, `_single_step_v1`, and `_foreach_step_v1`), systematically eliminating gradient stagnation induced by fractional bit truncation (LSB underflow).
|
| 201 |
-
- **Persistent Contiguous Buffer Caching**:
|
| 202 |
-
- Introduced persistent state cache tensors within `self.state[p]` (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, and `mp_contig`) to stage strided or sliced parameters before kernel launch.
|
| 203 |
-
- Eliminates dynamic memory allocations (`.contiguous()`) during iterative training loops, preserving PyTorch VRAM caching allocator stability and preventing CUDA Graph capture invalidation.
|
| 204 |
-
- **Zero-Copy Direct Tensor Aliasing for FP32 Master Weights**:
|
| 205 |
-
- When parameter tensors are natively `torch.float32`, `state["master_param"]` now directly aliases the parameter tensor pointer (`state["master_param"] = p`), avoiding additional master weight VRAM overhead for FP32 layers (0 bytes auxiliary memory footprint).
|
| 206 |
-
- **Checkpoint Sanitizer via State Dict Filter (`state_dict`)**:
|
| 207 |
-
- Overrode `state_dict()` to automatically purge temporary contiguous cache buffers (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, `mp_contig`), ensuring on-disk checkpoint payloads remain clean, compact, and fully backward-compatible.
|
| 208 |
-
- **Defensive Device Pipeline Flush on Kernel Fault (`torch.cuda.synchronize`)**:
|
| 209 |
-
- Embedded explicit hardware device synchronization (`torch.cuda.synchronize(p.device)`) within Triton exception recovery handlers prior to PyTorch fallback execution, preventing asynchronous CUDA error cascades into subsequent tensor operations.
|
| 210 |
-
|
| 211 |
-
### Changed
|
| 212 |
-
- **High-Entropy Bit-Avalanche PRNG Hash Function (`_prng`)**:
|
| 213 |
-
- Replaced the standard Linear Congruential Generator (LCG) with a MurmurHash3 / SplitMix32 integer bit-avalanche hashing routine:
|
| 214 |
-
```python
|
| 215 |
-
z = idx + seed + 0x9E3779B9
|
| 216 |
-
z = (z ^ (z >> 16)) * 0x85EBCA6B
|
| 217 |
-
z = (z ^ (z >> 13)) * 0xC2B2AE35
|
| 218 |
-
z = z ^ (z >> 16)
|
| 219 |
-
```
|
| 220 |
-
- Delivers uniform pseudo-random noise distributions with zero low-order bit periodicity, significantly refining stochastic rounding fidelity on BF16/FP16 parameters.
|
| 221 |
-
- **Memory-Contiguous Scratch Buffer Realignment for v1 Triton Kernels**:
|
| 222 |
-
- Re-indexed reduction buffer offsets in Triton v1 kernels:
|
| 223 |
-
- Pass 1 metrics (`[rms_sum, p_sq_sum]`) mapped to contiguous indices `[0, 1]`.
|
| 224 |
-
- Pass 2 metrics (`[mask_sum, u_sq_sum]`) mapped to contiguous indices `[2, 3]`.
|
| 225 |
-
- Enables vectorized contiguous slice all-reduces (`scratch_param[:2]` and `scratch_param[2:4]`) under FSDP without requiring non-contiguous memory gather/scatter overhead.
|
| 226 |
-
- **Multi-GPU Device Index Resolution in RNG Dispatcher (`_get_generator`)**:
|
| 227 |
-
- Fixed device index resolution when `device` is passed as a generic `torch.device("cuda")` without an explicit index by defaulting to `torch.cuda.current_device()`, avoiding cross-GPU generator key collisions during multi-GPU distributed runs.
|
| 228 |
-
- **Conditional Atomic Reduction Pruning in v2 Pass 1 Kernel**:
|
| 229 |
-
- Guarded `mask_sum` atomic accumulation in `_lumina_v2_pass1_kernel` with `if cautious:`, bypassing CTA reductions and L2 atomic additions when `cautious=False`.
|
| 230 |
-
- **Expanded Master Weight Precision Configuration Aliases**:
|
| 231 |
-
- Added the `"half"` alias for `"semi"` mode alongside `"fp32"` and `"masterweight"` aliases for `"full"` mode.
|
| 232 |
-
- **Metadata & Documentation Header Update**:
|
| 233 |
-
- Bumped specification version to `1.3.2`.
|
| 234 |
-
|
| 235 |
-
### Fixed
|
| 236 |
-
- **Singular Zero-Division & Step-Zero Bias Correction (`safe_base_step`)**:
|
| 237 |
-
- Introduced `safe_base_step = max(base_step, 1)` and `bc2_base = max(1.0 - (beta2 ** safe_base_step), 1e-15)` across Triton, single-step, and foreach loops.
|
| 238 |
-
- Fully resolves potential `ZeroDivisionError` and NaN blowups in `c1` / `c2` computations when steps evaluate at `step == 0`.
|
| 239 |
-
- **Numerical Overflow Clamp in Sigmoid-Based Tanh Fallback**:
|
| 240 |
-
- Applied register clamp bounds `x_clamped = tl.maximum(tl.minimum(x, 20.0), -20.0)` in analytical fallback `_triton_tanh_fast`, preventing `tl.exp(-2.0 * x)` from overflowing to `inf` or `nan` on devices lacking hardware SFU tanh instructions.
|
| 241 |
-
- **Missing Argument Forwarding in PyTorch Single-Step v1**:
|
| 242 |
-
- Corrected `_single_step_v1` method signature and call-sites to accept and propagate `scratches`, eliminating runtime `TypeError` during fallback invocation.
|
| 243 |
-
- **Multi-Rank Synchronization Skew in Sharded Parameter Bounding**:
|
| 244 |
-
- Aligned tensor norms and radial bounding factors across sharded ranks via synchronized global scalar reductions, preventing parameter update drift across distributed ranks.
|
| 245 |
-
|
| 246 |
-
### Performance & Engine Refactoring
|
| 247 |
-
- **Zero Dynamic VRAM Allocation on Strided / Sliced Tensors**:
|
| 248 |
-
- Persistent pre-allocated contiguous staging buffers avoid PyTorch caching allocator churn and eliminate CUDA Graph memory address invalidations.
|
| 249 |
-
- Preserves deterministic memory footprints across long pre-training runs.
|
| 250 |
-
- **Optimized Distributed Collective Bandwidth**:
|
| 251 |
-
- Grouping scalar reductions into contiguous vector slices compresses distributed synchronization overhead into a single collective kernel launch per pass.
|
| 252 |
-
|
| 253 |
-
---
|
| 254 |
-
|
| 255 |
-
## [1.3.0] - 2026-09-23
|
| 256 |
-
|
| 257 |
-
### Added
|
| 258 |
-
- **Fused Single-Pass Kernel with Post-Step EMA (`fused_single_pass`, `ema_decay`)**:
|
| 259 |
-
- Introduced `_lumina_v2_fused_single_pass_kernel` coupled with an asynchronous CTA micro-kernel `_lumina_v2_post_step_ema_kernel[(1,)]`.
|
| 260 |
-
- When `fused_single_pass=True`, avoids two-pass global reductions by leveraging running Exponential Moving Average (EMA) estimates of `cautious_scale` ($1 / \bar{M}$) and `bound_scale` ($\tanh(r) / r$).
|
| 261 |
-
- Unifies gradient filtering, momentum accumulation, variance dispersion, cautious masking, radial bounding, decoupled weight decay, stochastic rounding, and block partial reductions into a **single unified VRAM-resident pass**, reducing global GPU memory bandwidth traffic per optimizer step.
|
| 262 |
-
- **Hybrid Master Weight Precision Architecture (`master_weights`)**:
|
| 263 |
-
- Added multi-tier master weight flexibility via `master_weights: Union[bool, str] = False`:
|
| 264 |
-
- `"none"` / `False` *(Default)*: Pure master-free low-precision training leveraging on-chip bitwise stochastic rounding directly on native half-precision weights.
|
| 265 |
-
- `"semi"` / `"hybrid"` / `"half"`: 32-bit FP32 master parameters (`state["master_param"]`) coupled with 16-bit low-memory optimizer states (`exp_avg`, `exp_avg_sq` in BF16/FP16), saving ~50% optimizer state VRAM compared to traditional FP32 setups while maintaining full FP32 weight accumulation accuracy.
|
| 266 |
-
- `"full"` / `"fp32"` / `"masterweight"`: Traditional full FP32 master parameters with full FP32 optimizer states for standard baseline comparisons.
|
| 267 |
-
- **In-Kernel In-Flight NaN / Inf Gradient Sanitization**:
|
| 268 |
-
- Embedded register-level defensive sanitization across all Triton GPU kernels: `is_finite_g = (g == g) & (g < 1e38) & (g > -1e38); g = tl.where(is_finite_g, g, 0.0)`.
|
| 269 |
-
- Replaces non-finite gradients and subnormal corruptions with zero in registers before they can contaminate momentum (`exp_avg`) or variance (`exp_avg_sq`) state buffers.
|
| 270 |
-
- **Low-Precision State Buffer Serialization (`_store_state_buffer`)**:
|
| 271 |
-
- Added a dedicated Triton state serialization routine supporting FP32, BF16, and FP16 state representations with optional bitwise stochastic rounding applied directly to momentum buffers.
|
| 272 |
-
- **Multi-Device & Cross-Accelerator Hardware Context Isolation (`_get_device_context`)**:
|
| 273 |
-
- Unified device stream scoping supporting NVIDIA CUDA (`torch.cuda`), Intel XPU (`torch.xpu`), and modern PyTorch 2.x unified accelerator interfaces (`torch.accelerator.device`), eliminating multi-GPU stream hijacking and cross-device race conditions.
|
| 274 |
-
- **Strict Determinism Auto-Routing**:
|
| 275 |
-
- Automatically queries `torch.are_deterministic_algorithms_enabled()`. When enabled under `execution="auto"`, dynamically routes execution to C++ vectorized `foreach` to preserve bitwise cross-run reproducibility, circumventing non-deterministic floating-point hardware atomic reordering.
|
| 276 |
-
- **Granular Fault Recovery with State Guarding (`completed_indices`)**:
|
| 277 |
-
- Implemented per-parameter completion tracking in `_dispatch_triton`. If a kernel encounters an unexpected runtime fault, completed parameters are preserved while uncompleted ones fall back cleanly to `foreach`.
|
| 278 |
-
- Added `_single_step_v2_apply_param` and `_single_step_v1_apply_param` to safely finalize parameter updates if Pass 1 had already committed state transitions, strictly preventing double-update state corruption.
|
| 279 |
-
|
| 280 |
-
### Changed
|
| 281 |
-
- **Per-Parameter Persistent Scratch Tensors (Elimination of Global Slicing)**:
|
| 282 |
-
- Replaced the global dynamic slice allocator (`self._scratch_tensors[dev][:count]`) with isolated per-parameter persistent state allocations (`state["scratch"] = torch.zeros((5,), device=p.device, dtype=torch.float32)`).
|
| 283 |
-
- Memory pointers for `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` remain completely invariant throughout the entire training lifecycle, preserving pointer stability for **CUDA Graph Capture** (`torch.accelerator.Graph`) and TorchInductor AOT compilation without memory aliasing.
|
| 284 |
-
- **Analytically Exact Nesterov Bias Correction ($\beta_1^{\text{step} + 1}$)**:
|
| 285 |
-
- Corrected first-moment bias correction from the classical Adam formulation ($1.0 - \beta_1^{\text{step}}$) to the exact analytical expectation of the Nesterov blended momentum: `bc1_nes = 1.0 - beta1^(step + 1)`. Removes residual momentum bias during early warmup iterations.
|
| 286 |
-
- **Adaptive CTA Tile Sizing and Dynamic Thread Warp Scaling**:
|
| 287 |
-
- Introduced hardware-aware CTA tile scaling based on parameter volume: parameters with $\ge 2,097,152$ elements (2M+) dynamically configure `BLOCK_SIZE = 2048` and `num_warps = 8` (versus `BLOCK_SIZE = 1024`, `num_warps = 4` for smaller tensors).
|
| 288 |
-
- Cuts total thread-block grid counts and L2 cache atomic addition pressure by 50% on large MLP, Attention Projection, and Vocabulary layers.
|
| 289 |
-
- **Hardware-Native Tanh Instruction Dispatch (`tl.math.tanh` / `tl.tanh`)**:
|
| 290 |
-
- Upgraded `_triton_tanh_fast` to dynamically inspect compiler capabilities, binding directly to native PTX Special Function Unit (SFU) instructions (`tanh.approx.f32`) on modern Triton compilers while retaining analytical sigmoid fallbacks for legacy environments.
|
| 291 |
-
- **PRNG Decorrelation between Momentum and Parameter Updates**:
|
| 292 |
-
- Separated RNG seed streams using bitwise alternating mask shifts: `seed_p = (seed + 0x55555555) & 0x7FFFFFFF`, mathematically eliminating cross-coupling noise correlation between state buffer rounding and parameter update rounding.
|
| 293 |
-
- **Bumped specification version metadata to 1.3.0** across `luminav.py`.
|
| 294 |
-
|
| 295 |
-
### Fixed
|
| 296 |
-
- **Floating-Point Cancellation in Radial Bounding ($r \to 0$)**:
|
| 297 |
-
- Fixed precision jitter and roundoff degradation near zero by adding an asymptotic Taylor-series guard across Triton and PyTorch bounding engines: `torch.where(r < 1e-4, 1.0, tanh(r) / r)`.
|
| 298 |
-
- **Artificial Denominator Dilation in Dispersion Scale ($\sigma$)**:
|
| 299 |
-
- Replaced additive epsilon inflation (`sigma + 1e-6`) with a non-distorting floor clamp (`tl.maximum(sigma, 1e-12)`), preventing artificial dampening of subtle gradient signals.
|
| 300 |
-
- **Singular Zero-Division in Parameter Norm Clamping**:
|
| 301 |
-
- Replaced `norm_p + 1e-6` with `torch.clamp_min(norm_p, 1.0)` / `tl.maximum(norm_p, 1.0)`, maintaining numerical stability even when parameters are initialized near zero.
|
| 302 |
-
- **Transient Memory Pool Spikes during PyTorch Fallback Stochastic Rounding**:
|
| 303 |
-
- Refactored `_sr_update` to stream stochastic noise generation in bounded chunks of 1,048,576 elements (~4 MB chunk size), eliminating multi-hundred megabyte temporary int32 tensor spikes during fallback execution on large parameter matrices.
|
| 304 |
-
- **Non-Contiguous Buffer Safety & Strided Memory Copy**:
|
| 305 |
-
- Added strict contiguous layout verification for parameters, states, and master weights with in-place synchronization (`p.copy_(p_contig)`), guarded by a one-time developer warning.
|
| 306 |
-
|
| 307 |
-
### Performance & Engine Refactoring
|
| 308 |
-
- **~50% Global Memory Bandwidth Reduction via Fused Single-Pass Mode**:
|
| 309 |
-
- By unifying parameter loading, gradient evaluation, state transformation, and bounding into one GPU pass under `fused_single_pass=True`, VRAM read/write cycles are halved for cautious and radially bounded steps.
|
| 310 |
-
- **Sub-Microsecond L2 Hardware Atomic Reduction Architecture**:
|
| 311 |
-
- Formally documented the hybrid Intra-Block Tree Reduction + Inter-Block Hardware Atomic pattern. With `BLOCK_SIZE=2048`, a 10M-parameter tensor issues only 4,883 atomic additions directly into GPU L2 cache crossbars, executing in < 0.5 µs and strictly outperforming multi-tier cascaded reduction micro-kernels by eliminating 3–5 µs driver dispatch bubbles.
|
| 312 |
-
- **Static Buffer Address Invariance for CUDA Graphs & TorchInductor AOT**:
|
| 313 |
-
- Persistent 5-slot state scratch buffers eliminate all dynamic slicing and memory reallocations during optimization loops, preserving pointer address invariance for CUDA Graph replay.
|
| 314 |
-
|
| 315 |
-
---
|
| 316 |
-
|
| 317 |
-
## [1.2.1] - 2026-09-17
|
| 318 |
-
|
| 319 |
-
### Added
|
| 320 |
-
- **In-Kernel GPU Parameter Norm Reduction (`p_sq_sum_ptr`)**: Fused parameter Euclidean norm squared $\sum(p_i^2)$ calculation directly into Pass 1 Triton reduction kernels (`_lumina_v2_pass1_kernel` and `_lumina_v1_pass1_kernel`). Pass 2 and Pass 3 now load and resolve `norm_p` (`tl.sqrt(tl.maximum(p_sq_sum, 0.0))`) entirely inside GPU SRAM/registers without host intervention.
|
| 321 |
-
- **Stateless CPU Integer Multiplicative PRNG Hashing**: Replaced dynamic GPU RNG tensor allocation with a pure CPU integer hash using Knuth's 32-bit golden ratio constant: `(step * 0x9E3779B9 + i * 10007) & 0x7FFFFFFF`. Generates uncorrelated 31-bit integer seeds per layer with zero GPU kernel launch overhead and zero PCIe host-device latency.
|
| 322 |
-
- **Isolated Generator Cache for Fallback Engine (`self._generators`)**: Added an internal per-device `torch.Generator` cache, ensuring stochastic rounding in `_sr_update` operates in complete isolation from PyTorch's default CUDA generator state.
|
| 323 |
-
|
| 324 |
-
### Changed
|
| 325 |
-
- **Consolidated Scratch Buffer Zeroing**: Removed redundant per-parameter slice zeroing inside parameter loops. Buffer zeroing is now batched into a single consolidated `scratch.zero_()` call per step inside `_get_scratch_buffer`.
|
| 326 |
-
- **Scratch Buffer Stride Expansion**:
|
| 327 |
-
- `buffer=2`: Expanded stride from 2×N to 3×N elements per parameter to host `[mask_sum, u_sq_sum, p_sq_sum]`.
|
| 328 |
-
- `buffer=1`: Expanded stride from 3×N to 4×N elements per parameter to host `[rms_sum, mask_sum, u_sq_sum, p_sq_sum]`.
|
| 329 |
-
- **Bumped specification version metadata to 1.2.1**.
|
| 330 |
-
|
| 331 |
-
### Fixed
|
| 332 |
-
- **Host-Device Synchronization & Pipeline Stalls (`.item()` Bottleneck)**: Removed `norm_p = float(p_contig.float().norm().item())` and `seed = torch.randint(...).item()` from Triton step pipelines. Eliminated 200–400 synchronous PCIe roundtrips and GPU pipeline stalls per step on deep architectures (e.g., 28-layer Transformers), restoring full asynchronous GPU compute utilization.
|
| 333 |
-
- **CUDA Graphs & `torch.compile` Graph Breaks**: Fixed fatal graph capture crashes and TorchDynamo graph breaks caused by blocking host-device synchronization during radial bounding and seed generation. The optimizer step is now compatible with CUDA Graphs (`torch.accelerator.Graph`) and TorchInductor compilation.
|
| 334 |
-
- **Transient VRAM Allocator Thrashing**: Eliminated repeated temporary FP32 parameter cloning (`p_contig.float()`) during norm evaluation, preventing memory pool fragmentation in tight VRAM regimes.
|
| 335 |
-
- **Global RNG Pollution & Determinism Compliance**: Fixed global RNG state mutation in fallback stochastic rounding. Solved reproducibility drift in upstream Dropout, DataLoader shuffling, and RLHF reward modeling, ensuring strict adherence to PyTorch 2.10+ `torch.use_deterministic_algorithms(True)`.
|
| 336 |
-
|
| 337 |
-
### Performance & Engine Refactoring
|
| 338 |
-
- **Batch Kernel Launch Overhead Elimination**: Removed up to 400–800 individual `cudaMemsetAsync` micro-calls per step previously triggered by per-slice `.zero_()` invocations on 1-element scratch tensors.
|
| 339 |
-
- **Single-Pass Bypass for Coordinate Bounding**: Streamlined `_triton_step_v2` to immediately bypass two-pass reductions and invoke `_lumina_v2_single_pass_kernel` when `cautious=False` and coordinate bounding (`bound_mode == 2`) is selected.
|
| 340 |
-
|
| 341 |
-
---
|
| 342 |
-
|
| 343 |
-
## [1.2.0] - 2026-09-17
|
| 344 |
-
|
| 345 |
-
### Added
|
| 346 |
-
- **Automatic Dynamic FP16 Cliff Governor:** Built-in asymptotic boundary governor (`_triton_tanh_fast` in GPU register and PyTorch fallback) that automatically activates when `p.dtype == torch.float16`. Prevents unclipped steps from pushing parameters, activations, and residual streams into the IEEE-754 FP16 overflow cliff (> 65,504). Remains completely dormant (zero overhead) on `bfloat16` (TPU / Ampere+) and `float32`.
|
| 347 |
-
- **Smooth Asymptotic Parameter Bounding (`bound`, `bound_type`, `bound_ratio`):** Added non-intrusive step bounding with two mathematically continuous formulations:
|
| 348 |
-
- `"radial"`: Direction-Preserving Radial Squashing (`update * tanh(r) / r`) preserving gradient direction (Cosine Similarity = 1.000).
|
| 349 |
-
- `"coordinate"`: Dual-Hyperbolic Coordinate Squashing for fine-grained per-element damping.
|
| 350 |
-
- Configurable via constructor: `bound: bool = True`, `bound_type: str = "radial"`, `bound_ratio: float = 0.03`.
|
| 351 |
-
|
| 352 |
-
### Changed
|
| 353 |
-
- **Increased Default `cautious_clamp_min` from `0.2` to `0.5`:** Enforces an exact power-of-two maximum energy scaling factor (2.00x, $2^1$). Eliminates floating-point mantissa roundoff distortion in IEEE-754 hardware.
|
| 354 |
-
- Bumped specification version metadata to `1.2.0`.
|
| 355 |
-
|
| 356 |
-
### Fixed
|
| 357 |
-
* **FP16 Step Divergence & NaN Overflow at High Learning Rates:** Solved step divergence and subsequent NaN crashes when training deep 28-layer Transformer architectures (e.g., Qwen3-0.6B) in pure FP16 at nominal learning rates (`lr = 8e-4`).
|
| 358 |
-
* **Sparse Embedding & LM-Head Amplification:** Mitigated 5x over-amplification on sparse vocabulary gradients where active token counts are much smaller than total tensor parameters ($N \gg N_{\text{active}}$).
|
| 359 |
-
* **Verified stable 1,000-step pretraining from scratch on Qwen3-0.6B** (seq length 512, pure FP16, `lr = 8e-4`) on Nvidia Tesla T4 with zero gradient clipping and zero LR scheduler (loss dropped from 12.09 down to ~5.7 with zero NaNs).
|
| 360 |
-
* **Weight Decay Numerical Parity on Fallback Engine:** Resolved a silent precision-stagnation issue in `_single_step` and `_foreach_step`. Previously, decoupled weight decay (`p.mul_(1.0 - lr * weight_decay)`) was executed directly on native FP16/BF16 tensors prior to `_sr_update`. Due to IEEE-754 Round-to-Nearest (RTN) limits, `(1 - lr * weight_decay) ≈ 0.999936` rounded back to `1.0`, causing weight decay to freeze on half-precision parameters. Weight decay is now unified into FP32 registers inside `_sr_update` alongside stochastic rounding, matching the numerical behavior with the Triton kernel.
|
| 361 |
-
* **Fallback Foreach Indentation & Scope Alignment:** Corrected tensor iteration scope and indentation for `torch._foreach_add_` in non-SR fallback branches.
|
| 362 |
-
* **In-Place GPU Clamping in `_apply_bound`:** Replaced dynamic GPU scalar allocations (`torch.tensor(1.5e-4, device=p.device)`) with native `torch.clamp_min`, eliminating transient CUDA malloc spikes per update step.
|
| 363 |
-
|
| 364 |
-
### Performance & Engine Refactoring
|
| 365 |
-
* **Unified Bound Execution Pipeline:** Added an immediate short-circuit guard (`if lr == 0.0: return update`) and streamlined the execution flow: intermediate updates are scaled in FP32 (`step_val = lr * u_float`), passed through radial/coordinate bounding, chained cleanly into the FP16 Cliff Governor, and returned in a single normalized step.
|
| 366 |
-
|
| 367 |
-
---
|
| 368 |
-
|
| 369 |
-
## [1.1.5] - 2026-09-15
|
| 370 |
-
|
| 371 |
-
### Added
|
| 372 |
-
- **Native Hardware Capability Detection (`_supports_native_bf16`):** Added automatic device capability detection via `torch.cuda.is_bf16_supported()` to identify Ampere SM80+ architectures and streamline native execution paths.
|
| 373 |
-
- **Hugging Face Repository Reference:** Added official Hugging Face repository link in module docstrings.
|
| 374 |
-
|
| 375 |
-
### Changed
|
| 376 |
-
- **Dynamic Epsilon Clamping for FP16:** Dynamically floors effective epsilon to `max(eps, 1e-4)` when parameters or states are in `torch.float16`. Prevents bias-corrected scale factor `c2` from underflowing into IEEE-754 subnormal/zero limits on step 1.
|
| 377 |
-
- Bumped specification version to `1.1.5`.
|
| 378 |
-
|
| 379 |
-
### Fixed
|
| 380 |
-
- **FP16 Step-1 `NaN` from Epsilon Underflow:** Fixed `0.0 / 0.0 = NaN` division occurring on initial training steps when parameters receive zero gradients (e.g., unselected vocabulary embeddings, padding tokens, or dropout paths).
|
| 381 |
-
- **Second-Moment Variance Saturation (`+inf`):** Added safe upper-bound register clamping (`tl.minimum(v_new, 65000.0)` in Triton kernels and `.clamp_(0.0, 65000.0)` in PyTorch C++ foreach/single loops). Prevents unclipped initial pretraining gradient surges ($|g| > 256$) from overflowing the FP16 maximum dynamic range (65,504) into irreversible `+inf`.
|
| 382 |
-
- **Zero-Sigma Division Guard:** Injected explicit `sigma + 1e-6` denominator guards across all Triton kernel passes and PyTorch update steps to ensure numerical safety during cold-start iterations.
|
| 383 |
-
- Verified stable 100-step pretraining from scratch on `SmolLM-135M` using Wikimedia Wikipedia in pure FP16 on Nvidia Tesla T4 GPU with zero gradient clipping.
|
| 384 |
-
|
| 385 |
-
---
|
| 386 |
-
|
| 387 |
-
## [1.1.0] - 2026-09-11
|
| 388 |
-
|
| 389 |
-
### Added
|
| 390 |
-
- **Bitwise Stochastic Rounding (SR):** Integrated on-chip PRNG integer bit-manipulation hash in Triton kernels and vectorized PyTorch fallback for native FP16/BF16 training without master weights.
|
| 391 |
-
- Unified Triton store routine introducing compile-time branching (`dtype_mode` / `use_sr: tl.constexpr`) with explicit safe casting for FP16 and BF16 pointers.
|
| 392 |
-
- Vectorized fallback improvements utilizing C++ multi-tensor operations when stochastic rounding is disabled or tensors are FP32.
|
| 393 |
-
- State buffer contiguity validation (`exp_avg_contig`, `exp_avg_sq_contig`) ensuring safe linear memory addressing before kernel dispatch.
|
| 394 |
-
- Declarative configuration file (`config.json`) defining hyperparameters and optimizer presets.
|
| 395 |
-
|
| 396 |
-
### Changed
|
| 397 |
-
- Refactored PRNG integer arithmetic to strictly stay within signed 32-bit integer limits ($< 2^{31} - 1$), eliminating JIT compilation failures on newer Triton releases.
|
| 398 |
-
- State buffers (`exp_avg`, `exp_avg_sq`) now initialize with strict contiguous memory format.
|
| 399 |
-
|
| 400 |
-
### Fixed
|
| 401 |
-
- Fixed `ValueError: Scalar out of range for type int32` and Triton LLVM compile errors during unsigned 32-bit bitmask operations.
|
| 402 |
-
- Resolved kernel fallback hangs and compilation overhead on CUDA devices.
|
| 403 |
-
|
| 404 |
-
---
|
| 405 |
-
|
| 406 |
-
## [1.0.2] - 2026-09-08
|
| 407 |
-
|
| 408 |
-
### Added
|
| 409 |
-
- Official Apache-2.0 license notice and copyright headers.
|
| 410 |
-
- Standardized package exports (`__all__`, `__version__`, `__author__`, `__license__`).
|
| 411 |
-
|
| 412 |
-
---
|
| 413 |
-
|
| 414 |
-
## [1.0.1] - 2026-08-31
|
| 415 |
-
|
| 416 |
-
### Fixed
|
| 417 |
-
- **Memory Synchronization Fix (`423d171`):** Fixed a critical bug where non-contiguous parameter tensors (e.g., transposed linear layers or sliced weights) were updated on temporary contiguous copies without reflecting changes back to model weights. Added `p.copy_(p_contig)` after Triton execution.
|
| 418 |
-
|
| 419 |
-
---
|
| 420 |
-
|
| 421 |
-
## [1.0.0] - 2026-08-30
|
| 422 |
-
|
| 423 |
-
### Added
|
| 424 |
-
- Initial release of the LuminaV optimizer.
|
| 425 |
-
- **Zero Master-Weight Copies:** Direct parameter updates in native half-precision without allocating 4-byte FP32 master weights.
|
| 426 |
-
- **Hyperbolic Tangent (`tanh`) Bounding Envelope:** Projects normalized momentum ratio into `(-1.0, 1.0)` to eliminate exploding gradient shocks.
|
| 427 |
-
- **Directional Alignment Gate:** Cautious update masking that zeroes out momentum steps opposing mini-batch gradient directions.
|
| 428 |
-
- **Central Innovation Variance:** Noise dispersion tracking $(g_t - m_t)^2$ decoupling mini-batch turbulence from directional descent.
|
| 429 |
-
- Dual-buffer mode (`buffer=2`, LuminaV-2B) and single-buffer mode (`buffer=1`, LuminaV-1).
|
| 430 |
-
- Custom fused OpenAI Triton kernels with automated fallback execution.
|
|
|
|
| 1 |
+
# LuminaV Optimizer Algorithm Changelog
|
| 2 |
+
|
| 3 |
+
All notable changes to the LuminaV Optimizer algorithm are documented in this file.
|
| 4 |
+
|
| 5 |
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
+
|
| 7 |
+
* [Software Package Changelog (`luminamoon`)](SOFTWARE_CHANGELOG.md)
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## [1.6.0] - 2026-11-03
|
| 12 |
+
|
| 13 |
+
### Added
|
| 14 |
+
- **T-Family (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 15 |
+
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$, computing the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounding the lookahead update $\tilde{m}_t$ via the **Triangle Inequality Envelope** $\mathcal{E}_t^{(\text{T2})}$:
|
| 16 |
+
|
| 17 |
+
$\displaystyle d_t = g_t - m _ {t-1}, \qquad v_t = \beta_2 v _ {t-1} + (1 - \beta_2) d_t^2$
|
| 18 |
+
|
| 19 |
+
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \qquad w_1 = \beta_1^2, \qquad w_2 = 1 - \beta_1^2$
|
| 20 |
+
|
| 21 |
+
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 22 |
+
|
| 23 |
+
By the Triangle Inequality ($|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$), $|u_t^{(\text{T2})}| < 1$ is guaranteed without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, and $0.5\lambda \sigma_t$ provides belief-variance damping.
|
| 24 |
+
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Combines the single-buffer **Triangle-Defect** denominator $\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ (using residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below, reducing optimizer state memory by 50% via the Triangle Inequality.
|
| 25 |
+
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas:
|
| 26 |
+
|
| 27 |
+
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 28 |
+
* Keeps 100% backward compatibility and identical outputs for existing modes (`buffer=1`, `buffer=2`, and `version="1.5.5"`).
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## [1.5.5] - 2026-10-29
|
| 33 |
+
|
| 34 |
+
### Added
|
| 35 |
+
- **Contiguous Bucket Fusion (`fuse_tensors: bool = False`)**:
|
| 36 |
+
* Added opt-in contiguous parameter and state coalescing (`_build_contiguous_buckets`) that groups parameters within each `(param_group, device, dtype)` domain $\mathcal{B}_{g,d,\tau}$ into contiguous 1D storage (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) and rebinds `p.data`, `p.grad`, and `self.state[p]` entries as zero-copy views (`flat_*[offset : offset + n].view(p.shape)`).
|
| 37 |
+
* Eliminates per-tensor CPU-to-GPU kernel launch latency on fragmented vision and edge architectures by executing an entire bucket in a single fused kernel dispatch while computing `cautious_scale` and `radial` trust bounds across the bucket domain $\mathcal{B}_{g,d,\tau}$.
|
| 38 |
+
* Added in-place `zero_grad()` view preservation (`flat_g.zero_()`) and full `state_dict()` / `load_state_dict()` checkpoint compatibility without leaking temporary buffer keys (LSO-Eval Stage II compliant).
|
| 39 |
+
- **Triton JIT Pre-Warmup (`prewarm_jit: bool = True`)**:
|
| 40 |
+
* Added automatic initialization-time Triton JIT compilation (`_prewarm_all_configs`) using 64-element dummy buffers (`128–256 bytes` VRAM) with `lr=0.0` and `weight_decay=0.0` across both Step 1 (`pass1` + `pass2`) and Step 2+ (`fused_single_pass` + `post_step_ema`) paths with target `BLOCK_SIZE` specialization (`1024` or `2048`), eliminating first-step compilation stalls.
|
| 41 |
+
- **Bounded Bucket Chunking & Sequential In-Place Migration (`bucket_cap_mb: float = 500.0`)**:
|
| 42 |
+
* Bound contiguous bucket coalescing by an upper memory capacity ceiling $C_{\text{cap}} = 500\text{ MB}$, partitioning model parameters into ordered contiguous chunks $\mathcal{B}^{(1)}, \mathcal{B}^{(2)}, \dots, \mathcal{B}^{(K)}$ such that $\sum_{p \in \mathcal{B}^{(k)}} \text{bytes}(p) \le C_{\text{cap}}$ (with giant single layers $> C_{\text{cap}}$ isolated into individual buckets).
|
| 43 |
+
* Enforced sequential in-place migration with immediate parameter storage recycling (`del old_data`, `del old_grad`) and garbage collection, constraining peak transient memory allocation spikes during optimizer initialization to at most $\min(\text{model bytes}, C _ {\text{cap}})$.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## [1.5.4] - 2026-10-29
|
| 48 |
+
|
| 49 |
+
### Added
|
| 50 |
+
- **In-Container Native Bitwise-BF16 State Promotion (`dtype_mode == 2`, `master_weights="none"`)**:
|
| 51 |
+
* Promoted 16-bit optimizer state buffers ($m_t$ via stochastic rounding `f32_to_bf16_bits_sr` and $v_t$ via round-to-nearest-even `f32_to_bf16_bits_rne`) to native Bitwise-BF16 stored inside the existing 16-bit container during pure `FP16` execution, expanding state exponent dynamic range to 8 bits ($1.18 \times 10^{-38}$) with zero additional VRAM overhead.
|
| 52 |
+
|
| 53 |
+
### Changed
|
| 54 |
+
- **Unified Denominator Stability Constant ($\epsilon_{\text{eff}} = \epsilon = 10^{-8}$)**:
|
| 55 |
+
* Unified the effective denominator constant $\epsilon_{\text{eff}} = \epsilon = 10^{-8}$ across all precision modes and hardware backends (replacing the legacy `FP16` floor clamp $\max(\epsilon, 10^{-4})$ introduced in `1.1.5`), preventing update velocity attenuation and late-stage convergence loss resets when $\sqrt{v_t} \ll 10^{-4}$.
|
| 56 |
+
|
| 57 |
+
### Removed
|
| 58 |
+
- **Centered Defect Compensation (`centered_defect`, $\mu_d$)**:
|
| 59 |
+
* Removed the `centered_defect` hyperparameter and the global defect mean reduction $\mu_d = \frac{1}{N}\sum_{i=1}^N (g_{t,i} - m_{t-1,i})$ across both Dual-Buffer Convex Lookahead Momentum (`buffer=2` / CLM) and Single-Buffer Triangle-Defect Convex Lookahead Momentum (`buffer=1` / TD-CLM).
|
| 60 |
+
* Simplified the instantaneous gradient-momentum residual to purely local coordinate-wise differences:
|
| 61 |
+
|
| 62 |
+
$\displaystyle d_t^{(\text{B1})} = g_t - m_{t-1}, \qquad d_t^{(\text{B2})} = g_t - m_t$
|
| 63 |
+
|
| 64 |
+
* Eliminated the pre-step global reduction pass (`_lumina_diff_sum_kernel` in Triton JIT/AOT, `diff_sum.comp` in Vulkan SPIR-V, `reduce_diff_sum_simd` in x86\_64 AVX-512/AVX2, and slot 5 of the 6-element `ScratchBuffer`), removing cross-lane/cross-rank reduction latency while retaining the 6-element physical `ScratchBuffer` layout (`[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale, _reserved]`, 5 active slots + 1 reserved slot for ABI alignment).
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## [1.5.3] - 2026-10-20
|
| 69 |
+
|
| 70 |
+
No versions were published between 1.3.3 and 1.5.3. This release notes all changes made since version 1.3.3.
|
| 71 |
+
|
| 72 |
+
### Added
|
| 73 |
+
- **Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM, `buffer=1`)**:
|
| 74 |
+
* Replaced the legacy 3-pass RMS single-buffer update with a single-pass formulation:
|
| 75 |
+
|
| 76 |
+
$\displaystyle \tilde{m} _ t = w_1 m _ {t-1} + w_2 g_t, \quad w_1 = \beta_1^2, \quad w_2 = 1 - \beta_1^2$
|
| 77 |
+
|
| 78 |
+
$\displaystyle \mathcal{E} _ t = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t^{(\text{B1})}| + \epsilon _ {\text{eff}}$
|
| 79 |
+
|
| 80 |
+
$\displaystyle u_t^{(\text{B1})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t} \in (-1, 1)$
|
| 81 |
+
|
| 82 |
+
* In 1.3.3, single-buffer mode required three sequential GPU kernel launches (`_lumina_v1_pass1_kernel`, `_lumina_v1_pass2_kernel`, `_lumina_v1_pass3_update_kernel`) and global atomic reductions across thread blocks to compute tensor-wide root-mean-square ($\text{rms} = \sqrt{\frac{1}{N}\sum \tilde{m} _ i^2 + \epsilon}$).
|
| 83 |
+
* TD-CLM replaces the tensor-wide reduction with coordinate-wise envelope bounding via the Triangle Inequality Envelope $\mathcal{E} _ t$. This reduces single-buffer execution to a single GPU pass and eliminates all $\tanh$ evaluations in the momentum path.
|
| 84 |
+
* Calibrated the defect penalty weight $\lambda$ to the symmetric spike-reversal equilibrium:
|
| 85 |
+
|
| 86 |
+
$\displaystyle \lambda^* = \frac{2(1 - \beta_1^2)^2}{4\beta_1^2 - 3}$
|
| 87 |
+
|
| 88 |
+
For the default $\beta_1 = 0.9$, $\lambda^* \approx 0.3008$, setting the default parameter to `lam=0.3`.
|
| 89 |
+
|
| 90 |
+
- **Padé [13/6] Rational Approximation (`tanh_pade_f32`)**:
|
| 91 |
+
* Implemented a rational Padé [13/6] polynomial for hyperbolic tangent calculations:
|
| 92 |
+
|
| 93 |
+
$\displaystyle \tanh _ {\text{Pade}}(x) = \frac{x(1 + c_1 x^2 + c_2 x^4 + c_3 x^6 + c_4 x^8 + c_5 x^{10} + c_6 x^{12})}{1 + d_1 x^2 + d_2 x^4 + d_3 x^6}$
|
| 94 |
+
|
| 95 |
+
* Evaluated over the clamped domain $x \in [-7.9988117, 7.9988117]$ with asymptotic saturation to $\pm 1.0$.
|
| 96 |
+
* In 1.3.3, `_triton_tanh_fast` used hardware-dependent SFU instructions (`tl.math.tanh`) or analytical sigmoid fallbacks (`2 / (1 + exp(-2x)) - 1`). Different GPU architectures (e.g. Turing, Ampere, Hopper) evaluate SFU transcendental functions with slightly different mantissa rounding, leading to bitwise divergence during distributed training.
|
| 97 |
+
* Padé [13/6] provides uniform precision with $\lVert \tanh _ {\text{Pade}}(x) - \tanh(x) \rVert _ \infty < 1.5 \times 10^{-6}$ across CPU Scalar, AVX2, AVX-512, Vulkan SPIR-V, and Triton GPU kernels.
|
| 98 |
+
|
| 99 |
+
- **Centered Innovation Defect (`centered_defect`)**:
|
| 100 |
+
* Added batch-mean centering for gradient innovation tracking (`centered_defect=False` by default):
|
| 101 |
+
|
| 102 |
+
$\displaystyle \mu_d = \frac{1}{N}\sum _ {i=1}^N (g _ {t,i} - m _ {t-1,i})$
|
| 103 |
+
|
| 104 |
+
$\displaystyle d_t^{(\text{B1})} = (g_t - m _ {t-1}) - \mu_d$
|
| 105 |
+
|
| 106 |
+
$\displaystyle d_t^{(\text{B2})} = (g_t - m_t) - \beta_1 \mu_d$
|
| 107 |
+
|
| 108 |
+
* In 1.3.3, uncentered innovation $g_t - m_t$ was used directly. On layers where gradients have a non-zero mean (such as normalization scales, biases, and token embeddings), the raw difference caused systematic drift in the second-moment accumulator. Centering eliminates this mean shift.
|
| 109 |
+
|
| 110 |
+
- **Subnormal-Safe IEEE-754 FP16 Stochastic Rounding**:
|
| 111 |
+
* Added `f32_to_f16_bits_rne` to handle IEEE-754 subnormal numbers ($|x| < 2^{-14}$) during FP16 stochastic rounding.
|
| 112 |
+
* In 1.3.3, stochastic rounding added 13-bit random noise and masked with `& -8192`. When a floating-point value entered the subnormal range, bitwise addition directly corrupted the exponent field (bit 10), causing values to flip between normal and subnormal representations or trigger hardware flush-to-zero traps.
|
| 113 |
+
* The updated routine checks for zero exponent, shifts the significand into the subnormal format, and applies round-to-nearest-even tie-breaking before bit truncation.
|
| 114 |
+
|
| 115 |
+
- **Multi-Backend Execution Engine**:
|
| 116 |
+
* Replaced the Python-only runtime with a multi-backend execution layer:
|
| 117 |
+
* **Rust SIMD (`luminav-simd`)**: AVX-512 (512-bit registers), AVX2 with FMA, portable scalar fallback, and Rayon parallel chunk scheduling (`ELEM_CHUNK_SIZE = 2,097,152`).
|
| 118 |
+
* **Vulkan UMA (`luminav-vulkan`)**: SPIR-V compute pipeline for cross-vendor GPUs (AMD, Intel, Apple Silicon via MoltenVK, and NVIDIA) with zero-copy host-visible memory buffers.
|
| 119 |
+
* **Triton AOT (`luminav-triton-aot`)**: C-ABI wrapper dispatching ahead-of-time precompiled kernels for 84 GPU architectures (Blackwell SM100/SM120, Hopper SM90, Ada SM89, Ampere SM80/SM86, Turing SM75, and AMD ROCm gfx90a/gfx942/gfx1100).
|
| 120 |
+
* **Standalone Runtime (`luminamoon.optimizer.standalone`)**: Pure NumPy and C-ABI execution that runs without requiring PyTorch.
|
| 121 |
+
|
| 122 |
+
### Changed
|
| 123 |
+
- **Parameter `alpha_ss`**: Removed. In 1.3.3, `alpha_ss=0.5` was used for soft-sign scaling $z / (1 + \alpha _ {\text{ss}} |z|)$ in the legacy single-buffer path. In 1.5.3, TD-CLM envelope normalization bounds updates directly to $(-1, 1)$, making soft-sign obsolete.
|
| 124 |
+
- **Parameter `lam`**: Added with default `0.3`. Governs the defect penalty weight in TD-CLM.
|
| 125 |
+
- **Parameter `centered_defect`**: Added with default `False`. Enables batch-mean centering of the innovation defect.
|
| 126 |
+
- **Parameter `bound_type`**: Default changed from `"radial"` (1.3.3) to `"coordinate"` (1.5.3). Coordinate-wise hyperbolic bounding $(B_i \tanh(\Delta \theta_i / B_i))$ evaluates locally on each parameter without computing a tensor-wide norm reduction.
|
| 127 |
+
- **Parameter `bound_ratio`**: Default changed from `0.03` to `0.05` to provide better update headroom on large model architectures.
|
| 128 |
+
- **Parameter `ema_decay`**: Default changed from static `0.8` to `"auto"`. Resolves to `0.80` for Dual-Buffer mode (`buffer=2`) and `0.95` for Single-Buffer mode (`buffer=1`).
|
| 129 |
+
- **Scratch Buffer**: Expanded from 5 floats `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` to 6 floats, adding `diff_sum` at index 5 to accumulate batch-mean defect statistics.
|
| 130 |
+
- **Cautious Scaling Floor**: Enforced `cautious_clamp_min=0.5` across all backends to cap maximum cautious energy scaling at $2.0\times$ ($2^1$).
|
| 131 |
+
|
| 132 |
+
### Fixed
|
| 133 |
+
- **Hardware Mantissa Divergence**: Fixed bitwise output differences across GPU families by replacing hardware SFU `tl.math.tanh` with Padé [13/6] rational approximation.
|
| 134 |
+
- **FP16 Subnormal Bit Corruption**: Fixed exponent corruption in subnormal FP16 stochastic rounding by separating exponent extraction from significand shift logic.
|
| 135 |
+
- **Single-Buffer Kernel Launch Latency**: Reduced single-buffer kernel dispatches from 3 passes to 1 pass, eliminating host-side synchronization bubbles and intermediate L2 atomic reduction overhead.
|
| 136 |
+
- **Non-Zero Mean Gradient Drift**: Resolved directional accumulator drift on bias and normalization layers via `centered_defect`.
|
| 137 |
+
|
| 138 |
+
### Parameter Comparison (1.3.3 vs 1.5.3)
|
| 139 |
+
|
| 140 |
+
| Parameter | Version 1.3.3 | Version 1.5.3 | Technical Notes |
|
| 141 |
+
| :--- | :--- | :--- | :--- |
|
| 142 |
+
| `params` | `params` | `params` | Iterable of parameters or parameter groups. |
|
| 143 |
+
| `lr` | `8e-4` | `8e-4` | Base learning rate. |
|
| 144 |
+
| `betas` | `(0.9, 0.999)` | `(0.9, 0.999)` | First and second moment exponential decay rates. |
|
| 145 |
+
| `eps` | `1e-8` | `1e-8` | Numerical stability floor. |
|
| 146 |
+
| `weight_decay` | `8e-2` | `8e-2` | Decoupled weight decay rate. |
|
| 147 |
+
| `tau` | `0.8` | `0.8` | Noise dispersion scale factor. |
|
| 148 |
+
| `alpha_ss` | `0.5` | *(removed)* | Removed; superseded by TD-CLM envelope normalization. |
|
| 149 |
+
| `lam` | *(none)* | `0.3` | Defect penalty weight in TD-CLM (calibrated to $\lambda^* \approx 0.3008$). |
|
| 150 |
+
| `cautious` | `True` | `True` | Directional verification gate ($u_i g_i > 0$). |
|
| 151 |
+
| `cautious_clamp_min` | `0.5` | `0.5` | Maximum cautious scale cap ($2.0\times$). |
|
| 152 |
+
| `buffer` | `2` | `2` | `1` = Single-Buffer (TD-CLM), `2` = Dual-Buffer (CLM). |
|
| 153 |
+
| `centered_defect` | *(none)* | `False` | Enables subtraction of batch-mean defect $\mu_d$. |
|
| 154 |
+
| `stochastic_rounding` | `True` | `True` | Bitwise stochastic rounding on FP16/BF16 weights. |
|
| 155 |
+
| `bound` | `True` | `True` | Enables update trust-region bounding. |
|
| 156 |
+
| `bound_type` | `"radial"` | `"coordinate"` | Default changed to coordinate-wise bounding (zero reduction overhead). |
|
| 157 |
+
| `bound_ratio` | `0.03` | `0.05` | Maximum step magnitude relative to parameter magnitude. |
|
| 158 |
+
| `master_weights` | `False` | `"none"` | `"none"`, `"semi"`, or `"full"`. |
|
| 159 |
+
| `execution` | `"auto"` | `"auto"` | Dynamic routing: SIMD, Vulkan, Triton, or Foreach. |
|
| 160 |
+
| `fused_single_pass` | `False` | `True` | Enables single-pass streaming with EMA scaling. |
|
| 161 |
+
| `ema_decay` | `0.8` | `"auto"` | Automatically sets `0.80` for dual-buffer, `0.95` for single-buffer. |
|
| 162 |
+
| `fsdp_sync` | `False` | `False` | Cross-rank collective all-reduce synchronization. |
|
| 163 |
+
| `seed` | `1337` | `1337` | PRNG seed for deterministic stochastic rounding. |
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
## [1.3.3] - 2026-10-06
|
| 168 |
+
|
| 169 |
+
### Fixed
|
| 170 |
+
* **Triton AST Compiler Early-Return Inlining Error:**
|
| 171 |
+
* Resolved fatal MLIR/CFG code generation failure (`at 35:4: _store_state_buffer(...) ^`) triggered by mid-branch `return` statements in `@triton.jit` helper functions (`_store_param` and `_store_state_buffer`).
|
| 172 |
+
* Refactored helper control flow into structured, single-exit `if ... elif ... else` branches, ensuring compliant JIT function inlining across all kernel entry points.
|
| 173 |
+
* **Bitcast Width Mismatch (Int64 Promotion in Stochastic Rounding):**
|
| 174 |
+
* Fixed implicit 64-bit integer promotion caused by Python integer literals (`0x9E3779B9`, `~0x1FFF`, `~0xFFFF`) inside Triton bitwise operations.
|
| 175 |
+
* Enforced strict 32-bit signed arithmetic via two's complement constants (`-1640531527`, `-2048173461`, `-1028477387`) with explicit `.to(tl.int32)` coercion, and standardized bitwise masks (`-65536` for BF16, `-8192` for FP16), ensuring type-safe 32-bit bitcasting (`tl.int32` to `tl.float32`).
|
| 176 |
+
* **JIT Kernel Call Keyword Arguments:**
|
| 177 |
+
* Replaced internal keyword argument invocations (`use_sr=False`) with standard positional boolean parameters inside Triton JIT kernels to eliminate compiler argument binding warnings across Triton runtime versions.
|
| 178 |
+
|
| 179 |
+
### Improved
|
| 180 |
+
* **Throttled Fallback Telemetry & Console De-Noising:**
|
| 181 |
+
* Added `self._warned_triton_failure` state tracking to prevent multi-line compiler AST dumps from flooding the console across every parameter tensor in a parameter group.
|
| 182 |
+
* Formatted fallback notifications into a single concise one-line `WARNING` log on initial encounter, routing full tracebacks to `DEBUG` level.
|
| 183 |
+
|
| 184 |
+
### Verified
|
| 185 |
+
* **End-to-End JIT Execution on NVIDIA Architectures (Tesla T4):**
|
| 186 |
+
* Validated JIT execution across both Dual-Pass (`fused_single_pass=False`) and Fused Single-Pass (`fused_single_pass=True`) modes in native FP16.
|
| 187 |
+
* Confirmed non-zero weight update dynamics ($|\Delta W| > 0$) without gradient underflow or weight freezing in pure master-free training for Version 1.3.3.
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## [1.3.2] - 2026-10-06
|
| 192 |
+
|
| 193 |
+
### Added
|
| 194 |
+
- **Distributed FSDP Collective Synchronization (`fsdp_sync`)**:
|
| 195 |
+
- Introduced the `fsdp_sync: bool = False` configuration flag on optimizer initialization, backed by automated runtime guarding via `torch.distributed.is_initialized()`.
|
| 196 |
+
- Integrated cross-GPU collective communications (`torch.distributed.all_reduce(..., op=torch.distributed.ReduceOp.SUM)`) for global reduction metrics (`mask_sum`, `u_sq_sum`, and `p_sq_sum`) across Triton kernels, single-step execution, and vectorized foreach loops.
|
| 197 |
+
- Maintains bit-exact scalar consistency for `m_bar`, `cautious_scale`, and `bound_scale` across distributed parameter shards during large-scale pretraining under PyTorch FSDP (Fully Sharded Data Parallel) and DeepSpeed ZeRO-3.
|
| 198 |
+
- **Dedicated Low-Precision Stochastic Quantization Routine (`_sr_quantize`)**:
|
| 199 |
+
- Factored out bitwise stochastic rounding into a dedicated, modular helper function: `_sr_quantize(target, val_f32, use_sr, generator)`.
|
| 200 |
+
- Enabled native stochastic rounding on half-precision momentum states (`exp_avg` in BF16/FP16) across all PyTorch CPU/CUDA fallback paths (`_single_step_v2`, `_foreach_step_v2`, `_single_step_v1`, and `_foreach_step_v1`), systematically eliminating gradient stagnation induced by fractional bit truncation (LSB underflow).
|
| 201 |
+
- **Persistent Contiguous Buffer Caching**:
|
| 202 |
+
- Introduced persistent state cache tensors within `self.state[p]` (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, and `mp_contig`) to stage strided or sliced parameters before kernel launch.
|
| 203 |
+
- Eliminates dynamic memory allocations (`.contiguous()`) during iterative training loops, preserving PyTorch VRAM caching allocator stability and preventing CUDA Graph capture invalidation.
|
| 204 |
+
- **Zero-Copy Direct Tensor Aliasing for FP32 Master Weights**:
|
| 205 |
+
- When parameter tensors are natively `torch.float32`, `state["master_param"]` now directly aliases the parameter tensor pointer (`state["master_param"] = p`), avoiding additional master weight VRAM overhead for FP32 layers (0 bytes auxiliary memory footprint).
|
| 206 |
+
- **Checkpoint Sanitizer via State Dict Filter (`state_dict`)**:
|
| 207 |
+
- Overrode `state_dict()` to automatically purge temporary contiguous cache buffers (`p_contig`, `exp_avg_contig`, `exp_avg_sq_contig`, `mp_contig`), ensuring on-disk checkpoint payloads remain clean, compact, and fully backward-compatible.
|
| 208 |
+
- **Defensive Device Pipeline Flush on Kernel Fault (`torch.cuda.synchronize`)**:
|
| 209 |
+
- Embedded explicit hardware device synchronization (`torch.cuda.synchronize(p.device)`) within Triton exception recovery handlers prior to PyTorch fallback execution, preventing asynchronous CUDA error cascades into subsequent tensor operations.
|
| 210 |
+
|
| 211 |
+
### Changed
|
| 212 |
+
- **High-Entropy Bit-Avalanche PRNG Hash Function (`_prng`)**:
|
| 213 |
+
- Replaced the standard Linear Congruential Generator (LCG) with a MurmurHash3 / SplitMix32 integer bit-avalanche hashing routine:
|
| 214 |
+
```python
|
| 215 |
+
z = idx + seed + 0x9E3779B9
|
| 216 |
+
z = (z ^ (z >> 16)) * 0x85EBCA6B
|
| 217 |
+
z = (z ^ (z >> 13)) * 0xC2B2AE35
|
| 218 |
+
z = z ^ (z >> 16)
|
| 219 |
+
```
|
| 220 |
+
- Delivers uniform pseudo-random noise distributions with zero low-order bit periodicity, significantly refining stochastic rounding fidelity on BF16/FP16 parameters.
|
| 221 |
+
- **Memory-Contiguous Scratch Buffer Realignment for v1 Triton Kernels**:
|
| 222 |
+
- Re-indexed reduction buffer offsets in Triton v1 kernels:
|
| 223 |
+
- Pass 1 metrics (`[rms_sum, p_sq_sum]`) mapped to contiguous indices `[0, 1]`.
|
| 224 |
+
- Pass 2 metrics (`[mask_sum, u_sq_sum]`) mapped to contiguous indices `[2, 3]`.
|
| 225 |
+
- Enables vectorized contiguous slice all-reduces (`scratch_param[:2]` and `scratch_param[2:4]`) under FSDP without requiring non-contiguous memory gather/scatter overhead.
|
| 226 |
+
- **Multi-GPU Device Index Resolution in RNG Dispatcher (`_get_generator`)**:
|
| 227 |
+
- Fixed device index resolution when `device` is passed as a generic `torch.device("cuda")` without an explicit index by defaulting to `torch.cuda.current_device()`, avoiding cross-GPU generator key collisions during multi-GPU distributed runs.
|
| 228 |
+
- **Conditional Atomic Reduction Pruning in v2 Pass 1 Kernel**:
|
| 229 |
+
- Guarded `mask_sum` atomic accumulation in `_lumina_v2_pass1_kernel` with `if cautious:`, bypassing CTA reductions and L2 atomic additions when `cautious=False`.
|
| 230 |
+
- **Expanded Master Weight Precision Configuration Aliases**:
|
| 231 |
+
- Added the `"half"` alias for `"semi"` mode alongside `"fp32"` and `"masterweight"` aliases for `"full"` mode.
|
| 232 |
+
- **Metadata & Documentation Header Update**:
|
| 233 |
+
- Bumped specification version to `1.3.2`.
|
| 234 |
+
|
| 235 |
+
### Fixed
|
| 236 |
+
- **Singular Zero-Division & Step-Zero Bias Correction (`safe_base_step`)**:
|
| 237 |
+
- Introduced `safe_base_step = max(base_step, 1)` and `bc2_base = max(1.0 - (beta2 ** safe_base_step), 1e-15)` across Triton, single-step, and foreach loops.
|
| 238 |
+
- Fully resolves potential `ZeroDivisionError` and NaN blowups in `c1` / `c2` computations when steps evaluate at `step == 0`.
|
| 239 |
+
- **Numerical Overflow Clamp in Sigmoid-Based Tanh Fallback**:
|
| 240 |
+
- Applied register clamp bounds `x_clamped = tl.maximum(tl.minimum(x, 20.0), -20.0)` in analytical fallback `_triton_tanh_fast`, preventing `tl.exp(-2.0 * x)` from overflowing to `inf` or `nan` on devices lacking hardware SFU tanh instructions.
|
| 241 |
+
- **Missing Argument Forwarding in PyTorch Single-Step v1**:
|
| 242 |
+
- Corrected `_single_step_v1` method signature and call-sites to accept and propagate `scratches`, eliminating runtime `TypeError` during fallback invocation.
|
| 243 |
+
- **Multi-Rank Synchronization Skew in Sharded Parameter Bounding**:
|
| 244 |
+
- Aligned tensor norms and radial bounding factors across sharded ranks via synchronized global scalar reductions, preventing parameter update drift across distributed ranks.
|
| 245 |
+
|
| 246 |
+
### Performance & Engine Refactoring
|
| 247 |
+
- **Zero Dynamic VRAM Allocation on Strided / Sliced Tensors**:
|
| 248 |
+
- Persistent pre-allocated contiguous staging buffers avoid PyTorch caching allocator churn and eliminate CUDA Graph memory address invalidations.
|
| 249 |
+
- Preserves deterministic memory footprints across long pre-training runs.
|
| 250 |
+
- **Optimized Distributed Collective Bandwidth**:
|
| 251 |
+
- Grouping scalar reductions into contiguous vector slices compresses distributed synchronization overhead into a single collective kernel launch per pass.
|
| 252 |
+
|
| 253 |
+
---
|
| 254 |
+
|
| 255 |
+
## [1.3.0] - 2026-09-23
|
| 256 |
+
|
| 257 |
+
### Added
|
| 258 |
+
- **Fused Single-Pass Kernel with Post-Step EMA (`fused_single_pass`, `ema_decay`)**:
|
| 259 |
+
- Introduced `_lumina_v2_fused_single_pass_kernel` coupled with an asynchronous CTA micro-kernel `_lumina_v2_post_step_ema_kernel[(1,)]`.
|
| 260 |
+
- When `fused_single_pass=True`, avoids two-pass global reductions by leveraging running Exponential Moving Average (EMA) estimates of `cautious_scale` ($1 / \bar{M}$) and `bound_scale` ($\tanh(r) / r$).
|
| 261 |
+
- Unifies gradient filtering, momentum accumulation, variance dispersion, cautious masking, radial bounding, decoupled weight decay, stochastic rounding, and block partial reductions into a **single unified VRAM-resident pass**, reducing global GPU memory bandwidth traffic per optimizer step.
|
| 262 |
+
- **Hybrid Master Weight Precision Architecture (`master_weights`)**:
|
| 263 |
+
- Added multi-tier master weight flexibility via `master_weights: Union[bool, str] = False`:
|
| 264 |
+
- `"none"` / `False` *(Default)*: Pure master-free low-precision training leveraging on-chip bitwise stochastic rounding directly on native half-precision weights.
|
| 265 |
+
- `"semi"` / `"hybrid"` / `"half"`: 32-bit FP32 master parameters (`state["master_param"]`) coupled with 16-bit low-memory optimizer states (`exp_avg`, `exp_avg_sq` in BF16/FP16), saving ~50% optimizer state VRAM compared to traditional FP32 setups while maintaining full FP32 weight accumulation accuracy.
|
| 266 |
+
- `"full"` / `"fp32"` / `"masterweight"`: Traditional full FP32 master parameters with full FP32 optimizer states for standard baseline comparisons.
|
| 267 |
+
- **In-Kernel In-Flight NaN / Inf Gradient Sanitization**:
|
| 268 |
+
- Embedded register-level defensive sanitization across all Triton GPU kernels: `is_finite_g = (g == g) & (g < 1e38) & (g > -1e38); g = tl.where(is_finite_g, g, 0.0)`.
|
| 269 |
+
- Replaces non-finite gradients and subnormal corruptions with zero in registers before they can contaminate momentum (`exp_avg`) or variance (`exp_avg_sq`) state buffers.
|
| 270 |
+
- **Low-Precision State Buffer Serialization (`_store_state_buffer`)**:
|
| 271 |
+
- Added a dedicated Triton state serialization routine supporting FP32, BF16, and FP16 state representations with optional bitwise stochastic rounding applied directly to momentum buffers.
|
| 272 |
+
- **Multi-Device & Cross-Accelerator Hardware Context Isolation (`_get_device_context`)**:
|
| 273 |
+
- Unified device stream scoping supporting NVIDIA CUDA (`torch.cuda`), Intel XPU (`torch.xpu`), and modern PyTorch 2.x unified accelerator interfaces (`torch.accelerator.device`), eliminating multi-GPU stream hijacking and cross-device race conditions.
|
| 274 |
+
- **Strict Determinism Auto-Routing**:
|
| 275 |
+
- Automatically queries `torch.are_deterministic_algorithms_enabled()`. When enabled under `execution="auto"`, dynamically routes execution to C++ vectorized `foreach` to preserve bitwise cross-run reproducibility, circumventing non-deterministic floating-point hardware atomic reordering.
|
| 276 |
+
- **Granular Fault Recovery with State Guarding (`completed_indices`)**:
|
| 277 |
+
- Implemented per-parameter completion tracking in `_dispatch_triton`. If a kernel encounters an unexpected runtime fault, completed parameters are preserved while uncompleted ones fall back cleanly to `foreach`.
|
| 278 |
+
- Added `_single_step_v2_apply_param` and `_single_step_v1_apply_param` to safely finalize parameter updates if Pass 1 had already committed state transitions, strictly preventing double-update state corruption.
|
| 279 |
+
|
| 280 |
+
### Changed
|
| 281 |
+
- **Per-Parameter Persistent Scratch Tensors (Elimination of Global Slicing)**:
|
| 282 |
+
- Replaced the global dynamic slice allocator (`self._scratch_tensors[dev][:count]`) with isolated per-parameter persistent state allocations (`state["scratch"] = torch.zeros((5,), device=p.device, dtype=torch.float32)`).
|
| 283 |
+
- Memory pointers for `[mask_sum, u_sq_sum, p_sq_sum, cautious_scale, bound_scale]` remain completely invariant throughout the entire training lifecycle, preserving pointer stability for **CUDA Graph Capture** (`torch.accelerator.Graph`) and TorchInductor AOT compilation without memory aliasing.
|
| 284 |
+
- **Analytically Exact Nesterov Bias Correction ($\beta_1^{\text{step} + 1}$)**:
|
| 285 |
+
- Corrected first-moment bias correction from the classical Adam formulation ($1.0 - \beta_1^{\text{step}}$) to the exact analytical expectation of the Nesterov blended momentum: `bc1_nes = 1.0 - beta1^(step + 1)`. Removes residual momentum bias during early warmup iterations.
|
| 286 |
+
- **Adaptive CTA Tile Sizing and Dynamic Thread Warp Scaling**:
|
| 287 |
+
- Introduced hardware-aware CTA tile scaling based on parameter volume: parameters with $\ge 2,097,152$ elements (2M+) dynamically configure `BLOCK_SIZE = 2048` and `num_warps = 8` (versus `BLOCK_SIZE = 1024`, `num_warps = 4` for smaller tensors).
|
| 288 |
+
- Cuts total thread-block grid counts and L2 cache atomic addition pressure by 50% on large MLP, Attention Projection, and Vocabulary layers.
|
| 289 |
+
- **Hardware-Native Tanh Instruction Dispatch (`tl.math.tanh` / `tl.tanh`)**:
|
| 290 |
+
- Upgraded `_triton_tanh_fast` to dynamically inspect compiler capabilities, binding directly to native PTX Special Function Unit (SFU) instructions (`tanh.approx.f32`) on modern Triton compilers while retaining analytical sigmoid fallbacks for legacy environments.
|
| 291 |
+
- **PRNG Decorrelation between Momentum and Parameter Updates**:
|
| 292 |
+
- Separated RNG seed streams using bitwise alternating mask shifts: `seed_p = (seed + 0x55555555) & 0x7FFFFFFF`, mathematically eliminating cross-coupling noise correlation between state buffer rounding and parameter update rounding.
|
| 293 |
+
- **Bumped specification version metadata to 1.3.0** across `luminav.py`.
|
| 294 |
+
|
| 295 |
+
### Fixed
|
| 296 |
+
- **Floating-Point Cancellation in Radial Bounding ($r \to 0$)**:
|
| 297 |
+
- Fixed precision jitter and roundoff degradation near zero by adding an asymptotic Taylor-series guard across Triton and PyTorch bounding engines: `torch.where(r < 1e-4, 1.0, tanh(r) / r)`.
|
| 298 |
+
- **Artificial Denominator Dilation in Dispersion Scale ($\sigma$)**:
|
| 299 |
+
- Replaced additive epsilon inflation (`sigma + 1e-6`) with a non-distorting floor clamp (`tl.maximum(sigma, 1e-12)`), preventing artificial dampening of subtle gradient signals.
|
| 300 |
+
- **Singular Zero-Division in Parameter Norm Clamping**:
|
| 301 |
+
- Replaced `norm_p + 1e-6` with `torch.clamp_min(norm_p, 1.0)` / `tl.maximum(norm_p, 1.0)`, maintaining numerical stability even when parameters are initialized near zero.
|
| 302 |
+
- **Transient Memory Pool Spikes during PyTorch Fallback Stochastic Rounding**:
|
| 303 |
+
- Refactored `_sr_update` to stream stochastic noise generation in bounded chunks of 1,048,576 elements (~4 MB chunk size), eliminating multi-hundred megabyte temporary int32 tensor spikes during fallback execution on large parameter matrices.
|
| 304 |
+
- **Non-Contiguous Buffer Safety & Strided Memory Copy**:
|
| 305 |
+
- Added strict contiguous layout verification for parameters, states, and master weights with in-place synchronization (`p.copy_(p_contig)`), guarded by a one-time developer warning.
|
| 306 |
+
|
| 307 |
+
### Performance & Engine Refactoring
|
| 308 |
+
- **~50% Global Memory Bandwidth Reduction via Fused Single-Pass Mode**:
|
| 309 |
+
- By unifying parameter loading, gradient evaluation, state transformation, and bounding into one GPU pass under `fused_single_pass=True`, VRAM read/write cycles are halved for cautious and radially bounded steps.
|
| 310 |
+
- **Sub-Microsecond L2 Hardware Atomic Reduction Architecture**:
|
| 311 |
+
- Formally documented the hybrid Intra-Block Tree Reduction + Inter-Block Hardware Atomic pattern. With `BLOCK_SIZE=2048`, a 10M-parameter tensor issues only 4,883 atomic additions directly into GPU L2 cache crossbars, executing in < 0.5 µs and strictly outperforming multi-tier cascaded reduction micro-kernels by eliminating 3–5 µs driver dispatch bubbles.
|
| 312 |
+
- **Static Buffer Address Invariance for CUDA Graphs & TorchInductor AOT**:
|
| 313 |
+
- Persistent 5-slot state scratch buffers eliminate all dynamic slicing and memory reallocations during optimization loops, preserving pointer address invariance for CUDA Graph replay.
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## [1.2.1] - 2026-09-17
|
| 318 |
+
|
| 319 |
+
### Added
|
| 320 |
+
- **In-Kernel GPU Parameter Norm Reduction (`p_sq_sum_ptr`)**: Fused parameter Euclidean norm squared $\sum(p_i^2)$ calculation directly into Pass 1 Triton reduction kernels (`_lumina_v2_pass1_kernel` and `_lumina_v1_pass1_kernel`). Pass 2 and Pass 3 now load and resolve `norm_p` (`tl.sqrt(tl.maximum(p_sq_sum, 0.0))`) entirely inside GPU SRAM/registers without host intervention.
|
| 321 |
+
- **Stateless CPU Integer Multiplicative PRNG Hashing**: Replaced dynamic GPU RNG tensor allocation with a pure CPU integer hash using Knuth's 32-bit golden ratio constant: `(step * 0x9E3779B9 + i * 10007) & 0x7FFFFFFF`. Generates uncorrelated 31-bit integer seeds per layer with zero GPU kernel launch overhead and zero PCIe host-device latency.
|
| 322 |
+
- **Isolated Generator Cache for Fallback Engine (`self._generators`)**: Added an internal per-device `torch.Generator` cache, ensuring stochastic rounding in `_sr_update` operates in complete isolation from PyTorch's default CUDA generator state.
|
| 323 |
+
|
| 324 |
+
### Changed
|
| 325 |
+
- **Consolidated Scratch Buffer Zeroing**: Removed redundant per-parameter slice zeroing inside parameter loops. Buffer zeroing is now batched into a single consolidated `scratch.zero_()` call per step inside `_get_scratch_buffer`.
|
| 326 |
+
- **Scratch Buffer Stride Expansion**:
|
| 327 |
+
- `buffer=2`: Expanded stride from 2×N to 3×N elements per parameter to host `[mask_sum, u_sq_sum, p_sq_sum]`.
|
| 328 |
+
- `buffer=1`: Expanded stride from 3×N to 4×N elements per parameter to host `[rms_sum, mask_sum, u_sq_sum, p_sq_sum]`.
|
| 329 |
+
- **Bumped specification version metadata to 1.2.1**.
|
| 330 |
+
|
| 331 |
+
### Fixed
|
| 332 |
+
- **Host-Device Synchronization & Pipeline Stalls (`.item()` Bottleneck)**: Removed `norm_p = float(p_contig.float().norm().item())` and `seed = torch.randint(...).item()` from Triton step pipelines. Eliminated 200–400 synchronous PCIe roundtrips and GPU pipeline stalls per step on deep architectures (e.g., 28-layer Transformers), restoring full asynchronous GPU compute utilization.
|
| 333 |
+
- **CUDA Graphs & `torch.compile` Graph Breaks**: Fixed fatal graph capture crashes and TorchDynamo graph breaks caused by blocking host-device synchronization during radial bounding and seed generation. The optimizer step is now compatible with CUDA Graphs (`torch.accelerator.Graph`) and TorchInductor compilation.
|
| 334 |
+
- **Transient VRAM Allocator Thrashing**: Eliminated repeated temporary FP32 parameter cloning (`p_contig.float()`) during norm evaluation, preventing memory pool fragmentation in tight VRAM regimes.
|
| 335 |
+
- **Global RNG Pollution & Determinism Compliance**: Fixed global RNG state mutation in fallback stochastic rounding. Solved reproducibility drift in upstream Dropout, DataLoader shuffling, and RLHF reward modeling, ensuring strict adherence to PyTorch 2.10+ `torch.use_deterministic_algorithms(True)`.
|
| 336 |
+
|
| 337 |
+
### Performance & Engine Refactoring
|
| 338 |
+
- **Batch Kernel Launch Overhead Elimination**: Removed up to 400–800 individual `cudaMemsetAsync` micro-calls per step previously triggered by per-slice `.zero_()` invocations on 1-element scratch tensors.
|
| 339 |
+
- **Single-Pass Bypass for Coordinate Bounding**: Streamlined `_triton_step_v2` to immediately bypass two-pass reductions and invoke `_lumina_v2_single_pass_kernel` when `cautious=False` and coordinate bounding (`bound_mode == 2`) is selected.
|
| 340 |
+
|
| 341 |
+
---
|
| 342 |
+
|
| 343 |
+
## [1.2.0] - 2026-09-17
|
| 344 |
+
|
| 345 |
+
### Added
|
| 346 |
+
- **Automatic Dynamic FP16 Cliff Governor:** Built-in asymptotic boundary governor (`_triton_tanh_fast` in GPU register and PyTorch fallback) that automatically activates when `p.dtype == torch.float16`. Prevents unclipped steps from pushing parameters, activations, and residual streams into the IEEE-754 FP16 overflow cliff (> 65,504). Remains completely dormant (zero overhead) on `bfloat16` (TPU / Ampere+) and `float32`.
|
| 347 |
+
- **Smooth Asymptotic Parameter Bounding (`bound`, `bound_type`, `bound_ratio`):** Added non-intrusive step bounding with two mathematically continuous formulations:
|
| 348 |
+
- `"radial"`: Direction-Preserving Radial Squashing (`update * tanh(r) / r`) preserving gradient direction (Cosine Similarity = 1.000).
|
| 349 |
+
- `"coordinate"`: Dual-Hyperbolic Coordinate Squashing for fine-grained per-element damping.
|
| 350 |
+
- Configurable via constructor: `bound: bool = True`, `bound_type: str = "radial"`, `bound_ratio: float = 0.03`.
|
| 351 |
+
|
| 352 |
+
### Changed
|
| 353 |
+
- **Increased Default `cautious_clamp_min` from `0.2` to `0.5`:** Enforces an exact power-of-two maximum energy scaling factor (2.00x, $2^1$). Eliminates floating-point mantissa roundoff distortion in IEEE-754 hardware.
|
| 354 |
+
- Bumped specification version metadata to `1.2.0`.
|
| 355 |
+
|
| 356 |
+
### Fixed
|
| 357 |
+
* **FP16 Step Divergence & NaN Overflow at High Learning Rates:** Solved step divergence and subsequent NaN crashes when training deep 28-layer Transformer architectures (e.g., Qwen3-0.6B) in pure FP16 at nominal learning rates (`lr = 8e-4`).
|
| 358 |
+
* **Sparse Embedding & LM-Head Amplification:** Mitigated 5x over-amplification on sparse vocabulary gradients where active token counts are much smaller than total tensor parameters ($N \gg N_{\text{active}}$).
|
| 359 |
+
* **Verified stable 1,000-step pretraining from scratch on Qwen3-0.6B** (seq length 512, pure FP16, `lr = 8e-4`) on Nvidia Tesla T4 with zero gradient clipping and zero LR scheduler (loss dropped from 12.09 down to ~5.7 with zero NaNs).
|
| 360 |
+
* **Weight Decay Numerical Parity on Fallback Engine:** Resolved a silent precision-stagnation issue in `_single_step` and `_foreach_step`. Previously, decoupled weight decay (`p.mul_(1.0 - lr * weight_decay)`) was executed directly on native FP16/BF16 tensors prior to `_sr_update`. Due to IEEE-754 Round-to-Nearest (RTN) limits, `(1 - lr * weight_decay) ≈ 0.999936` rounded back to `1.0`, causing weight decay to freeze on half-precision parameters. Weight decay is now unified into FP32 registers inside `_sr_update` alongside stochastic rounding, matching the numerical behavior with the Triton kernel.
|
| 361 |
+
* **Fallback Foreach Indentation & Scope Alignment:** Corrected tensor iteration scope and indentation for `torch._foreach_add_` in non-SR fallback branches.
|
| 362 |
+
* **In-Place GPU Clamping in `_apply_bound`:** Replaced dynamic GPU scalar allocations (`torch.tensor(1.5e-4, device=p.device)`) with native `torch.clamp_min`, eliminating transient CUDA malloc spikes per update step.
|
| 363 |
+
|
| 364 |
+
### Performance & Engine Refactoring
|
| 365 |
+
* **Unified Bound Execution Pipeline:** Added an immediate short-circuit guard (`if lr == 0.0: return update`) and streamlined the execution flow: intermediate updates are scaled in FP32 (`step_val = lr * u_float`), passed through radial/coordinate bounding, chained cleanly into the FP16 Cliff Governor, and returned in a single normalized step.
|
| 366 |
+
|
| 367 |
+
---
|
| 368 |
+
|
| 369 |
+
## [1.1.5] - 2026-09-15
|
| 370 |
+
|
| 371 |
+
### Added
|
| 372 |
+
- **Native Hardware Capability Detection (`_supports_native_bf16`):** Added automatic device capability detection via `torch.cuda.is_bf16_supported()` to identify Ampere SM80+ architectures and streamline native execution paths.
|
| 373 |
+
- **Hugging Face Repository Reference:** Added official Hugging Face repository link in module docstrings.
|
| 374 |
+
|
| 375 |
+
### Changed
|
| 376 |
+
- **Dynamic Epsilon Clamping for FP16:** Dynamically floors effective epsilon to `max(eps, 1e-4)` when parameters or states are in `torch.float16`. Prevents bias-corrected scale factor `c2` from underflowing into IEEE-754 subnormal/zero limits on step 1.
|
| 377 |
+
- Bumped specification version to `1.1.5`.
|
| 378 |
+
|
| 379 |
+
### Fixed
|
| 380 |
+
- **FP16 Step-1 `NaN` from Epsilon Underflow:** Fixed `0.0 / 0.0 = NaN` division occurring on initial training steps when parameters receive zero gradients (e.g., unselected vocabulary embeddings, padding tokens, or dropout paths).
|
| 381 |
+
- **Second-Moment Variance Saturation (`+inf`):** Added safe upper-bound register clamping (`tl.minimum(v_new, 65000.0)` in Triton kernels and `.clamp_(0.0, 65000.0)` in PyTorch C++ foreach/single loops). Prevents unclipped initial pretraining gradient surges ($|g| > 256$) from overflowing the FP16 maximum dynamic range (65,504) into irreversible `+inf`.
|
| 382 |
+
- **Zero-Sigma Division Guard:** Injected explicit `sigma + 1e-6` denominator guards across all Triton kernel passes and PyTorch update steps to ensure numerical safety during cold-start iterations.
|
| 383 |
+
- Verified stable 100-step pretraining from scratch on `SmolLM-135M` using Wikimedia Wikipedia in pure FP16 on Nvidia Tesla T4 GPU with zero gradient clipping.
|
| 384 |
+
|
| 385 |
+
---
|
| 386 |
+
|
| 387 |
+
## [1.1.0] - 2026-09-11
|
| 388 |
+
|
| 389 |
+
### Added
|
| 390 |
+
- **Bitwise Stochastic Rounding (SR):** Integrated on-chip PRNG integer bit-manipulation hash in Triton kernels and vectorized PyTorch fallback for native FP16/BF16 training without master weights.
|
| 391 |
+
- Unified Triton store routine introducing compile-time branching (`dtype_mode` / `use_sr: tl.constexpr`) with explicit safe casting for FP16 and BF16 pointers.
|
| 392 |
+
- Vectorized fallback improvements utilizing C++ multi-tensor operations when stochastic rounding is disabled or tensors are FP32.
|
| 393 |
+
- State buffer contiguity validation (`exp_avg_contig`, `exp_avg_sq_contig`) ensuring safe linear memory addressing before kernel dispatch.
|
| 394 |
+
- Declarative configuration file (`config.json`) defining hyperparameters and optimizer presets.
|
| 395 |
+
|
| 396 |
+
### Changed
|
| 397 |
+
- Refactored PRNG integer arithmetic to strictly stay within signed 32-bit integer limits ($< 2^{31} - 1$), eliminating JIT compilation failures on newer Triton releases.
|
| 398 |
+
- State buffers (`exp_avg`, `exp_avg_sq`) now initialize with strict contiguous memory format.
|
| 399 |
+
|
| 400 |
+
### Fixed
|
| 401 |
+
- Fixed `ValueError: Scalar out of range for type int32` and Triton LLVM compile errors during unsigned 32-bit bitmask operations.
|
| 402 |
+
- Resolved kernel fallback hangs and compilation overhead on CUDA devices.
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
## [1.0.2] - 2026-09-08
|
| 407 |
+
|
| 408 |
+
### Added
|
| 409 |
+
- Official Apache-2.0 license notice and copyright headers.
|
| 410 |
+
- Standardized package exports (`__all__`, `__version__`, `__author__`, `__license__`).
|
| 411 |
+
|
| 412 |
+
---
|
| 413 |
+
|
| 414 |
+
## [1.0.1] - 2026-08-31
|
| 415 |
+
|
| 416 |
+
### Fixed
|
| 417 |
+
- **Memory Synchronization Fix (`423d171`):** Fixed a critical bug where non-contiguous parameter tensors (e.g., transposed linear layers or sliced weights) were updated on temporary contiguous copies without reflecting changes back to model weights. Added `p.copy_(p_contig)` after Triton execution.
|
| 418 |
+
|
| 419 |
+
---
|
| 420 |
+
|
| 421 |
+
## [1.0.0] - 2026-08-30
|
| 422 |
+
|
| 423 |
+
### Added
|
| 424 |
+
- Initial release of the LuminaV optimizer.
|
| 425 |
+
- **Zero Master-Weight Copies:** Direct parameter updates in native half-precision without allocating 4-byte FP32 master weights.
|
| 426 |
+
- **Hyperbolic Tangent (`tanh`) Bounding Envelope:** Projects normalized momentum ratio into `(-1.0, 1.0)` to eliminate exploding gradient shocks.
|
| 427 |
+
- **Directional Alignment Gate:** Cautious update masking that zeroes out momentum steps opposing mini-batch gradient directions.
|
| 428 |
+
- **Central Innovation Variance:** Noise dispersion tracking $(g_t - m_t)^2$ decoupling mini-batch turbulence from directional descent.
|
| 429 |
+
- Dual-buffer mode (`buffer=2`, LuminaV-2B) and single-buffer mode (`buffer=1`, LuminaV-1).
|
| 430 |
+
- Custom fused OpenAI Triton kernels with automated fallback execution.
|
changelogs/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
| 1 |
-
# LuminaMoon Changelogs
|
| 2 |
-
|
| 3 |
-
This directory separates release tracking into two distinct lineages to eliminate version ambiguity between the software distribution and the underlying mathematical specification:
|
| 4 |
-
|
| 5 |
-
| Document | Current Version | Scope |
|
| 6 |
-
| :--- | :--- | :--- |
|
| 7 |
-
| [**Software Changelog**](SOFTWARE_CHANGELOG.md) | **`v1.1.0`** | Tracks the `luminamoon` package distributed on PyPI and GitHub (`pip install luminamoon`), covering the Rust workspace crates, CPython FFI bindings, Data Engine, LSO-Eval, and CI/CD infrastructure. |
|
| 8 |
-
| [**LuminaV Algorithm Changelog**](LUMINAV_ALGORITHM_CHANGELOG.md) | **`v1.6.0`** | Tracks the mathematical formulation, numerical invariants, stochastic rounding algorithms, and GPU/SIMD kernel evolution of the **LuminaV Optimizer** from `v1.0.0` through the `v1.6.0` specification. |
|
| 9 |
-
|
| 10 |
-
## Version Introspection in Python
|
| 11 |
-
|
| 12 |
-
```python
|
| 13 |
-
import luminamoon
|
| 14 |
-
from luminamoon import LuminaV
|
| 15 |
-
import luminamoon.data as lmd
|
| 16 |
-
|
| 17 |
-
# 1. Software Package Version (PyPI / Cargo / Data Engine)
|
| 18 |
-
print(luminamoon.__version__) # "1.1.0"
|
| 19 |
-
print(lmd.get_version()) # "1.1.0"
|
| 20 |
-
|
| 21 |
-
# 2. LuminaV Algorithm Math Version
|
| 22 |
-
print(LuminaV.ALGORITHM_MATH) # "1.6.0"
|
| 23 |
-
print(LuminaV.ALGORITHM_SPEC) # "1.6.0" (Backward-compatible alias)
|
| 24 |
-
print(LuminaV.getVersion()) # "1.6.0"
|
| 25 |
-
```
|
|
|
|
| 1 |
+
# LuminaMoon Changelogs
|
| 2 |
+
|
| 3 |
+
This directory separates release tracking into two distinct lineages to eliminate version ambiguity between the software distribution and the underlying mathematical specification:
|
| 4 |
+
|
| 5 |
+
| Document | Current Version | Scope |
|
| 6 |
+
| :--- | :--- | :--- |
|
| 7 |
+
| [**Software Changelog**](SOFTWARE_CHANGELOG.md) | **`v1.1.0`** | Tracks the `luminamoon` package distributed on PyPI and GitHub (`pip install luminamoon`), covering the Rust workspace crates, CPython FFI bindings, Data Engine, LSO-Eval, and CI/CD infrastructure. |
|
| 8 |
+
| [**LuminaV Algorithm Changelog**](LUMINAV_ALGORITHM_CHANGELOG.md) | **`v1.6.0`** | Tracks the mathematical formulation, numerical invariants, stochastic rounding algorithms, and GPU/SIMD kernel evolution of the **LuminaV Optimizer** from `v1.0.0` through the `v1.6.0` specification. |
|
| 9 |
+
|
| 10 |
+
## Version Introspection in Python
|
| 11 |
+
|
| 12 |
+
```python
|
| 13 |
+
import luminamoon
|
| 14 |
+
from luminamoon import LuminaV
|
| 15 |
+
import luminamoon.data as lmd
|
| 16 |
+
|
| 17 |
+
# 1. Software Package Version (PyPI / Cargo / Data Engine)
|
| 18 |
+
print(luminamoon.__version__) # "1.1.0"
|
| 19 |
+
print(lmd.get_version()) # "1.1.0"
|
| 20 |
+
|
| 21 |
+
# 2. LuminaV Algorithm Math Version
|
| 22 |
+
print(LuminaV.ALGORITHM_MATH) # "1.6.0"
|
| 23 |
+
print(LuminaV.ALGORITHM_SPEC) # "1.6.0" (Backward-compatible alias)
|
| 24 |
+
print(LuminaV.getVersion()) # "1.6.0"
|
| 25 |
+
```
|
changelogs/SOFTWARE_CHANGELOG.md
CHANGED
|
@@ -1,261 +1,261 @@
|
|
| 1 |
-
# LuminaMoon Software Changelog
|
| 2 |
-
|
| 3 |
-
All notable software, packaging, FFI, and infrastructure changes to the `luminamoon` monorepo and PyPI distribution (`pip install luminamoon`) are documented in this file.
|
| 4 |
-
|
| 5 |
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
-
|
| 7 |
-
For the mathematical and kernel evolution of the **LuminaV Optimizer** specification (`v1.0.0` to `v1.6.0`), see [`LUMINAV_ALGORITHM_CHANGELOG.md`](LUMINAV_ALGORITHM_CHANGELOG.md).
|
| 8 |
-
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
## [1.1.0.post3] - 2026-11-07
|
| 12 |
-
|
| 13 |
-
### Changed
|
| 14 |
-
- **Academic Citation Consolidation**:
|
| 15 |
-
* Removed redundant `CITATION.cff` metadata file to consolidate citation records directly into Zenodo (DOI: [`10.5281/zenodo.23978801`](https://doi.org/10.5281/zenodo.23978801)) and repository `README.md`.
|
| 16 |
-
- **Data Engine Version Introspection (`python/luminamoon/data/version.py`)**:
|
| 17 |
-
* Streamlined data version checking so `get_version()` directly returns the active package version string without disk or mutable state side effects.
|
| 18 |
-
* Maintained full backward compatibility for `list_versions()` and `set_version()` with informative `DeprecationWarning` notices explaining that the data engine operates on the unified native SIMD implementation.
|
| 19 |
-
* Removed internal cache directory cloning and dynamic module loading routines from `~/.cache/luminamoon/versions`.
|
| 20 |
-
* Updated `data/README.md` to document direct version inspection via `luminamoon.data.get_version()`.
|
| 21 |
-
- **Documentation & Technical Guides**:
|
| 22 |
-
* Rewrote technical guides, component documentation, and reference overviews across `docs/` and `data/docs/` in direct, humble, and accessible language for human readability.
|
| 23 |
-
* Preserved core algorithmic identities including the Triangle Family, Triangle Inequality Envelope, Gradient-Momentum Residual, and Triangle-Defect Penalty without inflated descriptors.
|
| 24 |
-
* Normalized data engine documentation in `python/luminamoon/data` and `data/` to consistent English terminology.
|
| 25 |
-
* Aligned the MinHash documentation in `docs/dataloader/sources_and_streaming/README.md` with the Rust implementation using XXH3-64 seed multipliers.
|
| 26 |
-
|
| 27 |
-
### Refined
|
| 28 |
-
- **Evaluation Specification Artifacts (`eval/`)**:
|
| 29 |
-
* Updated `eval/lso_eval_specification.tex` with refined layout and mathematical notation.
|
| 30 |
-
* Added updated compiled specification manuscript `eval/LSO_Eval.pdf` and removed legacy PDF files.
|
| 31 |
-
* Aligned `tools/sync_huggingface.py` and `eval/README.md` to track `eval/LSO_Eval.pdf`.
|
| 32 |
-
- **PosRaGLU Activation Gating (`luminamoon.nn`)**:
|
| 33 |
-
* Added explicit positive anchor support ($b > 0$) for `pos_ra_gate` and `PosRaGLU`, preserving unit derivative at the origin while bounding output in $(0, 1)$.
|
| 34 |
-
|
| 35 |
-
### Fixed
|
| 36 |
-
- **Markdown Parameter Table Pipe Escaping**:
|
| 37 |
-
* Escaped pipe characters in inline code notation (`\|d_t\| = \|g_t - m_{t-1}\|`) within the README parameter table to prevent GitHub Flavored Markdown (GFM) table parsers from prematurely splitting table cells.
|
| 38 |
-
- **Static Type Analysis (`luminamoon.nn._compat`)**:
|
| 39 |
-
* Declared `_rs_engine` at module scope with multi-tier import fallbacks to eliminate Pyright unresolved import warnings.
|
| 40 |
-
|
| 41 |
-
### Infrastructure
|
| 42 |
-
- **Continuous Integration & Release Automation**:
|
| 43 |
-
* Added Python runtime setup to the Rust test workflow in `.github/workflows/ci.yml` so linkers reliably locate shared Python libraries during PyO3 FFI tests on updated runner images.
|
| 44 |
-
* Streamlined GitHub Release note generation by eliminating redundant boilerplate headers and prioritizing direct changelog entries.
|
| 45 |
-
|
| 46 |
-
---
|
| 47 |
-
|
| 48 |
-
## [1.1.0.post2] - 2026-11-05
|
| 49 |
-
|
| 50 |
-
### Fixed
|
| 51 |
-
- **Rust Arrow & Parquet Dependencies (`RUSTSEC-2026-0442`)**:
|
| 52 |
-
* Updated `parquet`, `arrow-array`, `arrow-schema`, and `arrow-ipc` to `60.1.0` in workspace manifests (`Cargo.toml` and `Cargo.lock`).
|
| 53 |
-
* Resolves advisory **RUSTSEC-2026-0442** (CVE-2026-111959) in `arrow-data`, preventing potential out-of-bounds reads when validating RunEndEncoded arrays during dataset ingestion.
|
| 54 |
-
|
| 55 |
-
---
|
| 56 |
-
|
| 57 |
-
## [1.1.0] - 2026-11-04
|
| 58 |
-
|
| 59 |
-
### Added
|
| 60 |
-
- **Hardware-Accelerated Triangle Family Fused Triton Kernels (GPU JIT & Rust AOT)**:
|
| 61 |
-
* **Python JIT (`_lumina_t1_single_pass_kernel`, `_lumina_t2_fused_single_pass_kernel`)**: Added dedicated fused register kernels for the Triangle Family (`buffer="T1"` and `buffer="T2"`), replacing eager PyTorch chunking and eliminating CPU-GPU `.item()` sync stalls, bringing T2 execution latency from 20.7 ms down to native GPU speed (~0.9–1.0 ms).
|
| 62 |
-
* **Rust Triton AOT Crate (`luminav-triton-aot`)**: Added Ahead-Of-Time compiled kernels for `T1` and `T2` across 12 architectures (Turing, Ampere, Ada, Hopper, Blackwell, CDNA, RDNA3), expanding the embedded manifest to 96 binary artifacts.
|
| 63 |
-
- **Strict Version Capability Enforcement**:
|
| 64 |
-
* Enforced specification capabilities across historical LuminaV algorithm versions (`1.6.0`, `1.5.5`, `1.5.4`, `1.5.3`): locking to `1.5.4` or `1.5.5` strictly rejects features introduced in later versions (such as `buffer="T2"` or `fuse_tensors=True`), guaranteeing reproducibility for academic benchmarks.
|
| 65 |
-
|
| 66 |
-
### Added
|
| 67 |
-
- **Ra-Family (Rational, Tanh-Free & Square-Root-Free Neural Architecture)**:
|
| 68 |
-
* **Zero Mandatory PyTorch Dependency**: The `luminamoon` package remains lightweight (~2.4 MB) with zero required dependencies (`dependencies = []`). The new `luminamoon.nn` module automatically detects the runtime environment (`_compat.py`), inheriting from `torch.nn.Module` when PyTorch is installed, or falling back cleanly to standalone NumPy buffers when PyTorch is absent.
|
| 69 |
-
* **`RaNorm` (`luminamoon.nn.RaNorm`)**: Token normalization using $L_1$ mean absolute deviation instead of root-mean-square. Eliminates square-root evaluations and amortizes a single scalar reciprocal per token vector.
|
| 70 |
-
* **`RaBatchNorm2d` (`luminamoon.nn.RaBatchNorm2d`)**: 2D batch normalization using cross-batch mean absolute deviation (MAD). Includes inference weight folding (`fold_into_conv`) that merges running statistics into preceding `Conv2d` layers with zero additional inference latency.
|
| 71 |
-
* **`RaTanh` & `ra_tanh`**: Rational hyperbolic tangent with non-vanishing algebraic gradient tails $O(1 / |x|^3)$ and unit derivative at origin.
|
| 72 |
-
* **`RaGELU` & `ra_gelu`**: Rational GELU activation using rational polynomial approximations.
|
| 73 |
-
* **`PosRaGLU` (`luminamoon.nn.PosRaGLU`)**: Gated Linear Unit with strictly positive rational gating bounded in $(0, 1)$. Eliminates quadratic activation outlier explosion for stable low-precision (INT4/FP8) quantization.
|
| 74 |
-
* **`RaGLU` & `TriGLU`**: Standard and triple rational gated linear unit variants.
|
| 75 |
-
* **`RaAttention` & `RaAttn` (`luminamoon.nn.RaAttention`, `luminamoon.nn.RaAttn`)**: Multi-head attention using Cayley-Padé rational softmax. Supports causal masking, QK-Norm (`qk_norm=True`), dropout, and custom head dimension scaling.
|
| 76 |
-
* **`ra_softmax` (`luminamoon.nn.ra_softmax`)**: Rational softmax with exact compact support for $\Delta \ge 16$, guaranteeing sum of numerators $\ge 1.0$ without requiring an epsilon floor.
|
| 77 |
-
* **Dual Hardware Acceleration (Triton JIT GPU & Rust AVX-512/AVX2 SIMD CPU)**:
|
| 78 |
-
- Added Triton GPU kernels (`_ra_norm_fwd_kernel`, `_pos_ra_glu_fwd_kernel`, `_ra_softmax_fwd_kernel`) in `luminamoon.nn.triton_kernels` for register-fused execution without intermediate global memory roundtrips.
|
| 79 |
-
- Added native Rust SIMD kernels in `luminav-simd` with AVX2/FMA vectorization and PyO3 bindings for high-throughput CPU token normalization and rational softmax.
|
| 80 |
-
- **Academic Aliases (`luminamoon.nn`)**:
|
| 81 |
-
* Exported standardized academic aliases across neural components: `L1Norm`, `L1LayerNorm`, `L1BatchNorm2d`, `RationalTanh`, `rational_tanh`, `RationalGELU`, `rational_gelu`, `RationalGLU`, `BoundedRationalGLU`, `rational_softmax`, `pade_softmax`, `RationalAttention`, and `PadeAttention`.
|
| 82 |
-
- **Theoretical References & Literature Documentation**:
|
| 83 |
-
* Added `references/README.md` and `references/bibliography.bib` documenting literature mappings and rational approximations, paired with `docs/references.md` in the documentation.
|
| 84 |
-
- **Empirical Evaluation**:
|
| 85 |
-
* Updated `eval/LSO-Eval.pdf` with the latest benchmark convergence findings and empirical evaluation report.
|
| 86 |
-
- **Unit Tests**: Added 32 unit tests in `tests/test_nn_ra_family.py` covering mathematical invariants, convolution folding, isolated zero-torch subprocess execution, and hardware introspection.
|
| 87 |
-
|
| 88 |
-
### Changed
|
| 89 |
-
- **Security Policy**: Streamlined `SECURITY.md` supported version matrix (`luminamoon >= 1.1.0`, `luminav >= 1.6.0`) with simplified reporting guidelines.
|
| 90 |
-
|
| 91 |
-
---
|
| 92 |
-
|
| 93 |
-
## [1.0.9] - 2026-11-03
|
| 94 |
-
|
| 95 |
-
### Added
|
| 96 |
-
- **LuminaV v1.6.0 — T-FAMILY (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 97 |
-
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$. Computes the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounds the lookahead update via the **Triangle Inequality Envelope**:
|
| 98 |
-
|
| 99 |
-
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 100 |
-
|
| 101 |
-
By the Triangle Inequality, $|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$, which guarantees the update step $u_t^{(\text{T2})}$ strictly remains within $(-1, 1)$ without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, while $0.5\lambda \sigma_t$ dampens belief-variance dispersion.
|
| 102 |
-
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Pairs the single-buffer **Triangle-Defect** denominator ($\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ with residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below via the Triangle Inequality, eliminating the second-moment accumulator ($v_t$) and reducing optimizer state memory by 50%.
|
| 103 |
-
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas across Rust, PyTorch, FSDP, and NumPy:
|
| 104 |
-
|
| 105 |
-
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 106 |
-
- **100% Backward Compatibility & Zero Breaking Changes**:
|
| 107 |
-
* Existing code and checkpoints using `buffer=2` (default), `buffer=1`, or `version="1.5.5"` continue to work as before with identical outputs and `state_dict()` compatibility.
|
| 108 |
-
|
| 109 |
-
---
|
| 110 |
-
|
| 111 |
-
## [1.0.8.post1] - 2026-10-30
|
| 112 |
-
|
| 113 |
-
### Fixed
|
| 114 |
-
- **PyPI Discovery Description & Package Metadata**:
|
| 115 |
-
* Updated `pyproject.toml` distribution `description` to prominently highlight LuminaV low-precision BF16/FP16 optimization, zero-copy multimodal dataset streaming, and native Rust SIMD, Vulkan, and Triton acceleration.
|
| 116 |
-
- **Un-fused Bucket Cap Configuration Warning**:
|
| 117 |
-
* Added protective `UserWarning` in `LuminaV.__init__` when `bucket_cap_mb` is explicitly configured while `fuse_tensors=False`, preventing developer confusion regarding memory bounding scope on uncoalesced parameter tensors.
|
| 118 |
-
|
| 119 |
-
---
|
| 120 |
-
|
| 121 |
-
## [1.0.8] - 2026-10-29
|
| 122 |
-
|
| 123 |
-
### Added
|
| 124 |
-
- **LuminaV v1.5.5 Contiguous Bucket Fusion (`fuse_tensors=False`) & Triton JIT Pre-Warmup (`prewarm_jit=True`)**:
|
| 125 |
-
* Added opt-in `fuse_tensors: bool = False` in `LuminaV` to coalesce fragmented parameter groups into contiguous 1D buffers (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) with zero-copy tensor views, in-place `zero_grad()` pointer preservation, and LSO-Eval Stage II compliant `state_dict()` / `load_state_dict()` round-tripping.
|
| 126 |
-
* Added `prewarm_jit: bool = True` (enabled by default) in `LuminaVGpuMixin._prewarm_all_configs` to pre-compile Triton GPU kernels (`step=1` two-pass and `step=2+` fused single-pass + post-step EMA) on 64-element dummy tensors (`lr=0.0`) during `__init__()`.
|
| 127 |
-
* Isolated `_prepare_triton_buffers` state lookup via `self.state.get(p)` to prevent contiguous bucket or warmup buffers from polluting `self.state`.
|
| 128 |
-
- **Bounded Bucket Chunking (`bucket_cap_mb = 500.0`) & Sequential In-Place Migration**:
|
| 129 |
-
* Added `bucket_cap_mb: float = 500.0` in `LuminaV` to partition contiguous parameter coalescing into bounded chunk buckets (maximum 500 MB per bucket), preventing monolithic VRAM allocation spikes on large models (e.g. 4B+).
|
| 130 |
-
* Implemented `_partition_into_bounded_chunks` with isolated single-tensor handling for huge layers (e.g. large embedding matrices).
|
| 131 |
-
* Implemented sequential in-place migration per parameter inside chunks with immediate dereferencing (`del old_data`, `del old_grad`, `del conv_m`, `del conv_v`) and `gc.collect()`, ensuring that peak transient memory spikes during initialization never exceed 1 chunk (max 500 MB).
|
| 132 |
-
|
| 133 |
-
---
|
| 134 |
-
|
| 135 |
-
## [1.0.7] - 2026-10-29
|
| 136 |
-
|
| 137 |
-
### Added
|
| 138 |
-
- **LuminaV v1.5.4 Specification Upgrade**: Upgraded the bundled LuminaV Optimizer specification to `v1.5.4`, removing `centered_defect` and its global reduction kernels (`_lumina_diff_sum_kernel`, `diff_sum.comp`, `reduce_diff_sum_simd`) across Rust core, SIMD, Vulkan 1.3/1.4, Triton AOT/JIT, PyTorch, and NumPy backends to reduce per-step latency.
|
| 139 |
-
- **In-Container Native Bitwise-BF16 State Promotion**: Added native Bitwise-BF16 state promotion for pure `FP16` execution (`dtype_mode == 2`, `master_weights=False`) across Rust core, AVX-512/AVX2 SIMD, Vulkan 1.3/1.4 compute shaders, Triton kernels, PyTorch fallback, and standalone NumPy, preserving the 16-bit container memory footprint (zero extra VRAM) while expanding state dynamic range to 8-bit exponent ($1.18 \times 10^{-38}$).
|
| 140 |
-
- **Cross-Precision Checkpoint Handshake**: Added `_convert_torch_state_buffer` in `luminamoon.optimizer.luminav` for bit-exact state conversions during `load_state_dict` and `step` between `master_weights=False` (`torch.float16` Bitwise-BF16) and `master_weights="full"` (`torch.float32` / `torch.bfloat16`).
|
| 141 |
-
- **Comprehensive Hardware Parity & Regression Tests**: Added `test_vulkan_parity_dual_fp16_native_bf16_state_131072_5steps` and `test_vulkan_parity_tdclm_fp16_native_bf16_state_131072_5steps` in `vulkan_parity.rs` and `run_pure_fp16_native_bf16_state_tests` in `test_end_to_end_training.py`.
|
| 142 |
-
|
| 143 |
-
### Fixed
|
| 144 |
-
- **FP16 Convergence Stability & Loss Reset Mitigation**: Resolved sudden loss reset to $\ln(V)$ during convergence in pure `FP16` mode by eliminating second-moment underflow and unifying adaptive denominator epsilon (`effective_eps = eps = 1e-8`) across all execution backends (fixes #65).
|
| 145 |
-
- **Scalar Reference Parity Synchronization**: Fixed state quantization mode propagation in CPU scalar reference `fused_single_pass` path (`dual.rs` and `tdclm.rs`), maintaining mathematical parity between CPU scalar and GPU Vulkan shaders.
|
| 146 |
-
- **Headless CI Vulkan Detection Guard**: Added `LuminaV.detect_hardware()["vulkan_available"]` check in end-to-end tests to prevent false failures on headless CPU runners.
|
| 147 |
-
|
| 148 |
-
---
|
| 149 |
-
|
| 150 |
-
## [1.0.6.post1] - 2026-10-26
|
| 151 |
-
|
| 152 |
-
### Fixed
|
| 153 |
-
- **Prefetcher Batch ID Synchronization**: Fixed batch ID drift in `ResumablePipeline` during async prefetching (`prefetch_batches > 0`) by tagging each queue item with `(batch_payload, batch_id)` and synchronizing `self._active_batch_id` on consumption, eliminating premature coordinate commits and `UnknownBatchId` errors.
|
| 154 |
-
- **Data Engine Fallback Parity**: Fixed `StreamDeduplicator.count` callable signature in `luminamoon.data.fallbacks.dedup` (and high-level wrapper polymorphic dispatch), restored RAM staging in `StateTracker.update(..., batch_id=1)`, and aligned `fused_pipeline_process_batch` with two-stage staging and `commit_dedup` gating.
|
| 155 |
-
- **Optimizer Hyperparameter Constants**: Explicitly populated complete default hyperparameter constants in `luminamoon.optimizer.constants`.
|
| 156 |
-
- **Version Synchronization Tooling**: Added `--check` mode and PEP 440 validation in `tools/sync_version.py` to prevent accidental version string corruption.
|
| 157 |
-
|
| 158 |
-
---
|
| 159 |
-
|
| 160 |
-
## [1.0.6] - 2026-10-26
|
| 161 |
-
|
| 162 |
-
### Added
|
| 163 |
-
- **Python 3.15 Classifier Support**: Added `Programming Language :: Python :: 3.15` to package metadata in `pyproject.toml`.
|
| 164 |
-
- **O(1) Deduplication Membership Check**: Added `TwoStageDeduplicator::is_seen` in `data/src/dedup.rs` and wired `PyStreamDeduplicator::is_seen` to avoid $O(N)$ hash-vector exports.
|
| 165 |
-
|
| 166 |
-
### Changed
|
| 167 |
-
- **PROJECT LOOMY Rebranding & Author Standardization**: Standardized project branding to **PROJECT LOOMY** (`Loomy(luminamoon)`) with **Silver Moon (`@cloverxion`)** listed first as lead author followed by **Lumina Moon (`@cloverx-id`) contributors** across documentation and manifests.
|
| 168 |
-
- **Optional Dependency Import Diagnostics**: Wrapped `numpy`, `torch`, `pyarrow`, and `polars` imports in `StringTensor` export methods with actionable installation hints.
|
| 169 |
-
- **Fallback Signature Parity**: Aligned `suggest_closest_matches` (with Damerau-Levenshtein edit-distance ranking) and `postprocess_and_dedup_records` return tuple `(cleaned_unique, newly_committed)` in `luminamoon.data.fallbacks` with Rust FFI and `_stubs.py`.
|
| 170 |
-
|
| 171 |
-
### Fixed
|
| 172 |
-
- **WAL & GPU Buffer Corruption Hardening**: Added byte-alignment validation (`% 8 == 0` for `.dedup` WAL files, `% 4 == 0` for GPU `uint32` offset buffers), exact pre-commit offset seeking in `DualMetadataManager::commit_gpu_batch`, and `StringView16` field encapsulation (`pub(crate)`), inline length clamping, and saturating `u32` offset arithmetic in `data/src/string_tensor.rs`.
|
| 173 |
-
- **Data Pipeline & Collate Hardening**: Fixed WAL double-commit skip in `fused_process_batch`, added missing column typo suggestions and `bytes` decoding in `_extract_text`, auto-populated `tokenizer.pad_token = tokenizer.eos_token` when missing, restored resume coordinates in `ResumablePipeline`, and eliminated background prefetcher queue timeout drops.
|
| 174 |
-
|
| 175 |
-
---
|
| 176 |
-
|
| 177 |
-
## [1.0.5.post1] - 2026-10-25
|
| 178 |
-
|
| 179 |
-
### Changed
|
| 180 |
-
- **Modular Data Processing Engine**: Decomposed the monolithic data processing fallback layer in `luminamoon.data` into dedicated submodules under `fallbacks/` (`cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `coordinate.py`, `tracker.py`, `string_tensor.py`, `fused.py`, `serialization.py`, `helpers.py`), preserving backward compatibility.
|
| 181 |
-
- **Updated Algorithm Changelog**: Updated `LUMINAV_ALGORITHM_CHANGELOG.md` with detailed mathematical formulation and parameter comparisons between 1.3.3 and 1.5.3.
|
| 182 |
-
- **PEP 440 Post-Release Synchronization**: Bumped package distribution version to `1.0.5.post1`.
|
| 183 |
-
|
| 184 |
-
---
|
| 185 |
-
|
| 186 |
-
## [1.0.5] - 2026-10-24
|
| 187 |
-
|
| 188 |
-
### Added
|
| 189 |
-
- **Comprehensive In-Editor Docstrings (`luminav.py`)**: Added Google-style docstrings across `LuminaV.__init__`, `step`, `zero_grad`, `state_dict`, `load_state_dict`, and `detect_hardware`, providing full IDE autocomplete tooltips and explicit default hyperparameter values.
|
| 190 |
-
- **Hyperparameter Quick Reference (`README.md`)**: Added a structured default parameter reference table directly to the main repository README.
|
| 191 |
-
- **Dedicated Dual-Track Changelog Directory (`changelogs/`)**: Separated release documentation into `SOFTWARE_CHANGELOG.md` (package releases) and `LUMINAV_ALGORITHM_CHANGELOG.md` (algorithm specification history).
|
| 192 |
-
- **Explicit Algorithm Specification Constant (`LuminaV.ALGORITHM_SPEC`)**: Added `LuminaV.ALGORITHM_SPEC = "1.5.3"` to clearly distinguish the mathematical specification version from the software package version.
|
| 193 |
-
|
| 194 |
-
### Changed
|
| 195 |
-
- **Unified Software Version Synchronization (`1.0.5`)**: Synchronized `luminamoon.__version__`, `luminamoon.data.get_version()`, `pyproject.toml`, and `Cargo.toml` to a single source of truth (`1.0.5`).
|
| 196 |
-
- **Offline Version Resolution (`version.py`)**: Removed runtime HTTP GitHub file fetching from `luminamoon.optimizer.version` and `luminamoon.data.version`, allowing offline execution in air-gapped HPC clusters.
|
| 197 |
-
|
| 198 |
-
### Security
|
| 199 |
-
- **Upgraded `setuptools` (`84.0.0` -> `84.1.0`)**: Patched development dependency `setuptools` in `requirements.txt` to resolve `GHSA-xmj3-9gf7-h52w` (`CWE-22`).
|
| 200 |
-
|
| 201 |
-
---
|
| 202 |
-
|
| 203 |
-
## [1.0.4] - 2026-10-24
|
| 204 |
-
|
| 205 |
-
### Added
|
| 206 |
-
- **Free-Threaded CPython (No-GIL / PEP 703) Support**: Verified compilation and runtime execution of native `abi3t` / free-threaded wheels on Python `3.14t` and `3.15t`.
|
| 207 |
-
- **Rust Core Invariant Tests**: Added `optimizer/crates/luminav-core/tests/core_tests.rs` covering PRNG seed determinism, Padé rational $\tanh$ bounds, scratch buffer EMA smoothing, and Levenshtein typo suggestions.
|
| 208 |
-
- **PyPI Project URLs & Contributors Metadata**: Added `Homepage`, `Repository`, `Issues`, `Documentation`, and `Changelog` links alongside `Lumina Moon (cloverx-id) Contributors` to `pyproject.toml`, `Cargo.toml`, and `LICENSE`.
|
| 209 |
-
|
| 210 |
-
### Changed
|
| 211 |
-
- **Rust Workspace Modularization**:
|
| 212 |
-
- Decomposed `luminav-core/src/reference_cpu.rs` into `reference_cpu/{mod, dual, tdclm}.rs`.
|
| 213 |
-
- Decomposed `luminav-simd` (`avx2.rs`, `avx512.rs`, `parallel.rs`) into dedicated submodule directories (`dual.rs`, `tdclm.rs`, `mod.rs`).
|
| 214 |
-
- Extracted Vulkan UMA step dispatches into `luminav-vulkan/src/{dual, tdclm}.rs`.
|
| 215 |
-
- Decomposed `optimizer/src/python_ffi.rs` into `python_ffi/{config_parser, extract, worker, mod}.rs`.
|
| 216 |
-
- **Python Package Modularization**:
|
| 217 |
-
- Split `luminav.py` into `standalone.py`, `validation.py`, and `numpy_backend.py`.
|
| 218 |
-
- Split `gpu_backend.py` into `fsdp_sync.py`, `torch_ops.py`, and `triton_kernels.py`.
|
| 219 |
-
- Added cross-platform cache directory resolution (`LOCALAPPDATA` on Windows, `XDG_CACHE_HOME` on POSIX).
|
| 220 |
-
- **Reference Implementation Restructuring**:
|
| 221 |
-
- Decomposed monolithic `optimizer/reference/luminav_v153.py` (3,996 lines) into `core/`, `kernels/`, `single_backend/`, `triton_backend/`, and `utils/`.
|
| 222 |
-
|
| 223 |
-
---
|
| 224 |
-
|
| 225 |
-
## [1.0.3] - 2026-10-21
|
| 226 |
-
|
| 227 |
-
### Added
|
| 228 |
-
- **Two-Stage Deferred Release Pipeline**: Implemented draft GitHub release artifact staging on tag push (`v*`) with automated PyPI publication triggered upon release publication.
|
| 229 |
-
|
| 230 |
-
### Fixed
|
| 231 |
-
- **SIMD Finite BF16 Clamps**: Hardened AVX-512 and AVX2 finite clamp boundaries prior to BFloat16 stochastic rounding.
|
| 232 |
-
- **Build Wheel Privacy Sanitizer**: Added automated local path scrubbing in `tools/build_wheel.py` without modifying compiled ELF/PE/Mach-O relocation tables.
|
| 233 |
-
|
| 234 |
-
---
|
| 235 |
-
|
| 236 |
-
## [1.0.2.post2] - 2026-10-20
|
| 237 |
-
|
| 238 |
-
### Fixed
|
| 239 |
-
- **Dynamic Tag Versioning**: Added `tools/sync_version.py` to synchronize `pyproject.toml` and `_version.py` directly from Git release tags during CI wheel builds.
|
| 240 |
-
|
| 241 |
-
---
|
| 242 |
-
|
| 243 |
-
## [1.0.2.post1] - 2026-10-20
|
| 244 |
-
|
| 245 |
-
### Fixed
|
| 246 |
-
- **PEP 440 Post-Release Compliance**: Standardized version regex and enabled `skip-existing: true` on PyPI publishing workflows.
|
| 247 |
-
|
| 248 |
-
---
|
| 249 |
-
|
| 250 |
-
## [1.0.2] - 2026-10-20
|
| 251 |
-
|
| 252 |
-
### Added
|
| 253 |
-
- **Modular `luminamoon.data` Engine**: Added SIMD text normalization (`TextCleaner`), Shannon entropy & symbol heuristic filtering (`QualityFilter`), two-stage XXH3-64 exact deduplication (`StreamDeduplicator`), 64-permutation MinHash LSH fuzzy deduplication (`MinHashDeduplicator`), PII masking (`PIIMasker`), 128-byte Ping-Pong crash recovery (`StateTracker`), and dual-layout `StringTensor` (CPU 16-byte Umbra `StringView` & GPU contiguous offset+payload buffers).
|
| 254 |
-
- **LuminaV v1.5.3 Engine Integration**: Integrated the Rust AVX-512/AVX2 SIMD, Vulkan 1.3/1.4 UMA compute, and Triton AOT/JIT backends.
|
| 255 |
-
|
| 256 |
-
---
|
| 257 |
-
|
| 258 |
-
## [1.0.1] - 2026-10-20
|
| 259 |
-
|
| 260 |
-
### Added
|
| 261 |
-
- Initial monorepo release unifying the `luminamoon` Universal DataLoader and `LuminaV` Optimizer under a single Rust + Python workspace.
|
|
|
|
| 1 |
+
# LuminaMoon Software Changelog
|
| 2 |
+
|
| 3 |
+
All notable software, packaging, FFI, and infrastructure changes to the `luminamoon` monorepo and PyPI distribution (`pip install luminamoon`) are documented in this file.
|
| 4 |
+
|
| 5 |
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
| 6 |
+
|
| 7 |
+
For the mathematical and kernel evolution of the **LuminaV Optimizer** specification (`v1.0.0` to `v1.6.0`), see [`LUMINAV_ALGORITHM_CHANGELOG.md`](LUMINAV_ALGORITHM_CHANGELOG.md).
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## [1.1.0.post3] - 2026-11-07
|
| 12 |
+
|
| 13 |
+
### Changed
|
| 14 |
+
- **Academic Citation Consolidation**:
|
| 15 |
+
* Removed redundant `CITATION.cff` metadata file to consolidate citation records directly into Zenodo (DOI: [`10.5281/zenodo.23978801`](https://doi.org/10.5281/zenodo.23978801)) and repository `README.md`.
|
| 16 |
+
- **Data Engine Version Introspection (`python/luminamoon/data/version.py`)**:
|
| 17 |
+
* Streamlined data version checking so `get_version()` directly returns the active package version string without disk or mutable state side effects.
|
| 18 |
+
* Maintained full backward compatibility for `list_versions()` and `set_version()` with informative `DeprecationWarning` notices explaining that the data engine operates on the unified native SIMD implementation.
|
| 19 |
+
* Removed internal cache directory cloning and dynamic module loading routines from `~/.cache/luminamoon/versions`.
|
| 20 |
+
* Updated `data/README.md` to document direct version inspection via `luminamoon.data.get_version()`.
|
| 21 |
+
- **Documentation & Technical Guides**:
|
| 22 |
+
* Rewrote technical guides, component documentation, and reference overviews across `docs/` and `data/docs/` in direct, humble, and accessible language for human readability.
|
| 23 |
+
* Preserved core algorithmic identities including the Triangle Family, Triangle Inequality Envelope, Gradient-Momentum Residual, and Triangle-Defect Penalty without inflated descriptors.
|
| 24 |
+
* Normalized data engine documentation in `python/luminamoon/data` and `data/` to consistent English terminology.
|
| 25 |
+
* Aligned the MinHash documentation in `docs/dataloader/sources_and_streaming/README.md` with the Rust implementation using XXH3-64 seed multipliers.
|
| 26 |
+
|
| 27 |
+
### Refined
|
| 28 |
+
- **Evaluation Specification Artifacts (`eval/`)**:
|
| 29 |
+
* Updated `eval/lso_eval_specification.tex` with refined layout and mathematical notation.
|
| 30 |
+
* Added updated compiled specification manuscript `eval/LSO_Eval.pdf` and removed legacy PDF files.
|
| 31 |
+
* Aligned `tools/sync_huggingface.py` and `eval/README.md` to track `eval/LSO_Eval.pdf`.
|
| 32 |
+
- **PosRaGLU Activation Gating (`luminamoon.nn`)**:
|
| 33 |
+
* Added explicit positive anchor support ($b > 0$) for `pos_ra_gate` and `PosRaGLU`, preserving unit derivative at the origin while bounding output in $(0, 1)$.
|
| 34 |
+
|
| 35 |
+
### Fixed
|
| 36 |
+
- **Markdown Parameter Table Pipe Escaping**:
|
| 37 |
+
* Escaped pipe characters in inline code notation (`\|d_t\| = \|g_t - m_{t-1}\|`) within the README parameter table to prevent GitHub Flavored Markdown (GFM) table parsers from prematurely splitting table cells.
|
| 38 |
+
- **Static Type Analysis (`luminamoon.nn._compat`)**:
|
| 39 |
+
* Declared `_rs_engine` at module scope with multi-tier import fallbacks to eliminate Pyright unresolved import warnings.
|
| 40 |
+
|
| 41 |
+
### Infrastructure
|
| 42 |
+
- **Continuous Integration & Release Automation**:
|
| 43 |
+
* Added Python runtime setup to the Rust test workflow in `.github/workflows/ci.yml` so linkers reliably locate shared Python libraries during PyO3 FFI tests on updated runner images.
|
| 44 |
+
* Streamlined GitHub Release note generation by eliminating redundant boilerplate headers and prioritizing direct changelog entries.
|
| 45 |
+
|
| 46 |
+
---
|
| 47 |
+
|
| 48 |
+
## [1.1.0.post2] - 2026-11-05
|
| 49 |
+
|
| 50 |
+
### Fixed
|
| 51 |
+
- **Rust Arrow & Parquet Dependencies (`RUSTSEC-2026-0442`)**:
|
| 52 |
+
* Updated `parquet`, `arrow-array`, `arrow-schema`, and `arrow-ipc` to `60.1.0` in workspace manifests (`Cargo.toml` and `Cargo.lock`).
|
| 53 |
+
* Resolves advisory **RUSTSEC-2026-0442** (CVE-2026-111959) in `arrow-data`, preventing potential out-of-bounds reads when validating RunEndEncoded arrays during dataset ingestion.
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## [1.1.0] - 2026-11-04
|
| 58 |
+
|
| 59 |
+
### Added
|
| 60 |
+
- **Hardware-Accelerated Triangle Family Fused Triton Kernels (GPU JIT & Rust AOT)**:
|
| 61 |
+
* **Python JIT (`_lumina_t1_single_pass_kernel`, `_lumina_t2_fused_single_pass_kernel`)**: Added dedicated fused register kernels for the Triangle Family (`buffer="T1"` and `buffer="T2"`), replacing eager PyTorch chunking and eliminating CPU-GPU `.item()` sync stalls, bringing T2 execution latency from 20.7 ms down to native GPU speed (~0.9–1.0 ms).
|
| 62 |
+
* **Rust Triton AOT Crate (`luminav-triton-aot`)**: Added Ahead-Of-Time compiled kernels for `T1` and `T2` across 12 architectures (Turing, Ampere, Ada, Hopper, Blackwell, CDNA, RDNA3), expanding the embedded manifest to 96 binary artifacts.
|
| 63 |
+
- **Strict Version Capability Enforcement**:
|
| 64 |
+
* Enforced specification capabilities across historical LuminaV algorithm versions (`1.6.0`, `1.5.5`, `1.5.4`, `1.5.3`): locking to `1.5.4` or `1.5.5` strictly rejects features introduced in later versions (such as `buffer="T2"` or `fuse_tensors=True`), guaranteeing reproducibility for academic benchmarks.
|
| 65 |
+
|
| 66 |
+
### Added
|
| 67 |
+
- **Ra-Family (Rational, Tanh-Free & Square-Root-Free Neural Architecture)**:
|
| 68 |
+
* **Zero Mandatory PyTorch Dependency**: The `luminamoon` package remains lightweight (~2.4 MB) with zero required dependencies (`dependencies = []`). The new `luminamoon.nn` module automatically detects the runtime environment (`_compat.py`), inheriting from `torch.nn.Module` when PyTorch is installed, or falling back cleanly to standalone NumPy buffers when PyTorch is absent.
|
| 69 |
+
* **`RaNorm` (`luminamoon.nn.RaNorm`)**: Token normalization using $L_1$ mean absolute deviation instead of root-mean-square. Eliminates square-root evaluations and amortizes a single scalar reciprocal per token vector.
|
| 70 |
+
* **`RaBatchNorm2d` (`luminamoon.nn.RaBatchNorm2d`)**: 2D batch normalization using cross-batch mean absolute deviation (MAD). Includes inference weight folding (`fold_into_conv`) that merges running statistics into preceding `Conv2d` layers with zero additional inference latency.
|
| 71 |
+
* **`RaTanh` & `ra_tanh`**: Rational hyperbolic tangent with non-vanishing algebraic gradient tails $O(1 / |x|^3)$ and unit derivative at origin.
|
| 72 |
+
* **`RaGELU` & `ra_gelu`**: Rational GELU activation using rational polynomial approximations.
|
| 73 |
+
* **`PosRaGLU` (`luminamoon.nn.PosRaGLU`)**: Gated Linear Unit with strictly positive rational gating bounded in $(0, 1)$. Eliminates quadratic activation outlier explosion for stable low-precision (INT4/FP8) quantization.
|
| 74 |
+
* **`RaGLU` & `TriGLU`**: Standard and triple rational gated linear unit variants.
|
| 75 |
+
* **`RaAttention` & `RaAttn` (`luminamoon.nn.RaAttention`, `luminamoon.nn.RaAttn`)**: Multi-head attention using Cayley-Padé rational softmax. Supports causal masking, QK-Norm (`qk_norm=True`), dropout, and custom head dimension scaling.
|
| 76 |
+
* **`ra_softmax` (`luminamoon.nn.ra_softmax`)**: Rational softmax with exact compact support for $\Delta \ge 16$, guaranteeing sum of numerators $\ge 1.0$ without requiring an epsilon floor.
|
| 77 |
+
* **Dual Hardware Acceleration (Triton JIT GPU & Rust AVX-512/AVX2 SIMD CPU)**:
|
| 78 |
+
- Added Triton GPU kernels (`_ra_norm_fwd_kernel`, `_pos_ra_glu_fwd_kernel`, `_ra_softmax_fwd_kernel`) in `luminamoon.nn.triton_kernels` for register-fused execution without intermediate global memory roundtrips.
|
| 79 |
+
- Added native Rust SIMD kernels in `luminav-simd` with AVX2/FMA vectorization and PyO3 bindings for high-throughput CPU token normalization and rational softmax.
|
| 80 |
+
- **Academic Aliases (`luminamoon.nn`)**:
|
| 81 |
+
* Exported standardized academic aliases across neural components: `L1Norm`, `L1LayerNorm`, `L1BatchNorm2d`, `RationalTanh`, `rational_tanh`, `RationalGELU`, `rational_gelu`, `RationalGLU`, `BoundedRationalGLU`, `rational_softmax`, `pade_softmax`, `RationalAttention`, and `PadeAttention`.
|
| 82 |
+
- **Theoretical References & Literature Documentation**:
|
| 83 |
+
* Added `references/README.md` and `references/bibliography.bib` documenting literature mappings and rational approximations, paired with `docs/references.md` in the documentation.
|
| 84 |
+
- **Empirical Evaluation**:
|
| 85 |
+
* Updated `eval/LSO-Eval.pdf` with the latest benchmark convergence findings and empirical evaluation report.
|
| 86 |
+
- **Unit Tests**: Added 32 unit tests in `tests/test_nn_ra_family.py` covering mathematical invariants, convolution folding, isolated zero-torch subprocess execution, and hardware introspection.
|
| 87 |
+
|
| 88 |
+
### Changed
|
| 89 |
+
- **Security Policy**: Streamlined `SECURITY.md` supported version matrix (`luminamoon >= 1.1.0`, `luminav >= 1.6.0`) with simplified reporting guidelines.
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
## [1.0.9] - 2026-11-03
|
| 94 |
+
|
| 95 |
+
### Added
|
| 96 |
+
- **LuminaV v1.6.0 — T-FAMILY (Triangle Family) tanh-free Modes (`buffer="T1"` & `buffer="T2"`)**:
|
| 97 |
+
* **`buffer="T2"` (2-Buffer Triangle Family Mode)**: Keeps the 2-buffer state (`exp_avg` and `exp_avg_sq`) without using $\tanh$. Computes the gradient-momentum residual $d_t = g_t - m _ {t-1}$ and bounds the lookahead update via the **Triangle Inequality Envelope**:
|
| 98 |
+
|
| 99 |
+
$\displaystyle \mathcal{E} _ t^{(\text{T2})} = w_1 |m _ {t-1}| + w_2 |g_t| + 0.5\lambda |d_t| + 0.5\lambda \sigma_t + \epsilon _ {\text{eff}}, \qquad u_t^{(\text{T2})} = \frac{\tilde{m} _ t}{\mathcal{E} _ t^{(\text{T2})}} \in (-1, 1)$
|
| 100 |
+
|
| 101 |
+
By the Triangle Inequality, $|w_1 m _ {t-1} + w_2 g_t| \le w_1 |m _ {t-1}| + w_2 |g_t| \le \mathcal{E} _ t^{(\text{T2})}$, which guarantees the update step $u_t^{(\text{T2})}$ strictly remains within $(-1, 1)$ without evaluating $\tanh$. The term $0.5\lambda |d_t|$ acts as a **triangle-defect penalty**, while $0.5\lambda \sigma_t$ dampens belief-variance dispersion.
|
| 102 |
+
* **`buffer="T1"` (1-Buffer Triangle Family Mode)**: Pairs the single-buffer **Triangle-Defect** denominator ($\mathcal{E} _ t^{(\text{T1})} = w_1 |m _ {t-1}| + w_2 |g_t| + \lambda |d_t| + \epsilon _ {\text{eff}}$ with residual $d_t = g_t - m _ {t-1}$) with the rational update bounds below via the Triangle Inequality, eliminating the second-moment accumulator ($v_t$) and reducing optimizer state memory by 50%.
|
| 103 |
+
* **Rational Update Bounds (for `T1` & `T2`)**: Replaces $\tanh$ and $\sqrt{\cdot}$ in the coordinate, radial, and FP16 update bounds with simple rational formulas across Rust, PyTorch, FSDP, and NumPy:
|
| 104 |
+
|
| 105 |
+
$\displaystyle b_i = \mathrm{bound} _ {\text{ratio}} \cdot (|w_i| + 10^{-2}), \qquad \Delta w_i = \frac{b_i (b_i + |s_i|) s_i}{b_i (b_i + |s_i|) + s_i^2}, \qquad \mathrm{scale} _ {\text{radial}} = \frac{1 + r}{1 + r + r^2}$
|
| 106 |
+
- **100% Backward Compatibility & Zero Breaking Changes**:
|
| 107 |
+
* Existing code and checkpoints using `buffer=2` (default), `buffer=1`, or `version="1.5.5"` continue to work as before with identical outputs and `state_dict()` compatibility.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## [1.0.8.post1] - 2026-10-30
|
| 112 |
+
|
| 113 |
+
### Fixed
|
| 114 |
+
- **PyPI Discovery Description & Package Metadata**:
|
| 115 |
+
* Updated `pyproject.toml` distribution `description` to prominently highlight LuminaV low-precision BF16/FP16 optimization, zero-copy multimodal dataset streaming, and native Rust SIMD, Vulkan, and Triton acceleration.
|
| 116 |
+
- **Un-fused Bucket Cap Configuration Warning**:
|
| 117 |
+
* Added protective `UserWarning` in `LuminaV.__init__` when `bucket_cap_mb` is explicitly configured while `fuse_tensors=False`, preventing developer confusion regarding memory bounding scope on uncoalesced parameter tensors.
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## [1.0.8] - 2026-10-29
|
| 122 |
+
|
| 123 |
+
### Added
|
| 124 |
+
- **LuminaV v1.5.5 Contiguous Bucket Fusion (`fuse_tensors=False`) & Triton JIT Pre-Warmup (`prewarm_jit=True`)**:
|
| 125 |
+
* Added opt-in `fuse_tensors: bool = False` in `LuminaV` to coalesce fragmented parameter groups into contiguous 1D buffers (`flat_p`, `flat_g`, `flat_m`, `flat_v`, `flat_mp`) with zero-copy tensor views, in-place `zero_grad()` pointer preservation, and LSO-Eval Stage II compliant `state_dict()` / `load_state_dict()` round-tripping.
|
| 126 |
+
* Added `prewarm_jit: bool = True` (enabled by default) in `LuminaVGpuMixin._prewarm_all_configs` to pre-compile Triton GPU kernels (`step=1` two-pass and `step=2+` fused single-pass + post-step EMA) on 64-element dummy tensors (`lr=0.0`) during `__init__()`.
|
| 127 |
+
* Isolated `_prepare_triton_buffers` state lookup via `self.state.get(p)` to prevent contiguous bucket or warmup buffers from polluting `self.state`.
|
| 128 |
+
- **Bounded Bucket Chunking (`bucket_cap_mb = 500.0`) & Sequential In-Place Migration**:
|
| 129 |
+
* Added `bucket_cap_mb: float = 500.0` in `LuminaV` to partition contiguous parameter coalescing into bounded chunk buckets (maximum 500 MB per bucket), preventing monolithic VRAM allocation spikes on large models (e.g. 4B+).
|
| 130 |
+
* Implemented `_partition_into_bounded_chunks` with isolated single-tensor handling for huge layers (e.g. large embedding matrices).
|
| 131 |
+
* Implemented sequential in-place migration per parameter inside chunks with immediate dereferencing (`del old_data`, `del old_grad`, `del conv_m`, `del conv_v`) and `gc.collect()`, ensuring that peak transient memory spikes during initialization never exceed 1 chunk (max 500 MB).
|
| 132 |
+
|
| 133 |
+
---
|
| 134 |
+
|
| 135 |
+
## [1.0.7] - 2026-10-29
|
| 136 |
+
|
| 137 |
+
### Added
|
| 138 |
+
- **LuminaV v1.5.4 Specification Upgrade**: Upgraded the bundled LuminaV Optimizer specification to `v1.5.4`, removing `centered_defect` and its global reduction kernels (`_lumina_diff_sum_kernel`, `diff_sum.comp`, `reduce_diff_sum_simd`) across Rust core, SIMD, Vulkan 1.3/1.4, Triton AOT/JIT, PyTorch, and NumPy backends to reduce per-step latency.
|
| 139 |
+
- **In-Container Native Bitwise-BF16 State Promotion**: Added native Bitwise-BF16 state promotion for pure `FP16` execution (`dtype_mode == 2`, `master_weights=False`) across Rust core, AVX-512/AVX2 SIMD, Vulkan 1.3/1.4 compute shaders, Triton kernels, PyTorch fallback, and standalone NumPy, preserving the 16-bit container memory footprint (zero extra VRAM) while expanding state dynamic range to 8-bit exponent ($1.18 \times 10^{-38}$).
|
| 140 |
+
- **Cross-Precision Checkpoint Handshake**: Added `_convert_torch_state_buffer` in `luminamoon.optimizer.luminav` for bit-exact state conversions during `load_state_dict` and `step` between `master_weights=False` (`torch.float16` Bitwise-BF16) and `master_weights="full"` (`torch.float32` / `torch.bfloat16`).
|
| 141 |
+
- **Comprehensive Hardware Parity & Regression Tests**: Added `test_vulkan_parity_dual_fp16_native_bf16_state_131072_5steps` and `test_vulkan_parity_tdclm_fp16_native_bf16_state_131072_5steps` in `vulkan_parity.rs` and `run_pure_fp16_native_bf16_state_tests` in `test_end_to_end_training.py`.
|
| 142 |
+
|
| 143 |
+
### Fixed
|
| 144 |
+
- **FP16 Convergence Stability & Loss Reset Mitigation**: Resolved sudden loss reset to $\ln(V)$ during convergence in pure `FP16` mode by eliminating second-moment underflow and unifying adaptive denominator epsilon (`effective_eps = eps = 1e-8`) across all execution backends (fixes #65).
|
| 145 |
+
- **Scalar Reference Parity Synchronization**: Fixed state quantization mode propagation in CPU scalar reference `fused_single_pass` path (`dual.rs` and `tdclm.rs`), maintaining mathematical parity between CPU scalar and GPU Vulkan shaders.
|
| 146 |
+
- **Headless CI Vulkan Detection Guard**: Added `LuminaV.detect_hardware()["vulkan_available"]` check in end-to-end tests to prevent false failures on headless CPU runners.
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
## [1.0.6.post1] - 2026-10-26
|
| 151 |
+
|
| 152 |
+
### Fixed
|
| 153 |
+
- **Prefetcher Batch ID Synchronization**: Fixed batch ID drift in `ResumablePipeline` during async prefetching (`prefetch_batches > 0`) by tagging each queue item with `(batch_payload, batch_id)` and synchronizing `self._active_batch_id` on consumption, eliminating premature coordinate commits and `UnknownBatchId` errors.
|
| 154 |
+
- **Data Engine Fallback Parity**: Fixed `StreamDeduplicator.count` callable signature in `luminamoon.data.fallbacks.dedup` (and high-level wrapper polymorphic dispatch), restored RAM staging in `StateTracker.update(..., batch_id=1)`, and aligned `fused_pipeline_process_batch` with two-stage staging and `commit_dedup` gating.
|
| 155 |
+
- **Optimizer Hyperparameter Constants**: Explicitly populated complete default hyperparameter constants in `luminamoon.optimizer.constants`.
|
| 156 |
+
- **Version Synchronization Tooling**: Added `--check` mode and PEP 440 validation in `tools/sync_version.py` to prevent accidental version string corruption.
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## [1.0.6] - 2026-10-26
|
| 161 |
+
|
| 162 |
+
### Added
|
| 163 |
+
- **Python 3.15 Classifier Support**: Added `Programming Language :: Python :: 3.15` to package metadata in `pyproject.toml`.
|
| 164 |
+
- **O(1) Deduplication Membership Check**: Added `TwoStageDeduplicator::is_seen` in `data/src/dedup.rs` and wired `PyStreamDeduplicator::is_seen` to avoid $O(N)$ hash-vector exports.
|
| 165 |
+
|
| 166 |
+
### Changed
|
| 167 |
+
- **PROJECT LOOMY Rebranding & Author Standardization**: Standardized project branding to **PROJECT LOOMY** (`Loomy(luminamoon)`) with **Silver Moon (`@cloverxion`)** listed first as lead author followed by **Lumina Moon (`@cloverx-id`) contributors** across documentation and manifests.
|
| 168 |
+
- **Optional Dependency Import Diagnostics**: Wrapped `numpy`, `torch`, `pyarrow`, and `polars` imports in `StringTensor` export methods with actionable installation hints.
|
| 169 |
+
- **Fallback Signature Parity**: Aligned `suggest_closest_matches` (with Damerau-Levenshtein edit-distance ranking) and `postprocess_and_dedup_records` return tuple `(cleaned_unique, newly_committed)` in `luminamoon.data.fallbacks` with Rust FFI and `_stubs.py`.
|
| 170 |
+
|
| 171 |
+
### Fixed
|
| 172 |
+
- **WAL & GPU Buffer Corruption Hardening**: Added byte-alignment validation (`% 8 == 0` for `.dedup` WAL files, `% 4 == 0` for GPU `uint32` offset buffers), exact pre-commit offset seeking in `DualMetadataManager::commit_gpu_batch`, and `StringView16` field encapsulation (`pub(crate)`), inline length clamping, and saturating `u32` offset arithmetic in `data/src/string_tensor.rs`.
|
| 173 |
+
- **Data Pipeline & Collate Hardening**: Fixed WAL double-commit skip in `fused_process_batch`, added missing column typo suggestions and `bytes` decoding in `_extract_text`, auto-populated `tokenizer.pad_token = tokenizer.eos_token` when missing, restored resume coordinates in `ResumablePipeline`, and eliminated background prefetcher queue timeout drops.
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
## [1.0.5.post1] - 2026-10-25
|
| 178 |
+
|
| 179 |
+
### Changed
|
| 180 |
+
- **Modular Data Processing Engine**: Decomposed the monolithic data processing fallback layer in `luminamoon.data` into dedicated submodules under `fallbacks/` (`cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `coordinate.py`, `tracker.py`, `string_tensor.py`, `fused.py`, `serialization.py`, `helpers.py`), preserving backward compatibility.
|
| 181 |
+
- **Updated Algorithm Changelog**: Updated `LUMINAV_ALGORITHM_CHANGELOG.md` with detailed mathematical formulation and parameter comparisons between 1.3.3 and 1.5.3.
|
| 182 |
+
- **PEP 440 Post-Release Synchronization**: Bumped package distribution version to `1.0.5.post1`.
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## [1.0.5] - 2026-10-24
|
| 187 |
+
|
| 188 |
+
### Added
|
| 189 |
+
- **Comprehensive In-Editor Docstrings (`luminav.py`)**: Added Google-style docstrings across `LuminaV.__init__`, `step`, `zero_grad`, `state_dict`, `load_state_dict`, and `detect_hardware`, providing full IDE autocomplete tooltips and explicit default hyperparameter values.
|
| 190 |
+
- **Hyperparameter Quick Reference (`README.md`)**: Added a structured default parameter reference table directly to the main repository README.
|
| 191 |
+
- **Dedicated Dual-Track Changelog Directory (`changelogs/`)**: Separated release documentation into `SOFTWARE_CHANGELOG.md` (package releases) and `LUMINAV_ALGORITHM_CHANGELOG.md` (algorithm specification history).
|
| 192 |
+
- **Explicit Algorithm Specification Constant (`LuminaV.ALGORITHM_SPEC`)**: Added `LuminaV.ALGORITHM_SPEC = "1.5.3"` to clearly distinguish the mathematical specification version from the software package version.
|
| 193 |
+
|
| 194 |
+
### Changed
|
| 195 |
+
- **Unified Software Version Synchronization (`1.0.5`)**: Synchronized `luminamoon.__version__`, `luminamoon.data.get_version()`, `pyproject.toml`, and `Cargo.toml` to a single source of truth (`1.0.5`).
|
| 196 |
+
- **Offline Version Resolution (`version.py`)**: Removed runtime HTTP GitHub file fetching from `luminamoon.optimizer.version` and `luminamoon.data.version`, allowing offline execution in air-gapped HPC clusters.
|
| 197 |
+
|
| 198 |
+
### Security
|
| 199 |
+
- **Upgraded `setuptools` (`84.0.0` -> `84.1.0`)**: Patched development dependency `setuptools` in `requirements.txt` to resolve `GHSA-xmj3-9gf7-h52w` (`CWE-22`).
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## [1.0.4] - 2026-10-24
|
| 204 |
+
|
| 205 |
+
### Added
|
| 206 |
+
- **Free-Threaded CPython (No-GIL / PEP 703) Support**: Verified compilation and runtime execution of native `abi3t` / free-threaded wheels on Python `3.14t` and `3.15t`.
|
| 207 |
+
- **Rust Core Invariant Tests**: Added `optimizer/crates/luminav-core/tests/core_tests.rs` covering PRNG seed determinism, Padé rational $\tanh$ bounds, scratch buffer EMA smoothing, and Levenshtein typo suggestions.
|
| 208 |
+
- **PyPI Project URLs & Contributors Metadata**: Added `Homepage`, `Repository`, `Issues`, `Documentation`, and `Changelog` links alongside `Lumina Moon (cloverx-id) Contributors` to `pyproject.toml`, `Cargo.toml`, and `LICENSE`.
|
| 209 |
+
|
| 210 |
+
### Changed
|
| 211 |
+
- **Rust Workspace Modularization**:
|
| 212 |
+
- Decomposed `luminav-core/src/reference_cpu.rs` into `reference_cpu/{mod, dual, tdclm}.rs`.
|
| 213 |
+
- Decomposed `luminav-simd` (`avx2.rs`, `avx512.rs`, `parallel.rs`) into dedicated submodule directories (`dual.rs`, `tdclm.rs`, `mod.rs`).
|
| 214 |
+
- Extracted Vulkan UMA step dispatches into `luminav-vulkan/src/{dual, tdclm}.rs`.
|
| 215 |
+
- Decomposed `optimizer/src/python_ffi.rs` into `python_ffi/{config_parser, extract, worker, mod}.rs`.
|
| 216 |
+
- **Python Package Modularization**:
|
| 217 |
+
- Split `luminav.py` into `standalone.py`, `validation.py`, and `numpy_backend.py`.
|
| 218 |
+
- Split `gpu_backend.py` into `fsdp_sync.py`, `torch_ops.py`, and `triton_kernels.py`.
|
| 219 |
+
- Added cross-platform cache directory resolution (`LOCALAPPDATA` on Windows, `XDG_CACHE_HOME` on POSIX).
|
| 220 |
+
- **Reference Implementation Restructuring**:
|
| 221 |
+
- Decomposed monolithic `optimizer/reference/luminav_v153.py` (3,996 lines) into `core/`, `kernels/`, `single_backend/`, `triton_backend/`, and `utils/`.
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## [1.0.3] - 2026-10-21
|
| 226 |
+
|
| 227 |
+
### Added
|
| 228 |
+
- **Two-Stage Deferred Release Pipeline**: Implemented draft GitHub release artifact staging on tag push (`v*`) with automated PyPI publication triggered upon release publication.
|
| 229 |
+
|
| 230 |
+
### Fixed
|
| 231 |
+
- **SIMD Finite BF16 Clamps**: Hardened AVX-512 and AVX2 finite clamp boundaries prior to BFloat16 stochastic rounding.
|
| 232 |
+
- **Build Wheel Privacy Sanitizer**: Added automated local path scrubbing in `tools/build_wheel.py` without modifying compiled ELF/PE/Mach-O relocation tables.
|
| 233 |
+
|
| 234 |
+
---
|
| 235 |
+
|
| 236 |
+
## [1.0.2.post2] - 2026-10-20
|
| 237 |
+
|
| 238 |
+
### Fixed
|
| 239 |
+
- **Dynamic Tag Versioning**: Added `tools/sync_version.py` to synchronize `pyproject.toml` and `_version.py` directly from Git release tags during CI wheel builds.
|
| 240 |
+
|
| 241 |
+
---
|
| 242 |
+
|
| 243 |
+
## [1.0.2.post1] - 2026-10-20
|
| 244 |
+
|
| 245 |
+
### Fixed
|
| 246 |
+
- **PEP 440 Post-Release Compliance**: Standardized version regex and enabled `skip-existing: true` on PyPI publishing workflows.
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## [1.0.2] - 2026-10-20
|
| 251 |
+
|
| 252 |
+
### Added
|
| 253 |
+
- **Modular `luminamoon.data` Engine**: Added SIMD text normalization (`TextCleaner`), Shannon entropy & symbol heuristic filtering (`QualityFilter`), two-stage XXH3-64 exact deduplication (`StreamDeduplicator`), 64-permutation MinHash LSH fuzzy deduplication (`MinHashDeduplicator`), PII masking (`PIIMasker`), 128-byte Ping-Pong crash recovery (`StateTracker`), and dual-layout `StringTensor` (CPU 16-byte Umbra `StringView` & GPU contiguous offset+payload buffers).
|
| 254 |
+
- **LuminaV v1.5.3 Engine Integration**: Integrated the Rust AVX-512/AVX2 SIMD, Vulkan 1.3/1.4 UMA compute, and Triton AOT/JIT backends.
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## [1.0.1] - 2026-10-20
|
| 259 |
+
|
| 260 |
+
### Added
|
| 261 |
+
- Initial monorepo release unifying the `luminamoon` Universal DataLoader and `LuminaV` Optimizer under a single Rust + Python workspace.
|
config.json
CHANGED
|
@@ -1,122 +1,122 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "LuminaV",
|
| 3 |
-
"version": "1.6.0",
|
| 4 |
-
"package": "luminamoon",
|
| 5 |
-
"package_version": "1.0.9",
|
| 6 |
-
"description": "Master-free, low-precision BF16/FP16 deep learning optimizer with Dual-Buffer Convex Lookahead Momentum (CLM), Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM) via the Triangle Inequality Envelope, rational Pade [13/6] bounding, stateless 32-bit MurmurHash3 stochastic rounding, and native Rust SIMD / Vulkan / Triton execution.",
|
| 7 |
-
"repository": "https://github.com/cloverx-id/luminamoon",
|
| 8 |
-
"documentation": "https://cloverx-id.github.io/luminamoon/",
|
| 9 |
-
"default_params": {
|
| 10 |
-
"lr": 0.0008,
|
| 11 |
-
"betas": [
|
| 12 |
-
0.9,
|
| 13 |
-
0.999
|
| 14 |
-
],
|
| 15 |
-
"eps": 1e-08,
|
| 16 |
-
"weight_decay": 0.08,
|
| 17 |
-
"tau": 0.8,
|
| 18 |
-
"lam": 0.3,
|
| 19 |
-
"cautious": true,
|
| 20 |
-
"cautious_clamp_min": 0.5,
|
| 21 |
-
"buffer": 2,
|
| 22 |
-
"stochastic_rounding": true,
|
| 23 |
-
"bound": true,
|
| 24 |
-
"bound_type": "coordinate",
|
| 25 |
-
"bound_ratio": 0.05,
|
| 26 |
-
"master_weights": "none",
|
| 27 |
-
"execution": "auto",
|
| 28 |
-
"fused_single_pass": true,
|
| 29 |
-
"fuse_tensors": false,
|
| 30 |
-
"bucket_cap_mb": 500.0,
|
| 31 |
-
"prewarm_jit": true,
|
| 32 |
-
"ema_decay": "auto",
|
| 33 |
-
"fsdp_sync": false,
|
| 34 |
-
"seed": 1337
|
| 35 |
-
},
|
| 36 |
-
"presets": {
|
| 37 |
-
"dual_buffer_clm": {
|
| 38 |
-
"buffer": 2,
|
| 39 |
-
"tau": 0.8,
|
| 40 |
-
"cautious": true,
|
| 41 |
-
"cautious_clamp_min": 0.5,
|
| 42 |
-
"bound": true,
|
| 43 |
-
"bound_type": "coordinate",
|
| 44 |
-
"bound_ratio": 0.05,
|
| 45 |
-
"stochastic_rounding": true,
|
| 46 |
-
"master_weights": "none",
|
| 47 |
-
"fused_single_pass": true,
|
| 48 |
-
"ema_decay": "auto",
|
| 49 |
-
"fsdp_sync": false,
|
| 50 |
-
"description": "Default Dual-Buffer Convex Lookahead Momentum (CLM) tracking m_t and innovation variance v_t with coordinate trust-region bounding."
|
| 51 |
-
},
|
| 52 |
-
"single_buffer_td_clm": {
|
| 53 |
-
"buffer": 1,
|
| 54 |
-
"lam": 0.3,
|
| 55 |
-
"cautious": true,
|
| 56 |
-
"cautious_clamp_min": 0.5,
|
| 57 |
-
"bound": true,
|
| 58 |
-
"bound_type": "coordinate",
|
| 59 |
-
"bound_ratio": 0.05,
|
| 60 |
-
"stochastic_rounding": true,
|
| 61 |
-
"master_weights": "none",
|
| 62 |
-
"fused_single_pass": true,
|
| 63 |
-
"ema_decay": "auto",
|
| 64 |
-
"fsdp_sync": false,
|
| 65 |
-
"description": "Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM) using the Triangle Inequality Envelope (cuts optimizer state RAM by 50% with zero global reductions in coordinate mode)."
|
| 66 |
-
},
|
| 67 |
-
"radial_trust_region_bound": {
|
| 68 |
-
"buffer": 2,
|
| 69 |
-
"tau": 0.8,
|
| 70 |
-
"cautious": true,
|
| 71 |
-
"cautious_clamp_min": 0.5,
|
| 72 |
-
"bound": true,
|
| 73 |
-
"bound_type": "radial",
|
| 74 |
-
"bound_ratio": 0.03,
|
| 75 |
-
"stochastic_rounding": true,
|
| 76 |
-
"master_weights": "none",
|
| 77 |
-
"fused_single_pass": true,
|
| 78 |
-
"ema_decay": "auto",
|
| 79 |
-
"fsdp_sync": false,
|
| 80 |
-
"description": "Direction-preserving radial trust-region bounding (tanh(r)/r) preserving 100% update angular geometry."
|
| 81 |
-
},
|
| 82 |
-
"semi_hybrid_fp32_master": {
|
| 83 |
-
"buffer": 2,
|
| 84 |
-
"cautious": true,
|
| 85 |
-
"cautious_clamp_min": 0.5,
|
| 86 |
-
"bound": true,
|
| 87 |
-
"bound_type": "coordinate",
|
| 88 |
-
"bound_ratio": 0.05,
|
| 89 |
-
"stochastic_rounding": true,
|
| 90 |
-
"master_weights": "semi",
|
| 91 |
-
"fused_single_pass": true,
|
| 92 |
-
"fsdp_sync": false,
|
| 93 |
-
"description": "Hybrid precision pairing 32-bit FP32 master weights with 16-bit BF16/FP16 optimizer moment buffers."
|
| 94 |
-
},
|
| 95 |
-
"full_fp32_baseline": {
|
| 96 |
-
"buffer": 2,
|
| 97 |
-
"cautious": true,
|
| 98 |
-
"cautious_clamp_min": 0.5,
|
| 99 |
-
"bound": true,
|
| 100 |
-
"bound_type": "coordinate",
|
| 101 |
-
"bound_ratio": 0.05,
|
| 102 |
-
"stochastic_rounding": true,
|
| 103 |
-
"master_weights": "full",
|
| 104 |
-
"fused_single_pass": true,
|
| 105 |
-
"fsdp_sync": false,
|
| 106 |
-
"description": "Full-precision FP32 master weights and FP32 optimizer states for controlled baseline evaluation."
|
| 107 |
-
},
|
| 108 |
-
"fsdp_distributed_sync": {
|
| 109 |
-
"buffer": 2,
|
| 110 |
-
"cautious": true,
|
| 111 |
-
"cautious_clamp_min": 0.5,
|
| 112 |
-
"bound": true,
|
| 113 |
-
"bound_type": "radial",
|
| 114 |
-
"bound_ratio": 0.03,
|
| 115 |
-
"stochastic_rounding": true,
|
| 116 |
-
"master_weights": "none",
|
| 117 |
-
"fused_single_pass": true,
|
| 118 |
-
"fsdp_sync": true,
|
| 119 |
-
"description": "Distributed multi-GPU training mode under PyTorch FSDP or DeepSpeed ZeRO-3 with cross-rank collective scratch synchronization."
|
| 120 |
-
}
|
| 121 |
-
}
|
| 122 |
-
}
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "LuminaV",
|
| 3 |
+
"version": "1.6.0",
|
| 4 |
+
"package": "luminamoon",
|
| 5 |
+
"package_version": "1.0.9",
|
| 6 |
+
"description": "Master-free, low-precision BF16/FP16 deep learning optimizer with Dual-Buffer Convex Lookahead Momentum (CLM), Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM) via the Triangle Inequality Envelope, rational Pade [13/6] bounding, stateless 32-bit MurmurHash3 stochastic rounding, and native Rust SIMD / Vulkan / Triton execution.",
|
| 7 |
+
"repository": "https://github.com/cloverx-id/luminamoon",
|
| 8 |
+
"documentation": "https://cloverx-id.github.io/luminamoon/",
|
| 9 |
+
"default_params": {
|
| 10 |
+
"lr": 0.0008,
|
| 11 |
+
"betas": [
|
| 12 |
+
0.9,
|
| 13 |
+
0.999
|
| 14 |
+
],
|
| 15 |
+
"eps": 1e-08,
|
| 16 |
+
"weight_decay": 0.08,
|
| 17 |
+
"tau": 0.8,
|
| 18 |
+
"lam": 0.3,
|
| 19 |
+
"cautious": true,
|
| 20 |
+
"cautious_clamp_min": 0.5,
|
| 21 |
+
"buffer": 2,
|
| 22 |
+
"stochastic_rounding": true,
|
| 23 |
+
"bound": true,
|
| 24 |
+
"bound_type": "coordinate",
|
| 25 |
+
"bound_ratio": 0.05,
|
| 26 |
+
"master_weights": "none",
|
| 27 |
+
"execution": "auto",
|
| 28 |
+
"fused_single_pass": true,
|
| 29 |
+
"fuse_tensors": false,
|
| 30 |
+
"bucket_cap_mb": 500.0,
|
| 31 |
+
"prewarm_jit": true,
|
| 32 |
+
"ema_decay": "auto",
|
| 33 |
+
"fsdp_sync": false,
|
| 34 |
+
"seed": 1337
|
| 35 |
+
},
|
| 36 |
+
"presets": {
|
| 37 |
+
"dual_buffer_clm": {
|
| 38 |
+
"buffer": 2,
|
| 39 |
+
"tau": 0.8,
|
| 40 |
+
"cautious": true,
|
| 41 |
+
"cautious_clamp_min": 0.5,
|
| 42 |
+
"bound": true,
|
| 43 |
+
"bound_type": "coordinate",
|
| 44 |
+
"bound_ratio": 0.05,
|
| 45 |
+
"stochastic_rounding": true,
|
| 46 |
+
"master_weights": "none",
|
| 47 |
+
"fused_single_pass": true,
|
| 48 |
+
"ema_decay": "auto",
|
| 49 |
+
"fsdp_sync": false,
|
| 50 |
+
"description": "Default Dual-Buffer Convex Lookahead Momentum (CLM) tracking m_t and innovation variance v_t with coordinate trust-region bounding."
|
| 51 |
+
},
|
| 52 |
+
"single_buffer_td_clm": {
|
| 53 |
+
"buffer": 1,
|
| 54 |
+
"lam": 0.3,
|
| 55 |
+
"cautious": true,
|
| 56 |
+
"cautious_clamp_min": 0.5,
|
| 57 |
+
"bound": true,
|
| 58 |
+
"bound_type": "coordinate",
|
| 59 |
+
"bound_ratio": 0.05,
|
| 60 |
+
"stochastic_rounding": true,
|
| 61 |
+
"master_weights": "none",
|
| 62 |
+
"fused_single_pass": true,
|
| 63 |
+
"ema_decay": "auto",
|
| 64 |
+
"fsdp_sync": false,
|
| 65 |
+
"description": "Single-Buffer Triangle-Defect Convex Lookahead Momentum (TD-CLM) using the Triangle Inequality Envelope (cuts optimizer state RAM by 50% with zero global reductions in coordinate mode)."
|
| 66 |
+
},
|
| 67 |
+
"radial_trust_region_bound": {
|
| 68 |
+
"buffer": 2,
|
| 69 |
+
"tau": 0.8,
|
| 70 |
+
"cautious": true,
|
| 71 |
+
"cautious_clamp_min": 0.5,
|
| 72 |
+
"bound": true,
|
| 73 |
+
"bound_type": "radial",
|
| 74 |
+
"bound_ratio": 0.03,
|
| 75 |
+
"stochastic_rounding": true,
|
| 76 |
+
"master_weights": "none",
|
| 77 |
+
"fused_single_pass": true,
|
| 78 |
+
"ema_decay": "auto",
|
| 79 |
+
"fsdp_sync": false,
|
| 80 |
+
"description": "Direction-preserving radial trust-region bounding (tanh(r)/r) preserving 100% update angular geometry."
|
| 81 |
+
},
|
| 82 |
+
"semi_hybrid_fp32_master": {
|
| 83 |
+
"buffer": 2,
|
| 84 |
+
"cautious": true,
|
| 85 |
+
"cautious_clamp_min": 0.5,
|
| 86 |
+
"bound": true,
|
| 87 |
+
"bound_type": "coordinate",
|
| 88 |
+
"bound_ratio": 0.05,
|
| 89 |
+
"stochastic_rounding": true,
|
| 90 |
+
"master_weights": "semi",
|
| 91 |
+
"fused_single_pass": true,
|
| 92 |
+
"fsdp_sync": false,
|
| 93 |
+
"description": "Hybrid precision pairing 32-bit FP32 master weights with 16-bit BF16/FP16 optimizer moment buffers."
|
| 94 |
+
},
|
| 95 |
+
"full_fp32_baseline": {
|
| 96 |
+
"buffer": 2,
|
| 97 |
+
"cautious": true,
|
| 98 |
+
"cautious_clamp_min": 0.5,
|
| 99 |
+
"bound": true,
|
| 100 |
+
"bound_type": "coordinate",
|
| 101 |
+
"bound_ratio": 0.05,
|
| 102 |
+
"stochastic_rounding": true,
|
| 103 |
+
"master_weights": "full",
|
| 104 |
+
"fused_single_pass": true,
|
| 105 |
+
"fsdp_sync": false,
|
| 106 |
+
"description": "Full-precision FP32 master weights and FP32 optimizer states for controlled baseline evaluation."
|
| 107 |
+
},
|
| 108 |
+
"fsdp_distributed_sync": {
|
| 109 |
+
"buffer": 2,
|
| 110 |
+
"cautious": true,
|
| 111 |
+
"cautious_clamp_min": 0.5,
|
| 112 |
+
"bound": true,
|
| 113 |
+
"bound_type": "radial",
|
| 114 |
+
"bound_ratio": 0.03,
|
| 115 |
+
"stochastic_rounding": true,
|
| 116 |
+
"master_weights": "none",
|
| 117 |
+
"fused_single_pass": true,
|
| 118 |
+
"fsdp_sync": true,
|
| 119 |
+
"description": "Distributed multi-GPU training mode under PyTorch FSDP or DeepSpeed ZeRO-3 with cross-rank collective scratch synchronization."
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
}
|
data/Cargo.toml
CHANGED
|
@@ -1,24 +1,24 @@
|
|
| 1 |
-
[package]
|
| 2 |
-
name = "luminamoon-dataloader"
|
| 3 |
-
version.workspace = true
|
| 4 |
-
edition.workspace = true
|
| 5 |
-
license.workspace = true
|
| 6 |
-
|
| 7 |
-
[lib]
|
| 8 |
-
name = "luminamoon_dataloader"
|
| 9 |
-
path = "src/lib.rs"
|
| 10 |
-
|
| 11 |
-
[dependencies]
|
| 12 |
-
pyo3.workspace = true
|
| 13 |
-
thiserror.workspace = true
|
| 14 |
-
serde.workspace = true
|
| 15 |
-
serde_json.workspace = true
|
| 16 |
-
rayon.workspace = true
|
| 17 |
-
parquet.workspace = true
|
| 18 |
-
arrow-array.workspace = true
|
| 19 |
-
arrow-schema.workspace = true
|
| 20 |
-
arrow-ipc.workspace = true
|
| 21 |
-
xxhash-rust.workspace = true
|
| 22 |
-
crc32fast.workspace = true
|
| 23 |
-
hashbrown.workspace = true
|
| 24 |
-
parking_lot.workspace = true
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "luminamoon-dataloader"
|
| 3 |
+
version.workspace = true
|
| 4 |
+
edition.workspace = true
|
| 5 |
+
license.workspace = true
|
| 6 |
+
|
| 7 |
+
[lib]
|
| 8 |
+
name = "luminamoon_dataloader"
|
| 9 |
+
path = "src/lib.rs"
|
| 10 |
+
|
| 11 |
+
[dependencies]
|
| 12 |
+
pyo3.workspace = true
|
| 13 |
+
thiserror.workspace = true
|
| 14 |
+
serde.workspace = true
|
| 15 |
+
serde_json.workspace = true
|
| 16 |
+
rayon.workspace = true
|
| 17 |
+
parquet.workspace = true
|
| 18 |
+
arrow-array.workspace = true
|
| 19 |
+
arrow-schema.workspace = true
|
| 20 |
+
arrow-ipc.workspace = true
|
| 21 |
+
xxhash-rust.workspace = true
|
| 22 |
+
crc32fast.workspace = true
|
| 23 |
+
hashbrown.workspace = true
|
| 24 |
+
parking_lot.workspace = true
|
data/README.md
CHANGED
|
@@ -1,206 +1,206 @@
|
|
| 1 |
-
# LuminaMoon Data Engine v1.1.0
|
| 2 |
-
|
| 3 |
-
LuminaMoon Data (`luminamoon.data`) provides text cleaning, SIMD normalization, heuristic quality filtering, exact and fuzzy deduplication, and crash-resilient checkpointing for Python and Rust data pipelines, including PyTorch, Hugging Face datasets, Ray, and WebDataset.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Modular Architecture & Responsibilities
|
| 8 |
-
|
| 9 |
-
LuminaMoon Data focuses on in-memory text processing and deterministic state tracking, integrating smoothly with existing dataset readers:
|
| 10 |
-
|
| 11 |
-
```mermaid
|
| 12 |
-
flowchart LR
|
| 13 |
-
subgraph External ["External Data Sources"]
|
| 14 |
-
HF["Hugging Face datasets.load_dataset(streaming=True)"]
|
| 15 |
-
Torch["PyTorch torch.utils.data.DataLoader"]
|
| 16 |
-
Custom["Custom Python Generator / Iterables"]
|
| 17 |
-
end
|
| 18 |
-
|
| 19 |
-
subgraph Data_Engine ["LuminaMoon Data Engine (Rust Core)"]
|
| 20 |
-
direction TB
|
| 21 |
-
Cleaner["1. TextCleaner\n(AVX-512/AVX2/NEON SIMD CRLF & JSON path extraction)"]
|
| 22 |
-
Quality["2. QualityFilter\n(Shannon entropy, repetition loop, symbol ratio)"]
|
| 23 |
-
Dedup["3. StreamDeduplicator & MinHash\n(XXH3-64, SwissTable WAL, MinHash fuzzy LSH)"]
|
| 24 |
-
Masker["4. PIIMasker\n(Email, IP, Bearer/API Key, Card pattern sanitizer)"]
|
| 25 |
-
Tracker["5. StateTracker\n(Dual-Slot 128B Ping-Pong Slot A/B CRC32)"]
|
| 26 |
-
Glue["6. Collate & Pipeline Glue\n(PyTorch collate_fn, ResumablePipeline)"]
|
| 27 |
-
end
|
| 28 |
-
|
| 29 |
-
HF --> Cleaner
|
| 30 |
-
Torch --> Cleaner
|
| 31 |
-
Custom --> Cleaner
|
| 32 |
-
Cleaner --> Quality
|
| 33 |
-
Quality --> Dedup
|
| 34 |
-
Dedup --> Masker
|
| 35 |
-
Masker --> Tracker
|
| 36 |
-
Tracker --> Glue
|
| 37 |
-
Glue --> Consumer["GPU Model Training Step"]
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
---
|
| 41 |
-
|
| 42 |
-
## 2. Component Breakdown
|
| 43 |
-
|
| 44 |
-
| Module | Core Responsibility | Underlying Implementation |
|
| 45 |
-
| :--- | :--- | :--- |
|
| 46 |
-
| **`luminamoon.data.cleaner`** | Whitespace trimming, SIMD newline normalization (`\r\n` $\rightarrow$ `\n`), byte length boundaries, and JSON path extraction. | `simd_cpu.rs`, `postprocess.rs` (Rayon parallel multi-threading, AVX-512BW, AVX2, NEON) |
|
| 47 |
-
| **`luminamoon.data.quality`** | Shannon Character Entropy, loop repetition detector, and symbol-to-word ratio filtering. | `quality.rs` (Parallel Rayon heuristic quality filter) |
|
| 48 |
-
| **`luminamoon.data.dedup`** | Stateful 64-bit exact hashing and deduplication with write-ahead log (`.dedup`) persistence. | `dedup.rs` (`xxh3_64` + `IdentityBuildHasher` SwissTable) |
|
| 49 |
-
| **`luminamoon.data.minhash`** | Fuzzy near-duplicate detection via MinHash shingling and Jaccard similarity estimation. | `minhash.rs` (Permuted XXH3-64 MinHash signatures) |
|
| 50 |
-
| **`luminamoon.data.masker`** | Pattern-based PII masking for emails, IPs, API keys/tokens, and payment cards. | `masker.py` (Compiled multi-pattern regex sanitizer) |
|
| 51 |
-
| **`luminamoon.data.checkpoint`** | Dual-slot atomic coordinate checkpointing (`shard_id`, `row_offset`, `byte_offset`, `global_idx`). | `metadata.rs`, `coord.rs` (128-byte Dual-Slot Ping-Pong Slot A/B CRC32) |
|
| 52 |
-
| **`luminamoon.data.string_tensor`** | Columnar string representation with CPU inlined StringView and GPU contiguous offset/payload buffers. | `string_tensor.rs` (16-byte Umbra/DuckDB StringView, SIMD prefix filter, contiguous u32 offsets + u8 payload) |
|
| 53 |
-
| **`luminamoon.data.adapters`** | PyTorch `collate_fn` adapter and batch `ResumablePipeline`. | `adapters.py` (Dynamic token padding & tensor generation) |
|
| 54 |
-
| **`luminamoon.data.serialization`** | Conversion of Python dictionaries to Parquet, JSONL, or Arrow IPC bytes. | `serialize.rs` (Arrow RecordBatch & Parquet ArrowWriter with ZSTD) |
|
| 55 |
-
|
| 56 |
-
---
|
| 57 |
-
|
| 58 |
-
## 3. Quickstart & Usage
|
| 59 |
-
|
| 60 |
-
### 3.1. Text Cleaning with SIMD & Rayon
|
| 61 |
-
```python
|
| 62 |
-
from luminamoon.data import TextCleaner
|
| 63 |
-
|
| 64 |
-
cleaner = TextCleaner(
|
| 65 |
-
trim_whitespace=True,
|
| 66 |
-
normalize_newlines=True, # AVX-512 / AVX2 / NEON SIMD
|
| 67 |
-
min_byte_len=10,
|
| 68 |
-
max_byte_len=2048,
|
| 69 |
-
truncate_over_max=True,
|
| 70 |
-
json_extract_key="data.text", # Extracts dotted path in Rust
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
-
cleaned = cleaner.clean_batch([" sample 1\r\n", "sample 2 "])
|
| 74 |
-
```
|
| 75 |
-
|
| 76 |
-
### 3.2. Quality Filtering (Shannon Entropy & Repetition Detector)
|
| 77 |
-
```python
|
| 78 |
-
from luminamoon.data import QualityFilter
|
| 79 |
-
|
| 80 |
-
qf = QualityFilter(
|
| 81 |
-
min_entropy=2.5, # Filters low-entropy noise
|
| 82 |
-
max_repetition_ratio=0.35, # Filters repetitive text loops
|
| 83 |
-
max_symbol_ratio=0.30, # Filters symbol-heavy dumps
|
| 84 |
-
)
|
| 85 |
-
|
| 86 |
-
if qf.is_high_quality("Sample prose text for language model training."):
|
| 87 |
-
...
|
| 88 |
-
```
|
| 89 |
-
|
| 90 |
-
### 3.3. Exact & Fuzzy Near-Deduplication
|
| 91 |
-
```python
|
| 92 |
-
from luminamoon.data import StreamDeduplicator, MinHashDeduplicator
|
| 93 |
-
|
| 94 |
-
# Exact dedup with WAL
|
| 95 |
-
dedup = StreamDeduplicator(wal_path="./cache/dataset.dedup")
|
| 96 |
-
unique_batch = dedup.filter_batch(cleaned)
|
| 97 |
-
dedup.commit()
|
| 98 |
-
|
| 99 |
-
# Fuzzy near-dedup (> 85% similarity)
|
| 100 |
-
fuzzy = MinHashDeduplicator(threshold=0.85)
|
| 101 |
-
if fuzzy.is_unique("A document with slight punctuation differences."):
|
| 102 |
-
...
|
| 103 |
-
```
|
| 104 |
-
|
| 105 |
-
### 3.4. PII Sanitization
|
| 106 |
-
```python
|
| 107 |
-
from luminamoon.data import PIIMasker
|
| 108 |
-
|
| 109 |
-
masker = PIIMasker(mask_emails=True, mask_ips=True, mask_api_keys=True)
|
| 110 |
-
safe_text = masker.mask("Contact support@lumi.ai with key Bearer sk-1234567890abcdef1234567890abcdef")
|
| 111 |
-
# Result: "Contact [EMAIL] with key [API_KEY]"
|
| 112 |
-
```
|
| 113 |
-
|
| 114 |
-
### 3.5. Dual-Layout StringTensor (CPU StringView & GPU Buffers)
|
| 115 |
-
```python
|
| 116 |
-
from luminamoon.data import StringTensor
|
| 117 |
-
|
| 118 |
-
# 1. CPU Layout (Pattern 2 - Umbra / DuckDB / Arrow 2.0 StringView)
|
| 119 |
-
# Strings <= 12 bytes are stored inline in a 16-byte struct
|
| 120 |
-
st = StringTensor(["short", "this is a longer sample exceeding 12 bytes", "pref_sample"])
|
| 121 |
-
print(st.is_inlined_mask()) # [True, False, True]
|
| 122 |
-
|
| 123 |
-
# SIMD prefix and length filtering
|
| 124 |
-
filtered = st.filter_by_prefix("pref_")
|
| 125 |
-
hashes = st.hash_all() # Multi-threaded XXH3-64 hashing in Rust
|
| 126 |
-
|
| 127 |
-
# 2. GPU Layout (Pattern 1 - Apache Arrow & TensorFlow Contiguous Offsets + Data Buffer)
|
| 128 |
-
offsets, payload = st.to_gpu_buffers() # Raw uint32 offsets and uint8 payload
|
| 129 |
-
off_tensor, pay_tensor = st.to_torch_gpu() # PyTorch tensors
|
| 130 |
-
off_arr, pay_arr = st.to_numpy_gpu() # NumPy buffers
|
| 131 |
-
arrow_array = st.to_arrow() # Apache Arrow 2.0 StringArray
|
| 132 |
-
polars_series = st.to_polars() # Polars Series
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
### 3.6. Fused Single-Pass Pipeline
|
| 136 |
-
```python
|
| 137 |
-
from luminamoon.data import FusedPipeline, StreamDeduplicator
|
| 138 |
-
|
| 139 |
-
pipeline = FusedPipeline(
|
| 140 |
-
trim_whitespace=True,
|
| 141 |
-
normalize_newlines=True,
|
| 142 |
-
min_chars=15,
|
| 143 |
-
min_entropy=2.0,
|
| 144 |
-
deduplicator=StreamDeduplicator(),
|
| 145 |
-
commit_dedup=True,
|
| 146 |
-
)
|
| 147 |
-
|
| 148 |
-
# Process a batch in a single multi-threaded Rayon pass
|
| 149 |
-
string_tensor = pipeline(raw_records)
|
| 150 |
-
```
|
| 151 |
-
|
| 152 |
-
### 3.7. PyTorch Collate & Async Prefetch Pipeline
|
| 153 |
-
```python
|
| 154 |
-
from torch.utils.data import DataLoader
|
| 155 |
-
from luminamoon.data import create_resumable_collate, ResumablePipeline, FusedPipeline
|
| 156 |
-
|
| 157 |
-
# 1. As PyTorch collate_fn
|
| 158 |
-
collate_fn = create_resumable_collate(
|
| 159 |
-
fused_pipeline=FusedPipeline(),
|
| 160 |
-
return_string_tensor=True,
|
| 161 |
-
tokenizer=my_tokenizer,
|
| 162 |
-
max_length=512,
|
| 163 |
-
)
|
| 164 |
-
loader = DataLoader(raw_dataset, batch_size=64, collate_fn=collate_fn)
|
| 165 |
-
|
| 166 |
-
# 2. Or as batch ResumablePipeline with background prefetcher
|
| 167 |
-
resumable_pipe = ResumablePipeline(
|
| 168 |
-
source=raw_dataset,
|
| 169 |
-
fused_pipeline=FusedPipeline(),
|
| 170 |
-
batch_size=64,
|
| 171 |
-
prefetch_batches=4, # Background prefetch worker for processed batches
|
| 172 |
-
return_string_tensor=True,
|
| 173 |
-
)
|
| 174 |
-
for batch in resumable_pipe:
|
| 175 |
-
...
|
| 176 |
-
```
|
| 177 |
-
|
| 178 |
-
---
|
| 179 |
-
|
| 180 |
-
## 4. Verification & Testing
|
| 181 |
-
|
| 182 |
-
Run Rust tests:
|
| 183 |
-
|
| 184 |
-
```bash
|
| 185 |
-
cargo test -p luminamoon-dataloader
|
| 186 |
-
```
|
| 187 |
-
|
| 188 |
-
Run Python modular test suite:
|
| 189 |
-
|
| 190 |
-
```bash
|
| 191 |
-
python -B tests/test_data_modular.py
|
| 192 |
-
```
|
| 193 |
-
|
| 194 |
-
---
|
| 195 |
-
|
| 196 |
-
## 5. Version Inspection
|
| 197 |
-
|
| 198 |
-
Query the current data engine module version:
|
| 199 |
-
|
| 200 |
-
```python
|
| 201 |
-
import luminamoon.data as lmd
|
| 202 |
-
|
| 203 |
-
print(lmd.get_version()) # Active package release version string
|
| 204 |
-
```
|
| 205 |
-
|
| 206 |
-
> **Note**: LuminaMoon Data operates on a single unified native SIMD engine. Package releases follow `luminamoon.__version__`.
|
|
|
|
| 1 |
+
# LuminaMoon Data Engine v1.1.0
|
| 2 |
+
|
| 3 |
+
LuminaMoon Data (`luminamoon.data`) provides text cleaning, SIMD normalization, heuristic quality filtering, exact and fuzzy deduplication, and crash-resilient checkpointing for Python and Rust data pipelines, including PyTorch, Hugging Face datasets, Ray, and WebDataset.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Modular Architecture & Responsibilities
|
| 8 |
+
|
| 9 |
+
LuminaMoon Data focuses on in-memory text processing and deterministic state tracking, integrating smoothly with existing dataset readers:
|
| 10 |
+
|
| 11 |
+
```mermaid
|
| 12 |
+
flowchart LR
|
| 13 |
+
subgraph External ["External Data Sources"]
|
| 14 |
+
HF["Hugging Face datasets.load_dataset(streaming=True)"]
|
| 15 |
+
Torch["PyTorch torch.utils.data.DataLoader"]
|
| 16 |
+
Custom["Custom Python Generator / Iterables"]
|
| 17 |
+
end
|
| 18 |
+
|
| 19 |
+
subgraph Data_Engine ["LuminaMoon Data Engine (Rust Core)"]
|
| 20 |
+
direction TB
|
| 21 |
+
Cleaner["1. TextCleaner\n(AVX-512/AVX2/NEON SIMD CRLF & JSON path extraction)"]
|
| 22 |
+
Quality["2. QualityFilter\n(Shannon entropy, repetition loop, symbol ratio)"]
|
| 23 |
+
Dedup["3. StreamDeduplicator & MinHash\n(XXH3-64, SwissTable WAL, MinHash fuzzy LSH)"]
|
| 24 |
+
Masker["4. PIIMasker\n(Email, IP, Bearer/API Key, Card pattern sanitizer)"]
|
| 25 |
+
Tracker["5. StateTracker\n(Dual-Slot 128B Ping-Pong Slot A/B CRC32)"]
|
| 26 |
+
Glue["6. Collate & Pipeline Glue\n(PyTorch collate_fn, ResumablePipeline)"]
|
| 27 |
+
end
|
| 28 |
+
|
| 29 |
+
HF --> Cleaner
|
| 30 |
+
Torch --> Cleaner
|
| 31 |
+
Custom --> Cleaner
|
| 32 |
+
Cleaner --> Quality
|
| 33 |
+
Quality --> Dedup
|
| 34 |
+
Dedup --> Masker
|
| 35 |
+
Masker --> Tracker
|
| 36 |
+
Tracker --> Glue
|
| 37 |
+
Glue --> Consumer["GPU Model Training Step"]
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## 2. Component Breakdown
|
| 43 |
+
|
| 44 |
+
| Module | Core Responsibility | Underlying Implementation |
|
| 45 |
+
| :--- | :--- | :--- |
|
| 46 |
+
| **`luminamoon.data.cleaner`** | Whitespace trimming, SIMD newline normalization (`\r\n` $\rightarrow$ `\n`), byte length boundaries, and JSON path extraction. | `simd_cpu.rs`, `postprocess.rs` (Rayon parallel multi-threading, AVX-512BW, AVX2, NEON) |
|
| 47 |
+
| **`luminamoon.data.quality`** | Shannon Character Entropy, loop repetition detector, and symbol-to-word ratio filtering. | `quality.rs` (Parallel Rayon heuristic quality filter) |
|
| 48 |
+
| **`luminamoon.data.dedup`** | Stateful 64-bit exact hashing and deduplication with write-ahead log (`.dedup`) persistence. | `dedup.rs` (`xxh3_64` + `IdentityBuildHasher` SwissTable) |
|
| 49 |
+
| **`luminamoon.data.minhash`** | Fuzzy near-duplicate detection via MinHash shingling and Jaccard similarity estimation. | `minhash.rs` (Permuted XXH3-64 MinHash signatures) |
|
| 50 |
+
| **`luminamoon.data.masker`** | Pattern-based PII masking for emails, IPs, API keys/tokens, and payment cards. | `masker.py` (Compiled multi-pattern regex sanitizer) |
|
| 51 |
+
| **`luminamoon.data.checkpoint`** | Dual-slot atomic coordinate checkpointing (`shard_id`, `row_offset`, `byte_offset`, `global_idx`). | `metadata.rs`, `coord.rs` (128-byte Dual-Slot Ping-Pong Slot A/B CRC32) |
|
| 52 |
+
| **`luminamoon.data.string_tensor`** | Columnar string representation with CPU inlined StringView and GPU contiguous offset/payload buffers. | `string_tensor.rs` (16-byte Umbra/DuckDB StringView, SIMD prefix filter, contiguous u32 offsets + u8 payload) |
|
| 53 |
+
| **`luminamoon.data.adapters`** | PyTorch `collate_fn` adapter and batch `ResumablePipeline`. | `adapters.py` (Dynamic token padding & tensor generation) |
|
| 54 |
+
| **`luminamoon.data.serialization`** | Conversion of Python dictionaries to Parquet, JSONL, or Arrow IPC bytes. | `serialize.rs` (Arrow RecordBatch & Parquet ArrowWriter with ZSTD) |
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## 3. Quickstart & Usage
|
| 59 |
+
|
| 60 |
+
### 3.1. Text Cleaning with SIMD & Rayon
|
| 61 |
+
```python
|
| 62 |
+
from luminamoon.data import TextCleaner
|
| 63 |
+
|
| 64 |
+
cleaner = TextCleaner(
|
| 65 |
+
trim_whitespace=True,
|
| 66 |
+
normalize_newlines=True, # AVX-512 / AVX2 / NEON SIMD
|
| 67 |
+
min_byte_len=10,
|
| 68 |
+
max_byte_len=2048,
|
| 69 |
+
truncate_over_max=True,
|
| 70 |
+
json_extract_key="data.text", # Extracts dotted path in Rust
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
cleaned = cleaner.clean_batch([" sample 1\r\n", "sample 2 "])
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
### 3.2. Quality Filtering (Shannon Entropy & Repetition Detector)
|
| 77 |
+
```python
|
| 78 |
+
from luminamoon.data import QualityFilter
|
| 79 |
+
|
| 80 |
+
qf = QualityFilter(
|
| 81 |
+
min_entropy=2.5, # Filters low-entropy noise
|
| 82 |
+
max_repetition_ratio=0.35, # Filters repetitive text loops
|
| 83 |
+
max_symbol_ratio=0.30, # Filters symbol-heavy dumps
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
if qf.is_high_quality("Sample prose text for language model training."):
|
| 87 |
+
...
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
### 3.3. Exact & Fuzzy Near-Deduplication
|
| 91 |
+
```python
|
| 92 |
+
from luminamoon.data import StreamDeduplicator, MinHashDeduplicator
|
| 93 |
+
|
| 94 |
+
# Exact dedup with WAL
|
| 95 |
+
dedup = StreamDeduplicator(wal_path="./cache/dataset.dedup")
|
| 96 |
+
unique_batch = dedup.filter_batch(cleaned)
|
| 97 |
+
dedup.commit()
|
| 98 |
+
|
| 99 |
+
# Fuzzy near-dedup (> 85% similarity)
|
| 100 |
+
fuzzy = MinHashDeduplicator(threshold=0.85)
|
| 101 |
+
if fuzzy.is_unique("A document with slight punctuation differences."):
|
| 102 |
+
...
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
### 3.4. PII Sanitization
|
| 106 |
+
```python
|
| 107 |
+
from luminamoon.data import PIIMasker
|
| 108 |
+
|
| 109 |
+
masker = PIIMasker(mask_emails=True, mask_ips=True, mask_api_keys=True)
|
| 110 |
+
safe_text = masker.mask("Contact support@lumi.ai with key Bearer sk-1234567890abcdef1234567890abcdef")
|
| 111 |
+
# Result: "Contact [EMAIL] with key [API_KEY]"
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
### 3.5. Dual-Layout StringTensor (CPU StringView & GPU Buffers)
|
| 115 |
+
```python
|
| 116 |
+
from luminamoon.data import StringTensor
|
| 117 |
+
|
| 118 |
+
# 1. CPU Layout (Pattern 2 - Umbra / DuckDB / Arrow 2.0 StringView)
|
| 119 |
+
# Strings <= 12 bytes are stored inline in a 16-byte struct
|
| 120 |
+
st = StringTensor(["short", "this is a longer sample exceeding 12 bytes", "pref_sample"])
|
| 121 |
+
print(st.is_inlined_mask()) # [True, False, True]
|
| 122 |
+
|
| 123 |
+
# SIMD prefix and length filtering
|
| 124 |
+
filtered = st.filter_by_prefix("pref_")
|
| 125 |
+
hashes = st.hash_all() # Multi-threaded XXH3-64 hashing in Rust
|
| 126 |
+
|
| 127 |
+
# 2. GPU Layout (Pattern 1 - Apache Arrow & TensorFlow Contiguous Offsets + Data Buffer)
|
| 128 |
+
offsets, payload = st.to_gpu_buffers() # Raw uint32 offsets and uint8 payload
|
| 129 |
+
off_tensor, pay_tensor = st.to_torch_gpu() # PyTorch tensors
|
| 130 |
+
off_arr, pay_arr = st.to_numpy_gpu() # NumPy buffers
|
| 131 |
+
arrow_array = st.to_arrow() # Apache Arrow 2.0 StringArray
|
| 132 |
+
polars_series = st.to_polars() # Polars Series
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### 3.6. Fused Single-Pass Pipeline
|
| 136 |
+
```python
|
| 137 |
+
from luminamoon.data import FusedPipeline, StreamDeduplicator
|
| 138 |
+
|
| 139 |
+
pipeline = FusedPipeline(
|
| 140 |
+
trim_whitespace=True,
|
| 141 |
+
normalize_newlines=True,
|
| 142 |
+
min_chars=15,
|
| 143 |
+
min_entropy=2.0,
|
| 144 |
+
deduplicator=StreamDeduplicator(),
|
| 145 |
+
commit_dedup=True,
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
# Process a batch in a single multi-threaded Rayon pass
|
| 149 |
+
string_tensor = pipeline(raw_records)
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### 3.7. PyTorch Collate & Async Prefetch Pipeline
|
| 153 |
+
```python
|
| 154 |
+
from torch.utils.data import DataLoader
|
| 155 |
+
from luminamoon.data import create_resumable_collate, ResumablePipeline, FusedPipeline
|
| 156 |
+
|
| 157 |
+
# 1. As PyTorch collate_fn
|
| 158 |
+
collate_fn = create_resumable_collate(
|
| 159 |
+
fused_pipeline=FusedPipeline(),
|
| 160 |
+
return_string_tensor=True,
|
| 161 |
+
tokenizer=my_tokenizer,
|
| 162 |
+
max_length=512,
|
| 163 |
+
)
|
| 164 |
+
loader = DataLoader(raw_dataset, batch_size=64, collate_fn=collate_fn)
|
| 165 |
+
|
| 166 |
+
# 2. Or as batch ResumablePipeline with background prefetcher
|
| 167 |
+
resumable_pipe = ResumablePipeline(
|
| 168 |
+
source=raw_dataset,
|
| 169 |
+
fused_pipeline=FusedPipeline(),
|
| 170 |
+
batch_size=64,
|
| 171 |
+
prefetch_batches=4, # Background prefetch worker for processed batches
|
| 172 |
+
return_string_tensor=True,
|
| 173 |
+
)
|
| 174 |
+
for batch in resumable_pipe:
|
| 175 |
+
...
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
## 4. Verification & Testing
|
| 181 |
+
|
| 182 |
+
Run Rust tests:
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
cargo test -p luminamoon-dataloader
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
Run Python modular test suite:
|
| 189 |
+
|
| 190 |
+
```bash
|
| 191 |
+
python -B tests/test_data_modular.py
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## 5. Version Inspection
|
| 197 |
+
|
| 198 |
+
Query the current data engine module version:
|
| 199 |
+
|
| 200 |
+
```python
|
| 201 |
+
import luminamoon.data as lmd
|
| 202 |
+
|
| 203 |
+
print(lmd.get_version()) # Active package release version string
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
> **Note**: LuminaMoon Data operates on a single unified native SIMD engine. Package releases follow `luminamoon.__version__`.
|
data/docs/README.md
CHANGED
|
@@ -1,62 +1,62 @@
|
|
| 1 |
-
# LuminaMoon Data Engine Documentation
|
| 2 |
-
|
| 3 |
-
Documentation index for the **LuminaMoon Data Engine (`luminamoon.data` / `luminamoon-dataloader`)**.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Architecture, User Guide & Developer Specs
|
| 8 |
-
|
| 9 |
-
- **[LuminaMoon Data Engine Guide (`data/README.md`)](../README.md)**: Architecture overview, crate module breakdown, `FusedPipeline` single-pass Rayon execution, dual-layout `StringTensor` (CPU 16-byte `StringView16` & GPU contiguous buffers), 128-byte Ping-Pong checkpoint recovery (`Slot A` / `Slot B`), two-stage `xxh3_64` deduplication, 64-permutation MinHash LSH, `AVX-512BW` / `AVX2` / `NEON` CPU SIMD normalization, and PyTorch `ResumablePipeline` / `create_resumable_collate` integration.
|
| 10 |
-
- **[Developer Modular Documentation Index (`docs/dataloader/README.md`)](../../docs/dataloader/README.md)**:
|
| 11 |
-
- **[01. Coordinates & Dual Metadata (`coord.rs`, `metadata.rs`, `constants.rs`, `error.rs`)](../../docs/dataloader/coord_and_metadata/README.md)**
|
| 12 |
-
- **[02. Two-Stage XXH3-64 Deduplication (`dedup.rs`)](../../docs/dataloader/dedup/README.md)**
|
| 13 |
-
- **[03. CPU SIMD & Parallel Post-Processing (`simd_cpu.rs`, `postprocess.rs`)](../../docs/dataloader/simd_and_postprocess/README.md)**
|
| 14 |
-
- **[04. Heuristic Quality Filter & MinHash LSH (`quality.rs`, `minhash.rs`)](../../docs/dataloader/sources_and_streaming/README.md)**
|
| 15 |
-
- **[05. Dual-Layout `StringTensor` & Resumable Adapters (`string_tensor.rs`, `adapters.py`)](../../docs/dataloader/preram/README.md)**
|
| 16 |
-
- **[06. Columnar Table Serialization (`serialize.rs`, `serialization.py`)](../../docs/dataloader/multimodal_and_hf_hub/README.md)**
|
| 17 |
-
- **[07. PyO3 `abi3t` FFI Bridge & Modular Runtime (`py_bridge.rs`, `python/luminamoon/data/`)](../../docs/dataloader/python_ffi_and_runtime/README.md)**
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
## 2. External Library Compatibility Notes (`data/docs/library_audit/`)
|
| 22 |
-
|
| 23 |
-
Version verification, changelog tracking, and compatibility notes for external dependencies used by `luminamoon-dataloader`:
|
| 24 |
-
|
| 25 |
-
- **[Library Compatibility Index](library_audit/README.md)**
|
| 26 |
-
- **[01. Rust Toolchain, SIMD & Memory Alignment (`rayon`)](library_audit/01_rust_toolchain_simd_and_mmap.md)**: Rust 1.99.1, Edition 2024 `unsafe_op_in_unsafe_fn`, `OnceLock` ISA caching, and `AVX-512BW` / `AVX2` / `NEON` intrinsics.
|
| 27 |
-
- **[02. Python, PyO3 (`abi3t`), and Maturin](library_audit/02_python_pyo3_abi3t_and_maturin.md)**: Free-threaded Python (`abi3t`), `py.detach` GIL release, `Bound::cast::<T>()`, `PyBool` vs `PyInt` type ordering, and table/postprocess FFI functions.
|
| 28 |
-
- **[03. Apache Arrow & Parquet (`arrow-array`, `arrow-schema`, `parquet`)](library_audit/03_parquet_arrow_and_http206_streaming.md)**: Columnar `RecordBatch` construction, ZSTD Parquet compression, and HuggingFace Dataset Viewer `Struct<bytes, path>` schema generation.
|
| 29 |
-
- **[04. `xxhash-rust`, `hashbrown`, `crc32fast`, and `parking_lot`](library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md)**: `xxh3_64` + `IdentityBuildHasher` SwissTable, `crc32fast` Ping-Pong checkpoint slots, and synchronization primitives.
|
| 30 |
-
- **[05. Universal Formats (`arrow-ipc`, `serde_json`) and Typo Diagnostics](library_audit/05_universal_formats_compression_and_diagnostics.md)**: Arrow IPC (`ARROW1`), NDJSON serialization, and Damerau-Levenshtein parameter suggestions (`did_you_mean`).
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
## 3. Module Summary & Verification
|
| 35 |
-
|
| 36 |
-
### 3.1. Module Matrix
|
| 37 |
-
|
| 38 |
-
| Module / File | Core Responsibility | Status |
|
| 39 |
-
| :--- | :--- | :--- |
|
| 40 |
-
| [`data/src/constants.rs`](../src/constants.rs) | Magic bytes (`LMDL`, `PAR1`, `ARROW1`), slot sizes, and default bounds. | Verified |
|
| 41 |
-
| [`data/src/coord.rs`](../src/coord.rs) | 32-byte little-endian `StreamCoordinate` (`shard_id`, `byte_offset`, `row_group_idx`, `page_idx`, `row_offset_in_page`, `global_row_idx`). | Verified |
|
| 42 |
-
| [`data/src/dedup.rs`](../src/dedup.rs) | `TwoStageDeduplicator` using `xxh3_64` with `IdentityBuildHasher` SwissTable. | Verified |
|
| 43 |
-
| [`data/src/error.rs`](../src/error.rs) | Unified `DataLoaderError` with Damerau-Levenshtein edit-distance suggestions (`did_you_mean`). | Verified |
|
| 44 |
-
| [`data/src/metadata.rs`](../src/metadata.rs) | 128-byte Dual-Slot Ping-Pong (`Slot A` / `Slot B`) checkpoint file with IEEE CRC32 (`crc32fast`) and `.dedup` WAL. | Verified |
|
| 45 |
-
| [`data/src/minhash.rs`](../src/minhash.rs) | 64-permutation MinHash LSH fuzzy deduplicator and Jaccard similarity estimation. | Verified |
|
| 46 |
-
| [`data/src/quality.rs`](../src/quality.rs) | Shannon Character Entropy, loop repetition detector, and symbol-to-word ratio filters. | Verified |
|
| 47 |
-
| [`data/src/simd_cpu.rs`](../src/simd_cpu.rs) | `AVX-512BW`, `AVX2`, `NEON`, and Scalar kernels (`find_byte_fast`, `rfind_byte_fast`) + 64-byte `CacheAlignedBuffer`. | Verified |
|
| 48 |
-
| [`data/src/string_tensor.rs`](../src/string_tensor.rs) | Dual-layout container for CPU `StringView16` (<=12B inline) and GPU contiguous offsets/payload buffers. | Verified |
|
| 49 |
-
| [`data/src/postprocess.rs`](../src/postprocess.rs) | Rayon parallel UTF-8 validation, SIMD `\r` detection, JSON dotted-path extraction, and whitespace normalization. | Verified |
|
| 50 |
-
| [`data/src/serialize.rs`](../src/serialize.rs) | Table serializers for Apache Arrow IPC, Parquet (ZSTD), and NDJSON (`DataValue`). | Verified |
|
| 51 |
-
| [`data/src/py_bridge.rs`](../src/py_bridge.rs) | `PyO3 0.30.1` (`abi3t`) bindings for `FusedPipeline`, `StringTensor`, `StateTracker`, `StreamDeduplicator`, `QualityFilter`, and `MinHashDeduplicator`. | Verified |
|
| 52 |
-
| [`python/luminamoon/data/`](../../python/luminamoon/data/__init__.py) | Modular Python package (`fused.py`, `cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `string_tensor.py`, `checkpoint.py`, `adapters.py`, `serialization.py`, `version.py`, and `fallbacks/`). | Verified |
|
| 53 |
-
|
| 54 |
-
### 3.2. Verification Commands
|
| 55 |
-
|
| 56 |
-
| Check | Command | Result |
|
| 57 |
-
| :--- | :--- | :--- |
|
| 58 |
-
| Rust Workspace Linter | `cargo clippy --workspace --all-targets -- -D warnings` | **PASSED** (0 Warnings) |
|
| 59 |
-
| Rust Release Tests | `cargo test --workspace` | **PASSED** |
|
| 60 |
-
| Python Static Type Check | `npx pyright python/luminamoon` | **PASSED** (0 Errors, 0 Warnings) |
|
| 61 |
-
| Python Modular Data Tests | `python -B tests/test_data_modular.py` | **PASSED** |
|
| 62 |
-
| Python StringTensor CPU/GPU Tests | `python -B tests/test_gpu_vs_cpu_strings.py` | **PASSED** |
|
|
|
|
| 1 |
+
# LuminaMoon Data Engine Documentation
|
| 2 |
+
|
| 3 |
+
Documentation index for the **LuminaMoon Data Engine (`luminamoon.data` / `luminamoon-dataloader`)**.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Architecture, User Guide & Developer Specs
|
| 8 |
+
|
| 9 |
+
- **[LuminaMoon Data Engine Guide (`data/README.md`)](../README.md)**: Architecture overview, crate module breakdown, `FusedPipeline` single-pass Rayon execution, dual-layout `StringTensor` (CPU 16-byte `StringView16` & GPU contiguous buffers), 128-byte Ping-Pong checkpoint recovery (`Slot A` / `Slot B`), two-stage `xxh3_64` deduplication, 64-permutation MinHash LSH, `AVX-512BW` / `AVX2` / `NEON` CPU SIMD normalization, and PyTorch `ResumablePipeline` / `create_resumable_collate` integration.
|
| 10 |
+
- **[Developer Modular Documentation Index (`docs/dataloader/README.md`)](../../docs/dataloader/README.md)**:
|
| 11 |
+
- **[01. Coordinates & Dual Metadata (`coord.rs`, `metadata.rs`, `constants.rs`, `error.rs`)](../../docs/dataloader/coord_and_metadata/README.md)**
|
| 12 |
+
- **[02. Two-Stage XXH3-64 Deduplication (`dedup.rs`)](../../docs/dataloader/dedup/README.md)**
|
| 13 |
+
- **[03. CPU SIMD & Parallel Post-Processing (`simd_cpu.rs`, `postprocess.rs`)](../../docs/dataloader/simd_and_postprocess/README.md)**
|
| 14 |
+
- **[04. Heuristic Quality Filter & MinHash LSH (`quality.rs`, `minhash.rs`)](../../docs/dataloader/sources_and_streaming/README.md)**
|
| 15 |
+
- **[05. Dual-Layout `StringTensor` & Resumable Adapters (`string_tensor.rs`, `adapters.py`)](../../docs/dataloader/preram/README.md)**
|
| 16 |
+
- **[06. Columnar Table Serialization (`serialize.rs`, `serialization.py`)](../../docs/dataloader/multimodal_and_hf_hub/README.md)**
|
| 17 |
+
- **[07. PyO3 `abi3t` FFI Bridge & Modular Runtime (`py_bridge.rs`, `python/luminamoon/data/`)](../../docs/dataloader/python_ffi_and_runtime/README.md)**
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## 2. External Library Compatibility Notes (`data/docs/library_audit/`)
|
| 22 |
+
|
| 23 |
+
Version verification, changelog tracking, and compatibility notes for external dependencies used by `luminamoon-dataloader`:
|
| 24 |
+
|
| 25 |
+
- **[Library Compatibility Index](library_audit/README.md)**
|
| 26 |
+
- **[01. Rust Toolchain, SIMD & Memory Alignment (`rayon`)](library_audit/01_rust_toolchain_simd_and_mmap.md)**: Rust 1.99.1, Edition 2024 `unsafe_op_in_unsafe_fn`, `OnceLock` ISA caching, and `AVX-512BW` / `AVX2` / `NEON` intrinsics.
|
| 27 |
+
- **[02. Python, PyO3 (`abi3t`), and Maturin](library_audit/02_python_pyo3_abi3t_and_maturin.md)**: Free-threaded Python (`abi3t`), `py.detach` GIL release, `Bound::cast::<T>()`, `PyBool` vs `PyInt` type ordering, and table/postprocess FFI functions.
|
| 28 |
+
- **[03. Apache Arrow & Parquet (`arrow-array`, `arrow-schema`, `parquet`)](library_audit/03_parquet_arrow_and_http206_streaming.md)**: Columnar `RecordBatch` construction, ZSTD Parquet compression, and HuggingFace Dataset Viewer `Struct<bytes, path>` schema generation.
|
| 29 |
+
- **[04. `xxhash-rust`, `hashbrown`, `crc32fast`, and `parking_lot`](library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md)**: `xxh3_64` + `IdentityBuildHasher` SwissTable, `crc32fast` Ping-Pong checkpoint slots, and synchronization primitives.
|
| 30 |
+
- **[05. Universal Formats (`arrow-ipc`, `serde_json`) and Typo Diagnostics](library_audit/05_universal_formats_compression_and_diagnostics.md)**: Arrow IPC (`ARROW1`), NDJSON serialization, and Damerau-Levenshtein parameter suggestions (`did_you_mean`).
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 3. Module Summary & Verification
|
| 35 |
+
|
| 36 |
+
### 3.1. Module Matrix
|
| 37 |
+
|
| 38 |
+
| Module / File | Core Responsibility | Status |
|
| 39 |
+
| :--- | :--- | :--- |
|
| 40 |
+
| [`data/src/constants.rs`](../src/constants.rs) | Magic bytes (`LMDL`, `PAR1`, `ARROW1`), slot sizes, and default bounds. | Verified |
|
| 41 |
+
| [`data/src/coord.rs`](../src/coord.rs) | 32-byte little-endian `StreamCoordinate` (`shard_id`, `byte_offset`, `row_group_idx`, `page_idx`, `row_offset_in_page`, `global_row_idx`). | Verified |
|
| 42 |
+
| [`data/src/dedup.rs`](../src/dedup.rs) | `TwoStageDeduplicator` using `xxh3_64` with `IdentityBuildHasher` SwissTable. | Verified |
|
| 43 |
+
| [`data/src/error.rs`](../src/error.rs) | Unified `DataLoaderError` with Damerau-Levenshtein edit-distance suggestions (`did_you_mean`). | Verified |
|
| 44 |
+
| [`data/src/metadata.rs`](../src/metadata.rs) | 128-byte Dual-Slot Ping-Pong (`Slot A` / `Slot B`) checkpoint file with IEEE CRC32 (`crc32fast`) and `.dedup` WAL. | Verified |
|
| 45 |
+
| [`data/src/minhash.rs`](../src/minhash.rs) | 64-permutation MinHash LSH fuzzy deduplicator and Jaccard similarity estimation. | Verified |
|
| 46 |
+
| [`data/src/quality.rs`](../src/quality.rs) | Shannon Character Entropy, loop repetition detector, and symbol-to-word ratio filters. | Verified |
|
| 47 |
+
| [`data/src/simd_cpu.rs`](../src/simd_cpu.rs) | `AVX-512BW`, `AVX2`, `NEON`, and Scalar kernels (`find_byte_fast`, `rfind_byte_fast`) + 64-byte `CacheAlignedBuffer`. | Verified |
|
| 48 |
+
| [`data/src/string_tensor.rs`](../src/string_tensor.rs) | Dual-layout container for CPU `StringView16` (<=12B inline) and GPU contiguous offsets/payload buffers. | Verified |
|
| 49 |
+
| [`data/src/postprocess.rs`](../src/postprocess.rs) | Rayon parallel UTF-8 validation, SIMD `\r` detection, JSON dotted-path extraction, and whitespace normalization. | Verified |
|
| 50 |
+
| [`data/src/serialize.rs`](../src/serialize.rs) | Table serializers for Apache Arrow IPC, Parquet (ZSTD), and NDJSON (`DataValue`). | Verified |
|
| 51 |
+
| [`data/src/py_bridge.rs`](../src/py_bridge.rs) | `PyO3 0.30.1` (`abi3t`) bindings for `FusedPipeline`, `StringTensor`, `StateTracker`, `StreamDeduplicator`, `QualityFilter`, and `MinHashDeduplicator`. | Verified |
|
| 52 |
+
| [`python/luminamoon/data/`](../../python/luminamoon/data/__init__.py) | Modular Python package (`fused.py`, `cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `string_tensor.py`, `checkpoint.py`, `adapters.py`, `serialization.py`, `version.py`, and `fallbacks/`). | Verified |
|
| 53 |
+
|
| 54 |
+
### 3.2. Verification Commands
|
| 55 |
+
|
| 56 |
+
| Check | Command | Result |
|
| 57 |
+
| :--- | :--- | :--- |
|
| 58 |
+
| Rust Workspace Linter | `cargo clippy --workspace --all-targets -- -D warnings` | **PASSED** (0 Warnings) |
|
| 59 |
+
| Rust Release Tests | `cargo test --workspace` | **PASSED** |
|
| 60 |
+
| Python Static Type Check | `npx pyright python/luminamoon` | **PASSED** (0 Errors, 0 Warnings) |
|
| 61 |
+
| Python Modular Data Tests | `python -B tests/test_data_modular.py` | **PASSED** |
|
| 62 |
+
| Python StringTensor CPU/GPU Tests | `python -B tests/test_gpu_vs_cpu_strings.py` | **PASSED** |
|
data/docs/library_audit/01_rust_toolchain_simd_and_mmap.md
CHANGED
|
@@ -1,56 +1,56 @@
|
|
| 1 |
-
# 1. Rust Toolchain, `std::arch` SIMD Intrinsics, `memmap2`, and Core Crates
|
| 2 |
-
|
| 3 |
-
## 1.1 Latest Stable Versions
|
| 4 |
-
- **Rust Compiler (`rustc` & `cargo`)**: `1.99.1` (`edition = "2024"`, `resolver = "3"`)
|
| 5 |
-
- **`memmap2`**: `0.9.11`
|
| 6 |
-
- **`rayon`**: `1.12.0`
|
| 7 |
-
- **`thiserror`**: `2.0.21`
|
| 8 |
-
- **`serde`**: `1.0.229`
|
| 9 |
-
- **`serde_json`**: `1.0.154`
|
| 10 |
-
|
| 11 |
-
---
|
| 12 |
-
|
| 13 |
-
## 1.2 Version Range Changelog (`Rust 1.85.0` - `1.99.1` & Supporting Crates)
|
| 14 |
-
|
| 15 |
-
### A. Rust 1.85.0 (`edition = "2024"`)
|
| 16 |
-
- **What changed**: The `unsafe_op_in_unsafe_fn` lint is enabled by default in Rust Edition 2024. Declaring a function as `unsafe fn` no longer implicitly permits calling `unsafe` operations inside the function body without an explicit `unsafe { ... }` block.
|
| 17 |
-
- **Syntax changes**: Stabilized `let-chains` (`if let Some(a) = opt && a > 0`) inside `if` and `while` conditions, now enforced by `clippy::collapsible_if`.
|
| 18 |
-
|
| 19 |
-
### B. Rust 1.86.0 - 1.89.0 (`std::arch` Target Features & `avx512bw` Stabilization)
|
| 20 |
-
- **Rust 1.86.0 - 1.87.0**: Value-based SIMD intrinsics (such as `_mm512_set1_epi8`, `_mm512_cmpeq_epi8_mask`, `_mm256_set1_epi8`, `_mm256_cmpeq_epi8`, `_mm256_movemask_epi8`, `vdupq_n_u8`, `vceqq_u8`, `vmaxvq_u8`) no longer require an `unsafe` block when called inside a `#[target_feature]` function. Wrapping value intrinsics in `unsafe` triggers an `unused_unsafe` compiler warning. Only raw pointer dereference intrinsics (`_mm512_loadu_si512`, `_mm256_loadu_si256`, `vld1q_u8`) and pointer arithmetic (`ptr.add(offset)`) remain `unsafe`.
|
| 21 |
-
- **Rust 1.89.0**: All `avx512f` and `avx512bw` target features and 512-bit byte intrinsics (`_mm512_cmpeq_epi8_mask`) were stabilized on the stable compiler channel without requiring `#![feature(stdarch_x86_avx512)]`.
|
| 22 |
-
|
| 23 |
-
### C. Rust 1.88.0 - 1.95.0 (`[T]::as_chunks` & `std::cfg_select!`)
|
| 24 |
-
- **Rust 1.88.0**: Stabilized the standard library method `slice::as_chunks::<N>()` to partition slices into fixed-size array chunks `&[[T; N]]` plus a remainder slice, replacing legacy `chunks_exact(N)` patterns (enforced by `clippy::chunks_exact_to_as_chunks`).
|
| 25 |
-
- **Rust 1.95.0**: Added the standard macro `std::cfg_select!` for cross-architecture conditional compilation without requiring the external `cfg-if` crate.
|
| 26 |
-
|
| 27 |
-
### D. `memmap2` (`0.9.0` - `0.9.11`): Mitigation of `RUSTSEC-2026-0186`
|
| 28 |
-
- **What changed**: In `memmap2 < 0.9.11`, internal memory-mapping end-boundary calculations used `ptr.add(len)`. Under modern Rust/LLVM pointer provenance rules, calling `ptr.add` on an empty mapping or at the edge of the address space violates `inbounds` invariants and triggers Undefined Behavior (UB).
|
| 29 |
-
- **Upstream fix (`0.9.11`)**: Replaced internal pointer boundary calculations with `wrapping_add` and stabilized `Advice::PopulateRead` (`MADV_POPULATE_READ` on Linux) for page pre-faulting.
|
| 30 |
-
|
| 31 |
-
### E. `thiserror` (`1.0` - `2.0.21`), `rayon` (`1.10` - `1.12.0`), and `serde_json` (`1.0.120` - `1.0.154`)
|
| 32 |
-
- **`thiserror 2.0.21`**: Migrated to the stabilized `core::error::Error` trait in `core`, removing mandatory reliance on internal `std::error::Error` paths and improving variable format expansion in `#[error("...")]` macros.
|
| 33 |
-
- **`rayon 1.12.0`**: Improved work-stealing scheduling efficiency on indexed parallel iterators (`into_par_iter().enumerate()`).
|
| 34 |
-
- **`serde_json 1.0.154`**: Optimized direct byte-buffer serialization (`serde_json::to_writer(&mut Vec<u8>, ...)`) without intermediate `String` allocations.
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## 1.3 Adaptations in LuminaMoon Source Code
|
| 39 |
-
|
| 40 |
-
1. **Architecture Detection via `std::cfg_select!` and Scoped `unsafe` Intrinsic Blocks (`data/src/simd_cpu.rs`)**:
|
| 41 |
-
- Uses `std::cfg_select!` inside `detect_cpu_isa()` to select `x86`/`x86_64`, `aarch64`, or `Scalar` fallback branches without any `cfg-if` dependency.
|
| 42 |
-
- In `find_byte_avx512`, `rfind_byte_avx512`, `find_byte_avx2`, `rfind_byte_avx2`, `find_byte_neon`, and `rfind_byte_neon`, `unsafe { ... }` blocks are restricted strictly to `_mm512_loadu_si512`, `_mm256_loadu_si256`, `vld1q_u8`, and `ptr.add(...)`:
|
| 43 |
-
```rust
|
| 44 |
-
while offset + 64 <= len {
|
| 45 |
-
let chunk = unsafe { _mm512_loadu_si512(ptr.add(offset).cast()) };
|
| 46 |
-
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 47 |
-
if mask != 0 {
|
| 48 |
-
return Some(offset + mask.trailing_zeros() as usize);
|
| 49 |
-
}
|
| 50 |
-
offset += 64;
|
| 51 |
-
}
|
| 52 |
-
```
|
| 53 |
-
2. **Adoption of `slice::as_chunks::<3>()` (`data/src/multimodal_serialize.rs`)**:
|
| 54 |
-
- The `encode_base64` function uses `let (chunks, rem) = input.as_chunks::<3>();` to process 3-byte blocks directly without per-iteration bounds checks.
|
| 55 |
-
3. **Safe Memory Mapping with `memmap2 0.9.11` (`data/src/source_local.rs`)**:
|
| 56 |
-
- `LocalMmapLineSource::open` maps local files using `memmap2 0.9.11` (patched against `RUSTSEC-2026-0186`) and enforces `start_coord.byte_offset.min(total_len)` bounds checks prior to slicing.
|
|
|
|
| 1 |
+
# 1. Rust Toolchain, `std::arch` SIMD Intrinsics, `memmap2`, and Core Crates
|
| 2 |
+
|
| 3 |
+
## 1.1 Latest Stable Versions
|
| 4 |
+
- **Rust Compiler (`rustc` & `cargo`)**: `1.99.1` (`edition = "2024"`, `resolver = "3"`)
|
| 5 |
+
- **`memmap2`**: `0.9.11`
|
| 6 |
+
- **`rayon`**: `1.12.0`
|
| 7 |
+
- **`thiserror`**: `2.0.21`
|
| 8 |
+
- **`serde`**: `1.0.229`
|
| 9 |
+
- **`serde_json`**: `1.0.154`
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## 1.2 Version Range Changelog (`Rust 1.85.0` - `1.99.1` & Supporting Crates)
|
| 14 |
+
|
| 15 |
+
### A. Rust 1.85.0 (`edition = "2024"`)
|
| 16 |
+
- **What changed**: The `unsafe_op_in_unsafe_fn` lint is enabled by default in Rust Edition 2024. Declaring a function as `unsafe fn` no longer implicitly permits calling `unsafe` operations inside the function body without an explicit `unsafe { ... }` block.
|
| 17 |
+
- **Syntax changes**: Stabilized `let-chains` (`if let Some(a) = opt && a > 0`) inside `if` and `while` conditions, now enforced by `clippy::collapsible_if`.
|
| 18 |
+
|
| 19 |
+
### B. Rust 1.86.0 - 1.89.0 (`std::arch` Target Features & `avx512bw` Stabilization)
|
| 20 |
+
- **Rust 1.86.0 - 1.87.0**: Value-based SIMD intrinsics (such as `_mm512_set1_epi8`, `_mm512_cmpeq_epi8_mask`, `_mm256_set1_epi8`, `_mm256_cmpeq_epi8`, `_mm256_movemask_epi8`, `vdupq_n_u8`, `vceqq_u8`, `vmaxvq_u8`) no longer require an `unsafe` block when called inside a `#[target_feature]` function. Wrapping value intrinsics in `unsafe` triggers an `unused_unsafe` compiler warning. Only raw pointer dereference intrinsics (`_mm512_loadu_si512`, `_mm256_loadu_si256`, `vld1q_u8`) and pointer arithmetic (`ptr.add(offset)`) remain `unsafe`.
|
| 21 |
+
- **Rust 1.89.0**: All `avx512f` and `avx512bw` target features and 512-bit byte intrinsics (`_mm512_cmpeq_epi8_mask`) were stabilized on the stable compiler channel without requiring `#![feature(stdarch_x86_avx512)]`.
|
| 22 |
+
|
| 23 |
+
### C. Rust 1.88.0 - 1.95.0 (`[T]::as_chunks` & `std::cfg_select!`)
|
| 24 |
+
- **Rust 1.88.0**: Stabilized the standard library method `slice::as_chunks::<N>()` to partition slices into fixed-size array chunks `&[[T; N]]` plus a remainder slice, replacing legacy `chunks_exact(N)` patterns (enforced by `clippy::chunks_exact_to_as_chunks`).
|
| 25 |
+
- **Rust 1.95.0**: Added the standard macro `std::cfg_select!` for cross-architecture conditional compilation without requiring the external `cfg-if` crate.
|
| 26 |
+
|
| 27 |
+
### D. `memmap2` (`0.9.0` - `0.9.11`): Mitigation of `RUSTSEC-2026-0186`
|
| 28 |
+
- **What changed**: In `memmap2 < 0.9.11`, internal memory-mapping end-boundary calculations used `ptr.add(len)`. Under modern Rust/LLVM pointer provenance rules, calling `ptr.add` on an empty mapping or at the edge of the address space violates `inbounds` invariants and triggers Undefined Behavior (UB).
|
| 29 |
+
- **Upstream fix (`0.9.11`)**: Replaced internal pointer boundary calculations with `wrapping_add` and stabilized `Advice::PopulateRead` (`MADV_POPULATE_READ` on Linux) for page pre-faulting.
|
| 30 |
+
|
| 31 |
+
### E. `thiserror` (`1.0` - `2.0.21`), `rayon` (`1.10` - `1.12.0`), and `serde_json` (`1.0.120` - `1.0.154`)
|
| 32 |
+
- **`thiserror 2.0.21`**: Migrated to the stabilized `core::error::Error` trait in `core`, removing mandatory reliance on internal `std::error::Error` paths and improving variable format expansion in `#[error("...")]` macros.
|
| 33 |
+
- **`rayon 1.12.0`**: Improved work-stealing scheduling efficiency on indexed parallel iterators (`into_par_iter().enumerate()`).
|
| 34 |
+
- **`serde_json 1.0.154`**: Optimized direct byte-buffer serialization (`serde_json::to_writer(&mut Vec<u8>, ...)`) without intermediate `String` allocations.
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## 1.3 Adaptations in LuminaMoon Source Code
|
| 39 |
+
|
| 40 |
+
1. **Architecture Detection via `std::cfg_select!` and Scoped `unsafe` Intrinsic Blocks (`data/src/simd_cpu.rs`)**:
|
| 41 |
+
- Uses `std::cfg_select!` inside `detect_cpu_isa()` to select `x86`/`x86_64`, `aarch64`, or `Scalar` fallback branches without any `cfg-if` dependency.
|
| 42 |
+
- In `find_byte_avx512`, `rfind_byte_avx512`, `find_byte_avx2`, `rfind_byte_avx2`, `find_byte_neon`, and `rfind_byte_neon`, `unsafe { ... }` blocks are restricted strictly to `_mm512_loadu_si512`, `_mm256_loadu_si256`, `vld1q_u8`, and `ptr.add(...)`:
|
| 43 |
+
```rust
|
| 44 |
+
while offset + 64 <= len {
|
| 45 |
+
let chunk = unsafe { _mm512_loadu_si512(ptr.add(offset).cast()) };
|
| 46 |
+
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 47 |
+
if mask != 0 {
|
| 48 |
+
return Some(offset + mask.trailing_zeros() as usize);
|
| 49 |
+
}
|
| 50 |
+
offset += 64;
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
2. **Adoption of `slice::as_chunks::<3>()` (`data/src/multimodal_serialize.rs`)**:
|
| 54 |
+
- The `encode_base64` function uses `let (chunks, rem) = input.as_chunks::<3>();` to process 3-byte blocks directly without per-iteration bounds checks.
|
| 55 |
+
3. **Safe Memory Mapping with `memmap2 0.9.11` (`data/src/source_local.rs`)**:
|
| 56 |
+
- `LocalMmapLineSource::open` maps local files using `memmap2 0.9.11` (patched against `RUSTSEC-2026-0186`) and enforces `start_coord.byte_offset.min(total_len)` bounds checks prior to slicing.
|
data/docs/library_audit/02_python_pyo3_abi3t_and_maturin.md
CHANGED
|
@@ -1,53 +1,53 @@
|
|
| 1 |
-
# 2. Python, PyO3 (`abi3t`), and Maturin
|
| 2 |
-
|
| 3 |
-
## 2.1 Latest Stable Versions
|
| 4 |
-
- **Python**: `3.15.0` (supports both standard CPython and free-threaded `Py_GIL_DISABLED` builds)
|
| 5 |
-
- **`pyo3`**: `0.30.1` (`features = ["abi3-py310", "abi3t"]`)
|
| 6 |
-
- **`maturin`**: `1.15.0` (`requires = ["maturin>=1.15,<2.0"]`)
|
| 7 |
-
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
## 2.2 Version Range Changelog (`pyo3 0.24.0` - `0.30.1` & `maturin 1.8` - `1.15.0`)
|
| 11 |
-
|
| 12 |
-
### A. PyO3 0.25.0 - 0.26.0: Deprecation of `extension-module` Feature
|
| 13 |
-
- **What changed**: The Cargo feature `pyo3/extension-module` was deprecated starting in `pyo3 0.26.0`.
|
| 14 |
-
- **Upstream technical rationale**: Cargo features are additive across an entire workspace. Enabling `extension-module` in `Cargo.toml` caused `cargo test` to fail during linking because `libpython` symbols were intentionally omitted when that feature was active.
|
| 15 |
-
- **New idiom**: Extension module linking is controlled directly by `maturin >= 1.8` (or the environment variable `PYO3_BUILD_EXTENSION_MODULE=1`), rather than static declarations in `Cargo.toml`.
|
| 16 |
-
|
| 17 |
-
### B. PyO3 0.26.0 - 0.28.0: Removal of `Python::allow_threads` $\rightarrow$ `Python::detach`
|
| 18 |
-
- **What changed**: `py.allow_threads(|| ...)` was deprecated in `pyo3 0.26.0` and **completely removed** in `pyo3 0.28.0`.
|
| 19 |
-
- **Upstream technical rationale**: On free-threaded Python runtimes (PEP 703 / PEP 803 without the GIL), Python threads already execute in parallel; releasing the context actually detaches the OS thread from the CPython runtime state.
|
| 20 |
-
- **New idiom**: Use `py.detach(|| ...)`.
|
| 21 |
-
|
| 22 |
-
### C. PyO3 0.27.0 - 0.30.1: Removal of `Bound::downcast` $\rightarrow$ `Bound::cast::<T>()`
|
| 23 |
-
- **What changed**: `Bound<'py, PyAny>::downcast::<T>()` and `downcast_into::<T>()` were deprecated and removed in `pyo3 0.30.1`, replaced by `Bound<'py, PyAny>::cast::<T>()` and `cast_into::<T>()`.
|
| 24 |
-
- **Upstream technical rationale**: Unified smart-reference type conversion naming across the `Bound<'py, T>` API.
|
| 25 |
-
|
| 26 |
-
### D. PyO3 0.29.0 - 0.30.1 & Python 3.15: Stable ABI `abi3t` (PEP 803) and `#[pyclass(frozen, subclass)]`
|
| 27 |
-
- **What changed**:
|
| 28 |
-
1. Added the `abi3t` feature alongside `abi3-py310` to support the cross-version Stable ABI across Python 3.10–3.15 on both standard GIL builds and free-threaded builds (`python3.14t` / `python3.15t`).
|
| 29 |
-
2. Rust classes exposed to Python via `#[pyclass]` no longer permit Python-side subclassing by default unless the `subclass` attribute is explicitly specified, and `frozen` is recommended for lock-free concurrency without `PyCell` borrow-checking overhead in free-threaded mode.
|
| 30 |
-
3. Inside declarative `#[pymodule] mod ...` blocks, external sub-components are registered using functions annotated with `#[pymodule_init]`.
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
## 2.3 Adaptations in LuminaMoon Source Code
|
| 35 |
-
|
| 36 |
-
1. **`Cargo.toml` & `data/Cargo.toml`**:
|
| 37 |
-
- Configured `pyo3 = { version = "0.30.1", features = ["abi3-py310", "abi3t"] }` in `[workspace.dependencies]` without `"extension-module"`, allowing `cargo test -p luminamoon-dataloader` to link and run standalone.
|
| 38 |
-
2. **Runtime Detachment via `py.detach` (`data/src/py_bridge.rs`)**:
|
| 39 |
-
- All disk I/O, `mmap` reads, HTTP 206 network streaming, Parquet schema inference, and HuggingFace Hub NDJSON uploads execute inside `py.detach(|| ...)`:
|
| 40 |
-
```rust
|
| 41 |
-
let batch_opt = py
|
| 42 |
-
.detach(|| self.inner.next_batch())
|
| 43 |
-
.map_err(to_py_err)?;
|
| 44 |
-
```
|
| 45 |
-
3. **Type Casting via `Bound::cast::<T>()` and `PyBool` vs `PyInt` Hierarchy (`data/src/py_bridge.rs`)**:
|
| 46 |
-
- Uses `columns.cast::<PyDict>()`, `v.cast::<PySequence>()`, and `pair.cast::<PyTuple>()` inside `extract_py_columns_table`.
|
| 47 |
-
- Because `bool` is a subclass of `int` in CPython, `py_any_to_multimodal_value` always checks `item.is_instance_of::<PyBool>()` before `item.is_instance_of::<PyInt>()` so boolean columns are never coerced into `1`/`0` integers.
|
| 48 |
-
4. **`#[pyclass(frozen, subclass)]` Declarations and `#[pymodule_init]` Registration (`data/src/py_bridge.rs` & `optimizer/src/lib.rs`)**:
|
| 49 |
-
- `PyDataStreamLoader`, `PyMultimodalParquetLoader`, and `PyHfHubUploader` are declared with `#[pyclass(frozen, subclass, ...)]` and synchronized on the Rust side using `parking_lot::Mutex`.
|
| 50 |
-
- `register_dataloader_classes(m)` is invoked from `#[pymodule_init] fn init(m: &Bound<'_, PyModule>)` inside `optimizer/src/lib.rs`, registering all classes and module-level functions (`serialize_table_to_bytes`, `postprocess_and_dedup_records`, `suggest_closest_matches`).
|
| 51 |
-
5. **Automatic Row-to-Column Transposition & GIL-Free Table/Postprocess Functions (`data/src/py_bridge.rs`)**:
|
| 52 |
-
- `extract_py_columns_table` supports three Python input layouts simultaneously: `dict[str, Sequence[Any]]`, `Sequence[tuple[str, Sequence[Any]]]`, and `Sequence[dict[str, Any]]` (transposing row dictionaries into ordered columns by first-seen key order and filling missing keys with `MultimodalValue::Null`).
|
| 53 |
-
- `serialize_table_to_bytes` and `postprocess_and_dedup_records` (with `deduplicate: bool = true`) execute Parquet/Arrow IPC/CSV/JSONL/JSON serialization and Rayon + SIMD normalization + `xxh3_64` deduplication completely outside the GIL via `py.detach(|| ...)`.
|
|
|
|
| 1 |
+
# 2. Python, PyO3 (`abi3t`), and Maturin
|
| 2 |
+
|
| 3 |
+
## 2.1 Latest Stable Versions
|
| 4 |
+
- **Python**: `3.15.0` (supports both standard CPython and free-threaded `Py_GIL_DISABLED` builds)
|
| 5 |
+
- **`pyo3`**: `0.30.1` (`features = ["abi3-py310", "abi3t"]`)
|
| 6 |
+
- **`maturin`**: `1.15.0` (`requires = ["maturin>=1.15,<2.0"]`)
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 2.2 Version Range Changelog (`pyo3 0.24.0` - `0.30.1` & `maturin 1.8` - `1.15.0`)
|
| 11 |
+
|
| 12 |
+
### A. PyO3 0.25.0 - 0.26.0: Deprecation of `extension-module` Feature
|
| 13 |
+
- **What changed**: The Cargo feature `pyo3/extension-module` was deprecated starting in `pyo3 0.26.0`.
|
| 14 |
+
- **Upstream technical rationale**: Cargo features are additive across an entire workspace. Enabling `extension-module` in `Cargo.toml` caused `cargo test` to fail during linking because `libpython` symbols were intentionally omitted when that feature was active.
|
| 15 |
+
- **New idiom**: Extension module linking is controlled directly by `maturin >= 1.8` (or the environment variable `PYO3_BUILD_EXTENSION_MODULE=1`), rather than static declarations in `Cargo.toml`.
|
| 16 |
+
|
| 17 |
+
### B. PyO3 0.26.0 - 0.28.0: Removal of `Python::allow_threads` $\rightarrow$ `Python::detach`
|
| 18 |
+
- **What changed**: `py.allow_threads(|| ...)` was deprecated in `pyo3 0.26.0` and **completely removed** in `pyo3 0.28.0`.
|
| 19 |
+
- **Upstream technical rationale**: On free-threaded Python runtimes (PEP 703 / PEP 803 without the GIL), Python threads already execute in parallel; releasing the context actually detaches the OS thread from the CPython runtime state.
|
| 20 |
+
- **New idiom**: Use `py.detach(|| ...)`.
|
| 21 |
+
|
| 22 |
+
### C. PyO3 0.27.0 - 0.30.1: Removal of `Bound::downcast` $\rightarrow$ `Bound::cast::<T>()`
|
| 23 |
+
- **What changed**: `Bound<'py, PyAny>::downcast::<T>()` and `downcast_into::<T>()` were deprecated and removed in `pyo3 0.30.1`, replaced by `Bound<'py, PyAny>::cast::<T>()` and `cast_into::<T>()`.
|
| 24 |
+
- **Upstream technical rationale**: Unified smart-reference type conversion naming across the `Bound<'py, T>` API.
|
| 25 |
+
|
| 26 |
+
### D. PyO3 0.29.0 - 0.30.1 & Python 3.15: Stable ABI `abi3t` (PEP 803) and `#[pyclass(frozen, subclass)]`
|
| 27 |
+
- **What changed**:
|
| 28 |
+
1. Added the `abi3t` feature alongside `abi3-py310` to support the cross-version Stable ABI across Python 3.10–3.15 on both standard GIL builds and free-threaded builds (`python3.14t` / `python3.15t`).
|
| 29 |
+
2. Rust classes exposed to Python via `#[pyclass]` no longer permit Python-side subclassing by default unless the `subclass` attribute is explicitly specified, and `frozen` is recommended for lock-free concurrency without `PyCell` borrow-checking overhead in free-threaded mode.
|
| 30 |
+
3. Inside declarative `#[pymodule] mod ...` blocks, external sub-components are registered using functions annotated with `#[pymodule_init]`.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 2.3 Adaptations in LuminaMoon Source Code
|
| 35 |
+
|
| 36 |
+
1. **`Cargo.toml` & `data/Cargo.toml`**:
|
| 37 |
+
- Configured `pyo3 = { version = "0.30.1", features = ["abi3-py310", "abi3t"] }` in `[workspace.dependencies]` without `"extension-module"`, allowing `cargo test -p luminamoon-dataloader` to link and run standalone.
|
| 38 |
+
2. **Runtime Detachment via `py.detach` (`data/src/py_bridge.rs`)**:
|
| 39 |
+
- All disk I/O, `mmap` reads, HTTP 206 network streaming, Parquet schema inference, and HuggingFace Hub NDJSON uploads execute inside `py.detach(|| ...)`:
|
| 40 |
+
```rust
|
| 41 |
+
let batch_opt = py
|
| 42 |
+
.detach(|| self.inner.next_batch())
|
| 43 |
+
.map_err(to_py_err)?;
|
| 44 |
+
```
|
| 45 |
+
3. **Type Casting via `Bound::cast::<T>()` and `PyBool` vs `PyInt` Hierarchy (`data/src/py_bridge.rs`)**:
|
| 46 |
+
- Uses `columns.cast::<PyDict>()`, `v.cast::<PySequence>()`, and `pair.cast::<PyTuple>()` inside `extract_py_columns_table`.
|
| 47 |
+
- Because `bool` is a subclass of `int` in CPython, `py_any_to_multimodal_value` always checks `item.is_instance_of::<PyBool>()` before `item.is_instance_of::<PyInt>()` so boolean columns are never coerced into `1`/`0` integers.
|
| 48 |
+
4. **`#[pyclass(frozen, subclass)]` Declarations and `#[pymodule_init]` Registration (`data/src/py_bridge.rs` & `optimizer/src/lib.rs`)**:
|
| 49 |
+
- `PyDataStreamLoader`, `PyMultimodalParquetLoader`, and `PyHfHubUploader` are declared with `#[pyclass(frozen, subclass, ...)]` and synchronized on the Rust side using `parking_lot::Mutex`.
|
| 50 |
+
- `register_dataloader_classes(m)` is invoked from `#[pymodule_init] fn init(m: &Bound<'_, PyModule>)` inside `optimizer/src/lib.rs`, registering all classes and module-level functions (`serialize_table_to_bytes`, `postprocess_and_dedup_records`, `suggest_closest_matches`).
|
| 51 |
+
5. **Automatic Row-to-Column Transposition & GIL-Free Table/Postprocess Functions (`data/src/py_bridge.rs`)**:
|
| 52 |
+
- `extract_py_columns_table` supports three Python input layouts simultaneously: `dict[str, Sequence[Any]]`, `Sequence[tuple[str, Sequence[Any]]]`, and `Sequence[dict[str, Any]]` (transposing row dictionaries into ordered columns by first-seen key order and filling missing keys with `MultimodalValue::Null`).
|
| 53 |
+
- `serialize_table_to_bytes` and `postprocess_and_dedup_records` (with `deduplicate: bool = true`) execute Parquet/Arrow IPC/CSV/JSONL/JSON serialization and Rayon + SIMD normalization + `xxh3_64` deduplication completely outside the GIL via `py.detach(|| ...)`.
|
data/docs/library_audit/03_parquet_arrow_and_http206_streaming.md
CHANGED
|
@@ -1,48 +1,48 @@
|
|
| 1 |
-
# 3. Apache Arrow/Parquet, `reqwest`, `tokio`, `bytes`, and `futures`
|
| 2 |
-
|
| 3 |
-
## 3.1 Latest Stable Versions
|
| 4 |
-
- **`parquet`**: `60.0.0` (`features = ["async"]`)
|
| 5 |
-
- **`arrow-array`**: `60.0.0`
|
| 6 |
-
- **`arrow-schema`**: `60.0.0`
|
| 7 |
-
- **`reqwest`**: `0.13.5` (`default-features = false`, `features = ["default-tls", "http2", "system-proxy", "json", "stream", "gzip"]`)
|
| 8 |
-
- **`tokio`**: `1.53.3` (`features = ["rt-multi-thread", "fs", "io-util", "sync", "time", "macros"]`)
|
| 9 |
-
- **`bytes`**: `1.12.1`
|
| 10 |
-
- **`futures`**: `0.3.34`
|
| 11 |
-
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
## 3.2 Version Range Changelog (`arrow-rs 53.0.0` - `60.0.0`, `reqwest 0.12` - `0.13.5`, `bytes 1.6` - `1.12.1`)
|
| 15 |
-
|
| 16 |
-
### A. Apache Arrow & Parquet (`53.0.0` - `60.0.0`)
|
| 17 |
-
- **`Utf8View` (`StringViewArray`) and `BinaryView` (`BinaryViewArray`) Type Promotion**:
|
| 18 |
-
Across `arrow 54.0`–`60.0.0`, modern Parquet readers can map string and binary columns into *ByteView* representations (12-byte inline prefix + offset buffer) alongside `StringArray`/`LargeStringArray` and `BinaryArray`/`LargeBinaryArray`.
|
| 19 |
-
- **Separation of `ArrowReaderMetadata` for Repeated Reads**:
|
| 20 |
-
`ParquetRecordBatchReaderBuilder::try_new(file)` re-reads and re-parses the Parquet footer metadata on every invocation. The `ArrowReaderMetadata::load(&file, options)` API paired with `ParquetRecordBatchReaderBuilder::new_with_metadata(file, metadata)` allows sharing cached metadata in memory in $O(1)$ time.
|
| 21 |
-
- **`ParquetMetaDataReader::load_and_finish` Signature Change (`parquet 58.0` - `60.0.0`)**:
|
| 22 |
-
The async method `ParquetMetaDataReader::load_and_finish` now takes a generic parameter `F: MetadataSuffixFetch` (blanket-implemented for `&mut T` where `T: AsyncFileReader`). Calling `.load_and_finish(self, file_size)` directly inside `fn get_metadata(&'a mut self, ...)` fails trait-bound resolution because `self` has type `&'a mut Self`, requiring an explicit reborrow `&mut *self`.
|
| 23 |
-
|
| 24 |
-
### B. `reqwest` (`0.12.0` - `0.13.5`) & `tokio` (`1.38.0` - `1.53.3`)
|
| 25 |
-
- **Default TLS Backend Change in `reqwest 0.13.x`**:
|
| 26 |
-
Starting in `reqwest 0.13.0`, default features (`default-features = true`) switched from `native-tls` to `rustls` backed by `aws-lc-sys`. On Windows, compiling `aws-lc-sys` requires external CMake and NASM installations, causing build failures when absent.
|
| 27 |
-
- **Modularization of `json`, `stream`, and `gzip` Features**:
|
| 28 |
-
`.json(...)`, `.bytes_stream()`, and automatic response decompression in `reqwest 0.13.5` are gated behind explicit feature flags.
|
| 29 |
-
|
| 30 |
-
### C. `bytes` (`1.6.0` - `1.12.1`): Mitigation of `RUSTSEC-2026-0007` / `CVE-2026-25541`
|
| 31 |
-
- **What changed**: In `bytes < 1.11.1`, an integer overflow vulnerability existed in capacity calculations inside `BytesMut::reserve` when reservation sizes approached `usize::MAX`, potentially allocating a smaller buffer than requested (*heap buffer overflow*).
|
| 32 |
-
- **Upstream fix (`1.11.1` - `1.12.1`)**: Added checked arithmetic across all `BytesMut` capacity expansion paths and zero-copy `Bytes::slice` operations.
|
| 33 |
-
|
| 34 |
-
---
|
| 35 |
-
|
| 36 |
-
## 3.3 Adaptations in LuminaMoon Source Code
|
| 37 |
-
|
| 38 |
-
1. **`StringViewArray` and `BinaryViewArray` Support (`data/src/source_local.rs` & `data/src/multimodal.rs`)**:
|
| 39 |
-
- `extract_strings_from_batch` and `extract_array_values` handle all three Arrow string/binary type families (`StringArray` / `LargeStringArray` / `StringViewArray` and `BinaryArray` / `LargeBinaryArray` / `BinaryViewArray`).
|
| 40 |
-
2. **`ArrowReaderMetadata` Caching and `RowSelection` (`data/src/source_local.rs` & `data/src/multimodal.rs`)**:
|
| 41 |
-
- `LocalParquetSource` and `LocalMultimodalParquetSource` load `ArrowReaderMetadata` once during `open()` and combine it with `ProjectionMask::roots(...)` and `RowSelection::from(vec![RowSelector::skip(...), RowSelector::select(...)])`.
|
| 42 |
-
3. **Explicit `&mut *self` Reborrow on `HttpParquetReader` (`data/src/source_hf.rs`)**:
|
| 43 |
-
- `AsyncFileReader::get_metadata` for `HttpParquetReader` calls `ParquetMetaDataReader::new().with_arrow_reader_options(options).load_and_finish(&mut *self, file_size).await?` and caches the result in `cached_metadata: Option<Arc<ParquetMetaData>>`.
|
| 44 |
-
4. **`reqwest 0.13.5` and `bytes 1.12.1` Configuration (`Cargo.toml`)**:
|
| 45 |
-
- Configured `reqwest = { version = "0.13.5", default-features = false, features = ["default-tls", "http2", "system-proxy", "json", "stream", "gzip"] }` and `bytes = "1.12.1"` so cross-platform builds remain free of external `aws-lc-sys` CMake/NASM dependencies while mitigating `CVE-2026-25541`.
|
| 46 |
-
5. **Concurrent Multi-Range Downloads (`fetch_ranges_concurrent`) & Multi-Thread Tokio Pool (`data/src/source_hf.rs` & `data/src/preram.rs`)**:
|
| 47 |
-
- `HttpRangeClient::fetch_ranges_concurrent` uses `futures::stream::iter(...).buffered(concurrency)` to download multiple HTTP 206 byte ranges in parallel across workers while preserving range ordering.
|
| 48 |
-
- `HttpParquetReader::get_byte_ranges` forwards Arrow/Parquet column range lists to `fetch_ranges_concurrent`, while `RemoteLineSource` and `PreRamDataLoader` run a multi-threaded Tokio runtime (`Builder::new_multi_thread().worker_threads(download_workers)`) for parallel multi-chunk prefetching.
|
|
|
|
| 1 |
+
# 3. Apache Arrow/Parquet, `reqwest`, `tokio`, `bytes`, and `futures`
|
| 2 |
+
|
| 3 |
+
## 3.1 Latest Stable Versions
|
| 4 |
+
- **`parquet`**: `60.0.0` (`features = ["async"]`)
|
| 5 |
+
- **`arrow-array`**: `60.0.0`
|
| 6 |
+
- **`arrow-schema`**: `60.0.0`
|
| 7 |
+
- **`reqwest`**: `0.13.5` (`default-features = false`, `features = ["default-tls", "http2", "system-proxy", "json", "stream", "gzip"]`)
|
| 8 |
+
- **`tokio`**: `1.53.3` (`features = ["rt-multi-thread", "fs", "io-util", "sync", "time", "macros"]`)
|
| 9 |
+
- **`bytes`**: `1.12.1`
|
| 10 |
+
- **`futures`**: `0.3.34`
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 3.2 Version Range Changelog (`arrow-rs 53.0.0` - `60.0.0`, `reqwest 0.12` - `0.13.5`, `bytes 1.6` - `1.12.1`)
|
| 15 |
+
|
| 16 |
+
### A. Apache Arrow & Parquet (`53.0.0` - `60.0.0`)
|
| 17 |
+
- **`Utf8View` (`StringViewArray`) and `BinaryView` (`BinaryViewArray`) Type Promotion**:
|
| 18 |
+
Across `arrow 54.0`–`60.0.0`, modern Parquet readers can map string and binary columns into *ByteView* representations (12-byte inline prefix + offset buffer) alongside `StringArray`/`LargeStringArray` and `BinaryArray`/`LargeBinaryArray`.
|
| 19 |
+
- **Separation of `ArrowReaderMetadata` for Repeated Reads**:
|
| 20 |
+
`ParquetRecordBatchReaderBuilder::try_new(file)` re-reads and re-parses the Parquet footer metadata on every invocation. The `ArrowReaderMetadata::load(&file, options)` API paired with `ParquetRecordBatchReaderBuilder::new_with_metadata(file, metadata)` allows sharing cached metadata in memory in $O(1)$ time.
|
| 21 |
+
- **`ParquetMetaDataReader::load_and_finish` Signature Change (`parquet 58.0` - `60.0.0`)**:
|
| 22 |
+
The async method `ParquetMetaDataReader::load_and_finish` now takes a generic parameter `F: MetadataSuffixFetch` (blanket-implemented for `&mut T` where `T: AsyncFileReader`). Calling `.load_and_finish(self, file_size)` directly inside `fn get_metadata(&'a mut self, ...)` fails trait-bound resolution because `self` has type `&'a mut Self`, requiring an explicit reborrow `&mut *self`.
|
| 23 |
+
|
| 24 |
+
### B. `reqwest` (`0.12.0` - `0.13.5`) & `tokio` (`1.38.0` - `1.53.3`)
|
| 25 |
+
- **Default TLS Backend Change in `reqwest 0.13.x`**:
|
| 26 |
+
Starting in `reqwest 0.13.0`, default features (`default-features = true`) switched from `native-tls` to `rustls` backed by `aws-lc-sys`. On Windows, compiling `aws-lc-sys` requires external CMake and NASM installations, causing build failures when absent.
|
| 27 |
+
- **Modularization of `json`, `stream`, and `gzip` Features**:
|
| 28 |
+
`.json(...)`, `.bytes_stream()`, and automatic response decompression in `reqwest 0.13.5` are gated behind explicit feature flags.
|
| 29 |
+
|
| 30 |
+
### C. `bytes` (`1.6.0` - `1.12.1`): Mitigation of `RUSTSEC-2026-0007` / `CVE-2026-25541`
|
| 31 |
+
- **What changed**: In `bytes < 1.11.1`, an integer overflow vulnerability existed in capacity calculations inside `BytesMut::reserve` when reservation sizes approached `usize::MAX`, potentially allocating a smaller buffer than requested (*heap buffer overflow*).
|
| 32 |
+
- **Upstream fix (`1.11.1` - `1.12.1`)**: Added checked arithmetic across all `BytesMut` capacity expansion paths and zero-copy `Bytes::slice` operations.
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## 3.3 Adaptations in LuminaMoon Source Code
|
| 37 |
+
|
| 38 |
+
1. **`StringViewArray` and `BinaryViewArray` Support (`data/src/source_local.rs` & `data/src/multimodal.rs`)**:
|
| 39 |
+
- `extract_strings_from_batch` and `extract_array_values` handle all three Arrow string/binary type families (`StringArray` / `LargeStringArray` / `StringViewArray` and `BinaryArray` / `LargeBinaryArray` / `BinaryViewArray`).
|
| 40 |
+
2. **`ArrowReaderMetadata` Caching and `RowSelection` (`data/src/source_local.rs` & `data/src/multimodal.rs`)**:
|
| 41 |
+
- `LocalParquetSource` and `LocalMultimodalParquetSource` load `ArrowReaderMetadata` once during `open()` and combine it with `ProjectionMask::roots(...)` and `RowSelection::from(vec![RowSelector::skip(...), RowSelector::select(...)])`.
|
| 42 |
+
3. **Explicit `&mut *self` Reborrow on `HttpParquetReader` (`data/src/source_hf.rs`)**:
|
| 43 |
+
- `AsyncFileReader::get_metadata` for `HttpParquetReader` calls `ParquetMetaDataReader::new().with_arrow_reader_options(options).load_and_finish(&mut *self, file_size).await?` and caches the result in `cached_metadata: Option<Arc<ParquetMetaData>>`.
|
| 44 |
+
4. **`reqwest 0.13.5` and `bytes 1.12.1` Configuration (`Cargo.toml`)**:
|
| 45 |
+
- Configured `reqwest = { version = "0.13.5", default-features = false, features = ["default-tls", "http2", "system-proxy", "json", "stream", "gzip"] }` and `bytes = "1.12.1"` so cross-platform builds remain free of external `aws-lc-sys` CMake/NASM dependencies while mitigating `CVE-2026-25541`.
|
| 46 |
+
5. **Concurrent Multi-Range Downloads (`fetch_ranges_concurrent`) & Multi-Thread Tokio Pool (`data/src/source_hf.rs` & `data/src/preram.rs`)**:
|
| 47 |
+
- `HttpRangeClient::fetch_ranges_concurrent` uses `futures::stream::iter(...).buffered(concurrency)` to download multiple HTTP 206 byte ranges in parallel across workers while preserving range ordering.
|
| 48 |
+
- `HttpParquetReader::get_byte_ranges` forwards Arrow/Parquet column range lists to `fetch_ranges_concurrent`, while `RemoteLineSource` and `PreRamDataLoader` run a multi-threaded Tokio runtime (`Builder::new_multi_thread().worker_threads(download_workers)`) for parallel multi-chunk prefetching.
|
data/docs/library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md
CHANGED
|
@@ -1,49 +1,49 @@
|
|
| 1 |
-
# 4. `xxhash-rust`, `hashbrown`, `crc32fast`, `parking_lot`, and HuggingFace Hub API Specifications
|
| 2 |
-
|
| 3 |
-
## 4.1 Latest Stable Versions
|
| 4 |
-
- **`xxhash-rust`**: `0.8.19` (`features = ["xxh3"]`)
|
| 5 |
-
- **`hashbrown`**: `0.17.1`
|
| 6 |
-
- **`crc32fast`**: `1.5.2`
|
| 7 |
-
- **`parking_lot`**: `0.12.5`
|
| 8 |
-
- **External HuggingFace Hub Protocol**: `huggingface_hub >= 0.28 / 1.x` (HTTP Commit NDJSON API & Dataset Viewer Parquet Spec)
|
| 9 |
-
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
## 4.2 Version Range Changelog (`2024` - `2026`)
|
| 13 |
-
|
| 14 |
-
### A. `hashbrown` (`0.14.0` - `0.17.1`)
|
| 15 |
-
- **What changed**: Starting in `hashbrown 0.15.0` through `0.17.1`, the internal default hasher for `HashMap` and `HashSet` switched from `ahash` to `foldhash`.
|
| 16 |
-
- **SwissTable technical implications**: `hashbrown` uses the SwissTable architecture, splitting a 64-bit hash into two parts: the lower 57 bits (`h1`) for bucket slot selection and the upper 7 bits (`h2`, bits 57..63) as the control byte for SIMD group probing. When table keys are already uniformly distributed 64-bit hashes (`u64`), running `foldhash` on top of them performs redundant computation.
|
| 17 |
-
|
| 18 |
-
### B. `xxhash-rust` (`0.8.10` - `0.8.19`)
|
| 19 |
-
- **What changed**: Enhanced `const fn` compilation optimizations and auto-vectorization in `xxh3_64(input: &[u8]) -> u64`.
|
| 20 |
-
- **Bit distribution properties**: `xxh3_64` provides full avalanche across all 64 bits (including the upper 7 `h2` bits), making it safe to pair directly with SwissTable via an identity hasher without re-hashing.
|
| 21 |
-
|
| 22 |
-
### C. `crc32fast` (`1.4.0` - `1.5.2`)
|
| 23 |
-
- **What changed**: The `1.5.x` series enables `vpclmulqdq` hardware instructions (Vector Carry-Less Multiplication on AVX-512) alongside classic `pclmulqdq` to accelerate IEEE CRC32 checksum calculation.
|
| 24 |
-
|
| 25 |
-
### D. `parking_lot` (`0.12.1` - `0.12.5`)
|
| 26 |
-
- **What changed**: Maintenance updates for Rust Edition 2024 lint compatibility across `Mutex<T>` and `Condvar` synchronization primitives with zero OS mutex allocation on the uncontended fast path.
|
| 27 |
-
|
| 28 |
-
### E. HuggingFace Hub Credential & External Endpoint Conventions (`huggingface_hub 0.25` - `1.x`)
|
| 29 |
-
- **Standard environment variables**: The official `huggingface_hub` library standardized token resolution priority on `HF_TOKEN` (superseding legacy `HUGGING_FACE_HUB_TOKEN` as primary), local token files at `$HF_HOME/token` or `~/.cache/huggingface/token`, and `HF_ENDPOINT` (defaulting to `https://huggingface.co`).
|
| 30 |
-
- **`application/x-ndjson` Commit Protocol**: The `POST /api/datasets/{owner}/{repo}/commit/{revision}` endpoint accepts Newline-Delimited JSON streams beginning with `{"key": "header", "value": {"summary": ...}}` followed by `{"key": "file", "value": {"content": "<base64>", "path": ..., "encoding": "base64"}}`.
|
| 31 |
-
- **Dataset Viewer Image Schema Convention**: For image columns in Parquet files to render automatically in the HuggingFace Dataset Viewer without manual configuration, image columns must be stored as an Arrow `Struct` with two fields: `bytes: Binary` and `path: Utf8`.
|
| 32 |
-
|
| 33 |
-
---
|
| 34 |
-
|
| 35 |
-
## 4.3 Adaptations in LuminaMoon Source Code
|
| 36 |
-
|
| 37 |
-
1. **Bypassing `foldhash` with `IdentityBuildHasher` + `xxh3_64` (`data/src/dedup.rs`)**:
|
| 38 |
-
- Defines `IdentityHasher` (`write_u64` stores the `xxh3_64` hash directly) for `HashSet<u64, IdentityBuildHasher>` and `HashMap<u64, u32, IdentityBuildHasher>`, eliminating `foldhash` double-hashing overhead in `hashbrown 0.17.1`.
|
| 39 |
-
2. **Checkpoint Slot Validation via `crc32fast::Hasher` (`data/src/metadata.rs`)**:
|
| 40 |
-
- Uses `crc32fast` to verify the integrity of both 64-byte Ping-Pong checkpoint slots (`Slot A` and `Slot B`).
|
| 41 |
-
3. **Fine-Grained `parking_lot::Mutex` Separation (`data/src/preram.rs`)**:
|
| 42 |
-
- Separates `Mutex<QueueState>` and `Condvar` from `Mutex<DualMetadataManager>` so background Pre-RAM refill workers and `commit_gpu_batch()` callers never block in-memory batch pops.
|
| 43 |
-
4. **HuggingFace Hub Protocol Compliance (`data/src/hf_hub.rs` & `data/src/multimodal_serialize.rs`)**:
|
| 44 |
-
- `HfCredentials` resolves `HF_TOKEN`, `HUGGING_FACE_HUB_TOKEN`, local token files, `HF_ORG`, `HF_USERNAME`, and `HF_ENDPOINT`, redacts token values as `"[REDACTED]"` in `fmt::Debug`, and blocks token transmission over non-loopback `http://` connections.
|
| 45 |
-
- `serialize_columns_to_parquet_bytes_with_options` encodes `MultimodalValue::Bytes` columns into `{bytes: Binary, path: Utf8}` `StructArray` layouts directly compatible with the HuggingFace Dataset Viewer.
|
| 46 |
-
5. **Colab/Kaggle Credential Auto-Resolver, RFC 5988 `Link` Pagination, and Hybrid LFS/Xet Upload (`python/luminamoon/data/hub.py`)**:
|
| 47 |
-
- `resolve_hf_secrets` automatically resolves `HF_TOKEN`, `HF_ORG`, and `HF_USERNAME` from environment variables, `google.colab.userdata`, or `kaggle_secrets.UserSecretsClient`.
|
| 48 |
-
- `expand_hf_sources` supports automatic shard discovery from repository roots `hf://datasets/org/repo[@revision]` via `huggingface_hub.HfApi.list_repo_files` or the REST `/api/datasets/.../tree/...?recursive=true` endpoint with RFC 5988 `Link: <...>; rel="next"` header pagination and per-page HTTPS validation.
|
| 49 |
-
- `push_to_hub` combines GIL-free Rust serialization (`serialize_table_to_bytes`) with `huggingface_hub.HfApi.upload_file` (supporting Git LFS / Xet multipart uploads for large shards) and automatic fallback to the native Rust `HfHubUploader` (`application/x-ndjson`).
|
|
|
|
| 1 |
+
# 4. `xxhash-rust`, `hashbrown`, `crc32fast`, `parking_lot`, and HuggingFace Hub API Specifications
|
| 2 |
+
|
| 3 |
+
## 4.1 Latest Stable Versions
|
| 4 |
+
- **`xxhash-rust`**: `0.8.19` (`features = ["xxh3"]`)
|
| 5 |
+
- **`hashbrown`**: `0.17.1`
|
| 6 |
+
- **`crc32fast`**: `1.5.2`
|
| 7 |
+
- **`parking_lot`**: `0.12.5`
|
| 8 |
+
- **External HuggingFace Hub Protocol**: `huggingface_hub >= 0.28 / 1.x` (HTTP Commit NDJSON API & Dataset Viewer Parquet Spec)
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 4.2 Version Range Changelog (`2024` - `2026`)
|
| 13 |
+
|
| 14 |
+
### A. `hashbrown` (`0.14.0` - `0.17.1`)
|
| 15 |
+
- **What changed**: Starting in `hashbrown 0.15.0` through `0.17.1`, the internal default hasher for `HashMap` and `HashSet` switched from `ahash` to `foldhash`.
|
| 16 |
+
- **SwissTable technical implications**: `hashbrown` uses the SwissTable architecture, splitting a 64-bit hash into two parts: the lower 57 bits (`h1`) for bucket slot selection and the upper 7 bits (`h2`, bits 57..63) as the control byte for SIMD group probing. When table keys are already uniformly distributed 64-bit hashes (`u64`), running `foldhash` on top of them performs redundant computation.
|
| 17 |
+
|
| 18 |
+
### B. `xxhash-rust` (`0.8.10` - `0.8.19`)
|
| 19 |
+
- **What changed**: Enhanced `const fn` compilation optimizations and auto-vectorization in `xxh3_64(input: &[u8]) -> u64`.
|
| 20 |
+
- **Bit distribution properties**: `xxh3_64` provides full avalanche across all 64 bits (including the upper 7 `h2` bits), making it safe to pair directly with SwissTable via an identity hasher without re-hashing.
|
| 21 |
+
|
| 22 |
+
### C. `crc32fast` (`1.4.0` - `1.5.2`)
|
| 23 |
+
- **What changed**: The `1.5.x` series enables `vpclmulqdq` hardware instructions (Vector Carry-Less Multiplication on AVX-512) alongside classic `pclmulqdq` to accelerate IEEE CRC32 checksum calculation.
|
| 24 |
+
|
| 25 |
+
### D. `parking_lot` (`0.12.1` - `0.12.5`)
|
| 26 |
+
- **What changed**: Maintenance updates for Rust Edition 2024 lint compatibility across `Mutex<T>` and `Condvar` synchronization primitives with zero OS mutex allocation on the uncontended fast path.
|
| 27 |
+
|
| 28 |
+
### E. HuggingFace Hub Credential & External Endpoint Conventions (`huggingface_hub 0.25` - `1.x`)
|
| 29 |
+
- **Standard environment variables**: The official `huggingface_hub` library standardized token resolution priority on `HF_TOKEN` (superseding legacy `HUGGING_FACE_HUB_TOKEN` as primary), local token files at `$HF_HOME/token` or `~/.cache/huggingface/token`, and `HF_ENDPOINT` (defaulting to `https://huggingface.co`).
|
| 30 |
+
- **`application/x-ndjson` Commit Protocol**: The `POST /api/datasets/{owner}/{repo}/commit/{revision}` endpoint accepts Newline-Delimited JSON streams beginning with `{"key": "header", "value": {"summary": ...}}` followed by `{"key": "file", "value": {"content": "<base64>", "path": ..., "encoding": "base64"}}`.
|
| 31 |
+
- **Dataset Viewer Image Schema Convention**: For image columns in Parquet files to render automatically in the HuggingFace Dataset Viewer without manual configuration, image columns must be stored as an Arrow `Struct` with two fields: `bytes: Binary` and `path: Utf8`.
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## 4.3 Adaptations in LuminaMoon Source Code
|
| 36 |
+
|
| 37 |
+
1. **Bypassing `foldhash` with `IdentityBuildHasher` + `xxh3_64` (`data/src/dedup.rs`)**:
|
| 38 |
+
- Defines `IdentityHasher` (`write_u64` stores the `xxh3_64` hash directly) for `HashSet<u64, IdentityBuildHasher>` and `HashMap<u64, u32, IdentityBuildHasher>`, eliminating `foldhash` double-hashing overhead in `hashbrown 0.17.1`.
|
| 39 |
+
2. **Checkpoint Slot Validation via `crc32fast::Hasher` (`data/src/metadata.rs`)**:
|
| 40 |
+
- Uses `crc32fast` to verify the integrity of both 64-byte Ping-Pong checkpoint slots (`Slot A` and `Slot B`).
|
| 41 |
+
3. **Fine-Grained `parking_lot::Mutex` Separation (`data/src/preram.rs`)**:
|
| 42 |
+
- Separates `Mutex<QueueState>` and `Condvar` from `Mutex<DualMetadataManager>` so background Pre-RAM refill workers and `commit_gpu_batch()` callers never block in-memory batch pops.
|
| 43 |
+
4. **HuggingFace Hub Protocol Compliance (`data/src/hf_hub.rs` & `data/src/multimodal_serialize.rs`)**:
|
| 44 |
+
- `HfCredentials` resolves `HF_TOKEN`, `HUGGING_FACE_HUB_TOKEN`, local token files, `HF_ORG`, `HF_USERNAME`, and `HF_ENDPOINT`, redacts token values as `"[REDACTED]"` in `fmt::Debug`, and blocks token transmission over non-loopback `http://` connections.
|
| 45 |
+
- `serialize_columns_to_parquet_bytes_with_options` encodes `MultimodalValue::Bytes` columns into `{bytes: Binary, path: Utf8}` `StructArray` layouts directly compatible with the HuggingFace Dataset Viewer.
|
| 46 |
+
5. **Colab/Kaggle Credential Auto-Resolver, RFC 5988 `Link` Pagination, and Hybrid LFS/Xet Upload (`python/luminamoon/data/hub.py`)**:
|
| 47 |
+
- `resolve_hf_secrets` automatically resolves `HF_TOKEN`, `HF_ORG`, and `HF_USERNAME` from environment variables, `google.colab.userdata`, or `kaggle_secrets.UserSecretsClient`.
|
| 48 |
+
- `expand_hf_sources` supports automatic shard discovery from repository roots `hf://datasets/org/repo[@revision]` via `huggingface_hub.HfApi.list_repo_files` or the REST `/api/datasets/.../tree/...?recursive=true` endpoint with RFC 5988 `Link: <...>; rel="next"` header pagination and per-page HTTPS validation.
|
| 49 |
+
- `push_to_hub` combines GIL-free Rust serialization (`serialize_table_to_bytes`) with `huggingface_hub.HfApi.upload_file` (supporting Git LFS / Xet multipart uploads for large shards) and automatic fallback to the native Rust `HfHubUploader` (`application/x-ndjson`).
|
data/docs/library_audit/05_universal_formats_compression_and_diagnostics.md
CHANGED
|
@@ -1,87 +1,87 @@
|
|
| 1 |
-
# 5. Universal Formats (`arrow-ipc`, `half`), Stream Decompression (`flate2`, `zstd`, `lz4_flex`), and Typo Diagnostics
|
| 2 |
-
|
| 3 |
-
## 5.1 Latest Stable Versions (October 2026)
|
| 4 |
-
- **`arrow-ipc`**: `60.0.0` (synchronized with `arrow-array = "60.0.0"`, `arrow-schema = "60.0.0"`, `parquet = "60.0.0"`)
|
| 5 |
-
- **`half`**: `2.7.1` (`f16` / `bf16` IEEE 754-2008 half-precision floating point)
|
| 6 |
-
- **`flate2`**: `1.1.10` (`features = ["rust_backend"]` / `miniz_oxide 0.8.9` + `crc32fast 1.5.2`)
|
| 7 |
-
- **`zstd`**: `0.14.3` (`zstd-safe 8.2.0` + `zstd-sys 2.0.16+zstd.1.5.7`)
|
| 8 |
-
- **`lz4_flex`**: `0.14.0` (`safe-decode` & `frame` enabled by default, `unsafe`-free decompression path)
|
| 9 |
-
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
## 5.2 Detailed Version-by-Version Changelog over a 1-Year Window (`2025` - `October 2026`)
|
| 13 |
-
|
| 14 |
-
### A. `arrow-ipc` (`53.0.0` - `60.0.0`)
|
| 15 |
-
| Version | Date / Era | API, ABI, and Internal Behavior Changes |
|
| 16 |
-
| :--- | :--- | :--- |
|
| 17 |
-
| **`53.0.0` - `54.0.0`** | Q4 2024 - Q1 2025 | Added full IPC serialization and deserialization support (`FileReader`, `StreamReader`, `FileWriter`, `StreamWriter`) for variable-length view types `Utf8View` (`StringViewArray`) and `BinaryView` (`BinaryViewArray`) with dedicated variadic buffers. |
|
| 18 |
-
| **`55.0.0` - `56.0.0`** | Q2 2025 | Strict 8-byte / 64-byte buffer alignment validation on IPC `RecordBatch` messages to prevent unaligned pointer reads when memory-mapping `.arrow` / `.feather` files (`mmap` or `Cursor<&[u8]>`). |
|
| 19 |
-
| **`57.0.0`** | Q3 2025 | Improved IPC body compression (`LZ4_FRAME` and `ZSTD`) and variadic buffer bounds checking on `BinaryView` to prevent out-of-bounds slice panics on corrupted IPC streams. |
|
| 20 |
-
| **`58.0.0`** | Q4 2025 | Deprecated `StreamReader::try_new_unbuffered`; unified internal buffering in `StreamReader::try_new` and raised MSRV to match modern Rust toolchains. |
|
| 21 |
-
| **`59.0.0` - `60.0.0`** | 2026 | Removed legacy methods deprecated in `57.x`/`58.x`, aligned `Field` and `Schema` structures with `arrow-schema 60.0.0`, and enabled zero-copy slicing across `ListArray`, `LargeListArray`, `FixedSizeListArray`, and `Float16Array`. |
|
| 22 |
-
|
| 23 |
-
### B. `lz4_flex` (`0.11.0` - `0.14.0`) & Mitigation of `CVE-2026-32829` (`RUSTSEC-2026-0041`)
|
| 24 |
-
| Version | Release Date | API Changes, Security Features, and CVE Mitigations |
|
| 25 |
-
| :--- | :--- | :--- |
|
| 26 |
-
| **`0.11.0` - `0.11.5`** | 2024 - 2025 | Inverted the feature flag from `checked-decode` to `unchecked-decode`. Previously, additive Cargo feature unification meant a transitive dependency disabling `checked-decode` could silently enable `unsafe` pointer decoding. Inverting the flag to `unchecked-decode` (off by default) with `safe-decode` (on by default) maintains `unsafe`-free decompression unless explicitly opted in. |
|
| 27 |
-
| **`0.11.6` & `0.12.0` - `0.12.1`** | **2026-05-09** | **Critical Security Mitigation `CVE-2026-32829` / `RUSTSEC-2026-0041` (CVSS 7.5 High)**: In `lz4_flex < 0.11.6` and `0.12.0`, when using APIs that write into uninitialized output buffers (`decompress_into` / `Sink`) on malformed LZ4 inputs containing an invalid match offset beyond already-written bytes, the decompressor could copy uninitialized memory bytes into the output buffer before returning an error (*information leak*). Fixed in `0.11.6` and `0.12.1` by validating match offset bounds against the actual output cursor prior to copying. |
|
| 28 |
-
| **`0.13.0`** | **2026-05-09** | Added external dictionary compression/decompression support (`compress_prepend_size_with_dict`, `decompress_size_prepended_with_dict`) for small LZ4 blocks. |
|
| 29 |
-
| **`0.13.1`** | **2026-05-09** | Fixed an out-of-bounds panic when an external dictionary smaller than 4 bytes (`dict.len() < 4`) was passed to dictionary compression functions. |
|
| 30 |
-
| **`0.14.0`** | **2026-07-14** | Explicitly separated the `alloc` feature flag from `no_std` paths, refined `frame::FrameDecoder` (`std::io::Read`) for framed `.lz4` streams (*LZ4 Frame Format*), and optimized `safe-decode` throughput. |
|
| 31 |
-
|
| 32 |
-
### C. `flate2` (`1.0.30` - `1.1.10`)
|
| 33 |
-
| Version | Release Date | Backend, Performance, and Feature Flag Changes |
|
| 34 |
-
| :--- | :--- | :--- |
|
| 35 |
-
| **`1.0.30` - `1.0.35`** | 2024 - Early 2025 | Upgraded `miniz_oxide` to the `0.8.x` series and introduced optional `zlib-rs` backend support (pure-Rust SIMD-accelerated Zlib). |
|
| 36 |
-
| **`1.1.0` - `1.1.4`** | Mid 2025 | Added `.reset()` and internal buffer reuse across decoders (`GzDecoder`, `ZlibDecoder`, `DeflateDecoder`), eliminating repeated heap allocations when decompressing thousands of small `.gz` shards. |
|
| 37 |
-
| **`1.1.5` - `1.1.8`** | Late 2025 - Early 2026 | Optimized `MultiGzDecoder` for concatenated `.jsonl.gz` streams and improved handling of trailing bytes after Gzip CRC32 footers. |
|
| 38 |
-
| **`1.1.9`** | Q2 2026 | Refactored internal `zlib-rs` and `crc32fast` feature flags, which temporarily disabled runtime CPU SIMD detection (`std`) when consumers customized default features. |
|
| 39 |
-
| **`1.1.10`** | **Q3 2026** | **Throughput Regression Fix (`runtime_detection`)**: Added the `runtime_detection` feature flag (`zlib-rs?/std`, `crc32fast?/std`) directly into `default` features, ensuring `crc32fast` (`vpclmulqdq` / `pclmulqdq` / `asimd`) and compression backends automatically detect CPU SIMD capabilities at runtime. |
|
| 40 |
-
|
| 41 |
-
### D. `zstd` (`0.13.0` - `0.14.3`) & `zstd-safe` (`7.2.0` - `8.2.0`)
|
| 42 |
-
| Version | Release Date | Memory Safety (`Soundness`) and C `libzstd` Engine Changes |
|
| 43 |
-
| :--- | :--- | :--- |
|
| 44 |
-
| **`0.13.0` - `0.13.3`** | 2024 - 2025 | Upgraded `zstd-sys` bindings to `libzstd 1.5.6` and `1.5.7` (faster large-frame decompression and rare long-distance matcher bug fixes). |
|
| 45 |
-
| **`0.14.0`** | **2026** | **`zstd-safe 8.0.0+` Soundness Fix (`CCtx` / `DCtx`)**: Resolved a memory-safety soundness bug where streaming wrappers allowed direct `&mut CCtx` / `&mut DCtx` borrows (`DerefMut`), permitting callers to `std::mem::swap` contexts while holding raw pointers to short-lived dictionary buffers (*lifetime violation*). In `0.14.x`, direct mutable context access is restricted in favor of safe delegation methods on `Encoder` and `Decoder`. |
|
| 46 |
-
| **`0.14.1` - `0.14.3`** | **2026** | Updated dependencies to `zstd-safe ^8.2.0` (`zstd 1.5.7`), refined `zstd::stream::read::Decoder` and `zstd::stream::encode_all` for multi-frame `.zst` streams (including HuggingFace Xet/Parquet skippable metadata frames), and eliminated compiler warnings on Rust `1.99.1`. |
|
| 47 |
-
|
| 48 |
-
### E. `half` (`2.4.0` - `2.7.1`)
|
| 49 |
-
| Version | Release Date | `f16` / `bf16` Conversion and Hardware Intrinsic Changes |
|
| 50 |
-
| :--- | :--- | :--- |
|
| 51 |
-
| **`2.4.0` - `2.5.0`** | 2024 - 2025 | Stabilized batch slice conversions (`f16::to_f32_slice`, `bf16::to_f32_slice`) and full `bytemuck` and `serde` integration. |
|
| 52 |
-
| **`2.6.0`** | 2025 | Raised MSRV to Rust `1.81+`, utilizing `f16c` / `avx512fp16` intrinsics on `x86_64` and `fp16` on `aarch64` on stable compilers without nightly feature flags. |
|
| 53 |
-
| **`2.7.0` - `2.7.1`** | **2026** | Full alignment with `arrow-array 60.0.0` (`Float16Array` exports `half::f16` elements), branchless `to_f64()` optimization for subnormal and `NaN` values, and full compliance with Rust Edition 2024 `unsafe_op_in_unsafe_fn` rules. |
|
| 54 |
-
|
| 55 |
-
### F. Self-Contained WebDataset `.tar` Parser & Typo Diagnostics (Self-Contained Implementation)
|
| 56 |
-
- **Why External `tar` Crates Were Avoided (`CVE-2025-62518` / TARmageddon)**:
|
| 57 |
-
In late 2025, a critical vulnerability (**`CVE-2025-62518` / TARmageddon**) was disclosed across async TAR parsers in the Rust ecosystem (`tokio-tar` / `astral-tokio-tar`) caused by desynchronization between PAX extended headers and GNU longname headers, enabling file smuggling and path traversal.
|
| 58 |
-
- **LuminaMoon's Self-Contained Solution (`parse_webdataset_tar_bytes` in `data/src/multimodal_formats.rs`)**:
|
| 59 |
-
Because WebDataset ingestion only groups paired in-memory payloads (e.g., `sample_001.jpg`, `sample_001.txt`, `sample_001.json`) into tabular rows without ever extracting files to disk, LuminaMoon implements a pure in-memory 512-byte USTAR block parser that strictly rejects directory/symlink entries and deterministically groups sample stems.
|
| 60 |
-
|
| 61 |
-
---
|
| 62 |
-
|
| 63 |
-
## 5.3 Adaptations in LuminaMoon Source Code
|
| 64 |
-
|
| 65 |
-
1. **Universal Format Support (`data/src/multimodal_formats.rs`, `data/src/multimodal_serialize.rs`, `data/src/preram.rs`, `data/src/hf_hub.rs`)**:
|
| 66 |
-
- `InMemoryMultimodalTable::from_bytes_auto` automatically detects and parses:
|
| 67 |
-
* **Apache Arrow IPC / Feather v2** (`.arrow`, `.feather`, `.ipc`, or `ARROW1` magic bytes) via `arrow_ipc::reader::{FileReader, StreamReader}`.
|
| 68 |
-
* **WebDataset TAR** (`.tar`, `.tgz`, `.tar.gz`, `.tar.zst`, `.tar.lz4`), grouping sample files by stem (`00001.png` + `00001.txt` + `00001.json` $\rightarrow$ 1 multimodal row).
|
| 69 |
-
* **CSV & TSV** (`.csv`, `.tsv`) with an RFC 4180 state-machine parser (escaped quotes, automatic `Int`/`Float`/`Bool`/`Json`/`Text` type inference, and `min_score` filtering).
|
| 70 |
-
* **JSON Array / Object Wrapper & JSONL** (`.json`, `.jsonl`, `.ndjson`).
|
| 71 |
-
- `serialize_columns_by_path_format` (`data/src/hf_hub.rs`) serializes tables into `.parquet`, `.arrow`/`.feather`/`.ipc`, `.jsonl`/`.ndjson`, `.json`, `.csv`, and `.tsv`, transparently wrapping output in `.gz` (`flate2::write::GzEncoder`) or `.zst` (`zstd::stream::encode_all`) compression based on `path_in_repo`.
|
| 72 |
-
|
| 73 |
-
2. **Transparent Local & Remote Stream Decompression (`data/src/source_local.rs` & `data/src/source_hf.rs`)**:
|
| 74 |
-
- `decompress_bytes_if_compressed` detects compression via both file extensions (`.gz`, `.zst`/`.zstd`, `.lz4`) and header magic bytes:
|
| 75 |
-
* **Gzip (`[0x1F, 0x8B]`)**: `flate2::read::MultiGzDecoder` (`1.1.10`).
|
| 76 |
-
* **Zstandard (`[0x28, 0xB5, 0x2F, 0xFD]`)**: `zstd::stream::decode_all` (`0.14.3`).
|
| 77 |
-
* **LZ4 Frame (`[0x04, 0x22, 0x4D, 0x18]`)**: `lz4_flex::frame::FrameDecoder` (`0.14.0`, protected against `CVE-2026-32829`).
|
| 78 |
-
|
| 79 |
-
3. **Full Arrow Type Extraction & Nested JSON Paths (`data/src/multimodal.rs` & `data/src/postprocess.rs`)**:
|
| 80 |
-
- `MultimodalValue::List(Vec<MultimodalValue>)` recursively extracts `ListArray`, `LargeListArray`, and `FixedSizeListArray` (supporting multi-turn chat `messages` and embedding vectors).
|
| 81 |
-
- `Float16Array` values are converted losslessly via `half::f16::to_f64()`.
|
| 82 |
-
- `extract_json_value_by_path` supports nested dot-paths and array indices (e.g., `json_extract_key="messages.0.content"` or `"meta.quality.score"`).
|
| 83 |
-
|
| 84 |
-
4. **Typo Diagnostic Engine & Developer-Friendly Errors (`data/src/error.rs` & `python/luminamoon/data/diagnostics.py`)**:
|
| 85 |
-
- **Damerau-Levenshtein Optimal String Alignment (`damerau_levenshtein`, `find_closest_matches`)**: Computes edit distance including adjacent character transpositions (e.g., `"txet"` $\rightarrow$ `"text"`, `"prmopt"` $\rightarrow$ `"prompt"`).
|
| 86 |
-
- **Fake File / Git LFS Pointer Detection (`inspect_magic_bytes_hint`)**: Detects when a `.parquet`/`.arrow` file that fails to open is actually an unfetched Git LFS text pointer (`version https://git-lfs.github.com/spec/v1`), an HTML error page (`<!DOCTYPE html>`), or a misnamed JSON/Gzip/Zstd file.
|
| 87 |
-
- **Sibling Filename & URI Suggestions (`open_local_file_with_diagnostics`, `diagnose_uri_typo`)**: Suggests similar filenames in the same directory on `FileNotFound`, corrects URI scheme typos (`hf://dataset/...` $\rightarrow$ `hf://datasets/...`, `httpss://`), and validates unknown Python `**kwargs` with closest-parameter suggestions.
|
|
|
|
| 1 |
+
# 5. Universal Formats (`arrow-ipc`, `half`), Stream Decompression (`flate2`, `zstd`, `lz4_flex`), and Typo Diagnostics
|
| 2 |
+
|
| 3 |
+
## 5.1 Latest Stable Versions (October 2026)
|
| 4 |
+
- **`arrow-ipc`**: `60.0.0` (synchronized with `arrow-array = "60.0.0"`, `arrow-schema = "60.0.0"`, `parquet = "60.0.0"`)
|
| 5 |
+
- **`half`**: `2.7.1` (`f16` / `bf16` IEEE 754-2008 half-precision floating point)
|
| 6 |
+
- **`flate2`**: `1.1.10` (`features = ["rust_backend"]` / `miniz_oxide 0.8.9` + `crc32fast 1.5.2`)
|
| 7 |
+
- **`zstd`**: `0.14.3` (`zstd-safe 8.2.0` + `zstd-sys 2.0.16+zstd.1.5.7`)
|
| 8 |
+
- **`lz4_flex`**: `0.14.0` (`safe-decode` & `frame` enabled by default, `unsafe`-free decompression path)
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 5.2 Detailed Version-by-Version Changelog over a 1-Year Window (`2025` - `October 2026`)
|
| 13 |
+
|
| 14 |
+
### A. `arrow-ipc` (`53.0.0` - `60.0.0`)
|
| 15 |
+
| Version | Date / Era | API, ABI, and Internal Behavior Changes |
|
| 16 |
+
| :--- | :--- | :--- |
|
| 17 |
+
| **`53.0.0` - `54.0.0`** | Q4 2024 - Q1 2025 | Added full IPC serialization and deserialization support (`FileReader`, `StreamReader`, `FileWriter`, `StreamWriter`) for variable-length view types `Utf8View` (`StringViewArray`) and `BinaryView` (`BinaryViewArray`) with dedicated variadic buffers. |
|
| 18 |
+
| **`55.0.0` - `56.0.0`** | Q2 2025 | Strict 8-byte / 64-byte buffer alignment validation on IPC `RecordBatch` messages to prevent unaligned pointer reads when memory-mapping `.arrow` / `.feather` files (`mmap` or `Cursor<&[u8]>`). |
|
| 19 |
+
| **`57.0.0`** | Q3 2025 | Improved IPC body compression (`LZ4_FRAME` and `ZSTD`) and variadic buffer bounds checking on `BinaryView` to prevent out-of-bounds slice panics on corrupted IPC streams. |
|
| 20 |
+
| **`58.0.0`** | Q4 2025 | Deprecated `StreamReader::try_new_unbuffered`; unified internal buffering in `StreamReader::try_new` and raised MSRV to match modern Rust toolchains. |
|
| 21 |
+
| **`59.0.0` - `60.0.0`** | 2026 | Removed legacy methods deprecated in `57.x`/`58.x`, aligned `Field` and `Schema` structures with `arrow-schema 60.0.0`, and enabled zero-copy slicing across `ListArray`, `LargeListArray`, `FixedSizeListArray`, and `Float16Array`. |
|
| 22 |
+
|
| 23 |
+
### B. `lz4_flex` (`0.11.0` - `0.14.0`) & Mitigation of `CVE-2026-32829` (`RUSTSEC-2026-0041`)
|
| 24 |
+
| Version | Release Date | API Changes, Security Features, and CVE Mitigations |
|
| 25 |
+
| :--- | :--- | :--- |
|
| 26 |
+
| **`0.11.0` - `0.11.5`** | 2024 - 2025 | Inverted the feature flag from `checked-decode` to `unchecked-decode`. Previously, additive Cargo feature unification meant a transitive dependency disabling `checked-decode` could silently enable `unsafe` pointer decoding. Inverting the flag to `unchecked-decode` (off by default) with `safe-decode` (on by default) maintains `unsafe`-free decompression unless explicitly opted in. |
|
| 27 |
+
| **`0.11.6` & `0.12.0` - `0.12.1`** | **2026-05-09** | **Critical Security Mitigation `CVE-2026-32829` / `RUSTSEC-2026-0041` (CVSS 7.5 High)**: In `lz4_flex < 0.11.6` and `0.12.0`, when using APIs that write into uninitialized output buffers (`decompress_into` / `Sink`) on malformed LZ4 inputs containing an invalid match offset beyond already-written bytes, the decompressor could copy uninitialized memory bytes into the output buffer before returning an error (*information leak*). Fixed in `0.11.6` and `0.12.1` by validating match offset bounds against the actual output cursor prior to copying. |
|
| 28 |
+
| **`0.13.0`** | **2026-05-09** | Added external dictionary compression/decompression support (`compress_prepend_size_with_dict`, `decompress_size_prepended_with_dict`) for small LZ4 blocks. |
|
| 29 |
+
| **`0.13.1`** | **2026-05-09** | Fixed an out-of-bounds panic when an external dictionary smaller than 4 bytes (`dict.len() < 4`) was passed to dictionary compression functions. |
|
| 30 |
+
| **`0.14.0`** | **2026-07-14** | Explicitly separated the `alloc` feature flag from `no_std` paths, refined `frame::FrameDecoder` (`std::io::Read`) for framed `.lz4` streams (*LZ4 Frame Format*), and optimized `safe-decode` throughput. |
|
| 31 |
+
|
| 32 |
+
### C. `flate2` (`1.0.30` - `1.1.10`)
|
| 33 |
+
| Version | Release Date | Backend, Performance, and Feature Flag Changes |
|
| 34 |
+
| :--- | :--- | :--- |
|
| 35 |
+
| **`1.0.30` - `1.0.35`** | 2024 - Early 2025 | Upgraded `miniz_oxide` to the `0.8.x` series and introduced optional `zlib-rs` backend support (pure-Rust SIMD-accelerated Zlib). |
|
| 36 |
+
| **`1.1.0` - `1.1.4`** | Mid 2025 | Added `.reset()` and internal buffer reuse across decoders (`GzDecoder`, `ZlibDecoder`, `DeflateDecoder`), eliminating repeated heap allocations when decompressing thousands of small `.gz` shards. |
|
| 37 |
+
| **`1.1.5` - `1.1.8`** | Late 2025 - Early 2026 | Optimized `MultiGzDecoder` for concatenated `.jsonl.gz` streams and improved handling of trailing bytes after Gzip CRC32 footers. |
|
| 38 |
+
| **`1.1.9`** | Q2 2026 | Refactored internal `zlib-rs` and `crc32fast` feature flags, which temporarily disabled runtime CPU SIMD detection (`std`) when consumers customized default features. |
|
| 39 |
+
| **`1.1.10`** | **Q3 2026** | **Throughput Regression Fix (`runtime_detection`)**: Added the `runtime_detection` feature flag (`zlib-rs?/std`, `crc32fast?/std`) directly into `default` features, ensuring `crc32fast` (`vpclmulqdq` / `pclmulqdq` / `asimd`) and compression backends automatically detect CPU SIMD capabilities at runtime. |
|
| 40 |
+
|
| 41 |
+
### D. `zstd` (`0.13.0` - `0.14.3`) & `zstd-safe` (`7.2.0` - `8.2.0`)
|
| 42 |
+
| Version | Release Date | Memory Safety (`Soundness`) and C `libzstd` Engine Changes |
|
| 43 |
+
| :--- | :--- | :--- |
|
| 44 |
+
| **`0.13.0` - `0.13.3`** | 2024 - 2025 | Upgraded `zstd-sys` bindings to `libzstd 1.5.6` and `1.5.7` (faster large-frame decompression and rare long-distance matcher bug fixes). |
|
| 45 |
+
| **`0.14.0`** | **2026** | **`zstd-safe 8.0.0+` Soundness Fix (`CCtx` / `DCtx`)**: Resolved a memory-safety soundness bug where streaming wrappers allowed direct `&mut CCtx` / `&mut DCtx` borrows (`DerefMut`), permitting callers to `std::mem::swap` contexts while holding raw pointers to short-lived dictionary buffers (*lifetime violation*). In `0.14.x`, direct mutable context access is restricted in favor of safe delegation methods on `Encoder` and `Decoder`. |
|
| 46 |
+
| **`0.14.1` - `0.14.3`** | **2026** | Updated dependencies to `zstd-safe ^8.2.0` (`zstd 1.5.7`), refined `zstd::stream::read::Decoder` and `zstd::stream::encode_all` for multi-frame `.zst` streams (including HuggingFace Xet/Parquet skippable metadata frames), and eliminated compiler warnings on Rust `1.99.1`. |
|
| 47 |
+
|
| 48 |
+
### E. `half` (`2.4.0` - `2.7.1`)
|
| 49 |
+
| Version | Release Date | `f16` / `bf16` Conversion and Hardware Intrinsic Changes |
|
| 50 |
+
| :--- | :--- | :--- |
|
| 51 |
+
| **`2.4.0` - `2.5.0`** | 2024 - 2025 | Stabilized batch slice conversions (`f16::to_f32_slice`, `bf16::to_f32_slice`) and full `bytemuck` and `serde` integration. |
|
| 52 |
+
| **`2.6.0`** | 2025 | Raised MSRV to Rust `1.81+`, utilizing `f16c` / `avx512fp16` intrinsics on `x86_64` and `fp16` on `aarch64` on stable compilers without nightly feature flags. |
|
| 53 |
+
| **`2.7.0` - `2.7.1`** | **2026** | Full alignment with `arrow-array 60.0.0` (`Float16Array` exports `half::f16` elements), branchless `to_f64()` optimization for subnormal and `NaN` values, and full compliance with Rust Edition 2024 `unsafe_op_in_unsafe_fn` rules. |
|
| 54 |
+
|
| 55 |
+
### F. Self-Contained WebDataset `.tar` Parser & Typo Diagnostics (Self-Contained Implementation)
|
| 56 |
+
- **Why External `tar` Crates Were Avoided (`CVE-2025-62518` / TARmageddon)**:
|
| 57 |
+
In late 2025, a critical vulnerability (**`CVE-2025-62518` / TARmageddon**) was disclosed across async TAR parsers in the Rust ecosystem (`tokio-tar` / `astral-tokio-tar`) caused by desynchronization between PAX extended headers and GNU longname headers, enabling file smuggling and path traversal.
|
| 58 |
+
- **LuminaMoon's Self-Contained Solution (`parse_webdataset_tar_bytes` in `data/src/multimodal_formats.rs`)**:
|
| 59 |
+
Because WebDataset ingestion only groups paired in-memory payloads (e.g., `sample_001.jpg`, `sample_001.txt`, `sample_001.json`) into tabular rows without ever extracting files to disk, LuminaMoon implements a pure in-memory 512-byte USTAR block parser that strictly rejects directory/symlink entries and deterministically groups sample stems.
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## 5.3 Adaptations in LuminaMoon Source Code
|
| 64 |
+
|
| 65 |
+
1. **Universal Format Support (`data/src/multimodal_formats.rs`, `data/src/multimodal_serialize.rs`, `data/src/preram.rs`, `data/src/hf_hub.rs`)**:
|
| 66 |
+
- `InMemoryMultimodalTable::from_bytes_auto` automatically detects and parses:
|
| 67 |
+
* **Apache Arrow IPC / Feather v2** (`.arrow`, `.feather`, `.ipc`, or `ARROW1` magic bytes) via `arrow_ipc::reader::{FileReader, StreamReader}`.
|
| 68 |
+
* **WebDataset TAR** (`.tar`, `.tgz`, `.tar.gz`, `.tar.zst`, `.tar.lz4`), grouping sample files by stem (`00001.png` + `00001.txt` + `00001.json` $\rightarrow$ 1 multimodal row).
|
| 69 |
+
* **CSV & TSV** (`.csv`, `.tsv`) with an RFC 4180 state-machine parser (escaped quotes, automatic `Int`/`Float`/`Bool`/`Json`/`Text` type inference, and `min_score` filtering).
|
| 70 |
+
* **JSON Array / Object Wrapper & JSONL** (`.json`, `.jsonl`, `.ndjson`).
|
| 71 |
+
- `serialize_columns_by_path_format` (`data/src/hf_hub.rs`) serializes tables into `.parquet`, `.arrow`/`.feather`/`.ipc`, `.jsonl`/`.ndjson`, `.json`, `.csv`, and `.tsv`, transparently wrapping output in `.gz` (`flate2::write::GzEncoder`) or `.zst` (`zstd::stream::encode_all`) compression based on `path_in_repo`.
|
| 72 |
+
|
| 73 |
+
2. **Transparent Local & Remote Stream Decompression (`data/src/source_local.rs` & `data/src/source_hf.rs`)**:
|
| 74 |
+
- `decompress_bytes_if_compressed` detects compression via both file extensions (`.gz`, `.zst`/`.zstd`, `.lz4`) and header magic bytes:
|
| 75 |
+
* **Gzip (`[0x1F, 0x8B]`)**: `flate2::read::MultiGzDecoder` (`1.1.10`).
|
| 76 |
+
* **Zstandard (`[0x28, 0xB5, 0x2F, 0xFD]`)**: `zstd::stream::decode_all` (`0.14.3`).
|
| 77 |
+
* **LZ4 Frame (`[0x04, 0x22, 0x4D, 0x18]`)**: `lz4_flex::frame::FrameDecoder` (`0.14.0`, protected against `CVE-2026-32829`).
|
| 78 |
+
|
| 79 |
+
3. **Full Arrow Type Extraction & Nested JSON Paths (`data/src/multimodal.rs` & `data/src/postprocess.rs`)**:
|
| 80 |
+
- `MultimodalValue::List(Vec<MultimodalValue>)` recursively extracts `ListArray`, `LargeListArray`, and `FixedSizeListArray` (supporting multi-turn chat `messages` and embedding vectors).
|
| 81 |
+
- `Float16Array` values are converted losslessly via `half::f16::to_f64()`.
|
| 82 |
+
- `extract_json_value_by_path` supports nested dot-paths and array indices (e.g., `json_extract_key="messages.0.content"` or `"meta.quality.score"`).
|
| 83 |
+
|
| 84 |
+
4. **Typo Diagnostic Engine & Developer-Friendly Errors (`data/src/error.rs` & `python/luminamoon/data/diagnostics.py`)**:
|
| 85 |
+
- **Damerau-Levenshtein Optimal String Alignment (`damerau_levenshtein`, `find_closest_matches`)**: Computes edit distance including adjacent character transpositions (e.g., `"txet"` $\rightarrow$ `"text"`, `"prmopt"` $\rightarrow$ `"prompt"`).
|
| 86 |
+
- **Fake File / Git LFS Pointer Detection (`inspect_magic_bytes_hint`)**: Detects when a `.parquet`/`.arrow` file that fails to open is actually an unfetched Git LFS text pointer (`version https://git-lfs.github.com/spec/v1`), an HTML error page (`<!DOCTYPE html>`), or a misnamed JSON/Gzip/Zstd file.
|
| 87 |
+
- **Sibling Filename & URI Suggestions (`open_local_file_with_diagnostics`, `diagnose_uri_typo`)**: Suggests similar filenames in the same directory on `FileNotFound`, corrects URI scheme typos (`hf://dataset/...` $\rightarrow$ `hf://datasets/...`, `httpss://`), and validates unknown Python `**kwargs` with closest-parameter suggestions.
|
data/docs/library_audit/README.md
CHANGED
|
@@ -1,30 +1,30 @@
|
|
| 1 |
-
# Library Version & Changelog Audit Documentation (October 2026)
|
| 2 |
-
|
| 3 |
-
This directory documents the version-by-version changelog audit, intermediate API/ABI deprecations, security advisory mitigations (`RUSTSEC` / `CVE`), and source-code adaptations to the latest stable releases across all external libraries used by the **LuminaMoon Data Engine (`luminamoon-dataloader v1.1.0`)** crate.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## Library Summary
|
| 8 |
-
|
| 9 |
-
| Component / Library | Audited Version Range | Latest Stable Version | Adaptation Status | Detailed Document |
|
| 10 |
-
| :--- | :--- | :--- | :--- | :--- |
|
| 11 |
-
| **Rust Toolchain & `std::arch`** | `1.85.0` - `1.99.1` | `1.99.1` (`edition = "2024"`) | Adopted `std::cfg_select!`, `[T]::as_chunks::<3>()`, and `unsafe_op_in_unsafe_fn` compliance across `AVX-512BW` / `AVX2` / `NEON` intrinsics | [01_rust_toolchain_simd_and_mmap.md](01_rust_toolchain_simd_and_mmap.md) |
|
| 12 |
-
| **System & Parallelism Crates** | `2024` - `2026` | `memmap2 0.9.11`, `rayon 1.12.0`, `thiserror 2.0.21`, `serde 1.0.229`, `serde_json 1.0.154` | Mitigated `RUSTSEC-2026-0186` (`memmap2`), indexed parallel batch processing via `rayon`, and direct byte-buffer streaming via `serde_json::to_writer` | [01_rust_toolchain_simd_and_mmap.md](01_rust_toolchain_simd_and_mmap.md) |
|
| 13 |
-
| **Python, PyO3 & Maturin** | `Py 3.10` - `3.15.0`, `pyo3 0.24` - `0.30.1` | `Python 3.15.0`, `pyo3 0.30.1`, `maturin 1.15.0` | Removed `extension-module`, enabled `abi3t`, migrated to `py.detach`, `Bound::cast::<T>()`, and `#[pyclass(frozen, subclass)]` | [02_python_pyo3_abi3t_and_maturin.md](02_python_pyo3_abi3t_and_maturin.md) |
|
| 14 |
-
| **Apache Arrow & Parquet** | `53.0.0` - `60.0.0` | `parquet 60.0.0`, `arrow-array 60.0.0`, `arrow-schema 60.0.0` | Added `StringViewArray`/`BinaryViewArray` support, `ArrowReaderMetadata` caching, and `&mut *self` reborrowing in `ParquetMetaDataReader` | [03_parquet_arrow_and_http206_streaming.md](03_parquet_arrow_and_http206_streaming.md) |
|
| 15 |
-
| **Async Networking & HTTP Buffers** | `reqwest 0.12` - `0.13.5`, `tokio 1.38` - `1.53.3`, `bytes 1.6` - `1.12.1` | `reqwest 0.13.5`, `tokio 1.53.3`, `bytes 1.12.1`, `futures 0.3.34` | Mitigated `CVE-2026-25541` (`bytes 1.12.1`), explicit `default-tls` + `stream` + `json` feature selection on `reqwest 0.13.5` | [03_parquet_arrow_and_http206_streaming.md](03_parquet_arrow_and_http206_streaming.md) |
|
| 16 |
-
| **Hashing, Tables & Synchronization** | `2024` - `2026` | `xxhash-rust 0.8.19`, `hashbrown 0.17.1`, `crc32fast 1.5.2`, `parking_lot 0.12.5` | Bypassed default `foldhash` in `hashbrown 0.17` via `IdentityBuildHasher`, enabled `vpclmulqdq` hardware acceleration in `crc32fast`, and enforced HuggingFace Hub NDJSON security | [04_xxh3_dedup_crc32_and_hf_hub_security.md](04_xxh3_dedup_crc32_and_hf_hub_security.md) |
|
| 17 |
-
| **Universal Formats & Decompression** | `2025` - `2026` | `arrow-ipc 60.0.0`, `half 2.7.1`, `flate2 1.1.10`, `zstd 0.14.3`, `lz4_flex 0.14.0` | Mitigated `CVE-2026-32829` (`lz4_flex 0.14.0`), `DCtx` soundness fixes (`zstd 0.14.3`), `runtime_detection` (`flate2 1.1.10`), Arrow IPC/Feather/WebDataset TAR, and Damerau-Levenshtein typo diagnostics | [05_universal_formats_compression_and_diagnostics.md](05_universal_formats_compression_and_diagnostics.md) |
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
## File Navigation Map
|
| 22 |
-
|
| 23 |
-
| File Path | Role Summary | Status |
|
| 24 |
-
| :--- | :--- | :--- |
|
| 25 |
-
| `data/docs/library_audit/README.md` | Master index and version summary for all external libraries in `luminamoon-dataloader`. | Active |
|
| 26 |
-
| `data/docs/library_audit/01_rust_toolchain_simd_and_mmap.md` | Changelog audit for Rust 1.85-1.99.1 (`std::arch`), `memmap2 0.9.11`, `rayon 1.12.0`, `thiserror 2.0.21`, and `serde`. | Active |
|
| 27 |
-
| `data/docs/library_audit/02_python_pyo3_abi3t_and_maturin.md` | Changelog audit for Python 3.10-3.15, PyO3 0.24-0.30.1 (`abi3t`, `py.detach`, `Bound::cast`), and Maturin 1.15. | Active |
|
| 28 |
-
| `data/docs/library_audit/03_parquet_arrow_and_http206_streaming.md` | Changelog audit for Apache Arrow/Parquet 53-60.0.0, `reqwest 0.13.5`, `tokio 1.53.3`, and `bytes 1.12.1`. | Active |
|
| 29 |
-
| `data/docs/library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md` | Changelog audit for `xxhash-rust 0.8.19`, `hashbrown 0.17.1`, `crc32fast 1.5.2`, `parking_lot 0.12.5`, and HuggingFace Hub API specs. | Active |
|
| 30 |
-
| `data/docs/library_audit/05_universal_formats_compression_and_diagnostics.md` | Version-by-version changelog audit (2025-2026) for `arrow-ipc 60.0.0`, `half 2.7.1`, `flate2 1.1.10`, `zstd 0.14.3`, `lz4_flex 0.14.0`, WebDataset `.tar` parser, and typo diagnostics. | Active |
|
|
|
|
| 1 |
+
# Library Version & Changelog Audit Documentation (October 2026)
|
| 2 |
+
|
| 3 |
+
This directory documents the version-by-version changelog audit, intermediate API/ABI deprecations, security advisory mitigations (`RUSTSEC` / `CVE`), and source-code adaptations to the latest stable releases across all external libraries used by the **LuminaMoon Data Engine (`luminamoon-dataloader v1.1.0`)** crate.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Library Summary
|
| 8 |
+
|
| 9 |
+
| Component / Library | Audited Version Range | Latest Stable Version | Adaptation Status | Detailed Document |
|
| 10 |
+
| :--- | :--- | :--- | :--- | :--- |
|
| 11 |
+
| **Rust Toolchain & `std::arch`** | `1.85.0` - `1.99.1` | `1.99.1` (`edition = "2024"`) | Adopted `std::cfg_select!`, `[T]::as_chunks::<3>()`, and `unsafe_op_in_unsafe_fn` compliance across `AVX-512BW` / `AVX2` / `NEON` intrinsics | [01_rust_toolchain_simd_and_mmap.md](01_rust_toolchain_simd_and_mmap.md) |
|
| 12 |
+
| **System & Parallelism Crates** | `2024` - `2026` | `memmap2 0.9.11`, `rayon 1.12.0`, `thiserror 2.0.21`, `serde 1.0.229`, `serde_json 1.0.154` | Mitigated `RUSTSEC-2026-0186` (`memmap2`), indexed parallel batch processing via `rayon`, and direct byte-buffer streaming via `serde_json::to_writer` | [01_rust_toolchain_simd_and_mmap.md](01_rust_toolchain_simd_and_mmap.md) |
|
| 13 |
+
| **Python, PyO3 & Maturin** | `Py 3.10` - `3.15.0`, `pyo3 0.24` - `0.30.1` | `Python 3.15.0`, `pyo3 0.30.1`, `maturin 1.15.0` | Removed `extension-module`, enabled `abi3t`, migrated to `py.detach`, `Bound::cast::<T>()`, and `#[pyclass(frozen, subclass)]` | [02_python_pyo3_abi3t_and_maturin.md](02_python_pyo3_abi3t_and_maturin.md) |
|
| 14 |
+
| **Apache Arrow & Parquet** | `53.0.0` - `60.0.0` | `parquet 60.0.0`, `arrow-array 60.0.0`, `arrow-schema 60.0.0` | Added `StringViewArray`/`BinaryViewArray` support, `ArrowReaderMetadata` caching, and `&mut *self` reborrowing in `ParquetMetaDataReader` | [03_parquet_arrow_and_http206_streaming.md](03_parquet_arrow_and_http206_streaming.md) |
|
| 15 |
+
| **Async Networking & HTTP Buffers** | `reqwest 0.12` - `0.13.5`, `tokio 1.38` - `1.53.3`, `bytes 1.6` - `1.12.1` | `reqwest 0.13.5`, `tokio 1.53.3`, `bytes 1.12.1`, `futures 0.3.34` | Mitigated `CVE-2026-25541` (`bytes 1.12.1`), explicit `default-tls` + `stream` + `json` feature selection on `reqwest 0.13.5` | [03_parquet_arrow_and_http206_streaming.md](03_parquet_arrow_and_http206_streaming.md) |
|
| 16 |
+
| **Hashing, Tables & Synchronization** | `2024` - `2026` | `xxhash-rust 0.8.19`, `hashbrown 0.17.1`, `crc32fast 1.5.2`, `parking_lot 0.12.5` | Bypassed default `foldhash` in `hashbrown 0.17` via `IdentityBuildHasher`, enabled `vpclmulqdq` hardware acceleration in `crc32fast`, and enforced HuggingFace Hub NDJSON security | [04_xxh3_dedup_crc32_and_hf_hub_security.md](04_xxh3_dedup_crc32_and_hf_hub_security.md) |
|
| 17 |
+
| **Universal Formats & Decompression** | `2025` - `2026` | `arrow-ipc 60.0.0`, `half 2.7.1`, `flate2 1.1.10`, `zstd 0.14.3`, `lz4_flex 0.14.0` | Mitigated `CVE-2026-32829` (`lz4_flex 0.14.0`), `DCtx` soundness fixes (`zstd 0.14.3`), `runtime_detection` (`flate2 1.1.10`), Arrow IPC/Feather/WebDataset TAR, and Damerau-Levenshtein typo diagnostics | [05_universal_formats_compression_and_diagnostics.md](05_universal_formats_compression_and_diagnostics.md) |
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## File Navigation Map
|
| 22 |
+
|
| 23 |
+
| File Path | Role Summary | Status |
|
| 24 |
+
| :--- | :--- | :--- |
|
| 25 |
+
| `data/docs/library_audit/README.md` | Master index and version summary for all external libraries in `luminamoon-dataloader`. | Active |
|
| 26 |
+
| `data/docs/library_audit/01_rust_toolchain_simd_and_mmap.md` | Changelog audit for Rust 1.85-1.99.1 (`std::arch`), `memmap2 0.9.11`, `rayon 1.12.0`, `thiserror 2.0.21`, and `serde`. | Active |
|
| 27 |
+
| `data/docs/library_audit/02_python_pyo3_abi3t_and_maturin.md` | Changelog audit for Python 3.10-3.15, PyO3 0.24-0.30.1 (`abi3t`, `py.detach`, `Bound::cast`), and Maturin 1.15. | Active |
|
| 28 |
+
| `data/docs/library_audit/03_parquet_arrow_and_http206_streaming.md` | Changelog audit for Apache Arrow/Parquet 53-60.0.0, `reqwest 0.13.5`, `tokio 1.53.3`, and `bytes 1.12.1`. | Active |
|
| 29 |
+
| `data/docs/library_audit/04_xxh3_dedup_crc32_and_hf_hub_security.md` | Changelog audit for `xxhash-rust 0.8.19`, `hashbrown 0.17.1`, `crc32fast 1.5.2`, `parking_lot 0.12.5`, and HuggingFace Hub API specs. | Active |
|
| 30 |
+
| `data/docs/library_audit/05_universal_formats_compression_and_diagnostics.md` | Version-by-version changelog audit (2025-2026) for `arrow-ipc 60.0.0`, `half 2.7.1`, `flate2 1.1.10`, `zstd 0.14.3`, `lz4_flex 0.14.0`, WebDataset `.tar` parser, and typo diagnostics. | Active |
|
data/src/constants.rs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
| 1 |
-
pub const STREAM_COORD_BYTE_LEN: usize = 32;
|
| 2 |
-
pub const STREAM_COORDINATE_BYTE_LEN: usize = STREAM_COORD_BYTE_LEN;
|
| 3 |
-
|
| 4 |
-
pub const CHECKPOINT_SLOT_MAGIC: [u8; 4] = *b"LMDL";
|
| 5 |
-
pub const META_SLOT_MAGIC: [u8; 4] = CHECKPOINT_SLOT_MAGIC;
|
| 6 |
-
pub const CHECKPOINT_SLOT_VERSION: u32 = 1;
|
| 7 |
-
pub const META_SLOT_VERSION: u32 = CHECKPOINT_SLOT_VERSION;
|
| 8 |
-
pub const CHECKPOINT_SLOT_SIZE: usize = 64;
|
| 9 |
-
pub const META_SLOT_SIZE: usize = CHECKPOINT_SLOT_SIZE;
|
| 10 |
-
pub const CHECKPOINT_TOTAL_META_SIZE: u64 = (CHECKPOINT_SLOT_SIZE * 2) as u64;
|
| 11 |
-
pub const TOTAL_META_SIZE: u64 = CHECKPOINT_TOTAL_META_SIZE;
|
| 12 |
-
pub const DEDUP_FILE_SUFFIX: &str = ".dedup";
|
| 13 |
-
|
| 14 |
-
pub const PARQUET_MAGIC_BYTES: &[u8; 4] = b"PAR1";
|
| 15 |
-
pub const MAGIC_PARQUET: &[u8; 4] = PARQUET_MAGIC_BYTES;
|
| 16 |
-
pub const ARROW_IPC_MAGIC_BYTES: &[u8; 6] = b"ARROW1";
|
| 17 |
-
pub const MAGIC_ARROW_IPC: &[u8; 6] = ARROW_IPC_MAGIC_BYTES;
|
| 18 |
-
|
| 19 |
-
pub const DEFAULT_BATCH_SIZE: usize = 64;
|
| 20 |
-
pub const MAX_TYPO_SUGGESTIONS: usize = 3;
|
| 21 |
-
pub const MAX_COLUMN_PREVIEW_COUNT: usize = 16;
|
|
|
|
| 1 |
+
pub const STREAM_COORD_BYTE_LEN: usize = 32;
|
| 2 |
+
pub const STREAM_COORDINATE_BYTE_LEN: usize = STREAM_COORD_BYTE_LEN;
|
| 3 |
+
|
| 4 |
+
pub const CHECKPOINT_SLOT_MAGIC: [u8; 4] = *b"LMDL";
|
| 5 |
+
pub const META_SLOT_MAGIC: [u8; 4] = CHECKPOINT_SLOT_MAGIC;
|
| 6 |
+
pub const CHECKPOINT_SLOT_VERSION: u32 = 1;
|
| 7 |
+
pub const META_SLOT_VERSION: u32 = CHECKPOINT_SLOT_VERSION;
|
| 8 |
+
pub const CHECKPOINT_SLOT_SIZE: usize = 64;
|
| 9 |
+
pub const META_SLOT_SIZE: usize = CHECKPOINT_SLOT_SIZE;
|
| 10 |
+
pub const CHECKPOINT_TOTAL_META_SIZE: u64 = (CHECKPOINT_SLOT_SIZE * 2) as u64;
|
| 11 |
+
pub const TOTAL_META_SIZE: u64 = CHECKPOINT_TOTAL_META_SIZE;
|
| 12 |
+
pub const DEDUP_FILE_SUFFIX: &str = ".dedup";
|
| 13 |
+
|
| 14 |
+
pub const PARQUET_MAGIC_BYTES: &[u8; 4] = b"PAR1";
|
| 15 |
+
pub const MAGIC_PARQUET: &[u8; 4] = PARQUET_MAGIC_BYTES;
|
| 16 |
+
pub const ARROW_IPC_MAGIC_BYTES: &[u8; 6] = b"ARROW1";
|
| 17 |
+
pub const MAGIC_ARROW_IPC: &[u8; 6] = ARROW_IPC_MAGIC_BYTES;
|
| 18 |
+
|
| 19 |
+
pub const DEFAULT_BATCH_SIZE: usize = 64;
|
| 20 |
+
pub const MAX_TYPO_SUGGESTIONS: usize = 3;
|
| 21 |
+
pub const MAX_COLUMN_PREVIEW_COUNT: usize = 16;
|
data/src/coord.rs
CHANGED
|
@@ -1,39 +1,39 @@
|
|
| 1 |
-
use crate::constants::STREAM_COORDINATE_BYTE_LEN;
|
| 2 |
-
|
| 3 |
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 4 |
-
pub struct StreamCoordinate {
|
| 5 |
-
pub shard_id: u32,
|
| 6 |
-
pub byte_offset: u64,
|
| 7 |
-
pub row_group_idx: u32,
|
| 8 |
-
pub page_idx: u32,
|
| 9 |
-
pub row_offset_in_page: u32,
|
| 10 |
-
pub global_row_idx: u64,
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
impl StreamCoordinate {
|
| 14 |
-
pub const BYTE_LEN: usize = STREAM_COORDINATE_BYTE_LEN;
|
| 15 |
-
|
| 16 |
-
#[inline]
|
| 17 |
-
pub fn to_bytes(&self) -> [u8; Self::BYTE_LEN] {
|
| 18 |
-
let mut buf = [0u8; Self::BYTE_LEN];
|
| 19 |
-
buf[0..4].copy_from_slice(&self.shard_id.to_le_bytes());
|
| 20 |
-
buf[4..12].copy_from_slice(&self.byte_offset.to_le_bytes());
|
| 21 |
-
buf[12..16].copy_from_slice(&self.row_group_idx.to_le_bytes());
|
| 22 |
-
buf[16..20].copy_from_slice(&self.page_idx.to_le_bytes());
|
| 23 |
-
buf[20..24].copy_from_slice(&self.row_offset_in_page.to_le_bytes());
|
| 24 |
-
buf[24..32].copy_from_slice(&self.global_row_idx.to_le_bytes());
|
| 25 |
-
buf
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
#[inline]
|
| 29 |
-
pub fn from_bytes(buf: &[u8; Self::BYTE_LEN]) -> Self {
|
| 30 |
-
Self {
|
| 31 |
-
shard_id: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
|
| 32 |
-
byte_offset: u64::from_le_bytes(buf[4..12].try_into().unwrap()),
|
| 33 |
-
row_group_idx: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
|
| 34 |
-
page_idx: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
|
| 35 |
-
row_offset_in_page: u32::from_le_bytes(buf[20..24].try_into().unwrap()),
|
| 36 |
-
global_row_idx: u64::from_le_bytes(buf[24..32].try_into().unwrap()),
|
| 37 |
-
}
|
| 38 |
-
}
|
| 39 |
-
}
|
|
|
|
| 1 |
+
use crate::constants::STREAM_COORDINATE_BYTE_LEN;
|
| 2 |
+
|
| 3 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 4 |
+
pub struct StreamCoordinate {
|
| 5 |
+
pub shard_id: u32,
|
| 6 |
+
pub byte_offset: u64,
|
| 7 |
+
pub row_group_idx: u32,
|
| 8 |
+
pub page_idx: u32,
|
| 9 |
+
pub row_offset_in_page: u32,
|
| 10 |
+
pub global_row_idx: u64,
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
impl StreamCoordinate {
|
| 14 |
+
pub const BYTE_LEN: usize = STREAM_COORDINATE_BYTE_LEN;
|
| 15 |
+
|
| 16 |
+
#[inline]
|
| 17 |
+
pub fn to_bytes(&self) -> [u8; Self::BYTE_LEN] {
|
| 18 |
+
let mut buf = [0u8; Self::BYTE_LEN];
|
| 19 |
+
buf[0..4].copy_from_slice(&self.shard_id.to_le_bytes());
|
| 20 |
+
buf[4..12].copy_from_slice(&self.byte_offset.to_le_bytes());
|
| 21 |
+
buf[12..16].copy_from_slice(&self.row_group_idx.to_le_bytes());
|
| 22 |
+
buf[16..20].copy_from_slice(&self.page_idx.to_le_bytes());
|
| 23 |
+
buf[20..24].copy_from_slice(&self.row_offset_in_page.to_le_bytes());
|
| 24 |
+
buf[24..32].copy_from_slice(&self.global_row_idx.to_le_bytes());
|
| 25 |
+
buf
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
#[inline]
|
| 29 |
+
pub fn from_bytes(buf: &[u8; Self::BYTE_LEN]) -> Self {
|
| 30 |
+
Self {
|
| 31 |
+
shard_id: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
|
| 32 |
+
byte_offset: u64::from_le_bytes(buf[4..12].try_into().unwrap()),
|
| 33 |
+
row_group_idx: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
|
| 34 |
+
page_idx: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
|
| 35 |
+
row_offset_in_page: u32::from_le_bytes(buf[20..24].try_into().unwrap()),
|
| 36 |
+
global_row_idx: u64::from_le_bytes(buf[24..32].try_into().unwrap()),
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
}
|
data/src/dedup.rs
CHANGED
|
@@ -1,112 +1,112 @@
|
|
| 1 |
-
use std::hash::{BuildHasherDefault, Hasher};
|
| 2 |
-
|
| 3 |
-
use hashbrown::{HashMap, HashSet};
|
| 4 |
-
use xxhash_rust::xxh3::xxh3_64;
|
| 5 |
-
|
| 6 |
-
#[derive(Default)]
|
| 7 |
-
pub struct IdentityHasher(u64);
|
| 8 |
-
|
| 9 |
-
impl Hasher for IdentityHasher {
|
| 10 |
-
#[inline]
|
| 11 |
-
fn finish(&self) -> u64 {
|
| 12 |
-
self.0
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
#[inline]
|
| 16 |
-
fn write(&mut self, bytes: &[u8]) {
|
| 17 |
-
self.0 = xxh3_64(bytes);
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
#[inline]
|
| 21 |
-
fn write_u64(&mut self, i: u64) {
|
| 22 |
-
self.0 = i;
|
| 23 |
-
}
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
pub type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
|
| 27 |
-
|
| 28 |
-
#[derive(Debug, Default)]
|
| 29 |
-
pub struct TwoStageDeduplicator {
|
| 30 |
-
committed_hashes: HashSet<u64, IdentityBuildHasher>,
|
| 31 |
-
staged_lookup: HashSet<u64, IdentityBuildHasher>,
|
| 32 |
-
staged_batches: HashMap<u64, Vec<u64>>,
|
| 33 |
-
}
|
| 34 |
-
|
| 35 |
-
impl TwoStageDeduplicator {
|
| 36 |
-
pub fn new() -> Self {
|
| 37 |
-
Self::default()
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
pub fn with_committed(hashes: impl IntoIterator<Item = u64>) -> Self {
|
| 41 |
-
let committed_hashes: HashSet<u64, IdentityBuildHasher> = hashes.into_iter().collect();
|
| 42 |
-
Self {
|
| 43 |
-
committed_hashes,
|
| 44 |
-
staged_lookup: HashSet::default(),
|
| 45 |
-
staged_batches: HashMap::new(),
|
| 46 |
-
}
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
#[inline]
|
| 50 |
-
pub fn compute_hash(payload: &[u8]) -> u64 {
|
| 51 |
-
xxh3_64(payload)
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
#[inline]
|
| 55 |
-
pub fn is_seen(&self, payload: &[u8]) -> bool {
|
| 56 |
-
let hash = Self::compute_hash(payload);
|
| 57 |
-
self.committed_hashes.contains(&hash) || self.staged_lookup.contains(&hash)
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
#[inline]
|
| 61 |
-
pub fn try_stage(&mut self, batch_id: u64, payload: &[u8]) -> Option<u64> {
|
| 62 |
-
let hash = Self::compute_hash(payload);
|
| 63 |
-
if self.committed_hashes.contains(&hash) || self.staged_lookup.contains(&hash) {
|
| 64 |
-
return None;
|
| 65 |
-
}
|
| 66 |
-
self.staged_lookup.insert(hash);
|
| 67 |
-
self.staged_batches.entry(batch_id).or_default().push(hash);
|
| 68 |
-
Some(hash)
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
pub fn commit_batch(&mut self, batch_id: u64) -> Vec<u64> {
|
| 72 |
-
let Some(hashes) = self.staged_batches.remove(&batch_id) else {
|
| 73 |
-
return Vec::new();
|
| 74 |
-
};
|
| 75 |
-
for &h in &hashes {
|
| 76 |
-
self.staged_lookup.remove(&h);
|
| 77 |
-
self.committed_hashes.insert(h);
|
| 78 |
-
}
|
| 79 |
-
hashes
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
pub fn rollback_batch(&mut self, batch_id: u64) -> usize {
|
| 83 |
-
let Some(hashes) = self.staged_batches.remove(&batch_id) else {
|
| 84 |
-
return 0;
|
| 85 |
-
};
|
| 86 |
-
for &h in &hashes {
|
| 87 |
-
self.staged_lookup.remove(&h);
|
| 88 |
-
}
|
| 89 |
-
hashes.len()
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
pub fn clear_staged(&mut self) {
|
| 93 |
-
self.staged_lookup.clear();
|
| 94 |
-
self.staged_batches.clear();
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
pub fn committed_len(&self) -> usize {
|
| 98 |
-
self.committed_hashes.len()
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
pub fn staged_len(&self) -> usize {
|
| 102 |
-
self.staged_lookup.len()
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
pub fn export_committed_hashes(&self) -> Vec<u64> {
|
| 106 |
-
self.committed_hashes.iter().copied().collect()
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
pub fn import_committed_hashes(&mut self, hashes: impl IntoIterator<Item = u64>) {
|
| 110 |
-
self.committed_hashes.extend(hashes);
|
| 111 |
-
}
|
| 112 |
-
}
|
|
|
|
| 1 |
+
use std::hash::{BuildHasherDefault, Hasher};
|
| 2 |
+
|
| 3 |
+
use hashbrown::{HashMap, HashSet};
|
| 4 |
+
use xxhash_rust::xxh3::xxh3_64;
|
| 5 |
+
|
| 6 |
+
#[derive(Default)]
|
| 7 |
+
pub struct IdentityHasher(u64);
|
| 8 |
+
|
| 9 |
+
impl Hasher for IdentityHasher {
|
| 10 |
+
#[inline]
|
| 11 |
+
fn finish(&self) -> u64 {
|
| 12 |
+
self.0
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
#[inline]
|
| 16 |
+
fn write(&mut self, bytes: &[u8]) {
|
| 17 |
+
self.0 = xxh3_64(bytes);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
#[inline]
|
| 21 |
+
fn write_u64(&mut self, i: u64) {
|
| 22 |
+
self.0 = i;
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
pub type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
|
| 27 |
+
|
| 28 |
+
#[derive(Debug, Default)]
|
| 29 |
+
pub struct TwoStageDeduplicator {
|
| 30 |
+
committed_hashes: HashSet<u64, IdentityBuildHasher>,
|
| 31 |
+
staged_lookup: HashSet<u64, IdentityBuildHasher>,
|
| 32 |
+
staged_batches: HashMap<u64, Vec<u64>>,
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
impl TwoStageDeduplicator {
|
| 36 |
+
pub fn new() -> Self {
|
| 37 |
+
Self::default()
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
pub fn with_committed(hashes: impl IntoIterator<Item = u64>) -> Self {
|
| 41 |
+
let committed_hashes: HashSet<u64, IdentityBuildHasher> = hashes.into_iter().collect();
|
| 42 |
+
Self {
|
| 43 |
+
committed_hashes,
|
| 44 |
+
staged_lookup: HashSet::default(),
|
| 45 |
+
staged_batches: HashMap::new(),
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
#[inline]
|
| 50 |
+
pub fn compute_hash(payload: &[u8]) -> u64 {
|
| 51 |
+
xxh3_64(payload)
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
#[inline]
|
| 55 |
+
pub fn is_seen(&self, payload: &[u8]) -> bool {
|
| 56 |
+
let hash = Self::compute_hash(payload);
|
| 57 |
+
self.committed_hashes.contains(&hash) || self.staged_lookup.contains(&hash)
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
#[inline]
|
| 61 |
+
pub fn try_stage(&mut self, batch_id: u64, payload: &[u8]) -> Option<u64> {
|
| 62 |
+
let hash = Self::compute_hash(payload);
|
| 63 |
+
if self.committed_hashes.contains(&hash) || self.staged_lookup.contains(&hash) {
|
| 64 |
+
return None;
|
| 65 |
+
}
|
| 66 |
+
self.staged_lookup.insert(hash);
|
| 67 |
+
self.staged_batches.entry(batch_id).or_default().push(hash);
|
| 68 |
+
Some(hash)
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
pub fn commit_batch(&mut self, batch_id: u64) -> Vec<u64> {
|
| 72 |
+
let Some(hashes) = self.staged_batches.remove(&batch_id) else {
|
| 73 |
+
return Vec::new();
|
| 74 |
+
};
|
| 75 |
+
for &h in &hashes {
|
| 76 |
+
self.staged_lookup.remove(&h);
|
| 77 |
+
self.committed_hashes.insert(h);
|
| 78 |
+
}
|
| 79 |
+
hashes
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
pub fn rollback_batch(&mut self, batch_id: u64) -> usize {
|
| 83 |
+
let Some(hashes) = self.staged_batches.remove(&batch_id) else {
|
| 84 |
+
return 0;
|
| 85 |
+
};
|
| 86 |
+
for &h in &hashes {
|
| 87 |
+
self.staged_lookup.remove(&h);
|
| 88 |
+
}
|
| 89 |
+
hashes.len()
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
pub fn clear_staged(&mut self) {
|
| 93 |
+
self.staged_lookup.clear();
|
| 94 |
+
self.staged_batches.clear();
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
pub fn committed_len(&self) -> usize {
|
| 98 |
+
self.committed_hashes.len()
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
pub fn staged_len(&self) -> usize {
|
| 102 |
+
self.staged_lookup.len()
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
pub fn export_committed_hashes(&self) -> Vec<u64> {
|
| 106 |
+
self.committed_hashes.iter().copied().collect()
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
pub fn import_committed_hashes(&mut self, hashes: impl IntoIterator<Item = u64>) {
|
| 110 |
+
self.committed_hashes.extend(hashes);
|
| 111 |
+
}
|
| 112 |
+
}
|
data/src/error.rs
CHANGED
|
@@ -1,166 +1,166 @@
|
|
| 1 |
-
use thiserror::Error;
|
| 2 |
-
|
| 3 |
-
use crate::constants::{MAX_COLUMN_PREVIEW_COUNT, MAX_TYPO_SUGGESTIONS};
|
| 4 |
-
|
| 5 |
-
#[derive(Debug, Error)]
|
| 6 |
-
pub enum DataLoaderError {
|
| 7 |
-
#[error("I/O failure: {0}")]
|
| 8 |
-
Io(#[from] std::io::Error),
|
| 9 |
-
|
| 10 |
-
#[error("Parquet failure: {0}")]
|
| 11 |
-
Parquet(#[from] parquet::errors::ParquetError),
|
| 12 |
-
|
| 13 |
-
#[error("Arrow failure: {0}")]
|
| 14 |
-
Arrow(#[from] arrow_schema::ArrowError),
|
| 15 |
-
|
| 16 |
-
#[error("Corrupted metadata checkpoint in both slots")]
|
| 17 |
-
CorruptedMetadata,
|
| 18 |
-
|
| 19 |
-
#[error("Unknown staged batch id: {0}")]
|
| 20 |
-
UnknownBatchId(u64),
|
| 21 |
-
|
| 22 |
-
#[error("Invalid coordinate buffer length")]
|
| 23 |
-
InvalidCoordinate,
|
| 24 |
-
|
| 25 |
-
#[error("Target column not found: {0}")]
|
| 26 |
-
ColumnNotFound(String),
|
| 27 |
-
|
| 28 |
-
#[error("Invalid configuration: {0}")]
|
| 29 |
-
InvalidConfig(String),
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
pub type Result<T> = std::result::Result<T, DataLoaderError>;
|
| 33 |
-
|
| 34 |
-
pub fn damerau_levenshtein(a: &str, b: &str) -> usize {
|
| 35 |
-
let a_chars: Vec<char> = a.chars().collect();
|
| 36 |
-
let b_chars: Vec<char> = b.chars().collect();
|
| 37 |
-
let len_a = a_chars.len();
|
| 38 |
-
let len_b = b_chars.len();
|
| 39 |
-
|
| 40 |
-
if len_a == 0 {
|
| 41 |
-
return len_b;
|
| 42 |
-
}
|
| 43 |
-
if len_b == 0 {
|
| 44 |
-
return len_a;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
let width = len_b + 1;
|
| 48 |
-
let mut matrix = vec![0usize; (len_a + 1) * width];
|
| 49 |
-
|
| 50 |
-
for i in 0..=len_a {
|
| 51 |
-
matrix[i * width] = i;
|
| 52 |
-
}
|
| 53 |
-
for (j, cell) in matrix.iter_mut().enumerate().take(len_b + 1) {
|
| 54 |
-
*cell = j;
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
for i in 1..=len_a {
|
| 58 |
-
for j in 1..=len_b {
|
| 59 |
-
let cost = usize::from(a_chars[i - 1] != b_chars[j - 1]);
|
| 60 |
-
let deletion = matrix[(i - 1) * width + j] + 1;
|
| 61 |
-
let insertion = matrix[i * width + (j - 1)] + 1;
|
| 62 |
-
let substitution = matrix[(i - 1) * width + (j - 1)] + cost;
|
| 63 |
-
let mut best = deletion.min(insertion).min(substitution);
|
| 64 |
-
|
| 65 |
-
if i > 1
|
| 66 |
-
&& j > 1
|
| 67 |
-
&& a_chars[i - 1] == b_chars[j - 2]
|
| 68 |
-
&& a_chars[i - 2] == b_chars[j - 1]
|
| 69 |
-
{
|
| 70 |
-
let transposition = matrix[(i - 2) * width + (j - 2)] + 1;
|
| 71 |
-
best = best.min(transposition);
|
| 72 |
-
}
|
| 73 |
-
matrix[i * width + j] = best;
|
| 74 |
-
}
|
| 75 |
-
}
|
| 76 |
-
|
| 77 |
-
matrix[len_a * width + len_b]
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
pub fn find_closest_matches<S: AsRef<str>>(
|
| 81 |
-
target: &str,
|
| 82 |
-
candidates: &[S],
|
| 83 |
-
max_suggestions: usize,
|
| 84 |
-
) -> Vec<String> {
|
| 85 |
-
let target_trim = target.trim();
|
| 86 |
-
if target_trim.is_empty() || candidates.is_empty() || max_suggestions == 0 {
|
| 87 |
-
return Vec::new();
|
| 88 |
-
}
|
| 89 |
-
let target_lower = target_trim.to_ascii_lowercase();
|
| 90 |
-
|
| 91 |
-
let mut scored: Vec<(usize, usize, String)> = Vec::new();
|
| 92 |
-
for cand in candidates {
|
| 93 |
-
let c_str = cand.as_ref();
|
| 94 |
-
if c_str.is_empty() {
|
| 95 |
-
continue;
|
| 96 |
-
}
|
| 97 |
-
let c_lower = c_str.to_ascii_lowercase();
|
| 98 |
-
if c_lower == target_lower {
|
| 99 |
-
scored.push((0, 0, c_str.to_owned()));
|
| 100 |
-
continue;
|
| 101 |
-
}
|
| 102 |
-
let dist = damerau_levenshtein(&target_lower, &c_lower);
|
| 103 |
-
let max_len = target_lower.chars().count().max(c_lower.chars().count());
|
| 104 |
-
let threshold = match max_len {
|
| 105 |
-
0..=3 => 1,
|
| 106 |
-
4..=6 => 2,
|
| 107 |
-
7..=11 => 3,
|
| 108 |
-
_ => (max_len * 2) / 5,
|
| 109 |
-
};
|
| 110 |
-
let is_sub = c_lower.contains(&target_lower) || target_lower.contains(&c_lower);
|
| 111 |
-
if dist <= threshold || (target_lower.len() >= 3 && is_sub) {
|
| 112 |
-
let rank_bonus = if is_sub { 0 } else { 1 };
|
| 113 |
-
scored.push((dist, rank_bonus, c_str.to_owned()));
|
| 114 |
-
}
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
scored.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)).then(a.2.cmp(&b.2)));
|
| 118 |
-
scored.dedup_by(|a, b| a.2 == b.2);
|
| 119 |
-
scored
|
| 120 |
-
.into_iter()
|
| 121 |
-
.take(max_suggestions)
|
| 122 |
-
.map(|(_, _, s)| s)
|
| 123 |
-
.collect()
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
pub fn format_did_you_mean<S: AsRef<str>>(target: &str, candidates: &[S]) -> String {
|
| 127 |
-
let matches = find_closest_matches(target, candidates, MAX_TYPO_SUGGESTIONS);
|
| 128 |
-
if matches.is_empty() {
|
| 129 |
-
return String::new();
|
| 130 |
-
}
|
| 131 |
-
if matches.len() == 1 {
|
| 132 |
-
format!(" Did you mean '{}'?", matches[0])
|
| 133 |
-
} else {
|
| 134 |
-
let quoted: Vec<String> = matches.iter().map(|m| format!("'{m}'")).collect();
|
| 135 |
-
format!(" Did you mean one of [{}]?", quoted.join(", "))
|
| 136 |
-
}
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
pub fn column_not_found_with_suggestions<S: AsRef<str>>(
|
| 140 |
-
requested: &str,
|
| 141 |
-
available: &[S],
|
| 142 |
-
) -> DataLoaderError {
|
| 143 |
-
let suggestion = format_did_you_mean(requested, available);
|
| 144 |
-
let avail_preview: Vec<String> = available
|
| 145 |
-
.iter()
|
| 146 |
-
.take(MAX_COLUMN_PREVIEW_COUNT)
|
| 147 |
-
.map(|s| format!("'{}'", s.as_ref()))
|
| 148 |
-
.collect();
|
| 149 |
-
let avail_suffix = if available.len() > MAX_COLUMN_PREVIEW_COUNT {
|
| 150 |
-
format!(
|
| 151 |
-
" (and {} more)",
|
| 152 |
-
available.len().saturating_sub(avail_preview.len())
|
| 153 |
-
)
|
| 154 |
-
} else {
|
| 155 |
-
String::new()
|
| 156 |
-
};
|
| 157 |
-
|
| 158 |
-
if avail_preview.is_empty() {
|
| 159 |
-
DataLoaderError::ColumnNotFound(requested.to_owned())
|
| 160 |
-
} else {
|
| 161 |
-
DataLoaderError::ColumnNotFound(format!(
|
| 162 |
-
"'{requested}'.{suggestion} Available columns: [{}]{avail_suffix}",
|
| 163 |
-
avail_preview.join(", ")
|
| 164 |
-
))
|
| 165 |
-
}
|
| 166 |
-
}
|
|
|
|
| 1 |
+
use thiserror::Error;
|
| 2 |
+
|
| 3 |
+
use crate::constants::{MAX_COLUMN_PREVIEW_COUNT, MAX_TYPO_SUGGESTIONS};
|
| 4 |
+
|
| 5 |
+
#[derive(Debug, Error)]
|
| 6 |
+
pub enum DataLoaderError {
|
| 7 |
+
#[error("I/O failure: {0}")]
|
| 8 |
+
Io(#[from] std::io::Error),
|
| 9 |
+
|
| 10 |
+
#[error("Parquet failure: {0}")]
|
| 11 |
+
Parquet(#[from] parquet::errors::ParquetError),
|
| 12 |
+
|
| 13 |
+
#[error("Arrow failure: {0}")]
|
| 14 |
+
Arrow(#[from] arrow_schema::ArrowError),
|
| 15 |
+
|
| 16 |
+
#[error("Corrupted metadata checkpoint in both slots")]
|
| 17 |
+
CorruptedMetadata,
|
| 18 |
+
|
| 19 |
+
#[error("Unknown staged batch id: {0}")]
|
| 20 |
+
UnknownBatchId(u64),
|
| 21 |
+
|
| 22 |
+
#[error("Invalid coordinate buffer length")]
|
| 23 |
+
InvalidCoordinate,
|
| 24 |
+
|
| 25 |
+
#[error("Target column not found: {0}")]
|
| 26 |
+
ColumnNotFound(String),
|
| 27 |
+
|
| 28 |
+
#[error("Invalid configuration: {0}")]
|
| 29 |
+
InvalidConfig(String),
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
pub type Result<T> = std::result::Result<T, DataLoaderError>;
|
| 33 |
+
|
| 34 |
+
pub fn damerau_levenshtein(a: &str, b: &str) -> usize {
|
| 35 |
+
let a_chars: Vec<char> = a.chars().collect();
|
| 36 |
+
let b_chars: Vec<char> = b.chars().collect();
|
| 37 |
+
let len_a = a_chars.len();
|
| 38 |
+
let len_b = b_chars.len();
|
| 39 |
+
|
| 40 |
+
if len_a == 0 {
|
| 41 |
+
return len_b;
|
| 42 |
+
}
|
| 43 |
+
if len_b == 0 {
|
| 44 |
+
return len_a;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
let width = len_b + 1;
|
| 48 |
+
let mut matrix = vec![0usize; (len_a + 1) * width];
|
| 49 |
+
|
| 50 |
+
for i in 0..=len_a {
|
| 51 |
+
matrix[i * width] = i;
|
| 52 |
+
}
|
| 53 |
+
for (j, cell) in matrix.iter_mut().enumerate().take(len_b + 1) {
|
| 54 |
+
*cell = j;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
for i in 1..=len_a {
|
| 58 |
+
for j in 1..=len_b {
|
| 59 |
+
let cost = usize::from(a_chars[i - 1] != b_chars[j - 1]);
|
| 60 |
+
let deletion = matrix[(i - 1) * width + j] + 1;
|
| 61 |
+
let insertion = matrix[i * width + (j - 1)] + 1;
|
| 62 |
+
let substitution = matrix[(i - 1) * width + (j - 1)] + cost;
|
| 63 |
+
let mut best = deletion.min(insertion).min(substitution);
|
| 64 |
+
|
| 65 |
+
if i > 1
|
| 66 |
+
&& j > 1
|
| 67 |
+
&& a_chars[i - 1] == b_chars[j - 2]
|
| 68 |
+
&& a_chars[i - 2] == b_chars[j - 1]
|
| 69 |
+
{
|
| 70 |
+
let transposition = matrix[(i - 2) * width + (j - 2)] + 1;
|
| 71 |
+
best = best.min(transposition);
|
| 72 |
+
}
|
| 73 |
+
matrix[i * width + j] = best;
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
matrix[len_a * width + len_b]
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
pub fn find_closest_matches<S: AsRef<str>>(
|
| 81 |
+
target: &str,
|
| 82 |
+
candidates: &[S],
|
| 83 |
+
max_suggestions: usize,
|
| 84 |
+
) -> Vec<String> {
|
| 85 |
+
let target_trim = target.trim();
|
| 86 |
+
if target_trim.is_empty() || candidates.is_empty() || max_suggestions == 0 {
|
| 87 |
+
return Vec::new();
|
| 88 |
+
}
|
| 89 |
+
let target_lower = target_trim.to_ascii_lowercase();
|
| 90 |
+
|
| 91 |
+
let mut scored: Vec<(usize, usize, String)> = Vec::new();
|
| 92 |
+
for cand in candidates {
|
| 93 |
+
let c_str = cand.as_ref();
|
| 94 |
+
if c_str.is_empty() {
|
| 95 |
+
continue;
|
| 96 |
+
}
|
| 97 |
+
let c_lower = c_str.to_ascii_lowercase();
|
| 98 |
+
if c_lower == target_lower {
|
| 99 |
+
scored.push((0, 0, c_str.to_owned()));
|
| 100 |
+
continue;
|
| 101 |
+
}
|
| 102 |
+
let dist = damerau_levenshtein(&target_lower, &c_lower);
|
| 103 |
+
let max_len = target_lower.chars().count().max(c_lower.chars().count());
|
| 104 |
+
let threshold = match max_len {
|
| 105 |
+
0..=3 => 1,
|
| 106 |
+
4..=6 => 2,
|
| 107 |
+
7..=11 => 3,
|
| 108 |
+
_ => (max_len * 2) / 5,
|
| 109 |
+
};
|
| 110 |
+
let is_sub = c_lower.contains(&target_lower) || target_lower.contains(&c_lower);
|
| 111 |
+
if dist <= threshold || (target_lower.len() >= 3 && is_sub) {
|
| 112 |
+
let rank_bonus = if is_sub { 0 } else { 1 };
|
| 113 |
+
scored.push((dist, rank_bonus, c_str.to_owned()));
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
scored.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)).then(a.2.cmp(&b.2)));
|
| 118 |
+
scored.dedup_by(|a, b| a.2 == b.2);
|
| 119 |
+
scored
|
| 120 |
+
.into_iter()
|
| 121 |
+
.take(max_suggestions)
|
| 122 |
+
.map(|(_, _, s)| s)
|
| 123 |
+
.collect()
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
pub fn format_did_you_mean<S: AsRef<str>>(target: &str, candidates: &[S]) -> String {
|
| 127 |
+
let matches = find_closest_matches(target, candidates, MAX_TYPO_SUGGESTIONS);
|
| 128 |
+
if matches.is_empty() {
|
| 129 |
+
return String::new();
|
| 130 |
+
}
|
| 131 |
+
if matches.len() == 1 {
|
| 132 |
+
format!(" Did you mean '{}'?", matches[0])
|
| 133 |
+
} else {
|
| 134 |
+
let quoted: Vec<String> = matches.iter().map(|m| format!("'{m}'")).collect();
|
| 135 |
+
format!(" Did you mean one of [{}]?", quoted.join(", "))
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
pub fn column_not_found_with_suggestions<S: AsRef<str>>(
|
| 140 |
+
requested: &str,
|
| 141 |
+
available: &[S],
|
| 142 |
+
) -> DataLoaderError {
|
| 143 |
+
let suggestion = format_did_you_mean(requested, available);
|
| 144 |
+
let avail_preview: Vec<String> = available
|
| 145 |
+
.iter()
|
| 146 |
+
.take(MAX_COLUMN_PREVIEW_COUNT)
|
| 147 |
+
.map(|s| format!("'{}'", s.as_ref()))
|
| 148 |
+
.collect();
|
| 149 |
+
let avail_suffix = if available.len() > MAX_COLUMN_PREVIEW_COUNT {
|
| 150 |
+
format!(
|
| 151 |
+
" (and {} more)",
|
| 152 |
+
available.len().saturating_sub(avail_preview.len())
|
| 153 |
+
)
|
| 154 |
+
} else {
|
| 155 |
+
String::new()
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
+
if avail_preview.is_empty() {
|
| 159 |
+
DataLoaderError::ColumnNotFound(requested.to_owned())
|
| 160 |
+
} else {
|
| 161 |
+
DataLoaderError::ColumnNotFound(format!(
|
| 162 |
+
"'{requested}'.{suggestion} Available columns: [{}]{avail_suffix}",
|
| 163 |
+
avail_preview.join(", ")
|
| 164 |
+
))
|
| 165 |
+
}
|
| 166 |
+
}
|
data/src/lib.rs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
| 1 |
-
pub mod constants;
|
| 2 |
-
pub mod coord;
|
| 3 |
-
pub mod dedup;
|
| 4 |
-
pub mod error;
|
| 5 |
-
pub mod metadata;
|
| 6 |
-
pub mod minhash;
|
| 7 |
-
pub mod postprocess;
|
| 8 |
-
pub mod py_bridge;
|
| 9 |
-
pub mod quality;
|
| 10 |
-
pub mod serialize;
|
| 11 |
-
pub mod simd_cpu;
|
| 12 |
-
pub mod string_tensor;
|
| 13 |
-
|
| 14 |
-
#[cfg(test)]
|
| 15 |
-
mod tests;
|
|
|
|
| 1 |
+
pub mod constants;
|
| 2 |
+
pub mod coord;
|
| 3 |
+
pub mod dedup;
|
| 4 |
+
pub mod error;
|
| 5 |
+
pub mod metadata;
|
| 6 |
+
pub mod minhash;
|
| 7 |
+
pub mod postprocess;
|
| 8 |
+
pub mod py_bridge;
|
| 9 |
+
pub mod quality;
|
| 10 |
+
pub mod serialize;
|
| 11 |
+
pub mod simd_cpu;
|
| 12 |
+
pub mod string_tensor;
|
| 13 |
+
|
| 14 |
+
#[cfg(test)]
|
| 15 |
+
mod tests;
|
data/src/metadata.rs
CHANGED
|
@@ -1,264 +1,264 @@
|
|
| 1 |
-
use std::fs::{File, OpenOptions};
|
| 2 |
-
use std::io::{Read, Seek, SeekFrom, Write};
|
| 3 |
-
use std::path::{Path, PathBuf};
|
| 4 |
-
|
| 5 |
-
use crc32fast::Hasher;
|
| 6 |
-
use hashbrown::HashMap;
|
| 7 |
-
|
| 8 |
-
use crate::constants::{
|
| 9 |
-
DEDUP_FILE_SUFFIX, META_SLOT_MAGIC, META_SLOT_SIZE, META_SLOT_VERSION, TOTAL_META_SIZE,
|
| 10 |
-
};
|
| 11 |
-
use crate::coord::StreamCoordinate;
|
| 12 |
-
use crate::dedup::TwoStageDeduplicator;
|
| 13 |
-
use crate::error::{DataLoaderError, Result};
|
| 14 |
-
|
| 15 |
-
const SLOT_MAGIC: [u8; 4] = META_SLOT_MAGIC;
|
| 16 |
-
const SLOT_VERSION: u32 = META_SLOT_VERSION;
|
| 17 |
-
const SLOT_SIZE: usize = META_SLOT_SIZE;
|
| 18 |
-
|
| 19 |
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 20 |
-
struct DiskSlot {
|
| 21 |
-
epoch: u64,
|
| 22 |
-
committed_batch_id: u64,
|
| 23 |
-
coord: StreamCoordinate,
|
| 24 |
-
dedup_count: u64,
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
impl DiskSlot {
|
| 28 |
-
fn encode(&self) -> [u8; SLOT_SIZE] {
|
| 29 |
-
let mut buf = [0u8; SLOT_SIZE];
|
| 30 |
-
buf[0..4].copy_from_slice(&SLOT_MAGIC);
|
| 31 |
-
buf[4..8].copy_from_slice(&SLOT_VERSION.to_le_bytes());
|
| 32 |
-
buf[8..16].copy_from_slice(&self.epoch.to_le_bytes());
|
| 33 |
-
buf[16..24].copy_from_slice(&self.committed_batch_id.to_le_bytes());
|
| 34 |
-
buf[24..56].copy_from_slice(&self.coord.to_bytes());
|
| 35 |
-
buf[56..60].copy_from_slice(&(self.dedup_count as u32).to_le_bytes());
|
| 36 |
-
|
| 37 |
-
let mut hasher = Hasher::new();
|
| 38 |
-
hasher.update(&buf[0..60]);
|
| 39 |
-
let crc = hasher.finalize();
|
| 40 |
-
buf[60..64].copy_from_slice(&crc.to_le_bytes());
|
| 41 |
-
buf
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
fn decode(buf: &[u8; SLOT_SIZE]) -> Option<Self> {
|
| 45 |
-
if buf[0..4] != SLOT_MAGIC {
|
| 46 |
-
return None;
|
| 47 |
-
}
|
| 48 |
-
let version = u32::from_le_bytes(buf[4..8].try_into().unwrap());
|
| 49 |
-
if version != SLOT_VERSION {
|
| 50 |
-
return None;
|
| 51 |
-
}
|
| 52 |
-
let expected_crc = u32::from_le_bytes(buf[60..64].try_into().unwrap());
|
| 53 |
-
let mut hasher = Hasher::new();
|
| 54 |
-
hasher.update(&buf[0..60]);
|
| 55 |
-
if hasher.finalize() != expected_crc {
|
| 56 |
-
return None;
|
| 57 |
-
}
|
| 58 |
-
let coord_bytes: &[u8; StreamCoordinate::BYTE_LEN] = buf[24..56].try_into().unwrap();
|
| 59 |
-
Some(Self {
|
| 60 |
-
epoch: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
|
| 61 |
-
committed_batch_id: u64::from_le_bytes(buf[16..24].try_into().unwrap()),
|
| 62 |
-
coord: StreamCoordinate::from_bytes(coord_bytes),
|
| 63 |
-
dedup_count: u32::from_le_bytes(buf[56..60].try_into().unwrap()) as u64,
|
| 64 |
-
})
|
| 65 |
-
}
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
pub struct DualMetadataManager {
|
| 69 |
-
meta_file: File,
|
| 70 |
-
dedup_file: File,
|
| 71 |
-
epoch: u64,
|
| 72 |
-
committed_batch_id: u64,
|
| 73 |
-
committed_coord: StreamCoordinate,
|
| 74 |
-
in_flight_coord: StreamCoordinate,
|
| 75 |
-
staged_coords: HashMap<u64, StreamCoordinate>,
|
| 76 |
-
pub deduplicator: TwoStageDeduplicator,
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
impl DualMetadataManager {
|
| 80 |
-
pub fn open(meta_path: impl AsRef<Path>) -> Result<Self> {
|
| 81 |
-
let meta_path = meta_path.as_ref();
|
| 82 |
-
if let Some(parent) = meta_path.parent()
|
| 83 |
-
&& !parent.as_os_str().is_empty()
|
| 84 |
-
{
|
| 85 |
-
std::fs::create_dir_all(parent)?;
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
let dedup_path = Self::dedup_path_for(meta_path);
|
| 89 |
-
|
| 90 |
-
let mut meta_file = OpenOptions::new()
|
| 91 |
-
.read(true)
|
| 92 |
-
.write(true)
|
| 93 |
-
.create(true)
|
| 94 |
-
.truncate(false)
|
| 95 |
-
.open(meta_path)?;
|
| 96 |
-
|
| 97 |
-
let mut dedup_file = OpenOptions::new()
|
| 98 |
-
.read(true)
|
| 99 |
-
.write(true)
|
| 100 |
-
.create(true)
|
| 101 |
-
.truncate(false)
|
| 102 |
-
.open(&dedup_path)?;
|
| 103 |
-
|
| 104 |
-
let file_len = meta_file.metadata()?.len();
|
| 105 |
-
let active_slot = if file_len < TOTAL_META_SIZE {
|
| 106 |
-
meta_file.set_len(TOTAL_META_SIZE)?;
|
| 107 |
-
let initial = DiskSlot::default();
|
| 108 |
-
let encoded = initial.encode();
|
| 109 |
-
meta_file.seek(SeekFrom::Start(0))?;
|
| 110 |
-
meta_file.write_all(&encoded)?;
|
| 111 |
-
meta_file.write_all(&encoded)?;
|
| 112 |
-
meta_file.sync_data()?;
|
| 113 |
-
initial
|
| 114 |
-
} else {
|
| 115 |
-
let mut raw = [0u8; SLOT_SIZE * 2];
|
| 116 |
-
meta_file.seek(SeekFrom::Start(0))?;
|
| 117 |
-
meta_file.read_exact(&mut raw)?;
|
| 118 |
-
|
| 119 |
-
let slot_a = DiskSlot::decode(raw[0..SLOT_SIZE].try_into().unwrap());
|
| 120 |
-
let slot_b = DiskSlot::decode(raw[SLOT_SIZE..SLOT_SIZE * 2].try_into().unwrap());
|
| 121 |
-
|
| 122 |
-
match (slot_a, slot_b) {
|
| 123 |
-
(Some(a), Some(b)) => {
|
| 124 |
-
if a.epoch >= b.epoch {
|
| 125 |
-
a
|
| 126 |
-
} else {
|
| 127 |
-
b
|
| 128 |
-
}
|
| 129 |
-
}
|
| 130 |
-
(Some(a), None) => a,
|
| 131 |
-
(None, Some(b)) => b,
|
| 132 |
-
(None, None) => return Err(DataLoaderError::CorruptedMetadata),
|
| 133 |
-
}
|
| 134 |
-
};
|
| 135 |
-
|
| 136 |
-
let valid_dedup_bytes = active_slot.dedup_count * 8;
|
| 137 |
-
if dedup_file.metadata()?.len() < valid_dedup_bytes {
|
| 138 |
-
return Err(DataLoaderError::CorruptedMetadata);
|
| 139 |
-
}
|
| 140 |
-
dedup_file.set_len(valid_dedup_bytes)?;
|
| 141 |
-
dedup_file.seek(SeekFrom::Start(0))?;
|
| 142 |
-
|
| 143 |
-
let mut raw_hashes = vec![0u8; valid_dedup_bytes as usize];
|
| 144 |
-
if valid_dedup_bytes > 0 {
|
| 145 |
-
dedup_file.read_exact(&mut raw_hashes)?;
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
let restored_hashes = raw_hashes
|
| 149 |
-
.as_chunks::<8>()
|
| 150 |
-
.0
|
| 151 |
-
.iter()
|
| 152 |
-
.map(|&c| u64::from_le_bytes(c));
|
| 153 |
-
|
| 154 |
-
let deduplicator = TwoStageDeduplicator::with_committed(restored_hashes);
|
| 155 |
-
|
| 156 |
-
Ok(Self {
|
| 157 |
-
meta_file,
|
| 158 |
-
dedup_file,
|
| 159 |
-
epoch: active_slot.epoch,
|
| 160 |
-
committed_batch_id: active_slot.committed_batch_id,
|
| 161 |
-
committed_coord: active_slot.coord,
|
| 162 |
-
in_flight_coord: active_slot.coord,
|
| 163 |
-
staged_coords: HashMap::new(),
|
| 164 |
-
deduplicator,
|
| 165 |
-
})
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
fn dedup_path_for(meta_path: &Path) -> PathBuf {
|
| 169 |
-
let mut p = meta_path.as_os_str().to_owned();
|
| 170 |
-
p.push(DEDUP_FILE_SUFFIX);
|
| 171 |
-
PathBuf::from(p)
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
#[inline]
|
| 175 |
-
pub fn committed_coordinate(&self) -> StreamCoordinate {
|
| 176 |
-
self.committed_coord
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
#[inline]
|
| 180 |
-
pub fn in_flight_coordinate(&self) -> StreamCoordinate {
|
| 181 |
-
self.in_flight_coord
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
#[inline]
|
| 185 |
-
pub fn committed_batch_id(&self) -> u64 {
|
| 186 |
-
self.committed_batch_id
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
#[inline]
|
| 190 |
-
pub fn set_in_flight_coordinate(&mut self, next_coord: StreamCoordinate) {
|
| 191 |
-
self.in_flight_coord = next_coord;
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
pub fn stage_batch_in_ram(&mut self, batch_id: u64, next_coord: StreamCoordinate) {
|
| 195 |
-
self.in_flight_coord = next_coord;
|
| 196 |
-
self.staged_coords.insert(batch_id, next_coord);
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
pub fn commit_gpu_batch(&mut self, batch_id: u64) -> Result<StreamCoordinate> {
|
| 200 |
-
let Some(next_coord) = self.staged_coords.remove(&batch_id) else {
|
| 201 |
-
return Err(DataLoaderError::UnknownBatchId(batch_id));
|
| 202 |
-
};
|
| 203 |
-
|
| 204 |
-
let prev_dedup_count = self.deduplicator.committed_len() as u64;
|
| 205 |
-
let new_hashes = self.deduplicator.commit_batch(batch_id);
|
| 206 |
-
if !new_hashes.is_empty() {
|
| 207 |
-
let mut hash_buf = Vec::with_capacity(new_hashes.len() * 8);
|
| 208 |
-
for h in new_hashes {
|
| 209 |
-
hash_buf.extend_from_slice(&h.to_le_bytes());
|
| 210 |
-
}
|
| 211 |
-
let write_offset = prev_dedup_count * 8;
|
| 212 |
-
self.dedup_file.seek(SeekFrom::Start(write_offset))?;
|
| 213 |
-
self.dedup_file.write_all(&hash_buf)?;
|
| 214 |
-
self.dedup_file
|
| 215 |
-
.set_len(write_offset + hash_buf.len() as u64)?;
|
| 216 |
-
self.dedup_file.sync_data()?;
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
self.epoch += 1;
|
| 220 |
-
self.committed_batch_id = batch_id;
|
| 221 |
-
self.committed_coord = next_coord;
|
| 222 |
-
|
| 223 |
-
let slot = DiskSlot {
|
| 224 |
-
epoch: self.epoch,
|
| 225 |
-
committed_batch_id: self.committed_batch_id,
|
| 226 |
-
coord: self.committed_coord,
|
| 227 |
-
dedup_count: self.deduplicator.committed_len() as u64,
|
| 228 |
-
};
|
| 229 |
-
|
| 230 |
-
let slot_offset = if self.epoch % 2 == 1 {
|
| 231 |
-
0
|
| 232 |
-
} else {
|
| 233 |
-
SLOT_SIZE as u64
|
| 234 |
-
};
|
| 235 |
-
|
| 236 |
-
let encoded = slot.encode();
|
| 237 |
-
self.meta_file.seek(SeekFrom::Start(slot_offset))?;
|
| 238 |
-
self.meta_file.write_all(&encoded)?;
|
| 239 |
-
self.meta_file.sync_data()?;
|
| 240 |
-
|
| 241 |
-
Ok(self.committed_coord)
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
pub fn rollback_staged_batch(&mut self, batch_id: u64) -> Result<StreamCoordinate> {
|
| 245 |
-
if self.staged_coords.remove(&batch_id).is_none() {
|
| 246 |
-
return Err(DataLoaderError::UnknownBatchId(batch_id));
|
| 247 |
-
}
|
| 248 |
-
self.deduplicator.rollback_batch(batch_id);
|
| 249 |
-
self.in_flight_coord = self
|
| 250 |
-
.staged_coords
|
| 251 |
-
.iter()
|
| 252 |
-
.max_by_key(|&(&id, _)| id)
|
| 253 |
-
.map(|(_, &coord)| coord)
|
| 254 |
-
.unwrap_or(self.committed_coord);
|
| 255 |
-
Ok(self.in_flight_coord)
|
| 256 |
-
}
|
| 257 |
-
|
| 258 |
-
pub fn rollback_ram_to_committed(&mut self) -> StreamCoordinate {
|
| 259 |
-
self.staged_coords.clear();
|
| 260 |
-
self.deduplicator.clear_staged();
|
| 261 |
-
self.in_flight_coord = self.committed_coord;
|
| 262 |
-
self.committed_coord
|
| 263 |
-
}
|
| 264 |
-
}
|
|
|
|
| 1 |
+
use std::fs::{File, OpenOptions};
|
| 2 |
+
use std::io::{Read, Seek, SeekFrom, Write};
|
| 3 |
+
use std::path::{Path, PathBuf};
|
| 4 |
+
|
| 5 |
+
use crc32fast::Hasher;
|
| 6 |
+
use hashbrown::HashMap;
|
| 7 |
+
|
| 8 |
+
use crate::constants::{
|
| 9 |
+
DEDUP_FILE_SUFFIX, META_SLOT_MAGIC, META_SLOT_SIZE, META_SLOT_VERSION, TOTAL_META_SIZE,
|
| 10 |
+
};
|
| 11 |
+
use crate::coord::StreamCoordinate;
|
| 12 |
+
use crate::dedup::TwoStageDeduplicator;
|
| 13 |
+
use crate::error::{DataLoaderError, Result};
|
| 14 |
+
|
| 15 |
+
const SLOT_MAGIC: [u8; 4] = META_SLOT_MAGIC;
|
| 16 |
+
const SLOT_VERSION: u32 = META_SLOT_VERSION;
|
| 17 |
+
const SLOT_SIZE: usize = META_SLOT_SIZE;
|
| 18 |
+
|
| 19 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 20 |
+
struct DiskSlot {
|
| 21 |
+
epoch: u64,
|
| 22 |
+
committed_batch_id: u64,
|
| 23 |
+
coord: StreamCoordinate,
|
| 24 |
+
dedup_count: u64,
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
impl DiskSlot {
|
| 28 |
+
fn encode(&self) -> [u8; SLOT_SIZE] {
|
| 29 |
+
let mut buf = [0u8; SLOT_SIZE];
|
| 30 |
+
buf[0..4].copy_from_slice(&SLOT_MAGIC);
|
| 31 |
+
buf[4..8].copy_from_slice(&SLOT_VERSION.to_le_bytes());
|
| 32 |
+
buf[8..16].copy_from_slice(&self.epoch.to_le_bytes());
|
| 33 |
+
buf[16..24].copy_from_slice(&self.committed_batch_id.to_le_bytes());
|
| 34 |
+
buf[24..56].copy_from_slice(&self.coord.to_bytes());
|
| 35 |
+
buf[56..60].copy_from_slice(&(self.dedup_count as u32).to_le_bytes());
|
| 36 |
+
|
| 37 |
+
let mut hasher = Hasher::new();
|
| 38 |
+
hasher.update(&buf[0..60]);
|
| 39 |
+
let crc = hasher.finalize();
|
| 40 |
+
buf[60..64].copy_from_slice(&crc.to_le_bytes());
|
| 41 |
+
buf
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
fn decode(buf: &[u8; SLOT_SIZE]) -> Option<Self> {
|
| 45 |
+
if buf[0..4] != SLOT_MAGIC {
|
| 46 |
+
return None;
|
| 47 |
+
}
|
| 48 |
+
let version = u32::from_le_bytes(buf[4..8].try_into().unwrap());
|
| 49 |
+
if version != SLOT_VERSION {
|
| 50 |
+
return None;
|
| 51 |
+
}
|
| 52 |
+
let expected_crc = u32::from_le_bytes(buf[60..64].try_into().unwrap());
|
| 53 |
+
let mut hasher = Hasher::new();
|
| 54 |
+
hasher.update(&buf[0..60]);
|
| 55 |
+
if hasher.finalize() != expected_crc {
|
| 56 |
+
return None;
|
| 57 |
+
}
|
| 58 |
+
let coord_bytes: &[u8; StreamCoordinate::BYTE_LEN] = buf[24..56].try_into().unwrap();
|
| 59 |
+
Some(Self {
|
| 60 |
+
epoch: u64::from_le_bytes(buf[8..16].try_into().unwrap()),
|
| 61 |
+
committed_batch_id: u64::from_le_bytes(buf[16..24].try_into().unwrap()),
|
| 62 |
+
coord: StreamCoordinate::from_bytes(coord_bytes),
|
| 63 |
+
dedup_count: u32::from_le_bytes(buf[56..60].try_into().unwrap()) as u64,
|
| 64 |
+
})
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
pub struct DualMetadataManager {
|
| 69 |
+
meta_file: File,
|
| 70 |
+
dedup_file: File,
|
| 71 |
+
epoch: u64,
|
| 72 |
+
committed_batch_id: u64,
|
| 73 |
+
committed_coord: StreamCoordinate,
|
| 74 |
+
in_flight_coord: StreamCoordinate,
|
| 75 |
+
staged_coords: HashMap<u64, StreamCoordinate>,
|
| 76 |
+
pub deduplicator: TwoStageDeduplicator,
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
impl DualMetadataManager {
|
| 80 |
+
pub fn open(meta_path: impl AsRef<Path>) -> Result<Self> {
|
| 81 |
+
let meta_path = meta_path.as_ref();
|
| 82 |
+
if let Some(parent) = meta_path.parent()
|
| 83 |
+
&& !parent.as_os_str().is_empty()
|
| 84 |
+
{
|
| 85 |
+
std::fs::create_dir_all(parent)?;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
let dedup_path = Self::dedup_path_for(meta_path);
|
| 89 |
+
|
| 90 |
+
let mut meta_file = OpenOptions::new()
|
| 91 |
+
.read(true)
|
| 92 |
+
.write(true)
|
| 93 |
+
.create(true)
|
| 94 |
+
.truncate(false)
|
| 95 |
+
.open(meta_path)?;
|
| 96 |
+
|
| 97 |
+
let mut dedup_file = OpenOptions::new()
|
| 98 |
+
.read(true)
|
| 99 |
+
.write(true)
|
| 100 |
+
.create(true)
|
| 101 |
+
.truncate(false)
|
| 102 |
+
.open(&dedup_path)?;
|
| 103 |
+
|
| 104 |
+
let file_len = meta_file.metadata()?.len();
|
| 105 |
+
let active_slot = if file_len < TOTAL_META_SIZE {
|
| 106 |
+
meta_file.set_len(TOTAL_META_SIZE)?;
|
| 107 |
+
let initial = DiskSlot::default();
|
| 108 |
+
let encoded = initial.encode();
|
| 109 |
+
meta_file.seek(SeekFrom::Start(0))?;
|
| 110 |
+
meta_file.write_all(&encoded)?;
|
| 111 |
+
meta_file.write_all(&encoded)?;
|
| 112 |
+
meta_file.sync_data()?;
|
| 113 |
+
initial
|
| 114 |
+
} else {
|
| 115 |
+
let mut raw = [0u8; SLOT_SIZE * 2];
|
| 116 |
+
meta_file.seek(SeekFrom::Start(0))?;
|
| 117 |
+
meta_file.read_exact(&mut raw)?;
|
| 118 |
+
|
| 119 |
+
let slot_a = DiskSlot::decode(raw[0..SLOT_SIZE].try_into().unwrap());
|
| 120 |
+
let slot_b = DiskSlot::decode(raw[SLOT_SIZE..SLOT_SIZE * 2].try_into().unwrap());
|
| 121 |
+
|
| 122 |
+
match (slot_a, slot_b) {
|
| 123 |
+
(Some(a), Some(b)) => {
|
| 124 |
+
if a.epoch >= b.epoch {
|
| 125 |
+
a
|
| 126 |
+
} else {
|
| 127 |
+
b
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
(Some(a), None) => a,
|
| 131 |
+
(None, Some(b)) => b,
|
| 132 |
+
(None, None) => return Err(DataLoaderError::CorruptedMetadata),
|
| 133 |
+
}
|
| 134 |
+
};
|
| 135 |
+
|
| 136 |
+
let valid_dedup_bytes = active_slot.dedup_count * 8;
|
| 137 |
+
if dedup_file.metadata()?.len() < valid_dedup_bytes {
|
| 138 |
+
return Err(DataLoaderError::CorruptedMetadata);
|
| 139 |
+
}
|
| 140 |
+
dedup_file.set_len(valid_dedup_bytes)?;
|
| 141 |
+
dedup_file.seek(SeekFrom::Start(0))?;
|
| 142 |
+
|
| 143 |
+
let mut raw_hashes = vec![0u8; valid_dedup_bytes as usize];
|
| 144 |
+
if valid_dedup_bytes > 0 {
|
| 145 |
+
dedup_file.read_exact(&mut raw_hashes)?;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
let restored_hashes = raw_hashes
|
| 149 |
+
.as_chunks::<8>()
|
| 150 |
+
.0
|
| 151 |
+
.iter()
|
| 152 |
+
.map(|&c| u64::from_le_bytes(c));
|
| 153 |
+
|
| 154 |
+
let deduplicator = TwoStageDeduplicator::with_committed(restored_hashes);
|
| 155 |
+
|
| 156 |
+
Ok(Self {
|
| 157 |
+
meta_file,
|
| 158 |
+
dedup_file,
|
| 159 |
+
epoch: active_slot.epoch,
|
| 160 |
+
committed_batch_id: active_slot.committed_batch_id,
|
| 161 |
+
committed_coord: active_slot.coord,
|
| 162 |
+
in_flight_coord: active_slot.coord,
|
| 163 |
+
staged_coords: HashMap::new(),
|
| 164 |
+
deduplicator,
|
| 165 |
+
})
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
fn dedup_path_for(meta_path: &Path) -> PathBuf {
|
| 169 |
+
let mut p = meta_path.as_os_str().to_owned();
|
| 170 |
+
p.push(DEDUP_FILE_SUFFIX);
|
| 171 |
+
PathBuf::from(p)
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
#[inline]
|
| 175 |
+
pub fn committed_coordinate(&self) -> StreamCoordinate {
|
| 176 |
+
self.committed_coord
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
#[inline]
|
| 180 |
+
pub fn in_flight_coordinate(&self) -> StreamCoordinate {
|
| 181 |
+
self.in_flight_coord
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
#[inline]
|
| 185 |
+
pub fn committed_batch_id(&self) -> u64 {
|
| 186 |
+
self.committed_batch_id
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
#[inline]
|
| 190 |
+
pub fn set_in_flight_coordinate(&mut self, next_coord: StreamCoordinate) {
|
| 191 |
+
self.in_flight_coord = next_coord;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
pub fn stage_batch_in_ram(&mut self, batch_id: u64, next_coord: StreamCoordinate) {
|
| 195 |
+
self.in_flight_coord = next_coord;
|
| 196 |
+
self.staged_coords.insert(batch_id, next_coord);
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
pub fn commit_gpu_batch(&mut self, batch_id: u64) -> Result<StreamCoordinate> {
|
| 200 |
+
let Some(next_coord) = self.staged_coords.remove(&batch_id) else {
|
| 201 |
+
return Err(DataLoaderError::UnknownBatchId(batch_id));
|
| 202 |
+
};
|
| 203 |
+
|
| 204 |
+
let prev_dedup_count = self.deduplicator.committed_len() as u64;
|
| 205 |
+
let new_hashes = self.deduplicator.commit_batch(batch_id);
|
| 206 |
+
if !new_hashes.is_empty() {
|
| 207 |
+
let mut hash_buf = Vec::with_capacity(new_hashes.len() * 8);
|
| 208 |
+
for h in new_hashes {
|
| 209 |
+
hash_buf.extend_from_slice(&h.to_le_bytes());
|
| 210 |
+
}
|
| 211 |
+
let write_offset = prev_dedup_count * 8;
|
| 212 |
+
self.dedup_file.seek(SeekFrom::Start(write_offset))?;
|
| 213 |
+
self.dedup_file.write_all(&hash_buf)?;
|
| 214 |
+
self.dedup_file
|
| 215 |
+
.set_len(write_offset + hash_buf.len() as u64)?;
|
| 216 |
+
self.dedup_file.sync_data()?;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
self.epoch += 1;
|
| 220 |
+
self.committed_batch_id = batch_id;
|
| 221 |
+
self.committed_coord = next_coord;
|
| 222 |
+
|
| 223 |
+
let slot = DiskSlot {
|
| 224 |
+
epoch: self.epoch,
|
| 225 |
+
committed_batch_id: self.committed_batch_id,
|
| 226 |
+
coord: self.committed_coord,
|
| 227 |
+
dedup_count: self.deduplicator.committed_len() as u64,
|
| 228 |
+
};
|
| 229 |
+
|
| 230 |
+
let slot_offset = if self.epoch % 2 == 1 {
|
| 231 |
+
0
|
| 232 |
+
} else {
|
| 233 |
+
SLOT_SIZE as u64
|
| 234 |
+
};
|
| 235 |
+
|
| 236 |
+
let encoded = slot.encode();
|
| 237 |
+
self.meta_file.seek(SeekFrom::Start(slot_offset))?;
|
| 238 |
+
self.meta_file.write_all(&encoded)?;
|
| 239 |
+
self.meta_file.sync_data()?;
|
| 240 |
+
|
| 241 |
+
Ok(self.committed_coord)
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
pub fn rollback_staged_batch(&mut self, batch_id: u64) -> Result<StreamCoordinate> {
|
| 245 |
+
if self.staged_coords.remove(&batch_id).is_none() {
|
| 246 |
+
return Err(DataLoaderError::UnknownBatchId(batch_id));
|
| 247 |
+
}
|
| 248 |
+
self.deduplicator.rollback_batch(batch_id);
|
| 249 |
+
self.in_flight_coord = self
|
| 250 |
+
.staged_coords
|
| 251 |
+
.iter()
|
| 252 |
+
.max_by_key(|&(&id, _)| id)
|
| 253 |
+
.map(|(_, &coord)| coord)
|
| 254 |
+
.unwrap_or(self.committed_coord);
|
| 255 |
+
Ok(self.in_flight_coord)
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
pub fn rollback_ram_to_committed(&mut self) -> StreamCoordinate {
|
| 259 |
+
self.staged_coords.clear();
|
| 260 |
+
self.deduplicator.clear_staged();
|
| 261 |
+
self.in_flight_coord = self.committed_coord;
|
| 262 |
+
self.committed_coord
|
| 263 |
+
}
|
| 264 |
+
}
|
data/src/minhash.rs
CHANGED
|
@@ -1,186 +1,186 @@
|
|
| 1 |
-
use hashbrown::{HashMap, HashSet};
|
| 2 |
-
use xxhash_rust::xxh3::{xxh3_64, xxh3_64_with_seed};
|
| 3 |
-
|
| 4 |
-
pub const DEFAULT_MINHASH_PERMUTATIONS: usize = 64;
|
| 5 |
-
|
| 6 |
-
#[derive(Debug, Clone)]
|
| 7 |
-
pub struct MinHashSignature {
|
| 8 |
-
pub values: Vec<u64>,
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
impl MinHashSignature {
|
| 12 |
-
pub fn compute_char_shingles(text: &str, k: usize, num_perm: usize) -> Self {
|
| 13 |
-
let mut mins = vec![u64::MAX; num_perm];
|
| 14 |
-
if text.is_empty() {
|
| 15 |
-
return Self { values: mins };
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
if text.is_ascii() {
|
| 19 |
-
let raw = text.as_bytes();
|
| 20 |
-
let effective_k = k.min(raw.len()).max(1);
|
| 21 |
-
for bytes in raw.windows(effective_k) {
|
| 22 |
-
for (i, min_val) in mins.iter_mut().enumerate() {
|
| 23 |
-
let seed = (i as u64).wrapping_mul(0x517cc1b727220a95);
|
| 24 |
-
let h = xxh3_64_with_seed(bytes, seed);
|
| 25 |
-
if h < *min_val {
|
| 26 |
-
*min_val = h;
|
| 27 |
-
}
|
| 28 |
-
}
|
| 29 |
-
}
|
| 30 |
-
return Self { values: mins };
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
let char_offsets: Vec<usize> = text.char_indices().map(|(idx, _)| idx).collect();
|
| 34 |
-
let num_chars = char_offsets.len();
|
| 35 |
-
let effective_k = k.min(num_chars).max(1);
|
| 36 |
-
|
| 37 |
-
for start_idx in 0..=(num_chars - effective_k) {
|
| 38 |
-
let byte_start = char_offsets[start_idx];
|
| 39 |
-
let byte_end = if start_idx + effective_k < num_chars {
|
| 40 |
-
char_offsets[start_idx + effective_k]
|
| 41 |
-
} else {
|
| 42 |
-
text.len()
|
| 43 |
-
};
|
| 44 |
-
let bytes = &text.as_bytes()[byte_start..byte_end];
|
| 45 |
-
|
| 46 |
-
for (i, min_val) in mins.iter_mut().enumerate() {
|
| 47 |
-
let seed = (i as u64).wrapping_mul(0x517cc1b727220a95);
|
| 48 |
-
let h = xxh3_64_with_seed(bytes, seed);
|
| 49 |
-
if h < *min_val {
|
| 50 |
-
*min_val = h;
|
| 51 |
-
}
|
| 52 |
-
}
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
Self { values: mins }
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
pub fn jaccard_similarity(&self, other: &Self) -> f64 {
|
| 59 |
-
if self.values.is_empty() || self.values.len() != other.values.len() {
|
| 60 |
-
return 0.0;
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
let mut matches = 0usize;
|
| 64 |
-
for (a, b) in self.values.iter().zip(other.values.iter()) {
|
| 65 |
-
if a == b {
|
| 66 |
-
matches += 1;
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
(matches as f64) / (self.values.len() as f64)
|
| 71 |
-
}
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
pub struct MinHashDeduplicator {
|
| 75 |
-
threshold: f64,
|
| 76 |
-
k_shingle: usize,
|
| 77 |
-
num_perm: usize,
|
| 78 |
-
num_bands: usize,
|
| 79 |
-
rows_per_band: usize,
|
| 80 |
-
signatures: Vec<MinHashSignature>,
|
| 81 |
-
lsh_buckets: HashMap<(usize, u64), Vec<usize>>,
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
-
impl MinHashDeduplicator {
|
| 85 |
-
pub fn new(threshold: f64, k_shingle: usize, num_perm: usize) -> Self {
|
| 86 |
-
let n_perm = num_perm.max(8);
|
| 87 |
-
let num_bands = if n_perm >= 64 { 8 } else { 4.min(n_perm) };
|
| 88 |
-
let rows_per_band = n_perm / num_bands;
|
| 89 |
-
|
| 90 |
-
Self {
|
| 91 |
-
threshold: threshold.clamp(0.0, 1.0),
|
| 92 |
-
k_shingle: k_shingle.max(1),
|
| 93 |
-
num_perm: n_perm,
|
| 94 |
-
num_bands,
|
| 95 |
-
rows_per_band,
|
| 96 |
-
signatures: Vec::new(),
|
| 97 |
-
lsh_buckets: HashMap::new(),
|
| 98 |
-
}
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
pub fn compute_signature(&self, text: &str) -> MinHashSignature {
|
| 102 |
-
MinHashSignature::compute_char_shingles(text, self.k_shingle, self.num_perm)
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
fn band_hash(&self, sig: &MinHashSignature, band_idx: usize) -> u64 {
|
| 106 |
-
let start = band_idx * self.rows_per_band;
|
| 107 |
-
let end = (start + self.rows_per_band).min(sig.values.len());
|
| 108 |
-
let slice = &sig.values[start..end];
|
| 109 |
-
let mut byte_buf = Vec::with_capacity(slice.len() * 8);
|
| 110 |
-
for &v in slice {
|
| 111 |
-
byte_buf.extend_from_slice(&v.to_le_bytes());
|
| 112 |
-
}
|
| 113 |
-
xxh3_64(&byte_buf)
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
pub fn max_similarity(&self, sig: &MinHashSignature) -> f64 {
|
| 117 |
-
if self.signatures.is_empty() {
|
| 118 |
-
return 0.0;
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
// For datasets under 128 items, perform exact scan for 100% precision
|
| 122 |
-
if self.signatures.len() <= 128 {
|
| 123 |
-
let mut max_sim = 0.0f64;
|
| 124 |
-
for existing in &self.signatures {
|
| 125 |
-
let sim = sig.jaccard_similarity(existing);
|
| 126 |
-
if sim > max_sim {
|
| 127 |
-
max_sim = sim;
|
| 128 |
-
if max_sim >= self.threshold {
|
| 129 |
-
return max_sim;
|
| 130 |
-
}
|
| 131 |
-
}
|
| 132 |
-
}
|
| 133 |
-
return max_sim;
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
// For larger datasets, query LSH candidate buckets in O(1) amortized time
|
| 137 |
-
let mut candidates = HashSet::new();
|
| 138 |
-
for b in 0..self.num_bands {
|
| 139 |
-
let bh = self.band_hash(sig, b);
|
| 140 |
-
if let Some(doc_ids) = self.lsh_buckets.get(&(b, bh)) {
|
| 141 |
-
for &id in doc_ids {
|
| 142 |
-
candidates.insert(id);
|
| 143 |
-
}
|
| 144 |
-
}
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
let mut max_sim = 0.0f64;
|
| 148 |
-
for &cand_id in &candidates {
|
| 149 |
-
if let Some(existing) = self.signatures.get(cand_id) {
|
| 150 |
-
let sim = sig.jaccard_similarity(existing);
|
| 151 |
-
if sim > max_sim {
|
| 152 |
-
max_sim = sim;
|
| 153 |
-
if max_sim >= self.threshold {
|
| 154 |
-
return max_sim;
|
| 155 |
-
}
|
| 156 |
-
}
|
| 157 |
-
}
|
| 158 |
-
}
|
| 159 |
-
max_sim
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
pub fn is_unique(&mut self, text: &str) -> bool {
|
| 163 |
-
let sig = self.compute_signature(text);
|
| 164 |
-
let max_sim = self.max_similarity(&sig);
|
| 165 |
-
if max_sim >= self.threshold {
|
| 166 |
-
return false;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
let doc_id = self.signatures.len();
|
| 170 |
-
for b in 0..self.num_bands {
|
| 171 |
-
let bh = self.band_hash(&sig, b);
|
| 172 |
-
self.lsh_buckets.entry((b, bh)).or_default().push(doc_id);
|
| 173 |
-
}
|
| 174 |
-
self.signatures.push(sig);
|
| 175 |
-
true
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
pub fn count(&self) -> usize {
|
| 179 |
-
self.signatures.len()
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
pub fn clear(&mut self) {
|
| 183 |
-
self.signatures.clear();
|
| 184 |
-
self.lsh_buckets.clear();
|
| 185 |
-
}
|
| 186 |
-
}
|
|
|
|
| 1 |
+
use hashbrown::{HashMap, HashSet};
|
| 2 |
+
use xxhash_rust::xxh3::{xxh3_64, xxh3_64_with_seed};
|
| 3 |
+
|
| 4 |
+
pub const DEFAULT_MINHASH_PERMUTATIONS: usize = 64;
|
| 5 |
+
|
| 6 |
+
#[derive(Debug, Clone)]
|
| 7 |
+
pub struct MinHashSignature {
|
| 8 |
+
pub values: Vec<u64>,
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
impl MinHashSignature {
|
| 12 |
+
pub fn compute_char_shingles(text: &str, k: usize, num_perm: usize) -> Self {
|
| 13 |
+
let mut mins = vec![u64::MAX; num_perm];
|
| 14 |
+
if text.is_empty() {
|
| 15 |
+
return Self { values: mins };
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
if text.is_ascii() {
|
| 19 |
+
let raw = text.as_bytes();
|
| 20 |
+
let effective_k = k.min(raw.len()).max(1);
|
| 21 |
+
for bytes in raw.windows(effective_k) {
|
| 22 |
+
for (i, min_val) in mins.iter_mut().enumerate() {
|
| 23 |
+
let seed = (i as u64).wrapping_mul(0x517cc1b727220a95);
|
| 24 |
+
let h = xxh3_64_with_seed(bytes, seed);
|
| 25 |
+
if h < *min_val {
|
| 26 |
+
*min_val = h;
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
return Self { values: mins };
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
let char_offsets: Vec<usize> = text.char_indices().map(|(idx, _)| idx).collect();
|
| 34 |
+
let num_chars = char_offsets.len();
|
| 35 |
+
let effective_k = k.min(num_chars).max(1);
|
| 36 |
+
|
| 37 |
+
for start_idx in 0..=(num_chars - effective_k) {
|
| 38 |
+
let byte_start = char_offsets[start_idx];
|
| 39 |
+
let byte_end = if start_idx + effective_k < num_chars {
|
| 40 |
+
char_offsets[start_idx + effective_k]
|
| 41 |
+
} else {
|
| 42 |
+
text.len()
|
| 43 |
+
};
|
| 44 |
+
let bytes = &text.as_bytes()[byte_start..byte_end];
|
| 45 |
+
|
| 46 |
+
for (i, min_val) in mins.iter_mut().enumerate() {
|
| 47 |
+
let seed = (i as u64).wrapping_mul(0x517cc1b727220a95);
|
| 48 |
+
let h = xxh3_64_with_seed(bytes, seed);
|
| 49 |
+
if h < *min_val {
|
| 50 |
+
*min_val = h;
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
Self { values: mins }
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
pub fn jaccard_similarity(&self, other: &Self) -> f64 {
|
| 59 |
+
if self.values.is_empty() || self.values.len() != other.values.len() {
|
| 60 |
+
return 0.0;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
let mut matches = 0usize;
|
| 64 |
+
for (a, b) in self.values.iter().zip(other.values.iter()) {
|
| 65 |
+
if a == b {
|
| 66 |
+
matches += 1;
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
(matches as f64) / (self.values.len() as f64)
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
pub struct MinHashDeduplicator {
|
| 75 |
+
threshold: f64,
|
| 76 |
+
k_shingle: usize,
|
| 77 |
+
num_perm: usize,
|
| 78 |
+
num_bands: usize,
|
| 79 |
+
rows_per_band: usize,
|
| 80 |
+
signatures: Vec<MinHashSignature>,
|
| 81 |
+
lsh_buckets: HashMap<(usize, u64), Vec<usize>>,
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
impl MinHashDeduplicator {
|
| 85 |
+
pub fn new(threshold: f64, k_shingle: usize, num_perm: usize) -> Self {
|
| 86 |
+
let n_perm = num_perm.max(8);
|
| 87 |
+
let num_bands = if n_perm >= 64 { 8 } else { 4.min(n_perm) };
|
| 88 |
+
let rows_per_band = n_perm / num_bands;
|
| 89 |
+
|
| 90 |
+
Self {
|
| 91 |
+
threshold: threshold.clamp(0.0, 1.0),
|
| 92 |
+
k_shingle: k_shingle.max(1),
|
| 93 |
+
num_perm: n_perm,
|
| 94 |
+
num_bands,
|
| 95 |
+
rows_per_band,
|
| 96 |
+
signatures: Vec::new(),
|
| 97 |
+
lsh_buckets: HashMap::new(),
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
pub fn compute_signature(&self, text: &str) -> MinHashSignature {
|
| 102 |
+
MinHashSignature::compute_char_shingles(text, self.k_shingle, self.num_perm)
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
fn band_hash(&self, sig: &MinHashSignature, band_idx: usize) -> u64 {
|
| 106 |
+
let start = band_idx * self.rows_per_band;
|
| 107 |
+
let end = (start + self.rows_per_band).min(sig.values.len());
|
| 108 |
+
let slice = &sig.values[start..end];
|
| 109 |
+
let mut byte_buf = Vec::with_capacity(slice.len() * 8);
|
| 110 |
+
for &v in slice {
|
| 111 |
+
byte_buf.extend_from_slice(&v.to_le_bytes());
|
| 112 |
+
}
|
| 113 |
+
xxh3_64(&byte_buf)
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
pub fn max_similarity(&self, sig: &MinHashSignature) -> f64 {
|
| 117 |
+
if self.signatures.is_empty() {
|
| 118 |
+
return 0.0;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
// For datasets under 128 items, perform exact scan for 100% precision
|
| 122 |
+
if self.signatures.len() <= 128 {
|
| 123 |
+
let mut max_sim = 0.0f64;
|
| 124 |
+
for existing in &self.signatures {
|
| 125 |
+
let sim = sig.jaccard_similarity(existing);
|
| 126 |
+
if sim > max_sim {
|
| 127 |
+
max_sim = sim;
|
| 128 |
+
if max_sim >= self.threshold {
|
| 129 |
+
return max_sim;
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
return max_sim;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
// For larger datasets, query LSH candidate buckets in O(1) amortized time
|
| 137 |
+
let mut candidates = HashSet::new();
|
| 138 |
+
for b in 0..self.num_bands {
|
| 139 |
+
let bh = self.band_hash(sig, b);
|
| 140 |
+
if let Some(doc_ids) = self.lsh_buckets.get(&(b, bh)) {
|
| 141 |
+
for &id in doc_ids {
|
| 142 |
+
candidates.insert(id);
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
let mut max_sim = 0.0f64;
|
| 148 |
+
for &cand_id in &candidates {
|
| 149 |
+
if let Some(existing) = self.signatures.get(cand_id) {
|
| 150 |
+
let sim = sig.jaccard_similarity(existing);
|
| 151 |
+
if sim > max_sim {
|
| 152 |
+
max_sim = sim;
|
| 153 |
+
if max_sim >= self.threshold {
|
| 154 |
+
return max_sim;
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
max_sim
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
pub fn is_unique(&mut self, text: &str) -> bool {
|
| 163 |
+
let sig = self.compute_signature(text);
|
| 164 |
+
let max_sim = self.max_similarity(&sig);
|
| 165 |
+
if max_sim >= self.threshold {
|
| 166 |
+
return false;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
let doc_id = self.signatures.len();
|
| 170 |
+
for b in 0..self.num_bands {
|
| 171 |
+
let bh = self.band_hash(&sig, b);
|
| 172 |
+
self.lsh_buckets.entry((b, bh)).or_default().push(doc_id);
|
| 173 |
+
}
|
| 174 |
+
self.signatures.push(sig);
|
| 175 |
+
true
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
pub fn count(&self) -> usize {
|
| 179 |
+
self.signatures.len()
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
pub fn clear(&mut self) {
|
| 183 |
+
self.signatures.clear();
|
| 184 |
+
self.lsh_buckets.clear();
|
| 185 |
+
}
|
| 186 |
+
}
|
data/src/postprocess.rs
CHANGED
|
@@ -1,135 +1,135 @@
|
|
| 1 |
-
use rayon::prelude::*;
|
| 2 |
-
use serde_json::Value;
|
| 3 |
-
|
| 4 |
-
#[derive(Debug, Clone)]
|
| 5 |
-
pub struct PostProcessConfig {
|
| 6 |
-
pub trim_whitespace: bool,
|
| 7 |
-
pub normalize_newlines: bool,
|
| 8 |
-
pub min_byte_len: usize,
|
| 9 |
-
pub max_byte_len: usize,
|
| 10 |
-
pub truncate_over_max: bool,
|
| 11 |
-
pub json_extract_key: Option<String>,
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
impl Default for PostProcessConfig {
|
| 15 |
-
fn default() -> Self {
|
| 16 |
-
Self {
|
| 17 |
-
trim_whitespace: true,
|
| 18 |
-
normalize_newlines: true,
|
| 19 |
-
min_byte_len: 0,
|
| 20 |
-
max_byte_len: 0,
|
| 21 |
-
truncate_over_max: false,
|
| 22 |
-
json_extract_key: None,
|
| 23 |
-
}
|
| 24 |
-
}
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
pub fn extract_json_value_by_path<'a>(root: &'a Value, key_path: &str) -> Option<&'a Value> {
|
| 28 |
-
if let Some(direct) = root.get(key_path) {
|
| 29 |
-
return Some(direct);
|
| 30 |
-
}
|
| 31 |
-
if !key_path.contains('.') {
|
| 32 |
-
return None;
|
| 33 |
-
}
|
| 34 |
-
let mut current = root;
|
| 35 |
-
for segment in key_path.split('.') {
|
| 36 |
-
if segment.is_empty() {
|
| 37 |
-
continue;
|
| 38 |
-
}
|
| 39 |
-
match current {
|
| 40 |
-
Value::Object(map) => {
|
| 41 |
-
current = map.get(segment)?;
|
| 42 |
-
}
|
| 43 |
-
Value::Array(arr) => {
|
| 44 |
-
let idx: isize = segment.parse().ok()?;
|
| 45 |
-
let resolved = if idx < 0 {
|
| 46 |
-
let abs = idx.unsigned_abs();
|
| 47 |
-
if abs > arr.len() {
|
| 48 |
-
return None;
|
| 49 |
-
}
|
| 50 |
-
arr.len() - abs
|
| 51 |
-
} else {
|
| 52 |
-
idx as usize
|
| 53 |
-
};
|
| 54 |
-
current = arr.get(resolved)?;
|
| 55 |
-
}
|
| 56 |
-
_ => return None,
|
| 57 |
-
}
|
| 58 |
-
}
|
| 59 |
-
Some(current)
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
fn json_value_to_extracted_string(val: &Value) -> Option<String> {
|
| 63 |
-
match val {
|
| 64 |
-
Value::Null => None,
|
| 65 |
-
Value::String(s) => Some(s.clone()),
|
| 66 |
-
Value::Bool(b) => Some(b.to_string()),
|
| 67 |
-
Value::Number(n) => Some(n.to_string()),
|
| 68 |
-
Value::Array(_) | Value::Object(_) => Some(val.to_string()),
|
| 69 |
-
}
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
pub fn collect_json_object_keys(raw_line: &str) -> Option<Vec<String>> {
|
| 73 |
-
let parsed: Value = serde_json::from_str(raw_line).ok()?;
|
| 74 |
-
let obj = parsed.as_object()?;
|
| 75 |
-
Some(obj.keys().cloned().collect())
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
pub fn process_single_record(raw: &str, cfg: &PostProcessConfig) -> Option<String> {
|
| 79 |
-
let mut text = if let Some(key) = &cfg.json_extract_key {
|
| 80 |
-
let parsed: Value = serde_json::from_str(raw).ok()?;
|
| 81 |
-
let val = extract_json_value_by_path(&parsed, key)?;
|
| 82 |
-
json_value_to_extracted_string(val)?
|
| 83 |
-
} else {
|
| 84 |
-
raw.to_owned()
|
| 85 |
-
};
|
| 86 |
-
|
| 87 |
-
if cfg.normalize_newlines && crate::simd_cpu::contains_byte_fast(text.as_bytes(), b'\r') {
|
| 88 |
-
text = text.replace("\r\n", "\n").replace('\r', "\n");
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
if cfg.trim_whitespace {
|
| 92 |
-
let trimmed = text.trim();
|
| 93 |
-
if trimmed.len() != text.len() {
|
| 94 |
-
text = trimmed.to_owned();
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
if cfg.min_byte_len > 0 && text.len() < cfg.min_byte_len {
|
| 99 |
-
return None;
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
if cfg.max_byte_len > 0 && text.len() > cfg.max_byte_len {
|
| 103 |
-
if cfg.truncate_over_max {
|
| 104 |
-
let mut cut = cfg.max_byte_len;
|
| 105 |
-
while cut > 0 && !text.is_char_boundary(cut) {
|
| 106 |
-
cut -= 1;
|
| 107 |
-
}
|
| 108 |
-
text.truncate(cut);
|
| 109 |
-
if cfg.min_byte_len > 0 && text.len() < cfg.min_byte_len {
|
| 110 |
-
return None;
|
| 111 |
-
}
|
| 112 |
-
} else {
|
| 113 |
-
return None;
|
| 114 |
-
}
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
Some(text)
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
pub fn process_batch_parallel(raw_records: &[String], cfg: &PostProcessConfig) -> Vec<String> {
|
| 121 |
-
raw_records
|
| 122 |
-
.par_iter()
|
| 123 |
-
.filter_map(|item| process_single_record(item, cfg))
|
| 124 |
-
.collect()
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
pub fn process_batch_parallel_indexed(
|
| 128 |
-
raw_records: &[String],
|
| 129 |
-
cfg: &PostProcessConfig,
|
| 130 |
-
) -> Vec<Option<String>> {
|
| 131 |
-
raw_records
|
| 132 |
-
.par_iter()
|
| 133 |
-
.map(|item| process_single_record(item, cfg))
|
| 134 |
-
.collect()
|
| 135 |
-
}
|
|
|
|
| 1 |
+
use rayon::prelude::*;
|
| 2 |
+
use serde_json::Value;
|
| 3 |
+
|
| 4 |
+
#[derive(Debug, Clone)]
|
| 5 |
+
pub struct PostProcessConfig {
|
| 6 |
+
pub trim_whitespace: bool,
|
| 7 |
+
pub normalize_newlines: bool,
|
| 8 |
+
pub min_byte_len: usize,
|
| 9 |
+
pub max_byte_len: usize,
|
| 10 |
+
pub truncate_over_max: bool,
|
| 11 |
+
pub json_extract_key: Option<String>,
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
impl Default for PostProcessConfig {
|
| 15 |
+
fn default() -> Self {
|
| 16 |
+
Self {
|
| 17 |
+
trim_whitespace: true,
|
| 18 |
+
normalize_newlines: true,
|
| 19 |
+
min_byte_len: 0,
|
| 20 |
+
max_byte_len: 0,
|
| 21 |
+
truncate_over_max: false,
|
| 22 |
+
json_extract_key: None,
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
pub fn extract_json_value_by_path<'a>(root: &'a Value, key_path: &str) -> Option<&'a Value> {
|
| 28 |
+
if let Some(direct) = root.get(key_path) {
|
| 29 |
+
return Some(direct);
|
| 30 |
+
}
|
| 31 |
+
if !key_path.contains('.') {
|
| 32 |
+
return None;
|
| 33 |
+
}
|
| 34 |
+
let mut current = root;
|
| 35 |
+
for segment in key_path.split('.') {
|
| 36 |
+
if segment.is_empty() {
|
| 37 |
+
continue;
|
| 38 |
+
}
|
| 39 |
+
match current {
|
| 40 |
+
Value::Object(map) => {
|
| 41 |
+
current = map.get(segment)?;
|
| 42 |
+
}
|
| 43 |
+
Value::Array(arr) => {
|
| 44 |
+
let idx: isize = segment.parse().ok()?;
|
| 45 |
+
let resolved = if idx < 0 {
|
| 46 |
+
let abs = idx.unsigned_abs();
|
| 47 |
+
if abs > arr.len() {
|
| 48 |
+
return None;
|
| 49 |
+
}
|
| 50 |
+
arr.len() - abs
|
| 51 |
+
} else {
|
| 52 |
+
idx as usize
|
| 53 |
+
};
|
| 54 |
+
current = arr.get(resolved)?;
|
| 55 |
+
}
|
| 56 |
+
_ => return None,
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
Some(current)
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
fn json_value_to_extracted_string(val: &Value) -> Option<String> {
|
| 63 |
+
match val {
|
| 64 |
+
Value::Null => None,
|
| 65 |
+
Value::String(s) => Some(s.clone()),
|
| 66 |
+
Value::Bool(b) => Some(b.to_string()),
|
| 67 |
+
Value::Number(n) => Some(n.to_string()),
|
| 68 |
+
Value::Array(_) | Value::Object(_) => Some(val.to_string()),
|
| 69 |
+
}
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
pub fn collect_json_object_keys(raw_line: &str) -> Option<Vec<String>> {
|
| 73 |
+
let parsed: Value = serde_json::from_str(raw_line).ok()?;
|
| 74 |
+
let obj = parsed.as_object()?;
|
| 75 |
+
Some(obj.keys().cloned().collect())
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
pub fn process_single_record(raw: &str, cfg: &PostProcessConfig) -> Option<String> {
|
| 79 |
+
let mut text = if let Some(key) = &cfg.json_extract_key {
|
| 80 |
+
let parsed: Value = serde_json::from_str(raw).ok()?;
|
| 81 |
+
let val = extract_json_value_by_path(&parsed, key)?;
|
| 82 |
+
json_value_to_extracted_string(val)?
|
| 83 |
+
} else {
|
| 84 |
+
raw.to_owned()
|
| 85 |
+
};
|
| 86 |
+
|
| 87 |
+
if cfg.normalize_newlines && crate::simd_cpu::contains_byte_fast(text.as_bytes(), b'\r') {
|
| 88 |
+
text = text.replace("\r\n", "\n").replace('\r', "\n");
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
if cfg.trim_whitespace {
|
| 92 |
+
let trimmed = text.trim();
|
| 93 |
+
if trimmed.len() != text.len() {
|
| 94 |
+
text = trimmed.to_owned();
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
if cfg.min_byte_len > 0 && text.len() < cfg.min_byte_len {
|
| 99 |
+
return None;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
if cfg.max_byte_len > 0 && text.len() > cfg.max_byte_len {
|
| 103 |
+
if cfg.truncate_over_max {
|
| 104 |
+
let mut cut = cfg.max_byte_len;
|
| 105 |
+
while cut > 0 && !text.is_char_boundary(cut) {
|
| 106 |
+
cut -= 1;
|
| 107 |
+
}
|
| 108 |
+
text.truncate(cut);
|
| 109 |
+
if cfg.min_byte_len > 0 && text.len() < cfg.min_byte_len {
|
| 110 |
+
return None;
|
| 111 |
+
}
|
| 112 |
+
} else {
|
| 113 |
+
return None;
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
Some(text)
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
pub fn process_batch_parallel(raw_records: &[String], cfg: &PostProcessConfig) -> Vec<String> {
|
| 121 |
+
raw_records
|
| 122 |
+
.par_iter()
|
| 123 |
+
.filter_map(|item| process_single_record(item, cfg))
|
| 124 |
+
.collect()
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
pub fn process_batch_parallel_indexed(
|
| 128 |
+
raw_records: &[String],
|
| 129 |
+
cfg: &PostProcessConfig,
|
| 130 |
+
) -> Vec<Option<String>> {
|
| 131 |
+
raw_records
|
| 132 |
+
.par_iter()
|
| 133 |
+
.map(|item| process_single_record(item, cfg))
|
| 134 |
+
.collect()
|
| 135 |
+
}
|
data/src/py_bridge.rs
CHANGED
|
@@ -1,839 +1,839 @@
|
|
| 1 |
-
use std::fs::File;
|
| 2 |
-
use std::io::{Read, Write};
|
| 3 |
-
|
| 4 |
-
use parking_lot::Mutex;
|
| 5 |
-
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
| 6 |
-
use pyo3::prelude::*;
|
| 7 |
-
use pyo3::types::{PyBool, PyByteArray, PyBytes, PyDict, PyFloat, PyInt, PySequence, PyString};
|
| 8 |
-
use rayon::prelude::*;
|
| 9 |
-
|
| 10 |
-
use crate::coord::StreamCoordinate;
|
| 11 |
-
use crate::dedup::TwoStageDeduplicator;
|
| 12 |
-
use crate::error::{DataLoaderError, find_closest_matches};
|
| 13 |
-
use crate::metadata::DualMetadataManager;
|
| 14 |
-
use crate::postprocess::PostProcessConfig;
|
| 15 |
-
use crate::quality::QualityConfig;
|
| 16 |
-
use crate::serialize::{DataValue, classify_string_or_json, serialize_columns_by_format};
|
| 17 |
-
|
| 18 |
-
fn to_py_err(err: DataLoaderError) -> PyErr {
|
| 19 |
-
match err {
|
| 20 |
-
DataLoaderError::InvalidConfig(msg) | DataLoaderError::ColumnNotFound(msg) => {
|
| 21 |
-
PyValueError::new_err(msg)
|
| 22 |
-
}
|
| 23 |
-
DataLoaderError::InvalidCoordinate => PyValueError::new_err(err.to_string()),
|
| 24 |
-
other => PyRuntimeError::new_err(other.to_string()),
|
| 25 |
-
}
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
#[pyclass(frozen, name = "StreamCoordinate")]
|
| 29 |
-
#[derive(Clone, Copy)]
|
| 30 |
-
pub struct PyStreamCoordinate {
|
| 31 |
-
#[pyo3(get)]
|
| 32 |
-
pub shard_id: u32,
|
| 33 |
-
#[pyo3(get)]
|
| 34 |
-
pub byte_offset: u64,
|
| 35 |
-
#[pyo3(get)]
|
| 36 |
-
pub row_group_idx: u32,
|
| 37 |
-
#[pyo3(get)]
|
| 38 |
-
pub page_idx: u32,
|
| 39 |
-
#[pyo3(get)]
|
| 40 |
-
pub row_offset_in_page: u32,
|
| 41 |
-
#[pyo3(get)]
|
| 42 |
-
pub global_row_idx: u64,
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
#[pymethods]
|
| 46 |
-
impl PyStreamCoordinate {
|
| 47 |
-
#[new]
|
| 48 |
-
#[pyo3(signature = (
|
| 49 |
-
shard_id = 0,
|
| 50 |
-
byte_offset = 0,
|
| 51 |
-
row_group_idx = 0,
|
| 52 |
-
page_idx = 0,
|
| 53 |
-
row_offset_in_page = 0,
|
| 54 |
-
global_row_idx = 0,
|
| 55 |
-
))]
|
| 56 |
-
pub fn new(
|
| 57 |
-
shard_id: u32,
|
| 58 |
-
byte_offset: u64,
|
| 59 |
-
row_group_idx: u32,
|
| 60 |
-
page_idx: u32,
|
| 61 |
-
row_offset_in_page: u32,
|
| 62 |
-
global_row_idx: u64,
|
| 63 |
-
) -> Self {
|
| 64 |
-
Self {
|
| 65 |
-
shard_id,
|
| 66 |
-
byte_offset,
|
| 67 |
-
row_group_idx,
|
| 68 |
-
page_idx,
|
| 69 |
-
row_offset_in_page,
|
| 70 |
-
global_row_idx,
|
| 71 |
-
}
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
pub fn to_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
|
| 75 |
-
let coord = StreamCoordinate {
|
| 76 |
-
shard_id: self.shard_id,
|
| 77 |
-
byte_offset: self.byte_offset,
|
| 78 |
-
row_group_idx: self.row_group_idx,
|
| 79 |
-
page_idx: self.page_idx,
|
| 80 |
-
row_offset_in_page: self.row_offset_in_page,
|
| 81 |
-
global_row_idx: self.global_row_idx,
|
| 82 |
-
};
|
| 83 |
-
PyBytes::new(py, &coord.to_bytes())
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
#[staticmethod]
|
| 87 |
-
pub fn from_bytes(raw: &[u8]) -> PyResult<Self> {
|
| 88 |
-
if raw.len() != StreamCoordinate::BYTE_LEN {
|
| 89 |
-
return Err(PyValueError::new_err(format!(
|
| 90 |
-
"Invalid coordinate length: expected {} bytes, got {}",
|
| 91 |
-
StreamCoordinate::BYTE_LEN,
|
| 92 |
-
raw.len()
|
| 93 |
-
)));
|
| 94 |
-
}
|
| 95 |
-
let arr: &[u8; StreamCoordinate::BYTE_LEN] = raw.try_into().unwrap();
|
| 96 |
-
let c = StreamCoordinate::from_bytes(arr);
|
| 97 |
-
Ok(Self {
|
| 98 |
-
shard_id: c.shard_id,
|
| 99 |
-
byte_offset: c.byte_offset,
|
| 100 |
-
row_group_idx: c.row_group_idx,
|
| 101 |
-
page_idx: c.page_idx,
|
| 102 |
-
row_offset_in_page: c.row_offset_in_page,
|
| 103 |
-
global_row_idx: c.global_row_idx,
|
| 104 |
-
})
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
pub fn __repr__(&self) -> String {
|
| 108 |
-
format!(
|
| 109 |
-
"StreamCoordinate(shard_id={}, byte_offset={}, row_group={}, page={}, row_offset={}, global_row={})",
|
| 110 |
-
self.shard_id,
|
| 111 |
-
self.byte_offset,
|
| 112 |
-
self.row_group_idx,
|
| 113 |
-
self.page_idx,
|
| 114 |
-
self.row_offset_in_page,
|
| 115 |
-
self.global_row_idx
|
| 116 |
-
)
|
| 117 |
-
}
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
#[pyclass(name = "StreamDeduplicator")]
|
| 121 |
-
pub struct PyStreamDeduplicator {
|
| 122 |
-
inner: Mutex<TwoStageDeduplicator>,
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
#[pymethods]
|
| 126 |
-
impl PyStreamDeduplicator {
|
| 127 |
-
#[new]
|
| 128 |
-
#[pyo3(signature = (initial_hashes = None))]
|
| 129 |
-
pub fn new(initial_hashes: Option<Vec<u64>>) -> Self {
|
| 130 |
-
let dedup = match initial_hashes {
|
| 131 |
-
Some(hashes) => TwoStageDeduplicator::with_committed(hashes),
|
| 132 |
-
None => TwoStageDeduplicator::new(),
|
| 133 |
-
};
|
| 134 |
-
Self {
|
| 135 |
-
inner: Mutex::new(dedup),
|
| 136 |
-
}
|
| 137 |
-
}
|
| 138 |
-
|
| 139 |
-
#[staticmethod]
|
| 140 |
-
pub fn compute_hash(payload: &[u8]) -> u64 {
|
| 141 |
-
TwoStageDeduplicator::compute_hash(payload)
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
pub fn is_seen(&self, payload: &[u8]) -> bool {
|
| 145 |
-
self.inner.lock().is_seen(payload)
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
pub fn try_add(&self, payload: &[u8]) -> bool {
|
| 149 |
-
let mut dedup = self.inner.lock();
|
| 150 |
-
dedup.try_stage(1, payload).is_some()
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
pub fn filter_batch(&self, records: Vec<String>) -> Vec<String> {
|
| 154 |
-
let mut dedup = self.inner.lock();
|
| 155 |
-
let mut out = Vec::with_capacity(records.len());
|
| 156 |
-
for rec in records {
|
| 157 |
-
if dedup.try_stage(1, rec.as_bytes()).is_some() {
|
| 158 |
-
out.push(rec);
|
| 159 |
-
}
|
| 160 |
-
}
|
| 161 |
-
out
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
pub fn commit(&self) -> usize {
|
| 165 |
-
let mut dedup = self.inner.lock();
|
| 166 |
-
dedup.commit_batch(1).len()
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
pub fn rollback(&self) -> usize {
|
| 170 |
-
let mut dedup = self.inner.lock();
|
| 171 |
-
dedup.rollback_batch(1)
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
pub fn clear(&self) {
|
| 175 |
-
let mut dedup = self.inner.lock();
|
| 176 |
-
dedup.clear_staged();
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
pub fn count(&self) -> usize {
|
| 180 |
-
self.inner.lock().committed_len()
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
pub fn save_wal(&self, path: &str) -> PyResult<usize> {
|
| 184 |
-
let dedup = self.inner.lock();
|
| 185 |
-
let hashes = dedup.export_committed_hashes();
|
| 186 |
-
let mut buf = Vec::with_capacity(hashes.len() * 8);
|
| 187 |
-
for h in &hashes {
|
| 188 |
-
buf.extend_from_slice(&h.to_le_bytes());
|
| 189 |
-
}
|
| 190 |
-
let mut file = File::create(path).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 191 |
-
file.write_all(&buf)
|
| 192 |
-
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 193 |
-
file.sync_data()
|
| 194 |
-
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 195 |
-
Ok(hashes.len())
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
pub fn load_wal(&self, path: &str) -> PyResult<usize> {
|
| 199 |
-
let mut file = match File::open(path) {
|
| 200 |
-
Ok(f) => f,
|
| 201 |
-
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(0),
|
| 202 |
-
Err(e) => return Err(PyRuntimeError::new_err(e.to_string())),
|
| 203 |
-
};
|
| 204 |
-
let mut buf = Vec::new();
|
| 205 |
-
file.read_to_end(&mut buf)
|
| 206 |
-
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 207 |
-
if buf.len() % 8 != 0 {
|
| 208 |
-
return Err(PyValueError::new_err(format!(
|
| 209 |
-
"Corrupted WAL file '{}': byte length {} is not a multiple of 8",
|
| 210 |
-
path,
|
| 211 |
-
buf.len()
|
| 212 |
-
)));
|
| 213 |
-
}
|
| 214 |
-
let count = buf.len() / 8;
|
| 215 |
-
let mut dedup = self.inner.lock();
|
| 216 |
-
let (chunks, _) = buf.as_chunks::<8>();
|
| 217 |
-
for chunk in chunks {
|
| 218 |
-
let h = u64::from_le_bytes(*chunk);
|
| 219 |
-
dedup.import_committed_hashes(std::iter::once(h));
|
| 220 |
-
}
|
| 221 |
-
Ok(count)
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
-
pub fn export_hashes(&self) -> Vec<u64> {
|
| 225 |
-
self.inner.lock().export_committed_hashes()
|
| 226 |
-
}
|
| 227 |
-
}
|
| 228 |
-
|
| 229 |
-
#[pyclass(name = "StateTracker")]
|
| 230 |
-
pub struct PyStateTracker {
|
| 231 |
-
inner: Mutex<DualMetadataManager>,
|
| 232 |
-
}
|
| 233 |
-
|
| 234 |
-
#[pymethods]
|
| 235 |
-
impl PyStateTracker {
|
| 236 |
-
#[new]
|
| 237 |
-
pub fn new(meta_path: &str) -> PyResult<Self> {
|
| 238 |
-
let mgr = DualMetadataManager::open(meta_path).map_err(to_py_err)?;
|
| 239 |
-
Ok(Self {
|
| 240 |
-
inner: Mutex::new(mgr),
|
| 241 |
-
})
|
| 242 |
-
}
|
| 243 |
-
|
| 244 |
-
pub fn read(&self) -> PyStreamCoordinate {
|
| 245 |
-
let mgr = self.inner.lock();
|
| 246 |
-
let c = mgr.committed_coordinate();
|
| 247 |
-
PyStreamCoordinate {
|
| 248 |
-
shard_id: c.shard_id,
|
| 249 |
-
byte_offset: c.byte_offset,
|
| 250 |
-
row_group_idx: c.row_group_idx,
|
| 251 |
-
page_idx: c.page_idx,
|
| 252 |
-
row_offset_in_page: c.row_offset_in_page,
|
| 253 |
-
global_row_idx: c.global_row_idx,
|
| 254 |
-
}
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
#[pyo3(signature = (
|
| 258 |
-
shard_id = 0,
|
| 259 |
-
byte_offset = 0,
|
| 260 |
-
row_group_idx = 0,
|
| 261 |
-
page_idx = 0,
|
| 262 |
-
row_offset_in_page = 0,
|
| 263 |
-
global_row_idx = 0,
|
| 264 |
-
batch_id = 1
|
| 265 |
-
))]
|
| 266 |
-
#[allow(clippy::too_many_arguments)]
|
| 267 |
-
pub fn update(
|
| 268 |
-
&self,
|
| 269 |
-
shard_id: u32,
|
| 270 |
-
byte_offset: u64,
|
| 271 |
-
row_group_idx: u32,
|
| 272 |
-
page_idx: u32,
|
| 273 |
-
row_offset_in_page: u32,
|
| 274 |
-
global_row_idx: u64,
|
| 275 |
-
batch_id: u64,
|
| 276 |
-
) {
|
| 277 |
-
let coord = StreamCoordinate {
|
| 278 |
-
shard_id,
|
| 279 |
-
byte_offset,
|
| 280 |
-
row_group_idx,
|
| 281 |
-
page_idx,
|
| 282 |
-
row_offset_in_page,
|
| 283 |
-
global_row_idx,
|
| 284 |
-
};
|
| 285 |
-
let mut mgr = self.inner.lock();
|
| 286 |
-
mgr.stage_batch_in_ram(batch_id, coord);
|
| 287 |
-
}
|
| 288 |
-
|
| 289 |
-
#[pyo3(signature = (batch_id = 1))]
|
| 290 |
-
pub fn commit(&self, batch_id: u64) -> PyResult<PyStreamCoordinate> {
|
| 291 |
-
let mut mgr = self.inner.lock();
|
| 292 |
-
let c = mgr.commit_gpu_batch(batch_id).map_err(to_py_err)?;
|
| 293 |
-
Ok(PyStreamCoordinate {
|
| 294 |
-
shard_id: c.shard_id,
|
| 295 |
-
byte_offset: c.byte_offset,
|
| 296 |
-
row_group_idx: c.row_group_idx,
|
| 297 |
-
page_idx: c.page_idx,
|
| 298 |
-
row_offset_in_page: c.row_offset_in_page,
|
| 299 |
-
global_row_idx: c.global_row_idx,
|
| 300 |
-
})
|
| 301 |
-
}
|
| 302 |
-
|
| 303 |
-
pub fn committed_batch_id(&self) -> u64 {
|
| 304 |
-
self.inner.lock().committed_batch_id()
|
| 305 |
-
}
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
#[pyclass(name = "QualityFilter")]
|
| 309 |
-
pub struct PyQualityFilter {
|
| 310 |
-
cfg: QualityConfig,
|
| 311 |
-
}
|
| 312 |
-
|
| 313 |
-
#[pymethods]
|
| 314 |
-
impl PyQualityFilter {
|
| 315 |
-
#[new]
|
| 316 |
-
#[pyo3(signature = (
|
| 317 |
-
min_chars = 0,
|
| 318 |
-
max_chars = 0,
|
| 319 |
-
min_words = 0,
|
| 320 |
-
min_entropy = 0.0,
|
| 321 |
-
max_repetition_ratio = 1.0,
|
| 322 |
-
max_symbol_ratio = 1.0
|
| 323 |
-
))]
|
| 324 |
-
pub fn new(
|
| 325 |
-
min_chars: usize,
|
| 326 |
-
max_chars: usize,
|
| 327 |
-
min_words: usize,
|
| 328 |
-
min_entropy: f64,
|
| 329 |
-
max_repetition_ratio: f64,
|
| 330 |
-
max_symbol_ratio: f64,
|
| 331 |
-
) -> Self {
|
| 332 |
-
Self {
|
| 333 |
-
cfg: QualityConfig {
|
| 334 |
-
min_chars,
|
| 335 |
-
max_chars,
|
| 336 |
-
min_words,
|
| 337 |
-
min_entropy,
|
| 338 |
-
max_repetition_ratio,
|
| 339 |
-
max_symbol_ratio,
|
| 340 |
-
},
|
| 341 |
-
}
|
| 342 |
-
}
|
| 343 |
-
|
| 344 |
-
pub fn is_high_quality(&self, text: &str) -> bool {
|
| 345 |
-
crate::quality::evaluate_quality(text, &self.cfg).is_passed
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
pub fn metrics(&self, text: &str) -> (usize, usize, f64, f64, f64, bool) {
|
| 349 |
-
let m = crate::quality::evaluate_quality(text, &self.cfg);
|
| 350 |
-
(
|
| 351 |
-
m.char_count,
|
| 352 |
-
m.word_count,
|
| 353 |
-
m.entropy,
|
| 354 |
-
m.repetition_ratio,
|
| 355 |
-
m.symbol_ratio,
|
| 356 |
-
m.is_passed,
|
| 357 |
-
)
|
| 358 |
-
}
|
| 359 |
-
|
| 360 |
-
pub fn filter_flags(&self, py: Python<'_>, texts: Vec<String>) -> Vec<bool> {
|
| 361 |
-
let cfg = self.cfg.clone();
|
| 362 |
-
py.detach(|| crate::quality::filter_quality_batch_parallel(&texts, &cfg))
|
| 363 |
-
}
|
| 364 |
-
|
| 365 |
-
pub fn filter_batch(&self, py: Python<'_>, texts: Vec<String>) -> Vec<String> {
|
| 366 |
-
let cfg = self.cfg.clone();
|
| 367 |
-
py.detach(|| {
|
| 368 |
-
texts
|
| 369 |
-
.into_iter()
|
| 370 |
-
.filter(|t| crate::quality::evaluate_quality(t, &cfg).is_passed)
|
| 371 |
-
.collect()
|
| 372 |
-
})
|
| 373 |
-
}
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
#[pyclass(name = "MinHashDeduplicator")]
|
| 377 |
-
pub struct PyMinHashDeduplicator {
|
| 378 |
-
inner: Mutex<crate::minhash::MinHashDeduplicator>,
|
| 379 |
-
}
|
| 380 |
-
|
| 381 |
-
#[pymethods]
|
| 382 |
-
impl PyMinHashDeduplicator {
|
| 383 |
-
#[new]
|
| 384 |
-
#[pyo3(signature = (threshold = 0.85, k_shingle = 5, num_perm = 64))]
|
| 385 |
-
pub fn new(threshold: f64, k_shingle: usize, num_perm: usize) -> Self {
|
| 386 |
-
Self {
|
| 387 |
-
inner: Mutex::new(crate::minhash::MinHashDeduplicator::new(
|
| 388 |
-
threshold, k_shingle, num_perm,
|
| 389 |
-
)),
|
| 390 |
-
}
|
| 391 |
-
}
|
| 392 |
-
|
| 393 |
-
pub fn is_unique(&self, text: &str) -> bool {
|
| 394 |
-
self.inner.lock().is_unique(text)
|
| 395 |
-
}
|
| 396 |
-
|
| 397 |
-
pub fn similarity(&self, text_a: &str, text_b: &str) -> f64 {
|
| 398 |
-
let dedup = self.inner.lock();
|
| 399 |
-
let sig_a = dedup.compute_signature(text_a);
|
| 400 |
-
let sig_b = dedup.compute_signature(text_b);
|
| 401 |
-
sig_a.jaccard_similarity(&sig_b)
|
| 402 |
-
}
|
| 403 |
-
|
| 404 |
-
pub fn count(&self) -> usize {
|
| 405 |
-
self.inner.lock().count()
|
| 406 |
-
}
|
| 407 |
-
|
| 408 |
-
pub fn clear(&self) {
|
| 409 |
-
self.inner.lock().clear();
|
| 410 |
-
}
|
| 411 |
-
}
|
| 412 |
-
|
| 413 |
-
#[pyclass(name = "StringTensor")]
|
| 414 |
-
#[derive(Clone)]
|
| 415 |
-
pub struct PyStringTensor {
|
| 416 |
-
inner: crate::string_tensor::StringTensor,
|
| 417 |
-
}
|
| 418 |
-
|
| 419 |
-
#[pymethods]
|
| 420 |
-
impl PyStringTensor {
|
| 421 |
-
#[staticmethod]
|
| 422 |
-
pub fn from_list(strings: Vec<String>) -> Self {
|
| 423 |
-
Self {
|
| 424 |
-
inner: crate::string_tensor::StringTensor::from_strings(strings),
|
| 425 |
-
}
|
| 426 |
-
}
|
| 427 |
-
|
| 428 |
-
#[staticmethod]
|
| 429 |
-
pub fn from_gpu_buffers(offsets: Vec<u32>, payload: &[u8]) -> Self {
|
| 430 |
-
Self {
|
| 431 |
-
inner: crate::string_tensor::StringTensor::from_gpu_buffers(&offsets, payload),
|
| 432 |
-
}
|
| 433 |
-
}
|
| 434 |
-
|
| 435 |
-
pub fn to_gpu_buffers<'py>(
|
| 436 |
-
&self,
|
| 437 |
-
py: Python<'py>,
|
| 438 |
-
) -> (Bound<'py, PyBytes>, Bound<'py, PyBytes>) {
|
| 439 |
-
let (offsets, payload) = self.inner.to_gpu_buffers();
|
| 440 |
-
let mut offset_bytes = Vec::with_capacity(offsets.len() * 4);
|
| 441 |
-
for &off in &offsets {
|
| 442 |
-
offset_bytes.extend_from_slice(&off.to_le_bytes());
|
| 443 |
-
}
|
| 444 |
-
(PyBytes::new(py, &offset_bytes), PyBytes::new(py, &payload))
|
| 445 |
-
}
|
| 446 |
-
|
| 447 |
-
pub fn to_offsets(&self) -> Vec<u32> {
|
| 448 |
-
self.inner.to_gpu_buffers().0
|
| 449 |
-
}
|
| 450 |
-
|
| 451 |
-
pub fn to_payload<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
|
| 452 |
-
let (_, payload) = self.inner.to_gpu_buffers();
|
| 453 |
-
PyBytes::new(py, &payload)
|
| 454 |
-
}
|
| 455 |
-
|
| 456 |
-
pub fn __len__(&self) -> usize {
|
| 457 |
-
self.inner.len()
|
| 458 |
-
}
|
| 459 |
-
|
| 460 |
-
pub fn __getitem__(&self, index: usize) -> PyResult<String> {
|
| 461 |
-
self.inner
|
| 462 |
-
.get(index)
|
| 463 |
-
.map(|s| s.to_owned())
|
| 464 |
-
.ok_or_else(|| PyValueError::new_err("StringTensor index out of range"))
|
| 465 |
-
}
|
| 466 |
-
|
| 467 |
-
pub fn get(&self, index: usize) -> Option<String> {
|
| 468 |
-
self.inner.get(index).map(|s| s.to_owned())
|
| 469 |
-
}
|
| 470 |
-
|
| 471 |
-
pub fn to_list(&self) -> Vec<String> {
|
| 472 |
-
self.inner.to_vec()
|
| 473 |
-
}
|
| 474 |
-
|
| 475 |
-
pub fn lengths(&self) -> Vec<u32> {
|
| 476 |
-
self.inner.lengths()
|
| 477 |
-
}
|
| 478 |
-
|
| 479 |
-
pub fn is_inlined_mask(&self) -> Vec<bool> {
|
| 480 |
-
self.inner.is_inlined_mask()
|
| 481 |
-
}
|
| 482 |
-
|
| 483 |
-
pub fn total_bytes(&self) -> usize {
|
| 484 |
-
self.inner.views.iter().map(|v| v.len as usize).sum()
|
| 485 |
-
}
|
| 486 |
-
|
| 487 |
-
pub fn filter_by_length(&self, min_len: u32, max_len: u32) -> Self {
|
| 488 |
-
Self {
|
| 489 |
-
inner: self.inner.filter_by_length(min_len, max_len),
|
| 490 |
-
}
|
| 491 |
-
}
|
| 492 |
-
|
| 493 |
-
pub fn filter_by_prefix(&self, prefix: &str) -> Self {
|
| 494 |
-
Self {
|
| 495 |
-
inner: self.inner.filter_by_prefix(prefix),
|
| 496 |
-
}
|
| 497 |
-
}
|
| 498 |
-
|
| 499 |
-
pub fn hash_all(&self, py: Python<'_>) -> Vec<u64> {
|
| 500 |
-
py.detach(|| self.inner.hash_all_parallel())
|
| 501 |
-
}
|
| 502 |
-
|
| 503 |
-
pub fn __repr__(&self) -> String {
|
| 504 |
-
format!(
|
| 505 |
-
"StringTensor(count={}, inlined={})",
|
| 506 |
-
self.inner.len(),
|
| 507 |
-
self.inner.views.iter().filter(|v| v.is_inlined()).count()
|
| 508 |
-
)
|
| 509 |
-
}
|
| 510 |
-
}
|
| 511 |
-
|
| 512 |
-
pub fn extract_py_columns_table(
|
| 513 |
-
py: Python<'_>,
|
| 514 |
-
columns: &Bound<'_, PyAny>,
|
| 515 |
-
) -> PyResult<Vec<(String, Vec<DataValue>)>> {
|
| 516 |
-
let mut rust_cols = Vec::new();
|
| 517 |
-
|
| 518 |
-
if let Ok(dict) = columns.cast::<PyDict>() {
|
| 519 |
-
for (k, v) in dict.iter() {
|
| 520 |
-
let col_name = k.extract::<String>()?;
|
| 521 |
-
let vals = extract_py_seq_values(py, &v)?;
|
| 522 |
-
rust_cols.push((col_name, vals));
|
| 523 |
-
}
|
| 524 |
-
} else if let Ok(seq) = columns.cast::<PySequence>() {
|
| 525 |
-
let len = seq.len()?;
|
| 526 |
-
for i in 0..len {
|
| 527 |
-
let item = seq.get_item(i)?;
|
| 528 |
-
if let Ok(pair) = item.cast::<PySequence>()
|
| 529 |
-
&& pair.len()? == 2
|
| 530 |
-
{
|
| 531 |
-
let col_name = pair.get_item(0)?.extract::<String>()?;
|
| 532 |
-
let col_vals_seq = pair.get_item(1)?;
|
| 533 |
-
let vals = extract_py_seq_values(py, &col_vals_seq)?;
|
| 534 |
-
rust_cols.push((col_name, vals));
|
| 535 |
-
continue;
|
| 536 |
-
}
|
| 537 |
-
return Err(PyValueError::new_err(
|
| 538 |
-
"columns sequence must contain (name, values) pairs or be a dict",
|
| 539 |
-
));
|
| 540 |
-
}
|
| 541 |
-
} else {
|
| 542 |
-
return Err(PyValueError::new_err(
|
| 543 |
-
"columns must be a dict {name: [values]} or sequence of (name, [values]) pairs",
|
| 544 |
-
));
|
| 545 |
-
}
|
| 546 |
-
|
| 547 |
-
Ok(rust_cols)
|
| 548 |
-
}
|
| 549 |
-
|
| 550 |
-
fn extract_py_seq_values(_py: Python<'_>, seq_obj: &Bound<'_, PyAny>) -> PyResult<Vec<DataValue>> {
|
| 551 |
-
let seq = seq_obj
|
| 552 |
-
.cast::<PySequence>()
|
| 553 |
-
.map_err(|_| PyValueError::new_err("column values must be a sequence (list/tuple)"))?;
|
| 554 |
-
let len = seq.len()?;
|
| 555 |
-
let mut out = Vec::with_capacity(len);
|
| 556 |
-
|
| 557 |
-
for i in 0..len {
|
| 558 |
-
let item = seq.get_item(i)?;
|
| 559 |
-
if item.is_none() {
|
| 560 |
-
out.push(DataValue::Null);
|
| 561 |
-
} else if let Ok(b) = item.cast::<PyBool>() {
|
| 562 |
-
out.push(DataValue::Bool(b.is_true()));
|
| 563 |
-
} else if let Ok(i) = item.cast::<PyInt>() {
|
| 564 |
-
out.push(DataValue::Int(i.extract::<i64>()?));
|
| 565 |
-
} else if let Ok(f) = item.cast::<PyFloat>() {
|
| 566 |
-
out.push(DataValue::Float(f.extract::<f64>()?));
|
| 567 |
-
} else if let Ok(s) = item.cast::<PyString>() {
|
| 568 |
-
let str_val = s.to_string_lossy();
|
| 569 |
-
out.push(classify_string_or_json(&str_val));
|
| 570 |
-
} else if let Ok(bytes) = item.cast::<PyBytes>() {
|
| 571 |
-
out.push(DataValue::Bytes(bytes.as_bytes().to_vec()));
|
| 572 |
-
} else if let Ok(bytes) = item.cast::<PyByteArray>() {
|
| 573 |
-
out.push(DataValue::Bytes(bytes.to_vec()));
|
| 574 |
-
} else {
|
| 575 |
-
let py_str = item.str()?;
|
| 576 |
-
let str_repr = py_str.to_string_lossy();
|
| 577 |
-
out.push(classify_string_or_json(&str_repr));
|
| 578 |
-
}
|
| 579 |
-
}
|
| 580 |
-
|
| 581 |
-
Ok(out)
|
| 582 |
-
}
|
| 583 |
-
|
| 584 |
-
#[pyfunction]
|
| 585 |
-
pub fn dataloader_cpu_isa() -> &'static str {
|
| 586 |
-
crate::simd_cpu::detect_cpu_isa().as_str()
|
| 587 |
-
}
|
| 588 |
-
|
| 589 |
-
#[pyfunction]
|
| 590 |
-
#[pyo3(signature = (target, candidates, max_suggestions=3))]
|
| 591 |
-
pub fn suggest_closest_matches(
|
| 592 |
-
target: &str,
|
| 593 |
-
candidates: Vec<String>,
|
| 594 |
-
max_suggestions: usize,
|
| 595 |
-
) -> Vec<String> {
|
| 596 |
-
find_closest_matches(target, &candidates, max_suggestions)
|
| 597 |
-
}
|
| 598 |
-
|
| 599 |
-
#[pyfunction]
|
| 600 |
-
#[pyo3(signature = (columns, format="parquet", hf_image_struct=true))]
|
| 601 |
-
pub fn serialize_table_to_bytes(
|
| 602 |
-
py: Python<'_>,
|
| 603 |
-
columns: &Bound<'_, PyAny>,
|
| 604 |
-
format: &str,
|
| 605 |
-
hf_image_struct: bool,
|
| 606 |
-
) -> PyResult<Vec<u8>> {
|
| 607 |
-
let rust_cols = extract_py_columns_table(py, columns)?;
|
| 608 |
-
py.detach(|| serialize_columns_by_format(&rust_cols, format, hf_image_struct))
|
| 609 |
-
.map_err(to_py_err)
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
#[pyfunction]
|
| 613 |
-
#[pyo3(signature = (
|
| 614 |
-
raw,
|
| 615 |
-
trim_whitespace=true,
|
| 616 |
-
normalize_newlines=true,
|
| 617 |
-
min_byte_len=0,
|
| 618 |
-
max_byte_len=0,
|
| 619 |
-
truncate_over_max=false,
|
| 620 |
-
json_extract_key=None
|
| 621 |
-
))]
|
| 622 |
-
pub fn clean_single_record(
|
| 623 |
-
raw: &str,
|
| 624 |
-
trim_whitespace: bool,
|
| 625 |
-
normalize_newlines: bool,
|
| 626 |
-
min_byte_len: usize,
|
| 627 |
-
max_byte_len: usize,
|
| 628 |
-
truncate_over_max: bool,
|
| 629 |
-
json_extract_key: Option<String>,
|
| 630 |
-
) -> Option<String> {
|
| 631 |
-
let cfg = PostProcessConfig {
|
| 632 |
-
trim_whitespace,
|
| 633 |
-
normalize_newlines,
|
| 634 |
-
min_byte_len,
|
| 635 |
-
max_byte_len,
|
| 636 |
-
truncate_over_max,
|
| 637 |
-
json_extract_key,
|
| 638 |
-
};
|
| 639 |
-
crate::postprocess::process_single_record(raw, &cfg)
|
| 640 |
-
}
|
| 641 |
-
|
| 642 |
-
#[pyfunction]
|
| 643 |
-
#[pyo3(signature = (
|
| 644 |
-
records,
|
| 645 |
-
trim_whitespace=true,
|
| 646 |
-
normalize_newlines=true,
|
| 647 |
-
min_byte_len=0,
|
| 648 |
-
max_byte_len=0,
|
| 649 |
-
truncate_over_max=false,
|
| 650 |
-
json_extract_key=None
|
| 651 |
-
))]
|
| 652 |
-
#[allow(clippy::too_many_arguments)]
|
| 653 |
-
pub fn clean_batch_records(
|
| 654 |
-
py: Python<'_>,
|
| 655 |
-
records: Vec<String>,
|
| 656 |
-
trim_whitespace: bool,
|
| 657 |
-
normalize_newlines: bool,
|
| 658 |
-
min_byte_len: usize,
|
| 659 |
-
max_byte_len: usize,
|
| 660 |
-
truncate_over_max: bool,
|
| 661 |
-
json_extract_key: Option<String>,
|
| 662 |
-
) -> Vec<String> {
|
| 663 |
-
let cfg = PostProcessConfig {
|
| 664 |
-
trim_whitespace,
|
| 665 |
-
normalize_newlines,
|
| 666 |
-
min_byte_len,
|
| 667 |
-
max_byte_len,
|
| 668 |
-
truncate_over_max,
|
| 669 |
-
json_extract_key,
|
| 670 |
-
};
|
| 671 |
-
py.detach(|| crate::postprocess::process_batch_parallel(&records, &cfg))
|
| 672 |
-
}
|
| 673 |
-
|
| 674 |
-
#[pyfunction]
|
| 675 |
-
#[pyo3(signature = (
|
| 676 |
-
records,
|
| 677 |
-
trim_whitespace=true,
|
| 678 |
-
normalize_newlines=true,
|
| 679 |
-
min_byte_len=1,
|
| 680 |
-
max_byte_len=0,
|
| 681 |
-
truncate_over_max=false,
|
| 682 |
-
json_extract_key=None,
|
| 683 |
-
seen_hashes=None,
|
| 684 |
-
deduplicate=true
|
| 685 |
-
))]
|
| 686 |
-
#[allow(clippy::too_many_arguments)]
|
| 687 |
-
pub fn postprocess_and_dedup_records(
|
| 688 |
-
py: Python<'_>,
|
| 689 |
-
records: Vec<String>,
|
| 690 |
-
trim_whitespace: bool,
|
| 691 |
-
normalize_newlines: bool,
|
| 692 |
-
min_byte_len: usize,
|
| 693 |
-
max_byte_len: usize,
|
| 694 |
-
truncate_over_max: bool,
|
| 695 |
-
json_extract_key: Option<String>,
|
| 696 |
-
seen_hashes: Option<Vec<u64>>,
|
| 697 |
-
deduplicate: bool,
|
| 698 |
-
) -> PyResult<(Vec<String>, Vec<u64>)> {
|
| 699 |
-
let cfg = PostProcessConfig {
|
| 700 |
-
trim_whitespace,
|
| 701 |
-
normalize_newlines,
|
| 702 |
-
min_byte_len,
|
| 703 |
-
max_byte_len,
|
| 704 |
-
truncate_over_max,
|
| 705 |
-
json_extract_key,
|
| 706 |
-
};
|
| 707 |
-
Ok(py.detach(|| {
|
| 708 |
-
let processed = crate::postprocess::process_batch_parallel_indexed(&records, &cfg);
|
| 709 |
-
if !deduplicate {
|
| 710 |
-
let cleaned_all: Vec<String> = processed.into_iter().flatten().collect();
|
| 711 |
-
return (cleaned_all, Vec::new());
|
| 712 |
-
}
|
| 713 |
-
let mut dedup = TwoStageDeduplicator::with_committed(seen_hashes.unwrap_or_default());
|
| 714 |
-
let mut cleaned_unique = Vec::with_capacity(processed.len());
|
| 715 |
-
for cleaned in processed.into_iter().flatten() {
|
| 716 |
-
if dedup.try_stage(1, cleaned.as_bytes()).is_some() {
|
| 717 |
-
cleaned_unique.push(cleaned);
|
| 718 |
-
}
|
| 719 |
-
}
|
| 720 |
-
let newly_committed = dedup.commit_batch(1);
|
| 721 |
-
(cleaned_unique, newly_committed)
|
| 722 |
-
}))
|
| 723 |
-
}
|
| 724 |
-
|
| 725 |
-
#[pyfunction]
|
| 726 |
-
#[pyo3(signature = (
|
| 727 |
-
records,
|
| 728 |
-
trim_whitespace = true,
|
| 729 |
-
normalize_newlines = true,
|
| 730 |
-
min_byte_len = 0,
|
| 731 |
-
max_byte_len = 0,
|
| 732 |
-
truncate_over_max = false,
|
| 733 |
-
json_extract_key = None,
|
| 734 |
-
filter_quality = false,
|
| 735 |
-
min_chars = 0,
|
| 736 |
-
max_chars = 0,
|
| 737 |
-
min_words = 0,
|
| 738 |
-
min_entropy = 0.0,
|
| 739 |
-
max_repetition_ratio = 1.0,
|
| 740 |
-
max_symbol_ratio = 1.0,
|
| 741 |
-
deduplicator = None,
|
| 742 |
-
commit_dedup = false,
|
| 743 |
-
))]
|
| 744 |
-
#[allow(clippy::too_many_arguments)]
|
| 745 |
-
pub fn fused_pipeline_process_batch(
|
| 746 |
-
py: Python<'_>,
|
| 747 |
-
records: Vec<String>,
|
| 748 |
-
trim_whitespace: bool,
|
| 749 |
-
normalize_newlines: bool,
|
| 750 |
-
min_byte_len: usize,
|
| 751 |
-
max_byte_len: usize,
|
| 752 |
-
truncate_over_max: bool,
|
| 753 |
-
json_extract_key: Option<String>,
|
| 754 |
-
filter_quality: bool,
|
| 755 |
-
min_chars: usize,
|
| 756 |
-
max_chars: usize,
|
| 757 |
-
min_words: usize,
|
| 758 |
-
min_entropy: f64,
|
| 759 |
-
max_repetition_ratio: f64,
|
| 760 |
-
max_symbol_ratio: f64,
|
| 761 |
-
deduplicator: Option<&PyStreamDeduplicator>,
|
| 762 |
-
commit_dedup: bool,
|
| 763 |
-
) -> PyResult<PyStringTensor> {
|
| 764 |
-
let post_cfg = PostProcessConfig {
|
| 765 |
-
trim_whitespace,
|
| 766 |
-
normalize_newlines,
|
| 767 |
-
min_byte_len,
|
| 768 |
-
max_byte_len,
|
| 769 |
-
truncate_over_max,
|
| 770 |
-
json_extract_key,
|
| 771 |
-
};
|
| 772 |
-
let qual_cfg = crate::quality::QualityConfig {
|
| 773 |
-
min_chars,
|
| 774 |
-
max_chars,
|
| 775 |
-
min_words,
|
| 776 |
-
min_entropy,
|
| 777 |
-
max_repetition_ratio,
|
| 778 |
-
max_symbol_ratio,
|
| 779 |
-
};
|
| 780 |
-
|
| 781 |
-
let result = py.detach(|| {
|
| 782 |
-
let cleaned_candidates: Vec<String> = records
|
| 783 |
-
.par_iter()
|
| 784 |
-
.filter_map(|raw| {
|
| 785 |
-
let cleaned = crate::postprocess::process_single_record(raw, &post_cfg)?;
|
| 786 |
-
if filter_quality {
|
| 787 |
-
let eval = crate::quality::evaluate_quality(&cleaned, &qual_cfg);
|
| 788 |
-
if !eval.is_passed {
|
| 789 |
-
return None;
|
| 790 |
-
}
|
| 791 |
-
}
|
| 792 |
-
Some(cleaned)
|
| 793 |
-
})
|
| 794 |
-
.collect();
|
| 795 |
-
|
| 796 |
-
let final_strings = if let Some(dedup_wrapper) = deduplicator {
|
| 797 |
-
let mut dedup = dedup_wrapper.inner.lock();
|
| 798 |
-
let mut unique = Vec::with_capacity(cleaned_candidates.len());
|
| 799 |
-
for s in cleaned_candidates {
|
| 800 |
-
if dedup.try_stage(1, s.as_bytes()).is_some() {
|
| 801 |
-
unique.push(s);
|
| 802 |
-
}
|
| 803 |
-
}
|
| 804 |
-
if commit_dedup {
|
| 805 |
-
dedup.commit_batch(1);
|
| 806 |
-
}
|
| 807 |
-
unique
|
| 808 |
-
} else {
|
| 809 |
-
cleaned_candidates
|
| 810 |
-
};
|
| 811 |
-
|
| 812 |
-
crate::string_tensor::StringTensor::from_strings(final_strings)
|
| 813 |
-
});
|
| 814 |
-
|
| 815 |
-
Ok(PyStringTensor { inner: result })
|
| 816 |
-
}
|
| 817 |
-
|
| 818 |
-
pub fn register_dataloader_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
|
| 819 |
-
parent_module.add_class::<PyStreamCoordinate>()?;
|
| 820 |
-
parent_module.add_class::<PyStreamDeduplicator>()?;
|
| 821 |
-
parent_module.add_class::<PyStateTracker>()?;
|
| 822 |
-
parent_module.add_class::<PyQualityFilter>()?;
|
| 823 |
-
parent_module.add_class::<PyMinHashDeduplicator>()?;
|
| 824 |
-
parent_module.add_class::<PyStringTensor>()?;
|
| 825 |
-
parent_module.add_function(wrap_pyfunction!(dataloader_cpu_isa, parent_module)?)?;
|
| 826 |
-
parent_module.add_function(wrap_pyfunction!(suggest_closest_matches, parent_module)?)?;
|
| 827 |
-
parent_module.add_function(wrap_pyfunction!(serialize_table_to_bytes, parent_module)?)?;
|
| 828 |
-
parent_module.add_function(wrap_pyfunction!(clean_single_record, parent_module)?)?;
|
| 829 |
-
parent_module.add_function(wrap_pyfunction!(clean_batch_records, parent_module)?)?;
|
| 830 |
-
parent_module.add_function(wrap_pyfunction!(
|
| 831 |
-
postprocess_and_dedup_records,
|
| 832 |
-
parent_module
|
| 833 |
-
)?)?;
|
| 834 |
-
parent_module.add_function(wrap_pyfunction!(
|
| 835 |
-
fused_pipeline_process_batch,
|
| 836 |
-
parent_module
|
| 837 |
-
)?)?;
|
| 838 |
-
Ok(())
|
| 839 |
-
}
|
|
|
|
| 1 |
+
use std::fs::File;
|
| 2 |
+
use std::io::{Read, Write};
|
| 3 |
+
|
| 4 |
+
use parking_lot::Mutex;
|
| 5 |
+
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
| 6 |
+
use pyo3::prelude::*;
|
| 7 |
+
use pyo3::types::{PyBool, PyByteArray, PyBytes, PyDict, PyFloat, PyInt, PySequence, PyString};
|
| 8 |
+
use rayon::prelude::*;
|
| 9 |
+
|
| 10 |
+
use crate::coord::StreamCoordinate;
|
| 11 |
+
use crate::dedup::TwoStageDeduplicator;
|
| 12 |
+
use crate::error::{DataLoaderError, find_closest_matches};
|
| 13 |
+
use crate::metadata::DualMetadataManager;
|
| 14 |
+
use crate::postprocess::PostProcessConfig;
|
| 15 |
+
use crate::quality::QualityConfig;
|
| 16 |
+
use crate::serialize::{DataValue, classify_string_or_json, serialize_columns_by_format};
|
| 17 |
+
|
| 18 |
+
fn to_py_err(err: DataLoaderError) -> PyErr {
|
| 19 |
+
match err {
|
| 20 |
+
DataLoaderError::InvalidConfig(msg) | DataLoaderError::ColumnNotFound(msg) => {
|
| 21 |
+
PyValueError::new_err(msg)
|
| 22 |
+
}
|
| 23 |
+
DataLoaderError::InvalidCoordinate => PyValueError::new_err(err.to_string()),
|
| 24 |
+
other => PyRuntimeError::new_err(other.to_string()),
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
#[pyclass(frozen, name = "StreamCoordinate")]
|
| 29 |
+
#[derive(Clone, Copy)]
|
| 30 |
+
pub struct PyStreamCoordinate {
|
| 31 |
+
#[pyo3(get)]
|
| 32 |
+
pub shard_id: u32,
|
| 33 |
+
#[pyo3(get)]
|
| 34 |
+
pub byte_offset: u64,
|
| 35 |
+
#[pyo3(get)]
|
| 36 |
+
pub row_group_idx: u32,
|
| 37 |
+
#[pyo3(get)]
|
| 38 |
+
pub page_idx: u32,
|
| 39 |
+
#[pyo3(get)]
|
| 40 |
+
pub row_offset_in_page: u32,
|
| 41 |
+
#[pyo3(get)]
|
| 42 |
+
pub global_row_idx: u64,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
#[pymethods]
|
| 46 |
+
impl PyStreamCoordinate {
|
| 47 |
+
#[new]
|
| 48 |
+
#[pyo3(signature = (
|
| 49 |
+
shard_id = 0,
|
| 50 |
+
byte_offset = 0,
|
| 51 |
+
row_group_idx = 0,
|
| 52 |
+
page_idx = 0,
|
| 53 |
+
row_offset_in_page = 0,
|
| 54 |
+
global_row_idx = 0,
|
| 55 |
+
))]
|
| 56 |
+
pub fn new(
|
| 57 |
+
shard_id: u32,
|
| 58 |
+
byte_offset: u64,
|
| 59 |
+
row_group_idx: u32,
|
| 60 |
+
page_idx: u32,
|
| 61 |
+
row_offset_in_page: u32,
|
| 62 |
+
global_row_idx: u64,
|
| 63 |
+
) -> Self {
|
| 64 |
+
Self {
|
| 65 |
+
shard_id,
|
| 66 |
+
byte_offset,
|
| 67 |
+
row_group_idx,
|
| 68 |
+
page_idx,
|
| 69 |
+
row_offset_in_page,
|
| 70 |
+
global_row_idx,
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
pub fn to_bytes<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
|
| 75 |
+
let coord = StreamCoordinate {
|
| 76 |
+
shard_id: self.shard_id,
|
| 77 |
+
byte_offset: self.byte_offset,
|
| 78 |
+
row_group_idx: self.row_group_idx,
|
| 79 |
+
page_idx: self.page_idx,
|
| 80 |
+
row_offset_in_page: self.row_offset_in_page,
|
| 81 |
+
global_row_idx: self.global_row_idx,
|
| 82 |
+
};
|
| 83 |
+
PyBytes::new(py, &coord.to_bytes())
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
#[staticmethod]
|
| 87 |
+
pub fn from_bytes(raw: &[u8]) -> PyResult<Self> {
|
| 88 |
+
if raw.len() != StreamCoordinate::BYTE_LEN {
|
| 89 |
+
return Err(PyValueError::new_err(format!(
|
| 90 |
+
"Invalid coordinate length: expected {} bytes, got {}",
|
| 91 |
+
StreamCoordinate::BYTE_LEN,
|
| 92 |
+
raw.len()
|
| 93 |
+
)));
|
| 94 |
+
}
|
| 95 |
+
let arr: &[u8; StreamCoordinate::BYTE_LEN] = raw.try_into().unwrap();
|
| 96 |
+
let c = StreamCoordinate::from_bytes(arr);
|
| 97 |
+
Ok(Self {
|
| 98 |
+
shard_id: c.shard_id,
|
| 99 |
+
byte_offset: c.byte_offset,
|
| 100 |
+
row_group_idx: c.row_group_idx,
|
| 101 |
+
page_idx: c.page_idx,
|
| 102 |
+
row_offset_in_page: c.row_offset_in_page,
|
| 103 |
+
global_row_idx: c.global_row_idx,
|
| 104 |
+
})
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
pub fn __repr__(&self) -> String {
|
| 108 |
+
format!(
|
| 109 |
+
"StreamCoordinate(shard_id={}, byte_offset={}, row_group={}, page={}, row_offset={}, global_row={})",
|
| 110 |
+
self.shard_id,
|
| 111 |
+
self.byte_offset,
|
| 112 |
+
self.row_group_idx,
|
| 113 |
+
self.page_idx,
|
| 114 |
+
self.row_offset_in_page,
|
| 115 |
+
self.global_row_idx
|
| 116 |
+
)
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
#[pyclass(name = "StreamDeduplicator")]
|
| 121 |
+
pub struct PyStreamDeduplicator {
|
| 122 |
+
inner: Mutex<TwoStageDeduplicator>,
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
#[pymethods]
|
| 126 |
+
impl PyStreamDeduplicator {
|
| 127 |
+
#[new]
|
| 128 |
+
#[pyo3(signature = (initial_hashes = None))]
|
| 129 |
+
pub fn new(initial_hashes: Option<Vec<u64>>) -> Self {
|
| 130 |
+
let dedup = match initial_hashes {
|
| 131 |
+
Some(hashes) => TwoStageDeduplicator::with_committed(hashes),
|
| 132 |
+
None => TwoStageDeduplicator::new(),
|
| 133 |
+
};
|
| 134 |
+
Self {
|
| 135 |
+
inner: Mutex::new(dedup),
|
| 136 |
+
}
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
#[staticmethod]
|
| 140 |
+
pub fn compute_hash(payload: &[u8]) -> u64 {
|
| 141 |
+
TwoStageDeduplicator::compute_hash(payload)
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
pub fn is_seen(&self, payload: &[u8]) -> bool {
|
| 145 |
+
self.inner.lock().is_seen(payload)
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
pub fn try_add(&self, payload: &[u8]) -> bool {
|
| 149 |
+
let mut dedup = self.inner.lock();
|
| 150 |
+
dedup.try_stage(1, payload).is_some()
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
pub fn filter_batch(&self, records: Vec<String>) -> Vec<String> {
|
| 154 |
+
let mut dedup = self.inner.lock();
|
| 155 |
+
let mut out = Vec::with_capacity(records.len());
|
| 156 |
+
for rec in records {
|
| 157 |
+
if dedup.try_stage(1, rec.as_bytes()).is_some() {
|
| 158 |
+
out.push(rec);
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
out
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
pub fn commit(&self) -> usize {
|
| 165 |
+
let mut dedup = self.inner.lock();
|
| 166 |
+
dedup.commit_batch(1).len()
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
pub fn rollback(&self) -> usize {
|
| 170 |
+
let mut dedup = self.inner.lock();
|
| 171 |
+
dedup.rollback_batch(1)
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
pub fn clear(&self) {
|
| 175 |
+
let mut dedup = self.inner.lock();
|
| 176 |
+
dedup.clear_staged();
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
pub fn count(&self) -> usize {
|
| 180 |
+
self.inner.lock().committed_len()
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
pub fn save_wal(&self, path: &str) -> PyResult<usize> {
|
| 184 |
+
let dedup = self.inner.lock();
|
| 185 |
+
let hashes = dedup.export_committed_hashes();
|
| 186 |
+
let mut buf = Vec::with_capacity(hashes.len() * 8);
|
| 187 |
+
for h in &hashes {
|
| 188 |
+
buf.extend_from_slice(&h.to_le_bytes());
|
| 189 |
+
}
|
| 190 |
+
let mut file = File::create(path).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 191 |
+
file.write_all(&buf)
|
| 192 |
+
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 193 |
+
file.sync_data()
|
| 194 |
+
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 195 |
+
Ok(hashes.len())
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
pub fn load_wal(&self, path: &str) -> PyResult<usize> {
|
| 199 |
+
let mut file = match File::open(path) {
|
| 200 |
+
Ok(f) => f,
|
| 201 |
+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(0),
|
| 202 |
+
Err(e) => return Err(PyRuntimeError::new_err(e.to_string())),
|
| 203 |
+
};
|
| 204 |
+
let mut buf = Vec::new();
|
| 205 |
+
file.read_to_end(&mut buf)
|
| 206 |
+
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
|
| 207 |
+
if buf.len() % 8 != 0 {
|
| 208 |
+
return Err(PyValueError::new_err(format!(
|
| 209 |
+
"Corrupted WAL file '{}': byte length {} is not a multiple of 8",
|
| 210 |
+
path,
|
| 211 |
+
buf.len()
|
| 212 |
+
)));
|
| 213 |
+
}
|
| 214 |
+
let count = buf.len() / 8;
|
| 215 |
+
let mut dedup = self.inner.lock();
|
| 216 |
+
let (chunks, _) = buf.as_chunks::<8>();
|
| 217 |
+
for chunk in chunks {
|
| 218 |
+
let h = u64::from_le_bytes(*chunk);
|
| 219 |
+
dedup.import_committed_hashes(std::iter::once(h));
|
| 220 |
+
}
|
| 221 |
+
Ok(count)
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
pub fn export_hashes(&self) -> Vec<u64> {
|
| 225 |
+
self.inner.lock().export_committed_hashes()
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
#[pyclass(name = "StateTracker")]
|
| 230 |
+
pub struct PyStateTracker {
|
| 231 |
+
inner: Mutex<DualMetadataManager>,
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
#[pymethods]
|
| 235 |
+
impl PyStateTracker {
|
| 236 |
+
#[new]
|
| 237 |
+
pub fn new(meta_path: &str) -> PyResult<Self> {
|
| 238 |
+
let mgr = DualMetadataManager::open(meta_path).map_err(to_py_err)?;
|
| 239 |
+
Ok(Self {
|
| 240 |
+
inner: Mutex::new(mgr),
|
| 241 |
+
})
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
pub fn read(&self) -> PyStreamCoordinate {
|
| 245 |
+
let mgr = self.inner.lock();
|
| 246 |
+
let c = mgr.committed_coordinate();
|
| 247 |
+
PyStreamCoordinate {
|
| 248 |
+
shard_id: c.shard_id,
|
| 249 |
+
byte_offset: c.byte_offset,
|
| 250 |
+
row_group_idx: c.row_group_idx,
|
| 251 |
+
page_idx: c.page_idx,
|
| 252 |
+
row_offset_in_page: c.row_offset_in_page,
|
| 253 |
+
global_row_idx: c.global_row_idx,
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
#[pyo3(signature = (
|
| 258 |
+
shard_id = 0,
|
| 259 |
+
byte_offset = 0,
|
| 260 |
+
row_group_idx = 0,
|
| 261 |
+
page_idx = 0,
|
| 262 |
+
row_offset_in_page = 0,
|
| 263 |
+
global_row_idx = 0,
|
| 264 |
+
batch_id = 1
|
| 265 |
+
))]
|
| 266 |
+
#[allow(clippy::too_many_arguments)]
|
| 267 |
+
pub fn update(
|
| 268 |
+
&self,
|
| 269 |
+
shard_id: u32,
|
| 270 |
+
byte_offset: u64,
|
| 271 |
+
row_group_idx: u32,
|
| 272 |
+
page_idx: u32,
|
| 273 |
+
row_offset_in_page: u32,
|
| 274 |
+
global_row_idx: u64,
|
| 275 |
+
batch_id: u64,
|
| 276 |
+
) {
|
| 277 |
+
let coord = StreamCoordinate {
|
| 278 |
+
shard_id,
|
| 279 |
+
byte_offset,
|
| 280 |
+
row_group_idx,
|
| 281 |
+
page_idx,
|
| 282 |
+
row_offset_in_page,
|
| 283 |
+
global_row_idx,
|
| 284 |
+
};
|
| 285 |
+
let mut mgr = self.inner.lock();
|
| 286 |
+
mgr.stage_batch_in_ram(batch_id, coord);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
#[pyo3(signature = (batch_id = 1))]
|
| 290 |
+
pub fn commit(&self, batch_id: u64) -> PyResult<PyStreamCoordinate> {
|
| 291 |
+
let mut mgr = self.inner.lock();
|
| 292 |
+
let c = mgr.commit_gpu_batch(batch_id).map_err(to_py_err)?;
|
| 293 |
+
Ok(PyStreamCoordinate {
|
| 294 |
+
shard_id: c.shard_id,
|
| 295 |
+
byte_offset: c.byte_offset,
|
| 296 |
+
row_group_idx: c.row_group_idx,
|
| 297 |
+
page_idx: c.page_idx,
|
| 298 |
+
row_offset_in_page: c.row_offset_in_page,
|
| 299 |
+
global_row_idx: c.global_row_idx,
|
| 300 |
+
})
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
pub fn committed_batch_id(&self) -> u64 {
|
| 304 |
+
self.inner.lock().committed_batch_id()
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
#[pyclass(name = "QualityFilter")]
|
| 309 |
+
pub struct PyQualityFilter {
|
| 310 |
+
cfg: QualityConfig,
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
#[pymethods]
|
| 314 |
+
impl PyQualityFilter {
|
| 315 |
+
#[new]
|
| 316 |
+
#[pyo3(signature = (
|
| 317 |
+
min_chars = 0,
|
| 318 |
+
max_chars = 0,
|
| 319 |
+
min_words = 0,
|
| 320 |
+
min_entropy = 0.0,
|
| 321 |
+
max_repetition_ratio = 1.0,
|
| 322 |
+
max_symbol_ratio = 1.0
|
| 323 |
+
))]
|
| 324 |
+
pub fn new(
|
| 325 |
+
min_chars: usize,
|
| 326 |
+
max_chars: usize,
|
| 327 |
+
min_words: usize,
|
| 328 |
+
min_entropy: f64,
|
| 329 |
+
max_repetition_ratio: f64,
|
| 330 |
+
max_symbol_ratio: f64,
|
| 331 |
+
) -> Self {
|
| 332 |
+
Self {
|
| 333 |
+
cfg: QualityConfig {
|
| 334 |
+
min_chars,
|
| 335 |
+
max_chars,
|
| 336 |
+
min_words,
|
| 337 |
+
min_entropy,
|
| 338 |
+
max_repetition_ratio,
|
| 339 |
+
max_symbol_ratio,
|
| 340 |
+
},
|
| 341 |
+
}
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
pub fn is_high_quality(&self, text: &str) -> bool {
|
| 345 |
+
crate::quality::evaluate_quality(text, &self.cfg).is_passed
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
pub fn metrics(&self, text: &str) -> (usize, usize, f64, f64, f64, bool) {
|
| 349 |
+
let m = crate::quality::evaluate_quality(text, &self.cfg);
|
| 350 |
+
(
|
| 351 |
+
m.char_count,
|
| 352 |
+
m.word_count,
|
| 353 |
+
m.entropy,
|
| 354 |
+
m.repetition_ratio,
|
| 355 |
+
m.symbol_ratio,
|
| 356 |
+
m.is_passed,
|
| 357 |
+
)
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
pub fn filter_flags(&self, py: Python<'_>, texts: Vec<String>) -> Vec<bool> {
|
| 361 |
+
let cfg = self.cfg.clone();
|
| 362 |
+
py.detach(|| crate::quality::filter_quality_batch_parallel(&texts, &cfg))
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
pub fn filter_batch(&self, py: Python<'_>, texts: Vec<String>) -> Vec<String> {
|
| 366 |
+
let cfg = self.cfg.clone();
|
| 367 |
+
py.detach(|| {
|
| 368 |
+
texts
|
| 369 |
+
.into_iter()
|
| 370 |
+
.filter(|t| crate::quality::evaluate_quality(t, &cfg).is_passed)
|
| 371 |
+
.collect()
|
| 372 |
+
})
|
| 373 |
+
}
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
#[pyclass(name = "MinHashDeduplicator")]
|
| 377 |
+
pub struct PyMinHashDeduplicator {
|
| 378 |
+
inner: Mutex<crate::minhash::MinHashDeduplicator>,
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
#[pymethods]
|
| 382 |
+
impl PyMinHashDeduplicator {
|
| 383 |
+
#[new]
|
| 384 |
+
#[pyo3(signature = (threshold = 0.85, k_shingle = 5, num_perm = 64))]
|
| 385 |
+
pub fn new(threshold: f64, k_shingle: usize, num_perm: usize) -> Self {
|
| 386 |
+
Self {
|
| 387 |
+
inner: Mutex::new(crate::minhash::MinHashDeduplicator::new(
|
| 388 |
+
threshold, k_shingle, num_perm,
|
| 389 |
+
)),
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
pub fn is_unique(&self, text: &str) -> bool {
|
| 394 |
+
self.inner.lock().is_unique(text)
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
pub fn similarity(&self, text_a: &str, text_b: &str) -> f64 {
|
| 398 |
+
let dedup = self.inner.lock();
|
| 399 |
+
let sig_a = dedup.compute_signature(text_a);
|
| 400 |
+
let sig_b = dedup.compute_signature(text_b);
|
| 401 |
+
sig_a.jaccard_similarity(&sig_b)
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
pub fn count(&self) -> usize {
|
| 405 |
+
self.inner.lock().count()
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
pub fn clear(&self) {
|
| 409 |
+
self.inner.lock().clear();
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
#[pyclass(name = "StringTensor")]
|
| 414 |
+
#[derive(Clone)]
|
| 415 |
+
pub struct PyStringTensor {
|
| 416 |
+
inner: crate::string_tensor::StringTensor,
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
#[pymethods]
|
| 420 |
+
impl PyStringTensor {
|
| 421 |
+
#[staticmethod]
|
| 422 |
+
pub fn from_list(strings: Vec<String>) -> Self {
|
| 423 |
+
Self {
|
| 424 |
+
inner: crate::string_tensor::StringTensor::from_strings(strings),
|
| 425 |
+
}
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
#[staticmethod]
|
| 429 |
+
pub fn from_gpu_buffers(offsets: Vec<u32>, payload: &[u8]) -> Self {
|
| 430 |
+
Self {
|
| 431 |
+
inner: crate::string_tensor::StringTensor::from_gpu_buffers(&offsets, payload),
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
pub fn to_gpu_buffers<'py>(
|
| 436 |
+
&self,
|
| 437 |
+
py: Python<'py>,
|
| 438 |
+
) -> (Bound<'py, PyBytes>, Bound<'py, PyBytes>) {
|
| 439 |
+
let (offsets, payload) = self.inner.to_gpu_buffers();
|
| 440 |
+
let mut offset_bytes = Vec::with_capacity(offsets.len() * 4);
|
| 441 |
+
for &off in &offsets {
|
| 442 |
+
offset_bytes.extend_from_slice(&off.to_le_bytes());
|
| 443 |
+
}
|
| 444 |
+
(PyBytes::new(py, &offset_bytes), PyBytes::new(py, &payload))
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
pub fn to_offsets(&self) -> Vec<u32> {
|
| 448 |
+
self.inner.to_gpu_buffers().0
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
pub fn to_payload<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> {
|
| 452 |
+
let (_, payload) = self.inner.to_gpu_buffers();
|
| 453 |
+
PyBytes::new(py, &payload)
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
pub fn __len__(&self) -> usize {
|
| 457 |
+
self.inner.len()
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
pub fn __getitem__(&self, index: usize) -> PyResult<String> {
|
| 461 |
+
self.inner
|
| 462 |
+
.get(index)
|
| 463 |
+
.map(|s| s.to_owned())
|
| 464 |
+
.ok_or_else(|| PyValueError::new_err("StringTensor index out of range"))
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
pub fn get(&self, index: usize) -> Option<String> {
|
| 468 |
+
self.inner.get(index).map(|s| s.to_owned())
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
pub fn to_list(&self) -> Vec<String> {
|
| 472 |
+
self.inner.to_vec()
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
pub fn lengths(&self) -> Vec<u32> {
|
| 476 |
+
self.inner.lengths()
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
pub fn is_inlined_mask(&self) -> Vec<bool> {
|
| 480 |
+
self.inner.is_inlined_mask()
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
pub fn total_bytes(&self) -> usize {
|
| 484 |
+
self.inner.views.iter().map(|v| v.len as usize).sum()
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
pub fn filter_by_length(&self, min_len: u32, max_len: u32) -> Self {
|
| 488 |
+
Self {
|
| 489 |
+
inner: self.inner.filter_by_length(min_len, max_len),
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
pub fn filter_by_prefix(&self, prefix: &str) -> Self {
|
| 494 |
+
Self {
|
| 495 |
+
inner: self.inner.filter_by_prefix(prefix),
|
| 496 |
+
}
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
pub fn hash_all(&self, py: Python<'_>) -> Vec<u64> {
|
| 500 |
+
py.detach(|| self.inner.hash_all_parallel())
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
pub fn __repr__(&self) -> String {
|
| 504 |
+
format!(
|
| 505 |
+
"StringTensor(count={}, inlined={})",
|
| 506 |
+
self.inner.len(),
|
| 507 |
+
self.inner.views.iter().filter(|v| v.is_inlined()).count()
|
| 508 |
+
)
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
pub fn extract_py_columns_table(
|
| 513 |
+
py: Python<'_>,
|
| 514 |
+
columns: &Bound<'_, PyAny>,
|
| 515 |
+
) -> PyResult<Vec<(String, Vec<DataValue>)>> {
|
| 516 |
+
let mut rust_cols = Vec::new();
|
| 517 |
+
|
| 518 |
+
if let Ok(dict) = columns.cast::<PyDict>() {
|
| 519 |
+
for (k, v) in dict.iter() {
|
| 520 |
+
let col_name = k.extract::<String>()?;
|
| 521 |
+
let vals = extract_py_seq_values(py, &v)?;
|
| 522 |
+
rust_cols.push((col_name, vals));
|
| 523 |
+
}
|
| 524 |
+
} else if let Ok(seq) = columns.cast::<PySequence>() {
|
| 525 |
+
let len = seq.len()?;
|
| 526 |
+
for i in 0..len {
|
| 527 |
+
let item = seq.get_item(i)?;
|
| 528 |
+
if let Ok(pair) = item.cast::<PySequence>()
|
| 529 |
+
&& pair.len()? == 2
|
| 530 |
+
{
|
| 531 |
+
let col_name = pair.get_item(0)?.extract::<String>()?;
|
| 532 |
+
let col_vals_seq = pair.get_item(1)?;
|
| 533 |
+
let vals = extract_py_seq_values(py, &col_vals_seq)?;
|
| 534 |
+
rust_cols.push((col_name, vals));
|
| 535 |
+
continue;
|
| 536 |
+
}
|
| 537 |
+
return Err(PyValueError::new_err(
|
| 538 |
+
"columns sequence must contain (name, values) pairs or be a dict",
|
| 539 |
+
));
|
| 540 |
+
}
|
| 541 |
+
} else {
|
| 542 |
+
return Err(PyValueError::new_err(
|
| 543 |
+
"columns must be a dict {name: [values]} or sequence of (name, [values]) pairs",
|
| 544 |
+
));
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
Ok(rust_cols)
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
fn extract_py_seq_values(_py: Python<'_>, seq_obj: &Bound<'_, PyAny>) -> PyResult<Vec<DataValue>> {
|
| 551 |
+
let seq = seq_obj
|
| 552 |
+
.cast::<PySequence>()
|
| 553 |
+
.map_err(|_| PyValueError::new_err("column values must be a sequence (list/tuple)"))?;
|
| 554 |
+
let len = seq.len()?;
|
| 555 |
+
let mut out = Vec::with_capacity(len);
|
| 556 |
+
|
| 557 |
+
for i in 0..len {
|
| 558 |
+
let item = seq.get_item(i)?;
|
| 559 |
+
if item.is_none() {
|
| 560 |
+
out.push(DataValue::Null);
|
| 561 |
+
} else if let Ok(b) = item.cast::<PyBool>() {
|
| 562 |
+
out.push(DataValue::Bool(b.is_true()));
|
| 563 |
+
} else if let Ok(i) = item.cast::<PyInt>() {
|
| 564 |
+
out.push(DataValue::Int(i.extract::<i64>()?));
|
| 565 |
+
} else if let Ok(f) = item.cast::<PyFloat>() {
|
| 566 |
+
out.push(DataValue::Float(f.extract::<f64>()?));
|
| 567 |
+
} else if let Ok(s) = item.cast::<PyString>() {
|
| 568 |
+
let str_val = s.to_string_lossy();
|
| 569 |
+
out.push(classify_string_or_json(&str_val));
|
| 570 |
+
} else if let Ok(bytes) = item.cast::<PyBytes>() {
|
| 571 |
+
out.push(DataValue::Bytes(bytes.as_bytes().to_vec()));
|
| 572 |
+
} else if let Ok(bytes) = item.cast::<PyByteArray>() {
|
| 573 |
+
out.push(DataValue::Bytes(bytes.to_vec()));
|
| 574 |
+
} else {
|
| 575 |
+
let py_str = item.str()?;
|
| 576 |
+
let str_repr = py_str.to_string_lossy();
|
| 577 |
+
out.push(classify_string_or_json(&str_repr));
|
| 578 |
+
}
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
Ok(out)
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
#[pyfunction]
|
| 585 |
+
pub fn dataloader_cpu_isa() -> &'static str {
|
| 586 |
+
crate::simd_cpu::detect_cpu_isa().as_str()
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
#[pyfunction]
|
| 590 |
+
#[pyo3(signature = (target, candidates, max_suggestions=3))]
|
| 591 |
+
pub fn suggest_closest_matches(
|
| 592 |
+
target: &str,
|
| 593 |
+
candidates: Vec<String>,
|
| 594 |
+
max_suggestions: usize,
|
| 595 |
+
) -> Vec<String> {
|
| 596 |
+
find_closest_matches(target, &candidates, max_suggestions)
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
#[pyfunction]
|
| 600 |
+
#[pyo3(signature = (columns, format="parquet", hf_image_struct=true))]
|
| 601 |
+
pub fn serialize_table_to_bytes(
|
| 602 |
+
py: Python<'_>,
|
| 603 |
+
columns: &Bound<'_, PyAny>,
|
| 604 |
+
format: &str,
|
| 605 |
+
hf_image_struct: bool,
|
| 606 |
+
) -> PyResult<Vec<u8>> {
|
| 607 |
+
let rust_cols = extract_py_columns_table(py, columns)?;
|
| 608 |
+
py.detach(|| serialize_columns_by_format(&rust_cols, format, hf_image_struct))
|
| 609 |
+
.map_err(to_py_err)
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
#[pyfunction]
|
| 613 |
+
#[pyo3(signature = (
|
| 614 |
+
raw,
|
| 615 |
+
trim_whitespace=true,
|
| 616 |
+
normalize_newlines=true,
|
| 617 |
+
min_byte_len=0,
|
| 618 |
+
max_byte_len=0,
|
| 619 |
+
truncate_over_max=false,
|
| 620 |
+
json_extract_key=None
|
| 621 |
+
))]
|
| 622 |
+
pub fn clean_single_record(
|
| 623 |
+
raw: &str,
|
| 624 |
+
trim_whitespace: bool,
|
| 625 |
+
normalize_newlines: bool,
|
| 626 |
+
min_byte_len: usize,
|
| 627 |
+
max_byte_len: usize,
|
| 628 |
+
truncate_over_max: bool,
|
| 629 |
+
json_extract_key: Option<String>,
|
| 630 |
+
) -> Option<String> {
|
| 631 |
+
let cfg = PostProcessConfig {
|
| 632 |
+
trim_whitespace,
|
| 633 |
+
normalize_newlines,
|
| 634 |
+
min_byte_len,
|
| 635 |
+
max_byte_len,
|
| 636 |
+
truncate_over_max,
|
| 637 |
+
json_extract_key,
|
| 638 |
+
};
|
| 639 |
+
crate::postprocess::process_single_record(raw, &cfg)
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
#[pyfunction]
|
| 643 |
+
#[pyo3(signature = (
|
| 644 |
+
records,
|
| 645 |
+
trim_whitespace=true,
|
| 646 |
+
normalize_newlines=true,
|
| 647 |
+
min_byte_len=0,
|
| 648 |
+
max_byte_len=0,
|
| 649 |
+
truncate_over_max=false,
|
| 650 |
+
json_extract_key=None
|
| 651 |
+
))]
|
| 652 |
+
#[allow(clippy::too_many_arguments)]
|
| 653 |
+
pub fn clean_batch_records(
|
| 654 |
+
py: Python<'_>,
|
| 655 |
+
records: Vec<String>,
|
| 656 |
+
trim_whitespace: bool,
|
| 657 |
+
normalize_newlines: bool,
|
| 658 |
+
min_byte_len: usize,
|
| 659 |
+
max_byte_len: usize,
|
| 660 |
+
truncate_over_max: bool,
|
| 661 |
+
json_extract_key: Option<String>,
|
| 662 |
+
) -> Vec<String> {
|
| 663 |
+
let cfg = PostProcessConfig {
|
| 664 |
+
trim_whitespace,
|
| 665 |
+
normalize_newlines,
|
| 666 |
+
min_byte_len,
|
| 667 |
+
max_byte_len,
|
| 668 |
+
truncate_over_max,
|
| 669 |
+
json_extract_key,
|
| 670 |
+
};
|
| 671 |
+
py.detach(|| crate::postprocess::process_batch_parallel(&records, &cfg))
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
#[pyfunction]
|
| 675 |
+
#[pyo3(signature = (
|
| 676 |
+
records,
|
| 677 |
+
trim_whitespace=true,
|
| 678 |
+
normalize_newlines=true,
|
| 679 |
+
min_byte_len=1,
|
| 680 |
+
max_byte_len=0,
|
| 681 |
+
truncate_over_max=false,
|
| 682 |
+
json_extract_key=None,
|
| 683 |
+
seen_hashes=None,
|
| 684 |
+
deduplicate=true
|
| 685 |
+
))]
|
| 686 |
+
#[allow(clippy::too_many_arguments)]
|
| 687 |
+
pub fn postprocess_and_dedup_records(
|
| 688 |
+
py: Python<'_>,
|
| 689 |
+
records: Vec<String>,
|
| 690 |
+
trim_whitespace: bool,
|
| 691 |
+
normalize_newlines: bool,
|
| 692 |
+
min_byte_len: usize,
|
| 693 |
+
max_byte_len: usize,
|
| 694 |
+
truncate_over_max: bool,
|
| 695 |
+
json_extract_key: Option<String>,
|
| 696 |
+
seen_hashes: Option<Vec<u64>>,
|
| 697 |
+
deduplicate: bool,
|
| 698 |
+
) -> PyResult<(Vec<String>, Vec<u64>)> {
|
| 699 |
+
let cfg = PostProcessConfig {
|
| 700 |
+
trim_whitespace,
|
| 701 |
+
normalize_newlines,
|
| 702 |
+
min_byte_len,
|
| 703 |
+
max_byte_len,
|
| 704 |
+
truncate_over_max,
|
| 705 |
+
json_extract_key,
|
| 706 |
+
};
|
| 707 |
+
Ok(py.detach(|| {
|
| 708 |
+
let processed = crate::postprocess::process_batch_parallel_indexed(&records, &cfg);
|
| 709 |
+
if !deduplicate {
|
| 710 |
+
let cleaned_all: Vec<String> = processed.into_iter().flatten().collect();
|
| 711 |
+
return (cleaned_all, Vec::new());
|
| 712 |
+
}
|
| 713 |
+
let mut dedup = TwoStageDeduplicator::with_committed(seen_hashes.unwrap_or_default());
|
| 714 |
+
let mut cleaned_unique = Vec::with_capacity(processed.len());
|
| 715 |
+
for cleaned in processed.into_iter().flatten() {
|
| 716 |
+
if dedup.try_stage(1, cleaned.as_bytes()).is_some() {
|
| 717 |
+
cleaned_unique.push(cleaned);
|
| 718 |
+
}
|
| 719 |
+
}
|
| 720 |
+
let newly_committed = dedup.commit_batch(1);
|
| 721 |
+
(cleaned_unique, newly_committed)
|
| 722 |
+
}))
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
#[pyfunction]
|
| 726 |
+
#[pyo3(signature = (
|
| 727 |
+
records,
|
| 728 |
+
trim_whitespace = true,
|
| 729 |
+
normalize_newlines = true,
|
| 730 |
+
min_byte_len = 0,
|
| 731 |
+
max_byte_len = 0,
|
| 732 |
+
truncate_over_max = false,
|
| 733 |
+
json_extract_key = None,
|
| 734 |
+
filter_quality = false,
|
| 735 |
+
min_chars = 0,
|
| 736 |
+
max_chars = 0,
|
| 737 |
+
min_words = 0,
|
| 738 |
+
min_entropy = 0.0,
|
| 739 |
+
max_repetition_ratio = 1.0,
|
| 740 |
+
max_symbol_ratio = 1.0,
|
| 741 |
+
deduplicator = None,
|
| 742 |
+
commit_dedup = false,
|
| 743 |
+
))]
|
| 744 |
+
#[allow(clippy::too_many_arguments)]
|
| 745 |
+
pub fn fused_pipeline_process_batch(
|
| 746 |
+
py: Python<'_>,
|
| 747 |
+
records: Vec<String>,
|
| 748 |
+
trim_whitespace: bool,
|
| 749 |
+
normalize_newlines: bool,
|
| 750 |
+
min_byte_len: usize,
|
| 751 |
+
max_byte_len: usize,
|
| 752 |
+
truncate_over_max: bool,
|
| 753 |
+
json_extract_key: Option<String>,
|
| 754 |
+
filter_quality: bool,
|
| 755 |
+
min_chars: usize,
|
| 756 |
+
max_chars: usize,
|
| 757 |
+
min_words: usize,
|
| 758 |
+
min_entropy: f64,
|
| 759 |
+
max_repetition_ratio: f64,
|
| 760 |
+
max_symbol_ratio: f64,
|
| 761 |
+
deduplicator: Option<&PyStreamDeduplicator>,
|
| 762 |
+
commit_dedup: bool,
|
| 763 |
+
) -> PyResult<PyStringTensor> {
|
| 764 |
+
let post_cfg = PostProcessConfig {
|
| 765 |
+
trim_whitespace,
|
| 766 |
+
normalize_newlines,
|
| 767 |
+
min_byte_len,
|
| 768 |
+
max_byte_len,
|
| 769 |
+
truncate_over_max,
|
| 770 |
+
json_extract_key,
|
| 771 |
+
};
|
| 772 |
+
let qual_cfg = crate::quality::QualityConfig {
|
| 773 |
+
min_chars,
|
| 774 |
+
max_chars,
|
| 775 |
+
min_words,
|
| 776 |
+
min_entropy,
|
| 777 |
+
max_repetition_ratio,
|
| 778 |
+
max_symbol_ratio,
|
| 779 |
+
};
|
| 780 |
+
|
| 781 |
+
let result = py.detach(|| {
|
| 782 |
+
let cleaned_candidates: Vec<String> = records
|
| 783 |
+
.par_iter()
|
| 784 |
+
.filter_map(|raw| {
|
| 785 |
+
let cleaned = crate::postprocess::process_single_record(raw, &post_cfg)?;
|
| 786 |
+
if filter_quality {
|
| 787 |
+
let eval = crate::quality::evaluate_quality(&cleaned, &qual_cfg);
|
| 788 |
+
if !eval.is_passed {
|
| 789 |
+
return None;
|
| 790 |
+
}
|
| 791 |
+
}
|
| 792 |
+
Some(cleaned)
|
| 793 |
+
})
|
| 794 |
+
.collect();
|
| 795 |
+
|
| 796 |
+
let final_strings = if let Some(dedup_wrapper) = deduplicator {
|
| 797 |
+
let mut dedup = dedup_wrapper.inner.lock();
|
| 798 |
+
let mut unique = Vec::with_capacity(cleaned_candidates.len());
|
| 799 |
+
for s in cleaned_candidates {
|
| 800 |
+
if dedup.try_stage(1, s.as_bytes()).is_some() {
|
| 801 |
+
unique.push(s);
|
| 802 |
+
}
|
| 803 |
+
}
|
| 804 |
+
if commit_dedup {
|
| 805 |
+
dedup.commit_batch(1);
|
| 806 |
+
}
|
| 807 |
+
unique
|
| 808 |
+
} else {
|
| 809 |
+
cleaned_candidates
|
| 810 |
+
};
|
| 811 |
+
|
| 812 |
+
crate::string_tensor::StringTensor::from_strings(final_strings)
|
| 813 |
+
});
|
| 814 |
+
|
| 815 |
+
Ok(PyStringTensor { inner: result })
|
| 816 |
+
}
|
| 817 |
+
|
| 818 |
+
pub fn register_dataloader_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
|
| 819 |
+
parent_module.add_class::<PyStreamCoordinate>()?;
|
| 820 |
+
parent_module.add_class::<PyStreamDeduplicator>()?;
|
| 821 |
+
parent_module.add_class::<PyStateTracker>()?;
|
| 822 |
+
parent_module.add_class::<PyQualityFilter>()?;
|
| 823 |
+
parent_module.add_class::<PyMinHashDeduplicator>()?;
|
| 824 |
+
parent_module.add_class::<PyStringTensor>()?;
|
| 825 |
+
parent_module.add_function(wrap_pyfunction!(dataloader_cpu_isa, parent_module)?)?;
|
| 826 |
+
parent_module.add_function(wrap_pyfunction!(suggest_closest_matches, parent_module)?)?;
|
| 827 |
+
parent_module.add_function(wrap_pyfunction!(serialize_table_to_bytes, parent_module)?)?;
|
| 828 |
+
parent_module.add_function(wrap_pyfunction!(clean_single_record, parent_module)?)?;
|
| 829 |
+
parent_module.add_function(wrap_pyfunction!(clean_batch_records, parent_module)?)?;
|
| 830 |
+
parent_module.add_function(wrap_pyfunction!(
|
| 831 |
+
postprocess_and_dedup_records,
|
| 832 |
+
parent_module
|
| 833 |
+
)?)?;
|
| 834 |
+
parent_module.add_function(wrap_pyfunction!(
|
| 835 |
+
fused_pipeline_process_batch,
|
| 836 |
+
parent_module
|
| 837 |
+
)?)?;
|
| 838 |
+
Ok(())
|
| 839 |
+
}
|
data/src/quality.rs
CHANGED
|
@@ -1,169 +1,169 @@
|
|
| 1 |
-
use hashbrown::{HashMap, HashSet};
|
| 2 |
-
use rayon::prelude::*;
|
| 3 |
-
|
| 4 |
-
#[derive(Debug, Clone)]
|
| 5 |
-
pub struct QualityConfig {
|
| 6 |
-
pub min_chars: usize,
|
| 7 |
-
pub max_chars: usize,
|
| 8 |
-
pub min_words: usize,
|
| 9 |
-
pub min_entropy: f64,
|
| 10 |
-
pub max_repetition_ratio: f64,
|
| 11 |
-
pub max_symbol_ratio: f64,
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
impl Default for QualityConfig {
|
| 15 |
-
fn default() -> Self {
|
| 16 |
-
Self {
|
| 17 |
-
min_chars: 0,
|
| 18 |
-
max_chars: 0,
|
| 19 |
-
min_words: 0,
|
| 20 |
-
min_entropy: 0.0,
|
| 21 |
-
max_repetition_ratio: 1.0,
|
| 22 |
-
max_symbol_ratio: 1.0,
|
| 23 |
-
}
|
| 24 |
-
}
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
#[derive(Debug, Clone, Copy, PartialEq)]
|
| 28 |
-
pub struct QualityMetrics {
|
| 29 |
-
pub char_count: usize,
|
| 30 |
-
pub word_count: usize,
|
| 31 |
-
pub entropy: f64,
|
| 32 |
-
pub repetition_ratio: f64,
|
| 33 |
-
pub symbol_ratio: f64,
|
| 34 |
-
pub is_passed: bool,
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
pub fn calculate_shannon_entropy(text: &str) -> f64 {
|
| 38 |
-
if text.is_empty() {
|
| 39 |
-
return 0.0;
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
// Zero-allocation stack array fast-path for pure ASCII text
|
| 43 |
-
if text.is_ascii() {
|
| 44 |
-
let mut freq = [0u32; 256];
|
| 45 |
-
for &b in text.as_bytes() {
|
| 46 |
-
freq[b as usize] += 1;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
let n = text.len() as f64;
|
| 50 |
-
let mut entropy = 0.0;
|
| 51 |
-
for &count in &freq {
|
| 52 |
-
if count > 0 {
|
| 53 |
-
let p = (count as f64) / n;
|
| 54 |
-
entropy -= p * p.log2();
|
| 55 |
-
}
|
| 56 |
-
}
|
| 57 |
-
return entropy;
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
let char_count = text.chars().count();
|
| 61 |
-
let n = char_count as f64;
|
| 62 |
-
let mut freq = HashMap::new();
|
| 63 |
-
for c in text.chars() {
|
| 64 |
-
*freq.entry(c).or_insert(0usize) += 1;
|
| 65 |
-
}
|
| 66 |
-
let mut entropy = 0.0;
|
| 67 |
-
for &count in freq.values() {
|
| 68 |
-
let p = (count as f64) / n;
|
| 69 |
-
entropy -= p * p.log2();
|
| 70 |
-
}
|
| 71 |
-
entropy
|
| 72 |
-
}
|
| 73 |
-
|
| 74 |
-
pub fn calculate_repetition_ratio(words: &[&str]) -> f64 {
|
| 75 |
-
if words.len() < 4 {
|
| 76 |
-
return 0.0;
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
let total_trigrams = words.len() - 2;
|
| 80 |
-
let mut unique_trigrams = HashSet::with_capacity(total_trigrams);
|
| 81 |
-
|
| 82 |
-
for w in words.windows(3) {
|
| 83 |
-
unique_trigrams.insert((w[0], w[1], w[2]));
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
1.0 - ((unique_trigrams.len() as f64) / (total_trigrams as f64))
|
| 87 |
-
}
|
| 88 |
-
|
| 89 |
-
pub fn calculate_symbol_ratio(text: &str) -> (usize, f64) {
|
| 90 |
-
let mut symbols = 0usize;
|
| 91 |
-
let mut visible = 0usize;
|
| 92 |
-
|
| 93 |
-
for c in text.chars() {
|
| 94 |
-
if !c.is_whitespace() {
|
| 95 |
-
visible += 1;
|
| 96 |
-
if !c.is_alphanumeric() {
|
| 97 |
-
symbols += 1;
|
| 98 |
-
}
|
| 99 |
-
}
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
if visible == 0 {
|
| 103 |
-
return (0, 0.0);
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
(visible, (symbols as f64) / (visible as f64))
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
pub fn evaluate_quality(text: &str, cfg: &QualityConfig) -> QualityMetrics {
|
| 110 |
-
let char_count = text.chars().count();
|
| 111 |
-
if cfg.min_chars > 0 && char_count < cfg.min_chars {
|
| 112 |
-
return QualityMetrics {
|
| 113 |
-
char_count,
|
| 114 |
-
word_count: 0,
|
| 115 |
-
entropy: 0.0,
|
| 116 |
-
repetition_ratio: 0.0,
|
| 117 |
-
symbol_ratio: 0.0,
|
| 118 |
-
is_passed: false,
|
| 119 |
-
};
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
if cfg.max_chars > 0 && char_count > cfg.max_chars {
|
| 123 |
-
return QualityMetrics {
|
| 124 |
-
char_count,
|
| 125 |
-
word_count: 0,
|
| 126 |
-
entropy: 0.0,
|
| 127 |
-
repetition_ratio: 0.0,
|
| 128 |
-
symbol_ratio: 0.0,
|
| 129 |
-
is_passed: false,
|
| 130 |
-
};
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
let words: Vec<&str> = text.split_whitespace().collect();
|
| 134 |
-
let word_count = words.len();
|
| 135 |
-
if cfg.min_words > 0 && word_count < cfg.min_words {
|
| 136 |
-
return QualityMetrics {
|
| 137 |
-
char_count,
|
| 138 |
-
word_count,
|
| 139 |
-
entropy: 0.0,
|
| 140 |
-
repetition_ratio: 0.0,
|
| 141 |
-
symbol_ratio: 0.0,
|
| 142 |
-
is_passed: false,
|
| 143 |
-
};
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
let entropy = calculate_shannon_entropy(text);
|
| 147 |
-
let repetition_ratio = calculate_repetition_ratio(&words);
|
| 148 |
-
let (_, symbol_ratio) = calculate_symbol_ratio(text);
|
| 149 |
-
|
| 150 |
-
let is_passed = (cfg.min_entropy <= 0.0 || entropy >= cfg.min_entropy)
|
| 151 |
-
&& (cfg.max_repetition_ratio >= 1.0 || repetition_ratio <= cfg.max_repetition_ratio)
|
| 152 |
-
&& (cfg.max_symbol_ratio >= 1.0 || symbol_ratio <= cfg.max_symbol_ratio);
|
| 153 |
-
|
| 154 |
-
QualityMetrics {
|
| 155 |
-
char_count,
|
| 156 |
-
word_count,
|
| 157 |
-
entropy,
|
| 158 |
-
repetition_ratio,
|
| 159 |
-
symbol_ratio,
|
| 160 |
-
is_passed,
|
| 161 |
-
}
|
| 162 |
-
}
|
| 163 |
-
|
| 164 |
-
pub fn filter_quality_batch_parallel(texts: &[String], cfg: &QualityConfig) -> Vec<bool> {
|
| 165 |
-
texts
|
| 166 |
-
.par_iter()
|
| 167 |
-
.map(|t| evaluate_quality(t, cfg).is_passed)
|
| 168 |
-
.collect()
|
| 169 |
-
}
|
|
|
|
| 1 |
+
use hashbrown::{HashMap, HashSet};
|
| 2 |
+
use rayon::prelude::*;
|
| 3 |
+
|
| 4 |
+
#[derive(Debug, Clone)]
|
| 5 |
+
pub struct QualityConfig {
|
| 6 |
+
pub min_chars: usize,
|
| 7 |
+
pub max_chars: usize,
|
| 8 |
+
pub min_words: usize,
|
| 9 |
+
pub min_entropy: f64,
|
| 10 |
+
pub max_repetition_ratio: f64,
|
| 11 |
+
pub max_symbol_ratio: f64,
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
impl Default for QualityConfig {
|
| 15 |
+
fn default() -> Self {
|
| 16 |
+
Self {
|
| 17 |
+
min_chars: 0,
|
| 18 |
+
max_chars: 0,
|
| 19 |
+
min_words: 0,
|
| 20 |
+
min_entropy: 0.0,
|
| 21 |
+
max_repetition_ratio: 1.0,
|
| 22 |
+
max_symbol_ratio: 1.0,
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
| 28 |
+
pub struct QualityMetrics {
|
| 29 |
+
pub char_count: usize,
|
| 30 |
+
pub word_count: usize,
|
| 31 |
+
pub entropy: f64,
|
| 32 |
+
pub repetition_ratio: f64,
|
| 33 |
+
pub symbol_ratio: f64,
|
| 34 |
+
pub is_passed: bool,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
pub fn calculate_shannon_entropy(text: &str) -> f64 {
|
| 38 |
+
if text.is_empty() {
|
| 39 |
+
return 0.0;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Zero-allocation stack array fast-path for pure ASCII text
|
| 43 |
+
if text.is_ascii() {
|
| 44 |
+
let mut freq = [0u32; 256];
|
| 45 |
+
for &b in text.as_bytes() {
|
| 46 |
+
freq[b as usize] += 1;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
let n = text.len() as f64;
|
| 50 |
+
let mut entropy = 0.0;
|
| 51 |
+
for &count in &freq {
|
| 52 |
+
if count > 0 {
|
| 53 |
+
let p = (count as f64) / n;
|
| 54 |
+
entropy -= p * p.log2();
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
return entropy;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
let char_count = text.chars().count();
|
| 61 |
+
let n = char_count as f64;
|
| 62 |
+
let mut freq = HashMap::new();
|
| 63 |
+
for c in text.chars() {
|
| 64 |
+
*freq.entry(c).or_insert(0usize) += 1;
|
| 65 |
+
}
|
| 66 |
+
let mut entropy = 0.0;
|
| 67 |
+
for &count in freq.values() {
|
| 68 |
+
let p = (count as f64) / n;
|
| 69 |
+
entropy -= p * p.log2();
|
| 70 |
+
}
|
| 71 |
+
entropy
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
pub fn calculate_repetition_ratio(words: &[&str]) -> f64 {
|
| 75 |
+
if words.len() < 4 {
|
| 76 |
+
return 0.0;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
let total_trigrams = words.len() - 2;
|
| 80 |
+
let mut unique_trigrams = HashSet::with_capacity(total_trigrams);
|
| 81 |
+
|
| 82 |
+
for w in words.windows(3) {
|
| 83 |
+
unique_trigrams.insert((w[0], w[1], w[2]));
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
1.0 - ((unique_trigrams.len() as f64) / (total_trigrams as f64))
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
pub fn calculate_symbol_ratio(text: &str) -> (usize, f64) {
|
| 90 |
+
let mut symbols = 0usize;
|
| 91 |
+
let mut visible = 0usize;
|
| 92 |
+
|
| 93 |
+
for c in text.chars() {
|
| 94 |
+
if !c.is_whitespace() {
|
| 95 |
+
visible += 1;
|
| 96 |
+
if !c.is_alphanumeric() {
|
| 97 |
+
symbols += 1;
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
if visible == 0 {
|
| 103 |
+
return (0, 0.0);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
(visible, (symbols as f64) / (visible as f64))
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
pub fn evaluate_quality(text: &str, cfg: &QualityConfig) -> QualityMetrics {
|
| 110 |
+
let char_count = text.chars().count();
|
| 111 |
+
if cfg.min_chars > 0 && char_count < cfg.min_chars {
|
| 112 |
+
return QualityMetrics {
|
| 113 |
+
char_count,
|
| 114 |
+
word_count: 0,
|
| 115 |
+
entropy: 0.0,
|
| 116 |
+
repetition_ratio: 0.0,
|
| 117 |
+
symbol_ratio: 0.0,
|
| 118 |
+
is_passed: false,
|
| 119 |
+
};
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
if cfg.max_chars > 0 && char_count > cfg.max_chars {
|
| 123 |
+
return QualityMetrics {
|
| 124 |
+
char_count,
|
| 125 |
+
word_count: 0,
|
| 126 |
+
entropy: 0.0,
|
| 127 |
+
repetition_ratio: 0.0,
|
| 128 |
+
symbol_ratio: 0.0,
|
| 129 |
+
is_passed: false,
|
| 130 |
+
};
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
let words: Vec<&str> = text.split_whitespace().collect();
|
| 134 |
+
let word_count = words.len();
|
| 135 |
+
if cfg.min_words > 0 && word_count < cfg.min_words {
|
| 136 |
+
return QualityMetrics {
|
| 137 |
+
char_count,
|
| 138 |
+
word_count,
|
| 139 |
+
entropy: 0.0,
|
| 140 |
+
repetition_ratio: 0.0,
|
| 141 |
+
symbol_ratio: 0.0,
|
| 142 |
+
is_passed: false,
|
| 143 |
+
};
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
let entropy = calculate_shannon_entropy(text);
|
| 147 |
+
let repetition_ratio = calculate_repetition_ratio(&words);
|
| 148 |
+
let (_, symbol_ratio) = calculate_symbol_ratio(text);
|
| 149 |
+
|
| 150 |
+
let is_passed = (cfg.min_entropy <= 0.0 || entropy >= cfg.min_entropy)
|
| 151 |
+
&& (cfg.max_repetition_ratio >= 1.0 || repetition_ratio <= cfg.max_repetition_ratio)
|
| 152 |
+
&& (cfg.max_symbol_ratio >= 1.0 || symbol_ratio <= cfg.max_symbol_ratio);
|
| 153 |
+
|
| 154 |
+
QualityMetrics {
|
| 155 |
+
char_count,
|
| 156 |
+
word_count,
|
| 157 |
+
entropy,
|
| 158 |
+
repetition_ratio,
|
| 159 |
+
symbol_ratio,
|
| 160 |
+
is_passed,
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
pub fn filter_quality_batch_parallel(texts: &[String], cfg: &QualityConfig) -> Vec<bool> {
|
| 165 |
+
texts
|
| 166 |
+
.par_iter()
|
| 167 |
+
.map(|t| evaluate_quality(t, cfg).is_passed)
|
| 168 |
+
.collect()
|
| 169 |
+
}
|
data/src/serialize.rs
CHANGED
|
@@ -1,268 +1,268 @@
|
|
| 1 |
-
use std::sync::Arc;
|
| 2 |
-
|
| 3 |
-
use arrow_array::builder::{
|
| 4 |
-
BinaryBuilder, BooleanBuilder, Float64Builder, Int64Builder, StringBuilder,
|
| 5 |
-
};
|
| 6 |
-
use arrow_array::{Array, RecordBatch, StructArray};
|
| 7 |
-
use arrow_ipc::writer::FileWriter as ArrowIpcFileWriter;
|
| 8 |
-
use arrow_schema::{DataType, Field, Fields, Schema};
|
| 9 |
-
use parquet::arrow::ArrowWriter;
|
| 10 |
-
use parquet::basic::Compression;
|
| 11 |
-
use parquet::file::properties::WriterProperties;
|
| 12 |
-
|
| 13 |
-
use crate::error::{DataLoaderError, Result};
|
| 14 |
-
|
| 15 |
-
#[derive(Debug, Clone, PartialEq)]
|
| 16 |
-
pub enum DataValue {
|
| 17 |
-
Null,
|
| 18 |
-
Text(String),
|
| 19 |
-
Bytes(Vec<u8>),
|
| 20 |
-
Json(String),
|
| 21 |
-
Int(i64),
|
| 22 |
-
Float(f64),
|
| 23 |
-
Bool(bool),
|
| 24 |
-
List(Vec<DataValue>),
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
pub fn classify_string_or_json(s: &str) -> DataValue {
|
| 28 |
-
let trimmed = s.trim();
|
| 29 |
-
if ((trimmed.starts_with('{') && trimmed.ends_with('}'))
|
| 30 |
-
|| (trimmed.starts_with('[') && trimmed.ends_with(']')))
|
| 31 |
-
&& serde_json::from_str::<serde_json::Value>(trimmed).is_ok()
|
| 32 |
-
{
|
| 33 |
-
return DataValue::Json(trimmed.to_owned());
|
| 34 |
-
}
|
| 35 |
-
DataValue::Text(s.to_owned())
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
pub fn build_arrow_record_batch(
|
| 39 |
-
columns: &[(String, Vec<DataValue>)],
|
| 40 |
-
hf_image_struct: bool,
|
| 41 |
-
) -> Result<(Arc<Schema>, RecordBatch)> {
|
| 42 |
-
if columns.is_empty() {
|
| 43 |
-
return Err(DataLoaderError::InvalidConfig(
|
| 44 |
-
"Cannot serialize empty column list".to_owned(),
|
| 45 |
-
));
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
let num_rows = columns[0].1.len();
|
| 49 |
-
let mut fields = Vec::with_capacity(columns.len());
|
| 50 |
-
let mut arrays: Vec<Arc<dyn Array>> = Vec::with_capacity(columns.len());
|
| 51 |
-
|
| 52 |
-
for (name, vals) in columns {
|
| 53 |
-
if vals.len() != num_rows {
|
| 54 |
-
return Err(DataLoaderError::InvalidConfig(format!(
|
| 55 |
-
"Column '{name}' length mismatch: expected {num_rows}, got {}",
|
| 56 |
-
vals.len()
|
| 57 |
-
)));
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
let has_non_null = vals.iter().any(|v| !matches!(v, DataValue::Null));
|
| 61 |
-
let is_binary = vals.iter().any(|v| matches!(v, DataValue::Bytes(_)));
|
| 62 |
-
let all_int = has_non_null
|
| 63 |
-
&& vals
|
| 64 |
-
.iter()
|
| 65 |
-
.all(|v| matches!(v, DataValue::Null | DataValue::Int(_)));
|
| 66 |
-
let has_float = vals.iter().any(|v| matches!(v, DataValue::Float(_)));
|
| 67 |
-
let all_numeric = has_non_null
|
| 68 |
-
&& has_float
|
| 69 |
-
&& vals
|
| 70 |
-
.iter()
|
| 71 |
-
.all(|v| matches!(v, DataValue::Null | DataValue::Int(_) | DataValue::Float(_)));
|
| 72 |
-
let all_bool = has_non_null
|
| 73 |
-
&& vals
|
| 74 |
-
.iter()
|
| 75 |
-
.all(|v| matches!(v, DataValue::Null | DataValue::Bool(_)));
|
| 76 |
-
|
| 77 |
-
if is_binary {
|
| 78 |
-
if hf_image_struct {
|
| 79 |
-
let mut bytes_builder = BinaryBuilder::with_capacity(num_rows, num_rows * 1024);
|
| 80 |
-
let mut path_builder = StringBuilder::with_capacity(num_rows, num_rows * 16);
|
| 81 |
-
|
| 82 |
-
for val in vals {
|
| 83 |
-
match val {
|
| 84 |
-
DataValue::Bytes(b) => {
|
| 85 |
-
bytes_builder.append_value(b);
|
| 86 |
-
path_builder.append_null();
|
| 87 |
-
}
|
| 88 |
-
DataValue::Text(t) => {
|
| 89 |
-
bytes_builder.append_null();
|
| 90 |
-
path_builder.append_value(t);
|
| 91 |
-
}
|
| 92 |
-
_ => {
|
| 93 |
-
bytes_builder.append_null();
|
| 94 |
-
path_builder.append_null();
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
let bytes_arr = Arc::new(bytes_builder.finish()) as Arc<dyn Array>;
|
| 100 |
-
let path_arr = Arc::new(path_builder.finish()) as Arc<dyn Array>;
|
| 101 |
-
|
| 102 |
-
let struct_fields = Fields::from(vec![
|
| 103 |
-
Field::new("bytes", DataType::Binary, true),
|
| 104 |
-
Field::new("path", DataType::Utf8, true),
|
| 105 |
-
]);
|
| 106 |
-
|
| 107 |
-
let struct_arr =
|
| 108 |
-
StructArray::new(struct_fields.clone(), vec![bytes_arr, path_arr], None);
|
| 109 |
-
|
| 110 |
-
fields.push(Field::new(name, DataType::Struct(struct_fields), true));
|
| 111 |
-
arrays.push(Arc::new(struct_arr));
|
| 112 |
-
} else {
|
| 113 |
-
let mut b_builder = BinaryBuilder::with_capacity(num_rows, num_rows * 1024);
|
| 114 |
-
for val in vals {
|
| 115 |
-
match val {
|
| 116 |
-
DataValue::Bytes(b) => b_builder.append_value(b),
|
| 117 |
-
_ => b_builder.append_null(),
|
| 118 |
-
}
|
| 119 |
-
}
|
| 120 |
-
fields.push(Field::new(name, DataType::Binary, true));
|
| 121 |
-
arrays.push(Arc::new(b_builder.finish()));
|
| 122 |
-
}
|
| 123 |
-
} else if all_int {
|
| 124 |
-
let mut i_builder = Int64Builder::with_capacity(num_rows);
|
| 125 |
-
for val in vals {
|
| 126 |
-
match val {
|
| 127 |
-
DataValue::Int(i) => i_builder.append_value(*i),
|
| 128 |
-
_ => i_builder.append_null(),
|
| 129 |
-
}
|
| 130 |
-
}
|
| 131 |
-
fields.push(Field::new(name, DataType::Int64, true));
|
| 132 |
-
arrays.push(Arc::new(i_builder.finish()));
|
| 133 |
-
} else if all_numeric {
|
| 134 |
-
let mut f_builder = Float64Builder::with_capacity(num_rows);
|
| 135 |
-
for val in vals {
|
| 136 |
-
match val {
|
| 137 |
-
DataValue::Float(f) => f_builder.append_value(*f),
|
| 138 |
-
DataValue::Int(i) => f_builder.append_value(*i as f64),
|
| 139 |
-
_ => f_builder.append_null(),
|
| 140 |
-
}
|
| 141 |
-
}
|
| 142 |
-
fields.push(Field::new(name, DataType::Float64, true));
|
| 143 |
-
arrays.push(Arc::new(f_builder.finish()));
|
| 144 |
-
} else if all_bool {
|
| 145 |
-
let mut b_builder = BooleanBuilder::with_capacity(num_rows);
|
| 146 |
-
for val in vals {
|
| 147 |
-
match val {
|
| 148 |
-
DataValue::Bool(b) => b_builder.append_value(*b),
|
| 149 |
-
_ => b_builder.append_null(),
|
| 150 |
-
}
|
| 151 |
-
}
|
| 152 |
-
fields.push(Field::new(name, DataType::Boolean, true));
|
| 153 |
-
arrays.push(Arc::new(b_builder.finish()));
|
| 154 |
-
} else {
|
| 155 |
-
let mut s_builder = StringBuilder::with_capacity(num_rows, num_rows * 64);
|
| 156 |
-
for val in vals {
|
| 157 |
-
match val {
|
| 158 |
-
DataValue::Null => s_builder.append_null(),
|
| 159 |
-
DataValue::Text(s) | DataValue::Json(s) => s_builder.append_value(s),
|
| 160 |
-
DataValue::Int(i) => s_builder.append_value(i.to_string()),
|
| 161 |
-
DataValue::Float(f) => s_builder.append_value(f.to_string()),
|
| 162 |
-
DataValue::Bool(b) => s_builder.append_value(b.to_string()),
|
| 163 |
-
DataValue::Bytes(b) => s_builder.append_value(format!("<{} bytes>", b.len())),
|
| 164 |
-
DataValue::List(l) => {
|
| 165 |
-
let json = serde_json::to_string(l).unwrap_or_default();
|
| 166 |
-
s_builder.append_value(json);
|
| 167 |
-
}
|
| 168 |
-
}
|
| 169 |
-
}
|
| 170 |
-
fields.push(Field::new(name, DataType::Utf8, true));
|
| 171 |
-
arrays.push(Arc::new(s_builder.finish()));
|
| 172 |
-
}
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
let schema = Arc::new(Schema::new(fields));
|
| 176 |
-
let batch = RecordBatch::try_new(schema.clone(), arrays)?;
|
| 177 |
-
Ok((schema, batch))
|
| 178 |
-
}
|
| 179 |
-
|
| 180 |
-
impl serde::Serialize for DataValue {
|
| 181 |
-
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
| 182 |
-
where
|
| 183 |
-
S: serde::Serializer,
|
| 184 |
-
{
|
| 185 |
-
match self {
|
| 186 |
-
DataValue::Null => serializer.serialize_none(),
|
| 187 |
-
DataValue::Text(s) => serializer.serialize_str(s),
|
| 188 |
-
DataValue::Bytes(b) => serializer.serialize_bytes(b),
|
| 189 |
-
DataValue::Json(j) => {
|
| 190 |
-
if let Ok(v) = serde_json::from_str::<serde_json::Value>(j) {
|
| 191 |
-
v.serialize(serializer)
|
| 192 |
-
} else {
|
| 193 |
-
serializer.serialize_str(j)
|
| 194 |
-
}
|
| 195 |
-
}
|
| 196 |
-
DataValue::Int(i) => serializer.serialize_i64(*i),
|
| 197 |
-
DataValue::Float(f) => serializer.serialize_f64(*f),
|
| 198 |
-
DataValue::Bool(b) => serializer.serialize_bool(*b),
|
| 199 |
-
DataValue::List(l) => l.serialize(serializer),
|
| 200 |
-
}
|
| 201 |
-
}
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
pub fn serialize_columns_to_parquet_bytes(
|
| 205 |
-
columns: &[(String, Vec<DataValue>)],
|
| 206 |
-
hf_image_struct: bool,
|
| 207 |
-
) -> Result<Vec<u8>> {
|
| 208 |
-
let (schema, batch) = build_arrow_record_batch(columns, hf_image_struct)?;
|
| 209 |
-
let mut out = Vec::new();
|
| 210 |
-
let props = WriterProperties::builder()
|
| 211 |
-
.set_compression(Compression::ZSTD(Default::default()))
|
| 212 |
-
.build();
|
| 213 |
-
let mut writer = ArrowWriter::try_new(&mut out, schema, Some(props))?;
|
| 214 |
-
writer.write(&batch)?;
|
| 215 |
-
writer.close()?;
|
| 216 |
-
Ok(out)
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
pub fn serialize_columns_to_arrow_ipc_bytes(
|
| 220 |
-
columns: &[(String, Vec<DataValue>)],
|
| 221 |
-
hf_image_struct: bool,
|
| 222 |
-
) -> Result<Vec<u8>> {
|
| 223 |
-
let (schema, batch) = build_arrow_record_batch(columns, hf_image_struct)?;
|
| 224 |
-
let mut out = Vec::new();
|
| 225 |
-
let mut writer = ArrowIpcFileWriter::try_new(&mut out, &schema)?;
|
| 226 |
-
writer.write(&batch)?;
|
| 227 |
-
writer.finish()?;
|
| 228 |
-
Ok(out)
|
| 229 |
-
}
|
| 230 |
-
|
| 231 |
-
pub fn serialize_columns_to_jsonl_bytes(columns: &[(String, Vec<DataValue>)]) -> Result<Vec<u8>> {
|
| 232 |
-
if columns.is_empty() {
|
| 233 |
-
return Ok(Vec::new());
|
| 234 |
-
}
|
| 235 |
-
let num_rows = columns[0].1.len();
|
| 236 |
-
let mut out = Vec::new();
|
| 237 |
-
for row_idx in 0..num_rows {
|
| 238 |
-
let mut map = serde_json::Map::with_capacity(columns.len());
|
| 239 |
-
for (col_name, vals) in columns {
|
| 240 |
-
let val = vals.get(row_idx).unwrap_or(&DataValue::Null);
|
| 241 |
-
let json_val = serde_json::to_value(val).unwrap_or(serde_json::Value::Null);
|
| 242 |
-
map.insert(col_name.clone(), json_val);
|
| 243 |
-
}
|
| 244 |
-
let line = serde_json::to_string(&map)
|
| 245 |
-
.map_err(|e| DataLoaderError::InvalidConfig(e.to_string()))?;
|
| 246 |
-
out.extend_from_slice(line.as_bytes());
|
| 247 |
-
out.push(b'\n');
|
| 248 |
-
}
|
| 249 |
-
Ok(out)
|
| 250 |
-
}
|
| 251 |
-
|
| 252 |
-
pub fn serialize_columns_by_format(
|
| 253 |
-
columns: &[(String, Vec<DataValue>)],
|
| 254 |
-
format: &str,
|
| 255 |
-
hf_image_struct: bool,
|
| 256 |
-
) -> Result<Vec<u8>> {
|
| 257 |
-
let lower = format.trim().to_ascii_lowercase();
|
| 258 |
-
if lower == "parquet" || lower.ends_with(".parquet") || lower.ends_with(".pq") {
|
| 259 |
-
serialize_columns_to_parquet_bytes(columns, hf_image_struct)
|
| 260 |
-
} else if lower == "arrow" || lower == "feather" || lower == "ipc" || lower.ends_with(".arrow")
|
| 261 |
-
{
|
| 262 |
-
serialize_columns_to_arrow_ipc_bytes(columns, hf_image_struct)
|
| 263 |
-
} else if lower == "jsonl" || lower == "ndjson" || lower.ends_with(".jsonl") {
|
| 264 |
-
serialize_columns_to_jsonl_bytes(columns)
|
| 265 |
-
} else {
|
| 266 |
-
serialize_columns_to_parquet_bytes(columns, hf_image_struct)
|
| 267 |
-
}
|
| 268 |
-
}
|
|
|
|
| 1 |
+
use std::sync::Arc;
|
| 2 |
+
|
| 3 |
+
use arrow_array::builder::{
|
| 4 |
+
BinaryBuilder, BooleanBuilder, Float64Builder, Int64Builder, StringBuilder,
|
| 5 |
+
};
|
| 6 |
+
use arrow_array::{Array, RecordBatch, StructArray};
|
| 7 |
+
use arrow_ipc::writer::FileWriter as ArrowIpcFileWriter;
|
| 8 |
+
use arrow_schema::{DataType, Field, Fields, Schema};
|
| 9 |
+
use parquet::arrow::ArrowWriter;
|
| 10 |
+
use parquet::basic::Compression;
|
| 11 |
+
use parquet::file::properties::WriterProperties;
|
| 12 |
+
|
| 13 |
+
use crate::error::{DataLoaderError, Result};
|
| 14 |
+
|
| 15 |
+
#[derive(Debug, Clone, PartialEq)]
|
| 16 |
+
pub enum DataValue {
|
| 17 |
+
Null,
|
| 18 |
+
Text(String),
|
| 19 |
+
Bytes(Vec<u8>),
|
| 20 |
+
Json(String),
|
| 21 |
+
Int(i64),
|
| 22 |
+
Float(f64),
|
| 23 |
+
Bool(bool),
|
| 24 |
+
List(Vec<DataValue>),
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
pub fn classify_string_or_json(s: &str) -> DataValue {
|
| 28 |
+
let trimmed = s.trim();
|
| 29 |
+
if ((trimmed.starts_with('{') && trimmed.ends_with('}'))
|
| 30 |
+
|| (trimmed.starts_with('[') && trimmed.ends_with(']')))
|
| 31 |
+
&& serde_json::from_str::<serde_json::Value>(trimmed).is_ok()
|
| 32 |
+
{
|
| 33 |
+
return DataValue::Json(trimmed.to_owned());
|
| 34 |
+
}
|
| 35 |
+
DataValue::Text(s.to_owned())
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
pub fn build_arrow_record_batch(
|
| 39 |
+
columns: &[(String, Vec<DataValue>)],
|
| 40 |
+
hf_image_struct: bool,
|
| 41 |
+
) -> Result<(Arc<Schema>, RecordBatch)> {
|
| 42 |
+
if columns.is_empty() {
|
| 43 |
+
return Err(DataLoaderError::InvalidConfig(
|
| 44 |
+
"Cannot serialize empty column list".to_owned(),
|
| 45 |
+
));
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
let num_rows = columns[0].1.len();
|
| 49 |
+
let mut fields = Vec::with_capacity(columns.len());
|
| 50 |
+
let mut arrays: Vec<Arc<dyn Array>> = Vec::with_capacity(columns.len());
|
| 51 |
+
|
| 52 |
+
for (name, vals) in columns {
|
| 53 |
+
if vals.len() != num_rows {
|
| 54 |
+
return Err(DataLoaderError::InvalidConfig(format!(
|
| 55 |
+
"Column '{name}' length mismatch: expected {num_rows}, got {}",
|
| 56 |
+
vals.len()
|
| 57 |
+
)));
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
let has_non_null = vals.iter().any(|v| !matches!(v, DataValue::Null));
|
| 61 |
+
let is_binary = vals.iter().any(|v| matches!(v, DataValue::Bytes(_)));
|
| 62 |
+
let all_int = has_non_null
|
| 63 |
+
&& vals
|
| 64 |
+
.iter()
|
| 65 |
+
.all(|v| matches!(v, DataValue::Null | DataValue::Int(_)));
|
| 66 |
+
let has_float = vals.iter().any(|v| matches!(v, DataValue::Float(_)));
|
| 67 |
+
let all_numeric = has_non_null
|
| 68 |
+
&& has_float
|
| 69 |
+
&& vals
|
| 70 |
+
.iter()
|
| 71 |
+
.all(|v| matches!(v, DataValue::Null | DataValue::Int(_) | DataValue::Float(_)));
|
| 72 |
+
let all_bool = has_non_null
|
| 73 |
+
&& vals
|
| 74 |
+
.iter()
|
| 75 |
+
.all(|v| matches!(v, DataValue::Null | DataValue::Bool(_)));
|
| 76 |
+
|
| 77 |
+
if is_binary {
|
| 78 |
+
if hf_image_struct {
|
| 79 |
+
let mut bytes_builder = BinaryBuilder::with_capacity(num_rows, num_rows * 1024);
|
| 80 |
+
let mut path_builder = StringBuilder::with_capacity(num_rows, num_rows * 16);
|
| 81 |
+
|
| 82 |
+
for val in vals {
|
| 83 |
+
match val {
|
| 84 |
+
DataValue::Bytes(b) => {
|
| 85 |
+
bytes_builder.append_value(b);
|
| 86 |
+
path_builder.append_null();
|
| 87 |
+
}
|
| 88 |
+
DataValue::Text(t) => {
|
| 89 |
+
bytes_builder.append_null();
|
| 90 |
+
path_builder.append_value(t);
|
| 91 |
+
}
|
| 92 |
+
_ => {
|
| 93 |
+
bytes_builder.append_null();
|
| 94 |
+
path_builder.append_null();
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
let bytes_arr = Arc::new(bytes_builder.finish()) as Arc<dyn Array>;
|
| 100 |
+
let path_arr = Arc::new(path_builder.finish()) as Arc<dyn Array>;
|
| 101 |
+
|
| 102 |
+
let struct_fields = Fields::from(vec![
|
| 103 |
+
Field::new("bytes", DataType::Binary, true),
|
| 104 |
+
Field::new("path", DataType::Utf8, true),
|
| 105 |
+
]);
|
| 106 |
+
|
| 107 |
+
let struct_arr =
|
| 108 |
+
StructArray::new(struct_fields.clone(), vec![bytes_arr, path_arr], None);
|
| 109 |
+
|
| 110 |
+
fields.push(Field::new(name, DataType::Struct(struct_fields), true));
|
| 111 |
+
arrays.push(Arc::new(struct_arr));
|
| 112 |
+
} else {
|
| 113 |
+
let mut b_builder = BinaryBuilder::with_capacity(num_rows, num_rows * 1024);
|
| 114 |
+
for val in vals {
|
| 115 |
+
match val {
|
| 116 |
+
DataValue::Bytes(b) => b_builder.append_value(b),
|
| 117 |
+
_ => b_builder.append_null(),
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
fields.push(Field::new(name, DataType::Binary, true));
|
| 121 |
+
arrays.push(Arc::new(b_builder.finish()));
|
| 122 |
+
}
|
| 123 |
+
} else if all_int {
|
| 124 |
+
let mut i_builder = Int64Builder::with_capacity(num_rows);
|
| 125 |
+
for val in vals {
|
| 126 |
+
match val {
|
| 127 |
+
DataValue::Int(i) => i_builder.append_value(*i),
|
| 128 |
+
_ => i_builder.append_null(),
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
fields.push(Field::new(name, DataType::Int64, true));
|
| 132 |
+
arrays.push(Arc::new(i_builder.finish()));
|
| 133 |
+
} else if all_numeric {
|
| 134 |
+
let mut f_builder = Float64Builder::with_capacity(num_rows);
|
| 135 |
+
for val in vals {
|
| 136 |
+
match val {
|
| 137 |
+
DataValue::Float(f) => f_builder.append_value(*f),
|
| 138 |
+
DataValue::Int(i) => f_builder.append_value(*i as f64),
|
| 139 |
+
_ => f_builder.append_null(),
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
fields.push(Field::new(name, DataType::Float64, true));
|
| 143 |
+
arrays.push(Arc::new(f_builder.finish()));
|
| 144 |
+
} else if all_bool {
|
| 145 |
+
let mut b_builder = BooleanBuilder::with_capacity(num_rows);
|
| 146 |
+
for val in vals {
|
| 147 |
+
match val {
|
| 148 |
+
DataValue::Bool(b) => b_builder.append_value(*b),
|
| 149 |
+
_ => b_builder.append_null(),
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
fields.push(Field::new(name, DataType::Boolean, true));
|
| 153 |
+
arrays.push(Arc::new(b_builder.finish()));
|
| 154 |
+
} else {
|
| 155 |
+
let mut s_builder = StringBuilder::with_capacity(num_rows, num_rows * 64);
|
| 156 |
+
for val in vals {
|
| 157 |
+
match val {
|
| 158 |
+
DataValue::Null => s_builder.append_null(),
|
| 159 |
+
DataValue::Text(s) | DataValue::Json(s) => s_builder.append_value(s),
|
| 160 |
+
DataValue::Int(i) => s_builder.append_value(i.to_string()),
|
| 161 |
+
DataValue::Float(f) => s_builder.append_value(f.to_string()),
|
| 162 |
+
DataValue::Bool(b) => s_builder.append_value(b.to_string()),
|
| 163 |
+
DataValue::Bytes(b) => s_builder.append_value(format!("<{} bytes>", b.len())),
|
| 164 |
+
DataValue::List(l) => {
|
| 165 |
+
let json = serde_json::to_string(l).unwrap_or_default();
|
| 166 |
+
s_builder.append_value(json);
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
fields.push(Field::new(name, DataType::Utf8, true));
|
| 171 |
+
arrays.push(Arc::new(s_builder.finish()));
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
let schema = Arc::new(Schema::new(fields));
|
| 176 |
+
let batch = RecordBatch::try_new(schema.clone(), arrays)?;
|
| 177 |
+
Ok((schema, batch))
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
impl serde::Serialize for DataValue {
|
| 181 |
+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
| 182 |
+
where
|
| 183 |
+
S: serde::Serializer,
|
| 184 |
+
{
|
| 185 |
+
match self {
|
| 186 |
+
DataValue::Null => serializer.serialize_none(),
|
| 187 |
+
DataValue::Text(s) => serializer.serialize_str(s),
|
| 188 |
+
DataValue::Bytes(b) => serializer.serialize_bytes(b),
|
| 189 |
+
DataValue::Json(j) => {
|
| 190 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(j) {
|
| 191 |
+
v.serialize(serializer)
|
| 192 |
+
} else {
|
| 193 |
+
serializer.serialize_str(j)
|
| 194 |
+
}
|
| 195 |
+
}
|
| 196 |
+
DataValue::Int(i) => serializer.serialize_i64(*i),
|
| 197 |
+
DataValue::Float(f) => serializer.serialize_f64(*f),
|
| 198 |
+
DataValue::Bool(b) => serializer.serialize_bool(*b),
|
| 199 |
+
DataValue::List(l) => l.serialize(serializer),
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
pub fn serialize_columns_to_parquet_bytes(
|
| 205 |
+
columns: &[(String, Vec<DataValue>)],
|
| 206 |
+
hf_image_struct: bool,
|
| 207 |
+
) -> Result<Vec<u8>> {
|
| 208 |
+
let (schema, batch) = build_arrow_record_batch(columns, hf_image_struct)?;
|
| 209 |
+
let mut out = Vec::new();
|
| 210 |
+
let props = WriterProperties::builder()
|
| 211 |
+
.set_compression(Compression::ZSTD(Default::default()))
|
| 212 |
+
.build();
|
| 213 |
+
let mut writer = ArrowWriter::try_new(&mut out, schema, Some(props))?;
|
| 214 |
+
writer.write(&batch)?;
|
| 215 |
+
writer.close()?;
|
| 216 |
+
Ok(out)
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
pub fn serialize_columns_to_arrow_ipc_bytes(
|
| 220 |
+
columns: &[(String, Vec<DataValue>)],
|
| 221 |
+
hf_image_struct: bool,
|
| 222 |
+
) -> Result<Vec<u8>> {
|
| 223 |
+
let (schema, batch) = build_arrow_record_batch(columns, hf_image_struct)?;
|
| 224 |
+
let mut out = Vec::new();
|
| 225 |
+
let mut writer = ArrowIpcFileWriter::try_new(&mut out, &schema)?;
|
| 226 |
+
writer.write(&batch)?;
|
| 227 |
+
writer.finish()?;
|
| 228 |
+
Ok(out)
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
pub fn serialize_columns_to_jsonl_bytes(columns: &[(String, Vec<DataValue>)]) -> Result<Vec<u8>> {
|
| 232 |
+
if columns.is_empty() {
|
| 233 |
+
return Ok(Vec::new());
|
| 234 |
+
}
|
| 235 |
+
let num_rows = columns[0].1.len();
|
| 236 |
+
let mut out = Vec::new();
|
| 237 |
+
for row_idx in 0..num_rows {
|
| 238 |
+
let mut map = serde_json::Map::with_capacity(columns.len());
|
| 239 |
+
for (col_name, vals) in columns {
|
| 240 |
+
let val = vals.get(row_idx).unwrap_or(&DataValue::Null);
|
| 241 |
+
let json_val = serde_json::to_value(val).unwrap_or(serde_json::Value::Null);
|
| 242 |
+
map.insert(col_name.clone(), json_val);
|
| 243 |
+
}
|
| 244 |
+
let line = serde_json::to_string(&map)
|
| 245 |
+
.map_err(|e| DataLoaderError::InvalidConfig(e.to_string()))?;
|
| 246 |
+
out.extend_from_slice(line.as_bytes());
|
| 247 |
+
out.push(b'\n');
|
| 248 |
+
}
|
| 249 |
+
Ok(out)
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
pub fn serialize_columns_by_format(
|
| 253 |
+
columns: &[(String, Vec<DataValue>)],
|
| 254 |
+
format: &str,
|
| 255 |
+
hf_image_struct: bool,
|
| 256 |
+
) -> Result<Vec<u8>> {
|
| 257 |
+
let lower = format.trim().to_ascii_lowercase();
|
| 258 |
+
if lower == "parquet" || lower.ends_with(".parquet") || lower.ends_with(".pq") {
|
| 259 |
+
serialize_columns_to_parquet_bytes(columns, hf_image_struct)
|
| 260 |
+
} else if lower == "arrow" || lower == "feather" || lower == "ipc" || lower.ends_with(".arrow")
|
| 261 |
+
{
|
| 262 |
+
serialize_columns_to_arrow_ipc_bytes(columns, hf_image_struct)
|
| 263 |
+
} else if lower == "jsonl" || lower == "ndjson" || lower.ends_with(".jsonl") {
|
| 264 |
+
serialize_columns_to_jsonl_bytes(columns)
|
| 265 |
+
} else {
|
| 266 |
+
serialize_columns_to_parquet_bytes(columns, hf_image_struct)
|
| 267 |
+
}
|
| 268 |
+
}
|
data/src/simd_cpu.rs
CHANGED
|
@@ -1,313 +1,313 @@
|
|
| 1 |
-
use std::sync::OnceLock;
|
| 2 |
-
|
| 3 |
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 4 |
-
pub enum CpuIsa {
|
| 5 |
-
Avx512,
|
| 6 |
-
Avx2,
|
| 7 |
-
Neon,
|
| 8 |
-
Scalar,
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
impl CpuIsa {
|
| 12 |
-
pub fn as_str(self) -> &'static str {
|
| 13 |
-
match self {
|
| 14 |
-
Self::Avx512 => "AVX-512",
|
| 15 |
-
Self::Avx2 => "AVX2",
|
| 16 |
-
Self::Neon => "NEON",
|
| 17 |
-
Self::Scalar => "SCALAR",
|
| 18 |
-
}
|
| 19 |
-
}
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
#[repr(align(64))]
|
| 23 |
-
#[derive(Debug, Clone)]
|
| 24 |
-
pub struct CacheAlignedBuffer {
|
| 25 |
-
pub data: Vec<u8>,
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
impl CacheAlignedBuffer {
|
| 29 |
-
pub fn with_capacity(cap: usize) -> Self {
|
| 30 |
-
let aligned_cap = (cap + 63) & !63;
|
| 31 |
-
Self {
|
| 32 |
-
data: Vec::with_capacity(aligned_cap),
|
| 33 |
-
}
|
| 34 |
-
}
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
#[inline]
|
| 38 |
-
pub fn detect_cpu_isa() -> CpuIsa {
|
| 39 |
-
static CACHED_ISA: OnceLock<CpuIsa> = OnceLock::new();
|
| 40 |
-
*CACHED_ISA.get_or_init(|| {
|
| 41 |
-
std::cfg_select! {
|
| 42 |
-
any(target_arch = "x86", target_arch = "x86_64") => {
|
| 43 |
-
if std::arch::is_x86_feature_detected!("avx512f")
|
| 44 |
-
&& std::arch::is_x86_feature_detected!("avx512bw")
|
| 45 |
-
{
|
| 46 |
-
CpuIsa::Avx512
|
| 47 |
-
} else if std::arch::is_x86_feature_detected!("avx2") {
|
| 48 |
-
CpuIsa::Avx2
|
| 49 |
-
} else {
|
| 50 |
-
CpuIsa::Scalar
|
| 51 |
-
}
|
| 52 |
-
}
|
| 53 |
-
target_arch = "aarch64" => {
|
| 54 |
-
if std::arch::is_aarch64_feature_detected!("neon") {
|
| 55 |
-
CpuIsa::Neon
|
| 56 |
-
} else {
|
| 57 |
-
CpuIsa::Scalar
|
| 58 |
-
}
|
| 59 |
-
}
|
| 60 |
-
_ => {
|
| 61 |
-
CpuIsa::Scalar
|
| 62 |
-
}
|
| 63 |
-
}
|
| 64 |
-
})
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
#[inline]
|
| 68 |
-
pub fn find_byte_fast(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 69 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 70 |
-
{
|
| 71 |
-
match detect_cpu_isa() {
|
| 72 |
-
CpuIsa::Avx512 => return unsafe { find_byte_avx512(haystack, needle) },
|
| 73 |
-
CpuIsa::Avx2 => return unsafe { find_byte_avx2(haystack, needle) },
|
| 74 |
-
_ => {}
|
| 75 |
-
}
|
| 76 |
-
}
|
| 77 |
-
#[cfg(target_arch = "aarch64")]
|
| 78 |
-
{
|
| 79 |
-
if detect_cpu_isa() == CpuIsa::Neon {
|
| 80 |
-
return unsafe { find_byte_neon(haystack, needle) };
|
| 81 |
-
}
|
| 82 |
-
}
|
| 83 |
-
haystack.iter().position(|&b| b == needle)
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
#[inline]
|
| 87 |
-
pub fn rfind_byte_fast(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 88 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 89 |
-
{
|
| 90 |
-
match detect_cpu_isa() {
|
| 91 |
-
CpuIsa::Avx512 => return unsafe { rfind_byte_avx512(haystack, needle) },
|
| 92 |
-
CpuIsa::Avx2 => return unsafe { rfind_byte_avx2(haystack, needle) },
|
| 93 |
-
_ => {}
|
| 94 |
-
}
|
| 95 |
-
}
|
| 96 |
-
#[cfg(target_arch = "aarch64")]
|
| 97 |
-
{
|
| 98 |
-
if detect_cpu_isa() == CpuIsa::Neon {
|
| 99 |
-
return unsafe { rfind_byte_neon(haystack, needle) };
|
| 100 |
-
}
|
| 101 |
-
}
|
| 102 |
-
haystack.iter().rposition(|&b| b == needle)
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
#[inline]
|
| 106 |
-
pub fn contains_byte_fast(haystack: &[u8], needle: u8) -> bool {
|
| 107 |
-
find_byte_fast(haystack, needle).is_some()
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
#[inline]
|
| 111 |
-
pub fn find_newline_fast(haystack: &[u8]) -> Option<usize> {
|
| 112 |
-
find_byte_fast(haystack, b'\n')
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
#[inline]
|
| 116 |
-
pub fn rfind_newline_fast(haystack: &[u8]) -> Option<usize> {
|
| 117 |
-
rfind_byte_fast(haystack, b'\n')
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 121 |
-
#[target_feature(enable = "avx512f,avx512bw")]
|
| 122 |
-
unsafe fn find_byte_avx512(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 123 |
-
#[cfg(target_arch = "x86")]
|
| 124 |
-
use std::arch::x86::*;
|
| 125 |
-
#[cfg(target_arch = "x86_64")]
|
| 126 |
-
use std::arch::x86_64::*;
|
| 127 |
-
|
| 128 |
-
let len = haystack.len();
|
| 129 |
-
let ptr = haystack.as_ptr();
|
| 130 |
-
let v_needle = _mm512_set1_epi8(needle as i8);
|
| 131 |
-
let mut offset = 0usize;
|
| 132 |
-
|
| 133 |
-
while offset + 64 <= len {
|
| 134 |
-
let chunk = unsafe { _mm512_loadu_si512(ptr.add(offset).cast()) };
|
| 135 |
-
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 136 |
-
if mask != 0 {
|
| 137 |
-
return Some(offset + mask.trailing_zeros() as usize);
|
| 138 |
-
}
|
| 139 |
-
offset += 64;
|
| 140 |
-
}
|
| 141 |
-
|
| 142 |
-
while offset < len {
|
| 143 |
-
if unsafe { *ptr.add(offset) } == needle {
|
| 144 |
-
return Some(offset);
|
| 145 |
-
}
|
| 146 |
-
offset += 1;
|
| 147 |
-
}
|
| 148 |
-
None
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 152 |
-
#[target_feature(enable = "avx512f,avx512bw")]
|
| 153 |
-
unsafe fn rfind_byte_avx512(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 154 |
-
#[cfg(target_arch = "x86")]
|
| 155 |
-
use std::arch::x86::*;
|
| 156 |
-
#[cfg(target_arch = "x86_64")]
|
| 157 |
-
use std::arch::x86_64::*;
|
| 158 |
-
|
| 159 |
-
let len = haystack.len();
|
| 160 |
-
let ptr = haystack.as_ptr();
|
| 161 |
-
let v_needle = _mm512_set1_epi8(needle as i8);
|
| 162 |
-
let mut end = len;
|
| 163 |
-
|
| 164 |
-
while end >= 64 {
|
| 165 |
-
let start = end - 64;
|
| 166 |
-
let chunk = unsafe { _mm512_loadu_si512(ptr.add(start).cast()) };
|
| 167 |
-
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 168 |
-
if mask != 0 {
|
| 169 |
-
let highest_bit = 63 - mask.leading_zeros() as usize;
|
| 170 |
-
return Some(start + highest_bit);
|
| 171 |
-
}
|
| 172 |
-
end = start;
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
while end > 0 {
|
| 176 |
-
end -= 1;
|
| 177 |
-
if unsafe { *ptr.add(end) } == needle {
|
| 178 |
-
return Some(end);
|
| 179 |
-
}
|
| 180 |
-
}
|
| 181 |
-
None
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 185 |
-
#[target_feature(enable = "avx2")]
|
| 186 |
-
unsafe fn find_byte_avx2(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 187 |
-
#[cfg(target_arch = "x86")]
|
| 188 |
-
use std::arch::x86::*;
|
| 189 |
-
#[cfg(target_arch = "x86_64")]
|
| 190 |
-
use std::arch::x86_64::*;
|
| 191 |
-
|
| 192 |
-
let len = haystack.len();
|
| 193 |
-
let ptr = haystack.as_ptr();
|
| 194 |
-
let v_needle = _mm256_set1_epi8(needle as i8);
|
| 195 |
-
let mut offset = 0usize;
|
| 196 |
-
|
| 197 |
-
while offset + 32 <= len {
|
| 198 |
-
let chunk = unsafe { _mm256_loadu_si256(ptr.add(offset).cast()) };
|
| 199 |
-
let cmp = _mm256_cmpeq_epi8(chunk, v_needle);
|
| 200 |
-
let mask = _mm256_movemask_epi8(cmp) as u32;
|
| 201 |
-
if mask != 0 {
|
| 202 |
-
return Some(offset + mask.trailing_zeros() as usize);
|
| 203 |
-
}
|
| 204 |
-
offset += 32;
|
| 205 |
-
}
|
| 206 |
-
|
| 207 |
-
while offset < len {
|
| 208 |
-
if unsafe { *ptr.add(offset) } == needle {
|
| 209 |
-
return Some(offset);
|
| 210 |
-
}
|
| 211 |
-
offset += 1;
|
| 212 |
-
}
|
| 213 |
-
None
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 217 |
-
#[target_feature(enable = "avx2")]
|
| 218 |
-
unsafe fn rfind_byte_avx2(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 219 |
-
#[cfg(target_arch = "x86")]
|
| 220 |
-
use std::arch::x86::*;
|
| 221 |
-
#[cfg(target_arch = "x86_64")]
|
| 222 |
-
use std::arch::x86_64::*;
|
| 223 |
-
|
| 224 |
-
let len = haystack.len();
|
| 225 |
-
let ptr = haystack.as_ptr();
|
| 226 |
-
let v_needle = _mm256_set1_epi8(needle as i8);
|
| 227 |
-
let mut end = len;
|
| 228 |
-
|
| 229 |
-
while end >= 32 {
|
| 230 |
-
let start = end - 32;
|
| 231 |
-
let chunk = unsafe { _mm256_loadu_si256(ptr.add(start).cast()) };
|
| 232 |
-
let cmp = _mm256_cmpeq_epi8(chunk, v_needle);
|
| 233 |
-
let mask = _mm256_movemask_epi8(cmp) as u32;
|
| 234 |
-
if mask != 0 {
|
| 235 |
-
let highest_bit = 31 - mask.leading_zeros() as usize;
|
| 236 |
-
return Some(start + highest_bit);
|
| 237 |
-
}
|
| 238 |
-
end = start;
|
| 239 |
-
}
|
| 240 |
-
|
| 241 |
-
while end > 0 {
|
| 242 |
-
end -= 1;
|
| 243 |
-
if unsafe { *ptr.add(end) } == needle {
|
| 244 |
-
return Some(end);
|
| 245 |
-
}
|
| 246 |
-
}
|
| 247 |
-
None
|
| 248 |
-
}
|
| 249 |
-
|
| 250 |
-
#[cfg(target_arch = "aarch64")]
|
| 251 |
-
#[target_feature(enable = "neon")]
|
| 252 |
-
unsafe fn find_byte_neon(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 253 |
-
use std::arch::aarch64::*;
|
| 254 |
-
|
| 255 |
-
let len = haystack.len();
|
| 256 |
-
let ptr = haystack.as_ptr();
|
| 257 |
-
let v_needle = vdupq_n_u8(needle);
|
| 258 |
-
let mut offset = 0usize;
|
| 259 |
-
|
| 260 |
-
while offset + 16 <= len {
|
| 261 |
-
let chunk = unsafe { vld1q_u8(ptr.add(offset)) };
|
| 262 |
-
let cmp = vceqq_u8(chunk, v_needle);
|
| 263 |
-
if vmaxvq_u8(cmp) != 0 {
|
| 264 |
-
for lane in 0..16 {
|
| 265 |
-
if unsafe { *ptr.add(offset + lane) } == needle {
|
| 266 |
-
return Some(offset + lane);
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
}
|
| 270 |
-
offset += 16;
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
while offset < len {
|
| 274 |
-
if unsafe { *ptr.add(offset) } == needle {
|
| 275 |
-
return Some(offset);
|
| 276 |
-
}
|
| 277 |
-
offset += 1;
|
| 278 |
-
}
|
| 279 |
-
None
|
| 280 |
-
}
|
| 281 |
-
|
| 282 |
-
#[cfg(target_arch = "aarch64")]
|
| 283 |
-
#[target_feature(enable = "neon")]
|
| 284 |
-
unsafe fn rfind_byte_neon(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 285 |
-
use std::arch::aarch64::*;
|
| 286 |
-
|
| 287 |
-
let len = haystack.len();
|
| 288 |
-
let ptr = haystack.as_ptr();
|
| 289 |
-
let v_needle = vdupq_n_u8(needle);
|
| 290 |
-
let mut end = len;
|
| 291 |
-
|
| 292 |
-
while end >= 16 {
|
| 293 |
-
let start = end - 16;
|
| 294 |
-
let chunk = unsafe { vld1q_u8(ptr.add(start)) };
|
| 295 |
-
let cmp = vceqq_u8(chunk, v_needle);
|
| 296 |
-
if vmaxvq_u8(cmp) != 0 {
|
| 297 |
-
for lane in (0..16).rev() {
|
| 298 |
-
if unsafe { *ptr.add(start + lane) } == needle {
|
| 299 |
-
return Some(start + lane);
|
| 300 |
-
}
|
| 301 |
-
}
|
| 302 |
-
}
|
| 303 |
-
end = start;
|
| 304 |
-
}
|
| 305 |
-
|
| 306 |
-
while end > 0 {
|
| 307 |
-
end -= 1;
|
| 308 |
-
if unsafe { *ptr.add(end) } == needle {
|
| 309 |
-
return Some(end);
|
| 310 |
-
}
|
| 311 |
-
}
|
| 312 |
-
None
|
| 313 |
-
}
|
|
|
|
| 1 |
+
use std::sync::OnceLock;
|
| 2 |
+
|
| 3 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 4 |
+
pub enum CpuIsa {
|
| 5 |
+
Avx512,
|
| 6 |
+
Avx2,
|
| 7 |
+
Neon,
|
| 8 |
+
Scalar,
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
impl CpuIsa {
|
| 12 |
+
pub fn as_str(self) -> &'static str {
|
| 13 |
+
match self {
|
| 14 |
+
Self::Avx512 => "AVX-512",
|
| 15 |
+
Self::Avx2 => "AVX2",
|
| 16 |
+
Self::Neon => "NEON",
|
| 17 |
+
Self::Scalar => "SCALAR",
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
#[repr(align(64))]
|
| 23 |
+
#[derive(Debug, Clone)]
|
| 24 |
+
pub struct CacheAlignedBuffer {
|
| 25 |
+
pub data: Vec<u8>,
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
impl CacheAlignedBuffer {
|
| 29 |
+
pub fn with_capacity(cap: usize) -> Self {
|
| 30 |
+
let aligned_cap = (cap + 63) & !63;
|
| 31 |
+
Self {
|
| 32 |
+
data: Vec::with_capacity(aligned_cap),
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
#[inline]
|
| 38 |
+
pub fn detect_cpu_isa() -> CpuIsa {
|
| 39 |
+
static CACHED_ISA: OnceLock<CpuIsa> = OnceLock::new();
|
| 40 |
+
*CACHED_ISA.get_or_init(|| {
|
| 41 |
+
std::cfg_select! {
|
| 42 |
+
any(target_arch = "x86", target_arch = "x86_64") => {
|
| 43 |
+
if std::arch::is_x86_feature_detected!("avx512f")
|
| 44 |
+
&& std::arch::is_x86_feature_detected!("avx512bw")
|
| 45 |
+
{
|
| 46 |
+
CpuIsa::Avx512
|
| 47 |
+
} else if std::arch::is_x86_feature_detected!("avx2") {
|
| 48 |
+
CpuIsa::Avx2
|
| 49 |
+
} else {
|
| 50 |
+
CpuIsa::Scalar
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
target_arch = "aarch64" => {
|
| 54 |
+
if std::arch::is_aarch64_feature_detected!("neon") {
|
| 55 |
+
CpuIsa::Neon
|
| 56 |
+
} else {
|
| 57 |
+
CpuIsa::Scalar
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
_ => {
|
| 61 |
+
CpuIsa::Scalar
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
})
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
#[inline]
|
| 68 |
+
pub fn find_byte_fast(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 69 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 70 |
+
{
|
| 71 |
+
match detect_cpu_isa() {
|
| 72 |
+
CpuIsa::Avx512 => return unsafe { find_byte_avx512(haystack, needle) },
|
| 73 |
+
CpuIsa::Avx2 => return unsafe { find_byte_avx2(haystack, needle) },
|
| 74 |
+
_ => {}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
#[cfg(target_arch = "aarch64")]
|
| 78 |
+
{
|
| 79 |
+
if detect_cpu_isa() == CpuIsa::Neon {
|
| 80 |
+
return unsafe { find_byte_neon(haystack, needle) };
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
haystack.iter().position(|&b| b == needle)
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
#[inline]
|
| 87 |
+
pub fn rfind_byte_fast(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 88 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 89 |
+
{
|
| 90 |
+
match detect_cpu_isa() {
|
| 91 |
+
CpuIsa::Avx512 => return unsafe { rfind_byte_avx512(haystack, needle) },
|
| 92 |
+
CpuIsa::Avx2 => return unsafe { rfind_byte_avx2(haystack, needle) },
|
| 93 |
+
_ => {}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
#[cfg(target_arch = "aarch64")]
|
| 97 |
+
{
|
| 98 |
+
if detect_cpu_isa() == CpuIsa::Neon {
|
| 99 |
+
return unsafe { rfind_byte_neon(haystack, needle) };
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
haystack.iter().rposition(|&b| b == needle)
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
#[inline]
|
| 106 |
+
pub fn contains_byte_fast(haystack: &[u8], needle: u8) -> bool {
|
| 107 |
+
find_byte_fast(haystack, needle).is_some()
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
#[inline]
|
| 111 |
+
pub fn find_newline_fast(haystack: &[u8]) -> Option<usize> {
|
| 112 |
+
find_byte_fast(haystack, b'\n')
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
#[inline]
|
| 116 |
+
pub fn rfind_newline_fast(haystack: &[u8]) -> Option<usize> {
|
| 117 |
+
rfind_byte_fast(haystack, b'\n')
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 121 |
+
#[target_feature(enable = "avx512f,avx512bw")]
|
| 122 |
+
unsafe fn find_byte_avx512(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 123 |
+
#[cfg(target_arch = "x86")]
|
| 124 |
+
use std::arch::x86::*;
|
| 125 |
+
#[cfg(target_arch = "x86_64")]
|
| 126 |
+
use std::arch::x86_64::*;
|
| 127 |
+
|
| 128 |
+
let len = haystack.len();
|
| 129 |
+
let ptr = haystack.as_ptr();
|
| 130 |
+
let v_needle = _mm512_set1_epi8(needle as i8);
|
| 131 |
+
let mut offset = 0usize;
|
| 132 |
+
|
| 133 |
+
while offset + 64 <= len {
|
| 134 |
+
let chunk = unsafe { _mm512_loadu_si512(ptr.add(offset).cast()) };
|
| 135 |
+
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 136 |
+
if mask != 0 {
|
| 137 |
+
return Some(offset + mask.trailing_zeros() as usize);
|
| 138 |
+
}
|
| 139 |
+
offset += 64;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
while offset < len {
|
| 143 |
+
if unsafe { *ptr.add(offset) } == needle {
|
| 144 |
+
return Some(offset);
|
| 145 |
+
}
|
| 146 |
+
offset += 1;
|
| 147 |
+
}
|
| 148 |
+
None
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 152 |
+
#[target_feature(enable = "avx512f,avx512bw")]
|
| 153 |
+
unsafe fn rfind_byte_avx512(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 154 |
+
#[cfg(target_arch = "x86")]
|
| 155 |
+
use std::arch::x86::*;
|
| 156 |
+
#[cfg(target_arch = "x86_64")]
|
| 157 |
+
use std::arch::x86_64::*;
|
| 158 |
+
|
| 159 |
+
let len = haystack.len();
|
| 160 |
+
let ptr = haystack.as_ptr();
|
| 161 |
+
let v_needle = _mm512_set1_epi8(needle as i8);
|
| 162 |
+
let mut end = len;
|
| 163 |
+
|
| 164 |
+
while end >= 64 {
|
| 165 |
+
let start = end - 64;
|
| 166 |
+
let chunk = unsafe { _mm512_loadu_si512(ptr.add(start).cast()) };
|
| 167 |
+
let mask = _mm512_cmpeq_epi8_mask(chunk, v_needle);
|
| 168 |
+
if mask != 0 {
|
| 169 |
+
let highest_bit = 63 - mask.leading_zeros() as usize;
|
| 170 |
+
return Some(start + highest_bit);
|
| 171 |
+
}
|
| 172 |
+
end = start;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
while end > 0 {
|
| 176 |
+
end -= 1;
|
| 177 |
+
if unsafe { *ptr.add(end) } == needle {
|
| 178 |
+
return Some(end);
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
None
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 185 |
+
#[target_feature(enable = "avx2")]
|
| 186 |
+
unsafe fn find_byte_avx2(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 187 |
+
#[cfg(target_arch = "x86")]
|
| 188 |
+
use std::arch::x86::*;
|
| 189 |
+
#[cfg(target_arch = "x86_64")]
|
| 190 |
+
use std::arch::x86_64::*;
|
| 191 |
+
|
| 192 |
+
let len = haystack.len();
|
| 193 |
+
let ptr = haystack.as_ptr();
|
| 194 |
+
let v_needle = _mm256_set1_epi8(needle as i8);
|
| 195 |
+
let mut offset = 0usize;
|
| 196 |
+
|
| 197 |
+
while offset + 32 <= len {
|
| 198 |
+
let chunk = unsafe { _mm256_loadu_si256(ptr.add(offset).cast()) };
|
| 199 |
+
let cmp = _mm256_cmpeq_epi8(chunk, v_needle);
|
| 200 |
+
let mask = _mm256_movemask_epi8(cmp) as u32;
|
| 201 |
+
if mask != 0 {
|
| 202 |
+
return Some(offset + mask.trailing_zeros() as usize);
|
| 203 |
+
}
|
| 204 |
+
offset += 32;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
while offset < len {
|
| 208 |
+
if unsafe { *ptr.add(offset) } == needle {
|
| 209 |
+
return Some(offset);
|
| 210 |
+
}
|
| 211 |
+
offset += 1;
|
| 212 |
+
}
|
| 213 |
+
None
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
| 217 |
+
#[target_feature(enable = "avx2")]
|
| 218 |
+
unsafe fn rfind_byte_avx2(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 219 |
+
#[cfg(target_arch = "x86")]
|
| 220 |
+
use std::arch::x86::*;
|
| 221 |
+
#[cfg(target_arch = "x86_64")]
|
| 222 |
+
use std::arch::x86_64::*;
|
| 223 |
+
|
| 224 |
+
let len = haystack.len();
|
| 225 |
+
let ptr = haystack.as_ptr();
|
| 226 |
+
let v_needle = _mm256_set1_epi8(needle as i8);
|
| 227 |
+
let mut end = len;
|
| 228 |
+
|
| 229 |
+
while end >= 32 {
|
| 230 |
+
let start = end - 32;
|
| 231 |
+
let chunk = unsafe { _mm256_loadu_si256(ptr.add(start).cast()) };
|
| 232 |
+
let cmp = _mm256_cmpeq_epi8(chunk, v_needle);
|
| 233 |
+
let mask = _mm256_movemask_epi8(cmp) as u32;
|
| 234 |
+
if mask != 0 {
|
| 235 |
+
let highest_bit = 31 - mask.leading_zeros() as usize;
|
| 236 |
+
return Some(start + highest_bit);
|
| 237 |
+
}
|
| 238 |
+
end = start;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
while end > 0 {
|
| 242 |
+
end -= 1;
|
| 243 |
+
if unsafe { *ptr.add(end) } == needle {
|
| 244 |
+
return Some(end);
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
None
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
#[cfg(target_arch = "aarch64")]
|
| 251 |
+
#[target_feature(enable = "neon")]
|
| 252 |
+
unsafe fn find_byte_neon(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 253 |
+
use std::arch::aarch64::*;
|
| 254 |
+
|
| 255 |
+
let len = haystack.len();
|
| 256 |
+
let ptr = haystack.as_ptr();
|
| 257 |
+
let v_needle = vdupq_n_u8(needle);
|
| 258 |
+
let mut offset = 0usize;
|
| 259 |
+
|
| 260 |
+
while offset + 16 <= len {
|
| 261 |
+
let chunk = unsafe { vld1q_u8(ptr.add(offset)) };
|
| 262 |
+
let cmp = vceqq_u8(chunk, v_needle);
|
| 263 |
+
if vmaxvq_u8(cmp) != 0 {
|
| 264 |
+
for lane in 0..16 {
|
| 265 |
+
if unsafe { *ptr.add(offset + lane) } == needle {
|
| 266 |
+
return Some(offset + lane);
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
}
|
| 270 |
+
offset += 16;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
while offset < len {
|
| 274 |
+
if unsafe { *ptr.add(offset) } == needle {
|
| 275 |
+
return Some(offset);
|
| 276 |
+
}
|
| 277 |
+
offset += 1;
|
| 278 |
+
}
|
| 279 |
+
None
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
#[cfg(target_arch = "aarch64")]
|
| 283 |
+
#[target_feature(enable = "neon")]
|
| 284 |
+
unsafe fn rfind_byte_neon(haystack: &[u8], needle: u8) -> Option<usize> {
|
| 285 |
+
use std::arch::aarch64::*;
|
| 286 |
+
|
| 287 |
+
let len = haystack.len();
|
| 288 |
+
let ptr = haystack.as_ptr();
|
| 289 |
+
let v_needle = vdupq_n_u8(needle);
|
| 290 |
+
let mut end = len;
|
| 291 |
+
|
| 292 |
+
while end >= 16 {
|
| 293 |
+
let start = end - 16;
|
| 294 |
+
let chunk = unsafe { vld1q_u8(ptr.add(start)) };
|
| 295 |
+
let cmp = vceqq_u8(chunk, v_needle);
|
| 296 |
+
if vmaxvq_u8(cmp) != 0 {
|
| 297 |
+
for lane in (0..16).rev() {
|
| 298 |
+
if unsafe { *ptr.add(start + lane) } == needle {
|
| 299 |
+
return Some(start + lane);
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
end = start;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
while end > 0 {
|
| 307 |
+
end -= 1;
|
| 308 |
+
if unsafe { *ptr.add(end) } == needle {
|
| 309 |
+
return Some(end);
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
None
|
| 313 |
+
}
|
data/src/string_tensor.rs
CHANGED
|
@@ -1,269 +1,269 @@
|
|
| 1 |
-
use std::sync::Arc;
|
| 2 |
-
|
| 3 |
-
use rayon::prelude::*;
|
| 4 |
-
use xxhash_rust::xxh3::xxh3_64;
|
| 5 |
-
|
| 6 |
-
#[repr(C)]
|
| 7 |
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 8 |
-
pub struct StringView16 {
|
| 9 |
-
pub(crate) len: u32,
|
| 10 |
-
pub(crate) prefix_or_inline: [u8; 4],
|
| 11 |
-
pub(crate) suffix_or_offset: [u8; 8],
|
| 12 |
-
}
|
| 13 |
-
|
| 14 |
-
impl StringView16 {
|
| 15 |
-
pub const INLINE_CAPACITY: usize = 12;
|
| 16 |
-
|
| 17 |
-
#[inline]
|
| 18 |
-
pub fn new(s: &str, buffer_id: u32, offset: u32) -> Self {
|
| 19 |
-
let len = s.len() as u32;
|
| 20 |
-
let bytes = s.as_bytes();
|
| 21 |
-
let mut prefix_or_inline = [0u8; 4];
|
| 22 |
-
let mut suffix_or_offset = [0u8; 8];
|
| 23 |
-
|
| 24 |
-
if (len as usize) <= Self::INLINE_CAPACITY {
|
| 25 |
-
let n = bytes.len();
|
| 26 |
-
let first_part = n.min(4);
|
| 27 |
-
prefix_or_inline[..first_part].copy_from_slice(&bytes[..first_part]);
|
| 28 |
-
if n > 4 {
|
| 29 |
-
suffix_or_offset[..n - 4].copy_from_slice(&bytes[4..n]);
|
| 30 |
-
}
|
| 31 |
-
} else {
|
| 32 |
-
prefix_or_inline.copy_from_slice(&bytes[..4]);
|
| 33 |
-
suffix_or_offset[0..4].copy_from_slice(&offset.to_le_bytes());
|
| 34 |
-
suffix_or_offset[4..8].copy_from_slice(&buffer_id.to_le_bytes());
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
Self {
|
| 38 |
-
len,
|
| 39 |
-
prefix_or_inline,
|
| 40 |
-
suffix_or_offset,
|
| 41 |
-
}
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
#[inline]
|
| 45 |
-
pub fn is_inlined(&self) -> bool {
|
| 46 |
-
(self.len as usize) <= Self::INLINE_CAPACITY
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
#[inline]
|
| 50 |
-
pub fn as_str<'a>(&'a self, pool: &'a [u8]) -> &'a str {
|
| 51 |
-
if self.is_inlined() {
|
| 52 |
-
let len = (self.len as usize).min(Self::INLINE_CAPACITY);
|
| 53 |
-
// The 12 inline bytes immediately follow the 4-byte len in memory
|
| 54 |
-
unsafe {
|
| 55 |
-
let ptr = (self as *const Self as *const u8).add(4);
|
| 56 |
-
let slice = std::slice::from_raw_parts(ptr, len);
|
| 57 |
-
std::str::from_utf8_unchecked(slice)
|
| 58 |
-
}
|
| 59 |
-
} else {
|
| 60 |
-
let offset =
|
| 61 |
-
u32::from_le_bytes(self.suffix_or_offset[0..4].try_into().unwrap()) as usize;
|
| 62 |
-
let len = self.len as usize;
|
| 63 |
-
if let Some(slice) = pool.get(offset..offset + len) {
|
| 64 |
-
unsafe { std::str::from_utf8_unchecked(slice) }
|
| 65 |
-
} else {
|
| 66 |
-
""
|
| 67 |
-
}
|
| 68 |
-
}
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
#[inline]
|
| 72 |
-
pub fn fast_eq<'a>(&'a self, other: &'a Self, pool: &'a [u8]) -> bool {
|
| 73 |
-
if self.len != other.len {
|
| 74 |
-
return false;
|
| 75 |
-
}
|
| 76 |
-
if self.prefix_or_inline != other.prefix_or_inline {
|
| 77 |
-
return false;
|
| 78 |
-
}
|
| 79 |
-
if self.is_inlined() {
|
| 80 |
-
return self.suffix_or_offset == other.suffix_or_offset;
|
| 81 |
-
}
|
| 82 |
-
self.as_str(pool) == other.as_str(pool)
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
#[inline]
|
| 86 |
-
pub fn compute_hash<'a>(&'a self, pool: &'a [u8]) -> u64 {
|
| 87 |
-
let s = self.as_str(pool);
|
| 88 |
-
xxh3_64(s.as_bytes())
|
| 89 |
-
}
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
#[derive(Clone, Debug, Default)]
|
| 93 |
-
pub struct StringTensor {
|
| 94 |
-
pub views: Vec<StringView16>,
|
| 95 |
-
pub pool: Arc<Vec<u8>>,
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
impl StringTensor {
|
| 99 |
-
pub fn new() -> Self {
|
| 100 |
-
Self {
|
| 101 |
-
views: Vec::new(),
|
| 102 |
-
pool: Arc::new(Vec::new()),
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
pub fn from_strings<I, S>(strings: I) -> Self
|
| 107 |
-
where
|
| 108 |
-
I: IntoIterator<Item = S>,
|
| 109 |
-
S: AsRef<str>,
|
| 110 |
-
{
|
| 111 |
-
let items: Vec<S> = strings.into_iter().collect();
|
| 112 |
-
let mut total_ref_bytes = 0usize;
|
| 113 |
-
for s in &items {
|
| 114 |
-
let str_ref = s.as_ref();
|
| 115 |
-
if str_ref.len() > StringView16::INLINE_CAPACITY {
|
| 116 |
-
total_ref_bytes += str_ref.len();
|
| 117 |
-
}
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
let mut pool = Vec::with_capacity(total_ref_bytes);
|
| 121 |
-
let mut views = Vec::with_capacity(items.len());
|
| 122 |
-
|
| 123 |
-
for s in items {
|
| 124 |
-
let str_ref = s.as_ref();
|
| 125 |
-
if str_ref.len() <= StringView16::INLINE_CAPACITY {
|
| 126 |
-
views.push(StringView16::new(str_ref, 0, 0));
|
| 127 |
-
} else {
|
| 128 |
-
let offset = u32::try_from(pool.len()).unwrap_or(u32::MAX);
|
| 129 |
-
pool.extend_from_slice(str_ref.as_bytes());
|
| 130 |
-
views.push(StringView16::new(str_ref, 0, offset));
|
| 131 |
-
}
|
| 132 |
-
}
|
| 133 |
-
|
| 134 |
-
Self {
|
| 135 |
-
views,
|
| 136 |
-
pool: Arc::new(pool),
|
| 137 |
-
}
|
| 138 |
-
}
|
| 139 |
-
|
| 140 |
-
pub fn from_gpu_buffers(offsets: &[u32], payload: &[u8]) -> Self {
|
| 141 |
-
if offsets.is_empty() {
|
| 142 |
-
return Self::new();
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
let num_strings = offsets.len().saturating_sub(1);
|
| 146 |
-
let mut views = Vec::with_capacity(num_strings);
|
| 147 |
-
let pool = Arc::new(payload.to_vec());
|
| 148 |
-
|
| 149 |
-
for i in 0..num_strings {
|
| 150 |
-
let start = offsets[i] as usize;
|
| 151 |
-
let end = offsets[i + 1] as usize;
|
| 152 |
-
if start <= end && end <= payload.len() {
|
| 153 |
-
if let Ok(s) = std::str::from_utf8(&payload[start..end]) {
|
| 154 |
-
views.push(StringView16::new(s, 0, start as u32));
|
| 155 |
-
} else {
|
| 156 |
-
views.push(StringView16::new("", 0, 0));
|
| 157 |
-
}
|
| 158 |
-
} else {
|
| 159 |
-
views.push(StringView16::new("", 0, 0));
|
| 160 |
-
}
|
| 161 |
-
}
|
| 162 |
-
|
| 163 |
-
Self { views, pool }
|
| 164 |
-
}
|
| 165 |
-
|
| 166 |
-
pub fn to_gpu_buffers(&self) -> (Vec<u32>, Vec<u8>) {
|
| 167 |
-
let num_strings = self.views.len();
|
| 168 |
-
let mut offsets = Vec::with_capacity(num_strings + 1);
|
| 169 |
-
let mut total_chars = 0usize;
|
| 170 |
-
|
| 171 |
-
for v in &self.views {
|
| 172 |
-
total_chars += v.len as usize;
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
let mut payload = Vec::with_capacity(total_chars);
|
| 176 |
-
let mut current_offset = 0u32;
|
| 177 |
-
offsets.push(current_offset);
|
| 178 |
-
|
| 179 |
-
for v in &self.views {
|
| 180 |
-
let s = v.as_str(&self.pool);
|
| 181 |
-
payload.extend_from_slice(s.as_bytes());
|
| 182 |
-
current_offset = current_offset.saturating_add(v.len);
|
| 183 |
-
offsets.push(current_offset);
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
(offsets, payload)
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
#[inline]
|
| 190 |
-
pub fn len(&self) -> usize {
|
| 191 |
-
self.views.len()
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
#[inline]
|
| 195 |
-
pub fn is_empty(&self) -> bool {
|
| 196 |
-
self.views.is_empty()
|
| 197 |
-
}
|
| 198 |
-
|
| 199 |
-
#[inline]
|
| 200 |
-
pub fn get(&self, index: usize) -> Option<&str> {
|
| 201 |
-
self.views.get(index).map(|v| v.as_str(&self.pool))
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
pub fn to_vec(&self) -> Vec<String> {
|
| 205 |
-
self.views
|
| 206 |
-
.iter()
|
| 207 |
-
.map(|v| v.as_str(&self.pool).to_owned())
|
| 208 |
-
.collect()
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
pub fn lengths(&self) -> Vec<u32> {
|
| 212 |
-
self.views.iter().map(|v| v.len).collect()
|
| 213 |
-
}
|
| 214 |
-
|
| 215 |
-
pub fn is_inlined_mask(&self) -> Vec<bool> {
|
| 216 |
-
self.views.iter().map(|v| v.is_inlined()).collect()
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
pub fn hash_all_parallel(&self) -> Vec<u64> {
|
| 220 |
-
let pool = &self.pool;
|
| 221 |
-
self.views
|
| 222 |
-
.par_iter()
|
| 223 |
-
.map(|v| v.compute_hash(pool))
|
| 224 |
-
.collect()
|
| 225 |
-
}
|
| 226 |
-
|
| 227 |
-
pub fn filter_by_length(&self, min_len: u32, max_len: u32) -> Self {
|
| 228 |
-
let mut new_views = Vec::new();
|
| 229 |
-
for v in &self.views {
|
| 230 |
-
let l = v.len;
|
| 231 |
-
if l >= min_len && (max_len == 0 || l <= max_len) {
|
| 232 |
-
new_views.push(*v);
|
| 233 |
-
}
|
| 234 |
-
}
|
| 235 |
-
Self {
|
| 236 |
-
views: new_views,
|
| 237 |
-
pool: self.pool.clone(),
|
| 238 |
-
}
|
| 239 |
-
}
|
| 240 |
-
|
| 241 |
-
pub fn filter_by_prefix(&self, prefix: &str) -> Self {
|
| 242 |
-
let prefix_bytes = prefix.as_bytes();
|
| 243 |
-
let pool = &self.pool;
|
| 244 |
-
let mut new_views = Vec::new();
|
| 245 |
-
|
| 246 |
-
for v in &self.views {
|
| 247 |
-
let l = v.len as usize;
|
| 248 |
-
if l < prefix_bytes.len() {
|
| 249 |
-
continue;
|
| 250 |
-
}
|
| 251 |
-
// Fast prefix check
|
| 252 |
-
if prefix_bytes.len() <= 4 {
|
| 253 |
-
if v.prefix_or_inline[..prefix_bytes.len()] == prefix_bytes[..] {
|
| 254 |
-
new_views.push(*v);
|
| 255 |
-
}
|
| 256 |
-
} else if v.prefix_or_inline[..4] == prefix_bytes[..4] {
|
| 257 |
-
let s = v.as_str(pool);
|
| 258 |
-
if s.as_bytes().starts_with(prefix_bytes) {
|
| 259 |
-
new_views.push(*v);
|
| 260 |
-
}
|
| 261 |
-
}
|
| 262 |
-
}
|
| 263 |
-
|
| 264 |
-
Self {
|
| 265 |
-
views: new_views,
|
| 266 |
-
pool: self.pool.clone(),
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
}
|
|
|
|
| 1 |
+
use std::sync::Arc;
|
| 2 |
+
|
| 3 |
+
use rayon::prelude::*;
|
| 4 |
+
use xxhash_rust::xxh3::xxh3_64;
|
| 5 |
+
|
| 6 |
+
#[repr(C)]
|
| 7 |
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 8 |
+
pub struct StringView16 {
|
| 9 |
+
pub(crate) len: u32,
|
| 10 |
+
pub(crate) prefix_or_inline: [u8; 4],
|
| 11 |
+
pub(crate) suffix_or_offset: [u8; 8],
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
impl StringView16 {
|
| 15 |
+
pub const INLINE_CAPACITY: usize = 12;
|
| 16 |
+
|
| 17 |
+
#[inline]
|
| 18 |
+
pub fn new(s: &str, buffer_id: u32, offset: u32) -> Self {
|
| 19 |
+
let len = s.len() as u32;
|
| 20 |
+
let bytes = s.as_bytes();
|
| 21 |
+
let mut prefix_or_inline = [0u8; 4];
|
| 22 |
+
let mut suffix_or_offset = [0u8; 8];
|
| 23 |
+
|
| 24 |
+
if (len as usize) <= Self::INLINE_CAPACITY {
|
| 25 |
+
let n = bytes.len();
|
| 26 |
+
let first_part = n.min(4);
|
| 27 |
+
prefix_or_inline[..first_part].copy_from_slice(&bytes[..first_part]);
|
| 28 |
+
if n > 4 {
|
| 29 |
+
suffix_or_offset[..n - 4].copy_from_slice(&bytes[4..n]);
|
| 30 |
+
}
|
| 31 |
+
} else {
|
| 32 |
+
prefix_or_inline.copy_from_slice(&bytes[..4]);
|
| 33 |
+
suffix_or_offset[0..4].copy_from_slice(&offset.to_le_bytes());
|
| 34 |
+
suffix_or_offset[4..8].copy_from_slice(&buffer_id.to_le_bytes());
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
Self {
|
| 38 |
+
len,
|
| 39 |
+
prefix_or_inline,
|
| 40 |
+
suffix_or_offset,
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
#[inline]
|
| 45 |
+
pub fn is_inlined(&self) -> bool {
|
| 46 |
+
(self.len as usize) <= Self::INLINE_CAPACITY
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
#[inline]
|
| 50 |
+
pub fn as_str<'a>(&'a self, pool: &'a [u8]) -> &'a str {
|
| 51 |
+
if self.is_inlined() {
|
| 52 |
+
let len = (self.len as usize).min(Self::INLINE_CAPACITY);
|
| 53 |
+
// The 12 inline bytes immediately follow the 4-byte len in memory
|
| 54 |
+
unsafe {
|
| 55 |
+
let ptr = (self as *const Self as *const u8).add(4);
|
| 56 |
+
let slice = std::slice::from_raw_parts(ptr, len);
|
| 57 |
+
std::str::from_utf8_unchecked(slice)
|
| 58 |
+
}
|
| 59 |
+
} else {
|
| 60 |
+
let offset =
|
| 61 |
+
u32::from_le_bytes(self.suffix_or_offset[0..4].try_into().unwrap()) as usize;
|
| 62 |
+
let len = self.len as usize;
|
| 63 |
+
if let Some(slice) = pool.get(offset..offset + len) {
|
| 64 |
+
unsafe { std::str::from_utf8_unchecked(slice) }
|
| 65 |
+
} else {
|
| 66 |
+
""
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
#[inline]
|
| 72 |
+
pub fn fast_eq<'a>(&'a self, other: &'a Self, pool: &'a [u8]) -> bool {
|
| 73 |
+
if self.len != other.len {
|
| 74 |
+
return false;
|
| 75 |
+
}
|
| 76 |
+
if self.prefix_or_inline != other.prefix_or_inline {
|
| 77 |
+
return false;
|
| 78 |
+
}
|
| 79 |
+
if self.is_inlined() {
|
| 80 |
+
return self.suffix_or_offset == other.suffix_or_offset;
|
| 81 |
+
}
|
| 82 |
+
self.as_str(pool) == other.as_str(pool)
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
#[inline]
|
| 86 |
+
pub fn compute_hash<'a>(&'a self, pool: &'a [u8]) -> u64 {
|
| 87 |
+
let s = self.as_str(pool);
|
| 88 |
+
xxh3_64(s.as_bytes())
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
#[derive(Clone, Debug, Default)]
|
| 93 |
+
pub struct StringTensor {
|
| 94 |
+
pub views: Vec<StringView16>,
|
| 95 |
+
pub pool: Arc<Vec<u8>>,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
impl StringTensor {
|
| 99 |
+
pub fn new() -> Self {
|
| 100 |
+
Self {
|
| 101 |
+
views: Vec::new(),
|
| 102 |
+
pool: Arc::new(Vec::new()),
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
pub fn from_strings<I, S>(strings: I) -> Self
|
| 107 |
+
where
|
| 108 |
+
I: IntoIterator<Item = S>,
|
| 109 |
+
S: AsRef<str>,
|
| 110 |
+
{
|
| 111 |
+
let items: Vec<S> = strings.into_iter().collect();
|
| 112 |
+
let mut total_ref_bytes = 0usize;
|
| 113 |
+
for s in &items {
|
| 114 |
+
let str_ref = s.as_ref();
|
| 115 |
+
if str_ref.len() > StringView16::INLINE_CAPACITY {
|
| 116 |
+
total_ref_bytes += str_ref.len();
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
let mut pool = Vec::with_capacity(total_ref_bytes);
|
| 121 |
+
let mut views = Vec::with_capacity(items.len());
|
| 122 |
+
|
| 123 |
+
for s in items {
|
| 124 |
+
let str_ref = s.as_ref();
|
| 125 |
+
if str_ref.len() <= StringView16::INLINE_CAPACITY {
|
| 126 |
+
views.push(StringView16::new(str_ref, 0, 0));
|
| 127 |
+
} else {
|
| 128 |
+
let offset = u32::try_from(pool.len()).unwrap_or(u32::MAX);
|
| 129 |
+
pool.extend_from_slice(str_ref.as_bytes());
|
| 130 |
+
views.push(StringView16::new(str_ref, 0, offset));
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
Self {
|
| 135 |
+
views,
|
| 136 |
+
pool: Arc::new(pool),
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
pub fn from_gpu_buffers(offsets: &[u32], payload: &[u8]) -> Self {
|
| 141 |
+
if offsets.is_empty() {
|
| 142 |
+
return Self::new();
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
let num_strings = offsets.len().saturating_sub(1);
|
| 146 |
+
let mut views = Vec::with_capacity(num_strings);
|
| 147 |
+
let pool = Arc::new(payload.to_vec());
|
| 148 |
+
|
| 149 |
+
for i in 0..num_strings {
|
| 150 |
+
let start = offsets[i] as usize;
|
| 151 |
+
let end = offsets[i + 1] as usize;
|
| 152 |
+
if start <= end && end <= payload.len() {
|
| 153 |
+
if let Ok(s) = std::str::from_utf8(&payload[start..end]) {
|
| 154 |
+
views.push(StringView16::new(s, 0, start as u32));
|
| 155 |
+
} else {
|
| 156 |
+
views.push(StringView16::new("", 0, 0));
|
| 157 |
+
}
|
| 158 |
+
} else {
|
| 159 |
+
views.push(StringView16::new("", 0, 0));
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
Self { views, pool }
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
pub fn to_gpu_buffers(&self) -> (Vec<u32>, Vec<u8>) {
|
| 167 |
+
let num_strings = self.views.len();
|
| 168 |
+
let mut offsets = Vec::with_capacity(num_strings + 1);
|
| 169 |
+
let mut total_chars = 0usize;
|
| 170 |
+
|
| 171 |
+
for v in &self.views {
|
| 172 |
+
total_chars += v.len as usize;
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
let mut payload = Vec::with_capacity(total_chars);
|
| 176 |
+
let mut current_offset = 0u32;
|
| 177 |
+
offsets.push(current_offset);
|
| 178 |
+
|
| 179 |
+
for v in &self.views {
|
| 180 |
+
let s = v.as_str(&self.pool);
|
| 181 |
+
payload.extend_from_slice(s.as_bytes());
|
| 182 |
+
current_offset = current_offset.saturating_add(v.len);
|
| 183 |
+
offsets.push(current_offset);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
(offsets, payload)
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
#[inline]
|
| 190 |
+
pub fn len(&self) -> usize {
|
| 191 |
+
self.views.len()
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
#[inline]
|
| 195 |
+
pub fn is_empty(&self) -> bool {
|
| 196 |
+
self.views.is_empty()
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
#[inline]
|
| 200 |
+
pub fn get(&self, index: usize) -> Option<&str> {
|
| 201 |
+
self.views.get(index).map(|v| v.as_str(&self.pool))
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
pub fn to_vec(&self) -> Vec<String> {
|
| 205 |
+
self.views
|
| 206 |
+
.iter()
|
| 207 |
+
.map(|v| v.as_str(&self.pool).to_owned())
|
| 208 |
+
.collect()
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
pub fn lengths(&self) -> Vec<u32> {
|
| 212 |
+
self.views.iter().map(|v| v.len).collect()
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
pub fn is_inlined_mask(&self) -> Vec<bool> {
|
| 216 |
+
self.views.iter().map(|v| v.is_inlined()).collect()
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
pub fn hash_all_parallel(&self) -> Vec<u64> {
|
| 220 |
+
let pool = &self.pool;
|
| 221 |
+
self.views
|
| 222 |
+
.par_iter()
|
| 223 |
+
.map(|v| v.compute_hash(pool))
|
| 224 |
+
.collect()
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
pub fn filter_by_length(&self, min_len: u32, max_len: u32) -> Self {
|
| 228 |
+
let mut new_views = Vec::new();
|
| 229 |
+
for v in &self.views {
|
| 230 |
+
let l = v.len;
|
| 231 |
+
if l >= min_len && (max_len == 0 || l <= max_len) {
|
| 232 |
+
new_views.push(*v);
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
Self {
|
| 236 |
+
views: new_views,
|
| 237 |
+
pool: self.pool.clone(),
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
pub fn filter_by_prefix(&self, prefix: &str) -> Self {
|
| 242 |
+
let prefix_bytes = prefix.as_bytes();
|
| 243 |
+
let pool = &self.pool;
|
| 244 |
+
let mut new_views = Vec::new();
|
| 245 |
+
|
| 246 |
+
for v in &self.views {
|
| 247 |
+
let l = v.len as usize;
|
| 248 |
+
if l < prefix_bytes.len() {
|
| 249 |
+
continue;
|
| 250 |
+
}
|
| 251 |
+
// Fast prefix check
|
| 252 |
+
if prefix_bytes.len() <= 4 {
|
| 253 |
+
if v.prefix_or_inline[..prefix_bytes.len()] == prefix_bytes[..] {
|
| 254 |
+
new_views.push(*v);
|
| 255 |
+
}
|
| 256 |
+
} else if v.prefix_or_inline[..4] == prefix_bytes[..4] {
|
| 257 |
+
let s = v.as_str(pool);
|
| 258 |
+
if s.as_bytes().starts_with(prefix_bytes) {
|
| 259 |
+
new_views.push(*v);
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
Self {
|
| 265 |
+
views: new_views,
|
| 266 |
+
pool: self.pool.clone(),
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
}
|
data/src/tests.rs
CHANGED
|
@@ -1,384 +1,384 @@
|
|
| 1 |
-
use std::fs;
|
| 2 |
-
use std::path::PathBuf;
|
| 3 |
-
|
| 4 |
-
use crate::coord::StreamCoordinate;
|
| 5 |
-
use crate::dedup::TwoStageDeduplicator;
|
| 6 |
-
use crate::metadata::DualMetadataManager;
|
| 7 |
-
use crate::postprocess::{PostProcessConfig, process_batch_parallel, process_single_record};
|
| 8 |
-
use crate::serialize::{
|
| 9 |
-
DataValue, build_arrow_record_batch, classify_string_or_json,
|
| 10 |
-
serialize_columns_to_arrow_ipc_bytes, serialize_columns_to_jsonl_bytes,
|
| 11 |
-
serialize_columns_to_parquet_bytes,
|
| 12 |
-
};
|
| 13 |
-
use crate::simd_cpu::{contains_byte_fast, detect_cpu_isa, find_newline_fast};
|
| 14 |
-
|
| 15 |
-
fn temp_test_path(name: &str, ext: &str) -> PathBuf {
|
| 16 |
-
let mut p = std::env::temp_dir();
|
| 17 |
-
p.push(format!(
|
| 18 |
-
"luminamoon_test_{}_{}.{}",
|
| 19 |
-
name,
|
| 20 |
-
std::process::id(),
|
| 21 |
-
ext
|
| 22 |
-
));
|
| 23 |
-
p
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
#[test]
|
| 27 |
-
fn test_stream_coordinate_roundtrip() {
|
| 28 |
-
let c = StreamCoordinate {
|
| 29 |
-
shard_id: 42,
|
| 30 |
-
byte_offset: 1024 * 1024,
|
| 31 |
-
row_group_idx: 3,
|
| 32 |
-
page_idx: 7,
|
| 33 |
-
row_offset_in_page: 128,
|
| 34 |
-
global_row_idx: 99999,
|
| 35 |
-
};
|
| 36 |
-
let bytes = c.to_bytes();
|
| 37 |
-
assert_eq!(bytes.len(), StreamCoordinate::BYTE_LEN);
|
| 38 |
-
let decoded = StreamCoordinate::from_bytes(&bytes);
|
| 39 |
-
assert_eq!(c, decoded);
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
#[test]
|
| 43 |
-
fn test_dual_metadata_ping_pong_and_dedup_wal() {
|
| 44 |
-
let meta_path = temp_test_path("ping_pong_meta", "meta");
|
| 45 |
-
let _ = fs::remove_file(&meta_path);
|
| 46 |
-
let _ = fs::remove_file(format!("{}.dedup", meta_path.display()));
|
| 47 |
-
|
| 48 |
-
{
|
| 49 |
-
let mut mgr = DualMetadataManager::open(&meta_path).unwrap();
|
| 50 |
-
assert_eq!(mgr.committed_batch_id(), 0);
|
| 51 |
-
|
| 52 |
-
assert!(mgr.deduplicator.try_stage(1, b"line_alpha").is_some());
|
| 53 |
-
assert!(mgr.deduplicator.try_stage(1, b"line_alpha").is_none());
|
| 54 |
-
|
| 55 |
-
let c1 = StreamCoordinate {
|
| 56 |
-
shard_id: 1,
|
| 57 |
-
byte_offset: 512,
|
| 58 |
-
row_group_idx: 0,
|
| 59 |
-
page_idx: 0,
|
| 60 |
-
row_offset_in_page: 0,
|
| 61 |
-
global_row_idx: 10,
|
| 62 |
-
};
|
| 63 |
-
mgr.stage_batch_in_ram(1, c1);
|
| 64 |
-
let committed = mgr.commit_gpu_batch(1).unwrap();
|
| 65 |
-
assert_eq!(committed, c1);
|
| 66 |
-
assert_eq!(mgr.committed_batch_id(), 1);
|
| 67 |
-
}
|
| 68 |
-
|
| 69 |
-
// Re-open and verify recovery
|
| 70 |
-
{
|
| 71 |
-
let mut mgr = DualMetadataManager::open(&meta_path).unwrap();
|
| 72 |
-
assert_eq!(mgr.committed_batch_id(), 1);
|
| 73 |
-
assert_eq!(mgr.committed_coordinate().shard_id, 1);
|
| 74 |
-
assert_eq!(mgr.committed_coordinate().byte_offset, 512);
|
| 75 |
-
|
| 76 |
-
// "line_alpha" must still be marked as committed
|
| 77 |
-
assert!(mgr.deduplicator.try_stage(2, b"line_alpha").is_none());
|
| 78 |
-
assert!(mgr.deduplicator.try_stage(2, b"line_beta").is_some());
|
| 79 |
-
|
| 80 |
-
let c2 = StreamCoordinate {
|
| 81 |
-
shard_id: 1,
|
| 82 |
-
byte_offset: 1024,
|
| 83 |
-
row_group_idx: 0,
|
| 84 |
-
page_idx: 1,
|
| 85 |
-
row_offset_in_page: 0,
|
| 86 |
-
global_row_idx: 20,
|
| 87 |
-
};
|
| 88 |
-
mgr.stage_batch_in_ram(2, c2);
|
| 89 |
-
mgr.commit_gpu_batch(2).unwrap();
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
let _ = fs::remove_file(&meta_path);
|
| 93 |
-
let _ = fs::remove_file(format!("{}.dedup", meta_path.display()));
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
#[test]
|
| 97 |
-
fn test_two_stage_deduplicator() {
|
| 98 |
-
let mut dedup = TwoStageDeduplicator::new();
|
| 99 |
-
assert_eq!(dedup.committed_len(), 0);
|
| 100 |
-
|
| 101 |
-
assert!(dedup.try_stage(1, b"apple").is_some());
|
| 102 |
-
assert!(dedup.try_stage(1, b"apple").is_none());
|
| 103 |
-
assert!(dedup.try_stage(1, b"banana").is_some());
|
| 104 |
-
assert_eq!(dedup.staged_len(), 2);
|
| 105 |
-
|
| 106 |
-
let committed = dedup.commit_batch(1);
|
| 107 |
-
assert_eq!(committed.len(), 2);
|
| 108 |
-
assert_eq!(dedup.committed_len(), 2);
|
| 109 |
-
assert_eq!(dedup.staged_len(), 0);
|
| 110 |
-
|
| 111 |
-
// Staging the same in batch 2 should be rejected
|
| 112 |
-
assert!(dedup.try_stage(2, b"apple").is_none());
|
| 113 |
-
assert!(dedup.try_stage(2, b"orange").is_some());
|
| 114 |
-
|
| 115 |
-
// Rollback batch 2
|
| 116 |
-
let rolled = dedup.rollback_batch(2);
|
| 117 |
-
assert_eq!(rolled, 1);
|
| 118 |
-
assert!(dedup.try_stage(3, b"orange").is_some());
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
#[test]
|
| 122 |
-
fn test_simd_cpu_detection_and_scans() {
|
| 123 |
-
let isa = detect_cpu_isa();
|
| 124 |
-
assert!(!isa.as_str().is_empty());
|
| 125 |
-
|
| 126 |
-
let text = b"hello\r\nworld\nfrom\rsimd";
|
| 127 |
-
assert!(contains_byte_fast(text, b'\r'));
|
| 128 |
-
assert!(contains_byte_fast(text, b'\n'));
|
| 129 |
-
assert!(!contains_byte_fast(text, b'z'));
|
| 130 |
-
|
| 131 |
-
let pos = find_newline_fast(text);
|
| 132 |
-
assert!(pos.is_some());
|
| 133 |
-
}
|
| 134 |
-
|
| 135 |
-
#[test]
|
| 136 |
-
fn test_postprocess_cleaning_and_json_extraction() {
|
| 137 |
-
let cfg = PostProcessConfig {
|
| 138 |
-
trim_whitespace: true,
|
| 139 |
-
normalize_newlines: true,
|
| 140 |
-
min_byte_len: 3,
|
| 141 |
-
max_byte_len: 20,
|
| 142 |
-
truncate_over_max: true,
|
| 143 |
-
json_extract_key: Some("data.text".to_owned()),
|
| 144 |
-
};
|
| 145 |
-
|
| 146 |
-
let raw_json = r#"{"data": {"text": " Hello\r\nWorld! "}}"#;
|
| 147 |
-
let cleaned = process_single_record(raw_json, &cfg);
|
| 148 |
-
assert_eq!(cleaned, Some("Hello\nWorld!".to_owned()));
|
| 149 |
-
|
| 150 |
-
// Truncation test
|
| 151 |
-
let long_json = r#"{"data": {"text": "A very long sentence that will exceed twenty bytes"}}"#;
|
| 152 |
-
let truncated = process_single_record(long_json, &cfg).unwrap();
|
| 153 |
-
assert!(truncated.len() <= 20);
|
| 154 |
-
|
| 155 |
-
// Parallel batch test
|
| 156 |
-
let batch = vec![
|
| 157 |
-
r#"{"data": {"text": "row 1\r\n"}}"#.to_owned(),
|
| 158 |
-
r#"{"data": {"text": "row 2"}}"#.to_owned(),
|
| 159 |
-
r#"{"data": {"text": ""}}"#.to_owned(), // filtered due to min_byte_len
|
| 160 |
-
];
|
| 161 |
-
let batch_cleaned = process_batch_parallel(&batch, &cfg);
|
| 162 |
-
assert_eq!(batch_cleaned.len(), 2);
|
| 163 |
-
assert_eq!(batch_cleaned[0], "row 1");
|
| 164 |
-
assert_eq!(batch_cleaned[1], "row 2");
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
#[test]
|
| 168 |
-
fn test_classify_string_or_json() {
|
| 169 |
-
let s1 = "just plain text";
|
| 170 |
-
assert_eq!(classify_string_or_json(s1), DataValue::Text(s1.to_owned()));
|
| 171 |
-
|
| 172 |
-
let s2 = r#"{"key": "value", "id": 123}"#;
|
| 173 |
-
assert_eq!(classify_string_or_json(s2), DataValue::Json(s2.to_owned()));
|
| 174 |
-
}
|
| 175 |
-
|
| 176 |
-
#[test]
|
| 177 |
-
fn test_serialization_parquet_and_jsonl() {
|
| 178 |
-
let columns = vec![
|
| 179 |
-
(
|
| 180 |
-
"text".to_owned(),
|
| 181 |
-
vec![
|
| 182 |
-
DataValue::Text("first".to_owned()),
|
| 183 |
-
DataValue::Text("second".to_owned()),
|
| 184 |
-
],
|
| 185 |
-
),
|
| 186 |
-
(
|
| 187 |
-
"score".to_owned(),
|
| 188 |
-
vec![DataValue::Float(0.95), DataValue::Float(0.42)],
|
| 189 |
-
),
|
| 190 |
-
(
|
| 191 |
-
"label".to_owned(),
|
| 192 |
-
vec![DataValue::Int(1), DataValue::Int(0)],
|
| 193 |
-
),
|
| 194 |
-
];
|
| 195 |
-
|
| 196 |
-
let (schema, batch) = build_arrow_record_batch(&columns, false).unwrap();
|
| 197 |
-
assert_eq!(batch.num_rows(), 2);
|
| 198 |
-
assert_eq!(schema.fields().len(), 3);
|
| 199 |
-
|
| 200 |
-
// Parquet serialization
|
| 201 |
-
let pq_bytes = serialize_columns_to_parquet_bytes(&columns, false).unwrap();
|
| 202 |
-
assert!(!pq_bytes.is_empty());
|
| 203 |
-
assert_eq!(&pq_bytes[0..4], b"PAR1");
|
| 204 |
-
|
| 205 |
-
// Arrow IPC serialization
|
| 206 |
-
let ipc_bytes = serialize_columns_to_arrow_ipc_bytes(&columns, false).unwrap();
|
| 207 |
-
assert!(!ipc_bytes.is_empty());
|
| 208 |
-
assert_eq!(&ipc_bytes[0..6], b"ARROW1");
|
| 209 |
-
|
| 210 |
-
// JSONL serialization
|
| 211 |
-
let jsonl_bytes = serialize_columns_to_jsonl_bytes(&columns).unwrap();
|
| 212 |
-
assert!(!jsonl_bytes.is_empty());
|
| 213 |
-
let jsonl_str = String::from_utf8(jsonl_bytes).unwrap();
|
| 214 |
-
assert!(jsonl_str.contains("\"text\":\"first\""));
|
| 215 |
-
assert!(jsonl_str.contains("\"score\":0.95"));
|
| 216 |
-
}
|
| 217 |
-
|
| 218 |
-
#[test]
|
| 219 |
-
fn test_quality_filter() {
|
| 220 |
-
use crate::quality::{QualityConfig, evaluate_quality};
|
| 221 |
-
|
| 222 |
-
let cfg = QualityConfig {
|
| 223 |
-
min_chars: 20,
|
| 224 |
-
max_chars: 0,
|
| 225 |
-
min_words: 3,
|
| 226 |
-
min_entropy: 2.5,
|
| 227 |
-
max_repetition_ratio: 0.35,
|
| 228 |
-
max_symbol_ratio: 0.30,
|
| 229 |
-
};
|
| 230 |
-
|
| 231 |
-
// 1. High quality normal text
|
| 232 |
-
let good_text =
|
| 233 |
-
"The quick brown fox jumps over the lazy dog and runs across the wide green meadow.";
|
| 234 |
-
let res = evaluate_quality(good_text, &cfg);
|
| 235 |
-
assert!(res.is_passed);
|
| 236 |
-
assert!(res.entropy > 2.5);
|
| 237 |
-
assert!(res.repetition_ratio < 0.2);
|
| 238 |
-
assert!(res.symbol_ratio < 0.1);
|
| 239 |
-
|
| 240 |
-
// 2. Degenerate repetition
|
| 241 |
-
let repeated =
|
| 242 |
-
"spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam";
|
| 243 |
-
let res_rep = evaluate_quality(repeated, &cfg);
|
| 244 |
-
assert!(!res_rep.is_passed);
|
| 245 |
-
assert!(res_rep.repetition_ratio > 0.35);
|
| 246 |
-
|
| 247 |
-
// 3. Code / symbol dump
|
| 248 |
-
let symbol_dump = "{{{[(((@@@###$$$%%%^^^&&&***)))}}}];;;===+++///<<<>>>!!!???";
|
| 249 |
-
let res_sym = evaluate_quality(symbol_dump, &cfg);
|
| 250 |
-
assert!(!res_sym.is_passed);
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
#[test]
|
| 254 |
-
fn test_minhash_fuzzy_deduplicator() {
|
| 255 |
-
use crate::minhash::MinHashDeduplicator;
|
| 256 |
-
|
| 257 |
-
let mut dedup = MinHashDeduplicator::new(0.80, 4, 64);
|
| 258 |
-
|
| 259 |
-
let doc_a =
|
| 260 |
-
"Artificial intelligence and deep learning models require efficient data pipelines.";
|
| 261 |
-
// Near duplicate (just 1 typo/word difference)
|
| 262 |
-
let doc_a_near =
|
| 263 |
-
"Artificial intelligence and deep learning models require efficient data pipelining.";
|
| 264 |
-
// Completely different document
|
| 265 |
-
let doc_b = "Quantum mechanical systems exhibit wave particle duality in atomic physics.";
|
| 266 |
-
|
| 267 |
-
assert!(dedup.is_unique(doc_a));
|
| 268 |
-
// doc_a_near should be caught as near-duplicate (> 80% similarity)
|
| 269 |
-
assert!(!dedup.is_unique(doc_a_near));
|
| 270 |
-
// doc_b is different, should pass
|
| 271 |
-
assert!(dedup.is_unique(doc_b));
|
| 272 |
-
assert_eq!(dedup.count(), 2);
|
| 273 |
-
}
|
| 274 |
-
|
| 275 |
-
#[test]
|
| 276 |
-
fn test_string_tensor_cpu_and_gpu() {
|
| 277 |
-
use crate::string_tensor::StringTensor;
|
| 278 |
-
|
| 279 |
-
let raw = vec![
|
| 280 |
-
"apple", // 5B <= 12B (inlined)
|
| 281 |
-
"cat", // 3B <= 12B (inlined)
|
| 282 |
-
"twelve_bytes", // 12B <= 12B (inlined boundary)
|
| 283 |
-
"thirteen_bytes!", // 15B > 12B (referenced into pool)
|
| 284 |
-
"The quick brown fox jumps over the lazy dog.", // 44B > 12B
|
| 285 |
-
];
|
| 286 |
-
|
| 287 |
-
let st = StringTensor::from_strings(&raw);
|
| 288 |
-
assert_eq!(st.len(), 5);
|
| 289 |
-
|
| 290 |
-
// Verify inlining
|
| 291 |
-
assert!(st.views[0].is_inlined());
|
| 292 |
-
assert!(st.views[1].is_inlined());
|
| 293 |
-
assert!(st.views[2].is_inlined());
|
| 294 |
-
assert!(!st.views[3].is_inlined());
|
| 295 |
-
assert!(!st.views[4].is_inlined());
|
| 296 |
-
|
| 297 |
-
// Verify string content recovery
|
| 298 |
-
assert_eq!(st.get(0).unwrap(), "apple");
|
| 299 |
-
assert_eq!(st.get(1).unwrap(), "cat");
|
| 300 |
-
assert_eq!(st.get(2).unwrap(), "twelve_bytes");
|
| 301 |
-
assert_eq!(st.get(3).unwrap(), "thirteen_bytes!");
|
| 302 |
-
assert_eq!(
|
| 303 |
-
st.get(4).unwrap(),
|
| 304 |
-
"The quick brown fox jumps over the lazy dog."
|
| 305 |
-
);
|
| 306 |
-
|
| 307 |
-
// Fast equality check
|
| 308 |
-
let v0 = st.views[0];
|
| 309 |
-
let v1 = st.views[1];
|
| 310 |
-
assert!(v0.fast_eq(&v0, &st.pool));
|
| 311 |
-
assert!(!v0.fast_eq(&v1, &st.pool));
|
| 312 |
-
|
| 313 |
-
// Convert to GPU buffers: (offsets, payload)
|
| 314 |
-
let (offsets, payload) = st.to_gpu_buffers();
|
| 315 |
-
assert_eq!(offsets.len(), 6);
|
| 316 |
-
assert_eq!(offsets[0], 0);
|
| 317 |
-
assert_eq!(offsets[5] as usize, payload.len());
|
| 318 |
-
|
| 319 |
-
// Reconstruct from GPU buffers
|
| 320 |
-
let st_gpu = StringTensor::from_gpu_buffers(&offsets, &payload);
|
| 321 |
-
assert_eq!(st_gpu.len(), 5);
|
| 322 |
-
assert_eq!(st_gpu.to_vec(), raw);
|
| 323 |
-
|
| 324 |
-
// Filtering
|
| 325 |
-
let long_only = st.filter_by_length(13, 0);
|
| 326 |
-
assert_eq!(long_only.len(), 2);
|
| 327 |
-
assert_eq!(long_only.get(0).unwrap(), "thirteen_bytes!");
|
| 328 |
-
|
| 329 |
-
let prefix_filtered = st.filter_by_prefix("thirt");
|
| 330 |
-
assert_eq!(prefix_filtered.len(), 1);
|
| 331 |
-
assert_eq!(prefix_filtered.get(0).unwrap(), "thirteen_bytes!");
|
| 332 |
-
|
| 333 |
-
// Parallel hashing
|
| 334 |
-
let hashes = st.hash_all_parallel();
|
| 335 |
-
assert_eq!(hashes.len(), 5);
|
| 336 |
-
assert!(hashes[0] != hashes[1]);
|
| 337 |
-
}
|
| 338 |
-
|
| 339 |
-
#[test]
|
| 340 |
-
fn test_fused_pipeline_core() {
|
| 341 |
-
let raw = vec![
|
| 342 |
-
" Valid prose sentence for LLM training alpha\r\n".to_string(),
|
| 343 |
-
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(), // degenerate repetition -> reject
|
| 344 |
-
"Valid prose sentence for LLM training beta".to_string(),
|
| 345 |
-
"Valid prose sentence for LLM training alpha\r\n".to_string(), // duplicate -> reject
|
| 346 |
-
"".to_string(), // empty -> reject
|
| 347 |
-
];
|
| 348 |
-
|
| 349 |
-
let post_cfg = PostProcessConfig {
|
| 350 |
-
min_byte_len: 1,
|
| 351 |
-
..Default::default()
|
| 352 |
-
};
|
| 353 |
-
let qual_cfg = crate::quality::QualityConfig {
|
| 354 |
-
min_entropy: 1.5,
|
| 355 |
-
max_repetition_ratio: 0.4,
|
| 356 |
-
..Default::default()
|
| 357 |
-
};
|
| 358 |
-
let mut dedup = TwoStageDeduplicator::new();
|
| 359 |
-
|
| 360 |
-
let mut survivors = Vec::new();
|
| 361 |
-
for item in raw {
|
| 362 |
-
if let Some(cleaned) = process_single_record(&item, &post_cfg) {
|
| 363 |
-
let eval = crate::quality::evaluate_quality(&cleaned, &qual_cfg);
|
| 364 |
-
if eval.is_passed && dedup.try_stage(1, cleaned.as_bytes()).is_some() {
|
| 365 |
-
survivors.push(cleaned);
|
| 366 |
-
}
|
| 367 |
-
}
|
| 368 |
-
}
|
| 369 |
-
|
| 370 |
-
assert_eq!(survivors.len(), 2);
|
| 371 |
-
assert_eq!(survivors[0], "Valid prose sentence for LLM training alpha");
|
| 372 |
-
assert_eq!(survivors[1], "Valid prose sentence for LLM training beta");
|
| 373 |
-
|
| 374 |
-
let st = crate::string_tensor::StringTensor::from_strings(survivors);
|
| 375 |
-
assert_eq!(st.len(), 2);
|
| 376 |
-
assert_eq!(
|
| 377 |
-
st.get(0).unwrap(),
|
| 378 |
-
"Valid prose sentence for LLM training alpha"
|
| 379 |
-
);
|
| 380 |
-
assert_eq!(
|
| 381 |
-
st.get(1).unwrap(),
|
| 382 |
-
"Valid prose sentence for LLM training beta"
|
| 383 |
-
);
|
| 384 |
-
}
|
|
|
|
| 1 |
+
use std::fs;
|
| 2 |
+
use std::path::PathBuf;
|
| 3 |
+
|
| 4 |
+
use crate::coord::StreamCoordinate;
|
| 5 |
+
use crate::dedup::TwoStageDeduplicator;
|
| 6 |
+
use crate::metadata::DualMetadataManager;
|
| 7 |
+
use crate::postprocess::{PostProcessConfig, process_batch_parallel, process_single_record};
|
| 8 |
+
use crate::serialize::{
|
| 9 |
+
DataValue, build_arrow_record_batch, classify_string_or_json,
|
| 10 |
+
serialize_columns_to_arrow_ipc_bytes, serialize_columns_to_jsonl_bytes,
|
| 11 |
+
serialize_columns_to_parquet_bytes,
|
| 12 |
+
};
|
| 13 |
+
use crate::simd_cpu::{contains_byte_fast, detect_cpu_isa, find_newline_fast};
|
| 14 |
+
|
| 15 |
+
fn temp_test_path(name: &str, ext: &str) -> PathBuf {
|
| 16 |
+
let mut p = std::env::temp_dir();
|
| 17 |
+
p.push(format!(
|
| 18 |
+
"luminamoon_test_{}_{}.{}",
|
| 19 |
+
name,
|
| 20 |
+
std::process::id(),
|
| 21 |
+
ext
|
| 22 |
+
));
|
| 23 |
+
p
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
#[test]
|
| 27 |
+
fn test_stream_coordinate_roundtrip() {
|
| 28 |
+
let c = StreamCoordinate {
|
| 29 |
+
shard_id: 42,
|
| 30 |
+
byte_offset: 1024 * 1024,
|
| 31 |
+
row_group_idx: 3,
|
| 32 |
+
page_idx: 7,
|
| 33 |
+
row_offset_in_page: 128,
|
| 34 |
+
global_row_idx: 99999,
|
| 35 |
+
};
|
| 36 |
+
let bytes = c.to_bytes();
|
| 37 |
+
assert_eq!(bytes.len(), StreamCoordinate::BYTE_LEN);
|
| 38 |
+
let decoded = StreamCoordinate::from_bytes(&bytes);
|
| 39 |
+
assert_eq!(c, decoded);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
#[test]
|
| 43 |
+
fn test_dual_metadata_ping_pong_and_dedup_wal() {
|
| 44 |
+
let meta_path = temp_test_path("ping_pong_meta", "meta");
|
| 45 |
+
let _ = fs::remove_file(&meta_path);
|
| 46 |
+
let _ = fs::remove_file(format!("{}.dedup", meta_path.display()));
|
| 47 |
+
|
| 48 |
+
{
|
| 49 |
+
let mut mgr = DualMetadataManager::open(&meta_path).unwrap();
|
| 50 |
+
assert_eq!(mgr.committed_batch_id(), 0);
|
| 51 |
+
|
| 52 |
+
assert!(mgr.deduplicator.try_stage(1, b"line_alpha").is_some());
|
| 53 |
+
assert!(mgr.deduplicator.try_stage(1, b"line_alpha").is_none());
|
| 54 |
+
|
| 55 |
+
let c1 = StreamCoordinate {
|
| 56 |
+
shard_id: 1,
|
| 57 |
+
byte_offset: 512,
|
| 58 |
+
row_group_idx: 0,
|
| 59 |
+
page_idx: 0,
|
| 60 |
+
row_offset_in_page: 0,
|
| 61 |
+
global_row_idx: 10,
|
| 62 |
+
};
|
| 63 |
+
mgr.stage_batch_in_ram(1, c1);
|
| 64 |
+
let committed = mgr.commit_gpu_batch(1).unwrap();
|
| 65 |
+
assert_eq!(committed, c1);
|
| 66 |
+
assert_eq!(mgr.committed_batch_id(), 1);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Re-open and verify recovery
|
| 70 |
+
{
|
| 71 |
+
let mut mgr = DualMetadataManager::open(&meta_path).unwrap();
|
| 72 |
+
assert_eq!(mgr.committed_batch_id(), 1);
|
| 73 |
+
assert_eq!(mgr.committed_coordinate().shard_id, 1);
|
| 74 |
+
assert_eq!(mgr.committed_coordinate().byte_offset, 512);
|
| 75 |
+
|
| 76 |
+
// "line_alpha" must still be marked as committed
|
| 77 |
+
assert!(mgr.deduplicator.try_stage(2, b"line_alpha").is_none());
|
| 78 |
+
assert!(mgr.deduplicator.try_stage(2, b"line_beta").is_some());
|
| 79 |
+
|
| 80 |
+
let c2 = StreamCoordinate {
|
| 81 |
+
shard_id: 1,
|
| 82 |
+
byte_offset: 1024,
|
| 83 |
+
row_group_idx: 0,
|
| 84 |
+
page_idx: 1,
|
| 85 |
+
row_offset_in_page: 0,
|
| 86 |
+
global_row_idx: 20,
|
| 87 |
+
};
|
| 88 |
+
mgr.stage_batch_in_ram(2, c2);
|
| 89 |
+
mgr.commit_gpu_batch(2).unwrap();
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
let _ = fs::remove_file(&meta_path);
|
| 93 |
+
let _ = fs::remove_file(format!("{}.dedup", meta_path.display()));
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
#[test]
|
| 97 |
+
fn test_two_stage_deduplicator() {
|
| 98 |
+
let mut dedup = TwoStageDeduplicator::new();
|
| 99 |
+
assert_eq!(dedup.committed_len(), 0);
|
| 100 |
+
|
| 101 |
+
assert!(dedup.try_stage(1, b"apple").is_some());
|
| 102 |
+
assert!(dedup.try_stage(1, b"apple").is_none());
|
| 103 |
+
assert!(dedup.try_stage(1, b"banana").is_some());
|
| 104 |
+
assert_eq!(dedup.staged_len(), 2);
|
| 105 |
+
|
| 106 |
+
let committed = dedup.commit_batch(1);
|
| 107 |
+
assert_eq!(committed.len(), 2);
|
| 108 |
+
assert_eq!(dedup.committed_len(), 2);
|
| 109 |
+
assert_eq!(dedup.staged_len(), 0);
|
| 110 |
+
|
| 111 |
+
// Staging the same in batch 2 should be rejected
|
| 112 |
+
assert!(dedup.try_stage(2, b"apple").is_none());
|
| 113 |
+
assert!(dedup.try_stage(2, b"orange").is_some());
|
| 114 |
+
|
| 115 |
+
// Rollback batch 2
|
| 116 |
+
let rolled = dedup.rollback_batch(2);
|
| 117 |
+
assert_eq!(rolled, 1);
|
| 118 |
+
assert!(dedup.try_stage(3, b"orange").is_some());
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
#[test]
|
| 122 |
+
fn test_simd_cpu_detection_and_scans() {
|
| 123 |
+
let isa = detect_cpu_isa();
|
| 124 |
+
assert!(!isa.as_str().is_empty());
|
| 125 |
+
|
| 126 |
+
let text = b"hello\r\nworld\nfrom\rsimd";
|
| 127 |
+
assert!(contains_byte_fast(text, b'\r'));
|
| 128 |
+
assert!(contains_byte_fast(text, b'\n'));
|
| 129 |
+
assert!(!contains_byte_fast(text, b'z'));
|
| 130 |
+
|
| 131 |
+
let pos = find_newline_fast(text);
|
| 132 |
+
assert!(pos.is_some());
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
#[test]
|
| 136 |
+
fn test_postprocess_cleaning_and_json_extraction() {
|
| 137 |
+
let cfg = PostProcessConfig {
|
| 138 |
+
trim_whitespace: true,
|
| 139 |
+
normalize_newlines: true,
|
| 140 |
+
min_byte_len: 3,
|
| 141 |
+
max_byte_len: 20,
|
| 142 |
+
truncate_over_max: true,
|
| 143 |
+
json_extract_key: Some("data.text".to_owned()),
|
| 144 |
+
};
|
| 145 |
+
|
| 146 |
+
let raw_json = r#"{"data": {"text": " Hello\r\nWorld! "}}"#;
|
| 147 |
+
let cleaned = process_single_record(raw_json, &cfg);
|
| 148 |
+
assert_eq!(cleaned, Some("Hello\nWorld!".to_owned()));
|
| 149 |
+
|
| 150 |
+
// Truncation test
|
| 151 |
+
let long_json = r#"{"data": {"text": "A very long sentence that will exceed twenty bytes"}}"#;
|
| 152 |
+
let truncated = process_single_record(long_json, &cfg).unwrap();
|
| 153 |
+
assert!(truncated.len() <= 20);
|
| 154 |
+
|
| 155 |
+
// Parallel batch test
|
| 156 |
+
let batch = vec![
|
| 157 |
+
r#"{"data": {"text": "row 1\r\n"}}"#.to_owned(),
|
| 158 |
+
r#"{"data": {"text": "row 2"}}"#.to_owned(),
|
| 159 |
+
r#"{"data": {"text": ""}}"#.to_owned(), // filtered due to min_byte_len
|
| 160 |
+
];
|
| 161 |
+
let batch_cleaned = process_batch_parallel(&batch, &cfg);
|
| 162 |
+
assert_eq!(batch_cleaned.len(), 2);
|
| 163 |
+
assert_eq!(batch_cleaned[0], "row 1");
|
| 164 |
+
assert_eq!(batch_cleaned[1], "row 2");
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
#[test]
|
| 168 |
+
fn test_classify_string_or_json() {
|
| 169 |
+
let s1 = "just plain text";
|
| 170 |
+
assert_eq!(classify_string_or_json(s1), DataValue::Text(s1.to_owned()));
|
| 171 |
+
|
| 172 |
+
let s2 = r#"{"key": "value", "id": 123}"#;
|
| 173 |
+
assert_eq!(classify_string_or_json(s2), DataValue::Json(s2.to_owned()));
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
#[test]
|
| 177 |
+
fn test_serialization_parquet_and_jsonl() {
|
| 178 |
+
let columns = vec![
|
| 179 |
+
(
|
| 180 |
+
"text".to_owned(),
|
| 181 |
+
vec![
|
| 182 |
+
DataValue::Text("first".to_owned()),
|
| 183 |
+
DataValue::Text("second".to_owned()),
|
| 184 |
+
],
|
| 185 |
+
),
|
| 186 |
+
(
|
| 187 |
+
"score".to_owned(),
|
| 188 |
+
vec![DataValue::Float(0.95), DataValue::Float(0.42)],
|
| 189 |
+
),
|
| 190 |
+
(
|
| 191 |
+
"label".to_owned(),
|
| 192 |
+
vec![DataValue::Int(1), DataValue::Int(0)],
|
| 193 |
+
),
|
| 194 |
+
];
|
| 195 |
+
|
| 196 |
+
let (schema, batch) = build_arrow_record_batch(&columns, false).unwrap();
|
| 197 |
+
assert_eq!(batch.num_rows(), 2);
|
| 198 |
+
assert_eq!(schema.fields().len(), 3);
|
| 199 |
+
|
| 200 |
+
// Parquet serialization
|
| 201 |
+
let pq_bytes = serialize_columns_to_parquet_bytes(&columns, false).unwrap();
|
| 202 |
+
assert!(!pq_bytes.is_empty());
|
| 203 |
+
assert_eq!(&pq_bytes[0..4], b"PAR1");
|
| 204 |
+
|
| 205 |
+
// Arrow IPC serialization
|
| 206 |
+
let ipc_bytes = serialize_columns_to_arrow_ipc_bytes(&columns, false).unwrap();
|
| 207 |
+
assert!(!ipc_bytes.is_empty());
|
| 208 |
+
assert_eq!(&ipc_bytes[0..6], b"ARROW1");
|
| 209 |
+
|
| 210 |
+
// JSONL serialization
|
| 211 |
+
let jsonl_bytes = serialize_columns_to_jsonl_bytes(&columns).unwrap();
|
| 212 |
+
assert!(!jsonl_bytes.is_empty());
|
| 213 |
+
let jsonl_str = String::from_utf8(jsonl_bytes).unwrap();
|
| 214 |
+
assert!(jsonl_str.contains("\"text\":\"first\""));
|
| 215 |
+
assert!(jsonl_str.contains("\"score\":0.95"));
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
#[test]
|
| 219 |
+
fn test_quality_filter() {
|
| 220 |
+
use crate::quality::{QualityConfig, evaluate_quality};
|
| 221 |
+
|
| 222 |
+
let cfg = QualityConfig {
|
| 223 |
+
min_chars: 20,
|
| 224 |
+
max_chars: 0,
|
| 225 |
+
min_words: 3,
|
| 226 |
+
min_entropy: 2.5,
|
| 227 |
+
max_repetition_ratio: 0.35,
|
| 228 |
+
max_symbol_ratio: 0.30,
|
| 229 |
+
};
|
| 230 |
+
|
| 231 |
+
// 1. High quality normal text
|
| 232 |
+
let good_text =
|
| 233 |
+
"The quick brown fox jumps over the lazy dog and runs across the wide green meadow.";
|
| 234 |
+
let res = evaluate_quality(good_text, &cfg);
|
| 235 |
+
assert!(res.is_passed);
|
| 236 |
+
assert!(res.entropy > 2.5);
|
| 237 |
+
assert!(res.repetition_ratio < 0.2);
|
| 238 |
+
assert!(res.symbol_ratio < 0.1);
|
| 239 |
+
|
| 240 |
+
// 2. Degenerate repetition
|
| 241 |
+
let repeated =
|
| 242 |
+
"spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam spam";
|
| 243 |
+
let res_rep = evaluate_quality(repeated, &cfg);
|
| 244 |
+
assert!(!res_rep.is_passed);
|
| 245 |
+
assert!(res_rep.repetition_ratio > 0.35);
|
| 246 |
+
|
| 247 |
+
// 3. Code / symbol dump
|
| 248 |
+
let symbol_dump = "{{{[(((@@@###$$$%%%^^^&&&***)))}}}];;;===+++///<<<>>>!!!???";
|
| 249 |
+
let res_sym = evaluate_quality(symbol_dump, &cfg);
|
| 250 |
+
assert!(!res_sym.is_passed);
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
#[test]
|
| 254 |
+
fn test_minhash_fuzzy_deduplicator() {
|
| 255 |
+
use crate::minhash::MinHashDeduplicator;
|
| 256 |
+
|
| 257 |
+
let mut dedup = MinHashDeduplicator::new(0.80, 4, 64);
|
| 258 |
+
|
| 259 |
+
let doc_a =
|
| 260 |
+
"Artificial intelligence and deep learning models require efficient data pipelines.";
|
| 261 |
+
// Near duplicate (just 1 typo/word difference)
|
| 262 |
+
let doc_a_near =
|
| 263 |
+
"Artificial intelligence and deep learning models require efficient data pipelining.";
|
| 264 |
+
// Completely different document
|
| 265 |
+
let doc_b = "Quantum mechanical systems exhibit wave particle duality in atomic physics.";
|
| 266 |
+
|
| 267 |
+
assert!(dedup.is_unique(doc_a));
|
| 268 |
+
// doc_a_near should be caught as near-duplicate (> 80% similarity)
|
| 269 |
+
assert!(!dedup.is_unique(doc_a_near));
|
| 270 |
+
// doc_b is different, should pass
|
| 271 |
+
assert!(dedup.is_unique(doc_b));
|
| 272 |
+
assert_eq!(dedup.count(), 2);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
#[test]
|
| 276 |
+
fn test_string_tensor_cpu_and_gpu() {
|
| 277 |
+
use crate::string_tensor::StringTensor;
|
| 278 |
+
|
| 279 |
+
let raw = vec![
|
| 280 |
+
"apple", // 5B <= 12B (inlined)
|
| 281 |
+
"cat", // 3B <= 12B (inlined)
|
| 282 |
+
"twelve_bytes", // 12B <= 12B (inlined boundary)
|
| 283 |
+
"thirteen_bytes!", // 15B > 12B (referenced into pool)
|
| 284 |
+
"The quick brown fox jumps over the lazy dog.", // 44B > 12B
|
| 285 |
+
];
|
| 286 |
+
|
| 287 |
+
let st = StringTensor::from_strings(&raw);
|
| 288 |
+
assert_eq!(st.len(), 5);
|
| 289 |
+
|
| 290 |
+
// Verify inlining
|
| 291 |
+
assert!(st.views[0].is_inlined());
|
| 292 |
+
assert!(st.views[1].is_inlined());
|
| 293 |
+
assert!(st.views[2].is_inlined());
|
| 294 |
+
assert!(!st.views[3].is_inlined());
|
| 295 |
+
assert!(!st.views[4].is_inlined());
|
| 296 |
+
|
| 297 |
+
// Verify string content recovery
|
| 298 |
+
assert_eq!(st.get(0).unwrap(), "apple");
|
| 299 |
+
assert_eq!(st.get(1).unwrap(), "cat");
|
| 300 |
+
assert_eq!(st.get(2).unwrap(), "twelve_bytes");
|
| 301 |
+
assert_eq!(st.get(3).unwrap(), "thirteen_bytes!");
|
| 302 |
+
assert_eq!(
|
| 303 |
+
st.get(4).unwrap(),
|
| 304 |
+
"The quick brown fox jumps over the lazy dog."
|
| 305 |
+
);
|
| 306 |
+
|
| 307 |
+
// Fast equality check
|
| 308 |
+
let v0 = st.views[0];
|
| 309 |
+
let v1 = st.views[1];
|
| 310 |
+
assert!(v0.fast_eq(&v0, &st.pool));
|
| 311 |
+
assert!(!v0.fast_eq(&v1, &st.pool));
|
| 312 |
+
|
| 313 |
+
// Convert to GPU buffers: (offsets, payload)
|
| 314 |
+
let (offsets, payload) = st.to_gpu_buffers();
|
| 315 |
+
assert_eq!(offsets.len(), 6);
|
| 316 |
+
assert_eq!(offsets[0], 0);
|
| 317 |
+
assert_eq!(offsets[5] as usize, payload.len());
|
| 318 |
+
|
| 319 |
+
// Reconstruct from GPU buffers
|
| 320 |
+
let st_gpu = StringTensor::from_gpu_buffers(&offsets, &payload);
|
| 321 |
+
assert_eq!(st_gpu.len(), 5);
|
| 322 |
+
assert_eq!(st_gpu.to_vec(), raw);
|
| 323 |
+
|
| 324 |
+
// Filtering
|
| 325 |
+
let long_only = st.filter_by_length(13, 0);
|
| 326 |
+
assert_eq!(long_only.len(), 2);
|
| 327 |
+
assert_eq!(long_only.get(0).unwrap(), "thirteen_bytes!");
|
| 328 |
+
|
| 329 |
+
let prefix_filtered = st.filter_by_prefix("thirt");
|
| 330 |
+
assert_eq!(prefix_filtered.len(), 1);
|
| 331 |
+
assert_eq!(prefix_filtered.get(0).unwrap(), "thirteen_bytes!");
|
| 332 |
+
|
| 333 |
+
// Parallel hashing
|
| 334 |
+
let hashes = st.hash_all_parallel();
|
| 335 |
+
assert_eq!(hashes.len(), 5);
|
| 336 |
+
assert!(hashes[0] != hashes[1]);
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
#[test]
|
| 340 |
+
fn test_fused_pipeline_core() {
|
| 341 |
+
let raw = vec![
|
| 342 |
+
" Valid prose sentence for LLM training alpha\r\n".to_string(),
|
| 343 |
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(), // degenerate repetition -> reject
|
| 344 |
+
"Valid prose sentence for LLM training beta".to_string(),
|
| 345 |
+
"Valid prose sentence for LLM training alpha\r\n".to_string(), // duplicate -> reject
|
| 346 |
+
"".to_string(), // empty -> reject
|
| 347 |
+
];
|
| 348 |
+
|
| 349 |
+
let post_cfg = PostProcessConfig {
|
| 350 |
+
min_byte_len: 1,
|
| 351 |
+
..Default::default()
|
| 352 |
+
};
|
| 353 |
+
let qual_cfg = crate::quality::QualityConfig {
|
| 354 |
+
min_entropy: 1.5,
|
| 355 |
+
max_repetition_ratio: 0.4,
|
| 356 |
+
..Default::default()
|
| 357 |
+
};
|
| 358 |
+
let mut dedup = TwoStageDeduplicator::new();
|
| 359 |
+
|
| 360 |
+
let mut survivors = Vec::new();
|
| 361 |
+
for item in raw {
|
| 362 |
+
if let Some(cleaned) = process_single_record(&item, &post_cfg) {
|
| 363 |
+
let eval = crate::quality::evaluate_quality(&cleaned, &qual_cfg);
|
| 364 |
+
if eval.is_passed && dedup.try_stage(1, cleaned.as_bytes()).is_some() {
|
| 365 |
+
survivors.push(cleaned);
|
| 366 |
+
}
|
| 367 |
+
}
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
assert_eq!(survivors.len(), 2);
|
| 371 |
+
assert_eq!(survivors[0], "Valid prose sentence for LLM training alpha");
|
| 372 |
+
assert_eq!(survivors[1], "Valid prose sentence for LLM training beta");
|
| 373 |
+
|
| 374 |
+
let st = crate::string_tensor::StringTensor::from_strings(survivors);
|
| 375 |
+
assert_eq!(st.len(), 2);
|
| 376 |
+
assert_eq!(
|
| 377 |
+
st.get(0).unwrap(),
|
| 378 |
+
"Valid prose sentence for LLM training alpha"
|
| 379 |
+
);
|
| 380 |
+
assert_eq!(
|
| 381 |
+
st.get(1).unwrap(),
|
| 382 |
+
"Valid prose sentence for LLM training beta"
|
| 383 |
+
);
|
| 384 |
+
}
|
docs/README.md
CHANGED
|
@@ -1,101 +1,101 @@
|
|
| 1 |
-
# Loomy (luminamoon) Documentation
|
| 2 |
-
|
| 3 |
-
<div class="portal-badges">
|
| 4 |
-
<span class="pill-badge">LuminaV Optimizer v1.6.0</span>
|
| 5 |
-
<span class="pill-badge pill-badge-green">Data Engine v1.1.0</span>
|
| 6 |
-
<span class="pill-badge pill-badge-cyan">LSO-Eval Verified</span>
|
| 7 |
-
<span class="pill-badge pill-badge-cyan">Rust 1.99.1 + Python ABI3T</span>
|
| 8 |
-
</div>
|
| 9 |
-
|
| 10 |
-
**Loomy (`luminamoon`)** provides low-precision numerical optimization, rational neural network layers, and dataset text preprocessing utilities developed by **Silver Moon ([@cloverxion](https://github.com/cloverxion))** and **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**.
|
| 11 |
-
|
| 12 |
-
<div class="friendly-grid">
|
| 13 |
-
<a class="friendly-card" href="en/01_getting_started/">
|
| 14 |
-
<h3>Getting Started</h3>
|
| 15 |
-
<p>System requirements, pip install, Rust compilation, and quickstart examples for NumPy, PyTorch, and Data Engine.</p>
|
| 16 |
-
</a>
|
| 17 |
-
<a class="friendly-card" href="en/02_user_guide/">
|
| 18 |
-
<h3>LuminaV Optimizer</h3>
|
| 19 |
-
<p>Dual-Buffer CLM vs Single-Buffer TD-CLM, mixed-precision, stochastic rounding, and FSDP distributed training.</p>
|
| 20 |
-
</a>
|
| 21 |
-
<a class="friendly-card" href="dataloader/standalone_guide/">
|
| 22 |
-
<h3>Data Engine</h3>
|
| 23 |
-
<p>Fused SIMD text preprocessing, StringTensor CPU/GPU buffers, deduplication, and 128B Ping-Pong checkpoint recovery.</p>
|
| 24 |
-
</a>
|
| 25 |
-
<a class="friendly-card" href="lso_eval/">
|
| 26 |
-
<h3>LSO-Eval</h3>
|
| 27 |
-
<p>Z3 SMT safety checks, 2D trajectory benchmarks, 1-Pass EMA parity, and Qwen3.5-4B & Vision Two-Class evaluation.</p>
|
| 28 |
-
</a>
|
| 29 |
-
<a class="friendly-card" href="en/03_configuration_api/">
|
| 30 |
-
<h3>API Reference</h3>
|
| 31 |
-
<p>Complete signatures, parameter reference tables, validation constraints, and return types for all modules.</p>
|
| 32 |
-
</a>
|
| 33 |
-
<a class="friendly-card" href="security/">
|
| 34 |
-
<h3>Security & Notes</h3>
|
| 35 |
-
<p>Direct vulnerability disclosure policy to Silver Moon, CodeQL scanning, and third-party library compatibility notes.</p>
|
| 36 |
-
</a>
|
| 37 |
-
<a class="friendly-card" href="changelogs/">
|
| 38 |
-
<h3>Changelogs</h3>
|
| 39 |
-
<p>Version-by-version release history for Software package distributions and LuminaV algorithm specifications.</p>
|
| 40 |
-
</a>
|
| 41 |
-
</div>
|
| 42 |
-
|
| 43 |
-
---
|
| 44 |
-
|
| 45 |
-
## 1. User Documentation
|
| 46 |
-
|
| 47 |
-
Source directory: `docs/en/`
|
| 48 |
-
|
| 49 |
-
- **[01. Getting Started](en/01_getting_started.md)**: System requirements, installation, source compilation via Maturin/Rust, hardware & SIMD ISA detection (`LuminaV.detect_hardware()`, `lm.data_cpu_isa()`), and quickstarts for NumPy, PyTorch, and Data Engine (`FusedPipeline`, `StringTensor`, `StateTracker`, `ResumablePipeline`).
|
| 50 |
-
- **[02. User Guide](en/02_user_guide.md)**: Optimizer buffer modes (2-Buffer CLM, 1-Buffer TD-CLM, and Triangle Family T1/T2), mixed-precision & stochastic rounding, step bounding, FSDP distributed training, plus Data Engine SIMD text cleaning, quality filtering, two-stage XXH3-64 & MinHash deduplication, PII masking, StringTensor buffers, and Ping-Pong checkpoint recovery.
|
| 51 |
-
- **[03. Configuration & API Reference](en/03_configuration_api.md)**: Complete parameter reference tables, public methods, and validation rules for `LuminaV`, `TextCleaner`, `QualityFilter`, `StreamDeduplicator`, `MinHashDeduplicator`, `PIIMasker`, `StateTracker`, `StreamCoordinate`, `StringTensor`, `FusedPipeline`, and `ResumablePipeline`.
|
| 52 |
-
- **[04. Architecture & Hardware Acceleration](en/04_architecture_and_hardware.md)**: C4 dispatch hierarchy, Optimizer Rust crates (`core`, `simd`, `vulkan`, `dispatch`, `triton-aot`), Data Engine Rust modules (`constants`, `coord`, `dedup`, `error`, `metadata`, `minhash`, `postprocess`, `py_bridge`, `quality`, `serialize`, `simd_cpu`, `string_tensor`), buffer memory protocols, and free-threaded Python (`abi3t` / PEP 703).
|
| 53 |
-
|
| 54 |
-
---
|
| 55 |
-
|
| 56 |
-
## 2. Developer Modular Documentation
|
| 57 |
-
|
| 58 |
-
Technical documentation for contributors working on the Rust crates, GPU kernels, and Python bindings (`docs/optimize/` & `data/docs/`):
|
| 59 |
-
|
| 60 |
-
### A. LuminaV Optimizer
|
| 61 |
-
Source directories: `docs/optimize/` & `optimizer/`
|
| 62 |
-
|
| 63 |
-
- **[LuminaV Optimizer Overview & User Guide (`optimizer/README.md`)](optimize/standalone_guide.md)**
|
| 64 |
-
- **[Developer Optimizer Index (`docs/optimize/README.md`)](optimize/README.md)**
|
| 65 |
-
- **[01. Core & Math](optimize/core/README.md)**: CLM, TD-CLM Triangle Defect, PRNG stochastic rounding, and bias correction.
|
| 66 |
-
- **[02. SIMD Vectorization](optimize/simd/README.md)**: AVX-512 and AVX2/FMA x86_64 intrinsics and Rayon parallel threadpool.
|
| 67 |
-
- **[03. Vulkan Compute](optimize/vulkan/README.md)**: Ash Vulkan 1.3/1.4 SPIR-V compute shaders and UMA host buffers.
|
| 68 |
-
- **[04. Triton AOT & GPU Drivers](optimize/triton_aot/README.md)**: AOT compilation, CUDA/HIP driver dynamic loading, and standalone C-API.
|
| 69 |
-
- **[05. Dispatch & Routing](optimize/dispatch/README.md)**: Hardware capability detector and 16,384-element dispatch threshold heuristic.
|
| 70 |
-
- **[06. Python FFI & PyO3 Bridge](optimize/python_ffi/README.md)**: PyO3 GIL release (`py.detach()`), pointer aliasing checks, and NumPy buffer protocols.
|
| 71 |
-
- **[07. Python Runtime & GPU Mixin](optimize/python_runtime/README.md)**: `LuminaV` class, `constants.py`, Triton JIT kernels, FSDP synchronization, and state checkpointing.
|
| 72 |
-
|
| 73 |
-
### B. LuminaMoon Data Engine
|
| 74 |
-
Source directories: `data/` & `data/docs/`
|
| 75 |
-
|
| 76 |
-
- **[LuminaMoon Data Engine Overview & User Guide (`data/README.md`)](dataloader/standalone_guide.md)**
|
| 77 |
-
- **[Developer Data Engine Index (`data/docs/README.md`)](dataloader/README.md)**
|
| 78 |
-
- **[01. TextCleaner & QualityFilter](dataloader/simd_and_postprocess/README.md)**: AVX-512/AVX2/NEON SIMD carriage-return stripping, JSON extraction, Shannon entropy, repetition loop, and symbol ratio filters.
|
| 79 |
-
- **[02. StreamDeduplicator & MinHashDeduplicator](dataloader/dedup/README.md)**: Exact streaming deduplication with `xxh3_64` (`IdentityBuildHasher` SwissTable), WAL persistence, and 64-permutation MinHash LSH fuzzy deduplication.
|
| 80 |
-
- **[03. PIIMasker & Data Sanitization](dataloader/simd_and_postprocess/README.md)**: Pattern-based and regex masking for emails, IPv4 addresses, phone numbers, and secret tokens.
|
| 81 |
-
- **[04. StateTracker & StreamCoordinate](dataloader/coord_and_metadata/README.md)**: 32-byte `StreamCoordinate` navigation, dual-slot 128-byte Ping-Pong checkpoint persistence, and multi-epoch state tracking.
|
| 82 |
-
- **[05. StringTensor & Memory Layouts](dataloader/coord_and_metadata/README.md)**: CPU StringView (inlined <= 12 bytes) and GPU contiguous offset/payload buffers compatible with PyTorch, NumPy, Arrow, and Polars.
|
| 83 |
-
- **[06. FusedPipeline & ResumablePipeline](dataloader/python_ffi_and_runtime/README.md)**: Multi-threaded Rayon single-pass preprocessing pipeline and resumable batch pipeline with background prefetcher.
|
| 84 |
-
- **[07. Multimodal Serialization](dataloader/multimodal_and_hf_hub/README.md)**: Serialization to JSONL, Parquet with Zstd compression and embedded image payloads, Arrow IPC, and CSV.
|
| 85 |
-
|
| 86 |
-
### C. Ra-Family Neural Architecture & Theoretical References
|
| 87 |
-
Source directories: `docs/nn/` & `references/`
|
| 88 |
-
|
| 89 |
-
- **[Ra-Family Overview (`docs/nn/README.md`)](nn/README.md)**: Square-root-free `RaNorm` & `RaBatchNorm2d`, rational `RaTanh` & `RaGELU`, bounded `PosRaGLU` / `RaGLU` / `TriGLU`, and compact-support `ra_softmax` & `RaAttention`.
|
| 90 |
-
- **[Ra-Family Standalone Guide (`docs/nn/standalone_guide.md`)](nn/standalone_guide.md)**: Drop-in Transformer block replacement, `RaBatchNorm2d` `Conv2d` parameter folding, and backend introspection.
|
| 91 |
-
- **[Theoretical References (`docs/references.md`)](references.md)**: Component mapping, literature comparisons across all 29 referenced papers, and BibTeX database (`references/bibliography.bib`).
|
| 92 |
-
|
| 93 |
-
---
|
| 94 |
-
|
| 95 |
-
## 3. LSO-Eval Verification & Library Compatibility Notes
|
| 96 |
-
|
| 97 |
-
Source directories: `eval/`, `docs/library_audit/`, & `data/docs/library_audit/`
|
| 98 |
-
|
| 99 |
-
- **[LSO-Eval Optimizer Evaluation & Verification (`eval/README.md`)](lso_eval.md)**: Algorithm-agnostic Z3 SMT solver invariant checks, 2D trajectory benchmarks, numerical parity tests, and Two-Class 4B LLM & Vision evaluation (`LuminaV v1.5.3–v1.6.0`).
|
| 100 |
-
- **[Optimizer Library Notes (`docs/library_audit/`)](library_audit/README.md)**: Version and ABI compatibility notes for Rust 1.99.1 toolchain, PyO3/Maturin, PyTorch 2.14.1, Triton JIT 3.8.0, CUDA/ROCm, and Vulkan Ash compute pipeline.
|
| 101 |
-
- **[Data Engine Library Notes (`data/docs/library_audit/`)](library_audit/README.md)**: Version and compatibility notes for Rust 1.99.1 / Edition 2024, `pyo3 0.30.1` (`abi3t`), Apache Arrow/Parquet/IPC `60.0.0`, `rayon 1.12.0`, `xxhash-rust 0.8.19` (`xxh3`), `crc32fast 1.5.2`, `hashbrown 0.17.1`, `parking_lot 0.12.5`, `serde 1.0.229`, and HuggingFace Hub API protocols.
|
|
|
|
| 1 |
+
# Loomy (luminamoon) Documentation
|
| 2 |
+
|
| 3 |
+
<div class="portal-badges">
|
| 4 |
+
<span class="pill-badge">LuminaV Optimizer v1.6.0</span>
|
| 5 |
+
<span class="pill-badge pill-badge-green">Data Engine v1.1.0</span>
|
| 6 |
+
<span class="pill-badge pill-badge-cyan">LSO-Eval Verified</span>
|
| 7 |
+
<span class="pill-badge pill-badge-cyan">Rust 1.99.1 + Python ABI3T</span>
|
| 8 |
+
</div>
|
| 9 |
+
|
| 10 |
+
**Loomy (`luminamoon`)** provides low-precision numerical optimization, rational neural network layers, and dataset text preprocessing utilities developed by **Silver Moon ([@cloverxion](https://github.com/cloverxion))** and **Lumina Moon ([@cloverx-id](https://github.com/cloverx-id)) contributors**.
|
| 11 |
+
|
| 12 |
+
<div class="friendly-grid">
|
| 13 |
+
<a class="friendly-card" href="en/01_getting_started/">
|
| 14 |
+
<h3>Getting Started</h3>
|
| 15 |
+
<p>System requirements, pip install, Rust compilation, and quickstart examples for NumPy, PyTorch, and Data Engine.</p>
|
| 16 |
+
</a>
|
| 17 |
+
<a class="friendly-card" href="en/02_user_guide/">
|
| 18 |
+
<h3>LuminaV Optimizer</h3>
|
| 19 |
+
<p>Dual-Buffer CLM vs Single-Buffer TD-CLM, mixed-precision, stochastic rounding, and FSDP distributed training.</p>
|
| 20 |
+
</a>
|
| 21 |
+
<a class="friendly-card" href="dataloader/standalone_guide/">
|
| 22 |
+
<h3>Data Engine</h3>
|
| 23 |
+
<p>Fused SIMD text preprocessing, StringTensor CPU/GPU buffers, deduplication, and 128B Ping-Pong checkpoint recovery.</p>
|
| 24 |
+
</a>
|
| 25 |
+
<a class="friendly-card" href="lso_eval/">
|
| 26 |
+
<h3>LSO-Eval</h3>
|
| 27 |
+
<p>Z3 SMT safety checks, 2D trajectory benchmarks, 1-Pass EMA parity, and Qwen3.5-4B & Vision Two-Class evaluation.</p>
|
| 28 |
+
</a>
|
| 29 |
+
<a class="friendly-card" href="en/03_configuration_api/">
|
| 30 |
+
<h3>API Reference</h3>
|
| 31 |
+
<p>Complete signatures, parameter reference tables, validation constraints, and return types for all modules.</p>
|
| 32 |
+
</a>
|
| 33 |
+
<a class="friendly-card" href="security/">
|
| 34 |
+
<h3>Security & Notes</h3>
|
| 35 |
+
<p>Direct vulnerability disclosure policy to Silver Moon, CodeQL scanning, and third-party library compatibility notes.</p>
|
| 36 |
+
</a>
|
| 37 |
+
<a class="friendly-card" href="changelogs/">
|
| 38 |
+
<h3>Changelogs</h3>
|
| 39 |
+
<p>Version-by-version release history for Software package distributions and LuminaV algorithm specifications.</p>
|
| 40 |
+
</a>
|
| 41 |
+
</div>
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## 1. User Documentation
|
| 46 |
+
|
| 47 |
+
Source directory: `docs/en/`
|
| 48 |
+
|
| 49 |
+
- **[01. Getting Started](en/01_getting_started.md)**: System requirements, installation, source compilation via Maturin/Rust, hardware & SIMD ISA detection (`LuminaV.detect_hardware()`, `lm.data_cpu_isa()`), and quickstarts for NumPy, PyTorch, and Data Engine (`FusedPipeline`, `StringTensor`, `StateTracker`, `ResumablePipeline`).
|
| 50 |
+
- **[02. User Guide](en/02_user_guide.md)**: Optimizer buffer modes (2-Buffer CLM, 1-Buffer TD-CLM, and Triangle Family T1/T2), mixed-precision & stochastic rounding, step bounding, FSDP distributed training, plus Data Engine SIMD text cleaning, quality filtering, two-stage XXH3-64 & MinHash deduplication, PII masking, StringTensor buffers, and Ping-Pong checkpoint recovery.
|
| 51 |
+
- **[03. Configuration & API Reference](en/03_configuration_api.md)**: Complete parameter reference tables, public methods, and validation rules for `LuminaV`, `TextCleaner`, `QualityFilter`, `StreamDeduplicator`, `MinHashDeduplicator`, `PIIMasker`, `StateTracker`, `StreamCoordinate`, `StringTensor`, `FusedPipeline`, and `ResumablePipeline`.
|
| 52 |
+
- **[04. Architecture & Hardware Acceleration](en/04_architecture_and_hardware.md)**: C4 dispatch hierarchy, Optimizer Rust crates (`core`, `simd`, `vulkan`, `dispatch`, `triton-aot`), Data Engine Rust modules (`constants`, `coord`, `dedup`, `error`, `metadata`, `minhash`, `postprocess`, `py_bridge`, `quality`, `serialize`, `simd_cpu`, `string_tensor`), buffer memory protocols, and free-threaded Python (`abi3t` / PEP 703).
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## 2. Developer Modular Documentation
|
| 57 |
+
|
| 58 |
+
Technical documentation for contributors working on the Rust crates, GPU kernels, and Python bindings (`docs/optimize/` & `data/docs/`):
|
| 59 |
+
|
| 60 |
+
### A. LuminaV Optimizer
|
| 61 |
+
Source directories: `docs/optimize/` & `optimizer/`
|
| 62 |
+
|
| 63 |
+
- **[LuminaV Optimizer Overview & User Guide (`optimizer/README.md`)](optimize/standalone_guide.md)**
|
| 64 |
+
- **[Developer Optimizer Index (`docs/optimize/README.md`)](optimize/README.md)**
|
| 65 |
+
- **[01. Core & Math](optimize/core/README.md)**: CLM, TD-CLM Triangle Defect, PRNG stochastic rounding, and bias correction.
|
| 66 |
+
- **[02. SIMD Vectorization](optimize/simd/README.md)**: AVX-512 and AVX2/FMA x86_64 intrinsics and Rayon parallel threadpool.
|
| 67 |
+
- **[03. Vulkan Compute](optimize/vulkan/README.md)**: Ash Vulkan 1.3/1.4 SPIR-V compute shaders and UMA host buffers.
|
| 68 |
+
- **[04. Triton AOT & GPU Drivers](optimize/triton_aot/README.md)**: AOT compilation, CUDA/HIP driver dynamic loading, and standalone C-API.
|
| 69 |
+
- **[05. Dispatch & Routing](optimize/dispatch/README.md)**: Hardware capability detector and 16,384-element dispatch threshold heuristic.
|
| 70 |
+
- **[06. Python FFI & PyO3 Bridge](optimize/python_ffi/README.md)**: PyO3 GIL release (`py.detach()`), pointer aliasing checks, and NumPy buffer protocols.
|
| 71 |
+
- **[07. Python Runtime & GPU Mixin](optimize/python_runtime/README.md)**: `LuminaV` class, `constants.py`, Triton JIT kernels, FSDP synchronization, and state checkpointing.
|
| 72 |
+
|
| 73 |
+
### B. LuminaMoon Data Engine
|
| 74 |
+
Source directories: `data/` & `data/docs/`
|
| 75 |
+
|
| 76 |
+
- **[LuminaMoon Data Engine Overview & User Guide (`data/README.md`)](dataloader/standalone_guide.md)**
|
| 77 |
+
- **[Developer Data Engine Index (`data/docs/README.md`)](dataloader/README.md)**
|
| 78 |
+
- **[01. TextCleaner & QualityFilter](dataloader/simd_and_postprocess/README.md)**: AVX-512/AVX2/NEON SIMD carriage-return stripping, JSON extraction, Shannon entropy, repetition loop, and symbol ratio filters.
|
| 79 |
+
- **[02. StreamDeduplicator & MinHashDeduplicator](dataloader/dedup/README.md)**: Exact streaming deduplication with `xxh3_64` (`IdentityBuildHasher` SwissTable), WAL persistence, and 64-permutation MinHash LSH fuzzy deduplication.
|
| 80 |
+
- **[03. PIIMasker & Data Sanitization](dataloader/simd_and_postprocess/README.md)**: Pattern-based and regex masking for emails, IPv4 addresses, phone numbers, and secret tokens.
|
| 81 |
+
- **[04. StateTracker & StreamCoordinate](dataloader/coord_and_metadata/README.md)**: 32-byte `StreamCoordinate` navigation, dual-slot 128-byte Ping-Pong checkpoint persistence, and multi-epoch state tracking.
|
| 82 |
+
- **[05. StringTensor & Memory Layouts](dataloader/coord_and_metadata/README.md)**: CPU StringView (inlined <= 12 bytes) and GPU contiguous offset/payload buffers compatible with PyTorch, NumPy, Arrow, and Polars.
|
| 83 |
+
- **[06. FusedPipeline & ResumablePipeline](dataloader/python_ffi_and_runtime/README.md)**: Multi-threaded Rayon single-pass preprocessing pipeline and resumable batch pipeline with background prefetcher.
|
| 84 |
+
- **[07. Multimodal Serialization](dataloader/multimodal_and_hf_hub/README.md)**: Serialization to JSONL, Parquet with Zstd compression and embedded image payloads, Arrow IPC, and CSV.
|
| 85 |
+
|
| 86 |
+
### C. Ra-Family Neural Architecture & Theoretical References
|
| 87 |
+
Source directories: `docs/nn/` & `references/`
|
| 88 |
+
|
| 89 |
+
- **[Ra-Family Overview (`docs/nn/README.md`)](nn/README.md)**: Square-root-free `RaNorm` & `RaBatchNorm2d`, rational `RaTanh` & `RaGELU`, bounded `PosRaGLU` / `RaGLU` / `TriGLU`, and compact-support `ra_softmax` & `RaAttention`.
|
| 90 |
+
- **[Ra-Family Standalone Guide (`docs/nn/standalone_guide.md`)](nn/standalone_guide.md)**: Drop-in Transformer block replacement, `RaBatchNorm2d` `Conv2d` parameter folding, and backend introspection.
|
| 91 |
+
- **[Theoretical References (`docs/references.md`)](references.md)**: Component mapping, literature comparisons across all 29 referenced papers, and BibTeX database (`references/bibliography.bib`).
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
|
| 95 |
+
## 3. LSO-Eval Verification & Library Compatibility Notes
|
| 96 |
+
|
| 97 |
+
Source directories: `eval/`, `docs/library_audit/`, & `data/docs/library_audit/`
|
| 98 |
+
|
| 99 |
+
- **[LSO-Eval Optimizer Evaluation & Verification (`eval/README.md`)](lso_eval.md)**: Algorithm-agnostic Z3 SMT solver invariant checks, 2D trajectory benchmarks, numerical parity tests, and Two-Class 4B LLM & Vision evaluation (`LuminaV v1.5.3–v1.6.0`).
|
| 100 |
+
- **[Optimizer Library Notes (`docs/library_audit/`)](library_audit/README.md)**: Version and ABI compatibility notes for Rust 1.99.1 toolchain, PyO3/Maturin, PyTorch 2.14.1, Triton JIT 3.8.0, CUDA/ROCm, and Vulkan Ash compute pipeline.
|
| 101 |
+
- **[Data Engine Library Notes (`data/docs/library_audit/`)](library_audit/README.md)**: Version and compatibility notes for Rust 1.99.1 / Edition 2024, `pyo3 0.30.1` (`abi3t`), Apache Arrow/Parquet/IPC `60.0.0`, `rayon 1.12.0`, `xxhash-rust 0.8.19` (`xxh3`), `crc32fast 1.5.2`, `hashbrown 0.17.1`, `parking_lot 0.12.5`, `serde 1.0.229`, and HuggingFace Hub API protocols.
|
docs/dataloader/README.md
CHANGED
|
@@ -1,69 +1,69 @@
|
|
| 1 |
-
# LuminaMoon Data Engine Architecture
|
| 2 |
-
|
| 3 |
-
Technical architecture and module reference for the **LuminaMoon Data Engine (`luminamoon.data` / `luminamoon-dataloader`)**.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Subsystem Architecture & Modularity
|
| 8 |
-
|
| 9 |
-
The Data Engine consists of a modular Python interface (`python/luminamoon/data/` with pure-Python `fallbacks/`), a `PyO3 0.30.1` (`abi3t`) FFI bridge (`data/src/py_bridge.rs`), and a Rust SIMD, deduplication, and columnar processing crate (`data/src/`):
|
| 10 |
-
|
| 11 |
-
```mermaid
|
| 12 |
-
flowchart LR
|
| 13 |
-
Input["Raw Text / Iterable / HF Stream"] --> Cleaner["1. SIMD TextCleaner & PostProcess (AVX-512BW / AVX2 / NEON)"]
|
| 14 |
-
Cleaner --> Quality["2. QualityFilter (Shannon Entropy, Repetition & Symbol Ratio)"]
|
| 15 |
-
Quality --> Dedup["3. TwoStageDeduplicator (XXH3-64 SwissTable) & MinHash LSH"]
|
| 16 |
-
Dedup --> Masker["4. PIIMasker (Email, IP, API Key, Card)"]
|
| 17 |
-
Masker --> Tensor["5. StringTensor (16B CPU StringView16 & Contiguous GPU Buffers)"]
|
| 18 |
-
Tensor --> Export["Arrow / Polars / PyTorch GPU Tensors / Parquet / IPC"]
|
| 19 |
-
Tensor --> Meta["6. StateTracker (128B Ping-Pong Slot A/B CRC32 & .dedup WAL)"]
|
| 20 |
-
```
|
| 21 |
-
|
| 22 |
-
---
|
| 23 |
-
|
| 24 |
-
## 2. Directory Navigation & Technical Specs
|
| 25 |
-
|
| 26 |
-
| Module | Source Path | Focus & Core Invariants | Spec Document |
|
| 27 |
-
| :--- | :--- | :--- | :--- |
|
| 28 |
-
| **Coordinates, Constants & Dual Metadata** | `data/src/constants.rs`, `error.rs`, `coord.rs`, `metadata.rs` | 32-byte `StreamCoordinate`, 128-byte Ping-Pong (`Slot A`/`Slot B`) CRC32 checkpoint, `.dedup` WAL, Damerau-Levenshtein suggestions | [01. Coord & Metadata](coord_and_metadata/README.md) |
|
| 29 |
-
| **Two-Stage XXH3-64 Deduplication** | `data/src/dedup.rs` | `TwoStageDeduplicator`, `xxh3_64`, `IdentityBuildHasher` SwissTable, staged vs committed sets | [02. Deduplication](dedup/README.md) |
|
| 30 |
-
| **SIMD & Parallel Post-Process** | `data/src/simd_cpu.rs`, `postprocess.rs` | `AVX-512BW` / `AVX2` / `NEON` byte kernels, `CacheAlignedBuffer`, Rayon UTF-8/CRLF/JSON dotted-path extraction | [03. SIMD & Post-Process](simd_and_postprocess/README.md) |
|
| 31 |
-
| **Heuristic Quality & MinHash LSH** | `data/src/quality.rs`, `minhash.rs` | Shannon character entropy, n-gram repetition ratio, symbol ratio, and 64-permutation MinHash Jaccard similarity | [04. Quality & MinHash](sources_and_streaming/README.md) |
|
| 32 |
-
| **Dual-Layout `StringTensor` & Adapters** | `data/src/string_tensor.rs`, `python/luminamoon/data/adapters.py` | 16-byte `StringView16` (<=12B inline), GPU contiguous `uint32` offsets + `uint8` payload, `ResumablePipeline` async prefetch | [05. StringTensor & Adapters](preram/README.md) |
|
| 33 |
-
| **Columnar Serialization Engine** | `data/src/serialize.rs`, `python/luminamoon/data/serialization.py` | `DataValue` enum, ZSTD Parquet (`hf_image_struct`), Apache Arrow IPC (`ARROW1`), and NDJSON | [06. Serialization](multimodal_and_hf_hub/README.md) |
|
| 34 |
-
| **Python FFI & Modular Runtime** | `data/src/py_bridge.rs`, `python/luminamoon/data/` | `abi3t` bindings, `FusedPipeline`, `TextCleaner`, `PIIMasker`, `StateTracker`, and `fallbacks/` | [07. Python FFI & Runtime](python_ffi_and_runtime/README.md) |
|
| 35 |
-
|
| 36 |
-
---
|
| 37 |
-
|
| 38 |
-
## 3. Developer Invariants & Coding Standards
|
| 39 |
-
|
| 40 |
-
1. **Dual-Layout String Representation (`StringTensor`)**:
|
| 41 |
-
- CPU operations use a 16-byte `StringView16` struct (storing strings $\le 12$ bytes inline and comparing 4-byte prefixes in a register), while GPU/Arrow exports emit contiguous `uint32` offset and `uint8` UTF-8 payload buffers.
|
| 42 |
-
2. **Dual-Slot Metadata & Two-Stage Deduplication**:
|
| 43 |
-
- In-flight batches staged via `StateTracker.update()` and `StreamDeduplicator.filter_batch()` update in-memory state first. On-disk state (`Slot A` / `Slot B` protected by IEEE CRC32 and the `.dedup` WAL) is written when `.commit()` is called.
|
| 44 |
-
3. **Self-Contained SIMD Module**:
|
| 45 |
-
- `luminamoon-dataloader` maintains its own `AVX-512BW`, `AVX2`, `NEON`, and `Scalar` kernels in `simd_cpu.rs` (cached via `OnceLock<CpuIsa>`) independent of the `optimizer` crates.
|
| 46 |
-
4. **GIL Release (`py.detach()`)**:
|
| 47 |
-
- Multi-threaded Rayon batch cleaning, quality filtering, `xxh3_64` hashing, `FusedPipeline` execution, and Parquet/Arrow IPC/JSONL serialization execute inside `py.detach()` on CPython 3.10–3.15+ (`abi3` and `abi3t`).
|
| 48 |
-
|
| 49 |
-
---
|
| 50 |
-
|
| 51 |
-
## 4. Local Build & Test Verification
|
| 52 |
-
|
| 53 |
-
```bash
|
| 54 |
-
# Run static analysis across the Rust workspace
|
| 55 |
-
cargo clippy --workspace --all-targets -- -D warnings
|
| 56 |
-
|
| 57 |
-
# Build release extension locally
|
| 58 |
-
maturin develop --release
|
| 59 |
-
|
| 60 |
-
# Run Rust DataLoader unit and integration tests
|
| 61 |
-
cargo test -p luminamoon-dataloader
|
| 62 |
-
|
| 63 |
-
# Run Python static type check
|
| 64 |
-
npx pyright python/luminamoon
|
| 65 |
-
|
| 66 |
-
# Run Python Data Engine & StringTensor test suites
|
| 67 |
-
python -B tests/test_data_modular.py
|
| 68 |
-
python -B tests/test_gpu_vs_cpu_strings.py
|
| 69 |
-
```
|
|
|
|
| 1 |
+
# LuminaMoon Data Engine Architecture
|
| 2 |
+
|
| 3 |
+
Technical architecture and module reference for the **LuminaMoon Data Engine (`luminamoon.data` / `luminamoon-dataloader`)**.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Subsystem Architecture & Modularity
|
| 8 |
+
|
| 9 |
+
The Data Engine consists of a modular Python interface (`python/luminamoon/data/` with pure-Python `fallbacks/`), a `PyO3 0.30.1` (`abi3t`) FFI bridge (`data/src/py_bridge.rs`), and a Rust SIMD, deduplication, and columnar processing crate (`data/src/`):
|
| 10 |
+
|
| 11 |
+
```mermaid
|
| 12 |
+
flowchart LR
|
| 13 |
+
Input["Raw Text / Iterable / HF Stream"] --> Cleaner["1. SIMD TextCleaner & PostProcess (AVX-512BW / AVX2 / NEON)"]
|
| 14 |
+
Cleaner --> Quality["2. QualityFilter (Shannon Entropy, Repetition & Symbol Ratio)"]
|
| 15 |
+
Quality --> Dedup["3. TwoStageDeduplicator (XXH3-64 SwissTable) & MinHash LSH"]
|
| 16 |
+
Dedup --> Masker["4. PIIMasker (Email, IP, API Key, Card)"]
|
| 17 |
+
Masker --> Tensor["5. StringTensor (16B CPU StringView16 & Contiguous GPU Buffers)"]
|
| 18 |
+
Tensor --> Export["Arrow / Polars / PyTorch GPU Tensors / Parquet / IPC"]
|
| 19 |
+
Tensor --> Meta["6. StateTracker (128B Ping-Pong Slot A/B CRC32 & .dedup WAL)"]
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. Directory Navigation & Technical Specs
|
| 25 |
+
|
| 26 |
+
| Module | Source Path | Focus & Core Invariants | Spec Document |
|
| 27 |
+
| :--- | :--- | :--- | :--- |
|
| 28 |
+
| **Coordinates, Constants & Dual Metadata** | `data/src/constants.rs`, `error.rs`, `coord.rs`, `metadata.rs` | 32-byte `StreamCoordinate`, 128-byte Ping-Pong (`Slot A`/`Slot B`) CRC32 checkpoint, `.dedup` WAL, Damerau-Levenshtein suggestions | [01. Coord & Metadata](coord_and_metadata/README.md) |
|
| 29 |
+
| **Two-Stage XXH3-64 Deduplication** | `data/src/dedup.rs` | `TwoStageDeduplicator`, `xxh3_64`, `IdentityBuildHasher` SwissTable, staged vs committed sets | [02. Deduplication](dedup/README.md) |
|
| 30 |
+
| **SIMD & Parallel Post-Process** | `data/src/simd_cpu.rs`, `postprocess.rs` | `AVX-512BW` / `AVX2` / `NEON` byte kernels, `CacheAlignedBuffer`, Rayon UTF-8/CRLF/JSON dotted-path extraction | [03. SIMD & Post-Process](simd_and_postprocess/README.md) |
|
| 31 |
+
| **Heuristic Quality & MinHash LSH** | `data/src/quality.rs`, `minhash.rs` | Shannon character entropy, n-gram repetition ratio, symbol ratio, and 64-permutation MinHash Jaccard similarity | [04. Quality & MinHash](sources_and_streaming/README.md) |
|
| 32 |
+
| **Dual-Layout `StringTensor` & Adapters** | `data/src/string_tensor.rs`, `python/luminamoon/data/adapters.py` | 16-byte `StringView16` (<=12B inline), GPU contiguous `uint32` offsets + `uint8` payload, `ResumablePipeline` async prefetch | [05. StringTensor & Adapters](preram/README.md) |
|
| 33 |
+
| **Columnar Serialization Engine** | `data/src/serialize.rs`, `python/luminamoon/data/serialization.py` | `DataValue` enum, ZSTD Parquet (`hf_image_struct`), Apache Arrow IPC (`ARROW1`), and NDJSON | [06. Serialization](multimodal_and_hf_hub/README.md) |
|
| 34 |
+
| **Python FFI & Modular Runtime** | `data/src/py_bridge.rs`, `python/luminamoon/data/` | `abi3t` bindings, `FusedPipeline`, `TextCleaner`, `PIIMasker`, `StateTracker`, and `fallbacks/` | [07. Python FFI & Runtime](python_ffi_and_runtime/README.md) |
|
| 35 |
+
|
| 36 |
+
---
|
| 37 |
+
|
| 38 |
+
## 3. Developer Invariants & Coding Standards
|
| 39 |
+
|
| 40 |
+
1. **Dual-Layout String Representation (`StringTensor`)**:
|
| 41 |
+
- CPU operations use a 16-byte `StringView16` struct (storing strings $\le 12$ bytes inline and comparing 4-byte prefixes in a register), while GPU/Arrow exports emit contiguous `uint32` offset and `uint8` UTF-8 payload buffers.
|
| 42 |
+
2. **Dual-Slot Metadata & Two-Stage Deduplication**:
|
| 43 |
+
- In-flight batches staged via `StateTracker.update()` and `StreamDeduplicator.filter_batch()` update in-memory state first. On-disk state (`Slot A` / `Slot B` protected by IEEE CRC32 and the `.dedup` WAL) is written when `.commit()` is called.
|
| 44 |
+
3. **Self-Contained SIMD Module**:
|
| 45 |
+
- `luminamoon-dataloader` maintains its own `AVX-512BW`, `AVX2`, `NEON`, and `Scalar` kernels in `simd_cpu.rs` (cached via `OnceLock<CpuIsa>`) independent of the `optimizer` crates.
|
| 46 |
+
4. **GIL Release (`py.detach()`)**:
|
| 47 |
+
- Multi-threaded Rayon batch cleaning, quality filtering, `xxh3_64` hashing, `FusedPipeline` execution, and Parquet/Arrow IPC/JSONL serialization execute inside `py.detach()` on CPython 3.10–3.15+ (`abi3` and `abi3t`).
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## 4. Local Build & Test Verification
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
# Run static analysis across the Rust workspace
|
| 55 |
+
cargo clippy --workspace --all-targets -- -D warnings
|
| 56 |
+
|
| 57 |
+
# Build release extension locally
|
| 58 |
+
maturin develop --release
|
| 59 |
+
|
| 60 |
+
# Run Rust DataLoader unit and integration tests
|
| 61 |
+
cargo test -p luminamoon-dataloader
|
| 62 |
+
|
| 63 |
+
# Run Python static type check
|
| 64 |
+
npx pyright python/luminamoon
|
| 65 |
+
|
| 66 |
+
# Run Python Data Engine & StringTensor test suites
|
| 67 |
+
python -B tests/test_data_modular.py
|
| 68 |
+
python -B tests/test_gpu_vs_cpu_strings.py
|
| 69 |
+
```
|
docs/dataloader/coord_and_metadata/README.md
CHANGED
|
@@ -1,70 +1,70 @@
|
|
| 1 |
-
# Stream Coordinates & Ping-Pong Checkpoint Recovery
|
| 2 |
-
|
| 3 |
-
[`data/src/coord.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/coord.rs) and [`data/src/metadata.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/metadata.rs) define stream coordinate tracking and atomic dual-slot checkpointing, enabling dataset resume without re-reading or re-hashing earlier shards.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. 32-Byte `StreamCoordinate` Binary Layout (`src/coord.rs`)
|
| 8 |
-
|
| 9 |
-
Positions across multi-shard streams are addressed by a fixed 32-byte little-endian struct:
|
| 10 |
-
|
| 11 |
-
| Byte Offset | Field | Rust Type | Width | Description |
|
| 12 |
-
| :--- | :--- | :--- | :--- | :--- |
|
| 13 |
-
| `0..4` | `shard_id` | `u32` | 4 B | Active index in the expanded shard URI list. |
|
| 14 |
-
| `4..12` | `byte_offset` | `u64` | 8 B | Exact byte offset for `.jsonl` / `.txt` `mmap` or HTTP 206 Range streams. |
|
| 15 |
-
| `12..16` | `row_group_idx` | `u32` | 4 B | Active Parquet Row Group index. |
|
| 16 |
-
| `16..20` | `page_idx` | `u32` | 4 B | Reserved page index within the Row Group. |
|
| 17 |
-
| `20..24` | `row_offset_in_page` | `u32` | 4 B | Exact row offset within the active Parquet Row Group (`RowSelector::skip`). |
|
| 18 |
-
| `24..32` | `global_row_idx` | `u64` | 8 B | Cumulative number of raw records scanned across all shards. |
|
| 19 |
-
|
| 20 |
-
```rust
|
| 21 |
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 22 |
-
pub struct StreamCoordinate {
|
| 23 |
-
pub shard_id: u32,
|
| 24 |
-
pub byte_offset: u64,
|
| 25 |
-
pub row_group_idx: u32,
|
| 26 |
-
pub page_idx: u32,
|
| 27 |
-
pub row_offset_in_page: u32,
|
| 28 |
-
pub global_row_idx: u64,
|
| 29 |
-
}
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
## 2. 128-Byte Ping-Pong Checkpoint File (`src/metadata.rs`)
|
| 35 |
-
|
| 36 |
-
To prevent checkpoint corruption from sudden power loss or process interruption, `DualMetadataManager` maintains a fixed 128-byte `.meta` file divided into two 64-byte slots (`Slot A` at `0..64` and `Slot B` at `64..128`):
|
| 37 |
-
|
| 38 |
-
```mermaid
|
| 39 |
-
flowchart LR
|
| 40 |
-
Commit["commit_gpu_batch(batch_id)"] --> WAL["Flush Hashes to .dedup WAL"]
|
| 41 |
-
WAL --> Epoch["Increment Epoch Counter"]
|
| 42 |
-
Epoch --> SlotA["Odd Epoch: Slot A (Bytes 0..64 + CRC32)"]
|
| 43 |
-
Epoch --> SlotB["Even Epoch: Slot B (Bytes 64..128 + CRC32)"]
|
| 44 |
-
SlotA --> Recover["Verify CRC32 & Select max(epoch)"]
|
| 45 |
-
SlotB --> Recover
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
### 64-Byte `DiskSlot` Wire Format
|
| 49 |
-
|
| 50 |
-
| Byte Range | Field | Type | Description |
|
| 51 |
-
| :--- | :--- | :--- | :--- |
|
| 52 |
-
| `0..4` | `SLOT_MAGIC` | `[u8; 4]` | Constant magic header `*b"LMDL"`. |
|
| 53 |
-
| `4..8` | `SLOT_VERSION` | `u32` | Format version (`1`). |
|
| 54 |
-
| `8..16` | `epoch` | `u64` | Monotonically increasing commit sequence number. |
|
| 55 |
-
| `16..24` | `committed_batch_id` | `u64` | Last batch ID confirmed consumed by the GPU training step. |
|
| 56 |
-
| `24..56` | `coord` | `[u8; 32]` | Encoded `StreamCoordinate` immediately following the committed batch. |
|
| 57 |
-
| `56..60` | `dedup_count` | `u32` | Total number of committed 8-byte `xxh3_64` hashes valid in `.meta.dedup`. |
|
| 58 |
-
| `60..64` | `crc32` | `u32` | Hardware-accelerated IEEE CRC32 (`crc32fast`) over bytes `0..60`. |
|
| 59 |
-
|
| 60 |
-
---
|
| 61 |
-
|
| 62 |
-
## 3. Recovery & Truncation Invariants
|
| 63 |
-
|
| 64 |
-
When initializing `DualMetadataManager`:
|
| 65 |
-
|
| 66 |
-
1. Both `Slot A` and `Slot B` are decoded and verified against `SLOT_MAGIC`, `SLOT_VERSION`, and their 32-bit IEEE CRC32 checksum.
|
| 67 |
-
2. If both slots are valid, the slot with the higher `epoch` is selected:
|
| 68 |
-
$$\text{Slot}_{\text{active}} = \arg\max_{S \in \{A, B\}} S.\text{epoch}$$
|
| 69 |
-
3. If one slot was torn during a system crash, its CRC32 check fails and the manager falls back to the intact partner slot.
|
| 70 |
-
4. The companion `.meta.dedup` write-ahead log is truncated to exactly $\text{dedup}_{\text{count}} \times 8$ bytes (`dedup_file.set_len(valid_dedup_bytes)`), discarding any uncommitted partial hash appends before reconstructing `TwoStageDeduplicator::with_committed`.
|
|
|
|
| 1 |
+
# Stream Coordinates & Ping-Pong Checkpoint Recovery
|
| 2 |
+
|
| 3 |
+
[`data/src/coord.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/coord.rs) and [`data/src/metadata.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/metadata.rs) define stream coordinate tracking and atomic dual-slot checkpointing, enabling dataset resume without re-reading or re-hashing earlier shards.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. 32-Byte `StreamCoordinate` Binary Layout (`src/coord.rs`)
|
| 8 |
+
|
| 9 |
+
Positions across multi-shard streams are addressed by a fixed 32-byte little-endian struct:
|
| 10 |
+
|
| 11 |
+
| Byte Offset | Field | Rust Type | Width | Description |
|
| 12 |
+
| :--- | :--- | :--- | :--- | :--- |
|
| 13 |
+
| `0..4` | `shard_id` | `u32` | 4 B | Active index in the expanded shard URI list. |
|
| 14 |
+
| `4..12` | `byte_offset` | `u64` | 8 B | Exact byte offset for `.jsonl` / `.txt` `mmap` or HTTP 206 Range streams. |
|
| 15 |
+
| `12..16` | `row_group_idx` | `u32` | 4 B | Active Parquet Row Group index. |
|
| 16 |
+
| `16..20` | `page_idx` | `u32` | 4 B | Reserved page index within the Row Group. |
|
| 17 |
+
| `20..24` | `row_offset_in_page` | `u32` | 4 B | Exact row offset within the active Parquet Row Group (`RowSelector::skip`). |
|
| 18 |
+
| `24..32` | `global_row_idx` | `u64` | 8 B | Cumulative number of raw records scanned across all shards. |
|
| 19 |
+
|
| 20 |
+
```rust
|
| 21 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
| 22 |
+
pub struct StreamCoordinate {
|
| 23 |
+
pub shard_id: u32,
|
| 24 |
+
pub byte_offset: u64,
|
| 25 |
+
pub row_group_idx: u32,
|
| 26 |
+
pub page_idx: u32,
|
| 27 |
+
pub row_offset_in_page: u32,
|
| 28 |
+
pub global_row_idx: u64,
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 2. 128-Byte Ping-Pong Checkpoint File (`src/metadata.rs`)
|
| 35 |
+
|
| 36 |
+
To prevent checkpoint corruption from sudden power loss or process interruption, `DualMetadataManager` maintains a fixed 128-byte `.meta` file divided into two 64-byte slots (`Slot A` at `0..64` and `Slot B` at `64..128`):
|
| 37 |
+
|
| 38 |
+
```mermaid
|
| 39 |
+
flowchart LR
|
| 40 |
+
Commit["commit_gpu_batch(batch_id)"] --> WAL["Flush Hashes to .dedup WAL"]
|
| 41 |
+
WAL --> Epoch["Increment Epoch Counter"]
|
| 42 |
+
Epoch --> SlotA["Odd Epoch: Slot A (Bytes 0..64 + CRC32)"]
|
| 43 |
+
Epoch --> SlotB["Even Epoch: Slot B (Bytes 64..128 + CRC32)"]
|
| 44 |
+
SlotA --> Recover["Verify CRC32 & Select max(epoch)"]
|
| 45 |
+
SlotB --> Recover
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### 64-Byte `DiskSlot` Wire Format
|
| 49 |
+
|
| 50 |
+
| Byte Range | Field | Type | Description |
|
| 51 |
+
| :--- | :--- | :--- | :--- |
|
| 52 |
+
| `0..4` | `SLOT_MAGIC` | `[u8; 4]` | Constant magic header `*b"LMDL"`. |
|
| 53 |
+
| `4..8` | `SLOT_VERSION` | `u32` | Format version (`1`). |
|
| 54 |
+
| `8..16` | `epoch` | `u64` | Monotonically increasing commit sequence number. |
|
| 55 |
+
| `16..24` | `committed_batch_id` | `u64` | Last batch ID confirmed consumed by the GPU training step. |
|
| 56 |
+
| `24..56` | `coord` | `[u8; 32]` | Encoded `StreamCoordinate` immediately following the committed batch. |
|
| 57 |
+
| `56..60` | `dedup_count` | `u32` | Total number of committed 8-byte `xxh3_64` hashes valid in `.meta.dedup`. |
|
| 58 |
+
| `60..64` | `crc32` | `u32` | Hardware-accelerated IEEE CRC32 (`crc32fast`) over bytes `0..60`. |
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 3. Recovery & Truncation Invariants
|
| 63 |
+
|
| 64 |
+
When initializing `DualMetadataManager`:
|
| 65 |
+
|
| 66 |
+
1. Both `Slot A` and `Slot B` are decoded and verified against `SLOT_MAGIC`, `SLOT_VERSION`, and their 32-bit IEEE CRC32 checksum.
|
| 67 |
+
2. If both slots are valid, the slot with the higher `epoch` is selected:
|
| 68 |
+
$$\text{Slot}_{\text{active}} = \arg\max_{S \in \{A, B\}} S.\text{epoch}$$
|
| 69 |
+
3. If one slot was torn during a system crash, its CRC32 check fails and the manager falls back to the intact partner slot.
|
| 70 |
+
4. The companion `.meta.dedup` write-ahead log is truncated to exactly $\text{dedup}_{\text{count}} \times 8$ bytes (`dedup_file.set_len(valid_dedup_bytes)`), discarding any uncommitted partial hash appends before reconstructing `TwoStageDeduplicator::with_committed`.
|
docs/dataloader/dedup/README.md
CHANGED
|
@@ -1,66 +1,66 @@
|
|
| 1 |
-
# Two-Stage XXH3-64 Deduplication & Identity SwissTable
|
| 2 |
-
|
| 3 |
-
[`data/src/dedup.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/dedup.rs) provides a two-stage streaming deduplicator that separates prefetched records from records committed by training steps.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Two-Stage State Machine (`TwoStageDeduplicator`)
|
| 8 |
-
|
| 9 |
-
In asynchronous pipelines, multiple batches can be staged in memory ahead of the active GPU step. If a single set marked hashes as seen upon prefetch and the process stopped before training consumed them, resuming would either skip unread samples or lose deduplication consistency.
|
| 10 |
-
|
| 11 |
-
`TwoStageDeduplicator` avoids this by partitioning tracked hashes into two disjoint sets:
|
| 12 |
-
|
| 13 |
-
$$\mathcal{H}_{\text{committed}} \cap \mathcal{H}_{\text{staged}} = \emptyset$$
|
| 14 |
-
|
| 15 |
-
```mermaid
|
| 16 |
-
flowchart LR
|
| 17 |
-
Record["Candidate Record"] --> Hash["Compute XXH3-64 (IdentityHasher)"]
|
| 18 |
-
Hash --> Drop["Duplicate Digest: Drop Record"]
|
| 19 |
-
Hash --> Stage["Unique Digest: Insert into staged_lookup"]
|
| 20 |
-
Stage --> Commit["commit_batch: Promote to committed_hashes & WAL"]
|
| 21 |
-
Stage --> Evict["rollback_batch: Evict from staged_lookup"]
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
### Core Operations
|
| 25 |
-
|
| 26 |
-
1. **`try_stage(batch_id, payload) -> Option<u64>`**:
|
| 27 |
-
- Computes $h = \text{XXH3}_{64}(\text{payload})$.
|
| 28 |
-
- Rejects the record (`None`) if $h \in \text{committed}_{\text{hashes}} \cup \text{staged}_{\text{lookup}}$.
|
| 29 |
-
- Otherwise inserts $h$ into `staged_lookup` and appends $h$ to `staged_batches[batch_id]`.
|
| 30 |
-
2. **`commit_batch(batch_id) -> Vec<u64>`**:
|
| 31 |
-
- Atomically promotes all hashes in `staged_batches[batch_id]` from `staged_lookup` into `committed_hashes` and returns the vector of promoted hashes to be flushed to the `.meta.dedup` write-ahead log.
|
| 32 |
-
3. **`rollback_batch(batch_id)` & `clear_staged()`**:
|
| 33 |
-
- Evicts uncommitted batch hashes from `staged_lookup` without touching `committed_hashes`, restoring exact state parity with the last committed GPU step.
|
| 34 |
-
|
| 35 |
-
---
|
| 36 |
-
|
| 37 |
-
## 2. Zero-Rehash SwissTable (`IdentityBuildHasher`)
|
| 38 |
-
|
| 39 |
-
Because keys stored in `committed_hashes` and `staged_lookup` are already uniformly distributed 64-bit `xxh3_64` digests, hashing them again with `hashbrown`'s default `foldhash` would waste CPU cycles.
|
| 40 |
-
|
| 41 |
-
`dedup.rs` defines an `IdentityHasher` that passes the digest directly to the table probe:
|
| 42 |
-
|
| 43 |
-
```rust
|
| 44 |
-
#[derive(Default)]
|
| 45 |
-
pub struct IdentityHasher(u64);
|
| 46 |
-
|
| 47 |
-
impl Hasher for IdentityHasher {
|
| 48 |
-
#[inline]
|
| 49 |
-
fn finish(&self) -> u64 {
|
| 50 |
-
self.0
|
| 51 |
-
}
|
| 52 |
-
|
| 53 |
-
#[inline]
|
| 54 |
-
fn write_u64(&mut self, i: u64) {
|
| 55 |
-
self.0 = i;
|
| 56 |
-
}
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
pub type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
|
| 60 |
-
```
|
| 61 |
-
|
| 62 |
-
---
|
| 63 |
-
|
| 64 |
-
## 3. MinHash LSH Near-Duplicate Shingling (`src/minhash.rs`)
|
| 65 |
-
|
| 66 |
-
For fuzzy near-duplicate rejection alongside exact 64-bit `xxh3_64` deduplication, `MinHashDeduplicator` computes $K$-character UTF-8 shingles hashed across $P$ deterministic permuted `xxh3_64_with_seed` lanes and estimates pairwise Jaccard similarity against indexed document signatures.
|
|
|
|
| 1 |
+
# Two-Stage XXH3-64 Deduplication & Identity SwissTable
|
| 2 |
+
|
| 3 |
+
[`data/src/dedup.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/dedup.rs) provides a two-stage streaming deduplicator that separates prefetched records from records committed by training steps.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Two-Stage State Machine (`TwoStageDeduplicator`)
|
| 8 |
+
|
| 9 |
+
In asynchronous pipelines, multiple batches can be staged in memory ahead of the active GPU step. If a single set marked hashes as seen upon prefetch and the process stopped before training consumed them, resuming would either skip unread samples or lose deduplication consistency.
|
| 10 |
+
|
| 11 |
+
`TwoStageDeduplicator` avoids this by partitioning tracked hashes into two disjoint sets:
|
| 12 |
+
|
| 13 |
+
$$\mathcal{H}_{\text{committed}} \cap \mathcal{H}_{\text{staged}} = \emptyset$$
|
| 14 |
+
|
| 15 |
+
```mermaid
|
| 16 |
+
flowchart LR
|
| 17 |
+
Record["Candidate Record"] --> Hash["Compute XXH3-64 (IdentityHasher)"]
|
| 18 |
+
Hash --> Drop["Duplicate Digest: Drop Record"]
|
| 19 |
+
Hash --> Stage["Unique Digest: Insert into staged_lookup"]
|
| 20 |
+
Stage --> Commit["commit_batch: Promote to committed_hashes & WAL"]
|
| 21 |
+
Stage --> Evict["rollback_batch: Evict from staged_lookup"]
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### Core Operations
|
| 25 |
+
|
| 26 |
+
1. **`try_stage(batch_id, payload) -> Option<u64>`**:
|
| 27 |
+
- Computes $h = \text{XXH3}_{64}(\text{payload})$.
|
| 28 |
+
- Rejects the record (`None`) if $h \in \text{committed}_{\text{hashes}} \cup \text{staged}_{\text{lookup}}$.
|
| 29 |
+
- Otherwise inserts $h$ into `staged_lookup` and appends $h$ to `staged_batches[batch_id]`.
|
| 30 |
+
2. **`commit_batch(batch_id) -> Vec<u64>`**:
|
| 31 |
+
- Atomically promotes all hashes in `staged_batches[batch_id]` from `staged_lookup` into `committed_hashes` and returns the vector of promoted hashes to be flushed to the `.meta.dedup` write-ahead log.
|
| 32 |
+
3. **`rollback_batch(batch_id)` & `clear_staged()`**:
|
| 33 |
+
- Evicts uncommitted batch hashes from `staged_lookup` without touching `committed_hashes`, restoring exact state parity with the last committed GPU step.
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 2. Zero-Rehash SwissTable (`IdentityBuildHasher`)
|
| 38 |
+
|
| 39 |
+
Because keys stored in `committed_hashes` and `staged_lookup` are already uniformly distributed 64-bit `xxh3_64` digests, hashing them again with `hashbrown`'s default `foldhash` would waste CPU cycles.
|
| 40 |
+
|
| 41 |
+
`dedup.rs` defines an `IdentityHasher` that passes the digest directly to the table probe:
|
| 42 |
+
|
| 43 |
+
```rust
|
| 44 |
+
#[derive(Default)]
|
| 45 |
+
pub struct IdentityHasher(u64);
|
| 46 |
+
|
| 47 |
+
impl Hasher for IdentityHasher {
|
| 48 |
+
#[inline]
|
| 49 |
+
fn finish(&self) -> u64 {
|
| 50 |
+
self.0
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
#[inline]
|
| 54 |
+
fn write_u64(&mut self, i: u64) {
|
| 55 |
+
self.0 = i;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
pub type IdentityBuildHasher = BuildHasherDefault<IdentityHasher>;
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## 3. MinHash LSH Near-Duplicate Shingling (`src/minhash.rs`)
|
| 65 |
+
|
| 66 |
+
For fuzzy near-duplicate rejection alongside exact 64-bit `xxh3_64` deduplication, `MinHashDeduplicator` computes $K$-character UTF-8 shingles hashed across $P$ deterministic permuted `xxh3_64_with_seed` lanes and estimates pairwise Jaccard similarity against indexed document signatures.
|
docs/dataloader/multimodal_and_hf_hub/README.md
CHANGED
|
@@ -1,38 +1,38 @@
|
|
| 1 |
-
# Columnar Serialization (Parquet, Arrow IPC & JSONL)
|
| 2 |
-
|
| 3 |
-
[`data/src/serialize.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/serialize.rs) and [`python/luminamoon/data/serialization.py`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/serialization.py) serialize Python dictionaries and columnar tables into Apache Parquet (ZSTD), Apache Arrow IPC (`ARROW1`), and line-delimited JSONL (`NDJSON`) with the Python GIL released.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Unified Columnar Type System (`DataValue`)
|
| 8 |
-
|
| 9 |
-
`data/src/serialize.rs` maps Python sequences and dictionaries into an 8-variant `DataValue` enum:
|
| 10 |
-
|
| 11 |
-
| Variant | Python Input Types | Arrow / Parquet Target Type (`hf_image_struct = true`) |
|
| 12 |
-
| :--- | :--- | :--- |
|
| 13 |
-
| `DataValue::Null` | `None` | Nullable slot in target column array |
|
| 14 |
-
| `DataValue::Text(String)` | `str` (non-JSON) | `DataType::Utf8` (`StringArray`) |
|
| 15 |
-
| `DataValue::Bytes(Vec<u8>)` | `bytes`, `bytearray` | `DataType::Struct(bytes: Binary, path: Utf8)` (or `DataType::Binary` when `hf_image_struct = false`) |
|
| 16 |
-
| `DataValue::Json(String)` | Valid JSON object/array `str` | `DataType::Utf8` in Parquet/Arrow; parsed JSON value in `.jsonl` |
|
| 17 |
-
| `DataValue::Int(i64)` | `int` | `DataType::Int64` (`Int64Array`) |
|
| 18 |
-
| `DataValue::Float(f64)` | `float` | `DataType::Float64` (`Float64Array`) |
|
| 19 |
-
| `DataValue::Bool(bool)` | `bool` | `DataType::Boolean` (`BooleanArray`) |
|
| 20 |
-
| `DataValue::List(Vec<DataValue>)` | Nested sequences | JSON-encoded array representation |
|
| 21 |
-
|
| 22 |
-
---
|
| 23 |
-
|
| 24 |
-
## 2. HuggingFace Dataset Viewer Image Struct Compatibility
|
| 25 |
-
|
| 26 |
-
When `hf_image_struct = True` (default in `serialize_to_parquet`), binary columns (`DataValue::Bytes`) are automatically encoded as an Arrow `StructArray` with schema:
|
| 27 |
-
|
| 28 |
-
$$\text{Struct}\langle\text{bytes}: \text{Binary},\ \text{path}: \text{Utf8}\rangle$$
|
| 29 |
-
|
| 30 |
-
This matches the standard HuggingFace Dataset Viewer image/audio schema, enabling inline previews on `huggingface.co` without post-processing.
|
| 31 |
-
|
| 32 |
-
---
|
| 33 |
-
|
| 34 |
-
## 3. Public Python Serialization Helpers (`serialization.py`)
|
| 35 |
-
|
| 36 |
-
- **`serialize_to_parquet(columns, output_path=None, hf_image_struct=True) -> bytes`**: Encodes columnar data into ZSTD-compressed Apache Parquet bytes (`PAR1`) with the GIL released (`py.detach()`).
|
| 37 |
-
- **`serialize_to_arrow(columns, output_path=None) -> bytes`**: Encodes columnar data into Apache Arrow IPC File Format bytes (`ARROW1`).
|
| 38 |
-
- **`serialize_to_jsonl(columns, output_path=None) -> bytes`**: Encodes columnar data into UTF-8 line-delimited JSONL records.
|
|
|
|
| 1 |
+
# Columnar Serialization (Parquet, Arrow IPC & JSONL)
|
| 2 |
+
|
| 3 |
+
[`data/src/serialize.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/serialize.rs) and [`python/luminamoon/data/serialization.py`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/serialization.py) serialize Python dictionaries and columnar tables into Apache Parquet (ZSTD), Apache Arrow IPC (`ARROW1`), and line-delimited JSONL (`NDJSON`) with the Python GIL released.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Unified Columnar Type System (`DataValue`)
|
| 8 |
+
|
| 9 |
+
`data/src/serialize.rs` maps Python sequences and dictionaries into an 8-variant `DataValue` enum:
|
| 10 |
+
|
| 11 |
+
| Variant | Python Input Types | Arrow / Parquet Target Type (`hf_image_struct = true`) |
|
| 12 |
+
| :--- | :--- | :--- |
|
| 13 |
+
| `DataValue::Null` | `None` | Nullable slot in target column array |
|
| 14 |
+
| `DataValue::Text(String)` | `str` (non-JSON) | `DataType::Utf8` (`StringArray`) |
|
| 15 |
+
| `DataValue::Bytes(Vec<u8>)` | `bytes`, `bytearray` | `DataType::Struct(bytes: Binary, path: Utf8)` (or `DataType::Binary` when `hf_image_struct = false`) |
|
| 16 |
+
| `DataValue::Json(String)` | Valid JSON object/array `str` | `DataType::Utf8` in Parquet/Arrow; parsed JSON value in `.jsonl` |
|
| 17 |
+
| `DataValue::Int(i64)` | `int` | `DataType::Int64` (`Int64Array`) |
|
| 18 |
+
| `DataValue::Float(f64)` | `float` | `DataType::Float64` (`Float64Array`) |
|
| 19 |
+
| `DataValue::Bool(bool)` | `bool` | `DataType::Boolean` (`BooleanArray`) |
|
| 20 |
+
| `DataValue::List(Vec<DataValue>)` | Nested sequences | JSON-encoded array representation |
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. HuggingFace Dataset Viewer Image Struct Compatibility
|
| 25 |
+
|
| 26 |
+
When `hf_image_struct = True` (default in `serialize_to_parquet`), binary columns (`DataValue::Bytes`) are automatically encoded as an Arrow `StructArray` with schema:
|
| 27 |
+
|
| 28 |
+
$$\text{Struct}\langle\text{bytes}: \text{Binary},\ \text{path}: \text{Utf8}\rangle$$
|
| 29 |
+
|
| 30 |
+
This matches the standard HuggingFace Dataset Viewer image/audio schema, enabling inline previews on `huggingface.co` without post-processing.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 3. Public Python Serialization Helpers (`serialization.py`)
|
| 35 |
+
|
| 36 |
+
- **`serialize_to_parquet(columns, output_path=None, hf_image_struct=True) -> bytes`**: Encodes columnar data into ZSTD-compressed Apache Parquet bytes (`PAR1`) with the GIL released (`py.detach()`).
|
| 37 |
+
- **`serialize_to_arrow(columns, output_path=None) -> bytes`**: Encodes columnar data into Apache Arrow IPC File Format bytes (`ARROW1`).
|
| 38 |
+
- **`serialize_to_jsonl(columns, output_path=None) -> bytes`**: Encodes columnar data into UTF-8 line-delimited JSONL records.
|
docs/dataloader/preram/README.md
CHANGED
|
@@ -1,37 +1,37 @@
|
|
| 1 |
-
# Dual-Layout StringTensor & Async Resumable Pipelines
|
| 2 |
-
|
| 3 |
-
[`data/src/string_tensor.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/string_tensor.rs) and [`python/luminamoon/data/adapters.py`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/adapters.py) provide complementary CPU and GPU string layouts alongside asynchronous background batch prefetching for PyTorch and standalone training loops.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Dual-Layout `StringTensor` (`src/string_tensor.rs`)
|
| 8 |
-
|
| 9 |
-
`StringTensor` bridges CPU string processing and GPU tensor ingestion via two complementary memory representations:
|
| 10 |
-
|
| 11 |
-
### 1.1 CPU Layout: 16-Byte Umbra / DuckDB `StringView16`
|
| 12 |
-
```rust
|
| 13 |
-
#[repr(C)]
|
| 14 |
-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 15 |
-
pub struct StringView16 {
|
| 16 |
-
pub(crate) len: u32,
|
| 17 |
-
pub(crate) prefix_or_inline: [u8; 4],
|
| 18 |
-
pub(crate) suffix_or_offset: [u8; 8],
|
| 19 |
-
}
|
| 20 |
-
```
|
| 21 |
-
|
| 22 |
-
- **Inlined Strings ($\text{len} \le 12$ bytes)**: Stored completely inside the 16-byte struct (`prefix_or_inline` + `suffix_or_offset`) with zero heap pointer dereferences.
|
| 23 |
-
- **Pool-Referenced Strings ($\text{len} > 12$ bytes)**: Stores the first 4 bytes in `prefix_or_inline` (enabling single-register 32-bit prefix rejection in `filter_by_prefix` and `fast_eq`), alongside a 4-byte `offset` and 4-byte `buffer_id` into `Arc<Vec<u8>>`.
|
| 24 |
-
|
| 25 |
-
### 1.2 GPU & Arrow Layout: Contiguous Offsets + Payload
|
| 26 |
-
- `to_gpu_buffers()` emits a contiguous `uint32` offset array `[0, off_1, ..., off_N]` of length $N + 1$ and a flat `uint8` UTF-8 byte buffer.
|
| 27 |
-
- Directly exported to PyTorch (`to_torch_gpu(device)`), NumPy (`to_numpy_gpu()`), Apache Arrow (`to_arrow(zero_copy=True)` via `pa.StringArray.from_buffers`), and Polars (`to_polars()`).
|
| 28 |
-
|
| 29 |
-
---
|
| 30 |
-
|
| 31 |
-
## 2. Asynchronous Prefetching & PyTorch Integration (`adapters.py`)
|
| 32 |
-
|
| 33 |
-
1. **`ResumablePipeline`**:
|
| 34 |
-
- Wraps any Python iterable or streaming dataset with single-pass `FusedPipeline` execution, `StateTracker` 128-byte Ping-Pong coordinate tracking, and background thread prefetching (`prefetch_batches > 0` via bounded `queue.Queue`).
|
| 35 |
-
- Calling `pipe.commit()` atomically persists both the active `StateTracker` batch coordinate (`Slot A` / `Slot B`) and `StreamDeduplicator` WAL.
|
| 36 |
-
2. **`create_resumable_collate`**:
|
| 37 |
-
- Generates a drop-in `collate_fn` for `torch.utils.data.DataLoader` that extracts text fields, runs `FusedPipeline` (or modular `TextCleaner` -> `QualityFilter` -> `StreamDeduplicator` -> `PIIMasker`), and returns either tokenized PyTorch tensors or a `StringTensor`.
|
|
|
|
| 1 |
+
# Dual-Layout StringTensor & Async Resumable Pipelines
|
| 2 |
+
|
| 3 |
+
[`data/src/string_tensor.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/string_tensor.rs) and [`python/luminamoon/data/adapters.py`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/adapters.py) provide complementary CPU and GPU string layouts alongside asynchronous background batch prefetching for PyTorch and standalone training loops.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Dual-Layout `StringTensor` (`src/string_tensor.rs`)
|
| 8 |
+
|
| 9 |
+
`StringTensor` bridges CPU string processing and GPU tensor ingestion via two complementary memory representations:
|
| 10 |
+
|
| 11 |
+
### 1.1 CPU Layout: 16-Byte Umbra / DuckDB `StringView16`
|
| 12 |
+
```rust
|
| 13 |
+
#[repr(C)]
|
| 14 |
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
| 15 |
+
pub struct StringView16 {
|
| 16 |
+
pub(crate) len: u32,
|
| 17 |
+
pub(crate) prefix_or_inline: [u8; 4],
|
| 18 |
+
pub(crate) suffix_or_offset: [u8; 8],
|
| 19 |
+
}
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
- **Inlined Strings ($\text{len} \le 12$ bytes)**: Stored completely inside the 16-byte struct (`prefix_or_inline` + `suffix_or_offset`) with zero heap pointer dereferences.
|
| 23 |
+
- **Pool-Referenced Strings ($\text{len} > 12$ bytes)**: Stores the first 4 bytes in `prefix_or_inline` (enabling single-register 32-bit prefix rejection in `filter_by_prefix` and `fast_eq`), alongside a 4-byte `offset` and 4-byte `buffer_id` into `Arc<Vec<u8>>`.
|
| 24 |
+
|
| 25 |
+
### 1.2 GPU & Arrow Layout: Contiguous Offsets + Payload
|
| 26 |
+
- `to_gpu_buffers()` emits a contiguous `uint32` offset array `[0, off_1, ..., off_N]` of length $N + 1$ and a flat `uint8` UTF-8 byte buffer.
|
| 27 |
+
- Directly exported to PyTorch (`to_torch_gpu(device)`), NumPy (`to_numpy_gpu()`), Apache Arrow (`to_arrow(zero_copy=True)` via `pa.StringArray.from_buffers`), and Polars (`to_polars()`).
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 2. Asynchronous Prefetching & PyTorch Integration (`adapters.py`)
|
| 32 |
+
|
| 33 |
+
1. **`ResumablePipeline`**:
|
| 34 |
+
- Wraps any Python iterable or streaming dataset with single-pass `FusedPipeline` execution, `StateTracker` 128-byte Ping-Pong coordinate tracking, and background thread prefetching (`prefetch_batches > 0` via bounded `queue.Queue`).
|
| 35 |
+
- Calling `pipe.commit()` atomically persists both the active `StateTracker` batch coordinate (`Slot A` / `Slot B`) and `StreamDeduplicator` WAL.
|
| 36 |
+
2. **`create_resumable_collate`**:
|
| 37 |
+
- Generates a drop-in `collate_fn` for `torch.utils.data.DataLoader` that extracts text fields, runs `FusedPipeline` (or modular `TextCleaner` -> `QualityFilter` -> `StreamDeduplicator` -> `PIIMasker`), and returns either tokenized PyTorch tensors or a `StringTensor`.
|
docs/dataloader/python_ffi_and_runtime/README.md
CHANGED
|
@@ -1,29 +1,29 @@
|
|
| 1 |
-
# PyO3 FFI Bridge & Modular Python Runtime
|
| 2 |
-
|
| 3 |
-
[`data/src/py_bridge.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/py_bridge.rs) and the modular [`python/luminamoon/data/`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/__init__.py) package connect the native Rust SIMD and columnar engine with Python under the CPython Stable ABI (`abi3-py310` and free-threaded `abi3t`).
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Native PyO3 Classes & Functions (`src/py_bridge.rs`)
|
| 8 |
-
|
| 9 |
-
All native symbols are compiled into `_luminamoon_optimizer_rs` and re-exported through `python/luminamoon/data/_ffi.py`:
|
| 10 |
-
|
| 11 |
-
| Rust PyO3 Symbol | Python Wrapper (`luminamoon.data`) | GIL Detached (`py.detach`) | Core Responsibility |
|
| 12 |
-
| :--- | :--- | :--- | :--- |
|
| 13 |
-
| `PyStreamCoordinate` | `StreamCoordinate` (`checkpoint.py`) | N/A (32B value) | 32-byte little-endian stream coordinate with `to_bytes()` / `from_bytes()`. |
|
| 14 |
-
| `PyStateTracker` | `StateTracker` (`checkpoint.py`) | Yes | 128-byte Dual-Slot Ping-Pong (`Slot A` / `Slot B`) atomic checkpoint manager (`read`, `update`, `commit`). |
|
| 15 |
-
| `PyStreamDeduplicator` | `StreamDeduplicator` (`dedup.py`) | Yes | Two-stage `xxh3_64` SwissTable deduplicator with `.dedup` WAL (`is_unique`, `filter_batch`, `commit`, `rollback`). |
|
| 16 |
-
| `PyMinHashDeduplicator` | `MinHashDeduplicator` (`minhash.py`) | Yes | 64-permutation MinHash LSH near-duplicate detector (`is_unique`, `similarity`, `filter_batch`). |
|
| 17 |
-
| `PyQualityFilter` | `QualityFilter` (`quality.py`) | Yes | Parallel Rayon Shannon entropy, repetition, and symbol ratio filter (`is_high_quality`, `metrics`, `filter_batch`). |
|
| 18 |
-
| `PyStringTensor` | `StringTensor` (`string_tensor.py`) | Yes | Dual-layout 16B CPU `StringView16` and contiguous GPU/Arrow buffer container. |
|
| 19 |
-
| `fused_pipeline_process_batch` | `FusedPipeline` (`fused.py`) | Yes | Single-pass Rayon + SIMD text cleaning, quality filtering, and deduplication returning `StringTensor`. |
|
| 20 |
-
| `serialize_table_to_bytes` | `serialize_to_*` (`serialization.py`) | Yes | Columnar serialization to Parquet (`PAR1`), Arrow IPC (`ARROW1`), and JSONL. |
|
| 21 |
-
|
| 22 |
-
---
|
| 23 |
-
|
| 24 |
-
## 2. Modular Pure-Python Fallback Layer (`python/luminamoon/data/fallbacks/`)
|
| 25 |
-
|
| 26 |
-
When running in environments without the compiled native binary, `_ffi.py` transparently falls back to the modular `fallbacks/` subpackage:
|
| 27 |
-
|
| 28 |
-
- `cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `coordinate.py`, `tracker.py`, `string_tensor.py`, `fused.py`, `serialization.py`, and `helpers.py`.
|
| 29 |
-
- Preserves API and binary format compatibility (including 32-byte `StreamCoordinate` and 128-byte IEEE CRC32 Ping-Pong `.meta` checkpoints).
|
|
|
|
| 1 |
+
# PyO3 FFI Bridge & Modular Python Runtime
|
| 2 |
+
|
| 3 |
+
[`data/src/py_bridge.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/py_bridge.rs) and the modular [`python/luminamoon/data/`](https://github.com/cloverx-id/luminamoon/blob/main/python/luminamoon/data/__init__.py) package connect the native Rust SIMD and columnar engine with Python under the CPython Stable ABI (`abi3-py310` and free-threaded `abi3t`).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Native PyO3 Classes & Functions (`src/py_bridge.rs`)
|
| 8 |
+
|
| 9 |
+
All native symbols are compiled into `_luminamoon_optimizer_rs` and re-exported through `python/luminamoon/data/_ffi.py`:
|
| 10 |
+
|
| 11 |
+
| Rust PyO3 Symbol | Python Wrapper (`luminamoon.data`) | GIL Detached (`py.detach`) | Core Responsibility |
|
| 12 |
+
| :--- | :--- | :--- | :--- |
|
| 13 |
+
| `PyStreamCoordinate` | `StreamCoordinate` (`checkpoint.py`) | N/A (32B value) | 32-byte little-endian stream coordinate with `to_bytes()` / `from_bytes()`. |
|
| 14 |
+
| `PyStateTracker` | `StateTracker` (`checkpoint.py`) | Yes | 128-byte Dual-Slot Ping-Pong (`Slot A` / `Slot B`) atomic checkpoint manager (`read`, `update`, `commit`). |
|
| 15 |
+
| `PyStreamDeduplicator` | `StreamDeduplicator` (`dedup.py`) | Yes | Two-stage `xxh3_64` SwissTable deduplicator with `.dedup` WAL (`is_unique`, `filter_batch`, `commit`, `rollback`). |
|
| 16 |
+
| `PyMinHashDeduplicator` | `MinHashDeduplicator` (`minhash.py`) | Yes | 64-permutation MinHash LSH near-duplicate detector (`is_unique`, `similarity`, `filter_batch`). |
|
| 17 |
+
| `PyQualityFilter` | `QualityFilter` (`quality.py`) | Yes | Parallel Rayon Shannon entropy, repetition, and symbol ratio filter (`is_high_quality`, `metrics`, `filter_batch`). |
|
| 18 |
+
| `PyStringTensor` | `StringTensor` (`string_tensor.py`) | Yes | Dual-layout 16B CPU `StringView16` and contiguous GPU/Arrow buffer container. |
|
| 19 |
+
| `fused_pipeline_process_batch` | `FusedPipeline` (`fused.py`) | Yes | Single-pass Rayon + SIMD text cleaning, quality filtering, and deduplication returning `StringTensor`. |
|
| 20 |
+
| `serialize_table_to_bytes` | `serialize_to_*` (`serialization.py`) | Yes | Columnar serialization to Parquet (`PAR1`), Arrow IPC (`ARROW1`), and JSONL. |
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 2. Modular Pure-Python Fallback Layer (`python/luminamoon/data/fallbacks/`)
|
| 25 |
+
|
| 26 |
+
When running in environments without the compiled native binary, `_ffi.py` transparently falls back to the modular `fallbacks/` subpackage:
|
| 27 |
+
|
| 28 |
+
- `cleaner.py`, `quality.py`, `dedup.py`, `minhash.py`, `masker.py`, `coordinate.py`, `tracker.py`, `string_tensor.py`, `fused.py`, `serialization.py`, and `helpers.py`.
|
| 29 |
+
- Preserves API and binary format compatibility (including 32-byte `StreamCoordinate` and 128-byte IEEE CRC32 Ping-Pong `.meta` checkpoints).
|
docs/dataloader/simd_and_postprocess/README.md
CHANGED
|
@@ -1,62 +1,62 @@
|
|
| 1 |
-
# CPU SIMD Kernels & Rayon Normalization
|
| 2 |
-
|
| 3 |
-
[`data/src/simd_cpu.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/simd_cpu.rs) and [`data/src/postprocess.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/postprocess.rs) provide vectorized byte scanning and multi-core text normalization without external crate dependencies.
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## 1. Instruction Set Support & Vector Widths (`src/simd_cpu.rs`)
|
| 8 |
-
|
| 9 |
-
`luminamoon-dataloader` detects host CPU capabilities at startup and caches the active `CpuIsa` in a lock-free `OnceLock<CpuIsa>`:
|
| 10 |
-
|
| 11 |
-
| Architecture Target | Enum Variant | Registers | Bytes / Cycle | Intrinsics Used |
|
| 12 |
-
| :--- | :--- | :--- | :--- | :--- |
|
| 13 |
-
| **x86 / x86_64 AVX-512BW** | `CpuIsa::Avx512` | ZMM (`__m512i`) | 64 bytes | `_mm512_set1_epi8`, `_mm512_loadu_si512`, `_mm512_cmpeq_epi8_mask` |
|
| 14 |
-
| **x86 / x86_64 AVX2** | `CpuIsa::Avx2` | YMM (`__m256i`) | 32 bytes | `_mm256_set1_epi8`, `_mm256_loadu_si256`, `_mm256_cmpeq_epi8`, `_mm256_movemask_epi8` |
|
| 15 |
-
| **ARM64 NEON** | `CpuIsa::Neon` | V-Reg (`uint8x16_t`) | 16 bytes | `vdupq_n_u8`, `vld1q_u8`, `vceqq_u8`, `vmaxvq_u8` |
|
| 16 |
-
| **Portable Fallback** | `CpuIsa::Scalar` | General Purpose | 1 byte | Iterator `position` / `rposition` |
|
| 17 |
-
|
| 18 |
-
---
|
| 19 |
-
|
| 20 |
-
## 2. Vectorized Byte Scanning Kernels
|
| 21 |
-
|
| 22 |
-
1. **`find_byte_fast(haystack: &[u8], needle: u8) -> Option<usize>`**:
|
| 23 |
-
- Scans forward in 64-byte (`AVX-512BW`), 32-byte (`AVX2`), or 16-byte (`NEON`) unaligned chunks, locating the first matching byte offset via hardware bitmask `trailing_zeros()`.
|
| 24 |
-
- Powers `find_newline_fast` for `.jsonl`/`.txt` line splitting and whitespace boundary detection.
|
| 25 |
-
2. **`rfind_byte_fast(haystack: &[u8], needle: u8) -> Option<usize>`**:
|
| 26 |
-
- Scans backward from the end of a byte slice using `leading_zeros()` on the comparison mask.
|
| 27 |
-
- Powers `rfind_newline_fast` to locate the exact last complete line boundary in a byte buffer in $O(1)$ vector steps.
|
| 28 |
-
3. **`contains_byte_fast(haystack: &[u8], needle: u8) -> bool`**:
|
| 29 |
-
- Fast-path guard in `postprocess.rs` that skips string reallocation during CRLF (`\r\n`) normalization whenever a record contains no `\r` (`0x0D`) byte.
|
| 30 |
-
|
| 31 |
-
---
|
| 32 |
-
|
| 33 |
-
## 3. 64-Byte Cache-Line Aligned Buffer (`CacheAlignedBuffer`)
|
| 34 |
-
|
| 35 |
-
```rust
|
| 36 |
-
#[repr(align(64))]
|
| 37 |
-
#[derive(Debug, Clone)]
|
| 38 |
-
pub struct CacheAlignedBuffer {
|
| 39 |
-
pub data: Vec<u8>,
|
| 40 |
-
}
|
| 41 |
-
```
|
| 42 |
-
|
| 43 |
-
Rounds buffer capacities up to 64-byte cache-line multiples (`(cap + 63) & !63`) to avoid split-line penalties during SIMD scanning and batch serialization.
|
| 44 |
-
|
| 45 |
-
---
|
| 46 |
-
|
| 47 |
-
## 4. Parallel Rayon Normalization Pipeline (`src/postprocess.rs`)
|
| 48 |
-
|
| 49 |
-
`TextCleaner` and `FusedPipeline` govern parallel per-record sanitization across raw text batches:
|
| 50 |
-
|
| 51 |
-
```mermaid
|
| 52 |
-
flowchart LR
|
| 53 |
-
Input["Raw Shard Bytes"] --> Scan["SIMD Newline Scan (AVX-512BW / AVX2 / NEON)"]
|
| 54 |
-
Scan --> Extract["Rayon Parallel JSON Field Extraction"]
|
| 55 |
-
Extract --> Clean["Fast CRLF Normalization & Whitespace Trim"]
|
| 56 |
-
Clean --> Output["Valid Length: Normalized UTF-8 Batch"]
|
| 57 |
-
Clean --> Truncate["Exceeds Max: Snap to UTF-8 Boundary"]
|
| 58 |
-
Truncate --> Output
|
| 59 |
-
Clean --> Drop["Below Min / Empty: Filter Out Record"]
|
| 60 |
-
```
|
| 61 |
-
|
| 62 |
-
- **Order-Preserving Parallel Execution**: `process_batch_parallel_indexed` uses Rayon's indexed `par_iter().map(...)` to normalize batches across all CPU cores while preserving exact deterministic record ordering for `StreamCoordinate` progression.
|
|
|
|
| 1 |
+
# CPU SIMD Kernels & Rayon Normalization
|
| 2 |
+
|
| 3 |
+
[`data/src/simd_cpu.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/simd_cpu.rs) and [`data/src/postprocess.rs`](https://github.com/cloverx-id/luminamoon/blob/main/data/src/postprocess.rs) provide vectorized byte scanning and multi-core text normalization without external crate dependencies.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Instruction Set Support & Vector Widths (`src/simd_cpu.rs`)
|
| 8 |
+
|
| 9 |
+
`luminamoon-dataloader` detects host CPU capabilities at startup and caches the active `CpuIsa` in a lock-free `OnceLock<CpuIsa>`:
|
| 10 |
+
|
| 11 |
+
| Architecture Target | Enum Variant | Registers | Bytes / Cycle | Intrinsics Used |
|
| 12 |
+
| :--- | :--- | :--- | :--- | :--- |
|
| 13 |
+
| **x86 / x86_64 AVX-512BW** | `CpuIsa::Avx512` | ZMM (`__m512i`) | 64 bytes | `_mm512_set1_epi8`, `_mm512_loadu_si512`, `_mm512_cmpeq_epi8_mask` |
|
| 14 |
+
| **x86 / x86_64 AVX2** | `CpuIsa::Avx2` | YMM (`__m256i`) | 32 bytes | `_mm256_set1_epi8`, `_mm256_loadu_si256`, `_mm256_cmpeq_epi8`, `_mm256_movemask_epi8` |
|
| 15 |
+
| **ARM64 NEON** | `CpuIsa::Neon` | V-Reg (`uint8x16_t`) | 16 bytes | `vdupq_n_u8`, `vld1q_u8`, `vceqq_u8`, `vmaxvq_u8` |
|
| 16 |
+
| **Portable Fallback** | `CpuIsa::Scalar` | General Purpose | 1 byte | Iterator `position` / `rposition` |
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## 2. Vectorized Byte Scanning Kernels
|
| 21 |
+
|
| 22 |
+
1. **`find_byte_fast(haystack: &[u8], needle: u8) -> Option<usize>`**:
|
| 23 |
+
- Scans forward in 64-byte (`AVX-512BW`), 32-byte (`AVX2`), or 16-byte (`NEON`) unaligned chunks, locating the first matching byte offset via hardware bitmask `trailing_zeros()`.
|
| 24 |
+
- Powers `find_newline_fast` for `.jsonl`/`.txt` line splitting and whitespace boundary detection.
|
| 25 |
+
2. **`rfind_byte_fast(haystack: &[u8], needle: u8) -> Option<usize>`**:
|
| 26 |
+
- Scans backward from the end of a byte slice using `leading_zeros()` on the comparison mask.
|
| 27 |
+
- Powers `rfind_newline_fast` to locate the exact last complete line boundary in a byte buffer in $O(1)$ vector steps.
|
| 28 |
+
3. **`contains_byte_fast(haystack: &[u8], needle: u8) -> bool`**:
|
| 29 |
+
- Fast-path guard in `postprocess.rs` that skips string reallocation during CRLF (`\r\n`) normalization whenever a record contains no `\r` (`0x0D`) byte.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## 3. 64-Byte Cache-Line Aligned Buffer (`CacheAlignedBuffer`)
|
| 34 |
+
|
| 35 |
+
```rust
|
| 36 |
+
#[repr(align(64))]
|
| 37 |
+
#[derive(Debug, Clone)]
|
| 38 |
+
pub struct CacheAlignedBuffer {
|
| 39 |
+
pub data: Vec<u8>,
|
| 40 |
+
}
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
Rounds buffer capacities up to 64-byte cache-line multiples (`(cap + 63) & !63`) to avoid split-line penalties during SIMD scanning and batch serialization.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## 4. Parallel Rayon Normalization Pipeline (`src/postprocess.rs`)
|
| 48 |
+
|
| 49 |
+
`TextCleaner` and `FusedPipeline` govern parallel per-record sanitization across raw text batches:
|
| 50 |
+
|
| 51 |
+
```mermaid
|
| 52 |
+
flowchart LR
|
| 53 |
+
Input["Raw Shard Bytes"] --> Scan["SIMD Newline Scan (AVX-512BW / AVX2 / NEON)"]
|
| 54 |
+
Scan --> Extract["Rayon Parallel JSON Field Extraction"]
|
| 55 |
+
Extract --> Clean["Fast CRLF Normalization & Whitespace Trim"]
|
| 56 |
+
Clean --> Output["Valid Length: Normalized UTF-8 Batch"]
|
| 57 |
+
Clean --> Truncate["Exceeds Max: Snap to UTF-8 Boundary"]
|
| 58 |
+
Truncate --> Output
|
| 59 |
+
Clean --> Drop["Below Min / Empty: Filter Out Record"]
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
- **Order-Preserving Parallel Execution**: `process_batch_parallel_indexed` uses Rayon's indexed `par_iter().map(...)` to normalize batches across all CPU cores while preserving exact deterministic record ordering for `StreamCoordinate` progression.
|