☰
单卡4090跑DeepSeek-R1满血版:KTransformers成本账
2026/10/29 16:12:16 网站建设 项目流程

简介:围绕 DeepSeek-R1 满血版部署成本过高这一痛点,这份 PDF 文档给出了一套可落地的低成本高性能推理路径,面向具备 Python 开发经验的研究员与工程师,尤其是希望在受限硬件上运行大模型的团队。内容从部署方案综述展开,对比“强推理、弱训练”硬件选型、蒸馏模型、KTransformers 与 Unsloth 动态量化三条降本路线,并给出各量化版本在不同推理、微调场景下的最低配置参考。重点篇幅落在 KTransformers 上:说明其把专家权重卸载到 CPU、将 MLA/KVCache 留在 GPU 的显存优化机制,以及在单卡 4090 加 480G 内存环境中的完整实践,包括软件栈搭建、模型获取与配置、FastAPI 接口封装,直至与 Open-WebUI 集成。文中另记录了版本差异、AMX CPU 支持与显存余量等实操细节,便于按自身硬件复现排错。包内为 1 个 PDF 文件,约 17.75MB,已有 733 人学习。

1. 671B 满血版压进单卡 4090:这套 DeepSeek-R1 部署方案的真实成本账

一台双节点 8 卡 A100 的报价单摆在面前,预算栏写着两百多万,而需求只是“让内部几十号人能稳定对话”。这是很多团队做大模型部署时遇到的第一个岔路口。DeepSeek-R1 满血版 671B 参数,常规 BF16 精度下要吃 1200G 左右显存,哪怕压到 INT4 也要 490G,看起来除了堆卡没别的路。但这次拆的这套 KTransformers 方案给出了另一个答案:480G 内存 + 单卡 4090(24G 显存),跑 Q4_K_M 量化版,实测 15 token/s,整机成本不到五万。

它解决的不是“最强推理性能”,而是“在有限预算下能不能把满血版跑起来并且能对外服务”。适合已经有几台大内存机器、想做私有化对话服务又不想买卡的团队;不适合要做微调训练、或者要求高并发的场景。下面把硬件门槛、编译过程、启动参数和上线封装一层层拆开。

2. 三条低成本路线怎么选:蒸馏、动态量化与 KTransformers 的硬件边界

在动手之前必须先把选型想清楚,因为这三条路的硬件形态完全不同,买错机器比装错库的代价大得多。蒸馏模型是最省事的,1.5B 到 70B 的尺寸几乎能塞进任何一台带显卡的机器;Unsloth 的动态量化走的是把激活专家加载到 GPU 的路子,对内存和 CPU 没要求,单卡 24G 就能跑 1.58bit 到 2.51bit 的版本;KTransformers 则是把专家权重留在内存、把注意力和 KV Cache 放到显存。三者不是替代关系,是三种不同的资源置换方式。

2.1 先把显存和内存账算清

部署方案的选择本质是一道资源约束题。把几个关键档位列出来,选型时对着自己的机器一看就清楚:

方案权重精度显存需求内存需求硬件形态大致成本量级
原生满血版(百人内并发)低精度浮点约 1200G常规双节点 8 卡 A100260 万至 320 万
传统 INT4 量化INT4至少 490G常规单节点 8 卡 A100接近 200 万
KTransformers + Q4_K_M约 4bit13G(长输出/多并发 20G)480G(下限 382G)单卡 4090 + 大内存不到 5 万
Unsloth 动态量化1.58bit 至 2.51bit24G 起,可扩到双卡 80G无特殊要求单卡或双卡工作站视显卡而定
蒸馏模型(1.5B 至 70B)多种从几 G 到上百 G常规通用最低

这张表里最容易被忽略的是“内存”这一列。很多人看到 13G 显存就以为随便一台机器都能跑,实际上 480G 内存才是这套方案真正的准入门槛,而 480G 内存的机器在二手市场上比一张 4090 便宜得多,这才是成本能压下来的原因。

提示:382G 是理论下限,实际跑起来 Linux 的文件页缓存、Python 进程本身、以及并发时的 KV 增长都会额外占内存,480G 是比较稳的配置。320G 的机器建议直接放弃这条路。

2.2 KTransformers 到底把什么搬到了 CPU 上

理解卸载策略,才知道后面哪些参数可以调。DeepSeek-R1 是 MoE 架构,每个 token 只激活一小部分专家网络。KTransformers 的核心思路是:把体量最大、但访问稀疏的专家权重放在 CPU 内存里,用 CPU 做这部分矩阵乘法;把对延迟最敏感的 MLA 注意力和 KV Cache 留在 GPU 上。GPU 负责的是每个 token 都要走一遍的稠密计算,CPU 负责的是“这次轮到哪几个专家”的稀疏查找。

这个分工之所以成立,是因为 MoE 的专家访问在单 token 维度上是稀疏的,CPU 那边只是做一批小规模的 GEMV,虽然内存带宽是瓶颈,但不会像稠密模型那样把 CPU 直接压死。代价就是速度上不去——15 token/s 是这套方案的典型水平,比 GPU 全载要慢一个量级,但对话场景完全够用。

项目本身是 Python 为中心的框架,设计上强调可扩展:一行代码接入优化模块,接口和 Transformers 保持兼容,还提供符合 OpenAI 和 Ollama 规范的 RESTful API。早期版本带过一个简化版 Web UI,新版已经弃用,所以后面要自己接 Open-WebUI。

2.3 三条路线的选型对照

选型时我一般按三个问题走一遍:

第一,你要不要微调?要微调,KTransformers 这条线直接排除,它面向的是纯推理。原文里提到的“强推理、弱训练”硬件配置也是同一个逻辑——国产芯片、一体机、Mac Mini 集群这类方案训练能力弱但推理能打,45 万左右能凑出跑满血版的 Mac Mini 集群,比买英伟达卡省很多,但同样不适合做训练和微调。

第二,你的并发是多少?KTransformers 在长输出和多并发时显存会从 13G 涨到 20G,如果预期同时在线人数上到几十人,要提前把显存水位留够,或者接受排队。

第三,你能接受多慢?15 token/s 大约是每秒七八个汉字,阅读体验略慢于正常语速但可接受。如果业务要求实时字幕级别的响应,那就得回到 GPU 方案或者用蒸馏模型。

Unsloth 那条路的优势在于对 CPU 和内存完全没要求,一台普通工作站插张 24G 卡就能起,量化到 1.58bit 后仍保留原版大部分能力,这是它技术能力强的地方。缺点是极低比特量化的稳定性在长上下文任务上还需要自己验。两条路我都建议先小规模试,别一次性把机器买死。

3. 环境准备:480G 内存机器上的版本锁定与 350G 权重落地

环境这一章看起来枯燥,但翻车基本都发生在这里。权重 350G 左右,下载动辄四到六小时,中途断一次就要重来;依赖版本差一个小版本,编译阶段就报一堆看不懂的链接错误。所以这一步的原则是:版本全部锁死,下载全部可续传。

3.1 硬件与软件栈清单

这次实验的配置如下,可以直接照抄:

项目配置
GPURTX 4090 24GB × 4(实际只用一张)
CPU64 vCPU(Intel Xeon Gold 6430 级别)
内存480G
硬盘1.8T(实际占用约 380G)
操作系统Ubuntu 22.04
Python3.12
PyTorch2.5.1
CUDA12.4

几个配置上的判断值得说清楚。GPU 对实际运行效率提升不大,单卡 3090、单卡 4090、甚至多卡服务器差别不明显,只要留够 20G 以上显存就行,做最小可行性实验 14G 也能跑起来。多卡服务器可以手动编写权重卸载规则,把更多层放到 GPU 上,能一定程度减少内存需求,但对速度提升有限。最省钱的组合仍然是单卡 + 大内存。

CPU 这边要留意指令集。从 V3.0 开始只支持 AMX CPU,也就是最近几代 Intel 才有;V2.0 和 V2.1 对各类 CPU 都兼容。AMX 是 Intel 在 Sapphire Rapids 系列上引入的矩阵运算加速指令集,对深度学习场景的矩阵乘提升明显。如果你的 CPU 支持 AMX,用 V3.0 速度会快不少;不支持就只能走 V2.x。

没有物理机的话,可以在 GPU 租赁平台上开四卡 4090、480G 内存的实例,价格大约 14 元每小时,跑通全流程再决定要不要买机器,这个顺序我强烈建议。

3.2 用 screen 和 HF_HOME 把 6 小时下载变成可控任务

下载 350G 权重最怕的是 SSH 断连。第一件事是开持久化会话:

# 安装并创建一个名为 kt 的持久会话,断连后进程继续跑 sudo apt install -y screen screen -S kt # 之后任何时候重新连上机器,用这条命令回到会话 screen -r kt

screen -S创建的是命名会话,进程挂在会话里而不是挂在 SSH 连接上,所以关掉终端、断网、笔记本合盖都不影响。screen -r kt是重新附着。这套东西看起来老派,但在几百 G 的下载任务面前比任何花哨的工具都靠谱。

接下来改默认下载路径。HuggingFace 系的工具默认把文件塞到/root/.cache,而系统盘往往只有几十 G,下载到一半磁盘满是最常见的翻车方式之一:

# 在大容量数据盘上建目录 mkdir -p /root/autodl-tmp/HF_download # 写入环境变量,追加到 ~/.bashrc 末尾 echo 'export HF_HOME="/root/autodl-tmp/HF_download"' >> ~/.bashrc source ~/.bashrc # 验证是否生效,应该输出上面那个路径 echo $HF_HOME

HF_HOME是模型托管工具链读取的根目录环境变量,缓存、锁文件、临时分片都会落在它下面。写成export放进.bashrc而不是只在当前 shell 里export,是为了让 screen 里新开的 shell 也能继承。改完一定要source一次并用echo验证,这一步漏了的话后面下载照样往系统盘写。

3.3 只拉 Q4_K_M:allow_patterns 的写法和校验

仓库里的 GGUF 文件包含多个量化档位,全拉下来是几百 G 的浪费。用allow_patterns只匹配需要的那个:

pip install -U huggingface_hub
# 只下载 Q4_K_M 档位的权重文件 from huggingface_hub import snapshot_download snapshot_download( repo_id="unsloth/DeepSeek-R1-GGUF", # 社区压制的 R1 GGUF 仓库 local_dir="DeepSeek-R1-GGUF", # 相对当前目录落地 allow_patterns=["*Q4_K_M*"], # 关键:只匹配 Q4_K_M 分片 max_workers=8, # 并发下载线程,带宽足可调高 resume_download=True, # 断点续传,中断后重跑不重复拉 )

repo_id指向的是社区已经压制好的 GGUF 仓库,也可以自己用 llama.cpp 的量化工具从原始权重转。local_dir建议放在数据盘而不是家目录。allow_patterns是 glob 匹配,*Q4_K_M*会把所有带这个标记的分片和配套文件都拉下来,注意不要写成Q4_K_M*,那样可能漏掉前缀带路径的文件。resume_download在旧版本里是默认行为,新版本用hf_hub_download时需要显式开,写上是保险。

下载完成后必须做一次完整性检查,不能直接进下一步:

# 检查总分片数和总体积,Q4_K_M 全量在 350G 上下 ls -lh DeepSeek-R1-GGUF/*Q4_K_M*.gguf | wc -l du -sh DeepSeek-R1-GGUF # 确认 config、tokenizer 等配套文件是否齐全 ls DeepSeek-R1-GGUF | head -50

分片数量对不上或者体积明显偏小,说明下载中断过,重跑一次snapshot_download即可补齐。配套的 config 和 tokenizer 文件如果缺失,启动时会报模型加载失败,这个错误信息往往指向权重路径,实际原因却是配置文件没下全,很容易带偏排查方向。

4. 编译安装 KTransformers:CPU 指令集决定你走 V2 还是 V3

到这一步机器和权重都就位了,剩下的核心问题只有一个:你的 CPU 支不支持 AMX。这个判断决定了装哪个版本,也决定了后面能拿到什么速度。装错了不会立刻报错,而是在启动瞬间收到一条 illegal instruction 然后进程直接死掉,第一次遇到很容易以为是内存问题。

4.1 V2.0 / V2.1 / V3.0 的分叉点:AMX

目前开放的三个版本里,V2.0 和 V2.1 支持各类 CPU,适配面最广;V3.0 目前只有预览版,只提供二进制文件下载和安装,且只支持 AMX CPU。三个版本的实际部署流程和调用指令完全一样,区别只在底层内核路径和运行速度。所以策略很简单:先查 CPU 是否支持 AMX,支持就上 V3.0,不支持就用 V2.0 打底。

# 查 CPU 型号与指令集,重点看输出里有没有 amx_tile / amx_bf16 lscpu | grep -E "Model name|Flags" | head -2 grep -o 'amx_[a-z0-9]*' /proc/cpuinfo | sort -u

第二条命令如果输出amx_tile、amx_bf16、amx_int8这类标记,说明硬件支持。如果什么都没输出,就走 V2.0,别抱侥幸心理。我一般还会顺手确认一下核数和 NUMA 布局,因为后面cpu_infer要按物理核数来设:

# 物理核数(不含超线程)与 NUMA 节点分布 lscpu | grep -E "^CPU\(s\)|Thread|Core|Socket|NUMA node\(s\)"

64 vCPU 的机器如果是双路,通常意味着两个 NUMA 节点,每个节点各自挂一部分内存。跨节点访问内存的延迟明显更高,后面用numactl绑核能拿到几个 token/s 的提升,这一点在别的部署方案里不太被提及,但对这套重度依赖 CPU 内存带宽的方案很关键。

4.2 源码编译的依赖顺序与常见报错

源码部署比 Docker 麻烦,但对版本的控制力更强,企业环境里更常见。顺序不能乱:先拉代码,再装 Python 依赖,最后编译 C++ 扩展。

# 1. 拉源码到数据盘 cd /root/autodl-tmp git clone https://github.com/kvcache-ai/ktransformers.git cd ktransformers # 2. 建独立环境,Python 版本锁 3.12 conda create -n kt python=3.12 -y conda activate kt # 3. 先装 PyTorch,务必和 CUDA 12.4 对齐 pip install torch==2.5.1 torchvision torchaudio \ --index-url https://download.pytorch.org/whl/cu124 # 4. 装项目依赖 pip install -r requirements.txt # 5. 编译扩展并安装(会调 ninja/cmake,耗时几分钟到十几分钟) bash install.sh

torch的版本必须和 CUDA 对齐,cu124对应 CUDA 12.4,写错成cu121会因为运行时库版本不匹配在 import 阶段报undefined symbol。install.sh内部会调 cmake 和 ninja 编译ktransformers_ext,编译期对 gcc 版本有要求,Ubuntu 22.04 自带的 gcc 11 通常够用,如果报 C++17 特性相关的错误,先看是不是 conda 环境里混进了别的编译器。

编译过程中最常见的两类报错,一类是找不到libnuma,装一下libnuma-dev即可;另一类是 CPU 不支持 AVX512 导致某些算子编译失败,这种情况要么换机器,要么在编译选项里关掉对应优化。编译成功后会装出一个ktransformers包,python -c "import ktransformers"能过就算通了。

如果是 V3.0 预览版,走的是二进制安装路径,不需要编译,但必须确认 CPU 有 AMX,否则运行到第一个矩阵运算就崩。

4.3 启动参数逐个拆:cpu_infer、optimize_rule_path 与显存水位

启动命令不长,但每个参数都对结果有影响:

# 单卡 4090 + 大内存,加载 Q4_K_M 满血版 python -m ktransformers.local_chat \ --model_path /root/autodl-tmp/DeepSeek-R1-GGUF \ --gguf_path /root/autodl-tmp/DeepSeek-R1-GGUF/DeepSeek-R1-Q4_K_M-00001-of-000XX.gguf \ --optimize_rule_path ./ktransformers/optimize/optimize_rules/DeepSeek-V3-Chat.yaml \ --cpu_infer 64 \ --max_new_tokens 1024 \ --port 10002

--model_path指向权重目录,用来读 config 和 tokenizer;--gguf_path指向第一个分片,程序会按命名规则自动找到同目录下的其余分片,所以分片文件名不能改。--optimize_rule_path是这套方案的核心开关,yaml 里定义了哪些模块留在 GPU、哪些下沉到 CPU,官方给 DeepSeek 系列准备了对应的规则文件,直接用不要自己改,除非你要做多卡卸载。

--cpu_infer是 CPU 推理线程数,最容易被随手填成 64 然后速度只有个位数。正确做法是按物理核数而不是 vCPU 数来设,开了超线程的话填 32 往往比 64 快。可以先用lscpu看 Core 数,再上下浮动试两档。

--max_new_tokens直接影响显存水位。KV Cache 随生成长度线性增长,设成 8192 时显存可能从 13G 顶到 20G 以上,24G 卡上再叠加并发就有 OOM 风险。对话场景 1024 到 2048 够用。

启动之后第一件事不是测速度,是盯内存和显存:

# 实时看内存(关键指标 available 不要低于 20G) free -g -s 2 # 每 2 秒刷一次显存占用 nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu \ --format=csv -l 2

正常状态下显存稳定在 13G 左右,内存占用会爬到 400G 上下并保持。如果内存一路涨到接近满并触发 OOM killer,说明max_new_tokens或者并发太高,先把生成长度砍半再观察。

绑 NUMA 能拿到额外收益,尤其是双路机器:

# 把进程绑在 0 号 NUMA 节点,内存也优先从本地节点分配 numactl --cpunodebind=0 --membind=0 \ python -m ktransformers.local_chat --model_path ... --cpu_infer 32 ...

--cpunodebind限制 CPU 亲和性,--membind限制内存分配节点,两个一起用才能避免跨节点访存。如果机器只有一个 NUMA 节点,这条命令没有意义,直接用--interleave=all让内存均匀分布也行。

5. 避坑实录:从 OOM 到速度掉到 2 token/s 的五次翻车

这一节是血泪经验合集。前面几步按文档走基本能通,真正卡人的是那些文档里不会写的边界情况,每条都按“现象、原因、解决”记下来。

5.1 权重下完却报模型加载失败

现象:snapshot_download显示成功,体积也接近 350G,但启动时报找不到 config 或者 tokenizer 文件,进程直接退出。

原因:allow_patterns=["*Q4_K_M*"]只匹配了带这个标记的文件,仓库里的 config.json、tokenizer.json 这些配套文件往往不带量化标记,被过滤掉了。GGUF 目录有时候也依赖仓库根的配置文件。

解决:把匹配模式放宽成["*Q4_K_M*", "*.json", "*.md"],或者干脆先allow_patterns=None拉一遍元数据再补分片。落地后ls一眼,目录里必须同时有权重分片和配置文本,缺一不可。

5.2 跑到一半进程被系统杀掉,日志里只有 Killed

现象:对话几十轮之后终端突然显示Killed,没有任何 Python 异常栈。

原因:Linux 的 OOM killer 在内存耗尽时直接杀进程,不给你留遗言。480G 是推荐值,382G 是理论下限,但并发时的 KV Cache、Python 堆、以及文件页缓存会一起挤占,320G 的机器几乎必然触发。

解决:dmesg -T | tail -50能看到Out of memory: Killed process的记录,确认是被系统杀的。把max_new_tokens砍到 1024、限制同时在线人数、关掉不必要的常驻进程;内存实在不够就换机器,别指望改参数能绕过物理限制。同时vm.overcommit_memory保持默认 0,不要改成 1,那会让系统在真正耗尽前不拒绝分配,死得更难看。

5.3 启动瞬间崩溃,日志只有 illegal instruction

现象:import ktransformers正常,一启动推理就段错误或者Illegal instruction。

原因:装的是 V3.0 预览版,但 CPU 不支持 AMX。二进制里编进了 AMX 指令,跑到对应内核路径就崩,报错信息完全不提 AMX。

解决:先用 4.1 节的命令确认/proc/cpuinfo里有没有amx_tile。没有就卸掉 V3.0,换 V2.0 或 V2.1 重新装。V3.0 目前只有预览版,生产环境我一般不用,稳定性和排错资料都不够。

5.4 速度只有 2 到 3 token/s,比预期慢五倍

现象:能跑通,输出也对,但速度远低于 15 token/s 的参考值。

原因:三个常见来源。--cpu_infer按 vCPU 数填成 64 导致线程争抢;进程跨 NUMA 节点访存;内存没插满或者插成了非对称配置,带宽只有一半。

解决:--cpu_infer改成物理核数(开了超线程就填一半),用numactl绑核绑内存,lscpu确认 NUMA 节点内存在位。改完重测,一般能从 2 到 3 token/s 回到 10 以上。如果还是慢,看nvidia-smi里 GPU 利用率,长期贴近 0 说明 GPU 那边没干活,检查optimize_rule_path是否指向了正确的规则文件——指向错的 yaml 会让本该上 GPU 的模块留在 CPU,速度掉一个量级。

5.5 长输出或多并发时显存爆到 24G

现象:单人短对话没问题,一旦生成长文本或者第二个人同时提问,显存冲到 23G 以上并 OOM。

原因:KV Cache 随上下文长度和并发数增长,13G 是空闲基线,长输出时 20G 是正常水位,再叠加一路并发就顶到天花板。

解决:显存预算按 20G 打,留 4G 余量;max_new_tokens设上限;服务层做并发限流(后面 FastAPI 那层就是干这个的)。如果确实需要更高并发,可以考虑多卡部署并手写权重卸载规则把部分专家也放到第二张卡上,但收益有限,不如直接上第二台机器做水平扩展。

6. 包一层 OpenAI 风格 API:接上 Open-WebUI 后的并发与延迟实测

KTransformers 自带的服务入口已经提供了符合 OpenAI 和 Ollama 规范的 RESTful 接口,理论上 Open-WebUI 可以直接连。但直接暴露给内部使用有两个问题:模型名是本地路径形态,前端选模型时很别扭;没有鉴权和限流,谁都能把并发打满。所以我一般加一层很薄的 FastAPI 代理,只做三件事:统一模型名、加鉴权、流式透传。

6.1 FastAPI 薄壳:鉴权、模型名改写与流式透传

import os import httpx from fastapi import FastAPI, Request, Header, HTTPException from fastapi.responses import StreamingResponse, JSONResponse KT_BASE = os.getenv("KT_BASE", "http://127.0.0.1:10002") # 后端 KT 服务地址 MODEL_ALIAS = os.getenv("MODEL_ALIAS", "deepseek-r1-q4km") # 对外统一模型名 API_KEY = os.getenv("API_KEY", "sk-local-only") # 简易鉴权口令 app = FastAPI() client = httpx.AsyncClient(base_url=KT_BASE, timeout=None) # 长输出不能设短超时 @app.post("/v1/chat/completions") async def chat(request: Request, authorization: str = Header(default="")): if authorization.replace("Bearer ", "") != API_KEY: raise HTTPException(status_code=401, detail="invalid key") body = await request.json() body["model"] = MODEL_ALIAS # 屏蔽后端真实路径,前端只认别名 stream = body.get("stream", False) headers = {"Content-Type": "application/json"} if stream: async def gen(): # 逐块透传 SSE,不要先聚合再返回,否则首 token 延迟翻倍 async with client.stream("POST", "/v1/chat/completions", json=body, headers=headers) as r: async for chunk in r.aiter_raw(): yield chunk return StreamingResponse(gen(), media_type="text/event-stream") r = await client.post("/v1/chat/completions", json=body, headers=headers) return JSONResponse(r.json(), status_code=r.status_code)

KT_BASE指向本机 KT 服务的端口,改这里就能切换后端。MODEL_ALIAS是给前端看的名字,Open-WebUI 那边填这个名字就能选到模型。timeout=None很关键,15 token/s 生成两千字要一分多钟,httpx 默认 5 秒超时会直接掐断。流式分支用aiter_raw而不是aiter_text,避免代理层做二次编码导致 SSE 的data:前缀错乱。非流式那条路主要用于脚本调用和健康检查。

启动:

# 单 worker 即可,瓶颈在后端不在代理层 uvicorn api:app --host 0.0.0.0 --port 8000 --workers 1

代理层不要开多 worker,因为每路请求最终都压到同一个 KT 后端,多开只会让限流更难做。

6.2 Open-WebUI 对接的三个参数

Open-WebUI 侧只需要填三处,都在设置里的连接页面:

配置项填写值说明
API 基础地址http://<服务器IP>:8000/v1指向代理层而不是 KT 后端
API Key与API_KEY一致代理层做校验,后端不校验
模型名称deepseek-r1-q4km手填,不要依赖自动拉取列表

模型名必须手填,因为代理层没有实现/v1/models的完整列表返回,自动拉取会拿到空列表,前端就显示“无可用模型”。如果希望自动发现,在代理里补一个GET /v1/models返回固定结构即可,几行代码的事。另外 Open-WebUI 默认会把请求发成流式,正好匹配上面的透传分支。

6.3 并发压测与验证指标

上线前必须自己压一遍,知道天花板在哪。我一般用几十条固定 prompt 跑梯度并发,同时盯三个指标:首 token 延迟、单路生成速度、显存水位。

并发数首 token 延迟单路速度显存占用观察
11 至 2 秒约 15 token/s13G 至 14G基线
22 至 4 秒约 9 token/s17G 左右可接受
45 秒以上约 5 token/s20G 上下体验下降明显
810 秒以上3 token/s 以下接近 24G不建议开放到达

这组数字说明这套方案的合理定位是“小团队内部工具”,两人并行还算顺畅,四人以上就要靠排队。压测命令简单写:

# 4 路并发,每路 200 token,观察总耗时与显存 for i in $(seq 1 4); do curl -s http://127.0.0.1:8000/v1/chat/completions \ -H "Authorization: Bearer sk-local-only" \ -H "Content-Type: application/json" \ -d '{"model":"deepseek-r1-q4km","messages":[{"role":"user","content":"用三百字解释 MoE"}],"max_tokens":200,"stream":false}' \ -o /tmp/out_$i.json & done wait

跑的时候另开一个窗口nvidia-smi -l 2,看显存曲线是否陡增。显存逼近 22G 就该在后端加信号量限流了,别等 OOM 再说。

最后说个我自己的习惯。这套方案我从第一次跑通到现在,每次换机器部署,都会先用 200 token 的短请求把“启动、生成、显存、内存”四个指标各打一遍点,全绿了才敢接前端。有一次图省事跳过了这步,直接连 Open-WebUI 给同事用,结果第二天早上内存被页缓存和并发一起顶满,进程被系统杀掉,前端一直转圈,排查花了两个小时才发现是max_new_tokens忘了设上限。从那以后,max_new_tokens、cpu_infer、numactl三项和压测表,我都强制走一遍再交付。希望这套流程能帮到你,少走点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询