借著熱點,簡單聊聊大模型的部署方案,作為一個只搞過CV部署的算法工程師,在最近LLM逐漸改變生活的大背景下,猛然意識到LLM部署也是很重要的。大模型很火,而且確實有用(很多垂類場景可以針對去訓練),并且和Vision結合的大模型也逐漸多了起來。所以怎么部署大模型是一個超級重要的工程問題,很多公司也在緊鑼密鼓的搞著。
目前效果最好討論最多的開源實現就是LLAMA,所以我這里討論的也是基于LLAMA的魔改部署。
基于LLAMA的finetune模型有很多,比如效果開源最好的vicuna-13b和較早開始基于llama做實驗的alpaca-13b,大家可以看:
https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard 開源LLM
https://lmsys.org/blog/2023-05-03-arena/ 基于llama的開源對比
https://github.com/camenduru/text-generation-webui-colab 一些開源LLM的notebook
至于為啥要選LLAMA,因為當前基于LLAMA的模型很多,基礎的llama的效果也不錯,當然RWKV也很不錯,我也一直在看。
具體的可以看這里,https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard
Github上較多的是實現是直接python推理然后基于gradio的網頁端展示,還不能被當成服務調用(或者說不大優雅),一般來說作為服務:
響應速度要快,每秒的tokens越多越好
穩定性,出現core了不會影響推理或者可以及時恢復
支持各種形式,http、sse、grpc等形式
其實最重要的一點還是要快,其次必須要量化,因為大模型的權重往往很大,量化可以有效減小權重(省顯卡),是非常必要的。要做成服務的話,需要稍稍花一點功夫和魔改一下,幸運的是網上已經有不錯的demo實現供我們嘗試,這篇文章主要是總結下以LLAMA為例的LLM部署方案,希望能拋磚引玉。
PS:我們常用的ChatGPT據說是居于Python服務搭建的,相比模型的耗時,python環境的開銷可以忽略不計。
以LLAMA為例
LLM很大,比如GPT3-175B的大模型,700GB的參數以及運行過程中600GB的激活值,1.3TB總共,正常來說,得32個A100-40GB才能放的下。
但實際應用中,消費級顯卡要比專業顯卡便宜的多(比如3090相比A10,同樣都是24G顯存),所以用消費級顯卡部署LLM也很有錢途。一張卡放不下那就放兩張,如果沒有nvlink,那么PCIE的也湊合用。
回到LLAMA模型,有7B、13B、30B、65B的版本,當然是越大的版本效果最好,相應的也需要更多的顯存(其實放到內存或者SSD也是可以的,但是速度相比權重全放到顯存里頭,肯定要慢)。
LLAMA的實現很多,簡單列幾個我看過的,都可以參考:
https://github.com/juncongmoo/pyllama 原始llama的實現方式
https://github.com/qwopqwop200/GPTQ-for-LLaMa 支持量化,INT4、INT8量化的llama
https://github.com/tpoisonooo/llama.onnx.git 以ONNX的方式運行llama
量化和精度
對于消費級顯卡,直接FP32肯定放不下,一般最基本的是FP16(llama的7B,FP16需要14G的顯存,大多數的消費級顯卡已經說拜拜了),而INT8和INT4量化則就很有用了,舉幾個例子:
對于3080顯卡,10G顯存,那么13B的INT4就很有性價比,精度比7B-FP16要高很多
對于3090顯卡,24G顯存,那么30B的INT4可以在單個3090顯卡部署,精度更高
可以看下圖,列舉了目前多種開源預訓練模型在各種數據集上的分數和量化精度的關系:
我自己也測試了幾個模型,我使用的是A6000顯卡,48G的顯存,基于GPTQ-for-LLAMA測試了各種不同規格模型的PPL精度和需要的顯存大小。
執行以下命令CUDA_VISIBLE_DEVICES=0 python llama.py ${MODEL_DIR} c4 --wbits 4 --groupsize 128 --load llama7b-4bit-128g.pt --benchmark 2048 --check測試不同量化精度不同規格模型的指標:
#7B-FP16 Median:0.03220057487487793 PPL:5.227280139923096 maxmemory(MiB):13948.7333984375 #7B-INT8 Median:0.13523507118225098 PPL:5.235021114349365 maxmemory(MiB):7875.92529296875 #7B-INT4 Median:0.038548946380615234 PPL:5.268043041229248 maxmemory(MiB):4850.73095703125 #13B-FP16 Median:0.039263248443603516 PPL:4.999974727630615 maxmemory(MiB):26634.0205078125 #13B-INT8 Median:0.18153250217437744 PPL:5.039003849029541 maxmemory(MiB):14491.73095703125 #13B-INT4 Median:0.06513667106628418 PPL:5.046994209289551 maxmemory(MiB):8677.134765625 #30B-FP16 OOM #30B-INT8 Median:0.2696110010147095 PPL:4.5508503913879395 maxmemory(MiB):34745.9384765625 #30B-INT4 Median:0.1333252191543579 PPL:4.526902675628662 maxmemory(MiB):20070.197265625
30B的FP16和65B的爆顯存了,還沒搞自己的多卡環境,之后會補充結果到這里。
可以先看看其他大佬的測試結果,大概有個預期,65B模型的話,INT4在兩張3090上是可以放得下的:
速度測試
我這邊也測試了llama在huggingface中Python庫的實現速度、以及基于GPTQ的量化、多卡的速度,其中stream模式參考了text-generation-webui中的Stream實現。
其中FP16就是直接調用GPTQ-for-LLaMa/llama_inference.py中未量化的FP16推理,調用方式為model.generate,model來自:
model=LlamaForCausalLM.from_pretrained(model,torch_dtype='auto', #load_in_8bit=True,device_map='auto' )
而量化版本的模型:
INT8模型調用方式同FP16,參數load_in_8bit設置為True,直接利用hugglingface的transformer庫INT8的實現
INT4的模型使用GPTQ的方式進行量化,具體代碼通過triton語言(區別于我之前提到的triton inference server)進行加速
模型 | 平臺 | 精度 | 顯存 | 速度 | 備注 |
---|---|---|---|---|---|
llama-7B | A4000 | FP16 | 13.6g |
Output generated in 1.74 seconds (28.74 tokens/s, 50 tokens) Output generated in 9.63 seconds (25.97 tokens/s, 250 tokens) |
GPTQ-for-LLaMa/llama_inference.py 測試包含模型前后處理 利用率99% |
A4000 | 4-bit | 5g | Output generated in 2.89 seconds (17.51 tokens/s, 50 tokens) | ||
A4000 | 4-bit | 5g | Output generated in 2.93 seconds (17.11 tokens/s, 50 tokens) |
stream模式 一次輸出1個token |
|
A4000 | 4-bit | 3.4+2.3g | Output generated in 2.91 seconds (17.31 tokens/s, 50 tokens) |
多卡測試雙A4000 兩張卡利用率 20-30左右 |
|
A4000 | INT8 | 8.3g |
Output generated in 10.20 seconds (5.8 tokens/s, 50 tokens) int8 實現用的huggingface的實現 |
利用率25% |
我這邊拿我的A4000測了下,測試從tokenizer編碼后開始,到tokenizer解碼后結束。
大概的結論:
FP16速度最快,因為INT4和INT8的量化沒有優化好(理論上INT8和INT4比FP16要快不少),而INT4的triton優化明顯比huggingface中INT8的實現要好,建議使用INT4量化
stream模式和普通模型的速度似乎差不多
A6000的懶得測試了,補充一個網上搜到的指標,A6000相比A4000相當于類似于3090和3070的性能差距吧...
LLM和普通小模型在部署方面的區別
在我以往的任務中,都是處理CV相關的模型,檢測、分類、識別、關鍵點啥的,最大模型可能也只有2、3G的樣子。平時的檢測模型,大點的也就300多M,在FP16或者INT8量化后則更小了,所以一般來說沒有顯存焦慮(當然有特殊情況,有時候可能會在一張卡上部署很多個模型,比如自動駕駛場景或者其他工業場景,這時候也是需要合理分配模型的顯存占用)。
但部署LLM這種大模型就不一樣了,隨便一個6、7B的模型,動不動就20多G的權重;65B、175B的模型更是有幾百G,模型變得異常大,因為這個原因,原來的部署方式都要發生一些變化。
模型方面的區別
首先模型很大,大模型導出ONNX有一些問題,ONNX保存大模型的權重存在一些限制
LLM模型中一般包含很多if-else分支,比如是否采用kv-cache,對轉換一些個別結構的模型(比如tensorrt)不是很友好
我們之前都是單GPU運行,多GPU的話,很多常用的runtime都不支持,onnxruntime或者tensorrt(tensorrt有內測多GPU的支持)默認都不支持多GPU
對于大模型來說,量化是必要的,因為FP16或者FP32的模型需要的顯存太大,都是錢啊。量化起來也不容易,QAT代價太大,PTQ校準的時候也需要很大的內存和顯存,會用INT8和INT4量化
網上對于這類模型的加速kernel不是很多,可以參考的較少,很多需要自己手寫
服務方式的區別
對于小模型來說,推理速度一般不會太慢,基本都在500ms以內,稍微等待下就得到結果了,也即是普通的http請求,一次請求得到一次結果。
而LLM因為是一個一個token產出來的,如果等全部token都出來再返回,那么用戶等待時間就挺長的,體驗不好,所以一般都是使用stream模式,就是出一點返回一點,類似于打字機的趕腳。
部署方案討論
這部分是本篇文章主要想說的地方,也是想和大家討論,一起想想方案,拋磚引玉。
對于部署像LLAMA這種的大語言模型,我之前也沒有經驗,瀏覽了一些開源的倉庫和資料,大概也有些思路,簡單總結總結,有那么幾個方案:
依賴Python實現的方案
和普通的CV模型一樣,python實現肯定是最簡單也是實現最多的,我們可以基于現有的開源實現直接包一層服務框架,類似于flask的服務即可,但是也需要有一定的可靠性。
所以這里可以選擇triton-inference-server的python后端,自己包一層前后處理,可以支持stream模式(使用grpc)。
這個實現起來比較簡單,多注意輸入輸出即可,相對于CV來說,我們的輸入可以是text或者是input_ids,要和圖像的unchar區別開,加速部分只能依賴python實現,同時也依賴python環境。
fastertransformer_backend方案
對于生產環境的部署,可以使用triton inference server,然后基于tritonserver有fastertransformer-backend。fastertransformer-backend是一個支持多個LLM模型的backend,手工實現了很多高性能的針對transformer的算子,每個模型都是手寫cuda搭建起來的,性能相比使用庫要高一檔。不過代價就是比較難支持新的模型框架,需要修改大量的源碼。
NVIDIA Triton introduces Multi-GPU Multi-node inference. It uses model parallelism techniques below to split a large model across multiple GPUs and nodes:
Pipeline (Inter-Layer) Parallelism that splits contiguous sets of layers across multiple GPUs. This maximizes GPU utilization in single-node.
Tensor (Intra-Layer) Parallelism that splits individual layers across multiple GPUs. This minimizes latency in single-node
所幸開源社區的大佬很多,近期的非官方PR也支持了LLAMA。我自己嘗試跑了下速度要比第一個huggingface的實現要快20%,這個精度基于FP16,支持多卡,目前暫時未支持INT8和INT4。
利用加速庫分而治之的方案
我們知道LLAMA的7B模型包含32個結構相同的decoder:
#transformers/src/transformers/models/llama/modeling_llama.py self.layers=nn.ModuleList([LlamaDecoderLayer(config)for_inrange(config.num_hidden_layers)])
因此我們也可以將這32個子模型分別使用我們常用的加速庫部署起來,比如7B的大模型,拆分成子模型每個模型300多M,使用TensorRT可以輕松轉換,也有大佬已經在做了:
https://github.com/tpoisonooo/llama.onnx
7B的llama模型轉成ONNX大概是這些:
decoder-merge-0.onnx
embed.onnx
head.onnx
norm.onnx
串起來的話,可以把這些模型放到不同的顯卡上也是可以的,比如兩張顯卡,第一張卡放15個子模型,第二張卡放剩余17個子模型,組成pipeline parallelism也是可以的。
有幾點需要注意:
加速庫可以使用不限于TensorRT,比如TVM、AItemplate啥的
需要一個后端將所有子模型串起來,最好C++實現
對于kv-cache,怎么存放需要考慮下
可以使用triton-inference-server去組pipeline,不同子模型的instance可以放到不同的gpu上。
后記
暫時先說這些,這篇文章之后也會隨時更新。目前只是簡單列了列我自己的想法,大家如果有好的想法也歡迎跟老潘一起交流。
每天出的新東西新技術太多了,看不過來,這篇文章也拖了好久,上述的這三種方案我自己都嘗試過了,都是可行的,大家感興趣可以試試,不過有個消息是TensorRT已經在默默支持多卡推理了,最快可能下個月(6月)就會出來(可能是對外release),不知道TensorRT大模型版本怎么樣?
-
顯存
+關注
關注
0文章
108瀏覽量
13654 -
開源
+關注
關注
3文章
3309瀏覽量
42471 -
模型
+關注
關注
1文章
3226瀏覽量
48809
原文標題:參考鏈接
文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論