☰
批量推理工程实践:vLLM与Ray Data实现高吞吐离线任务
2026/9/28 19:07:39 网站建设 项目流程

1. 批量推理到底在解决什么问题

1.1 从一次尴尬的线上事故说起

去年冬天我帮一个做智能客服的团队排查线上问题,他们的场景很典型:白天用户咨询量不大,单条请求的响应速度也还行,但每天凌晨两点会跑一批历史会话的意图重分类任务,一次要处理八十多万条文本。他们最初的做法很朴素,写了个 Python 脚本,用for循环一条一条调本地部署的推理服务,结果这批任务从凌晨两点一直跑到第二天上午十点还没结束,GPU 利用率在监控面板上像心电图一样忽高忽低,平均只有百分之十几。

这个现象背后就是批量推理要解决的核心矛盾。单条推理追求的是低延迟,用户发一句话,希望几百毫秒内看到回复;而批量推理追求的是高吞吐,同样一批数据,希望单位时间内处理得越多越好。这两个目标在工程实现上几乎是相反的:为了降低单条延迟,你会希望请求一到就立刻调度、立刻占用算力、立刻返回;为了提升整体吞吐,你反而希望攒一攒、凑一凑,把多个请求打包成一个大批次一起送进 GPU,让矩阵乘法的维度尽可能大,把显存带宽和计算单元喂饱。

同一个推理引擎,比如 vLLM,白天在线上服务时用的是连续批处理(Continuous Batching)配合较小的批大小,保证首 token 延迟;到了夜间离线任务,就应该切换到另一套参数配置,把max_num_seqs、max_num_batched_tokens拉高,甚至关掉一些为交互场景优化的调度策略。这就是标题里说的“同一个引擎,相反的目标”——引擎是同一套代码、同一份权重,但调度目标、参数取向、资源编排方式完全不同。

1.2 谁需要认真对待批量推理

如果你符合下面任意一条,批量推理就不是一个可以糊弄过去的环节:

  • 手上有几十万到上亿条文本、图片或音频需要做离线打标、分类、摘要、向量化;
  • 在做 RAG 系统的知识库构建,需要把海量文档切片后批量生成 embedding;
  • 做模型评测,要在几千条测试集上跑多个模型的对比;
  • 做数据清洗和合成,用大模型批量生成训练语料;
  • 做推荐系统的特征回填,需要对历史行为做批量打分。

这些场景的共同点是:数据量大、对单条延迟不敏感、对总耗时和成本极度敏感。一条请求慢两百毫秒没人会在意,但整批任务多跑六个小时,就意味着 GPU 租用成本直接翻倍。我见过太多团队在模型选型上反复纠结,却在批量推理的工程实现上随手写个循环,最后算力账单里一大半都浪费在了调度空转和显存碎片上。

1.3 批量推理和在线推理的本质差异

很多人以为批量推理就是“把在线接口循环调用一遍”,这个认知偏差会导致后面所有的优化都跑偏。我把两者的关键差异整理成一张表,你可以对照自己的场景看看:

维度在线推理批量推理
核心指标首 token 延迟、尾延迟总吞吐、单位成本
请求到达随机、稀疏、不可预测已知、可一次性枚举
批大小策略动态、偏小静态或大动态、偏大
调度优先级公平性、超时保护吞吐最大化、显存利用率
失败处理立即重试、降级记录后跳过、批量重跑
资源编排常驻服务、弹性扩缩一次性任务、用完即释放
典型工具vLLM 在线模式、TGIvLLM 离线模式、Ray Data

看这张表你会发现,批量推理其实更接近传统的大数据离线作业,而不是 Web 服务。它需要的是任务编排、分片、容错、断点续跑这一整套东西,而不是一个 HTTP 接口。这也是为什么后面我会重点讲 Ray Data 和 Kubernetes 的配合——它们本来就是为这类离线作业设计的。

2. 引擎选型:为什么是 vLLM,而不是别的

2.1 vLLM 在批量场景下的三个杀手锏

选 vLLM 做批量推理的底座,不是因为它名气大,而是它在批量场景下有三个实打实的优势。

第一个是PagedAttention。传统推理框架给每个请求预分配一块连续的 KV Cache 显存,请求长度不可预测时,要么浪费(按最大长度分配),要么频繁重分配(按实际长度动态扩)。PagedAttention 把 KV Cache 切成固定大小的块,像操作系统管理内存页一样按需分配,显存浪费能压到百分之几以内。批量推理里请求长度往往参差不齐,短的两百 token,长的八千 token,这个机制带来的显存节省直接决定了你一张卡能同时塞多少条请求。

第二个是连续批处理。静态批处理要等一整批请求全部生成完才能开始下一批,一条长请求会拖住整批。连续批处理在每一步解码后就把已完成的请求踢出、把新请求塞进来,GPU 几乎不会空转。在离线批量场景下,这个机制让吞吐能比静态批处理高出两到四倍。

第三个是离线推理接口。vLLM 提供了LLM.generate()这样的离线入口,直接吃一个 prompt 列表,内部自动做批调度,不需要你起 HTTP 服务、不需要处理网络开销。这一点在批量任务里非常关键——网络往返和序列化在百万级请求下会变成不可忽视的成本。

2.2 vLLM 和 SGLang 在批量场景的取舍

最近社区里 SGLang 和 vLLM 的对比讨论很多,我两个都用过,说说批量场景下的实际感受。

SGLang 的RadixAttention在前缀共享明显的场景下确实猛,比如你有一批请求都带着同一个很长的系统提示词,或者做多轮对话的批量回放,它能把这些公共前缀的 KV Cache 复用起来,吞吐提升非常可观。如果你的批量任务恰好是这种形态,SGLang 值得一试。

但 vLLM 的优势在于生态成熟度和稳定性。它的离线接口 API 更稳定,和 Ray、Kubernetes 的集成案例更多,社区里踩过的坑也更多,遇到问题更容易搜到答案。而且 vLLM 对量化模型的支持更全面,AWQ、GPTQ、FP8 这些量化格式在批量场景下能显著降低显存占用,让你用更少的卡跑更多的数据。

我的建议是:前缀共享极强、追求极致吞吐,可以试 SGLang;追求稳定、需要复杂编排、要用量化,选 vLLM。批量推理最怕的不是慢一点,而是跑到一半崩了还得从头再来,稳定性权重应该给得很高。

2.3 版本选择的坑:别盲目追新

热词里有一条“vllm 新版本性能下降”,这不是个例。vLLM 迭代非常快,几乎每周都有新版本,但新版本不一定适合你的批量场景。我踩过的坑是:某个版本为了优化在线场景的调度公平性,调整了调度器的默认行为,结果离线批量吞吐掉了将近百分之二十。

我的做法是锁定版本、做好基准测试。具体来说:

  • 选定一个版本后,用你自己的真实数据跑一次基准,记录吞吐和显存峰值;
  • 升级前先在测试环境用同样的数据跑一遍,对比数字;
  • 把镜像 tag 写死,比如vllm/vllm-openai:v0.27.1这种带具体版本号的,绝对不要用latest。

提示:批量任务的镜像一定要固定 digest 或精确版本号。用latest意味着某天凌晨任务重跑时拉到的镜像可能已经变了,行为不一致会让你排查到怀疑人生。

3. 用 Ray Data 把批量任务拆开

3.1 为什么不能一个进程吃下所有数据

假设你要给一千万条文本生成 embedding。最直觉的做法是全部读进内存,组成一个大列表,丢给 vLLM 的generate()。这个做法在小数据量下没问题,但一千万条文本光原始数据就可能几十 GB,加上 tokenize 后的结果、中间状态,内存直接爆掉。就算内存扛得住,单进程单卡的吞吐也有上限,你没法利用多卡并行。

Ray Data 解决的就是这个问题。它把数据集抽象成一个分布式的流式管道,数据被切成块(block),在多个 worker 上并行处理,每个 worker 负责一部分数据,处理完的块流向下一个阶段。整个过程是流式的,不需要把全量数据同时放进内存。

3.2 Ray Data 管道的四个阶段

一个典型的批量推理管道长这样:

import ray from ray.data import Dataset from vllm import LLM, SamplingParams # 1. 读取阶段:从各种数据源加载 ds = ray.data.read_parquet("s3://my-bucket/input-data/") # 2. 预处理阶段:清洗、截断、构造 prompt def build_prompt(row): text = row["content"][:4000] # 截断,防止超长 return {"prompt": f"请对以下文本分类:\n{text}\n类别:"} ds = ds.map(build_prompt) # 3. 推理阶段:每个 worker 持有一个 vLLM 实例 class VLLMPredictor: def __init__(self): self.llm = LLM( model="Qwen/Qwen2.5-7B-Instruct", tensor_parallel_size=1, max_num_seqs=256, gpu_memory_utilization=0.9, ) self.sampling = SamplingParams(temperature=0, max_tokens=16) def __call__(self, batch): prompts = batch["prompt"] outputs = self.llm.generate(prompts, self.sampling) return {"result": [o.outputs[0].text for o in outputs]} ds = ds.map_batches( VLLMPredictor, batch_size=512, concurrency=4, # 4 个并发 worker num_gpus=1, # 每个 worker 占 1 张卡 ) # 4. 写出阶段 ds.write_parquet("s3://my-bucket/output-data/")

这四个阶段对应了批量推理的完整生命周期。读取阶段要注意数据源的分片,Parquet 天然支持按 row group 切分,比读一堆小 JSON 文件高效得多。预处理阶段是最容易被忽视的,很多人把原始数据直接丢给模型,结果超长文本触发截断逻辑、脏数据导致 tokenize 报错,整个任务挂掉。推理阶段是核心,下面单独展开。写出阶段建议用 Parquet 而不是 JSON,压缩率高、读取快、schema 明确。

3.3 并发度和批大小的调参逻辑

concurrency和batch_size这两个参数是批量推理调优的重头戏,很多人凭感觉设,结果要么 GPU 没吃满,要么显存爆掉。

先说concurrency,它决定同时有多少个 worker 在跑。每个 worker 占一张 GPU(num_gpus=1),所以concurrency基本等于你分配的 GPU 数量。如果你有 8 张卡,设concurrency=8就能全部用上。但要注意,如果单卡显存不够放下一个完整的 vLLM 实例,你就得考虑用张量并行,让多个 worker 共享一个模型实例,这时候num_gpus要设成小数,比如num_gpus=0.5表示两个 worker 共用一张卡。

再说batch_size,它决定每个 worker 一次处理多少条数据。这个值不是越大越好。批太大,单次generate()调用会占用大量显存做 KV Cache,可能触发 OOM;批太小,GPU 利用率上不去,调度开销占比过高。我的经验值是:从 256 开始试,观察显存峰值和吞吐,逐步加到显存用到百分之八十五左右为止。

这里有个容易忽略的点:batch_size和 vLLM 内部的max_num_seqs是两回事。batch_size是 Ray 层面一次喂给 vLLM 的请求数,max_num_seqs是 vLLM 内部同时调度的序列数。如果batch_size远大于max_num_seqs,多出来的请求会在 vLLM 内部排队,不会浪费,但也不会提升吞吐。理想情况下两者接近,让 Ray 的批和 vLLM 的调度窗口匹配。

3.4 显存估算:动手算一遍

批量推理最容易翻车的地方就是显存。我给你一个粗略但实用的估算方法,以 7B 模型、FP16 精度为例:

模型权重本身占7B × 2 字节 = 14GB。KV Cache 的计算稍微复杂:每个 token 的 KV Cache 大小约等于2 × 层数 × 隐藏维度 × 2 字节。以 Qwen2.5-7B 为例,28 层、隐藏维度 3584,那么每个 token 约2 × 28 × 3584 × 2 = 401KB。如果平均序列长度 1024,同时调度 256 条序列,KV Cache 就是401KB × 1024 × 256 ≈ 105GB。

这个数字一看就超了单张 80GB 卡的容量。所以实际部署时要么降低max_num_seqs,要么用张量并行把模型和 KV Cache 分摊到多张卡,要么上量化把权重压到 4bit。这就是为什么前面强调要动手算——凭感觉设参数,十有八九会 OOM。

注意:gpu_memory_utilization设成 0.9 意味着 vLLM 会尝试占用 90% 的显存,剩下的留给 CUDA 上下文和其他开销。设太高(比如 0.98)容易在长序列时 OOM,设太低(比如 0.7)则浪费显存。0.85 到 0.92 是比较稳的区间。

4. Kubernetes 上的批量任务编排

4.1 为什么批量推理要上 K8s

有人会问,批量任务跑一次就完了,用 K8s 是不是杀鸡用牛刀?我的回答是:数据量小、跑一次就完,确实不用;但只要涉及多卡、多机、需要重跑、需要排队,K8s 就是刚需。

批量推理的资源需求是脉冲式的:任务来了要几十张卡,任务跑完这些卡就该释放给别的任务。如果用手动管理,你得盯着任务什么时候结束、手动释放机器,效率极低。K8s 的 Job 和 CronJob 天然适合这种场景,任务提交后自动调度、自动分配 GPU、跑完自动回收。

更重要的是容错。批量任务动辄跑几个小时,中间某张卡出问题、某个 worker 挂掉是常态。K8s 的 Job 支持backoffLimit,worker 失败后自动重启;配合 Ray 的断点续跑能力,可以从上次的进度继续,而不是从头再来。

4.2 GPU 资源声明的正确姿势

在 K8s 里申请 GPU,最基础的方式是在 Pod spec 里写:

resources: limits: nvidia.com/gpu: 1

这要求集群里装了 NVIDIA 的设备插件。但批量推理场景下,这种粗粒度的申请往往不够用。比如你想让两个小模型共享一张卡,或者想把一张卡切成几份给不同的任务,就需要GPU 虚拟化方案,比如 HAMi 这类工具。它能把一张物理卡切成多个虚拟 GPU,每个 Pod 申请nvidia.com/gpu: 0.5这样的份额。

不过我要提醒一句:GPU 虚拟化在批量推理里要慎用。切分后的显存和算力都是打折的,如果每个分片都跑一个 vLLM 实例,KV Cache 空间会被严重压缩,反而容易 OOM。虚拟化更适合推理负载轻、模型小的场景。大模型的批量推理,老老实实一卡一实例更稳。

4.3 一个可复用的 Job 模板

下面这个 Job 模板是我在实际项目里反复用过的,做了精简,你可以直接改成自己的:

apiVersion: batch/v1 kind: Job metadata: name: batch-inference-job spec: backoffLimit: 3 ttlSecondsAfterFinished: 3600 template: spec: restartPolicy: OnFailure containers: - name: inference image: vllm/vllm-openai:v0.27.1 command: ["python", "/workspace/run_batch.py"] resources: limits: nvidia.com/gpu: 4 volumeMounts: - name: data mountPath: /data - name: model-cache mountPath: /root/.cache/huggingface env: - name: RAY_ADDRESS value: "auto" volumes: - name: data persistentVolumeClaim: claimName: batch-data-pvc - name: model-cache persistentVolumeClaim: claimName: model-cache-pvc

几个关键点值得说明。ttlSecondsAfterFinished让任务完成后一小时自动清理 Pod,避免堆积一堆 Completed 状态的 Pod 占着资源。restartPolicy: OnFailure保证 worker 挂了会重启。模型缓存挂一个 PVC 非常重要——如果不挂,每次任务启动都要重新下载几十 GB 的权重,光下载就能耗掉半小时。数据卷同理,输入输出都走 PVC 或对象存储挂载。

4.4 断点续跑:批量任务的保命符

批量任务最怕的就是跑到百分之九十崩了,然后从头再来。断点续跑的实现思路是:每处理完一个分片,就把进度记录到外部存储。

具体做法是把输入数据按行数或文件切分成多个分片,每个分片处理完后在输出目录写一个标记文件,或者往一个进度表里写一条记录。任务重启时先读进度,跳过已完成的分片。Ray Data 本身对某些数据源支持 checkpoint,但更通用的做法是自己控制分片粒度。

我一般会把分片大小控制在单分片处理时间五到十分钟。太小了进度记录开销占比高,太大了崩一次损失太多。这个粒度下,即使任务中途挂了,最多损失十分钟的算力。

5. 实操中踩过的坑和排查技巧

5.1 吞吐上不去的五个常见原因

批量推理跑起来后,第一件事是看吞吐。如果发现 GPU 利用率上不去、总耗时远超预期,按下面这个顺序排查:

现象可能原因排查方法解决方向
GPU 利用率低于 30%批太小或数据加载慢看nvidia-smi和 CPU 占用增大 batch_size,加速数据读取
显存快满但吞吐低KV Cache 碎片或序列过长看 vLLM 日志的 running/waiting 队列限制 max_tokens,调 max_num_seqs
吞吐忽高忽低数据分片不均看各 worker 处理速度重新分片,打散长尾数据
启动后长时间无输出模型加载或下载慢看容器日志挂载模型缓存 PVC
多卡吞吐不线性通信瓶颈或负载不均看 NCCL 日志检查张量并行配置和网络

这张表里的每一条我都在真实项目里遇到过。最常见的是第一条和第四条。数据加载慢尤其隐蔽——你以为瓶颈在 GPU,其实 CPU 在拼命解析 JSON,GPU 在等数据。解决办法是把输入数据预先转成 Parquet 或 Arrow 格式,读取速度能快一个数量级。

5.2 长序列导致的 OOM 怎么破

批量推理里最头疼的就是长序列。你的数据里可能百分之九十九都是短文本,但总有那么几条超长的,一旦它们被调度进来,KV Cache 瞬间膨胀,直接 OOM 把整个 worker 干掉。

我的处理策略是分层处理:先按长度把数据分桶,短序列一批、长序列单独一批。短序列用大批大小跑,长序列用小批大小甚至单条跑。这样既保证了整体吞吐,又避免了长尾数据拖垮整个任务。

具体实现上,可以在预处理阶段统计每条数据的 token 长度,按长度区间打标签,然后按标签分组处理。vLLM 的max_model_len参数要设成你数据里的最大长度,但max_num_seqs要根据当前批次的实际长度动态调整——这一步 Ray Data 的map_batches里可以自己控制。

提示:如果数据里存在极端长尾(比如有几千条超过模型上下文长度的),一定要在预处理阶段就截断或过滤掉,不要让它们进入推理阶段。截断策略要结合业务,分类任务截断尾部通常没问题,摘要任务截断可能丢关键信息。

5.3 结果顺序错乱和去重问题

批量推理有个隐蔽的坑:输出顺序和输入顺序对不上。Ray Data 是分布式并行的,多个 worker 同时处理不同的分片,写出时顺序是乱的。如果你后续要按原始顺序对齐结果,必须在数据里带一个唯一 ID,输出时也带上,最后按 ID 排序。

另一个坑是重复处理。任务失败重跑时,如果进度记录不精确,已经处理过的数据可能被再处理一遍,导致输出里有重复。解决办法是在输出端做幂等——按 ID 去重,或者用支持覆盖写的存储格式。

5.4 监控指标该看哪些

批量任务跑起来后,别只盯着“跑完了没”。我一般会监控这几个指标:

  • 吞吐:每秒处理多少条,这是最核心的指标;
  • GPU 利用率:稳定在百分之七十以上算健康;
  • 显存峰值:离上限留百分之十以上余量;
  • 各 worker 进度差异:差异过大说明分片不均;
  • 失败重试次数:频繁重试说明有系统性问题。

这些指标可以通过 Ray Dashboard 看,也可以导出到 Prometheus 做告警。批量任务不需要实时告警,但任务结束后要能复盘——哪个阶段慢、哪张卡拖后腿,这些数据是下次调优的依据。

6. 成本优化的几个实战思路

6.1 量化:用精度换显存和速度

批量推理对精度的小幅下降通常不敏感,这给了量化很大的空间。7B 模型从 FP16 量化到 AWQ 4bit,显存占用从 14GB 降到约 4GB,同样的卡能塞下更多序列,吞吐提升明显。实测下来,AWQ 量化在分类、抽取这类任务上,准确率下降通常在百分之一以内,完全可接受。

但要注意,量化不是免费的。反量化计算会带来额外开销,在某些卡上可能抵消掉显存节省带来的收益。而且不是所有模型都有现成的量化版本,自己量化需要校准数据集,有额外工作量。我的建议是:先用官方或社区提供的量化版本试,效果好再考虑自己量化。

6.2 抢占式实例:便宜但要有容错

云上的抢占式 GPU 实例价格可能只有按需实例的三分之一甚至更低,对批量任务非常有吸引力。但抢占式实例随时可能被回收,这就要求你的任务必须支持断点续跑。前面讲的进度记录机制在这里就是刚需。

我的做法是:把批量任务设计成可中断、可恢复的,然后用抢占式实例跑。任务被回收了,K8s 会自动重新调度,从上次进度继续。这样既省了钱,又不影响最终结果。前提是进度记录要足够频繁,损失控制在可接受范围内。

6.3 批大小和成本的量化关系

批大小对成本的影响不是线性的。批太小,GPU 利用率低,单位数据的算力成本高;批太大,可能 OOM 导致任务失败重跑,反而更贵。存在一个成本最优的批大小,通常在显存用到百分之八十五左右的位置。

我做过一组对比测试,同一个 7B 模型处理十万条文本:

批大小总耗时GPU 利用率单位成本
6482 分钟41%基准的 1.8 倍
25638 分钟78%基准
51235 分钟83%基准的 0.95 倍
1024OOM 重跑-基准的 1.3 倍

可以看到,从 64 加到 256,成本几乎腰斩;从 256 加到 512,收益已经很小;再往上就 OOM 了。所以调参的目标不是“越大越好”,而是找到那个拐点。

7. 从单机到集群的扩展路径

7.1 单机多卡:先跑通再扩展

我建议所有批量推理项目都从单机多卡开始。一台 8 卡机器,用 Ray 起一个本地集群,concurrency=8,先把整个管道跑通。这个阶段的目标不是性能,而是验证正确性——数据读取对不对、prompt 构造对不对、输出格式对不对、断点续跑能不能工作。

单机阶段最容易暴露的问题是数据格式和预处理逻辑。这些问题在单机上排查成本低,一旦上了多机集群,日志分散、复现困难,排查成本会高很多。

7.2 多机集群:网络和存储是瓶颈

扩展到多机后,瓶颈往往从 GPU 转移到网络和存储。模型权重加载、数据读取、中间结果传输都要走网络。这时候要注意几点:

  • 模型缓存要共享:用 NFS 或对象存储做模型缓存,避免每台机器都下载一遍;
  • 数据本地性:尽量让计算靠近数据,或者把数据预先分发到各节点;
  • 网络带宽:张量并行对网络带宽要求高,跨机张量并行最好用高速网络。

多机场景下,Ray 的集群模式能自动处理节点发现和任务分发,但前提是各节点的环境要一致——Python 版本、CUDA 版本、依赖库版本都要对齐,否则会出现各种诡异的错误。

7.3 任务队列:让批量任务排队跑

当团队里有多个人都要跑批量任务时,就需要一个任务队列来管理 GPU 资源。K8s 的 Job 本身有调度能力,但多个 Job 争抢 GPU 时,默认是先到先得,容易造成资源碎片。

更优雅的做法是用Kueue这类批处理调度器,它支持队列、配额、优先级,能让多个批量任务有序地共享 GPU 资源池。配置好配额后,每个团队提交的任务会在自己的配额内排队,不会互相抢占。这个在多人协作的环境里非常实用。

8. 一些不那么显然的经验

8.1 预热很重要

vLLM 实例启动后,第一次推理会明显慢——CUDA 内核要编译、显存要分配、各种缓存要建立。如果批量任务一开始就处理真实数据,前几百条会拖慢整体进度。我的做法是在正式处理前,用几条假数据做一次预热推理,把各种初始化开销提前消化掉。

8.2 日志要结构化

批量任务的日志量很大,几百万条数据的处理过程如果每条都打日志,日志文件能撑爆磁盘。但完全不打日志,出问题又没法排查。我的做法是结构化日志加采样:每个分片记录开始和结束、处理条数、耗时、失败条数;单条数据的详细日志只在失败时记录。这样既能定位问题,又不会淹没在日志海里。

8.3 输出要可验证

批量任务跑完后,怎么确认结果是对的?我的习惯是在输出里保留足够的元信息:原始 ID、输入摘要、模型输出、耗时、使用的模型版本。这样后续做质量抽检时,能快速定位到具体是哪条数据、用了哪个版本、输出了什么。没有这些元信息,出了问题只能干瞪眼。

8.4 别忽视 tokenizer 的开销

在大批量场景下,tokenize 本身可能成为瓶颈。尤其是用 Python 的 tokenizer 逐条处理时,CPU 会成为限制。解决办法是用批量 tokenize 接口,或者用 Rust 实现的快速 tokenizer。vLLM 内部已经做了优化,但如果你在预处理阶段自己调 tokenizer,要注意这一点。

8.5 版本锁定要贯彻到底

前面提过镜像版本要锁定,其实模型版本、依赖库版本、甚至 CUDA 版本都要锁定。批量任务的可复现性依赖于整个环境的确定性。我见过因为 transformers 库小版本升级导致 tokenizer 行为变化,最终输出结果和上次不一致的案例。在批量场景下,这种不一致可能意味着整个数据集要重跑。

批量推理这件事,说到底就是把“同一个引擎”用在“相反的目标”上。在线服务那套为延迟优化的思路,到了离线批量场景要整个反过来。引擎还是那个引擎,但调度策略、参数配置、资源编排、容错机制,全都要重新设计。我个人的体会是,批量推理的难点从来不在模型本身,而在工程——怎么把数据喂饱、怎么让 GPU 不空转、怎么在崩了之后不从头再来。把这几个问题解决了,批量推理的效率和成本自然就上去了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询