Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

HunchEmbeddingGemma2蒸馏LocalInference

Hunch:把重复的大模型判断下沉到 CPU

Hunch 先复用精确答案,再用 EmbeddingGemma 2 驱动的单问题学生处理熟悉状态;陌生、不确定或未毕业的判断交给教师模型,并由后台审计持续校准。

undrgrnd-labs/hunch347PythonApache-2.02 min read

把重复判断从大模型调用中拆出来

Hunch 要解决的不是“让一个小模型全面替代大模型”,而是一个更窄、也更容易核算的问题:智能体运行时会对每一段工具输出、日志、用户消息或网页状态反复提出同几种判断。教师模型能够给出高质量答案,但当记录量达到数千、数万条,或者需要贴近实时处理时,逐条调用大模型的时间和费用会迅速变成系统瓶颈。

Hunch 把自己放在教师模型前面。它先检查精确答案缓存,再用每个问题各自训练的学生分类器处理熟悉且高置信的样本,其余请求交给教师。学生只在交叉验证证明它与教师足够一致后“毕业”并开始独立回答;毕业之后仍会接受后台抽查,发现分布变化或准确率下降时重新把流量交还给教师。

这个定位让它更像代理运行时里的快速反射层,而不是一个通用推理模型。它擅长判断“这段文本里有没有密钥”“这次工具调用是否失败”“输出属于测试、构建、搜索还是编辑”,不擅长回答需要外部知识、前因后果或多步推断的开放问题。

三种结构化问题,一套明确协议

Hunch 把业务判断限制为三种可校准的答案类型:

  • noul:是或否的问题,返回“是”的概率。
  • choice:从一组无序选项中选择一个,例如测试、构建、搜索、版本控制或其他。
  • score:从有序等级中选择一个,例如无、轻微、可恢复和阻断。

一次请求包含一个状态和若干带名称的问题。状态可以是任意 JSON;工具调用场景通常把调用与输出拼在一起,再把其他已知字段作为键值行附加进去。教师和学生读取的是同一份状态文本,因此缓存、训练样本与最终回退不会针对不同的输入各自解释一套口径。

问题不是随手写的一句提示词。问题类型、指令和选项定义共同构成版本键。换一种说法就会产生新的采样训练集和新的毕业判断,因为教师对旧措辞的回答不能直接证明新措辞也成立。若希望改写问题时保留人工标注,可以为问题指定同一个 family;同一家族共享标注样本,但类型与选项必须保持一致。

从缓存到学生,再到教师

Hunch 的在线路径分成三层。第一层是精确答案存储:如果这个状态的文本哈希已经拥有全部问题的标签或教师答案,就直接返回,不需要编码。第二层是学生分类器:每个问题有自己的逻辑回归模型,读取 EmbeddingGemma 2 中间层向量,只有同时满足“已经毕业、当前置信度足够、状态不属于陌生分布”才在本地回答。第三层才是教师模型。

路由以请求为单位,而不是以单个问题为单位。只要一个请求里有一个问题尚未毕业、被降级或不够自信,整个请求都会交给教师。这不是偷懒,而是成本模型决定的选择:教师一次读取状态就能回答同一请求中的所有问题,拆成部分转发既增加协议复杂度,也省不下主要读取成本。

教师返回的答案会成为采样训练样本。若状态本身已有精确缓存,则仍然优先复用;若之后收到人工标签,同一状态上的采样答案会被标签覆盖。这样重复出现的状态不会挤满训练窗口,也不会因为流量重复而获得本不该有的权重。

学生的特征为什么落在第 8 层

Hunch 的默认特征不是最终句向量,而是 EmbeddingGemma 2 解码器第 8 层之后、对 token 隐状态做均值池化得到的 512 维向量。完整编码最终会进行 24 层、归一化、均值池化和 512 到 768 的投影,但很多结构化判断在第 8 层已经包含足够信号,而提前退出可以显著减少计算。

项目里提供的 CPU 引擎 eg2cpu 支持分段前向与恢复。第 8 层出口并不改变结果定义,只是提前截取中间表示;如果后续需要完整向量,编码可以从中途继续。实现中包含针对不同 CPU 的矩阵运算路径,包括 Arm 的 NEON、SME 和 BF16 指令,以及 Intel Sapphire Rapids 的 AMX。目标不是使用 GPU,而是把编码成本压到普通服务器 CPU 可以承受的范围。

每个问题训练一个逻辑回归,而不是把全部问题混成一个多任务网络。这个选择让训练样本、毕业标准、审计窗口和故障回退都能按问题独立管理。不同层次也可能需要不同特征:项目文档指出,工具失败判断在真实轨迹上第 20 层表现最好,而某些隐私信息判断使用最终层更好。默认第 8 层是一个性能与通用性之间的起点,部署前应通过预检确认具体问题适合哪一层。

Hunch live 物体识别演示,通过摄像头画面学习新的物体名称
hunch.live 的物体演示:教师或人工标注摄像头帧,学生随后在本地识别新类别。

“毕业”不是一次训练完成

一个学生要开始独立服务,必须通过分组五折交叉验证。Hunch 使用 Wilson 置信区间,而不是只看训练集准确率,也不被多数类的高一致率欺骗。

对称毕业要求:在置信度达到阈值的留出样本中,至少有 30 个本地回答;与教师一致率的 95% 下界不低于 90%;非多数类至少出现 15 次,且其召回率下界不低于 80%。对称学生可以回答它足够自信的所有类别。

是或否问题还可以选择单边毕业:学生只回答高置信的“否”,把可能的“是”全部转给教师。它要求本地“否”被教师判成“是”的比率,其 95% 上界不超过 2%;同时,教师所有“是”答案中至少 85% 必须被正确转发。这个模式适合提示注入、密钥泄露、危险动作等漏报代价远高于多一次教师调用的场景。

教师答案本身并不等于真相。项目在注入实验中展示了一个典型风险:教师只能发现约 20% 的礼貌型提示注入,完全从教师答案蒸馏出来的学生也只能继承这个盲区。人工标签权重是采样答案的两倍,并覆盖同状态的教师答案,因此可以用基准标签、工具运行标志或人工复核把学生训练到教师盲区之外。前提是先测量教师,而不是默认教师永远正确。

毕业后的抽查、陌生样本与降级

本地回答并不是“训练一次,永久放行”。默认有 10% 的本地回答会在后台送给教师复核,客户端无需等待这次调用。任何原本就要转发给教师的请求,也会顺带审计那些本来可以本地回答的学生。

每个问题保留最近 100 次审计,至少积累 20 次后才触发降级。对称问题的一致率若显著低于 90%,或者单边问题的漏报率显著高于 2%,该问题会停止本地作答。降级状态保存在存储中,进程重启后不会自动恢复;只有当后续审计重新达到标准时,它才会再次开始本地回答。

另一道闸门是比较新状态与最近最多 1,000 个训练状态的余弦相似度。如果新状态与最近邻的相似度低于训练样本最近邻相似度的第 5 百分位,就判定为陌生并交给教师。这个机制不能理解语义,但能识别输入格式、工具或文本分布已经偏离训练区间的情况。

这种保守设计解释了 Hunch 为什么适合渐进式接管。它不是先承诺一个固定的本地覆盖率,再要求业务接受偶发错误;它用带下界的统计规则逐步提高覆盖,并允许运营变化把覆盖重新压低。

数据保存与训练节奏

每个问题版本默认保留最近 2,000 条采样答案,每个 family 的每个类别默认保留最近 5,000 条人工标签。标注权重是 2.0,采样答案按教师置信度加权;一个状态在同一问题下只保留一条样本。新样本达到 100 条时会在工作线程中触发重训,旧模型继续服务到新模型完整替换为止。

训练得到的模型、状态特征和审计窗口可以存进 SQLite,也可以使用 Redis 由多个进程共享。特征指纹包含模型检查点版本、层号、矩阵运算精度和截断策略;换了编码方式却试图混用旧特征时,存储会直接拒绝。保留原文的部署还可以在升级编码器后重新编码并重建全部学生。

当教师存在系统性校准偏差时,Hunch 可以在至少 50 个“标签加教师答案”配对后学习教师重校准。只有在五折交叉验证中显著提升平衡准确率、且不明显降低总体准确率时才启用。重校准能移动判定阈值,但不能修复教师看不见的规律;真正的盲区仍要靠人工标签和新特征来补。

性能数字应该怎样读

仓库给出的 Apple M5 Pro 实测中,约 190 token 的工具结果由学生回答时,单请求 p50 为 22 毫秒,p95 为 51 毫秒,p99 为 76 毫秒;教师回放加入 500 毫秒延迟后,p50 为 531 毫秒。32 个并发调用者共享编码批次时,吞吐达到每秒 86 个请求,p99 为 466 毫秒。单请求会并行使用约 7 个核心,包含模型在内的进程常驻内存约为 1.5 GB。

CPU 成本随硬件和路径变化很大。项目在 24 核 Sapphire Rapids、AWS 按需实例上测得,完整前向约为每百万 token 2.2 到 3.0 美分,第 8 层提前退出约为每百万 token 0.87 美分。原视频里“低于约 3 美分/百万 token”的说法在这个硬件与计费前提下成立,但不能直接外推到所有服务器、所有 token 长度或每请求都有教师回退的流量。

真正决定系统成本的不是单次编码的漂亮数字,而是本地覆盖率。如果一个问题持续无法毕业,请求会一直支付教师调用;如果问题简单且稳定,学生可以吃掉大部分流量。Hunch 提供的是一个可测量的覆盖率和审计成本曲线,而不是一个保证固定节省比例的营销承诺。

适合与不适合的场景

适合的问题是“答案直接写在当前文本里”的判断,例如输出类型、失败与否、是否包含隐私信息、是否存在注入迹象、情绪或严重度等级。更好的做法是把复杂任务拆成几个可观察的子问题,再用普通代码组合。例如不要直接问“失败有多严重、该由谁修复”,可以先判断调用是否失败、是否提到权限、是否为空结果,再由确定性代码映射严重度。

不适合的问题是间接推理、世界状态判断、因果归因、用户真实意图,以及需要当前文本之外知识的问题。这些问题即使换一种问法,也不会因为逻辑回归而突然获得推理能力。项目建议在正式部署前收集 150 到 300 个真实样本,用预检脚本比较第 4、8、12 层和最终层,得到一个明确结论:可对称毕业、可单边毕业、数据不足或只能留在教师侧。

还有几个运行边界需要注意。编码器最多读取 1,024 个 token,长状态只保留头尾,因此小而关键的信号可能被稀释。训练分布改变后,学生可能把格式线索误当成语义线索。教师答案不稳定时,学生也会复制这种不稳定。提示注入、密钥和破坏性动作应优先使用单边路由。当前 Hunch 代理主要处理文本,hunch.live 已支持文本与图像流,但代理的多模态输入仍在开发中。

Hunch live 手势识别演示,从摄像头画面识别竖起大拇指与微笑
hunch.live 的手势演示:同一套编码与逐步毕业机制也可以应用到图像流。

如何运行和评估

项目要求 Rust 1.88 或更高版本以及 uv。初始化环境后下载 EmbeddingGemma 2 权重,即可启动 HTTP 代理或运行示例:

uv sync --all-packages
cd examples && uv run download.py embeddinggemma-2 --yes
uv run --package hunch pytest -q hunch/tests

代理提供回答、训练、问题状态和健康检查接口。训练时既可以发送人工标签,也可以发送教师采样答案;前者用于纠偏,后者用于扩大覆盖。上游教师只需要接受同一个“状态加问题集合”的请求,并返回结构化概率。默认服务只监听本机;加入访问令牌和独立训练令牌后,才能安全暴露给其他进程。

评估时至少要看四组数字:本地覆盖率、与教师的一致率、少数类召回或缺报率、每次本地回答的编码成本。只报告“准确率高于 90%”没有意义,因为在 5% 正例的数据中,永远回答“否”也能达到 95%。Hunch 的毕业规则、审计和预检都围绕这个统计陷阱设计,这也是它比单纯缓存或固定阈值分类器更值得研究的部分。

结论

Hunch 的核心贡献不是某个更大的模型,而是把“何时可以让小模型接管”做成了可审计的运行协议:结构化问题、分层路由、置信区间毕业、陌生样本回退、人工标签纠偏和持续抽查。它适合嵌入智能体轨迹分析、安全过滤、日志标注和高频文本分类;前提是团队愿意先定义可观察的问题、收集真实样本,并接受一部分流量继续付费给教师。

这个边界使它在机器人智能体和通用 AGI 工具链里都有实际位置。机器人运行时会产生大量重复的工具调用、感知描述和故障日志,并不是每一步都需要完整的大模型推理。把高频、可验证的判断下沉到 CPU,把复杂因果判断留给教师,可以得到更稳定的延迟和更透明的质量责任链。

项目源码采用 MIT 或 Apache-2.0 双许可,GitHub API 当前识别为 Apache-2.0;EmbeddingGemma 2 权重单独以 Apache-2.0 分发,不包含在仓库中。完整实现、示例与性能说明见 undrgrnd-labs/hunch。

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。