10.2 ChatGLM3 部署與分析
前言
本小節介紹國內大模型開源界的先驅,ChatGLM,其提供了多種開源大模型,以及工具調用功能。
本節將介紹ChatGLM發展歷史,模型結構,prompt分析,顯存分析等內容,説明大家理解ChatGLM。
ChatGLM 簡介
ChatGLM是由北京智譜華章科技有限公司開發的基於GLM(General Language Model )的對話模型。
ChatGLM目前(2024年4月16日)已發展到v4(未開源),其中v3版已開源,並且獲得相當高的關注。
ChatGLM開源方面主打性價比,目前只有一個尺寸6B,但支持32K,128K不同上下文長度的版本。
智譜華章(智譜AI)公司2019年胎於清華大學的知識工程實驗室(KEG),其技術和產品的發展與清華大學的研究成果緊密相關。清華大學的背景為智譜AI提供了強大的學術和技術支援。
除了ChatGLM,智譜AI還有CogVLM 、 CogAgent和CharacterGLM的開源,為人工智慧開源社區的發展提供了幫助。產品方面,推出三大系列,大模型、AMiner學術情報收集、數位人產品。
智譜華章的價值觀也非常好——讓機器像人一樣思考。
智譜的技術背景歷史相對其他開源模型是比較長的,早在ChatGPT爆火之前,他們就已經發表了相關的文章,如2021年3月發表的《GLM: General Language Model Pretraining with Autoregressive Blank Infilling》(https://arxiv.org/pdf/2103.10360.pdf)、2022年10月發表的《Glm-130b: An open bilingual pre-trained model》(https://arxiv.org/pdf/2210.02414.pdf)
更多智譜AI資訊可關注:
- github:https://github.com/THUDM/ChatGLM3
- HF主頁:https://huggingface.co/THUDM/
- 官方文檔:https://zhipu-ai.feishu.cn/wiki/WvQbwIJ9tiPAxGk8ywDck6yfnof
- 官方博客:https://zhipuai.cn/devday
- 公司首頁:https://www.zhipuai.cn/
本地部署安裝
接下來就來當地語系化部署ChatGLM3-6B模型,試試效果
版本說明
ChatGLM3本地部署安裝不複雜,首先看官方建議的環境要求:
huggingface_hub>=0.19.4
pillow>=10.1.0
pyyaml>=6.0.1
requests>=2.31.0
ipykernel>=6.26.0
ipython>=8.18.1
jupyter_client>=8.6.0
Copy
本案例環境詳情:
- Win11 、 RAM 32GB、RTX 4060 Laptop 8GB
- Python 3.10.14
- transformers 4.38.2
- pytorch 2.2.0
- CUDA 12.1
操作步驟
第一步,下載代碼倉庫
git clone https://github.com/THUDM/ChatGLM3.git
Copy
第二步,下載模型
模型除了權重檔,還有一系列設定檔等資訊,因此需要完整的檔下載才可使用,鑒於網路問題,這裡介紹兩種下載方式:
方式一:純命令列(拼網速)
git clone https://www.modelscope.cn/ZhipuAI/chatglm3-6b.git
or
git clone https://huggingface.co/THUDM/chatglm3-6b
Copy
方式二:命令列+ 手動下載權重文件
git clone https://www.modelscope.cn/ZhipuAI/chatglm3-6b.git
首先,查看資料夾下設定檔是否下載完畢,下載完畢後,ctrl + c停止。
然後,可手動下載.safetensors文件,放到資料夾中,手動下載.safetensors檔,速度高達10+MB/s
Copy
第三步,環境安裝
pip install -r requirments.txt
第四步,設置模型路徑
ChatGLM3\basic_demo\cli_demo.py中,修改
MODEL_PATH = r"G:\04-model-weights\chatglm\chatglm3-6b"
TOKENIZER_PATH = r"G:\04-model-weights\chatglm\chatglm3-6b"
Copy
如果需要int4量化,則修改以下代碼(不量化,需要7.9GB載入,int4量化,需要4.7GB載入)
model = AutoModel.from_pretrained(MODEL_PATH, trust_remote_code=True).quantize(bits=4, device="cuda").cuda().eval()
# add .quantize(bits=4, device="cuda").cuda() before .eval() to use int4 model
Copy
第五步,運行代碼
進入虛擬環境,運行basic_demo\cli_demo.py, terminal中顯示如下資訊表明載入成功,載入完成後,顯存佔用4678MB。
C:\Users\yts32\anaconda3\envs\chatglm\python.exe D:\github_desktop\ChatGLM3\basic_demo\cli_demo.py
Setting eos_token is not supported, use the default one.
Setting pad_token is not supported, use the default one.
Setting unk_token is not supported, use the default one.
Loading checkpoint shards: 100%|██████████| 7/7 [00:16<00:00, 2.34s/it]
歡迎使用 ChatGLM3-6B 模型,輸入內容即可進行對話,clear 清空對話歷史,stop 終止程式
用戶:你好
你好👋!我是人工智慧助手 ChatGLM3-6B,很高興見到你,歡迎問我任何問題。
用戶:
Copy
對於6B模型,筆記本級顯卡就非常慢,已經到達用戶無法接受的程度,解碼時延約1.5s/token,通常LLM服務的解碼時延在50ms。
這或許與代碼有關,在baichuan2的7B-int4模型中,速度還是合理的。
模型結構分析
為了詳細瞭解ChatGLM3模型代碼的設計結構,接下來分析模型UML類圖結構。
首先關心的是,模型在哪裡創建的,是什麼形式,這個可從ChatGLM3\basic_demo\cli_demo.py的12行代碼看到
model = AutoModel.from_pretrained(MODEL_PATH, trust_remote_code=True).quantize(bits=4, device="cuda").cuda().eval()
Copy
這裡基於transformers庫的規則採用AutoModel類的介面進行模型初始化,內部實際調用為使用者自訂的模型類。
最終調用的是C:\Users\yts32.cache\huggingface\modules\transformers_modules\chatglm3-6b\modeling_chatglm.py當中的ChatGLMForConditionalGeneration。
其內部關係如下圖所示:
UML類圖分兩部分
左邊的_BaseAutoModelClass和AutoModelForCausalLM是transformers庫的標準設計,基於transformers推理的LLM需要遵循這套規則。
右邊則是ChatGLM的代碼結構設計,包括:
- ChatGLMForConditionalGeneration:提供用戶使用的類,有stream_chat、chat這兩個對話介面
- ChatGLMPreTrainedModel:所有預訓練模型基類,提供通用介面,如get_position_ids、get_masks
- ChatGLMModel:LLM基模型,在ChatGLMForConditionalGeneration中被產生實體為transformer屬性,可理解為一個神經網路模型。
- 再往上,就是transformers庫的基礎類了,包括PreTrainedModel、nn.Module, ModuleUtilsMixin, GenerationMixin, PushToHubMixin, PeftAdapterMixin。這些都是基礎的必備模組。可以看到熟悉的pytorch的nn.Module,以及一系列Mixin類。
補充知識:Mixin類是一種設計模式,表示一些通用的功能放到這裡,其他需要此功能的模組,通過繼承的方式將這些共同行為混入到其他類中。
流式推理流程分析
cli_demo.py採用的是流式返回,推理流程在.cache\huggingface\modules\transformers_modules\chatglm3-6b\modeling_chatglm.py 中ChatGLMForConditionalGeneration.stream_generate()
LLM輸出的過程採用while迴圈進行控制,當達到停止條件時,break跳出迴圈。
下面分析stream_generate()中while迴圈裡的代碼,可以分為四個步驟
- 獲取LLM的輸入,執行推理,即outputs = self(xxx)
- 採樣,獲取本次推理得到的tokens
- yield 拋出結果
- 判斷是否已停止,對於生成器而言,下一次進入迴圈會到yield之後的代碼。
while True:
# ================================== step1 ==================================
# 獲取LLM模型需要的輸入,例如,input_ids, position_ids, att_mask等
model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs)
# forward pass to get next token
# LLM 模型的一次推理,輸出1個向量,在此處為 1*65024維的向量,表明詞表大小為65024。
outputs = self(
**model_inputs,
return_dict=True,
output_attentions=False,
output_hidden_states=False,
)
# ================================== step2 ==================================
next_token_logits = outputs.logits[:, -1, :]
# pre-process distribution
next_token_scores = logits_processor(input_ids, next_token_logits)
next_token_scores = logits_warper(input_ids, next_token_scores)
# sample
probs = nn.functional.softmax(next_token_scores, dim=-1) # 未採用溫度懲罰
if generation_config.do_sample:
next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1) # 這裡採用top-p=1
else:
next_tokens = torch.argmax(probs, dim=-1)
# update generated ids, model inputs, and length for next step
input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1)
model_kwargs = self._update_model_kwargs_for_generation(
outputs, model_kwargs, is_encoder_decoder=self.config.is_encoder_decoder
)
unfinished_sequences = unfinished_sequences.mul(
next_tokens.tile(eos_token_id_tensor.shape[0], 1).ne(eos_token_id_tensor.unsqueeze(1)).prod(dim=0)
)
# ================================== step3 ==================================
if return_past_key_values:
yield input_ids, outputs.past_key_values
else:
yield input_ids
# ================================== step0 ==================================
# stop when each sentence is finished, or if we exceed the maximum length
if unfinished_sequences.max() == 0 or stopping_criteria(input_ids, scores):
break
Copy
prompt結構分析
ChatGLM3 prompt結構由對話頭和內容組成,一個典型的多輪對話結構如官方文檔所示:
<|system|>
You are ChatGLM3, a large language model trained by Zhipu.AI. Follow the user's instructions carefully.
<|user|>
Hello
<|assistant|>
Hello, I'm ChatGLM3. What can I assist you today?
Copy
對比Qwen的結構如下:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是誰<|im_end|>
<|im_start|>assistant
Copy
ChatGLM3 為了支持Code Interpreter,Tool & Agent 等任務的輸入,還增加了一個角色,總共支持四種角色,具體是:
<|system|>:系統資訊,設計上可穿插於對話中,但目前規定僅可以出現在開頭
<|user|>:使用者 不會連續出現多個來自 <|user|> 的資訊
<|assistant|>:AI 助手 在出現之前必須有一個來自 <|user|> 的資訊
<|observation|>:外部的返回結果 必須在 <|assistant|> 的資訊之後
Copy
<|observation|>的加入,使得ChatGLM3具備更豐富的功能,可以讓LLM加上“四肢“,讓大模型學會使用工具。
工具功能是一種擴展機制,它允許大模型通過調用外部工具來增強自身的能力,解決一些由於訓練資料限制或專業領域知識缺乏而無法獨立完成的任務。
常用的工具有計算器、搜尋引擎、天氣查詢等,這個部分設計Agent的內容,此處暫不展開。
顯存使用與上下文長度分析
本案例中並未能正確分析出上下文長度與顯存佔用的情況,出現了一些奇怪現象,這裡總結如下:
- int4 啟動需要4.6GB顯存
- 上下文增加到3000時,顯存僅增加了100MB
- 上下文超3500之後,顯存不再增加,應該與上下文截斷有關
此處,未進一步查看原始程式碼尋找原因,畢竟這裡是demo,生產部署會採用其他推理框架。
在cli_demo.py代碼後加入如下代碼,可進行統計,完整代碼位於github
# 統計文本長度
conversation_length = sum([len(content['content']) for content in history])
import subprocess
import json
result = subprocess.run(['gpustat', '--json'], stdout=subprocess.PIPE)
output = result.stdout.decode()
data = json.loads(output)
used_memory = data['gpus'][0]['memory.used']
f.writelines("{}, {}\n".format(conversation_length, used_memory))
f.flush()
Copy
在此處用的是文本長度,而不是tokens長度,是因為代碼介面返回的只有文本,沒有tokens_ids,但不影響分析結果的趨勢。
小結
本節詳細介紹了ChatGLM的背景與本地部署實戰,包括以下核心內容:
- ChatGLM3模型介紹,是基於GLM(General Language Model)的對話模型,目前發展到v4版本,v3版本已開源,獲得相當高的關注
- 本地部署ChatGLM3-6B模型詳細步驟,包括環境配置介紹
- ChatGLM3-6B模型結構分析:對ChatGLM3的模型結構進行了詳細分析,包括其類圖結構、流式推理流程以及prompt結構等
- 顯存分析:分析了顯存使用與上下文長度的關係
下一節將介紹百川2的本地部署