从 TensorSharp 视角解读 Ternary Bonsai 2 27B:当 1.72 比特的权重遇上 Hadamard 变换

本文基于 PrismML 发布资料、CloudNavi 部署指南(2026-09-19)与 TensorSharp 官方模型文档(docs/models/bonsai2.md,集成验证日期 2026-09-22)整理,聚焦 TensorSharp 作为推理引擎是怎么"接住"这个模型的,不谈 Ollama / llama.cpp 路线。
01-公众号头图-三元盆栽27B


1. 引言:一个"普通运行时跑不了"的模型

2026 年 9 月 17 日,PrismML(出自 Caltech 的研究团队,获 Khosla Ventures、Cerberus、Google 支持)发布了 Ternary Bonsai 2 27B:一个把 27.36B 参数压进 5.9GB 文件的三元量化多模态模型,在 20 项基准上保留了基座模型 98.2% 的分数。

数字很诱人,但它有一个苛刻的前提:原版 llama.cpp 与现行 Ollama 都无法运行它。权重不在普通的数值基底上,运行时必须执行 activation 侧的 Walsh–Hadamard 变换,而这个变换至今没有合并进上游 llama.cpp。

这就给推理引擎出了一道工程题:不魔改底层库、不重新量化,怎么把一个"带旋转基底"的低比特模型跑对、跑得可验证?TensorSharp 的集成方案是一个值得拆开看的样本,本文就沿着它展开。


2. 模型速览:压缩了什么,保留了什么

2.1 基本规格

项目 内容
参数量 273.6 亿(语言 243.5 亿 · 视觉塔 4.7 亿 · 嵌入与 LM head 25.4 亿)
架构 混合注意力(线性约 75% · 全注意力约 25%),SwiGLU、RoPE、RMSNorm
权重格式 三元 g128 + FP16 分组缩放,分块 Hadamard 旋转
有效位宽 真三元 1.72 bit/权重;发布的 PTQ1_0 为 1.76 bit
最大上下文 262,144 token
输入 / 输出 文本 + 图像输入 / 文本输出
许可 Apache 2.0

模型本质上是 PrismML 对 Qwen 系列稠密基座的三元量化版:权重只用 −1、0、+1 三个值存放,配合 FP16 分组缩放,FP16 下 53.8GB 的权重被压到约九分之一。

2.2 基准保留率(官方公布值)

20 项基准在思考模式下平均 83.9 分,相对基座 27B 的 85.4 分保留 98.2%。更值得看的是分项:

能力 Bonsai 2 27B 基座 27B 保留率
知识与推理 83.95 86.66 96.9%
数学 96.57 97.06 99.5%
编码 81.58 82.17 99.3%
代理与工具调用 77.57 79.74 97.3%
指令遵循 82.66 81.25 102.0%
视觉 78.59 81.64 96.3%
综合 83.9 85.4 98.2%

数学与编码几乎无损,指令遵循甚至反超基座;知识与推理、视觉各下降约 3%。误差会在长链路代理任务里累积,这个区间需要留意。

2.3 官方发布的三种文件

格式 体积 特点
GGUF PTQ1_0 5.93GB 体积最小,每权重 1.76 比特
GGUF PQ2_0 7.25GB 解包更轻,提示词处理更快(demo 默认)
MLX 2bit 8.49GB 面向 Apple Silicon,含全精度视觉塔,可用于原版 MLX

3. 核心难点:权重不只是三元数

这是理解整个集成工作的关键,TensorSharp 文档开篇就说得很直白:

Merely interpreting its weights as ordinary ternary numbers produces the wrong network: projection inputs and embedding outputs must also receive the declared transforms.

(仅仅把权重当作普通三元数去解释,得到的是一个错误的网络:投影的输入和嵌入的输出必须施加声明的变换。)

换句话说,单做"把权重换成 −1/0/+1"这一步并不够:权重被存放在一个旋转基底上,运行时必须配合执行逆变换,网络才是原网络。具体来说:

  • 嵌入查表后,要经过逆变换;
  • 每个低比特投影在矩阵乘之前,要对输入激活做变换;
  • 输出 head 之前还要再做一次正变换。

如果运行时"看起来能加载、能出 token",却跳过了这些变换,它输出的其实是一个从未训练过的网络的结果。这正是 CloudNavi 文章警告的陷阱:旧格式文件在某些原版构建上会静默加载并输出乱码;低比特格式没有被原生支持时,有的运行时会静默按高精度反量化运行,输出对了,但速度与内存优势全部消失。


4. TensorSharp 的接入方案:转码而非魔改

4.1 设计哲学:不 patch ggml

TensorSharp 对这两种自定义格式的处理方式很克制:

  • GGUF 读取器把 PQ2_0 识别为张量类型 142、PTQ1_0 识别为 143;
  • TensorSharp 自有的 native 代码把它们的块打包无损展开为上游 GGML 的 Q2_0 块;
  • 保留权重表示值,不做再量化;
  • 从不改写原始 GGUF 文件,也不给 ggml 打补丁去引入发行方私有的张量类型。

代价是内存占用不再等于文件体积:无损展开会增大量化负载,PQ2_0 约多 6%,PTQ1_0 约多 29%。这是用内存换兼容性的明确取舍。

4.2 变换契约:prism.hadamard.*

模型文件里带有一组 prism.hadamard.* 元数据,声明了:变换版本、归一化的 Sylvester-Walsh-Hadamard 变换、输入轴、块大小(本组模型为 1,024 元素/块)、显式 ±1 符号表,以及哪些权重名要做正变换或逆变换。

数学上,对一个投影输入 x,每个块计算:

H(D·x) / sqrt(block_size)

其中 D 是存储的 ±1 符号对角矩阵,H 是未归一化 Hadamard 矩阵。嵌入行则用逆序:

D·(H·x) / sqrt(block_size)

此外,分组 GDN 元数据还会把 SSM 输出输入按发行方的 head 顺序重排后再变换;融合的 QKV 和 gate/up 权重保留其原投影各自的变换。

TensorSharp 在加载权重之前校验完整的契约,遇到未知的变换布局直接拒绝:宁可跑不起来,也不静默跑错。

4.3 代码入口

整个集成散布在五个文件里,职责划分清晰:

文件 职责
BonsaiHadamardMetadata.cs 校验元数据,实现托管侧的逆嵌入变换
ModelBase.Bonsai.cs 模型加载期的转码与变换注册
QuantizedWeight.Bonsai.cs 权重缓存键的变换注册,释放时注销
bonsai_quant.cpp 精确的块解码/转码,不修改上游 GGML
ggml_ops_bonsai.cpp 把符号变换接入 TensorSharp 的 native 计算图路径

4.4 网络执行流程

token ID -> 低比特嵌入行 -> 逆符号 Hadamard 变换
  -> 64 个混合 Qwen 3.5 层:
       RMSNorm -> attention 或 GatedDeltaNet -> 残差
       RMSNorm -> 稠密门控 FFN -> 残差
       (声明的低比特投影会旋转其输入激活)
  -> RMSNorm -> 符号 Hadamard 变换 -> 低比特输出 head -> logits

底层架构上,两个 GGUF 文件声明 general.architecture=qwen35:64 层、hidden width 5,120、FFN width 17,408、词表 248,320、262,144 token 上下文。其中 48 层是递归的 GatedDeltaNet,16 层是全注意力;全注意力用 24 个 query 头 + 4 个 KV 头(宽度 256),GatedDeltaNet 用 16 个 key 组 + 48 个 value 头(宽度 128)。TensorSharp 复用了现成的 Qwen 3.5 实现(整模型 prefill/decode、逐序列的 KV 与递归状态、continuous batching),PRISM 变换被插入到这些既有路径里,直接继承其融合与状态管理。


5. 怎么跑:命令与限制

5.1 首版集成的硬性限制

初始集成只接受单设备 GGML backend。纯托管 CPU、直连 CUDA、MLX、张量并行配置都会被直接拒绝,而不是静默省略旋转变换。宁可拒绝加载,也不给出错误结果,这是整条集成线一致的原则。

5.2 运行示例

显式设置上下文上限,而不是按宣传的 262k 全量分配:

# CLI 推理
MAX_CONTEXT=4096 KV_CACHE_DTYPE=f16 \
dotnet run --project TensorSharp.Cli -c Release -- \
  --model /path/to/Ternary-Bonsai-2-27B-PQ2_0.gguf \
  --backend ggml_metal --input prompt.txt --max-tokens 128 \
  --temperature 0

# 带 OpenAI 兼容 API 的服务端,挂载视觉投影器
MAX_CONTEXT=4096 KV_CACHE_DTYPE=f16 \
dotnet run --project TensorSharp.Server.Host -c Release -- \
  --model /path/to/Ternary-Bonsai-2-27B-PTQ1_0.gguf \
  --mmproj /path/to/Ternary-Bonsai-2-27B-mmproj-Q8_0.gguf \
  --backend ggml_metal --host 127.0.0.1 --port 5000

配套的投影器文件有两个:mmproj-BF16(全精度)和 mmproj-Q8_0(更省内存),对比图像质量或内存占用时用 --mmproj 显式指定。


6. 可复现验证:数字摆到桌面上

TensorSharp 的验证流程覆盖:精确 raw-token 参照、单路/并行流式请求、算术、结构化 JSON、工具调用、两个投影器、解析器测试。基线是 PrismML 官方的 llama.cpp 分支(bdc23b56 提交)。文档特别强调:一个连 PQ2_0/PTQ1_0 都加载不了的上游 llama.cpp 构建,不构成有效的性能对比对象。

6.1 正确性(2026-09-22,Metal)

在未改动的上游 ggml 179b60f2 上:

  • 两种格式在 Metal 上逐 token 精确复现发行方基线的 4 条贪心提示(各 32 token);
  • 4 路并发序列的输出与串行完全一致,31 步融合 decode、零回退步;
  • 每种格式通过 21 项 HTTP 单路/并发对比,以及算术、JSON、工具调用、图像颜色检查(PQ2 + Q8_0 投影器、PTQ1 + BF16 投影器各测一组)。

注意这些是功能性冒烟测试,不是全面的质量评估;262k 上下文、CUDA、Vulkan、iOS、张量并行均未验证。

6.2 性能(Apple M5 Pro,48GB 统一内存)

格式 指标 (tokens/s) TensorSharp 发行方 llama.cpp
PQ2_0 Prefill 512 364.05 384.44
PQ2_0 Decode 64 25.50 26.96
PQ2_0 HTTP 并发 4 28.40 32.57
PTQ1_0 Prefill 512 364.49 357.67
PTQ1_0 Decode 64 25.53 26.31
PTQ1_0 HTTP 并发 4 28.17 16.15

模型级速率为三次预热运行(零上下文深度)的均值;HTTP 速率为三次运行的均值(每请求 64 生成 token)。两侧引擎均用 F16 KV 和 512 token 物理 prefill 批。

文档对结果的定性很直白:性能目标尚未达成。模型级 decode 比基线慢 5.4%(PQ2)和 3.0%(PTQ1),PQ2 并发 4 的 HTTP 吞吐低 12.8%。旋转变换本身有开销,转码后的权重也比发行方自定义格式更占内存。PTQ1_0 的 prefill 和高并发 HTTP 反而更快,但样本量太小,文档明确声明"小幅差异不构成普遍性能优势的证据"。vLLM/SGLang 的吞吐未测量(其实现参考了融合投影、分块递归 prefill 等模式,但不构成对等基线)。


7. 对照:为什么 Ollama / 原版 llama.cpp 走不通

三条路线放进一张表对照:

维度 PrismML llama.cpp 分支 原版 llama.cpp / Ollama TensorSharp
PQ2_0 / PTQ1_0 加载 原生支持 拒绝加载(旧 Q2_0 会静默乱码) 识别为类型 142/143,无损转码
Walsh–Hadamard 变换 已实现(未合并上游) 缺失 通过 ggml_ops_bonsai.cpp 接入计算图
ggml 是否被修改 是(发行方分支) — 否(转码到上游格式)
元数据契约校验 内建 — 加载前全量校验,未知布局拒绝
部署方式 setup.sh 预编译二进制 一键(但跑不了) dotnet run,.NET 生态

CloudNavi 的建议是走 PrismML 官方 demo 仓库(PrismML-Eng/Bonsai-demo,setup.sh 获取二进制),这确实是普通用户最省事的路径。而 TensorSharp 的价值在另一面:给 .NET 生态一个可审计、可嵌入的参考实现。它用托管代码加独立 native 层完成接入,不 fork 上游库,全部变换契约显式校验,验证数据和限制如实公开。


8. 内存账本:跑起来要多少资源

总内存 = 权重 + KV 缓存 +(用图像输入时的)视觉塔。27B 的 KV 缓存约 64 KiB/token,随上下文线性增长:

用途 所需内存 合适的硬件
短上下文(约 8K) 约 8.4GB 16GB 统一内存 Mac,或 12GB 显存显卡
实用线(32K) 约 9.9GB 16GB 统一内存或 12GB 显存以上
长上下文(64K)+ 图像 约 11.9GB 24GB 统一内存,或 16GB 显存
最大上下文(262K) 约 23.9GB(4bit KV 约 12.5GB) 32GB 以上内存 + 4bit KV 缓存
纯 CPU 建议 32GB 无独显的迷你主机,速度会下降

两个紧凑配置开关(PrismML 侧):BONSAI_KV4=1 把 KV 缓存量化为 4bit(约缩至三分之一),BONSAI_MMPROJ_CPU=1 把视觉投影器移到系统内存(释放约 0.9GiB 显存)。

对 TensorSharp 用户还要叠加 4.1 节的转码开销:加载后的权重内存会比文件体积大 6%(PQ2_0)到 29%(PTQ1_0),规划内存时不能只看磁盘占用。

速度参考(官方公布):RTX 5090(CUDA)最高 143 token/s;M5 Max(MLX)46.8 token/s。


9. 现状与局限:文档没有回避的事

TensorSharp 文档在结尾把局限一条条列了出来:

  • 性能目标未达成:decode 慢 3–5.4%,PQ2 高并发吞吐低 12.8%;
  • 仅验证 Metal 单设备路径:CUDA、Vulkan、iOS、张量并行均未验证;
  • CPU 路径只做了最小检查(PQ2,两条提示各 8 token);
  • 262k 上下文未验证;
  • native CPU 测试套件中有一个继承自上游 HEAD 的 DeepSeek41 容差失败,未被计入通过项;
  • 所有保留率与速度均为 PrismML 公布值,独立复现需要时间。

10. 结语

Ternary Bonsai 2 27B 代表了低比特推理的一个真实拐点:压缩本身已经足够好(98.2% 保留率、1.72 有效位宽),瓶颈转移到了运行时基础设施:变换契约、格式转码、kernel 融合、验证方法学。

TensorSharp 的集成里有三个决策尤其值得参考:

  1. 转码而非 patch:把私有格式无损展开到上游格式,底层库零改动,兼容性风险被隔离在自己这一层;
  2. 校验而非假设:prism.hadamard.* 契约加载前全量验证,不满足条件(多设备、无变换能力的 backend)直接拒绝。拒绝加载永远好过静默跑错;
  3. 公开而非粉饰:性能差距、未验证路径、失败用例全部写进文档,对比基线严格限定为"能加载同款文件的引擎"。

对想在 .NET 技术栈里跑端侧大模型、或想搞清楚"带旋转基底的三元量化到底怎么在引擎层落地"的开发者来说,docs/models/bonsai2.md 及其引用的五个实现文件,是目前公开资料里最完整的一条路径。


参考

  • PrismML:Ternary Bonsai 2 27B 发布资料(2026-09-17)
  • CloudNavi:《【2026】Ternary Bonsai 2 27B 本地部署:内存需求、量化与推荐硬件》(2026-09-19)
  • TensorSharp:https://github.com/zhongkaifu/TensorSharp/docs/models/bonsai2.md(zhongkaifu/TensorSharp,集成验证 2026-09-22)
    • BonsaiHadamardMetadata.cs / ModelBase.Bonsai.cs / QuantizedWeight.Bonsai.cs / bonsai_quant.cpp / ggml_ops_bonsai.cpp
  • PrismML-Eng/llama.cpp 分支(prism-b10658 及更新;验证基线 bdc23b56)

注:文中基准分数、保留率与速度均为 PrismML 公布值;TensorSharp 性能数据来自其 2026-09-22 在 Apple M5 Pro(48GB)上的测量,样本量有限,不构成普遍性结论。

posted @ 2026-09-26 13:05  张善友  阅读(84)  评论(1)    收藏  举报