返回 文章 学习 CMS 文章

决策模型:让LLM直接输出概率分布,而非生成文本

决策模型用概率分布替代文本生成,为结构化决策任务提供更高效、可靠的接口。

决策模型LLM概率分布Hugging Face
成长分 / 100 72 综合收获、行动、留存与影响

决策模型:让LLM直接输出概率分布,而非生成文本
为什么值得读理解决策模型与传统分类、零样本分类的本质区别,以及为何在结构化任务中优于LLM生成。

掌握决策模型的三种基本原语(Choice、Score、Noul)及其概率输出形式。

关键洞察
  1. 决策模型的核心接口是State + Questions + Allowed Answers → Decisions + Probabilities,直接对预定义答案分配概率,无需逐token生成。
  2. 相比LLM生成JSON,决策模型避免了schema违规、约束解码开销和自回归解码的冗余计算。
  3. 与传统分类模型不同,决策模型的标签在推理时定义,且支持多个带类型的问题(choice、score、noul)。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:25809

大型语言模型(LLMs)非常擅长生成文本。我们用它们来回答问题、编写代码、推理问题等等。但并非所有问题都需要生成文本。

考虑一个客户支持系统收到以下消息:

“我的订阅被重复收费了。我今天需要退款。”

系统需要确定哪个团队应处理该请求、请求的紧急程度以及客户是否在请求退款。

我们可以要求 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]
```json
{
  "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
#       }
#     }
# }

这就是决策模型背后的核心思想。

什么是决策模型?

决策模型接收一些输入上下文(状态),评估一个带有预定义可能答案(原语)的问题,并返回一个结构化的决策以及概率。我们不是要求模型逐词生成答案,而是要求它为我们的应用已经理解的答案分配概率。

决策模型的状态和原语

TypeSafe AI 推出了 Jev,将其描述为系统一模型。这个名字的灵感来源于快速、直觉的思维(系统1)与较慢、审慎的思维(系统2)之间的区别。

社区已经统一使用术语决策模型,因为它描述了模型的计算角色,而不是援引特定的认知理论。在本文的剩余部分,我们将把这些模型称为“决策模型”而不是“系统一模型”。(Twitter 上的一项投票决定了我们应该如何称呼此类模型)

对于决策模型,基本接口是:

State + Questions + Allowed Answers → Decisions + Probabilities

为什么不使用 LLM 做所有事情?

我们可以简单地提示 LLM 返回一个包含我们决策的 JSON 对象。事实上,许多应用已经这样使用语言模型进行分类、路由和其他结构化任务。

然而,这种方法存在一些问题。

  • LLM 不保证遵循请求的模式:即使有明确的指令,模型也可能产生无效的 JSON、引入意外的键或返回允许选项之外的答案。
  • 约束解码有帮助,但不能消除计算开销:像 Outlines 和 XGrammar 这样的库可以通过限制模型在每一步允许生成的 token 来强制语法或 JSON 模式。这确保输出遵循支持的结构约束,但引入了额外的工作。
  • 为什么要生成整个响应:LLM 是为开放式生成而设计的,但当我们的应用只需要在预定义答案之间进行选择时,自回归解码可能是不必要的工作。

决策模型与分类模型有何不同?

在理解决策模型的接口之前,回顾两种密切相关的方法——传统分类和零样本分类——是有用的。

传统分类

假设我们训练一个分类器,将客户支持请求路由到三个部门:

  • 账单
  • 技术支持
  • 销售

传统分类模型学习从输入到预定义标签集的映射。在训练期间,模型学习从示例中预测这些标签。分类头通常为每个类别产生一个 logit,softmax 将这些 logit 转换为概率分布。

这里的限制是输出类别被固化在模型的分类头中。

如果我们引入第四个部门,例如法务,现有的分类器不会自动获得相应的输出类别。我们可能需要修改分类头、收集训练数据,并重新训练或调整模型。

零样本分类

零样本分类解决了这一限制的重要部分。我们不需要分类器专门针对每个输出类别进行训练,而是在推理时提供候选标签。

例如:

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]}

一种常见的实现使用为自然语言推理(NLI)训练的模型。每个候选标签被转换为一个假设,模型衡量输入蕴含该假设的强度。

这听起来已经与决策模型非常相似。在许多方面,确实如此。

决策模型的不同之处

决策模型将这一思想扩展到不仅仅是将输入分配给一个类别。决策模型可以接受关于同一状态的多个命名问题,并具有不同的答案类型。类型可以是选择备选方案(选择)、对有序量表评分(评分)或估计陈述为真的概率(noul)。

更重要的是,其接口围绕可能决策上的概率分布设计。

考虑这三种方法:

属性 分类 零样本分类 决策模型
选择上的概率 是 是 是
推理时定义标签 否 是 是
多个类型化问题 否 否 是
有序评分和是/否决策 任务特定 可通过适配实现 原生原语

决策模型的优势不在于它们发明了运行时定义的标签或概率分类。而在于它们将这些能力组织成一个一致的接口,可能由专门为决策优化的架构和训练程序支持。

决策模型接口

决策模型请求包含以下两个组成部分:

  1. 状态
  2. 问题

状态

状态是模型应当评估的信息。它可以是字符串、结构化 JSON 对象,或受支持的全模态模型的其他模态。

例如:

{
  "customer_message": "I was charged twice. Please refund me today.",
  "subscription": "pro",
  "previous_tickets": 2
}

问题及其原语

问题描述我们想了解状态的哪些方面。在决策模型接口中,三个基本原语代表不同形式的决策:

  1. 选择(Choice):从若干可能答案中选择一个。
  2. 评分(Score):在有序尺度上评估某事物。
  3. Noul:以概率方式回答是/否问题。

我们逐一来看。

选择:选项上的概率分布

选择是最直观的原语,也最接近传统分类。

假设我们问:

这个客户请求应由哪个部门处理?

可用选项为 Billing、Technical support 和 Sales。

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 得到概率:

pi=exp⁡(zi/T)∑j=1Kexp⁡(zj)pi=exp⁡(zi/T)∑j=1Kexp⁡(zj)pi=exp⁡(zi/T)∑j=1Kexp⁡(zj) p_i=\frac{\exp(z_i/T)}{\sum_{j=1}^{K}\exp(z_j)}

这里,(K)是选项数量。

考虑这个示例分布:

选择 概率
Billing 0.998
Technical support 0.001
Sales 0.001

概率之和为一:

∑i=1Kpi=1.∑i=1Kpi=1.∑i=1Kpi=1. \sum_{i=1}^{K}p_i=1.

模型选择概率最大的选项:

y=arg⁡max⁡ipiy^=arg⁡max⁡ipiy^=arg⁡max⁡ipi \hat y=\arg\max_i p_i

在本例中,选择为 Billing,概率为 0.998,但其余概率也有用。它们告诉我们模型如何在备选项之间分配其信念。

评分:有序分布的期望

当存在若干不同备选项时,选择原语适用。但如果我们想在有序尺度上度量某事物呢?

考虑这个问题:

这个客户请求有多紧急?

我们定义三个等级:

  • 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

评分原语不返回最可能的等级,而是计算等级索引的期望值。

对于表示等级的随机变量(X),期望为:

E[X]=∑i=0K−1ipiE[X]=∑i=0K−1ipiE[X]=∑i=0K−1ipi \mathbb{E}[X]=\sum_{i=0}^{K-1}ip_i

对于我们的示例:

E[X]=0(0.10)+1(0.26)+2(0.72)=1.7E[X]=0(0.10)+1(0.26)+2(0.72)=1.7E[X]=0(0.10)+1(0.26)+2(0.72)=1.7 \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∼Bernoulli⁡(p)Y∼Bernoulli⁡(p)Y∼Bernoulli⁡(p) Y\sim\operatorname{Bernoulli}(p)

其中

P(Y=1)=p,P(Y=0)=1−p.P(Y=1)=p,P(Y=0)=1−p.P(Y=1)=p,P(Y=0)=1−p. P(Y=1)=p,\qquad P(Y=0)=1-p.

如果模型返回:

{"type": "noul", "noul": 0.92}

它将概率0.92分配给 Yes,将0.08分配给 No。

模型在这里并不是随机采样一个二元答案。它返回概率参数本身,由应用决定如何据此行动。

置信度:模型有多确定?

到目前为止,我们已经从模型获得了概率分布。但决策模型接口还会为 Choice 和 Score 返回一个名为confidence的字段。

完整分布可能包含多个可能的答案,总结模型信念的集中程度是很有用的。借助confidence,我们正好可以实现这一点。

对于具有 (K) 个选项的 Choice,置信度为:

pmax⁡−1K1−1Kpmax⁡−1K1−1Kpmax⁡−1K1−1K \frac{p_{\max}-\frac{1}{K}}{1-\frac{1}{K}}

其中 ($p_{\max}$) 是最大的预测概率。

这里的直觉是:

  • 均匀分布的置信度为 0。($p_{\max} = \frac{1}{K}$)。这意味着模型估计所有选项的可能性相同。
  • 完全集中于一个选项的分布置信度为 1。($p_{\max} = 1$)
  • 获胜概率相对于均匀分布越大,置信度就越高。

在我们之前的三个选项示例中,Billing 的概率为 0.80:

C=3(0.99)−12=0.98C=3(0.99)−12=0.98C=3(0.99)−12=0.98 C=\frac{3(0.99)-1}{2}=0.98

请注意,获胜概率为 0.99,但报告的置信度为 0.98。置信度将最大概率相对于可用选项数量进行归一化。相比之下,有序 Score 示例的置信度仅为 0.558。

Hugging Face 生态系统中的决策模型

决策模型是一个年轻且快速发展的领域。每个模型在底层做出决策的方式各不相同(对整个 schema 联合打分的头、在 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 引入了一个专用的 decision-model 标签,用于发现兼容模型。这很有用,因为决策模型不必共享单一主干或架构。

例如,该生态系统包括基于 ModernBERT 等编码器架构的模型,以及源自更大生成式语言模型主干的模型。

4. 使用 Hugging Face 工具训练决策模型

一旦我们理解了推理,下一个问题是我们能否适配决策模型。由于决策模型已集成到 transformers 中,我们可以使用 Trainer API 对它们进行微调。

下面是一个玩具示例,它微调 JEV-9B 来路由支持请求。它故意做得很小,但展示了关键部分:

  • 每个示例都是一个决策请求(一个 state 加上 questions),并与黄金答案配对。
  • 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 等模型进行微调。

最终思考

决策模型之所以有趣,不是因为分类是新的,而是因为它们使决策和概率成为软件的一流输出。

传统分类器已经产生概率,零样本分类器已经允许运行时定义类别。决策模型将这些想法带入一个类型化接口,具有多种问题原语、非生成式评分,以及为可靠、校准的决策进行专门训练的可能性。