借助DeepSeek之东风扬帆AI海洋(三):打造高可用大模型集群

8 阅读17分钟

# 借助DeepSeek之东风扬帆AI海洋(二):无GPU服务器部署DeepSeek

介绍一种相对简单且在业界进行私有化模型的 AI 应用开发时极为常用的方案,即通过 LobeChat + 网关 + Ollama 打造高可用的部署方案。

什么是 Ollama ?

首先来了解一下 Ollama。Ollama 是一个专为在本地机器上便捷部署和运行大型语言模型(LLM)而设计的开源框架,它可以用简单的命令行快捷部署多种大模型,例如 DeepSeek、Qwen、Llama3 等等模型。

Ollama 自身还会通过权重量化技术,调整模型权重,并通过分块加载与缓存机制以及 GPU/CPU 灵活调度等技术,使得模型能够降低对硬件的要求,提高资源利用率。以 DeepSeek-R1 的蒸馏模型 DeepSeek-R1-Distill-Qwen-7B 为例,最小也需要 14 G 的显存。但 Ollama 通过对模型的量化,可以显著降低对于显存的占用。

什么是量化

举一个简单的例子做一下类比。我们在使用微信时,经常会互相发一些图片。如果我们在收到图片时不点下载原图,图片画质就不清晰(分辨率低),但图片大小可能只有几百 K,占用存储空间小;如果我们点了下载原图,图片更加清晰(分辨率高),但占用存储空间大。原版模型和量化模型之间的关系呢,大概就类似于原图和非原图之间的关系,如果为了节省显存,就需要对模型进行量化。

Ollama 有一个非常出色的特性,这也是众多开发者选择它的关键原因,即 Ollama 为所有支持的模型封装了统一的 API,并且兼容 OpenAI 数据格式。这一点至关重要,由于模型是由不同公司或团队训练的,每种模型原本都提供各自的开发接口。因此,Ollama 进行统一封装后,用户在使用时就变得极为便捷。

比如,编写 Agent 代码时,就会把标准的 OpenAI SDK 的 base_url 参数和模型名称做出修改。同样地,如果通过 Ollama 访问 DeepSeek,只需将 base_url 修改为 Ollama 的地址即可。后期如果需要切换到 Qwen 模型,也无需再修改 base_url,只需更换模型名称即可。

client = OpenAI(
    api_key=os.getenv("AliDeep"),  
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

Ollama 部署 DeepSeek-R1

环境准备

为了简单,以本机安装作为演示。操作系统是 Windows 10。使用的是 Ollama 部署,因此我们可以以 7B 模型为例做演示。只要你学会了 7B 模型的部署,之后不管是部署 1.5B 还是 671B,方法都一模一样,没有任何区别。

  • CPU:Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz 3.60 GHz
  • 内存:32.0 GB
  • 显卡:七彩虹 Nvidia GeForce RTX 4070 Ti 12GB

首先使用 nvidia-smi 命令,确认 GPU 卡的驱动已经装好,可以被识别。

root@aitest:~# nvidia-smi                                                                                                                                                
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 572.83                 Driver Version: 572.83         CUDA Version: 12.8     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                  Driver-Model | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4070 Ti   WDDM  |   00000000:01:00.0  On |                  N/A |
|  0%   41C    P8             10W /  285W |    2084MiB /  12282MiB |      1%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI              PID   Type   Process name                        GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|                                                                                       
|  No running processes found                                                             |                                                                                          
+-----------------------------------------------------------------------------------------+ 

安装 Ollama

用官方推荐的 Docker 方式部署 Ollama,便于进行版本的管理与测试。

首先将 Ollama 镜像下载到本地,由于国内无法访问 DockerHub,因此可以使用后面命令中的代理地址访问:

docker pull docker.1ms.run/ollama/ollama:0.5.11

之后使用命令启动 Ollama 容器:

docker run -dp 8880:11434 --gpus device=0 --name DeepSeek-R1-1 docker.1ms.run/ollama/ollama:0.5.11

这条命令的意思是,首先将容器的 11434 端口映射到宿主机的 8880 端口,11434 即 Ollama 提供 API 访问的端口。–gpus device=0 表示该容器要使用 GPU 0 号卡,怎么知道 GPU 卡的编号的呢?是在前面执行 nvidia-smi 命令时知道的。

根据前面 nvidia-smi 命令的执行结果,显卡的编号分别是 0。–name DeepSeek-R1-1 是给容器起一个名字,叫任何名字都可以,只要你喜欢,建议取个有意义的名字,比如 DeepSeek-R1-1。最后是下载下来的容器镜像的名称。

如果一切正常,启动后,就会返回容器的 ID。接着我们通过 docker ps 命令,就可以查询到容器的信息。

root@aitest:~# docker run -dp 8880:11434 --gpus device=0 --name DeepSeek-R1-1 docker.1ms.run/ollama/ollama:latest                                                        
7049f65fd9d34392c26355174f4701c1b2c5aa718cdc168d19a982f0519d7635

root@aitest:~# docker ps                                                                                                                                                 
CONTAINER ID   IMAGE                                 COMMAND               CREATED          STATUS         PORTS                                           NAMES         
7049f65fd9d3   docker.1ms.run/ollama/ollama:0.5.11   "/bin/ollama serve"   10 seconds ago   Up 9 seconds   0.0.0.0:8880->11434/tcp, [::]:8880->11434/tcp   DeepSeek-R1-1

这一步,经常会报如下的错误:

root@aitest:~# docker run -dp 8880:11434 --gpus device=0 --name DeepSeek-R1-1 docker.1ms.run/ollama/ollama:0.5.11
bd74646a26c6a539aa7660eb664caa585dba188720f0975701b73d87263b6cdf                                                                                                         
docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]]. 

这是因为服务器的 NVIDIA Container Toolkit 没有装,需要执行如下命令安装一下:

Windows 安装 nvidia-container-toolkit

在 Windows 上安装 NVIDIA Container Toolkit 的步骤如下:

准备工作

  1. 安装 Docker Desktop:从官网下载并安装 Docker Desktop for Windows。
  2. 检查并安装 Hyper-V:
    • 在“开始”菜单中搜索并打开“启用或关闭 Windows 功能”。
    • 勾选“Hyper-V”和“Windows 虚拟机平台”,然后重启电脑。
  3. 安装并配置正确的 NVIDIA 驱动程序:
    • 在“开始”菜单中搜索并打开“命令提示符”,输入并运行以下命令以检查是否已安装正确的 NVIDIA 驱动程序: nvidia-smi
    • 如果未安装驱动程序或版本过旧,可前往 NVIDIA 官方网站下载并安装最新的驱动程序。
  4. 启用虚拟化功能:如果未启用虚拟化功能,可以在 BIOS 中启用,或者在 Windows 中以管理员身份运行 PowerShell 并执行以下命令:
    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
    
  5. 安装 WSL 2:
    • 查看 WSL 版本: wsl -l -v
    • 如果需要安装 WSL 2,可运行以下命令: wsl --install
    • 安装 Ubuntu 20.04: wsl --install -d Ubuntu-20.04
    • 安装完成后,设置用户名和密码。

安装 NVIDIA Container Toolkit

  1. 在 WSL 2 的 Ubuntu 环境中打开终端,然后执行以下命令以添加 NVIDIA Container Toolkit 的仓库密钥和源列表:
    curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
      && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
        sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
        sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
    
  2. 可选步骤:如果需要使用实验性功能,可以运行以下命令: sed -i -e '/experimental/ s/^#//g' /etc/apt/sources.list.d/nvidia-container-toolkit.list
  3. 更新包列表并安装 NVIDIA Container Toolkit:
    sudo apt-get update
    sudo apt-get install -y nvidia-container-toolkit
    
    如果出现错误提示,可尝试重启电脑。

配置 Docker

  1. 配置 Docker 守护程序以使用 NVIDIA Container Runtime: sudo nvidia-ctk runtime configure --runtime=docker
  2. 重启 Docker 服务: sudo systemctl restart docker

Docker Desktop 设置:

  • 进入 Settings > General,确保 Use the WSL 2 based engine 已启用。
  • 进入 Settings > Resources > WSL Integration,启用与你的 WSL 2 发行版的集成(例如 Ubuntu)。

验证安装

  1. 验证 NVIDIA Container Toolkit 是否正确安装: nvidia-container-cli --version
  2. 测试是否能够使用 GPU 运行容器: docker run --rm --gpus all nvidia/cuda:12.0-base nvidia-smi 如果成功显示 GPU 信息,则说明 NVIDIA Container Toolkit 已正确安装。
Linux 安装 nvidia-container-toolkit

以Ubuntu为例,执行以下命令:

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
apt-get update
apt-get install -y nvidia-docker2
systemctl restart docker

安装完成后,执行 docker info 命令,确保 docker 守护进程已经正确配置 GPU 支持。命令和输出如下:

root@aitest:~# docker info | grep -i nvidia                                                                                                                              
 Runtimes: io.containerd.runc.v2 nvidia runc

此时再重新 docker run,就可以将 Ollama 容器拉起了。

部署与测试 DeepSeek-R1

Ollama 官方提供了一个可视化的模型仓库,便于我们了解 Ollama 已经支持了哪些模型,以及下载模型。仓库的地址是:ollama.com/library

介绍两种 Ollama 拉起 DeepSeek-R1 的方案。

在容器内下载模型和拉起

需要进入到 Ollama 容器的内部,去执行模型运行命令。进入容器的命令为:

docker exec -it <你的容器名称或 ID> /bin/bash

然后执行模型运行命令:

ollama run deepseek-r1:32b

由于本地没有模型,所以,会先在模型仓库下载模型,我们需要耐心等待模型下载完毕。

下载完成后,就会加载模型,直到出现 success,模型就加载好了。

将模型文件挂载进容器(推荐)

执行如下命令,直接将模型下载到本地:

ollama pull deepseek-r1:32b

启动容器,将模型挂载进去,需要注意 Ollama 在 windows的目录是/Users/[replace your username]/.ollama,如果是 ubuntu 服务器上的默认是模型文件存放目录 /usr/share/ollama/.ollama/models,但是在容器中的目录是 /root/.ollama/models,挂载时要写成服务器目录: 容器目录的格式,注意不要写反了。

docker run -dp 8880:11434 --runtime=nvidia --gpus device=0 --name DeepSeek-R1-1 -v /Users/username/.ollama/models:/root/.ollama/models
:/root/.ollama/models docker.1ms.run/ollama/ollama:0.5.11

最后查看容器中的模型是否已经运行:

root@aitest:~# docker ps                                                                                                                                                 
CONTAINER ID   IMAGE                                 COMMAND               CREATED         STATUS         PORTS                                           NAMES          
9a44f04e78f7   docker.1ms.run/ollama/ollama:0.5.11   "/bin/ollama serve"   6 minutes ago   Up 6 minutes   0.0.0.0:8880->11434/tcp, [::]:8880->11434/tcp   DeepSeek-R1-1  

root@aitest:~# docker exec -it 9a44f04e78f7 ollama list                                                                                                                  
NAME               ID              SIZE     MODIFIED                                                                                                                     
deepseek-r1:32b    38056bbcbb2d    19 GB    About an hour ago

模型为什么不思考?

在上文中,我们问 “hello”,DeepSeek 并没有思考。但是我们用官方版本的去问同样的问题,会发现,官方版的无论简单问题还是复杂问题都会思考:

这是什么原因呢?我们使用如下命令看一下 deepseek-r1:32b 的聊天模板。

ollama show --modelfile deepseek-r1:32b

可以看到在 <| Assistant| > 后面直接就是跟的 Content,没有可以强调 的事情,因此就相当于把是否 think 的主动权交给大模型了。这个其实是对的,如果每一个问题无论简单与否都要思考,会劳民伤财,费时费钱。但这个事情反过来看,如果大模型的能力不能达到极致,在遇到我们想让它思考的问题时,它不思考,体验性又不好。所以这也是官方版本做了强制 think 的原因吧。

如何像官方一样强制 think 呢?其实很简单,按官方教程描述,只需要加一个 think 标签即可。

操作步骤是这样的。首先将聊天模板保存下来:

ollama show --modelfile deepseek-r1:32b > Modelfile

之后,在 <| Assistant| > 之后加上 \n,如下图所示:

之后基于 Modelfile 创建一个新的模型:

ollama create deepseek-r1-think:32b -f ./Modelfile

这时,再按照前面讲的将模型文件挂载进容器的启动方式,启动容器。

进入容器后,执行一下 ollama list,可以看到现在有两个模型了。

使用如下命令,将模型 deepseek-r1-think:32b 运行起来,然后测试:

ollama run deepseek-r1-think:32b

API 访问

由于 Ollama 的 API 兼容了 OpenAI 的数据结构,因此测试非常简单,直接 curl 一下即可:

Windows下可以用postman或者Apifox

Ubuntu环境下的 curl 命令:

curl http://localhost:8880/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
    "model": "deepseek-r1-think:32b",
  "messages": [
    {"role": "user", "content": "hello"}
  ]
}'

Ollama 实例的负载均衡的配置

网关选型

根据我们的部署架构图,接下来就是 API 网关的选型了。在传统的业务中,我们经常使用 Nginx 等作为流量转发和负载均衡网关。这对于单次 HTTP 连接时间短,单次请求数据量小的场景来说没有问题。但在 AI 时代,网关后端对接的不再是传统的 HTTP Server,而是大模型。

如果只是简单测试一下负载均衡效果,那使用 Nginx 完全 OK。但如果是真正的实现生产级别的应用,那么我们就需要针对 AI 时代的新挑战而使用 AI 网关,比如 apache-apisix、Kong、Higress 等,关于这几个网关的选择,可以自行网上搜索比较下,各有优缺点,选择合适的即可。

AI 时代给网关带来的挑战

AI 时代给网关带来了哪些新挑战呢?

  • 第一,服务连续性。LLM 应用通常需要较长的内容生成时间,用户体验很大程度上依赖于对话的连续性。因此,如何在后端系统更新时保持服务不中断成为了一个关键问题。
  • 第二,资源安全。与传统应用相比,LLM 服务处理单个请求时需要消耗大量计算资源。这种特性使得服务特别容易受到攻击,攻击者可以用很小的成本发起请求,却会给服务器带来巨大负担。如何保障后端系统的稳定性变得尤为重要。
  • 第三,商业模式保护。很多 AGI 企业会提供免费调用额度来吸引用户,但这也带来了风险,部分黑灰产会利用这些免费额度封装成收费 API 牟利。如何防范这类商业损失是一个现实问题。
  • 第四,内容安全。不同于传统 Web 应用的简单信息匹配,LLM 应用通过 AI 推理来生成内容。如何确保这些自动生成的内容安全合规,需要特别关注。
  • 第五,多模型管理。当我们需要接入多个大模型时,如何统一管理不同厂商的 API,降低开发和维护成本也是一个重要课题。

在网关的流量层面,AI 时代还面临着三大新需求,分别是长连接、高延时和大带宽,我整理了一张表格描述每个需求的特点。

需求描述
长连接长连接允许客户端在连接建立后,持续保持与后端服务器的连接,直到客户端显式地断开连接。这种连接方式允许客户端在连接建立后,持续发送请求,而不需要每次请求都建立新的连接。
高延时高延时是指客户端与后端服务器之间的网络延迟时间较长。这种延迟时间通常在 100ms 到 500ms 之间。高延时对客户端的用户体验影响较大,用户在等待时间过长时,可能会放弃使用服务。因此,需要设计网关以减少客户端等待时间。
大带宽大带宽是指网关处理请求的带宽能力非常大。这种带宽能力通常在 10Gbps 到 100Gbps 之间。大带宽对网关的性能和吞吐量有 significant 的影响。因此,需要设计网

分布式部署演示

这边为了方便,使用apache-apisix作为网关,然后使用2台intel版本的mac mini作为演示。前端应用采用LobeChat作为演示。

安装apache-apisix

以docker部署作为演示,因为只是演示,这个可以部署在上面2台mac mini上,也可以单独使用一台机器。

docker-compose.yaml

services:
  etcd:
    image: bitnami/etcd:3.5.11
    restart: always
    volumes:
      - etcd_data:/bitnami/etcd
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://etcd:2379"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
    ports:
      - "2379:2379/tcp"
    networks:
      apisix:

  apisix:
    image: apache/apisix:3.11.0-debian
    restart: always
    volumes:
      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
    ports:
      - "9080:9080"  # 代理端口
      - "9180:9180"  # 管理 API 端口
    environment:
      APISIX_DEPLOYMENT: "traditional"
      APISIX_NGINX_WORKER_PROCESSES: "1"
    depends_on:
      - etcd
    networks:
      apisix:

  apisix-dashboard:
    image: apache/apisix-dashboard:3.0.0-alpine
    restart: always
    ports:
      - "9000:9000"  # Dashboard 端口
    environment:
      APISIX_DEPLOYMENT: "traditional"
      APISIX_ADMIN_API_URL: "http://apisix:9180/apisix/admin"
      APISIX_ADMIN_API_KEY: "edd1c9f034335f136f87ad84b625c8f1"
    volumes:
      - ./apisix-dashboard-conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml:ro
    depends_on:
      - apisix
    networks:
      apisix:

volumes:
  etcd_data:
    driver: local

networks:
  apisix:
    driver: bridge

apisix_conf/config.yaml

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

apisix:
  node_listen: 9080              # APISIX listening port
  enable_ipv6: false

  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092

deployment:
  admin:
    allow_admin:               # https://nginx.org/en/docs/http/ngx_http_access_module.html#allow
      - 0.0.0.0/0              # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

    admin_key:
      - name: "admin"
        key: edd1c9f034335f136f87ad84b625c8f1
        role: admin                 # admin: manage all configuration data

      - name: "viewer"
        key: 4054f7cf07e344346cd3f287985e76a2
        role: viewer

  etcd:
    host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
      - "http://etcd:2379"          # multiple etcd address
    prefix: "/apisix"               # apisix configurations prefix
    timeout: 30                     # 30 seconds

plugin_attr:
  prometheus:
    export_addr:
      ip: "0.0.0.0"
      port: 9091

apisix-dashboard-conf/conf.yaml

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

conf:
  listen:
    host: 0.0.0.0     # `manager api` listening ip or host name
    port: 9000          # `manager api` listening port
  etcd:
    endpoints:          # supports defining multiple etcd host addresses for an etcd cluster
      - etcd:2379

                        # etcd basic auth info
    # username: "root"    # ignore etcd username if not enable etcd auth
    # password: "123456"  # ignore etcd password if not enable etcd auth
  log:
    error_log:
      level: warn       # supports levels, lower to higher: debug, info, warn, error, panic, fatal
      file_path:
        logs/error.log  # supports relative path, absolute path, standard output
                        # such as: logs/error.log, /tmp/logs/error.log, /dev/stdout, /dev/stderr

authentication:
  secret:
    secret              # secret for jwt token generation.
                        # NOTE: Highly recommended to modify this value to protect `manager api`.
                        # if it's default value, when `manager api` start, it will generate a random string to replace it.
  expire_time: 3600     # jwt token expire time, in second
  users:
    - username: admin   # username and password for login `manager api`
      password: admin
    - username: user
      password: user

plugin_attr:
  prometheus:
    export_addr:
      ip: "0.0.0.0"
      port: 9091

安装ollama

参考前面ollama docker部署的步骤

安装LobeChat

LobeChat是一个开源的AI聊天机器人,它基于OpenAI的GPT模型,使用Python和Flask构建。

docker-compose.yaml

services:
  lobechat:
    image: lobehub/lobe-chat
    container_name: lobechat
    ports:
      - "3210:3210"
    environment:
      - OPENAI_API_KEY=your_openai_api_key  # 替换为你的 OpenAI API Key
      - OPENAI_PROXY_URL=https://api.openai.com/v1  # 如果需要代理,可以修改为代理地址
    volumes:
      - ./data:/app/data  # 持久化数据
    restart: always
    networks:
      lobechat:

volumes:
  lobechat_data:


networks:
  lobechat:
    driver: bridge

配置apisix路由

通过LobeChat测试部署