工程踩坑:用 Ray 分布式启动 NCCL 的巨额冷启动开销问题

单机八卡,Ray启动训练任务发现启动很慢,具体慢在torchdist的barrier(),然后做了几组对比实验。分别是经典torch.run,torch.run(virtual_visible),Ray。 发现torchrun是正常速度,但是后两者firstbarrier有几秒的时间。 Ray为了资源隔离,设计的进程模型是只能看到一张卡的形态。认为是这个设定导致了overhead。 torchdist的barrier由于在整个训练流程的入口,也会承担建立通信域(NCCL视角是Comm,Torch视角是processGroup)的功能,直观来看就有可能是初始化通信域太满了,这是一个猜想。 深入到torchdist的代码发现barrier做的事情是dist.all_reduce(),这个里面又做了initProcessGroupNCCL-initNCCLComm-initTransport-enqueueNCCLKernel-AsyncWaitKernelQueue 其中从initNCCLComm开始都是NCCL的内部代码。 但是这个还是不好拆,因为dist.all_reduce是一个AsyncCallback,所以得进一步再往下拆,是丢任务的过程慢了还是任务本身执行慢了。所以这样测: work = dist.all_reduce(tensor, group=group, async_op=True) work.wait() 返回work之前的时间是丢任务这个过程链路的时间(也就是enqueue以及之前),wait的时间是collkernel执行时间。最后发现是返回work之前很慢。 所以深入NCCL建立Comm的过程,问题出在cudaDeviceGetPCIBusId这个函数。 首先需要了解本文语境中的设备可见性问题。这里的设备可见性分为几层: CUDA runtime可见性 这个是进程级别的,由CUDA_VISIBLE_DEVICES这个环境变量决定,同时也决定了runtime接口能不能看到足够多的GPU。 NCCL peer可见性 这个是逻辑级别的,代表的是当前rank需要通信的另一个GPU,不考虑物理上是否可达,只描述NCCL要和谁进行通信。 NCCL Topology / NVML可见性 这个是设备物理连接级别的,需要收集busId,nvmlDev信息。 现在NCCL建立Comm的时候用的是runtime接口,而如果我们设置了GPU资源的隔离,runtime这一层就真的感知不到busId,而不会选择去找物理拓扑。NCCL对于peer逻辑上可见但找不到物理busId的情况,设置了一种分支状态叫做invisible P2P,在cuda 10.1之后支持。Ray就是触发了这条比较少触发的路径。 那么为什么触发了这个路径会变慢呢?因为为了p2p又不能基于runtime接口直接hack到busid,就要做一些跨进程共享机制,也就是NCCL语境下的p2p。 看p2p.cc可以发现跨进程通信有两类,一类是cuMem,一类是IpcHandle。默认走的是cuMem。二者区别在于cuMem是一种eager模式,而IpcHandle是一种lazy模式,所以cuMem一开始慢,但是后面会更快。解决办法就是关掉cuMem。因为目前场景没有看到显著的IpcHandle拖累的现状,后续有需求再改。

June 24, 2026 · 1 min

环境踩坑:wsl+proxy+codex cli

挂代理,然后在wsl里面使用codex/cc cli是很常见的操作。这个过程中代理的一些问题时有发生。特意记录在此学习总结。 发现问题: codex报错 Falling back from WebSockets to HTTPS transport. stream disconnected before completion: Host is unreachable (os error 113) error sending request for url: https://chatgpt.com/backend-api/codex/responses 开发环境的网络有很多层,agent的网络访问是在sandbox里面还是外面,agent要用wsl的网络配置,wsl又是通过虚拟网卡经过windows主机中转,windows那边还配置了代理。所以只能一层层去查。既可以从外到内,也可以从内到外。但是外部问题比较好定位,只需要在windows打开chrome看能不能连上,就能排除代理问题。所以笔者通常从agent这边开始查,因为外部一般是好的。 先讲一下sandbox的问题。cli内部一般存在两个进程。一个是主进程,另一个是在需要执行测试等命令时创建的子进程。而为了保证测试的安全性,子进程会被放在一个隔离环境sandbox里面。抛开抽象的“容器”概念,sandbox从技术上可以理解为是在执行任意操作之前都要加上一系列限制,显然也包括网络。sandbox和主进程的网络是两个独立的上下文。所以在sandbox里面有可能访问不到远程的api,是很正常的。 接下来谈谈cli agent和wsl之间是如何进行网络交互的,或者说cli是如何使用wsl的网络。从最基本的os知识出发,cli是一个普通的wsl操作系统进程,所以使用的是wsl的网络协议栈。 wsl和windows之间的交互则是通过虚拟化实现的。正常来说,系统访问外部网络是通过网卡,网卡把数据转发到网关,也就是出外网之前必经的下一跳设备。在wsl里面,网卡和网关都是虚拟的。虚拟网关也是一个ip,记作wsl-gateway。wsl这侧看到的wsl-gateway就是他认为真实的网关。而在windows侧wsl-gateway则是一个被特殊标记的ip。在默认设置下windows会根据NAT规则把wsl-gateway转发到windows的真实网卡。当然这里的转发规则不止NAT,大同小异。所以这一部分主要需要检查一个事情:wsl能否解析自己的wsl-gateway对应的MAC地址。 再讲讲windows的代理机制。这里一般用的是clash,clash会在本地按不同协议开几个端口,其他本地windows进程会把自己的消息先转发给这些端口,clash把这些请求转发出去。比如说clash在HTTPS上监听127.0.0.1:7890,那么windows系统代理会被设置成127.0.0.1:7890。需要注意的是wsl不能直接访问127.0.0.1:7890因为在wsl和windows的网络上下文不同,这个ip:port代表的不是同一个local host。

May 10, 2026 · 1 min