尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

LLM推理速度优化实战:从TTFT到TPS的调优指南

LLM推理速度优化实战:从TTFT到TPS的调优指南 最近在 Hacker News 上看到一个名字很直接的 Show HN 项目Frontier.fast副标题是 “Help push the frontier of LLM speed forward”。做本地 LLM 部署的人看到这句话应该都有同感同一个模型在别人机器上跑到 50 token/s在自己机器上可能只有 10 token/s甚至更糟。真正影响体验的不是模型本身有多强而是你的硬件、推理引擎、量化精度和批处理策略有没有把算力榨干。这篇文章不打算复刻某个具体仓库的安装文档而是把 LLM 推理速度优化这条链路拆开先讲评测维度再讲部署选型然后是实际验证方法和调优手段。哪怕你不使用 Frontier.fast 这个名字下的任何代码这套思路也适用于 vLLM、llama.cpp、ONNX Runtime、TensorRT-LLM 等主流推理方案。看完之后你能知道自己该测哪些指标、怎么测、怎么判断瓶颈、怎么去优化。1. 核心问题地图评测维度、推理引擎与加速手段速览LLM 生成速度不是一个单一数字而是几个指标的组合。只看一个“每秒生成多少 token”远远不够因为用户感知到的“卡顿”往往来自首 token 等待时间而不是后续的生成速率。能力项说明首 Token 时间用户发出请求到模型返回第一个 token 的耗时决定对话“有没有反应”生成速度稳定生成阶段每秒产出的 token 数决定长文本输出是否高效显存占用模型权重、KV Cache、临时激活值共同占用的显存决定能不能跑起来批量并发同时处理多少条请求决定能不能作为 API 服务对外使用精度支持FP16、BF16、FP8、INT8、INT4 等格式对速度和显存的影响平台覆盖NVIDIA GPU、AMD GPU、Apple Silicon、纯 CPU 环境首 Token 时间TTFTTime To First Token用户问完问题之后多久能看到第一个字。TTFT 太长体验会非常“僵硬”。生成速度TPSTokens Per Second进入稳定输出后每秒生成多少个 token。这个指标决定长篇回答、代码生成、批量任务的吞吐量。显存占用模型文件加载后KV Cache 会随上下文长度动态增长。上下文越长显存占用越大。并发能力如果你是做服务化部署还需要关注同时处理多个请求时的吞吐量。从选型角度看不同推理引擎的侧重点也不一样。vLLM 更偏向 GPU 环境下的在线服务和并发吞吐llama.cpp 对 CPU/GPU 混合环境和低显存场景更友好ONNX Runtime 适合和既有 Python/Windows 生态集成TensorRT-LLM 则更追求 NVIDIA 显卡上的极致性能。没有哪个引擎绝对最好只有适不适合你的硬件和场景。2. 适用场景与使用边界LLM 推理速度优化适合这些场景本地私有化部署。数据不出内网同时希望模型响应速度可用。内部工具接入。把本地模型封装成 API供 OA、研发辅助、知识库问答等系统调用。批量评测和批量生成。需要跑大量提示词对比不同模型、不同量化档位、不同推理参数下的速度和效果。边缘设备和老显卡优化。显存有限需要通过量化、KV Cache 复用等方式把模型塞进可用显存。不适合的情况也要说清楚。如果要做大规模跨机房推理集群靠单机调参和本地量化解决不了问题需要更专业的部署方案。如果只是临时体验一下对话效果不需要自己部署推理引擎直接用现成产品可能更高效。速度优化并不是做得越激进越好量化位数太低会明显拉低回答质量甚至会变成“乱说话”这在生产环境是会出事故的。使用边界是必须强调的部分。本地部署大模型时要注意模型权重本身的 License 是否允许商用、是否允许二次分发用外部数据集做评测或者微调时要确认数据来源合法尤其是涉及个人隐私、内部业务数据或者版权材料时必须先做脱敏和授权确认。把模型服务开放给团队或者互联网使用要加访问控制和日志审计避免被滥用。3. 本地部署环境准备与前置条件不同推理引擎的环境要求差异较大。这里给出一套通用检查清单实际以你选择的引擎官方文档为准。检查项建议操作系统常用的是 LinuxUbuntu/Debian 系较多Windows 也有部分引擎支持GPUNVIDIA 显卡优先显存建议至少能容纳量化后的模型权重CPU仅 CPU 推理也可以跑但速度会明显低于 GPU内存建议 32GB 起大模型加载时 CPU 内存和显存都需要预留CUDA先确认驱动版本支持的 CUDA 版本再安装对应 PyTorch/TensorRT 版本磁盘模型文件动辄几 GB 到几十 GB预留充足空间Python建议 3.10 以上不同框架依赖版本不同尽量用虚拟环境隔离安装 PyTorch 前建议先检查 CUDA 是否可用nvidia-smi python -c import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()返回False基本可以确定是 PyTorch 的 CUDA 版本和驱动版本不匹配。这时候不要急着换显卡驱动先确认驱动支持的 CUDA 最高版本再重新安装对应 PyTorch。很多启动报错不是模型的问题而是 PyTorch、CUDA、驱动三者没对齐。磁盘空间要额外提醒。一个 7B 模型 FP16 权重大约 14GB量化到 INT4 能降到 4GB 左右但模型文件、Python 虚拟环境、依赖包和输出日志加起来很容易超过几十 GB。建议单独建一个models/目录存放权重文件不要和代码目录混在一起否则后面换模型或者清理缓存会很痛苦。4. 推理引擎选择与启动方式选择推理引擎前先想清楚你是在做单机实验、服务化部署还是边缘端推理。下面是几个常见方案的定位vLLM面向 GPU 在线推理支持 PagedAttention、Continuous Batching并发吞吐强适合做 API 服务。llama.cpp包含 GGUF 量化格式CPU/GPU 都能跑对低显存环境友好适合单机实验和 Mac 用户。ONNX Runtime和 Windows/.NET/传统软件栈集成方便适合已有 Python/ONNX 管线的项目。TensorRT-LLMNVIDIA 显卡上追求极限性能但编译和配置相对复杂适合固定硬件环境。这里给出一个 vLLM 风格的启动命令模板实际参数以项目 README 为准python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --dtype bfloat16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --host 127.0.0.1 \ --port 8000重要说明--model既可以是本地目录也可以是 Hugging Face 仓库路径--dtype要参考模型自身的精度要求--gpu-memory-utilization是 vLLM 的参数表示允许使用的显存上限。如果你的显卡只有 8G 显存不要直接设为0.95需要留出系统显示和并发任务的余量。更稳妥的方式是先跑一次小上下文请求用nvidia-smi观察显存占用再逐步调高。如果是 llama.cpp 风格命令类似./llama-cli -m /path/to/model.gguf \ -c 4096 \ -n -1 \ -t 8 \ --temp 0.7-c是上下文大小-n是生成 token 数-t是 CPU 线程数。GGUF 格式下载后先确认它是对应的模型量化版本不要拿一个 8G 显存跑 14GB 的 FP16 GGUF那很可能直接在加载阶段就 OOM。Apple Silicon 用户也可以优先考虑基于 llama.cpp 的 LM Studio、Ollama 这类工具它们已经把 Metal 加速封装好了省去手动编译的步骤。5. 功能测试与效果验证部署完成后一定要做标准化评测而不是凭感觉判断“速度快了”。我建议把测试分为三个层次基础可达性、速度指标、质量抽查。5.1 基础可达性测试先发一个最简单的请求确认服务能返回结果。用 curl 测试 OpenAI 兼容接口curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: your-model, messages: [{role: user, content: 你好}] }如果返回 JSON 里有choices字段说明服务基本可用。这一步失败往往不是推理参数问题而是模型路径错误、依赖缺失、端口被占用。先看服务日志不要急着改推理参数。5.2 速度指标测试下面这个 Python 脚本可以测 TTFT 和稳定生成速度。这段代码是通用模板接口地址和字段需要按你实际部署的推理服务调整import time import requests url http://127.0.0.1:8000/v1/chat/completions payload { model: your-model, messages: [{role: user, content: 用三句话解释什么是 KV Cache}], max_tokens: 512, temperature: 0.7, stream: True } start time.perf_counter() response requests.post(url, jsonpayload, streamTrue, timeout120) first_token_time None token_count 0 for line in response.iter_lines(): if line: line line.decode(utf-8) if line.startswith(data: ) and line ! data: [DONE]: if first_token_time is None: first_token_time time.perf_counter() token_count 1 end time.perf_counter() ttft first_token_time - start if first_token_time else None total end - start tps token_count / (end - first_token_time) if first_token_time else 0 print(fTTFT: {ttft:.2f}s) print(f总耗时: {total:.2f}s) print(f生成 token 数: {token_count}) print(f生成速度: {tps:.2f} token/s)这个脚本用流式响应计算首 token 耗时比非流式请求更贴近真实用户感受。判断标准是如果 TTFT 超过 5 秒用户体感会明显“卡”如果 TPS 低于 5长文本生成会让人等得不耐烦。具体阈值取决于你的应用类型对话场景更看重 TTFT批量生成场景更看重 TPS。5.3 上下文长度与稳定性测试除了速度还要测长上下文稳定性。用同一段请求逐步增加输入长度256、1024、4096 token。观察两个现象一是显存占用是否随上下文线性增长二是长上下文下速度是否会断崖式下降。多数推理引擎在上下文超过某个阈值后KV Cache 重新计算会出现明显降速这时需要检查引擎是否使用了 PagedAttention 或者 KV Cache 量化。判断成功的标准不是“跑通了”而是“在目标上下文长度内速度和显存都处于可接受区间”。如果显存爆掉优先降低max-model-len或者换更低的量化精度而不是强行开一个不现实的超长上下文。6. 优化手段精度、缓存、批处理与投机解码速度评测发现问题之后再谈优化。LLM 推理优化通常围绕四件事减少计算量、减少显存移动、提高并发复用、减少生成步数。6.1 精度选择FP16、BF16、FP32、INT8、INT4这是 LLM 推理里最容易踩坑的环节。FP32 是训练和推理的“安全基准”精度高但占显存多、速度慢。FP16 是很多 GPU 上推理的默认选择速度比 FP32 快显存占用减半。BF16 和 FP16 在显存占用上相同但表示范围更大对梯度数值溢出更宽容不少新版训练权重默认就是 BF16。量化的逻辑则走得更远。INT8 和 INT4 是把权重压缩到更低位宽显存占用大幅下降推理速度在某些硬件上会有提升。但量化有代价过度量化会导致回答质量下降逻辑能力变弱甚至出现幻觉。更稳妥的做法是“先跑 FP16 确认质量再尝试 INT8/INT4 看质量是否可接受”不要一上来就极限量化。使用精度时需要搭配引擎支持。不是所有模型都适合量化成 INT4有些模型在低比特量化下会明显变傻。GitHub 上的模型卡通常都会写推荐精度优先参考模型发布方给的默认参数。显存和质量的平衡点一般需要在自己机器上实际测几个档位才能确定。6.2 上下文长度和 KV CacheLLM 逐 token 生成时需要缓存历史 token 的 Key 和 Value这就是 KV Cache。它随上下文长度线性增长。假设一个 7B 模型在 8192 上下文下KV Cache 可能额外占用数 GB 显存。因此不要盲目把max-model-len设得很大。服务化部署时考虑限制单条请求的最大 token 数。如果引擎支持 KV Cache 量化可以在几乎不影响质量的情况下省下一部分显存。大批量并发时KV Cache 是显存压力的主要来源比模型权重本身更值得关注。6.3 动态批处理和并发GPU 推理最怕“一次只处理一条请求”。很多推理引擎支持 Continuous Batching也就是一边生成新 token一边接收新的请求插队。并发请求越多GPU 利用率越高单用户延迟可能小幅上升但整体吞吐会明显提升。这个特性是 vLLM 这类服务化方案的核心优势也是它比直接跑 Hugging Face Transformers 快很多的原因之一。如果你在脚本里循环调用单条请求没有并发那么 GPU 大部分时间处于等待状态。并发量从 1 提到 8 或 16吞吐量通常会有明显提升但显存占用也会上升。需要找到一个拐点并发继续增加吞吐不再增长反而出现 OOM。6.4 投机解码和前缀复用投机解码的思路是用小模型草拟多个 token大模型一次确认从而减少大模型的串行生成次数。适合 GPU 性能充足、小模型质量能兜底的场景。实现复杂度不低是否启用要看引擎支持程度。另一项容易被忽略的优化是 prompt 缓存。如果多条请求共享相同的前缀比如相同的 system prompt、知识库片段引擎可以复用这部分前缀的 KV Cache避免重复计算。批量评测时如果所有测试提示词都以同一段指令开头这个优化效果会非常明显。7. 接口 API 与批量任务接入把推理服务做成本地 API 之后速度评测只是第一步后面接入业务系统才是重点。主流引擎基本都提供 OpenAI 兼容接口这意味着你的业务代码不需要绑定某个具体引擎只需要实现一套 OpenAI 风格的调用层。7.1 OpenAI 兼容接口调用使用 Python requests 直接调用import requests import json url http://127.0.0.1:8000/v1/chat/completions payload { model: your-model, messages: [ {role: system, content: 你是一个测试助手回答尽量简短。}, {role: user, content: 写一段 Python 代码反转一个字符串。} ], temperature: 0.3, max_tokens: 256, stream: False } resp requests.post(url, jsonpayload, timeout180) data resp.json() print(data[choices][0][message][content]) print(usage:, data.get(usage))注意异常处理。不要只写resp.json()要先检查resp.status_code否则接口超时或返回 500 时脚本会直接抛 JSON 解析报错业务日志里很难排查。7.2 批量任务设计批量任务要注意三个点限流、重试、幂等。一个简单的批量脚本可以这样组织import time import json import requests def call_llm(prompt, retries3): url http://127.0.0.1:8000/v1/chat/completions payload { model: your-model, messages: [{role: user, content: prompt}], max_tokens: 256, temperature: 0.2 } for attempt in range(retries): try: resp requests.post(url, jsonpayload, timeout120) if resp.status_code 200: return resp.json() except requests.exceptions.RequestException as e: print(fattempt {attempt1} failed: {e}) time.sleep(2 ** attempt) return None prompts [ 总结这篇文章的要点..., 把这段文字翻译成英文..., 给这段代码写单元测试... ] results [] for idx, prompt in enumerate(prompts): result call_llm(prompt) if result is not None: results.append(result) print(f[{idx1}/{len(prompts)}] 成功) else: print(f[{idx1}/{len(prompts)}] 失败) time.sleep(0.5) with open(results.json, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2)核心思路是把每条提示词和输出结果落到磁盘失败任务记录重试次数而不是一个异常中断整个队列。任务量很大时不要在一个进程里无限循环把任务队列拆分成多个文件或者数据库表由多个 worker 并发消费失败任务单独导出重跑。如果服务出现超时优先排查的是服务端是否还活着curl http://127.0.0.1:8000/v1/models如果这个接口都响应不了说明服务已经不可用这时候不是脚本重试能解决的需要回到服务端日志看是否有 OOM 或者死锁。8. 资源占用观察与性能调优思路性能调优最核心的一步是学会观察资源占用。进程看起来“卡住”的时候要先分清瓶颈在 GPU 利用率、显存、CPU、内存、还是磁盘 IO。8.1 显存观察在服务运行期间另开一个终端观察显存nvidia-smi --query-gpuindex,name,memory.used,memory.total,utilization.gpu,utilization.memory --formatcsv -l 1显存占用率接近 100% 不代表一定有问题需要结合 GPU 利用率看。如果显存高但 GPU 利用率很低说明模型参数或者 KV Cache 已经加载但请求并没有打满计算单元问题可能在请求端频率太低或者单条请求的批处理逻辑没有生效。如果显存持续增长然后崩溃多数是 KV Cache 或并发请求数量控制不住需要调低max-model-len或并发上限。8.2 CPU 内存观察纯 CPU 推理时观察系统内存和 CPU 多核占用top -o %MEM如果 CPU 线程数超过 16注意核心数是不是被填满。llama.cpp 这类引擎的线程参数-t一般设置为物理核心数不要盲目设为 64过高的线程数反而会因为调度开销降低速度。8.3 性能调优顺序我更建议按顺序做先确认模型能跑、质量正常。测出默认参数下的 TTFT 和 TPS。看显存占用确认剩余空间。小步调整量化档位或上下文长度复测速度和显存。增加并发请求观察吞吐量变化。记录每次调整的配置和结果。所有参数调整都要有记录不要“一边改一边忘”。用量化档位、上下文长度、并发数、TPS 四项列一个简单表格对比不同组合这样最后能得出一个适合你硬件的稳定配置。9. 常见问题与排查方法本地部署 LLM 推理引擎时大部分报错都有固定套路下面列几个高频问题。问题现象可能原因排查方式解决方案启动时报 CUDA 不可用PyTorch、CUDA、驱动版本不匹配运行torch.cuda.is_available()按实际驱动版本重装 PyTorch/CUDA模型加载阶段 OOM模型权重超过显存容量查看nvidia-smi显存占用换量化版本或降低上下文长度请求返回 400 或 404模型名不存在或路径不对调/v1/models接口确认模型名修正请求中的 model 字段响应很慢但 GPU 利用率不高请求并发不足或批处理未生效观察 GPU utilization提高并发请求或调整批处理参数长文本生成中途爆显存上下文超过 KV Cache 容量查看服务日志中的 OOM 记录降低max-model-len或启用 KV Cache 量化API 超时服务端卡死或推理队列过长检查服务日志调大超时时间减少并发必要时重启服务量化后回答质量明显下降量化位数过低或量化方式不合适对比 FP16 输出的回答质量提高量化档位或改用混合精度/部分量化排查的通用原则是先看日志再看资源最后猜参数。不要凭直觉去改temperature或top_p很多“生成结果不稳定”的问题根源不在采样参数而在量化精度、上下文窗口被截断、模型本身能力不足。日志里如果出现OutOfMemoryError直接去降显存需求日志里如果出现CUDA error: out of memory更是明确告诉你显存不够不要绕圈子。10. 最佳实践与建议把速度优化从“能跑”推进到“稳定跑”需要建立一套可以重复执行的工程习惯。第一保留一套最小可运行配置。不管项目多复杂先在固定硬件上跑通一个最小模型和一个最短请求记录命令、配置、结果。后续优化失败时随时可以回退到这套基准而不是从零开始调整。第二模型文件、输入素材、输出结果分目录管理。建议目录结构类似models/ llama-7b-fp16/ llama-7b-int4/ inputs/ prompts.txt outputs/ results_20250101.json logs/ server.log这样批量任务和实验才能追溯。第三批量任务必须加日志和失败重试。一次批量跑 1000 条提示词总会有网络超时、服务重启、单条数据格式异常等问题。没有日志失败后根本不知道从哪里断的。第四接口服务要限制访问范围。本地部署的 API 默认绑定127.0.0.1即可不要为了方便直接绑定0.0.0.0。如果确实需要局域网访问至少加一层 API Key 或者防火墙规则避免服务被内网其他机器恶意调用。如果把 LLM 服务暴露到公网还需要考虑鉴权、限流和内容审核。第五涉及人脸、声音、版权素材、内部文档时先确认授权再使用。在本地跑模型不代表可以随便拿生产数据推理尤其是这些数据经过模型服务时可能被写入缓存或日志。对敏感数据建议关闭服务端日志或做脱敏处理。第六发布或商用前要做效果复核。速度优化和量化降低了成本但可能改变模型行为。上线前准备一组标准测试用例跑一遍优化前后的输出人工确认质量没有明显劣化。11. 总结与下一步Frontier.fast 这个名字抓的是 LLM 推理速度这个痛点但真正值得你投入时间的不是某个具体脚本而是你自己机器上那套可复用的“评测-优化-验证”链路。先测出基线速度再判断瓶颈在显存、并发还是量化精度最后用标准化脚本验证每一次改动。最容易踩的坑有三个一是没测 TTFT只盯着总耗时就觉得“变慢了”二是无脑量化到 INT4结果回答质量崩了还找不到原因三是并发量一调高就 OOM不知道 KV Cache 才是显存大头。下一步可以做这几件事把你常用的模型在 vLLM 和 llama.cpp 上各跑一遍记录 TPS 和显存选一个量化档位测长上下文下的稳定性然后接上 RAG、Agent、MCP 这类应用框架把本地推理服务真正嵌入到业务里。速度优化只是起点稳定和可用才是终点。建议先收藏这篇文章等你在部署时遇到具体报错再回来对照排查。想感谢的话不如点个赞让更多人看到这份实测思路。
返回列表