面向本科 CS 学生的完整实操教程:AstraSim 是什么、为什么需要它、以及如何在你的电脑上把一次真实的仿真从代码拉取跑到输出结果——全部基于一次真实安装过程整理。
一句话:AstraSim(ASTRA-sim 2.0)是一个分布式 AI 系统模拟器,用来回答一个问题——"如果我用 XX 张 GPU、YY 网络拓扑、ZZ 集合通信算法训练一个大模型,到底要跑多久?"——而不需要真的买硬件。
现代大模型(如 GPT、Llama)的单卡显存放不下,必须把模型切分到成百上千张 GPU 上并行训练。这时通信开销(GPU 之间传数据)往往比计算更慢,成为性能瓶颈。而通信的时间取决于:
| 因素 | 举例 |
|---|---|
| 硬件 | 单卡算力、NVLink/IB 网络带宽与延迟 |
| 拓扑 | 环(Ring)、2D-Torus、Fat-Tree、Clos 等互联结构 |
| 算法 | AllReduce 用 Ring / Tree / 分层实现,通信量不同 |
| 负载 | 训练脚本产生的通信模式(算子执行轨迹) |
想在买硬件之前就评估这些组合的性能?物理实验太贵太慢,于是有了 模拟器:把上述因素抽象成数学模型或事件驱动仿真,在普通电脑上"预演"一次分布式训练。
AstraSim 2.0 把整个模拟栈拆成可插拔的层次,每一层都可以换实现——这是它最大的设计亮点:
模拟器本身不是"模型",而是一个可组合的框架——你换一个后端,就换了一种仿真保真度;换一份轨迹,就换了一个训练负载。
下面这些术语会反复出现在命令、配置和日志里,先混个脸熟。
工作负载的输入格式。一个 .et 文件记录一个 NPU 上按时间排列的算子事件(计算/通信/空闲)。AstraSim 读取它来驱动仿真。
多卡协同的通信原语:AllReduce(规约再广播)、AllGather(各卡汇总)、ReduceScatter(分散规约)、AllToAll(全交换)。训练中占比极高。
把 N 个节点串成环,数据沿环传递。示例配置里的 Ring_4npus = 4 个 NPU 组成环。
是否考虑网络拥塞。unaware 用排队论公式快速估算;aware 追踪链路占用,更准但更慢。对应两个可执行文件。
运行时的 3 类输入:workload(轨迹前缀)、system(调度与内存参数 json)、network(带宽/延迟/拓扑 yml)。
模拟的单位计算设备(GPU 的抽象)。示例里有 4、8、16 NPU 的配置。
| analytical | htsim | ns-3 | |
|---|---|---|---|
| 原理 | 数学公式近似(无拥塞模型) | 包级离散事件模拟(拥塞感知) | 完整网络协议栈仿真(最精确) |
| 速度 | ⚡ 极快(秒级) | 🕐 中等 | 🐢 慢(分钟~小时) |
| 保真度 | 低(适合快速对比算法) | 中 | 高(研究网络细节) |
| 构建难度 | 简单(纯 CMake) | 中等(需 patch 第三方库) | 较复杂(ns-3 本体庞大) |
| 产物 | AstraSim_Analytical_* | AstraSim_HTSim | ns3.42-AstraSimNetwork |
| 我们的实测 | 4 NPU 仿真 0.1 秒 | 8 NPU 约数秒 | 16 NPU 约 1 分钟 |
学习建议:先 analytical 跑通理解流程,再用 ns-3 感受真实网络仿真。
AstraSim 官方只支持 Linux / Docker。我们在 Windows 上选择了 WSL2(Windows 自带 Linux 子系统)——无需买服务器、无需装双系统。
| 工具 | 用途 | Ubuntu 包名 |
|---|---|---|
| g++ / build-essential | C++ 编译器(AstraSim 是 C++17 项目) | build-essential |
| cmake | 构建系统 | cmake |
| protobuf | Chakra 轨迹的序列化协议(编译 .proto 文件) | protobuf-compiler libprotobuf-dev |
| python3 | 工具脚本 / ns-3 构建脚本 | python3 |
| git | 拉取代码 | git |
| OpenMPI(仅 ns-3) | ns-3 并行仿真支持 | libopenmpi-dev openmpi-bin |
# WSL 中执行(需管理员权限安装 WSL 本体) $ wsl --install -d Ubuntu-22.04 # 进入 Ubuntu 后,安装工具链 $ sudo apt update $ sudo apt install -y build-essential cmake ninja-build \ protobuf-compiler libprotobuf-dev python3 python3-pip git \ libopenmpi-dev openmpi-bin
AstraSim 不是一个单仓库,而是 1 个主仓库 + 7 个 Git 子模块。子模块就是"仓库里的仓库",指向各自独立维护的库(Chakra、fmt、spdlog、ns-3 等)。
$ git clone https://github.com/astra-sim/astra-sim.git $ cd astra-sim
$ git submodule update --init --recursive
--recursive 表示子模块里的子模块也一起拉(嵌套)。这一步会下载 ns-3(约 170MB),耐心等待。
$ git clone https://ghfast.top/https://github.com/astra-sim/astra-sim.git $ cd astra-sim $ git -c 'url.https://ghfast.top/https://github.com/.insteadOf=https://github.com/' \ submodule update --init --recursive
| 目录 | 内容 |
|---|---|
| astra-sim/ | 模拟器核心源码(system / workload / network_frontend) |
| build/ | 各后端的构建脚本(astra_analytical / astra_htsim / astra_ns3) |
| examples/ | 示例配置:workload(轨迹)、system(json)、network(yml)、run_scripts |
| extern/ | 子模块:chakra、fmt、spdlog、analytical 后端、ns-3、csg-htsim |
| inputs/ | 额外输入配置(如真实硬件 H100 参数) |
以 analytical 后端为例,构建本质是 3 步:protoc 编译 proto → cmake 配置 → make 编译链接。
$ cd build/astra_analytical $ ./build.sh -t all # 同时构建 congestion_unaware 和 congestion_aware
等价的手动命令(理解原理用):
# ① 编译 Chakra proto $ protoc et_def.proto --proto_path=extern/graph_frontend/chakra/schema/protobuf \ --cpp_out=extern/graph_frontend/chakra/schema/protobuf # ② cmake 配置 + ③ 编译 $ mkdir -p build/astra_analytical/build && cd build/astra_analytical/build $ cmake .. -DBUILDTARGET=congestion_unaware $ make -j$(nproc)
| 后端 | 命令 | 产物位置 |
|---|---|---|
| analytical | build/astra_analytical/build.sh -t all | build/astra_analytical/build/bin/ |
| htsim | build/astra_htsim/build.sh(自动给 csg-htsim 打补丁) | build/astra_htsim/build/bin/ |
| ns-3 | build/astra_ns3/build.sh -c(编译整个 ns-3,最慢) | extern/network_backend/ns-3/build/scratch/ |
AstraSim 的输入是三种格式的文件。这一章把仓库里的 6 个真实配置文件 全部嵌入网页——点开即可查看完整内容,每个字段都有中文注释。这些文件也真实存在于你的 D:\astrasim\examples\ 目录下,可用 VSCode / 记事本直接打开。
键值对 + 缩进层级,key: value,# 开头是注释,没有大括号。用于网络配置。
花括号 { } 包对象,键和字符串都要双引号,[ ] 是数组。用于系统 / 内存 / 拓扑配置。
二进制 protobuf,记录每个 NPU 的算子事件。文本编辑器打开是乱码,要用 Python 读取(见下方脚本)。
最简网络配置:4 个 NPU 组成环形(Ring),每条链路 50 GB/s、500 ns 延迟。
topology: [ Ring ] npus_count: [ 4 ] bandwidth: [ 50.0 ] # GB/s latency: [ 500.0 ] # ns
| 字段 | 含义 | 示例值 |
|---|---|---|
| topology | 逻辑拓扑类型:节点怎么连 | [ Ring ] |
| npus_count | 参与仿真的 NPU(GPU)个数 | [ 4 ] |
| bandwidth | 链路带宽,单位 GB/s | [ 50.0 ] |
| latency | 链路延迟,单位 ns(纳秒) | [ 500.0 ] |
真实硬件配置:NVIDIA H100 8 卡服务器(官方实测校准参数),交换机直连拓扑。
topology: [ Switch ] npus_count: [ 8 ] bandwidth: [ 400.0 ] # GB/s latency: [ 936.25 ] # ns
| 字段 | 含义 | 示例值 |
|---|---|---|
| topology | 拓扑类型 | [ Switch ] |
| npus_count | NPU 数量 | [ 8 ] |
| bandwidth | NVLink 带宽(GB/s) | [ 400.0 ] |
| latency | 链路延迟(ns) | [ 936.25 ] |
系统配置(最值得研究):决定调度策略、集合通信用什么算法、内存带宽等。
{
"scheduling-policy": "LIFO",
"endpoint-delay": 10,
"active-chunks-per-dimension": 1,
"preferred-dataset-splits": 4,
"all-reduce-implementation": [
"ring"
],
"all-gather-implementation": [
"ring"
],
"reduce-scatter-implementation": [
"ring"
],
"all-to-all-implementation": [
"ring"
],
"collective-optimization": "localBWAware",
"local-mem-bw": 1600,
"boost-mode": 0,
"roofline-enabled": 0,
"peak-perf": 900
}
| 字段 | 含义 | 示例值 |
|---|---|---|
| scheduling-policy | 算子调度顺序 | "LIFO" |
| endpoint-delay | 端点(网卡侧)处理延迟(ns) | 10 |
| active-chunks-per-dimension | 每个维度同时传输的数据块数 | 1 |
| preferred-dataset-splits | 训练数据切分份数 | 4 |
| all-reduce-implementation | AllReduce 用哪个算法实现 | ["ring"] |
| all-gather-implementation | AllGather 算法 | ["ring"] |
| reduce-scatter-implementation | ReduceScatter 算法 | ["ring"] |
| all-to-all-implementation | AllToAll 算法 | ["ring"] |
| collective-optimization | 集合通信优化策略 | "localBWAware" |
| local-mem-bw | 本地显存带宽(GB/s) | 1600 |
| boost-mode | 加速模式(0=关闭) | 0 |
| roofline-enabled | 是否启用 Roofline 计算模型 | 0 |
| peak-perf | 单卡峰值算力(TFLOPS) | 900 |
H100 实测版系统配置:与上面结构相同,只改了和真实硬件相关的参数。
{
"scheduling-policy": "LIFO",
"endpoint-delay": 10,
"active-chunks-per-dimension": 2,
"preferred-dataset-splits": 4,
"all-reduce-implementation": [
"ring"
],
"all-gather-implementation": [
"ring"
],
"reduce-scatter-implementation": [
"ring"
],
"all-to-all-implementation": [
"ring"
],
"collective-optimization": "localBWAware",
"local-mem-bw": 3350,
"boost-mode": 0
}
| 字段 | 含义 | 示例值 |
|---|---|---|
| active-chunks-per-dimension | 同时传输的数据块数(更大=更激进) | 2 |
| local-mem-bw | H100 显存带宽(GB/s) | 3350 |
| (无 roofline / peak-perf) | 不写这两个字段 = 不启用计算模型 | — |
远端内存配置:声明不扩展内存——只用本地显存,模拟最朴素的情况。
{
"memory-type": "NO_MEMORY_EXPANSION"
}
| 字段 | 含义 | 示例值 |
|---|---|---|
| memory-type | 内存扩展模式 | "NO_MEMORY_EXPANSION" |
逻辑拓扑配置(ns-3 后端专用):告诉模拟器从物理网络里取哪 16 个节点、排成什么逻辑形状。
{
"logical-dims": ["16"]
}
| 字段 | 含义 | 示例值 |
|---|---|---|
| logical-dims | 逻辑拓扑各维大小(1D=一排) | ["16"] |
.et 是二进制 protobuf 格式,记事本打开是乱码。用下面这段 Python 就能读出每个 NPU 上的算子事件(构建时已生成 et_def_pb2.py):
# ① 生成 protobuf 的 Python 代码(构建流程里已自动做过):
# protoc et_def.proto --proto_path=extern/graph_frontend/chakra/schema/protobuf \
# --python_out=extern/graph_frontend/chakra/schema/protobuf
import sys
sys.path.insert(0, "extern/graph_frontend/chakra/schema/protobuf")
import et_def_pb2 as et
trace = et.ExecutionTrace()
with open("examples/workload/microbenchmarks/all_reduce/4npus_1MB/all_reduce.0.et", "rb") as f:
trace.ParseFromString(f.read())
print("NPU id:", trace.id)
print("事件总数:", len(trace.events))
for ev in list(trace.events)[:8]:
print(ev.type, "|", ev.name, "|", ev.attr)
下面的内容不是示意图,而是从你仓库里 examples/workload/microbenchmarks/ 的 16 个真实 .et 文件解析出来的(每个文件对应一个 NPU)。可以看到:微基准轨迹 = 每个文件只有 1 个抽象通信节点,具体的"怎么传、传几轮"由 AstraSim 在运行时结合系统/网络配置展开。
Ring AllReduce 分两阶段:Reduce-Scatter(数据沿环规约求和,3 步)→ All-Gather(结果沿环广播,3 步)。每步每个 NPU 只和邻居传 1 个 chunk(256 KB)。
所有后端都接受同样的 4 个核心参数——这就是"插件式架构"带来的统一接口:
| 参数 | 含义 | 示例值 |
|---|---|---|
| --workload-configuration | Chakra 轨迹文件前缀(.et 文件按 NPU 编号) | examples/workload/.../4npus_1MB/reduce_scatter |
| --system-configuration | 系统参数:调度策略、集合通信算法、内存带宽 | examples/system/native_collectives/Ring_4chunks.json |
| --network-configuration | 网络参数:拓扑、NPU 数、带宽、延迟 | examples/network/analytical/Ring_4npus.yml |
| --remote-memory-configuration | 远端内存模型(训练中参数交换的存储抽象) | examples/remote_memory/analytical/no_memory_expansion.json |
$ ./build/astra_analytical/build/bin/AstraSim_Analytical_Congestion_Unaware \
--workload-configuration=examples/workload/microbenchmarks/reduce_scatter/4npus_1MB/reduce_scatter \
--system-configuration=examples/system/native_collectives/Ring_4chunks.json \
--remote-memory-configuration=examples/remote_memory/analytical/no_memory_expansion.json \
--network-configuration=examples/network/analytical/Ring_4npus.yml
看到类似下面的输出,就说明仿真跑完了(这是本机真实输出):
[info] sys[0] finished, 22240 cycles, exposed communication 22240 cycles. [info] sys[0], Wall time: 22240 [info] sys[0], Comm time: 22240 ... [warning] Exiting
$ ./build/astra_htsim/build/bin/AstraSim_HTSim \
--workload-configuration=examples/workload/microbenchmarks/all_reduce/8npus_1MB/all_reduce \
--system-configuration=examples/system/native_collectives/Ring_4chunks.json \
--remote-memory-configuration=examples/remote_memory/analytical/no_memory_expansion.json \
--network-configuration=examples/network/analytical/Ring_8npus.yml \
--htsim_opts -topo examples/network/htsim/8nodes.topo
$ cd extern/network_backend/ns-3/build/scratch $ ./ns3.42-AstraSimNetwork-default \ --workload-configuration=/home/你的用户名/astra-sim/examples/workload/microbenchmarks/all_gather/16npus_1MB/all_gather \ --system-configuration=/home/你的用户名/astra-sim/examples/system/native_collectives/Ring_4chunks.json \ --network-configuration=/home/你的用户名/astra-sim/extern/network_backend/ns-3/scratch/config/config_clos.txt \ --remote-memory-configuration=/home/你的用户名/astra-sim/examples/remote_memory/analytical/no_memory_expansion.json \ --logical-topology-configuration=/home/你的用户名/astra-sim/examples/network/ns3/sample_16nodes_1D.json \ --comm-group-configuration=empty
仿真的"时间"以周期(cycle)为单位——一个 cycle 通常对应一个时钟周期(例:1 GHz 时钟下 22240 cycles ≈ 22.24 µs 仿真时间)。
| 指标 | 含义 | 解读 |
|---|---|---|
| Wall time | 该 NPU 从开始到结束的总时间 | 整体性能:越大越慢 |
| Comm time | 其中花在通信上的时间 | 通信占比越高,网络越可能是瓶颈 |
| exposed communication | 暴露的通信延迟(计算无法掩盖的部分) | 理想情况下通信应被计算"隐藏"(重叠) |
本例中 Wall time = Comm time = 22240,说明这是一个纯通信微基准(microbenchmark)——没有计算,所有时间都花在通信上,符合预期。
| 后端 | 示例 | Wall time (cycles) | 说明 |
|---|---|---|---|
| analytical | 4 NPU ReduceScatter | 22,240 | 公式估算,瞬时完成 |
| htsim | 8 NPU AllReduce | 1,040,027,218 | 包级仿真,时间粒度更细(数量级差异来自建模方式,不代表谁更"对") |
| ns-3 | 16 NPU AllGather | 1,850,320 | 完整协议栈仿真 |
输出用 spdlog 格式化:[2026-08-17 14:38:26.241] [system::topology::RingTopology] [info] ...。字段依次是:时间戳 → 模块 → 级别(info/warning/error)→ 消息。级别可用环境变量或配置调整。
大陆网络访问 GitHub 不稳定。解决办法:① 用镜像前缀 https://ghfast.top/https://github.com/...;② 或配置 git 全局改写规则:git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/",之后所有 GitHub 操作自动走镜像(不想要时用 --unset 移除)。
这是 AstraSim 最核心的设计:职责分离——轨迹只回答"做什么",其他问题由配置和模拟器回答:
| 问题 | 谁回答 | 定义位置 |
|---|---|---|
| 做什么? | .et 轨迹 | comm_type=ALL_REDUCE、comm_size=1MB |
| 和谁做? | 网络配置 | npus_count: [4] + comm-group 配置(默认空组 = 全部 NPU) |
| 怎么做? | 系统配置 | all-reduce-implementation: ["ring"] 等 |
| 何时做? | 模拟器 | 事件驱动调度:模拟开始即注入无依赖节点,调度策略 LIFO 决定顺序 |
源码依据(Workload.cc extract_comm_group):空通信组或组 "0" 都对应默认通信组 = 包含所有 rank。microbenchmark 轨迹没有 involved_dim 属性,默认所有维度参与。所以"1MB AllReduce"的范围就是当前仿真里的全部 4 个 NPU——"4"来自 Ring_4npus.yml,不是来自 .et。时间方面:start_time=0 只是占位,真正的开始由模拟 tick 0 注入 + 依赖关系 + 调度策略决定。
flow.txt 是 ns-3 的输入文件(描述预定义流量),不在 git 仓库里,需要手动创建。它只需首行一个数字(flow 数量,本教程实测写 0 即可,因为流量由 AstraSim 动态生成):echo 0 > extern/network_backend/ns-3/scratch/output/flow.txt
可以(用 MinGW/MSYS2),但需要打几个兼容补丁(lockf → _locking、cxxopts 补 cstdint、链接静态 absl 等),且 ns-3 后端无法在 Windows 原生构建。强烈建议用 WSL2:一条命令装好,全后端无补丁跑通。
用 make -j$(nproc) 或减少并行度 make -j4。ns-3 编译建议至少 4 核 8G 内存。编译产物占用约 1.8 GB,注意磁盘空间。
检查路径是否写对(相对路径基于运行命令时所在的目录),以及文件格式。analytical 的 network 配置是 YAML(bandwidth/latency),system 配置是 JSON(all-reduce-implementation 等)。
Ubuntu 22.04 自带 protobuf 3.12 可用。若需要新版,可用 apt install protobuf-compiler libprotobuf-dev 确认版本:protoc --version。
| 资源 | 地址 / 内容 |
|---|---|
| 官网 | https://astra-sim.github.io/ |
| Wiki / 文档 | https://astra-sim.github.io/astra-sim-docs/ (安装、配置、API 详解) |
| GitHub | https://github.com/astra-sim/astra-sim |
| Chakra 轨迹格式 | https://github.com/mlcommons/chakra |
| 论文 | ASTRA-sim: Enabling SW/HW Co-Design Exploration...(HPCA 2020);ASTRA-sim 2.0(arXiv 2025) |