[ET-VK] fp16 linear on an RDNA3.5 iGPU: default (texture) export runs at ~1.1 TFLOP/s, coopmat unreachable without buffer storage, and the stock buffer path is ~20x less accurate
维护者通常 1 天内回复
@SS-JIA 已经在做这个了。
开始于 2026年10月29日。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 42/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
调研方向
Start with gemm/GemmCommon.h and is_coopmat_eligible(), then trace how VulkanPartitioner chooses texture or buffer storage for linear outputs. Reproduce the reported measurements with the provided export and check scripts on an RDNA3.5 Vulkan GPU. Done means the default export reaches an appropriate accurate kernel, with measurements confirming both performance and relative error.
由索引模型根据 Issue 内容生成。
描述
Summary
On an RDNA3.5 integrated GPU (Radeon 880M/890M, gfx1150, RADV), an fp16 nn.Linear lowered to the Vulkan backend shows three issues at main (4db778e):
- The default export uses texture storage and runs at about 1.1 TFLOP/s. A standalone fp16 cooperative-matrix GEMM with fp32 accumulation runs at 7.4 TFLOP/s on the same GPU at the same shape, so the default path reaches about 15% of that.
- The cooperative-matrix path is unreachable from a default export.
is_coopmat_eligible()requiresstorage_type_of(out) == kBuffer, and the default export produces texture-backed outputs. Lifting the integrated-GPU gate (#22665, #22899) therefore changes nothing for a default-exported model: the timings with the gate removed are identical to stock. - Forcing buffer storage is faster, but on stock builds it is about 20× less accurate. The error is 1.6e-2 relative, against 7.5e-4 for the texture path. With the integrated-GPU gate removed, the same buffer model runs on the coopmat kernel at 0.73 ms (2.9 TFLOP/s) with 3.8e-4 relative error, so the loss of accuracy comes from the non-coopmat buffer kernel. My guess is fp16 accumulation, but I have not checked the shader.
Taken together: a user who exports normally gets the slow path, and a user who forces buffers to get the fast path gets 20× more error unless the GPU also passes the coopmat gate.
Results
Model: fp16, input 256×2048, Linear(2048, 2048). "one" is a single bias-free layer; "two" is Linear, ReLU, Linear with bias. "Buffer" means VulkanPartitioner({"texture_limits": (1, 1, 1)}). "Gate removed" means main with only !adapter->is_integrated_gpu() && deleted from gemm/GemmCommon.h.
The time is per inference: (wall time of 401 executions − wall time of 1 execution) / 400, in one executor_runner process each, taken twice. The error is max|out − ref| / max|ref|, where ref is the same fp16 weights and input computed in fp32 by eager PyTorch.
| model | storage | stock main: ms |
TFLOP/s | rel. error | gate removed: ms | TFLOP/s | rel. error |
|---|---|---|---|---|---|---|---|
| one | default (texture) | 1.95 / 1.97 | 1.10 | 7.48e-4 | 2.01 / 1.97 | 1.08 | 7.48e-4 |
| one | buffer | 1.39 / 1.41 | 1.53 | 1.62e-2 | 0.78 / 0.73 | 2.86 | 3.79e-4 |
| two | default (texture) | 3.77 / 3.86 | 1.13 | 1.22e-3 | 3.73 / 3.62 | 1.17 | 1.22e-3 |
| two | buffer | 2.52 / 2.46 | 1.72 | 1.21e-2 | 1.26 / 1.22 | 3.47 | 4.14e-4 |
For reference, a standalone fp16 × fp16 → fp32 VK_KHR_cooperative_matrix GEMM at M=256, N=K=2048 on the same GPU takes 0.29 ms per dispatch, about 7.4 TFLOP/s.
The same eight measurements at the older commit b20f16a gave the same numbers within noise.
Questions
- Should a linear or matmul whose shape is coopmat-eligible get buffer-backed output at export time, so that the coopmat kernel is reachable without the user knowing about
texture_limits? - Is the error in the buffer tiled kernel expected, for example because it accumulates in fp16? If it is, fp32 accumulation would bring it in line with the texture path.
Repro
# export.py - writes one_default.pte, one_buffer.pte, two_default.pte, two_buffer.pte
import torch
from executorch.exir import to_edge
from executorch.backends.vulkan.partitioner.vulkan_partitioner import VulkanPartitioner
class Two(torch.nn.Module):
def __init__(s):
super().__init__()
s.seq = torch.nn.Sequential(torch.nn.Linear(2048, 2048), torch.nn.ReLU(), torch.nn.Linear(2048, 2048))
def forward(s, x): return s.seq(x)
class One(torch.nn.Module):
def __init__(s):
super().__init__(); s.l = torch.nn.Linear(2048, 2048, bias=False)
def forward(s, x): return s.l(x)
torch.manual_seed(0)
x = torch.randn(256, 2048).half()
for name, m in (("two", Two()), ("one", One())):
m = m.eval().half()
for tag, opts in (("default", {}), ("buffer", {"texture_limits": (1, 1, 1)})):
ep = torch.export.export(m, (x,), strict=False)
prog = to_edge(ep).to_backend(VulkanPartitioner(opts)).to_executorch()
open(f"{name}_{tag}.pte", "wb").write(prog.buffer)
# check.py <one|two> <default|buffer> - accuracy against eager fp32 on the same weights
import sys, torch
from executorch.runtime import Runtime
# (same Two / One classes as above)
torch.manual_seed(0)
x = torch.randn(256, 2048).half()
models = {"two": Two().eval().half(), "one": One().eval().half()} # same construction order as export.py
name, tag = sys.argv[1], sys.argv[2]
prog = Runtime.get().load_program(open(f"{name}_{tag}.pte", "rb").read())
meth = prog.load_method("forward")
out = meth.execute([x])[0].float()
ref = models[name].float()(x.float())
print(f"{name}_{tag}: {((out - ref).abs().max() / ref.abs().max()):.2e}")
Timing: executor_runner --model_path <pte> --num_executions {1,401}, wall-clocked as described above.
Environment
- ExecuTorch:
mainat 4db778ea79b4c34777a9da94dd4d3c9627aef9be (1.6.0+4db778e), built from source withEXECUTORCH_BUILD_VULKAN=ON. - torch / torchao: torch 2.14.0+cpu; torchao 0.19.0.dev20260907 (nightly, the version
mainrequires). - GPU: AMD Ryzen AI 9 365, Radeon 880M (reported as "AMD Radeon 890M Graphics (RADV GFX1150)"), subgroup size 64,
VK_KHR_cooperative_matrixsupported. - Driver: Mesa 25.2.8-0ubuntu0.24.04.4 (RADV), Vulkan API 1.4.318.
- OS: Linux 7.0.0-38-generic, Ubuntu.
Related: #22665 (the integrated-GPU coopmat gate) and #22899 (supports_fp16_cooperative_matrix). cc @SS-JIA @manuelcandales @digantdesai @cbilgin
- 主要语言
- Python
- 星标
- 5.1k
- 派生
- 1.2k
- 平均合并
- 1 天 21 小时
- 30 天内合并 PR
- 695
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
pytorch/executorch 的其他 Issue
-
enhancement triaged
难度 2/5 半天 新手友好度 68/100
pytorch/executorch#21640 ·
维护者通常 1 天内回复
-
module: cuda
难度 5/5 一周以上 新手友好度 10/100
pytorch/executorch#24353 ·
维护者通常 1 天内回复
-
难度 5/5 一周以上 新手友好度 10/100
pytorch/executorch#24350 ·
维护者通常 1 天内回复
-
`torch.complex` fails Edge export and CPU execution可能已有人在做 @JakeStevens 今天认领。 未关闭bug module: kernels triaged
pytorch/executorch#24328 · 已指派 2 人 ·
维护者通常 1 天内回复
-
module: openvino
难度 5/5 一周以上 新手友好度 15/100
pytorch/executorch#24325 ·
维护者通常 1 天内回复
查看 pytorch/executorch 的全部 Issue
相似的 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 86/100
NousResearch/hermes-agent#147104 ·
维护者通常 1 天内回复
-
[oblt-aw][autodoc] Document new required `shared-org-key` input for `obs-aw-dependency-review`可能已有人在做 @github-actions 今天认领。 未关闭
难度 1/5 1 小时以内 新手友好度 90/100
维护者通常 1 天内回复
-
bot-triaged module: inductor oncall: pt2 topic: fuzzer triaged
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
getchaintips leaves out the active tip once a header is staged on it, where Core always lists it未关闭
难度 2/5 1-3 小时 新手友好度 88/100
btclib-org/btclib-node#2158 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 75/100
patrick-hudson/put.gd-desktop#979 · 1 条评论 ·
维护者通常 1 天内回复