DeepSeek-R1私有化部署——使用Python实现DeepSeek-R1-Distill-Qwen模型部署调用与流式输出
一、概述
DeepSeek-R1-Distill-Qwen 是 DeepSeek 团队基于 DeepSeek-R1 模型通过蒸馏技术生成的一系列轻量化模型。这些模型在保持高性能推理能力的同时,显著降低了计算资源和内存需求,适合在资源受限的环境中部署。DeepSeek-R1-Distill-Qwen 系列模型是通过从 DeepSeek-R1 模型中提取推理模式并迁移到更小的模型架构中生成的。这些模型基于 Qwen 系列架构,包括 1.5B、7B、14B 和 32B 等不同参数规模的版本,适用于多种任务场景,如数学推理、代码生成和逻辑推理等。
想试用满血版的DeepSeek与各种开源的大语言模型可以转到,带联网搜索+图片+文档+语音+群聊,实测比DeepSeek官网还牛逼:https://askmany.cn/login?i=d788ca33
二、性能与部署要求
2.1 性能
DeepSeek-R1-Distill-Qwen 系列模型在多个基准测试中表现出色:
- 数学推理:在 AIME 2024 和 MATH-500 测试中,DeepSeek-R1-Distill-Qwen-32B 的准确率分别达到 72.6% 和 94.3%,接近 OpenAI-o1-mini 的表现。
- 代码生成:在 LiveCodeBench 和 Codeforces 测试中,DeepSeek-R1-Distill-Qwen-32B 的评分分别为 57.2 和 1691,展现了强大的代码生成能力。
- 逻辑推理:在 GPQA Diamond 测试中,DeepSeek-R1-Distill-Qwen-32B 的准确率为 62.1%,显著优于许多同类模型。
2.2 部署与硬件要求
DeepSeek-R1-Distill-Qwen 系列模型支持多种部署方式,包括本地部署和云服务部署:
- 本地部署:可以通过 LM Studio 或 Ollama 等工具进行部署。例如,DeepSeek-R1-Distill-Qwen-7B 的最低硬件要求为 NVIDIA Tesla 16GB GPU,而 32B 版本则需要更高配置的 GPU(如 NVIDIA A100)。
- 云服务部署:阿里云函数计算 FC 和摩尔线程的 KUAE GPU 集群均支持 DeepSeek-R1-Distill-Qwen 系列模型的推理服务部署,显著降低了部署成本。
2.3 代码部署
为了方便嵌入自己的项目,这里演示如何基于python部署DeepSeek-R1模型,当前开发环境Win11,IDE是PyCharm,GPU是RTX 4080 8G,CUDA 是11.8。这里默认已安装好GPU驱动与CUDA与CUDNN环境,如未安装则转到博主另一个博客:Windows下从零开始基于Ollama与Open-WebUI本地部署deepseek R1详细指南(包含软件包和模型网盘下载) 。
二、环境安装
这里使用conda进行环境搭建,首先创建一个conda环境:
conda create -n deepseek python==3.10
激活环境:
conda activate deepseek
安装与cuda对应的pytorch库,可以从torch官网得到安装指令,我的装的是cuda 11.8,找到对应的torch版本:
pip install torch==2.3.0 torchvision==0.18.0 torchaudio==2.3.0 --index-url https://download.pytorch.org/whl/cu118
之后安装对应的依赖库:
requests==2.32.3
fastapi==0.115.8
uvicorn==0.34.0
transformers==4.48.2
huggingface-hub==0.28.1
accelerate==1.3.0
modelscope==1.22.3
numpy==1.22.4
安装完成环境之后,配置已安装好的Conda虚拟环境到PyCharm,可参考博主这个博客:详细记录Pycharm配置已安装好的Conda虚拟环境。
三、模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为模型的下载路径。
使用PyCharm中新建download_ model.py 文件,文件代码如下:
import os
from modelscope import snapshot_download
from modelscope.utils.constant import DownloadMode
def download_model(model_name,cache_path):
"""
此函数用于从 modelscope 下载指定模型到指定路径。
函数会尝试从 modelscope 下载 'deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B' 模型,
并将其保存到 './models' 目录下。如果下载过程中出现任何异常,
将会捕获并打印错误信息。
返回值:
- 如果下载成功,返回模型保存的目录路径。
- 如果下载失败,返回 None。
"""
try:
# 确保下载路径存在,如果不存在则创建
if not os.path.exists(cache_path):
os.makedirs(cache_path)
# 使用 snapshot_download 函数下载模型,设置下载模式为强制重新下载以获取最新版本
model_dir = snapshot_download(
model_name,
cache_dir=cache_path,
revision='master',
download_mode=DownloadMode.FORCE_REDOWNLOAD
)
print(f"模型已成功下载到: {model_dir}")
return model_dir
except Exception as e:
print(f"下载模型时出现错误: {e}")
return None
if __name__ == "__main__":
model_name = 'deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B'
cache_path = './models'
download_model(model_name,cache_path)
四、 终端输出
在PyChram添加代码:
4.1 导入必要的模块
from transformers import AutoTokenizer, AutoModelForCausalLM
import datetime
import torch
import re
注释:
transformers
是一个流行的自然语言处理库,用于加载预训练模型和分词器。AutoTokenizer
和AutoModelForCausalLM
是transformers
提供的类,分别用于加载分词器和因果语言模型。datetime
用于获取当前时间,便于记录日志。torch
是 PyTorch 深度学习框架,用于 GPU 加速和模型推理。re
是 Python 的正则表达式模块,用于文本匹配和分割。
4.2 设置设备参数
DEVICE = "cuda" # 指定使用 CUDA 设备(GPU)
DEVICE_ID = "0" # 指定 GPU 的 ID(如果有多个 GPU)
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE # 构造完整的设备路径
注释:
DEVICE
是设备类型,这里设置为"cuda"
,表示使用 GPU。DEVICE_ID
是 GPU 的编号,如果有多个 GPU,可以通过编号指定使用哪一个。CUDA_DEVICE
是完整的设备路径,格式为"cuda:0"
,表示使用第一个 GPU。
4.3 清理 GPU 内存的函数
def torch_gc():
if torch.cuda.is_available(): # 检查是否支持 CUDA
with torch.cuda.device(CUDA_DEVICE): # 指定清理的设备
torch.cuda.empty_cache() # 清空未使用的缓存
torch.cuda.ipc_collect() # 收集并释放未使用的 IPC 内存
注释:
torch_gc()
是一个清理 GPU 内存的函数。torch.cuda.empty_cache()
用于释放未使用的 GPU 缓存。torch.cuda.ipc_collect()
用于清理未使用的进程间通信(IPC)内存。
4.4 文本分割函数
def split_text(text):
pattern = re.compile(r'<think>(.*?)</think>(.*)', re.DOTALL) # 定义正则表达式模式
match = pattern.search(text) # 在文本中搜索匹配的内容
if match:
think_content = match.group(1).strip() # 提取 <think> 标签内的内容
answer_content = match.group(2).strip() # 提取剩余部分的内容
else:
think_content = "" # 如果未匹配到,返回空字符串
answer_content = text.strip() # 返回原始文本
return think_content, answer_content
注释:
split_text()
是一个用于分割生成文本的函数。- 使用正则表达式
<think>(.*?)</think>(.*)
匹配特定格式的文本。<think>
和</think>
是标签,用于标记特定内容。(.*?)
是非贪婪匹配,用于提取标签内的内容。(.*)
匹配剩余的文本。
- 如果匹配成功,提取标签内的内容和剩余部分;否则返回空字符串和原始文本。
4.5 主程序入口
if __name__ == '__main__':
model_name_or_path = './models/deepseek-ai/DeepSeek-R1-Distill-Qwen-1-5B' # 模型路径
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False) # 加载分词器
# 显式设置 pad_token_id 为 eos_token_id,避免生成时的警告
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id
注释:
model_name_or_path
是模型的路径或名称,这里指定为本地路径。AutoTokenizer.from_pretrained()
用于加载分词器。use_fast=False
表示不使用快速分词器(Rust 实现)。
pad_token_id
是填充标记的 ID,用于处理文本长度不一致的情况。- 如果
pad_token_id
未设置,则将其设置为eos_token_id
(结束标记的 ID)。
4.6 加载模型
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
device_map=CUDA_DEVICE,
torch_dtype=torch.bfloat16 # 使用 bfloat16 数据类型以节省显存
)
注释:
AutoModelForCausalLM.from_pretrained()
用于加载因果语言模型。device_map=CUDA_DEVICE
将模型加载到指定的 GPU 设备。torch_dtype=torch.bfloat16
使用bfloat16
数据类型,可以减少显存占用,同时保持一定的计算精度。
4.7 主循环:用户输入和文本生成
while True:
prompt = input("请输入提示信息(输入 'exit' 退出):") # 获取用户输入
if prompt.lower() == 'exit': # 如果输入 'exit',退出循环
break
messages = [
{"role": "user", "content": prompt} # 构造对话格式的输入
]
注释:
- 使用
while True
创建一个无限循环,等待用户输入。 - 如果用户输入
'exit'
,程序退出。 - 用户输入的内容被封装为一个字典,格式为
{"role": "user", "content": prompt}
,模拟对话输入。
4.8 构造模型输入
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([input_ids], return_tensors="pt").to(model.device)
注释:
tokenizer.apply_chat_template()
将对话格式的输入转换为模型可接受的格式。tokenize=False
表示不直接分词,而是将输入作为整体处理。add_generation_prompt=True
添加生成提示符,帮助模型更好地生成文本。
tokenizer([input_ids], return_tensors="pt")
将输入文本编码为 PyTorch 张量。.to(model.device)
将输入张量移至 GPU 设备。
4.9 文本生成
generated_ids = model.generate(
model_inputs.input_ids,
attention_mask=model_inputs.attention_mask,
max_new_tokens=8192 # 设置最大生成 token 数
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
注释:
model.generate()
是模型的核心生成函数。input_ids
是输入的 token ID。attention_mask
是注意力掩码,用于处理填充部分。max_new_tokens=8192
设置生成的最大新 token 数。
generated_ids
是生成的 token ID 列表。- 通过
tokenizer.batch_decode()
将生成的 token ID 解码为文本。
4.10 分割生成的文本并记录日志
think_content, answer_content = split_text(response)
now = datetime.datetime.now() # 获取当前时间
time = now.strftime("%Y-%m-%d %H:%M:%S") # 格式化时间
answer = {
"response": response,
"think": think_content,
"answer": answer_content,
"status": 200,
"time": time
}
log = f"[{time}], prompt:\"{prompt}\", response:\"{repr(response)}\", think:\"{think_content}\", answer:\"{answer_content}\""
print(log)
torch_gc() # 清理 GPU 内存
注释:
- 使用
split_text()
函数分割生成的文本,提取特定内容。 - 获取当前时间并格式化为字符串。
- 构造日志信息,记录用户输入、生成的文本、分割的内容和时间。
- 使用
torch_gc()
清理 GPU 内存,避免内存泄漏。
输入效果:
五、 流式输出
代码实现五个部分:
-
加载模型和分词器:
- 使用
AutoTokenizer
和AutoModelForCausalLM
加载预训练的分词器和模型。 - 显式设置
pad_token_id
,避免生成时的警告。
- 使用
-
流式文本生成:
- 使用
TextIteratorStreamer
实现流式输出。 - 在单独的线程中调用
model.generate
,避免阻塞主线程。
- 使用
-
文本处理:
- 使用正则表达式分割生成的文本,提取特定内容。
-
日志记录:
- 记录用户输入、生成的文本、分割的内容和时间。
-
GPU 内存管理:
- 在每次生成后清理 GPU 内存,避免内存泄漏。
from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
import datetime
import torch
import re
import logging
from threading import Thread
# 设置日志级别,隐藏 transformers 的警告信息
logging.getLogger("transformers").setLevel(logging.ERROR)
# 设置设备参数
DEVICE = "cuda" # 使用 CUDA 设备(GPU)
DEVICE_ID = "0" # 指定 GPU 的 ID
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE # 构造完整的设备路径
# 清理 GPU 内存的函数
def torch_gc():
if torch.cuda.is_available(): # 检查是否支持 CUDA
with torch.cuda.device(CUDA_DEVICE): # 指定清理的设备
torch.cuda.empty_cache() # 清空未使用的缓存
torch.cuda.ipc_collect() # 收集并释放未使用的 IPC 内存
# 文本分割函数
def split_text(text):
pattern = re.compile(r'<think>(.*?)</think>(.*)', re.DOTALL) # 定义正则表达式模式
match = pattern.search(text) # 在文本中搜索匹配的内容
if match:
think_content = match.group(1).strip() # 提取 <think> 标签内的内容
answer_content = match.group(2).strip() # 提取剩余部分的内容
else:
think_content = "" # 如果未匹配到,返回空字符串
answer_content = text.strip() # 返回原始文本
return think_content, answer_content
if __name__ == '__main__':
model_name_or_path = './models/deepseek-ai/DeepSeek-R1-Distill-Qwen-1-5B' # 模型路径
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=True) # 使用快速分词器
# 显式设置 pad_token_id
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id[0] if isinstance(tokenizer.eos_token_id, list) else tokenizer.eos_token_id
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
device_map=CUDA_DEVICE,
torch_dtype=torch.bfloat16 # 使用 bfloat16 数据类型以节省显存
)
while True:
prompt = input("请输入提示信息(输入 'exit' 退出):") # 获取用户输入
if prompt.lower() == 'exit': # 如果输入 'exit',退出循环
break
messages = [
{"role": "user", "content": prompt} # 构造对话格式的输入
]
input_ids = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
) # 将对话格式转换为模型输入
model_inputs = tokenizer([input_ids], return_tensors="pt").to(model.device) # 编码输入并移至 GPU
# 创建流式输出器
streamer = TextIteratorStreamer(
tokenizer,
skip_prompt=True, # 跳过提示文本
skip_special_tokens=True # 忽略特殊标记
)
# 使用线程来生成文本
generation_kwargs = dict(model_inputs, streamer=streamer, max_new_tokens=8192) # 设置生成参数
thread = Thread(target=model.generate, kwargs=generation_kwargs) # 在单独的线程中调用生成函数
thread.start()
# 逐步输出生成的文本
generated_text = ""
print("模型输出:", end="", flush=True)
for new_text in streamer: # 从 streamer 中逐段获取生成的文本
generated_text += new_text
print(new_text, end="", flush=True) # 逐步打印生成的文本
# 处理生成的文本
think_content, answer_content = split_text(generated_text) # 分割生成的文本
now = datetime.datetime.now() # 获取当前时间
time = now.strftime("%Y-%m-%d %H:%M:%S") # 格式化时间
answer = {
"response": generated_text,
"think": think_content,
"answer": answer_content,
"status": 200,
"time": time
}
log = f"[{time}], prompt:\"{prompt}\", response:\"{repr(generated_text)}\", think:\"{think_content}\", answer:\"{answer_content}\""
print("\n" + log) # 打印日志信息
torch_gc() # 清理 GPU 内存
实现效果如下:
DeepSeek-R1代码部署流式输出
备注:
源码下载地址:https://download.csdn.net/download/matt45m/90439745