通用大模型压测报告工具

通用大模型压测工具发布

前言

我们部署完大模型在上线之前需要做一个压测报告,需要涵盖业界标准的压测指标,并输出结构化的压测报告。接下来我们写一个通用的压测报告代码。

核心压测指标

指标类别具体指标说明
吞吐量Tokens per second (TPS)每秒生成的 token 数,衡量系统整体处理能力
延迟Time to First Token (TTFT)首 token 延迟(ms),影响用户体验
Inter-token Latencytoken 间平均延迟(ms)
End-to-End Latency (E2E)完整请求响应时间(ms)
并发能力Max Concurrent Requests系统能稳定支持的最大并发数
资源利用率GPU Memory Usage, GPU Util%, CPU%, RAM辅助分析瓶颈
错误率Error Rate (%)请求失败比例(超时、5xx 等)
P99/P95 延迟P95 TTFT, P99 E2E尾部延迟,反映服务质量稳定性

压测工具设计原则

  • 协议兼容:支持 OpenAI API 格式(主流推理后端均兼容)
  • 异步高并发:使用 asyncio + aiohttp 实现高效并发
  • 动态负载:支持固定并发、阶梯加压、RPS 控制
  • 结果结构化:输出 JSON + Markdown 报告
  • 可配置:通过 YAML/JSON 配置模型地址、prompt、并发数等

完整压测代码(Python)

# llm_benchmark.py
import asyncio
import time
import json
import argparse
import numpy as np
import pandas as pd
from typing import List, Dict, Any
import aiohttp
from tqdm.asyncio import tqdm
import yaml

class LLMBenchmark:
    def __init__(self, config_path: str):
        with open(config_path, 'r') as f:
            self.config = yaml.safe_load(f)
        self.base_url = self.config['base_url'].rstrip('/')
        self.model = self.config['model']
        self.headers = {"Content-Type": "application/json"}
        if 'api_key' in self.config:
            self.headers["Authorization"] = f"Bearer {self.config['api_key']}"

        # 压测参数
        self.concurrency = self.config['concurrency']
        self.total_requests = self.config['total_requests']
        self.timeout = self.config.get('timeout', 120)
        self.max_tokens = self.config.get('max_tokens', 512)
        self.temperature = self.config.get('temperature', 0.0)

        # 存储结果
        self.results: List[Dict] = []

    async def send_request(self, session: aiohttp.ClientSession, prompt: str):
        payload = {
            "model": self.model,
            "messages": [{"role": "user", "content": prompt}],
            "max_tokens": self.max_tokens,
            "temperature": self.temperature,
            "stream": False
        }
        start_time = time.time()
        try:
            async with session.post(
                f"{self.base_url}/v1/chat/completions",
                json=payload,
                headers=self.headers,
                timeout=aiohttp.ClientTimeout(total=self.timeout)
            ) as resp:
                response_time = time.time()
                if resp.status != 200:
                    error_text = await resp.text()
                    print(f"Error: {resp.status} - {error_text}")
                    return {
                        "success": False,
                        "error": f"HTTP {resp.status}",
                        "e2e_latency": response_time - start_time
                    }

                data = await resp.json()
                completion = data['choices'][0]['message']['content']
                prompt_tokens = data['usage']['prompt_tokens']
                completion_tokens = data['usage']['completion_tokens']
                total_tokens = data['usage']['total_tokens']

                ttft = None  # 非流式无法获取 TTFT,若需 TTFT 请启用 stream=True 并解析 SSE
                e2e_latency = response_time - start_time
                tps = completion_tokens / e2e_latency if e2e_latency > 0 else 0

                return {
                    "success": True,
                    "prompt_tokens": prompt_tokens,
                    "completion_tokens": completion_tokens,
                    "total_tokens": total_tokens,
                    "e2e_latency": e2e_latency,
                    "ttft": ttft,  # 可扩展为流式实现
                    "tps": tps,
                    "error": None
                }

        except Exception as e:
            return {
                "success": False,
                "error": str(e),
                "e2e_latency": time.time() - start_time
            }

    async def worker(self, session: aiohttp.ClientSession, prompts: List[str]):
        for prompt in prompts:
            result = await self.send_request(session, prompt)
            self.results.append(result)

    async def run(self):
        # 准备 prompts(可从文件加载或生成)
        prompts = self._generate_prompts()
        requests_per_worker = len(prompts) // self.concurrency
        remainder = len(prompts) % self.concurrency
        tasks = []
        async with aiohttp.ClientSession() as session:
            for i in range(self.concurrency):
                start_idx = i * requests_per_worker
                end_idx = start_idx + requests_per_worker
                if i < remainder:
                    end_idx += 1
                task_prompts = prompts[start_idx:end_idx]
                tasks.append(self.worker(session, task_prompts))
            await tqdm.gather(*tasks, desc="Running benchmark")

    def _generate_prompts(self) -> List[str]:
        # 可替换为从文件读取或使用真实数据集
        base_prompt = "Explain the theory of relativity in simple terms."
        return [base_prompt] * self.total_requests

    def generate_report(self) -> Dict[str, Any]:
        df = pd.DataFrame(self.results)
        total_requests = len(df)
        successful = df[df['success'] == True]
        failed = df[df['success'] == False]

        report = {
            "model": self.model,
            "total_requests": total_requests,
            "successful_requests": len(successful),
            "failed_requests": len(failed),
            "error_rate": len(failed) / total_requests * 100,
            "metrics": {}
        }

        if not successful.empty:
            e2e_latencies = successful['e2e_latency'].values
            tps_values = successful['tps'].values
            completion_tokens = successful['completion_tokens'].values

            report["metrics"] = {
                "throughput": {
                    "avg_tokens_per_sec": float(np.sum(completion_tokens) / np.sum(e2e_latencies)),
                    "avg_tps_per_request": float(np.mean(tps_values))
                },
                "latency_ms": {
                    "avg_e2e": float(np.mean(e2e_latencies) * 1000),
                    "p50_e2e": float(np.percentile(e2e_latencies, 50) * 1000),
                    "p95_e2e": float(np.percentile(e2e_latencies, 95) * 1000),
                    "p99_e2e": float(np.percentile(e2e_latencies, 99) * 1000),
                    "max_e2e": float(np.max(e2e_latencies) * 1000)
                },
                "token_stats": {
                    "avg_prompt_tokens": float(successful['prompt_tokens'].mean()),
                    "avg_completion_tokens": float(successful['completion_tokens'].mean()),
                    "avg_total_tokens": float(successful['total_tokens'].mean())
                }
            }

        # 错误详情
        if not failed.empty:
            report["errors"] = failed['error'].value_counts().to_dict()

        return report

    def save_report(self, output_path: str):
        report = self.generate_report()
        with open(output_path, 'w') as f:
            json.dump(report, f, indent=2)
        self._print_markdown_summary(report)

    def _print_markdown_summary(self, report: Dict):
        print("\n" + "="*60)
        print("📊 LLM 压测报告摘要")
        print("="*60)
        print(f"- 模型: `{report['model']}`")
        print(f"- 总请求数: {report['total_requests']}")
        print(f"- 成功请求: {report['successful_requests']}")
        print(f"- 失败请求: {report['failed_requests']}")
        print(f"- 错误率: {report['error_rate']:.2f}%")
        if 'metrics' in report and report['metrics']:
            m = report['metrics']
            print(f"\n📈 吞吐量:")
            print(f"  - 系统总 TPS: {m['throughput']['avg_tokens_per_sec']:.2f} tokens/s")
            print(f"  - 单请求平均 TPS: {m['throughput']['avg_tps_per_request']:.2f} tokens/s")
            print(f"\n⏱️ 延迟 (ms):")
            lat = m['latency_ms']
            print(f"  - 平均 E2E: {lat['avg_e2e']:.2f}")
            print(f"  - P95 E2E: {lat['p95_e2e']:.2f}")
            print(f"  - P99 E2E: {lat['p99_e2e']:.2f}")
            print(f"  - 最大 E2E: {lat['max_e2e']:.2f}")
            print(f"\n🔤 Token 统计:")
            tok = m['token_stats']
            print(f"  - 平均输入: {tok['avg_prompt_tokens']:.1f}")
            print(f"  - 平均输出: {tok['avg_completion_tokens']:.1f}")
        print("="*60)


# 配置文件示例 config.yaml
"""
base_url: http://localhost:8000
model: qwen3-30b-a3b-instruct-2507
concurrency: 16
total_requests: 200
max_tokens: 512
temperature: 0.0
# api_key: your-api-key-if-needed
"""

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--config", type=str, default="config.yaml", help="配置文件路径")
    parser.add_argument("--output", type=str, default="benchmark_report.json", help="输出报告路径")
    args = parser.parse_args()

    bench = LLMBenchmark(args.config)
    asyncio.run(bench.run())
    bench.save_report(args.output)

使用方法

1. 创建配置文件 config.yaml
base_url: http://your-llm-server:8000
model: qwen3-30b-a3b-instruct-2507
concurrency: 32
total_requests: 500
max_tokens: 256
temperature: 0.0
2. 运行压测
python llm_benchmark.py --config config.yaml --output qwen3_30b_report.json
3. 输出内容
  • qwen3_30b_report.json:完整结构化数据(可用于自动化分析)
  • 控制台打印 Markdown 风格摘要(便于汇报)
【2025年10月最新优化算法】混沌增强领导者黏菌算法(Matlab代码实现)内容概要:本文档介绍了2025年10月最新提出的混沌增强领导者黏菌算法(Matlab代码实现),属于智能优化算法领域的一项前沿研究。该算法结合混沌机制与黏菌优化算法,通过引入领导者策略提升搜索效率和全局寻优能力,适用于复杂工程优化问题的求解。文档不仅提供完整的Matlab实现代码,还涵盖了算法原理、性能验证及与其他优化算法的对比分析,体现了较强的科研复现性和应用拓展性。此外,文中列举了大量相关科研方向和技术应用场景,展示其在微电网调度、路径规划、图像处理、信号分析、电力系统优化等多个领域的广泛应用潜力。; 适合人群:具备一定编程基础和优化理论知识,从事科研工作的研究生、博士生及高校教师,尤其是关注智能优化算法及其在工程领域应用的研发人员;熟悉Matlab编程环境者更佳。; 使用场景及目标:①用于解决复杂的连续空间优化问题,如函数优化、参数辨识、工程设计等;②作为新型元启发式算法的学习与教学案例;③支持高水平论文复现与算法改进创新,推动在微电网、无人机路径规划、电力系统等实际系统中的集成应用; 其他说明:资源包含完整Matlab代码和复现指导,建议结合具体应用场景进行调试与拓展,鼓励在此基础上开展算法融合与性能优化研究。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRivers

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值