相同的 LLM 在「不同 GPU 上」会产生不同输出?为什么?

news2024/9/21 19:08:33

编者按: 在大语言模型(LLMs)的部署及其相关的算力扩容过程中,更换 GPU 是否也可能会对模型的输出产生重大影响?这个问题的答案对于确保 LLMs 在不同硬件环境下的一致性和可靠性至关重要。

我们今天为大家带来的这篇文章,作者的核心观点是:即使在相同的开发环境、系统配置和随机种子下,不同的 GPU 也会导致 LLMs 产生不同的模型输出。

作者通过实验证明,在使用 Nvidia Tesla T4 和 Nvidia A10G 两种不同 GPU 的情况下,Mistral-7b-v0.1 模型对相同的输入产生了不同的输出。这种差异主要源于 GPU 在并行计算处理、硬件架构和模型量化的影响等方面的不同。随着提示词长度的增加,这种不准确性会被放大,因为更长的提示词需要进行更多计算,从而加剧了不准确性的传播。在使用多个 GPU 扩展时,如果采用模型分片策略,理论上可能会因计算分布的不同而导致结果产生变化,但实践中 PyTorch 的设计似乎保证了结果的一致性。

作者 | Anis Zakari

编译 | 岳扬

大多数技术工程师都了解,依赖库或依赖组件的版本不同都可能会导致系统行为产生变化。但在大语言模型(Large Language Models)领域,由于算力需求巨大,在训练和推理任务中我们都极度依赖 GPU。然而,很少有人真正意识到,更换 GPU 也会对 LLMs 的输出产生影响。

假如你想创建两个完全一致的开发环境:

  • 可以指定依赖库或组件的版本。
  • 可以使用 Dockerization。
  • 可以将 LLMs 的 temperature 设置为 0。
  • 可以选择任意的随机种子。 但是,如果使用的不是完全相同的 GPU 型号,以上所有努力都将白费。

本文将进行一次实验来强调这一现象,说明差异出现的位置及其原因。

Note:如果对实验过程的重现或具体代码不感兴趣,可以跳过本文展示代码片段,直接阅读“7. 为什么同样的 inputs 和同样的 LLMs 在两块不同 GPU 上生成的模型响应会有如此大的差别?”这部分内容。即便不看前面的代码片段,Conclusion 部分仍然有助于我们理解其中的原理。

01 为什么要写这篇文章?

有一天,我和一些人讨论为什么 OpenAI 和 Anthropic 的那些模型在设计时没有被构建为确定性的系统。我解释说,它们可能采用了混合专家模型(Mixture of Experts, MoE)方法[1],偶尔不会将 tokens 路由给最优的专家模型,因为这些专家模型可能正忙于处理其他 tokens,所以可能会导致模型响应的不一致。

另一个因素可能是 OpenAI 为了提高效率而对 queries 进行了批量处理。batches size 会根据传入的 queries 数量而变化,可能会改变 GPU 的计算策略,从而导致不同的模型响应。

当有人指出,“不同的 GPU 也可能导致出现不同的模型响应,不是吗?”时,我们之间的对话开始变得耐人寻味起来了。

仔细想一想……当我们使用 OpenAI API 时,实际上是有一台远程服务器帮我们执行计算并返回模型响应。现在,如果这台机器并非总是在相同的算力基础设施上运行,那么最终得到的模型响应就不会相同。

想到这一点,可能就会出现其他问题:

  • 如果有一个在生产开发环境中运行的 LLM app,并且需要将其扩展到拥有不同 GPU 的其他实例,是否会出现很严重的问题?
  • 如果开发环境(development environment)中的 GPU 与生产环境(production environment)存在大量不同之处,会怎么样?

这些问题促使我想设置一个实验来突出这一现象,并探究它可能造成的影响有多大。

02 配置实验环境

为了突出这一现象,我将设置两个完全相同的开发环境,它们唯一的区别在于其所使用的 GPU:第一个开发环境中使用的是 Nvidia Tesla T4,第二个开发环境使用的便是 Nvidia A10G。然后,我们将使用 Mistral-7b-v0.1 进行测试,看看会发生什么。

要在 notebook 中运行实验,请按照以下步骤操作。

2.1 配置开发环境(Setup the environment)

1. 配置 CUDA 版本

2. 配置 transformers 和其他依赖

3. 设置随机种子(random seeds)

注释 1:

仅设置 transformers.set_seed 应该就足够了,但我还是想要确保万无一失。

注释 2:

本例使用的是 Python 3.10。

2.2 加载 Mistral 模型

要从 Hugging Face 中加载 Mistral-7B-v0.1 模型,我们需要在环境变量 HF_TOKEN 中设置 Hugging Face tokens。

本文将会使用量化版本的模型,降低计算精度来减少 GPU 的内存占用。

2.3 使用 transformers 库中的 pipeline

我们将使用 transformers 库中的 pipeline 来简化从大语言模型(LLMs)生成模型响应的过程。

为了确保模型输出是可预测和一致的,我们希望从大语言模型的 vocabulary 中持续预测出最有可能的 tokens,因此我们可以将 top_k 设置为 1 或将 temperature 设置为接近 0 的值。

此外,为了简单起见,我们将把 max_new_tokens 参数设置为 1,这样 LLMs 就能只用单个 token 完成提示词。

当给出提示词序列 “I enjoy walking in the” 时,大语言模型(LLMs)只会生成一个单词:“woods”。如果大语言模型(LLMs)正确地生成并输出了这个单词,我们就可以继续进行实验了。

03 实验结果:T4 vs A10G

为了能够使用这两块 GPU,我通过 AWS SageMaker 启动了 ml.g4dn.xlarge (T4) 和 ml.g5.xlarge (A10G) 实例。

让我们尝试运行一个简单的 query :

T4 和 A10G 给我的模型响应是一样的:

到目前为止一切进展顺利。不过,这只是一个简短的 query 。在 RAG(检索增强生成)的应用场景里,我们通常会处理成千上万个 tokens 。现在让我们使用在 Hugging Face 上托管的 llama-2-arxiv-papers-chunked 数据集来进行更大规模的 query 测试。

在下面的代码示例中,我将模仿 RAG 的工作方式,使用数据集索引 0、4518、4519 和 799 处获取的文本片段。其中第 4518 和 4519 个数据块(chunks)讨论了 “Llama 2”,而其他片段则没有提及。我们期待 LLMs 能基于这些上下文信息回答:“Llama 2 有什么特别之处?”该提示词大概有 1,400 个 tokens 长。

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 为什么相同的用户输入(inputs)和相同的 LLMs 在两个 GPUs 上生成的答案会如此不同?

最终,这些模型响应因为 LLMs 的自回归特性而变得截然不同。由于下一个 token 是根据之前的 tokens 选择的,任何细微的变化都会引发一连串的连锁反应,就像蝴蝶效应(butterfly effect)一样。

请注意,这些模型响应并没有像提示词中所要求的那样基于所提供的上下文。LLMs 并没有完全遵循指导性提示词(instructions),但这并不是很重要。

因为我们假设 LLMs 总是基于前面的 tokens 选择概率(probabilities)最高的 token,所以我们可以肯定,区别在于如何在 GPU 上计算该概率(probabilities),下面让我们来看一看如何计算该概率~

06 计算 tokens 的选择概率(probabilities)

为了打印出每个被选中 token 的概率,我们将绕过常规处理流程(pipeline),直接使用 tokenizer 和 model.generate 方法。这样我们就能设置 return_dict_in_generate=Trueoutput_scores=True。接着,我们就可以进行计算(compute)操作、对其进行归一化操作(normalize),并将 transition scores(译者注:在自然语言处理领域,尤其是使用自回归模型生成文本时,模型会为每个 next token 分配一个概率分数,这个分数反映了该 token 作为 tokens 序列中 next token 的可能性大小。) 转换为概率(probabilities)。

上述代码会显示每个 token 的 ID、解码后的 token 以及其对应的概率(probability)。此处我只列出相关的模型输出内容,因为完整的内容非常长。

T4 Output:

A10G Output:

好了,现在事情变得越来越有趣了。T4 和 A10G 上的概率值(probabilities)并不完全一致。一般情况下,这样并不会影响 tokens 的排序序列(无法在生成的 tokens 序列中察觉到任何不同),但有时候确实会造成影响。

例如,在 T4 模型中,“trust” 出现的概率为 18.74 %,而在 A10G 上,“be” 出现的概率则更高,达到了 18.62 %。从这一点来看,由于大语言模型的自回归特性,生成的内容将会出现偏差(diverge)。

注释:量化大语言模型会降低计算精度(calculation precision),导致这类差异变得更为常见。

现在,一个非常合理的问题就出现了:“为什么计算结果会因为 GPU 的不同而产生差异呢?”

07 为什么 GPU 不同,模型运算结果也不同?

虽然我不是 CUDA expert(译者注:这类人能够熟练使用 CUDA C/C++ 编程语言来开发高性能的并行计算应用,并了解如何优化 GPU 上的计算任务来获得最佳性能。),但我进行过一些研究。不同 GPU 之间的计算差异可以归因于以下几个因素:

并行计算处理(Parallel Computation Handling):

GPUs 的特点是能够高效地并行处理大量的计算任务。然而,不同 GPU 在管理这些并行任务时可能会有所差异,从而影响到运算顺序以及内存的访问方式。

这一点非常重要,因为在编程过程中,即使是数值大小相差很大的简单加法也可能是非关联的(non-associative),从而影响到精确计算(precise calculations)的准确性。所谓 “Non-associativity” 是指:(a + b) + c ≠ a + (b + c)。

Non associativity

因此,计算任务会被分割开来,独立进行处理,然后以非关联性的(non-associative)方式组合在一起。因此,这些部分的内容如何重新组合会影响到最终结果。

这里有一个关于非关联性计算(non-associative computation)的简单示例:

对于大语言模型(LLMs),数百万次的计算可能会因为重复出现的微小误差而导致出现偏差(diverge),进而影响到序列生成过程中的字词选择。

硬件架构(Hardware Architecture):

不同型号的 GPU,如 Nvidia Tesla T4 和 Nvidia A10G ,具备不同的硬件架构。这些硬件架构能够优化模型各个方面的性能,包括并行处理能力(parallel processing capabilities)、内存带宽(memory bandwidth)和计算单元(compute units)。

例如,T4 模型采用了 Turing[2] 架构,而 A10G 模型基于 Ampere[3] 架构。

不同的模型架构意味着在浮点运算(floating-point arithmetic)、内存访问模式(memory access patterns)和其他底层操作上有着不同的实现方式。即使这些实现方式(implementations)存在细微差别,也可能会导致计算结果出现差异。

例如,与针对更高计算精度而进行优化的模型架构相比,为了计算速度而优化的模型架构可能会产生不同的模型响应,即便两者都在执行相同的浮点运算。

模型量化的影响(Quantization Effects):

通过模型量化(Quantizing)来降低计算精度可以节省内存资源和计算资源,但这样做也会引入额外的误差源(sources of error)。这些误差的影响因 GPU 对低精度运算(lower precision arithmetic)的处理方式不同而不同。

由于模型量化(quantization)过程中涉及到对数值的近似处理,因此不同的 GPU 在处理这些近似值时可能会有所差异,从而最终会导致 token 的预测概率产生变化。

08 使用多个 GPU 水平扩展 LLMs 时需要注意什么?

这个问题问得非常好,非常感谢!: )

如果只是简单地增加相同型号的 GPU 数量(例如,从单个 A10G GPU 扩展到拥有 4 个 A10G GPU 的实例),是否还有必要担心?

使用多个 GPU 进行推理时,有几种策略可供选择:

  • 第一种策略是,如果模型可以装入 GPU 中,可以在每个 GPU 上加载一份模型副本。例如,如果向 pipeline 发送四条查询语句(queries),每条查询语句(queries)可以由不同的 GPU 来处理。这样,我们将得到与仅使用一个 GPU 时相同的输出内容,但吞吐量会有所提高。
  • 第二种策略通常用于因为模型太大一个 GPU 无法装入的情况,可以采用模型分片策略(sharding),将模型的权重分布到各个 GPU 上。虽然从理论上讲,这种做法可能会因为计算(computation)的分布(distribution)和执行(execution)的不同而导致模型响应产生变化,但在实践测试中,使用模型切片技术得到的序列(sequences)和概率(probabilities)与单个 GPU 上得到的结果是一致的。我猜测这是因为 PyTorch 在设计时考虑到了 deterministic operations(译者注:那些每次在相同输入下都能产生相同输出的操作过程。)。

09 Conclusion

我们已经证明了,即便是相同的开发环境(environment)、系统配置(settings)和随机种子(seed),不同的 GPU 也会导致 LLMs 产生不同的结果。随着提示词长度的增长,这种不准确性(inaccuracies)也会随之增加,因为更长的提示词需要更多的算力,这会加剧不准确性(inaccuracies)的传播并促进两个 GPU 之间的差异。此外,在进行模型量化的情况下,这种效应更加显著。

我并不是说这种情况一定是灾难性的,但这是我们在处理 LLMs 的部署时需要注意的一个因素。

如果我们开发时使用的 GPU 与生产环境中使用的 GPU 不同,应该设置测试实验确保性能仍然保持在可接受的范围内。如果我们计划将 LLMs 扩展到拥有不同 GPU 的新实例上,这一点也很重要。

如果你坚持读到了最后,那我可太高兴了,希望你会喜欢这篇文章。如果你喜欢的话,希望你能给我点赞,鼓励我继续写作,也欢迎在评论区中分享你的想法。


Anis Zakari

I’m a passionate ML/AI Engineer based in Paris. I am particularly interested in NLP, LLM, Software Engineering and Cloud Engineering subjects

文中链接

[1]https://standardscaler.com/2024/03/06/the-non-determinism-of-openai-and-anthropic-models/

[2]https://www.nvidia.com/fr-fr/geforce/turing/

[3]https://www.nvidia.com/fr-fr/data-center/ampere-architecture/

原文链接:

https://medium.com/@cpdough/building-ai-agents-lessons-learned-over-the-past-year-41dc4725d8e5

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1994426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用docker部署图形化工具 portainer

docker查找图形化工具 Portainer 拉取镜像 docker pull portainer/portainer启动docker UI容器 docker run -d -p 9209:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v /opt/data3/mydocker/portainer_data:/data portainer/porta…

MFC多个控件组合存在显示不出来现象

MFC多个控件组合存在显示不出来现象 1、找到rc文件 , 右键查看代码 2、 3、将基础组件放在最前面即可

C++(week16): C++提高:(六) Qt基础

文章目录 零、课前须知一、Qt基础1.CLI与GUI2.事件驱动模型3.Qt快捷键 二、QtCreator1.Qt的安装:Qt框架、IDE2.Qt的六大模式3.核心模块4.布局5.Qt项目中的文件6.信号与槽机制7.添加资源:资源文件qrc8.main.cpp解析(1)ui文件 和 纯代码(2)按钮 信号槽机制…

在嵌入式Linux平台上使用Nginx搭建RTMP流媒体服务器

概述 Nginx是一个以高效稳定著称的高性能的HTTP和反向代理web服务器,它同时也是基于事件驱动开发的异步高性能跨平台服务器。Nginx-RTMP是基于Nginx框架的模块开发,很好地继承了Nginx的异步高性能以及扩展性好的优点。 RTMP 是 Real Time Messaging Pr…

docker 部署 ElasticSearch;Kibana

ELasticSearch 创建网络 docker network create es-netES配合Kibana使用时需要组网,使两者运行在同一个网络下 命令 docker run -d \ --name es \ -e "discovery.typesingle-node" \ -v /usr/local/es/data:/usr/share/elasticsearch/data \ -v /usr/…

C语言——编译与链接

目录 引言 翻译环境与运行环境 翻译环境 1.翻译环境的简述 2.编译过程 2.1 预处理(预编译) 2.2 编译 2.2.1 词法分析 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 3.链接 运行环境 结束语 引言 C语言编译与链接过程是理解程序如何从代码转化…

8月5日学习笔记 glibc安装与安全用户角色权限

一,glibc安装 https://www.mysql.com/ 官⽹ https://downloads.mysql.com/archives/community/ https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.33-li nux-glibc2.12-x86_64.tar 安装步骤 1.安装依赖库 [rootlocalhost ~]# yum list installed |g…

在vscode中使用ssh运行docker:从下载到运行全流程

首先在本机或者服务器上下载docker并运行 本文目的旨在本机下载docker并打包,然后在服务器上进行加载 docker -v Docker version 27.0.3, build 7d4bcd8有输出说明在运行 一、下载 在docker hub上下载docker以tensorflow为例 点击tag搜索自己想要的版本 copy命…

All-Reduce通信原语;Reduce+LayerNorm+Broadcast算子;gRPC:远程过程调用(RPC)框架;

目录 All-Reduce通信原语 定义与作用 实例说明 示例图解(以Ring算法为例) 结论 Reduce+LayerNorm+Broadcast算子 1. Reduce算子 2. LayerNorm算子 3. Broadcast算子 组合使用场景 gRPC:远程过程调用(RPC)框架 All-Reduce通信原语 是计算机科学中,特别是在分布式…

解锁成都跃享未来教育咨询抖音小店

在数字化浪潮汹涌的今天,教育行业的变革与创新层出不穷,其中,成都跃享未来教育咨询以其敏锐的洞察力和前瞻性的教育理念,在抖音平台上开设的小店,正悄然改变着人们的学习方式和教育资源的获取途径。本文将深入探讨成都…

骰子游戏的UML分析

一、需求分析 游戏者掷两个骰子,如果总点数是7则赢得游戏,否则为输 二、概要设计 2.1 设计用例 用例是基于某个场景(注:包括成功和失败场景,重要体现需求的边界)说明了用户如何通过系统实现实现其目标。 示例:玩游戏场景用例 用例名称:玩游戏 主要参与者: 游戏用户 前…

实时数据监控,三防平板在工业领域的应用解析

随着工业4.0时代的到来,数字化转型已成为各行各业的共同目标。在这一过程中,实时数据监控扮演着至关重要的角色,为企业提供数据驱动的决策支持,提升效率、降低成本、提高安全性。而作为移动终端设备,三防平板凭借其可靠…

深兰科技荣获2024年度金势奖“AI出海先锋品牌”金奖

近日,由金势奖组委会、凤凰网、营销国际协会等国内外知名机构、集团共同主办的“第四届未来营销大会暨锐品牌盛典”在上海举行。大会揭晓了第四届“金势奖锐品牌大赏”奖项的评选结果,深兰科技凭借自身在机器人产品出口和海外市场开拓等出海全球化发展方…

2-59 基于matlab的全离散法单自由度稳定极限切深叶瓣图绘制、两自由度稳定极限切深叶瓣图绘制

基于matlab的全离散法单自由度稳定极限切深叶瓣图绘制、两自由度稳定极限切深叶瓣图绘制,特定切削力系数进行数值积分。输出相应的叶瓣图。程序已调通,可直接运行。 2-59 两自由度稳定极限切深叶瓣图 - 小红书 (xiaohongshu.com)

【计算机组成原理】3.程序的执行

程序的执行 进程与线程 一个程序,读入内存,全是0和1构成 从内存读入到CPU计算,这个时候要通过总线 怎么区分一段01的数据到底是数据还是指令? 总线分类为三种:控制线 地址线 数据线 一个程序的执行,首…

美团创始人的亲授产品课

2020年王慧文受邀在清华大学演讲了个人的非产品公开课,课程内容以美团早期的实战经验结合规模效益、马太理论等诸多知名理论为主。 前两天重新翻阅的时候,还是有很多新的感悟,所以也借此机会把课程内容和大家分享一下。 规模效益 一个业务有…

视频压缩文件太大了怎么缩小?这6个视频压缩方法真有效

视频压缩文件太大了怎么缩小?视频文件太大不仅会占据磁盘空间,而且会影响分享传输,因此太大的视频文件我们可以通过压缩缩小来减少体积,那么要怎么压缩视频文件大小呢?在这里小编要分享亲测有用的6个视频压缩方法&…

EF8 学习过程中的问题和解决方案

一、varchar类型字段如果为null 无法使用contains来判断是否包含字符串 1. 有问题的代码: contractList _dbcontext.contractHeads.Where(u > u.code.Contains(queryStr) || u.name.Contains(queryStr) || u.companyName.Contains(queryStr) || u.customerNa…

Linux 内核源码分析---挂载文件系统

挂载描述符 Linux 操作系统的一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能够访问这个文件系统。 每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符(mount 结构体)。 挂载描述符用来描述文件系统的…

CSS 多按钮根据半圆弧度排列

需求 多个按钮根据弧度&#xff0c;延边均匀排列。 实现 HTML 分两级&#xff1b;第一级&#xff0c;外层定义按钮的 compose-container 宽度&#xff1b;第二级&#xff0c;按钮集合&#xff0c;使用方法 styleBtn(index)&#xff0c;根据索引计算&#xff1b; <div c…