~/app/articles/NORMAL

cat article/5.md

我解剖了一个 14MB 的 AI 模型,然后重新理解了“大模型“这件事

技术
AI模型
轻量级架构
源码解析
工具调用
大模型思考

本文从源码层面深入剖析了仅有14MB的AI模型Needle 2,揭秘其通过裁剪Transformer结构、哈希表存储知识等手段实现极低资源消耗的技术原理。文章探讨了该模型在工具调用场景的能力边界,并重新审视了‘大模型’的架构取舍,为AI产品开发提供了新的思路和启发。

我解剖了一个 14MB 的 AI 模型,然后重新理解了"大模型"这件事

—— Needle 2 源码级解析与产品冷思考

写在前面

前段时间,一个叫 Needle 2 的项目在 GitHub trending 上挂了好几天,Hacker News 上 527 分、182 条评论。它的宣传语很挑衅:45M 参数的模型,压成 14MB 的单个文件,能跑 AI 工具调用,一个会话只要 28MB 内存——要知道,现在主流大模型的参数量是几千亿,它是人家的万分之一。

我第一反应是怀疑,第二反应是好奇。于是我把它的源码从头到尾读了一遍,把官方的评测数据和博客翻了个底朝天,又去看了 Hacker News 和各路社区的争论。这篇文章就是这次解剖的完整记录。

它试图回答三个问题:

  1. 这个东西是怎么做出来的?
  2. 它的能力边界到底在哪——是突破还是花哨的正则表达式?
  3. 抛开项目本身,它给我们的架构思考带来了什么?

文章分九章。前三章讲实现,中间三章讲能力与场景,后两章讲产品和架构层面的思考,最后一章结账。源码级的实现细节收在附录 A,看完正文还想抠细节的可以去翻。文中出现的术语都会在第一次使用时解释,不需要任何大模型背景也能读。


一、一个反着来的发布

先看一组数字,感受一下这个项目有多"反潮流":

指标Needle 2作为参照的主流大模型
参数量(模型的"脑容量")45M数百亿到数千亿
文件体积14MB 单文件几百 GB
运行内存28MB(固定不涨)几十 GB 起
训练数据量1150 亿 + 380 亿 token对手用了 19 万亿 token,是它的 120 倍
推理速度树莓派 5 上每秒 500 token通常需要 GPU 集群

(token 是模型处理文本的基本单位,一个汉字或半个英文单词大约是一到两个 token。)

更反潮流的是它的定位。它不是聊天模型。你问它"天空为什么是蓝色的",它不会回答——它只会做一件事:

一句自然语言进,一个结构化的工具调用出。

比如你说"把客厅灯调暗到 30%",它输出:

{"name": "set_lights", "arguments": {"room": "客厅", "brightness": 30}}

就这么一件事。它的开发者(一家叫 Cactus Compute 的小公司)把赌注押在了一句很有煽动性的判断上:

"开灯不需要前沿模型。把一句混乱的话映射到带类型的函数签名上,不需要世界知识,也不需要开放式的散文。这就是为什么 45M 参数就够了,而聊天需要几十亿。"

这是个很有意思的立场:不是把模型做大去覆盖更多任务,而是把任务收窄到不需要大模型。整篇文章其实就是围绕这个立场展开的——它做到了吗?代价是什么?这个思路值得学吗?

顺带一提,它已经有生产案例了:Pebble(智能手表鼻祖,后被创始人复活)的 Index 戒指,在配套 App 里本地跑这个模型,把语音指令转成设备操作,全程不联网。


二、它是怎么做出来的

先给一张全景图。这个项目分六层,各司其职:

层位置职责
应用 APIneedle/__init__.py对外接口:创建 agent、执行循环、结构化抽取
Schema 编译needle/agent/tools.py把你写的 Python 函数翻译成模型能读的"工具说明书"
引擎绑定ctypes 绑定让 Python 能调用那个 14MB 的 C++ 推理引擎
推理引擎预编译的 C++ 二进制真正干活的:解码、语法约束、检索、内存管理
模型定义needle/model/(JAX)训练用的参考实现,推理时不需要
训练/导出微调流水线给用户自己定制模型用

2.1 入口:一个刻意保持"轻"的包

import needle 这行代码背后有个讲究的设计:它不会导入任何重型训练框架。真正的大块头——那个 C++ 推理引擎——是第一次使用时才按平台(Mac / Linux / Windows,x86 / ARM)从 HuggingFace 下载的一个 14MB 动态库,之后永久缓存。

Python 这边通过 ctypes(Python 调用 C 语言动态库的标准机制)绑定了只有五个函数的极简接口:初始化(传入工具列表)、执行一轮对话、重置会话、加载自定义权重。就这么几个。

对外的核心 Python API 是三个:

  • run("指令") —— 完整代理循环:模型决定调什么工具 → 帮你执行你注册的 Python 函数 → 把结果喂回去 → 循环到结束;
  • complete("一句话") —— 单轮:只返回模型想调用的工具,执行权在你手里;
  • extract("一段文本", 一个数据结构) —— 结构化抽取:从文本里按你声明的格式抠出字段。

2.2 Schema 编译器:把 Python 函数变成"工具说明书"

这是普通开发者接触最多的部分。你写一个普通的 Python 函数,加上 docstring(函数说明文档),它就能自动推导出一份 JSON Schema(一种用 JSON 描述数据结构的标准格式,包含字段名、类型、取值范围等):

import needle
from typing import Literal

@needle.tool
def set_thermostat(temperature: int, mode: Literal["heat", "cool", "auto"] = "auto"):
    """Set the thermostat.

    Args:
        temperature: target temperature in Celsius
    """
    return hub.api.thermostat(temperature, mode)

编译器会读取函数签名(参数类型变成 JSON 类型)、docstring(变成工具描述和参数说明)、Literal(变成枚举——模型只能从这几个值里选)。更狠的是约束:你可以声明"金额必须大于 0 且不超过 10000"、"用户名必须匹配某个正则",这些约束会被直接编译进解码语法,模型在物理上不可能生成违规的值。这不是"提示模型别犯错",是把犯错的路堵死。

2.3 模型本身:一个做了减法的 Transformer

Needle 2 的架构叫 Simple Attention Network(简单注意力网络)。它依然是 Transformer 家族的(注意力机制是 2017 年那篇著名论文提出的核心机制,简单说就是让模型在处理每个词时"回头看"上下文中相关的其他词),但做了几个激进的减法,这里用平实的话讲三个最重要的:

减法一:删掉了前馈层(FFN)。 标准 Transformer 的每一层由"注意力 + 前馈网络"组成,其中前馈网络是两层全连接(可以理解为每个神经元和下一层所有神经元都有连接的大矩阵),占了整个模型约三分之二的参数。学界有个著名的研究结论(Geva 等人 2021 年的论文《Transformer Feed-Forward Layers Are Key-Value Memories》):前馈层本质上是模型的"知识存储器"。Needle 的做法是:既然工具调用不需要世界知识,那就把这个"知识器官"整个切掉,换成一个几乎零参数的固定数学变换(Hadamard 变换——一种类似快速傅里叶变换的正交变换,只做混合、不存知识)。

减法二:知识放进哈希表。 它保留了一个叫 engram 的部件:把最近几个词的组合哈希(一种快速定位的索引方法)到一张查找表里,取出预存的记忆向量注入计算。8M 参数放在这张表里,读取它们几乎不耗算力——官方算账时这 8M 参数甚至不计入计算量。

减法三:内存从设计上封顶。 这个后面第三章单独讲,是它最独特的地方之一。

删完之后的效果:同参数量的普通 Transformer 每处理一个 token 要 87 MFLOPs(百万次浮点运算的单位),它只要 70,而且其中五分之一的参数是"只读不算"的。对比之下,它基准测试里的对手 FunctionGemma 是 540,苹果的端侧模型约 6000。

2.4 14MB 之谜:从娘胎里就量化的模型

45M 参数如果按常规的 16 位浮点数存储,大约要 90MB。它只有 14MB,秘密是 2-bit 量化——把每个权重从 16 位压缩到 2 位(只能表示 4 个离散值)。

这里有个关键区别:业界常规做法是"训完再压",而小模型经不起这种事后压缩,精度会崩。Needle 的做法是量化从预训练第一天就烘焙进去——模型从出生起看到的世界就是 2-bit 的。官方的原话是:"你部署的那个 2-bit 模型,就是训练的那个模型。"在他们自己的基准上,压缩没有损失任何精度。

2.5 定制流水线:任何人都能微调

这个模型小到可以在个人电脑上微调(用你自己的数据在别人训练好的模型上继续训练,调整它的行为)。流程是:用大模型帮你合成训练数据 → 用 LoRA 技术(一种只训练少量附加参数、不动原模型的高效微调方法)训练 → 合并导出成一个新的 14MB 文件。官方说在 Mac 上几分钟到几小时搞定。

记住"用大模型合成训练数据"这个细节,第八章它会回来成为一个漂亮的伏笔。


三、三个让它与众不同的机制

这一章是它真正的技术卖点,也是理解它能力边界的钥匙。

3.1 语法约束解码:JSON 永远不会畸形

大模型生成 JSON 时最经典的问题是"格式崩了"——少个引号、多个逗号、字段名拼错。常规解法是生成后校验、重试,或者用护栏模型兜底。

Needle 的做法是从根上解决:在你声明工具的那一刻,就从 JSON Schema 编译出一份字节级的语法(可以理解为一份"合法字符序列的地图"),解码时每一步只允许出现符合语法的下一个字。工具名只能是你声明的那些名字之一,参数名只能是 schema 里的字段,枚举值只能在列表内,数值必须落在声明的范围内。

证据有多硬?整个 BFCL 基准测试(伯克利函数调用排行榜)都在它的训练分布之外——官方明说训练语料里没有这些通用和企业 API。其中从未见过的 Java/JavaScript 类别,内容准确率只有 29~32%;但全部 3,641 条测下来,格式合法率依然有 93.4%。格式正确性跟随的是运行时的 schema,不是训练记忆。

还有个隐藏彩蛋:语法约束同时也是算力优化。因为引擎在算概率之前就知道哪些 token 是合法的,结构性 token(括号、引号这类被语法强制确定的输出)可以直接跳过整个词表投影计算——最多能省 98% 的输出层计算。正确性和性能在这里是同一件事。

3.2 内建工具检索:把 RAG 长在了模型体内

问题:如果你有 200 个工具,全部塞进上下文,256 token 的窗口(马上讲)根本装不下。

Needle 的解法非常有想象力:模型自带一个检索头,把 RAG(检索增强生成——先从知识库里搜出相关内容,再交给模型用)的思想倒转过来用在了工具上。机制上它是个"双编码器":把每个工具的 schema 和用户的查询分别编码成一个 128 维向量(可以理解为把文本压缩成一个语义坐标),每轮对话算相似度,只取前 5 名的工具渲染进上下文。

两个关键细节:

  1. "不可达,而非低概率"——没被选中的工具不只是"不太可能被调用",而是解码语法里压根没有它的名字,模型想调也调不了。这是硬门控。45M 的模型承受不起"在 200 个工具里选错",只能承受"没检索到"。
  2. 这个检索头是和主模型一起训练的,共享同一套底层表征。持久化时按 schema 和模型的联合指纹做缓存,改一个工具只重新编码那一个。

实测效果:在以"大工具目录"著称的 Seal-Tools 基准上,它拿了第一(包括整个工具域都没见过的 out-of-domain 测试)。

3.3 滑动窗口 + 记忆锚点:内存封顶,代价是"失忆"

这是理解它一切能力边界的机制。

先解释 KV 缓存(键值缓存):大模型推理时会把已经处理过的每个词的中间计算结果缓存下来,避免重复计算——这是推理加速的标准手段,但代价是内存随对话长度线性增长。一个长对话能把几十 GB 内存吃掉。

Needle 的做法:注意力只保留最近 256 个 token 的缓存(滑动窗口),超出的旧内容直接丢弃;但工具声明和系统信息被"钉"成永久锚点(sink),永远不会被丢弃——官方的说法很妙:"工具是工具调用模型唯一不能忘的东西,所以从结构上让它不可能被遗忘。"

这带来一个必须掰清楚的性质,我称之为**"撑爆 vs 滑出"**:

如果你把一个 3000 token 的检索结果喂回给它,什么都不会崩——内存依然是 28MB,这正是设计的卖点。但生成时刻它只看得到这个结果的尾部约 256 token,开头的内容对它来说不存在。

失败模式是信息丢失,不是内存爆炸。会话可以无限长,但"记忆"永远只有"钉住的工具 + 最近 256 token"。

(还有一个坑:官方实测(西班牙语)非英文文本会被切分成约 1.7 倍的 token 数——中文大概率同样吃亏,同样的窗口装下的内容更少。)


四、数据不会说谎:它到底有多准

前面讲的都是机制,这一章讲实测。官方在五个公开的工具调用基准上做了评测,评分标准用的是最严的那种:有序严格精确匹配——函数名、调用顺序、每一个参数值全部正确才算过。而且 Needle 的成绩是端到端跑在出厂的 14MB 二进制上测的(2-bit 量化、检索开启、256 窗口照常生效);对手模型全部以 16 位精度运行——三个对手跑在云端推理服务上,苹果的端侧模型在设备上跑,且都放开完整上下文。

Mobile Actions(谷歌设备动作,961 条)

模型全对率工具名对单调用双调用
LFM2.5 230M69.193.076.155.0
FunctionGemma 270M64.087.373.046.2
Needle 2(2-bit)63.798.371.348.4
Apple 端侧模型57.694.264.543.8

Seal-Tools(大工具目录,多调用为主)

模型域内域外(整个工具域没见过)
Needle 232.628.7
LFM2.5 230M26.917.0
FunctionGemma 270M16.315.6

(另外还有 DroidCall(安卓意图式调用,200 条):FunctionGemma 17.5、Needle 17.0、LFM2.5 11.0——Needle 与体积六倍于己的 FunctionGemma 基本打平。这是五个基准的全貌。)

BFCL v4(伯克利函数调用排行榜,3641 条)

类别Apple 端侧LFM2.5FG 270MNeedle 2
Python 简单调用86.885.562.361.2
Java 简单调用67.048.038.029.0
并行多调用65.064.036.530.0
该拒绝时拒绝100.068.881.281.2
总评61.760.846.142.6

三个关键读数

读数一:意图识别是它的王牌。 工具名准确率 98.3%,全场第一;没见过的工具域照样第一。"用户想干嘛 → 该调哪个工具"这件事,它做到了准分类器级别。

读数二:参数填写是它的软肋,而且差距恰好是 35 个点(98.3 的名字准确率 vs 63.7 的全对率)。但这不是简单的"能力不行",而是一份刻意签订的契约:

参数只填有证据的值。 用户没提到的可选字段,直接省略,不猜。

对比一下:大模型填参数,没证据时会流畅地编一个像模像样的值(这叫"幻觉");Needle 填参数,没证据就留空或干脆拒绝整个调用。在"错误执行一个转账/开锁"比"不执行并升级人工"严重得多的设备场景里,这是对的安全取舍——失败模式被设计成"升级处理",而不是"错误执行"。

顺带要泼的冷水:它确实会做一些形式转换("$1,200.00" → 数字 1200.0,"十分钟" → 数字 10),但这些都是训练时见过才会的。指望它对没见过的说法自动举一反三?不行。想让它学会你领域的说法,老老实实微调——官方自己的经验是:教会"选对工具"只要几百个样本,教会"填对参数"要几千个。

读数三:出圈的活干不了。 BFCL 总评 42.6 落后于所有对手,输的地方恰好是它训练数据没覆盖的领域(Java/JavaScript 的企业 API)。它是专才,出了专业领域就露怯——这一点官方倒是很坦白。

还有一个数字值得记住:一次调用越多,准确率越低——单调用 71.3% → 双调用 48.4%(前两个来自 Mobile Actions)→ 并行多重调用 22.5%(来自 BFCL)。复杂多工具编排不是它的菜。


五、灵魂拷问:这不就是个分类器吗?

读到这里,你可能和我最初的想法一样:

"这不就是个感知机/分类器吗?一句指令进来,从固定的输出里挑一个,映射规则靠训练。那套 JSON Schema 不过是它输出结果的一种格式。搞这么复杂干嘛,训练个多层感知机不就行了?"

有意思的是,Hacker News 上 182 条评论吵的核心也是这个,有人直接开喷:"这是突破,还是花哨的正则表达式?"

这个质疑对了一半,而且对的那一半很有启发。

对的一半:工具选择确实是"分类器级"的

98.3% 的工具名准确率,就是高质量分类器的水平。架构上也一样:那个检索头本质上就是个可训练的最近邻分类器(把查询和工具都映射成向量,算距离,取最近)。我在源码里也没找到任何玄学——这个子问题就是被当成分类问题解决的,而且解决得很好。

同理,你的直觉对多工具调用的判断也是对的:1-call 71.3%、2-call 48.4%、并行多重 22.5%,单调下滑。一次映射到一个输出它很强,一次映射到多个输出就超纲。

错的一半:Schema 不是输出格式,是输入

分类器的定义是:输出标签在训练时就冻结了。加一个新类别?重新训练。

而 Needle 的定义是:输出空间在运行时由你提供的 schema 动态定义。schema 是喂进模型上下文的输入 token,不是输出端的格式约定。这个差别带来三个分类器原理上做不到的能力:

证据一:新工具零样本。 Seal-Tools 域外测试,整个工具域从没见过,照样第一。给它加一个新工具 = 编辑一段 JSON,不用重训。分类器加一个类 = 输出层都没有那个神经元可用。

证据二:参数值要从输入里"抄"。 用户说"拉各斯天气怎么样",参数 city="Lagos" 是从用户的话里逐字提取的。输出是变长字符串、值依赖输入内容——这是序列到序列的转换问题,不是"输出一个类别编号"能表达的。

证据三:格式跟随 schema 而不是训练记忆。 前面说过的:整个 BFCL 都没学过,格式合法率照样有 93.4%,因为语法是从运行时的 schema 现编译的。

所以准确的说法是:选工具 ≈ 分类(它用检索头解决,很强);填参数 ≈ 有约束的序列生成(它用语言模型解码解决,勉强及格);保证合法 ≈ 语法(它用 schema 编译解决,满分)。删掉任何一块,另外两块都补不上。

其实你重新发明了一个经典范式

"指令进来 → 识别意图 → 抽取参数",这个东西有个正式的名字:意图识别 + 槽位填充,2011 年的第一代 Siri 就是这么做的(一个分类器识别你想干嘛,一个标注器从话里抠参数)。在固定领域、固定参数、参数都是枚举值的前提下,这套古典方案今天依然好用,而且更便宜。

Needle 本质上是这个范式的"schema 条件化泛化版":把"每个领域标注一套分类器"换成了"写一段 JSON"。代价是必须有个生成式模型,收益是换工具集不用重新训练。**如果你的工具集永久固定、参数全是枚举、单调用——别用 Needle,一个微调过的小分类器更准、更小、更好调试。**它赢的地方在"动态"二字。


六、它能干什么:两个场景推演

聊完边界,来推演真实场景。我挑了自己最关心的两个:RAG 系统的前置层,和 Multi-Agent 架构的路由器。

6.1 当 RAG 的"召回分发器"

设想把知识库的检索能力封装成工具:向量检索(按语义相似度搜)、关键词检索(按倒排索引搜),让模型决定用哪个、传什么参数。

可行的部分:这正中它的最强轴——选对工具(98.3%)+ 语法保证参数合法。而且"混合召回"(同时发两个检索请求)恰好是它训练过的"并行多调用"形态。用户说"查一下 Q3 供应链的报销制度,文件编号 REIM-1024",它一轮同时发出关键词检索(精确编号)和向量检索(语义模糊匹配),这个画面很美。

但有三个坑,都是结构性的:

  1. 它不改写查询。 那份"只认证据"的契约是双刃剑:它只会把用户原话搬运进参数,不会做同义改写、查询扩展。想要好的召回质量,要么你的向量模型对口语足够鲁棒,要么在它前后加一个专门的改写环节。同理,用户的长提问也会滑出窗口——这个场景同样只对短查询成立,长问题要么走截断策略,要么直接交给大模型。
  2. 结果必须由你压缩。 还记得"撑爆 vs 滑出"吗?检索工具必须只返回紧凑的结构化结果(编号、分数、标题),整段原文喂回去等于喂了个寂寞。阅读、重排、摘要——全是上层系统的活。
  3. 它不是迭代检索 agent。 "搜→看→改关键词→再搜"这种反思循环,虽然它支持多轮循环(最多八步),但跨轮记忆会被窗口逐出,且它不会改写查询(见第一条),链路一长就失忆。

一句话定位:它是召回的"分发器",不是检索的"大脑"。

6.2 当 Multi-Agent 的路由器——但只对"短指令"成立

把各个领域的 Agent 封装成工具,让 Needle 做意图路由,乍看是它最理想的用法:路由落在最强轴上(意图识别),Agent 描述就是检索键,目录几百个也不怕(检索头每轮自动筛前 5),拒绝和置信度还构成天然的升级接口。

但这里必须先戳破一个悖论:长指令会把它整个打回原形。

设想一个典型场景:用户絮絮叨叨说了一大段(远超 256 token),里面真正可执行的意图只有两三个。此时有两条路,都不通:

  1. 直接交给 Needle? 不行。滑动窗口意味着生成时刻它只看得到尾部,开头说的东西对它不存在;而且"从一段话里拆出两三个意图"恰好撞上它最弱的并行多调用(22.5%)。双重不合格。
  2. 前面放个大模型先把长指令精炼成短命令? 这就自相矛盾了:大模型要精炼这段话,就必须先理解它;而理解即路由——路由对已经完成理解的大模型是零边际成本的副产品,多输出一个工具名和参数几乎不增加任何费用。此时再串一个小模型,钱没省(大头付给了精炼),反而多了一层失败点。

换句话说,"大模型精炼 → 小模型路由"这条链在逻辑上是不稳定的:精炼的成本已经覆盖了路由的成本,小模型在链条里没有存在的位置。

所以这个场景的正确打开方式不是串联,而是按输入形态并联分流:

  • 短指令(大约 200 token 以内,口语化、单意图为主)→ Needle 直接路由,全程不经过大模型。语音助手、设备控制、表单交互天然产出的就是这类指令——这是它真正的主场;
  • 长指令 / 多意图混杂 / 书面化长 prompt → 直接走大模型,跳过 Needle。分流判断用规则就能做(看长度、看句式),不需要模型。

(一个不冗余的例外模式是会话级摊销:大模型把一段长上下文一次性处理成结构化的会话状态——这叫"建立上下文",不叫路由;之后用户在这个状态上做的大量短指令交互("开始下一章""换成表格"),再交给 Needle 路由。两者职责不重叠:大模型管一次性的理解,小模型管高频的短交互。)

剩下的约束照旧:一次路由一个目标最稳;子 Agent 收到的任务参数基本是用户原话,别指望它加工;微调后置信度会失效(置信头不参与微调),升级策略要改用"拒绝行为"——训练数据里放约八分之一的域外样本,让它学会说"这不管"。

对大目录还有个官方推荐的两段式玩法:第一轮挂全目录只为选出目标,第二轮新会话只挂选中的那个工具填参数——"渐进式披露"思想在会话级的实现。

6.3 两边共用的架构图

注意架构是分流而非串联——长指令根本不应该流经 Needle:

flowchart TD
    U[用户请求] --> G{"输入形态分流<br/>(规则即可,无需模型)"}
    G -->|"短指令(≲200 token,单意图)<br/>语音/命令类交互的主体"| N["Needle(本地/离线,14MB)<br/>意图识别 + 参数落地"]
    G -->|"长指令 / 多意图混杂"| B[云端前沿大模型]
    N -->|"高置信(短指令域的大多数)"| E[本地/低成本执行]
    N -->|"拒绝 / 低置信"| B
    B --> E
Mermaid diagram source. A rendered diagram replaces this fallback when available.

一次路由决策的输出就几十上百个 token,树莓派上 200~400 毫秒。对比"让旗舰大模型做路由"动辄一两秒加在线费用,它的边际成本趋近于零——但前提始终是:这条快车道只服务短指令。


七、冷思考:star 的经济学

现在到了泼冷水的时间。这个项目在 GitHub 上拿了很多 star,但我认为作为产品判断,star 是偏高的。拆开看 star 的构成,没有一个是"付费客户"投的:

star 来源他们在为什么激动
工程极客"14MB 里塞了个模型"的奇观感——和当年 4KB demo、llama.cpp 的 star 逻辑相同
ML 研究者"前馈层可以整个删掉"这种论文级挑衅结论
反军备竞赛情绪45M 打平 270M、训练数据只有对手 1/120 的 David vs Goliath 叙事爽感
嵌入式开发者被大模型时代遗忘的人群第一次被认真对待——稀缺性狂热是真的
产品人Pebble 背书带来的"已投产"信号

"工具要联网,为什么不全部上云"——我自己被驳倒过一次的论证

我最初的反驳和很多人一样:戒指、手表这些设备的工具(查天气、发消息)本身就需要联网,既然横竖要上网,为什么不把意图识别也放云端,一步到位?

后来我意识到这个论证混淆了两段网络:"理解的网络"和"执行的网络"不是同一段。

云端方案:麦克风 → 上传音频 → 云端识别+理解 → 下发指令 → 执行
本地方案:麦克风 → 本地理解 → 指令 → 需要联网的工具上云执行,本地的直接执行

差别不在"最终要不要联网",在于**"理解"是否在网络的关键路径上**,以及离开设备的是原始音频还是几十字节的指令。这带来三个真实差异:

  1. 纯本地命令的覆盖率:穿戴设备的高频命令大头是本地域——闹钟、计时器、手电筒、切歌、锻炼。"把闹钟关了"这种指令没有任何理由需要一次网络往返。Pebble 那句话的重点是"无屏设备上,语音是唯一交互,理解层失败等于设备变砖"。
  2. 隐私的对象是音频:云端理解要求把麦克风采集的原始音频持续传出设备;本地理解只传执行所需的指令。对常听设备(儿童、医疗、欧盟合规),这是合规差异,不是营销话术。
  3. 射频即电池:穿戴设备最大的耗电大户是无线发射,不是计算。每次传一段音频 vs 传几十字节,续航差距是实打实的。

但是——这些优势成立的前提非常苛刻。整理成一个判断表:

条件本地理解赢云端全包赢
纯本地命令占比高(控制类为主)低(查询内容类为主)
音频隐私敏感度高(常听、儿童、医疗)低
电池/射频预算极紧(戒指、传感器)宽松(插电设备)
产品确定性要求固件冻结 3-7 年不能漂移可接受模型随时升级
网络状况常离线常在线

**左边占三条以上,本地理解才是刚需;否则,云 API 就是更优解。**天气音箱、查内容的智能音箱这类设备,全上云毫无问题——对这些场景拿 Needle 说事,确实是伪需求。

还有个印证怀疑的细节:Pebble 的实际部署是"跑在配套 App 里"(手机端离线解析),不是戒指固件上。营销里的"微控制器上跑大模型"和真实落地之间隔着不小的距离。

锋利的结论

**这个仓库的真实身份是:一篇能跑的论文 + SDK 营销物料 + 融资/招聘信号。**官网那句"合作请发邮件到 founders@xxx"基本明示了商业模式。star 度量的是注意力,不是采用量——它由 ML 极客和嵌入式老兵两个社区各取所需地投出,大多数人 star 完不会部署一行代码。

它的真实买家画像很窄:无屏常离线设备(戒指、遥控器、玩具)、合规驱动场景(音频不出设备)、需要固件冻结多年的工业/车载产品。这个市场养活一个小团队绰绰有余,撑不起 trending 所暗示的叙事。

至于"DeepSeek 这类大模型 API 已经很便宜了,要它干嘛"——在服务器侧,这个质疑完全成立。有网络、有 GPU、QPS 不到爆炸规模时,调一次云端便宜模型更强也更省事,Needle 不是必需品。它的"非它不可"长在固件里,不长在你的服务器上。


八、真正的启示:Rethinking the Big Model

前七章是解剖,这一章是我认为这个项目真正值钱的部分——哪怕它作为产品死了,下面这些思想也能迁移走。

8.1 它反的不是大模型,是"容量-任务错配"

先纠正一个容易走偏的读法:Needle 的出现不是"别搞大模型了,回归感知机"。它自己就是 27 层的深度注意力网络,用了 1150 亿 + 380 亿 token 训练,全套深度学习正规军工艺。

它真正示范的是三条纪律:

  1. 先把任务收窄到最小形式("灯控命令→类型化调用"被证明不需要世界知识),容量需求自然塌缩;
  2. 容量按任务的 information 需求分配,不按"显得先进"分配;
  3. 协同设计:架构(删器官)、量化(训练期 2-bit)、运行时(寄存器内解压)、约束(语法剪枝)互相咬合——单点优化谁也做不到 14MB。

8.2 最漂亮的一刀:在架构层面实现"能力与知识分离"

我最初以为这个项目做到了"知识完全外置、能力留在模型内"(像 RAG 那样),细看发现不是——它的记忆在参数里,连长文档都读不了。我失望了一阵,后来才意识到它做了件更彻底的事:

还记得第二章那条研究脉络吗——Transformer 的前馈层是知识存储器。Needle 把这个"知识器官"整个切了,只留下做路由和对齐计算的注意力,再把记忆需求外包给一张哈希表。

它没有外置知识,它做的是知识戒断:把任务重新定义到"不需要知识",知识需求直接归零。

这是"能力与知识分离"最激进的实现方式——不是把知识挪到外部,而是让这个任务根本没有知识需求。想复制这个思路的人,第一步不是找模型,是问:我的任务里,哪一部分真的需要"知道事情"?

8.3 历史周期律:每一轮"更大的锤子"都以分层收场

  • CPU:复杂指令集堆到极致 → 精简指令集证明"少而精 + 编译器协同"更优 → 终局是分层;
  • 软件架构:单体 → 微服务(全面复杂化)→ 模块化单体(按需拆分);
  • 机器学习:专家系统(规则膨胀)→ 统计学习 → 深度学习 → 参数规模竞赛 → 现在:任务专门化。

每一轮的终局都不是新王全面登基,而是分层:大而全的东西退到它不可替代的位置,其余的被更小的专用件接管。

8.4 大模型没有消失,它搬到了两个新位置

这是整个故事里最有意味的闭环。还记得第二章结尾那个细节吗——Needle 的微调流水线,调用大模型来合成训练数据。

flowchart LR
    B["前沿大模型<br/>(DeepSeek/Gemini 级)"] -->|"①当工厂:<br/>生成小模型的训练数据"| S["小型专用模型<br/>(Needle 类)"]
    U[用户流量] --> S
    S -->|"~98% 高置信简单请求"| E[低成本执行]
    S -->|"~2% 拒绝/低置信/复杂"| B
    B -->|"②当兜底:处理长尾"| E
Mermaid diagram source. A rendered diagram replaces this fallback when available.

**小模型是大模型生的,小模型消化不了的再吐回给大模型。**大模型从"产品"退位成"工厂 + 兜底"——不是被取代,是被搬家。而"98% 的流量在最便宜的一层解决,2% 升级给最贵的层"这个比例经济学,比任何一个具体模型都值钱。只是要记住它成立的隐含前提:输入本身是短的——长输入在第一层就该被分流走,它们本来就不属于便宜的那一层。

8.5 一张可以每天用的决策阶梯

规则/正则 → 经典机器学习分类器 → 微型专用模型(Needle级) → 小型语言模型 → 前沿大模型
    ↑                                                                        ↓
    └──────────── 选能通过你评测的最低一级;不确定就升级 ←──────────────────┘

配套三条纪律:

  • 永远先问"这个任务的哪一部分真的需要智能"——大多数流量是解析,不是推理;
  • 容量按流量分布分配,不按任务难度分配;
  • 不确定性要有出口:置信度和拒绝不是模型的短板,是分层的接口。

九、结语

回到最初的问题:这个 14MB 的东西,是突破还是花哨的正则?

我的最终答案是:作为通用产品,不是——场景窄、护城河浅、被便宜的云 API 持续侵蚀,star 里泡沫不少。作为特定领域(无屏、常离线、电池敏感、合规严格)的组件,是——那里面"每次都必须响应"的确定性需求,云给不了。而作为思想标本,它值这批 star——人们 star 的本来就不是软件,是一个被证明可行的方向。

大模型时代训练出了一种集体惯性:遇到任何问题,先问"还有什么是大模型做不了的"。这个项目(连同"前馈层是知识存储"那条研究脉络)把问题倒了过来:

你已经会用大模型了——那你敢不敢说清楚,你的问题里,哪一部分根本不需要它?

能回答这个问题的人和组织,拿到的是下一阶段的成本结构。这大概就是那 14MB 真正在卖的东西。


附录 A:源码级实现细节

这一附录是给想抠实现的人的,正文为流畅性省略的细节都在这里。

A.1 入口与 C ABI

import needle 只做轻量初始化,不导入 JAX(训练框架)。推理引擎的查找顺序:NEEDLE_LIB_PATH 环境变量 → 包目录内 → ~/.cache/cactus-needle/<版本>/ → 从 HuggingFace 仓库 Cactus-Compute/needle2 下载对应平台的 wheel 并解出动态库(按 glibc/musl、x86_64/arm64 区分,musl 检测靠读 /proc/self/maps)。

ctypes 绑定的完整 C 接口只有五个函数:

int  needle_init(const char* system, const char* tools_json,
                 const char* tool_index_path);
int  needle_complete(const char* text, int max_new_tokens,
                     char* out_buf, int buf_len);
void needle_reset(void);
int  needle_load(const char* weights_blob, uint64_t len);

注意引擎是进程级单例:一个进程同时只有一套(系统信息、工具集、权重)生效,切换需要重新 needle_init(会话不保留)。做多租户/多工具集服务时要么每套一个进程,要么接受重初始化开销。

A.2 Schema 编译器要点

needle/agent/tools.py 用 inspect.signature + typing.get_type_hints 提取参数:Python 类型映射 JSON 类型;Enum/Literal 映射枚举;Google 风格 docstring 的 Args: 块映射参数描述;有默认值或 Optional 的参数不进 required。needle.Field 的约束(ge/le/pattern/min_length 等)逐一映射为标准 JSON Schema 关键字,由引擎编译进解码语法。Pydantic 模型通过 MRO 探测识别(非硬依赖),直接调 model_json_schema()。

A.3 Simple Attention Network 组件细节

  • Hadamard MLP:z → d1·z @ H → silu(d2·z) @ H → d3·z,H 是归一化 Walsh-Hadamard 矩阵(固定、无参数、n·log n 时间),d1/d2/d3 是三个可学习对角向量。FFN 的两个大矩阵被彻底移除。
  • Engram:FNV 风格哈希把 token 的 2-gram/3-gram 映射到 8192 slot 的可学习查找表,在第 2、15 层把 (k, v) 注入残差流,带 4-tap 因果卷积平滑。
  • 多车道超连接(mHC):4 条并联残差流,每层用三个小投影生成流混合权重,其中重路由矩阵经 20 轮 Sinkhorn 归一化(一种把矩阵变成行列和都为 1 的迭代算法)保证双随机性;初始化偏向"每层主用一条车道"。
  • 门控注意力:分组查询注意力(多组查询头共享少量键值头,省内存)+ QK 归一化 + 旋转位置编码;注意力输出乘学习到的 sigmoid 门;两种残差都做"三明治归一化 + 门控"。
  • 三个头:词表输出头(与输入嵌入共享权重);工具检索头(4 个可学习 probe 对所有层的隐状态做注意力池化 → 128 维向量,训练温度初始 0.07,梯度不回传主干);置信头(8 probe 池化 → 单数值,输出校准置信度)。
  • 配置(仓库 preset):27 层,词表 8192,engram 在第 2/15 层,单次前向位置上限 2048。

A.4 .cact 导出格式

单文件自包含:120 字节头(全部架构几何 + RoPE 参数 + KV 窗口/位宽)→ 紧随其后的混合精度码本 → 无名张量目录(按固定顺序定位)→ 64 字节对齐的张量数据块 → 追加的 probe 头(FP16)→ 内嵌的完整分词器(SentencePiece 的片段表、分数、类型直接打包成二进制)。

量化细节:矩阵权重按 128 一组做 Hadamard 旋转,每组只存 L2 范数(FP16)+ 码本索引(2/3/4-bit 的 Lloyd-Max 单位球码本,LSB 优先位打包);矩阵预转置为 [输出维, 输入维] 且按层连续存放,为 C++ 引擎的流式访存优化。非矩阵参数(归一化 scale、门、Hadamard 对角)保持 FP16。

KV 窗口大小由 11.5MB 的内存预算反推:每位置字节数 = 层数 × (K+V + 分组量化 scale) + engram 位数 × (维度 + scale),除出窗口后对齐到 32,训练窗口写进文件头,引擎必须按头里的窗口跑。

A.5 微调流水线

训练数据格式(JSONL,每行一条):{"query": ..., "tools": [schema...], "answers": [调用...], "reasoning": "一行推导", "system": "可选"}。域外样本的 answers 为空数组(教拒绝),官方合成器默认配比约八分之一。

聊天模板(也是引擎运行时的格式):

<|im_start|>user
<tools>[工具schema]</tools>
用户query
<|im_end|>
<|im_start|>assistant
<think>一行推理(无约束生成)</think>
<tool_call>[调用JSON(语法约束)]</tool_call><|im_end|>

LoRA:只对每层的 q/k/v/gate/out 五类投影训练低秩附加矩阵(默认秩 16、α 32),主模型冻结;每步前向时即时合并,导出时固化。优化器 AdamW + 预热余弦退火 + 梯度裁剪。注意:微调数据格式是单轮的(没有对话历史字段),多轮链式能力只能靠基座模型自带;置信头不参与微调,微调后的模型置信度报告为 None。


附录 B:术语速查

术语一句话解释
token模型处理文本的基本单位,约等于半个词或一个汉字
参数模型内部的可学习数值,参数越多"脑容量"越大
Transformer / 注意力当前所有大模型的基础架构;注意力让每个词能"回头看"上下文中相关的词
FFN(前馈层)Transformer 每层里的大矩阵组件,占约 2/3 参数,学界认为它是"知识存储器"
量化把权重从 16 位浮点压成更少位数(如 2-bit)的整数以缩小体积
KV 缓存推理时缓存的中间结果,加速生成但内存随对话变长
滑动窗口只保留最近 N 个 token 的缓存、丢弃更旧内容的机制
Sink(锚点)永远不被窗口逐出的固定记忆位(这里指工具声明)
JSON Schema用 JSON 描述数据结构(字段、类型、范围)的标准格式
语法制约解码生成时每步只允许出现符合预编译语法的字,保证输出结构合法
RAG检索增强生成:先搜相关内容再交给模型回答
双编码器把查询和文档分别编码成向量、用向量距离匹配的检索架构
意图识别 + 槽位填充经典对话系统范式:分类器判断用户想干嘛,抽取器抠参数
微调 / LoRA在训好的模型上继续训练 / 只训少量附加参数的高效微调法
幻觉模型一本正经地编造没有依据的内容
端云协作简单请求本地小模型处理,复杂请求升级云端大模型
BFCL伯克利函数调用排行榜,工具调用能力的标准评测
有序严格精确匹配最严的评分标准:函数名、顺序、每个参数值全对才得分
$ comments --list (0)

// not signed in


// no comments yet