<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>工程踩坑 on Echo的技术博客</title><link>https://cybersecurityerial.github.io/echo_blog/series/%E5%B7%A5%E7%A8%8B%E8%B8%A9%E5%9D%91/</link><description>Recent content in 工程踩坑 on Echo的技术博客</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Wed, 24 Jun 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://cybersecurityerial.github.io/echo_blog/series/%E5%B7%A5%E7%A8%8B%E8%B8%A9%E5%9D%91/index.xml" rel="self" type="application/rss+xml"/><item><title>工程踩坑：用 Ray 分布式启动 NCCL 的巨额冷启动开销问题</title><link>https://cybersecurityerial.github.io/echo_blog/posts/ray-distributed-nccl-cold-start-overhead/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0800</pubDate><guid>https://cybersecurityerial.github.io/echo_blog/posts/ray-distributed-nccl-cold-start-overhead/</guid><description>&lt;p&gt;单机八卡，Ray启动训练任务发现启动很慢，具体慢在torchdist的barrier()，然后做了几组对比实验。分别是经典torch.run，torch.run(virtual_visible)，Ray。&lt;/p&gt;
&lt;p&gt;发现torchrun是正常速度，但是后两者firstbarrier有几秒的时间。&lt;/p&gt;
&lt;p&gt;Ray为了资源隔离，设计的进程模型是只能看到一张卡的形态。认为是这个设定导致了overhead。&lt;/p&gt;
&lt;p&gt;torchdist的barrier由于在整个训练流程的入口，也会承担建立通信域（NCCL视角是Comm，Torch视角是processGroup）的功能，直观来看就有可能是初始化通信域太满了，这是一个猜想。&lt;/p&gt;
&lt;p&gt;深入到torchdist的代码发现barrier做的事情是dist.all_reduce()，这个里面又做了initProcessGroupNCCL-initNCCLComm-initTransport-enqueueNCCLKernel-AsyncWaitKernelQueue
其中从initNCCLComm开始都是NCCL的内部代码。&lt;/p&gt;
&lt;p&gt;但是这个还是不好拆，因为dist.all_reduce是一个AsyncCallback，所以得进一步再往下拆，是丢任务的过程慢了还是任务本身执行慢了。所以这样测：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;work &lt;span style="color:#f92672"&gt;=&lt;/span&gt; dist&lt;span style="color:#f92672"&gt;.&lt;/span&gt;all_reduce(tensor, group&lt;span style="color:#f92672"&gt;=&lt;/span&gt;group, async_op&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;work&lt;span style="color:#f92672"&gt;.&lt;/span&gt;wait()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;返回work之前的时间是丢任务这个过程链路的时间（也就是enqueue以及之前），wait的时间是collkernel执行时间。最后发现是返回work之前很慢。&lt;/p&gt;
&lt;p&gt;所以深入NCCL建立Comm的过程，问题出在cudaDeviceGetPCIBusId这个函数。&lt;/p&gt;
&lt;p&gt;首先需要了解本文语境中的设备可见性问题。这里的设备可见性分为几层：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CUDA runtime可见性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个是进程级别的，由CUDA_VISIBLE_DEVICES这个环境变量决定，同时也决定了runtime接口能不能看到足够多的GPU。&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;NCCL peer可见性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个是逻辑级别的，代表的是当前rank需要通信的另一个GPU，不考虑物理上是否可达，只描述NCCL要和谁进行通信。&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;NCCL Topology / NVML可见性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个是设备物理连接级别的，需要收集busId，nvmlDev信息。&lt;/p&gt;
&lt;p&gt;现在NCCL建立Comm的时候用的是runtime接口，而如果我们设置了GPU资源的隔离，runtime这一层就真的感知不到busId，而不会选择去找物理拓扑。NCCL对于peer逻辑上可见但找不到物理busId的情况，设置了一种分支状态叫做invisible P2P，在cuda 10.1之后支持。Ray就是触发了这条比较少触发的路径。&lt;/p&gt;
&lt;p&gt;那么为什么触发了这个路径会变慢呢？因为为了p2p又不能基于runtime接口直接hack到busid，就要做一些跨进程共享机制，也就是NCCL语境下的p2p。&lt;/p&gt;
&lt;p&gt;看p2p.cc可以发现跨进程通信有两类，一类是cuMem，一类是IpcHandle。默认走的是cuMem。二者区别在于cuMem是一种eager模式，而IpcHandle是一种lazy模式，所以cuMem一开始慢，但是后面会更快。解决办法就是关掉cuMem。因为目前场景没有看到显著的IpcHandle拖累的现状，后续有需求再改。&lt;/p&gt;</description></item><item><title>环境踩坑：wsl+proxy+codex cli</title><link>https://cybersecurityerial.github.io/echo_blog/posts/environment-wsl-proxy-codex-cli/</link><pubDate>Sun, 10 May 2026 10:48:50 +0800</pubDate><guid>https://cybersecurityerial.github.io/echo_blog/posts/environment-wsl-proxy-codex-cli/</guid><description>&lt;p&gt;挂代理，然后在wsl里面使用codex/cc cli是很常见的操作。这个过程中代理的一些问题时有发生。特意记录在此学习总结。&lt;/p&gt;
&lt;p&gt;发现问题：
codex报错
Falling back from WebSockets to HTTPS transport.
stream disconnected before completion: Host is unreachable (os error 113)&lt;/p&gt;
&lt;p&gt;error sending request for url:
&lt;a href="https://chatgpt.com/backend-api/codex/responses"&gt;https://chatgpt.com/backend-api/codex/responses&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;开发环境的网络有很多层，agent的网络访问是在sandbox里面还是外面，agent要用wsl的网络配置，wsl又是通过虚拟网卡经过windows主机中转，windows那边还配置了代理。所以只能一层层去查。既可以从外到内，也可以从内到外。但是外部问题比较好定位，只需要在windows打开chrome看能不能连上，就能排除代理问题。所以笔者通常从agent这边开始查，因为外部一般是好的。&lt;/p&gt;
&lt;p&gt;先讲一下sandbox的问题。cli内部一般存在两个进程。一个是主进程，另一个是在需要执行测试等命令时创建的子进程。而为了保证测试的安全性，子进程会被放在一个隔离环境sandbox里面。抛开抽象的“容器”概念，sandbox从技术上可以理解为是在执行任意操作之前都要加上一系列限制，显然也包括网络。sandbox和主进程的网络是两个独立的上下文。所以在sandbox里面有可能访问不到远程的api，是很正常的。&lt;/p&gt;
&lt;p&gt;接下来谈谈cli agent和wsl之间是如何进行网络交互的，或者说cli是如何使用wsl的网络。从最基本的os知识出发，cli是一个普通的wsl操作系统进程，所以使用的是wsl的网络协议栈。&lt;/p&gt;
&lt;p&gt;wsl和windows之间的交互则是通过虚拟化实现的。正常来说，系统访问外部网络是通过网卡，网卡把数据转发到网关，也就是出外网之前必经的下一跳设备。在wsl里面，网卡和网关都是虚拟的。虚拟网关也是一个ip，记作wsl-gateway。wsl这侧看到的wsl-gateway就是他认为真实的网关。而在windows侧wsl-gateway则是一个被特殊标记的ip。在默认设置下windows会根据NAT规则把wsl-gateway转发到windows的真实网卡。当然这里的转发规则不止NAT，大同小异。所以这一部分主要需要检查一个事情：wsl能否解析自己的wsl-gateway对应的MAC地址。&lt;/p&gt;
&lt;p&gt;再讲讲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。&lt;/p&gt;</description></item></channel></rss>