Bespoke Nimble:一天之内做一个会读概率的 9B 决策模型

Bespoke Nimble: A 9B Decision Model Built in One Day That Reads Probabilities

Tech-Experiment #open-source#decision-model#logprob#LoRA#inference#Qwen
🇨🇳 中文

Jev 还在排队,开源版已经出来了。

Bespoke Labs 发布了 Bespoke Nimble,一个基于 Qwen3.5-9B 的 LoRA 决策模型。核心设计只有一条:它不生成文字,只在你给的选项里挑一个,直接读 logprobs。 数据、训练配方、权重全摊在 GitHub 上(bespokelabsai/nimble,目前约 463 stars)。

这篇文章做一次工程层面的完整拆解。


它解决的问题

用大模型做判断有两种路子:

  1. 让模型写出推理过程,最后再给答案(CoT)——慢,输出量大,还要解析 JSON。
  2. 直接读候选 token 的 logits,转成概率,挑概率最高的——快,零解析成本,天然返回置信度分布。

TypeSafe 的 Jev 走的就是第二条路,做法叫”System One”决策。Bespoke Labs 看了 Niels Rogge 的拆解帖子,用了一天时间,把同样的路子用 LoRA 复现出来,然后把全套东西公开了。

重点:他们没有蒸馏 Jev,Jev 只用来打分


工作方式

使用接口极简:

from nimble.scoring.parallel_scorer import ParallelScorer

scorer = ParallelScorer("path/to/bespoke-nimble-9b")
result = scorer.score(
    context="退款申请:用户在收货后 15 天提交,商品未拆封。",
    schema={
        "eligible": {"type": "boolean"},
        "category": {"type": "enum", "choices": ["full_refund", "partial_refund", "denied"]},
    }
)
# result.eligible.answer → True / False,附带 True/False 的概率
# result.category.answer → "full_refund",附带三个选项的概率分布

两个约束要记住:

  • Schema 必须是平的:没有嵌套字段,每个字段要么是 enum 要么是 boolean。
  • 最多 2,048 tokens:context + schema 合计不能超,超了直接拒。

每个字段独立评分,意味着字段之间没有依赖关系——一个字段的答案看不见另一个字段的结果。


底层机制

每个允许的答案被映射到一个 token(1-token code)。评分时:

prompt → model → logits[token_A, token_B, token_C] → softmax → 概率分布 → 选最高的

没有 JSON 生成,没有解析,没有采样。

在 Mac 上,ParallelScorer 用 MLX 实现,共享 context 只处理一次,然后所有字段并行评分。CUDA 版本每个字段跑一次完整 forward pass,无法共享 KV cache(这是 GPU 上延迟比 Mac 高的原因之一)。


数据和训练

训练集共 2,826 个样本,评估集 324 个。数据构造方法叫对比数据策划(contrastive data curation)

  • 先生成一条正确样本(context + schema + 正确答案)。
  • 修改 context 里的一个关键事实,让答案翻转,生成负样本。
  • 模型从这对对比样本里学习”如何定位关键证据”,而不是记住答案本身。

训练用 LoRA 加在 Qwen3.5-9B 上,BF16 精度,只对候选 token 的位置优化交叉熵。硬件:L40S 上训练,H100 上做最终拟合和评估。


基准测试

在 324 个 held-out 样本上的准确率(来自项目自报数据,请打折看):

模型准确率
Qwen3.5-9B 基础模型66.4%
Bespoke-Nimble-9B90.1%
Jev 1.13.093.2%

跟 Jev 差 3 个点,比基础模型高 24 个点。考虑到只有 2,826 条训练样本,差距已经相当小。

延迟对比(同一 324 样本集):

系统中位数p95
Bespoke-Nimble-9B(H100,120样本子集)106ms120ms
Bespoke-Nimble-9B(M5 Pro 64GB,完整324样本)444ms981ms
Jev 1.13.0(TypeSafe API)247ms347ms
Qwen3.5-9B 基础(H100)58ms76ms

注意:GPU 上 Nimble 比 Jev API 要快,本地 M5 Pro 上用完整 9B 跑当然比云端 API 慢一截。


本地运行(Mac Apple Silicon)

前提:Apple Silicon Mac,Python 3.12(必须用原生 macOS Python 才能用 Metal)。18GB 未量化权重,64GB 内存的 Mac 比 24GB 机器留有更多裕量给 merge 步骤。

git clone https://github.com/bespokelabsai/nimble.git
cd nimble

# 创建 MLX 推理环境
python3.12 -m venv .venvs/mlx
source .venvs/mlx/bin/activate
pip install mlx mlx-lm -r requirements/mlx.txt

# 下载并 merge LoRA adapter
python - <<'PYTHON'
from huggingface_hub import snapshot_download
snapshot_download("bespokelabs/Bespoke-Nimble-9B", cache_dir=".cache/huggingface/hub")
PYTHON

# 运行示例
python examples/basic_scoring.py

目前没有官方量化版本,18GB 就是 18GB。需要降显存的用户要自己量化或等官方后续。


适用场景

场景你定义你得到
请求路由目标列表 + 各自触发条件目标选项 + 每个选项的概率
条件检查是非题 + 证据文本true/false + 置信度
策略执行规则 + 允许的结果基于 context 的决策
结果评级有序等级 + 明确评分标准等级 + 概率分布(可算期望值)

不适用的场景:图片/多模态输入、需要模型自己写文字、嵌套结构输出、单字段超过 26 个选项。


关键限制

  • 概率不等于正确率:0.9 的置信度不代表答案 90% 是对的。输出的概率是 softmax 归一化到你提供的选项上的,如果所有选项都不对,最高概率也会被分到某一个上。实际使用前要在自己的数据集上标定阈值。
  • 训练数据范围窄:2,826 条样本覆盖 10 个类别,对域外任务的泛化能力有限。README 原话:别期望太多泛化,但比基础模型整体还是好。
  • 字段顺序无依赖:单个 prompt 里的多个字段互相看不见答案,需要应用层做一致性校验。
  • 没有量化:目前无官方量化,18GB 是硬门槛。
  • 无标准开源 License:GitHub 没有 SPDX license 标注,商用前需要确认。

工程价值

真正值得关注的不是 90.1% vs 93.2%,而是整套东西全摊开了:

  • 数据怎么造(contrastive curation 的完整流程)
  • LoRA 怎么训(schema-aware 的目标函数)
  • logprob 评分怎么实现(MLX parallel scorer 和 CUDA scorer 都有)
  • 怎么评估(跟 Jev 的对比 app、公开基准测试流程)

这套配方可以移植到任何 Qwen 系列模型上,也可以针对特定领域自己扩充训练数据。前置项目 Bespoke-MiniCheck(跟 Greg Durett 合作的事实核查模型)两年前就在这条路上,Nimble 是逻辑延伸。

开源代码与模型仅供学习研究,请勿直接用于生产系统。


仓库:github.com/bespokelabsai/nimble
模型:huggingface.co/bespokelabs/Bespoke-Nimble-9B
作者:Bespoke Labs + Maheswaran Sathiamoorthy

🇬🇧 English

Jev is still in waitlist. The open-source version just shipped.

Bespoke Labs released Bespoke Nimble, a LoRA decision model based on Qwen3.5-9B. The core design has exactly one rule: it never generates text — it picks from the options you provide by reading logprobs directly. The data, training recipe, and weights are all on GitHub (bespokelabsai/nimble, ~463 stars).

This article is a full engineering teardown.


The problem it solves

Using large models for judgment follows two paths:

  1. Have the model write out reasoning, then give an answer (CoT) — slow, high token count, requires JSON parsing.
  2. Read logits for candidate tokens directly, convert to probabilities, pick the highest — fast, zero parsing cost, returns a confidence distribution naturally.

TypeSafe’s Jev takes the second path, calling it “System One” decision-making. Bespoke Labs saw Niels Rogge’s teardown post, spent one day replicating the approach with LoRA, and open-sourced everything.

Key point: they did not distill from Jev. Jev was only used for scoring.


How it works

The API is minimal:

from nimble.scoring.parallel_scorer import ParallelScorer

scorer = ParallelScorer("path/to/bespoke-nimble-9b")
result = scorer.score(
    context="Refund request: user submitted 15 days after delivery, product unopened.",
    schema={
        "eligible": {"type": "boolean"},
        "category": {"type": "enum", "choices": ["full_refund", "partial_refund", "denied"]},
    }
)
# result.eligible.answer → True/False with per-label probability
# result.category.answer → "full_refund" with full probability distribution

Two hard constraints:

  • Schema must be flat: no nested fields, every field is either enum or boolean.
  • 2,048 token limit: context + schema combined — exceeded prompts are rejected outright.

Each field is scored independently — one field cannot see another field’s answer.


The mechanism

Each allowed answer maps to one token (1-token code). Scoring:

prompt → model → logits[token_A, token_B, token_C] → softmax → probability distribution → argmax

No JSON generation, no parsing, no sampling.

On Mac, ParallelScorer uses MLX: the shared context is processed once, then all fields are scored in parallel. The CUDA scorer runs one full forward pass per field — no KV cache sharing — which is why Mac latency is lower per-example when schemas have multiple fields.


Data and training

Training set: 2,826 samples. Eval set: 324 samples. The data construction method is called contrastive data curation:

  • Generate a correct sample (context + schema + correct answer).
  • Modify one key fact in the context to flip the answer — create a negative sample.
  • The model learns from these contrastive pairs how to isolate critical evidence.

Training applies LoRA on Qwen3.5-9B, BF16 precision, cross-entropy only over candidate token positions. Hardware: L40S for training, H100 for final fit and evaluation.


Benchmarks

Accuracy on 324 held-out samples (self-reported — apply a discount):

ModelAccuracy
Qwen3.5-9B base66.4%
Bespoke-Nimble-9B90.1%
Jev 1.13.093.2%

3 points behind Jev, 24 points above the base model — on only 2,826 training samples.

Latency comparison (same 324-sample set):

SystemMedianp95
Bespoke-Nimble-9B (H100, 120-sample subset)106ms120ms
Bespoke-Nimble-9B (M5 Pro 64GB, full 324)444ms981ms
Jev 1.13.0 (TypeSafe API)247ms347ms
Qwen3.5-9B base (H100)58ms76ms

On GPU, Nimble is faster than the Jev API. On local M5 Pro with the full 9B, it’s slower than the cloud API.


Running locally on Mac (Apple Silicon)

Prerequisites: Apple Silicon Mac, Python 3.12 (native macOS Python for Metal). 18GB unquantized weights. 64GB RAM recommended for the merge step.

git clone https://github.com/bespokelabsai/nimble.git
cd nimble

python3.12 -m venv .venvs/mlx
source .venvs/mlx/bin/activate
pip install mlx mlx-lm -r requirements/mlx.txt

python - <<'PYTHON'
from huggingface_hub import snapshot_download
snapshot_download("bespokelabs/Bespoke-Nimble-9B", cache_dir=".cache/huggingface/hub")
PYTHON

python examples/basic_scoring.py

No official quantization exists yet. 18GB is the floor until the community adds it.


Use cases

Use caseYou defineYou get back
Request routingDestinations + trigger conditionsChosen destination + probability per option
Condition checkingYes/no question + evidenceTrue/False + confidence
Policy enforcementRules + allowed outcomesTyped decision from context
Outcome ratingOrdered levels + clear criteriaLevel + probability distribution (computable expected value)

Not applicable: image/multimodal input, free-text generation, nested output structures, enum fields with more than 26 choices.


Key limitations

  • Probability ≠ correctness: A probability of 0.9 does not mean the answer is correct 90% of the time. The output is softmax-normalized over the options you supplied — if none of your options fit, the highest probability still gets assigned to something. Calibrate thresholds on your own data.
  • Narrow training coverage: 2,826 samples across 10 categories. Out-of-domain generalization is limited. The README is honest: “don’t expect a lot of generalization.”
  • No cross-field dependencies: Fields in a single prompt can’t see each other’s answers. Application code must do consistency checks.
  • No quantization: 18GB is the hard floor for now.
  • No standard OSI license: No SPDX license in the GitHub repo. Verify before any commercial use.

Engineering value

The real story isn’t 90.1% vs 93.2%. It’s that the full stack is open:

  • How to build contrastive data (complete curation pipeline)
  • How to train the LoRA (schema-aware objective function)
  • How to implement logprob scoring (both MLX parallel scorer and CUDA scorer)
  • How to evaluate (Jev comparison app, public benchmark workflows)

This recipe can be ported to any Qwen-series model and extended with domain-specific training data. The predecessor Bespoke-MiniCheck (a factual verification model built with Greg Durett) laid the groundwork two years ago. Nimble is the logical extension.

Open-source code and model are for learning and research only. Do not deploy directly in production systems.


Repository: github.com/bespokelabsai/nimble
Model: huggingface.co/bespokelabs/Bespoke-Nimble-9B
Authors: Bespoke Labs + Maheswaran Sathiamoorthy

💬 评论与讨论

使用 GitHub 账号登录后发表评论

关于本站 · 免责声明

🍄 Mushroom Research Blog 是非营利、免费公开的个人科技观察博客与公众号 XStack18,不接受商业合作、不代表任何企业或机构立场,也不谋求商业利益。我们以个人视角客观中立地记录和分析 AI、Web3 等领域的最新模型发布与技术动态——不止转述新闻标题或二手信息,而是给出有独立思考的深入分析,希望帮更多人获得有价值的一手科技认知。

⚠️ 文中介绍的开源代码与模型,仅供学习交流与技术借鉴。它们大多仍处于早期阶段,有待进一步研究和验证,请勿直接用于工作或生产环境;如需采用,请先自行充分测试,并核实其许可证与安全性。
Open-source code and models featured here are shared for learning and reference only. Most are early-stage and still need further study and verification — please don't use them directly in your work or in production. Test them thoroughly and check their licenses and security first.

  1. 本站文章均为作者基于公开信息的个人研究与观点整理,不代表文中提及的任何公司、产品、模型的官方立场,未与其构成商业关联或合作关系。
  2. 科技行业信息更新极快,我们尽力保证内容准确、及时,但不对完整性、实时性做绝对保证,具体请以相关企业/项目官方公告为准。
  3. 文中引用的第三方商标、产品名称、图片、数据等版权归原权利人所有,我们会尽量注明来源;如你认为存在版权疑问或侵权,请通过下方邮箱联系我们,收到通知后会尽快核实处理(更正、加注来源或删除)。
  4. 文章内容仅为技术科普与个人观点,不构成投资、法律或其他专业建议,据此进行任何决策的后果需自行判断和承担。

📮 侵权 / 勘误 / 合作咨询:[email protected]