Basalt»3.4.0»API 参考»basalt.core
模块 索引 GitHub

模块 basalt.core

16 个公开类型 · 1 个包内可见类型

基础数据层:行主序矩阵、布局标记、原生经典核、模型容器读写原语。

类型概览

类型种类说明
BCclass批×通道布局标记,同时用于批×特征与批×类别
BCHWclass批×通道×高×宽布局标记,卷积网络的图像张量
BHWclass批×高×宽布局标记,省略通道轴的三维图像张量
BSFclass批×序列×特征布局标记,序列模型的标准输入
Layoutinterface张量维度顺序的编译期标记接口,只约束泛型参数
MatMulclass矩阵乘法引擎:标量基准与分块并行的 SIMD 实现
Matrixclass行主序、单块 double 数组承载的可变矩阵
ModelFormatExceptionclass.bsl 流截断、损坏或版本不兼容时抛出的非受检异常
ModelKindenum可持久化模型族的稳定线格式标识码
ModelReaderclass与 ModelWriter 配对的大端、带越界校验的读取器
ModelWriterclass可增长的大端二进制写入器
NativeKernelsclass通过 JNI 暴露的可选 Rust 内核集合
Persistableinterface可写入 .bsl 容器的模型契约
Tensorclass布局由类型参数约束的可变连续张量
VectorMathclass被乘法、距离、优化器与归一化共用的 SIMD 原语
NativeKernels.HistogramSessionHandleclass持有原生直方图会话指针的可关闭句柄
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 阶段。

参见:basalt.core.Layout, basalt.core.Tensor, basalt.nn.CNN

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 完成。

参见:basalt.core.Layout, basalt.core.BHW, basalt.nn.Conv2D

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> 是不同类型,轴数不同的张量无法互相赋值。

参见:basalt.core.Layout, basalt.core.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 数组