简介:DeepSeek 在 Linux 系统下的手动部署步骤与注意事项 PDF 是专门面向具备一定 Linux 基础、希望自行从源码部署 DeepSeek 的深度学习开发者的实用指南。文档覆盖 Ubuntu 20.04 LTS 及更高版本的系统准备、Python 3.8 环境安装、基于 venv 的虚拟环境创建、Git 克隆代码库、依赖包安装与 GPU 支持检测等关键环节,并对 config.yaml 配置、数据集路径设置、train.py 训练和 infer.py 推理命令给出了明确操作说明。资源以单个 PDF 文件提供,约 215KB,虽体量小巧,但额外列出了一章“注意事项”,集中讲解了依赖包版本冲突、GPU 未识别、内存不足、文件权限等典型案例的排查思路,能有效帮助读者避开部署中的常见坑。目前已有 794 人学习下载,内容步骤清晰、结构完整,适合作为 Linux 部署 DeepSeek 的速查参考。
1. DeepSeek在Linux下手动部署:从一键脚本到亲手掌控
某开发者的GPU服务器上装了一键部署脚本,跑起来倒是快,可模型一升级、显存一吃紧,想换个量化参数都找不到入口;后来他索性把容器删掉,在裸机的Linux环境里手动部署了一遍DeepSeek。这个标题讲的就是这件事,不是让你用别人打包好的镜像,而是自己把权重文件拉下来、自己写启动命令、自己决定服务怎么托管,每一步都在掌控里。
手动部署的实际价值不只是“能跑”,而是让推理服务变成你可以查日志、改参数、复制到另一台机器的普通进程。适合手里有一张NVIDIA显卡和一台Linux服务器的工程师,也适合想搞明白DeepSeek到底怎么落地的初学者。它会暴露很多一键脚本替你藏起来的问题——显存怎么算、上下文长度怎么设、进程挂了怎么拉起来。这篇笔记把这些步骤和对应坑位一次讲透。
2. 部署前的软硬件边界:显存、驱动与模型选型
2.1 硬件需求:先算显存,再选模型
DeepSeek在这两年开源了一批权重,但它们的体量完全不是一个量级:完整版的DeepSeek-V3和R1参数量达到671B,是稀疏MoE结构,推理时要占用数百GB显存,个人单台机器基本不要想;而官方开源的蒸馏系列(R1-Distill)从7B到70B不等,在消费级显卡上就能跑起来。手动部署的第一步不是装软件,而是先确认你手里的卡能装下多大的模型。
显存的粗略估算公式很简单:权重显存=参数量×每参数字节数。FP16半精度下每参数占2字节,INT4量化下每参数约0.5字节。7B模型FP16理论需要约14GB,INT4约3.5GB;32B模型FP16约64GB,INT4约16GB——但公式忽略了两块大头:KV Cache和激活值,它们由上下文长度(max-model-len)和并发数决定。所以实践里的规律是:24GB单卡跑7B的FP16比较从容,跑14B需要量化,32B建议多卡或干脆换量化加短上下文。
我建议按下面的表先做一轮筛选,表中的“实践推荐配置”已经预留了KV Cache余量。
| 模型 | 参数量 | FP16理论权重 | INT4量化权重 | 实践推荐配置 |
|---|---|---|---|---|
| R1-Distill-Qwen-7B | 7B | ~14GB | ~4GB | 单卡24GB,FP16 |
| R1-Distill-Qwen-14B | 14B | ~28GB | ~7GB | 单卡24GB,INT4 |
| R1-Distill-Qwen-32B | 32B | ~64GB | ~16GB | 双卡24GB,或单卡48GB |
| R1-Distill-Llama-70B | 70B | ~140GB | ~35GB | 四卡才稳妥 |
| DeepSeek-V3/R1原版 | 671B MoE | 数百GB | 单机多卡很难覆盖 | 多机集群或走API |
还有一条容易忽略的是物理内存。vLLM在加载权重时会把模型文件读进内存再搬运到显存,内存太小会触发OOM Kill。个人实践的底线是:内存不小于模型权重文件体积的1.2倍,8G内存跑7B模型会非常勉强。
2.2 环境准备:驱动、CUDA与Python运行时
在Linux上部署DeepSeek,环境问题的根子出在“CUDA版本”这个词上。nvidia-smi右上角显示的CUDA Version是指当前驱动最多支持的CUDA运行版本,它和你Python环境里实际使用的CUDA runtime是两回事。很多人看到驱动支持CUDA 12就以为万事大吉,结果vLLM一加载就报找不到libcudart之类的错。手动部署时,把这两层分开看:驱动只要满足框架的最低要求即可,真正的runtime装进conda环境里随用随扔。
我一般会先做一轮基础检查,用下面这段命令确认显卡、驱动和Python环境三个关键项:
# 1. 确认显卡型号和驱动状态,CUDA Version是驱动支持上限 nvidia-smi # 2. 用独立conda环境隔离依赖,避免污染系统Python conda create -n ds python=3.10 -y conda activate ds # 3. 安装vLLM,它同时拉入PyTorch的CUDA依赖 pip install vllm # 4. 验证vLLM能正常导入,顺便确认CUDA runtime可用 python -c "import vllm; print('vllm ok')"逻辑说明:第一步先看“卡在不在”和“驱动活没活”,如果nvidia-smi本身都报错,后续不用继续;第二步用conda创建隔离环境是因为vLLM对PyTorch版本有强依赖,装在系统Python里容易和已有项目互相踩;第三步装vLLM时它会自动把配套的PyTorch带进来,不需要手动先装PyTorch。第四步的import vllm如果没有任何输出直接退出,说明环境可用。
参数说明:ds是我常用的环境名,你可以改成项目名;Python 3.10是目前vLLM和PyTorch兼容面都比较稳的版本区间,3.11和3.12也能用,但真没必要赌兼容性。如果服务器上已经装了NVIDIA容器工具,用Docker会更省事,但手动部署的精神就是让过程透明,裸机直装更能看清每一步依赖是什么。
2.3 模型选型:蒸馏版、量化版和原版的取舍
DeepSeek的开源模型分两条线:一条是完整版R1和V3,另一条是R1-Distill蒸馏系列。蒸馏版是把R1的长思维链能力“教”给Qwen或Llama的小模型,质量会打折,但换来的是单卡可跑。选型时很多人会纠结要不要上32B,我的建议是:先把7B或8B的蒸馏版在本地完整跑通,确认吞吐和延迟符合预期,再考虑往上升级。“能跑”和“能落地”之间隔着一条叫做KV Cache的河,模型越大,这条河越宽。
量化是第二道选择题。7B模型FP16只需要14GB显存,没有量化必要;14B或32B想上单卡,量化就是唯一出路。AWQ和GPTQ是目前vLLM支持得最好的两种量化格式,效果差别不大,但有个必须记住的规则:你下载的权重是什么量化格式,启动服务时就要带上对应的--quantization参数,用错轻则警告,重则输出胡言乱语。谨慎的路线是:优先FP16,显存不够再量化,不要一上来就追求最小的模型和最短的上下文。
3. 从下载到跑通:DeepSeek在Linux下的最小部署命令
3.1 拉取模型权重:用huggingface-cli并支持断点续传
模型下载是整个部署里最容易被低估的一步。常见误用是git clone某个Hugging Face仓库——大模型仓库走的是Git LFS,不仅慢,还容易把仓库历史和权重文件双重占满磁盘。我一般用huggingface-cli直接下载,它的--local-dir参数可以指定落地路径,而且重复执行同一命令能断点续传,网络断了不用从头拉。
# 在ds环境里安装下载工具和加速插件 conda activate ds pip install -U huggingface_hub hf_transfer export HF_HUB_ENABLE_HF_TRANSFER=1 # 下载7B蒸馏模型到本地目录,重复执行可续传 huggingface-cli download deepseek-ai/DeepSeek-R1-Distill-Qwen-7B \ --local-dir ./models/DeepSeek-R1-Distill-Qwen-7B逻辑说明:第一段命令把Hugging Face官方下载器和多线程传输插件装进隔离环境,HF_HUB_ENABLE_HF_TRANSFER这个环境变量让下载走多线程通道,对大文件的提速非常明显,体感上能把快慢差出好几倍。第二段命令中,--local-dir指定模型文件落地位置,下载完成后目录里会包含config.json、model.safetensors等核心文件。如果网络访问huggingface.co不通畅,可以设置HF_ENDPOINT环境变量指向一个可用的镜像源,这一步按你的网络实际情况配置即可,不影响后续任何步骤。
参数说明:deepseek-ai/DeepSeek-R1-Distill-Qwen-7B是模型仓库的标准标识,想换14B就把后半段改成对应名字。使用--local-dir而非--cache-dir的好处是模型文件以普通目录形式躺在那里,你可以随时du -sh看体积,也方便之后直接作为--model参数传入。下载中断后重跑这条命令,会从断点继续而不是从头开始,这是手动部署的底牌。
3.2 启动vLLM服务:输入参数与显存控制
vLLM是当前DeepSeek部署最常用的推理服务框架,原因有三:内部实现了continuous batching,多个并发请求会动态拼batch,吞吐明显高于逐条请求;OpenAI兼容接口意味着原有client代码不用改;显存管理是预分配式的,服务启动时就把KV Cache的池子划好,运行中不会突然申请引发抖动。启动命令看起来长,但每个参数都值得逐项理解。
python -m vllm.entrypoints.openai.api_server \ --model ./models/DeepSeek-R1-Distill-Qwen-7B \ --served-model-name deepseek-r1-7b \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.90 \ --max-model-len 8192 \ --enforce-eager逻辑说明:--model传的是上一步下载的本地路径,vLLM会自动读取config.json识别结构和精度;--served-model-name是暴露给客户端的模型名,后续curl请求里的"model"字段必须和它一致;--host 0.0.0.0让服务监听所有网卡,这样局域网内其他机器也能访问,只在本机调试时留在默认的127.0.0.1即可;--port是TCP端口,默认8000。--gpu-memory-utilization是vLLM最关键的显存控制参数,0.90表示预取90%的显存用于权重和KV Cache,留10%给CUDA context和碎片;--max-model-len直接决定KV Cache池的大小,也是单次请求最长上下文的硬上限;--enforce-eager关闭CUDA graph优化来节省显存,适合24G这种不那么充裕的卡。
参数说明:显存紧张时优先降--max-model-len到4096,这一项每降一半,KV Cache省一半;--gpu-memory-utilization不要超过0.95,否则启动阶段就可能OOM。如果下载的是AWQ或GPTQ量化版,必须加--quantization awq或--quantization gptq。启动日志观察点:出现类似Loading model weights took是正常进度,出现Uvicorn running on http://0.0.0.0:8000就是服务就绪,看到CUDA error: out of memory则回到参数表检查。
3.3 首次请求验证:从模型列表到第一个对话
服务起来后不要急着接业务,先用两个curl确认链路是通的。第一个请求查模型列表,第二个发真正的对话补全请求。这里最容易犯的错是模型名拼错——--served-model-name设成什么,请求体里的model就必须是什么,大小写都要一致。
# 请求1:列出当前服务加载的模型 curl -s http://127.0.0.1:8000/v1/models | python -m json.tool # 请求2:发送一条对话补全请求,验证推理链路 curl -s http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-7b", "messages": [ {"role": "user", "content": "用一句话解释什么是MoE"} ], "max_tokens": 256, "temperature": 0.6 }' | python -m json.tool逻辑说明:第一个请求返回的JSON里data[].id就是服务当前暴露的模型名,如果和你预期不同,优先检查启动时的--served-model-name。第二个请求里,messages数组里的系统角色和用户角色组成了基本对话上下文;max_tokens限制生成长度,这里设256足够确认链路通;temperature控制随机性,DeepSeek官方API对R1系列模型建议取值0.6,这是长期推理任务验证过的经验值,保持默认就好。
第一次请求往往比后续请求慢,因为vLLM在启动时虽然预分配了显存池,但CUDA kernel的首次调用仍有预热开销。响应JSON里的choices[0].message.content就是模型生成的结果。若看到HTTP 404,检查URL路径是否为/v1/chat/completions;若看到HTTP 400,看返回体的message字段,多半是max_tokens超出上限或model不匹配。
4. 把推理服务变成常驻进程:systemd托管与API参数调优
4.1 用systemd托管:开机自启与崩溃自动拉起
手动部署的进程如果直接用python命令跑在前台,SSH窗口一关服务就跟着没了,这不是生产形态。Linux上最省心的做法是用systemd把这套vLLM命令包装成系统服务,由init进程接管生命周期:开机自启、崩溃自动拉起、日志统一进journal。
[Unit] Description=DeepSeek R1 Distill 7B vLLM Service After=network-online.target Wants=network-online.target [Service] Type=simple User=deploy WorkingDirectory=/opt/deepseek Environment=HF_HOME=/opt/deepseek/.cache Environment=HF_HUB_ENABLE_HF_TRANSFER=1 ExecStart=/home/deploy/miniconda3/envs/ds/bin/python -m vllm.entrypoints.openai.api_server --model /opt/deepseek/models/DeepSeek-R1-Distill-Qwen-7B --served-model-name deepseek-r1-7b --host 0.0.0.0 --port 8000 --gpu-memory-utilization 0.90 --max-model-len 8192 Restart=always RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target逻辑说明:[Unit]段的After和Wants确保网络就绪后再启动服务;[Service]段的User=deploy让服务以普通用户身份运行,不要用root跑推理服务;WorkingDirectory是服务的工作目录;Environment设置Hugging Face缓存的路径和加速开关;最关键的是ExecStart——这里必须写conda环境里Python解释器的绝对路径,因为systemd的运行环境不会自动加载conda的PATH配置,直接写python会报“command not found”。
把这个文件保存为/etc/systemd/system/deepseek.service,然后执行:
# 重新加载systemd配置,并设置开机自启 sudo systemctl daemon-reload sudo systemctl enable --now deepseek.service # 查看服务状态和实时日志 systemctl status deepseek.service journalctl -u deepseek.service -fenable --now把服务设为开机自启并立即启动。journalctl是排错主战场,vLLM的启动日志、Python traceback全都汇聚在这里。如果服务起不来,先看systemctl status里提示的退出码,再翻journalctl -u deepseek.service -n 50看最后几十行日志,多半能在里面找到CUDA或路径相关线索。
4.2 按业务场景调参:并发、上下文和超时
服务常驻之后,下一步是按业务负载做参数配平。vLLM的并发模型和传统服务不一样,它不需要你在应用层开线程池,continuous batching机制会在内部把多个并发请求动态拼进同一个batch;并发上限不直接由--max-num-seqs参数决定,而是由KV Cache的剩余空间动态约束。你真正需要关心的是三个配置之间的三角关系:--max-model-len定单请求上下文上限,--gpu-memory-utilization定总显存池,两者之差就是能同时承载的并发深度。上下文越长、并发越大,KV Cache消耗越快。
下面是我在手动部署时常用的参数基线:
| 参数 | 推荐值 | 影响范围 |
|---|---|---|
--max-model-len | 8192 | KV Cache大小,超过显存会启动失败 |
--gpu-memory-utilization | 0.90 | 显存池占比,0.95以上有OOM风险 |
--served-model-name | 自定义名 | 客户端model字段,改动需同步 |
--enforce-eager | 默认关 | 显存紧张时开启,牺牲部分吞吐 |
--quantization | awq / gptq | 权重格式,必须与下载版本一致 |
如果业务是短问答,上下文需求不超过2048,建议把--max-model-len调到4096以节省KV Cache,换更大的并发余量;如果业务要读长文档,则要接受单并发占用变大的现实,把--max-model-len调到16384,同时降低--gpu-memory-utilization里的并发预期。没有免费的午餐,参数之间全是交换。
4.3 多模型与端口规划
单卡部署一套服务只是入门。真正落地的场景通常是:同一台机器上要同时跑7B的快速问答和14B的高质量推理,或者要给不同业务线分配独立模型实例。常见做法是每张卡跑一个进程,端口错开,并在systemd service里用环境变量锁定可见的GPU,避免两个进程争抢同一块显存。
在一张卡跑双实例时要格外小心:vLLM的显存预分配机制会各占一块,两个实例都设0.90就容易有一个起不来。较好的策略是给快速问答实例设0.45,给高质量实例设0.45,留10%余量给系统。多实例的本质是用显存换隔离,要同时跑几个模型,先算清总显存预算。
# 用环境变量锁定第一张GPU,跑7B实例 NVIDIA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.openai.api_server \ --model /opt/deepseek/models/DeepSeek-R1-Distill-Qwen-7B \ --served-model-name deepseek-r1-7b \ --port 8000 --gpu-memory-utilization 0.45 --max-model-len 8192 & # 用环境变量锁定第二张GPU,跑14B量化实例 NVIDIA_VISIBLE_DEVICES=1 python -m vllm.entrypoints.openai.api_server \ --model /opt/deepseek/models/DeepSeek-R1-Distill-Qwen-14B-AWQ \ --served-model-name deepseek-r1-14b \ --quantization awq \ --port 8001 --gpu-memory-utilization 0.45 --max-model-len 8192 &这段命令展示的只是验证多实例的裸启动方式。实际生产环境里,这两条命令分别写进两个systemd unit文件,各设一个Environment=NVIDIA_VISIBLE_DEVICES,端口从8000、8001依次外延。后端接业务时,由网关层按模型名把请求路由到对应端口。
5. 手动部署避坑指南:5个高频翻车点与排查方案
5.1 现象:启动即报CUDA out of memory
服务一开始加载,日志直接抛CUDA error: out of memory,闪退。这是最容易遇到的开局翻车点。
原因有两层:一是--gpu-memory-utilization设得过高,比如0.95,vLLM在预分配KV Cache时超过了实际可用显存;二是--max-model-len按大上下文设置了32768,单请求能塞下的KV Cache大幅膨胀,小型号的卡根本扛不住。
解决方式是退回安全区间:先执行nvidia-smi确认当前显存是否已被其他进程占满,把--gpu-memory-utilization降到0.88,并把--max-model-len降到4096;若仍报OOM,加上--enforce-eager关闭CUDA graph再启动。这套组合拳能解决绝大多数的启动期显存问题。
5.2 现象:模型下载到一半卡死,重新执行从头开始
用huggingface-cli下载权重时,网络抖动导致传输中断,重新跑同一条下载命令,进度条从0开始跳动,之前几个小时白等。
原因是旧版本的huggingface_hub断点续传能力不稳定,且默认没有启用多线程传输,单个连接被掐断就全盘重来。
解决方式是装最新版下载工具并开启传输加速,命令在前面3.1小节已经给过:pip install -U huggingface_hub hf_transfer,同时export HF_HUB_ENABLE_HF_TRANSFER=1。之后中断时不用删任何临时文件,重跑同一条huggingface-cli download --local-dir ...命令,它会自动识别未完成的分片续传。还有一条血泪经验:不要在下到一半时按Ctrl+C中断,让进程自然退出,临时文件才能被正确识别。
5.3 现象:局域网内其他机器连不上,curl超时或拒绝连接
服务在本机curl一切正常,换到另一台机器执行curl http://<服务器IP>:8000/v1/models却超时或被拒。
原因是vLLM默认只监听回环地址127.0.0.1,启动命令里漏了--host 0.0.0.0;另一个高频原因是系统防火墙拦截了端口。
解决方式是先确认启动日志里是否出现Uvicorn running on http://0.0.0.0:8000,而不是127.0.0.1;再看防火墙状态:Ubuntu系执行sudo ufw status,若8000端口未放行则执行sudo ufw allow 8000/tcp。如果这台机器本身在云上,还要去云控制台检查安全组有没有放行该TCP端口。三层都打通,跨机器访问自然就通了。
5.4 现象:多卡启动报NCCL或peer通信错误
在一台多卡服务器上启动时加了--tensor-parallel-size 2,服务直接报NCCL相关的通信错误,例如NCCL error in: ProcessGroupNCCL.cpp。
原因通常是多张显卡型号或显存容量不一致,导致张量并行无法均匀切分权重;或是GPU之间的NVLink桥接、PCIe通信环境本身有问题。
解决方式是先用单卡启动验证环境和权重本身都正常,再检查执行nvidia-smi时有没有出现ERR!等高线标识。若两张卡确实混插且型号差异大,最稳的路线是放弃张量并行,改为每卡跑独立进程加端口隔离,方案在4.3小节已经讲过。只有两张同型号同显存的卡,才建议启用--tensor-parallel-size 2。
5.5 现象:回复内容胡言乱语或中文乱码
模型服务本身正常启动,请求也能返回,但生成的内容大量重复、前后矛盾,甚至出现乱码。
原因大概率是权重格式和加载参数不匹配——下载的是带AWQ量化配置的权重,启动命令里却没有加--quantization awq,导致vLLM用错误方式解析权重,推理过程完全失真。
解决方式是检查模型目录下的config.json,看quantization_config字段里标注的量化类型是awq还是gptq,然后在启动参数里补上对应的--quantization选项。改完参数重启服务,问题通常当场消失。如果确认参数已匹配但输出仍异常,常见处置是删掉本地模型缓存重新拉取一次,排除下载损坏的可能。
6. 部署收尾的进阶验证:压测基线、量化参数与日志习惯
6.1 用流式请求记录首token延迟和吐出速度
服务部署完成,功能调通,这只是起点。接下来要做的是把一个可信的延迟基线拿到手。单一发curl只能告诉你“有响应”,无法区分是缓存命中还是真正在推理。我习惯写一段小脚本,用流式接口记录两个核心指标:首token延迟(TTFT)和端到端生成时间。
import time import requests url = "http://127.0.0.1:8000/v1/chat/completions" payload = { "model": "deepseek-r1-7b", "messages": [{"role": "user", "content": "写一段200字关于Linux进程调度的说明"}], "max_tokens": 512, "temperature": 0.6, "stream": True } t0 = time.time() first_token_at = None token_count = 0 with requests.post(url, json=payload, stream=True) as resp: for line in resp.iter_lines(): if line and line.startswith(b"data:") and b"content" in line: if first_token_at is None: first_token_at = time.time() token_count += 1 print(f"首token延迟: {first_token_at - t0:.2f}s") print(f"生成token数: {token_count}")逻辑说明:设置stream: True后,vLLM会按流式返回分片,iter_lines逐行处理响应,首个包含content字段的data:行即首token到达时刻。对7B蒸馏模型在24GB单卡上,首token延迟在0.5到2秒之间都是健康区间,持续高于2秒则要检查显存是否被其他进程抢占。每次压测至少跑3取中位数,比单次结果有说服力得多。
6.2 用指标接口量化健康度:看/metrics端点
vLLM自带一个Prometheus格式的指标端点,不需要额外装监控系统就能抓到关键运行数据。/metrics输出里,vllm:num_requests_running表示当前正在推理的请求数,vllm:generation_tokens_total是累计生成token数。通过对比这两个值的增速,能快速判断服务是处在空闲还是满载。跑一轮压测前后各抓一次:
# 压测前抓一次基线 curl -s http://127.0.0.1:8000/metrics | grep -E "vllm:num_requests_running|vllm:generation_tokens_total" # 压测中再抓一次,对比生成token增速 watch -n 1 "curl -s http://127.0.0.1:8000/metrics | grep generation_tokens_total"连看几次generation_tokens_total的差值,就能算出每秒生成token数,也就是吞吐的近似值。这个数字在调参后回回来,是判断--enforce-eager开关和量化选择的最可靠依据。
6.3 把“能跑”变成“可重放”:固化配置习惯
手动部署的终极形态不是一台机器上跑起来,而是这套配置在另一台机器能原样复现。我每次部署收尾都会做三件事:把启动命令整理成start.sh脚本放进项目目录;把systemd unit文件留档;在README里记下显卡型号、显存容量、模型路径、--max-model-len和--gpu-memory-utilization的最终值。下次换机器或升级模型时,照着这套组合跑一遍,半小时就能复现,而不是翻着聊天记录找回当初敲的命令。
这个习惯帮我避过不少坑,最典型的是有次在某服务上调整了temperature之后对话质量明显变差,翻遍日志才发现是上次压测时顺手改了参数没复位。后来我把启动参数全都固化进脚本,连调整候选值也写在注释里,才彻底告别这类“玄学问题”。手动部署DeepSeek真正让人踏实的,不是第一次部署成功,而是每次部署都能复现成功。从压测基线做起,把延迟、吞吐、参数固化三个习惯建立起来,这套服务才算真正交到你手里。希望帮到你。
本文还有配套的精品资源,点击获取