CHAPTER 47 / Cerebras · 晶圆级计算
多PE scan:消息里到底应该传什么
局部前缀和怎样变成保持原顺序的全局前缀和?
这一章要弄清楚
- 区分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传播
每个分区先独立scan。
第二条消息已经包含P0。
把offset加到本地每项。
阅读完整推演文字
- 局部
P0:3 2;P1:4;P2:2 4
每个分区先独立scan。
- 传播
P0→P1:2;P1→P2:6;offsets:0 2 6
第二条消息已经包含P0。
- 修正
P0:3 2;P1:6;P2:8 10
把offset加到本地每项。
跟着例子,走完一遍
三分区inclusive scan
分区[3,-1]、[4]、[2,2]。
- 局部scan及总和2、4、4
- offset为0、2、6
- 修正局部结果并比oracle
// 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';
}
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
总和相同不代表scan相同
比较[2,4,4]和[4,2,4]的分区inclusive累计。
- 分别计算前缀
- 比较最后一项
- 比较全部位置
// 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是什么?
给我一点提示
- 空和为0
- offset只累积前面的分区
查看答案与推理
局部总和3、0、3;offset为0、3、3;全局inclusive结果1、3、6。空分区不产生输出,但通信是否发送仍由协议约定。
练习 2
PE2算得6、8而预期8、10,最可能的offset错误是什么?
给我一点提示
- 本地结果2、4
- 反推实际加上的数
查看答案与推理
实际加了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.继续查证
- Cerebras routes tutorial ↗
Routes, colors and asynchronous completion
- Cerebras streaming wavelet tutorial ↗
Wavelet-triggered tasks and payloads
公开资料用于查证;本章图解和例题是独立教学内容。CPU 逻辑模型不能证明设备性能。