华为昇腾服务器部署DeepSeek模型实战

发布于:2025-02-19 ⋅ 阅读:(24) ⋅ 点赞:(0)

在华为的昇腾服务器上部署了DeepSeek R1的模型进行验证测试,记录一下相关的过程。服务器是配置了8块910B3的显卡,每块显卡有64GB显存,根据DeepSeek R1各个模型的参数计算,如果部署R1的Qwen 14B版本,需要1张显卡,如果是32B版本,需要2张,Llama 70B的模型需要4张显卡。如果是R1全参数版本,则需要32张显卡,也就是4台满配的昇腾服务器。这里先选择32B的模型进行部署测试,等之后申请更多的算力资源之后再测试部署全参数版本。另外除了部署32B之外,为了能更好地使用DeepSeek,还部署了一个语义嵌入向量模型Bge-zh和向量数据库Chromadb,用于对文本进行编码,方便构建知识库,以及部署了Open Webui服务,使得用户可以访问Webui的方式来使用DeepSeek。

DeepSeek的部署

首先简单介绍一下DeepSeek模型的一些技术特点,包括了

  1. 基于Transformer搭建的深度神经网络,可以实现高效的语义理解和文本生成能力。
  2. 改进的Transformer多头注意力MHA架构,提出了多头潜在注意力MLA,实现对K,V向量的低秩分解,降低了训练和推理中的算力需求。
  3. 混合专家结构,DeepSeek大模型由多个小型专家网络模型组成,不同的专家网络为不同的场景领域进行优化训练,在接受任务时根据任务类型自动路由到特定的专家网络进行推理,有效节省了对算力的需求。DeepSeek大模型全参数版本由6710亿参数,但推理时只需要用到特定专家网络的370亿参数。
  4. 强化学习技术,通过在V3大模型上采用改进后的GPRO优化算法以及基于规则的奖励系统进行强化学习,可以有效地训练模型使用长思维链方式来增加其推理思考能力。
  5. 知识蒸馏技术,把R1大模型生成的预料数据,对其他较小参数量的大模型如LLAMA或阿里通义千问进行知识蒸馏训练,有效提高了这些大模型的性能,实现了在相同参数量技术上对原有性能的提高。

部署过程是,首先要在华为官网下载相应的MindIE镜像,这个网页有产品版本信息-版本说明-MindIE1.0.0开发文档-昇腾社区相关的介绍,在模型库-ModelZoo-昇腾这个网站上选择我们需要的模型,然后会介绍相应的镜像版本。对于DeepSeek-R1-Distill-Qwen系列的模型,对应910显卡的是1.0.0-800I-A2-py311-openeuler24.03-lts这个镜像版本。需要注意的是,这个镜像有ARM64和X86_64两个版本,因为我需要现在本地笔记本电脑下载镜像再上传到服务器,所以需要先在服务器上通过命令uname -m来查看服务器的架构,如果是ARM64的架构,在本地笔记本下载镜像的时候需要在docker pull后面加上--platform=linux/aarch64。

下一步是下载模型的权重文件,我们可以在hf-mirror.com这个网站上找到对应的权重文件,下载到本地。我选择的是DeepSeek-R1-Distill-Qwen-32B的模型。

然后在服务器端我们可以加载镜像运行容器了,例如以下的命令

docker run -it -d --net=host --shm-size=1g  --network=llm-network -p 9999:1025 --privileged     --name deepseek-qwen-32b     --device=/dev/davinci_manager     --device=/dev/hisi_hdc     --device=/dev/devmm_svm     -v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro     -v /usr/local/sbin:/usr/local/sbin:ro     -v /data/models/DeepSeek-R1-Distill-Qwen-32B:/model:rw     swr.cn-south-1.myhuaweicloud.com/ascendhub/mindie:1.0.0-800I-A2-py311-openeuler24.03-lts bash

容器启动后,我们通过命令登录到容器上,修改Config.json配置文件。

docker exec -it deepseek-qwen-32b bash
chmod 750 /model
vim /usr/local/Ascend/mindie/latest/mindie-service/conf/config.json

修改以下位置

{
    ...
    "ServerConfig" :
    {
        "ipAddress" : "0.0.0.0",
        "allowAllZeroIpListening" : true,
        "httpsEnabled" : false,
        ...
    }
    "BackendConfig" : {
        "npuDeviceIds" : [[0,1]],
        "ModelDeployConfig" :
        {
            "maxSeqLen" : 8192,
            "maxInputTokenLen" : 4096,
            "truncation" : false,
            "ModelConfig" : [
                {
                    "modelInstanceType" : "Standard",
                    "modelName" : "DeepSeek-R1-Distill-Qwen-32B",
                    "modelWeightPath" : "/model",
                    "worldSize" : 2,
                    "cpuMemSize" : 5,
                    "npuMemSize" : -1,
                    "backendType" : "atb",
                    "trustRemoteCode" : false
                }
            ]
        },

        "ScheduleConfig" :
        {
            ...
            "maxIterTimes" : 8192,
        }
    }
}

在以上配置中,我设置了模型部署在2张910 NPU卡上,然后调整了maxSeqLen,maxInputTokenLen,maxIterTimes这几个参数。原来的默认值maxSeqLen=2560,maxInputTokenLen=2048,我理解对应的意思上输入Token不能超过2048,然后输出Token不能超过2560-2048=512个,如果要调大输出的token数,也要相应做出调整,但是好像maxIterTimes这个参数也要做相应调整,按照文档意思这是控制一句话的最大长度的。

最后就是启动推理服务,提供服务化API接口给外部应用调用。

cd /usr/local/Ascend/mindie/latest/mindie-service/bin
./mindieservice_daemon

API接口提供了兼容OpenAI的格式,所以服务启动后我们可以调用接口测试一下

curl -X POST "http://127.0.0.1:1025/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
"model": "DeepSeek-R1-Distill-Qwen-32B",
"max_tokens": 3000,
"stream": false,
"temperature": 0.6,
"messages": [
{"role": "user", "content": "Please describe guangzhou city in about 2000 words"}
]
}'

注意这里设置了Temperature为0.6,以及在Message里面没有设置System Prompt,这是根据官网上的说明来设置的:

  1. Set the temperature within the range of 0.5-0.7 (0.6 is recommended) to prevent endless repetitions or incoherent outputs.
  2. Avoid adding a system prompt; all instructions should be contained within the user prompt.

向量嵌入模型的部署

现在要部署一个向量嵌入模型来配合知识库的使用。在MindIE的网站提到了可以适配HuggingFace的Text Embeddings Interface框架,见网页功能介绍-TEI-MindIE开源第三方服务化框架适配开发指南-服务化集成部署-MindIE1.0.0开发文档-昇腾社区

但是这个TEI的接口和OpenAI或或者Ollama的embedding接口不兼容,因为我想部署Open Webui来对接,Open webui无法调用TEI,因此我还不能直接采取部署TEI镜像的方式来启动Embedding服务。

在MindIE镜像的/usr/local/Ascend/atb-models/examples/models目录下,有介绍当前适配的一些模型,其中我们可以找到Bge这个目录,这是对应bge-zh这个embedding模型的。因此我尝试加载这个模型。同样在hf-mirror网站上找到bge-large-zh-v1.5模型的权重并下载,然后启动一个新的容器,如以下命令

docker run -it -d --net=host --shm-size=1g  --network=llm-network  -p 9998:8088 --privileged     --name bge-zh     --device=/dev/davinci_manager     --device=/dev/hisi_hdc     --device=/dev/devmm_svm     -v /usr/local/Ascend/driver:/usr/local/Ascend/driver:ro     -v /usr/local/sbin:/usr/local/sbin:ro     -v /data/models/bge-base-zh-v1.5:/model:rw     swr.cn-south-1.myhuaweicloud.com/ascendhub/mindie:1.0.0-800I-A2-py311-openeuler24.03-lts bash

容器启动后,进入容器

docker exec -it bge-zh bash
chmod 750 /model
source /usr/local/Ascend/ascend-toolkit/set_env.sh

下载的模型权重都放置在容器的/model目录下,然后调用模型的脚本文件是放置在容器的/usr/local/Ascend/atb-models/examples/models/embeddings目录下,参考/model/config.json中的model_type的值,用脚本文件目录/usr/local/Ascend/atb-models/examples/models/embeddings/bert的'modeling_bert.py`替换下载模型权重的 `modeling_bert.py'

修改模型权重配置文件/model/config.json,修改_name_or_path的值指向/model,设置auto_map的值,修改后的配置如下:

{
  "_name_or_path": "/model",
  "auto_map": {
    "AutoModel": "/model--modeling_bert.BertModel"
  },
  ...
}

完成后即可在脚本文件目录下运行以下命令来测试

python run.py \
  embed \
  --model_name_or_path=/model \
  --trust_remote_code \
  --device_type=npu \
  --device_id=3 \
  --text='this is a test'

可以看到能成功输出Embedding后的向量。

之后就是增加一个和Ollama兼容的embed接口,这里我采用fastapi来做,新建一个名为embed_api.py文件,内容如下:

import time
from typing import Any, List, Union

import torch
from transformers.tokenization_utils_base import BatchEncoding

from atb_llm.utils.log.logging import logger, message_filter
from atb_llm.utils.log.error_code import ErrorCode

from model_runner import ModelRunner, Arguments

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder

import uvicorn
from pydantic import BaseModel

app = FastAPI()

class Runner:
    def __init__(self, **kwargs: Any) -> None:
        self.model = ModelRunner(
            str(kwargs.pop("model_name_or_path", None)),
            bool(kwargs.pop("trust_remote_code", True)),
            getattr(torch, kwargs.pop("torch_dtype", "float16")),
            torch.device(f"{kwargs.pop('device_type', 'cpu')}:{str(kwargs.pop('device_id', 0))}"),
            str(kwargs.pop("model_type", None))
        )

        self.model_name_or_path = self.model.model_name_or_path
        self.torch_dtype = self.model.torch_dtype
        self.device = self.model.device
        #self.device = 'npu:4'
        self.model_type = self.model.model_type
        self.config = self.model.config
        self.tokenizer = self.model.tokenizer

        self.max_batch_size = kwargs.pop("max_batch_size", 1)
        self.max_seq_len = kwargs.pop("max_seq_len", self.tokenizer.model_max_length)
        self.return_tensors = kwargs.pop("return_tensors", "pt")

        self.tokenizer_args = {
            "padding": kwargs.pop("padding", "max_length"),
            "truncation": kwargs.pop("truncation", True),
            "return_tensors": self.return_tensors,
            "max_length": self.max_seq_len,
        }

    def warm_up(self) -> None:
        inputs = self.model.generate_inputs(
            self.tokenizer.model_input_names,
            self.tokenizer.vocab_size,
            self.max_batch_size,
            self.max_seq_len,
            self.device
        )
        encoded_inputs = BatchEncoding(inputs, tensor_type=self.return_tensors)

        logger.info("---------------begin warm_up---------------")
        tick = time.perf_counter()
        model_outputs = self.model.forward(encoded_inputs)
        torch.nn.functional.normalize(model_outputs[0]).view(-1, ).float().cpu()
        tock = time.perf_counter()
        logger.info("---------------end warm_up---------------")
        logger.info(f"warm time: {(tock - tick) * 1000:.2f} ms")

    def embed(self, texts: Union[str, List[str], List[List[str]]]) -> torch.Tensor:
        encoded_inputs = self.model.tokenize(texts, **self.tokenizer_args)
        logger.info("---------------begin embed---------------")
        tick = time.perf_counter()
        embeddings = self.model.embed(encoded_inputs)
        tock = time.perf_counter()
        logger.info("---------------end embed---------------")
        logger.info(f"embed time: {(tock - tick) * 1000:.2f} ms")
        return embeddings

    def rerank(self, texts: Union[str, List[str], List[List[str]]]) -> torch.Tensor:
        encoded_inputs = self.model.tokenize(texts, **self.tokenizer_args)
        logger.info("---------------begin rerank---------------")
        tick = time.perf_counter()
        scores = self.model.rerank(encoded_inputs)
        tock = time.perf_counter()
        logger.info("---------------end rerank---------------")
        logger.info(f"rerank time: {(tock - tick) * 1000:.2f} ms")
        return scores

class EmbedRequest(BaseModel):
    model: str
    input: Union[str, List[str]]

runner = None

@app.on_event("startup")
async def startup_event():
    global runner
    runner = Runner(
        model_name_or_path='/model', 
        device_type='npu', 
        device_id=4, 
        trust_remote_code=True, 
        model_type='float',
        torch_dtype='float16',
        padding='max_length',
        truncation=True,
        return_tensors='pt',
        request='embed'
        )

@app.post("/embed")
async def embed_text(request: EmbedRequest):
    results = runner.embed(request.input)
    results_list = results.tolist()

    json_compatible_data = jsonable_encoder(results_list)

    return {
        "embeddings": json_compatible_data
    }

if __name__ == "__main__":
    uvicorn.run("embed_api:app", host="0.0.0.0", port=8088, reload=True, log_level="info")

运行这个文件即可提供和Ollama兼容的embed API服务。

Open Webui的部署

最后可以部署一个Web服务,来提供对Deepseek服务的调用了。

Open WebUI 是一个开源的、功能丰富且用户友好的自托管 AI 平台,支持调用 Ollama 和 OpenAI 兼容的 API来使用大语言模型,并内置了用于检索增强生成(RAG)的推理引擎。

下载ARM64平台的Open webui镜像

docker pull ghcr.io/open-webui/open-webui:main --platform=linux/aarch64

在服务器建立一个open_webui的目录,并新建一个.env配置文件,内容如下

OPENAI_API_BASE_URL='http://deepseek-qwen-32b:1025/v1'
OPENAI_API_KEY=''

ENABLE_OLLAMA_API=false
HF_HUB_OFFLINE=1

然后通过命令启动

docker run -d -p 80:8080 --network=llm-network -v /data/open_webui:/app/backend/data -v /data/open_webui/.env:/app/.env --name open-webui ghcr.io/open-webui/open-webui:main

题外话:

因为服务器是在内网,需要通过Nginx转发才能访问Open webui服务,最开始调用Open webui始终有问题,页面加载后显示空白,看了一下浏览器开发者工具的日志,提示是_app目录下的js文件路径不对,看了一下open webui的代码,发现这些路径都是通过href='/_app/xxx.js'这种绝对路径的方式来写的,因此在Nginx下面需要配置一下路径改写。配置后可以成功访问页面,但是在聊天窗口调用Deepseek又出现问题,一直停留在等待回复的状态,看了一下日志,发现很多web socket的链接被关掉了,怀疑是因为Nginx没有配置http 1.1版本导致的,在Nginx中增加以下配置即可。

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";


网站公告

今日签到

点亮在社区的每一天
去签到