相同的 LLM 在「不同 GPU 上」會產生不同輸出?為什么? 原創 精華
編者按: 在大語言模型(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=True?
?? 和 ??output_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/??
原文鏈接:
