跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 14 章 算子实战 II:GEMM 全家族

/code/14-operators-gemm/

06-bench-methodology/bench_methodology.py3360 字节
#!/usr/bin/env python3
"""第 14 章第 7 节:性能对齐的「口径换算器」(纯标准库,不需要 GPU/NPU)。

它不测量任何真实性能,只做三件事:

1. 按「目标毫秒数 → 迭代次数」的规则,把 warmup / rep 换算成迭代数
   (TileLang `do_bench` 默认 warmup=25ms、rep=100ms;cuBLAS 对比脚本用
   `minimal_repeat_ms=100` 且至少 5 次);
2. 打印一份「对比前必须填写的口径表」;
3. 提供一个 `tflops(m, n, k, latency_ms)` 的换算,确保大家用同一个 2MNK 口径。

用法::

    python3 bench_methodology.py
    python3 bench_methodology.py --per-iter 0.35 --m 4096 --n 4096 --k 4096
"""

from __future__ import annotations

import argparse
import math


def plan(warmup_ms: float = 25.0, rep_ms: float = 100.0, per_iter_ms: float = 0.1,
         min_repeat: int = 5) -> dict:
    """按目标毫秒数换算预热/重复次数。

    与 cuBLAS 对比脚本(corpus 快照)和 TileLang `do_bench` 的口径一致:
    先估出单次耗时,再按目标毫秒数向上取整得到迭代数;重复次数设下限,
    避免极短的 kernel 只跑一两次。
    """
    if per_iter_ms <= 0:
        raise ValueError("per_iter_ms 必须为正")
    n_warmup = max(1, math.ceil(warmup_ms / per_iter_ms))
    n_repeat = max(min_repeat, math.ceil(rep_ms / per_iter_ms))
    return {
        "n_warmup": n_warmup,
        "n_repeat": n_repeat,
        "estimated_total_ms": (n_warmup + n_repeat) * per_iter_ms,
    }


def tflops(m: int, n: int, k: int, latency_ms: float) -> float:
    """统一按 2MNK 计算 TFLOP/s,避免「是否含 epilogue」之外的算法口径分歧。"""
    if latency_ms <= 0:
        raise ValueError("latency_ms 必须为正")
    return 2.0 * m * n * k / (latency_ms / 1e3) / 1e12


CHECKLIST = """\
对比前必须固定的口径(缺一条,数字就不可比):
  1. 基线是谁            cuBLAS / cublasLt / CATLASS 模板 / Ascend C 手写 / Torch NPU
  2. shape               M, N, K;batch;是否转置;ROW/COLUMN major 还是 NZ
  3. dtype               输入 dtype、累加 dtype、输出 dtype;fp32 是否允许 TF32
  4. epilogue            是否含 bias / 激活 / 量化 / 类型转换;含与不含分开报
  5. 计时范围            kernel-only(cupti / cudagraph)还是含 host 启动
  6. 预热与重复          warmup 与 rep 的目标毫秒数,或固定迭代数
  7. 取数方式            mean / median / min;是否 flush L2
  8. 算法口径            FLOPs 一律按 2MNK;memory-bound 的再报有效 GB/s
  9. 编译期参数          block_M/N/K, num_stages, threads, policy, split_k
 10. 正确性              与同一 golden 比,写出 rtol / atol
"""


def main() -> int:
    parser = argparse.ArgumentParser(description="性能对比口径换算器")
    parser.add_argument("--warmup-ms", type=float, default=25.0)
    parser.add_argument("--rep-ms", type=float, default=100.0)
    parser.add_argument("--per-iter", type=float, default=None,
                        help="单次 kernel 的估计耗时(ms);不给则打印一张扫描表")
    parser.add_argument("--m", type=int, default=4096)
    parser.add_argument("--n", type=int, default=4096)
    parser.add_argument("--k", type=int, default=4096)
    args = parser.parse_args()

    print("=" * 72)
    print(f"目标口径:warmup={args.warmup_ms} ms, rep={args.rep_ms} ms")
    print("=" * 72)
    print()

    if args.per_iter is not None:
        rows = [args.per_iter]
    else:
        rows = [0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0]

    print(f"{'单次耗时(ms)':>12} {'预热次数':>10} {'重复次数':>10} {'总耗时(ms)':>12}")
    print("-" * 48)
    for per_iter in rows:
        p = plan(args.warmup_ms, args.rep_ms, per_iter)
        print(f"{per_iter:>12.4g} {p['n_warmup']:>10d} {p['n_repeat']:>10d} "
              f"{p['estimated_total_ms']:>12.1f}")

    print()
    print(f"换算示例:shape {args.m}x{args.n}x{args.k},"
          f"若单次耗时 1.0 ms → {tflops(args.m, args.n, args.k, 1.0):.2f} TFLOP/s")
    print()
    print(CHECKLIST)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

← 浏览本章全部代码