2026/11/4 13:51:16

Ubuntu部署DeepSeek Harness:驱动、显存与稳定运行

Ubuntu部署DeepSeek Harness:驱动、显存与稳定运行 凌晨两点半一台刚上架的 Ubuntu 服务器驱动装到一半黑屏SSH 断连机房里没人只能提工单重启。这种场景我经历过不止一次而每一次的起点都差不多——想赶紧把 DeepSeek Harness 跑起来结果卡在最不该卡的地方。这篇就把我在 Ubuntu 服务器上安装和调通 DeepSeek Harness 的完整链路摊开讲从系统版本选择、驱动与 CUDA 工具链对齐、依赖隔离到权重落地、显存预算估算、冒烟测试、systemd 守护再到上线后那些只在深夜出现的报错。如果你手上正好有一台带显卡的 Ubuntu 机器想把它变成一个能稳定跑模型推理和评测任务的环境下面这些步骤基本可以直接照着抄如果你只是好奇这套东西到底在干什么也能从里面看到一层运行脚手架真正需要关心的边界在哪。1. 服务器底子体检装之前先弄清这台机器能承受什么很多人拿到服务器第一件事就是apt install我现在的习惯是先花二十分钟做体检。DeepSeek Harness 这类运行框架本身对系统要求不算苛刻真正挑食的是它下面的推理后端——显卡驱动、CUDA 运行时、Python 扩展的编译工具链这三者任何一环错位后面都会以莫名其妙跑不起来的形式还给你。体检的目的不是走流程而是提前知道这台机器的天花板在哪免得装到一半才发现显存不够或者驱动版本根本对不上。1.1 系统版本与内核为什么我更倾向 22.04 LTSUbuntu 24.04 不是不能用但它默认内核更新更激进对某些较老的显卡驱动包兼容性会时不时抽风。DeepSeek Harness 的部署环境我一般锁定在Ubuntu 22.04 LTS 内核 5.15 系列理由是这一组合的驱动包、CUDA 仓库、Python 3.10 生态都已经被大量生产环境踩过一遍出问题时搜到的答案也对得上。先确认系统信息lsb_release -a uname -r lspci | grep -i nvidialspci这一条特别关键。如果它什么都没输出说明显卡没被识别可能是没插好、BIOS 里没开、或者是虚拟机没做 PCI 直通——这时候继续装驱动纯属浪费生命。我遇到过一次折腾了两小时驱动最后发现是机器上架时显卡供电线没插紧lspci里压根没这个设备。养成先看lspci的习惯能省掉大量无效劳动。另外要确认磁盘空间。一套 7B 级别的权重加上推理框架的依赖轻松吃掉 40GB 以上如果打算同时放多个模型或者保留评测数据集建议给/var和模型目录各留足余量df -h free -h内存也别忽略。有些框架在加载权重时会先在内存里做一次转换再搬到显存内存不够会直接 OOM kill日志里只留一行Killed非常难查。1.2 显卡驱动与 CUDA 工具链的对齐方法这是整个安装过程里最容易翻车的一环也是我见过最多人卡住的地方。核心原则只有一句驱动版本决定它能支持的最高 CUDA 运行时版本而框架依赖的 CUDA 版本不能超过这个上限。先看当前状态nvidia-smi如果这条命令报command not found说明驱动还没装。装驱动我推荐用 apt 仓库的方式而不是官网下载.run文件手动安装——后者在卸载和内核升级时非常痛苦我吃过一次亏内核一升级驱动模块编译失败整台机器进不了图形界面最后只能进恢复模式重来。sudo apt update sudo apt install -y ubuntu-drivers-common ubuntu-drivers devicesubuntu-drivers devices会列出推荐驱动通常带recommended标记的那个就是相对稳妥的选择。直接用sudo ubuntu-drivers autoinstall sudo reboot重启后nvidia-smi应该能输出一张表右上角会显示Driver Version和CUDA Version。这里的CUDA Version是驱动支持的最高运行时版本不是你实际安装的 CUDA 版本很多人会看混。比如显示CUDA Version: 12.2意味着你可以安装 12.0、12.1、12.2 的运行时但装不了 12.4 的。如果你不想在系统层面装完整 CUDA Toolkit其实不必强求。现在的 PyTorch 生态基本都提供预编译 wheel自带 CUDA 运行时只要驱动版本够新就能跑。这也是我现在的默认做法——系统只装驱动CUDA 运行时交给 pip 包带环境干净卸载也简单。只有在需要自己编译某些算子时才装完整的 toolkit。需要装的话记得先禁用开源驱动 nouveau否则会和 NVIDIA 驱动冲突sudo bash -c echo blacklist nouveau /etc/modprobe.d/blacklist-nvidia-nouveau.conf sudo bash -c echo options nouveau modeset0 /etc/modprobe.d/blacklist-nvidia-nouveau.conf sudo update-initramfs -u1.3 Python 运行时系统 Python 绝对不能碰Ubuntu 22.04 自带 Python 3.10而系统里有大量工具依赖这个 Python。永远不要sudo pip install到系统环境我在早期项目里干过一次装完之后 apt 包管理器直接坏掉报各种奇怪的 import 错误最后重装系统才解决。正确做法是用 venv 做隔离sudo apt install -y python3.10-venv python3.10-dev build-essential python3 -m venv /opt/harness/venv source /opt/harness/venv/bin/activate python -m pip install --upgrade pip setuptools wheelpython3.10-dev和build-essential这两个别省。有些依赖在找不到预编译 wheel 时会退回源码编译缺了头文件就会报Python.h: No such file or directory这个报错和 DeepSeek Harness 本身一点关系都没有但会让你误以为是框架的问题浪费大量时间排查。装完之后一定要确认 pip 装到的是 venv 里which pip # 期望输出 /opt/harness/venv/bin/pip这一步看着啰嗦但我见过至少三次因为 shell 里残留的 PATH 或者sudo环境继承把包装到系统 Python 里的事故。2. 三条安装路径的取舍源码、虚拟环境、容器各管什么DeepSeek Harness 的安装没有唯一正确答案取决于你要拿它干什么。如果只是自己调试和改代码源码方式最顺手如果是给团队提供稳定服务容器镜像更可控如果服务器上还跑着别的业务环境隔离需求强那容器几乎是必选项。我把三条路径的实际体验摊开讲你可以按自己的场景对号入座。2.1 源码 venv透明但需要自己收拾残局源码方式的好处是所有东西都在明面上改一行代码立刻生效出问题也容易定位到具体文件。缺点是依赖冲突要自己处理。cd /opt git clone harness-repo deepseek-harness cd deepseek-harness source /opt/harness/venv/bin/activate pip install -r requirements.txtrequirements.txt里通常会包含推理后端比如 torch 系列、HTTP 服务框架、配置解析、序列化库等。这里有个关键动作先单独装推理后端再装其余依赖。原因是 pip 的依赖解析器有时会为了满足某个次要依赖而降级 torch导致你本来装好的 CUDA 版本被换掉nvidia-smi看着正常但一跑就报CUDA driver version is insufficient。我现在的顺序是pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install -r requirements.txt pip checkpip check会告诉你有没有依赖冲突输出No broken requirements found才算干净。如果它报某两个包版本要求矛盾别急着强行装先看清楚是哪个包在拖后腿——通常可以通过pip install 包名x,y精确锁定解决。安装完成后验证一下框架能不能感知到显卡python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.device_count())期望看到True和显卡数量。如果is_available()返回False八成是驱动和 torch 的 CUDA 版本不匹配回去对一下 1.2 节那张表。2.2 容器镜像一次构建反复部署容器方式的核心价值是把能跑这个状态固化下来。我在给团队交付时基本都用这条路因为新同事入职只要docker run一条命令不用再经历一遍上面的坑。前提是宿主机装好驱动和容器运行时sudo apt install -y docker.io sudo apt install -y nvidia-container-toolkit sudo systemctl restart docker验证容器内能看见显卡docker run --rm --gpus all nvidia/cuda:12.1.1-base-ubuntu22.04 nvidia-smi这条命令如果能正常输出显卡信息说明驱动透传通了。注意nvidia-container-toolkit装完必须重启 docker 服务否则--gpus all会报could not select device driver这个报错我第一次遇到时查了很久。镜像构建建议分两层基础层放 CUDA 和 Python 依赖应用层只拷贝代码。这样改代码时不需要重装依赖构建时间从十几分钟降到几十秒。FROM nvidia/cuda:12.1.1-runtime-ubuntu22.04 RUN apt update apt install -y python3.10 python3-pip WORKDIR /app COPY requirements.txt . RUN pip install torch --index-url https://download.pytorch.org/whl/cu121 \ pip install -r requirements.txt COPY . . CMD [python, -m, harness.server, --config, /app/config/serve.yaml]有个坑要提前说容器里的模型权重如果用 bind mount 挂进去注意权限。宿主机的模型目录属主是 root 时容器内非 root 用户读不到会报PermissionError。我一般统一在宿主机上chown -R 1000:1000模型目录容器内用同 UID 运行。2.3 三条路径的对照与选择建议维度源码 venvDocker 容器系统 pip 直装环境隔离中等仅 Python 层强无调试便利性最好改完即生效需要重建镜像或挂载代码好迁移复现难度高依赖漂移风险大低镜像即快照高升级/回滚成本手动容易残留换 tag 即可手动适合场景单人开发、改代码团队交付、多服务共存临时试验如果服务器上只跑这一个服务源码 venv 足够如果这台机器还要跑别的业务或者需要同时维护多个模型版本直接上容器别犹豫。系统 pip 直装这条我基本不推荐除非你在一次性环境里快速验证某个想法用完就销毁。3. 权重落地与显存预算装完之后真正决定能不能跑的两件事框架装好只是把车子造出来了能不能上路取决于油和路况——权重放对了没有显存够不够。这两件事做错报错信息往往非常抽象比如RuntimeError: CUDA out of memory后面跟一串数字或者KeyError: model.layers.0看着像框架 bug其实都是配置问题。3.1 权重目录的结构与完整性校验一个标准的模型权重目录通常长这样/opt/models/deepseek-model/ ├── config.json ├── generation_config.json ├── tokenizer.json ├── tokenizer_config.json ├── model-00001-of-00004.safetensors ├── model-00002-of-00004.safetensors ├── model-00003-of-00004.safetensors ├── model-00004-of-00004.safetensors └── model.safetensors.index.json几个容易忽略的点model.safetensors.index.json这个文件描述了分片和参数的对应关系缺了它框架会认为权重不完整直接拒绝加载。很多人下载时只下了safetensors文件漏掉 index然后卡在加载阶段。分片数量要和 index 里记录的一致。曾经有一次网络中断导致最后一个分片只有几百字节加载时报safetensors解析错误报错信息里完全没提文件不完整。权重文件建议做一次哈希校验尤其是从不同渠道拷贝过来的。大文件传输中断很常见静默损坏反而更麻烦。cd /opt/models/deepseek-model sha256sum *.safetensors checksums.txt # 传输后在目标机器上复核 sha256sum -c checksums.txt目录权限也要注意。如果 Harness 用非 root 用户运行模型目录必须对该用户可读。我一般建一个专门的运行用户sudo useradd -r -s /bin/false harness sudo chown -R harness:harness /opt/models /opt/harness3.2 显存预算怎么算一次完整的算术推演这部分我建议每个人自己算一遍因为网上那些7B 模型需要 14GB 显存的说法太粗略实际部署中 KV cache 往往才是压垮骆驼的最后一根稻草。显存占用大致分三块第一块是权重本身。参数量乘以每参数字节数精度每参数字节7B 模型约占32B 模型约占FP32428 GB128 GBFP16 / BF16214 GB64 GBINT817 GB32 GBINT4约 0.55约 4 GB约 18 GBINT4 为什么不是精确的 0.5因为量化通常要保留一部分 scale 和 zero-point 参数实际比理论值略高工程上按 0.55 估比较安全。第二块是 KV cache。公式是KV cache 字节数 2 × 层数 × KV头数 × head_dim × 序列长度 × 并发数 × 每元素字节以某个 32 层、8 个 KV 头、head_dim 为 128 的模型为例序列长度 4096并发 8FP16 存储2 × 32 × 8 × 128 × 4096 × 8 × 2 4,294,967,296 字节 ≈ 4 GB如果并发提到 32这一块就变成 16GB。这就是为什么同一个模型单人用着很宽裕一上生产就 OOM——并发数直接线性放大 KV cache。第三块是激活值和框架预留。这部分不好精确计算我一般按权重占用的 10%~15% 预留再加上 1~2GB 的 CUDA context 开销。举个完整例子24GB 显存的卡跑一个 7B 模型INT4 量化后权重约 4GBKV cache 按 8 并发 4GB激活和预留算 3GB总共约 11GB留出足够余量。同样的卡跑 FP16 权重就是 14 4 3 21GB已经贴着上限稍微提点并发就爆。提示不要按刚好够来规划。显存占用会有波动碎片化也会导致明明总量够却分配失败。我通常把实际可用显存控制在总容量的 85% 左右。3.3 配置文件逐条拆解Harness 的配置文件一般长这样我把每一项的作用和踩过的坑都标出来server: host: 0.0.0.0 port: 8080 workers: 1 model: path: /opt/models/deepseek-model dtype: bfloat16 max_model_len: 4096 gpu_memory_utilization: 0.85 tensor_parallel_size: 1 trust_remote_code: false runtime: max_batch_size: 8 request_timeout: 120 log_level: info逐条说workers多进程 worker 在单卡场景下通常设 1。设多了每个进程都想独占显存直接互相打架。dtype有 bfloat16 就用 bfloat16数值稳定性和 float16 相当但溢出风险小。老卡比如计算能力 7.0 以下的不支持 bf16得退回 float16。max_model_len这个值直接决定 KV cache 上限。设成 32768 但实际只跑 2K 的请求等于白白预留了一大块显存。我一般按真实业务的最长上下文设不追求越大越好。gpu_memory_utilization框架会按这个比例去预分配显存。设 0.95 看着充分利用实际很容易因为碎片导致后续分配失败0.85 是我试下来比较稳的值。tensor_parallel_size多卡张量并行。单卡务必设 1设错了框架会在初始化阶段报找不到对应设备。trust_remote_code除非权重确实需要加载自定义模型代码否则保持 false。这既是安全考虑也能避免一些版本不兼容的奇怪报错。改完配置别急着启动先用框架自带的配置校验如果有或者手动解析一遍 YAML缩进错了报错信息会非常难懂。4. 从冒烟测试到吞吐基准怎么确认它真的跑起来了服务能启动不等于能用。我见过太多次进程活着、端口开着、请求一发就超时的情况。所以跑通之后必须做两级验证先冒烟确认链路通再压测确认性能在可接受范围。4.1 最小可用冒烟测试启动服务source /opt/harness/venv/bin/activate python -m harness.server --config /opt/harness/config/serve.yaml看到日志里出现类似model loaded、server listening on 0.0.0.0:8080才算真正就绪。有些框架会在模型加载完成前就打印端口监听这时候发请求会 503别被骗了。另开一个终端发请求curl -s http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d { model: deepseek-model, messages: [{role: user, content: 用一句话说明什么是显存碎片}], max_tokens: 64, temperature: 0.2 }冒烟测试要看的不是回答质量而是三件事HTTP 状态码是不是 200。返回 422 通常是请求体字段不对返回 500 就去翻服务端日志里最后的 traceback。响应里有没有usage字段里面的prompt_tokens和completion_tokens是否合理。如果 token 数是 0 或者异常大说明 tokenizer 加载有问题。首次请求耗时。第一次请求往往包含 kernel 编译和缓存预热比后续慢好几倍是正常的但如果超过 30 秒就要怀疑是不是在 CPU 上跑了。再验证一下流式输出这个在生产里用得最多也最容易出问题curl -N http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d {model:deepseek-model,messages:[{role:user,content:数到十}],stream:true}-N参数是关掉 curl 的缓冲否则你看不到逐字输出的效果。如果流式返回卡住不动常见原因是反向代理层做了缓冲或者框架的stream分支有 bug。4.2 压测脚本与三个关键指标冒烟通过之后我会跑一轮轻量压测摸清这台机器的真实能力。不需要复杂工具一个 Python 脚本足够import time, json, statistics import urllib.request from concurrent.futures import ThreadPoolExecutor URL http://127.0.0.1:8080/v1/chat/completions def one_call(i): payload json.dumps({ model: deepseek-model, messages: [{role: user, content: f第{i}个请求简述缓存命中率的意义}], max_tokens: 128, temperature: 0.0 }).encode() req urllib.request.Request(URL, datapayload, headers{Content-Type: application/json}) t0 time.time() with urllib.request.urlopen(req, timeout180) as r: body json.loads(r.read()) cost time.time() - t0 out_tokens body[usage][completion_tokens] return cost, out_tokens for conc in (1, 4, 8, 16): with ThreadPoolExecutor(max_workersconc) as pool: results list(pool.map(one_call, range(conc * 4))) costs [r[0] for r in results] tokens sum(r[1] for r in results) print(f并发{conc:2d} 平均耗时{statistics.mean(costs):.2f}s fP95{sorted(costs)[int(len(costs)*0.95)-1]:.2f}s f吞吐{tokens/sum(costs):.1f} tok/s)跑完重点看三个指标首 token 延迟TTFT用户感知最直接。超过 2 秒就开始有明显卡顿感。单请求平均耗时随并发上升会有增长但如果并发翻倍耗时也翻倍说明没有真正的批处理请求在排队。总吞吐tokens/s这是衡量硬件利用率的核心指标。并发升高时吞吐应该上升然后趋于平缓如果一开始就持平说明并发没生效检查max_batch_size配置。我在一台 24GB 显存的机器上跑 7B INT4 模型实测大致是并发 1 时吞吐 25 tok/s 左右并发 8 时能到 90 tok/s并发 16 反而降到 80 tok/s 并且 P95 延迟翻倍——这就是典型的过了拐点继续加并发只会让所有人变慢。找到自己的拐点比盲目堆并发有价值得多。5. 长期运行后的守护、排障与性能漂移能跑通是一回事跑一周不出事是另一回事。生产环境里我遇到的大部分问题都不在安装阶段而在运行几天之后服务半夜挂了没人知道、日志把磁盘写满、显存占用缓慢上涨最后 OOM。5.1 systemd 守护别用 nohup 和 screen 了用nohup python ... 启动服务机器一重启就没了进程挂了也没人拉起来。正确做法是写一个 systemd 单元# /etc/systemd/system/deepseek-harness.service [Unit] DescriptionDeepSeek Harness Inference Service Afternetwork-online.target Wantsnetwork-online.target [Service] Typesimple Userharness Groupharness WorkingDirectory/opt/harness EnvironmentPATH/opt/harness/venv/bin:/usr/local/bin:/usr/bin:/bin EnvironmentCUDA_VISIBLE_DEVICES0 ExecStart/opt/harness/venv/bin/python -m harness.server --config /opt/harness/config/serve.yaml Restartalways RestartSec10 StandardOutputjournal StandardErrorjournal LimitNOFILE65535 [Install] WantedBymulti-user.target几个细节值得说EnvironmentPATH...必须显式写。systemd 不会继承你 shell 里的虚拟环境不写这一行就会用系统 Python 启动报找不到模块。Restartalways配RestartSec10避免崩溃后疯狂重启把日志刷爆。LimitNOFILE调到 65535。默认值在高并发下会出现Too many open files这个错误和框架毫无关系但会让你误判。不要用Typeforking模型服务通常是前台进程。启用sudo systemctl daemon-reload sudo systemctl enable --now deepseek-harness sudo systemctl status deepseek-harness journalctl -u deepseek-harness -f日志轮转也得配否则 journal 或者应用日志能吃掉整个磁盘sudo mkdir -p /etc/systemd/journald.conf.d sudo tee /etc/systemd/journald.conf.d/size.conf EOF [Journal] SystemMaxUse2G MaxRetentionSec2week EOF sudo systemctl restart systemd-journald5.2 六类高频报错的定位链路下面这张表是我实际排障中遇到频率最高的几类问题按现象—先查什么—常见根因整理遇到时照着走能省不少时间。现象第一手排查动作常见根因启动即CUDA out of memorynvidia-smi看占用fuser -v /dev/nvidia*查残留进程上一个进程没退干净gpu_memory_utilization设太高加载到一半KeyError/ shape 不匹配对照config.json里的层数和index.json的分片清单权重分片缺失或版本与配置不匹配请求返回 503 / 连接被拒ss -lntp | grep 8080确认监听状态服务还在加载模型端口被其他进程占用首次请求极慢之后正常观察日志里是否有编译/预热阶段正常现象若一直慢则检查是否回退到 CPU运行数小时后 OOM记录显存随时间变化曲线KV cache 未释放、请求超时未清理、内存泄漏日志刷屏too many open filesulimit -n、systemd 单元里的LimitNOFILE文件描述符上限过低我特别说一下运行数小时后 OOM这一类。它的排查难度最高因为复现要等。我的做法是在服务里挂一个轻量监控每 30 秒记录一次显存和请求队列长度while true; do nvidia-smi --query-gpumemory.used,memory.total,utilization.gpu \ --formatcsv,noheader /var/log/harness/gpu.log sleep 30 done跑一天之后看曲线。如果显存是阶梯式上升然后突然掉下来那是正常的分配释放如果是单调爬升从不回落基本可以确定有请求没被正确清理重点查超时和异常分支的释放逻辑。5.3 性能漂移为什么第二天的吞吐比第一天低这个现象我遇到过两次第一次以为是硬件降频查了半天温度才发现不是。常见的性能漂移原因有这么几个显存碎片化。长时间运行后反复分配释放不同大小的块会留下大量碎片。表现是nvidia-smi显示还有空闲显存但新请求就是分配失败。缓解办法是定期重启服务或者开启框架的内存池复用配置。我一般配一个每天凌晨的低峰期重启任务sudo systemctl restart deepseek-harness用 timer 定时执行比 crontab 更好管理# /etc/systemd/system/harness-restart.timer [Unit] DescriptionRestart Harness at low peak [Timer] OnCalendar*-*-* 04:30:00 Persistenttrue [Install] WantedBytimers.targetGPU 温度墙。机房散热不好的时候显卡跑到 85 度以上会自动降频吞吐直接掉 20% 以上。nvidia-smi -q -d TEMPERATURE能看到当前温度和历史最高温度如果发现经常贴着上限就该检查风道和风扇转速了。请求分布变化。这个最容易被忽略。第一天你测的是短 prompt第二天真实业务上来了长上下文请求KV cache 占用暴增吞吐自然下降。所以压测时一定要用真实分布的输入长度别拿你好测出来的数字当基准。磁盘 I/O 抖动。如果模型权重放在网络存储上首次加载或者被换出后重新加载会非常慢。我一般把权重放本地 NVMe网络存储只放评测数据集和日志。最后分享一个我个人的习惯每次装完一套新环境我都会把关键版本号、驱动版本、显存容量、实测吞吐这几个数字记在一个environment.md里跟代码一起提交。半年后回来排查问题这份记录比任何日志都管用因为你能立刻判断出是环境变了还是是代码变了。装 DeepSeek Harness 这件事本身不难难的是三个月后你还能准确说出当时为什么选了这个驱动版本——把它写下来比记在脑子里可靠得多。