← 返回主题列表
✨步子哥
@steper · 2026年06月12日 21:32 · 5浏览

leaves 项目演进计划

> 状态: 草案 v1.0 > 日期: 2026-06-13 > 参考项目: XGBoost · GoMLX > 当前版本: v0.8.0(纯 Go GBRT 预测库)

---

目录

---

〇、总纲

费曼式一句话

> leaves 现在是个「翻译器」——读到 XGBoost/LightGBM 训练好的模型文件,逐棵树走一遍,吐出预测结果。它的骨架是对的(统一接口 + 多后端适配),缺的是肌肉(GPU 加速、SHAP 解释)和大脑(训练能力)。GoMLX 就是那块能长肌肉和大脑的培养皿——它的符号图 + XLA JIT 编译,恰好能把树遍历变成可并行、可微分的图操作。

核心命题

如何在不丢弃现有生态兼容性(LightGBM、XGBoost、scikit-learn 模型加载)的前提下,用 GoMLX 作为计算底座,分阶段将 leaves 从一个纯预测库演进为一个支持训练、GPU 加速、可解释的完整 GBRT 框架?

设计原则

1. 渐进增强,非推倒重来:每个 Phase 产出可用的中间版本 2. 接口稳定性Ensemble 公开 API 保持兼容,内部实现可替换 3. GoMLX 优先,本地回退:GPU/XLA 可用则用 GoMLX,否则回退当前纯 Go 路径 4. 参考 XGBoost,不做克隆:学习 XGBoost 的架构思想,但用 Go 的惯用方式表达 5. 测试驱动演进:每个 Phase 有对应基准测试和兼容性测试

---

一、现状剖析

1.1 当前 leaves 架构

┌──────────────────────────────────────────────────────┐
│                    Ensemble (公开 API)                │
│  PredictSingle / Predict / PredictDense / PredictCSR │
├──────────────────────────────────────────────────────┤
│               ensembleBaseInterface (内部接口)         │
├─────────────────┬────────────────┬───────────────────┤
│   lgEnsemble    │   xgEnsemble   │     xgLinear      │
│  (LightGBM树)   │  (XGBoost树)   │   (XGBoost线性)    │
├─────────────────┴────────────────┴───────────────────┤
│                    lgTree (树结构体)                    │
│    逐节点比较 → 走左/右分支 → 到达叶子 → 取值           │
└──────────────────────────────────────────────────────┘
        │ IO 层
        ▼
┌───────────────┐  ┌──────────────────┐  ┌──────────────┐
│ lgEnsemble_io │  │  xgEnsemble_io   │  │ skEnsemble_io│
│ (文本/JSON)   │  │  (二进制 XGBoost) │  │ (pickle)     │
└───────────────┘  └──────────────────┘  └──────────────┘

1.2 leaves 已具备的能力

能力详情
模型加载LightGBM (文本/JSON)、XGBoost (二进制)、scikit-learn (pickle)
模型类型gbtree、dart、gblinear、random_forest、GradientBoostingClassifier
数据格式Dense (Row-Major)、CSR (稀疏行压缩)
变换函数Raw、Logistic(Sigmoid)、Softmax、Exponential、LeafIndex
缺失值NaN 检测 → default_left 分支(与 XGBoost 兼容)
LightGBM 分类特征one-hot 决策规则优化
多线程goroutine pool 并行批预测
单样本预测PredictSingle / Predict
批预测PredictDense / PredictCSR

1.3 与 XGBoost 的差距(仅列可补足的)

类别XGBoost 有,leaves 无优先级
模型格式JSON 模型、dump 文本/JSON/dot★★★
SHAP 值Tree SHAP(贡献值)、SHAP 交互值★★★
特征重要性weight / gain / cover / total_gain / total_cover★★☆
评估指标24+ 内置指标 (rmse, auc, ndcg, logloss 等)★★☆
分类特征XGBoost 原生 categorical(非 LightGBM 的 one-hot)★★☆
多输出树MultiOutputTree 策略、向量叶★★☆
GPU 推理CUDA 预测★★★
训练完整训练循环、CV、early stopping★☆☆(远期目标)
JSON 模型XGBoost JSON 格式序列化★★☆
生存分析AFT、Cox 专门变换★☆☆
分布式Dask/Spark/Rabit★☆☆
模型属性attributes 字典读写★☆☆

1.4 当前性能基准

单线程下,leaves 与 C API 性能接近(差距 < 20%)。多线程下持平。瓶颈在树遍历的标量循环——每棵树每条样本走一遍分支判断,纯标量操作,无法利用 SIMD 或 GPU。

---

二、GoMLX 技术基础评估

2.1 GoMLX 核心模型

GoMLX 采用符号计算图 + JIT 编译范式(类似 JAX/TF2):

阶段1: 图构建 (Graph Building)
  → 用 Go 函数描述符号操作,不执行实际计算
  
阶段2: 编译 (Compile)  
  → JIT 编译为设备可执行代码(XLA HLO → 机器码/PTX)
  
阶段3: 执行 (Execute)
  → 输入具体 Tensor 值,获取输出(自动缓存编译结果)

2.2 与树推理相关的关键原语

GoMLX 操作在树推理中的用途
LessThan / GreaterOrEqual特征值与分裂阈值比较
Where(cond, onTrue, onFalse)三元选择 → 决定左子树还是右子树
Gather(params, indices)按节点索引查表取特征 ID、阈值、子节点索引
While(cond, body, init)树遍历循环(从根到叶)
If(pred, trueBranch, falseBranch)条件分支
Const(g, value)将树结构嵌入为图常量
Reshape / Broadcast形状适配,将 [batch] 对齐到 [batch, numTrees]
ReduceSum汇总所有树的叶子值
IsNaN缺失值检测 → 走 default_left 分支

2.3 后端选择矩阵

后端加速器部署场景性能预期
XLA (默认)CPU/GPU(Nvidia)/TPU服务器推理最高
SimpleGo纯 CPUWASM 浏览器、边缘设备可移植
go-coremlApple MetaliOS/macOS

2.4 GoMLX 的限制

限制影响对策
图构建时形状需固定batch size 变化触发重编译利用 Exec 的形状缓存(默认 32 个槽位)
动态形状支持标记为 in_progress可变树结构难以表达每棵树构建独立子图,用整数索引组合
While 循环有一定开销纯展开可能更快对 max_depth ≤ 8 的树,完全展开循环
无直接 CSR Gather稀疏输入需要先 DensifyPhase 1 先支持 Dense,后续优化
---

三、演进路线图概览

Phase 0          Phase 1           Phase 2            Phase 3           Phase 4           Phase 5
架构双轨化        GoMLX推理引擎      格式与功能对齐      可解释性          训练能力           高性能分布式
(兼容期)          (加速期)           (补全期)            (增值期)          (质变期)           (成熟期)
                                                                                          
v0.8.0 ──────► v0.9.0 ──────► v1.0.0 ──────► v1.1.0 ──────► v2.0.0 ──────► v3.0.0
   │                │                │                 │                │                │
   │ 拆分monolith   │ 图编译树推理    │ JSON模型         │ Tree SHAP      │ 基础训练循环    │ GPU/分布式
   │ 接口抽象       │ XLA/GPU加速    │ 评估指标         │ 特征重要性     │ GBDT训练器     │ 量化推理
   │ 引入gomlx dep  │ SimpleGo后端   │ categorical feat │ 近似贡献值     │ objective/metric│ ONNX互操作
   │                │                │ 多输出树          │ 模型可视化     │ CV/early stop  │ WASM部署

---

四、Phase 0:架构双轨化(兼容期) 目标版本 v0.9.0

目标

在不改变任何公开 API 的前提下,重构内部架构,为引入 GoMLX 做准备。

4.1 引入 GoMLX 依赖

go get github.com/gomlx/gomlx@latest

注意:GoMLX 要求 Go ≥ 1.25.0,当前 leaves 使用 Go 1.12。必须分两步: 1. 先升级 Go 版本(leaves 代码本身兼容 Go 1.18+ 即可) 2. 再添加 GoMLX 依赖

4.2 拆分 monolithic 根包

当前 leaves 根包混合了所有逻辑。改为按职责拆包:

leaves/                    # 根包:仅公开 API(向后兼容的类型别名)
├── model/
│   ├── ensemble.go        # Ensemble 公开类型(当前 leaves.go 核心)
│   ├── interface.go       # ensembleBaseInterface(保持不变)
│   └── options.go         # 预测选项(nEstimators, nThreads 等)
├── tree/
│   ├── lgtree.go          # 树数据结构(当前 lgtree.go)
│   ├── native.go          # 原生纯 Go 树推理引擎(当前逻辑)
│   └── gomlx.go           # GoMLX 图推理引擎(Phase 1 实现)
├── io/
│   ├── lgb.go             # 当前 lgensemble_io.go
│   ├── xgb.go             # 当前 xgensemble_io.go + xgblinear_io.go
│   ├── skl.go             # 当前 skensemble_io.go
│   └── format.go          # 格式检测、版本协商
├── ensemble/
│   ├── lgb.go             # 当前 lgensemble.go
│   ├── xgb.go             # 当前 xgensemble.go
│   └── xgblinear.go       # 当前 xgblinear.go
├── transform/
│   ├── transform.go       # 当前 transformation/ 包内容
│   └── metrics.go         # Phase 2 评估指标
├── mat/
│   ├── dense.go           # 当前 mat/dense.go
│   ├── csr.go             # 当前 mat/csr.go
│   └── util.go            # 当前 mat/util.go
├── internal/              # 内部工具(当前 internal/ 内容,保持不变)
└── util/                  # 通用工具(当前 util/ 内容)

迁移策略:根包使用类型别名(type Ensemble = model.Ensemble),确保旧代码零改动编译通过。在 v1.0.0 时移除别名。

4.3 定义推理引擎接口

// tree/engine.go
package tree

// Engine 是树推理的可插拔后端
// 两种实现:NativeEngine(当前纯 Go)、GoMLXEngine(Phase 1)
type Engine interface {
    // PredictDense 在稠密矩阵上执行预测
    // 输入: vals[row*cols + col] 格式
    // 输出: predictions[row*outputGroups + group]
    PredictDense(
        vals []float64, nrows int, ncols int,
        predictions []float64,
        nEstimators int,
    ) error

    // PredictCSR 在 CSR 稀疏矩阵上执行预测
    PredictCSR(
        indptr []int, cols []int, vals []float64,
        predictions []float64,
        nEstimators int,
    ) error

    // PredictSingle 单样本预测
    PredictSingle(fvals []float64, nEstimators int) float64

    // NOutputGroups 输出维度
    NOutputGroups() int

    // Close 释放资源(如 GoMLX 的 Exec 缓存)
    Close() error
}

// Factory 根据配置创建引擎
type Factory func(treeData *TreeData, transform transform.Transform) (Engine, error)

4.4 提取树数据为中间表示 (IR)

当前 lgTree 是 LightGBM-specific 的结构。需要定义统一的树中间表示,让 GoMLX 能一次加载,多后端执行:

// tree/ir.go
package tree

// ForestIR 是所有树模型的通用中间表示
// 与框架来源无关(LightGBM/XGBoost/scikit-learn 加载后统一转为此格式)
type ForestIR struct {
    NumFeatures     int
    NumOutputGroups int  // 1 for binary/regression, N for multiclass
    Trees           []TreeIR
    BaseScore       float64
}

// TreeIR 单棵决策树的中间表示
type TreeIR struct {
    NumLeaves     int
    MaxDepth      int
    // 以完整二叉树方式存储(非叶节点有 left/right child)
    SplitFeature  []int32   // [numNodes] 分裂特征索引,-1 for leaf
    SplitThreshold []float64 // [numNodes] 分裂阈值
    DefaultLeft   []bool    // [numNodes] 缺失值默认方向
    LeftChild     []int32   // [numNodes] 左子节点索引
    RightChild    []int32   // [numNodes] 右子节点索引
    LeafValue     []float64 // [numLeaves] 叶子值
    // 分类特征(LightGBM 特有)
    CatBoundaries []uint32  // 分类特征边界
    CatThresholds []uint32  // 分类特征阈值集合
}

4.5 Phase 0 产出

  • [x] go.mod 更新到 Go 1.21+(兼容 GoMLX 要求的 Go 1.25 之前,先用最小可行版本)
  • [ ] 包结构重组,根包保留类型别名
  • [ ] tree.Engine 接口定义
  • [ ] tree.ForestIR / tree.TreeIR 通用 IR 定义
  • [ ] NativeEngine 实现(封装当前预测逻辑)
  • [ ] 所有现有测试通过
  • [ ] 性能回归测试基准(benchmark 对比)
---

五、Phase 1:GoMLX 推理引擎(加速期) 目标版本 v1.0.0

目标

用 GoMLX 符号图实现树推理,获得 GPU 加速和 XLA 编译优化。作为可选后端,用户可通过选项切换。

5.1 树遍历的图实现

核心挑战:如何把「逐节点 if/else 判断」表示成可并行计算的图?

#### 方案 A:完全循环展开(推荐起步方案)

XGBoost 树的 max_depth 通常为 6(63 个非叶节点)。对于固定深度的树,可以将循环完全展开为静态图:

// tree/gomlx_engine.go
// 为 max_depth=6 的树构建展开后的推理图
func buildTreeGraph(g *graph.Graph, features *graph.Node, tree *TreeIR) *graph.Node {
    // 把树编码为常量
    splitFeat := graph.Const(g, tree.SplitFeature)     // [numNodes]int32
    thresholds := graph.Const(g, tree.SplitThreshold)   // [numNodes]float64
    leftChild  := graph.Const(g, tree.LeftChild)        // [numNodes]int32
    rightChild := graph.Const(g, tree.RightChild)       // [numNodes]int32
    leafValues := graph.Const(g, tree.LeafValue)        // [numLeaves]float64
    
    // 从根节点(索引0)开始
    currentNode := graph.Scalar(g, dtypes.Int32, 0)
    
    // 展开 max_depth 层
    for depth := 0; depth < tree.MaxDepth; depth++ {
        // 取当前节点的分裂特征和阈值
        featIdx := graph.Gather(splitFeat, currentNode)
        featVal := graph.Gather(features, featIdx)     // 从输入特征取值
        thr     := graph.Gather(thresholds, currentNode)
        
        // 比较: feature[splitIdx] < threshold ?
        goLeft := graph.LessThan(featVal, thr)
        
        // 路由: goLeft ? leftChild[current] : rightChild[current]
        leftIdx  := graph.Gather(leftChild, currentNode)
        rightIdx := graph.Gather(rightChild, currentNode)
        currentNode = graph.Where(goLeft, leftIdx, rightIdx)
    }
    
    // currentNode 现在是叶子索引(负值),取反得到正索引
    leafIdx := graph.Neg(currentNode)
    return graph.Gather(leafValues, leafIdx)
}

#### 方案 B:While 循环(通用方案)

对于深度不确定的树(如 LightGBM 的 num_leaves 优先策略),使用 While 循环:

func buildTreeGraphWhile(g *graph.Graph, features *graph.Node, tree *TreeIR) *graph.Node {
    // ...常量设置同上...
    
    initState := []*graph.Node{
        graph.Scalar(g, dtypes.Int32, 0),  // current node index
    }
    
    // 条件:currentNode >= 0(叶子节点编码为负)
    condFn := func(g *graph.Graph, state []*graph.Node) *graph.Node {
        return graph.GreaterOrEqual(state[0], graph.Scalar(g, dtypes.Int32, 0))
    }
    
    // 主体:走一步
    bodyFn := func(g *graph.Graph, state []*graph.Node) []*graph.Node {
        currentNode := state[0]
        featIdx := graph.Gather(splitFeat, currentNode)
        featVal := graph.Gather(features, featIdx)
        thr := graph.Gather(thresholds, currentNode)
        goLeft := graph.LessThan(featVal, thr)
        next := graph.Where(goLeft,
            graph.Gather(leftChild, currentNode),
            graph.Gather(rightChild, currentNode))
        return []*graph.Node{next}
    }
    
    result := graph.While(condFn, bodyFn, initState...)
    leafIdx := graph.Neg(result[0])
    return graph.Gather(leafValues, leafIdx)
}

#### 方案 C:分类特征(LightGBM one-hot 决策)

LightGBM 的分类特征不需要数值比较,而是用 Contains 检查:

// 对 LightGBM 分类节点的判断
// 等价于: catThresholds[boundaries[idx]:boundaries[idx+1]] 中是否包含 featVal
func buildCatDecision(g *graph.Graph, featVal *graph.Node, 
    catBoundaries, catThresholds *graph.Node) *graph.Node {
    // 对每个可能的分类值做 Equal 比较,然后 ReduceOr
    // 这比循环高效,因为可以向量化
}

5.2 多棵树并行

将所有树同时展开,用 [batch, numTrees] 张量并行执行:

func buildForestGraph(g *graph.Graph, features *graph.Node, forest *ForestIR) *graph.Node {
    // features 形状: [batchSize, numFeatures]
    
    treeOutputs := make([]*graph.Node, len(forest.Trees))
    for i, tree := range forest.Trees {
        // 每棵树的图独立构建
        treeOutputs[i] = buildTreeGraph(g, features, &tree)
    }
    
    // 堆叠所有树输出: [batchSize, numTrees]
    stacked := graph.Stack(treeOutputs, 1) // axis=1
    
    // 求和得到最终预测: [batchSize, 1]
    return graph.ReduceSum(stacked, 1)
}

5.3 缺失值处理

嵌入 IsNaN 检测:

// 在比较之前插入缺失值检查
func buildNodeDecision(g *graph.Graph, featVal, thr *graph.Node, 
    defaultLeft *graph.Node) *graph.Node {
    isMissing := graph.IsNaN(featVal)
    goLeft := graph.LogicalOr(
        isMissing,
        graph.LessThan(featVal, thr),
    )
    // 如果 defaultLeft 为 false,取反
    if !tree.DefaultLeft[currentNode] {
        goLeft = graph.LogicalNot(goLeft)
    }
    return goLeft
}

5.4 变换函数集成

GoMLX 已内置 Softmax 融合操作、Logistic(Sigmoid)、Exp 等,直接使用,无需自己实现:

func applyTransform(g *graph.Graph, raw *graph.Node, t transform.Type) *graph.Node {
    switch t {
    case transform.Raw:
        return raw
    case transform.Logistic:
        return graph.Logistic(raw)
    case transform.Softmax:
        // GoMLX 内置融合 Softmax,包含数值稳定处理
        return graph.Softmax(raw)
    case transform.Exponential:
        return graph.Exp(raw)
    case transform.LeafIndex:
        // 在树遍历时直接返回叶子索引,不用额外处理
        return raw
    }
    return raw
}

5.5 引擎切换 API

// 新选项
type PredictOption func(*predictOptions)

type predictOptions struct {
    backend tree.Backend  // tree.NativeBackend | tree.GoMLXBackend
}

// BackendAuto: 自动选择 GoMLX(XLA),失败时回退 Native
// BackendGoMLX: 强制 GoMLX,无 GPU 则报错
// BackendNative: 强制当前纯 Go 实现
// BackendSimpleGo: GoMLX SimpleGo 后端(WASM 兼容)
func WithBackend(b tree.Backend) PredictOption { ... }

// 用法
model.PredictDense(vals, rows, cols, preds, nEstimators, nThreads,
    WithBackend(tree.BackendGoMLX),
)

5.6 Phase 1 产出

  • [ ] tree/gomlx_engine.go — 基于 GoMLX 图计算的推理引擎
  • [ ] go.mod 包含 github.com/gomlx/gomlx 依赖
  • [ ] tree/ir.goForestIR/TreeIR 通用 IR 定义
  • [ ] IO 层输出统一转为 ForestIR
  • [ ] WithBackend() 选项
  • [ ] XLA 后端 benchmark:GPU 推理性能对比
  • [ ] SimpleGo 后端 benchmark:纯 CPU 性能对比
  • [ ] 所有现有兼容性测试通过(XGBoost / LightGBM / sklearn)
  • [ ] 文档:GoMLX 后端使用指南

5.7 性能预期

场景Native (当前)GoMLX XLA GPUGoMLX XLA CPUGoMLX SimpleGo
小 batch (1-16)★★★ 最优★★☆ 有 kernel 启动开销★★☆★☆☆
中 batch (100-1K)★★☆★★★ 开始体现并行优势★★☆★★☆
大 batch (10K+)★☆☆★★★ 完全利用 GPU★★☆★★☆
WASM 部署★★★ 唯一选择
---

六、Phase 2:格式与功能对齐(补全期) 目标版本 v1.1.0

目标

对标 XGBoost 的主要模型格式和评估功能,消除关键功能缺口。

6.1 XGBoost JSON 模型格式

参考:C:\GitHub\xgboost\src\c_api\c_api_json.cc

XGBoost 自 1.0 起支持 JSON 格式序列化。JSON 格式比二进制格式有以下优势:

  • 人类可读,便于调试
  • 跨版本兼容性更好
  • 包含更丰富的元数据
实现要点:
  • 解析 gbtree_model_param(树数、特征数等)
  • 解析 trees[] 数组(每棵树的 JSON 表示,含 split_indicessplit_conditionsleft_childrenright_childrenbase_weights 等)
  • 解析 train_param 获取 base_score、num_class 等
  • 输出统一转 ForestIR
// io/xgb_json.go
func XGEnsembleFromJSON(reader io.Reader, loadTransformation bool) (*Ensemble, error)

6.2 评估指标 (Metrics)

参考 XGBoost 的 24 个内置指标,实现最常用的核心子集:

类别指标实现函数用途
回归RMSEmath.Sqrt(MSE)回归评估
MAEmean(abs(residuals))回归评估
MAPEmean(abs(residuals/actual))回归评估
RMSLEsqrt(mean(log(pred+1) - log(actual+1))²)回归评估
二分类LogLoss-(y·log(p) + (1-y)·log(1-p))概率校准
AUC梯形法则数值积分排序质量
Errormean(round(pred) != actual)分类准确率
多分类MLogLoss多类 LogLoss多分类校准
MError多类分类错误率多分类准确率
排序NDCG@k折损累积增益LTR 评估
MAP平均精度均值LTR 评估
// metrics/metrics.go
package metrics

type Metric interface {
    Name() string
    // Evaluate 计算指标值
    Evaluate(yTrue, yPred []float64) (float64, error)
    // EvaluatePerGroup 按 group 评估(排序任务)
    EvaluatePerGroup(yTrue, yPred []float64, groups []int) (float64, error)
    // HigherIsBetter 是否为越高越好的指标
    HigherIsBetter() bool
}

6.3 XGBoost 原生分类特征支持

参考:C:\GitHub\xgboost\src\encoder\categorical.h

XGBoost 1.6+ 引入了原生分类特征。与 LightGBM one-hot 方式不同,XGBoost 的分类节点使用分区比较

// tree/ir.go 扩展
type TreeIR struct {
    // ...现有字段...
    
    // 分类特征节点(XGBoost 原生格式)
    // CategoryNode: 检查特征值是否在 categories 集合中
    IsCategorical []bool     // [numNodes] 该节点是否为分类分裂
    Categories    [][]uint32 // [numNodes] 分类值集合(仅 IsCategorical=true 的节点)
}

在二进制模型解析中读取 ftype(feature type)字段,ftype == 1 表示分类特征。

6.4 多输出树支持

参考:C:\GitHub\xgboost\src\tree\tree_model.h -> MultiTargetTree

XGBoost 的 multi:softmax 模式使用多棵独立树(每个类一棵),leaves 已支持。但 XGBoost 3.x 新增了 MultiOutputTree 策略——一棵树输出多个值(向量叶):

// tree/ir.go
type TreeIR struct {
    // ...现有字段...
    
    // 多输出叶:LeafValues[numLeaves * outputDim]
    // 按 [leaf_i*dim : (leaf_i+1)*dim] 排列
    OutputDim  int
    LeafValues []float64 // 长度 = numLeaves * OutputDim
}

GoMLX 侧用 Reshape[numLeaves * outputDim] 转为 [numLeaves, outputDim],Gather 后得到 [batch, outputDim]

6.5 Phase 2 产出

  • [ ] JSON 格式模型加载器 io/xgb_json.go
  • [ ] metrics/ 包:12+ 核心评估指标
  • [ ] XGBoost 原生分类特征解析与推理
  • [ ] 多输出树支持
  • [ ] 兼容性测试覆盖上述新功能
  • [ ] 评估指标 benchmark
---

七、Phase 3:可解释性与高级预测(增值期) 目标版本 v1.2.0

目标

实现 XGBoost 的核心可解释性功能:Tree SHAP、特征重要性,填补当前最大的功能缺口。

7.1 Tree SHAP 贡献值

参考:C:\GitHub\xgboost\src\tree\tree_model.cc (CalcContrib)
论文:Lundberg & Lee, 2017. "A Unified Approach to Interpreting Model Predictions"

Tree SHAP 是 XGBoost 最强大的可解释性功能。算法本质是在树上计算每个特征的 Shapley 值:

核心公式(树 SHAP 的递归算法):

对于节点 i,定义两条路径的权重:

  • w_left = cover[left] / cover[i](训练样本落入左子树的比例)
  • w_right = cover[right] / cover[i]
则从根到叶,沿路径累积每个特征贡献。算法复杂度 O(TLD²),其中 T=树数,L=叶数,D=深度。对于典型 XGBoost 模型(depth=6,leaves≤64),这是可接受的。

#### GoMLX 实现方案

不能直接做符号图(因为 SHAP 的路径依赖逻辑太复杂),采用两阶段:

阶段 1:CPU 端做 SHAP 路径计算(纯 Go),利用 ForestIR 中已存储的 cover(覆盖率)数据。

阶段 2:如果输入是大 batch,将已算出的 SHAP 矩阵用 GoMLX 做批量后处理。

// shap/shap.go
package shap

// TreeExplainer 解释器
type TreeExplainer struct {
    forest *tree.ForestIR
}

// ShapleyValues 计算 SHAP 值
// 输入: featureValues [nSamples][nFeatures]float64
// 输出: contributions [nSamples][nFeatures + 1]float64
//       最后一列为 bias(base_score)
func (e *TreeExplainer) ShapleyValues(featureValues [][]float64) ([][]float64, error)

// InteractionValues 计算 SHAP 交互值
// 输出: [nSamples][nFeatures][nFeatures]float64
func (e *TreeExplainer) InteractionValues(featureValues [][]float64) ([][][]float64, error)

7.2 特征重要性

// importance/importance.go
package importance

type ImportanceType int
const (
    ImportanceWeight     ImportanceType = iota // 特征被使用的次数
    ImportanceGain                             // 平均增益
    ImportanceTotalGain                        // 总增益
    ImportanceCover                            // 平均覆盖
    ImportanceTotalCover                       // 总覆盖
)

// FeatureImportance 特征重要性结果
type FeatureImportance struct {
    Type   ImportanceType
    Scores []float64 // [numFeatures]
    Names  []string  // 特征名(如果有)
}

// Compute 从 ForestIR 计算特征重要性
func Compute(forest *tree.ForestIR, impType ImportanceType, featureNames []string) *FeatureImportance

计算来源:

  • Weight: 遍历所有树的 SplitFeature 数组,统计每个特征被用作分裂的次数
  • Gain/TotalGain: 读取 XGBoost 二进制中的 loss_chg(损失变化)/ split_gain
  • Cover: 读取 sum_hess(Hessian 和 = 覆盖率)
这需要在 IO 层将 loss_chgsum_hess 解析进 TreeIR

// tree/ir.go 扩展
type TreeIR struct {
    // ...
    SplitGain []float64 // [numNodes] 分裂增益
    SumHess   []float64 // [numNodes] Hessian 和
}

7.3 模型可视化

// viz/viz.go

// DumpText 输出类似 XGBoost dump 的文本树结构
func DumpText(forest *tree.ForestIR, featureNames []string) string

// DumpJSON 输出可被前端渲染的 JSON 树结构
func DumpJSON(forest *tree.ForestIR, featureNames []string) ([]byte, error)

// DumpDOT 输出 Graphviz DOT 格式
func DumpDOT(forest *tree.ForestIR, featureNames []string) string

7.4 近似贡献值(Saabas 方法)

作为 SHAP 的轻量替代方案:

// shap/approximate.go

// ApproximateContributions 使用 Saabas 方法近似特征贡献
// 原理:沿树路径,每个节点将分裂前后的预测差分配给对应特征
// 比 Tree SHAP 快 10x+,但仅适用于独立特征假设
func (e *TreeExplainer) ApproximateContributions(featureValues [][]float64) ([][]float64, error)

7.5 Phase 3 产出

  • [ ] shap/ 包:Tree SHAP 贡献值
  • [ ] shap/ 包:交互值
  • [ ] importance/ 包:5 种特征重要性
  • [ ] viz/ 包:模型文本/JSON/DOT 输出
  • [ ] 与 XGBoost Python SHAP 结果的一致性测试
  • [ ] 文档:SHAP 使用示例
---

八、Phase 4:训练能力(质变期) 目标版本 v2.0.0

目标

实现 GBDT 的基本训练能力,使 leaves 从纯预测库变为可训练框架。

8.1 训练架构设计

参考 XGBoost 的分层架构:

┌─────────────────────────────────────────────────┐
│                   Trainer                       │
│  (训练循环、CV、early stopping、回调)             │
├─────────────────────────────────────────────────┤
│            GradientBooster (GBM)                 │
│  gbtree / dart / gblinear                        │
├──────────────────┬──────────────────────────────┤
│   Objective      │       Metric                 │
│  (损失+梯度/hess) │  (评估指标)                  │
├──────────────────┴──────────────────────────────┤
│              TreeBuilder                         │
│  (直方图构建、最佳分裂搜索)                        │
├─────────────────────────────────────────────────┤
│              GoMLX 计算后端                       │
│  (梯度计算、直方图聚合、张量加速)                   │
└─────────────────────────────────────────────────┘

// train/trainer.go
package train

type Trainer struct {
    booster  Booster
    objective objective.Objective
    metrics  []metrics.Metric
    callbacks []Callback
}

// Booster 接口
type Booster interface {
    // UpdateOneIter 一轮迭代
    UpdateOneIter(data *DMatrix, gradients, hessians []float64) error
    
    // Predict 当前模型预测
    Predict(data *DMatrix) ([]float64, error)
    
    // Save/Load 模型序列化
    Save(writer io.Writer) error
    Load(reader io.Reader) error
}

// DMatrix 训练数据容器
type DMatrix struct {
    Data    *mat.DenseMat
    Labels  []float64
    Weights []float64
    Groups  []int // 排序任务
}

8.2 Objective 目标函数

// objective/objective.go
package objective

type Objective interface {
    // GetGradient 计算梯度和 Hessian
    // pred: 当前预测值 [nSamples]
    // label: 真实标签 [nSamples]
    // 返回: grad [nSamples], hess [nSamples]
    GetGradient(pred, label []float64) (grad, hess []float64, err error)
    
    // PredTransform 预测值到最终输出
    PredTransform(rawPred []float64) []float64
}

首批实现(共 6 个最常用目标函数):

目标函数梯度Hessian
reg:squarederrorpred - label1
reg:logisticsigmoid(pred) - labelsigmoid(pred)*(1-sigmoid(pred))
binary:logisticsigmoid(pred) - label同上
reg:gamma1 - label/exp(pred)label/exp(pred)
multi:softmaxsoftmax[i] - (label==i)softmax[i]*(1-softmax[i])
rank:pairwiseLambdaRank pairwise 梯度LambdaRank pairwise Hessian

8.3 TreeBuilder — 直方图方法

参考 XGBoost 的 hist 树构建方法(最常见、最高效的 CPU 方法):

// train/treebuilder.go
package train

type TreeBuilder struct {
    maxDepth     int
    maxLeaves    int
    minChildWeight float64
    regLambda    float64 // L2 正则
    colsampleBytree float64
    // GoMLX 加速:用 GoMLX 做直方图聚合 + 分裂增益计算
    useGoMLX     bool
}

// BuildTree 构建一棵回归树
// gradients, hessians: 样本梯度/二阶梯度 [nSamples]
// 返回: TreeIR
func (tb *TreeBuilder) BuildTree(
    data *mat.DenseMat,
    gradients, hessians []float64,
) (*tree.TreeIR, error)

GoMLX 加速点

  • 直方图构建:ReduceSum 按特征值分箱聚合梯度
  • 分裂增益计算:向量化 gain = G²/(H+λ) 评估所有候选分裂点
  • 最佳分裂搜索:ReduceMax 找最大增益

8.4 DART Booster

DART (Dropouts meet Multiple Additive Regression Trees) 的推理已在 leaves 中支持(从 XGBoost 训练好的模型加载)。现在补充训练逻辑:

// train/dart.go

type DartBooster struct {
    trees    []*tree.TreeIR
    weights  []float64  // 每棵树的权重
    
    // DART 特有
    dropRate     float64  // 每次迭代丢弃比例
    skipDrop     float64  // 跳过丢弃概率
    normalizeType string   // tree / forest
}

8.5 训练 API

// 基础用法
trainer := train.NewTrainer(train.Config{
    Objective:  objective.RegSquareError,
    Booster:    train.BoosterGBTree,
    NumRound:   100,
    MaxDepth:   6,
    LearningRate: 0.3,
    Metrics:    []metrics.Metric{metrics.RMSE, metrics.MAE},
})

// 训练
err := trainer.Fit(dtrain)
if err != nil { ... }

// 保存模型
err = trainer.Save("output.model")

// 加载预测(使用现有 Ensemble API)
model, err := leaves.XGEnsembleFromFile("output.model", true)
pred := model.PredictSingle(fvals, 0)

8.6 交叉验证与早停

// train/cv.go

type CVResult struct {
    TestMetricMean []float64  // 每轮验证集均值
    TestMetricStd  []float64  // 每轮验证集标准差
    BestIteration  int
    BestScore      float64
}

// CrossValidate K 折交叉验证
func CrossValidate(config train.Config, data *DMatrix, folds int) (*CVResult, error)

// EarlyStopping 回调
type EarlyStopping struct {
    MetricName   string
    StoppingRounds int
    Maximize     bool
}

8.7 Phase 4 产出

  • [ ] train/ 包:训练循环、Trainer、Booster 接口
  • [ ] objective/ 包:6 个核心目标函数
  • [ ] train/treebuilder.go:基于直方图的树构建器
  • [ ] GoMLX 加速的直方图聚合和分裂搜索
  • [ ] 与 XGBoost 训练结果的等价性测试(相同数据、相同参数)
  • [ ] train/dart.go:DART 训练器
  • [ ] train/cv.go:交叉验证 + 早停
  • [ ] 文档:训练使用指南
---

九、Phase 5:高性能与分布式(成熟期) 目标版本 v3.0.0

目标

实现 GPU 原生推理、量化推理、分布式部署,使 leaves 成为生产级可部署的 GBRT 框架。

9.1 量化推理

将树模型量化为 Int8/Int4 精度,减少模型体积和推理内存:

// quantize/quantize.go
package quantize

// QuantizedForest 量化森林模型
type QuantizedForest struct {
    // 阈值和叶子值量化为 int8
    Thresholds []int8
    LeafValues []float64  // 保留 float 精度(叶子值影响大)
    Scales     []float64  // 量化的 scale/zero_point
}

// 利用 GoMLX 的 QuantizedDense 融合操作模式

9.2 ONNX 互操作

利用 onnx-gomlx 将 XGBoost ONNX 模型转为 GoMLX 图:

XGBoost 训练 → ONNX-ML 导出 → onnx-gomlx 解析 → GoMLX 图执行

这为 leaves 提供了一条获取 GPU 推理的捷径——只要 XGBoost 模型能导出 ONNX,就能通过 GoMLX 的 XLA 后端获得 GPU 加速。

// io/onnx.go

// XGEnsembleFromONNX 从 ONNX 文件加载 XGBoost 模型
func XGEnsembleFromONNX(path string) (*Ensemble, error)

9.3 GPU 直方图训练

利用 GoMLX XLA GPU 后端,将 Phase 4 的直方图构建和分裂搜索放到 GPU 上:

// train/gpu_builder.go

type GPUTreeBuilder struct {
    // 使用 GoMLX XLA 后端
    // 参考 XGBoost device_hist.cu 的 GPU 直方图算法
    // 核心:并行归约构建直方图 → 并行扫描找到最佳分裂
}

关键优化:

  • GPU 上多特征直方图并行构建
  • 使用 ReduceScatter 做分布式直方图归约
  • 利用 XLA 的 kernel fusion 减少内存带宽

9.4 WASM 部署

利用 GoMLX 的 SimpleGo 后端(纯 Go,可编译到 WASM):

// tree/wasm_engine.go

// Go 编译: GOOS=js GOARCH=wasm go build
// 结果可在浏览器中运行 leaves 推理
func NewWASMEngine(forest *ForestIR) (Engine, error) {
    return NewGoMLXEngine(forest, gomlx.WithBackend(gomlx.SimpleGoBackend))
}

9.5 分布式推理

基于 GoMLX 的 SPMD / AutoSharding:

  • 模型分片:不同 GPU 加载不同的树子集
  • 数据分片:不同节点处理不同 batch
  • 结果集会:AllReduce 汇总
// distributed/distributed.go

type DistributedPredictor struct {
    // 使用 gomlx/pkg/core/distributed
    // 自动将 ForestIR 分片到多个设备
    // 利用 GoMLX 的 SPMD 模型统一管理数据并行和模型并行
}

9.6 Phase 5 产出

  • [ ] quantize/ 包:Int8 量化推理
  • [ ] io/onnx.go:ONNX 模型加载
  • [ ] train/gpu_builder.go:GPU 直方图训练
  • [ ] WASM 部署指南和 Dockerfile
  • [ ] distributed/ 包:分布式推理
  • [ ] 性能报告:GPU vs CPU、量化前后对比
  • [ ] 部署文档完整
---

十、风险与对策

风险等级对策
GoMLX API 不稳定 (v0.x)使用 facade 模式隔离 GoMLX 依赖,tree/gomlx_engine.go 单一入口
GoMLX While 循环性能不达预期默认对 depth ≤ 8 使用循环展开;While 作为 fallback
XGBoost 二进制格式变动已有成熟解析器;新增格式通过 feature flag 控制
训练结果与 XGBoost 不一致使用相同随机种子、数据、超参做等价性测试;容忍浮点误差
Go 版本升级阻力 (1.12 → 1.25)Phase 0 仅升级到 Go 1.21(兼容性桥);Phase 1 再升级到 1.25
GPU XLA 后端部署复杂度提供静态链接 PJRT 的构建标签,生成无外部依赖二进制
贡献者生态不足每个 Phase 产出独立可用版本,降低贡献门槛
与上游 XGBoost/LightGBM 的兼容性维护将兼容性测试加入 CI,定期运行
---

十一、里程碑与时间线

里程碑版本核心交付建议工期
M0: 架构重构v0.9.0包拆分、IR 定义、Engine 接口、Native 引擎2-4 周
M1: GoMLX 推理v1.0.0GoMLX XLA/SimpleGo 引擎、GPU 推理4-8 周
M2: 格式补全v1.1.0JSON 模型、12+ 评估指标、分类特征、多输出树3-6 周
M3: 可解释性v1.2.0Tree SHAP、特征重要性、模型可视化4-6 周
M4: 训练v2.0.0GBDT 训练器、6 个目标函数、CV/早停8-12 周
M5: 高性能v3.0.0量化推理、ONNX 互操作、GPU 训练、WASM6-10 周
---

附录 A:与 XGBoost 的功能对应表

XGBoost 功能leaves 目标 Phase实现方式
gbtree 推理✅ 已有xgEnsemble + lgTree
dart 推理✅ 已有xgEnsemble (weight_drop 已处理)
gblinear 推理✅ 已有xgLinear
稠密/CSR 输入✅ 已有DenseMat / CSRMat
缺失值处理✅ 已有NaN → default_left
sigmoid/softmax 变换✅ 已有transformation.Transform
GPU 推理Phase 1GoMLX XLA 后端
XGBoost JSON 模型Phase 2io/xgb_json.go
评估指标Phase 2metrics/
原生分类特征Phase 2TreeIR.IsCategorical
多输出树Phase 2TreeIR.OutputDim
Tree SHAPPhase 3shap/
特征重要性Phase 3importance/
模型 dump/可视化Phase 3viz/
训练 (gbtree)Phase 4train/
DART 训练Phase 4train/dart.go
目标函数 (6 个)Phase 4objective/
交叉验证Phase 4train/cv.go
早停Phase 4train/cv.go (callback)
量化推理Phase 5quantize/
ONNX 互操作Phase 5io/onnx.go
GPU 训练Phase 5train/gpu_builder.go
WASM 部署Phase 5GoMLX SimpleGo 后端
分布式推理Phase 5distributed/ + GoMLX SPMD

附录 B:GoMLX 集成关键代码路径

leaves API 调用
    │
    ▼
Ensemble.PredictDense(...)
    │
    ├── [backend=Native] ──► tree.NativeEngine.predictInner()
    │                              │
    │                              └── 逐树逐样本循环(当前逻辑)
    │
    └── [backend=GoMLX] ──► tree.GoMLXEngine.predictDense()
                                   │
                                   ├── 1. 构造 ForestIR → GoMLX 常量
                                   ├── 2. 构建计算图 (buildForestGraph)
                                   │      ├── For each tree: buildTreeGraph
                                   │      │     ├── 编码树结构为 Const
                                   │      │     ├── 展开遍历循环 (max_depth 层)
                                   │      │     └── Gather 叶子值
                                   │      └── Stack + ReduceSum
                                   ├── 3. graph.Exec.Compile() (首次/缓存命中)
                                   └── 4. exec.Call(params) → Tensor → []float64

---

暂无表态
💬 讨论回复 (2)
✨步子哥 #1 2026-06-13 07:54

Born 集成架构分析

1. 完整文件清单

直接依赖 github.com/born-ml/born(4 个源文件)

文件角色
c:\GitHub\leaves\tree\born_engine.goBorn 推理引擎入口,borncpu.New() + 调用 tree/born
c:\GitHub\leaves\tree\born\tensors.goBackend 类型别名 + tensor.FromSlice 辅助
c:\GitHub\leaves\tree\born\forest.goGBDT 张量算法:WalkTreeBatchForestMarginsDense
c:\GitHub\leaves\treebuilder\hist_accel_born.go训练直方图增益扫描(//go:build born_train),绕过 tree/born

tree/born 子包(3 个源文件,无 born-ml import 的 IR 层)

文件角色
c:\GitHub\leaves\tree\born\ir.goTreeData / ForestData / DecisionFunc
c:\GitHub\leaves\tree\born\forest.go见上
c:\GitHub\leaves\tree\born\tensors.go见上

Born 桥接 / 转换 / 选型(无 born-ml import)

文件角色
c:\GitHub\leaves\tree\born_convert.goForestIRborn.ForestData 深拷贝 + MakeDecisionFn
c:\GitHub\leaves\tree\decision.goTreeDecision — Born 标量回退路径的 categorical 逻辑
c:\GitHub\leaves\tree\backend_select.goBackend* 常量、SelectBackendBornSupports
c:\GitHub\leaves\tree\engine.goEngine 接口、Backend 枚举
c:\GitHub\leaves\tree\forest_predict.goNative 路径 margin 聚合(与 born/forest.go 逻辑平行)

引擎工厂 / API 层

文件角色
c:\GitHub\leaves\model\build.goBackendBornCPU/GPUNewBornEngine,否则 NewNativeEngine
c:\GitHub\leaves\model\backend.goModelIRModelCapstree.ResolveBackend
c:\GitHub\leaves\bridge.goEngineOptions{Backend, Workload}model.NewEngineWithHint
c:\GitHub\leaves\io\io.go / load.go重导出 Backend* 常量
c:\GitHub\leaves\predict\engine.go统一 predict.Engine 接口

Native 对照路径(golden,非 Born 但架构对称)

文件角色
c:\GitHub\leaves\tree\native.goNativeEngine,调用 ForestMargins
c:\GitHub\leaves\tree\ir.goForestIR / TreeIR 领域 IR

训练 Born 桩 / 测试

文件角色
c:\GitHub\leaves\treebuilder\hist_accel_stub.go!born_train 时回退 CPU
c:\GitHub\leaves\treebuilder\hist.go调用 scanHistGains
c:\GitHub\leaves\tree\born_test.goBorn vs Native smoke/parity
c:\GitHub\leaves\born_parity_test.go全库 parity 门禁
c:\GitHub\leaves\tree\backend_select_test.go后端选型测试
c:\GitHub\leaves\treebuilder\t4_born_test.gohist Born 增益测试
c:\GitHub\leaves\backend_bench_test.goNative/BornCPU/BornGPU benchmark
c:\GitHub\leaves\delegation_test.goNative vs BornCPU 委托对比
c:\GitHub\leaves\io\backend_test.goBackendAuto 加载测试

配置 / 文档

文件角色
c:\GitHub\leaves\go.modreplace github.com/born-ml/born => C:/GitHub/born
c:\GitHub\leaves\AGENTS.md, NOTES.md, README.md, 演进计划.md架构决策文档

未接线 / 遗留

  • BornConfig.UseGPUborn_engine.go 中被 _ = cfg.UseGPU 丢弃,始终 borncpu.New()BackendBornGPU 选型存在但 GPU 未实现
  • tree/born/forest.goHasNaN 无调用方(死代码)
  • glob 显示 tree/gomlx_engine.go 存在但当前环境读不到(可能未跟踪);AGENTS 已声明 GoMLX 废弃
---

2. Import 依赖图

github.com/born-ml/born
├── backend/cpu  ─────────────────────────────────────────┐
└── tensor       ─────────────────────────────────────────┤
                                                          │
    tree/born/tensors.go  ◄───────────────────────────────┤
    tree/born/forest.go   ◄───────────────────────────────┤
    tree/born_engine.go   ◄───────────────────────────────┤
    treebuilder/hist_accel_born.go  (born_train tag) ◄────┘

github.com/dmitryikh/leaves/tree/born
    tree/born/ir.go          (无 born-ml import)
    tree/born_convert.go  ◄── tree/born
    tree/born_engine.go   ◄── tree/born + born-ml

github.com/dmitryikh/leaves/tree  (领域 IR + 双引擎)
    ir.go, decision.go, forest_predict.go, native.go
    born_convert.go, born_engine.go, backend_select.go, engine.go
         │
         ├── model/build.go ──► predict.Engine
         ├── bridge.go (EngineOptions)
         └── io/load.go (Backend 重导出)

treebuilder/hist_accel_born.go  ──► born-ml 直接(不经 tree/born)
treebuilder/hist.go             ──► scanHistGains() 抽象

关键边界tree/ 不依赖 train/tree/born/ 不 import tree(避免循环),因此需要 TreeData/ForestData 快照 + DecisionFunc 回调注入 categorical 逻辑。

---

3. 各层抽象:leaves ↔ born-ml/born

抽象作用
L0 领域 IRForestIR / TreeIR (tree/ir.go)框架无关 GBDT 中间表示(LGB/XGB/sklearn 加载目标)
L1 快照 IRborn.TreeData / born.ForestData (tree/born/ir.go)TreeIR/ForestIR 字段镜像,解 import cycle
L2 转换ForestToBorn / TreeToBorn (tree/born_convert.go)引擎构造时 深拷贝 全部 slice
L3 决策注入DecisionFunc + MakeDecisionFnTreeDecisionCatSmall / 大 bitset 分类分裂回退到 tree/decision.go
L4 GBDT 算法tree/born/forest.go在 Born 张量上做 batch 树遍历、margin 聚合(非 born-ml 内置能力)
L5 张量辅助tree/born/tensors.goBackend = borncpu.Backend 别名 + FromSlice/Gather 封装
L6 引擎BornEngine vs NativeEngine同构 tree.Engine 接口;构造时二选一,非运行时互调
L7 选型Backend + SelectBackend + BornSupportsworkload 驱动的引擎选择
L8 训练hist_accel_born.go独立使用 born-ml 做 hist 增益向量化,与推理 tree/born 无关
born-ml/born 本身:通用张量库(tensor.TensorGatherMulDiv 等),无 GBDT / 树遍历抽象。leaves 在 tree/born/ 自行实现 GBDT 所需原语。

---

4. tree/born/ 子包:是 facade 吗?

不完全是。 三层混合:

1. 薄 facadetensors.go):type Backend = borncpu.BackendmustFromF64 等 — 可合并进 born_engine.go。 2. 适配器 IRir.go):TreeData/ForestDataTreeIR/ForestIR 的平行 struct — 为解循环,不是 born-ml API 的 facade。 3. 算法层forest.go,~487 行):WalkTreeBatchForestMarginsDense、叶值 gather — 核心业务逻辑,不应称为 facade。

设计意图(见 ir.go 注释):*「与 tree.TreeIR 解耦,避免 import cycle」*。

---

5. BornEngine vs NativeEngine 委托模式

引擎级:互斥,非委托

model/build.go 在构造时按 Backend 选择 一个 引擎:

``34:38:c:\GitHub\leaves\model\build.go case tree.BackendBornCPU, tree.BackendBornGPU: cfg := &tree.BornConfig{UseGPU: backend == tree.BackendBornGPU} return tree.NewBornEngine(ir.Forest, transform, outputType, ir.NOutputGroups, cfg) default: return tree.NewNativeEngine(ir.Forest, transform, outputType, ir.NOutputGroups), nil

两者平行实现同一 `tree.Engine` / `predict.Engine` 接口;`NativeEngine` 是 golden,`BornEngine` 是加速路径。

### Born 路径内部:张量 + Native 混合

`ForestMarginsDense` 对每棵树分支:

- **张量 batch**:数值分裂 + CatOneHot(`TreeNeedsNativeWalk == false`)→ `WalkTreeBatch` + Born `Gather`
- **标量回退**:CatSmall / 大 bitset → 逐行 `WalkTreeScalar(t, fvals, decide)`,`decide` 来自 `TreeDecision(t.TreeIR, ...)`

Leaf index 预测(`TransformLeafIndex`)在 `BornEngine` 中逐树、逐样本混合上述两路。

CSR 输入:`BornEngine.PredictCSR` 先 **densify** 再 `PredictDense`(无 Born 稀疏路径)。

### 选型级:`BornSupports` 整森林 gate

含 `CatSmall` 时 `BackendBornCPU` 自动选型返回 false;但 **显式** 指定 `BackendBornCPU` 仍可创建 `BornEngine`(per-tree 标量回退),见 `born_parity_test.go` KDDCup99 注释。

---

## 6. ForestData/TreeData vs ForestIR/TreeIR

| 维度 | `ForestIR` / `TreeIR` | `born.ForestData` / `born.TreeData` |
|------|----------------------|-------------------------------------|
| 位置 | `tree/ir.go` | `tree/born/ir.go` |
| 额外字段 | `Name`, `NumParallelTree`, `SplitGain`, `SumHess`, `NumLeaves` | 无(仅 Born 推理所需子集) |
| 生命周期 | 加载/训练全程 | `NewBornEngine` 时深拷贝,引擎内只读 |
| 用途 | 领域 IR、Native、explain、io | Born 张量图输入 |

转换在 `born_convert.go` 逐字段 `append([]T(nil), ...)` — **O(n) 内存复制**,引擎持有双份树数据(`BornEngine.forest` + `BornEngine.bornForest`)。

---

## 7. 冗余转换 / 包装类型

### 明确冗余

| 冗余 | 位置 A | 位置 B | 说明 |
|------|--------|--------|------|
| **Margin 聚合** | `tree/forest_predict.go` `ForestMargins` | `tree/born/forest.go` `ForestMarginsDense` | `adjustNEstimators`、`classBaseScore`、`forestTreeClassIndex`、`scaleExceptBase` 几乎同构 |
| **树遍历** | `forest_predict.go` `walkTree` | `born/forest.go` `WalkTreeScalar` | 相同 DFS 逻辑 |
| **叶值读取** | `treeLeafScalar/Vector` (两处) | 同上 | 重复 |
| **零值判定** | `decision.go` `isZeroFval` | `born/forest.go` `isZeroFval` | 第三份在 `explain/treeutil.go` |
| **IR 快照** | `TreeIR`/`ForestIR` | `TreeData`/`ForestData` + `ForestToBorn` | 字段镜像 + 深拷贝 |
| **引擎状态** | `BornEngine.forest *ForestIR` | `BornEngine.bornForest ForestData` | 双份持有;leaf index 路径仍读 `forest.Trees` |
| **Decision 闭包** | `[]born.DecisionFunc` 每棵树一个 | 闭包捕获 `*TreeIR` | 与 `bornForest` 中 `TreeData` 并行存在 |
| **Backend 包装** | `born.Backend = borncpu.Backend` | 可直接用 borncpu | 一行别名 |
| **GPU 占位** | `BackendBornGPU` / `BornConfig.UseGPU` | 实际只用 CPU | 虚假抽象层 |

### 非冗余(应保留)

- **`ForestIR` / `TreeIR`**:框架无关领域 IR,加载、explain、训练、Native golden 共用
- **`tree.Engine` / `predict.Engine`**:可插拔后端契约
- **`NativeEngine`**:正确性基准与 categorical 完整实现
- **`DecisionFunc` 注入模式**:在 Born 未实现 CatSmall/bitset 张量化前必要
- **`backend_select.go`**:部署/workload 策略
- **`tree/born/forest.go` 算法本身**:born-ml 不提供 GBDT,此层不可删,只能合并位置

---

## 8. 「完全拥抱 Born、去掉中间层」建议

### 可移除 / 合并

1. **`TreeData` / `ForestData` + `born_convert.go`**  
   - 方案:把 `tree/born/*.go` 移入 `tree` 包(如 `tree/born_forest.go`),直接读 `*ForestIR`/`*TreeIR`  
   - 前提:接受 `tree` 包 import `born-ml`(已有 `born_engine.go` 先例)  
   - 收益:消除深拷贝、双份 IR、转换维护成本

2. **`ForestMargins` vs `ForestMarginsDense` 重复**  
   - 方案:提取共享 `forestMarginsCore`(接受 interface 或函数参数),Native 单样本 / Born batch 共用迭代逻辑  
   - 或:Native 小 batch 也走 Born(仅保留 Native 作 golden 测试对照)

3. **`born.Backend` 类型别名 + `tensors.go` 小 helper**  
   - 可内联到 `born_engine.go` / `forest.go`,减文件数

4. **`BornEngine.forest` 冗余引用**  
   - leaf index 改为只用 `bornForest` + `decisions`,或统一 IR 后只保留一份

5. **`BackendBornGPU` / `UseGPU` 占位**  
   - 未实现前应删除或明确 stub,避免误导(当前 `backend_test` 期望 GPU 选型但引擎不接线)

6. **`HasNaN`(born/forest.go)**  
   - 未使用,可删

7. **训练路径统一**  
   - `hist_accel_born.go` 直接 import born-ml,与 `tree/born` 分裂;可共用 `tree/born/tensors.go` 的 helper(若保留子包)

### 应保留

| 组件 | 理由 |
|------|------|
| **`ForestIR` / `TreeIR`** | 领域 IR;Born 只是执行后端 |
| **`NativeEngine`** | Golden、完整 categorical、零 Born 依赖部署 |
| **`BornEngine` 作为 `Engine` 实现** | 接口清晰,benchmark/parity 可切换 |
| **`tree/born/` 算法层(或合并后的等价代码)** | born-ml 无 GBDT;必须在 leaves 实现 batch 树遍历 |
| **`DecisionFunc` + `TreeDecision`(至 CatSmall 张量化完成)** | 分类特征正确性 |
| **`backend_select.go`** | Auto/WASM/GPU batch 策略 |
| **`born_train` build tag** | 可选训练加速,默认零依赖 |
| **`model/build.go` 工厂** | 统一入口 |

### 「完全拥抱」的 realistic 终点

不是删掉所有 wrapper 直接用 born-ml API(born 没有树模型),而是:

ForestIR (保留) ↓ 无拷贝指针 Born GBDT kernels (合并进 tree 或保留 tree/born) ↓ born-ml tensor (Gather/Mul/…) + NativeEngine (golden + categorical fallback,长期可仅测试用)
---

## 9. 架构示意

mermaid flowchart TB subgraph domain ["领域层 (保留)"] IR["ForestIR / TreeIR"] Native["NativeEngine\nforest_predict.go"] end

subgraph born_layer ["Born 集成层 (可瘦身)"] Conv["born_convert.go\nForestToBorn 深拷贝"] Snap["TreeData / ForestData"] Algo["tree/born/forest.go\nWalkTreeBatch / ForestMarginsDense"] Eng["BornEngine"] end

subgraph external ["github.com/born-ml/born"] CPU["backend/cpu"] T["tensor"] end

subgraph train ["训练 (独立)"] Hist["hist_accel_born.go\nborn_train tag"] end

IR --> Native IR --> Conv --> Snap --> Algo IR --> Eng Snap --> Eng Eng --> Algo Algo --> T Algo --> CPU Eng --> CPU Hist --> T Hist --> CPU decision["tree/decision.go\nTreeDecision"] -.->|DecisionFunc 回调| Algo `

---

10. 要点总结

  • Born 集成是 四层:born-ml 张量 → tree/born GBDT 算法 → born_convert/born_enginemodel/io 选型。
  • tree/born/ 不是 born-ml 的 facade,而是 GBDT-on-tensor 算法包 + 解循环 IR 快照;仅 tensors.go 偏 facade。
  • BornEngineNativeEngine 构造期互斥;Born 内部对复杂 categorical per-tree/per-row 回退 Native 决策
  • 最大冗余:双份 IR(ForestIR + ForestData)双份 margin 逻辑(forest_predict vs born/forest)
  • BackendBornGPU 已选型但未实现;BornConfig.UseGPU 被忽略。
  • 训练加速 (hist_accel_born.go) 与推理 (tree/born`) 共享 born-ml 依赖但代码路径分离

👍 1
✨步子哥 #2 2026-06-15 15:00
👍 1
推荐

🌟 智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

🎁 领取 2000万 Tokens