在阶跃星辰实习时,我的工作之一是把相声录音——一种非常特定、非常「中国式」的对话喜剧形式——转化成能拿去训练模型的高质量对话数据。
整个 pipeline 分三个阶段:基于规则的过滤、LLM-as-a-judge 分类、人工抽检。真正让「多模型路由」变成一个需要认真设计的工程问题,
而不是随便调一次 openai.ChatCompletion.create() 的地方,就在中间这一步。
为什么要在多个模型之间路由
最朴素的做法是选定一个模型、写好 prompt、跑到底——但这个做法在处理 1 万多条原始对话、 还要拿真实标注去评估 judge 质量的时候会很快崩掉。我们当时在 GPT、Claude、Qwen 之间做对比, 它们各自的速率限制、延迟表现、以及在处理杂乱口语化中文对话时的失败模式都不一样。如果一开始就锁死一个模型, 之后每次发现更好的 judge,都得把整条 pipeline 重跑一遍。
所以这个 pipeline 从设计之初就是模型无关的:一层很薄的路由层,接收 prompt 模板和目标模型标识符, 统一响应格式,无论哪个后端返回结果都做同样的日志记录。这个抽象层听起来不起眼,但正是它让我们可以随时换 judge, 而完全不用碰过滤逻辑本身。
两个完全不同的并行化问题
实际上这里有两个不同的「速度」问题,如果把它们混为一谈,两边都会做得更差:
- 生产吞吐量——用选定的 judge 模型跑完整个数据集。这里的瓶颈是 API 往返延迟,不是算力。 用异步执行(asyncio + 针对每个供应商速率限制的有界信号量)替代同步调用,让我们拿到了大约 5 倍的提速,纯粹是因为不再让程序在等待网络 I/O 时空转。
- 实验吞吐量——把三个候选模型都跑一遍同一批样本,用来对比 judge 质量。 这个任务在模型维度和样本维度上都是天然可并行的,所以用本地并行化(跨 CPU 核心的多进程,因为大部分时间是很多并发连接在等网络 I/O) 让对比跑批速度提升了接近10 倍。
这里的经验是:「让它更快」根本不是一个问题。生产吞吐量和实验吞吐量的瓶颈不一样,需要的解法也不一样。 异步并发解决的是 I/O 空转;本地并行解决的是同时向多个模型扇出请求时,CPU 密集的调度开销。 把错误的解法用在错误的问题上,只会用更高的复杂度换来更小的收益。
一个没人提前提醒你的 prompt 位置发现
真正改变了我们写 prompt 方式的地方是:我们对「指令到底该放在 system 角色还是
user 角色」做了消融实验,结果发现最优位置在不同模型之间并不一致。
这意味着我们的 prompt 模板不能是一套字符串直接套用到所有供应商上——每个模型都需要一套单独的注入策略。 这是个很小的细节,但恰恰是那种要等你把同一个任务跑在多个模型上、开始逐条对比失败案例时才会浮出水面的东西。 如果你在做任何跨供应商路由的系统,建议尽早留出时间做这个消融实验;等 pipeline 已经默认了统一格式之后再返工, 会很麻烦。
最后落地成了什么
这套三阶段 pipeline 把原始的 1 万多条对话筛到了大约 6000 条金标准样本,密度不够、直接用没意义的对话段落 则通过 few-shot 引导的改写来补齐。这批清洗后的数据集在内部推理和语言能力基准上贡献了 6-7 个百分点的提升, 足以达到消费级部署的门槛。如果不是把「谁来当 judge」和「怎么跑得更快」当成两个各自独立、认真解决的问题, 在我们需要的这个数据量级下,这一切根本不可能做到。