Hacker News ★ 91 6 min

Jeeves. Reasoning improves Jev-like decision models

🔗 https://github.com/PostHog/jeeves

📌 Jeeves:讓Jev式分類器先想清楚再決定

TL;DR:PostHog開源9B推理分類器,決策前先思考,準確率贏過Jev與Kev-9B。

多數分類器要嘛快但不夠準,要嘛得靠額外的推理模型當備援。Jeeves想把「思考」這件事直接內建進分類器本身,而不是外掛一個更大的模型。

🤔 Jev式模型的老問題:準但不夠準

Jev-like模型能輸出校準過的決策機率,但準確率偏低,導致許多pipeline得依賴推理模型當fallback。Jeeves的做法是訓練一個Jev-like的Qwen3.5-9B(搭配LoRA與pointer head),用CISPO讓它在做決策前先進行推理,藉此提升分布外(out-of-domain)任務的表現。

🧩 Pointer Head與特殊token的設計

Jeeves把問題、狀態與答案選項套進Qwen的對話模板,讓模型先跑一段<think>推理鏈,推理結束後重新列出問題與選項,再由一個pointer head對每個選項評分:具體是在<decide>位置的hidden state做query投影,與每個選項</opt>位置的hidden state做key投影,計算縮放點積(scaled dot product)。這些特殊標記借用了Qwen tokenizer裡幾乎未被使用的稀有token(如<|fim_prefix|>等)。README提到的消融實驗(ablation)發現,若改用「State」這類一般文字取代特殊token,或省略推理後重複問題的步驟,效果都會變差。最終機率是選項分數的softmax,並除以一個在dev set上校準的溫度值。

訓練分兩階段:先做SFT(2個epoch,8張GPU跑596步),LoRA rank=16覆蓋Qwen3.5-9B所有投影層加pointer head,訓練資料來自12個公開資料集與合成政策資料共19,126題,其中一半附帶從base model取樣的推理鏈;接著用CISPO跑624步排程(在第402步提前停止),透過9,992題RL資料、每題8次rollout、溫度1、推理上限2,560 token進行強化學習。README特別提到,超過402步後calibration會因RL資料飽和而過度尖銳化,因此選擇在此提前停止。

為了加速推理,專案還附上受Orthrus啟發的diffusion drafter:不同於Orthrus只支援attention-only模型,Jeeves的drafter讓mask token能cross-attend Qwen3.5的Gated DeltaNet層的post-convolution key/value,藉此支援這類混合架構。

📊 測試集與JevBench上的表現

基準Kev-9BJevJeeves
Test overall(分布外+held-out)0.8220.8570.889
JevBench overall(231題公開)0.715*0.8660.935
JevBench hard(111題公開)0.451*0.7300.865
Held-out rule structures0.8960.8851.000
Transfer overall(MMLU-Pro/buried state)0.5790.8000.746

(*標記為Kev-8B數字,官方未公布Kev-9B在JevBench的成績)

速度方面,不啟用思考時約0.3秒/請求,啟用思考後在一張H100上中位數約3.3秒;透過截斷推理鏈長度(max_think)與nothink_threshold可以在準確率與延遲間取捨,例如把max_think設為768並搭配nothink_threshold 0.9,dev集準確率從全推理的0.825降到0.806,但中位延遲從3.3秒縮到2.0秒。

🛠 怎麼用:Jev相容API與Python SDK

安裝後可直接下載官方權重並啟動伺服器(python -m inference.serve),或自行訓練後用export.py融合成獨立模型再serve。API走/v1/systemone端點,請求格式與Jev相容,同一次請求可混合yes/no(noul)、多選(choice)、評分(score)三種題型。專案也提供sdk/作為Jev官方Python SDK(typesafe-sdk)的直接替代品,介面幾乎一致。

⚠️ 並非全面領先

數據顯示Jeeves並非在所有基準都贏過Jev:Transfer overall(0.746 vs 0.800)與MMLU-Pro(0.739 vs 0.840)上Jeeves反而低於Jev,說明它在分布遷移任務上的泛化仍有落差。此外完整推理模式延遲較高(中位3.3秒、p90達17.1秒),且FP8 kernel僅支援Hopper架構GPU,部署門檻不低。

🎯 實務啟示

如果你的pipeline正在用Jev式分類器搭配額外推理模型當fallback,Jeeves提供了一條把「思考」直接內建進分類器的路徑,且完整開源訓練程式碼與train/dev/test資料,方便直接復現或在自有資料上重新訓練。API與SDK都刻意做成Jev相容,遷移成本低;但上線前建議依自身任務對照上表,確認遷移後的表現是否真的優於既有方案。

🔗 來源

  • 標題:Jeeves. Reasoning improves Jev-like decision models
  • 作者/機構:PostHog(Hacker News 分享者:nicowaltz)
  • 連結:https://github.com/PostHog/jeeves

#LLM #MachineLearning #Classification #ReinforcementLearning #Qwen #PostHog #ModelServing #Inference #AIEngineering #OpenSource

原始資料 Hacker News · 收集於 2026-09-29
來源原標題
Jeeves. Reasoning improves Jev-like decision models
作者
nicowaltz
原始標籤
hackernews
來源訊號
HN points 212 HN 留言 85
原始連結
https://github.com/PostHog/jeeves

摘要原文

Jeeves – Reasoning improves Jev-like decision models A reasoning Jev-style classifier with a diffusion drafter, trained with SFT and CISPO. Acknowledgements Inspired by Kev . Highlights A 9B Jev-like model (Qwen3.5-9B, LoRA, pointer head) that thinks before it decides, with a block-4 diffusion drafter and the full training code and train/dev/test data. Beats Kev-9B and Jev on test data it was never trained on (0.889 vs 0.822 and 0.857) and on JevBench's public tiers (0.935 vs 0.866 for Jev). Supports yes/no ( noul ), multiple-choice ( choice ), and rating ( score ) questions in the same request, through a Jev-compatible API. About 0.3 s per request without thinking and a 3.3 s median with it on one H100. Can be sped up by truncating chain length. Runs on CUDA (Hopper for the FP8 kernel). Problem Jev-like models give calibrated decision probabilities, but at low accuracy. A lot of pipelines therefore rely on a reasoning model as a fallback. Jeeves trains a Jev-like Qwen3.5-9B (LoRA and a pointer head) using CISPO to reason before it decides. This results in better performance on out of domain tasks, and outperforms Jev in JevBench hard (public). Results Accuracy with thinking, greedy, 2,560-token cap. The Kev-9B and Jev columns are the numbers Kev publishes. bench Kev-9B Jev Jeeves Test overall (out-of-domain and held-out, item-weighted) 0.822 0.857 0.889 Transfer overall (MMLU-Pro and buried state) 0.579 0.800 0.746 JevBench overall (231 public items) 0.715* 0.866 0.935 QNLI 0.925 0.925 0.913 SciQ 0.963 0.988 0.991 TweetEval offensive 0.775 0.813 0.813 PAWS 0.763 0.788 0.875 MMLU 0.738 0.900 0.793 Emotion 0.600 0.588 0.647 Held-out rule structures 0.896 0.885 1.000 Contrastive policies 0.900 0.963 1.000 MMLU-Pro (10-way) 0.515 0.840 0.739 Buried state 0.740 0.700 0.759 Unknowable answered at p ≥ 0.9 (lower is better) 0.000 0.090 0.055 JevBench hard (111 public items) 0.451* 0.730 0.865 JevBench ECE (public items) 0.049 0.037 * No Kev-9B JevBench result is published. These are Kev-8B (Qwen3). All JevBench numbers are on the public easy, standard and hard tiers (231 items). The sealed judge tier is not included, and the Jev and Kev numbers are restricted to the same public items. Without thinking the same checkpoint scores 0.804 on our test split (2,962 items), against 0.840 with it. Quickstart Requirements: Python 3.12 and a CUDA GPU. pip install -r requirements.txt Download the released weights and serve them: hf download PostHog/jeeves --local-dir jeeves-weights python -m inference.serve --model jeeves-weights --drafter jeeves-weights/drafter_k4.safetensors --port 8009 Or fuse your own trained checkpoint into a standalone model and serve it with a drafter: python export.py runs/cispo/final --out runs/fused python -m inference.serve --model runs/fused --drafter runs/drafter_k4/drafter.safetensors --port 8009 Then send a request in Jev's format: curl -s localhost:8009/v1/systemone -H ' content-type: application/json ' -d ' { "state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges on my card.", "questions": { "department": {"type": "choice", "instructions": "Which team should handle this?", "criteria": {"returns": "Exchanges, refunds, wrong or damaged items", "shipping": "Delivery status, delays, lost packages", "billing": "Charges, invoices, payment problems"}}, "escalate": {"type": "noul", "instructions": "Does this need urgent human attention?"}, "frustration": {"type": "score", "instructions": "How frustrated is the customer?", "criteria": ["Calm", "Frustrated", "Very angry"]} }, "options": {"max_think": 512}} ' Response on one H100 (FP8), with the three questions thinking in parallel: { "model" : " jeeves-latest " , "answers" : { "department" : { "type" : " choice " , "choice" : " billing " , "confidence" : 0.19 , "probabilities" : { "returns" : 0.4 , "shipping" : 0.14 , "billing" : 0.46 } }, "escalate" : { "type" : " noul " , "noul" : 0.72 }, "frustration" : { "type" : " score " , "score" : 1.5 , "legend" : { "0" : " Calm " , "1" : " Frustrated " , "2" : " Very angry " }, "probabilities" : { "0" : 0.04 , "1" : 0.43 , "2" : 0.54 }, "confidence" : 0.75 } }, "usage" : { "input_tokens" : 129 , "output_tokens" : 160 , "reasoning_tokens" : 1536 }, "latency_ms" : 8141.6 } Python sdk/ is a drop-in replacement for Jev's Python SDK ( typesafe-sdk ): pip install ./sdk from jeeves_sdk import Choice , Noul , Score , TypeSafeClient with TypeSafeClient () as client : result = client . system_one ( state = "I was charged twice. Please help." , questions = { "billing" : Noul ( instructions = "Is this about billing?" ), "tone" : Choice ( instructions = "What is the tone?" , criteria = { "calm" : None , "angry" : None }), "urgency" : Score ( instructions = "How urgent is this?" , criteria = [ "can wait" , "this week" , "today" ]), }, max_think = 768 , return_reasoning = True , ) print ( result . nouls [ "billing" ]. noul , result . choices [ "tone" ]. choice , result . scores [ "urgency" ]. score ) print ( result . reasoning [ "tone" ]. text ) The client connects to http://127.0.0.1:8009 by default (or JEEVES_BASE_URL ), needs no API key, and waits up to 120s. Options options is optional and ignored by Jev clients that don't send it. Server-wide defaults are set with the matching serve flags. option default effect think true false answers from the prompt alone (about 0.3 s) max_think 2560 truncates each reasoning chain at this many tokens, then answers nothink_threshold null answers without thinking when the no-think confidence is at least this value return_reasoning false adds each question's reasoning text to the response On 325 dev questions: setting accuracy mean reasoning tokens median / p90 latency full thinking 0.825 1,138 3.3 s / 17.1 s max_think 768, nothink_threshold 0.9 0.806 344 2.0 s / 5.6 s no thinking 0.775 0 about 0.3 s How it works Questions, states and answers are loaded into the Qwen chat template like <state> …state… <q> instructions <opt> option 1 </opt> <opt> option 2 </opt> … <think> The model then rolls out its reasoning chain, and after the </think> token we append </think> <q> instructions <opt> option 1 </opt> <opt> option 2 </opt> … <decide> A pointer head scores each option with a scaled dot product between a query projection of the hidden state at <decide> and a key projection of the hidden state at that option's </opt> , where <state>, <q>, <opt>, </opt>, <decide> = "<|fim_prefix|>", "<|fim_middle|>", "<|box_start|>", "<|box_end|>", "<|fim_suffix|>" These are rare, largely unused tokens in the Qwen tokenizer. Ablations found that using plain text like "State" in the prompt instead worsened performance. Likewise, not repeating the questions after the reasoning block also decreases performance. The final probabilities are a softmax over the option scores, divided by a temperature fitted on the dev set. Training SFT (2 epochs, 596 steps on 8 GPUs). LoRA r=16 on all projections of Qwen3.5-9B plus the pointer head, trained on 19,126 questions from 12 public datasets and synthetic policy data. Half the questions carry a reasoning chain sampled from the base model. CISPO (a 624-step schedule stopped at step 402). 9,992 RL questions, 8 rollouts each at temperature 1, capped at 2,560 thinking tokens. Calibration . A single temperature fitted on dev, stored with the checkpoint. Stopping at step 402 keeps the best calibration and dev score. Past it, the head over-sharpens on the saturated RL pool. Diffusion drafter A diffusion view of the frozen model ( drafter/ ), inspired by Orthrus . Unlike Orthrus, which supports attention-only models, it supports Qwen3.5's Gated DeltaNet layers by letting mask tokens cross-attend to those layers' post-convolution keys and values. chain tokens per second plain graphed greedy decoding, one question 109 block 4, one question 176 (1.6×) block 8, one question 193 (1. (212 points, 85 comments on Hacker News)

由 claude-code/sonnet 自動生成