Skip to content

INF-ITERATION-BUDGET. Serving Iteration Cost and Batch Bounds | Serving 迭代成本与批次容量上界 ​

难度: Hard | 环境: CPU-first + GPU optional | 标签: 推理优化, Serving, Batch Budget, Cost Model

🚀 云端运行环境

本章节的实战代码可以点击以下链接在免费 GPU 算力平台上直接运行:

Open In ColabOpen In Studio (国内推荐:魔搭社区免费实例)

本节导读 ​

推理服务每一轮都要决定本轮接纳多少请求、处理多少 Prefill token、推进多少 Decode token。批次过小会浪费并行能力;批次过大时吞吐收益可能已经饱和,但每轮耗时、TPOT 和尾延迟仍继续上升。本节把请求队列、调度器和执行引擎放进同一轮迭代,建立计算、访存、KV 容量、token budget 与 SLO 的统一账本。

前置阅读 ​

Step 1:一次 Serving 迭代如何闭环 ​

请求队列保存新到达和等待继续执行的请求;调度器读取请求优先级、KV 容量和本轮 token budget,形成实际执行批次;执行引擎完成一次前向、采样与状态更新。完成请求退出,未完成请求携带新的 KV 状态进入下一轮。

层次输入本轮输出主要约束
请求队列新请求、未完成请求候选集合到达模式、等待时间、优先级
调度器候选集合、KV 状态、预算scheduled batchslot、token、KV、SLO
执行引擎Prefill chunk、Decode token、KVlogits、新 KV、完成状态kernel、带宽、算力、并行方式

推理服务的单轮执行闭环

Step 2:计算、访存与暴露开销共同决定迭代时间 ​

Roofline 直觉可用于估算单轮下界:访存时间由权重、历史 KV 读取和新增状态写入决定;计算时间由本轮 scheduled tokens、模型活跃参数和 Attention 工作量决定。若计算与访存能够充分重叠,两者取较大值;调度、kernel launch、同步和没有被隐藏的通信仍需额外计入。

Titer≈max(Tmemory,Tcompute)+Tschedule+Tlaunch+Texposed
成本项主要驱动因素随 batch / context 的变化
权重读取模型活跃权重、dtype、缓存与融合可由本轮更多 token 摊销,不等于只读一次
历史 KV 读取活跃请求上下文总长度、KV 表示context 和并发增加都会上升
新 KV 写入本轮 Prefill + Decode token 数scheduled tokens 越多越高
计算量Dense / MoE 活跃参数、Attention 形状token 数增加后可能由访存受限转为计算受限
暴露开销调度、launch、同步、通信不能用 Roofline 的 max 自动消除

Step 3:批次大小同时受到五类上界约束 ​

批内请求数不是独立旋钮。slot 限制最大序列数;KV 容量限制可驻留历史状态;token budget 限制本轮 Prefill 与 Decode 工作量;SLO 限制可接受的迭代时间;backend 还可能有 kernel、图捕获或并行实现边界。最终可执行规模取这些上界的最小值,并记录首先生效的约束。

上界典型输入上下文变长后的变化
Slotmax_num_seqs通常不直接变化
KV 容量可用字节、每 token KV、保留输出长度每请求状态增大,上界下降
Token budgetmax_num_batched_tokens、每请求本轮 tokenPrefill 更容易占满预算
SLOTTFT、TPOT、P95/P99 门槛Decode KV 读取增加,上界可能下降
Backendkernel、CUDA Graph、并行配置取决于实现和 shape 支持

Serving 批次的五类容量上界

Step 4:实现迭代成本、批次上界与运行拐点 ​

本题使用显式字节数、FLOPs、硬件有效带宽与算力构造教学成本账本,并把 slot、KV、token、SLO 和 backend 上界合并。题目区已提供字段约定;你将补全迭代下界、约束合并和吞吐—延迟拐点判断。测试覆盖主导成本、上下文导致的 KV 收缩、多个并列约束和 SLO 违规。

TODO / 函数学习者完成的机制必须满足的约束测试证据
TODO 1 · estimate_iteration_cost估算单轮计算与访存下界单位换算一致;迭代时间由计算、访存和暴露开销决定计算主导、访存主导与非法带宽
TODO 2 · compute_batch_bounds合并批次的五类上界保留最小硬上界及全部并列约束单一上界、并列上界与非法容量
TODO 3 · select_operating_knee从 Batch Sweep 选择运行拐点吞吐边际增益达到阈值后停止扩大批次已饱和、未饱和与单点曲线
python
from typing import Dict, List, Sequence
python
def estimate_iteration_cost(
    weight_bytes: int,
    kv_bytes_per_token: int,
    active_context_tokens: Sequence[int],
    scheduled_prefill_tokens: int,
    scheduled_decode_tokens: int,
    flops_per_token: float,
    effective_bandwidth_gbps: float,
    effective_compute_tflops: float,
    exposed_overhead_ms: float = 0.0,
) -> Dict[str, float]:
    """估算一次迭代的访存、计算下界与暴露总时间。"""
    values = [weight_bytes, kv_bytes_per_token, scheduled_prefill_tokens, scheduled_decode_tokens, flops_per_token]
    if any(value < 0 for value in values) or effective_bandwidth_gbps <= 0 or effective_compute_tflops <= 0 or exposed_overhead_ms < 0:
        raise ValueError('成本输入不得为负,硬件有效速率必须为正')
    if any(tokens < 0 for tokens in active_context_tokens):
        raise ValueError('上下文长度不得为负')
    scheduled_tokens = scheduled_prefill_tokens + scheduled_decode_tokens
    kv_read_bytes = kv_bytes_per_token * sum(active_context_tokens)
    kv_write_bytes = kv_bytes_per_token * scheduled_tokens
    total_flops = flops_per_token * scheduled_tokens
    # TODO 1(迭代成本):换算访存与计算下界,并识别主导项。
    # memory_bytes = ???  # 权重读取、历史 KV 读取与新增 KV 写入的总字节数。
    # memory_ms = ???  # 按有效显存带宽换算的毫秒数。
    # compute_ms = ???  # 按有效计算吞吐换算的毫秒数。
    # dominant = ???  # memory_ms 与 compute_ms 中较大的资源侧。
    return {
        'scheduled_tokens': scheduled_tokens, 'memory_bytes': memory_bytes,
        'kv_read_bytes': kv_read_bytes, 'kv_write_bytes': kv_write_bytes,
        'total_flops': total_flops, 'memory_ms': memory_ms, 'compute_ms': compute_ms,
        'exposed_overhead_ms': exposed_overhead_ms,
        'iteration_ms': max(memory_ms, compute_ms) + exposed_overhead_ms,
        'dominant': dominant,
    }


def compute_batch_bounds(
    max_num_seqs: int,
    available_kv_bytes: int,
    kv_bytes_per_token: int,
    context_tokens_per_request: int,
    reserved_output_tokens: int,
    max_num_batched_tokens: int,
    scheduled_tokens_per_request: int,
    slo_batch_limit: int,
    backend_batch_limit: int,
) -> Dict[str, object]:
    """计算五类 batch 上界及最终生效约束。"""
    values = [max_num_seqs, available_kv_bytes, kv_bytes_per_token, context_tokens_per_request, reserved_output_tokens, max_num_batched_tokens, scheduled_tokens_per_request, slo_batch_limit, backend_batch_limit]
    if any(value <= 0 for value in values):
        raise ValueError('所有上界输入必须为正')
    kv_per_request = kv_bytes_per_token * (context_tokens_per_request + reserved_output_tokens)
    # TODO 2(批次上界):合并五类上界并保留全部并列约束。
    # limits = ???  # 包含 slot、kv、token、slo、backend 五个正整数上界。
    # final_limit = ???  # 所有上界的最小值。
    # binding_constraints = ???  # 等于 final_limit 的全部约束名。
    return {
        'limits': limits, 'final_limit': final_limit,
        'binding_constraints': binding_constraints,
        'kv_bytes_per_request': kv_per_request,
    }


def select_operating_knee(
    records: Sequence[Dict[str, float]],
    max_iteration_ms: float,
    min_relative_throughput_gain: float = 0.10,
) -> Dict[str, object]:
    """在满足延迟门槛的 sweep 中选择吞吐开始饱和前的 batch。"""
    if not records or max_iteration_ms <= 0 or min_relative_throughput_gain < 0:
        raise ValueError('记录与阈值必须有效')
    ordered = sorted(records, key=lambda item: item['batch'])
    selected = None
    reason = 'largest_valid_batch'
    previous = None
    for record in ordered:
        if record['iteration_ms'] > max_iteration_ms:
            reason = 'slo_violation'
            break
        if previous is not None:
            # TODO 3(运行拐点):计算相邻吞吐增益并判断是否已饱和。
            # relative_gain = ???  # 相对 previous throughput 的增益比例。
            # throughput_saturated = ???  # 增益严格低于给定阈值时为真。
            if throughput_saturated:
                reason = 'throughput_saturation'
                break
        selected = record
        previous = record
    if selected is None:
        raise ValueError('没有 batch 满足延迟约束')
    return {'selected_batch': selected['batch'], 'selected_record': dict(selected), 'stop_reason': reason}

测试 ​

测试分别定位成本主导项、容量上界和运行拐点;CPU 结果验证机制关系,不代表真实 backend 吞吐。

python
def test_iteration_cost():
    """访存或计算下界应决定主导项,暴露开销单独累加。"""
    result = estimate_iteration_cost(1_000_000_000, 1_000, [100, 200], 16, 4, 1e9, 100.0, 10.0, 0.2)
    assert result['scheduled_tokens'] == 20
    assert result['dominant'] == 'memory'
    assert result['iteration_ms'] > result['memory_ms']


def test_batch_bounds():
    """上下文增长应收紧 KV 上界,并保留并列生效约束。"""
    short = compute_batch_bounds(64, 64_000_000, 1_000, 1_000, 100, 128, 1, 48, 64)
    long = compute_batch_bounds(64, 64_000_000, 1_000, 8_000, 100, 128, 1, 48, 64)
    assert short['final_limit'] == 48 and short['binding_constraints'] == ['slo']
    assert long['limits']['kv'] < short['limits']['kv']
    tied = compute_batch_bounds(8, 80_000, 1_000, 9, 1, 8, 1, 8, 8)
    assert tied['final_limit'] == 8
    assert set(tied['binding_constraints']) == {'slot', 'kv', 'token', 'slo', 'backend'}


def test_operating_knee():
    """吞吐增益饱和时应保留上一档满足 SLO 的 batch。"""
    records = [
        {'batch': 4, 'throughput': 100.0, 'iteration_ms': 8.0},
        {'batch': 8, 'throughput': 180.0, 'iteration_ms': 11.0},
        {'batch': 16, 'throughput': 190.0, 'iteration_ms': 18.0},
        {'batch': 32, 'throughput': 205.0, 'iteration_ms': 35.0},
    ]
    result = select_operating_knee(records, max_iteration_ms=25.0, min_relative_throughput_gain=0.10)
    assert result['selected_batch'] == 8
    assert result['stop_reason'] == 'throughput_saturation'


test_iteration_cost()
test_batch_bounds()
test_operating_knee()
print('All serving iteration budget tests passed.')

🛑 STOP HERE 🛑









请先尝试自己完成代码并跑通测试。
如果遇到困难,再向下查看参考代码与解析。










参考代码与解析 ​

代码 ​

python
# TODO 1:估算单轮计算与访存下界。
def estimate_iteration_cost(
    weight_bytes: int, kv_bytes_per_token: int, active_context_tokens: Sequence[int],
    scheduled_prefill_tokens: int, scheduled_decode_tokens: int, flops_per_token: float,
    effective_bandwidth_gbps: float, effective_compute_tflops: float, exposed_overhead_ms: float = 0.0,
) -> Dict[str, float]:
    values = [weight_bytes, kv_bytes_per_token, scheduled_prefill_tokens, scheduled_decode_tokens, flops_per_token, effective_bandwidth_gbps, effective_compute_tflops]
    if any(value < 0 for value in values) or effective_bandwidth_gbps <= 0 or effective_compute_tflops <= 0 or exposed_overhead_ms < 0:
        raise ValueError('cost inputs must be non-negative and hardware rates must be positive')
    if any(tokens < 0 for tokens in active_context_tokens):
        raise ValueError('context lengths must be non-negative')
    scheduled_tokens = scheduled_prefill_tokens + scheduled_decode_tokens
    kv_read_bytes = kv_bytes_per_token * sum(active_context_tokens)
    kv_write_bytes = kv_bytes_per_token * scheduled_tokens
    memory_bytes = weight_bytes + kv_read_bytes + kv_write_bytes
    total_flops = flops_per_token * scheduled_tokens
    memory_ms = memory_bytes / (effective_bandwidth_gbps * 1e9) * 1e3
    compute_ms = total_flops / (effective_compute_tflops * 1e12) * 1e3
    dominant = 'memory' if memory_ms >= compute_ms else 'compute'
    return {'scheduled_tokens': scheduled_tokens, 'memory_bytes': memory_bytes, 'kv_read_bytes': kv_read_bytes, 'kv_write_bytes': kv_write_bytes, 'total_flops': total_flops, 'memory_ms': memory_ms, 'compute_ms': compute_ms, 'exposed_overhead_ms': exposed_overhead_ms, 'iteration_ms': max(memory_ms, compute_ms) + exposed_overhead_ms, 'dominant': dominant}


# TODO 2:合并批次的五类上界。
def compute_batch_bounds(
    max_num_seqs: int, available_kv_bytes: int, kv_bytes_per_token: int, context_tokens_per_request: int,
    reserved_output_tokens: int, max_num_batched_tokens: int, scheduled_tokens_per_request: int,
    slo_batch_limit: int, backend_batch_limit: int,
) -> Dict[str, object]:
    values = [max_num_seqs, available_kv_bytes, kv_bytes_per_token, context_tokens_per_request, reserved_output_tokens, max_num_batched_tokens, scheduled_tokens_per_request, slo_batch_limit, backend_batch_limit]
    if any(value <= 0 for value in values):
        raise ValueError('all bound inputs must be positive')
    kv_per_request = kv_bytes_per_token * (context_tokens_per_request + reserved_output_tokens)
    limits = {'slot': max_num_seqs, 'kv': available_kv_bytes // kv_per_request, 'token': max_num_batched_tokens // scheduled_tokens_per_request, 'slo': slo_batch_limit, 'backend': backend_batch_limit}
    final_limit = min(limits.values())
    return {'limits': limits, 'final_limit': final_limit, 'binding_constraints': [name for name, limit in limits.items() if limit == final_limit], 'kv_bytes_per_request': kv_per_request}


# TODO 3:从 Batch Sweep 选择运行拐点。
def select_operating_knee(records: Sequence[Dict[str, float]], max_iteration_ms: float, min_relative_throughput_gain: float = 0.10) -> Dict[str, object]:
    if not records or max_iteration_ms <= 0 or min_relative_throughput_gain < 0:
        raise ValueError('records and thresholds must be valid')
    ordered = sorted(records, key=lambda item: item['batch'])
    selected = None
    reason = 'largest_valid_batch'
    previous = None
    for record in ordered:
        if record['iteration_ms'] > max_iteration_ms:
            reason = 'slo_violation'
            break
        if previous is not None:
            gain = (record['throughput'] - previous['throughput']) / max(previous['throughput'], 1e-12)
            if gain < min_relative_throughput_gain:
                reason = 'throughput_saturation'
                break
        selected = record
        previous = record
    if selected is None:
        raise ValueError('no batch satisfies the latency constraint')
    return {'selected_batch': selected['batch'], 'selected_record': dict(selected), 'stop_reason': reason}

解析 ​

TODO 1:迭代成本账本

权重成本由本轮 scheduled tokens 共同摊销,历史 KV 则随所有活跃上下文增长。这个公式是用于比较方向的下界,不替代逐算子 trace。

TODO 2:五类上界

最终限制取最小值,同时保留全部并列约束。参数量只决定权重账本的一部分;不同模型的 KV heads、层数和 dtype 可能使小模型拥有更高的每 token KV 成本。

TODO 3:运行拐点

只有满足延迟门槛的记录才可进入候选;一旦边际吞吐收益低于阈值,就保留上一档 batch,避免在收益饱和后继续支付迭代延迟。

Step 5:GPU 机制实验——真实模型的 batch × context 成本曲线 ​

学习问题CPU 已确认GPU 要确认与后续使用
batch 和上下文如何改变单轮成本与可用容量成本项、五类上界和拐点选择用同一 Transformers 模型测量 Prefill、单步 Decode、峰值显存和 KV 字节;结果进入 Serving 项目的 workload 选择

5.1 环境与固定工作负载 ​

使用 INF-CUDA-MECHANISM Profile;参考环境为单卡 12 GB 以上显存,首次运行需要下载模型,不需要外部数据集。创建或恢复环境见推理优化实验 Quickstart,实验登记见推理优化实验资产表。运行前的 Profile 预检必须返回 status=ok。

配置层本实验固定口径修改规则
模型Qwen/Qwen3-0.6B、同一 revision 与 dtype更换模型或精度后不得与旧结果直接合并
工作负载batch × context 矩阵;synthetic token IDs修改矩阵后递增 workload version
配对变量同一模型下只改变 batch 与 context预热、重复和随机种子保持一致
测量median / P95、KV 字节、峰值显存、OOM 恢复不用平均值掩盖长尾或失败 shape
结果每个 shape 的状态与 JSON 路径正式复测使用唯一 RUN_LABEL
python
RUN_MODE = 'dry_run'  # 默认值 dry_run:只登记计划;改为 real_gpu 执行真实矩阵。
RUN_LABEL = 'default'  # 默认值 default;正式复测改为唯一标签,避免覆盖旧结果。
PROFILE_ID = 'INF-CUDA-MECHANISM'  # 默认 Profile;预检必须返回 status=ok。
WORKLOAD_ID = 'inf-iteration-budget'  # 稳定实验身份;协议语义变化时才修改。
WORKLOAD_VERSION = 'v2'  # 默认 v2:记录 median/P95 与 OOM 恢复状态。
GPU_MODEL_ID = 'Qwen/Qwen3-0.6B'  # 默认 0.6B 模型;更换模型后结果不可直接合并。
GPU_MODEL_REVISION = 'main'  # 默认 main;正式快照应记录实际解析出的 commit。
GPU_DTYPE = 'bfloat16'  # 默认 bfloat16;硬件不支持 BF16 时改为 float16 并单独记录。
GPU_BATCH_SIZES = [1, 2, 4, 8]  # 默认四档 batch;增大会提高显存与迭代时间。
GPU_CONTEXT_LENGTHS = [128, 512, 1024]  # 默认 token 长度矩阵;增大会提高 KV 读取与容量压力。
GPU_WARMUP = 5  # 默认预热 5 次;用于稳定编译、缓存和时钟状态。
GPU_REPEATS = 20  # 默认重复 20 次;用于计算 median 与 P95。
GPU_SEED = 2026  # 默认随机种子 2026;保证 shape 输入可复测。
GPU_RESULT_DIR = 'benchmarks/results/inf_iteration_budget'  # 默认仓库相对结果目录。

5.2 执行并保存 JSON ​

执行单元对每个 shape 分别测 Prefill 与携带 past_key_values 的单步 Decode。OOM 作为该 shape 的失败证据写入结果,不据此推断生产并发上限。

python
import json
import math
import statistics
from pathlib import Path


def _cache_bytes(cache):
    if hasattr(cache, 'to_legacy_cache'):
        cache = cache.to_legacy_cache()
    tensors = []
    def visit(value):
        if isinstance(value, torch.Tensor):
            tensors.append(value)
        elif isinstance(value, (list, tuple)):
            for item in value: visit(item)
    visit(cache)
    return sum(t.numel() * t.element_size() for t in tensors)


def _cuda_latency_ms(fn, warmup, repeats):
    for _ in range(warmup):
        fn()
    samples = []
    for _ in range(repeats):
        start = torch.cuda.Event(enable_timing=True)
        end = torch.cuda.Event(enable_timing=True)
        start.record()
        fn()
        end.record()
        torch.cuda.synchronize()
        samples.append(start.elapsed_time(end))
    ordered = sorted(samples)
    p95_index = min(len(ordered) - 1, math.ceil(0.95 * len(ordered)) - 1)
    return {'median_ms': statistics.median(samples), 'p95_ms': ordered[p95_index]}


def _cuda_recovered_after_oom():
    try:
        probe = torch.empty(1, device='cuda')
        torch.cuda.synchronize()
        del probe
        return True
    except torch.cuda.OutOfMemoryError:
        torch.cuda.empty_cache()
        return False


def run_iteration_gpu_experiment():
    if RUN_MODE != 'real_gpu':
        return {'status': 'skipped', 'failure': "set RUN_MODE='real_gpu' after INF-CUDA-MECHANISM preflight"}
    if not torch.cuda.is_available():
        return {'status': 'failed', 'failure': 'CUDA is unavailable'}
    from transformers import AutoModelForCausalLM
    dtype = getattr(torch, GPU_DTYPE)
    torch.manual_seed(GPU_SEED)
    model = AutoModelForCausalLM.from_pretrained(GPU_MODEL_ID, revision=GPU_MODEL_REVISION, torch_dtype=dtype).cuda().eval()
    records = []
    for context in GPU_CONTEXT_LENGTHS:
        for batch in GPU_BATCH_SIZES:
            try:
                torch.cuda.empty_cache(); torch.cuda.reset_peak_memory_stats()
                input_ids = torch.randint(10, model.config.vocab_size, (batch, context), device='cuda')
                mask = torch.ones_like(input_ids)
                with torch.inference_mode():
                    prefill = _cuda_latency_ms(lambda: model(input_ids=input_ids, attention_mask=mask, use_cache=True), GPU_WARMUP, GPU_REPEATS)
                    output = model(input_ids=input_ids, attention_mask=mask, use_cache=True)
                    cache = output.past_key_values
                    decode_ids = torch.randint(10, model.config.vocab_size, (batch, 1), device='cuda')
                    decode_mask = torch.ones((batch, context + 1), dtype=mask.dtype, device='cuda')
                    decode = _cuda_latency_ms(lambda: model(input_ids=decode_ids, attention_mask=decode_mask, past_key_values=cache, use_cache=True), GPU_WARMUP, GPU_REPEATS)
                records.append({
                    'batch': batch, 'context_tokens': context,
                    'prefill_median_ms': prefill['median_ms'], 'prefill_p95_ms': prefill['p95_ms'],
                    'decode_median_ms': decode['median_ms'], 'decode_p95_ms': decode['p95_ms'],
                    'prefill_tokens_per_s': batch * context / (prefill['median_ms'] / 1000),
                    'decode_tokens_per_s': batch / (decode['median_ms'] / 1000),
                    'kv_cache_bytes': _cache_bytes(cache),
                    'peak_memory_bytes': torch.cuda.max_memory_allocated(),
                    'oom_recovered': None, 'status': 'ok',
                })
                del input_ids, mask, output, cache, decode_ids, decode_mask
            except torch.cuda.OutOfMemoryError as error:
                torch.cuda.empty_cache()
                records.append({'batch': batch, 'context_tokens': context, 'status': 'failed', 'failure': f'OOM: {error}', 'oom_recovered': _cuda_recovered_after_oom()})
    report = {'schema_version': 'inference-mechanism-gpu/v2', 'semantic_id': 'INF-ITERATION-BUDGET', 'profile_id': PROFILE_ID, 'run_label': RUN_LABEL, 'status': 'completed', 'model': {'id': GPU_MODEL_ID, 'revision': GPU_MODEL_REVISION, 'dtype': GPU_DTYPE}, 'hardware': {'gpu': torch.cuda.get_device_name(0), 'cuda': torch.version.cuda, 'torch': torch.__version__}, 'workload': {'id': WORKLOAD_ID, 'version': WORKLOAD_VERSION, 'batch_sizes': GPU_BATCH_SIZES, 'context_lengths': GPU_CONTEXT_LENGTHS, 'warmup': GPU_WARMUP, 'repeats': GPU_REPEATS, 'seed': GPU_SEED}, 'records': records, 'failure': None, 'evidence_level': 'local_mechanism_benchmark', 'decision': 'tune'}
    output_dir = Path(GPU_RESULT_DIR); output_dir.mkdir(parents=True, exist_ok=True)
    result_path = output_dir / f"iteration_budget_{RUN_LABEL}.json"
    result_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding='utf-8')
    report['result_path'] = str(result_path)
    return report


GPU_REPORT = run_iteration_gpu_experiment()
GPU_REPORT

5.3 读取结果 ​

只读取 5.2 保存的结果。先确认 schema、环境、工作负载、失败、证据等级和决策,再按 shape 展示 median/P95、KV 字节、峰值显存与 OOM 恢复状态;不重新加载模型或执行测量。

python
report = globals().get('GPU_REPORT', {'status': 'not_run', 'records': []})
summary = {
    'schema_version': report.get('schema_version'),
    'status': report.get('status'),
    'failure': report.get('failure'),
    'model': report.get('model'),
    'hardware': report.get('hardware'),
    'workload': report.get('workload'),
    'evidence_level': report.get('evidence_level'),
    'decision': report.get('decision', 'rerun'),
}
matrix = [
    {
        'batch': item.get('batch'), 'context_tokens': item.get('context_tokens'),
        'status': item.get('status'),
        'prefill_median_ms': item.get('prefill_median_ms'),
        'prefill_p95_ms': item.get('prefill_p95_ms'),
        'decode_median_ms': item.get('decode_median_ms'),
        'decode_p95_ms': item.get('decode_p95_ms'),
        'kv_cache_bytes': item.get('kv_cache_bytes'),
        'peak_memory_bytes': item.get('peak_memory_bytes'),
        'oom_recovered': item.get('oom_recovered'),
        'failure': item.get('failure'),
    }
    for item in report.get('records', [])
]
summary, matrix

5.4 解释结果并作出决策 ​

先检查失败 shape 是否在清理缓存后恢复,再确认模型、dtype、batch × context 矩阵和重复规则一致。随后比较 Prefill / Decode 的 median 与 P95、KV 字节和峰值显存,观察吞吐收益饱和时延迟与容量是否仍继续恶化。

当前页面尚无登记的真实 GPU 快照,因此结论保持“待验证”。下一步应在 INF-CUDA-MECHANISM 预检通过后运行完整矩阵,并把稳定拐点交给真实队列与连续批处理项目复测。

观察项读取字段当前结果解释与动作
运行与口径failure、模型、dtype、workload version待验证shape 或重复规则不一致时先重跑
机制与路径batch、context、KV 字节、OOM 恢复待验证恢复失败时停止后续 shape,先修复运行状态
正确性输出 shape 与缓存状态待验证状态不完整时不解释性能
资源与性能Prefill/Decode median、P95、吞吐、峰值显存待验证选择收益开始饱和前且满足延迟门槛的候选
证据与决策evidence_level、decision、下一步tune生成真实快照后进入 Serving 项目复测

相关阅读 ​

Released under the MIT License.