Skip to content

Vulkan weight-only int8 linear dispatches to shaders that do not exist (linear_qcs8w_*)#22429

Description

@msluszniak

馃悰 Describe the bug

Any model lowered to Vulkan with weight-only int8 linear aborts on its first execute. The AOT side produces the op, the partitioner accepts it, and the runtime then asks for a shader that is not in the repo.

The chain:

  • FuseQuantizedOpsTransform rewrites a dequantize_per_channel + linear pair to aten._weight_int8pack_mm (backends/vulkan/_passes/fuse_quantized_ops.py, fuse_into_linear_qcnw_node).
  • op_registry.py lists exir_ops.edge.aten._weight_int8pack_mm.default as supported, so VulkanPartitioner takes it.
  • QuantizedLinearQCSNW.cpp registers it and builds the kernel name linear_qcs8w_tiled / linear_qcs8w_coop.
  • No linear_qcs8w* glsl or yaml exists. c1ea7e9221 [ET-VK] Quantized Int8 Linear (#14041) introduced linear_q8csw_tiled (letters transposed) with a different suffix scheme, and the C++ was never moved over.

On device:

libc++abi: terminating due to uncaught exception of type vkcompute::vkapi::Error:
Exception raised from get_shader_info at .../api/ShaderRegistry.cpp:54:
(it != listings_.end()) is false! Could not find ShaderInfo with name
linear_qcs8w_tiled_texture3d_texture3d_texture2d_texture2d_half_o4x3

The maintained implementation is already there: QuantizedLinear.cpp registers et_vk.linear_q8csw on quantized_linear_impl, which uses the linear_q8csw_tiled shaders. It just has no AOT counterpart, so nothing can ever emit it, while the op that AOT does emit routes to the dead path.

et_vk.linear_qcs4w has the same problem (linear_qcs4w_tiled / _coop are also missing). PR the linked PR covers only the 8-bit case, since the group-wise 4-bit kernel is not a drop-in replacement for per-channel 4-bit.

Reproduction

Quantize any linear-heavy model weight-only and lower it to Vulkan:

from executorch.backends.vulkan.quantizer.vulkan_quantizer import (
    VulkanQuantizer, get_symmetric_quantization_config)
from torchao.quantization.pt2e.quantize_pt2e import prepare_pt2e, convert_pt2e

quantizer = VulkanQuantizer().set_global(
    get_symmetric_quantization_config(is_dynamic=False, weight_bits=8))
prepared = prepare_pt2e(torch.export.export(module, inputs).module(), quantizer)
prepared(*inputs)
converted = convert_pt2e(prepared)
# ... to_edge_transform_and_lower with VulkanPartitioner, then run on device

The produced .pte contains aten._weight_int8pack_mm.default (strings model.pte | grep int8pack) and aborts as above on any Vulkan device.

Measured with sentence-transformers/all-mpnet-base-v2 and multi-qa-mpnet-base-dot-v1 on a Galaxy S26 Ultra (Adreno 840), ExecuTorch 1.4.1 for the export and a runner built from the same tree. Both abort. With the fix in the linked PR they run, and match the fp32 eager reference at cosine 0.998280 / 0.995343 while the file drops from 217.9 MB to 133.2 MB and per-execution time from 79 ms to 59 ms at 382 tokens.

Note that reaching this point at all needs #22372, or _convert_scalars_to_attrs lifts the position-id add as float32 and embedding fails first.

Versions

  • Export: executorch==1.4.1, torch==2.14.0.dev20260702, torchao==0.18.0, Python 3.10, macOS arm64
  • Runtime: Galaxy S26 Ultra (SM-S948B), Adreno 840, Android 16
  • Also read on main at 834a4fb020: op_registry.py:434 still lists the op, QuantizedLinearQCSNW.cpp:296 still names linear_qcs8w_tiled, and no linear_qcs8w* shader exists.

cc @SS-JIA @manuelcandales @digantdesai @cbilgin

Metadata

Metadata

Assignees

No one assigned

    Labels

    module: vulkanIssues related to the Vulkan delegate and code under backends/vulkan/

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions