震惊!更换GPU会改变LLM的行为

news2024/11/15 11:18:41

文章目录

    • 新发现
    • 前言
    • 1. Why this article ?
    • 2. Setup the experimentation
    • 3. The experiment results:A100/A10/3090
    • 4. Why is it different?
    • 5. Why do the calculation differ depending on the GPU ?
    • 结论

新发现

  最近在做RAG相关的工作,偶然间发现,生产环境(A100)与测试环境(A10)大模型推理的结果不太一致,后来做了进一步实验发现,相同的数据,相同的推理代码,相同的模型以及相同的软件版本环境,不同的GPU下,推理结果不完全一致。具体如下:
  A100

在这里插入图片描述

  A10

在这里插入图片描述

  3090

在这里插入图片描述

  然后在Medium上发现了一篇博客,博主也分享了他的发现,与我的情况一模一样,在此做个记录。

前言

  大多数技术人员都知道,依赖项的不同版本可能会导致行为不同。然而,在大型语言模型领域,由于我们需要大量的计算资源,因此在训练和推理任务中我们严重依赖于GPU。然而,很少有人真正意识到更换GPU也会影响LLM的输出。
  当然,你可以创造两个完全相同的环境,可以设置依赖项版本,可以使用Dockerization,可以将LLM的温度系数设置为0,也可以设定任何你想要的seed。归根结底,除非你没有使用完全相同的GPU模型,否则这些都不会起作用。
  在这篇文章中,我将通过一个实验来强调这一现象,该实验显示了差异发生的位置和原因。

1. Why this article ?

  有一天,作者和一些人讨论为什么OpenAIAnthropic模型在设计上不是确定性的。作者解释说,他们可能会使用混合专家(MoE, Mixture of Experts)方法,偶尔不会将token路由给最优专家,因为这些专家忙于处理其他token,这会导致答案不一致。
  另一个因素可能是OpenAI的批量查询效率。这些批处理的大小可以根据传入查询的数量而变化,这可以改变GPU计算策略,从而导致不同的结果。
  但也有人指出:“不同的GPU也可能导致不同的结果,不是吗?”
  当你使用OpenAI API时,在某个地方有一台远程机器代表你运行计算并返回结果。现在,如果机器并不总是运行在相同的硬件上,那么最终得到的模型响应可能就不会相同。
  考虑到这一点,可能就会出现其他问题:
  (1)如果我在生产环境中有一个LLM应用程序,并且我需要扩展到具有不同GPU的其他生产环境上,这是否会出现很严重的问题?
  (2)如果开发环境中的GPU与生产环境中的GPU不同,会怎么样?
  基于这些问题,作者做了一个实验,看看它的影响有多大。

  Note: 以下是博主的真实实验。

2. Setup the experimentation

  主要环境库版本及其它参数:

# 依赖库
python        3.10.12
cuda          12.1
torch		  2.0.1
transformers  4.40.2
accelerate    0.26.1

# GPU型号
NVIDIA A100 40GB
NVIDIA A10  24GB
NVIDIA GeForce RTX 3090 24GB

# LLM
InternLM2.5-7B-Chat

# pip镜像
-i https://pypi.tuna.tsinghua.edu.cn/simple

  与作者的一致,这里也统一设置随机数:

# Set seeds for reproducibility
random_seed = 42
np_seed = 42
torch_seed = 42
transformers_seed = 42

random.seed(random_seed)
np.random.seed(np_seed)
torch.manual_seed(torch_seed)
set_seed(transformers_seed)

3. The experiment results:A100/A10/3090

  模型使用的是书生·浦语InternLM2.5-7B-Chat,测试代码如下:

# -*- coding: utf-8 -*-
# Author  : liyanpeng
# Email   : yanpeng.li@cumt.edu.cn
# Datetime: 2024/8/31 13:08
# Filename: llm_prob_test.py
import os
import random
import numpy as np
import torch
from transformers import set_seed
from transformers import AutoTokenizer, AutoModelForCausalLM
from prettytable import PrettyTable

# Set seeds for reproducibility
random_seed = 42
np_seed = 42
torch_seed = 42
transformers_seed = 42

random.seed(random_seed)
np.random.seed(np_seed)
torch.manual_seed(torch_seed)
set_seed(transformers_seed)

os.environ['CUDA_VISIBLE_DEVICES'] = '0'
device = "cuda"
model_path = '/data/liyanpeng/huggingface/internlm/internlm2-chat-7b'

tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", trust_remote_code=True,
                                             torch_dtype=torch.float16)
model = model.eval()

query = '你好'
prompt = """<|im_start|>user\n{query}<|im_end|>\n<|im_start|>assistant\n"""
prompt = prompt.format(query=query)

response, _ = model.chat(tokenizer, query, do_sample=False, temperature=0,
                         history=[], meta_instruction='')
print(response)

  我们看下不同型号GPU下,模型的输出:

# A100
你好!很高兴为你服务。有什么我可以帮助你的吗?

# A10
你好!很高兴为你服务。有什么我可以帮助你的吗?

# 3090
你好!很高兴为你服务。有什么我可以帮助你的吗?

  嗯哼,看着没啥问题,模型在三个不同型号的输出是一致的。现在将输入字符长度增加一些,再看看模型的输出,具体如下:

query = '2012年Hinton团队提出了AlexNet,它是首个充分利用GPU的并行能力训练出的卷积神经网络,并在ImageNet比赛一举夺魁,获得了85%的准确率,其准确分辨相似图像的能力惊艳了世界。如今,大部分的神经网络训练都离不开了GPU,对此,你有什么看法?(请注意,回复的内容长度不要超过300字,观点总结在一起,不要分开表述)'

  由于输出的文本很长,为了方便对比,直接放在了表格中:

在这里插入图片描述
  可以看到,相同的模型,相同的输入,在不同的硬件上,输出结果是有差异的。

4. Why is it different?

  为什么相同的输入和相同的LLM在不同的GPU上生成的内容是不同的?
  很容易回答,由于LLM的自回归性质,下一个token是基于前一个token选择的,任何微小的变化都会引起级联反应,从而导致蝴蝶效应,最终生成的内容是不同的。
  而token的选择是基于概率来选择的,比如贪婪采样,在每一步,模型会选择概率最高的token作为下一个输出。
  为了让模型输出时可以看到每个token的概率,不在使用官方的chat函数,对代码做了更改,增加了return_dict_in_generate参数和output_scores参数,具体如下:

inputs = tokenizer([prompt], return_tensors="pt").to(device)
eos_token_id = [tokenizer.eos_token_id, tokenizer.convert_tokens_to_ids(["<|im_end|>"])[0]]
outputs = model.generate(**inputs, max_new_tokens=512, do_sample=False, temperature=0,
                         eos_token_id=eos_token_id, return_dict_in_generate=True, output_scores=True)
                         
input_length = inputs.input_ids.shape[1]
generated_tokens = outputs.sequences[:, input_length:]
output_ids = generated_tokens[0].cpu().tolist()
response = tokenizer.decode(output_ids, skip_special_tokens=True)
response = response.split("<|im_end|>")[0]
print(response)

transition_scores = model.compute_transition_scores(outputs.sequences, outputs.scores, normalize_logits=True)
table = PrettyTable(["token id", "token str", "probability"])
for tok, score in zip(generated_tokens[0], transition_scores[0]):
    table.add_row([f'{tok:d}', tokenizer.decode(tok), f'{np.exp(score.cpu().numpy()):.2%}'])
print(table)

  以A100A10的对比结果为例,这里截取了部分内容,可以看下每个token的输出概率:

# A100
+----------+-------------+-------------+
| token id |  token str  | probability |
+----------+-------------+-------------+
|   ...    |     ...     |     ...     |
|  70939   |     推动    |    52.30%   |
|  60362   ||    99.34%   |
|  49145   |     GPU     |    75.64%   |
|  71071   |     硬件    |    27.03%   | <-- here
|  60381   ||    35.73%   |
|  68367   |     软件    |    64.54%   |
|  74657   |    技术的   |    69.70%   |
|  68705   |     不断    |    50.39%   |
|  70212   |     进步    |    54.20%   |
|  60355   ||    96.46%   |
|   ...    |     ...     |     ...     |
+----------+-------------+-------------+

# A10
+----------+-------------+-------------+
| token id |  token str  | probability |
+----------+-------------+-------------+
|   ...    |     ...     |     ...     |
|  70939   |     推动    |    51.97%   |
|  60362   ||    99.35%   |
|  49145   |     GPU     |    75.36%   |
|  75075   |     架构    |    31.85%   | <-- here
|  60354   ||    44.99%   |
|  68705   |     不断    |    36.70%   |
|  70386   |     优化    |    84.05%   |
|  60353   ||    95.13%   |
|  60367   ||    42.60%   |
|  70188   |     适应    |    70.76%   |
|  70907   |     深度    |    25.57%   |
|  68352   |     学习    |    81.61%   |
|  71370   |    的需求   |    75.34%   |
|  60355   ||    99.96%   |
|   ...    |     ...     |     ...     |
+----------+-------------+-------------+

  可以看到,A100A10输出的概率并不完全相同。通常情况下,这不会影响token的顺序,但在某些情况下有影响。例如,在A100"硬件"的概率为27.03%,而在A10上的"架构"的概率为31.85%,由于大模型自回归的性质,从这个token开始,输出的内容就不太一样了。

5. Why do the calculation differ depending on the GPU ?

  为什么不同GPU的计算会有所不同?
  GPU之间的不同计算可以归因于以下几个因素:
  (1)并行计算处理
  GPU都是关于高效并行处理大量计算的。但是,不同的GPU在管理并行任务时可能会有所不同,从而影响操作顺序和内存访问。这很重要,因为在编程中,数量级相差很大的数字即使是简单相加也可能是非结合的(non-associative),从而导致精确计算中的潜在不准确性。
在这里插入图片描述

  non-associative,通俗的理解为不满足结合律。

  比如下面的这个例子:

import torch

# Define three floating-point numbers in bfloat16 with a large difference in magnitude
a = torch.tensor(1e10, dtype=torch.bfloat16)
b = torch.tensor(-1e10, dtype=torch.bfloat16)
c = torch.tensor(1.0, dtype=torch.bfloat16)

# Calculate the sums in different orders
sum1 = (a + b) + c
sum2 = a + (b + c)
# Print the results in bfloat16
print(f"(a + b) + c in bfloat16: {sum1}")
# >>> 1.0
print(f"a + (b + c) in bfloat16: {sum2}")
# >>> 0.0

  对于LLM来说,数百万次的计算可能会导致由于小的重复不准确而产生偏差,这会影响序列生成过程中的词汇选择。
  (2)硬件架构
  不同的GPU硬件,如NVIDIA Tesla T4NVIDIA A10,具有不同的硬件架构。这些架构旨在优化性能的各个方面,包括并行处理能力、内存带宽和计算单元。例如,T4使用的是图灵架构,而A10基于安培架构。不同的架构意味着浮点运算、内存访问模式和其他低级操作的不同实现方式。即使这些实现中存在微小的差异,也可能导致计算结果的变化。例如,针对更高精度优化的架构可能与针对速度优化的架构产生不同的结果,即使两者都执行相同的浮点运算。

  A100A103090都是Ampere架构。

  (3)量化影响
  量化后的模型可以减少其精度以节省内存和计算资源,但它也引入了额外的误差来源。这些误差的影响可能会根据GPU处理较低精度算术的方式而有所不同。由于量化涉及对数字的近似处理,不同的GPU可能会以不同的方式处理这些近似值,从而导致token预测概率的变化。

结论

  很有趣的发现,博主本人对CUDA的具体计算原理了解的不多,但让我想起了以前写算子时,对数据进行不同的切片计算效率不一,结果一致,所以对上述的发现更倾向于第一种,即不同硬件上计算时,由于LLM的层数很多,导致在传递时,某些数值不满足了结合律,从而出现了计算的偏差,然后偏差继续向前传播,导致结果差别越来越大。
  对这一现象,我觉得还需要再研究一下。

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

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

相关文章

swift自定义数据集微调Qwen-7B大模型,转换模型后使用ollama跑起来

前文&#xff1a;swift微调Qwen-7B大模型-CSDN博客 我详细介绍了swift如何进行微调&#xff0c;但数据集均来自魔搭社区&#xff0c;如何想训练自定义数据集&#xff0c;实际上也很简单。 一、自定义数据集微调 export MKL_THREADING_LAYERGNU \ CUDA_VISIBLE_DEVICES0,1,2…

STM32:TIM定时中断配置的最全库函数讲解笔记

声明&#xff1a;本博客为哔哩哔哩up主江协科技 “STM32入门教程”的听课笔记&#xff0c;仅供学习、参考使用&#xff0c;不得用作其他用途&#xff0c;违者必究。如有版权问题&#xff0c;请联系作者修改。 目录 一、综述 二、TIM库 初始化 2.1、TIM_DeInit 恢复缺省值 …

经典文献阅读之--ParkingE2E(基于摄像头的端到端停车网络:从图像到规划)

0. 简介 自动泊车是智能驾驶领域的一项关键任务。传统泊车算法通常采用基于规则的方案来实现。然而&#xff0c;由于算法设计的复杂性&#xff0c;这些方法在复杂的泊车场景中效果欠佳。相比之下&#xff0c;基于神经网络的方法往往比基于规则的方法更加直观且功能多样。通过收…

中国的铁路订票系统在世界上属于什么水平?

每到节假日&#xff0c;中国的铁路订票系统总会成为人们热议的焦点。无论是“抢票大战”还是“秒杀特价票”&#xff0c;这一系统似乎总是牵动着亿万乘客的心。那么&#xff0c;中国的铁路订票系统到底有多强大&#xff1f;在全球范围内&#xff0c;它处于什么水平&#xff1f;…

Java_ElasticSearch(ES)——分布式搜索引擎

介绍&#xff1a; Elasticsearch是一个开源的分布式搜索和分析引擎&#xff0c;最初由Elastic公司开发。它构建在Apache Lucene搜索引擎库之上&#xff0c;提供了一个强大的全文搜索和分析引擎&#xff0c; 它结合kibana、Logstash、Beats&#xff0c;是一整套技术栈&#xff0…

C语言——简单的do while循环找100~999之间的水仙花数(所有的三位水仙花数)

这道题的关键是如何把这个三位数的个位、十位、百位表示出来 这里用到了 / &#xff08;整除&#xff09;和 % &#xff08;取余&#xff09;这两个运算符 #include<stdio.h> int main() { int num 100; do { int a; int b; int …

手把手教你:用sentence-transformers库进行文本嵌入

在 Python 中使用 sentence-transformers 库进行实操&#xff0c;你可以按照以下步骤进行&#xff1a; 1. 安装 sentence-transformers 库 首先&#xff0c;确保你已经安装了 sentence-transformers。如果没有&#xff0c;可以通过 pip 安装&#xff1a; pip install sentenc…

图像去噪评论:从经典到最先进的方法

系列文章目录 文章目录 系列文章目录前言摘要1 引言1.1.噪声抑制 2. 空间域过滤2.1.局部滤波器2.2.非局部滤波器 3.变换域滤波3.1.阈值3.1.1. 通用阈值3.1.2. VISUShrink3.1.3.SURE收缩3.1.4.BayesShrink3.1.5.概率收缩3.1.6.SURELET3.1.7.Neigh Shrink Sure&#xff08;NSS&am…

十一头像红旗怎么弄的?3个方法轻松教会你!

国庆佳节渐行渐至&#xff0c;朋友圈里早已掀起了一股更换国庆主题头像的热潮&#xff01;那些五彩斑斓、光彩夺目的渐变国旗头像&#xff0c;既美观又富有节日气氛。如果你也想加入这个行列&#xff0c;那么如何动手制作呢&#xff1f;别担心&#xff0c;接下来我将为你介绍三…

求解组合优化问题的具有递归特征的无监督图神经网络

文章目录 ABSTRACT1 Introduction2 Related Work3 QRF-GNN方法4 数值实验4.1 MAX-CUTABSTRACT 介绍了一种名为QRF-GNN的新型算法,有效解决具有二次无约束二进制优化(QUBO)表述的组合问题。依赖无监督学习,从最小化的QUBO放松导出的损失函数。该架构的关键组成部分是中间GNN…

服务端之Node的QQ邮件发送功能、授权码申请流程、邮箱、createTransport、sendMail

MENU 前言邮箱授权码的申请流程Node连续发送邮件失败的原因 前言 1、代码段的主要功能是通过nodemailer模块从一个QQ邮箱发送电子邮件。 2、代码段实现从QQ邮箱1283077926qq.com发送一封邮件到2506562048qq.com&#xff0c;邮件主题为“微信公众号推广”&#xff0c;正文为“亲…

Docker私有镜像仓库Harbor安装并推拉镜像

1.环境信息 前置要求&#xff1a; linux&#xff1a;Oracle Linux Server release 7.9 docker&#xff1a;26.1.4 安装&#xff1a; docker-compose: Docker Compose version v2.29.2 harbor&#xff1a;v2.11.1 2.下载安装说明 docker-compose下载&#xff1a; https://githu…

Vue(八) localStorage、组件的自定义事件、Todo案例修改

文章目录 一、浏览器本地存储1. 相关API2. Todo案例中的应用 二、组件的自定义事件1. 回顾props传值方式2. 绑定自定义事件&#xff08;1&#xff09;方式一&#xff1a;v-on或&#xff08;2&#xff09;方式二&#xff1a; ref 3. 解绑自定义事件4. 注意点总结 三、Todo案例采…

【机器学习】在 scikit-learn 中,有哪些特征编码方法?分布详细举例列出

一、在scikit-learn中&#xff0c;有多种特征编码方法可以用来处理分类数据&#xff0c;以下是一些常见的编码方法及其示例&#xff1a; One-Hot Encoding (独热编码): 使用 OneHotEncoder 类将分类特征转换为二进制向量。例如&#xff0c;对于颜色特征 [red, blue, green]&…

day01项目概述、环境搭建

1 软件开发整体介绍 软件开发流程 角色分工 软件环境 2 苍穹外卖项目介绍 项目介绍 产品原型 技术选型 3 开发环境搭建 前端环境搭建 注意&#xff1a; - Nginx目录必须放在没有中文的目录中才能正常运行 - 当前Nginx的配置文件中已经配置了反向代理&#xff0c;通过此配置…

面试SQL题的水到底有多深?一文带你揭晓

不谋万世者&#xff0c;不足谋一时&#xff1b;不谋全局者&#xff0c;不足谋一域 目录 0 面试现状 1 面试SQL题目的难度及特点 1.1 题目场景化 1.2 题目算法化 1.3 方法多元化 2 破局之道 3 总结 数字化建设通关指南 主要内容&#xff1a; &#xff08;1&#xff09;SQL进阶实…

ChatTTS 长音频合成和本地部署2种方式,让你的“儿童绘本”发声的实战教程(文末有福利)

接上文&#xff08;GLM-4-Flash 大模型 API 免费了&#xff0c;手把手构建“儿童绘本”应用实战&#xff08;附源码&#xff09;&#xff09;&#xff0c;老牛同学通过 GLM-4-Flash 文生文和 CogView-3 文生图大模型&#xff0c;和大家一起编写了一个图文并茂的儿童绘本应用&am…

Claude3.5 Sonnet模型评测(附使用方法)

随着模型的发展&#xff0c;之前大家常用的鉴别模型能力的测试已经有很多过时现象&#xff0c;比如经典的喝水测试&#xff0c;目前国内的先进模型也已经可以答对&#xff0c;我们需要更复杂的问题来测试模型能力&#xff0c;最近有研究人员发现&#xff0c;大模型不会比较浮点…

操作符详细解析

操作符详解 文章目录 操作符详解1.操作符分类2.算数操作符3.移位操作符3.1整型二进制的表示3.1.1整数二进制的种类3.1.2二进制原码反码补码的表示 3.2移位运算符使用规则3.2.1正数的左移运算符 3.2.2负数的左移运算符3.2.3右移操作符3.2.3.1右移运算符的两种形式&#xff1a;3.…

使用WSL在Windows上安装Linux

文章目录 环境步骤参考 注&#xff1a;WSL是“Windows Subsystem for Linux”的缩写&#xff0c;即“适用于 Linux 的 Windows 子系统”&#xff0c;说白了就是在Windows系统里直接使用Linux&#xff0c;而不需要VMWare等虚拟软件。 环境 Windows 11 家庭中文版 步骤 首先&…