全量任务跑了几小时还没出结果,通常不是并发开得不够,而是单任务耗时分布没摸清、超时值拍脑袋定的、外部调用占比没算过。建议先取几十条任务跑一轮小批量,把单任务耗时、等待外部工具返回的时长、失败后重跑一次的成本记录下来,再据此定并发上限和超时值。小批量跑通的意义不是拿到最终评测分数,而是拿到扩容前的基础数字。
适用场景:AlphaEval 一类需要多次调用外部模型或工具、单任务耗时差异大的评测任务。操作动作:先用一个很小的样本跑通完整链路,测单任务耗时分布和失败重跑代价,再按梯度测试找并发拐点,最后把并发、超时、重试写进配置文件。验证方式:看运行日志里实际生效的并发数、超时次数和重跑率。风险边界:样本太小时耗时分布不可靠,梯度测试也只说明当前环境下的表现,换模型版本或外部服务后需要重测。
先用少量任务测单任务耗时分布
取样数量建议控制在二十到五十条之间,太少看不出尾部耗时,太多又会占掉本应用于全量的时间和额度。取样时要覆盖不同任务类型和不同输入长度,不要只挑最短的几条,否则后面定的超时值一定会误伤慢任务。
每条任务至少记录这几个字段:任务标识、入队时间、开始执行时间、结束时间、等待外部工具返回的时长、状态、第几次尝试。等待时长要单独记,不能混在总耗时里,否则后面拆不出瓶颈在哪。日志建议直接写成结构化行,便于后面聚合:
{"task_id":"t-0231","type":"rag","enqueue_at":"...","start_at":"...","end_at":"...","queue_wait_ms":120,"model_ms":8400,"tool_ms":26000,"io_ms":900,"status":"ok","attempt":1}
拿到这批日志后,看三个数:中位数单任务耗时、尾部耗时(比如最慢的那几条)、以及重跑一次大概要多久。尾部耗时决定单任务超时设多少,中位数决定并发开几档比较合理。
把并发数当变量做一次梯度测试
不要一次把并发拉到目标值,也不要只跑一档。建议选四档递增,比如 1、2、4、8,每档跑同一批任务,任务集固定不变,这样才有可比性。跑的时候把每档结果单独输出到一个文件:
for c in 1 2 4 8; do
python run_eval.py `--suite` smoke `--limit` 40 `--concurrency` $c `--out` runs/c${c}.jsonl
done
每档要观察三个指标:完成数、失败率、平均耗时。判断拐点的依据是:并发继续上调,完成数不再明显增长,平均耗时反而上升,失败率开始抬头,说明瓶颈已经不在本地的并发能力上,加并发只会让任务在队列里互相挤。这个时候就应该停下来,转去看下一节的耗时构成。
看清哪些环节在等外部工具返回
把单任务耗时拆成四段:排队等待、模型调用、外部工具调用、本地读写。前一段看 queue_wait_ms,模型看 model_ms,外部工具看 tool_ms,本地看 io_ms。外部调用占比的算法很直接:用 tool_ms 加上 io_ms 除以三段之和,逐条算完再取平均值,用 jq 就能做:
jq -s '[.[] | (.tool_ms + .io_ms) / (.model_ms + .tool_ms + .io_ms)] | add / length' runs/c1.jsonl
如果这个比值偏高,说明大部分时间花在等外部返回,本地加并发收益有限,更有效的是给外部调用单独设超时和重试上限;如果 model_ms 占比高,才轮到考虑并发和批次大小。这一步必须先做,否则很容易把外部服务的排队时间误判成自己并发不够。
按失败重跑的代价留预算余量
重跑率不用精确预测,用小批量跑出来的数据估算即可:需要重跑的任务数除以总任务数,再加上外部工具报错、超时这两类可预期失败的经验比例,向上取整。单任务平均消耗量要按统一口径记录,比如每次任务消耗的调用次数、输入输出 token 数、计费单位,都在同一条日志里落下来,不要在事后靠估算补。
余量建议在全量预算上留出两成到三成的空间,专门用于重跑和尾部慢任务;如果小批量里重跑率明显偏高,就把余量再放大,或者先把失败原因修掉再扩量。判断标准是:全量跑到最后一成任务时,额度还不能见底。
把并发与超时写进运行配置
梯度测试确认过的值不要留在命令行参数里,写进配置文件,扩容和回滚都改这一处:
runner:
concurrency: 4 # 梯度测试确认的档位,上调前先重跑一次梯度
task_timeout_seconds: 180 # 略高于小批量尾部耗时
max_retries: 2
retry_backoff_seconds: 15
budget:
max_total_calls: 5000 # 额度上限,跑满即停
checkpoint_file: runs/last_batch.json
改动后的验证方式:先启动十到二十条任务,看日志里打印的实际生效并发数是否等于配置值,确认超时中断的任务能按 max_retries 重试且带上退避间隔,确认额度上限触发后进程能正常停下来而不是继续排队。三件事都符合预期,再把 limit 调大,并且保留上一版配置,出问题时能直接回滚到小批量状态。