推理成为 AI 主战场:为什么昂贵的 GPU 仍有大半时间在等待

2026-09-16 12 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

过去几年,AI 硬件竞争的核心问题是:谁能更快地训练更大的模型。到了 2026 年,焦点开始转向另一个更贴近生产环境的问题——模型训练完成后,怎样以可接受的成本和延迟,为海量请求持续生成答案。

黄仁勋在 GTC 2026 上将这一变化称为“推理的拐点”。尤其是推理模型兴起后,一次请求不再只是做一遍前向计算。模型可能生成大量中间推理 token,反复检查和修正答案。计算时间变长了,但 GPU 并不一定因此始终满载:显存、调度、CPU、网络和请求波动,都可能让昂贵的计算单元停下来等待。

推理不是缩小版的训练

训练通常处理形状相对规整的大批量数据,系统可以提前安排计算,并让大量 GPU 长时间执行高密度矩阵运算。在线推理则混合了两种特征截然不同的阶段:

  • Prefill(提示词处理):一次处理输入上下文,计算并行度较高,更接近计算密集型任务。
  • Decode(逐 token 生成):每一步只生成少量 token,却要反复读取模型权重和 KV Cache,容易受到显存带宽及容量限制。

推理模型进一步放大了 decode 阶段。答案越长、推理轮次越多,模型需要执行的串行步骤就越多。即使单个 token 的计算很快,数百甚至数千次依赖前一步结果的生成,也无法简单合并成一次大矩阵运算。

因此,“GPU 利用率不高”并不必然意味着芯片算力不足。它可能意味着系统正在等待:

  1. 从显存读取权重或 KV Cache;
  2. CPU 完成分词、采样或请求编排;
  3. 新请求进入可合批的队列;
  4. 其他 GPU 通过互连完成数据交换;
  5. 某个长请求结束,以便释放显存空间。

这也是为什么只看峰值 FLOPS 很容易选错推理硬件。对于 decode 密集型负载,显存带宽、容量、互连和调度软件可能比理论计算峰值更重要。

真正需要优化的是整个 token 流水线

推理服务不应只追求“每秒生成多少 token”。生产系统至少需要同时观察以下指标:

指标 代表什么 常见矛盾
TTFT 从请求进入到首个 token 返回的时间 批次越大,排队时间可能越长
ITL 连续两个 token 之间的延迟 更高吞吐可能带来更差的交互体验
Tokens/s 系统单位时间生成的 token 数 无法单独反映超时和失败请求
Goodput 满足延迟目标的有效吞吐 比原始吞吐更接近业务价值
KV Cache 占用 并发上下文消耗的显存 长上下文会挤压可服务的并发数
功耗/请求 完成一次请求消耗的能源 高利用率不等于高能效

围绕这些指标,硬件和系统设计正在改变打法。可以这样理解几个主要方向:

  • 连续批处理:请求不必等待整个批次结束,生成完成后立即补入新请求。
  • Prefill 与 Decode 分离:让不同服务器或加速器分别处理计算密集和带宽密集阶段。
  • 更低精度的数据格式:在可接受的质量损失下,减少权重和缓存的存储、传输成本。
  • KV Cache 管理:使用分页、复用和淘汰策略,降低显存碎片。
  • 推测解码:先由较小模型提出候选 token,再由主模型批量验证,以减少串行步骤。
  • 更快的互连和内存系统:避免计算单元因为数据未到位而空转。

这些方法没有一个是免费的。例如,拆分 prefill 和 decode 会增加网络传输;量化可能影响特定任务的质量;推测解码的收益也取决于候选接受率。

动手测一次:不要只盯着 GPU 利用率

下面是一套可以改造的最小实验。假设机器已经安装 NVIDIA 驱动和可用的 Python 环境。示例使用 vLLM 启动 OpenAI 兼容接口;请将模型名换成你有权限下载、并且显存能够容纳的模型。

python -m pip install 'vllm>=0.6'
export MODEL='Qwen/Qwen2.5-7B-Instruct'
python -m vllm.entrypoints.openai.api_server --model "$MODEL" --port 8000 --gpu-memory-utilization 0.90 --max-model-len 8192

在另一个终端观察 GPU。利用率、显存和功耗需要放在一起看:

watch -n 1 'nvidia-smi --query-gpu=timestamp,utilization.gpu,utilization.memory,memory.used,memory.total,power.draw --format=csv,noheader'

然后保存下面的脚本为 bench.py。它只依赖 Python 标准库,会并发发送 32 个请求,并输出端到端延迟和整体 token 吞吐:

import json
import os
import statistics
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed

MODEL = os.environ.get('MODEL', 'Qwen/Qwen2.5-7B-Instruct')
URL = 'http://127.0.0.1:8000/v1/chat/completions'
REQUESTS = 32
CONCURRENCY = 8


def percentile(values, ratio):
    ordered = sorted(values)
    return ordered[int((len(ordered) - 1) * ratio)]


def send_request(index):
    payload = {
        'model': MODEL,
        'messages': [
            {
                'role': 'user',
                'content': f'请求 {index}:分析数据库索引失效的五种原因,并给出排查顺序。'
            }
        ],
        'temperature': 0,
        'max_tokens': 256,
        'stream': False
    }
    request = urllib.request.Request(
        URL,
        data=json.dumps(payload).encode('utf-8'),
        headers={'Content-Type': 'application/json'}
    )

    started = time.perf_counter()
    with urllib.request.urlopen(request, timeout=300) as response:
        result = json.loads(response.read())
    latency = time.perf_counter() - started
    tokens = result.get('usage', {}).get('completion_tokens', 0)
    return latency, tokens


started = time.perf_counter()
results = []
with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
    futures = [pool.submit(send_request, i) for i in range(REQUESTS)]
    for future in as_completed(futures):
        results.append(future.result())

wall_time = time.perf_counter() - started
latencies = [item[0] for item in results]
total_tokens = sum(item[1] for item in results)

print(f'requests: {len(results)}')
print(f'wall time: {wall_time:.2f}s')
print(f'latency p50: {statistics.median(latencies):.2f}s')
print(f'latency p95: {percentile(latencies, 0.95):.2f}s')
print(f'output tokens: {total_tokens}')
print(f'output throughput: {total_tokens / wall_time:.2f} tokens/s')

运行:

MODEL='Qwen/Qwen2.5-7B-Instruct' python bench.py

接着把 CONCURRENCY 分别改成 1、4、16 和 32,记录吞吐、p95 延迟、显存占用和 GPU 功耗。常见现象是:并发上升时总吞吐提高,但单请求延迟恶化;并发过高后,吞吐增幅变小,排队时间却快速增加。

这个脚本测的是完整请求延迟,并未测量流式返回时的 TTFT 和 ITL。生产压测应启用 streaming,并结合服务端指标或 tracing 记录首 token 时间、每 token 间隔、队列等待和 KV Cache 使用率。

采购新硬件前,先回答五个问题

推理时代的硬件选型不能只比较芯片规格表。更稳妥的流程是:

  • 用真实提示词长度和输出长度建立请求分布,而不是只跑固定长度 benchmark;
  • 分开测量 prefill、decode、排队和网络耗时;
  • 同时报告延迟 SLO 下的 goodput,而非只报告峰值 tokens/s;
  • 对量化后的模型重新执行质量评估,尤其关注长推理和专业任务;
  • 按每个成功请求或每百万有效 token 计算成本与能耗。

所谓“推理硬件革命”,并不只是再造一块峰值算力更高的 GPU。真正的变化是:行业开始围绕 token 的生成、移动、缓存和调度重构整台机器乃至整个集群。对工程团队而言,最有价值的第一步也不是立即换卡,而是找出当前服务究竟在等待什么。


相关推荐