KDnuggets

Constraining Output Space for SLM Narrow Automation Optimization

8.5内容质量

TL;DR · AI 摘要

限制输出空间可使小型语言模型在窄自动化任务中效率提升8倍,成本降低千倍,适合工业级高并发场景。

核心要点

  • 分类任务固定输出空间可使推理速度提升8倍
  • SLM单次推理成本是LLM的1/1000
  • Qwen2.5-0.5B-Instruct在M2 MacBook Air上实现毫秒级响应

结构提纲

按章节快速跳转。

  1. 工业场景中窄自动化任务需要SLM而非LLM的优化方案

  2. 固定输出空间比生成文本再解析的效率高8倍

  3. 使用Qwen2.5-0.5B-Instruct在M2 MacBook Air进行浮点16测试

  4. SLM单次调用成本是LLM的千分之一

  5. 松散输出处理直接导致可衡量的错误率上升

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • SLM窄自动化优化
    • 核心方法
      • 输出空间约束
      • 分类任务固定答案集
    • 基准测试
      • Qwen2.5-0.5B-Instruct
      • M2 MacBook Air环境
    • 优势对比
      • 成本降低1000倍
      • 响应时间毫秒级

金句 / Highlights

值得收藏与分享的关键句。

#Small Language Models#Automation Optimization#NLP#Efficiency
打开原文

为SLM窄自动化优化限制输出空间 - KDnuggets

publ: 2026年8月13日

  • 博客热门文章
  • 主题 AI 职业建议 计算机视觉 数据工程 数据科学 语言模型 机器学习 MLOps NLP 编程 Python SQL
  • 数据集
  • 活动
  • 资源 快速参考指南 推荐 技术简报
  • 广告

订阅电子报

#header end

/ad_wrapper

为SLM窄自动化优化限制输出空间

本文将开启一系列关于小型语言模型(SLM)窄自动化优化的专题文章,作为开篇,我们将探讨其中最实用的技术之一:通过限制输出空间而非解析生成文本来实现优化。

作者:

Matthew Mayo

KDnuggets主编,2026年8月13日发布于

语言模型

<div class="addthis_native_toolbox"></div>

应用AI领域大量关注前沿级推理能力;然而,工业界实际生产工作中大量存在的需求远没有那么耀眼:窄自动化。这类任务包括正确路由支持工单、从表单中提取字段、标记文档以及标记记录供人工审核。这些任务都有共同特征:受限输入、固定输出空间和巨大的调用量。它们恰好是小型语言模型(SLMs)特别适合处理的任务类型。一个能舒适地部署在单块GPU上,甚至能通过CPU处理,并能在毫秒级返回答案的模型,往往比调用大型语言模型(LLM)API(每个项目成本可能高出千倍)更符合工程选择。

问题是团队往往将前沿模型的习惯带入小型模型。他们编写长篇对话式提示,让模型生成自由文本后再用正则表达式搜索。他们通过Python循环逐个调用模型。对于本地SLM来说,这些低效操作更加明显:当单次前向传递耗时10毫秒时,围绕该前向传递的所有操作都可能成为瓶颈;松散的输出处理会直接转化为可衡量的错误率。

为了建立公平的基准,以下所有测试均使用通过Hugging Face Transformers库在配备24GB内存、16核神经引擎的M2 MacBook Air上运行的Qwen2.5-0.5B-Instruct模型(float16精度)。

首先,设置Python环境并安装依赖项:

code
pip install torch transformers accelerate

# 为什么需要限制输出空间?

分类任务具有固定答案集。如果你需要将工单路由到账务、技术或账户部门,恰好只有三个有效输出,没有其他可能性。但标准做法却是要求模型写出答案,生成几个标记,然后在结果字符串中搜索可识别内容。

这种做法同时存在两个问题。首先,速度慢:generate()方法对每个输出标记执行一次顺序前向传递,因此生成8个标记的计算量大约是直接获取答案的8倍。其次,不可靠:小型模型可能会愉快地回复"当然!这看起来像是账务问题。"、"账务/账户"或你从未定义过的分类。每个响应都需要备用规则或重试,而每个备用规则都可能成为错误累积的温床。

解决方法是停止生成并开始评分。运行一次前向传递,读取模型的下一个标记分布,并将决策范围限制在候选标签的标记ID内。这样结构上就无法得出错误答案,并且作为副产品可以获得校准后的置信度评分。

# 解析自由文本

以下是天真版本的实现方式:生成自由文本并在事后进行解析。将其保存到文件中并通过命令行运行。

code
import os
import time
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"

torch.set_num_threads(os.cpu_count() or 1)

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(MODEL_ID, dtype=torch.float32)
model.eval()

# 我们的待分类样本数据(600条记录)
LABELS = ["billing", "technical", "account"]
tickets = [
    "My card was charged twice for the same invoice.",
    "The mobile app crashes whenever I open the settings page.",
    "I need to change the email address on my profile.",
] * 200

def build_prompt(ticket):
    messages = [
        {
            "role": "system",
            "content": "You classify support tickets. Answer with exactly one of: billing, technical, account.",
        },
        {"role": "user", "content": f"Ticket: {ticket}\nCategory:"},
    ]
    return tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )

# 此处输出未受约束,因此让模型生成简短回答并从中搜索标签(标记++)
# 每个新标记都需要单独的前向传递,每次调用处理一个工单意味着无法通过批量处理来分摊成本(时间++)
prompts = [build_prompt(t) for t in tickets]
predictions = []

# 计算推理耗时
start = time.time()

for n, prompt in enumerate(prompts, start=1):

    # 此循环在CPU上运行需要数分钟,因此需要显示进度而不是保持静默
    if n % 50 == 0:
        rate = (time.time() - start) / n
        print(f"  {n}/{len(prompts)} tickets ({rate:.2f}s each)", flush=True)
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.inference_mode():
        output = model.generate(
            **inputs,
            max_new_tokens=8,
            do_sample=False,
            pad_token_id=tokenizer.eos_token_id,
        )

    # generate() 返回 prompt + 续写内容,因此在解码前需要切片去除 prompt 部分
    generated = output[0, inputs["input_ids"].shape[1] :]
    text = tokenizer.decode(generated, skip_special_tokens=True).strip().lower()

    # 与标签列表进行子字符串匹配
    predictions.append(next((label for label in LABELS if label in text), "UNPARSED"))

duration = time.time() - start

# 输出任务指标
print(f"自由形式生成耗时: {duration:.2f} 秒")
print(f"无法解析的输出: {predictions.count('UNPARSED')} / {len(predictions)}")

# 推理输出示例
for ticket, label in zip(tickets[-3:], predictions[-3:], strict=True):
    print(f"{ticket} -> {label}")
code
加载权重: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 290/290 [00:01<00:00, 263.89it/s]
  50/600 个票据 (每个 0.19 秒)
  100/600 个票据 (每个 0.19 秒)
  150/600 个票据 (每个 0.19 秒)
  200/600 个票据 (每个 0.19 秒)
  250/600 个票据 (每个 0.19 秒)
  300/600 个票据 (每个 0.19 秒)
  350/600 个票据 (每个 0.19 秒)
  400/600 个票据 (每个 0.19 秒)
  450/600 个票据 (每个 0.19 秒)
  500/600 个票据 (每个 0.19 秒)
  550/600 个票据 (每个 0.19 秒)
  600/600 个票据 (每个 0.19 秒)
自由形式生成耗时:134.01 秒
无法解析的输出:0 / 600
我的信用卡因同一发票被收取两次费用。 -> billing
每次打开设置页面时移动应用都会崩溃。 -> technical
我需要修改个人资料上的电子邮件地址。 -> technical

虽然整个批次的数据都以解析器能够处理的格式返回,但我们仍需记录 134 秒的执行时间。

限制输出空间

现在让我们尝试一个受限版本,该版本可以直接从单次前向传递中对标签集进行评分:

/think

code
import os
import time
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"

torch.set_num_threads(os.cpu_count() or 1)

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)

model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32)
model.eval()

# 我们的分类测试数据(600条记录)
LABELS = ["billing", "technical", "account"]
tickets = [
    "My card was charged twice for the same invoice.",
    "The mobile app crashes whenever I open the settings page.",
    "I need to change the email address on my profile.",
] * 200

def build_prompt(ticket):
    messages = [
        {
            "role": "system",
            "content": "You classify support tickets. Answer with exactly one of: billing, technical, account.",
        },
        {"role": "user", "content": f"Ticket: {ticket}\nCategory:"},
    ]
    return tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )

# 提示信息以 "assistant\n" 结尾,因此模型的下一个标记将开始标签
# 只要这些第一个标记是唯一的,比较每个标签第一个标记的logits就足以确定结果
label_first_ids = [tokenizer.encode(label, add_special_tokens=False)[0] for label in LABELS]
assert len(set(label_first_ids)) == len(LABELS), (
    "标签共享第一个标记;请改用完整标签序列进行评分(详见说明)。"
)
label_first_ids = torch.tensor(label_first_ids, device=model.device)

# 每个票据只需一次前向传递,无需生成循环:决策完全包含在下一个标记的logits中
prompts = [build_prompt(t) for t in tickets]
predictions = []
confidences = []

# 测量推理时间
start = time.time()
for prompt in prompts:
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    with torch.inference_mode():
        logits = model(**inputs).logits[0, -1, :]
    # 仅对标签logits进行softmax,使候选项的概率总和为1
    probs = torch.softmax(logits[label_first_ids].float(), dim=-1)
    best = int(probs.argmax())
    predictions.append(LABELS[best])
    confidences.append(float(probs[best]))
duration = time.time() - start

# 输出任务指标
print(f"受限评分耗时:{duration:.2f} 秒")
print(f"无法解析的输出:{predictions.count('UNPARSED')} / {len(predictions)}")

# 推理输出示例
for ticket, label, confidence in zip(
    tickets[-3:], predictions[-3:], confidences[-3:], strict=True
):
    print(f"{ticket} -> {label} (置信度 {confidence:.3f})")
code
加载权重: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 290/290 [00:01<00:00, 285.60it/s]
受限评分耗时:94.51 秒
无法解析的输出:0 / 600
My card was charged twice for the same invoice. -> billing (置信度 0.793)
The mobile app crashes whenever I open the settings page. -> technical (置信度 0.798)
I need to change the email address on my profile. -> technical (置信度 0.673)

这比原始方法节省了约30%的时间,同时消除了失败的可能性。进一步的测试表明,这种时间比例在大规模数据中依然保持稳定,而且修改票据文本可以暴露出原始版本中的失败情况,而这些失败在改进后的版本中已被捕获。本文不再继续进行相关测试。

一些对上述代码的进一步解释:

  • 读取 logits[0, -1, :] 可以获取模型对下一个 token 的未归一化分布。如果答案是三个已知字符串之一,generate() 之后的所有操作都变得多余。
  • 在 label_first_ids 处索引该向量并取 argmax,会使超出词汇表的答案在结构上变得不可能。模型不再被允许在格式上发挥创意,这就是为什么通过构造而非偶然,无法解析的计数为 0 / 600。
  • 对受限 logits 的 softmax 是一个有用的置信度检查。实际上,你可以将低于选定阈值(例如 0.6 作为起点)的任何内容路由到人工队列,而不是让低置信度标签继续在工作流中传递。
  • 注意分词处理。大多数字节级 BPE 分词器会将 " billing" 和 "billing" 视为不同的 token,因此要编码模型在你的提示后实际会生成的变体。聊天模板以 "assistant\n" 结尾,所以下一个 token 紧跟在换行符后且没有前导空格,因此使用 encode(label) 而不是 encode(" " + label)。如果混淆了这一点,脚本仍能正常运行;但你最终会为模型永远不会生成的三个 token 进行评分。
  • 如果两个标签共享第一个 token(例如 "refund_request" 和 "refund_status"),断言会触发。要么将标签重命名为单个独立的 token(如 A、B、C),并在提示中添加图例,要么对完整标签序列进行评分,而不是仅对第一个 token 进行评分。

# 总结

这是我们首次尝试优化小型语言模型(SLMs)以用于特定领域的自动化,本次的目标技术是受限评分。该技术通过一次前向传递将生成过程限制在有效标签集合内,取代了自由生成和字符串解析。通过实现该技术,我们可以使格式错误的输出在结构上变得不可能,同时为你提供一个置信度评分,用于将边缘情况路由给人类处理。

当围绕小型语言模型的代码不再将其视为通用聊天机器人,也不再像对待 ChatGPT 那样与其交互时,一个小型语言模型(例如我们今天使用的 0.5B 参数模型)就成为特定领域自动化任务的实用生产选择。通过强制输出契约,小型模型不再是一种妥协,而是变得显而易见的解决方案。

Matthew Mayo(@mattmayo13)拥有计算机科学硕士学位和数据挖掘研究生文凭。作为 KDnuggets & Statology 的主编以及 Machine Learning Mastery 的特约编辑,Matthew 致力于使复杂的数据科学概念变得易于理解。他的专业兴趣包括自然语言处理、语言模型、机器学习算法以及探索新兴人工智能技术。他致力于在数据科学社区中普及知识的使命驱动着他的工作。Matthew 从六岁起就开始编程。

关于该主题的更多内容

  • 如果你想进入科技领域:成为一名软件开发人员
  • 3 个基于研究的高级提示技术,提高大语言模型效率……
  • Abacus AI 评测:功能、AI 代理与自动化解析(诚实指南)
  • 像软件工程师一样测试 SQL:单元测试、CI/CD 和数据……
  • AI 不会取代你的工作:自动化才是
  • 7 个 AI 自动化工具,用于简化工作流程

<hr class="grey-line"><br> <div><h3>我们推荐的前 5 门免费课程</h3><br> </div>

Mailchimp for WordPress v4.14.0 - https://wordpress.org/plugins/mailchimp-for-wp/

/ Mailchimp for WordPress 插件

您可以在此处开始编辑。

如果评论已关闭。

<= 上一篇帖子

下一篇帖子 =>

#content end

<script type="text/javascript">kda_sid_write(kda_sid_n);</script>

最新文章

  • 如何使用Python构建一个简单的AI网络爬虫 5篇有趣的智能体AI论文阅读 构建流式本地AI代理 为SLM限制输出空间 狭窄自动化优化 构建端到端的数据科学作品集项目 5种在Windows上安装Python的简便方法

热门文章

  • 规格工程:提示工程之后的新技能
  • 如何使用Python构建一个简单的AI网络爬虫
  • 5篇有趣的智能体AI论文阅读
  • 构建端到端的数据科学作品集项目
  • 5门免费课程学习现代AI和LLMs
  • 构建流式本地AI代理
  • 贡献开源项目的终极指南
  • 3个视觉证明中心极限定理以建立您的直觉
  • 5种在Windows上安装Python的简便方法
  • 2026年最小的人工智能工程师工具包

#content_wrapper end

© 2026

Guiding Tech Media

|

关于

联系方式

广告合作

隐私政策

服务条款

发布于2026年8月13日 by

blank

不,谢谢!

/.main_wrapper

<script defer type="text/javascript" src="https://s7.addthis.com/js/300/addthis_widget.js#pubid=gpsaddthis"></script>

noptimize

/noptimize