<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/rss-style.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wyz's blog</title>
        <link>https://www.wangyizhi.cn/</link>
        <description>wangyizhi 的计算机技术博客，专注于人工智能、云原生与 AI Agent 开发，分享工程实践、技术原理、开发教程和前沿趋势。</description>
        <lastBuildDate>Thu, 16 Jul 2026 06:59:59 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro</generator>
        <language>zh</language>
        <copyright>Copyright © 2026 wangyizhi</copyright>
        <atom:link href="https://www.wangyizhi.cn/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[集合通信动画演示]]></title>
            <link>https://www.wangyizhi.cn/posts/collective-communication/</link>
            <guid isPermaLink="false">https://www.wangyizhi.cn/posts/collective-communication/</guid>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[通过可交互动画观察 Broadcast、Scatter、Gather、AllGather、ReduceScatter、AllReduce 与 All-to-All 的数据流动过程。]]></description>
            <content:encoded><![CDATA[<p>本文通过交互动画和 PyTorch 示例，直观展示常见集合通信操作中数据如何在 GPU 之间流动。</p>
<h2>交互演示</h2>
<p>选择通信原语和 GPU 数量后，即可连续播放或逐轮观察。</p>
<h2>PyTorch CPU 代码示例</h2>
<p>下面的示例使用 PyTorch Distributed 的 Gloo 后端，在单机启动 4 个进程；保存代码后运行 &lt;code&gt;python 文件名.py&lt;/code&gt; 即可。</p>
<p>&lt;details&gt;
&lt;summary&gt;Broadcast：broadcast_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python broadcast_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29500",
    )

    if rank == 0:
        tensor = torch.tensor([1.0, 2.0, 3.0])
    else:
        tensor = torch.zeros(3)

    print(f"[rank {rank}] before: {tensor.tolist()}")
    dist.broadcast(tensor, src=0)
    print(f"[rank {rank}] after : {tensor.tolist()}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;Scatter：scatter_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python scatter_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29501",
    )

    output_tensor = torch.zeros(2)

    if rank == 0:
        full_data = torch.arange(2 * world_size, dtype=torch.float32)
        scatter_list = list(full_data.chunk(world_size))
        print(f"[rank 0] full_data = {full_data.tolist()}")
    else:
        scatter_list = None

    dist.scatter(output_tensor, scatter_list=scatter_list, src=0)
    print(f"[rank {rank}] received: {output_tensor.tolist()}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;Gather：gather_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python gather_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29502",
    )

    my_chunk = torch.full((2,), fill_value=float(rank))
    print(f"[rank {rank}] my_chunk = {my_chunk.tolist()}")

    gather_list = [torch.zeros(2) for _ in range(world_size)] if rank == 0 else None

    dist.gather(my_chunk, gather_list=gather_list, dst=0)

    if rank == 0:
        print(f"[rank 0] gathered: {[t.tolist() for t in gather_list]}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;Reduce：reduce_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python reduce_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29503",
    )

    tensor = torch.full((4,), fill_value=float(rank + 1))
    print(f"[rank {rank}] before: {tensor.tolist()}")

    # 只保证 dst 拿到正确的规约结果
    dist.reduce(tensor, dst=0, op=dist.ReduceOp.SUM)

    # 非 dst 的 tensor 内容未定义
    print(f"[rank {rank}] after : {tensor.tolist()}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;AllGather：allgather_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python allgather_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29504",
    )

    my_chunk = torch.full((2,), fill_value=float(rank))
    print(f"[rank {rank}] my_chunk = {my_chunk.tolist()}")

    # 所有 rank 都要准备长度为 world_size 的接收缓冲区列表
    gather_list = [torch.zeros(2) for _ in range(world_size)]

    dist.all_gather(gather_list, my_chunk)

    print(f"[rank {rank}] gathered: {[t.tolist() for t in gather_list]}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;ReduceScatter：reducescatter_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python reducescatter_cpu_demo.py
#
# Gloo 不支持原生 dist.reduce_scatter()，这里用 world_size 次
# dist.reduce(dst=j) 组合出等价效果。


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29505",
    )

    # input_list[j] 是当前 rank 要贡献给 rank j 的输入
    input_list = [
        torch.full((2,), fill_value=float(rank * 10 + j))
        for j in range(world_size)
    ]
    print(f"[rank {rank}] input_list = {[t.tolist() for t in input_list]}")

    output = torch.zeros(2)
    for j in range(world_size):
        part = input_list[j].clone()
        dist.reduce(part, dst=j, op=dist.ReduceOp.SUM)
        if rank == j:
            output = part

    print(f"[rank {rank}] reduce_scatter result: {output.tolist()}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details open&gt;
&lt;summary&gt;AllReduce：allreduce_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python allreduce_cpu_demo.py


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29506",
    )

    tensor = torch.full((4,), fill_value=float(rank + 1))
    print(f"[rank {rank}] before: {tensor.tolist()}")

    # 原地操作，所有 rank 都会拿到相同的求和结果
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)

    print(f"[rank {rank}] after : {tensor.tolist()}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
<p>&lt;details&gt;
&lt;summary&gt;All-to-All：alltoall_cpu_demo.py&lt;/summary&gt;</p>
<pre><code># 运行方式: python alltoall_cpu_demo.py
#
# Gloo 不支持列表形式的 dist.all_to_all()，改用等价接口
# dist.all_to_all_single()。


def worker(rank, world_size):
    dist.init_process_group(
        backend="gloo", rank=rank, world_size=world_size,
        init_method="tcp://127.0.0.1:29507",
    )

    chunk_size = 2
    # 第 j 段是当前 rank 要发给 rank j 的个性化数据
    input_tensor = torch.cat([
        torch.full((chunk_size,), fill_value=float(rank * 10 + j))
        for j in range(world_size)
    ])
    print(f"[rank {rank}] input_tensor = {input_tensor.tolist()}")

    output_tensor = torch.zeros_like(input_tensor)
    dist.all_to_all_single(output_tensor, input_tensor)

    # output_tensor 第 j 段就是 rank j 发来的数据
    received = list(output_tensor.chunk(world_size))
    print(f"[rank {rank}] received: {[t.tolist() for t in received]}")

    dist.destroy_process_group()


if __name__ == "__main__":
    world_size = 4
    mp.spawn(worker, args=(world_size,), nprocs=world_size, join=True)
</code></pre>
<p>&lt;/details&gt;</p>
]]></content:encoded>
            <author>wangyizhi</author>
        </item>
    </channel>
</rss>