很多团队把输出审核接进大模型服务后,第一反应是“更安全了”,真正上线才发现另一个代价更吓人:用户看到的流式回复开始一卡一顿,P95 首包时间和整段输出时间一起抖。⚠️ 这类问题往往不是审核模型慢,而是审核位置放错了。
[外链图片转存中…(img-3I85FoYj-1779534779586)]
很多推理链路默认按 token 或小分片流式输出。若每个分片都要先同步经过审核,再决定是否下发,解码线程就不再只受 GPU 约束,而是被 CPU 审核、网络往返和策略分支共同拖慢。🚨 一旦批处理里混入高风险请求,其余正常请求也会被连带回压。
问题不是要不要审,而是审查放在哪条路径
同步过滤最大的问题,不是平均延迟升高,而是把批处理节奏打碎。模型侧原本可以持续 decode,审核一插入就变成“生成一点、停一下、判一次”。🔍 当 Stop Sequences、结构化输出和审核同时存在时,服务端还要额外判断片段是否完整,导致 flush 粒度越来越小。
| 方案 | 首包时间 TTFT | P95 尾延迟 | 风险处置 |
|---|---|---|---|
| 无审核 | 420 ms | 2.8 s | 无 |
| 全量同步审核 | 760 ms | 5.4 s | 最保守 |
| 分级异步审核 | 470 ms | 3.2 s | 风险可控 |
从线上观测看,只要同步审核命中率超过 20%,队列就会明显拉长。📉 更麻烦的是,审核结果常依赖完整语义,而流式片段恰恰最缺上下文;于是系统只能把更多 token 攒成更大的 chunk 再判,结果又进一步拖慢用户感知。
[外链图片转存中…(img-9tHsXiPz-1779534779592)]
实战做法:热路径只做轻判,重审核异步化
更稳的工程方案是把审核拆成两层。✅ 热路径只保留极轻量的规则和小模型打分,用来拦截明显违规内容;完整审核、人工复核和审计留痕放到异步通道。这样做的核心,不是“降低审核强度”,而是把 GPU 解码和安全判定解耦。
asyncdefstream_with_guard(chunks,fast_guard,async_review):forchunkinchunks:verdict=fast_guard.score(chunk)ifverdict.block:yield"[内容已拦截]"breakyieldchunkifverdict.need_review:async_review.enqueue(chunk,verdict.reason)这套链路里,快速守卫只回答两个问题:是否立即阻断、是否需要后置复核。🧩 真正吃时延的大模型审核、跨段语义归并和策略解释,都通过消息队列异步执行。若后置审核发现风险,再补发撤回、替换或告警事件,而不是让每个 token 在出口排队。
再往前走一步,团队最好把审核粒度从 token 提升到语义片段。📌 典型做法是按句号、换行、函数调用结束符或 JSON 结构闭合点切 chunk。这样既能减少审核调用次数,又能让判定更接近完整语义,误杀率也更低。
[外链图片转存中…(img-jNhEmIAw-1779534779593)]
真正要盯的指标,不只是审核耗时
很多团队只盯审核服务 RT,最后却解释不了用户为什么仍然觉得卡。📊 更关键的指标其实有四个:审核调用频次、chunk 平均大小、decode 队列等待时间、因审核导致的 flush 延后比例。只看审核模型快不快,几乎抓不到根因。
笔者认为,输出审核和推理调度应该一起设计,而不是上线后再补。💡 未来 3 到 6 个月,更常见的做法会是“轻判前置、重审后置、风险分级、结果补偿”这套组合:既把明显风险拦在出口,又不让安全链路反向拖垮推理 SLA。
归根到底,审核不是不能同步做,而是不该无差别地卡住所有流量。🤝 如果你的服务一接审核就开始抖延迟,先别急着换更大的安全模型,先检查审核是不是已经偷偷进入了解码热路径。