本文將基于Windows系統(tǒng)和CPU環(huán)境,使用Qwen2.5系列模型,詳細實踐從大型語言模型的下載、部署到微調(diào)的全過程。
1、環(huán)境配置
1.1、部署開發(fā)環(huán)境
本地開發(fā)環(huán)境:windows
(1)python開發(fā)環(huán)境
①pycharm 安裝pycharm(community版本):www.jetbrains.com/pycharm/dow…
②anaconda 安裝anaconda:清華鏡像源,選擇合適的版本,例如:Anaconda3-2024.06-1-Windows-x86_64.exe 下載anaconda完成后,windows系統(tǒng)下點擊exe文件一路nex即可安裝完成。
③配置anaconda環(huán)境變量(非必須): 假設(shè)你的anaconda安裝地址為:D:\soft\anaconda;進入系統(tǒng)高級配置,添加系統(tǒng)變量:

然后點擊Path,添加如下變量:
%ANACONDA_HOME%
%ANACONDA_HOME%\Scripts
%ANACONDA_HOME%\Library\mingw-w64\bin
%ANACONDA_HOME%\Library\usr\bin
%ANACONDA_HOME%\Library\bin
配置完成后,使用conda --version
可以看到anaconda已經(jīng)安裝完成:

④anaconda使用:
一些簡單的命令,幫助我們使用它:
# 安裝一個新的anaconda環(huán)境,名為qwen1.5-4b,python版本為3.10
conda create -n qwen1.5-4b python=3.10
# 查詢安裝的anaconda環(huán)境
conda env list
# 手動切換anaconda環(huán)境
conda activate qwen1.5-4b
# 關(guān)閉anaconda環(huán)境
conda deactivate
# 檢查python的版本(當(dāng)前conda環(huán)境下的)
python --version
在我們新建完名為qianwen的conda虛擬環(huán)境后,去pycharm的setting->Python Interpreter中導(dǎo)入創(chuàng)建好的conda環(huán)境即可:

1.2、大模型下載
huggingface:略
modelscape,魔搭社區(qū)提供了相應(yīng)的組件來供使用者下載:
# 安裝ModelScope
pip install modelscope
# 下載完整模型repo
modelscope download --model qwen/Qwen2.5-1.5B
# 下載單個文件(以README.md為例)
modelscope download --model qwen/Qwen2.5-1.5B README.md
示例如下:
我在base的conda環(huán)境下進行安裝相應(yīng)組件,然后調(diào)用modelscope命令進行下載,且該組件具備斷點續(xù)傳的功能,如果當(dāng)前網(wǎng)絡(luò)不佳,可以殺死命令行,重新執(zhí)行命令,已下載的文件內(nèi)容不會丟失,可以繼續(xù)在進度條附近開始下載任務(wù)。

2、大模型部署使用
2.1、概述
使用:
import torch
from flask import Flask
from flask import request
from transformers import (AutoTokenizer, AutoModelForCausalLM, AutoModel,
Qwen2ForCausalLM, Qwen2Tokenizer)
# 參數(shù)
max_new_tokens: int = 512 # 生成響應(yīng)時最大的新token數(shù)
system_prompt = '你是一個專門分類新聞標(biāo)題的分析模型。你的任務(wù)是判斷給定新聞短文本標(biāo)題的分類。'
user_template_prompt = ('請評估以下網(wǎng)購評論的情感,不要返回0或1以外的任何信息,不要返回你的思考過程。'
'輸入正面評論輸出1,輸入負面評論輸出0。輸入如下:{}\n請?zhí)顚懩愕妮敵?)
eos_token_id = [151645, 151643]
app = Flask(__name__)
model_path = 'D:\project\llm\Qwen2.5-1.5B'
# tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer = Qwen2Tokenizer.from_pretrained(model_path)
# model = AutoModelForCausalLM.from_pretrained(model_path, device_map='cpu').eval()
model = Qwen2ForCausalLM.from_pretrained(model_path, device_map='cpu').eval()
# 非流式請求
@app.route('/chat', methods=['POST'])
def chat():
# 系統(tǒng)設(shè)定和prompt
req_json = request.json
content = user_template_prompt.format(req_json['message'])
messages = [
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': content}
]
print('input: ' + content)
input_ids = tokenizer.apply_chat_template(messages,
tokenize=False,
add_generation_prompt=True)
inputs = tokenizer([input_ids], return_tensors='pt').to(model.device)
generated_ids = model.generate(
inputs.input_ids,
max_new_tokens=max_new_tokens,
eos_token_id=eos_token_id, # 結(jié)束令牌,模型生成這個token時,停止生成
)
generated_ids = [
output_ids[len(inputs):] for inputs, output_ids in zip(inputs.input_ids, generated_ids)
]
print(f'generated_ids=\n{generated_ids}')
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
# 使用占位符拼接字符串
return response
if __name__ == '__main__':
app.run(port=8080, host='0.0.0.0')
其中用到的Qwen2ForCausalLM替換了AutoModelForCausalLM,是一個基于Transformer結(jié)構(gòu)的decoder-only模型,它是Qwen大模型的第二代架構(gòu)。
2.2、參數(shù)
通常來說參數(shù)越大的大模型。其中需要關(guān)注到的一些問題以及使用上需要注意的地方:
①apply_chat_template
用在會話中,告訴大模型接下來應(yīng)該生成新的東西,并且包含了整個會話的上下文信息。使用如下:
input_ids = tokenizer.apply_chat_template(messages, # 輸入的純文本信息
tokenize=False, # 告訴大模型暫時不需要分詞
add_generation_prompt=True) # 添加一個特殊的標(biāo)記,告訴大模型接下來應(yīng)該生成新的文本內(nèi)容。
輸出如下:

'<|im_start|>system
你是一個專門評估網(wǎng)購評論情感的分析模型。你的任務(wù)是判斷給定評論是正面還是負面。<|im_end|>
<|im_start|>user
請評估以下網(wǎng)購評論的情感,不要返回0或1以外的任何信息,不要返回你的思考過程。如果是正面評論返回1,如果是負面評論返回0:不錯!挺合適<|im_end|>
<|im_start|>assistant
'
②eos_token_id
設(shè)定了大模型生成文本的分割符,其中eos_token_id
= [151645, 151643],兩個id的含義分別是:
tokenizer.convert_ids_to_tokens(151645) # <|im_end|>
tokenizer.convert_ids_to_tokens(151643) # <|endoftext|>
tokenizer.convert_ids_to_tokens(1773) # 。
這兩個標(biāo)記與輸入中的標(biāo)記保持一致。若不設(shè)置該值,在未達到最大返回token數(shù)之前,對話將不會自動終止,大模型可能會進行不必要的自問自答。

為了控制大模型可能產(chǎn)生的不穩(wěn)定輸出,設(shè)置停用詞是一種有效手段。除了使用 eos_token_id
外,還可以采用以下方法:
generation_config = GenerationConfig(use_cache=True,
repetition_penalty=repetition_penalty,
do_sample=False, # 取消采樣,使用貪心策略,輸出是確定的
stop_strings='}')
generated_ids = model.generate(input_ids,
tokenizer=tokenizer,
generation_config=generation_config)
對于模型的推理參數(shù),可以統(tǒng)一放置在 GenerationConfig
中。通過 stop_strings
參數(shù)(其值可為字符串或字符串列表)來設(shè)置停用詞。在上例中,將 }
設(shè)為停用詞,這在要求大模型返回JSON數(shù)據(jù)時尤為有效,能夠有效避免大模型在輸出完整JSON數(shù)據(jù)后繼續(xù)進行不必要的推理。
③repetition_penalty
使用如下:
repetition_penalty float = 1.2 # 用于懲罰重復(fù)生成相同token的參數(shù)
generated_ids = model.generate(
inputs.input_ids,
max_new_tokens=max_new_tokens,
repetition_penalty=repetition_penalty, # 解決問題后面有過多重復(fù)問答
)
某些時候,大模型也會持續(xù)重復(fù)之前的對話,直到生成的token數(shù)等于max_new_tokens
為止。情況如下:

這個值不宜過低或過高:過低不生效;過高會導(dǎo)致大模型不敢生成正確答案,因為輸入的prompt中攜帶了正確答案。目前看1.2是一個比較合適的值。
④skip_special_tokens=True
這個告訴分詞器在解碼時跳過任何特殊的標(biāo)記,如結(jié)束標(biāo)記end-of-sequence token
或其他模型特定的標(biāo)記。
由于我們在上面調(diào)用model時設(shè)置了停用詞,在大模型推理到停用詞就會返回輸出。如果不設(shè)置該參數(shù),則效果如下:
1<|endoftext|>
優(yōu)化參數(shù)之后,效果如下:
| | gpt-4o | qwen2.5-1.5b | qwen2.5-1.5b_修改后 | qwen2.5-1.5b_微調(diào)后 | qwen2.5-3b | qwen2.5-3b_修改后 |
---|
二元分類 | | 0.96 | 幾乎無法輸出規(guī)定格式的結(jié)果 | 0.93 | <暫未微調(diào)> | 0.62 | 0.91 |
多元分類 | 樣本100條 | 0.93 | | 0.67 | 0.9 | 0.12 | 0.72 |
| 樣本1000條 | 0.785 | | 0.579 | 0.796 | | |
2.3、總結(jié)
-
本章節(jié)主要關(guān)注qwen2.5-1.5b_修改后的結(jié)果,有兩個主要成果:
1.1、修改了上述等啟動參數(shù)之后,大模型能夠正常輸出預(yù)期的結(jié)果
1.2、對于相對簡單的人呢?zé)o
-
下一章節(jié)將進行qwen2.5-1.5b模型的微調(diào)
2.3.1、最終代碼
最終代碼如下:
import torch
from flask import Flask
from flask import request
from transformers import (AutoTokenizer, AutoModelForCausalLM, AutoModel,
Qwen2ForCausalLM, Qwen2Tokenizer)
from peft import PeftModel
# 參數(shù)
max_new_tokens: int = 64 # 生成響應(yīng)時最大的新token數(shù)
temperature: float = 0.6 # 控制生成文本的隨機性
top_p: float = 0.9 # 用于概率限制的參數(shù),有助于控制生成文本的多樣性
top_k: int = 32 # 控制生成過程中每一步考慮的最可能token的數(shù)量
repetition_penalty: float = 1.2 # 用于懲罰重復(fù)生成相同token的參數(shù)
system_template_prompt = '你是一個專門評估網(wǎng)購評論情感的分析模型。你的任務(wù)是判斷給定評論是正面還是負面。'
system_prompt = '你是一個專門分類新聞標(biāo)題的分析模型。你的任務(wù)是判斷給定新聞短文本標(biāo)題的分類。'
user_template_prompt = ('請評估以下評論,不要返回0或1以外的任何信息,不要返回你的思考過程。'
'如果是正面評論輸出1,是反面評論輸出0。輸入如下:{}\n請?zhí)顚懩愕妮敵觯?)
user_prompt = ('請將以下新聞短文本標(biāo)題分類到以下類別之一:故事、文化、娛樂、體育、財經(jīng)、房產(chǎn)、汽車、教育、'
'科技、軍事、旅游、國際、股票、農(nóng)業(yè)、游戲。輸入如下:\n{}\n請?zhí)顚懩愕妮敵觯?)
eos_token_id = [151645, 151643]
app = Flask(__name__)
lora_model_path = './output/Qwen2.5-1.5b/checkpoint-100'
model_path = 'D:\project\llm\Qwen2.5-1.5B'
# 從指定路徑加載大模型的分詞器(tokenizer),用于加載預(yù)訓(xùn)練的文本處理模型(Tokenizer),以便將文本數(shù)據(jù)轉(zhuǎn)換為模型可以接受的輸入格式。
tokenizer = Qwen2Tokenizer.from_pretrained(model_path)
# AutoModelForCausalLM更適合語言大模型
model = Qwen2ForCausalLM.from_pretrained(model_path, device_map='cpu').eval()
# 非流式請求
@app.route('/chat_old', methods=['POST'])
def chat_old():
# 系統(tǒng)設(shè)定和prompt
req_json = request.json
content = user_template_prompt.format(req_json['message'])
messages = [
{'role': 'system', 'content': system_template_prompt},
{'role': 'user', 'content': content}
]
# 使用tokenizer將整個會話轉(zhuǎn)換成模型可以理解的input_ids,并將這些input_ids輸入到模型
# tokenize=False 表示不要立即分詞,只是使用apply_chat_template將會話進行格式化
input_ids = tokenizer.apply_chat_template(messages,
tokenize=False,
add_generation_prompt=True)
print(f'input:{input_ids}')
inputs = tokenizer([input_ids], return_tensors='pt').to(model.device)
generated_ids = model.generate(
inputs.input_ids,
max_new_tokens=max_new_tokens,
repetition_penalty=repetition_penalty, # 解決問題后面有過多重復(fù)問答(對重復(fù)token的懲罰)
eos_token_id=eos_token_id, # 結(jié)束令牌,模型生成這個token時,停止生成
)
generated_ids = [
output_ids[len(inputs):] for inputs, output_ids in zip(inputs.input_ids, generated_ids)
]
print(f'generated_ids=\n{generated_ids}')
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
response = response.encode('utf-8', errors='ignore').decode('utf-8')
print(response)
# 使用占位符拼接字符串
return response
@app.route('/chat', methods=['POST'])
def chat():
# 系統(tǒng)設(shè)定和prompt
req_json = request.json
prompt = user_prompt.format(req_json['message'])
print(prompt)
# 非會話的輸入方式,將單句話進行分詞成token ids
inputs = tokenizer(prompt, return_tensors='pt')
input_ids = inputs.input_ids
# Generate
generate_ids = model.generate(input_ids=input_ids,
bos_token_id=151645, # 開始令牌(在生成文本時,模型會在輸入序列的末尾添加這個令牌,以指示新生成的文本的開始。)
max_new_tokens=len(input_ids) + 1,
repetition_penalty=repetition_penalty)
print(generate_ids)
response = tokenizer.batch_decode(generate_ids, skip_special_tokens=True)[0]
print(response)
# # 去掉response中的包含prompt的文本
response = response[len(prompt):]
return response.strip()
if __name__ == '__main__':
app.run(port=8080, host='0.0.0.0')
2.3.2、數(shù)據(jù)集
數(shù)據(jù)集如下:
二元分類數(shù)據(jù)集:電商平臺評論數(shù)據(jù)集
多元分類數(shù)據(jù)集:今日頭條文本分類數(shù)據(jù)集 / 數(shù)據(jù)集 / HyperAI超神經(jīng)
其他:天池數(shù)據(jù)集、ChineseNlpCorpus