From a4629cf3e85063c1f414795fcae49176fbb71e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Tue, 1 Sep 2026 14:46:29 +0200 Subject: [PATCH] [ET-VK] Fix squeeze_copy of the outermost dim under dynamic shapes add_squeeze_copy_dims_node() skips dim 0 and falls back to add_clone_node(). resize_clone_node() only propagates sizes when input and output have the same dim count, which a squeeze never does, so the output keeps the extents it was built with. With static shapes that is invisible. With dynamic shapes the output holds its upper-bound extents while consumers read it at the real size, so the copy lands in the wrong places and roughly half the output comes back zeroed -- silently, with no error. Route dim 0 through the permute path like every other squeeze dim; resize_permute_node() already has an explicit branch for the rank-reducing case. Repro: any model that ends up with torch.cat(list(x), -1) over a rank-4 tensor with a dynamic dim. The unbind lowers to slice_copy plus squeeze_copy.dims, and the second slice comes back zeroed for every extent below the bound. Reduced to a 15-line case: y[1:2] is correct while y[1:2].squeeze(0) returns exactly half zeros (cosine 0.704 = sqrt of 0.5 against the reference), correct only at the bound. Verified on a Galaxy S26 Ultra (Adreno 840): the reduced case goes from 0.704 to 1.000000 at extents 200, 500 and 1000, and a TTS model whose classifier-free-guidance batch is built this way goes from cosine 0.36 to 0.99993 against its CPU reference. --- .../vulkan/runtime/graph/ops/impl/Squeeze.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/backends/vulkan/runtime/graph/ops/impl/Squeeze.cpp b/backends/vulkan/runtime/graph/ops/impl/Squeeze.cpp index fce7600a035..a78456538bf 100644 --- a/backends/vulkan/runtime/graph/ops/impl/Squeeze.cpp +++ b/backends/vulkan/runtime/graph/ops/impl/Squeeze.cpp @@ -23,21 +23,27 @@ void add_squeeze_copy_dims_node( const ValueRef out) { const int64_t in_dim = graph.dim_of(in); const std::vector in_sizes = graph.sizes_of(in); - const std::vector out_sizes = graph.sizes_of(in); const std::vector dims = graph.extract_int_or_symint_list(dims_ref); std::vector squeeze_dims; - // Filter out edge cases that we don't need squeeze: - // 1. The size of squeeze dim is larger than 1. - // 2. Squeeze outter most dim - // For these cases, just pass input to output via clone. + // Filter out the edge case that we don't need to squeeze: the size of the + // squeeze dim is larger than 1. For that case, just pass input to output via + // clone. + // + // Note that the outermost dim must NOT be excluded here. Routing it to + // add_clone_node() leaves the output unresized at runtime, because + // resize_clone_node() only propagates sizes when input and output have the + // same dim count -- which is never true for a squeeze. Under dynamic shapes + // the output then keeps its upper-bound extents while consumers read it at + // the real size, silently producing wrong values. add_permute_node()'s + // resize function handles the rank-reducing case explicitly. for (int i = 0; i < dims.size(); ++i) { // adjust negative dims int64_t dim_val = dims.at(i); if (dim_val < 0) { dim_val += in_dim; } - if (dims.at(i) != 0 && in_sizes.at(dim_val) == 1) { + if (in_sizes.at(dim_val) == 1) { squeeze_dims.push_back(dim_val); } }