馃悰 Describe the bug
Any binary op whose output is 0-dimensional aborts at graph build with std::out_of_range: vector. This makes Whisper's whole encode method unloadable on Vulkan, because its log-mel normalisation subtracts one scalar from another.
Repro
class ZeroDimSub(torch.nn.Module):
def forward(self, x):
m = x.max() # 0-dim
return (m - (m - 1.0)).reshape(1)
# lowers fine, aborts at load
to_edge_transform_and_lower(
{"forward": torch.export.export(ZeroDimSub().eval(), (torch.randn(64),))},
partitioner=[VulkanPartitioner()],
).to_executorch()
libc++abi: terminating due to uncaught exception of type std::out_of_range: vector
Aborted
Two controls that pass, which pin the trigger to the output rank rather than the input rank:
| model |
result |
x.max() - (x.max() - 1.0), 0-dim output |
abort |
x - x.max(), 0-dim operand broadcast to rank 1 |
ok |
same arithmetic with .reshape(1) applied first, rank-1 output |
ok |
Cause
calculate_broadcasted_output_size() in backends/vulkan/runtime/graph/ops/impl/utils/TensorUtils.cpp:
std::vector<int64_t> out_sizes(std::max(sizes1.size(), sizes2.size()));
for (int i = -1; i >= -out_sizes.size(); --i) {
out_sizes.at(out_sizes.size() + i) = ...
}
out_sizes.size() is size_t, so -out_sizes.size() is unsigned. When both inputs are 0-dimensional the bound is 0, i promotes to SIZE_MAX, the guard SIZE_MAX >= 0 stays true, and the body evaluates out_sizes.at(0 - 1) = out_sizes.at(SIZE_MAX) on an empty vector.
For non-empty outputs the same unsigned wraparound happens to compare correctly, which is why this has gone unnoticed: with size() == 2 the guard is SIZE_MAX - 1, and i = -1, -2 pass while i = -3 fails, exactly as intended. Only the empty case breaks.
The call reaches this from check_binary_op_args(), so it aborts during graph construction rather than at execute time.
Impact
Whisper-tiny's encode method (mel preprocessor plus encoder) cannot be loaded on Vulkan at all. Confirmed that this is not caused by the dynamic audio dimension (a static 30 s export fails identically) and not by multi-method lowering (an encode-only .pte fails identically).
With the one-line fix, encode loads and runs, producing cosine 0.99998790 against the CPU reference on a Snapdragon SM8850 (Adreno 840).
I will send a PR.
Versions
ExecuTorch at c27baa8. Device: Samsung Galaxy S26 Ultra, Snapdragon SM8850, Adreno 840, Android 16.
馃悰 Describe the bug
Any binary op whose output is 0-dimensional aborts at graph build with
std::out_of_range: vector. This makes Whisper's wholeencodemethod unloadable on Vulkan, because its log-mel normalisation subtracts one scalar from another.Repro
Two controls that pass, which pin the trigger to the output rank rather than the input rank:
x.max() - (x.max() - 1.0), 0-dim outputx - x.max(), 0-dim operand broadcast to rank 1.reshape(1)applied first, rank-1 outputCause
calculate_broadcasted_output_size()inbackends/vulkan/runtime/graph/ops/impl/utils/TensorUtils.cpp:out_sizes.size()issize_t, so-out_sizes.size()is unsigned. When both inputs are 0-dimensional the bound is0,ipromotes toSIZE_MAX, the guardSIZE_MAX >= 0stays true, and the body evaluatesout_sizes.at(0 - 1)=out_sizes.at(SIZE_MAX)on an empty vector.For non-empty outputs the same unsigned wraparound happens to compare correctly, which is why this has gone unnoticed: with
size() == 2the guard isSIZE_MAX - 1, andi = -1, -2pass whilei = -3fails, exactly as intended. Only the empty case breaks.The call reaches this from
check_binary_op_args(), so it aborts during graph construction rather than at execute time.Impact
Whisper-tiny's
encodemethod (mel preprocessor plus encoder) cannot be loaded on Vulkan at all. Confirmed that this is not caused by the dynamic audio dimension (a static 30 s export fails identically) and not by multi-method lowering (an encode-only .pte fails identically).With the one-line fix,
encodeloads and runs, producing cosine 0.99998790 against the CPU reference on a Snapdragon SM8850 (Adreno 840).I will send a PR.
Versions
ExecuTorch at c27baa8. Device: Samsung Galaxy S26 Ultra, Snapdragon SM8850, Adreno 840, Android 16.