当前位置: 首页 > article >正文

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 是一个流行的自然语言处理库,用于加载预训练模型和分词器。
  • AutoTokenizerAutoModelForCausalLMtransformers 提供的类,分别用于加载分词器和因果语言模型。
  • 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 内存,避免内存泄漏。

输入效果:
在这里插入图片描述

五、 流式输出

代码实现五个部分:

  1. 加载模型和分词器

    • 使用 AutoTokenizerAutoModelForCausalLM 加载预训练的分词器和模型。
    • 显式设置 pad_token_id,避免生成时的警告。
  2. 流式文本生成

    • 使用 TextIteratorStreamer 实现流式输出。
    • 在单独的线程中调用 model.generate,避免阻塞主线程。
  3. 文本处理

    • 使用正则表达式分割生成的文本,提取特定内容。
  4. 日志记录

    • 记录用户输入、生成的文本、分割的内容和时间。
  5. 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


http://www.kler.cn/a/568598.html

相关文章:

  • 青海高校迎新系统的实施与影响
  • Qwen2-Audio系列学习笔记
  • TrustRAG:通过配置化模块化的检索增强生成(RAG)框架提高生成结果的可靠性和可追溯性
  • HIVE数据加载
  • LeetCode 202. 快乐数 java题解
  • uniapp 中引入使用uView UI
  • 前端文件分片上传深度解析:从原理到实践
  • 大模型微调入门(Transformers + Pytorch)
  • YOLOv8目标检测推理流程及C++代码
  • 5分钟看懂Deepseek开源周之六:Deepseek-V3/R1推理系统设计----揭开深度求索模型系统设计和运营成本之谜
  • 河南理工XCPC萌新选拔赛
  • 蓝桥杯备赛-前缀和-可获得的最小取值
  • fiscoBcos中手动部署webase-front
  • 《白帽子讲 Web 安全》之移动 Web 安全
  • 分布式微服务系统架构第92集:智能健康监测设备Java开发方案
  • 【Java项目】基于SpringBoot的地方废物回收机构管理系统
  • centos 下dockers部署surveyking-docker开源考试系统
  • 算法分析 —— 《栈》
  • Android6到Android15版本新增的功能和api
  • 2025-03-01 学习记录--C/C++-C语言 整数类型对比