前几天翻出一台 2016 年配的双路 Xeon 老服务器,顺手塞了一张 V100 16GB,折腾了一下午把 Qwen Flash 跑起来了,实测生成速度稳定在 20 tok/s 上下。说实话,这个数字跟新卡比根本不值一提,但放在一台没人愿意碰的老古董上,我觉得很能说明问题:老硬件不一定被时代淘汰,关键是你能不能把它的脾气摸清楚。这篇文章就把硬件判断、模型选型、推理引擎调参和踩过的坑一次性说清楚。手头有旧服务器、旧专业卡,或者单纯想低预算跑本地大模型的朋友,可以按我这套思路来试。
1. 老古董还值不值得折腾:先盘清家底
1.1 2016 年的 Xeon,问题不在核心数
这台服务器用的是 2016 年那批 Xeon,具体型号就不报了,总之是 DDR4 时代早期的产品线。很多人一听"老 Xeon"就觉得一定很慢,其实拆开看,它的问题根本不在 CPU 核心数上。那批 CPU 核心数并不少,双路加起来二十几个物理核心很常见,频率也凑合,真正的短板是指令集和 PCIe 通道的版本。没有 AVX-512,只有 AVX/AVX2,PCIe 也停留在 3.0 时代,单条 x16 的理论带宽大概 15GB/s 上下。
在大模型推理里,CPU 到底干什么?主要是三件事:tokenizer 把文本切词、采样器从概率分布里选下一个词、以及推理框架的调度逻辑。真正的矩阵乘法和注意力计算在 GPU 上跑,KV Cache 也基本在显存里。所以 CPU 慢一点、IPC 弱一点,只要不把整个模型 offload 到内存里跑,影响并没有想象中大。我在实测里发现,只要让 GPU 承担全部计算,老 Xeon 那点 CPU 能力基本能跟上 20 tok/s 的产出速度,tokenizer 和采样器的耗时都还不到一次前向推理的零头。
另一个容易被忽略的点是内存通道和内存容量。双路 Xeon 通常有 8 到 16 条 DDR4 内存通道,即使频率只有 2133/2400MHz,总内存带宽也不差。这在 CPU offload 部分层、或者把长上下文塞不进显存被迫换页到内存时,会成为最后的兜底。如果这台机器只有双通道内存,那才是真灾难。
1.2 V100 的账要这么算:带宽和显存才是关键
V100 是 2017 年出的专业卡,16GB 显存,HBM2,显存带宽接近 900GB/s。单看这个数字,哪怕放到今天也没比很多中端新卡差,甚至比不少 GDDR6 显存的消费卡还强。显存带宽对 LLM 生成速度的影响,比很多人以为的大得多。
大模型在做逐 token 生成时,每一步本质上就是把整个权重从显存里读一遍,然后做计算。所以解码速度的粗算公式可以写成:
理论解码速度 ≈ 显存带宽 / 每 token 需要读取的权重体积
假设一个模型的权重量化后是 5GB,V100 的 900GB/s 理论带宽给到的上限就是 180 token/s。当然这是理想值,实际还要扣掉 KV Cache 读取、算子开销、框架调度、显存 ECC 等因素,但你可以看到,只要模型能塞进显存,哪怕 V100 算力老,解码方向依然有赚头。
V100 真正的麻烦不是快不快,而是兼容性。它只有 sm_70 架构,没有 BF16 支持,第一代 Tensor Core 主要针对 FP16 加速。很多为 Ampere、Hopper 新架构写的算子,比如新的 FlashAttention-2 内核,无法在 V100 上运行,或者运行效率远不如预期。后面我会专门讲怎么绕开这个坑。
| 硬件项目 | V100 16GB 的情况 | 对 LLM 推理的影响 |
|---|---|---|
| 显存容量 | 16GB HBM2 | 能装下 7B 级量化模型,或 3B 级 FP16 模型 |
| 显存带宽 | 约 900GB/s | 解码速度上限更高,老卡也能跑出可用速度 |
| 架构版本 | sm_70,Volta | 部分新算子不兼容,需要选对推理框架和版本 |
| 量化能力 | 无 INT8 Tensor Core | INT4/INT8 量化主要靠通用 kernel,兼容性看运气 |
| PCIe 版本 | 3.0 x16 | 显存够用时影响小,模型反复换页时才明显 |
2. 为什么 Qwen Flash 跑得起来:模型、框架和版本的三方配合
2.1 先定义一下:我用的 Qwen Flash 是什么
标题里的"Qwen Flash"并不是某个严格定义的官方模型,而是我起的一个代号,指"Qwen 系模型跑在 Flash Attention 风格推理方案下的组合"。我这次用的具体权重是一个 Qwen 系列的轻量版本,参数量在 6B 到 7B 之间,经过 GGUF 量化后权重体积大概是 4GB 多,刚好能塞进 V100 的 16GB 显存,同时还能留出足够空间给 KV Cache 和推理框架的运行时缓存。
如果你手里没有现成的 Flash 版本,可以走两个方向:一个是选择一个参数量在 7B 左右的 Qwen 模型,用量化工具转成 GGUF 格式;另一个是直接用更小的 3B 级模型,FP16 原格式也才 6GB 左右,V100 完全吃得下。从我实测的结果看,3B 级模型在 V100 上能跑到 50 甚至更高,但回答质量跟 7B 级差了一截。所以我最终留在 20 tok/s 这个档位的,是一个 7B 级量化模型。
选模型的逻辑不是越大越好,而是要看三件事:显存是否够放量化后的权重、推理框架是否有兼容该量化的 kernel、以及生成速度是否在你的容忍范围内。老硬件尤其要把兼容性放在第一位。真到了 V100 上,某些新模型用 INT4 量化后能装上,但对应 kernel 压根不为 sm_70 编译,加载的时候就报 "no kernel image" 错,白忙一场。
2.2 推理引擎的选择:别再用一次生成一个 token 的老写法
如果直接用最朴素的 PyTorch 循环跑生成,老 Xeon 加 V100 的组合大概率跑不到 20 tok/s,原因不是硬件不行,而是软件栈太浪费。你应该用现代推理引擎,例如 vLLM 或者 llama.cpp。它们有几个核心优化:
PagedAttention/KV Cache 分页管理:不用一次性为整段上下文分配连续的显存,按页分配,碎片少,显存利用率高。Continuous Batching:多个请求不用排队到结束才处理下一个,而是把正在解码的多个请求打到同一个 batch 里一起算,GPU 利用率明显提升。CUDA Graph 和算子融合:减少 Python 调度和 kernel 启动的开销,尤其对小 batch 的单机部署非常明显。
以 llama.cpp 为例,它把模型量化成 GGUF 并用 CUDA backend 推理,V100 作为 sm_70 设备是可以吃满的。只要把-ngl参数拉到全部层都进 GPU,输出速度就会很稳定。以 vLLM 为例,它更适合多用户并发场景,自带 OpenAI 兼容接口,但如果遇到 V100 不支持的注意力算子,需要手动指定兼容的 attention backend。
我个人的建议是:如果你只打算自己用,最多几个入口,直接上 llama.cpp 的 server 最省心;如果你想搭一个对内可用的轻量服务,那 vLLM 更合适。这篇后续实操部分两条路都会写,但默认走 llama.cpp 这条路,因为 V100 上遇到兼容性问题的概率最低。
2.3 最重要的一课:V100 跑不了某些新算子
"新卡能跑,老卡为什么不能跑"这个困惑,我在 V100 上遇到过很多次。最典型的是 FlashAttention 系列。第一代 FlashAttention 支持 Volta,所以 V100 没问题;但后来的 FlashAttention-2 内核面向 Ampere 及以上架构,很多预编译包在 V100 上会直接报错,或者运行到一半崩掉。
有个非常实用的排查思路:凡是报错信息里出现no kernel image is available for execution on the device,基本就是底层算子根本没给 sm_70 编译过。这时候别硬调参数,先换推理后端。比如在 vLLM 里可以设环境变量VLLM_ATTENTION_BACKEND=XFORMERS,让注意力计算走 xFormers 的 kernel;或者退回 llama.cpp,它编译时通常还是保留了对老架构的兼容。
说到底,老卡跑新模型,顺序应该是:先确认算子兼容性,再确认显存容量,最后才看算力。
3. 完整实操:从旧服务器到 20 tok/s
3.1 环境准备与检查
动手之前先花五分钟确认硬件和驱动状态。老服务器最常见的坑是驱动太旧、CUDA 版本太新、以及 GPU 没有跑在正确的 PCIe 通道上。
nvidia-sminvidia-smi里重点看两个东西:驱动版本和显存占用。V100 需要较新的驱动才能支持现代 CUDA 运行时,但如果驱动太旧,装新一些的 PyTorch 会直接检测不到 CUDA。如果你手头的驱动卡在一个不上不下的版本,我建议先升级驱动到支持 CUDA 12.x 的版本,因为新的推理框架大多基于 CUDA 12 编译。
再检查 PyTorch 能不能正确利用 GPU:
python -c "import torch; print(torch.__version__); print(torch.cuda.get_device_capability(0))"如果输出类似(7, 0),说明设备是 Volta 架构,后面所有算子选择都要围绕这个数字。
还要确认一下 GPU 是不是真的跑在 x16 通道上:
lspci -vv -s 01:00.0 | grep LnkSta如果显示LnkSta: Speed 8GT/s, Width x16,那就是 PCIe 3.0 x16,没问题。如果只有 x8,请检查插槽物理位置和 BIOS 设置,老主板经常因为插了多张卡而降速。
3.2 拉模型、选格式、算显存
模型从模型仓库拉下来的时候,注意找带有量化标记的 GGUF 文件。以 7B 级模型为例,Q4_K_M 这种量化档位大约 4.4GB 到 4.7GB。计算显存时不要只算权重,还要给 CUDA context、KV Cache、推理引擎内部缓冲留余量。
显存占用可以粗略分成三块:
- 权重文件:比如 4.5GB
- KV Cache:跟上下文长度成正比
- 框架运行时缓冲:至少 1GB 到 2GB
V100 是 16GB 显存,所以如果权重 4.5GB,就算上下文开到很大,KV Cache 也能放得下。关键是把-ngl设置为足够大的数字,让所有层都在 GPU 上,否则就会把部分层放到 CPU 内存里,每次推理都要过一遍 PCIe 和内存,生成速度立刻掉到个位数。
这里我提供一个实用的显存速查逻辑:权重体积小于显存的一半时,放心全量上 GPU;大于显存的四分之三时,就要缩短上下文长度或者换更激进的量化档位。
3.3 启动服务:llama.cpp 路线
llama.cpp 编译很简单,重点在启动参数。我用的启动命令大概是:
llama-server \ -m /models/qwen-flash-q4_k_m.gguf \ -ngl 999 \ -c 4096 \ --host 127.0.0.1 \ --port 8000-ngl 999的意思是尽可能多地把层放到 GPU 上,实际上会有个层数上限,它会自己按显存余量选择。-c 4096是上下文长度,V100 上 4K 上下文比较稳妥,如果显存余量大,可以试着开到 8K,但要注意 KV Cache 和速度的取舍。
启动后看日志里有没有类似offloaded 32/32 layers to GPU这样的信息。如果显示offloaded 0/32 layers,说明-ngl没生效,赶紧检查参数。如果显示一部分层在 GPU,一部分在 CPU,速度上不去,多半是显存被上下文占满了,缩短-c或者降低 KV Cache 的量化精度。
3.4 启动服务:vLLM 路线
如果要用 vLLM,建议直接起一个 OpenAI 兼容的 API 服务,命令如下:
VLLM_ATTENTION_BACKEND=XFORMERS \ python -m vllm.entrypoints.openai.api_server \ --model /models/qwen-flash \ --dtype float16 \ --max-model-len 4096 \ --gpu-memory-utilization 0.90 \ --swap-space 0注意几点。VLLM_ATTENTION_BACKEND=XFORMERS是为了绕开 V100 上不支持的注意力算子。--dtype float16是因为 V100 原生支持 FP16,BF16 就不要用了。--gpu-memory-utilization 0.90是给显存留一点缓冲,防止 KV Cache 把显存吃满后 OOM。--swap-space 0是关闭 CPU 换页,因为老机器 CPU offload 额外引入的延迟太夸张。
启动后可以用 curl 测试:
curl http://127.0.0.1:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen-flash", "prompt": "讲一个关于老服务器的冷笑话", "max_tokens": 128 }'vLLM 返回结果里通常有 usage 信息,包含completion_tokens和total_tokens。再配合请求耗时,就能算出目前的吞吐。我自己测试的时候,单请求稳定在 20 tok/s 上下,偶尔会冲到 25,长提示词时回落到 17 左右。
3.5 性能测试与结果解读
性能测试别只盯着一个数字。我习惯把时间拆成两个指标:
- 首 token 延迟:从发出请求到吐出第一个 token 的时间,主要受 prefill 阶段影响。
- 生成吞吐:后续每秒钟能生成多少个 token,也就是标题里的 tok/s。
V100 跑 7B 量化模型的 prefill 速度不算快,长提示词下首 token 可能要等一两秒,但用户一旦开始阅读输出,后续的稳定速度更重要。实测里短提示词加长生成,吞吐能达到 21 到 23;长上下文加大量计算后,降到 17 到 18 也算正常。最终写标题时我取了最有代表性的 20,也算是老硬件的"日常水平"。
4. 调优与避坑:从能跑变成好用
4.1 为什么 20 tok/s 不是终点
如果你只发一个请求,20 tok/s 看起来并不是很快。这时候把并发加进去试一下,体验会有明显变化。Continuous Batching 的优势是:多个请求并行解码,GPU 的计算单元和显存带宽被填得更满。同样是 V100,单并发 20 tok/s,四并发下总体吞吐可能冲到 35 到 40,均摊到每个请求可能平均 8 到 12 之间。对老卡来说,这其实提供了一个重要思路:与其追求单个流的速度,不如用并发把硬件"喂饱"。
我在 vLLM 里做了一个简单的压测:同时向 API 发 8 个请求,每个要求生成 256 tokens。整批完成速度比串行快了大概 2 到 3 倍,说明 V100 的利用率和调优空间是存在的。llama.cpp 的 server 也支持多请求并行,效果类似,但内部的调度优化相对保守。
4.2 显存、KV Cache 和上下文长度的精算
很多人在 V100 上跑不出来,问题不是算力而是显存分配不当。KV Cache 的大小可以用一个很粗糙的公式估算:
KV Cache 字节 ≈ 2 × 层数 × 注意力头数 × 头维度 × 上下文长度 × 2 字节 × batch
参数不同差异很大,但直观结论是:上下文越长,KV Cache 越大,留给权重的余量就越小。这台机器最开始我用-c 8192跑,结果部分层被挤到 CPU 上,生成速度直接掉到 9 tok/s,后来调成-c 4096,GPU 全层加载,速度回到 20。这不是显存不够撑不住 8K 上下文,而是推理引擎为了保证不 OOM,会把一部分计算层留给 CPU,反而产生了更大的性能损失。
所以调参时的优先级是:全层 GPU 加载最优先,其次才是上下文长度。如果必须长上下文,优先减小权重体积,比如从 Q4_K_M 换成 Q4_0,而不是去牺牲 GPU offload。
4.3 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 启动就报 no kernel image | 算子没有编译进 sm_70 支持 | 换 llama.cpp;或在 vLLM 里改 attention backend |
| 速度只有个位数 tok/s | 部分层在 CPU 上推理 | 把 -ngl 拉高,或缩短上下文长度 |
| GPU 显存占用不足一半 | 模型根本没有加载到 GPU | 检查 -ngl 参数,确认日志中的 offload 层数 |
| 首 token 特别慢 | prefill 阶段算子效率低 | 减少提示词长度,或换更小的模型 |
| 生成到一半 OOM | KV Cache 占满显存 | 调低 -c,调低 gpu-memory-utilization |
| vLLM 加载量化模型失败 | 量化 kernel 不支持 Volta | 改用 GGUF+llama.cpp,或换回 FP16 小模型 |
4.4 我最后留下的配置
折腾到最后,我保留的配置其实很简单:llama.cpp 编译,Qwen 7B 级 GGUF 的 Q4_K_M,上下文 4096,-ngl 999,单机单卡,纯本地调用。这套配置的好处是启动快、依赖少、兼容性稳,非常适合老服务器。vLLM 那套我也留着了,等后续要多用户同时用的时候再切过去,毕竟 API 接口和并发调度确实更成熟。
5. 20 tok/s 能做什么,以及还能怎么榨
5.1 20 tok/s 的真实体验
20 tok/s 换算成人类阅读速度,大概就是每秒 20 个中文字符,一分钟 1200 字符左右。像这篇博文这么长的内容,AI 生成大概需要几分钟,作为本地聊天助手是够用的。做 RAG、处理文档摘要、写代码片段,都不会让人等得焦虑。
但要说一套 2016 年的老平台能替代新卡,那肯定不现实。V100 面对 7B 量化模型能跑出 20,本质上靠的是显存带宽和容量的硬实力。真到了 14B、32B 这种更大模型,V100 的 16GB 显存会立刻成为天花板,要么放不下,要么只能激进量化导致质量下降。所以老平台的价值在于"小模型也能有好体验",而不是"所有模型都能跑"。
5.2 如果还想继续压榨老平台
有几条我认为值得试的路子。第一条是投机解码,用一个很小的草稿模型先猜多个 token,再用大模型一次验证多个候选。理论上有机会让吞吐再提升一个档,但 V100 上能否用好,还看推理框架是否支持。第二条是把注意力计算换成更老的 FlashAttention-1 内核,它支持 sm_70,在某些场景下比通用 kernel 好很多。第三条是把上下文和 prefill 阶段拆开处理,让长提示词在非生成时段完成计算,避免占住生成阶段的显存和时间。
我个人实际用的技巧,是把 Qwen Flash 的 prompt 模板和系统提示词固定住,并开启前缀缓存。这样同样前缀的请求可以直接复用已经算好的 KV Cache,首 token 延迟能下降不少。老硬件资源有限,能用缓存换来一点都不浪费。
折腾这一趟下来,我的体会是:老平台跑大模型,最重要的不是追求什么新技术全部用上,而是把硬件塞得下、算子支持、速度够用这三个条件同时满足。V100 的 16GB 显存和 HBM2 带宽是它的底子,Qwen 量化模型是它的搭档,推理框架的兼容性选择是让两者配合的关键。20 tok/s 也许并不惊艳,但当它从一台 2016 年的老古董里稳定吐出来的时候,这个数字确实让我对"硬件退役"这件事有了新的看法。