本项目把“得出性能结论”和“解释时间花在哪里”分成两条路径:
*_performance可执行文件用于绝对运行时间、容错开销和单/多 GPU 对比。它保留运行级汇总计时,但编译时移除了逐轮 trace 与 NVTX 代码。*_profiling可执行文件用于逐轮活跃顶点、冗余任务、细粒度 CUDA event 和 Nsight Systems 时间线。采集本身会扰动执行,不能用它替代 performance 数据。
两类可执行文件来自相同算法源码:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNCCL_ROOT=/path/to/nccl
cmake --build build --target performance -j
cmake --build build --target profiling -j正式 benchmark 必须使用 Release 构建;profiling 也应使用同一 Release 配置,避免把构建类型差异误认成 instrumentation 差异。
BENCHMARK_TIMING 是程序内部的机器可读汇总。不要把父区间与其子集重复相加。
process_wall_ms 实验 runner 看到的进程外层时间
└─ 包含 mpirun/PMIx、各 rank 读图和进程退出等程序外部开销
algorithm_e2e_ms 算法函数内部完整完成时间
├─ single_gpu_setup_ms 单 GPU 设备/上下文、buffer 与状态初始化(仅单 GPU)
├─ distributed_setup_ms 分区、plan、设备/NCCL/buffer 初始化(仅多 GPU)
├─ initial_ghost_exchange_ms 首次 ghost 同步(仅多 GPU)
├─ algorithm_total_ms 稳态算法总时间
│ ├─ main_loop_ms
│ ├─ cpu_check_drain_ms
│ └─ postcheck_total_ms
├─ final_result_copy_ms 最终 GPU→host 复制(仅单 GPU)
├─ final_result_gather_ms owned D2H + MPI_Allgatherv(仅多 GPU)
└─ e2e_unattributed_ms 边界调用等未归入上述互斥阶段的余量
schema v9 的 24 个规范 timing 字段按固定顺序为:
main_loop_ms, gpu_compute_ms, graph_kernel_ms, graph_update_ms,
score_and_mark_ms, tolerance_graph_update_ms, tolerance_gpu_bookkeeping_ms,
d2h_summary_ms, cpu_validation_ms, checker_end_to_end_ms,
cpu_check_drain_ms, nccl_exchange_ms, mpi_sync_ms,
postcheck_total_ms, postcheck_mpi_ms, communication_ms, algorithm_total_ms,
single_gpu_setup_ms, distributed_setup_ms, initial_ghost_exchange_ms,
final_result_copy_ms, final_result_gather_ms, e2e_unattributed_ms,
algorithm_e2e_ms
algorithm_total_ms = main_loop_ms + cpu_check_drain_ms + postcheck_total_ms。这个等式在单 GPU,以及多 GPU 的每条 rank-local 记录上成立;rank mean 也因线性关系而可加。但各阶段的 rank max 可能来自不同 rank,不能将 rank_main_loop_max_ms + rank_cpu_check_drain_max_ms + rank_postcheck_total_max_ms 相加当作真实时间线;最慢 rank 的稳态完成延迟应直接读 rank_algorithm_total_max_ms。
algorithm_e2e_ms 还包括适用的 setup、首次 ghost exchange、最终 D2H/copy/gather 和未归属边界余量;single/multi 路径都在该边界内选择逻辑设备并初始化 CUDA 上下文,多 GPU 的 NCCL 初始化位于 distributed_setup_ms,final_result_gather_ms(owned D2H + MPI_Allgatherv)也明确在 e2e 内。process_wall_ms 由 runner 在算法进程外测量,还包括 launcher/PMIx、读图和退出,因而是第三种口径,不是 algorithm_e2e_ms 的别名。
下面这些是解释性子集或工作量,不能再加到 algorithm_total_ms 或 algorithm_e2e_ms 上:
graph_update_ms/tolerance_graph_update_ms:基线/容错主图 kernel 的 CUDA event 合计;graph_kernel_ms是兼容汇总字段。score_and_mark_ms:容错引入的关键顶点评分与标记 kernel。tolerance_gpu_bookkeeping_ms:检查摘要清零等容错 GPU 准备操作。d2h_summary_ms:固定大小检查摘要的 D2H 设备传输工作量。cpu_validation_ms:CPU 真正解码/验证摘要的工作时间,不含等待 CUDA event。checker_end_to_end_ms:从检查任务提交到 CPU 处理完成的延迟,包含排队和 D2H 等待;各任务延迟可以彼此重叠,不能作为可加 wall time。nccl_exchange_ms、mpi_sync_ms与postcheck_mpi_ms:通信调用时间;其中postcheck_mpi_ms是postcheck_total_ms的子集。
多 GPU 的 cpu_validation_ms、checker_end_to_end_ms 及相应逐轮 trace 均为 rank-local:一个 worker 只验证本 rank 的摘要。rank mean/max 描述本地 checker 工作量或最慢 rank 的本地延迟,并不是跨 rank 的全局检测时间。worker drain 后,程序才在 batched post-check 中批量归约所有 rank 保存的逐轮证据;全局 DMR/单调性/趋势结论来自该阶段,时间归入 postcheck_total_ms,其中 MPI 调用归入 postcheck_mpi_ms。
performance 可执行文件保留 algorithm_total_ms、algorithm_e2e_ms、cpu_check_drain_ms 和其他运行级汇总,但不创建逐任务 checker 的 timing event/CPU 时钟;因此 d2h_summary_ms、cpu_validation_ms、checker_end_to_end_ms 在 performance 输出中为 0。profiling 可执行文件才采集这三个详细字段及其逐轮记录。这里的 0 表示“该 profile 不采集”,不是“实际传输或检查耗时为零”。
多 GPU stdout 同时保留每 rank 的 BENCHMARK_RANK_TIMING。实际完成延迟看 rank max;rank mean 表示平均工作量。第三方分析应优先使用每-rank 原始记录,不能把不同字段各自的 rank max 拼成一条虚构时间线。
实验 runner 会为每个 profiling attempt 设置 GRAPHALG_PROFILE_OUTPUT_DIR,程序在其中写出:
iteration_activity.rankNNNN.csv
iteration_timings.rankNNNN.csv
trace_summary.rankNNNN.json
逐轮采集不强制构造一条每轮 D2H 的完整 trace record。active 数量复用算法本来就需要的 per-round scalar/D2H 或上一轮 host carry;仅 profiling 需要的 device-side history(例如 critical/redundant 计数)在算法结束时一次 bulk D2H。容错 checker 的固定大小摘要仍沿用其已有的异步 D2H 和 pinned host buffer。CSV/JSON 记录先在 host 内存累计,运行结束后统一写盘;activity 统计不会额外启动 active scan、引入同步或执行逐轮文件 I/O。
attempt 专属 CSV 行会保存 dataset, algorithm, method, instrumentation_profile, rank 等必要的本地 trace identity,但不会重复保存 attempt_id、session、repeat、全部算法参数和完整环境 provenance。因此它们可以作为普通 CSV 解析,却不是脱离实验目录后仍完整自描述的实验数据。应使用 raw/profiling/<attempt_id>/ 目录名关联同一运行目录下对应的 raw attempt JSON,并结合该 trace 目录内的 trace_summary.rankNNNN.json 解读。需要交给第三方独立分析时,应优先使用 collected/iteration_activity.csv 与 collected/iteration_timings.csv;collector 已为这些 merged 表补齐 attempt/session/record、phase/repeat、算法参数等实验上下文。
每行对应某 rank 某一轮主图更新实际消费的 work set:
work_vertices:single GPU 的实际顶点域,或该 rank 的 owned 顶点数;ghost 不重复计入。owned_vertices:显式保存上述 rank-owned 域;当前主更新记录中与work_vertices相等,便于第三方在不依赖项目约定时确认 ghost 未被计入。active_vertices:这一轮进入主更新逻辑的顶点数。KCore 等算法记录的是d_active,不是仍存活的d_alive数量。launched_threads与tail_threads:真实 launch 线程数和最后一个不满 block 的越界 lane。inactive_valid_threads = work_vertices - active_vertices。reusable_idle_pct = inactive_valid_threads / work_vertices * 100;分母不含 tail lane。critical_vertices:容错 score-and-mark 标出的关键顶点数。redundant_tasks_assigned:容错 kernel 实际分配的冗余任务数。
inactive_valid_threads 表示冗余领取前、由算法收敛产生的有效空闲容量;它不是“最终什么都没做的线程数”。当前融合 kernel 允许最后一个 block 的 tail lane 承担冗余任务,所以 redundant_tasks_assigned 必须按实际执行记录,不能由 inactive_valid_threads 推算。
多 GPU 不为 profiling 新增逐轮 MPI 规约。每个 rank 独立输出;离线按 iteration 对 work_vertices/active_vertices 求和后,再计算全局 idle 百分比。
这是长表:iteration, stage, elapsed_ms。可能出现的阶段包括:
graph_update或tolerance_graph_updatescore_and_mark;仅用于终止判定、没有被主图更新消费的额外一次评分记作score_and_mark_convergence_probetolerance_gpu_bookkeepingd2h_summarycpu_validationchecker_end_to_endnccl_exchangempi_sync
CUDA 阶段使用同一 stream 上的 CUDA event;MPI 使用 MPI_Wtime。CPU validation 从 CUDA event 已完成后开始计时,因此不会把 worker 等待误报为 CPU 验证计算。
每个逐轮 CUDA stage 直接取对应运行级 CUDA event accumulator 的本轮增量,不再为 trace 另套一对 event;因此,同一 rank 的逐轮值之和应与 stdout/rank timing 中对应 aggregate 一致(只允许浮点累计与四位小数输出造成的舍入误差)。score_and_mark_ms 的对账需同时累加 score_and_mark 与最后一次不进入下一轮主更新的 score_and_mark_convergence_probe。不同层级仍不能相加:例如 graph_update 是 gpu_compute 的子集,二者即便各自都能与 aggregate 对账,也不能堆叠成总时间。MPI/CPU 逐轮值同样由各自运行级累计量的增量产生。
trace_summary 记录完成迭代数、activity/timing 行数和 dropped-record 数。正常运行要求每个 rank 的 activity 行数等于 completed_iterations;任何 dropped record 都必须视为数据质量问题,不能补零后继续画图。
每次 run_item 调用生成一个 attempt_session_id,该调用实际写出的所有方法/repeat attempt 共用它;每条命令仍有独立 attempt_id。使用 --resume --retry-failed 时,只要一个 measured repeat 的 selected methods 不完整、含最新失败、缺 session ID 或混有不同 session,runner 就整组重跑,而不是复用部分旧方法结果。正式 pair 还要求两侧 session ID 相同。
collect_results.py 将 raw 证据投影为以下可供第三方直接读取的表:
observations.csv:选中 attempt 的 identity、参数、实际 worker GPU/驱动、process_wall_ms、结果/检测摘要(含三类检测的首次异常轮次),以及 24 个 timing 的 rank-max/rank-mean 列;attempts.csv还保留全部合法历史 attempt 和 selected 标志。rank_timings.csv:每个 attempt/rank 的 24 个 timing 原值。iteration_activity.csv:逐 attempt/session/rank/iteration 的 activity、idle、critical 和冗余任务计数。iteration_timings.csv:逐 attempt/session/rank/iteration/stage 的耗时。
四张表都保留 attempt_id 与 attempt_session_id;逐轮表还保留 schema、record、算法、数据集、方法、profile、MPI rank 数、BFS source、参数、图规模、hostname 和可见 GPU 等列。因而 collected merged 表是供第三方按行独立解释和筛选的自描述数据。attempt 专属 trace 仍在 raw/profiling/<attempt_id>/,它依赖 raw attempt JSON/同目录 summary 补足上下文;collected 仍只是可从这些 raw 证据重建的投影,而不是新的证据源。
collector 会先重建 manifest 中 algorithm×dataset 的完整 work-item 笛卡尔积,核对 item 编号、调用数与计划总数,再重新解析 attempt UUID 对应的原始 stdout,并把 machine records、参数、metadata、方法顺序和 profile 与冻结 manifest 逐项核对。profiling trace 必须位于该 UUID 的规范目录;每个 CSV/JSON 的 size 和 SHA256 会写入 raw validation,collector 重算并精确比较。因此即使把 elapsed_ms 改成另一个格式合法的数,或把 trace 跨 attempt 串换,也会使该 attempt 失去 success 资格,而不是继续出现在 successful_measurements.csv。
profiling run 在 collect_results.py 完成后即停止:本地与 Slurm 流水线都会跳过只接受 performance 数据的 analyze_overhead.py 和 plot_overhead.py。绝对开销来自单独的 performance run;两类 run 可用 check_profile_equivalence.py 检查结果、迭代、检测摘要/首次异常轮次的一致性,并拒绝 Git 工作树或 Release 构建配置不同的两组证据。
本项目只报告完整 kernel、独立 memcpy/NCCL/MPI 调用和 CPU worker 阶段。容错融合 kernel 内的关键任务领取、主/副本计算、结果比对和摘要原子更新共享一次 kernel launch、同步与内存访问;CUDA event 无法在不改实现的情况下给这些线程内步骤分别计时。
为了“测出内部毫秒数”而拆 kernel,会改变 launch 数、全局内存流量、同步和调度,测到的是另一个实现。对这些内部机制只报告计数,并在 Nsight Systems 中把整个融合 kernel 作为一个区间观察。
run_nsys_profile.sh 只选择名称以 _profiling 结尾的可执行文件。下面四条命令分别给出 BFS、CC、KCore 和 PageRank 的代表性双 rank 容错时间线;它们都只暴露物理 GPU 6、7,并使用 cit-HepPh。开发验收已对 PageRank 命令完成一次实采并确认 CUDA/NVTX/NCCL/MPI 域可读;生成的大型 report 不提交进仓库,正式论文采集仍须在目标环境按同一规则归档。
BFS:
CUDA_VISIBLE_DEVICES=6,7 \
bash scripts/experiments/run_nsys_profile.sh \
--algorithm bfs --method multi_gpu_fault_tolerant \
--dataset cit-HepPh --ranks 2 --gpus 6,7 \
--output outputs/nsys/bfs_multi_ft_cit_hepph_$(date +%Y%m%d_%H%M%S) \
-- -n -s 0 -a 0.5 -b 0.5 -t 0.3CC:
CUDA_VISIBLE_DEVICES=6,7 \
bash scripts/experiments/run_nsys_profile.sh \
--algorithm cc --method multi_gpu_fault_tolerant \
--dataset cit-HepPh --ranks 2 --gpus 6,7 \
--output outputs/nsys/cc_multi_ft_cit_hepph_$(date +%Y%m%d_%H%M%S) \
-- -n -a 0.5 -b 0.5 -t 0.3KCore:
CUDA_VISIBLE_DEVICES=6,7 \
bash scripts/experiments/run_nsys_profile.sh \
--algorithm kcore --method multi_gpu_fault_tolerant \
--dataset cit-HepPh --ranks 2 --gpus 6,7 \
--output outputs/nsys/kcore_multi_ft_cit_hepph_$(date +%Y%m%d_%H%M%S) \
-- -n -k 5 -a 0.5 -b 0.5 -t 0.3PageRank:
CUDA_VISIBLE_DEVICES=6,7 \
bash scripts/experiments/run_nsys_profile.sh \
--algorithm pagerank --method multi_gpu_fault_tolerant \
--dataset cit-HepPh --ranks 2 --gpus 6,7 \
--output outputs/nsys/pagerank_multi_ft_cit_hepph_$(date +%Y%m%d_%H%M%S) \
-- -n -a 0.5 -b 0.5 -t 0.3脚本没有把多 GPU rank 数写死为 2;可用 --ranks N --gpus <逗号分隔的至少N张卡> 采集硬件可承载的任意 N(N ≥ 2)。single-GPU 方法必须使用 --ranks 1,并只会使用可见列表中的第一张卡。
脚本默认采集 cuda,nvtx,osrt,禁用 CPU IP sampling,并采集 process-tree context switches,以便观察 compute/check stream、worker thread 和尾部 drain。产物为:
<output>.nsys-rep Nsight Systems 原始报告
<output>.log nsys 与被测程序 stdout/stderr
<output>.command.txt nsys 版本、GPU 映射和可重放命令
<output>.trace/ 程序自身逐 rank CSV/JSON
--force 只允许覆盖同名 nsys report、日志与命令元数据;脚本仍拒绝复用已有 .trace/ 目录,避免较少 rank 的新采集与旧 rank 文件混在一起。正式归档优先使用新的 --output。
先用 --dry-run 可完成参数、数据集、二进制与 GPU 数量检查并打印最终命令,而不占用 GPU。
CUDA timeline 已能看到 NCCL 触发的 device kernel,但若要记录 NCCL/MPI API 语义,显式增加通信域:
CUDA_VISIBLE_DEVICES=6,7 \
bash scripts/experiments/run_nsys_profile.sh \
--algorithm pagerank \
--method multi_gpu_fault_tolerant \
--dataset cit-HepPh \
--ranks 2 \
--trace-communication \
--mpi-impl openmpi \
--nccl-trace api,group,gpu \
--output outputs/nsys/pagerank_ft_with_comm可用 trace domain 与参数取决于安装的 Nsight Systems 版本。脚本会从本机 nsys profile --help 检查 mpi 和 nccl domain;缺失时明确失败,不会悄悄退化。--mpi-impl 支持 Nsight Systems 暴露的 openmpi 或 mpich(MPICH 衍生实现也选 mpich);auto 依赖动态链接路径自动识别,识别错误时应显式指定。MPI/NCCL 库必须是该 Nsight Systems 版本支持且能被注入/跟踪的动态库,最终应在 GUI 或 nsys stats 中确认确实出现 MPI/NCCL events。
该入口面向单节点、rank-per-GPU 的代表性采集,并由一个 nsys session 跟踪 mpirun process tree。某些集群 launcher 会把 rank 放到远端节点或脱离当前 process tree,此时需要按集群的 Nsight Systems/MPI 集成方式在每个节点或 rank 启动采集,不能假设这一个本地 report 覆盖远端进程。
Nsight Systems 用来确认:
- check stream 的摘要 D2H 是否与后续 compute stream kernel 重叠;
- CPU worker validation 是否在 GPU 主循环继续执行时完成;
- 最后仍有多少 checker drain;
- NCCL/MPI 调用、rank 等待与 kernel 的时间顺序;
- NVTX 标出的迭代和阶段边界是否与程序 trace 一致。
.nsys-rep 中观察到的应用总时长包含 profiler 注入、trace buffer 和报告生成影响,绝不能作为 algorithm_e2e_ms 或 performance benchmark。论文中的绝对数来自未被 nsys 包裹的 *_performance 实验;nsys 只提供少量代表性样本的重叠与因果时间线证据。
所有差值必须在相同 dataset、算法参数、repeat、适用的 GPU 配置、instrumentation profile 和 attempt_session_id 内配对,并保留原始运行记录。迭代数差异只作为诊断;最终结果 hash 相等才是正确性准入条件:
multi_gpu_fault_tolerant对multi_gpu_baseline:报告algorithm_e2e_ms与algorithm_total_ms的容错增量;用 score-and-mark、容错主 kernel、D2H/CPU/drain/postcheck 解释来源。NCCL 和终止 MPI 不是容错独有,只报告两方法间的变化。multi_gpu_baseline对single_gpu_baseline:报告完整/稳态加速比和 rank-max 完成延迟;NCCL ghost exchange 与 MPI termination 是分布式代价。单/多 GPU 迭代数允许不同,最终结果验证才是准入条件。single_gpu_fault_tolerant对single_gpu_baseline:隔离单机容错本身的总时间、主 kernel 增量、检查摘要与 CPU drain。
profiling 数据用于解释上述趋势,但 performance 与 profiling 不能组成同一个开销 pair,也不能画在同一条绝对时间柱上。
实验 runner 和该 nsys 入口默认设置 CUDA_MODULE_LOADING=EAGER。否则 CUDA 的 lazy function loading 可能发生在一对 CUDA event 已经入队、目标 kernel 尚未提交的间隙,导致首个 kernel 被虚增几十毫秒;一次性的模块加载应进入 setup/process 口径,而不是冒充 kernel 执行时间。