
1. 整体设计与思路拆解手上只有 8GB 显存还想跑本地大模型服务这个事听起来有点极限但其实是有成熟路径的。关键不在于显卡本身而在于你是否理解推理框架的资源调度逻辑以及愿不愿意在模型选型和参数配置上做一些取舍。本文就用 vLLM 这个框架完整走一遍部署、压测、调优的流程整个过程我都实测过下面直接分享我的经验和踩坑记录。先说结论8GB 显卡完全可以部署一个量化到 INT4 的 7B 模型并且提供一个能支撑中低并发请求的 OpenAI 兼容 API 服务。但这个过程不能直接套用网上那些面向 24GB 显卡的教程必须针对小显存场景做专门的参数调优。vLLM 的默认配置是为大显存高吞吐设计的如果你不加干预直接在小卡上跑大概率会碰到显存溢出或者请求排队到超时的问题。1.1 硬件与模型选型的核心逻辑8GB 显存能跑多大的模型关键看两个维度一是模型本身的参数体积二是推理框架在运行时的额外开销。以常见的 7B 级别模型为例FP16 精度下光权重就有约 14GB显然放不进 8GB 显存所以必须走量化路线。INT4 量化后权重约 4GB 左右剩下的空间留给 KV Cache 和激活值这就有戏了。这里有个容易踩的坑不是量化过的模型就一定能塞进小显存。有些 4bit 量化模型在加载时如果配置不对框架会先以 FP16 把部分模块加载到显存再转换瞬时显存就爆了。我后来固定下来的组合是8GB 显卡 量化到 INT4 的 7B 模型 vLLM 的显存调度配置这样能稳定跑起来。1.2 为什么选择 vLLM 而不是其他框架小显存显卡上其实有多个推理框架可选比如 llama.cpp、Transformers 以及 vLLM。我选 vLLM 有几个具体的理由吞吐量优势明显。vLLM 通过 PagedAttention 机制把 KV Cache 分页管理大大减少了显存碎片和浪费这对小显存卡尤其关键。API 服务模式成熟。一行命令就能拉起一个兼容 OpenAI 格式的 HTTP 服务压测时直接用标准客户端就可以打。社区活跃模型格式兼容性好。很多量化模型都能直接用AWQ、GPTQ 等格式支持度都不错。但 vLLM 也有一个明显的缺点为了追求吞吐量默认的显存分配策略比较激进如果不管直接在小显存显卡上启动大概率会遇到 OOM 或者启动失败。这也是这篇文章重点要讲的部分——通过参数调优让它在小显存卡上把资源用到极致同时又不至于崩溃。1.3 部署、压测、调优三阶段的工作流拆解整个实操分三个阶段每个阶段目标独立但前后关联第一阶段是部署目标是把模型用 vLLM 跑起来并确认推理结果正确。第二阶段是压测目标是搞清这个部署能扛住多大的并发请求以及响应时延和吞吐量的关系。第三阶段是调优目标是围绕显存分配、批处理大小和并发策略做调整把性能和稳定性平衡到一个比较理想的状态。这三个阶段如果打乱了顺序去做会出很多莫名其妙的问题。比如还没确认服务正常就先跑到调优改了一堆参数后发现是基线就坏了或者没压测就直接跟着网上的参数抄压测时才发现瓶颈完全不在显存而在并发配置。所以建议还是按部署、压测、调优这个顺序来做每完成一个阶段记录当时的配置和结果后面好对比。在初始阶段我建议把每个关键参数都记录下来比如模型路径、量化方式、最大模型长度、GPU 显存利用率等。这样后面调优时才能知道改了什么、效果怎么样不然改来改去跟玄学一样。2. 部署全流程在 8GB 显卡上把模型跑起来这一节直接进入实操。我会把我用的配置、启动命令和关键参数都列出来同时说明每个参数在小显存卡上为什么要这么设。2.1 环境准备与依赖安装先说环境。操作系统是 Ubuntu 系的 Linux 发行版显卡驱动需要提前装好确保nvidia-smi能看到显卡。Python 环境建议用 3.10 或 3.11 版本vLLM 对 Python 版本有要求版本太老或太新都可能出问题。建议用虚拟环境创建独立的 Python 环境避免和系统里的包冲突。安装 vLLM 最直接的方式是 pippip install vllm这里有个很关键的点vLLM 依赖特定版本的 CUDA 运行时和 PyTorch。直接 pip 安装通常会拉取匹配的预编译版本但如果你系统里的 CUDA 版本和它不一致可能出现启动时找不到 CUDA 库的报错。我的做法是先装 PyTorch 的官方轮子再装 vLLM这样依赖关系更可控。pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install vllm安装完成后验证一下版本和 GPU 是否可用import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))如果输出 True 和你的显卡名称说明环境基本就绪。2.2 模型选择与量化格式注意事项8GB 显存的空间非常紧张模型就不能选 FP16 精度的版本。我的选择是量化到 INT4 的 7B 模型这类模型在 Hugging Face 上通常有现成的量化版本文件大小约 4GB 左右。这样加载后权重占用的显存大约 4GB剩下的空间留给 KV Cache、激活值以及运行时开销。选量化模型时有几个坑要避开不要选那些需要额外动态量化模块的模型因为部分模块在小显存显卡上会在加载瞬间产生额外的显存峰值。尽量选择 vLLM 官方支持列表里的量化格式。像 AWQ 和 GPTQ 这两种量化格式vLLM 支持度比较好压测时也稳定。GGUF 格式也能在 vLLM 里用但表现略逊一筹。一定要看模型仓库页面的说明确认有没有标注 vLLM 兼容性。这里分享一下我踩过的一个具体问题我一开始找了一个 4bit 量化版本加载时直接就卡住不动后来看日志才发现是某些算子在显存不足时触发了 fallback把部分计算放到内存里做速度瞬间掉到不能用的程度。所以一定要看模型仓库页面的说明有没有标注 vLLM 兼容性。2.3 启动服务的核心命令与关键参数解析环境准备好之后就可以启动 vLLM 的 OpenAI 兼容服务了。我用的启动命令如下python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --quantization awq \ --max-model-len 2048 \ --gpu-memory-utilization 0.90 \ --enforce-eager下面逐个解释这些参数在小显存显卡上为什么这么定。--max-model-len是最大序列长度。这个值直接决定了 KV Cache 的上限。8GB 显存上不要贪长2048 是一个相对稳妥的起点。如果你确实需要更长的上下文可以往下调 batch 大小或者换更小一点的模型但默认我先用 2048。--gpu-memory-utilization是 vLLM 允许使用的显存比例。默认值是 0.9也就是说 vLLM 会尝试占用显卡 90% 的显存把一部分留给模型权重和激活值其余用作 KV Cache。小显存卡上可以稍微激进一点设到 0.9 左右但注意如果设成 0.95 以上可能挤压到其他进程的显存导致驱动层面出错。--enforce-eager这个参数非常关键。vLLM 默认会使用 CUDA Graph 来加速推理但 CUDA Graph 在捕获过程中会有额外的显存开销小显存显卡上容易直接爆显存。加上这个参数后vLLM 会切换到 eager 模式显存开销显著降低。代价是推理速度会稍微下降但在 8GB 卡上能稳定跑起来比快几毫秒重要得多。启动命令跑起来后看到类似Uvicorn running on http://0.0.0.0:8000的日志就说明服务已经就绪了。2.4 用 curl 验证服务是否正常服务启动后先用一个最简单的请求验证端到端流程通不通。vLLM 的接口兼容 OpenAI 格式所以可以直接用 curl 发请求curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: /path/to/your/model, messages: [{role: user, content: 你好请介绍一下你自己}], max_tokens: 128, temperature: 0.7 }如果配置正确会返回一段 JSON里面包含模型生成的回复。这一步非常关键一定要确认服务能正常返回结果再进入压测阶段不然压测挂了都不知道是模型问题还是并发问题。在这个阶段我通常还会观察显存占用情况。另开一个终端用nvidia-smi -l 1每秒刷新一次显存数据看看服务稳定时显存占用是多少。8GB 卡上跑 4bit 7B 模型正常显存占用应该在 6.5GB 到 7.5GB 之间如果看到 7.9GB 左右就说明显存非常紧张后面压测时并发稍微大一点就会 OOM。3. 并发压测实战搞清服务的真实容量部署通了之后最核心的问题就是这个服务到底能撑住多大的并发压测压测不仅是看吞吐量更重要的是找到性能拐点和稳定性边界。3.1 压测工具选型与压测思路压测 vLLM 服务最简单的方法是自己写一个 Python 并发脚本用concurrent.futures或aiohttp去并发请求。这样做的好处是灵活可以精确控制并发数和请求内容。如果你不想自己写代码也可以用一些现成的压测工具比如locust或者hey但它们的输出指标不一定贴合大模型推理场景。我的压测思路是这样先测单请求的时延基线再逐步增加并发数观察吞吐量每秒处理请求数和时延变化。重点关注的指标有三个端到端时延从发送请求到收到完整响应的耗时。吞吐量每秒完成的请求数或生成的 token 数。错误率超时或连接失败的请求比例。3.2 基于 Python 的并发压测脚本我写了一个简单的压测脚本用来模拟并发请求。为了不把服务压崩我会先用小并发数测试再逐步加大。脚本逻辑如下import asyncio import aiohttp import time import statistics BASE_URL http://localhost:8000/v1/chat/completions MODEL_NAME /path/to/your/model CONCURRENCY 8 TOTAL_REQUESTS 40 async def send_request(session, semaphore, results, index): async with semaphore: payload { model: MODEL_NAME, messages: [{role: user, content: 用一句话解释什么是量子计算}], max_tokens: 256, temperature: 0.7 } start time.time() try: async with session.post(BASE_URL, jsonpayload, timeout60) as resp: await resp.json() elapsed time.time() - start results.append((index, elapsed)) except Exception as e: results.append((index, -1)) async def main(): semaphore asyncio.Semaphore(CONCURRENCY) results [] async with aiohttp.ClientSession() as session: tasks [send_request(session, semaphore, results, i) for i in range(TOTAL_REQUESTS)] await asyncio.gather(*tasks) latencies [r[1] for r in results if r[1] 0] errors [r for r in results if r[1] 0] print(f并发数: {CONCURRENCY}) print(f总请求数: {TOTAL_REQUESTS}) print(f成功数: {len(latencies)}) print(f失败数: {len(errors)}) print(f平均时延: {statistics.mean(latencies):.2f}s) print(fP50时延: {statistics.median(latencies):.2f}s) print(fP95时延: {sorted(latencies)[int(len(latencies)*0.95)-1]:.2f}s) print(f总耗时: {max(r[1] for r in results if r[1] 0):.2f}s) asyncio.run(main())这个脚本里的semaphore控制并发数。这里要特别说明一下CONCURRENCY是同时发送的请求数量TOTAL_REQUESTS是总共要发的请求数量。压测时这两个值都要调但先动CONCURRENCY看时延变化再动TOTAL_REQUESTS看长时间稳定性。3.3 压测结果解读与性能拐点判断我在 8GB 显卡上实测下来并发数从 1 加到 16 的过程中性能表现很有意思。并发为 1 时时延最小因为所有显存和计算资源都给了这一个请求端到端时延最短。但吞吐量很低因为一个请求完成后下一个请求才能开始中间的空闲时间都浪费了。并发数提升到 4 或 8 时吞吐量显著上升因为 vLLM 的 continuous batching 机制会把多个请求拼成一个 batch 处理这回利用上了。但并发数继续往上加比如到 16 或 32时延会快速上升甚至出现超时。原因是 8GB 显存上的 KV Cache 有限当同时处理的请求太多时每个请求能分到的 KV Cache 空间变小批处理队列变长时延自然就上去了。这里有一个很实用的经验压测时记录吞吐量和时延的拐点。我自己的记录是并发 8 左右时吞吐量和时延的平衡点最好并发超过 16 后错误率开始上升。这个数据可以作为后续调优的基线。3.4 压测过程中的显存监控压测时除了看脚本输出还要盯住显存变化。我用nvidia-smi -l 1持续监控显存。小显存卡上最容易出现的问题是并发数一高显存突然涨到 7.9GB然后触发 OOM进程直接崩掉。这通常不是模型权重的问题而是 KV Cache 在并发升高时的动态分配导致的。看显存的时候不要只看占用总量还要留意波动——如果显存占用随着请求进入和结束出现大幅波动说明 KV Cache 的分配和释放很频繁这时候应该考虑调低--gpu-memory-utilization或限制并发数。我自己调优时最直观的指标就是显存波动的幅度如果波动在 0.5GB 以内说明调度比较健康。4. 显存调优把 8GB 的每一兆都花在刀刃上压测完之后就到了最核心的调优环节。这一节会讲各种调优参数的实际效果以及它们在小显存卡上的最佳实践。4.1 从 0.90 到 0.86一次真实的 OOM 排查记录这里我详细记录一次真实的排查过程。初始配置是--gpu-memory-utilization 0.90并发压测到 16 时服务进程突然退出日志里出现 CUDA OOM 报错。最开始我以为是模型权重加载有问题但重启后单独请求是正常的说明模型本身没问쳓。排查思路是逐步缩小范围看错误日志的完整堆栈确认 OOM 发生在哪个阶段。用nvidia-smi -l 1观察崩溃前显存变化发现显存已经顶到 7.9GB 左右。减小并发数发现并发 8 时稳定16 时崩溃说明问题与并发请求带来的 KV Cache 分配有关。最终解决方案是逐步调低--gpu-memory-utilization。从 0.90 降到 0.86重启服务后并发 16 不再崩溃。为什么 0.04 的差距这么关键因为 vLLM 的显存分配逻辑是先根据gpu-memory-utilization算出一个总预算然后模型权重、激活值、KV Cache 都在这个预算内分配。预算越紧KV Cache 可用的空间越小但一旦超过预算驱动层面就会报 OOM。0.90 时预算太满并发高一点系统开销或临时峰值就突破了边界降到 0.86 后留出了 4% 的缓冲反而能稳定运行。4.2 调整 max-model-len 与 KV Cache 的守恒关系--max-model-len一改KV Cache 的预设上限就会成比例变化。对 8GB 显卡来说这个参数和--gpu-memory-utilization是一对需要联动调整的参数。max-model-len设得越长KV Cache 为每个请求预留的空间就越大同等显存下能并发处理的请求数就越少。对比一下我在不同配置下的实测数据配置并发 8 时均时延并发 8 时吞吐量显存峰值max-model-len 2048, util 0.904.2s约 1.9 req/s7.8GBmax-model-len 2048, util 0.864.5s约 1.8 req/s7.4GBmax-model-len 1536, util 0.863.8s约 2.1 req/s7.2GB可以看到把max-model-len从 2048 降到 1536 后时延和吞吐量都有改善因为 KV Cache 省下来的空间可以容纳更多并发请求。如果你的业务场景不需要特别长的上下文缩短max-model-len是提升小显存卡性能最立竿见影的手段。这里要提示一下max-model-len短了请求里超过这个长度的输入会被拒绝所以一定要结合自己的内容长度需求来调整。如果你的场景以短对话为主1536 甚至 1024 都是够用的如果经常要处理长文档那还是要保留 2048 以上。4.3 降低单请求显存开销的技巧除了调 KV Cache还有一个方向是降低每个请求的显存开销。最常用的手段是控制max_tokens也就是限制模型生成的最大 token 数。生成过程中的 KV Cache 增长和max_tokens直接相关所以把max_tokens从 512 降到 256在长并发场景下能省下不少显存。另外在 8GB 小显存卡上可以尝试把--enforce-eager去掉看 CUDA Graph 能不能用。我实测下来小显存卡用 eager 模式更安全因为 CUDA Graph 捕获时的峰值显存很容易爆掉。但如果你把max-model-len调低后显存有富余可以试试打开 CUDA Graph吞吐量会有小幅提升。这个需要在具体卡上实测没有统一答案。还有一个容易被忽略的点请求的输入长度对显存的影响。如果你的业务里很多请求都带着超长的历史对话那 KV Cache 的膨胀速度会非常快。裁剪历史对话、只保留最近的几轮是对小显存卡最友好的策略。4.4 并发上限与排队策略设定在服务端除了调显存参数还能通过限制并发请求数来避免过载。vLLM 支持通过--max-num-seqs限制同时处理的序列数这个参数本质上是在说同一时间最多处理多少个请求。如果并发超过max-num-seqs多余的请求会排队等待而不是全部挤进显存然后 OOM。对 8GB 显卡来说我建议把--max-num-seqs设在 8 到 16 之间。设得太小显卡有富余算力也用不上设得太大显存和 OOM 风险都上去了。启动命令可以调整为python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --quantization awq \ --max-model-len 2048 \ --gpu-memory-utilization 0.86 \ --enforce-eager \ --max-num-seqs 8这样配置之后哪怕外部压测的并发数飙到 64服务端也会把并发请求控制在 8 以内剩下的排队不会再因为瞬时并发过高而 OOM。4.5 调优后的综合效果对比调优完成之后我重新跑了一轮压测。这里展示一个完整的对比数据给读者一个直观的感觉。调优前和调优后的配置差距主要在三个参数gpu-memory-utilization、max-model-len、max-num-seqs。指标调优前0.90/2048/无限制调优后0.86/1536/8并发 8 时均时延4.2s3.8s并发 8 时吞吐量约 1.9 req/s约 2.1 req/s并发 16 时错误率40%OOM0%排队显存峰值7.8GB7.2GB稳定性并发 16 崩溃并发 64 稳定排队最直观的变化是并发 16 从直接 OOM 变成稳定排队处理错误率降为 0。虽然时延和吞吐量没有翻倍提升但稳定性才是小显存卡部署服务的第一优先级——一个稳定的 2 req/s 服务远比一个偶尔崩溃的 3 req/s 服务可用。5. 常见问题与排查技巧实录调优的过程一定不会一帆风顺这里把我在实战中遇到的典型问题整理出来按现象、排查思路和解决方案列出。这些问题很多都是小显存卡特有的网络上的教程往往不会覆盖。5.1 不同报错类型的快速判读先做一个快速判读表方便你遇到问题时先对照一下。报错或现象可能原因应对方案CUDA out of memory显存预算过低或并发过高调低gpu-memory-utilization调低max-num-seqs启动时卡住不动动态量化模块加载异常换 vLLM 支持度好的量化格式如 AWQ/GPTQ单请求正常并发一高就崩KV Cache 分配不足缩短max-model-len或降并发响应时延突然飙升KV Cache 碎片化或队列堆积观察显存波动重启服务或降低max-num-seqs日志出现ValueError关于序列长度输入超过max-model-len裁剪输入长度或调大max-model-len5.2 启动阶段的问题与排查启动阶段最常见的问题是 CUDA 库版本不匹配。vLLM 在启动时会加载 CUDA 相关库如果系统里没有对应版本会直接报错。排查方法很简单先看报错里提到的.so文件路径再用ldconfig -p | grep搜索该文件是否存在。如果缺少通常需要重装对应 CUDA 版本的 PyTorch。另一个启动阶段的问题是模型加载时间过长。如果你发现服务一直没起来先不要着急 CtrlC看日志是否在正常加载模型。7B 模型从磁盘加载到显存需要一些时间尤其从机械硬盘读取时会更慢。如果加载到一半报 OOM说明初始显存预算不够需要先减小gpu-memory-utilization或换成更小的模型。5.3 压测阶段的问题与排查压测阶段最常见的问题是请求超时。当并发数超过服务处理能力时vLLM 不会拒绝请求而是将它们排队。但如果排队时间过长客户端设置超时时间太短就会提前超时。排查时要把客户端的超时时间和服务端的处理时间分开看先确认服务端日志里有没有对应请求的处理记录再确定是服务端处理慢还是客户端超时设置太短。压测阶段还有可能遇到错误率上升但不崩溃的情况。这种一般不是 OOM而是部分请求触发了显存分配失败但服务进程通过重试或报错返回了错误码。从日志里看有没有MemoryError或CUDA error关键字如果有还是得回到显存调优。5.4 显存监控中的隐藏信号显存监控时有一个容易被忽略的信号显存利用率高但吞吐量低。这种情况往往意味着显存被低效占用比如 KV Cache 碎片化严重或者模型权重占用的比例过高。如果出现这种情况可以试着减少max-model-len、限制max-num-seqs让 vLLM 有更多连续显存来分配 KV Cache。另外还有一个经验如果一段时间不发送请求显存占用会略有下降但不会降到很低因为 vLLM 的 KV Cache 块是预先分配的。所以不要用空闲显存来判断服务负载要看压测时的峰值显存和波动幅度。想要精确控制可以在压测时开启 vLLM 的 metrics 接口看 KV Cache 的使用率。这个接口默认在/metrics路径下返回 Prometheus 格式的指标里面有vllm:num_kv_cache_usage_percent之类的字段。6. 一些实操心得与最后的小建议这一节是我个人经验的总结也算是一些压箱底的技巧希望对你能有帮助。6.1 关于 8GB 显存极限的一点额外思考8GB 显存跑大模型本质上是在模型能力和服务容量之间做权衡。如果你把显存预算大部分给了模型权重模型更聪明了但可用的 KV Cache 变少并发处理能力就弱了。反过来如果为了高并发把模型换得更小模型本身的输出质量又可能下降。这个权衡没有标准答案完全取决于你的实际场景。我个人的体会是如果业务场景对生成质量要求高、并发要求相对低那就把gpu-memory-utilization适当留有余量max-num-seqs设小一点如果场景是大量短请求、对输出质量要求不苛刻那可以换更小的模型或更激进的量化格式把资源腾给 KV Cache提升并发。6.2 提升吞吐量的一个冷门技巧最后分享一个冷门但有效的技巧限制单请求的max_tokens。在压测场景里很多人会默认把max_tokens设成很大的值比如 1024但实际生成到 200 token 时就结束了。这会带来的问题是vLLM 必须为每个请求预留最大长度的 KV Cache 空间哪怕它最终只用了一小部分。把这个上限从 1024 调低到 256 或 512KV Cache 的空间占用能明显下降并发能力也跟着上来。另外还有一个技巧是预热。服务刚启动时前几个请求会触发 CUDA 相关的初始化时延会比正常值高出不少。压测之前先发几个请求预热一下等时延稳定下来再开始正式压测这样测出来的数据才接近真实服务状态。6.3 下一步可以怎么扩展这篇文章写到这里整个流程已经完整了部署、压测、调优以及在 8GB 显卡上的各种实战经验。如果你顺着这条路走通了下一步可以考虑几个方向尝试更小的模型配合更激进的量化方式看看能不能把并发能力提得更高。接入更完整的服务化方案比如加一个负载均衡层把多个推理服务实例串起来。在模型侧做优化比如用投机解码或小模型辅助生成把生成时延打下来。我自己在 8GB 显卡上折腾了一圈之后最深刻的感受是小显存跑大模型关键不是和显卡较劲而是理解框架的资源管理机制。把 vLLM 的显存调度逻辑搞明白之后不光是 8GB 卡换到更大的显卡也能一眼看出该调哪些参数、不该动哪些参数。希望这篇实战记录能帮你在同样的路上少踩几个坑把部署、压测、调优这三件事一次走通。