Skip to content

[ET-VK] Binary ops with a 0-dimensional output abort with std::out_of_range#22331

Description

@msluszniak

馃悰 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions