尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

本地AI任务拆分实战:两级流水线让准确率从61%提升到92%

本地AI任务拆分实战:两级流水线让准确率从61%提升到92% 1. 为什么我用本地AI跑任务老是翻车我一开始是在本地部署了一个7B的开源模型想让它全自动处理一批文档分类和摘要的任务。跑起来之后发现问题特别多分类结果经常漂移同样是“报销单”这个关键词上午还能准确识别下午就开始把“报销明细”分到另外一个类别里摘要任务更夸张模型上下文一长就开始自我发挥摘要里凭空出现原文档里根本没有的内容。最后统计了一下单纯靠模型一把梭的准确率只有61%。后来我把这个问题拆开了看发现根子不在模型本身而在任务结构上。1.1 本地模型和API模型在长链任务上的差距先坦白说本地模型和API模型在单次对话里完成多步推理的能力是有差距的。API模型参数量大、训练数据丰富在长链任务上表现相对稳定本地模型受显存和参数量限制指令跟随能力在复杂场景下会更脆弱。这不是说本地模型不能用而是说我们在设计任务流程时要尊重模型的能力边界。一个特别直观的现象当我在一条提示词里同时要求做“提取关键词、判断类别、生成摘要、输出结构化JSON”四件事时本地模型经常只完成前两件就草草收场JSON输出也经常多一个逗号或少一个花括号。我做了一个压力测试连续跑50条只有12条是完全符合JSON格式的成功率24%基本不可用。这不是模型笨而是任务太重推理路径太长概率性错误被叠加放大了。1.2 任务拆分不是玄学是工程很多人一提“任务拆分”就想到让模型自己拆比如用一条提示词说“请把以下任务拆成子任务”。这个思路在交互式场景里还行但在批处理流水线里是灾难。因为模型拆任务本身就要消耗一次推理拆完之后子任务的质量又是随机的等于把不确定性翻倍了。后来我换了思路把拆分规则从“模型判断”改成“程序判断”。也就是先写一层程序化的规则来做任务拆分模型只负责拿拆分好的小块去执行。这个改变让整体稳定性一下子提高了因为规则是确定的、可穷举的、可以单元测试的模型的不确定性被压缩到了最小的执行单元里。1.3 两级流水线是怎么想出来的所谓两级流水线就是一条自动化任务链上第一级L0只做分流和校验完全不调用模型第二级L1拿到L0分出来的干净子任务再调用本地模型执行真正的内容处理。这个想法不是一开始就有的是我在一次误路由事故之后才想明白的当时一批发票扫描件的识别任务全部进了闲聊通道模型的返回完全没法用浪费了整整一个上午。后来我在L0层加了几个正则规则彻底堵死了这类误路由才意识到L0应该是“硬规则”不是模型提示词能替代的。2. 两级流水线的整体设计与落地两级流水线听起来抽象但落地就两个渠道代码块的事。L0很多时候只是一个路由函数跑一遍关键词和正则几毫秒就返回L1则是一个模型调用函数把文本和系统提示词打包给本地模型拿回结构化结果。要说清楚这个设计我用一个实际场景来演示本地知识库文档分类。任务输入是一份文本需要判断它属于技术方案、测试报告、会议纪要还是其他并且输出对应的摘要和标签。2.1 L0层不靠模型靠规则L0要干的事包括三块。第一块是格式预处理比如去掉多余的换行、控制字符、HTML标签把全角符号转成半角。这步不做后面的正则容易漏。第二块是路由判断用关键词表和正则表达式判断文档类型。比如“测试报告”关键词加“用例通过率”正则基本可以锁定测试报告“会议时间”“参会人”加“决议事项”锁定会议纪要。关键词表是从历史数据里统计出来的高频词这是经验值前期可以先粗一点后面按失败样本逐步扩充。第三块是质量校验对L1的输出做硬校验——JSON是否能解析、字段是否齐全、值的类型是否正确。校验不合格就退回重跑最多重试两次。这块很重要因为本地模型偶尔会输出非常离谱的JSON。代码层面L0的核心逻辑大概是这样的def l0_route(text: str) - str: text normalize_text(text) for rule in RULES: # 按优先级顺序匹配 result rule.match(text) if result: return result.channel return CHANNEL_FALLBACK # 兜底通道RULES这里我按优先级排了一个列表每个规则对象包含正则、关键词权重和对应通道。规则匹配不是完全命中才算而是算一个累计分数超过阈值就放行。这样能避免某个单一正则因为文本微调而失效。注意L0做成纯规则还有一个附带好处——它可以做单元测试。我在项目里维护了一个带标注的测试集每次改L0规则都会批量跑一遍确保不会因为新增规则把历史案例打乱。这个习惯后面救了我好几次。2.2 L1层让模型只做它擅长的事L1层的设计原则很简单一次调用只做一件事。比如分类通道里系统提示词就只写“你是文档分类助手只能输出JSON字段为category、confidence”不要再让它顺便生成摘要。摘要通道同理只做摘要输出不做分类判断。这样做的好处有三个。第一上下文更聚焦模型不用在多个任务之间切换注意力输出质量更稳第二提示词模板可以独立优化分类效果不好只改分类模板不影响摘要模板第三并发调度更方便不同的通道可以用不同的上下文长度和温度参数。我实际的配置是分类通道temperature设到0.1几乎接近确定性输出摘要通道temperature设到0.3保留一点文字多样性关键词提取通道temperature设到0。本地模型在低温下表现稳定很多特别是结构化输出场景温度一高就容易在JSON里乱加内容。提示词模板拿分类通道举例大概长这样你是文档分类助手。请阅读下面文本判断它属于tech_solution / test_report / meeting_minutes / other。 只输出一个JSON对象不要输出任何解释和多余内容。 格式: {category: 类别, confidence: 0到1之间的小数}这个模板看着简单但实际测试下来加上“不要输出任何解释”这句话JSON解析成功率能从60%拉到85%以上。如果配合低温参数能稳定在95%以上。2.3 通道配置与分发策略通道分发的核心是“量体裁衣”。同样是本地模型推理服务不同的通道可以走不同的模型实例。我机器是双卡配置一张卡跑的是通用对话模型另一张卡跑的是精调过的结构化输出模型。L0路由之后技术方案类走通用模型测试报告类走结构化模型这样能把负载切开避免所有任务挤在同一个模型进程里争显存。还有一个容易被忽略的点队列和超时。批处理场景里如果业务方用同步方式等待结果L1模型一旦推理超时就会连带阻塞一批任务。我后来改成了异步队列加超时降级L0把任务丢进队列L1的消费进程慢慢跑超时的任务标记失败进入重试队列。改完之后单批处理时间从18分钟降到了11分钟吞吐提升很明显。3. L0硬规则的踩坑实录L0听起来简单但真正写起来坑特别多。这些坑大多是我在真实任务里撞出来的每一个都对应过一次线上事故。下面挑几个典型的说。3.1 规则优先级冲突第一个坑是规则优先级冲突。一开始我把技术方案的关键词“方案”放在了前面测试报告的关键词“报告”放在后面结果遇到一篇标题叫《XX系统测试方案》的文档L0毫不犹豫地进了技术方案通道。后来在排优先级的时候我就长了个心眼把“测试”和“报告”组合词提到前面把包含“测试方案”这种双重身份的文档用组合规则单独处理。这个坑的教训是L0规则不是越多越好而是优先级排序要能覆盖组合场景。我后来给每条规则加了一个权重值命中不是走“第一个命中的规则”而是累计权重哪个通道权重最高走哪个。这样即使组合词同时命中多个规则也能按权重得到合理的路由。3.2 正则过宽引发误路由第二个坑是正则表达式写得太宽。我曾经为了匹配日期格式写了一个\d{1,2}[月/]\d{1,2}的正则结果把“3/8节促销方案”这种文本也匹配成了会议纪要因为会议纪要通道里就有日期正则。整整一批营销文案被错误路由后续处理全部废掉重跑了一次。后来我的做法是正则只用于强特征比如“测试报告编号TEST-\d{4}”这种带固定前缀的而不是通用日期。通用弱特征靠关键词权重累加强特征才用正则。这是L0设计里非常核心的一条经验。注意新增L0正则前先拿10条真实历史样本跑一遍看看误命中情况。别觉得正则灵活就随手往上加误路由的代价往往比漏路由大得多。3.3 分词与编码的坑第三个坑跟编码相关。本地文档经常有中文全角符号和奇怪的编码如果不在L0做归一化后面全部都会乱。有一次一个PDF转出来的文本里全是\u3000空格和乱码符号正则匹配全部失败任务全部落到了兜底通道兜底模型又被这些乱码干扰输出的摘要直接不忍直视。解决办法是在L0最前面做一层文本清洗把全角转半角、把多个连续空格压缩成一个、去掉控制字符和零宽字符。写了个normalize_text()函数实测下来很多“诡异问题”直接消失。这个函数我建议每个人都提前写好别等踩坑再补。def normalize_text(text: str) - str: text text.replace(\u3000, ) text re.sub(r[\x00-\x1f\x7f], , text) text text.replace(\u200b, ).replace(\ufeff, ) text re.sub(r[ \t], , text) text re.sub(r\n{3,}, \n\n, text) return text.strip()3.4 兜底策略不能省第四个坑是关于兜底通道的。最开始我以为L0能覆盖绝大多数场景兜底通道只是随便放了个通用提示词。结果业务方陆续丢进来一批完全没有预料到的文档类型比如“操作手册”“安全巡检表”“需求变更单”。这些文档要么进入兜底后输出质量很差要么被强行匹配到一个接近的规则但处理结果完全不合适。后来我把兜底通道也做成了一种“半硬规则”先对兜底样本做统计抽取出几个高频特征词形成一个“软分类”逻辑至少能区分“文档类”“表格类”“代码类”这三种大方向。这样兜底通道至少能给出一个粗粒度的分类而不至于把操作手册当做代码块去解析。4. 调优实战从0.6到0.92的准确率提升我这一套系统最终的目标准确率是0.92。从最初的0.61一路调上来实验记录挺多我把关键步骤整理一下这套思路在别的任务上也大概率能复用。4.1 先在L0上做文章第一步就是优化L0规则本身。我维护了一个每天更新的误路由样本库收集当天识别失败的案例然后分析失败原因。原因分几类失败原因占比处理方式关键词缺失或表达变形42%扩充关键词表加入同义词与缩写组合规则优先级冲突23%调整规则顺序细化权重特殊字符导致匹配失败18%完善归一化函数补测试用例文档类型超出覆盖范围12%新增通道或兜底软分类其他5%逐个看log手动判断这一步做完准确率从0.61提升到了0.78。不要小看规则层它是最确定的杠杆改一次就能全局生效。4.2 L1侧参数怎么调第二步调L1侧的推理参数。我重点调了三项temperature、repeat_penalty、max_tokens。temperature从默认的0.7降到0.1~0.3之间结构化输出场景降到0。repeat_penalty从1.0往上加文本一长模型容易陷入重复配合max_tokens限制来压制。max_tokens不要给太少否则模型会在输出一半的时候截断也不要给太多否则多余token会被模型用来填充废话。另外一个关键点是提示词的输出格式约束。我发现与其让模型自由输出再解析JSON不如在提示词里给一个极其明确的模板示例甚至可以说“只输出一个JSON对象不要任何解释”。本地模型对“不要解释”的理解比API模型要弱一些所以输出后必须做一次程序化校验不合格就重试或降级。4.3 性能数据对比调优之后我做了一次对比测试同样是1000条混合文档结果如下指标优化前优化后端到端准确率61%92.3%平均单条处理时长9.8秒6.4秒JSON解析失败率21%3.2%误路由率14%1.8%时长下降也很有意思原因是任务被正确路由后模型的上下文和任务类型更匹配无效推理少了重试也少了。这也能说明任务拆分本身就在节省算力。4.4 一套可以复用的调优模板调优工具这块我固定了一套流程每次拿到一批新任务都会跑一遍用当前L0规则跑一遍历史样本记录误路由。按误路由原因分类优先修占比最大的那类。每修完一轮跑一次回归测试集确保整体准确率不下降。L1侧先不做大改等L0稳定后再去动temperature和提示词模板。每次实验都记录日志沉淀成一条可对比的实验记录表。这套流程看着简单但是坚持下来收益很大。我现在的模型没变只是把路由和提示词调顺了效果就已经完全是两个量级。5. 常见问题排查速查表最后整理一个速查表。这张表是我踩过坑之后的固定查表工具每次遇到问题先对照一遍能省很多排查时间。现象可能原因排查思路解决方案L1输出大量乱码L0未做编码归一化抽一条看原始文本字符编码在L0前补normalize_text()同类文档路由不稳定关键词表覆盖不足统计失败样本中的高频词扩充同义词与缩写列表多种类型文档互相抢占通道规则优先级冲突查看命中哪条规则权重最高调整优先级增加组合规则JSON解析偶尔失败模型温度过高或提示词约束不足看输出内容中JSON出错位置降temperature、强化输出模板任务处理时长突然暴涨队列积压或模型实例满载查看消费进程队列长度与显存占用加并发实例、开放异步降级兜底通道输出不可用兜底通道没有细化统计兜底样本类型增加软分类逻辑粗粒度分流我个人在实际使用中最深的体会是本地AI任务拆分的核心不是模型本身而是流程设计。买再好的显卡、部署再大的模型如果任务一股脑地塞给模型处理效果都会打折扣。反而是在源头把任务结构拆清楚让模型每次只做一件事整套系统才能又稳又省。最后再分享一个小技巧L0规则列表可以按周复盘一次。我每周五下午会拉出一周内所有走兜底通道的样本快速过一遍发现有明显规律的就追加一条规则进去。刚开始每周都要加两三条到后面就越来越少系统也就越来越省心。再补一句这套两级流水线的思路不局限于文档分类任何需要本地模型批量处理的场景都可以套用。核心就一句话——能用规则解决的事就别让模型去猜。拿这句话去做所有流程设计的出发点很多坑是可以提前避开的。
返回列表