GLM-4-9B-Chat-1M详细步骤:GPU显存监控+推理吞吐量压测方法论

1. 引言:为什么需要性能压测?

当你把GLM-4-9B-Chat-1M这样的大模型部署到本地服务器后,一个很实际的问题就摆在了面前:它到底能跑多快?能同时服务多少人?会不会把显卡搞崩溃?

很多朋友部署完模型,打开网页界面,输入几个问题,看到有回复就觉得“搞定了”。但这其实只是第一步。真正要把模型用起来,特别是在企业环境里,你需要知道它的性能边界在哪里。比如:

  • 显卡的显存用了多少?会不会在处理长文本时爆掉?
  • 模型每秒能处理多少个字(tokens)?这个速度能支持多少用户同时访问?
  • 如果同时有多个请求进来,模型会不会排队等待?响应时间会变慢多少?

这些问题,都需要通过系统性的性能测试来回答。今天,我就带你走一遍完整的GPU显存监控和推理吞吐量压测流程,让你对自己部署的模型了如指掌。

2. 测试环境与工具准备

在开始压测之前,我们需要准备好“武器”。下面这些工具,大部分你可能已经装好了。

2.1 硬件与基础环境

我的测试环境是这样的,你可以参考:

  • GPU:NVIDIA RTX 4090 (24GB显存)
  • CPU:Intel i9-13900K
  • 内存:64GB DDR5
  • 系统:Ubuntu 22.04 LTS
  • Python:3.10

如果你用的是其他显卡,比如RTX 3090、A100等,方法完全一样,只是具体数值会不同。

2.2 必备的Python库

除了运行GLM-4-9B-Chat-1M需要的库之外,我们还需要几个专门用于监控和测试的工具:

# 安装性能测试和监控相关的库
pip install nvidia-ml-py psutil requests tqdm

简单解释一下这几个库是干什么的:

  • nvidia-ml-py:NVIDIA官方的Python库,用来读取GPU的各种信息(显存、温度、利用率等)
  • psutil:获取系统资源信息(CPU、内存、磁盘等)
  • requests:用来模拟用户请求,发送HTTP请求到我们的模型服务
  • tqdm:显示进度条,让长时间测试有个直观的进度提示

3. GPU显存实时监控实战

显存是运行大模型时最宝贵的资源,也是最先容易出问题的地方。我们先来看看怎么实时监控它。

3.1 编写一个简单的显存监控脚本

我写了一个Python脚本,可以每隔1秒检查一次GPU的状态,并把数据保存下来。你可以直接复制使用:

# gpu_monitor.py
import time
import csv
from datetime import datetime
import pynvml  # 这是nvidia-ml-py的核心模块

def monitor_gpu(interval=1, duration=60, output_file="gpu_usage.csv"):
    """
    监控GPU使用情况
    
    参数:
    interval: 每次检查的间隔时间(秒)
    duration: 总监控时长(秒)
    output_file: 保存数据的CSV文件名
    """
    
    # 初始化NVML
    pynvml.nvmlInit()
    
    # 获取第一个GPU(如果你有多个GPU,可以修改这里)
    handle = pynvml.nvmlDeviceGetHandleByIndex(0)
    
    # 准备CSV文件
    with open(output_file, 'w', newline='') as f:
        writer = csv.writer(f)
        # 写入表头
        writer.writerow(['timestamp', 'gpu_utilization', 'memory_used_mb', 
                        'memory_total_mb', 'memory_utilization', 'temperature'])
        
        print(f"开始监控GPU,将持续{duration}秒...")
        print("按Ctrl+C可以提前结束")
        
        start_time = time.time()
        
        try:
            while time.time() - start_time < duration:
                # 获取当前时间
                current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                
                # 获取GPU利用率
                util = pynvml.nvmlDeviceGetUtilizationRates(handle)
                gpu_util = util.gpu
                
                # 获取显存信息
                mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)
                memory_used = mem_info.used // (1024 * 1024)  # 转换为MB
                memory_total = mem_info.total // (1024 * 1024)  # 转换为MB
                memory_util = (mem_info.used / mem_info.total) * 100
                
                # 获取温度
                temp = pynvml.nvmlDeviceGetTemperature(handle, pynvml.NVML_TEMPERATURE_GPU)
                
                # 写入数据
                writer.writerow([current_time, gpu_util, memory_used, 
                               memory_total, memory_util, temp])
                
                # 打印当前状态(可选)
                print(f"[{current_time}] GPU使用率: {gpu_util}%, 显存: {memory_used}/{memory_total}MB ({memory_util:.1f}%), 温度: {temp}°C")
                
                # 等待指定间隔
                time.sleep(interval)
                
        except KeyboardInterrupt:
            print("\n监控被用户中断")
        finally:
            # 清理
            pynvml.nvmlShutdown()
            print(f"监控结束,数据已保存到 {output_file}")

if __name__ == "__main__":
    # 监控60秒,每秒记录一次
    monitor_gpu(interval=1, duration=60)

3.2 如何运行监控脚本

使用这个脚本非常简单:

  1. 首先,确保你的GLM-4-9B-Chat-1M服务正在运行(通过Streamlit启动的那个)
  2. 打开一个新的终端窗口,运行监控脚本:
python gpu_monitor.py
  1. 在模型服务那边,开始进行一些操作,比如:

    • 上传一个长文档让模型总结
    • 连续问几个复杂问题
    • 尝试让模型分析代码仓库
  2. 观察监控终端的输出,你会看到实时的GPU状态变化。

3.3 监控数据分析:看懂这些数字

运行完监控后,你会得到一个CSV文件。用Excel或者Python的pandas打开,可以看到类似这样的数据:

timestamp gpu_utilization memory_used_mb memory_total_mb memory_utilization temperature
2024-01-15 10:30:01 0% 2456 24564 10.0% 45
2024-01-15 10:30:02 78% 15678 24564 63.8% 58
2024-01-15 10:30:03 95% 18942 24564 77.1% 62

这些数字代表什么?

  • gpu_utilization:GPU计算核心的利用率。0%表示空闲,100%表示满负荷运行。处理请求时应该接近100%,空闲时接近0%。
  • memory_used_mb:当前已经使用的显存大小(MB)。这是最重要的指标
  • memory_total_mb:显卡的总显存大小。
  • memory_utilization:显存使用百分比。建议长期运行不要超过90%,要留一些缓冲空间。
  • temperature:GPU温度。70°C以下比较安全,超过80°C就要注意散热了。

关键观察点

  1. 模型加载后的基础显存占用:启动服务后,即使不处理请求,模型本身就会占用一部分显存。对于GLM-4-9B-Chat-1M的4-bit量化版,这个值应该在8-10GB左右。
  2. 处理请求时的峰值显存:当模型处理长文本时,显存会临时增加。这个峰值决定了你能处理多长的文本。
  3. 多轮对话的显存累积:如果服务支持多轮对话,每轮对话的历史都会占用显存,要注意会不会随着对话轮数增加而显存泄漏。

4. 推理吞吐量压测完整方案

知道了显存情况,接下来我们测试模型的“处理速度”,也就是吞吐量。吞吐量通常用“每秒处理的tokens数”来衡量。

4.1 设计压测场景

一个完整的压测应该包含多种场景:

  1. 短文本单请求:模拟用户问一个简单问题
  2. 长文本单请求:模拟用户上传长文档分析
  3. 多用户并发请求:模拟多个用户同时使用
  4. 持续压力测试:模拟长时间高负载运行

4.2 编写压测脚本

下面是一个完整的压测脚本,它包含了上面提到的多种测试场景:

# stress_test.py
import requests
import time
import threading
import json
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
import statistics

class GLMStressTester:
    def __init__(self, base_url="http://localhost:8080"):
        """
        初始化压测器
        
        参数:
        base_url: GLM模型服务的地址
        """
        self.base_url = base_url
        self.api_url = f"{base_url}/v1/chat/completions"  # 假设使用OpenAI兼容的API
        
    def send_request(self, prompt, max_tokens=100):
        """
        发送单个请求到模型
        
        返回:
        response_time: 响应时间(秒)
        tokens_generated: 生成的tokens数量
        """
        headers = {
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": "glm-4-9b-chat-1m",
            "messages": [
                {"role": "user", "content": prompt}
            ],
            "max_tokens": max_tokens,
            "temperature": 0.7
        }
        
        start_time = time.time()
        
        try:
            response = requests.post(
                self.api_url,
                headers=headers,
                json=payload,
                timeout=120  # 120秒超时
            )
            end_time = time.time()
            
            if response.status_code == 200:
                result = response.json()
                # 计算生成的tokens数量(这里简化处理,实际应该从响应中获取)
                response_text = result['choices'][0]['message']['content']
                # 简单估算:英文大约1个token=0.75个单词,中文1个汉字≈1.2个tokens
                tokens_estimated = len(response_text) * 1.2
                return end_time - start_time, tokens_estimated
            else:
                print(f"请求失败: {response.status_code}")
                return None, 0
                
        except Exception as e:
            print(f"请求异常: {e}")
            return None, 0
    
    def test_single_short_request(self, num_requests=10):
        """
        测试短文本单请求性能
        """
        print("=== 短文本单请求测试 ===")
        print(f"发送 {num_requests} 个请求...")
        
        prompts = [
            "你好,请介绍一下你自己。",
            "Python和JavaScript哪个更容易学习?",
            "如何快速学习机器学习?",
            "写一个简单的Python函数计算斐波那契数列。",
            "解释一下什么是深度学习。",
            "如何提高编程效率?",
            "推荐几个学习AI的网站。",
            "什么是Transformer模型?",
            "如何部署一个机器学习模型?",
            "解释一下注意力机制的原理。"
        ]
        
        response_times = []
        total_tokens = 0
        
        for i in tqdm(range(num_requests)):
            prompt = prompts[i % len(prompts)]  # 循环使用提示词
            resp_time, tokens = self.send_request(prompt)
            
            if resp_time:
                response_times.append(resp_time)
                total_tokens += tokens
        
        if response_times:
            avg_time = statistics.mean(response_times)
            tokens_per_second = total_tokens / sum(response_times)
            
            print(f"\n测试结果:")
            print(f"- 总请求数: {len(response_times)}")
            print(f"- 平均响应时间: {avg_time:.2f}秒")
            print(f"- 最短响应时间: {min(response_times):.2f}秒")
            print(f"- 最长响应时间: {max(response_times):.2f}秒")
            print(f"- 吞吐量: {tokens_per_second:.1f} tokens/秒")
            
            return tokens_per_second
        return 0
    
    def test_concurrent_requests(self, num_concurrent=3, requests_per_user=5):
        """
        测试并发请求性能
        """
        print(f"\n=== 并发请求测试 ===")
        print(f"模拟 {num_concurrent} 个用户,每个用户发送 {requests_per_user} 个请求")
        
        results = []
        lock = threading.Lock()
        
        def user_simulation(user_id):
            user_results = []
            for i in range(requests_per_user):
                prompt = f"用户{user_id}的第{i+1}个问题:请用100字介绍人工智能。"
                resp_time, tokens = self.send_request(prompt, max_tokens=50)
                if resp_time:
                    user_results.append((resp_time, tokens))
            with lock:
                results.extend(user_results)
        
        # 使用线程池模拟并发用户
        with ThreadPoolExecutor(max_workers=num_concurrent) as executor:
            futures = [executor.submit(user_simulation, i) for i in range(num_concurrent)]
            # 等待所有任务完成
            for future in tqdm(futures, total=num_concurrent, desc="并发测试进度"):
                future.result()
        
        if results:
            response_times = [r[0] for r in results]
            total_tokens = sum(r[1] for r in results)
            total_time = max(response_times)  # 并发测试的总时间取最长的
            
            print(f"\n并发测试结果:")
            print(f"- 总完成请求数: {len(results)}")
            print(f"- 总测试时间: {total_time:.2f}秒")
            print(f"- 平均响应时间: {statistics.mean(response_times):.2f}秒")
            print(f"- 系统吞吐量: {total_tokens/total_time:.1f} tokens/秒")
            print(f"- QPS (每秒查询数): {len(results)/total_time:.2f}")
            
            return len(results)/total_time
        return 0
    
    def test_long_context(self, context_length=50000):
        """
        测试长上下文处理能力
        """
        print(f"\n=== 长上下文测试 ===")
        print(f"生成 {context_length} 字符的长文本...")
        
        # 生成一个长文本(这里用重复文本来模拟)
        long_text = "这是一段测试文本," * (context_length // 10)
        long_text = long_text[:context_length]
        
        prompt = f"请总结以下文本的核心内容:\n\n{long_text}\n\n要求:用200字以内总结。"
        
        print("发送长文本请求...")
        start_time = time.time()
        resp_time, tokens = self.send_request(prompt, max_tokens=200)
        end_time = time.time()
        
        if resp_time:
            print(f"\n长文本测试结果:")
            print(f"- 输入文本长度: {context_length} 字符")
            print(f"- 总处理时间: {resp_time:.2f}秒")
            print(f"- 实际生成tokens: {tokens:.0f}")
            print(f"- 处理速度: {context_length/resp_time:.0f} 字符/秒")
            
            # 监控显存峰值(需要结合前面的监控脚本)
            print(f"\n提示:请同时运行GPU监控脚本观察长文本处理时的显存峰值")
            
            return resp_time
        return 0
    
    def run_full_test_suite(self):
        """
        运行完整的测试套件
        """
        print("开始GLM-4-9B-Chat-1M性能压测")
        print("=" * 50)
        
        results = {}
        
        # 1. 短文本测试
        results['short_text_tps'] = self.test_single_short_request(num_requests=10)
        
        # 等待一下,让系统冷却
        time.sleep(5)
        
        # 2. 并发测试
        results['qps'] = self.test_concurrent_requests(num_concurrent=3, requests_per_user=3)
        
        # 等待一下
        time.sleep(5)
        
        # 3. 长文本测试(可以根据你的显存大小调整长度)
        results['long_text_time'] = self.test_long_context(context_length=20000)
        
        print("\n" + "=" * 50)
        print("压测总结报告:")
        print("=" * 50)
        
        for key, value in results.items():
            if 'tps' in key or 'qps' in key:
                print(f"{key}: {value:.2f}")
            elif 'time' in key:
                print(f"{key}: {value:.2f}秒")
        
        return results

if __name__ == "__main__":
    # 创建测试器实例
    tester = GLMStressTester("http://localhost:8080")
    
    # 运行完整测试
    results = tester.run_full_test_suite()

4.3 运行压测并解读结果

运行这个压测脚本:

python stress_test.py

你会看到类似这样的输出:

开始GLM-4-9B-Chat-1M性能压测
==================================================
=== 短文本单请求测试 ===
发送 10 个请求...
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐