Constraining Output Space for SLM Narrow Automation Optimization
TL;DR · AI 摘要
限制输出空间可使小型语言模型在窄自动化任务中效率提升8倍,成本降低千倍,适合工业级高并发场景。
核心要点
- 分类任务固定输出空间可使推理速度提升8倍
- SLM单次推理成本是LLM的1/1000
- Qwen2.5-0.5B-Instruct在M2 MacBook Air上实现毫秒级响应
结构提纲
按章节快速跳转。
- §引言
固定输出空间比生成文本再解析的效率高8倍
使用Qwen2.5-0.5B-Instruct在M2 MacBook Air进行浮点16测试
SLM单次调用成本是LLM的千分之一
松散输出处理直接导致可衡量的错误率上升
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- SLM窄自动化优化
- 核心方法
- 输出空间约束
- 分类任务固定答案集
- 基准测试
- Qwen2.5-0.5B-Instruct
- M2 MacBook Air环境
- 优势对比
- 成本降低1000倍
- 响应时间毫秒级
金句 / Highlights
值得收藏与分享的关键句。
生成()方法每个输出token需要一次前向传递,8个token成本是直接回答的8倍
SLM在单GPU或CPU上运行,响应时间可达毫秒级
基准测试环境:M2 MacBook Air 24GB RAM+16核神经引擎
为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环境并安装依赖项:
pip install torch transformers accelerate# 为什么需要限制输出空间?
分类任务具有固定答案集。如果你需要将工单路由到账务、技术或账户部门,恰好只有三个有效输出,没有其他可能性。但标准做法却是要求模型写出答案,生成几个标记,然后在结果字符串中搜索可识别内容。
这种做法同时存在两个问题。首先,速度慢:generate()方法对每个输出标记执行一次顺序前向传递,因此生成8个标记的计算量大约是直接获取答案的8倍。其次,不可靠:小型模型可能会愉快地回复"当然!这看起来像是账务问题。"、"账务/账户"或你从未定义过的分类。每个响应都需要备用规则或重试,而每个备用规则都可能成为错误累积的温床。
解决方法是停止生成并开始评分。运行一次前向传递,读取模型的下一个标记分布,并将决策范围限制在候选标签的标记ID内。这样结构上就无法得出错误答案,并且作为副产品可以获得校准后的置信度评分。
# 解析自由文本
以下是天真版本的实现方式:生成自由文本并在事后进行解析。将其保存到文件中并通过命令行运行。
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}")加载权重: 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
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})")加载权重: 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