模块 basalt.core¶
16 个公开类型 · 1 个包内可见类型
基础数据层:行主序矩阵、布局标记、原生经典核、模型容器读写原语。
类型概览
| 类型 | 种类 | 说明 |
|---|---|---|
BC | class | 批×通道布局标记,同时用于批×特征与批×类别 |
BCHW | class | 批×通道×高×宽布局标记,卷积网络的图像张量 |
BHW | class | 批×高×宽布局标记,省略通道轴的三维图像张量 |
BSF | class | 批×序列×特征布局标记,序列模型的标准输入 |
Layout | interface | 张量维度顺序的编译期标记接口,只约束泛型参数 |
MatMul | class | 矩阵乘法引擎:标量基准与分块并行的 SIMD 实现 |
Matrix | class | 行主序、单块 double 数组承载的可变矩阵 |
ModelFormatException | class | .bsl 流截断、损坏或版本不兼容时抛出的非受检异常 |
ModelKind | enum | 可持久化模型族的稳定线格式标识码 |
ModelReader | class | 与 ModelWriter 配对的大端、带越界校验的读取器 |
ModelWriter | class | 可增长的大端二进制写入器 |
NativeKernels | class | 通过 JNI 暴露的可选 Rust 内核集合 |
Persistable | interface | 可写入 .bsl 容器的模型契约 |
Tensor | class | 布局由类型参数约束的可变连续张量 |
VectorMath | class | 被乘法、距离、优化器与归一化共用的 SIMD 原语 |
NativeKernels.HistogramSessionHandle | class | 持有原生直方图会话指针的可关闭句柄 |
- final class BC implements Layout¶
批×通道布局标记,同时用于批×特征与批×类别
BC 表示二维张量,第 0 轴为批(batch),第 1 轴为通道(channels)。源码注释同时声明该布局复用为「批 × 特征」与「批 × 类别」,因此全连接层的输入输出、以及分类头的 logits 都落在这一标记上。
该类没有成员,构造器为 private,运行期不存在任何实例。它只出现在泛型参数位置,例如 Tensor<BC>,用来在编译期固定轴的含义。basalt.nn 中的 CNN 以 Tensor<BC> 表示输出与目标,Flatten 用它在 BCHW 与 BC 之间转换。
示例Tensor<BC> logits = Tensor.ofBC(2, 3, 0.5, 0.25, 0.75, 0.1, 0.2, 0.3); int batch = logits.dimension(0); int classes = logits.dimension(1);注意
仅做编译期标记:不产生对象、不占用堆内存、不带来运行时分派开销。
注意
构造器私有,无法实例化;类型检查全部发生在 javac 阶段。
- final class BCHW implements Layout¶
批×通道×高×宽布局标记,卷积网络的图像张量
BCHW 表示四维张量,轴序为 batch、channels、height、width。这是卷积与池化层的工作格式:同一批内的某个通道构成一张连续的特征图,通道维位于空间维之前。
Tensor.ofBCHW 与 Tensor.zerosBCHW 产生携带该标记的实例。Conv2D、MaxPooling2D 的 forward 与 backward 均以 Tensor<BCHW> 收发数据,CNN 则把它作为训练输入。
与其余三个标记相同,该类没有成员,构造器为 private,运行期不产生实例,只约束泛型参数。
示例Tensor<BCHW> images = Tensor.zerosBCHW(4, 3, 32, 32); int channels = images.dimension(1); int height = images.dimension(2);注意
仅做编译期标记,无运行期表示与开销。
注意
轴序固定为批在最前,因此批切片在存储中连续,sliceBatch 可用单次 arraycopy 完成。
- final class BHW implements Layout¶
批×高×宽布局标记,省略通道轴的三维图像张量
BHW 表示三维张量,轴序为 batch、height、width。与 BCHW 的差别只在于没有通道轴,适合灰度图或已合并通道的中间结果。
Tensor.ofBHW 是唯一产生该标记的工厂方法;本包未提供对应的 zeros 变体,需要全零张量时可先构造 double 数组再传入。
该类没有成员,构造器为 private,运行期不产生实例,仅在泛型参数位置出现。
示例double[] pixels = new double[2 * 8 * 8]; Tensor<BHW> gray = Tensor.ofBHW(2, 8, 8, pixels); int rank = gray.rank();注意
仅做编译期标记,无运行期表示与开销。
注意
Tensor<BHW> 与 Tensor<BCHW> 是不同类型,轴数不同的张量无法互相赋值。
- final class BSF implements Layout¶
批×序列×特征布局标记,序列模型的标准输入
BSF 表示三维张量,轴序为 batch、sequence(时间步)、features。同一时间步的特征在存储中相邻,循环层按时间步推进时每次读取一段连续内存。
Tensor.ofBSF 与 Tensor.zerosBSF 产生该标记的实例。LSTM、GRU、SelfAttention、SequenceNetwork 与 GradientCheck 都以 Tensor<BSF> 作为输入、输出与梯度的载体。
该类没有成员,构造器为 private,运行期不产生实例,仅在泛型参数位置出现。
示例Tensor<BSF> batch = Tensor.zerosBSF(8, 16, 32); int steps = batch.dimension(1); int features = batch.dimension(2);注意
仅做编译期标记,无运行期表示与开销。
注意
序列轴位于批轴之后,因此按批切片仍是一段连续存储。
参见:basalt.core.Layout, basalt.nn.LSTM, basalt.nn.SequenceNetwork
- sealed interface Layout¶
张量维度顺序的编译期标记接口,只约束泛型参数
Layout 是 sealed 接口,permits 子句把实现者限定为 BCHW、BHW、BC、BSF 四个类。接口本身不声明任何方法,四个实现类也没有成员,构造器全部为 private,因此运行期不存在 Layout 的实例,标记只以类型参数的形式存在。
它的作用是让轴的含义进入类型系统:Tensor<S extends Layout> 用 S 记录维度顺序,卷积层接收 Tensor<BCHW>、序列层接收 Tensor<BSF>,把错误的张量传给错误的层会在编译期失败,而不是在运行期产生形状异常。
由于没有实例与虚方法,标记不带来对象分配、内存占用或调用开销,形状与轴数仍由 Tensor 内部的 int[] 在运行期保存。
示例Tensor<BCHW> images = Tensor.zerosBCHW(2, 3, 8, 8); Tensor<BSF> sequence = Tensor.zerosBSF(2, 4, 16); // images 与 sequence 是不同类型,编译器拒绝互相赋值 int imageRank = images.rank(); int sequenceRank = sequence.rank();注意
sealed 限定实现者,第三方无法新增布局;新增布局需要修改 permits 子句。
注意
标记不含任何运行期信息,轴数仍需通过 Tensor.rank() 获取。
参见:basalt.core.Tensor, basalt.core.BC, basalt.core.BCHW, basalt.core.BHW, basalt.core.BSF
- final class MatMul implements AutoCloseable¶
矩阵乘法引擎:标量基准与分块并行的 SIMD 实现
实例封装一个 ForkJoinPool 与一个线程数提示值。multiply 在该池上派发按行切分的 RowTask;当工作量 rows * columns * shared 达到 100000000 且原生库可用时,改走 Rust 的 AVX2/FMA 内核,并把同一线程数提示传给原生侧,原生内核抛 IllegalStateException 时静默回退到 Java 路径。
Java 路径先把右操作数转置成列主序副本,使每个输出元素的点积在两个数组上都是连续读取,再按行二分递归切分,叶子节点逐元素调用 VectorMath.dot。切分在行数不超过 8 或子任务工作量不超过 65536 时停止。
线程数来源有三处:create() 取 Runtime.availableProcessors(),create(int) 取显式值,commonPool() 复用 JVM 公共池并把线程数记为可用核数。前两者拥有自己的池,close() 时关闭;commonPool() 的包装不拥有池。
示例try (MatMul engine = MatMul.create(4)) { Matrix a = Matrix.zeros(512, 256); Matrix b = Matrix.zeros(256, 128); Matrix c = engine.multiply(a, b); int columns = c.columns(); }注意
close() 只关闭 create 系列创建的池;对 commonPool() 的包装调用 close() 不产生任何效果。
注意
multiply 与 scalar 都校验内维相等,并检查输出元素数是否超过 Integer.MAX_VALUE。
注意
原生路径只在 NativeKernels.available() 为真且工作量达标时启用,失败时回退而非抛出 JNI 错误。
参见:basalt.core.Matrix, basalt.core.VectorMath, basalt.core.NativeKernels
方法
- create() → MatMulstatic¶
按可用处理器数创建引擎
与 create(int) 的唯一差别是线程数来源:这里取运行环境的可用处理器数作为池的并行度。引擎拥有该池,close() 会将其关闭。
- 返回
持有独立 ForkJoinPool 的引擎,线程数等于 Runtime.availableProcessors()
- create(int threads) → MatMulstatic¶
按指定并行度创建引擎
源码注释说明该重载用于扩展性基准测试:固定并行度可以让不同规模的乘积在同一线程预算下比较。该值同时作为原生内核的线程数提示传给 NativeKernels。
- 参数
threads (int) – ForkJoinPool 并行度,必须为正
- 返回
持有独立线程池的引擎,线程数等于 threads
- 抛出
IllegalArgumentException – threads 小于等于 0
- commonPool() → MatMulstatic¶
包装 JVM 公共 ForkJoinPool 的引擎
复用 ForkJoinPool.commonPool(),不新建线程;ownsPool 为 false,因此 close() 不会关闭公共池,实例可以随用随弃。Matrix.matMul 每次调用都走这条路径。
注意
公共池与同进程内其他 ForkJoinTask 共享,并行度不受本引擎控制。
- 返回
不拥有线程池的引擎,线程数记为可用核数
- scalar(Matrix left, Matrix right) → Matrixstatic¶
三重循环的标量乘法基准实现
静态方法,不使用线程池也不调用 SIMD,按 i、j、k 顺序逐元素累加,作为正确性与速度的参照。源码注释明确其定位是 baseline,不应作为生产路径。
注意
结果通过公开构造器装入,会再克隆一次输出数组。
- 参数
left (Matrix) – 左操作数,形状 m×k
right (Matrix) – 右操作数,形状 k×n,行数须等于 left.columns()
- 返回
形状为 m×n 的新矩阵
- 抛出
IllegalArgumentException – 内维不匹配,或输出元素数超过 Integer.MAX_VALUE
NullPointerException – 任一操作数为 null
- multiply(Matrix left, Matrix right) → Matrix¶
按规模选择原生内核或分块并行 Java 路径
工作量以 long 计算为 rows * columns * shared。达到 100000000 且原生库可用时调用 NativeKernels.gemmChecked,并用 Matrix.wrap 直接接管其输出缓冲区;否则转置右操作数后在池上执行 RowTask,逐元素用 VectorMath.dot 计算。
注意
原生路径的线程数取自构造引擎时的 threads 字段。
注意
Java 路径每次调用都会分配一份右操作数的转置副本。
- 参数
left (Matrix) – 左操作数,形状 m×k
right (Matrix) – 右操作数,形状 k×n,行数须等于 left.columns()
- 返回
形状为 m×n 的新矩阵
- 抛出
IllegalArgumentException – 内维不匹配,或输出元素数超过 Integer.MAX_VALUE
NullPointerException – 任一操作数为 null
- close()¶
关闭自有的线程池
仅当引擎由 create() 或 create(int) 创建时调用 pool.shutdown();commonPool() 的包装因 ownsPool 为 false 而不做任何事。方法幂等,可重复调用。
注意
关闭后不应再调用 multiply,池已不再接受新任务。
- final class Matrix¶
行主序、单块 double 数组承载的可变矩阵
全部元素存放在一个连续的 double[] 中,元素 (r, c) 的偏移由 index 计算为 r * columns + c,因此同一行在内存中相邻,按行遍历具有良好的局部性。行数、列数与数组引用都是 final,形状不可变而元素可变。
构造器与 fromRows 都校验维度为正、长度等于 rows * columns,并克隆传入数组;zeros 只校验维度后分配已清零的数组。data() 返回内部数组本体,toArray() 返回副本,二者的取舍决定调用方能否观察到后续写入。
乘法不在本类实现:matMul 每次调用都构造一个绑定 ForkJoinPool.commonPool() 的 MatMul 包装对象并转发,因此不持有长期线程资源,也不需要关闭。
示例Matrix m = Matrix.fromRows(new double[][]{{1, 2}, {3, 4}}); m.set(0, 1, 5.0); double[] live = m.data(); double[] safe = m.toArray(); Matrix product = m.matMul(m.transpose());注意
data() 暴露内部存储,写入该数组等同修改矩阵;需要快照时使用 toArray() 或 copy()。
注意
构造器接受外部数组时会克隆,后续修改源数组不会影响矩阵。
注意
形状校验用 long 计算 rows * columns,超过 Integer.MAX_VALUE 时抛出 IllegalArgumentException。
参见:basalt.core.MatMul, basalt.core.Tensor, basalt.core.VectorMath
构造器
- Matrix(int rows, int columns, double[] data)¶
校验形状后克隆数组内容构造矩阵
校验顺序为维度、null、长度:先确认 rows 与 columns 为正,再检查数组非空,最后比对长度与乘积。通过后调用 clone,因此矩阵与调用方不共享存储。
注意
传入数组不会被持有,构造完成后修改源数组不影响矩阵内容。
- 参数
rows (int) – 行数,必须为正
columns (int) – 列数,必须为正
data (double[]) – 行主序元素,长度必须等于 rows * columns,构造时被克隆
- 抛出
IllegalArgumentException – 维度非正、长度不符,或元素总数超过 Integer.MAX_VALUE
NullPointerException – data 为 null
方法
- zeros(int rows, int columns) → Matrixstatic¶
创建指定形状的全零矩阵
只做维度校验,随后按 rows * columns 分配新数组;初值 0.0 由 JVM 保证,不再逐元素填充,因此成本与元素数成线性关系但常数很小。
- 参数
rows (int) – 行数,必须为正
columns (int) – 列数,必须为正
- 返回
新分配的全零矩阵
- 抛出
IllegalArgumentException – 维度非正,或元素总数超过 Integer.MAX_VALUE
- fromRows(double[][] rows) → Matrixstatic¶
由二维数组按行拷贝构造矩阵
以第一行的长度作为列数,逐行用 System.arraycopy 拷入连续缓冲区。长度不一致会在该行处立即失败,不做截断或补零,因此可以安全地用于读入外部表格数据。
注意
空数组与零长度行都视为非法,矩阵不接受零元素。
- 参数
rows (double[][]) – 非空二维数组,各行长度必须相同
- 返回
行数等于 rows.length 的矩阵
- 抛出
IllegalArgumentException – 数组为空、某行长度不一致,或元素总数超过 Integer.MAX_VALUE
NullPointerException – rows 或其任一行为 null
- rows() → int¶
返回行数
- 返回
行数,恒为正
- columns() → int¶
返回列数
- 返回
列数,恒为正
- size() → int¶
返回元素总数
- 返回
rows() * columns(),即底层数组长度
- get(int row, int column) → double¶
读取指定位置的元素
下标先经 index 换算并校验,再访问数组;因此越界报错是 IndexOutOfBoundsException 而非 ArrayIndexOutOfBoundsException,消息中带有矩阵形状。
- 参数
row (int) – 行下标,范围 [0, rows())
column (int) – 列下标,范围 [0, columns())
- 返回
该位置的 double 值
- 抛出
IndexOutOfBoundsException – 任一坐标越界
- set(int row, int column, double value)¶
写入指定位置的元素
写入直接落到内部数组,不触发任何校验之外的副作用。值可以是 NaN 或无穷大,矩阵层不做清洗。
- 参数
row (int) – 行下标,范围 [0, rows())
column (int) – 列下标,范围 [0, columns())
value (double) – 待写入的 double 值,不做取值范围限制
- 抛出
IndexOutOfBoundsException – 任一坐标越界
- index(int row, int column) → int¶
把二维下标换算为一维偏移
这是行列到连续存储的唯一映射,也是行主序语义的落点:列下标直接乘以 1,行下标乘以列数。校验同时覆盖两个维度,消息包含越界坐标与矩阵形状。
- 参数
row (int) – 行下标,范围 [0, rows())
column (int) – 列下标,范围 [0, columns())
- 返回
row * columns + column
- 抛出
IndexOutOfBoundsException – 任一坐标越界
- data() → double[]¶
返回内部存储数组本身,不复制
源码注释说明该方法面向数值内核:MatMul、VectorMath 与各估计器都通过它拿到连续内存,避免逐元素访问的开销。代价是可变性外泄,任何持有者都能修改矩阵内容。
注意
写入返回值即修改矩阵元素,不存在写时复制。
注意
跨线程共享同一矩阵时需自行同步,矩阵本身不含任何同步措施。
注意
调用方不得改变数组长度语义(例如重新分配后回填),否则矩阵的行列划分与实际存储脱节。
- 返回
长度等于 size() 的数组,与矩阵共享存储
- toArray() → double[]¶
返回元素的独立副本
通过 clone 生成快照,后续对矩阵的写入不会反映到返回值上。源码注释把它标为需要防御性副本时的替代方案。
- 返回
新分配的数组,长度等于 size()
- copy() → Matrix¶
返回形状与内容相同的独立矩阵
行列数沿用当前值,存储为 data.clone()。返回对象与原矩阵完全独立,适合在就地运算前保留原值。
- 返回
持有克隆数组的新矩阵
- addInPlace(Matrix other) → Matrix¶
就地加上另一矩阵
按线性下标遍历两个数组并写回自身,不做形状广播。源码未禁止 other 就是 this,此时等价于每个元素翻倍。
注意
修改发生在调用方对象上,返回值只是同一实例的引用。
- 参数
other (Matrix) – 加数矩阵,行列数必须与本矩阵相同
- 返回
this,便于链式调用
- 抛出
IllegalArgumentException – 形状不同
NullPointerException – other 为 null
- scaleInPlace(double scalar) → Matrix¶
就地按标量缩放全部元素
对底层数组逐元素乘以 scalar。scalar 为 0 时得到全零矩阵,为负数时整体变号,为 NaN 时全部元素变为 NaN,矩阵层不拦截这些取值。
- 参数
scalar (double) – 乘数,不做取值范围限制
- 返回
this,便于链式调用
- matMul(Matrix other) → Matrix¶
与另一矩阵相乘,委托公共池上的 MatMul
实现只有一行:MatMul.commonPool().multiply(this, other)。因此每次调用都会新建一个不拥有线程池的 MatMul 包装对象,大工作量时走原生内核,否则走分块并行的 Java 路径。
注意
不复用引擎实例,需要固定线程预算时应直接使用 MatMul.create(int)。
- 参数
other (Matrix) – 右操作数,其行数必须等于本矩阵的列数
- 返回
形状为 rows() × other.columns() 的新矩阵
- 抛出
IllegalArgumentException – 内维不匹配
NullPointerException – other 为 null
- transpose() → Matrix¶
返回转置副本
按 32×32 分块遍历源矩阵,把 (r, c) 写到目标的 (c, r),使写操作也保持缓存局部性。源码注释点明这是转置复制的目的;源矩阵不被修改。
注意
分块边长固定为 32,与矩阵规模无关。
- 返回
形状为 columns() × rows() 的新矩阵
- copyRow(int row, double[] target, int offset)¶
把一行复制到目标数组的指定偏移
单次 System.arraycopy,从 row * columns 开始复制 columns 个元素。源码只校验行下标,目标容量由 arraycopy 自身检查。
注意
用于把矩阵行批量拼进外部缓冲区,例如组装样本批次。
- 参数
row (int) – 行下标,范围 [0, rows())
target (double[]) – 目标数组,容量至少为 offset + columns()
offset (int) – 写入起点
- 抛出
IndexOutOfBoundsException – row 越界
ArrayIndexOutOfBoundsException – target 容量不足
- toString() → String¶
返回带形状前缀的文本表示
前缀给出行列数,随后每行以方括号包裹、逗号分隔。只打印前 8 行,超出时以单独一行 ... 结束;行分隔符取自 System.lineSeparator(),输出随平台变化。
注意
列数不截断,宽矩阵会输出很长的单行。
- 返回
形如 Matrix[2x3] 后接最多 8 行的字符串
- final class ModelFormatException extends RuntimeException¶
.bsl 流截断、损坏或版本不兼容时抛出的非受检异常
ModelFormatException 继承 RuntimeException,方法签名里不出现受检声明,因此解码路径可以把格式错误从深层字段读取处直接向上抛出,无需逐层包装。捕获方只拦这一个类型即可覆盖全部 .bsl 解析失败。
触发点集中在两处:ModelReader 的长度与剩余字节校验,以及 ModelKind.fromCode 与 ModelCodec 的魔数、版本、长度和 CRC32C 校验。类本身不携带额外字段,异常消息是唯一的诊断信息。
注意
非受检异常:编译器不强制捕获,也不会被 try-with-resources 自动包装。
注意
保存路径同样复用它表示超过容器上限,因此该类型在读、写两侧都会出现。
参见:basalt.core.ModelReader, basalt.core.ModelKind, basalt.io.ModelCodec
构造器
- ModelFormatException(String message)¶
以描述性消息构造异常
消息不含错误码或字节偏移字段,getMessage() 返回同一字符串。源码中的抛出点都使用这个重载。
- 参数
message (String) – 异常消息,说明失败位置或原因,原样交给父类
- ModelFormatException(String message, Throwable cause)¶
以消息与根因构造异常
为需要保留底层异常的调用方准备,例如把解码过程中的数值异常包装成格式错误。当前源码内的抛出点未使用该重载。
- 参数
message (String) – 异常消息,原样交给父类
cause (Throwable) – 根因,可通过 getCause() 取回
- enum ModelKind¶
可持久化模型族的稳定线格式标识码
每个常量的 code 就是 .bsl 容器头部第三个 int 字段的取值:ModelCodec.save 写入它,load 读出后再经 fromCode 还原成常量。码值从 1 开始连续分配到 32,与声明顺序一致,中间没有空洞。
常量对应实现 Persistable 的模型族:经典估计器与预处理器在 basalt.ml,序列模型在 basalt.nn。3.4 起共 32 个族,按用途分为网络与集成模型(1 至 11)、预处理与流水线(12 至 17)、核方法与无监督(18 至 23)、特征工程(24 至 26)、线性与树模型(27 至 32)。
ModelCodec.readPayload 用 switch 把常量分派到各族的静态 read 方法,新增模型族必须同时扩展枚举与分派表。
注意
码值是线格式的一部分:已发布的取值不可修改或复用,否则旧文件会被解成错误的模型族。
注意
fromCode 对未定义码抛 ModelFormatException,不返回 null。
注意
枚举顺序被 values() 与 fromCode 的线性扫描依赖,重排常量不影响码值,但会影响扫描顺序。
注意
Pipeline 的 payload 内嵌子步骤的码值,因此新增预处理类型后旧读取方无法解开含该步骤的流水线文件。
参见:basalt.core.Persistable, basalt.io.ModelCodec
枚举成员
- CNN()¶
卷积网络族,码值 1
对应 basalt.nn.CNN,解码时由 ModelCodec 分派到 ModelSerializer.readCNN 重建层结构与参数。
- HISTOGRAM_BOOSTING()¶
直方图梯度提升族,码值 2
对应 basalt.ml.HistogramGradientBoosting,分派到该类的静态 read 方法。
- RANDOM_FOREST()¶
随机森林族,码值 3
对应 basalt.ml.RandomForest,分派到 readState 恢复树集合与类别表。
- KMEANS()¶
K-Means 聚类族,码值 4
对应 basalt.ml.KMeans,分派到该类的静态 read 方法恢复中心与超参数。
- PCA()¶
主成分分析族,码值 5
对应 basalt.ml.PCA,分派到该类的静态 read 方法恢复投影矩阵与均值。
- GAUSSIAN_NAIVE_BAYES()¶
高斯朴素贝叶斯族,码值 6
对应 basalt.ml.GaussianNaiveBayes,分派到该类的静态 read 方法恢复均值、方差与对数先验。
- LSTM()¶
长短期记忆网络族,码值 7
对应 basalt.nn.LSTM,分派到该类的静态 read 方法重建各门权重。
- GRU()¶
门控循环单元族,码值 8
对应 basalt.nn.GRU,分派到该类的静态 read 方法重建更新门与重置门权重。
- VANILLA_RNN()¶
基础循环网络族,码值 9
对应 basalt.nn.VanillaRNN,分派到该类的静态 read 方法重建循环权重。
- SELF_ATTENTION()¶
自注意力层族,码值 10
对应 basalt.nn.SelfAttention,分派到该类的静态 read 方法重建查询、键、值投影。
- SEQUENCE_NETWORK()¶
序列网络容器族,码值 11
对应 basalt.nn.SequenceNetwork,分派到该类的静态 read 方法按顺序重建子层列表。
- STANDARD_SCALER()¶
对应 StandardScaler,存取标准化均值与尺度
线格式码值 12,由枚举构造器写入。
ModelCodec.readPayload 分派到 StandardScaler.read,恢复 withMean/withStd 开关、特征数、样本数以及每列的均值与尺度。
- MIN_MAX_SCALER()¶
对应 MinMaxScaler,存取缩放区间与每列极值
线格式码值 13。
分派到 MinMaxScaler.read,恢复 featureMin/featureMax、clip 开关,以及每列的 dataMin、dataMax、dataRange、scale、minOffset。
- ONE_HOT_ENCODER()¶
对应 OneHotEncoder,存取类别表与未知值策略
线格式码值 14。
分派到 OneHotEncoder.read,恢复 HandleUnknown 序数、输入与输出特征数、每列是否为类别列及其类别表、输出偏移。
- LABEL_ENCODER()¶
对应 LabelEncoder,存取标签类别表
线格式码值 15。
分派到 LabelEncoder.read,恢复数值类别表与字符串类别表;类别数必须为正,否则抛 ModelFormatException。
- SIMPLE_IMPUTER()¶
对应 SimpleImputer,存取填充策略与每列统计量
线格式码值 16。
分派到 SimpleImputer.read,恢复 Strategy 序数、常数填充值与每列统计量 statistics,填充结果据此复现。
- PIPELINE()¶
对应 Pipeline,把整条流水线封装进单个文件
线格式码值 17。
分派到 Pipeline.read:逐步读入步骤名、嵌套的 ModelKind 码值与长度前缀 payload,再递归调用 ModelCodec.readPayload 重建每步;末尾可选的最终估计器同样带自身码值。
- SVC()¶
对应 SVC,存取支持向量与对偶系数
线格式码值 18。
分派到 SVC.read,先恢复 Kernel 描述,再读 C、tol、maxIter、seed,最后恢复类别表、支持向量矩阵、对偶系数与各类截距。
- KERNEL_RIDGE()¶
对应 KernelRidge,存取核参数与对偶系数
线格式码值 19。
分派到 KernelRidge.read,恢复 Kernel、alpha、训练矩阵形状与数据,以及对偶系数 dualCoef;预测只需训练矩阵与系数,无需重解线性方程组。
- KERNEL_PCA()¶
对应 KernelPCA,存取特征向量与核矩阵统计
线格式码值 20。
分派到 KernelPCA.read,恢复 Kernel、components/features/rows、全核和、训练矩阵、按行的核和、特征值与特征向量。
- GAUSSIAN_MIXTURE()¶
对应 GaussianMixture,存取分量权重与均值方差
线格式码值 21。
分派到 GaussianMixture.read,恢复分量数、特征数、是否收敛、迭代数、下界,以及 weight、means、variances 三组数组。
- ISOLATION_FOREST()¶
对应 IsolationForest,存取隔离树与异常阈值
线格式码值 22。
分派到 IsolationForest.read,恢复树数、子采样上限、contamination、seed、特征数、有效子采样数与判定阈值,再逐棵读入 TreeModel。
- KERNEL_DENSITY()¶
对应 KernelDensity,存取带宽与训练样本
线格式码值 23。
分派到 KernelDensity.read,恢复 bandwidth、KernelType 序数与训练矩阵,评分时按保存的样本重新计算核密度。
- SELECT_K_BEST()¶
对应 SelectKBest,存取得分与保留的特征索引
线格式码值 24。
分派到 SelectKBest.read,恢复 k、ScoreFunc 序数、输入特征数、各特征得分与 selectedIndices;k 必须为正且不超过输入特征数。
- POLYNOMIAL_FEATURES()¶
对应 PolynomialFeatures,存取展开阶数与项组合
线格式码值 25。
分派到 PolynomialFeatures.read,只恢复 degree、interactionOnly、includeBias 与输入特征数;项组合由 buildCombinations 按同样参数重建,不占 payload。
- KBINS_DISCRETIZER()¶
对应 KBinsDiscretizer,存取分箱边界与编码方式
线格式码值 26。
分派到 KBinsDiscretizer.read,恢复 nBins、Strategy 与 Encode 序数、输入特征数,以及每个特征的 binEdges。
- LINEAR_REGRESSION()¶
对应 LinearRegression,存取权重与截距
线格式码值 27。
分派到 LinearRegression.read,恢复 learningRate、epochs、l2、fitIntercept、特征数、截距与权重。
- LOGISTIC_REGRESSION()¶
对应 LogisticRegression,存取二分类权重与截距
线格式码值 28。
分派到 LogisticRegression.read,恢复 learningRate、epochs、l2、特征数、截距与权重;标签仍按 0/1 解释。
- RIDGE()¶
对应 Ridge,存取正则强度与闭式解
线格式码值 29。
分派到 Ridge.read,恢复 alpha、fitIntercept、特征数、截距与权重,加载后无需重做 Cholesky 分解。
- LASSO()¶
对应 Lasso,存取坐标下降得到的稀疏权重
线格式码值 30。
分派到 Lasso.read,恢复 alpha、maxIterations、tolerance、特征数、截距与权重。
- LINEAR_SVM()¶
对应 LinearSVM,存取合页损失下的权重与偏置
线格式码值 31。
分派到 LinearSVM.read,恢复 lambda、epochs、seed、特征数、偏置、类别表与权重。
- DECISION_TREE()¶
对应 DecisionTree,存取 CART 树拓扑与类别表
线格式码值 32。
分派到 DecisionTree.read,直接转调 readState:恢复特征数、TreeTask 序数、类别表,再读入扁平的 TreeModel 节点数组;分类树缺少类别表时抛 ModelFormatException。
方法
- code() → int¶
返回写入头部的稳定码值
code 字段由枚举构造器写入,运行期不可变。ModelCodec.save 直接把它写进头部的 kind 字段,不做任何映射。
- 返回
1 至 32 之间的常量码值,与声明顺序一致
- fromCode(int code) → ModelKindstatic¶
按码值反查枚举常量
实现按 values() 顺序线性扫描,命中即返回。未命中说明文件头损坏或来自更新版本的写入方,异常消息中的码值可直接用于定位。
- 参数
code (int) – 从 .bsl 头部读出的整型码值;只有 1 至 32 有定义
- 返回
码值匹配的枚举常量
- 抛出
ModelFormatException – code 不等于任何常量的码值,消息携带原始码值
- final class ModelReader¶
与 ModelWriter 配对的大端、带越界校验的读取器
读取位置由内部 ByteBuffer 维护,每次读取前先比较剩余字节,不足即抛 ModelFormatException,因此截断的负载不会读到半个字段,也不会产生越界访问。字段顺序完全由各模型族的 read 方法决定,读取器本身不记录任何模式。
byte[] 构造器包装整个数组,位置从 0 起;ByteBuffer 构造器在 position() 到 limit() 之间做切片,不复制也不移动源缓冲区。切片固定按大端解释,即使源缓冲区被设成小端也不改变读取结果。
注意
所有方法按大端解释字节,与 ModelWriter 的写入顺序一致。
注意
requireFullyConsumed 只在解码完成后调用一次,用于拒绝尾部多余字节。
注意
读取器不校验魔数、版本与 CRC32C,这些由 ModelCodec 在构造它之前完成。
参见:basalt.core.ModelWriter, basalt.io.ModelCodec, basalt.core.ModelFormatException
构造器
- ModelReader(byte[] payload)¶
包装整个字节数组
内部用 ByteBuffer.wrap 直接引用该数组,不做复制,因此构造后对数组内容的修改会影响后续读取结果。
- 参数
payload (byte[]) – 完整负载;按大端解释,读取位置从下标 0 开始
- 抛出
NullPointerException – payload 为 null
- ModelReader(ByteBuffer payload)¶
在缓冲区剩余区间上建立零拷贝视图
构造时调用 slice(),源缓冲区的 position 与 limit 保持不变,因此同一缓冲区可以派生多个读取器,各自推进位置。切片固定为大端字节序。
注意
ModelCodec.load 把内存映射缓冲区定位到头部之后传入,避免复制整个负载。
- 参数
payload (ByteBuffer) – 源缓冲区;读取范围是 position() 到 limit()
- 抛出
NullPointerException – payload 为 null
方法
- getByte() → int¶
读取一个字节并零扩展
返回值经 Byte.toUnsignedInt 转换,不会出现负数,可直接当作长度、标志位或枚举下标使用。
- 返回
0 至 255 的无符号字节值
- 抛出
ModelFormatException – 剩余字节不足 1
- getInt() → int¶
读取 4 字节大端整数
头部字段、类别码、数组长度等整型状态都经由该方法读入。
- 返回
按大端解释的 int 值
- 抛出
ModelFormatException – 剩余字节不足 4
- getLong() → long¶
读取 8 字节大端长整数
用于时间戳、随机种子与 CRC32C 一类 64 位字段。
- 返回
按大端解释的 long 值
- 抛出
ModelFormatException – 剩余字节不足 8
- getDouble() → double¶
读取 8 字节大端双精度浮点数
标量超参数与截距等单值字段使用该方法;成组参数走 getDoubles 的批量路径。
- 返回
按 IEEE-754 大端解释的 double 值
- 抛出
ModelFormatException – 剩余字节不足 8
- getDoubles(int length) → double[]¶
批量读取指定个数的 double
实现先用 Math.multiplyExact 算出字节数并整体校验,再通过 asDoubleBuffer 视图一次性拷贝,省去逐元素的边界检查。
注意
length 极大导致字节数溢出 int 时,Math.multiplyExact 抛 ArithmeticException,而不是 ModelFormatException。
- 参数
length (int) – 元素个数;必须非负,所需字节数不得超过剩余长度
- 返回
新分配的 double[length]
- 抛出
ModelFormatException – length 为负,或剩余字节不足 length 乘以 8
- getInts(int length) → int[]¶
批量读取指定个数的 int
与 getDoubles 不同,这里逐元素调用 getInt,因此每个元素都单独经过一次剩余字节校验。
- 参数
length (int) – 元素个数;必须非负,所需字节数不得超过剩余长度
- 返回
新分配的 int[length]
- 抛出
ModelFormatException – length 为负,或剩余字节不足 length 乘以 4
- remaining() → int¶
返回尚未读取的字节数
用于在解码前后做一致性判断,例如比较声明长度与实际消费量。
- 返回
limit 与当前读取位置之差,单位字节
- requireFullyConsumed()¶
断言负载已全部消费
用于发现声明长度与实际字段不符的负载:多写的字段会被判为格式错误,而不是被静默忽略。读取恰好结束时该方法不产生任何副作用。
- 抛出
ModelFormatException – 仍存在未读取的尾部字节,消息含剩余字节数
- final class ModelWriter¶
可增长的大端二进制写入器
写入位置单调前进,容量不足时按「当前容量的两倍与所需容量取较大者」重新分配并整体复制,因此追加写入的摊销成本与写入量成线性关系。double 数组经 DoubleBuffer 视图成块拷贝,避免逐元素的边界检查。
整型与浮点字段一律按大端写出,字节写入只保留低 8 位。当所需容量超过 Integer.MAX_VALUE 时抛 ModelFormatException,而不是让分配失败演变成 OutOfMemoryError。
注意
非线程安全:多线程同时写入会互相覆盖位置,需要外部同步。
注意
toByteArray 返回副本,写入器之后继续追加不影响已取出的数组。
注意
没有 close 或 reset,复用同一实例会继续追加而不是从头覆盖。
参见:basalt.core.ModelReader, basalt.io.ModelCodec
构造器
- ModelWriter()¶
以默认 256 字节容量创建
默认容量来自常量 DEFAULT_CAPACITY,适合小模型。ModelCodec.save 改用 4096 起步以减少扩容次数。
- ModelWriter(int initialCapacity)¶
以指定初始容量创建
容量只是预分配提示,超出后自动翻倍扩容,不影响可写入的数据量。
- 参数
initialCapacity (int) – 初始字节容量,必须为正
- 抛出
IllegalArgumentException – initialCapacity 小于等于 0
方法
- putByte(int value) → ModelWriter¶
写入一个字节
参数类型是 int 而非 byte,调用方可以直接传 0 至 255 的码值或枚举序号而无需强转。
- 参数
value (int) – 写入值,仅低 8 位被保留,高位截断
- 返回
this,支持链式调用
- putInt(int value) → ModelWriter¶
写入 4 字节大端整数
常用于写数组长度:先写 int 再写元素,读取侧才能知道要取多少个。
- 参数
value (int) – 待写入的 int 值
- 返回
this,支持链式调用
- putLong(long value) → ModelWriter¶
写入 8 字节大端长整数
用于种子、时间戳等 64 位字段,与 ModelReader.getLong 对称。
- 参数
value (long) – 待写入的 long 值
- 返回
this,支持链式调用
- putDouble(double value) → ModelWriter¶
写入 8 字节大端双精度浮点数
标量超参数与截距使用该方法,数值按 IEEE-754 位模式原样写出,不做格式化。
- 参数
value (double) – 待写入的 double 值
- 返回
this,支持链式调用
- putDoubles(double[] values) → ModelWriter¶
整体写入一个 double 数组
等价于 putDoubles(values, 0, values.length)。长度需要调用方自行先用 putInt 记录,读取时由 getDoubles 显式给出。
- 参数
values (double[]) – 待写入的数组,按顺序全部写出,不写长度前缀
- 返回
this,支持链式调用
- 抛出
NullPointerException – values 为 null
- putDoubles(double[] values, int offset, int length) → ModelWriter¶
写入 double 数组的一段切片
只写出 [offset, offset + length) 区间,经 DoubleBuffer 视图成块拷贝。适合从更大的缓冲区里摘出一段参数写出。
- 参数
values (double[]) – 源数组
offset (int) – 起始下标,必须非负
length (int) – 写入元素个数,必须非负
- 返回
this,支持链式调用
- 抛出
IndexOutOfBoundsException – offset 或 length 为负,或 offset 加 length 超过数组长度
- putInts(int[] values) → ModelWriter¶
整体写入一个 int 数组
没有切片重载,逐元素写入 int;与 putDoubles 的批量拷贝路径不同,但结果字节序列相同。
- 参数
values (int[]) – 待写入的数组,逐个按大端写出
- 返回
this,支持链式调用
- 抛出
NullPointerException – values 为 null
- putBytes(byte[] values) → ModelWriter¶
原样写入一个字节数组
用于写分箱矩阵一类已经是字节形式的负载,写入长度等于数组长度。
- 参数
values (byte[]) – 待写入的字节,按原顺序写出,不做转换
- 返回
this,支持链式调用
- 抛出
NullPointerException – values 为 null
- size() → int¶
返回已写入的字节数
与容量无关:扩容只改变缓冲区大小,不改变该值。
- 返回
当前写入位置,等于将要导出的数组长度
- toByteArray() → byte[]¶
导出已写入区间
内部用 Arrays.copyOf 截取到写入位置,因此不会带出扩容留下的空闲容量。
- 返回
长度为 size() 的新数组,内容为已写入字节的副本
- final class NativeKernels¶
通过 JNI 暴露的可选 Rust 内核集合
类初始化时按三级顺序寻找动态库:先看 -Djsl.native.path 指定的文件,再从 classpath 的 /native/<platform>/ 资源解压到临时目录,最后回退到 java.library.path 上的 basalt_native。三步全部失败时 available() 为 false,调用方改走纯 Java 实现。
公开方法分两类:*Checked 系列把原生状态码翻译成布尔值,0 表示成功、非 0 表示参数或形状被拒绝,调用方据此回退;gemmChecked 是唯一把失败翻译成异常的方法。所有数组参数都是行主序的扁平 double[]、int[]、byte[],不涉及张量句柄,也不进入计算图。
createHistogramSession 返回不透明 long 句柄,把分箱矩阵、梯度与二阶梯度常驻原生内存,之后每个树节点只传样本下标与活跃特征表。句柄需要显式销毁,否则原生内存保留到进程结束。
示例import basalt.core.NativeKernels; public final class NativeProbe { public static void main(String[] args) { if (!NativeKernels.available()) { System.out.println("unavailable: " + NativeKernels.loadError()); System.out.println("expected at " + NativeKernels.libraryPath()); return; } double[] data = {0.0, 0.0, 1.0, 1.0, 10.0, 10.0, 11.0, 11.0}; double[] centers = {0.0, 0.0, 10.0, 10.0}; int[] assignments = new int[4]; double[] distances = new double[4]; boolean ok = NativeKernels.kmeansAssignChecked( data, centers, 4, 2, 2, 1, assignments, distances); if (!ok) { throw new IllegalStateException("native kernel rejected the shapes"); } for (int row = 0; row < assignments.length; row++) { System.out.println(row + " -> cluster " + assignments[row] + " (squared distance " + distances[row] + ")"); } } }注意
原生库不可用时,除 version() 之外的 public 方法都会因缺少本地实现抛 UnsatisfiedLinkError;调用前必须先判断 available()。
注意
available()、loadError()、libraryPath() 在类初始化时一次确定,运行期不会因重新放置库文件而改变。
注意
直方图会话句柄必须在训练结束时调用 destroyHistogramSession;会话内部以 f32 桶累加,与一次性路径的 f64 累加存在末位差异。
注意
原生侧按线程数缓存 rayon 线程池,threads 参数只影响本次调用的并行度,不改变池的生命周期。
参见:basalt.nativebridge.NativeLibrary, basalt.core.MatMul, basalt.ml.HistogramGradientBoosting
方法
- available() → booleanstatic¶
报告原生库是否加载成功
结果由静态初始化块写入 final 字段,三级加载尝试全部失败才为 false。判断本身只是一次静态字段读取,可在热路径上反复调用。
- 返回
true 表示 JNI 符号可用;false 表示调用方需走纯 Java 路径
- loadError() → Stringstatic¶
返回加载失败的原因描述
静态初始化在每次加载尝试失败时记录异常的简单类名与消息,后一次失败会覆盖前一次。目标文件不存在但未抛异常时不会写入该字段。
注意
库最终加载成功但此前有过失败尝试时,该字段仍保留早期失败文本,需结合 available() 判断实际状态。
- 返回
失败异常的类名与消息拼接;从未发生异常时为空串
- libraryPath() → Stringstatic¶
返回解析到的库文件绝对路径
取值来自三级尝试中最后一次确定的目标:属性指定的路径、classpath 解压出的临时文件,或 java.library.path 与文件名拼接的结果,返回前统一转成绝对路径。
注意
不检查文件是否存在,只回报「曾经打算加载哪个文件」,适合拼进错误信息辅助排查。
- 返回
尝试加载的绝对路径;加载失败时同样返回候选路径
- version() → intstatic¶
返回原生引擎 ABI 版本号
该方法声明为 native,直接调用 JNI 导出符号,Java 侧没有可用性判断,也没有回退分支。
注意
库不可用时调用会抛 UnsatisfiedLinkError;需要安全取值时改用 NativeLibrary.version(),它在不可用时返回 0。
- 返回
当前原生实现固定返回 1
- gemmChecked(double[] a, double[] b, double[] c, int m, int k, int n, int threads)static¶
调用原生 GEMM 计算 C 等于 A 乘 B
原生实现以 beta 为 0 覆盖 c,再按行切分给 rayon 线程池;m 小于 32 或可用线程不足时退化为单线程 dgemm。BasaltRuntimeException 继承 IllegalStateException,因此按 JDK 异常捕获的调用方仍能回退到 Java 路径。
注意
未做可用性检查,需先判断 available(),否则抛 UnsatisfiedLinkError。
注意
c 的初值不参与计算,无需预先清零。
注意
形状不匹配时抛异常而不是返回 false,与其余 *Checked 方法不同。
- 参数
a (double[]) – 左矩阵,行主序 m 行 k 列,长度必须为 m*k
b (double[]) – 右矩阵,行主序 k 行 n 列,长度必须为 k*n
c (double[]) – 输出矩阵,行主序 m 行 n 列,长度必须为 m*n,原地覆盖
m (int) – A 的行数,同时是 C 的行数;0 视为非法
k (int) – 共享维,等于 A 的列数与 B 的行数;0 视为非法
n (int) – B 的列数,同时是 C 的列数;0 视为非法
threads (int) – 并行度提示;小于等于 0 时按 1 处理
- 返回
无返回值,结果写入 c
- 抛出
BasaltRuntimeException – 原生返回非 0 状态码,消息携带该码值
- kmeansAssignChecked(double[] data, double[] centers, int rows, int clusters, int features, int threads, int[] assignments, double[] distances) → booleanstatic¶
为每个样本挑选最近的聚类中心
距离按平方欧氏距离比较且不开方,因此 distances 的量纲是原特征单位的平方。比较使用严格小于,并列时保留下标较小的簇。
注意
特征维数足够大时走 AVX2/FMA 距离核,否则用标量乘加路径;两条路径的求和顺序不同,末位可能有差异。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 features 列,长度必须为 rows*features
centers (double[]) – 中心矩阵,行主序 clusters 行 features 列,长度必须为 clusters*features
rows (int) – 样本数;0 视为非法
clusters (int) – 簇数;0 视为非法
features (int) – 特征维数;0 视为非法
threads (int) – 并行度提示;小于等于 0 时按 1 处理
assignments (int[]) – 输出,长度必须为 rows,原地写入所属簇下标
distances (double[]) – 输出,长度必须为 rows,原地写入到该中心的平方欧氏距离
- 返回
true 表示形状校验通过并完成赋值;false 表示原生拒绝输入,两个输出数组内容未定义
- forestPredictChecked(double[] data, int rows, int columns, int[] feature, double[] threshold, int[] left, int[] right, double[] value, int[] offsets, double[] output, double scale) → booleanstatic¶
在原生侧对整片森林做回归累加
feature、threshold、left、right、value 五个数组按节点下标对齐,长度必须相同;offsets[t] 给出第 t 棵树的根节点位置,末项等于节点总数。遍历到 feature 为负的节点时把 scale 乘 value 累加进输出。
注意
输出是累加语义,调用方需预置基线分数(如提升模型的 baseScore)或先清零。
注意
判定条件为特征值小于等于阈值时走左子树,与 Java 实现的分支方向一致。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 columns 列,长度必须为 rows*columns
rows (int) – 样本数;0 视为非法
columns (int) – 特征数;0 视为非法
feature (int[]) – 拼接后的节点分裂特征下标,负值标记叶子节点
threshold (double[]) – 分裂阈值,特征值小于等于阈值走左子树
left (int[]) – 左子节点下标
right (int[]) – 右子节点下标
value (double[]) – 叶子输出值,内部节点位置的值不被读取
offsets (int[]) – 树起点表,长度等于树数加 1,末项为节点总数,至少 2 项
output (double[]) – 输出,长度必须为 rows,结果累加到现有值之上
scale (double) – 叶子值的缩放系数,通常为学习率或树数的倒数
- 返回
true 表示完成预测;false 表示形状被原生拒绝
- forestVoteChecked(double[] data, int rows, int columns, int[] feature, double[] threshold, int[] left, int[] right, double[] value, int[] offsets, double[] labels, double[] votes, double scale) → booleanstatic¶
在原生侧统计森林的类别投票
遍历顺序为树在外、样本在内,写 votes 的顺序与 Java 路径不同,但最终累加结果相同。叶子值在 labels 中找不到时该次投票被跳过,不抛异常。
注意
votes 必须由调用方预先清零,原生只做累加。
注意
形状校验要求 votes 长度等于 rows 乘 labels 长度。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 columns 列,长度必须为 rows*columns
rows (int) – 样本数;0 视为非法
columns (int) – 特征数;0 视为非法
feature (int[]) – 拼接后的节点分裂特征下标,负值标记叶子节点
threshold (double[]) – 分裂阈值,特征值小于等于阈值走左子树
left (int[]) – 左子节点下标
right (int[]) – 右子节点下标
value (double[]) – 叶子输出值,按相等匹配到类别
offsets (int[]) – 树起点表,长度等于树数加 1,末项为节点总数,至少 2 项
labels (double[]) – 类别取值表,长度即类别数,不能为 0
votes (double[]) – 输出,长度必须为 rows 乘类别数,每个命中的叶子累加 scale
scale (double) – 每票权重,通常为树数的倒数
- 返回
true 表示完成投票;false 表示形状被原生拒绝
- histogramAccumulateChecked(double[] gradients, double[] hessians, byte[] bins, int[] samples, int[] activeFeatures, int featureCount, int maxBins, int threads, double[] outGradient, double[] outHessian, int[] outCount) → booleanstatic¶
按样本下标累加梯度直方图
三个输出数组的下标布局是 feature*maxBins 加 bin,未参与的特征区间保持原值。原生侧只做累加、不清零,调用方需按活跃特征区间预先填充 0。
注意
bins 的长度必须能被 featureCount 整除,原生据此推断样本行数。
注意
样本数达到 1024 且并行度大于 1 时按块并行,每个线程各持一份临时直方图后合并。
注意
输出以 f64 累加,与直方图会话路径的 f32 桶不同。
- 参数
gradients (double[]) – 每个样本的一阶梯度,按样本下标索引
hessians (double[]) – 每个样本的二阶梯度,长度与 gradients 相同
bins (byte[]) – 分箱矩阵,行主序,行宽为 featureCount,元素为分箱下标
samples (int[]) – 参与累加的样本下标;为空时不修改输出
activeFeatures (int[]) – 参与累加的特征下标,不能为空且每个值须在 [0, featureCount)
featureCount (int) – 分箱矩阵的列数,同时作为行宽
maxBins (int) – 每个特征的桶数;0 视为非法
threads (int) – 并行度提示;小于等于 0 时按 1 处理
outGradient (double[]) – 输出,长度必须为 featureCount*maxBins,按 feature*maxBins 加 bin 索引
outHessian (double[]) – 输出,长度同 outGradient
outCount (int[]) – 输出计数,长度同 outGradient
- 返回
true 表示累加完成;false 表示形状或下标被拒绝
- ridgeFitChecked(double[] data, int rows, int columns, double[] targets, double alpha, boolean fitIntercept, int threads, double[] weights, double[] intercept) → booleanstatic¶
在原生侧求解岭回归
实现先累加列和与目标和得到中心,再并行累加正规方程的左半三角与右端项,把 alpha 加到每个对角元上,最后用 Cholesky 分解求解。fitIntercept 为 false 时中心量取 0,intercept 输出 0。
注意
Cholesky 对角元下限为 1e-12,因此共线特征不会导致除零,但系数解不唯一。
注意
alpha 为 NaN、无穷或负数时返回 false,不抛异常。
注意
原生与 Java 路径的求和顺序不同,系数可能在小数末位上有差异。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 columns 列,长度必须为 rows*columns
rows (int) – 样本数,必须至少为 1
columns (int) – 特征数,必须至少为 1
targets (double[]) – 目标值,长度必须为 rows
alpha (double) – L2 惩罚系数,必须有限且非负
fitIntercept (boolean) – true 时先按列中心化数据与目标再求解
threads (int) – 并行度提示,原生侧上限为 64
weights (double[]) – 输出,长度必须为 columns,原地覆盖
intercept (double[]) – 输出,长度必须为 1,原地覆盖
- 返回
true 表示求解完成;false 表示形状或 alpha 被拒绝
- gaussianNbFitChecked(double[] data, int rows, int columns, int[] classIndices, int classCount, double[] means, double[] variances, double[] logPriors, int[] counts, double varianceSmoothing) → booleanstatic¶
计算高斯朴素贝叶斯的充分统计量
均值与计数按类累加后除以类内样本数,方差为类内平方偏差的平均,先验取样本占比的自然对数。平滑量为系数乘以全局最大方差,若该乘积不为正数则退化为系数本身,再统一加到所有方差上。
注意
任何一类样本数为 0 时整体返回 false,调用方需回退到 Java 路径。
注意
输出数组在原生侧先被清零,调用方无需预置。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 columns 列,长度必须为 rows*columns
rows (int) – 样本数,必须至少为 1
columns (int) – 特征数,必须至少为 1
classIndices (int[]) – 每个样本的类别下标,长度必须为 rows,取值须在 [0, classCount)
classCount (int) – 类别数,必须至少为 1
means (double[]) – 输出,长度必须为 classCount*columns,按类优先排布
variances (double[]) – 输出,长度同 means
logPriors (double[]) – 输出,长度必须为 classCount,写入对数先验
counts (int[]) – 输出,长度必须为 classCount,写入每类样本数
varianceSmoothing (double) – 方差平滑系数,用于计算 epsilon
- 返回
true 表示统计完成;false 表示形状越界或存在空类别
- kmeansFitChecked(double[] data, int rows, int clusters, int features, int restarts, int maxIterations, double tolerance, long seed, int threads, double[] centers, double[] inertia, int[] iterations) → booleanstatic¶
在原生侧完成 K-Means++ 与 Lloyd 迭代
每次重启用 seed 与固定常量混合派生子种子,保证同一 seed 可复现。每轮先分配样本、累加各类坐标和、更新中心,空簇用距其中心最远的样本修复,再按中心位移平方和判断是否收敛。
注意
inertia 是平方欧氏距离之和,不是均方值。
注意
restarts 或 maxIterations 为 0、tolerance 非法、clusters 大于 rows 时返回 false。
注意
threads 改变分块方式,也改变浮点求和顺序,不同并行度下结果可能在末位不同。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 features 列,长度必须为 rows*features
rows (int) – 样本数,必须至少为 1
clusters (int) – 簇数,必须至少为 1 且不超过 rows
features (int) – 特征维数,必须至少为 1
restarts (int) – 重启次数,必须至少为 1
maxIterations (int) – 单次重启的最大迭代数,必须至少为 1
tolerance (double) – 收敛阈值,必须有限且非负;判据为中心位移平方和不超过阈值的平方
seed (long) – K-Means++ 抽样的种子
threads (int) – 并行度提示,原生侧上限为 64
centers (double[]) – 输出,长度必须为 clusters*features,写入最优重启的中心
inertia (double[]) – 输出,长度必须为 1,写入最优簇内平方和
iterations (int[]) – 输出,长度必须为 1,写入最优重启实际执行的迭代数
- 返回
true 表示完成;false 表示参数或数组长度被拒绝
- createHistogramSession(byte[] bins, double[] gradients, double[] hessians, int featureCount, int maxBins) → longstatic¶
创建持有分箱矩阵的原生会话
创建时把三个数组整体复制进原生堆,Java 侧的数组之后可以释放。句柄在原生注册表里登记,后续调用按句柄取回会话并加锁访问。
注意
返回 0 表示回退:调用方应改用无会话的 histogramAccumulateChecked 或纯 Java 路径。
注意
创建时只校验梯度与二阶梯度长度相等,不校验是否等于样本数。
注意
句柄必须用 destroyHistogramSession 释放。
- 参数
bins (byte[]) – 分箱矩阵,行主序,行宽为 featureCount,长度须能被 featureCount 整除
gradients (double[]) – 每个样本的一阶梯度
hessians (double[]) – 每个样本的二阶梯度,长度须与 gradients 相同
featureCount (int) – 特征数,必须至少为 1
maxBins (int) – 每个特征的桶数,必须至少为 1
- 返回
非 0 的不透明句柄;输入被拒绝时返回 0
- updateHistogramSession(long session, double[] gradients, double[] hessians) → booleanstatic¶
用新梯度替换会话中的缓存
每个提升轮次更新一次,使后续直方图累加读到当前梯度,而不必重复传输分箱矩阵。复制是整体覆盖,不做增量合并。
注意
句柄为 0 或已销毁时返回 false,不抛异常。
- 参数
session (long) – createHistogramSession 返回的句柄
gradients (double[]) – 新的一阶梯度,长度必须与创建时一致
hessians (double[]) – 新的二阶梯度,长度必须与创建时一致
- 返回
true 表示替换完成;false 表示句柄无效或长度不符
- histogramSessionAccumulateChecked(long session, int[] samples, int[] activeFeatures, int threads, double[] outGradient, double[] outHessian, int[] outCount) → booleanstatic¶
在会话上累加直方图
分箱矩阵与梯度取自会话,参数表里不再出现;三个输出数组的布局与一次性版本相同,同样只累加不清零。
注意
会话路径把梯度、二阶梯度与计数放进同一个 24 字节桶累加,桶内为 f32,合并后转回 double,精度低于一次性路径。
注意
输出必须由调用方按活跃特征区间预先清零。
- 参数
session (long) – 会话句柄
samples (int[]) – 参与累加的样本下标
activeFeatures (int[]) – 参与累加的特征下标,不能为空且须在 [0, featureCount)
threads (int) – 并行度提示;小于等于 0 时按 1 处理
outGradient (double[]) – 输出,长度必须为 featureCount*maxBins,按 feature*maxBins 加 bin 索引
outHessian (double[]) – 输出,长度同 outGradient
outCount (int[]) – 输出计数,长度同 outGradient
- 返回
true 表示累加完成;false 表示句柄无效或下标越界
- destroyHistogramSession(long session)static¶
释放会话占用的原生内存
从原生注册表移除句柄并释放会话对象。传入 0 时直接返回,因此可以在未启用原生路径时无条件调用。
注意
重复销毁不会抛异常,但句柄随即失效,后续调用返回 false。
注意
销毁后不得再用该句柄调用 update 或 accumulate。
- 参数
session (long) – 会话句柄;0 表示空操作
- 返回
无返回值
- binFeaturesChecked(double[] data, int rows, int columns, int maxBins, byte[] outBins, double[] outThresholds, int[] outCounts) → booleanstatic¶
对每个特征列做分位数分箱
每个特征列先整体排序,再按 rows 乘 bin 除以 maxBins 减 1 取位置,阈值取该位置相邻两值的中点;中点落在区间外时退回左值,连续重复的阈值被丢弃,因此实际箱数可能少于 maxBins。分箱下标等于严格小于该值的阈值个数。
注意
分箱下标用 byte 存放,因此上限为 255 且下标非负。
注意
阈值构造与 Java 路径逐位一致:中点写作左右值各半再相加,避免溢出。
注意
outThresholds 只在前 outCounts 个位置有定义,其余位置保持原值。
- 参数
data (double[]) – 样本矩阵,行主序 rows 行 columns 列,长度必须为 rows*columns
rows (int) – 样本数,必须至少为 2
columns (int) – 特征数,必须至少为 1
maxBins (int) – 分箱上限,必须在 2 到 255 之间
outBins (byte[]) – 输出,长度必须为 rows*columns,写入每个元素的分箱下标
outThresholds (double[]) – 输出,长度必须为 columns*(maxBins-1),按特征分段存放阈值
outCounts (int[]) – 输出,长度必须为 columns,写入每个特征实际产生的阈值数
- 返回
true 表示分箱完成;false 表示行数、maxBins 或数组长度不合法
- interface Persistable¶
可写入 .bsl 容器的模型契约
实现类声明自己的 ModelKind 并提供 write,把全部状态按固定顺序写进 ModelWriter。容器头部(魔数、版本、码值、长度、CRC32C)由 ModelCodec 负责,实现类只处理负载部分。
读取侧没有对称接口:解码由 ModelCodec.read 按码值分派到各模型族自己的静态 read(ModelReader) 方法。写入顺序必须与该方法逐字段对应,否则校验和仍然通过,但字段会整体错位。
注意
接口只有两个方法且不含版本号,负载格式的兼容性由容器版本字段整体控制。
注意
write 不负责写长度前缀,数组长度需要实现类自己先写一个 int 再写元素。
参见:basalt.core.ModelKind, basalt.core.ModelWriter, basalt.io.ModelCodec
- final class Tensor<S extends Layout>¶
布局由类型参数约束的可变连续张量
元素存放在单个连续 double[] 中,按行主序线性化:index 从第 0 轴起依次执行 index = index * shape[axis] + coordinate,最后一维变化最快。形状以 int[] 保存并在构造时克隆,shape() 返回的同样是副本。
布局不是运行期字段,而是泛型参数 S。Tensor<BCHW> 与 Tensor<BSF> 是不同类型,卷积层与序列层因此在编译期就无法互相传参;轴数与各轴长度仍由 shape 数组在运行期保存,可经 rank 与 dimension 读取。
of 系列工厂克隆传入的变长参数数组,zeros 系列直接分配已清零的数组;data() 返回内部数组本体,toArray() 与 copy() 才产生副本。本类属于 basalt.core,是神经网络层与经典估计器共用的数值容器,不参与计算图。
示例Tensor<BC> logits = Tensor.ofBC(2, 3, 0.5, 0.25, 0.75, 0.1, 0.2, 0.3); double v = logits.get(1, 2); Tensor<BC> head = logits.sliceBatch(0, 1); Tensor<BCHW> blank = Tensor.zerosBCHW(1, 3, 32, 32); int rank = blank.rank();注意
data() 暴露内部存储,写入等同修改张量;需要快照时使用 toArray()。
注意
sliceBatch 复制数据而非创建视图,返回的张量与源张量不共享存储。
注意
dimension(int) 不做轴范围校验,越界由数组访问抛出 ArrayIndexOutOfBoundsException。
注意
与 basalt.torch.Tensor 无关:后者是持有原生句柄、参与计算图的类型。
参见:basalt.core.Layout, basalt.core.Matrix, basalt.torch.Tensor
方法
- ofBCHW(int batch, int channels, int height, int width, double... data) → Tensor<BCHW>static¶
构造 BCHW 四维张量并拷贝数据
形状按 batch、channels、height、width 组装后交给统一的 create 校验,再以 copy 标志构造,因此变长参数数组被克隆。调用方后续修改原数组不影响张量内容。
- 参数
batch (int) – 批大小,必须为正
channels (int) – 通道数,必须为正
height (int) – 高度,必须为正
width (int) – 宽度,必须为正
data (double...) – 行主序元素,个数必须等于四维乘积
- 返回
携带 BCHW 标记的新张量
- 抛出
IllegalArgumentException – 任一维非正、元素总数超过 Integer.MAX_VALUE,或 data 长度不符
NullPointerException – data 为 null
- ofBHW(int batch, int height, int width, double... data) → Tensor<BHW>static¶
构造 BHW 三维张量并拷贝数据
形状为 batch、height、width 三维。数据按行主序排列,同一批内相邻两行在存储中连续。
- 参数
batch (int) – 批大小,必须为正
height (int) – 高度,必须为正
width (int) – 宽度,必须为正
data (double...) – 行主序元素,个数必须等于 batch * height * width
- 返回
携带 BHW 标记的新张量
- 抛出
IllegalArgumentException – 任一维非正、元素总数超过 Integer.MAX_VALUE,或 data 长度不符
NullPointerException – data 为 null
- ofBC(int batch, int channels, double... data) → Tensor<BC>static¶
构造 BC 二维张量并拷贝数据
二维布局,第 0 轴为批、第 1 轴为通道。这是全连接层与分类头最常用的形状,也用于把标签写成 one-hot 形式。
- 参数
batch (int) – 批大小,必须为正
channels (int) – 通道数,也可理解为特征数或类别数,必须为正
data (double...) – 行主序元素,个数必须等于 batch * channels
- 返回
携带 BC 标记的新张量
- 抛出
IllegalArgumentException – 任一维非正、元素总数超过 Integer.MAX_VALUE,或 data 长度不符
NullPointerException – data 为 null
- ofBSF(int batch, int sequence, int features, double... data) → Tensor<BSF>static¶
构造 BSF 三维张量并拷贝数据
同一时间步的特征向量在存储中连续,循环层按时间步推进时可整段读取。序列长度不允许为 0,空序列需由调用方自行约定。
- 参数
batch (int) – 批大小,必须为正
sequence (int) – 序列长度(时间步数),必须为正
features (int) – 每个时间步的特征数,必须为正
data (double...) – 行主序元素,个数必须等于三维乘积
- 返回
携带 BSF 标记的新张量
- 抛出
IllegalArgumentException – 任一维非正、元素总数超过 Integer.MAX_VALUE,或 data 长度不符
NullPointerException – data 为 null
- zerosBCHW(int batch, int channels, int height, int width) → Tensor<BCHW>static¶
创建 BCHW 全零张量
按四维乘积分配新数组,初值 0.0 由 JVM 保证。卷积层的输出缓冲区即由此创建,随后由内核逐元素填充。
- 参数
batch (int) – 批大小,必须为正
channels (int) – 通道数,必须为正
height (int) – 高度,必须为正
width (int) – 宽度,必须为正
- 返回
元素全为 0.0 的新张量
- 抛出
IllegalArgumentException – 任一维非正,或元素总数超过 Integer.MAX_VALUE
- zerosBC(int batch, int channels) → Tensor<BC>static¶
创建 BC 全零张量
按 batch * channels 分配新数组,不做逐元素填充。常用于累积梯度或存放待写入的标签。
- 参数
batch (int) – 批大小,必须为正
channels (int) – 通道数,必须为正
- 返回
元素全为 0.0 的新张量
- 抛出
IllegalArgumentException – 任一维非正,或元素总数超过 Integer.MAX_VALUE
- zerosBSF(int batch, int sequence, int features) → Tensor<BSF>static¶
创建 BSF 全零张量
按三维乘积分配新数组。LSTM 与 GRU 的 backward 用它分配输入梯度,再逐时间步累加。
- 参数
batch (int) – 批大小,必须为正
sequence (int) – 序列长度,必须为正
features (int) – 特征数,必须为正
- 返回
元素全为 0.0 的新张量
- 抛出
IllegalArgumentException – 任一维非正,或元素总数超过 Integer.MAX_VALUE
- rank() → int¶
返回张量维数
- 返回
形状数组长度,BC 为 2,BHW 与 BSF 为 3,BCHW 为 4
- dimension(int axis) → int¶
返回指定轴的长度
直接返回 shape[axis],不含任何检查。负下标与越界下标都由 JVM 的数组访问抛出异常,消息中不带轴名。
- 参数
axis (int) – 轴下标,从 0 起,源码未做范围校验
- 返回
该轴的长度
- 抛出
ArrayIndexOutOfBoundsException – axis 超出 [0, rank())
- shape() → int[]¶
返回形状副本
返回 clone 而非内部数组,修改返回值不会影响张量。需要频繁读取单轴长度时,用 dimension 可以避免这次分配。
- 返回
长度等于 rank() 的新数组
- size() → int¶
返回元素总数
- 返回
底层数组长度,等于各轴长度之积
- index(int... coordinates) → int¶
把多维坐标换算为线性下标
先校验坐标个数,再从第 0 轴起累乘累加:index = index * shape[axis] + coordinate,因此最后一维变化最快。越界消息给出坐标值、轴号与该轴长度。
- 参数
coordinates (int...) – 各轴坐标,个数必须等于 rank(),每个分量须落在对应轴范围内
- 返回
行主序线性下标,范围 [0, size())
- 抛出
IllegalArgumentException – 坐标个数与维数不符
IndexOutOfBoundsException – 任一分量越界
NullPointerException – coordinates 为 null
- get(int... coordinates) → double¶
读取指定坐标的元素
经 index 换算后访问内部数组,逐元素读取会有下标换算开销;批量访问应改用 data() 配合步长计算。
- 参数
coordinates (int...) – 各轴坐标,个数与范围须与形状匹配
- 返回
该位置的 double 值
- 抛出
IllegalArgumentException – 坐标个数与维数不符
IndexOutOfBoundsException – 任一分量越界
- set(double value, int... coordinates)¶
写入指定坐标的元素
值参数位于坐标之前,这是本方法与 get 在签名顺序上的差异,调用时需注意。写入直接落到内部数组,不触发其他副作用。
- 参数
value (double) – 待写入的 double 值,不做取值范围限制
coordinates (int...) – 各轴坐标,个数与范围须与形状匹配
- 抛出
IllegalArgumentException – 坐标个数与维数不符
IndexOutOfBoundsException – 任一分量越界
- data() → double[]¶
返回内部存储数组本身
源码注释说明该方法面向内核,用一次引用传递换取免下标的连续访问。各层在 forward 与 backward 中通过它直接读写,因此返回值就是张量的真实状态。
注意
写入返回值即修改张量,不存在写时复制。
注意
跨线程共享同一张量时需自行同步,张量不含同步措施。
- 返回
与张量共享存储的数组,长度等于 size()
- toArray() → double[]¶
返回元素的独立副本
通过 clone 生成快照,后续写入不会反映到返回值。需要在张量被继续更新时保留中间结果,应使用该方法。
- 返回
新分配的数组,长度等于 size()
- copy() → Tensor<S>¶
返回形状与内容相同的独立副本
形状与数据都被复制,返回对象与原张量完全独立。梯度检查与就地更新前的备份都依赖这一点。
- 返回
持有克隆数组与克隆形状的新张量,布局参数 S 不变
- sliceBatch(int fromBatch, int toBatch) → Tensor<S>¶
复制连续批区间为新张量
四种布局都把批轴放在最前,因此单批元素在存储中连续,整段区间可用一次 System.arraycopy 复制。结果是副本而非视图,修改切片不会影响源张量。
注意
空区间(fromBatch 等于 toBatch)被视为非法,不接受零批张量。
- 参数
fromBatch (int) – 起始批下标,包含
toBatch (int) – 结束批下标,不包含
- 返回
批维为 toBatch - fromBatch 的新张量,其余轴不变
- 抛出
IllegalArgumentException – fromBatch 为负、toBatch 超出批数,或 fromBatch 不小于 toBatch
- fill(double value) → Tensor<S>¶
把所有元素设为同一值
通过 Arrays.fill 就地写入全部元素。结合 zeros 系列工厂可先分配形状、再统一初始化,避免构造变长参数数组。
- 参数
value (double) – 填充值,可为 NaN 或无穷大
- 返回
this,便于链式调用
- final class VectorMath¶
被乘法、距离、优化器与归一化共用的 SIMD 原语
每个核函数都写成向量主循环加标量尾段:主循环以 DoubleVector.SPECIES_PREFERRED 的通道数步进,剩余不足一个向量的部分用标量循环补齐。因此任意长度与非对齐偏移都是安全的,调用方不需要把数组补齐到通道数的整数倍。
累加普遍使用向量 fma 或 Math.fma,把乘加融合成一条指令并省去一次中间舍入。所有函数都是静态方法、无内部状态,也不分配数组;axpy 与 scaleInPlace 直接写回目标。多个线程可并发调用,前提是各线程操作不同的数组区间。
本类不做 NaN 与无穷大的特殊处理:输入含 NaN 时结果按 IEEE 754 传播;norm 由 sumSquares 开方得到,未做溢出保护。唯一的零值保护出现在 clipNorm,它在范数为 0 时跳过缩放以避免除零。
示例double[] a = {1.0, 2.0, 3.0}; double[] b = {0.5, 0.5, 0.5}; double numerator = VectorMath.dot(a, 0, b, 0, a.length); VectorMath.axpy(-1.0, b, a); double d2 = VectorMath.squaredEuclidean(a, 0, b, 0, a.length); double before = VectorMath.clipNorm(a, 1.0);注意
带 offset 与 length 的函数不做边界检查,调用方须保证 offset + length 不越过数组末尾。
注意
axpy 要求 source 与 target 等长,否则抛出 IllegalArgumentException。
注意
类为 final 且构造器私有,只提供静态方法。
参见:basalt.core.MatMul, basalt.core.Matrix, basalt.ml.DistanceMetric
方法
- speciesLength() → intstatic¶
返回首选向量物种的通道数
该值是主循环的步进单位。调用方可据此判断某段长度能否完整走完向量路径,或按通道数对齐缓冲区以获得稳定的向量化效果。
- 返回
当前平台上 SPECIES_PREFERRED 的 lane 数
- dot(double[] a, int aOffset, double[] b, int bOffset, int length) → doublestatic¶
计算两个等长区间的点积
向量段用 fma 把乘积累加进零向量,再横向归约;尾段用 Math.fma 补齐。两个数组可以是同一个数组的不同区间,此时得到该区间的平方和。
注意
不做边界检查,length 过大时会读取到数组之外并抛出数组越界异常。
注意
NaN 输入按 IEEE 754 传播,不会被跳过或替换。
- 参数
a (double[]) – 左数组
aOffset (int) – 左数组起始下标,可为非对齐值
b (double[]) – 右数组
bOffset (int) – 右数组起始下标,可为非对齐值
length (int) – 参与计算的元素个数,0 表示空区间
- 返回
点积结果,length 为 0 时返回 0.0
- axpy(double alpha, double[] source, double[] target)static¶
就地执行 target += alpha * source
alpha 被广播成向量后与 source 相乘,再加到 target 上写回,全程不分配临时数组。源码注释强调这一免分配特性,优化器的参数更新正是依赖它。
注意
source 与 target 可以是同一数组,此时等价于按 1 + alpha 缩放。
- 参数
alpha (double) – 乘数,可为负数或 0
source (double[]) – 被加数组
target (double[]) – 累加目标,长度须与 source 相同
- 抛出
IllegalArgumentException – 两个数组长度不同
- addInto(double[] source, double[] target)static¶
就地执行 target += source
等价于 axpy(1.0, source, target),长度校验与向量化路径都由 axpy 承担,本身只是一层转发。
- 参数
source (double[]) – 被加数组
target (double[]) – 累加目标,长度须与 source 相同
- 抛出
IllegalArgumentException – 两个数组长度不同
- scaleInPlace(double[] values, double factor)static¶
就地按因子缩放数组
向量段把 factor 广播后相乘写回,尾段逐元素相乘。factor 为 0 时结果全为 0.0,为 NaN 时全部元素变为 NaN,本方法不做拦截。
- 参数
values (double[]) – 待缩放的数组,原地修改
factor (double) – 乘数,可为负数、0 或 NaN
- sum(double[] values) → doublestatic¶
求数组元素之和
向量段累加到零向量后横向归约,尾段逐元素相加。归约是树形的,求和顺序与朴素循环不同,浮点结果可能在末位上有差异。
- 参数
values (double[]) – 待求和的数组
- 返回
元素之和,空数组返回 0.0
- sumSquares(double[] values) → doublestatic¶
求数组元素的平方和
用 fma 把元素与自身相乘的结果累加,既用于 norm,也被梯度裁剪直接调用来估计参数范数。结果为非负值,但可能因溢出变为无穷大。
- 参数
values (double[]) – 待求平方和的数组
- 返回
平方和,空数组返回 0.0
- norm(double[] values) → doublestatic¶
求欧氏范数
实现只有一句 Math.sqrt(sumSquares(values)),未做缩放式的溢出与下溢保护。输入含 NaN 时结果为 NaN,零向量得到 0.0 而非异常。
- 参数
values (double[]) – 待求范数的数组
- 返回
sumSquares 的平方根;零向量与空数组返回 0.0
- maxAbs(double[] values) → doublestatic¶
求绝对值最大的元素
累加器从 0.0 起,向量段对元素取绝对值后逐通道取最大,尾段用 Math.max 与 Math.abs 补齐。由于起点是 0,结果恒为非负。
注意
空数组与全零数组都返回 0.0,无法据此区分两者。
- 参数
values (double[]) – 待扫描的数组
- 返回
最大绝对值,空数组返回 0.0
- clipNorm(double[] values, double limit) → doublestatic¶
按上限等比缩小数组并返回原范数
仅当范数同时大于 limit 与 0.0 时,按 limit / norm 调用 scaleInPlace 缩小。因此零向量不会触发除零,也不会被改写,返回值就是缩放前的范数,可用于判断是否发生了裁剪。
源码注释把返回值定义为裁剪前的范数,这与常见的「返回缩放因子」约定不同,调用方若要复现梯度裁剪需自行比较返回值与 limit。
注意
limit 为负时条件仍然成立,缩放因子为负,数组会整体变号而非被清零。
- 参数
values (double[]) – 就地缩放的数组
limit (double) – 范数上限,源码不校验正负
- 返回
缩放前的欧氏范数;空数组或零向量返回 0.0
- squaredEuclidean(double[] a, int aOffset, double[] b, int bOffset, int length) → doublestatic¶
计算两个区间的平方欧氏距离
对逐元素差值做 fma 自乘累加,跳过开方步骤,因此可以直接用于最近邻比较而不改变大小关系。KMeans 的内层循环即调用该函数。
注意
不做边界检查,偏移与长度由调用方保证。
- 参数
a (double[]) – 左数组
aOffset (int) – 左数组起始下标
b (double[]) – 右数组
bOffset (int) – 右数组起始下标
length (int) – 参与计算的元素个数,0 表示空区间
- 返回
平方距离,length 为 0 时返回 0.0
- manhattan(double[] a, int aOffset, double[] b, int bOffset, int length) → doublestatic¶
计算两个区间的曼哈顿距离
向量段对差值取绝对值后累加,尾段用 Math.abs 补齐,与 squaredEuclidean 共享同一套偏移与长度约定。DistanceMetric 的 MANHATTAN 分支调用该函数。
注意
不做边界检查;与平方欧氏距离不同,结果与维度同阶增长。
- 参数
a (double[]) – 左数组
aOffset (int) – 左数组起始下标
b (double[]) – 右数组
bOffset (int) – 右数组起始下标
length (int) – 参与计算的元素个数,0 表示空区间
- 返回
绝对差之和,length 为 0 时返回 0.0
- static final class NativeKernels.HistogramSessionHandle implements AutoCloseable¶
持有原生直方图会话指针的可关闭句柄
实例把 histogramSessionCreate 返回的指针存进 AtomicLong,并通过 Cleaner 注册清理动作。清理时以 getAndSet(0) 取出指针,仅在非零时调用 destroyHistogramSession,因此销毁最多发生一次。
这样即使在建树过程中抛出异常、调用方忘记关闭,会话也会在句柄不可达后被回收;显式 close() 则把回收时机提前到确定的位置。句柄关闭后 handle() 返回 0,此时不能再把该值传给 updateHistogramSession 等会话方法。
示例long session = NativeKernels.createHistogramSession(bins, gradients, hessians, features, maxBins); try (NativeKernels.HistogramSessionHandle handle = new NativeKernels.HistogramSessionHandle(session)) { NativeKernels.updateHistogramSession(handle.handle(), gradients, hessians); }注意
实现 AutoCloseable,配合 try-with-resources 使用可确定性地释放原生会话。
注意
close() 幂等:清理动作已执行时再次调用取到的是 0,不会重复销毁。
注意
构造不做非零校验,传入 0 会得到一个已失效的句柄,所有会话方法都会失败。
注意
真实指针只保存在 AtomicLong 中,句柄对象本身不被原生侧引用。
参见:basalt.core.NativeKernels, basalt.ml.HistogramGradientBoosting
构造器
- HistogramSessionHandle(long sessionPointer)¶
包装原生会话指针并注册清理动作
指针写入 AtomicLong 后立即向 Cleaner 注册回调,回调与 close() 执行的是同一段清理逻辑。构造本身不校验指针是否有效,也不与原生侧交互。
- 参数
sessionPointer (long) – histogramSessionCreate 返回的会话地址;0 表示创建失败,销毁时会被跳过
方法
- handle() → long¶
返回当前会话指针
每次调用都重新读取 AtomicLong,因此关闭后再取得到 0,可以据此判断句柄是否仍然有效。
- 返回
原生会话地址;未关闭时为构造时传入的值,关闭后为 0
- close()¶
销毁原生会话,可重复调用
触发构造时注册的清理动作:原子取出指针并置零,非零时调用 destroyHistogramSession。Cleaner 之后再执行同一动作时取到 0,不会重复释放。
注意
关闭后句柄不再可用,再次调用 close() 无副作用。
注意
不显式关闭时会话由 Cleaner 在句柄不可达后销毁,时机由 GC 决定。
包内可见类型
以下类型没有 public 修饰符,但出现在公开签名中,因此一并列出。它们不是稳定 API,跨版本可能变化。
- private static final class MatMul.RowTask extends RecursiveAction¶
按行区间切分的 ForkJoin 乘法子任务
MatMul.multiply 在未走原生内核时先转置右操作数,再把整个行区间封装成 RowTask 交给 pool.invoke;任务只持有数组引用与区间下标,所有子任务共享同一个 output 数组,各自只写自己负责的行。
compute() 是 RecursiveAction 的分治入口:行数不超过 ROW_BLOCK(8)或区间工作量不超过 WORK_THRESHOLD(65536)时直接计算,否则从中点二分并用 invokeAll 提交两个子任务并等待返回。
方法
- compute()¶
ForkJoin 调度入口:二分区间或就地计算行块
由 ForkJoinPool 在 MatMul.multiply 的 pool.invoke 之后调用,也可能由父任务通过 invokeAll 提交。判定为叶任务时转入 computeBlock,用 VectorMath.dot 逐列填充共享 output 数组;否则按行区间对半拆成两个 RowTask 并等待二者结束。
- 返回
无返回值;结果写入构造时传入的共享 output 数组