CHAPTER 42 / Tenstorrent · 显式数据流
TTNN 与 Metalium:不同抽象保留同一份数学合同
高层一次 add 与底层三个kernel,怎样验证做的是同一件事?
这一章要弄清楚
- 选择合适的抽象层
- 分开shape、dtype、layout与memory配置
- 用独立oracle检查高低层语义
先备知识:Tensix 与 tiles:先安排数据,再安排计算
C++20 本机逻辑模型;不是设备仿真或性能测量。厂商语法片段未在设备或 SDK 执行,实际 API 以匹配版本的公开文档为准。
先问要控制什么,再选接口
假设A为1、2、3、4,B为10、20、30、40,目标是逐项加法。高层接口让你表达“对两个tensor做add”,库负责选择并安排实现;低层接口让你更直接控制kernel、数据搬运、buffer与工作分布。两者的数学合同可以相同,开发和调试所需处理的细节却不同。
TTNN提供较高层的tensor操作;Metalium暴露更低层的Tensix编程方式。初学不需要为了显得底层而绕过现成算子。标准算子、模型验证和快速建立baseline适合从高层入手;需要新操作、特殊布局或具体瓶颈控制时,再向下深入。选择低层意味着多承担实现正确性,并不自动意味着更快。
Tensor 不只是一段数
Tensor同时涉及shape、dtype、layout及存储位置。Shape描述逻辑维度;dtype决定元素表示与数值精度;layout描述数据排列;memory配置关系到放在哪里、怎样分布。一个转换可能只改元数据,也可能真的复制、打包或改变数值精度,不能仅凭函数名假定零成本。
例一的高层路径用单循环,低层教学路径用两项一组的分块循环。逐元素结果都应为11、22、33、44。这个比较只说明两种原创C++表达实现同一算式,没有运行TTNN或Metalium。例二检查矩阵加法shape合同,主动拒绝两个元素数量相同但行列含义不同的输入。
把真实语法放回生命周期
四次调用,三个 API(补充,另估25分钟)
先读 Python 的外壳:a_tt = 调用(...) 把返回的对象绑定到名字 a_tt;它不是 C++ 的类型声明,也不保证复制对象。ttnn.from_torch 中的 ttnn 是模块名,点号选择其中的函数。device=device 左边是形参名字,右边是先前已有的变量;layout=ttnn.TILE_LAYOUT 同样通过名字传参,其中 TILE_LAYOUT 是模块提供的布局选项。
本片段假定 a_host、b_host 是已准备好的、形状和 dtype 兼容的 torch 张量,device 是可用的目标设备对象。这里只跟踪逻辑上逐项相加的四个值,不据此推断所有设备都支持某个特定 shape 或 dtype。
| 行 | 输入与动作 | 返回值归属 |
|---|---|---|
| 1、2 | 分别把 host 张量转成指定 device、tile layout 的 TT 张量 | a_tt、b_tt 是 Python 名字,引用的 TT 张量数据位于指定设备 |
| 3 | 对这两个 TT 张量做 add |
c_tt 接收结果 TT 张量 |
| 4 | 把结果转回 torch 张量 | c_host 接收可在 host 检查的结果 |
**纸上走一次:**给定逻辑输入 A=[1,2,3,4]、B=[10,20,30,40],先写出两次转换后各张量应代表哪些数,再预测加法和回读应代表 [11,22,33,44]。前两行没有相加,第三行也没有把结果自动赋给 a_host;少了第四行,就没有得到这里命名为 c_host 的回读结果。
转换可能涉及复制、打包、补齐或精度变化,不保证零拷贝。本章 CPU 例二自行采用“shape 必须相等”的窄合同;真实 ttnn.add 还支持规定的广播形式,不能用这个模型替代 API 规则。当前 latest 的 from_torch 将 device 类型列为 MeshDevice,add 默认 BF16 路径可能有最多 1 ULP 的误差(一个可表示数值间距);因此手算值是比较目标,真实结果须按选定 dtype 与版本检查。
本次只核对公开读法,未运行 SDK。来源为 from_torch、add、to_torch,均为滚动 latest,核对日期 2026-09-08;实验时另记安装的版本。
# TTNN公开API语法示意,未在设备执行。
# 已有兼容device与torch张量;具体dtype/layout支持以安装版本为准。
a_tt = ttnn.from_torch(a_host, device=device, layout=ttnn.TILE_LAYOUT)
b_tt = ttnn.from_torch(b_host, device=device, layout=ttnn.TILE_LAYOUT)
c_tt = ttnn.add(a_tt, b_tt)
c_host = ttnn.to_torch(c_tt)
这个片段隐藏了设备创建与关闭、异常清理、输入dtype选择和误差检查,所以它不是复制即运行的完整程序。真正实验应从官方对应版本完整示例开始,记录环境,然后用自己的小输入建立reference。若存在低精度转换,比较必须针对实际执行的数值合同,而不是要求和无限精度实数运算完全一致。
高层结果可以当什么证据
高层库输出可以作为交叉检查,但最好另有简单而独立的CPU oracle。若两个路径共享同一错误的reshape或同一输入打包函数,它们可能一起算错。使用非对称矩阵、负数、零、边缘shape和易手算值,能比随机大矩阵更快地暴露合同混淆。
一个有效降层过程是先固定高层输入输出,再一次替换一个环节:数据布局、单核实现或分布方式。每一步都重新比较正确性,最后才比较性能。不要同时改dtype、tile布局、kernel和batch大小,否则很难解释差异来自哪里。
什么问题才值得写低层kernel
例如trace显示某标准操作的前后反复转换布局,可能考虑减少转换或融合;某特殊操作没有合适实现,则需要自定义kernel。但如果瓶颈是host准备数据或反复读取结果,重写计算kernel未必有收益。面试讨论时应先说测量观察、假设和选择,再描述API细节。
TTNN、Metalium及其设备接口持续演进,部分历史示例使用旧设备管理方式。阅读时以相同版本的文档、headers和示例为一组,不能混搭旧CreateDevice代码与新mesh对象接口。教材讲的是职责与合同,不将某一版本的函数表当永久架构。
同一add的两种组织
两条实现共享明确数学输入。
教学低层按两项一块计算。
逐元素比对独立循环。
阅读完整推演文字
- 输入
A:1 2 3 4;B:10 20 30 40
两条实现共享明确数学输入。
- 分块
tile0:11 22;tile1:33 44
教学低层按两项一块计算。
- 比较
candidate:11 22 33 44;oracle:11 22 33 44
逐元素比对独立循环。
跟着例子,走完一遍
整段操作与分块操作
A=[1,2,3,4],B=[10,20,30,40]。
- 单循环产生oracle
- 分两块产生candidate
- 逐元素比较
// 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<int> a{1,2,3,4},b{10,20,30,40};
std::vector<int> oracle(4),candidate(4);
for(std::size_t i=0;i<4;++i) oracle[i]=a[i]+b[i];
for(std::size_t tile=0;tile<2;++tile) for(std::size_t j=0;j<2;++j) {
const auto i=tile*2+j; candidate[i]=a[i]+b[i];
}
require(candidate==oracle);
std::cout<<"result=";
for(std::size_t i=0;i<4;++i) std::cout<<(i?" ":"")<<candidate[i];
std::cout<<" match=1\n";
}
11 22 33 44,两路径一致。
抽象层改变组织,不改变逻辑算式。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 38-a.cpp -o example && ./example预期标准输出:
result=11 22 33 44 match=1
元素数相同不等于shape相同
A为2×3,B为3×2;本例只允许相同shape逐项加。
- 计算两者元素数均为6
- 逐维比较
- 拒绝不匹配shape
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <array>
#include <stdexcept>
void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }
int main() {
const std::array<int,2> a{2,3},b{3,2};
const bool count=a[0]*a[1]==b[0]*b[1];
const bool compatible=a==b;
require(count && !compatible);
std::cout<<"same_count="<<count<<" compatible="<<compatible<<'\n';
}
same_count=1,compatible=0。
本题没有广播或自动reshape合同。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 38-b.cpp -o example && ./example预期标准输出:
same_count=1 compatible=0
把抽象层当性能排名
认为Metalium必然快于TTNN。
修正思路:先固定数学和环境合同,再通过trace与同范围测量比较。
轮到你动手
先写预测或代码,再按需打开提示。完整答案用于对照自己的推理。
练习 1
为2×2加法挑一组能发现转置错误的数据。
给我一点提示
- 不要全部填一
- 让行列方向差异明显
查看答案与推理
例如A=[[1,2],[7,11]],B=[[10,20],[30,40]],期望[[11,22],[37,51]]。逐元素检查能发现B转置,单纯比较总和不能。
练习 2
高层与自写kernel差异同时涉及dtype和layout,怎样重做实验?
给我一点提示
- 一次改一项
- 保存每一步中间结果
查看答案与推理
先固定dtype、shape与同一输入,只验证layout打包回环;再比较同一数值合同的两种实现;最后单独评估dtype变化。分别记录正确性与性能,避免无法归因的组合改动。
把理解说出来
先用中文讲清因果,再用英文回答。问题依据技能主题编写,并非公司内部题库。
什么时候先选TTNN?
参考回答 / English answer
标准算子、模型功能验证或baseline;需要明确低层控制时再评估Metalium。
I start with TTNN for supported tensor operations and a baseline. I move lower when a specific operation or measured bottleneck requires control.同为六个元素,2×3和3×2一定能相加吗?
参考回答 / English answer
取决于操作合同;本例要求逐维相同,数量相等不够。
Equal element counts do not establish shape compatibility. I follow the operation's explicit shape and broadcasting rules.from_torch是否必然零拷贝?
参考回答 / English answer
不能假定。device、layout、dtype转换可能带来搬运和转换成本。
I do not assume conversion is free. Device placement, layout, and dtype changes can require data movement.两个实现输出相同就足够吗?
参考回答 / English answer
仍可能共享输入打包错误;需独立oracle及可手算非对称输入。
Two implementations can share the same preparation bug. I also use an independent oracle and diagnostic inputs.降低精度后有小误差,如何判断?
参考回答 / English answer
明确实际dtype与容差,检查非有限值和最大误差,不能随意放宽直到通过。
I define the numerical contract for the actual dtype. I inspect non-finite values and error magnitudes rather than weakening tolerances blindly.低层kernel为何可能更慢?
参考回答 / English answer
数据移动、布局转换、资源分配和调度仍有成本,低层实现也可能不如已有优化。
Lower-level code still pays transfer and scheduling costs. It may also be less optimized than the library implementation.继续查证
- TTNN Tensor and Add tutorial ↗
Tensor conversion and addition
- Metalium Getting Started ↗
Software stack and programming philosophy
公开资料用于查证;本章图解和例题是独立教学内容。CPU 逻辑模型不能证明设备性能。