CHAPTER 47 / Cerebras · 晶圆级计算

多PE scan:消息里到底应该传什么

局部前缀和怎样变成保持原顺序的全局前缀和?

阅读与推演约 55 分钟练习时间另计

这一章要弄清楚

  • 区分reduction和inclusive/exclusive scan
  • 推导分区offset并防止重复计数
  • 定义通信消息、数量和完成条件

先备知识:Reduction、scan 与数值正确性 / WSE 与 PE:把数据放在拥有它的计算节点旁边 / CSL 与 host:装载、启动、回读是一份共同合同

C++20 本机逻辑模型;不是设备仿真或性能测量。厂商语法片段未在设备或 SDK 执行,实际 API 以匹配版本的公开文档为准。

Scan保留每个位置的累计信息

Reduction把整个序列变成一个总值;scan为每个位置保留一个累计值。对3、负1、4、2、2,inclusive scan得到3、2、6、8、10。Inclusive包含当前位置,exclusive不包含当前位置,因此exclusive结果从零开始,为0、3、2、6、8。写接口时必须选定一种,不能在host和设备两侧使用不同定义。

如果把输入分成三份,第一份为3、负1,第二份为4,第三份为2、2,局部inclusive结果分别是3、2;4;2、4。直接拼接并不是全局答案,因为后两份缺少此前分区的总量。每个分区需要一个offset:所有更早分区的总和。

两级scan的推导

三个分区总和为2、4、4。对分区总和做exclusive scan得到offset 0、2、6。把对应offset加到各局部前缀上,就得到3、2;6;8、10。例一完整执行这个原创CPU分区模型,并与单循环oracle比较。关键不变量是每个offset恰好覆盖当前位置以前的全部分区,不多也不少。

这个推导并不要求每份长度相同。空分区的局部输出为空,总和为零,仍需考虑其在通信协议中的角色。若某实现对空分区完全跳过发送,而下游仍等待它,会出现无法完成的等待。数学单位元与协议终止是不同问题,二者都必须定义。

链式通信最容易先讲清楚

在三节点链中,PE0知道自己的总和2,向PE1发送累计量2。PE1把收到的2作为本地offset,再加自己的4向PE2发送6。PE2用6修正本地结果。注意第二条消息是累计6,不只是本地4;否则第三份会得到6、8而不是8、10。

Cerebras公开路由教程介绍color、路由配置、fabric数据描述符和异步完成任务。它们提供表达通信的机制,但算法仍要决定消息含义和数量。Color不是普通C++线程编号,也不是对所有内存自动生效的同步锁。真实CSL实现还需匹配目标架构的任务、队列和路由规则;本章不提供未经编译验证的路由配方。

顺序是合同的一部分

例二故意把分区总和次序交换,展示scan的中间结果改变,即使最终总和仍相同。对于整数求和reduction,有些重排保持总值;scan却把每个输出与输入位置绑定。只检查最后一个元素会遗漏许多通信或排列错误。

调试时给每条消息写下发送者、接收者、序号、载荷含义和是否累计。使用小输入手推每一步,再扩大规模。若某节点卡住,检查它等待几条消息、是否真的存在发送者、之前的异步操作是否能完成,而不只检查公式。

算法模型与设备执行分开验收

本章C++程序顺序执行,验证分区offset和位置语义,没有模拟fabric、任务调度、缓冲容量或时钟。真实设备实现还需验证同步、路由、重入状态、容量和边界。公开教程的样例也应与所用SDK release匹配,不能从某次CPU结果推断CSL程序已正确。

面试时可以先画五项输入和三个分区,让对方看到为什么offset是0、2、6,再解释如何发送以及怎样验证。这样的推导能够迁移到GPU、多核或其他加速器,但每个平台的通信实现和性能结论必须分别建立。

观察 · 推演

三个分区的offset传播

P03 2P14P22 401 / 03 · NETWORKP03 2P14P22 401 / 03 · NETWORK
局部

每个分区先独立scan。

1 / 3
阅读完整推演文字
  1. 局部

    P0:3 2;P1:4;P2:2 4

    每个分区先独立scan。

  2. 传播

    P0→P1:2;P1→P2:6;offsets:0 2 6

    第二条消息已经包含P0。

  3. 修正

    P0:3 2;P1:6;P2:8 10

    把offset加到本地每项。

跟着例子,走完一遍

例题 01C++20 · 本机可运行

三分区inclusive scan

分区[3,-1]、[4]、[2,2]。

  1. 局部scan及总和2、4、4
  2. offset为0、2、6
  3. 修正局部结果并比oracle
43-a.cpp
下载
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <vector>
#include <stdexcept>

void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }

int main() {
 const std::vector<std::vector<int>> parts{{3,-1},{4},{2,2}};
 std::vector<int> out,flat;
 int offset=0;
 for(const auto& part:parts) {
  int local=0;
  for(int value:part){local+=value;out.push_back(offset+local);flat.push_back(value);}
  offset+=local;
 }
 std::vector<int> oracle;int sum=0;
 for(int value:flat){sum+=value;oracle.push_back(sum);}
 require(out==oracle && out==std::vector<int>({3,2,6,8,10}));
 std::cout<<"scan=";
 for(std::size_t i=0;i<out.size();++i) std::cout<<(i?" ":"")<<out[i];
 std::cout<<'\n';
}

如何编译和运行下载的 .cpp 文件 →

结果与解释

3 2 6 8 10。

offset是更早分区总和,不含本分区。

在本机运行这个例子

下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。

clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 43-a.cpp -o example && ./example

预期标准输出:

scan=3 2 6 8 10
例题 02C++20 · 本机可运行

总和相同不代表scan相同

比较[2,4,4]和[4,2,4]的分区inclusive累计。

  1. 分别计算前缀
  2. 比较最后一项
  3. 比较全部位置
43-b.cpp
下载
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <vector>
#include <stdexcept>

void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }

std::vector<int> prefix(const std::vector<int>& a) {
 std::vector<int> out;int sum=0;for(int x:a){sum+=x;out.push_back(sum);}return out;
}
int main() {
 const auto a=prefix({2,4,4}),b=prefix({4,2,4});
 require(a.back()==b.back() && a!=b);
 std::cout<<"same_total="<<(a.back()==b.back())<<" same_scan="<<(a==b)<<'\n';
}
结果与解释

总和都10,但前缀不同。

scan有位置语义;只看总和会假通过。

在本机运行这个例子

下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。

clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 43-b.cpp -o example && ./example

预期标准输出:

same_total=1 same_scan=0
常见错误

用局部总和代替全局offset

后一个分区只加紧邻前驱的局部值。

修正思路:传递或计算所有更早分区的累计和,逐位置验证。

轮到你动手

先写预测或代码,再按需打开提示。完整答案用于对照自己的推理。

练习 1

输入[1,2]、[]、[3]的局部总和与offset是什么?

给我一点提示
  1. 空和为0
  2. offset只累积前面的分区
查看答案与推理

局部总和3、0、3;offset为0、3、3;全局inclusive结果1、3、6。空分区不产生输出,但通信是否发送仍由协议约定。

练习 2

PE2算得6、8而预期8、10,最可能的offset错误是什么?

给我一点提示
  1. 本地结果2、4
  2. 反推实际加上的数
查看答案与推理

实际加了4,可能收到PE1的本地总和而不是包含PE0的累计6。检查第二条消息的载荷含义,再确认没有重复加或漏加。

把理解说出来

先用中文讲清因果,再用英文回答。问题依据技能主题编写,并非公司内部题库。

解释

inclusive和exclusive的区别?

参考回答 / English answer

前者包括当前元素;后者只累积之前元素,求和时首项为零。

Inclusive scan includes the current element. Exclusive scan represents the prefix before that element.
预测

总和2、4、4的exclusive offsets是什么?

参考回答 / English answer

0、2、6;不能用2、6、10,那会把当前分区重复加进去。

The offsets are zero, two, and six. Including each partition's own total would double-count local contributions.
调试

为何只验证最后一项不够?

参考回答 / English answer

最后一项只是总和,分区或消息顺序错误可能保持总和但破坏中间位置。

The final value only checks the total. Reordering can preserve it while corrupting intermediate prefixes.
追问

空分区是否可以无条件不发消息?

参考回答 / English answer

不可以。数学贡献为零,但下游协议可能仍需要一条完成或累计消息。

An empty partition contributes the identity mathematically. The communication protocol may still require a message or completion signal.
解释

链里第二条消息应是本地4还是累计6?

参考回答 / English answer

累计6;接收端需要所有前面分区之和。

It must carry the cumulative value six. The next partition needs the sum of all earlier partitions.
调试

CPU scan对了,真实多PE还需检查什么?

参考回答 / English answer

路由、任务触发、消息计数、缓冲容量、异步完成与多次运行状态。

I still need to validate routing, task activation, message counts, and completion. Buffer limits and repeated-run state are separate concerns.

继续查证

公开资料用于查证;本章图解和例题是独立教学内容。CPU 逻辑模型不能证明设备性能。