Skip to content

服务器适配指南 ​

该章节提供了新型服务器的适配步骤。一款新型服务器,由各种硬件进行了新组合,需进行多维度测试,评估各组件是否达到了正常的预期状态,如有异常,定位修复后,才能用于研发和交付。

一款新机型的交付,通常会在公司内进行服务器适配流程,以保证质量。但也存在场外适配和未适配,直接交付客户使用的情况,需按照本指南进行适配和问题排查。

服务器适配的目的是检查不同服务器中,主板、CPU、PCIE Switch、GPU、Metaxlink桥接板、网卡等组件的兼容性,及性能情况,通过合适的配置(硬件变更、软件完善等)使该服务器系统发挥出最优稳定性及性能。

获取硬件信息 ​

获取服务器的硬件topo,主要包括GPU与PCIe SWITCH、PCIe SWITCH与GPU的连接关系。相关信息可以通过服务器白皮书获得,或者联系服务器厂商获取,部分服务器信息也可以通过System部门拿到。

服务器的详细信息可以通过dmidecode命令查询,如下图。

获取硬件信息

cpu信息可以通过lscpu指令获取。重点关注CPU的架构、numa分割情况。

获取硬件信息

PCIe的信息可以通过lspci命令获取。需要注意的是某些PCIe SWITCH支持双上行口,每个上行口分别连到了不同的RC,此时从lspci看到GPU是挂在了不同的PCIe SWITCH下面,实际在GPU间转发数据的时候可能并不会经过RC。相关信息都需要找服务器厂商确认。

检查基础环境 ​

为了提升性能,沐曦的GPU推荐关闭PCIe的ACS功能,数据直接由PCIe SWITCH转发,不需要经过RC,各服务器的关闭方式都不相同,最好找厂家确认。对应的IOMMU也需要关闭,检查IOMMU是否关闭可以检查/sys/class/iommu目录下是否为空来确认,若为空,则IOMMU已关闭。为了测试极限性能,CPU需要设置在高性能状态,不同的操作系统也不相同。

检查基础环境

检查链路速度 ​

服务器上各GPU之间连接关系可以通过mx-smi topo -m获取,如下图可以很清楚的看到各个GPU之间的连接关系。

检查链路速度

一般情况下,GPU之间如果由metalibk,执行p2p copy的时候,默认使用metalink,没有metalink再使用PCIe。执行/opt/maca/samples/mccl_tests/benchmarks/p2p.sh即可获取到各GPU到GPU的p2p copy的性能数据。通过p2p copy性能数据,可以检查各链路的连接状态是否正常。如metalink单条lane的速度通常是PCIe gen5.0,16条lane测试出来的性能数据在48左右较为正常,8条lane测试出来在24左右为正常。

检查链路速度

如果测试出来的性能数据不符合预期,可以通过mx-smi mxlk --show检查metalink是否发生了降速。正常情况下每条lane的speed都是32GT/s,如果不是,则表明发生了降速。

检查链路速度

除了检查metalink的连接状态外,还需要检查PCIe SWITCH的速度。为了避免metalink的影响,可以暂时将metalink禁用掉。执行命令rmmod metax先卸载ko,然后执行modprobe metax disable_metalink=1即可禁用metalink,此时再执行p2p.sh即可获取GPU通过PCIe SWITCH copy的性能数据。如果数据有异常,需要通过lspci检查SWITCH是否发生了降速。如下,执行lspci -vvv -s XX:XX.0之后发现速度不为32GT/s或者16GT/s,说明PCIe SWITCH发生了降速。需要排查原因。

检查链路速度

测试perf ​

正式开始抓取数据之前,可以先跑一下所有GPU的alltoall_perf,因为alltoall会涉及所有的通信链路,容易把一些隐藏问题给暴露出来。执行mx-smi --show-pcie-bandwidth命令可以监控各GPU通过PCIe发送或者接收的带宽数据,同样的,执行mx-smi --show-metaxlink-bandwidth可以监控各条metalink链路上的带宽。如下图,在某服务器上测试大size的alltoall_perf时,PCIe带宽奇差,说明有额外问题没有发现。

测试perf

此时,可以通过其他perf来辅助定位问题。设置MACA_VISIBLE_DEVICES可以选择服务器上特定GPU来做测试。也可通过单向传输的perf,如scatter_perf、gather_perf来缩小问题范围。最常遇到的问题是GPU挂在多个RC下面,从一个RC向多个RC打流有保序要求,此时就会降低性能,如上图遇到的就是这种情况。这种情况,目前的解决办法都是通过先将数据发送到DDR中,再有DDR转发到目的GPU来规避的。

不同的服务器由于硬件限制、或者生产要求关闭了某些链路导致能发挥的最大性能不同,此时可以借助环境变量来测试特定服务器适合的配置,如设置MCCL_PCIE_BUFFER_MODE=0来关闭PCIe链路,设置MCCL_PCIE_NCHANNELS来控制使用metalink传输的数据和使用PCIe传输的数据的比例,设置MCCL_P2P_LEVEL验证跨RC问题,手动设置MCCL_RINGS来获取发挥最大性能的ring等。

获取到了服务器能发挥最大性能的配置之后,需要在代码里面适配,通常的办法是在代码里面根据不同的配置写死。

获取完整性能数据 ​

服务器适配完成后,就可以用下面的脚本获取所有perf的数据。配置信息,如IP、子网掩码、网卡名称等根据具体情况加以修改。将数据打印信息重定向到test.txt中。

#!/bin/bash

MACA_PATH=/opt/maca

GPU_NUMS="4 8 16"
HOST_IP=192.3.44.12:8,192.3.44.13:8
IP_MASK=192.3.0.0/16
IB_PORT=mlx5_0,mlx5_1,mlx5_2,mlx5_3
IB_GID_INDEX=3

BENCH_PATH=/opt/maca/samples/mccl_tests/perf/mccl_perf
BENCH_NAMES="all_gather_perf  all_reduce_perf  alltoall_perf  broadcast_perf  gather_perf  reduce_perf  reduce_scatter_perf  scatter_perf  sendrecv_perf"

PERF_ENV="-x MCCL_LIMIT_RING_LL_THREADTHRESHOLDS=1 -x MCCL_IB_GID_INDEX=${IB_GID_INDEX}"
LIB_PATH_ENV="-x LD_LIBRARY_PATH=${MACA_PATH}/lib:/${MACA_PATH}/ompi/lib"
OPENMPI_ENV="--allow-run-as-root -mca pml ^ucx -mca btl ^openib -mca osc ^ucx -mca btl_tcp_if_include ${IP_MASK}"
ENV_VAR="${OPENMPI_ENV} -x MCCL_IB_HCA=${IB_PORT} -x MX_TRACER_ENABLED_MCPTI=ON -x MCCL_NET_GDR_LEVEL=SYS ${PERF_ENV} ${LIB_PATH_ENV}"

for BENCH_NAME in ${BENCH_NAMES}
do
for GPU_NUM in ${GPU_NUMS}
do
echo "---- mccl_test ${BENCH_NAME} ${GPU_NUM} ----"
${MACA_PATH}/ompi/bin/mpirun -n ${GPU_NUM} ${ENV_VAR} -host ${HOST_IP} ${BENCH_PATH}/${BENCH_NAME} -b 1K -e 2g -d bfloat16 -f 2 -n 100
done
done

抓到完整数据后,执行下面的脚本,可以将数据整理到xlsx中。执行命令为python3 perf_to_xlsx.py --log-file test.txt

import os
import argparse
from openpyxl import Workbook
from openpyxl.styles import Font
from openpyxl.styles import Border, Side
from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter
# 脚本1逻辑:用于生成“环境信息”页
def convertFileSize(size):
units = '', 'K', 'M', 'G', 'T'
unit = units[0]
for i in range(1, len(units)):
if size >= 1024:
size /= 1024
unit = units[i]
else:
break
return '{:.0f}{}'.format(size, unit)

def convert_row_types(row):
new_row = []
for item in row:
item = item.strip()
if item == "":
new_row.append("")
elif is_number(item):
if '.' in item:
new_row.append(float(item))
else:
new_row.append(int(item))
else:
new_row.append(item)
return new_row

def parse_env_info(log_file):
with open(log_file, 'r') as fs:
lines = fs.readlines()

useful_lines = []
head_line = "mccl_test,GPU,Bytes"
head_ok = 0
perf_line = ""
append_line = 0
for j in range(len(lines)):
line = lines[j].rstrip().replace("#", "")
split_list = line.split()
if "----" in line and "mccl_test" in line:
perf_line = split_list[2] + "," + split_list[3] + "," + "GB/s"
if "time" in line and "algbw" in line and "busbw" in line:
append_line = j + 2
if "Out of bounds values" in line:
if head_ok == 0:
useful_lines.append(head_line)
useful_lines.append(perf_line)
head_ok = 1
append_line = 0
if append_line != 0 and j >= append_line:
if any(keyword in perf_line for keyword in ["all_reduce_perf", "sendrecv_perf", "reduce_scatter_perf", "reduce_perf"]):
perf_line += "," + split_list[8]
else:
perf_line += "," + split_list[7]
if head_ok == 0:
head_line += "," + convertFileSize(int(split_list[0]))
return [line.split(",") for line in useful_lines]

# 脚本2逻辑:用于生成“测试数据”页
def parse_perf_information(log_file):
with open(log_file, 'r') as fs:
lines = fs.readlines()

useful_lines = []
append_line = 0
for j in range(len(lines)):
line = lines[j].rstrip()
if "----" in line and "mccl_test" in line:
useful_lines.append(line)
if "time" in line and "algbw" in line and "busbw" in line:
append_line = j + 2
if "Out of bounds values" in line:
useful_lines.append("")
append_line = 0
if append_line != 0 and j >= append_line:
useful_lines.append(line.replace("#", ""))
return useful_lines

def parse_nccl_performance(useful_lines):
perf_lines = []
header = "size,count,type,redop,root,kernel-avg-time(us),time-oplace(us),algbw(gb/s)-oplace,busbw(gb/s)-oplace,error," + \
"time-iplace(us),algbw(gb/s)-iplace,busbw(gb/s)-iplace,error"
info_line = ""
for line in useful_lines:
line = line.replace("# Avg bus bandwidth    : ", "")
perf_line = ""
split_list = line.split()
if "----" in line and "mccl_test" in line:
info_line = split_list[1] + "," + split_list[2] + "," + split_list[3]
perf_lines.append(convert_row_types(info_line.split(",")))
perf_lines.append(header.split(","))
else:
for i in range(len(split_list)):
if i == 3 and all(keyword not in info_line for keyword in ["all_reduce_perf", "sendrecv_perf", "reduce_scatter_perf", "reduce_perf"]):
perf_line += ","
perf_line += ("," if perf_line else "") + split_list[i]
perf_lines.append(convert_row_types(perf_line.split(",")))
return perf_lines

def is_number(s):
try:
float(s)  # 尝试转成 float
return True
except ValueError:
return False

# 生成输出 Excel 文件
def write_to_excel(log_file, out_file, env_info_lines, perf_lines):
wb = Workbook()

# Sheet 1: 环境信息
ws1 = wb.active
ws1.title = "环境信息"

env_item = ["服务器IP", "服务器型号", "服务器bios版本", "性能模式", "CPU型号", "PCIe Gen", "PCIe MaxPayload",
"PCIe ACS", "网卡型号", "网卡数量", "网卡带宽", "网络协议(IB/RoCE)", "GPU型号", "GPU拓扑", "OS",
"OS kernel", "SW Version", "vbios", "通信库测试配置"]
max_len = max(len(env_item), len(env_info_lines) + 4)
for i in range(max_len):
temp_len = []
if i < 3:
temp_len = [env_item[i]]
elif i == 3:
temp_len = [env_item[i]] + [""] * (len(env_info_lines[0]) + 4) + ["对比结果"]
elif i >= 4 and i < len(env_info_lines) + 4 and i < len(env_item):
temp_len = [env_item[i], "", "", "", ""] + env_info_lines[i - 4]
elif i >= len(env_item) and i < len(env_info_lines) + 4:
temp_len = ["", "", "", "", ""] + env_info_lines[i - 4]
elif i < len(env_item) and i >= len(env_info_lines) + 4:
temp_len = [env_item[i]]
ws1.append(temp_len)

# 设置列宽度
ws1.column_dimensions["A"].width = 20
ws1.column_dimensions["B"].width = 53
ws1.column_dimensions["F"].width = 20

# 设置第一列字体
first_col_font = Font(name='Arial')
for cell in ws1["A"]:
cell.font = first_col_font

# 设置全边框
thin_border = Border(
left=Side(style="thin", color="000000"),
right=Side(style="thin", color="000000"),
top=Side(style="thin", color="000000"),
bottom=Side(style="thin", color="000000"),
)
for row in range(1, 20):
for col in ["A", "B"]:
ws1[f"{col}{row}"].border = thin_border

# 设置右对齐与数字格式
max_column = ws1.max_column
for row in ws1.iter_rows():
for cell in row:
if isinstance(cell.value, float):
cell.number_format = '0.00'
cell.alignment = Alignment(horizontal="right")
elif isinstance(cell.value, int):
cell.alignment = Alignment(horizontal="right")
elif isinstance(cell.value, str):
if is_number(cell.value):
cell.alignment = Alignment(horizontal="right")
else:
cell.alignment = Alignment(horizontal="left")
else:
cell.alignment = Alignment(horizontal="left")

for cell in ws1[5]:
cell.alignment = Alignment(horizontal="left")

# Sheet 2: 测试数据
ws2 = wb.create_sheet("测试数据")
for row in perf_lines:
ws2.append(row)

for row in ws2.iter_rows():
for cell in row:
if isinstance(cell.value, float):
cell.number_format = '0.00'
cell.alignment = Alignment(horizontal="right")
elif isinstance(cell.value, int):
cell.alignment = Alignment(horizontal="right")
elif isinstance(cell.value, str):
if is_number(cell.value):
cell.alignment = Alignment(horizontal="right")
else:
cell.alignment = Alignment(horizontal="left")
else:
cell.alignment = Alignment(horizontal="left")

ws2.column_dimensions["A"].width = 15
ws2.column_dimensions["B"].width = 15
ws2.column_dimensions["C"].width = 10
ws2.column_dimensions["F"].width = 16
ws2.column_dimensions["G"].width = 20
ws2.column_dimensions["H"].width = 20
ws2.column_dimensions["J"].width = 16
ws2.column_dimensions["K"].width = 20
ws2.column_dimensions["L"].width = 20

wb.save(out_file)
print(f"✅ Saved to: {out_file}")

def main():
log_file = os.path.abspath(args.log_file)
out_file = os.path.abspath(args.out_file)

env_info_lines_raw = parse_env_info(log_file)
env_info_lines = [convert_row_types(row) for row in env_info_lines_raw]
useful_lines = parse_perf_information(log_file)
perf_lines = parse_nccl_performance(useful_lines)

write_to_excel(log_file, out_file, env_info_lines, perf_lines)

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--log-file", type=str, required=True, help="Log file generated while running mccl-tests")
parser.add_argument("--out-file", type=str, default="mccl_test.xlsx", help="Output file name, default:mccl_test.xlsx")
args = parser.parse_args()
main()

同时,还需要测试TP/DP 组合场景下perf数据,具体为:

TP2/TP4/TP8/TP16,可以使用NCCL_TESTS_SPLIT_MASK来模拟。

TP2要选择rank编号为0、2、4、6...等做all_reduce,此时可以加上环境变量NCCL_TESTS_SPLIT_MASK=0x1。TP4要选择rank编号为0、4、8、12...等做all_reduce,需要配置环境变量NCCL_TESTS_SPLIT_MASK=0x3。

类似的TP8配置NCCL_TESTS_SPLIT_MASK=0x7,TP16配置NCCL_TESTS_SPLIT_MASK=0xf。

测试报告 ​

将上一步得到的表格进行整理,最终形成报告,报告表格中应当包含5个工作表:环境信息,测试数据,DP数据,P2P数据,TOPO信息。

环境信息、测试数据这两项会由上节中python脚本自动生成,只需完善表格内容即可,需注意:环境信息工作表中“通信库测试配置” 一栏需填写测试或者对应服务器适配时使用到的环境变量,以便后续Team信息对齐。且该页还需包含性能数据对比(和同类型服务器的性能对比)。

MCCL的测试重点关注All Reduce/All Gather/Reduce Scatter/All To All/Send Recv这五个常用原语,其他原语的性能问题优先级略低。