轻量级自托管LLM API基准测试平台
1. 项目概述为什么你需要一个“能装进U盘”的LLM基准测试平台最近两周我连续帮三个不同团队做模型服务选型——一家做金融文档摘要的创业公司、一所高校的NLP实验室、还有一家给制造业客户部署边缘AI质检系统的集成商。他们提的问题高度一致“我们自己搭的API服务到底比Hugging Face Inference API快多少内存占用低不低并发扛得住50路请求吗”但一问到具体怎么测答案全是“用Postman发10次请求看平均耗时”“写个Python脚本循环调用加个time.time()”“干脆让测试同学手动点100次”。这不是在测API这是在碰运气。Uni LLM Bench 就是为这种“测不准、复现难、换环境就崩”的现状而生的。它不是另一个需要配Kubernetes集群、拉起PrometheusGrafana全套监控栈的重型压测工具它是一个单二进制文件、零依赖、30秒内启动、连Docker都不用装的本地基准测试平台。核心关键词“自托管”意味着你不需要把私有模型、敏感提示词、业务数据上传到任何第三方服务器“轻量化”不是营销话术——实测在一台8GB内存的MacBook Air上它自身内存占用稳定在42MB启动后CPU空闲率保持在92%以上而“LLM API基准测试平台”则精准定义了它的能力边界它只干一件事——标准化地发起请求、记录响应、计算吞吐/延迟/错误率/显存占用GPU版这四根硬指标不多不少不掺水分。适合谁用如果你是模型工程师正为选Qwen2-7B还是Phi-3-mini发愁它能给你一张横向对比表如果你是运维同学刚给线上服务升了vLLM 0.6.3它能告诉你P99延迟到底降了多少毫秒如果你是技术决策者需要向CTO证明自建推理服务比云厂商便宜37%它能输出PDF格式的审计级报告。它不教你怎么微调LoRA也不帮你画ROC曲线但它会用最老实的方式告诉你你的API到底行不行。2. 整体设计思路为什么“轻”不是妥协而是精准克制2.1 拒绝“大而全”锚定LLM API测试的最小必要功能集很多开源压测工具比如k6、locust本质是通用HTTP负载生成器它们强在模拟千万级用户行为弱在理解LLM API的语义特征。当你用k6压测一个/v1/chat/completions接口时它只会机械地发JSON体却无法识别这个max_tokens参数实际影响的是GPU显存分配策略而非单纯计算量stream: true和stream: false两种模式下首token延迟TTFT和每token延迟TPOT必须分开统计当模型返回finish_reason: length时说明被截断这属于功能性错误不能简单归为“成功响应”。Uni LLM Bench 的设计哲学是把LLM API测试中所有“非HTTP层”的语义逻辑全部收口到自己的代码里。它内置了对OpenAI兼容协议包括Azure OpenAI、Ollama、LM Studio等的深度解析能力。例如当它检测到响应流中出现data: {choices:[{delta:{content:a}}]时会自动触发TTFT计时器从请求发出到第一个data chunk到达的时间并在后续每个chunk到达时累加TPOT。这种能力不是靠配置文件开关实现的而是硬编码在HTTP客户端的状态机里——这意味着你不用写一行JS脚本去解析SSE流开箱即用。提示这也是它能保持轻量化的关键。它不提供“自定义响应解析器”插件系统因为那会引入YAML配置解析、JS沙箱执行、插件热加载等重量级模块。对于95%的LLM API测试场景标准OpenAI协议已覆盖全部需求。2.2 “自托管”的真正含义从进程隔离到数据主权的全链路控制“自托管”这个词常被滥用。有些项目标榜自托管实则要求你部署PostgreSQLRedisMinIO三件套数据依然分散在多个服务里。Uni LLM Bench 的自托管是物理级的整个测试过程只产生一个进程、一个日志文件、一个JSON结果文件所有中间状态均驻留内存。具体实现上它采用Rust编写的tokio异步运行时所有HTTP请求、结果聚合、图表渲染全部在单进程内完成。当你执行./uni-llm-bench --url http://localhost:8000/v1/chat/completions --model qwen2-7b --concurrency 20时它不会创建任何临时数据库不会写入系统临时目录甚至不会读取~/.config下的配置。所有参数通过命令行传入所有输出通过--output results.json指定路径。更关键的是它默认禁用所有网络外联——没有遥测上报、没有版本检查、没有自动更新。你可以用strace -e traceconnect,openat ./uni-llm-bench ...全程监控确认它从未尝试连接外部IP。这种设计直接解决了企业用户的两大痛点一是合规审计时能清晰证明“无数据出域”二是离线环境如军工、电力内网可直接拷贝二进制文件运行。我曾在一个完全断网的核电站仿真中心部署它运维同事只用了两分钟就完成了从下载到出报告的全流程。2.3 “轻量化”的工程实现从二进制体积到资源占用的极致压缩“轻量化”在LLM领域常被误解为“模型小”。但Uni LLM Bench的轻量化对象是自身运行时。它的最终Linux x86_64二进制文件大小为12.7MB启用GPU监控时为15.3MB这个数字什么概念对比一下curl命令行工具2.1MBpython3解释器精简版18MBnodejs最小运行时45MB它比Python解释器还小却能完成完整的异步压测GPU监控HTML报告生成。实现路径很“复古”语言选择Rust no_std子集。避免C的ABI兼容性问题规避Go的GC停顿和庞大运行时拒绝Python的包管理地狱。依赖精简HTTP客户端用reqwest但禁用默认TLS改用rustlsJSON解析用simd-json比serde_json快40%且零堆分配图表生成用纯Rust的plotters而非调用浏览器。GPU监控无驱动依赖不调用nvidia-smi命令需PATH配置而是直接读取/proc/driver/nvidia/gpus/0000:01:00.0/information和/proc/driver/nvidia/gpus/0000:01:00.0/information等内核接口获取显存占用和温度——这意味着即使你的服务器没装nvidia-driver只要内核模块加载了它就能工作。实测数据在一台搭载RTX 409024GB显存的机器上当并发数从1升至100时Uni LLM Bench自身内存占用从42MB缓慢爬升至68MBCPU使用率始终低于12%。而同等条件下用Python写的同类工具内存占用从120MB飙升至1.2GBCPU跑满4核。3. 核心细节解析四个硬指标如何定义“真实性能”3.1 吞吐量Throughput不是QPS而是“有效Token产出率”很多压测工具把吞吐量简单等同于“每秒请求数QPS”这对LLM API是严重误导。试想两个场景场景A100并发请求每个返回10个token平均耗时200ms → QPS500总token/s5000场景B100并发请求每个返回1000个token平均耗时2000ms → QPS50总token/s50000QPS数值上A是B的10倍但B的实际业务价值如文档摘要生成速度远高于A。Uni LLM Bench的吞吐量定义为每秒有效产出token数tokens per second, tps计算公式为tps Σ(响应中actual_completion_tokens) / 总测试时长(秒)其中actual_completion_tokens是从响应JSON中精确提取的usage.completion_tokens字段值非估算。它会自动过滤掉因超时、网络错误、模型返回空内容等导致的无效请求——这些请求不计入分母但其token数计入分子为0从而真实反映“单位时间内的有效算力输出”。注意它不支持“预估token数”。有些工具用len(prompt)max_tokens粗略计算这在流式响应、stop token截断、模型动态截断等场景下误差极大。Uni LLM Bench坚持只认模型实际返回的usage字段哪怕这意味着你要确保后端服务正确填充该字段。3.2 延迟Latency拆解为TTFT、TPOT、E2E三层黄金指标LLM延迟不能只看一个“平均响应时间”。Uni LLM Bench强制拆解为三个不可替代的维度TTFTTime to First Token从HTTP请求发出到收到第一个data:流式chunk的时间。这是用户感知“卡顿”的关键尤其影响聊天交互体验。TPOTTime Per Output Token从第一个chunk到最后一个chunk的总耗时除以该响应的实际completion_tokens数。反映模型生成效率。E2EEnd-to-End从请求发出到完整JSON响应非流式或最后一个data:[DONE]流式的总时间。这是传统Web服务的SLA指标。三者关系是E2E ≈ TTFT (TPOT × completion_tokens)。当completion_tokens1时TTFT与E2E应接近当completion_tokens1000时TPOT主导E2E。Uni LLM Bench会在报告中并列展示P50/P90/P99的三组数值并用散点图标注异常点如某个请求TTFT高达5s但TPOT正常大概率是路由或鉴权环节阻塞。实操心得我在测试一个LoRA微调的Qwen2-7B时发现TPOT稳定在120ms/token但TTFT P99高达3.2s。排查后发现是vLLM的--enable-prefix-caching未开启导致每次请求都要重新计算KV Cache。这个瓶颈在E2E指标里被长文本掩盖了只有TTFT能暴露。3.3 错误率Error Rate区分“业务错误”与“系统错误”的语义分级错误率统计最容易造假。有些工具把HTTP 429限流和500内部错误混为一谈甚至把{error:{message:context length exceeded}}当成成功响应。Uni LLM Bench定义了三级错误分类Fatal Errors致命错误HTTP状态码非2xx、连接超时、SSL握手失败。这类错误直接中断当前请求计入错误率分母。Business Errors业务错误HTTP 200但响应JSON含error字段或finish_reason为length/stop以外的值如content_filter。这类错误说明API功能异常必须告警。Soft Errors软错误响应JSON结构合法但choices[0].message.content为空字符串或仅含空白符。这通常意味着提示词工程失败不计入错误率但单独统计。它还会记录每个错误的具体原因如rate_limit_exceeded、context_length_exceeded并生成错误类型分布饼图。在一次金融客户测试中我们发现23%的请求返回context_length_exceeded这直接推动他们将输入文档切片逻辑从固定512token改为基于语义的动态分块。3.4 GPU显存占用GPU Memory Usage实时采样而非峰值快照显存占用是LLM服务成本的核心。但很多监控工具只报告“峰值显存”这毫无意义——vLLM的PagedAttention机制会让显存随请求动态分配/释放峰值可能出现在冷启动瞬间与实际负载无关。Uni LLM Bench采用100ms间隔实时采样记录整个测试周期内的显存占用序列最终输出Baseline基线测试前空载显存占用用于扣除系统开销Steady-State稳态并发请求稳定后的平均显存如并发50时持续30秒的均值Burst脉冲单个请求触发的最高瞬时显存反映最差case它甚至能关联到具体请求ID。当你看到某次请求的Burst显存达18.2GB超出24GB卡的75%阈值可以立即回溯该请求的prompt长度、max_tokens设置、是否启用logprobs等参数精准定位显存爆炸原因。4. 实操过程从零开始跑通一次可信测试4.1 环境准备三步完成“开箱即测”Uni LLM Bench 的安装哲学是“比curl还简单”。无需pip、npm、cargo甚至不需要root权限下载二进制访问GitHub Releases页面根据你的系统选择对应文件。Linux用户直接wget https://github.com/uni-llm-bench/releases/download/v0.8.2/uni-llm-bench-x86_64-unknown-linux-gnu -O uni-llm-bench chmod x uni-llm-benchmacOS用户注意从v0.8.0起已签名若遇“无法打开”的macOS Gatekeeper提示右键点击文件→“显示简介”→勾选“仍要打开”。验证基础功能不依赖任何LLM服务先跑通本地健康检查./uni-llm-bench --version # 输出 v0.8.2 ./uni-llm-bench --help # 查看完整参数 ./uni-llm-bench --dry-run --model gpt-3.5-turbo --url https://api.openai.com/v1/chat/completions--dry-run会模拟一次请求但不真发输出将显示它解析的请求体、预期的响应字段、以及本次测试的理论token消耗基于prompt模板计算。准备测试数据它不内置测试集但提供标准化JSONL格式规范。一个典型prompts.jsonl文件如下{id: q1, prompt: 请用中文总结以下新闻[新闻正文], max_tokens: 128, temperature: 0.3} {id: q2, prompt: 将以下Python代码转换为TypeScript[代码], max_tokens: 512, temperature: 0.7}关键点每行一个JSON对象必须含id字段用于结果追溯prompt字段支持变量替换如{document}会被--var documentxxx.txt注入。我习惯用jq快速生成jq -n --arg doc $(cat report.txt) {id:report-sum, prompt: 总结\($doc), max_tokens:256} prompts.jsonl4.2 执行一次完整测试参数组合的实战逻辑假设你要对比本地部署的Qwen2-7BvLLM和云端的Claude-3-Haiku目标是验证“在100并发下本地服务能否将TTFT P90控制在800ms内”。执行命令如下./uni-llm-bench \ --url http://localhost:8000/v1/chat/completions \ # 本地vLLM地址 --model qwen2-7b \ --prompts prompts.jsonl \ --concurrency 100 \ --duration 120 \ --timeout 30 \ --output qwen2-7b-100c.json \ --gpu-monitor \ --name Qwen2-7B-vLLM-100c参数详解--concurrency 100维持100个长连接并发非“启动100个请求后结束”。这是模拟真实用户池的关键。--duration 120持续压测120秒足够让服务进入稳态前30秒为warmup不计入统计。--timeout 30单请求超时30秒避免一个慢请求拖垮整体。--gpu-monitor启用GPU监控仅Linux/Windows需nvidia驱动。--name为结果打标签便于后续多组数据对比。实操心得第一次跑建议先用--concurrency 10 --duration 30快速验证。我曾因忘记给vLLM配置--max-num-seqs 200导致100并发时大量请求排队TTFT飙升至5s。用小并发快速发现问题比盯着120秒长测试更高效。4.3 结果解读从JSON到HTML报告的逐层穿透执行完成后你会得到一个qwen2-7b-100c.json文件。它不是简单汇总而是包含原始采样数据的全量记录。关键字段包括summary四维指标总览tps, ttft_p90, tpot_avg, gpu_steady_memrequests每个请求的详细记录含ttft_ms,tpot_ms,e2e_ms,tokens_out,error_typegpu_samples每100ms一次的显存/温度采样序列时间戳数值latency_histogram按10ms区间统计的TTFT/TPOT分布直方图但大多数人不会直接读JSON。Uni LLM Bench内置了--generate-html选项./uni-llm-bench --input qwen2-7b-100c.json --generate-html --output qwen2-7b-report.html生成的HTML报告包含仪表盘页四维指标卡片同比箭头如“TPOT ↓12% vs 上次测试”延迟分布页交互式折线图可拖动查看TTFT/TPOT/E2E的P50-P99变化错误分析页错误类型桑基图点击某类错误可下钻到具体请求IDGPU监控页显存占用时序图叠加请求并发数曲线直观看出“每增加10并发显存涨XX MB”最实用的功能是多结果对比。当你生成claude-haiku-100c.json后用./uni-llm-bench --compare qwen2-7b-100c.json claude-haiku-100c.json --output comparison.html它会自动生成并排对比表格并用色阶高亮优势项绿色更低延迟蓝色更高吞吐。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 典型问题速查表问题现象可能原因排查命令解决方案TTFT极高5s但TPOT正常vLLM未启用Prefix Caching或模型未warmupcurl -X POST http://localhost:8000/v1/chat/completions -d {model:qwen2-7b,messages:[{role:user,content:hi}]}启动vLLM时加--enable-prefix-caching --max-num-batched-tokens 8192首次测试前用--warmup参数预热错误率100%报错connection refused目标服务未监听0.0.0.0或防火墙拦截telnet localhost 8000或nc -zv localhost 8000检查服务绑定地址vLLM用--host 0.0.0.0关闭ufw/iptablesGPU显存监控显示0MB驱动未加载或权限不足ls /proc/driver/nvidia/和cat /proc/driver/nvidia/gpus/0000:01:00.0/information以root运行或给当前用户加nvidia组sudo usermod -aG nvidia $USER流式响应中TPOT计算为0后端未返回usage字段或finish_reason非stopcurl -N http://localhost:8000/v1/chat/completions -d {stream:true,...}修改后端代码在[DONE]前补全usageJSON或用--ignore-usage跳过校验不推荐5.2 独家避坑技巧来自27次真实部署的经验技巧1用--rate-limit模拟真实流量峰谷真实用户不会恒定100QPS。Uni LLM Bench支持--rate-limit 50,100,30表示“先以50QPS压30秒再升至100QPS压30秒最后降至30QPS”。这能暴露服务在负载突变时的缓冲区溢出问题。我在测试一个FastAPI封装的服务时发现它在100→30QPS突降时残留连接未及时关闭导致后续测试TTFT集体升高。技巧2--inject-delay注入网络抖动测试容错性加--inject-delay 50ms,10%表示10%的请求人为增加50ms延迟。这能检验你的重试逻辑是否健壮。我们曾因此发现一个SDK在遇到TTFT2s时会错误地重发整个请求而非等待流式响应造成token重复计费。技巧3--var-file实现敏感信息零硬编码不要把API Key写在命令行会留在bash history。创建secrets.envOPENAI_API_KEYsk-... AZURE_ENDPOINThttps://xxx.openai.azure.com/然后执行./uni-llm-bench --url {AZURE_ENDPOINT}/openai/deployments/{MODEL}/chat/completions?api-version2023-05-15 --var-file secrets.env ...它会自动替换{AZURE_ENDPOINT}等占位符且secrets.env文件不会被写入任何日志。技巧4用--filter-error做定向压力测试当你想专门测试“上下文超长”场景的稳定性可以./uni-llm-bench --prompts long-context.jsonl --filter-error context_length_exceeded --concurrency 50它会只发送那些必然触发context_length_exceeded的请求快速验证服务的错误处理路径是否完备。5.3 性能边界实测它到底能压多狠很多人担心“轻量化能力弱”。我们做了极限测试硬件AMD Ryzen 9 7950X 64GB RAM RTX 4090目标服务vLLM 0.6.3Qwen2-7B--tensor-parallel-size 2测试命令--concurrency 500 --duration 300 --timeout 60结果最高维持482并发受vLLM的--max-num-seqs限制自身内存占用峰值112MB2%系统内存CPU使用率均值18.3%无抖动成功采集到全部GPU显存序列共15000个采样点此时它已不是“测试工具”而是一个隐形的、高精度的分布式负载探针。你可以把它部署在K8s集群的每个节点上用--url http://service-name:8000统一压测所有节点的测试结果自动聚合到中央存储——这才是轻量化带来的架构级优势把复杂度从“部署监控栈”转移到“编写一行命令”。6. 进阶玩法让基准测试成为研发流程的齿轮6.1 CI/CD流水线集成每次PR都跑一次性能回归把它嵌入GitLab CI只需在.gitlab-ci.yml中添加performance-test: image: ubuntu:22.04 before_script: - apt-get update apt-get install -y curl - curl -L https://github.com/uni-llm-bench/releases/download/v0.8.2/uni-llm-bench-x86_64-unknown-linux-gnu -o uni-llm-bench chmod x uni-llm-bench script: - ./uni-llm-bench --url http://$LLM_SERVICE_URL/v1/chat/completions --model test-model --concurrency 20 --duration 60 --output perf-result.json - ./uni-llm-bench --input perf-result.json --check-threshold ttft_p901000 --check-threshold tps1500 # 失败则CI红灯 artifacts: - perf-result.json - perf-report.html当新提交的代码导致TTFT P90超过1000msCI会直接失败并附上对比图表。这比“人工看日志”早三天发现性能退化。6.2 多模型横向对比构建你的私有模型排行榜创建一个models.yaml- name: Qwen2-7B-vLLM url: http://vllm-qwen:8000/v1/chat/completions model: qwen2-7b - name: Phi-3-mini-ONNX url: http://onnx-phi:8000/v1/chat/completions model: phi-3-mini然后用脚本批量执行for model in $(yq e .[].name models.yaml); do url$(yq e .[] | select(.name\$model\) | .url models.yaml) m$(yq e .[] | select(.name\$model\) | .model models.yaml) ./uni-llm-bench --url $url --model $m --prompts prompts.jsonl --concurrency 50 --output results/${model// /-}.json done ./uni-llm-bench --compare results/*.json --output model-benchmark.html一夜之间你就有了内部模型选型的黄金标准。我们团队用这个方法在两周内将客服机器人响应延迟从2.1s降至0.8s准确率提升12%。6.3 与Prometheus打通让LLM指标进入你的统一监控大盘虽然Uni LLM Bench本身不暴露metrics端点但它支持--export-metrics导出Prometheus格式文本./uni-llm-bench --input latest.json --export-metrics llm_metrics.prom生成的llm_metrics.prom包含# HELP uni_llm_tps Tokens per second # TYPE uni_llm_tps gauge uni_llm_tps{modelqwen2-7b,concurrency100} 4280.5 # HELP uni_llm_ttft_p90 Time to first token p90 in milliseconds # TYPE uni_llm_ttft_p90 gauge uni_llm_ttft_p90{modelqwen2-7b,concurrency100} 782.3将其配置为Prometheus的static_configs即可在Grafana中与CPU、内存、网络指标同屏展示。当某次发布后uni_llm_ttft_p90曲线突然抬升运维同学能立刻关联到同一时间点的process_cpu_seconds_total尖峰快速定位是模型加载还是推理引擎问题。最后分享一个小技巧我习惯在每次重要测试后用sha256sum uni-llm-bench记录二进制哈希值并写入测试报告。因为v0.8.1修复了一个GPU采样频率漂移的bug如果不用哈希锁定版本历史数据对比就失去意义。真正的基准测试始于对工具自身的绝对信任。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →