昇思+昇騰開發板+DeepSeek模型推理和性能優化
模型推理
流程: 權重加載 -> 啟動推理 -> 效果比較與調優 -> 性能測試 -> 性能優化
權重加載
如微調章節介紹,最終的模型包含兩部分:base model 和 LoRA adapter,其中base model的權重在微調時被凍結,推理時加載原權重即可,LoRA adapter可通過PeftModel.from_pretrained進行加載。
### 加載基礎模型
model = AutoModelForCausalLM.from_pretrained("MindSpore-Lab/DeepSeek-R1-Distill-Qwen-1.5B-FP16", mirror="modelers", ms_dtype=mindspore.float16)
### 加載LoRA adapter
model = PeftModel.from_pretrained(model, "./output/adapter_model_for_demo") # adapter_model path
## 啟動推理
通過model.generate,啟動推理。generate_kwargs = dict(input_ids=input_ids,streamer=streamer,max_new_tokens=1024,do_sample=True,top_p=0.9,temperature=0.1,num_beams=1,
)
## 使用線程啟動生成
t = Thread(target=model.generate, kwargs=generate_kwargs)
'''
效果比較
演示中以一個微調多輪后的LoRA權重為例,在微調前(不加載LoRA adapter),在問模型“你是誰”時,回答的是 "DeepSeek-R1”,而在加載LoRA adapter之后,回答為“甄嬛”。
微調前:
問: 你是誰?
答: 您好!我是由中國的深度求索(DeepSeek)公司開發的智能助手DeepSeek-R1。如您有任何任何問題或需要幫助,我會盡我所能為您提供幫助。
微調后:
問: 你是誰?
答: 我是甄嬛,家父是大理寺少卿甄遠道。
效果調優
在進行長文本輸出的過程當中,輸出回答到一定長度后模型會輸出重復內容,如下圖所示,可在generate_kwargs中添加 repetition_penalty=1.2,解決長文本輸出重復問題。
調優前: 模型在生成長回復時,末尾出現大量重復語句。
調優后: 通過設置 repetition_penalty,模型能夠生成邏輯連貫且不重復的長篇回復。
性能測試
凡是在推理過程中涉及采樣(do_sample=True)的案例,可以通過配置如下變量,注釋掉之前添加的同步模式代碼,再運行代碼,即可獲取每個token的推理時長和平均時長。
export INFERENCE_TIME_RECORD=True
此時,從終端的運行日志可以看到,平均推理時間為0.727秒,可通過禁用多線程將推理速度適當提升為平均單token推理時長0.674秒。
操作: 在腳本中添加禁用多線程代碼
from mindspore._c_expression import disable_multi_thread
disable_multi_thread()
性能優化
通過上述禁用多線程的方式,可以適當減少平均單token的推理時長,但效果不明顯。在此基礎上,還可以通過jit即時編譯的方式進一步加速。jit即時編譯通過jit修飾器修飾Python函數或者Python類的成員函數使其被編譯成計算圖,通過圖優化等技術提高運行速度。
在本章節的場景下,jit修飾器應該修飾模型decode的函數,但由于原代碼將模型的logits計算、解碼等過程整體封裝成了一個model.generate函數,不好進行優化,所以需要手動實現解碼邏輯。
DeepSeek-R1-Distill-Qwen-1.5B 模型推理性能調優
性能優化
前序準備
實現解碼邏輯(decode函數、prefill-decode階段)。
實例化StaticCache,動態Cache無法成圖。
添加jit裝飾器
設置O2整圖下沉進一步優化。
調用 model.jit()。
使用 mindspore.jit 裝飾器修飾decode函數。
#### 1. 設置上下文
mindspore.set_context(enable_graph_kernel=True, mode=mindspore.GRAPH_MODE, jit_config={"jit_level": "02"})#### ... 模型加載 ...
#### 2. 編譯模型
model.jit()#### 3. jit裝飾器修飾解碼函數
@mindspore.jit(jit_config=mindspore.JitConfig(jit_syntax_level='STRICT'))
def decode_one_tokens_logits(model, cur_token, input_pos, cache_position, past_key_values):logits = model(...)return logits
Top_p函數的實現
出于效率的考慮,優先使用numpy進行函數的實現。
而在gather函數的實現上,基于mindspore.mint的實現方式會出現報錯,故使用mindspore.ops來實現。
modeling_qwen2.py的decoder_layer中,需添加_modules.values()
為了在靜態圖模式下能正確遍歷網絡層,需要修改循環方式。
原代碼
for decoder_layer in self.layers:
修改后
for decoder_layer in self.layers._modules.values():
modeling_qwen2.py原RotaryEmbedding在靜態圖編譯會出現報錯
需要參考modeling_llama.py將該類進行重寫。相關pr已經合入mindnlp的0.4分支。
性能優化效果測試
推理時間測試代碼
##### 自回歸生成循環
cache_position = mindspore.tensor([seq_length + 1])
for i in range(1, NUM_TOKENS_TO_GENERATE):s = time.time()next_token = decode_one_tokens(model, next_token, None, cache_position, past_key_values)generated_ids[:, cache_position] = next_token.int()cache_position += 1t = time.time()print("[%d]: %s" % (i, t-s)) # 打印單步生成耗時
不使用jit優化,每個token推理時間約為1.1秒。
使用jit優化,每個token推理時間約為0.32秒,效率顯著提高。
但是在推理首個token前需要對全圖進行編譯,故首token推理時間較長。在推理token數量較多時,使用JIT優化對效率提升效果更明顯。