Jeff:用 0.8B 小模型把零样本决策压到 22 毫秒

2026-09-29 33 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

多数大模型应用都遵循“输入提示词、生成文本、再解析结果”的链路。Jeff 选择了另一条路线:把模型收缩为专门处理决策问题的 zero-shot 分类器。调用方描述场景并给出候选项,模型通过一次前向计算直接返回各选项的校准概率,不生成解释文本,也不要求业务代码从自然语言中提取答案。

项目提供基于 Qwen3.5 和 Gemma 4 的微调版本。来源给出的性能数据是:在 RTX PRO 6000 上,一次决策约需 22 毫秒;其中 0.8B 版本在 benchmark 上已经逼近 Jev。这里更值得关注的并不只是单项成绩,而是它呈现出的工程取舍——如果任务最终只需要从有限选项中做判断,生成式模型可能并不是最合适的执行器。

从“生成答案”切换为“计算选项概率”

假设系统要判断一张工单应该进入哪个队列。传统做法可能要求模型输出 JSON:

你是工单分类器。请返回 JSON,不要输出其他内容。

工单:用户登录后立即被强制退出。
类别:账号安全、支付问题、产品建议、其他

即使提示词写得很严格,调用方仍要处理 Markdown 代码块、字段缺失、类别拼写变化和额外解释。生成过程还可能输出多个实际没有价值的 token。

Jeff 的接口思路可以抽象为:

{
  "scenario": "用户登录后立即被强制退出",
  "options": ["账号安全", "支付问题", "产品建议", "其他"]
}

返回值则应接近下面这种结构:

{
  "probabilities": {
    "账号安全": 0.82,
    "支付问题": 0.03,
    "产品建议": 0.01,
    "其他": 0.14
  }
}

以上 JSON 只是便于说明集成方式的接口示例,并非项目已公布的固定 API。真正接入时应以 Jeff 的模型调用方式和输出格式为准。

这种模式带来三个直接变化:

  • 延迟更稳定:计算量不再取决于生成答案的长度。
  • 结果更容易消费:业务代码读取概率数组即可,不需要容错式文本解析。
  • 决策可以分层:最高概率、置信度阈值和前两名差值都能参与后续路由。

22 毫秒的价值在服务链路里,而不只在模型里

约 22 毫秒是 RTX PRO 6000 上的模型决策数据,不等于线上请求一定能在 22 毫秒内完成。实际延迟还包含排队、序列化、网络传输、分词和服务框架开销。部署时应至少分别记录:

总延迟 = 排队时间 + 预处理 + 模型前向计算 + 后处理 + 网络开销

不过,一次前向计算、不进行自回归生成,仍然让系统更适合这些场景:

  • API 网关中的请求分流;
  • 客服工单和邮件分类;
  • 内容审核的初筛;
  • Agent 在有限工具集合中选择下一步动作;
  • 推荐或风控系统中的轻量级策略节点。

0.8B 参数规模也意味着它更有机会部署在成本受限或本地推理环境中。但“benchmark 逼近 Jev”不能直接推导出它在所有业务数据上都同样准确。候选项表述、领域术语、类别不平衡和分布漂移都会影响结果,正式替换现有分类器前仍需运行自己的离线测试。

可以这样设计一层稳定的决策 API

由于摘要没有给出 Jeff 的正式 Python API,下面提供的是一个可直接运行的集成骨架。它先用确定性的模拟评分器跑通 HTTP 契约;接入 Jeff 时,只需替换 score_with_model 函数,不必改动上游业务系统。

安装依赖:

python -m venv .venv
source .venv/bin/activate
pip install fastapi 'uvicorn[standard]' pydantic

创建 app.py:

from math import exp
from typing import List

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="Decision Classifier")


class DecisionRequest(BaseModel):
    scenario: str = Field(min_length=1, max_length=4000)
    options: List[str] = Field(min_length=2, max_length=50)


class Choice(BaseModel):
    option: str
    probability: float


class DecisionResponse(BaseModel):
    choices: List[Choice]
    selected: str
    confidence: float
    needs_review: bool


def softmax(logits: list[float]) -> list[float]:
    peak = max(logits)
    values = [exp(value - peak) for value in logits]
    total = sum(values)
    return [value / total for value in values]


def score_with_model(scenario: str, options: list[str]) -> list[float]:
    """演示评分器。

    接入 Jeff 时,将函数体替换为真实推理调用,并返回与 options
    顺序一致的 logits 或分数。当前实现仅用于跑通服务契约,不能用于生产分类。
    """
    scenario_terms = set(scenario.lower().split())
    logits = []
    for index, option in enumerate(options):
        option_terms = set(option.lower().split())
        overlap = len(scenario_terms & option_terms)
        logits.append(float(overlap) - index * 0.01)
    return logits


@app.post("/v1/decide", response_model=DecisionResponse)
def decide(request: DecisionRequest) -> DecisionResponse:
    if len(set(request.options)) != len(request.options):
        raise HTTPException(400, "options must be unique")

    logits = score_with_model(request.scenario, request.options)
    if len(logits) != len(request.options):
        raise HTTPException(500, "model returned an invalid score count")

    probabilities = softmax(logits)
    ranked = sorted(
        zip(request.options, probabilities),
        key=lambda item: item[1],
        reverse=True,
    )
    confidence = ranked[0][1]

    return DecisionResponse(
        choices=[
            Choice(option=option, probability=round(probability, 6))
            for option, probability in ranked
        ],
        selected=ranked[0][0],
        confidence=round(confidence, 6),
        needs_review=confidence < 0.70,
    )

启动服务并发送请求:

uvicorn app:app --host 0.0.0.0 --port 8000
curl -s http://127.0.0.1:8000/v1/decide \
  -H 'Content-Type: application/json' \
  -d '{
    "scenario": "用户登录后立即被强制退出,需要检查账号安全状态",
    "options": ["账号安全", "支付问题", "产品建议", "其他"]
  }'

生产接入 Jeff 时,score_with_model 应负责模型需要的模板构造、推理和输出映射。API 层则继续承担输入限制、重复选项检查、阈值策略和稳定响应格式。这样即使以后替换模型,上游调用方也不需要跟着修改。

概率不能只看最大值

“返回校准概率”比只返回类别更有用,但概率仍需要在真实数据上验证。一个模型输出 0.90,理想情况下意味着类似样本中约九成判断正确;如果实际正确率只有六成,这个数值就不能直接用于自动化审批。

上线前可以至少观察三项指标:

  1. 准确率或宏平均 F1:检查整体分类质量,并避免大类别掩盖小类别问题。
  2. 校准误差:比较置信度区间与真实正确率,例如预测概率在 0.8 到 0.9 的样本究竟有多少判断正确。
  3. 拒答覆盖曲线:逐步提高自动处理阈值,观察人工复核量与错误率如何变化。

业务规则也不应只使用最高概率。可以增加前两名概率差值:

def routing_policy(probabilities: list[float]) -> str:
    ranked = sorted(probabilities, reverse=True)
    top1 = ranked[0]
    margin = ranked[0] - ranked[1]

    if top1 >= 0.85 and margin >= 0.25:
        return "auto_execute"
    if top1 >= 0.60:
        return "human_review"
    return "fallback_model"

阈值只是示例,应根据误判成本调整。内容推荐可以容忍更多错误,资金操作、封禁账号或医疗分流则需要更保守的阈值和人工确认。

适合采用 Jeff 的判断清单

Jeff 最适合“候选项明确、需要快速重复决策”的节点,而不是开放式写作或复杂推理。试点时可以按下面的清单推进:

  • 固定候选项定义,避免同义、重叠或边界模糊的类别;
  • 用真实流量构建验证集,不只依赖公开 benchmark;
  • 同时测量模型前向延迟和端到端 P95、P99 延迟;
  • 为低置信度、概率接近和越界输入设置回退路径;
  • 记录模型版本、候选项、概率与最终人工结果;
  • 在高风险动作前保留权限检查和确定性业务规则。

这类小模型的核心优势不是“用更少参数完成所有事情”,而是让模型只承担它擅长的一项工作:在已知选项之间快速打分。只要问题能够被可靠地表达为分类或选择,Jeff 展示的单次前向决策方式就可能比“让大模型先写一段话,再猜它选了什么”更简单、更快,也更容易运营。


相关推荐