跳到主要内容
TileLang 系统教程

tilelang-tutorial.huandu.me

第 38 章 DeepEP-Ascend:专家并行通信与计算重叠

/code/38-deepep-ascend/

02-comm-ledger/comm_ledger.py5642 字节
#!/usr/bin/env python3
"""第 38 章 · 示例 02:一层 MoE 的 dispatch / combine 字节账。

这是第 16 章那套账本在 EP 场景下的直接延伸。口径(全部来自源码):

* 每 token 的 **dispatch payload**(`tests/ep/test_ep.py:404-406` 的
  ``count_bytes(x, topk_idx, topk_weights) / num_tokens``)= hidden 元素
  的字节 + SF 的字节 + ``topk * 8``(int64 索引)+ ``topk * 4``(float32 权重)。
* 每 token 的 **combine payload** = BF16 hidden 的字节 + ``topk * 4``
  (combine 的输入是 bf16,`csrc/kernels/ep/combine.hpp` 的调用约束)。
* 一个 token 覆盖的**不同目标 rank 数**期望值是 ``R·(1-(1-1/R)^k)``
  (R 个 rank、均匀路由、忽略放回;见正文 §1.3)。
* 每 rank 的接收 token 数 ≈ ``T × E[不同目标 rank 数]``(按对称性)。

作者验证级别:**CPU 逻辑验证**。本脚本只用标准库,不 import torch,
不连接任何设备。**这不是实测数字,是本书复算。**

用法::

    python3 comm_ledger.py
    python3 comm_ledger.py --layers 61 --json
"""

from __future__ import annotations

import argparse
import json
from dataclasses import dataclass


@dataclass(frozen=True)
class ModelConfig:
    hidden: int = 7168
    num_topk: int = 6
    num_experts: int = 256
    tokens_per_rank: int = 16384


@dataclass(frozen=True)
class ByteConfig:
    """每 token 的 payload 口径(与 DeepEP-Ascend 测试代码一致)。"""

    hidden_bytes_fp8: int
    sf_bytes: int
    indices_bytes: int
    weights_bytes: int
    hidden_bytes_bf16: int

    @property
    def dispatch(self) -> int:
        return (
            self.hidden_bytes_fp8 + self.sf_bytes + self.indices_bytes + self.weights_bytes
        )

    @property
    def combine(self) -> int:
        return self.hidden_bytes_bf16 + self.weights_bytes


def payload_bytes(model: ModelConfig, fp8_dispatch: bool = True) -> ByteConfig:
    """按源码口径算每 token 的 payload 字节数。"""
    # per_token_cast_to_fp8 用 128 元素一个 fp32 scale(tests/ep/test_ep.py:89-93)。
    num_sf = (model.hidden + 127) // 128
    sf_bytes = num_sf * 4  # fp32;view(torch.int16) 后字节数不变,变成 num_sf*2 个 int16
    return ByteConfig(
        hidden_bytes_fp8=model.hidden if fp8_dispatch else model.hidden * 2,
        sf_bytes=sf_bytes if fp8_dispatch else 0,
        indices_bytes=model.num_topk * 8,
        weights_bytes=model.num_topk * 4,
        hidden_bytes_bf16=model.hidden * 2,
    )


def expected_distinct_ranks(num_ranks: int, num_topk: int) -> float:
    """均匀路由下一个 token 覆盖的期望目标 rank 数。"""
    return num_ranks * (1.0 - (1.0 - 1.0 / num_ranks) ** num_topk)


def ledger(model: ModelConfig, num_ranks: int, fp8_dispatch: bool = True):
    by = payload_bytes(model, fp8_dispatch)
    distinct = expected_distinct_ranks(num_ranks, model.num_topk)
    # 每个 rank 收到的 token 数(去重后,按 rank 计):按对称性 ≈ T × 期望目标 rank 数。
    recv_tokens = model.tokens_per_rank * distinct
    return {
        "num_ranks": num_ranks,
        "experts_per_rank": model.num_experts // num_ranks,
        "expected_distinct_ranks": distinct,
        "recv_tokens_per_rank": recv_tokens,
        "dispatch_bytes_per_rank": recv_tokens * by.dispatch,
        "combine_bytes_per_rank": recv_tokens * by.combine,
        "payload_dispatch": by.dispatch,
        "payload_combine": by.combine,
    }


def human(n: float) -> str:
    for unit in ("B", "KB", "MB", "GB", "TB"):
        if abs(n) < 1024.0 or unit == "TB":
            return f"{n:,.1f} {unit}"
        n /= 1024.0
    return f"{n:,.1f} TB"


def main() -> int:
    parser = argparse.ArgumentParser(description="MoE 一层 dispatch / combine 字节账")
    parser.add_argument("--hidden", type=int, default=7168)
    parser.add_argument("--topk", type=int, default=6)
    parser.add_argument("--experts", type=int, default=256)
    parser.add_argument("--tokens", type=int, default=16384)
    parser.add_argument("--layers", type=int, default=1, help="按多少层汇总(V3 量级可填 61)")
    parser.add_argument("--bf16-dispatch", action="store_true", help="dispatch 也用 BF16")
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()

    model = ModelConfig(
        hidden=args.hidden,
        num_topk=args.topk,
        num_experts=args.experts,
        tokens_per_rank=args.tokens,
    )
    by = payload_bytes(model, not args.bf16_dispatch)

    rows = [ledger(model, r, not args.bf16_dispatch) for r in (8, 16, 32, 64, 128)]

    if args.json:
        print(json.dumps({"config": model.__dict__, "payload": by.__dict__, "rows": rows}, indent=2))
        return 0

    print("每 token payload(本书复算,口径 = DeepEP-Ascend/tests/ep/test_ep.py:404-406)")
    print(f"  hidden={model.hidden}, topk={model.num_topk}, experts={model.num_experts}, "
          f"T={model.tokens_per_rank}, layers={args.layers}")
    print(f"  dispatch: {by.dispatch:,} B/token "
          f"(hidden {by.hidden_bytes_fp8:,} + sf {by.sf_bytes} + idx {by.indices_bytes} + w {by.weights_bytes})")
    print(f"  combine : {by.combine:,} B/token (bf16 hidden {by.hidden_bytes_bf16:,} + w {by.weights_bytes})")
    print()
    print(f"{'EP':>4} {'专家/rank':>9} {'期望目标rank':>12} {'接收token/rank':>15} "
          f"{'dispatch/rank':>14} {'combine/rank':>14}")
    for row in rows:
        print(
            f"{row['num_ranks']:>4} {row['experts_per_rank']:>9} "
            f"{row['expected_distinct_ranks']:>12.3f} {row['recv_tokens_per_rank']:>15,.0f} "
            f"{human(row['dispatch_bytes_per_rank']):>14} {human(row['combine_bytes_per_rank']):>14}"
        )
    print()
    total = sum(r["dispatch_bytes_per_rank"] + r["combine_bytes_per_rank"] for r in rows) / len(rows)
    print(f"(上表按 EP8..EP128 各一行;若按 {args.layers} 层汇总,"
          f"平均每 rank 的通信量约为 {human(total * args.layers)},跨 rank 合计约为 "
          f"{human(total * args.layers * len(rows))}。)")
    print()
    print("注意:这是均匀路由假设下的**上界量级**估计,不是实测流量;")
    print("真实值取决于路由分布、去重率与每 rank 的本地专家数(见正文 §1.3)。")
    return 0


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

← 浏览本章全部代码