Skip to content

Releases

Basalt 3.13.0 — GEMM 重写 + 设备层

Choose a tag to compare

@github-actions github-actions released this 01 Nov 04:01

Basalt 3.13.0

「CPU 到瓶颈了」这个判断,实测不支持。真正的问题在 GEMM 实现,不在硬件。

  • 坐标 — io.github.9178nekomc:basalt:3.13.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 规模 — 244 个公开类型 / 1632 个公开成员,全部带逐参数说明

先说 CUDA

这台机器没有 NVIDIA 显卡(只有 AMD 核显 + 两个虚拟显示器),也没有 CUDA 工具链。
CUDA 代码在这里无法编译、无法运行、无法验证——交付一堆从没执行过的内核,比不写更糟。

所以这一版做的是设备层:接 GPU 的前置条件,而且完全可以在 CPU 上验证。
CUDA 后端留到有 N 卡的机器上再实现,届时只需填内核,不用改调用方。

GEMM 重写

原实现走 i → k → j,每做一次乘加就要读一整行 B,跑到的是内存带宽而不是算力。
512³ 只有 31.8 GFLOPS,约为此 CPU 峰值的 15%。

规模 改前 改后 倍数
512³ 31.8 GFLOPS 52.0 GFLOPS 1.63×
1024³ 32.7 GFLOPS 49.5 GFLOPS 1.51×

四处改动:

  • 6×16 微内核:12 个累加器常驻寄存器;每步 k 加载 6 个 A 标量与 2 个 B 向量,
    发出 12 次 FMA——每个 FMA 摊到 0.67 次加载(8×8 内核是 1.1 次)
  • B 面板打包:按 KC × NC 收成连续内存,微内核顺序读
  • 三层分块 KC=512 / MC=30 / NC=256:工作集压进 L2
  • 运行时指令集分派:is_x86_feature_detected! 检测 AVX2 + FMA,缺失则退回标量内核

分块参数是扫出来的:KC/NC 从 128 到 512、MC 从 24 到 120 各跑一遍取最优。

扫描过程中发现一个真 bug:MC 必须是 MR 的整数倍,否则最后一个寄存器块会写穿到下一块内存。
现象是段错误,而且只在线程池路径上出现(单线程测试全过)。现在有 debug_assert 兜着。

设备层

Tensor x = engine.tensor(data, 64, 64);
x.device();                          // cpu
Tensor copy = x.to(Device.CPU());    // 复制一份新存储
x.to(Device.cuda(0));                // UnsupportedOperationException: only cpu is available
  • 张量携带设备标签,跨设备运算拒绝执行,不做隐式搬运
  • Device.cuda(0) 可以创建、比较、打印,但用它计算会显式失败,不假装成功
  • 同设备传输同样走复制路径——这条通道现在就被 5 个测试覆盖,
    而不是等第一次上 GPU 才第一次执行

两个自己挖的坑

1. 运行时 cfg! 当编译期门控

#[target_feature(enable = "avx2,fma")] 只在 x86 有效,但我用 cfg!(target_arch = "x86_64")
做分派——那是运行时的布尔值,代码在 ARM 上仍然要编译。macOS aarch64 构建直接失败。
本地是 x86_64,怎么编都过,是 CI 抓到的。

2. JNI 属性错位

插入设备层入口时,#[no_mangle] 落到了 transferTensor 上而不是 tensorShape,
导致 tensorShape 的导出符号消失。这种错误不会在编译期报,要等第一次调用才炸。
已在 DLL 里确认两个符号都存在。

验证

  • 38 个 Rust 测试(新增 3 个 GEMM 用例:对齐尺寸、含余数尺寸、多面板)
  • 20 个 Java 测试(新增 5 个设备层用例)
  • ./verify.sh 全绿(9 组套件)
  • 文档:19 页 / 244 类型 / 1632 个成员全部有说明 / 0 占位符 / 0 断链

还没做的

  • CUDA 后端:需要 N 卡机器
  • 多线程 GEMM 负载均衡:现在按行切块,大矩阵下最后一个任务偏重
  • 掩码:where / maskedFill

产物

文件 说明
basalt-3.13.0.jar 标准 jar,含四平台原生库
basalt-3.13.0-all.jar Fat jar,无外部依赖
basalt-3.13.0-sources.jar 源码
basalt-docs-3.13.0.zip 离线文档,19 页

v3.12.0

Choose a tag to compare

@github-actions github-actions released this 01 Nov 03:04
feat(ops): slice, cat and index_select

Multi-head attention is, mechanically, "cut the last axis into pieces, run each
piece, put them back". Before this change none of those three moves existed: the
library could multiply matrices but could not assemble heads.

Three operators, all reducing along a single axis:

- slice(dim, start, end)   copies a range; backward writes the gradient back into
                           that range and leaves the rest zero
- cat(other, dim)          joins two tensors; backward splits the gradient at the
                           seam
- indexSelect(dim, idx)    gathers by index; backward scatters and accumulates,
                           so repeated indices add up

Native operators go from 34 to 37. The split-and-rejoin path is covered by
attentionHeadSplitAndMergeRoundTrips, which slices a [1,2,4] tensor into two
heads and asserts the concatenation is bit-identical to the original.

Two implementation notes:

The gather index tensor holds floats because tensors are f32 everywhere, so the
values are validated for integrality and range rather than truncated silently.

cat checks that the non-joined axes match in Java before crossing into native
code. Without that the failure surfaced as BasaltShapeException from the engine
while every other argument error was IllegalArgumentException, which made the
API inconsistent for no benefit.

Version 3.12.0. 32 Rust tests, 15 Java tests, verify.sh green with 9 groups,
1616 of 1616 public members documented, 0 placeholders.

Basalt 3.11.0 — 按轴归约

Choose a tag to compare

@github-actions github-actions released this 01 Nov 02:49

Basalt 3.11.0

这一版补的是按轴归约:没有它,数值稳定的 softmax 写不出来。

  • 坐标 — io.github.9178nekomc:basalt:3.11.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 243 个公开类型 / 1607 个公开成员,全部带逐参数说明

为什么

3.10.0 加了全量归约,但不能减最大值。这意味着 softmax 在输入偏大时直接溢出:
exp(1000) = inf,inf / inf = NaN。上一版的「手写 softmax」只在输入温和时成立。

新增

算子 反向规则
sum(dim) / sum(dim, keepdim) 梯度原样广播到该维每个位置
mean(dim) / mean(dim, keepdim) 梯度除以该维长度后广播
max(dim) / max(dim, keepdim) 只给到该组第一个最大值的位置,并列取最早

原生算子 31 → 34。keepdim 保留成长度 1,是为了能直接和原张量广播。

现在稳定的 softmax 是四行,且在 1000 量级输入上不溢出:

Tensor shifted = x.sub(x.max(1, true));
Tensor numer = shifted.exp();
Tensor denom = numer.sum(1, true);
Tensor probs = numer.div(denom);

一个形状决策

库里没有 0 维张量。rank-1 张量做 sum(0) 时,PyTorch 会给一个 0 维标量,
这里统一折成形状 [1]——和 sumAll 保持一致,也免得每个调用方都要处理空形状。

验证

  • 28 个 Rust 单元测试(新增 3 个:前向值、梯度路由、非法形状)
  • PrimitiveOpsTest 10 项,新增:
    • 三个归约的数值与 keepdim 形状
    • 越界维度抛异常
    • 三个归约各自的中心差分梯度检验
    • [1000, 1001, 1002] 的 softmax 必须有限且和为 1——不做 max 归约就会 NaN,这条是本次的判据
  • ./verify.sh 全绿(9 组套件)
  • 文档:19 页 / 243 类型 / 1607 个成员全部有说明 / 0 占位符 / 0 断链

还没做的

  • 索引算子:gather / slice / cat — 手写注意力头、embedding 变体需要
  • 掩码:where / maskedFill — 变长序列与 causal mask 需要
  • 多轴归约:一次压掉多个维度,现在只能一维一维来
  • 自定义算子注册:用 Java 写前向反向、不改 Rust。前置条件是延迟执行

产物

文件 说明
basalt-3.11.0.jar 标准 jar,含四平台原生库
basalt-3.11.0-all.jar Fat jar,无外部依赖
basalt-3.11.0-sources.jar 源码
basalt-docs-3.11.0.zip 离线文档,19 页

Basalt 3.10.0 — 底层算子补齐

Choose a tag to compare

@github-actions github-actions released this 01 Nov 02:31

Basalt 3.10.0

这一版补的是底层:让「库里没有的模型」也能自己写出来。

  • 坐标 — io.github.9178nekomc:basalt:3.10.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 243 个公开类型 / 1595 个公开成员,全部带逐参数说明

为什么改这个

模型层不可能覆盖所有算法。决定上限的不是「内置了多少模型」,而是底层有多少可组合的算子。

3.10.0 之前,Tensor 只有 23 个公开算子,而且一个归约都没有。这意味着连
softmax 的分母都写不出来——不是效率问题,是根本写不出来。

新增

原生算子 22 → 31:

类别 算子 用途
数学函数 exp log sqrt abs neg softmax 变体、对数似然、距离度量、自定义激活
二目运算 div pow(走既有广播路径) 归一化、幂律缩放、温度缩放
归约 sumAll meanAll softmax 分母、任意损失压成标量

每个算子都是端到端接好的:枚举与操作码 → ops.rs 前向与反向 → engine.rs 分发 →
队列方法 → Tensor 方法 → Rust 单测 → Java 梯度检验。

手写 softmax 只要三行

Tensor numer = x.exp();
Tensor denom = numer.sumAll();     // 新增:全量归约
Tensor probs = numer.div(denom);   // 新增:逐元素除法

PrimitiveOpsTest 里就是用这三个算子拼出 softmax,断言概率之和为 1、
大输入得到大概率。库没有内置这个分支,走的是公开算子组合——
这是本版真正的验收标准:不给现成模型,也能写出来。

边界行为写在文档里

算子 边界 行为
log x ≤ 0 返回 -inf / NaN,不加 epsilon
sqrt x < 0 返回 NaN
div 除数为 0 返回 inf / NaN
pow 负底数 + 非整数指数 返回 NaN
abs x = 0 反向取次梯度 0

库不做隐式保护:加不加 epsilon 是算法决定,不是库决定。

还没做的

诚实列出来,不假装完备:

  • 按轴归约:sum(dim) / mean(dim) / max(dim),目前只有全量归约
  • 索引算子:gather / slice / cat,手写 embedding 变体与自定义注意力头需要
  • 掩码:where / maskedFill,变长序列与 causal mask 需要
  • 自定义算子注册:用 Java 写前向与反向、不改 Rust 就能加算子。
    这是「创造新算法」的最后一块,前置条件是延迟执行,否则每加一个算子都要多付一次 JNI

验证

  • 25 个 Rust 单元测试(含 8 个新增:前向数值、反向、广播、非法形状拒绝)
  • PrimitiveOpsTest 6 项:前向值、广播、跨上下文拒绝、逐个算子的中心差分梯度检验、手写 softmax
  • ./verify.sh 全绿(9 组套件 + 6 个编译期拒绝用例,-Xlint:all 零告警)
  • 文档:19 页 / 243 类型 / 1595 个成员全部有说明 / 0 占位符 / 0 断链

一个加载陷阱

NativeKernels 的查找顺序是 -Dbasalt.native.path → build/native/ → classpath。
build/native/ 优先于打包进 jar 的那份。本地留着一份旧库时,新编的库会被静默忽略,
报的还是旧版的错误信息(expected 1..=22)。

改完原生代码要么同步 build/native/,要么用 -Dbasalt.native.path 直接指过去。

产物

文件 说明
basalt-3.10.0.jar 标准 jar,含四平台原生库
basalt-3.10.0-all.jar Fat jar,无外部依赖
basalt-3.10.0-sources.jar 源码
basalt-docs-3.10.0.zip 离线文档,19 页

Basalt 3.9.0 — 张量生命周期交给 Rust

Choose a tag to compare

@github-actions github-actions released this 31 Oct 01:27

Basalt 3.9.0

这一版只做一件事:把张量的生命周期从 Java 的 GC 手里拿回来,交给 Rust。

  • 坐标 — io.github.9178nekomc:basalt:3.9.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 243 个公开类型 / 1568 个公开成员,全部带逐参数说明

问题

一个字符级训练任务(每轮 2000 个窗口、batch 32、2.68 M 参数)在第 15 轮左右崩溃:

memory allocation of 4194304 bytes failed

不是常见的泄漏。每个原生张量的回收挂在一个约 40 字节的 Java 句柄上,通过 Cleaner 触发——
Java 堆几乎没有压力,GC 于是根本不来,而原生侧每一批输入、每个前向中间量、损失张量都还活着。

先试过两个补丁,都只把崩溃点往后推:

补丁 结果
在 Trainer 里显式 close() 四个可见张量 从第 15 轮推到第 30 轮
每 128 步 System.gc() 同样到第 30 轮

两个都是在给「GC 什么时候来」打补丁,没有触及设计本身。

改动

张量在作用域内由 Rust 持有:

  • Context 增加帧栈,每次分配把地址记进栈顶帧;begin_scope 压栈,end_scope 弹栈并把帧内张量一次性释放,返回实际释放个数。
  • 三个 JNI 入口,对应 NativeContext.beginScope() / endScope() / scopeDepth()。
  • Trainer 把每个训练步包进作用域:批次张量、前向中间量、损失都在这一步结束时回收。参数、输入、优化器状态在作用域外创建,不受影响。
  • 顺序有约束并在文档里写明:end_scope 必须在 backward 与 clear_graph 之后,否则计算图仍引用着已释放的张量。
context.beginScope();
try {
    Tensor logits = model.forward(inputs);
    Tensor loss = engine.crossEntropy().loss(logits, labels);
    loss.backward();
    optimizer.step();
} finally {
    context.endScope();   // Rust 一次回收整帧,不等 GC
}

验证

同一套配置改造后跑过 30 轮以上,没有再出现分配失败——这是本次修复的直接判据。
训练在 30 轮处主动停止,没有跑到收敛:这一版的目的是证明内存不再无界增长,不是产出一个模型。

  • ./verify.sh 全绿(7 组 legacy 套件 + JUnit 5 + 6 个编译期拒绝用例,-Xlint:all 零告警)
  • TextEngineTest 8/8、UnifiedModelTest 8/8、AdvancedApiTest 9/9
  • 文档:19 页 / 243 类型 / 1568 个成员中 1568 个有说明 / 0 占位符 / 0 断链

产物

文件 说明
basalt-3.9.0.jar 标准 jar,含四平台原生库
basalt-3.9.0-all.jar Fat jar,无外部依赖
basalt-3.9.0-sources.jar 源码
basalt-docs-3.9.0.zip 离线文档,19 页,解压双击 index.html

前几版留下的

  • 3.8.0 — TextEngine.stream / streamSampled:逐 token 回调,解码结果与批量接口逐位一致
  • 3.7.0 — 文档与基准报告 HTML 移出版本库,由 CI 构建并发布,单次推送从 17.1 MiB 降到 5.8 MiB
  • 3.6.0 — basalt.model 统一模型层,Auto.sequence 可筛序列模型

v3.8.0

Choose a tag to compare

@github-actions github-actions released this 30 Oct 13:42
feat(infer): stream tokens as they are produced

TextEngine could only hand back a finished array, so a caller that wanted to show
progress had to fake it by chunking the result. stream and streamSampled call a
sink per token instead.

The decoding rules are untouched: stream produces exactly what generate would
have produced, streamSampled exactly what sample would have produced under the
same seed. The test asserts both, token by token, because "streaming" that
quietly picks different tokens is a common way to ship a subtle bug.

Measured on the demo model (1.04 M parameters, window 16, one CPU core):
3380 tokens/second without display delay.

Version 3.8.0. Docs: 1565 of 1565 members documented, 0 placeholders. verify.sh
green with 9 suite groups.

v3.7.0

Choose a tag to compare

@github-actions github-actions released this 30 Oct 13:29
feat(infer): a decoding engine on top of any windowed language model

TextEngine wraps a Module with the parts every chat loop rewrites: keeping the
window, turning logits into a token, batching, and persisting weights.

What it adds over writing the loop by hand:

- scoresBatch runs N contexts through one forward, so decoding N conversations
  costs roughly one conversation's worth of native crossings per step instead of
  N. generateBatch is the same idea applied to greedy decoding.
- sample does temperature softmax over the top-k candidates with a seeded RNG,
  which is what keeps a small model from emitting a rare character every step.
  topK=1 degenerates to greedy, and the test asserts exactly that.
- save/load store weights by name next to the vocabulary and window, so loading
  into a differently shaped module fails with the offending name instead of
  silently reshaping.

Where the work happens is deliberate: every multiply and activation is executed
by the Rust backend, and the loop stays in Java because the module defines which
ops run. Recording a replayable native program would mean reimplementing the
architecture in Rust and keeping the two in sync; the batching is the part that
actually removes boundary crossings, so that is what got built.

Tests (7): batched scoring equals single-row scoring, greedy is deterministic and
truncates to the tail, batched generation matches sequential, sampling replays
under a seed and honours top-k, weights round-trip bit-exactly, bad shapes are
rejected, and a model trained on five characters decodes them back exactly.

Version 3.7.0. Docs: 19 pages, 243 public types, 1563 of 1563 members documented,
0 placeholders, 0 broken links. verify.sh green.

Basalt 3.6.0 — 统一模型层:Auto 也能筛语言模型

Choose a tag to compare

@github-actions github-actions released this 30 Oct 12:50

Basalt 3.6.0

Auto 不再只会筛表格模型。这一版在下面加了一层统一模型层,让经典估计器、神经网络和序列语言模型
在同一张筛选表里比较。

  • 坐标 — io.github.9178nekomc:basalt:3.6.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 242 个公开类型 / 1550 个公开成员,全部带逐参数说明

一句话的区别

AutoModel tabular = Auto.quickstart(features, labels);   // 之前只有这条
AutoModel lm      = Auto.sequence(tokenIds);             // 现在也能这样
int[] next = lm.generate(seed, 15);

两者走的是同一条筛选路径:数据声明任务 → 该任务下的全部求解器打分 → 最优者在全量数据上重训。

新增 basalt.model

类型 作用
Task 任务族:表格分类、表格回归、序列语言模型。指标语义也在这里,包括把 RMSE 取负
Dataset 表格数据或 token 流。任务由数据声明,不再是 Auto 猜的
Solver 一个可训练候选:能给自己打分,也能在全部数据上拟合
Fitted 训练好的模型:predict 或 generate,按任务二选一
ClassicalSolver 包装注册表里的估计器,用 k 折打分
NeuralSolver 包装「网络 + 训练配方」,用单次留出打分
Solvers 目录,跨模型家族

三个设计要点:

  1. 对称的接口,不对称的评分。 经典求解器交叉验证,神经求解器留出一片数据训一次——五折要对网络训五次,
    为了排序候选不值得。这个差异写在文档里,也原样列在筛选表中,而不是抹平成一个数。
  2. 经典求解器是推导出来的,不是拷贝的。 Solvers 按需从 Registry 生成它们,所以「注册一个
    Estimator 就能参与筛选」这个承诺依然成立,不需要再包一层。
  3. 能力按任务区分。 对序列模型调用 predict 会抛 UnsupportedOperationException,消息里点明它的任务,
    而不是返回一个空数组让错误在别处爆掉。

三层的对应变化

  • 第一层:Auto.sequence 进入序列领域;AutoModel 增加 generate 与 fitted,去掉 estimator()
    (获胜者可能是网络,返回一个 Estimator 已经没有意义)。Builder 去掉 testFraction 和 task——
    评分策略归求解器,任务归数据集。
  • 第二层:Zoo.charLm 提供一个窗口式字符级模型(嵌入 → 展平 → 瓶颈残差块 → 词表投影),
    序列家族因此有内置结构可筛。
  • 第三层:Solver 与 Module 两个契约都保持最小。写一个新算法 = 实现 Solver 并注册;
    写一个新结构 = 实现 Module 的 forward 与 parameters。

其它修正

  • Estimators.Metric 增加 CROSS_ENTROPY。所有对指标的穷尽 switch 随之编译报错,正好把
    Estimators 与 GridSearchCV 里两处漏掉的分支补齐。
  • verify.sh 的测试编译排除规则从「逐个点名 JUnit 类」改成 ! -name "*Test.java",
    以后新增测试文件不再需要改脚本。

产物

文件 说明
basalt-3.6.0.jar 标准 jar,含四个平台原生库
basalt-3.6.0-all.jar Fat jar,无外部依赖
basalt-3.6.0-sources.jar 源码
basalt-docs-3.6.0.zip 离线文档,18 页,解压双击 index.html

验证

  • ./verify.sh:Rust 单测、javac -Xlint:all 零告警、6 套 legacy 测试、Phase 1–3、JUnit 5、6 个编译期拒绝用例
  • UnifiedModelTest 8 项:序列筛选与生成、神经候选与估计器同台、注册表联动、能力按任务区分、数据校验
  • AdvancedApiTest 9 项:三层 API 各 3 项,含自定义层与自定义算法
  • 文档:18 页 / 242 类型 / 1550 成员中 1550 有说明 / 0 占位符 / 0 断链

Basalt 3.5.0 — 三层 API:一行建模、拼装训练、手写一切

Choose a tag to compare

@github-actions github-actions released this 30 Oct 11:46

Basalt 3.5.0

同一个库,三种介入深度。三层共用同一套张量与同一张计算图,所以上一层不会限制下一层。

  • 坐标 — io.github.9178nekomc:basalt:3.5.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 235 个公开类型 / 1510 个公开成员,全部带逐参数说明

第一层:一行跑起来

AutoModel model = Auto.quickstart(features, labels);
System.out.println(model.summary());
double[] prediction = model.predict(unseen);

它自己判断这是分类还是回归:目标全为整数且不同取值不多时按分类处理,否则按回归。
然后按该任务把注册过的算法全部用分层 K 折交叉验证跑一遍,取最优者在全量数据上重训,
最后在 25% 留出集上报分——留出集没有参与拟合,也没有参与筛选。

细节可以调:

AutoModel model = Auto.builder(features, labels)
        .folds(10)
        .testFraction(0.2)
        .candidates("random_forest", "gradient_boosting")
        .metric(Estimators.Metric.MACRO_F1)
        .fit();

筛选表按分数排序,summary() 直接打印成表。RMSE 这类越小越好的指标在写入前取负,
所以「分数越高越好」在所有指标上一致成立。

第二层:拼装与训练

try (Basalt engine = Basalt.create()) {
    Sequential<BC, BC> net = Zoo.tabularClassifier(engine, 4, 3);
    History history = Trainer.of(engine.context(), net,
                    engine.adamw(net.parameters(), 1e-2f), engine.crossEntropy())
            .epochs(50)
            .batchSize(16)
            .earlyStopping(5)
            .fit(engine.tensor(features, rows, 4), engine.tensor(labels, rows));
}
  • Zoo 提供 mlp、tabularClassifier、cnnClassifier,返回的都是普通 Sequential,可以继续 add
  • Trainer 是手写循环的封装:前向、损失、反向、更新、清梯度、清图,一步不省
  • 早停带 1e-12 容差,浮点抖动不会被当成进步
  • 某轮损失变成非有限值时直接抛异常,而不是继续训出一个坏模型

批次是把行复制进新张量,不是在原张量上切片——每个批次多一次数组拷贝,换来计算图里没有视图、没有隐藏别名。

第三层:手写一切

一个层只需要两个方法:

final class MyLayer extends Module<BC, BC> {
    private final Tensor weight = Tensor.parameter(context, 128, 64);

    @Override public Tensor forward(Tensor input) { return input.matMul(weight).tanh(); }
    @Override public List<Tensor> parameters() { return List.of(weight); }
}

Trainer 只认 Module,所以手写层和库里的层走完全相同的训练路径。

算法层面用注册表扩展:

Registry.register("my_regressor", Task.REGRESSION, MyRegressor::new);
Auto.quickstart(features, targets);   // 新算法与 23 个内置算法同台竞争

重复注册会被拒绝而不是静默替换——筛选顺序和报告标签都依赖名字唯一。

本版还修了什么

  • verify.sh 仍在找 jsl/tests 下的 Phase 1–3 类,改名后这三套测试一直被静默跳过。现已恢复,
    三套共 27 组用例重新纳入验证。
  • 新的 JUnit 测试被误纳入 legacy javac 路径(没有 JUnit classpath),已排除。

产物

文件 说明
basalt-3.5.0.jar 标准 jar,含四个平台原生库
basalt-3.5.0-all.jar Fat jar,无外部依赖
basalt-3.5.0-sources.jar 源码

验证

  • ./verify.sh:Rust 单测、javac -Xlint:all 零告警、6 套 legacy 测试、Phase 1–3、JUnit 5、6 个编译期拒绝用例
  • AdvancedApiTest 9 项:三层 API 各 3 项,含自定义层与自定义算法
  • 冒烟测试:对打包后的 all-jar 编译运行,三层各自跑通

Basalt 3.4.0 — 四平台原生内核、eager autograd、经典估计器

Choose a tag to compare

@9178nekomc 9178nekomc released this 25 Oct 11:49

Basalt 3.4.0

JVM 上的机器学习运行时:Java 调度,Rust 计算。eager 张量、自动求导、原生 CNN/Transformer 算子,以及经典统计学习估计器。

  • 坐标 — io.github.9178nekomc:basalt:3.4.0
  • 在线文档 — https://9178nekomc.github.io/basalt/
  • 运行要求 — Java 21,启动加 --add-modules jdk.incubator.vector
  • 规模 — 226 个公开类型 / 1463 个公开成员,全部附逐参数说明(1423 条参数条目)

安装

<dependency>
    <groupId>io.github.9178nekomc</groupId>
    <artifactId>basalt</artifactId>
    <version>3.4.0</version>
</dependency>

需要单文件时用 basalt-3.4.0-all.jar,四个平台的原生库已经打在里面。

本版要点

改名:JSL → Basalt

旧代号 JSL / Java Scikit-Learn 描述的是模仿对象,不是它自己,也无法作为公开坐标(io.github.jsl 不属于任何人)。这一版整体迁移:

项 旧 新
Java 包 jsl.* basalt.*
入口类 JSL Basalt
异常 JSLRuntimeException 等 BasaltRuntimeException 等
JNI 符号 Java_jsl_* Java_basalt_*
原生库 jsl_native basalt_native
容器魔数 JSLM / JSLK BSLT / BSLK
文件后缀 .jsl / .jsld .bsl / .bsld

不向后兼容,但旧坐标从未发布过,因此不构成真正的破坏性变更。

四平台交叉编译(CI 全绿)

原生内核在 CI 上对四个目标实际编译并跑通全部测试,不是只在本机验证:

平台 目标三元组
Linux x86_64 x86_64-unknown-linux-gnu
Linux aarch64 aarch64-unknown-linux-gnu
macOS aarch64 aarch64-apple-darwin
Windows x86_64 x86_64-pc-windows-msvc

CI 抓出两个只在 ARM 上暴露的真实缺陷:classic.rs 与 fastmath.rs 的 AVX2 路径缺少架构门控,导致 aarch64 上整个 crate 编译失败。已修复,标量回退路径在非 x86 上生效。

Intel Mac(macos-13)未包含在本版产物中:其 runner 池正在退役,排队 45 分钟未启动。Intel Mac 用户可用 ./native-build.sh 自行构建,或退回纯 Java 路径。

工程底座

  • 双重内存安全 — NativeContext、NativeTensorHandle、HistogramSessionHandle 同时实现 AutoCloseable 与 Cleaner
  • 64 位代数句柄 (generation << 32) | slot_index,杜绝悬垂指针与 ABA 复用崩溃
  • 128 字节指令队列,CommandQueueBuilder 全同步保护;原生层严禁跨线程复用 JNIEnv
  • 原生库自动加载,加载失败时经典模型自动回退纯 Java

能力面

  • eager 张量 / autograd — 动态形状、Module 组合、Kahn 拓扑排序反向、DAG 计算图、梯度累积、Checkpoint 续训
  • 原生算子 — Conv2d(grouped / dilated / depthwise-separable)、MaxPool2d、BatchNorm2d、GroupNorm、多头注意力与 causal mask
  • 经典机器学习 — 线性/逻辑回归、KNN、SVM、决策树、随机森林、直方图梯度提升、KMeans、GMM、层次聚类、PCA、IsolationForest、KernelDensity、朴素贝叶斯
  • 特征工程 — SelectKBest、RFE、PolynomialFeatures、KBinsDiscretizer、GridSearchCV / RandomizedSearchCV
  • 可解释性 — Tree/Permutation 特征重要度、LinearSHAP、TreeSHAP、LIME
  • 可观测性 — JFR 事件 + Prometheus/JSON 指标导出
  • 互操作 — ONNX 导出与导入推理往返
  • 持久化 — .bsl v2 容器,24 字节头 + CRC32C,零拷贝 ModelReader

产物

文件 说明
basalt-3.4.0.jar 标准 jar,含四个平台原生库
basalt-3.4.0-all.jar Fat jar,无外部依赖
basalt-3.4.0-sources.jar 源码
basalt-docs-3.4.0.zip 离线文档,解压双击 index.html

验证

  • ./verify.sh — Rust 单测、javac -Xlint:all 零告警、6 套 legacy 测试、Phase 1–3、JUnit 5、6 个编译期拒绝用例
  • 文档 15 页 / 226 类型 / 1463 成员,全部有说明
  • GitHub Actions 四平台矩阵 + 汇总打包,全部通过