ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RTX 5060本地LLM并发压测实战:显存、精度与推理框架调优

RTX 5060本地LLM并发压测实战:显存、精度与推理框架调优 在本地显卡上跑 LLM 服务最常遇到的不是模型加载失败而是“人一多就崩”单个请求响应挺快并发一上来要么排队等很久要么直接CUDA Out of Memory。尤其是像 RTX 5060 这类消费级显卡显存和算力被限制在一个相对“家用”的范围里并发能力到底能到多少、瓶颈出在显存还是算力、该用哪种精度和推理框架这些问题网上答案零散且很多都是拿数据中心显卡的结论来套消费级硬件参考价值有限。本文以 RTX 5060 为例完整梳理 LLM 并发测试的方法论。内容覆盖并发概念、显存占用拆解、精度选择FP16/BF16/FP8/INT8、推理框架选型、实际压测脚本与结果解读以及高频踩坑清单。无论你手里是 RTX 5060、RTX 4070 还是更老型号这套思路都能直接迁移。1. 背景与核心概念1.1 LLM 并发到底指什么在 GPU 推理场景里并发通常有两种含义第一种是请求级并发多个客户端同时向推理服务发送请求服务端需要在同一时间窗口内处理多个不同 prompt。比如你在本地部署了一个 Ollama 或 vLLM 服务前端 5 个用户同时提问这就属于请求级并发。第二种是任务级并发同一模型同时被多个业务模块调用每个模块作为独立客户端请求模型服务。比如一个 Agent 系统里有多个子 Agent各自都要调用 LLM 做推理这本质上也是请求并发只是调用方不在一个进程里。在消费级显卡上我们讨论的核心主要是请求级并发以及它带来的三个问题并发请求是否都能成功完成还是会 OOM 崩溃。并发上来之后单请求延迟恶化到什么程度。服务端能否通过连续批处理技术把并发带来的吞吐收益吃下来。需要区分的是LLM 的并发和传统 Web 服务并发很不一样。传统接口并发高瓶颈通常在数据库连接、线程池、IO 等待而 LLM 推理并发核心瓶颈是显存中的 KV Cache 容量和计算单元调度能力。并发请求越多每一条请求占用的 KV Cache 越多显存就越紧张。1.2 消费级显卡与数据中心显卡的差异这一点如果不讲清楚很多测试结论会失真。数据中心显卡如 A100、H100、L40S和消费级显卡如 RTX 5060的核心差异不只是算力还体现在以下几方面维度数据中心显卡消费级显卡显存容量通常 48GB 起步甚至 96GB/141GB常见 8GB~16GB高端 24GB显存带宽极高TB/s 级别相对有限GB/s 级别NVLink 互联支持多卡高速互联一般不支持只能走 PCIe驱动与软件生态为数据中心优化支持更多并发特性驱动面向游戏和创作场景功耗与散热服务器级散热可长时间高负载风冷/水冷长时间高负载会降频稳定性验证经过大规模集群验证更偏向单机个人场景这些差异直接决定并发测试的策略。消费级显卡显存小、带宽有限能支撑的 batch 数量和上下文长度都更受限制。多卡并行方面消费级显卡即便支持多张卡跨卡通信走 PCIe 的效率也比 NVLink 低很多。因此本文测试全部基于单卡场景。1.3 RTX 5060 的定位与并发能力边界RTX 5060 属于 NVIDIA 新一代 Blackwell 架构的消费级显卡RTX 50 系列定位是主流甜品级。它的具体算力和显存规格需要以 NVIDIA 官方发布为准这里不展开编造。但从架构趋势看Blackwell 架构的 Tensor Core 对低精度计算FP8/INT8做了进一步强化这对 LLM 推理是明显利好。关于显存需要提醒的是消费级显卡的“能否跑 LLM”并不只看显存大小还要看显存带宽。同样的 7B 模型如果按 FP16 加载权重需要约 14GB 显存而显存只有 12GB就必须考虑量化。通常 7B~8B 模型FP16/BF16 需要 14~16GB 显存INT8 约 7~8GBINT4 约 4~5GB。所以 RTX 5060 如果最终零售版是 16GB 显存跑 7B~8B 模型配合量化会比较从容如果是 8GB 或 12GB则需要更激进地压缩模型精度和上下文长度。具体以你自己的显卡实际参数为准。2. 环境准备与版本说明2.1 硬件环境清单我建议把以下信息先记录下来方便后续对照显卡型号例如 NVIDIA GeForce RTX 5060。显存容量通过nvidia-smi查看实际可用显存。系统内存建议 32GB 以上推理框架会有一部分 CPU 内存开销。磁盘模型文件较大建议 SSD 且预留至少 2 倍模型文件大小的空间。散热条件机箱风道、显卡满载温度会影响长时间压测的稳定性。注意不同品牌、不同厂商对 RTX 5060 的散热设计、功耗墙设定可能不同建议压测前先跑一轮 GPU 稳定性测试确认显卡不会在满载几分钟后就明显降频。2.2 软件栈说明由于 vLLM 对 Windows 原生支持不完善本文推荐在 Linux 环境或 WSL2Windows Subsystem for Linux下操作。如果你只有 Windows 且不想用 WSL2可以考虑使用 llama.cpp 的 server 模式它原生支持 Windows并发能力也能满足个人研究场景。建议软件版本如下操作系统Ubuntu 22.04 或 WSL2Ubuntu 22.04Python3.10 或 3.11CUDA Toolkit12.1 及以上具体以推理框架要求为准PyTorch2.1 及以上安装对应 CUDA 版本推理框架vLLM 0.6.x 或更新版本压测依赖aiohttp、asyncio如果你的环境已经装好了 CUDA可以在终端确认nvidia-smi python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))预期输出大致如下具体数值取决于环境NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver.如果出现这种提示说明驱动没装好需要先解决驱动问题再继续。2.3 启动前采集基线信息在开始并发测试之前建议先记录一组基线数据nvidia-smi --query-gpuname,memory.total,memory.used,utilization.gpu,temperature.gpu --formatcsv这些数据会在后续结果分析中非常有用。尤其是测试结束后对比显存占用、GPU 利用率和温度能快速判断瓶颈在显存容量还是算力调度。3. 核心原理拆解并发瓶颈从哪里来3.1 显存占用模型要理解并发上限先要理解一次 LLM 推理请求在显存里占了什么。一次完整的前向推理过程显存开销大致分为三块模型权重模型所有参数的存储。7B 模型在 FP16 下约 14GBBF16 同理INT8 约 7GBINT4 约 4GB。激活值Activation推理过程中每层输出的中间张量与 batch size、序列长度正相关。KV CacheTransformer Decoder 在生成每个 token 时需要缓存之前所有 token 的 Key 和 Value 张量。这个缓存是随并发请求数和上下文长度线性增长的。其中 KV Cache 是并发场景下最需要关注的变量。公式可以简化理解KV Cache 大小 ≈ 2 × 层数 × 注意力头数 × 头维度 × 精度字节数 × batch 内 token 总数实际操作中你不需要手算这个公式推理框架通常会在启动时打印显存分配信息。比如 vLLM 启动时会输出 GPU 内存预留情况包括gpu_memory_utilization参数控制的比例。在消费级显卡上由于显存总量有限KV Cache 往往是最先碰壁的地方。并发请求数一旦接近上限OOM 就来了。3.2 精度选择FP16/BF16/FP8/INT8 与显存换算精度选择直接决定显存预算。这里把常见精度的关键特征整理如下精度单字节7B 模型权重显存特点适用场景FP324 字节约 28GB精度最高显存占用最大训练、特殊调试FP16/BF162 字节约 14GB推理主流效果接近 FP32显存充足的消费卡FP81 字节约 7GBBlackwell 架构重点支持速度与显存均衡新一代显卡优先尝试INT81 字节约 7GB量化后显存减半推理速度提升明显多数消费级场景INT40.5 字节约 3.5GB显存占用极小但精度损失与量化耗时需权衡小显存显卡或长上下文需求这里必须提醒FP8 虽然显存减半但并不是所有显卡、所有算子都支持得很好。RTX 5060 属于 Blackwell 架构对 FP8 有硬件支持但具体算子兼容性还要看推理框架的适配进度。INT8/INT4 则主要依赖量化工具链比如 GPTQ、AWQ、GGUF。精度测试的正确做法是在单请求场景下先跑同一批 prompt对比不同精度的生成质量和速度再决定并发测试用什么精度。不要在并发测试阶段才去调精度否则你很难判断性能波动的根因。3.3 批处理与连续批处理为什么并发不等于显存叠加很多新手以为并发 N 个请求显存占用就是单请求的 N 倍其实不是。现代推理框架大多实现了连续批处理。传统静态批处理需要等一个 batch 里的所有请求都完成后才释放显存而连续批处理会在 token 级别动态调度某个请求提前生成完了它的 KV Cache 立即释放新的请求可以马上进入 batch。这样显存利用率更高并发能力也更强。vLLM、TensorRT-LLM、llama.cpp server 都实现了类似的调度机制。这也是为什么同样一张显卡用 vLLM 处理并发请求可能比用 Transformers 原生 pipeline 顺滑很多。不过连续批处理并不是银弹。并发量很高时所有并发请求需要共享计算资源每条请求的生成速度都会下降。并发带来的吞吐提升会有一个拐点超过拐点后延迟急剧上升甚至因为显存碎片或调度开销导致整体吞吐下降。这个拐点正是并发测试要找到的。3.4 延迟、吞吐与首 Token 时延并发测试至少要关注三个指标首 Token 时延Time to First TokenTTFT客户端发送请求到收到第一个生成 token 的时间。它反映的是服务端排队和预填充的效率在流式输出场景下特别重要。平均生成速度Throughput单位时间内服务端能生成多少个 token通常用 tokens/s 表示。单请求总延迟Latency从请求发出到完整响应返回的时间包含排队、预填充和解码阶段。在消费级显卡上一个很常见的现象是并发数从 1 升到 2总吞吐量提升但每个请求的 TTFT 变长并发数继续上升可能吞吐量还在涨但单请求延迟已经超出用户可接受范围再继续上涨就直接 OOM 或触发超时重试了。所以测试并发能力时不要只盯某一个指标。要同时记录这些指标才能判断瓶颈的类型是显存不够OOM还是算力饱和GPU 利用率接近 100%还是调度排队导致延迟过高。4. 完整实战RTX 5060 上 LLM 并发压测4.1 项目结构为了便于操作建议按以下结构组织测试文件llm-concurrency-test/ ├── api_server.sh # 启动推理服务的脚本 ├── concurrency_test.py # 并发压测客户端脚本 ├── prompt_set.txt # 测试用的 prompt 集合可选 └── results/ └── test_log.txt # 测试结果输出4.2 启动推理服务这里以 vLLM 为例。如果你使用的是 Windows 原生环境建议切换到 WSL2 或考虑 vLLM 官方支持的 Linux 环境。首先安装 vLLMpip install vllm然后编写启动脚本api_server.sh内容如下#!/bin/bash # 文件路径api_server.sh export CUDA_VISIBLE_DEVICES0 export VLLM_WORKER_MULTIPROC_METHODspawn vllm serve Qwen/Qwen2.5-7B-Instruct \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.85 \ --max-num-seqs 16 \ --host 0.0.0.0 \ --port 8000 \ --api-key test-key参数说明--tensor-parallel-size 1单卡推理。消费级显卡通常不需要也不建议设置大于 1。--max-model-len 4096限制最大上下文长度。7B 模型如果显存有限这个值要适当调小。--gpu-memory-utilization 0.85允许 vLLM 使用 85% 的显存剩余留给 CUDA context 和其他开销。--max-num-seqs 16最大并发序列数。这个值不是越大越好需要结合显存调整。--api-key test-key启用简单的 API Key 校验防止局域网内其他人误调用。启动后可以在另一个终端验证服务是否就绪curl http://localhost:8000/v1/models \ -H Authorization: Bearer test-key如果返回模型列表说明服务启动成功。关于模型选择如果显存小于 14GB建议选择量化版本或更小参数量的模型。例如可以尝试 Qwen2.5-7B-Instruct 的 INT8 或 INT4 量化版本或改用 Qwen2.5-3B。本文示例以 7B 模型为主实际请根据你的显存调整。4.3 编写并发测试脚本接下来编写一个基于 asyncio 的并发压测脚本。# 文件路径concurrency_test.py import asyncio import aiohttp import time import json import argparse API_URL http://localhost:8000/v1/completions API_KEY test-key PROMPT 人工智能的发展对普通人的日常生活有哪些影响请写出三个具体例子。 async def send_one_request(session: aiohttp.ClientSession, req_id: int, stop_event: asyncio.Event): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: Qwen/Qwen2.5-7B-Instruct, prompt: PROMPT, max_tokens: 256, temperature: 0.7, stream: False } start time.perf_counter() try: async with session.post(API_URL, headersheaders, jsonpayload, timeoutaiohttp.ClientTimeout(total300)) as resp: text await resp.text() elapsed time.perf_counter() - start if resp.status ! 200: return req_id, False, elapsed, 0, resp.status, text[:200] data json.loads(text) generated_tokens len(data.get(choices, [{}])[0].get(text, ).split()) return req_id, True, elapsed, generated_tokens, resp.status, except Exception as e: elapsed time.perf_counter() - start return req_id, False, elapsed, 0, -1, str(e)[:200] async def run_concurrency(concurrency: int, total_requests: int): async with aiohttp.ClientSession() as session: tasks [] for i in range(total_requests): tasks.append(asyncio.create_task(send_one_request(session, i, None))) # 使用信号量模拟并发窗口 semaphore asyncio.Semaphore(concurrency) async def worker(req_id): async with semaphore: return await send_one_request(session, req_id, None) tasks [asyncio.create_task(worker(i)) for i in range(total_requests)] results await asyncio.gather(*tasks) success [r for r in results if r[1]] failed [r for r in results if not r[1]] total_time sum(r[2] for r in results) avg_latency total_time / total_requests success_latencies [r[2] for r in success] avg_success_latency sum(success_latencies) / len(success_latencies) if success else 0 total_tokens sum(r[3] for r in success) print(f\n 并发数: {concurrency}, 总请求数: {total_requests} ) print(f成功请求数: {len(success)}) print(f失败请求数: {len(failed)}) print(f平均总延迟(所有请求): {avg_latency:.2f}s) print(f平均成功请求延迟: {avg_success_latency:.2f}s) if total_time 0: print(f总生成 token 数: {total_tokens}) print(f平均每秒生成 token 数(按成功请求计): {total_tokens / (avg_success_latency * len(success)):.2f} tokens/s) if failed: print(\n失败请求示例:) for r in failed[:3]: print(f req_id{r[0]}, status{r[4]}, err{r[5]}) async def main(): parser argparse.ArgumentParser() parser.add_argument(--concurrency, typeint, default1) parser.add_argument(--total, typeint, default5) args parser.parse_args() await run_concurrency(args.concurrency, args.total) if __name__ __main__: asyncio.run(main())这段脚本主要做了几件事通过asyncio.Semaphore限制同时进行的请求数量模拟不同并发档位。记录每个请求的成功失败状态、总耗时、生成 token 数。最后汇总输出成功数、失败数、平均延迟和吞吐量。需要说明的是脚本中generated_tokens的统计方式是粗略的用空白字符切分得到。更严谨的做法是通过响应中的usage.completion_tokens字段获取但这里为了演示简洁用了近似方式。实际测试时可以通过data[usage][completion_tokens]获取准确数值。4.4 分档压测并记录结果在启动服务后分别设置并发数为 1、2、4、8 进行测试。建议每个并发档位发送 10 个请求避免偶发波动。# 并发 1 python concurrency_test.py --concurrency 1 --total 10 # 并发 2 python concurrency_test.py --concurrency 2 --total 10 # 并发 4 python concurrency_test.py --concurrency 4 --total 10 # 并发 8 python concurrency_test.py --concurrency 8 --total 10同时在压测过程中需要观察 GPU 状态watch -n 1 nvidia-smi重点观察显存使用量、GPU 利用率、温度和功耗。4.5 结果解读下面是一份典型的测试结果示意表数据仅用于说明测试方法具体数字取决于你的显卡、模型、精度和上下文长度并发数成功/总请求平均成功延迟单请求 token 速度显存占用趋势GPU 利用率结论110/105.2s约 30 tokens/s较低60%~80%单请求质量正常210/108.1s约 38 tokens/s上升90%吞吐提升延迟可接受410/1015.6s约 42 tokens/s接近上限95%吞吐继续提升延迟翻倍86/1028.4s约 45 tokens/s触发 OOM90%显存不足出现失败请求从这个结果能明显看出并发数从 1 升到 2系统吞吐提升单请求延迟也上升但仍在可接受范围。并发数继续上升吞吐量提升幅度变小延迟增长却更快。并发数超过某个值后直接因显存不足导致请求失败。这里的“拐点”就是你的显卡在这个模型、这个精度、这个上下文长度下的实际并发上限。之后调优工作可以围绕如何把这个拐点向右移动降低精度、缩短上下文、减少max-num-seqs、调整gpu-memory-utilization等。5. 常见问题与排查思路并发测试过程中最容易遇到以下几类问题。下面用表格做一个总览再展开分析。问题现象常见原因解决思路vLLM 启动时显存不足模型权重 KV Cache 超出显存降低--max-model-len、降低--gpu-memory-utilization、换量化模型并发升高后个别请求失败返回 500 或超时KV Cache 显存不足或服务端排队超时降低--max-num-seqs减小并发窗口优化 prompt 长度单请求延迟正常并发后 TTFT 明显变长请求排队等待调度提高服务端批处理能力或降低单请求max_tokensGPU 利用率跑不满但显存已经满了瓶颈在 KV Cache 容量而非算力压缩上下文长度、使用 INT8/INT4 量化、控制并发数GPU 温度高、频率波动剧烈散热和功耗墙限制改善机箱散热、限制功耗墙nvidia-smi -pl、降低并发压力stream disconnected before completion: concurrency limit exceeded for account调用第三方云端 API 时账号并发受限本地部署不受此限制如果是云端 API需要降低客户端并发或联系服务商提升配额Windows 下 vLLM 安装失败vLLM 对 Windows 原生兼容有限使用 WSL2 或 Docker5.1 显存不足CUDA Out of Memory这是最典型的问题。现象包括 vLLM 启动时直接报错或者并发压测过程中请求返回 500。排查步骤先用nvidia-smi查看空闲显存。检查 vLLM 启动参数中--gpu-memory-utilization是否设置过高。检查--max-model-len是否过大。上下文长度每增加一倍KV Cache 显存会明显上升。确认模型精度。如果模型以 FP16 加载尝试换成 INT8/INT4 量化版本。降低--max-num-seqs给 KV Cache 预留更充分的空间。避免再次出现的建议把显存预算公式写在项目 README 里每次调整模型或精度后重新估算。5.2 排队导致的延迟飙升当并发数上升但显存没爆时最常见的问题是延迟飙升。这通常不是显存不够而是 GPU 计算资源被多个请求共享预填充和自回归解码都需要排队。这种场景下要区分延迟类型如果 TTFT首 token 时间变长说明预填充在排队如果总延迟变长但 TTFT 正常说明解码阶段吞吐不足。缓解方案缩短每条请求的max_tokens减少解码阶段总工作量。缩短输入 prompt 长度降低预填充压力。使用流式输出stream: true至少让用户尽快看到首 token改善体感。如果业务允许在服务端增加请求队列和限流策略。5.3 流式响应中断在流式模式下客户端可能遇到连接中断或响应不完整。这通常不是显卡问题而是客户端读取超时或服务端 keep-alive 配置不匹配。排查时先确认服务端日志是否正常完成请求再检查客户端超时设置。aiohttp.ClientTimeout(total300)代表单次请求最长等待 300 秒如果模型生成速度慢于这个值就会出现超时中断。实际使用中要根据模型速度和max_tokens计算合理的超时时间。5.4 CPU 占用过高有时并发压测后发现 GPU 利用率不高反而是 CPU 占用飙升。常见原因是数据处理、分词和采样逻辑跑在 CPU 上或者推理框架与 CPU 算子库不匹配。检查方法在压测期间执行top或htop确认是哪个进程占用 CPU。确认 PyTorch 是否安装了对齐 CUDA 的版本。检查 vLLM 的调度线程数设置必要时降低 worker 数量。5.5 关于第三方 API 的并发限制在调研时看到类似stream disconnected before completion: concurrency limit exceeded for account的报错。这是调用第三方云端 LLM API 时账号级并发超限导致的。它说明很多云端 API 对单个账号的并发请求数是有限制的超过限制会断开流式连接。这个限制通常与模型供应商的账号配额有关而不是客户端本身的问题。如果你是在本地显卡上自部署不会遇到这种账号级限制但如果你同时调用多个 API 供应商最好在客户端做好并发控制避免触发对方限流。6. 最佳实践与工程建议6.1 根据显存选择模型与精度消费级显卡跑 LLM第一原则是“先满足显存再谈并发”。一张 12GB~16GB 显存的 RTX 5060最稳妥的方案是7B~8B 模型优先用 INT8 或 BF16 缩短上下文。如果追求更高并发可以考虑 INT4 量化。如果模型超过 13B即使能加载KV Cache 空间也会被压缩得很小并发能力会很差。建议在项目开始前做一个显存预算测算表把模型权重、KV Cache 预估、框架开销列出来。6.2 合理设置并发上限与排队策略不要盲目把--max-num-seqs拉到很高。vLLM 的连续批处理虽然能提高利用率但算力是硬上限。推荐的做法是先以单请求为基准记录 GPU 利用率和延迟。再逐步提高并发数找到延迟可以接受的拐点。在服务层主动限流将业务请求控制在拐点以内。客户端也可以使用信号量限制并发避免瞬时请求量打满服务端。6.3 流式输出与响应体感优化在并发受限的前提下流式输出是提升用户体验最有效的手段。即便排队 10 秒只要首 token 快速流出用户感知会好很多。实现时使用 SSEServer-Sent Events解析响应避免等完整 JSON 返回后再渲染。另外可以在应用层增加缓存。相同或相似 prompt 的结果可以缓存一段时间减少重复计算。6.4 功耗、散热与长时间运行稳定性消费级显卡长时间高负载跑 LLM散热是绕不开的问题。压测过程中如果发现显卡温度过高导致频率下降最终吞吐反而下降就需要考虑功耗墙设置和机箱风道优化。可以通过以下命令限制显卡功耗保全频率稳定性sudo nvidia-smi -pl 150注意这个值必须低于显卡默认功耗墙具体数值因显卡型号和散热设计而异。设置后需要重新跑一轮压测确认性能没有明显下滑。6.5 日志与监控并发测试不是一次性任务建议形成可复用的监控方案使用nvidia-smi --query-gpu... --formatcsv -l 1记录 GPU 指标。服务端开启请求日志记录每次请求的 TTFT、生成速度、token 数。压测脚本输出结构化 JSON方便后续画趋势图。完整的监控链路对判断性能拐点和定位故障会有很大帮助。7. 总结与下一步思路这次在消费级显卡上测试 LLM 并发最重要的一个经验是并发能力不是由“显卡有多强”单独决定的而是由显存容量、KV Cache 预留、精度选择、推理框架调度策略共同决定的。RTX 5060 这类显卡单请求表现通常不会太差但并发一上来显存和算力的边界会很快暴露。建议你拿到一张新显卡时先做三件事确认显存和驱动、跑一次单请求基线、再按 1/2/4/8 的档位逐步压测。不要一上来就开 32 并发那样只会得到一堆 OOM 日志。下一步可以继续研究的方向包括量化方案GPTQ、AWQ、GGUF对并发的真实影响、不同推理框架vLLM、llama.cpp、TensorRT-LLM在消费级显卡上的调度差异以及多卡场景下 PCIe 通信对并发的约束。如果你正准备在自己电脑上部署一个本地 LLM 服务希望这篇内容能帮你避开显存和并发上的坑。测试方法论本身是可以复用的换一张显卡、换一个模型流程都一样。动手跑一遍你会对自己手头硬件的真实上限有更清晰的认识。
返回列表