Hugging Face 中的决策模型
2026 年 11 月 10 日
作者:Aritra Roy Gosthipaty、Lysandre、Pedro Cuenca、Tom Aarsen
大型语言模型(Large Language Models,LLM)非常擅长生成文本。我们用它们回答问题、编写代码、推理问题等等。但并非每个问题都需要生成文本。
设想一个客户支持系统收到以下消息:
“我的订阅被扣了两次费。我今天需要退款。”
系统需要确定哪个团队应处理该请求、它有多紧急,以及客户是否在请求退款。
我们可以让 LLM 返回一个包含这些答案的 JSON 对象。
llama cli -hf ggml-org/gemma-4-e4b-it-GGUF:Q4_0
> We have the following customer support ticket, "I was charged twice for my subscription. I need a refund today.", which team should handle the request, how urgent it is, and whether the customer is requesting a refund. Give the answers in JSON.
[Start thinking]
...
[end thinking]
{ "team_to_handle": "Billing", "urgency": "High", "request_refund": true }
但如果模型能直接评估可用选项并返回它们的概率分布,而不生成任何文本呢?
from transformers import pipeline
decide = pipeline("decision", model="autotrust/GEV-26B-Decide") decision = decide( { "state": "I was charged twice for my order and want a refund.", "questions": { "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Payments and refunds", "technical": "Bugs and technical issues", "sales": "Pricing and subscriptions" } }, } } )
{#
'department': {#
'type': 'choice',#
'choice': 'billing',#
'confidence': 0.9974000453948975,#
'probabilities': {#
'billing': 0.9982666969299316,#
'technical': 0.0010246571619063616,#
'sales': 0.0007087149424478412#
}#
}#
}#
这就是决策模型背后的核心思想。
## 什么是决策模型?
决策模型接收一些输入上下文(状态),评估一个带有预定义可能答案(原语)的问题,并返回结构化决策及概率。我们不是让模型逐 token 生成答案,而是让它为我们应用已经理解的答案分配概率。
TypeSafe AI 推出了 Jev,将其描述为 System One 模型。这个名字的灵感来自快速、直觉性思维(系统 1)与较慢、审慎思维(系统 2)之间的区别。
社区已经统一使用“决策模型(decision model)”这一术语,因为它描述的是模型的计算角色,而不是援引某种特定的认知理论。在本文余下部分,我们会称这些模型为“决策模型”,而不是“系统一模型”。(Twitter 上有一项投票,决定我们应如何称呼这类模型。)
对于决策模型,基本接口是:
**状态 + 问题 + 允许答案 → 决策 + 概率**
## 为什么不直接让 LLM 做所有事?
我们完全可以提示 LLM 返回一个包含决策的 JSON 对象。事实上,许多应用已经这样使用语言模型进行分类、路由和其他结构化任务。
然而,这种方法有几个问题。
**LLM 不保证遵循要求的模式**:即使有明确指令,模型也可能产生无效 JSON、引入意外键,或返回允许选项之外的答案。
**约束解码有帮助,但不能消除计算开销**:Outlines 和 XGrammar 等库可以通过限制模型在每个步骤允许生成的 token 来强制语法或 JSON 模式。这确保输出遵循受支持的结构约束,但会引入额外工作。
**为什么要生成整个响应**:LLM 是为开放式生成设计的,但当我们的应用只需要在预定义答案之间选择时,自回归解码可能是不必要的工作。
## 决策模型与分类模型有何不同?
在了解决策模型的接口之前,有必要回顾两种密切相关的做法:传统分类和零样本分类。
### 传统分类
假设我们训练一个分类器,把客户支持请求路由到三个部门:
- 账单
- 技术支持
- 销售
传统分类模型学习从输入到预定义标签集的映射。训练期间,模型从示例中学习预测这些标签。分类头通常为每个类别产生一个 logit,softmax 把这些 logits 转化为概率分布。
这里的限制在于输出类别被固化在模型的分类头中。
如果我们新增第四个部门,例如法务,现有分类器不会自动获得相应的输出类别。我们可能需要修改分类头、收集训练数据,并重新训练或调整模型。
### 零样本分类
零样本分类解决了这一限制的重要部分。它不要求分类器专门针对每个输出类别训练,而是在推理时提供候选标签。
例如:
from transformers import pipeline
classifier = pipeline( "zero-shot-classification", model="facebook/bart-large-mnli", )
classifier( "My payment was deducted twice.", candidate_labels=["billing", "technical support", "sales"], )
{'sequence': 'My payment was deducted twice.',#
'labels': ['billing', 'sales', 'technical support'],#
'scores': [0.8466812372207642, 0.0962134450674057, 0.05710534751415253]}#
与传统固定标签分类器不同,类别可以从一个请求到另一个请求变化。让我们添加另一个部门,例如“法务”,看看模型输出。
from transformers import pipeline
classifier = pipeline( "zero-shot-classification", model="facebook/bart-large-mnli", )
classifier( "My payment was deducted twice.", candidate_labels=["billing", "technical support", "sales", "legal"], )
{'sequence': 'My payment was deducted twice.',#
'labels': ['billing', 'sales', 'technical support', 'legal'],#
'scores': [0.8185835480690002, 0.09302053600549698, 0.05521026626229286, 0.03318570926785469]}#
一种常见实现使用为自然语言推理(Natural Language Inference,NLI)训练的模型。每个候选标签被转换为一个假设,模型衡量输入在多大程度上蕴含该假设。
这听起来已经与决策模型相当相似。在许多方面,确实如此。
### 决策模型的不同之处
决策模型把这一思想扩展到仅仅把输入分配到一个类别之外。决策模型可以接受关于同一状态的多个具名问题,且答案类型不同。类型可以是在备选方案之间选择(choice)、在有序量表上评分(score),或估计某个陈述为真的概率(noul)。
更重要的是,它的接口围绕可能决策上的概率分布而设计。
考虑这三种方法:
| 属性 | 分类 | 零样本分类 | 决策模型 |
| --- | --- | --- | --- |
| 选项上的概率 | 是 | 是 | 是 |
| 推理时定义标签 | 否 | 是 | 是 |
| 多个带类型的问题 | 否 | 否 | 是 |
| 有序评分和是/否决策 | 特定于任务 | 可通过适配实现 | 原生原语 |
决策模型的优势不在于它们发明了运行时定义的标签或概率分类。而在于它们把这些能力组织成一个一致接口,并可能由专门为决策优化的架构和训练流程支撑。
## 决策模型接口
一个决策模型请求包含以下两个组成部分:
- `state`
- `questions`
### 状态
状态是模型应评估的信息。它可以是字符串、结构化 JSON 对象,或受支持的全模态模型的其他模态。
例如:
{ "customer_message": "I was charged twice. Please refund me today.", "subscription": "pro", "previous_tickets": 2 }
### 问题及其原语
问题描述我们想了解状态的什么。在决策模型接口中,三种基本原语代表不同形式的决策:
- **Choice**:从若干可能答案中选择一个。
- **Score**:在有序量表上评估某事物。
- **Noul**:以概率方式回答是/否问题。
让我们逐一介绍。
### Choice:选项上的概率分布
Choice 是最直观的原语,也最接近传统分类。
假设我们问:
**这个客户请求应由哪个部门处理?**
可用选项是账单、技术支持和销售。
from transformers import pipeline
decide = pipeline("decision", model="autotrust/GEV-26B-Decide") decision = decide( { "state": "I was charged twice for my order and want a refund.", "questions": { "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Payments and refunds", "technical": "Bugs and technical issues", "sales": "Pricing and subscriptions" } }, } } )
{#
'department': {#
'type': 'choice',#
'choice': 'billing',#
'confidence': 0.9974000453948975,#
'probabilities': {#
'billing': 0.9982666969299316,#
'technical': 0.0010246571619063616,#
'sales': 0.0007087149424478412#
}#
}#
}#
模型为每个选项分配一个 logit。对于 logits($z_1,\ldots,z_K$),(有 K 个类别)我们可以使用 softmax 获得概率:
$$p_i=\frac{\exp(z_i/T)}{\sum_{j=1}^{K}\exp(z_j)}$$
这里 (K) 是选项数量。
考虑这个示例分布:
| 选项 | 概率 |
| --- | --- |
| 账单 | 0.998 |
| 技术支持 | 0.001 |
| 销售 | 0.001 |
概率之和为一:
$$\sum_{i=1}^{K}p_i=1.$$
模型选择概率最大的选项:
$$\hat y=\arg\max_i p_i$$
在本例中,选择是账单(Billing),概率为 0.998,但其余概率也有用。它们告诉我们模型如何在备选方案之间分配信念。
### Score:有序分布的期望
当存在若干不同备选方案时,Choice 很适用。但如果我们想在有序量表上衡量某事物呢?
考虑这个问题:
**这个客户请求有多紧急?**
我们定义三个级别:
- 0:低紧急度
- 1:中紧急度
- 2:高紧急度
from transformers import pipeline
decide = pipeline("decision", model="autotrust/GEV-26B-Decide") decision = decide( { "state": "I was charged twice for my order and want a refund.", "questions": { "urgency": { "type": "score", "instructions": "How urgent is the request?", "criteria": ["low", "medium", "high"] }, } } )
{#
'urgency': {#
'type': 'score',#
'score': 1.7053890228271484,#
'legend': {#
'0': 'low',#
'1': 'medium',#
'2': 'high'#
},#
'confidence': 0.5580834252759814,#
'probabilities': {#
'0': 0.014908230863511562,#
'1': 0.26479458808898926,#
'2': 0.7202972173690796#
}#
}#
}#
模型再次产生概率分布:
| 级别 | 概率 |
| --- | --- |
| 0 — 低 | 0.10 |
| 1 — 中 | 0.26 |
| 2 — 高 | 0.72 |
Score 原语不是只返回最可能的级别,而是计算级别索引的期望值。
对于表示级别的随机变量 (X),期望为:
$$\mathbb{E}[X]=\sum_{i=0}^{K-1}ip_i$$
对于我们的例子:
$$\mathbb{E}[X]=0(0.10)+1(0.26)+2(0.72)=1.7$$
因此报告的分数是 1.7。
这并不意味着模型选择了一个介于中与高之间的虚构级别。它意味着其概率分布的期望是 1.7。
### Noul:伯努利决策
第三个原语是 Noul,它表示二元问题。
例如:
**客户是否在请求退款?**
from transformers import pipeline
decide = pipeline("decision", model="autotrust/GEV-26B-Decide") decision = decide( { "state": "I was charged twice for my order and want a refund.", "questions": { "refund": { "type": "noul", "instructions": "Is the customer requesting a refund?" } } } )
{#
'refund': {#
'type': 'noul',#
'noul': 0.9947046637535095#
}#
}#
我们没有定义选项列表,而是让模型返回答案为真的概率。我们可以用伯努利分布来理解(noul 来自 Bernoulli)。
令 (Y) 表示二元结果:
$$Y\sim\operatorname{Bernoulli}(p)$$
其中
$$P(Y=1)=p,\qquad P(Y=0)=1-p.$$
如果模型返回:
{"type": "noul", "noul": 0.92}
则它给“是”分配 0.92 的概率,给“否”分配 0.08。
模型在这里不是随机采样一个二元答案。它返回概率参数本身,由应用决定如何据此行动。
### 置信度:模型有多确定?
到目前为止,我们已经从模型获得了概率分布。但决策模型接口还会为 Choice 和 Score 返回一个名为 `confidence` 的字段。
完整分布可能包含若干可能答案,概括模型信念的集中程度很有用。`confidence` 正是实现这一点。
对于有 (K) 个选项的 Choice,置信度为:
$$\frac{p_{\max}-\frac{1}{K}}{1-\frac{1}{K}}$$
其中($p_{\max}$)是最大预测概率。
这里的直觉是:
- 均匀分布的置信度为 0。($p_{\max} = \frac{1}{K}$)。这意味着模型估计所有选择都同样可能。
- 完全集中于一个选项的分布置信度为 1。($p_{\max} = 1$)
获胜概率相对均匀分布越大,置信度越高。
在我们之前的三选一示例中,账单概率为 0.80:
$$C=\frac{3(0.99)-1}{2}=0.98$$
注意获胜概率是 0.99,但报告的置信度是 0.98。置信度把最大概率相对于可用选项数量进行归一化。相比之下,有序 Score 示例的置信度仅为 0.558。
## Hugging Face 生态系统中的决策模型
决策模型是一个年轻且快速发展的领域。每个模型在底层做出决策的方式都不同(联合为整个模式打分、在 LLM 主干上增加独立读出、边缘规模和多模态变体等)。这种多样性是健康的,但也有代价。如果每个模型都暴露自己的接口,周围的每个工具都需要针对模型做适配。
我们在 Hugging Face 的目标是在不强制统一实现的前提下,就共同接口达成一致。决策模型可以在内部以任何方式工作,只要它接受相同的状态和问题,并返回相同的类型化决策。一旦这一契约存在,生态系统的其余部分就可以一次性构建在其上:
- 像 llama.cpp 这样的推理引擎可以通过相同 API 服务任何决策模型
- Hub 可以一致地打包和展示它们
- 训练工具可以针对共享原语,而不是单个架构
本节其余部分将逐一介绍这些部分。
### 1. 使用 Transformers 提供服务
Transformers 现在支持 `DecisionPipeline`、`AutoModelForDecision` 和 `DecisionConfig` 类。它们遵循 transformers 中 pipeline 和模型类的整体结构,但专为决策模型定制。
from transformers import pipeline
decide = pipeline("decision", model="autotrust/GEV-26B-Decide") print(decide)
DecisionPipeline: {'model': 'JevForDecision', 'dtype': 'bfloat16', 'device': 'cuda', 'input_modalities': 'text'}#
也可以像这样检查配置和模型:
from transformers import AutoModelForDecision, DecisionConfig
model_id = "autotrust/GEV-26B-Decide" config = DecisionConfig.from_pretrained(model_id) model = AutoModelForDecision.from_pretrained(model_id)
Transformers 还支持 `/v1/systemone` 端点。
我们可以使用服务依赖启动本地服务器:
pip install "transformers[serving]" transformers serve
使用兼容版本的 Transformers,我们可以发送决策请求:
curl http://localhost:8000/v1/systemone \ -H "Content-Type: application/json" \ -d '{ "model": "autotrust/GEV-26B-Decide", "state": "I was charged twice. Please refund me today.", "questions": { "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Payments and refunds", "technical": "Bugs and technical issues", "sales": "Pricing and subscriptions" } }, "urgency": { "type": "score", "instructions": "How urgent is the request?", "criteria": ["low", "medium", "high"] }, "refund": { "type": "noul", "instructions": "Is the customer requesting a refund?" } } }'
响应包含三个具名答案及相应的类型化结果和概率。
{ "model":"autotrust/GEV-26B-Decide", "answers":{ "department":{ "type":"choice", "choice":"billing", "confidence":0.9974000453948975, "probabilities":{ "billing":0.9982666969299316, "technical":0.0010246571619063616, "sales":0.0007087149424478412 } }, "urgency":{ "type":"score", "score":1.7525686174631119, "legend":{"0":"low","1":"medium","2":"high"}, "confidence":0.6288529792800546, "probabilities":{ "0":0.014000383205711842, "1":0.2194305807352066, "2":0.7665690183639526 } }, "refund":{ "type":"noul", "noul":0.9953224062919617 } }, "usage":{"input_tokens":148,"output_tokens":0} }
一个有趣的细节是,上述 API 也适用于兼容的自回归语言模型。Transformers 会构造决策提示,在批量前向传递中评估答案标签 logits,并应用 softmax,而不生成响应文本。这意味着我们不必训练新模型就可以试验决策接口。
### 2. 使用 llama.cpp 提供服务
llama.cpp 也使用相同的 `/v1/systemone` API 支持决策模型。
例如,我们可以从 Hub 启动一个 GGUF 决策模型:
llama serve -hf ggml-org/Kev-4B-GGUF
默认服务器运行在 9931 端口。
然后我们可以使用 `http://localhost:9931/v1/systemone` 发送包含相同状态和问题的请求。由于模型由服务器选定,我们可以省略请求中的 `model` 字段。
有用的特性是接口兼容性。
curl http://localhost:9931/v1/systemone \ -H "Content-Type: application/json" \ -d '{ "model": "ggml-org/Kev-4B-GGUF", "state": "I was charged twice. Please refund me today.", "questions": { "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Payments and refunds", "technical": "Bugs and technical issues", "sales": "Pricing and subscriptions" } }, "urgency": { "type": "score", "instructions": "How urgent is the request?", "criteria": ["low", "medium", "high"] }, "refund": { "type": "noul", "instructions": "Is the customer requesting a refund?" } } }'
### 3. 在 Hugging Face Hub 上发现模型
Hugging Face 引入了专门的决策模型标签,用于发现兼容模型。这很有用,因为决策模型不必共享单一主干或架构。
例如,生态系统中既有基于 ModernBERT 等编码器架构的模型,也有源自更大生成式语言模型主干的模型。
### 4. 使用 Hugging Face 工具训练决策模型
了解了推理之后,下一个问题是我们能否调整决策模型。由于决策模型已集成到 transformers 中,我们可以使用 Trainer API 微调它们。
下面是一个玩具示例,微调 JEV-9B 来路由支持请求。它有意保持很小,但展示了关键部分:
- 每个示例都是一个决策请求(状态加问题),并与金标准答案配对。
- `model.decision_config.prepare_inputs` 像推理时一样把请求转换为模型输入。
- 模型为每个选项返回一个 logit,因此 choice 问题用选项上的普通交叉熵损失训练。
import os; os.environ["CUDA_VISIBLE_DEVICES"]="2" import torch import torch.nn.functional as F from transformers import AutoTokenizer, AutoModelForDecision, Trainer, TrainingArguments
checkpoint = "autotrust/JEV-9B" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForDecision.from_pretrained(checkpoint)
examples = [ ("I was charged twice. Please refund me.", "refund"), ("My package has not arrived.", "shipping"), ("The application crashes when I open it.", "technical"), ("I want my money back.", "refund"), ("Where is my delivery?", "shipping"), ("I cannot log into my account.", "technical"), ]
options = { "refund": "Billing issues and refunds", "shipping": "Orders and deliveries", "technical": "Software and account issues", }
def preprocess(text, label): request = { "state": text, "questions": { "category": { "type": "choice", "instructions": "Classify the support request.", "criteria": options, } }, } (inputs, questions), = model.decision_config.prepare_inputs( request, tokenizer ) inputs["marks"] = inputs["marks"].unsqueeze(0) inputs["num_options"] = torch.tensor([[len(questions[0].options)]]) inputs["question_type_ids"] = torch.tensor([[1]]) # choice inputs["labels"] = torch.tensor( [questions[0].options.index(label)] ) return inputs
dataset = [preprocess(text, label) for text, label in examples]
class DecisionTrainer(Trainer): def compute_loss(self, model, inputs, return_outputs=False, kwargs): inputs = inputs.copy() labels = inputs.pop("labels") logits = model(inputs)
logits = logits.unsqueeze(0) # [3] -> [1, 3] loss = F.cross_entropy(logits.float(), labels)
return (loss, logits) if return_outputs else loss
trainer = DecisionTrainer( model=model, args=TrainingArguments( output_dir="./decision-toy", max_steps=30, per_device_train_batch_size=1, learning_rate=1e-3, logging_steps=5, save_strategy="no", report_to="none", remove_unused_columns=False, ), train_dataset=dataset, data_collator=lambda batch: batch[0], )
trainer.train()
对决策模型训练的一等支持也即将进入 TRL(huggingface/trl#8040),因此我们不必手写这些管道。截至撰写时,该 PR 仍处于开放状态。
如果我们想更进一步,把生成式 LLM 变成决策模型,Unsloth 提供了一份配方,使用 LoRA 和决策评分头微调 Qwen 等模型。
## 最后思考
决策模型之所以有趣,不是因为分类是新的,而是因为它们让决策和概率成为软件的一等输出。
传统分类器已经产生概率,零样本分类器已经允许运行时定义类别。决策模型把这些思想带入一个类型化接口,具有多种问题原语、非生成式评分,以及为可靠、校准良好的决策进行专门训练的可能性。