4. 吞吐量优化

任务目标

本案例通过实现一个动态批处理推理服务,对比不同批大小(batch size)对模型推理吞吐量的影响。核心目标是:在给定固定测试数据集和推理函数的前提下,量化批处理带来的吞吐量提升,并观察批大小与吞吐量之间的变化趋势。

环境与依赖

需要 Python 3.8+,仅使用标准库 time 和 statistics,无需安装第三方框架。为模拟真实推理负载,使用一个计算密集型函数代替实际模型(如矩阵乘法),保证 CPU 时间可测量且结果可复现。

关键完整代码

import time
import statistics
from concurrent.futures import ThreadPoolExecutor

# ---------- 模拟模型推理(计算密集型,代替真实模型) ----------
def mock_inference(sample):
    """模拟单样本推理:执行固定次数的浮点运算"""
    x = float(sample)
    for _ in range(2000):
        x = (x * 1.0001 + 0.3) % 100.0
    return x

# ---------- 批处理推理 ----------
def batch_inference(batch):
    """对一批样本执行推理,返回结果列表"""
    return [mock_inference(s) for s in batch]

# ---------- 吞吐量测试 ----------
def measure_throughput(dataset, batch_size, num_workers=4):
    """
    按指定批大小处理整个数据集,返回吞吐量(样本/秒)
    dataset: 输入样本列表
    batch_size: 每批样本数
    num_workers: 并行工作线程数(模拟多请求并发)
    """
    # 将数据集切分为固定大小的批次
    batches = [dataset[i:i+batch_size] for i in range(0, len(dataset), batch_size)]
    
    start = time.perf_counter()
    with ThreadPoolExecutor(max_workers=num_workers) as executor:
        # 并发提交所有批次(模拟服务端并发处理多个请求)
        futures = [executor.submit(batch_inference, b) for b in batches]
        results = [f.result() for f in futures]
    elapsed = time.perf_counter() - start
    
    total_samples = sum(len(b) for b in batches)
    return total_samples / elapsed, results

# ---------- 主程序:对比不同批大小 ----------
if __name__ == "__main__":
    # 固定测试数据集:1000个样本
    dataset = list(range(1000))
    
    # 测试不同批大小
    batch_sizes = [1, 4, 16, 64, 256]
    
    print(f"{'Batch Size':>10} | {'Throughput (samples/s)':>22} | {'Speedup vs bs=1':>16}")
    print("-" * 55)
    
    baseline = None
    for bs in batch_sizes:
        # 每个配置运行3次取中位数,减少噪声
        measurements = []
        for _ in range(3):
            tput, _ = measure_throughput(dataset, bs)
            measurements.append(tput)
        median_tput = statistics.median(measurements)
        
        if baseline is None:
            baseline = median_tput
            speedup = 1.0
        else:
            speedup = median_tput / baseline
        
        print(f"{bs:>10} | {median_tput:>22.2f} | {speedup:>16.2f}x")

运行方式

python throughput_benchmark.py

验证方法与判定标准

程序运行后输出一个对比表格。判定优化有效的标准如下:

  • 功能正确性:程序无异常退出,且每个批大小配置均输出吞吐量数值。
  • 优化效果:批大小从1增大到4或16时,吞吐量应显著提升(通常提升2倍以上)。由于 mock_inference 是纯计算函数,批处理减少了线程调度和函数调用开销,因此批大小越大,吞吐量越高。
  • 趋势合理性:吞吐量随批大小增大呈上升趋势,但增速逐渐放缓(边际收益递减)。当批大小达到256时,吞吐量应高于批大小为1时的数值。

若观察到批大小增大后吞吐量反而下降,说明批大小已超过CPU并行度上限(本机核心数×每核超线程数),此时应缩小测试范围(如将最大批大小设为64)。

预期输出示例

Batch Size | Throughput (samples/s) | Speedup vs bs=1
-------------------------------------------------------
         1 |                  125.30 |            1.00x
         4 |                  312.85 |            2.50x
        16 |                  498.20 |            3.98x
        64 |                  601.75 |            4.80x
       256 |                  623.10 |            4.97x

以上数值仅为逻辑一致的示例,实际数值取决于CPU性能与当前负载,但趋势应保持一致。

常见问题

  • 吞吐量波动较大:可增加重复次数(如5次)或使用更大数据集(如5000样本)来平滑噪声。
  • 批大小增大后吞吐量下降:说明批大小已超过硬件并行能力,应减小最大批大小。
  • 线程数设置:num_workers=4 模拟4个并发请求。若本机核心数较少,可调低该值避免上下文切换开销。