Why
X @ mesh.axis means two different things in the two places an author writes it. In a reshard layout it keeps the tensor's rank; in a Tensor[...] type it splits the axis into two. So the two cannot meet:
tilefoundry: error: Binary: Binary ADD: cannot broadcast shapes (1, 16, 8192) and (1, 16, 128, 64)
- Both operands were written with the same sugar over the same mesh in the same body.
What
tf.reshard(x, (1, 16, 8192 @ m.u), "smem") has type Tensor[(1, 16, 8192), ..., ((1, 16, 128 @ cta.u, 64), ...)] — logical rank 3, the split recorded in the layout.
tf.zeros(Tensor[(1, 16, 8192 @ m.u), "f32", "smem"]) has logical shape (1, 16, 128, 64) — the split is in the shape.
- Consequence: an accumulator seeded with
tf.zeros cannot be added to, or accumulated into by, anything produced by a resharded op, which is the ordinary way to open a reduction. docs/tutorial/authoring.md's Stage3_Fused works only because every operand there is tf.zeros/tf.full_like of the same seed, so the two forms never meet.
Contract
- One notation, one meaning. Whichever way it is resolved,
reshard and Tensor[...] should agree on whether @ changes rank.
Risk
- If
Tensor[...] is changed to match reshard, programs that index the split axes of a tf.zeros result break. If reshard is changed to match Tensor[...], most sharded programs break. A third option — reject the sugar in one of the two positions — is the smallest change and at least turns a broadcast error into a statement about the notation.
- Workaround today: open the accumulator with the first iteration's
matmul instead of tf.zeros, which is what we ended up doing everywhere.
Reproducer
from __future__ import annotations
from tilefoundry import func, module
from tilefoundry.dsl import Mesh, Tensor, tf
from tilefoundry.dsl.tf import * # noqa: F401, F403
from tilefoundry.ir.types.shard import Topology
from tilefoundry.target import CudaTarget
N = 128
_H200, _CTA = CudaTarget("nvidia.h200_sxm"), Topology("cta", N)
@module(entry="f", target=_H200, topologies=(_CTA,))
class M:
@func
def f(x: Tensor[(1, 16, 8192), "f32"]) -> Tensor[(1, 16, 8192), "f32"]:
with Mesh(("cta",), layout=(N,), names=("u",)) as m:
xs = tf.reshard(x, (1, 16, 8192 @ m.u), "smem") # rank kept
zs = tf.zeros(Tensor[(1, 16, 8192 @ m.u), "f32", "smem"]) # rank grown
return xs + zs
tilefoundry analyze case.py:M /tmp/r.txt --compute-cost
tilefoundry 0.0.2.dev43+g45032b967 (45032b9), target nvidia.h200_sxm.
Why
X @ mesh.axismeans two different things in the two places an author writes it. In areshardlayout it keeps the tensor's rank; in aTensor[...]type it splits the axis into two. So the two cannot meet:What
tf.reshard(x, (1, 16, 8192 @ m.u), "smem")has typeTensor[(1, 16, 8192), ..., ((1, 16, 128 @ cta.u, 64), ...)]— logical rank 3, the split recorded in the layout.tf.zeros(Tensor[(1, 16, 8192 @ m.u), "f32", "smem"])has logical shape(1, 16, 128, 64)— the split is in the shape.tf.zeroscannot be added to, or accumulated into by, anything produced by a resharded op, which is the ordinary way to open a reduction.docs/tutorial/authoring.md'sStage3_Fusedworks only because every operand there istf.zeros/tf.full_likeof the same seed, so the two forms never meet.Contract
reshardandTensor[...]should agree on whether@changes rank.Risk
Tensor[...]is changed to matchreshard, programs that index the split axes of atf.zerosresult break. Ifreshardis changed to matchTensor[...], most sharded programs break. A third option — reject the sugar in one of the two positions — is the smallest change and at least turns a broadcast error into a statement about the notation.matmulinstead oftf.zeros, which is what we ended up doing everywhere.Reproducer
tilefoundry 0.0.2.dev43+g45032b967(45032b9), targetnvidia.h200_sxm.