nettools · GPU 互联网络质量探测工具(夔牛)
Usage Guide — 面向 AI 训练集群 GPU 网卡互联监控,单进程同时承担 client + server 角色。
← 返回首页Client Server Both kuiniu(夔牛)是面向 AI 训练集群 GPU 互联网络的探测工具。它专门绑定 GPU NIC 进行 RoCEv2/UDP 探测,回程严格走 GPU 网卡,确保探测路径与真实训练流量对称,能够发现 GPU 互联链路上的丢包、延迟抖动以及网卡 bitflip。
与 baize、bitflip 不同,kuiniu 把"GPU 对"作为一等公民:通过 local_gpu_addrs 与 remote_gpu_addrs 平行数组定义本端 GPU 与远端 GPU 的对应关系,在同一进程中并发探测多对 GPU 的网络质量。在 role=both 模式下,单台机器既发包也回包,两台机器互相对称部署即可完成全双向监控。
核心特性:
local_gpu_addrs / remote_gpu_addrs 平行数组,按下标一一对应,自然支持多对 GPU 并行监控。localGPUSet,避免 server 错把自己的发包当成对端探测包回环处理。log.SetOutput(io.MultiWriter(os.Stderr, logWriter)) 同时打到终端和文件。kuiniu 主要面向 AI 训练集群中 GPU 网卡互联的健康度监控:
三个工具底层共享 sonar 探测引擎,但面向的网络层不同:
| 特性 | bitflip | baize | kuiniu |
|---|---|---|---|
| 面向网络 | 通用 IP 网络 | 通用 IP 网络 | GPU NIC 互联(RoCE) |
| 配置方式 | 命令行参数 | JSON 配置文件 | JSON 配置文件 |
| 地址模型 | client_addr + server_addrs | client_addr + server_addrs | local_gpu_addrs / remote_gpu_addrs(平行数组) |
| 多目标 | 多 server IP | 多 server IP | 多对 GPU(按下标对应) |
| 进程角色 | 单一角色 | 单进程 client + server | role=both 一等公民 + 自回声防护 |
| 典型部署 | 临时排查 | baize.json 长期监控 | 两台机器对称 kuiniu-machine-X.json |
| 日志 | 标准输出 | util.RotateWriter(文件) | util.RotateWriter(终端 + 文件) |
| 退出策略 | 到达 count/duration 即退出 | 到达限制后停止发送但进程不退 | 同 baize,长跑友好 |
# 克隆仓库
git clone https://github.com/baidu/nettools.git
cd nettools
# 编译全部工具
make compile
# 或单独编译 kuiniu
go build -o kuiniu ./cmd/kuiniu/
# 本地测试构建 (snapshot)
make snapshot
# 正式发布 (需要 git tag + GITHUB_TOKEN)
git tag v1.0.0
make deploy
| 操作系统 | 架构 | 支持 |
|---|---|---|
| Linux | AMD64 | ✓ 生产可用 |
| Linux | ARM64 | ✓ 生产可用 |
| macOS | AMD64 / ARM64 | 仅开发/编译 |
kuiniu 在两台机器上对称部署,把彼此的 GPU IP 互相填入对方的 remote_gpu_addrs。仓库 cmd/kuiniu/ 下提供了示例:
# 机器 A:把自己的 GPU IP 填到 local_gpu_addrs,机器 B 的 GPU IP 填到 remote_gpu_addrs
cp cmd/kuiniu/kuiniu-machine-a.json /etc/kuiniu/kuiniu.json
# 机器 B:与机器 A 镜像相反
cp cmd/kuiniu/kuiniu-machine-b.json /etc/kuiniu/kuiniu.json
# 使用配置文件启动(推荐)
sudo ./kuiniu -c /etc/kuiniu/kuiniu.json
# 也可以全部用命令行参数启动(不依赖配置文件)
sudo ./kuiniu --role both \
--local-gpu 33.0.1.25,33.0.1.26 \
--remote-gpu 33.0.2.27,33.0.2.28 \
--log-dir /var/log/kuiniu
sudo 权限来创建 raw IP socket 和设置 IP TOS/DSCP。建议通过 systemd 以 root 身份长期运行。
{
"role": "both",
"local_gpu_addrs": ["33.0.1.25"],
"remote_gpu_addrs": ["33.0.2.27"]
}
命令行参数会覆盖配置文件中的同名字段,便于在自动化系统中按需调整:
# 用配置文件做基础,按需覆盖部分字段
sudo ./kuiniu -c /etc/kuiniu/kuiniu.json \
--rate 10000 \
--verbose
kuiniu 使用 JSON 格式的配置文件,通过 -c / --config 参数指定路径。所有字段都可通过同名命令行参数覆盖。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
role | string | "" | 角色:client、server 或 both |
local_gpu_addrs | []string | [] | 本端 GPU IP 数组(IPv4),与 remote_gpu_addrs 等长 |
remote_gpu_addrs | []string | [] | 远端 GPU IP 数组(IPv4),按下标与本端一一对应 |
tos | int | 64 | IP TOS/DSCP 值 |
client_port_range | string | "43600,43699" | 客户端源端口范围 min,max |
server_port_range | string | "43600,43609" | 服务端目的端口范围 min,max |
rate_in_span | int64 | 5000 | 每个 span 内所有 GPU 对总发包速率(pps) |
span | string | "1s" | 统计时间窗口(Go duration 格式) |
delay | string | "3s" | 统计处理延迟,等待在途报文 |
msg_len | int | 1024 | 消息体大小(含 44 字节头部) |
count | int | 0 | 每对 GPU 最大发包数(0 = 不限制) |
send_duration | string | "0s" | 最大发送时长(0 = 不限制) |
verbose | bool | false | 丢包时打印每个端口的详细信息 |
pprof_addr | string | "" | pprof HTTP 监听地址(如 :6060) |
log_dir | string | "" | 日志目录,为空则只打到 stderr |
log_max_age_days | int | 7 | 日志保留天数(≤0 默认 7 天) |
{
"pprof_addr": ":6060",
"log_dir": "/var/log/kuiniu",
"log_max_age_days": 7,
"role": "both",
"local_gpu_addrs": [
"33.0.1.25", "33.0.1.26",
"33.0.1.153", "33.0.1.154"
],
"remote_gpu_addrs": [
"33.0.2.27", "33.0.2.28",
"33.0.2.155", "33.0.2.156"
],
"tos": 64,
"client_port_range": "43600,43699",
"server_port_range": "44600,44609",
"rate_in_span": 5000,
"span": "1s",
"delay": "3s",
"msg_len": 1024,
"verbose": false
}
rate_in_span 是所有 GPU 对总速率,会在所有对之间均摊。例如 4 对 GPU、rate_in_span=5000,则每对约 1250 pps。
kuiniu 用两个等长数组 local_gpu_addrs 和 remote_gpu_addrs 描述 GPU 探测对。下标 i 处的本端 IP 与远端 IP 组成一对探测目标:
"local_gpu_addrs": ["33.0.1.25", "33.0.1.26", "33.0.1.153", "33.0.1.154"],
"remote_gpu_addrs": ["33.0.2.27", "33.0.2.28", "33.0.2.155", "33.0.2.156"]
# 等价于以下 4 对探测:
# 33.0.1.25 <-> 33.0.2.27
# 33.0.1.26 <-> 33.0.2.28
# 33.0.1.153 <-> 33.0.2.155
# 33.0.1.154 <-> 33.0.2.156
两台机器互为对端时,local_gpu_addrs 与 remote_gpu_addrs 互换即可:
# 机器 A 的 kuiniu.json
{
"role": "both",
"local_gpu_addrs": ["33.0.1.25", "33.0.1.26"],
"remote_gpu_addrs": ["33.0.2.27", "33.0.2.28"]
}
# 机器 B 的 kuiniu.json — 完全镜像
{
"role": "both",
"local_gpu_addrs": ["33.0.2.27", "33.0.2.28"],
"remote_gpu_addrs": ["33.0.1.25", "33.0.1.26"]
}
cmd/kuiniu/kuiniu-machine-a.json 和 kuiniu-machine-b.json 是两台机器对称部署的真实配置示例,可直接修改 IP 后使用。
把同一个 GPU IP 在数组里重复填多次,可以让该对 GPU 占据更多速率配额,用于强化重点链路的探测:
"local_gpu_addrs": ["33.0.1.25", "33.0.1.25", "33.0.1.26"],
"remote_gpu_addrs": ["33.0.2.27", "33.0.2.27", "33.0.2.28"]
# 33.0.1.25 -> 33.0.2.27 占 2/3 速率,33.0.1.26 -> 33.0.2.28 占 1/3
role=both 是 kuiniu 的一等公民:在同一进程内并发跑 client 和 server 两个 goroutine,部署一份配置即可获得双向监控。
context.Context,捕获 SIGINT/SIGTERM 后两边一起优雅退出。log.Default() 写入同一个日志文件 kuiniu.log.YYYYMMDD。Run() 返回错误时,记录日志但不会终止整个进程,另一侧继续运行。在 role=both 模式下,本机 server 会同时收到本机 client 自己发出的探测包;如果不加防护,会把本机自身的发包当成对端的真实探测包做回包,导致自回环误判。kuiniu 通过 localGPUSet 解决:
// 启动时把所有 local_gpu_addrs 装入集合
localGPUSet := make(map[string]struct{})
for _, addr := range cfg.LocalGPUAddrs {
localGPUSet[addr] = struct{}{}
}
// server 收包时丢弃源 IP 命中本机集合的报文
if _, ok := localGPUSet[srcIP.String()]; ok {
// 本机 client 自己发的包,直接丢弃
continue
}
# 机器 A(8 张 GPU 卡,IP 33.0.1.25-32)
{
"role": "both",
"log_dir": "/var/log/kuiniu",
"local_gpu_addrs": [
"33.0.1.25", "33.0.1.26", "33.0.1.27", "33.0.1.28",
"33.0.1.29", "33.0.1.30", "33.0.1.31", "33.0.1.32"
],
"remote_gpu_addrs": [
"33.0.2.25", "33.0.2.26", "33.0.2.27", "33.0.2.28",
"33.0.2.29", "33.0.2.30", "33.0.2.31", "33.0.2.32"
],
"tos": 64,
"rate_in_span": 8000,
"span": "1s",
"delay": "3s"
}
kuiniu client started 和 kuiniu server started。如果只看到一个,说明另一侧初始化失败,需要查 [ERRO] 行的具体原因。
用于一端只发不收的场景,例如对端是已经独立运行的 kuiniu server:
{
"role": "client",
"local_gpu_addrs": ["33.0.1.25", "33.0.1.26"],
"remote_gpu_addrs": ["33.0.2.27", "33.0.2.28"],
"rate_in_span": 5000,
"span": "1s",
"delay": "3s"
}
{
"role": "server",
"local_gpu_addrs": ["33.0.2.27", "33.0.2.28"],
"remote_gpu_addrs": ["33.0.1.25", "33.0.1.26"],
"span": "1s",
"delay": "3s"
}
{
"pprof_addr": ":6060",
"log_dir": "/var/log/kuiniu",
"role": "both",
"local_gpu_addrs": ["33.0.1.25", "33.0.1.26"],
"remote_gpu_addrs": ["33.0.2.27", "33.0.2.28"],
"tos": 64,
"rate_in_span": 5000
}
{
"role": "both",
"local_gpu_addrs": ["33.0.1.25"],
"remote_gpu_addrs": ["33.0.2.27"],
"send_duration": "5m"
}
send_duration 或 count 后 client 停止发包,但进程不退出,server 继续工作。需要彻底退出请发送 SIGINT/SIGTERM。
{
"pprof_addr": ":6060",
"log_dir": "/var/log/kuiniu",
"role": "both",
"local_gpu_addrs": ["33.0.1.25"],
"remote_gpu_addrs": ["33.0.2.27"],
"verbose": true
}
# 查看 goroutine
go tool pprof http://localhost:6060/debug/pprof/goroutine
# 查看 CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# 查看堆内存
go tool pprof http://localhost:6060/debug/pprof/heap
当配置了 log_dir 时,kuiniu 通过通用工具 util.RotateWriter 写日志,自动按天轮转、维护 symlink、清理过期文件。kuiniu 同时把日志输出到 stderr 和文件(io.MultiWriter(os.Stderr, logWriter)),方便 systemd journal 与磁盘文件双重留痕。
/var/log/kuiniu/
├── kuiniu.log # symlink → 当天日志
├── kuiniu.log.20260601 # 按天轮转
├── kuiniu.log.20260602
├── kuiniu.log.20260603
└── kuiniu.log.20260604 # 当天
kuiniu.log 始终指向当天日志,便于 tail -f kuiniu.log 实时观察。log_max_age_days 的日志文件在轮转时自动删除。kuiniu.log.*,目录中其他无关文件不会被误删。{
"log_dir": "/var/log/kuiniu",
"log_max_age_days": 7
}
util.RotateWriter 提供,是 nettools 仓库中的通用工具,baize 也共用同一份实现。
log_dir 时,日志只输出到 stderr;适合容器化部署中由日志收集器统一采集。
看探测目标:通用业务网络选 baize(长期)或 bitflip(临时);AI 训练集群 GPU 互联选 kuiniu。kuiniu 把"GPU 对"作为一等公民并提供 role=both 自回声防护,更贴合 GPU NIC 的部署形态。
可以,但 baize 不理解"GPU 对"语义,需要自行展开为 N 份 client/server 配置;并且 baize 没有针对 role=both 的自回声防护,多对 GPU 同机部署容易把本机发包当对端探测包处理。kuiniu 在配置层与运行层都对 GPU 网络做了针对性优化。
不会。kuiniu 在启动时构造 localGPUSet,把所有 local_gpu_addrs 装入集合;server 收到源 IP 命中集合的报文会直接丢弃。详见第 8 节 role=both 互探模式。
是的。两者按下标一一对应组成 GPU 探测对。如果长度不一致,cfg.Validate() 会直接返回错误并阻止启动。
建议一致。两台机器的 client 端口范围一致,server 端口范围一致,可以让 ECMP 哈希行为对称、丢包定位时不容易看花眼。
是所有 GPU 对总速率。例如 4 对 GPU、rate_in_span=5000,则每对约 1250 pps。要想加大单对速率,可以减少 GPU 对数量或重复填同一对 IP。
需要。kuiniu 不支持热加载。建议用 systemd 管理:sudo systemctl restart kuiniu。
发送 SIGINT (Ctrl+C) 或 SIGTERM。程序会取消所有 goroutine 并睡眠 1 秒等在途报文回来后退出。
kuiniu 使用 raw IP socket 发送 RoCEv2/UDP 探测包,并需要设置 IP TOS/DSCP。Linux 上需要 CAP_NET_RAW;最简单的做法就是用 sudo 或 systemd 以 root 启动。
不会。kuiniu 在 client/server Run() 返回错误时只打印 [ERRO] 日志,不退出主进程。这避免了一侧瞬时故障导致整机失去监控能力。
不会。日志按天轮转,超过 log_max_age_days(默认 7 天)的文件在轮转时自动删除。