相同的 LLM 在「不同 GPU 上」会产生不同输出?为什么?
大多数技术工程师都清楚,依赖库版本不一致,可能会导致系统行为跟着跑偏。但大语言模型这个领域,情况要复杂得多——因为训练和推理极度依赖GPU,而很少有人真的意识到:换一块GPU,模型输出的结果可能就变了。
假如你费尽心思想搭建两个完全一致的开发环境——锁死依赖库版本、用Docker打包、把LLM的temperature设为0、再选好随机种子——你以为万无一失了吧?但真相可能让人有点泄气:只要GPU型号不是同一款,上面所有这些努力,基本都白费了。
这篇文章会用一个实验来直观展示这个现象,顺便聊聊问题出在哪、为什么会出现。如果你对代码细节不感兴趣,可以直接跳到第七节,那里集中解释了“为什么同样输入、同样模型,在不同GPU上的输出会差这么多”,结论部分也能帮你理清原理。
01 为什么要写这篇文章?
事情要从一次讨论说起。那天,几个人聊到OpenAI和Anthropic的模型为什么不能做成确定性的系统。我的解释是:这些模型很可能用了混合专家(MoE)架构,偶尔不会把token路由给最优的专家——因为那些专家正忙着处理其他token,这就造成了输出不一致。
另外,OpenAI为了提高效率,可能会对查询做批量处理,batch size随着传入请求的数量变化,这会改变GPU的计算策略,同样导致不同的输出。
聊着聊着,有人抛出一个问题:“难道不同的GPU就不会导致不同的输出吗?”空气安静了一下,然后对话变得有意思起来。
仔细想想:当我们调用OpenAI API时,实际上是远程某台服务器在执行计算并返回结果。如果那台机器的算力基础设施不是固定的,那么每次拿到的输出怎么可能一样?
顺着这个思路,一系列新问题涌现出来:如果生产环境里跑着的一个LLM应用,需要扩展到拥有不同GPU的实例上,会不会出现严重问题?开发环境的GPU和生产环境差别很大,又会怎么样?
这些疑问驱动我设计了一个实验,专门来凸显这个现象,并看看影响到底有多大。
02 配置实验环境
为了把问题说清楚,我搭建了两个完全一样的开发环境——唯一的区别是GPU:一个用Nvidia Tesla T4,另一个用Nvidia A10G。然后拿Mistral-7b-v0.1来测试。
如果你想在notebook里复现实验,可以按下面的步骤操作。
2.1 配置开发环境
1. 配置CUDA版本
2. 配置transformers和其他依赖
3. 设置随机种子
注释1: 只设置transformers.set_seed应该就够了,但为了确保万无一失,我多做了几步。
注释2: 这里用的是Python 3.10。
2.2 加载Mistral模型
要从Hugging Face加载Mistral-7B-v0.1,需要在环境变量HF_TOKEN里设置好Hugging Face的token。本文使用量化版本来降低计算精度、减少GPU内存占用。
2.3 使用transformers库的pipeline
我们用transformers的pipeline来简化生成过程。把top_k设为1,或者把temperature设到接近0,这样模型每次都会从词汇表中选出最有可能的token,输出变得可预测且一致。
为了简单起见,把max_new_tokens设为1,这样模型只用单个token就能完成提示词。
比如输入“I enjoy walking in the”,模型应该只输出“woods”。如果这个基本测试通过了,就可以继续往下了。
03 实验结果:T4 vs A10G
为了用上这两块GPU,我通过AWS SageMaker启动了ml.g4dn.xlarge(T4)和ml.g5.xlarge(A10G)实例。
先试试一个简单的查询:
T4和A10G给我的输出一样,一切顺利。不过这只是个短查询。在RAG场景里,我们通常要处理成千上万个token。于是我用Hugging Face上的llama-2-arxiv-papers-chunked数据集做了更大规模的测试。
代码示例里,我模仿RAG的工作方式,从数据集中取了索引0、4518、4519和799处的文本片段。其中4518和4519讨论的是“Llama 2”,其他的则没有。模型需要基于这些上下文回答“Llama 2有什么特别之处?”这个提示词大约有1400个token。
T4模型的输出:

A10G模型的输出:

有意思吧。乍一看,两者开头差不多,差别不大。但看到“等等(etc)……”后面就开始分叉了。
T4输出:“etc… This also means you can trust the output more since everything inside will be consistent across different runs!…”
A10G输出:“etc… This also means you can be more confident when asking questions specifically related to topics covered within those texts…”
04 T4 Colab vs T4 SageMaker
如果两台机器用相同的GPU,输出会不会一样?我测试了一下,结果完全相同。
05 为什么同样的输入和LLM,在不同GPU上输出差异这么大?
归根结底,这源于大语言模型的自回归特性。下一个token是根据前面所有token来选的,任何微小的变化都会像蝴蝶效应一样,引发一连串连锁反应。
注意,这些输出并不完全符合提示词里要求的上下文。模型没有严格遵循指令,但这不太重要,因为我们假设模型总是选概率最高的token,所以关键就在于:这个概率在GPU上是怎么算出来的。
06 计算token的选择概率
为了打印出每个被选中token的概率,我们绕过常规pipeline,直接用tokenizer和model.generate方法,并设置return_dict_in_generate=True和output_scores=True。然后计算、归一化,把transition scores转为概率。
T4和A10G上的概率不完全一致。一般情况下这不影响token的排序序列,但有时确实会引发变化。
比如,T4上“trust”出现的概率是18.74%,而在A10G上,“be”的概率更高,达到18.62%。从这一点开始,由于自回归特性,生成的内容就开始分叉了。
注释:量化大语言模型会降低计算精度,更容易放大这种差异。
一个很自然的问题就出现了:“为什么不同GPU的计算结果会不同?”
07 为什么GPU不同,计算结果也不同?
我不是CUDA专家,但做了些研究。差异可以归因于几个因素:
并行计算处理:
这一点很重要,因为在编程中,即使数值差别很大的简单加法也可能不满足结合律:(a + b) + c ≠ a + (b + c)。计算任务被分割、独立处理,然后以非结合的方式组合起来。这些部分如何重组合,直接影响到最终结果。
对于大语言模型,数百万次计算中的微小误差可能会反复累积,最终影响到序列生成过程中的字词选择。
硬件架构:
模型量化的影响:
08 使用多GPU扩展LLM时需要注意什么?
这是个好问题。
如果只是增加相同型号的GPU(比如从单个A10G扩展到4个),还有必要担心吗?
多GPU推理有几种策略:
- 如果模型能单卡放下,可以在每块GPU上加载一份副本。每个查询由不同GPU处理,输出和单卡一样,但吞吐量提高。
- 如果模型太大、单卡装不下,可以采用模型分片策略。理论上,因为计算分布和执行路径不同,可能会导致输出变化。但在实际测试中,分片得到的概率和序列与单卡是一致的。我猜这是PyTorch在设计时考虑了确定性操作。
结论
实验很清楚:即使开发环境、系统配置和随机种子完全一致,不同GPU也会让大语言模型给出不同的输出。随着提示词长度增长,这种不准确性会被放大——更长的提示词需要更多计算,加剧了误差的传播。在模型量化的场景下,这个效应会更明显。
这并不意味着一定是灾难性的,但确实是部署大语言模型时必须注意的一个因素。如果开发环境用的GPU和生产环境不同,一定要设置测试来确保性能在可接受范围内。如果要扩展到拥有不同GPU的新实例,这一点尤其重要。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名