CHAPTER 44 / Tenstorrent · 显式数据流

Tenstorrent 端到端例子:让每一层对同一结果负责

怎样从两张tensor一直检查到host回读?

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

这一章要弄清楚

  • 写出host与三个kernel的数据合同
  • 用位置编码发现分布错误
  • 分开数值、协议和性能验证

先备知识:Tensix 与 tiles:先安排数据,再安排计算 / TTNN 与 Metalium:不同抽象保留同一份数学合同 / Reader、compute、writer:缓冲区里的生产与消费

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

先固定一个可以手算的任务

我们计算两个四乘四矩阵相加。A按行保存0到15,B全部为10,因此结果为10到25。选择位置可辨认的A,是为了让错误的tile顺序或分区立即显现。若A和B都填一,许多布局错误仍会返回一片二,让测试误以为程序正确。

本章的C++端到端模型把工作分成两行一组:reader复制该组,compute逐项相加,writer按原始偏移写回。它不是Metalium模拟器;它检查我们自己设计的数据分配、buffer顺序和回收位置。真实程序中这些阶段由相应设备API和kernel实现,并有独立同步合同。

Host准备的不只是数组

Host需要确定shape、dtype、layout、数据位置、使用哪些核以及每个kernel的参数。输入buffer和输出buffer的大小,要与设备代码的读取数量一致。处理两块tile的kernel若收到三块的数据,可能留下未写结果;反过来多读一块则可能越界或等待不存在的数据。

公开Metalium示例的host流程包含设备与命令队列、program、kernel配置、runtime参数、数据传入、执行、回读和清理。新旧版本的设备管理接口有差异,因此应成套使用同一版本示例,不应拼接历史API。如下仅展示目前公开示例中的mesh入口,不是完整运行程序。

三行建立了什么对象(补充,另估20分钟)

沿用已学的 autoshared_ptr 和引用读法,把下面三行看成 host 对象的建立过程。假定匹配版本的环境中,编号 0 的设备可用;这个 0 是设备选择编号,与上一章的 tile 数量无关。

顺序 调用及返回值 到这里能确认什么
1 distributed::MeshDevice::create_unit_mesh(0);公开例子用 shared_ptr<distributed::MeshDevice> 接收 创建以一个设备构成的 mesh,device 是它的共享拥有者
2 device->mesh_command_queue()auto& 接收引用 queue 借用该 mesh 的命令队列,没有复制或取得一个独立拥有的队列
3 CreateProgram();返回 Program 对象 program 是 host 侧程序描述,后面还要向它配置 kernel 等内容

寿命关系可以画成:device 持有 mesh;queue → 借用该 mesh 的队列;program 保存待配置的程序描述。 箭头表示借用,不转移拥有权。不要让 mesh 已被关闭或销毁后还使用 queue;有提交的异步工作时,还须满足该版本规定的完成和资源回收顺序。

**纸上走一次:**仍使用本章 A=0…15、B全为10。执行第一行得到设备拥有者,第二行得到队列引用,第三行得到程序对象。此时应能指出三个名字各自是什么,但不能声称结果已变成10…25:三行没有安排矩阵输入、配置相加 kernel 或提交执行。如果删去后续工作,只保留这三行,数学目标就尚未计算。

以上依据 Metalium 的逐项加法 host 示例(滚动 latest,核对日期 2026-09-08)。这里只读现有三行,未运行 SDK;真实环境应成套使用匹配的 headers、文档与示例。

// Metalium host语法示意,未在SDK/设备执行。
// 需要对应版本headers、namespace与兼容设备环境。
auto device = distributed::MeshDevice::create_unit_mesh(0);
auto& queue = device->mesh_command_queue();
Program program = CreateProgram();
// 后续仍需buffer、reader/compute/writer、参数、执行和回读。

三种正确性分别检查

数学正确性检查每个有效元素;布局正确性检查逻辑坐标与实际存储位置;协议正确性检查生产数、消费数、完成条件及buffer复用。只检查总和不能发现排列错误,只检查程序退出不能证明输出已写完。例一给每个输出位置记录写入次数,要求恰好一次,避免漏写和重复覆盖。

例二把十项工作分给三个核,采用商与余数分配,数量为四、三、三,起点为零、四、七。负载不整除时最容易在最后一个核漏项,或者让相邻核重复处理边界。覆盖计数是一个不依赖计算公式的小检查,适合先在CPU验证分区。

从单核到多核只改一件事

建议先用最小数据完成一个核上的完整生命周期,再增加tile数量,再增加核数。每一步保留相同数学oracle,记录变化的分布参数。分布变大以后出现错误,优先检查每核起点、数量、输出偏移和跨核依赖,不要同时改数值精度与通信策略。

低精度dtype可能带来合理舍入,比较标准应预先定义。输入中的NaN、Inf以及输出未初始化值也应单独处理,不能让容差公式把非有限结果算成通过。正确性通过后再测性能;本机CPU模型的运行时间不代表Tensix计算、NoC带宽或真实设备吞吐。

端到端报告怎样讲得清楚

先说问题、输入和期望结果,再说如何分配数据,以及最重要的同步与生命周期约束。最后说明测试覆盖和实际运行环境。如果没有设备,只展示已运行的CPU合同模型与未执行的API设计;如果以后有设备,再追加精确版本、命令、输出和测量范围。清楚说明限制会让技术讨论更具体,而不是削弱已完成工作的价值。

观察 · 推演

16项分成两个处理区

group00–7group18–1501 / 03 · PIPELINEgroup00–7group18–1501 / 03 · PIPELINE
reader

每组明确源偏移。

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

    group0:0–7;group1:8–15

    每组明确源偏移。

  2. compute

    group0:10–17;group1:18–25

    每个值加10。

  3. writer

    output:10–25;coverage:每项1次

    按原位置写回。

跟着例子,走完一遍

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

两组reader/compute/writer

A=0–15,B=10;每组处理8项。

  1. reader复制8项
  2. compute逐项相加
  3. writer按偏移回填并计数
40-a.cpp
下载
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <vector>
#include <cstddef>
#include <stdexcept>

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

int main() {
 std::vector<int> a(16),out(16,-1),writes(16,0);
 for(int i=0;i<16;++i) a[static_cast<std::size_t>(i)]=i;
 for(std::size_t base=0;base<16;base+=8) {
  std::vector<int> local(a.begin()+static_cast<std::ptrdiff_t>(base),a.begin()+static_cast<std::ptrdiff_t>(base+8));
  for(int& v:local) v+=10;
  for(std::size_t j=0;j<8;++j){out[base+j]=local[j];++writes[base+j];}
 }
 for(std::size_t i=0;i<16;++i) require(writes[i]==1 && out[i]==a[i]+10);
 std::cout<<"first="<<out.front()<<" last="<<out.back()<<" writes=16\n";
}

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

结果与解释

输出首项10、末项25,16个位置恰写一次。

单独核对覆盖和数学结果。

在本机运行这个例子

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

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

预期标准输出:

first=10 last=25 writes=16
例题 02C++20 · 本机可运行

十项分到三个核

工作索引0–9,核数3。

  1. 商3余1
  2. 前一个核多拿一项
  3. 起点累计为0、4、7
40-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"); }

int main() {
 std::vector<int> visits(10,0),counts;
 int start=0;
 for(int core=0;core<3;++core) {
  const int count=10/3+(core<10%3);counts.push_back(count);
  for(int i=start;i<start+count;++i) ++visits[static_cast<std::size_t>(i)];
  start+=count;
 }
 for(int v:visits) require(v==1);
 require(start==10);
 std::cout<<"counts="<<counts[0]<<' '<<counts[1]<<' '<<counts[2]<<" coverage=ok\n";
}
结果与解释

counts=4 3 3,coverage=ok。

每项恰好一次,尾部分区不丢失。

在本机运行这个例子

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

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

预期标准输出:

counts=4 3 3 coverage=ok
常见错误

只验证数值总和

tile顺序错了仍因总和相等而通过。

修正思路:使用坐标可辨识输入,检查每项值、写入覆盖和shape。

轮到你动手

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

练习 1

两项工作分四核,给出每核数量与空核处理。

给我一点提示
  1. 商为0余2
  2. 空核不能等待不存在数据
查看答案与推理

数量1、1、0、0,起点0、1、2、2。空核不执行有效数据处理;真实kernel配置与同步必须允许这种情况,或host只启用有工作的核。

练习 2

reader写3块、compute算2块、writer等3块,解释故障。

给我一点提示
  1. 逐段比较数量
  2. 找永远不能满足的wait
查看答案与推理

第三块输入无人消费,第三块输出无人生产,writer等待可能永远不结束。统一工作量并为尾部建立明确合同;不是把输出buffer加大就能修复。

把理解说出来

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

解释

为什么输出总和一致不足以验证加法程序?

参考回答 / English answer

输出位置可能被置换,总和仍相等;应逐元素对比坐标相关输入。

A permutation can preserve the total sum. I compare each logical element using position-dependent inputs.
预测

十项分三核,起点为什么是0、4、7?

参考回答 / English answer

数量4、3、3,起点是此前数量的前缀和。

The work counts are four, three, and three. Each start offset is the prefix sum of previous counts.
调试

单核正确、多核错误,优先检查什么?

参考回答 / English answer

每核起点、数量、输出偏移及跨核依赖,保持dtype和算式不变。

I inspect per-core offsets, counts, and dependencies. I keep numerical choices fixed while isolating distribution errors.
追问

写入次数检查能发现什么,不能发现什么?

参考回答 / English answer

能发现漏写/重复覆盖;无法证明写入值正确,也不自动证明真实并发无race。

Coverage counts expose missing or duplicate ownership. They do not establish correct values or race freedom in a device implementation.
解释

配置kernel前host必须知道哪些合同?

参考回答 / English answer

shape、dtype、layout、每核工作量、buffer大小及参数约定;不是只传一个地址。

The host must agree on shape, dtype, layout, and work distribution. Buffer sizes and kernel arguments are part of the same contract.
解释

没有硬件时怎样报告本章结果?

参考回答 / English answer

报告CPU分区/数学模型通过;设备API片段未验证,不能写Tensix吞吐。

I report the CPU model and its tests. I explicitly leave device execution and performance unverified.

继续查证

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