Skip to content

Commit f828f68

Browse files
committed
[ET-VK] Address PR review: drop 8w/8da8w, add int8 coopmat gate, fix batch guard
Three changes addressing bot review on the quantized coopmat dispatch PR: 1. Drop 8w and 8da8w from the PR scope (reviewer request: keep this PR focused on int4 quantization). Removes linear_dq8ca_q8csw_tiled shader, the two 8w/8da8w YAML variants from the consolidated templates, all dispatch logic in QuantizedLinear.cpp, the op registration stack (custom_ops_lib.py / op_registry.py / patterns/quantized_linear.py), test_dq8ca_q8csw_linear.cpp, and the "8da8w" llm_config qmode entry. 2. Add int8 cooperative-matrix capability gate (Copilot #6): the existing can_use_q4gsw_coopmat() gate checked only supports_cooperative_matrix() which reports fp16 support, not whether VK_COMPONENT_TYPE_SINT8_KHR is enumerated. Adds supports_int8_cooperative_matrix() to Adapter.h (backed by a one-time vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR query in Device.cpp) and gates the dq8ca_q4gsw coopmat dispatch on it. 3. Reject batched outputs before coopmat dispatch (Codex P1 #8): adds a dim_of(output) > 2 check to can_use_q4gsw_coopmat(), matching the guard in is_coopmat_eligible(). The coopmat shaders use gl_WorkGroupID.xy only; a rank-3 output with aligned last two dims would silently miscompute. Also updates the YAML/GLSL template comments to reflect single-variant scope, fixes linear_qw_coopmat.yaml tile geometry to the tsweep optimum (128x64, 2x2 subgroup grid, sg32; ~+25% prefill on M5 EVT1 over the prior 128x128), and updates quantized_linear_global/local_wg_size to use per-shader wg_size from CoopmatTileDims rather than the hardcoded kCoopmatInvocations (which was wrong after the tile change reduced q4gsw WG size from 256 to 128).
1 parent 03cc7d8 commit f828f68

18 files changed

Lines changed: 103 additions & 920 deletions

backends/vulkan/_passes/tag_memory_meta_pass.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,10 @@ def constrain_op_arg_repset(self, arg_i: int, op_repsets: utils.OpRepSets) -> No
416416
over the upstream source's existing layout, falling back to the source only when
417417
downstream tracing does not fully constrain the repset.
418418
"""
419-
if self.force_fp16:
419+
# An explicit whole-graph buffer override (e.g. ET_VK_FORCE_BUFFER) must win
420+
# over the force_fp16 prefer-texture stopgap, otherwise buffer storage can
421+
# never be selected when force_fp16 is enabled.
422+
if self.force_fp16 and self.default_storage != VkStorageType.BUFFER:
420423
op_repsets.try_constrain_with_arg_repset(arg_i, utils.ANY_TEXTURE)
421424

422425
# First, trace downstream users to discover what layout they prefer.

backends/vulkan/custom_ops_lib.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -307,41 +307,6 @@ def linear_dq8ca_q4gsw(
307307
linear_dq8ca_q4gsw_op = getattr(getattr(torch.ops, namespace), name)
308308

309309

310-
#######################
311-
## linear_dq8ca_q8csw ##
312-
#######################
313-
314-
315-
def linear_dq8ca_q8csw(
316-
x: torch.Tensor,
317-
input_scale: torch.Tensor,
318-
input_zero_point: torch.Tensor,
319-
weights: torch.Tensor,
320-
weight_sums: torch.Tensor,
321-
weight_scales: torch.Tensor,
322-
bias: Optional[torch.Tensor] = None,
323-
):
324-
# Per-channel symmetric INT8 weight: dequant = weight.to(fp) * scales (per output channel)
325-
weights_dq = weights.to(x.dtype) * weight_scales.unsqueeze(-1)
326-
return torch.nn.functional.linear(x, weights_dq, bias)
327-
328-
329-
name = "linear_dq8ca_q8csw"
330-
lib.define(
331-
f"""
332-
{name}(
333-
Tensor input,
334-
Tensor input_scales,
335-
Tensor input_zp,
336-
Tensor weights,
337-
Tensor weight_sums,
338-
Tensor weight_scales,
339-
Tensor? bias = None) -> Tensor
340-
"""
341-
)
342-
lib.impl(name, linear_dq8ca_q8csw, "CompositeExplicitAutograd")
343-
linear_dq8ca_q8csw_op = getattr(getattr(torch.ops, namespace), name)
344-
345310
#################
346311
## qaqw_linear ##
347312
#################

backends/vulkan/op_registry.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -481,23 +481,6 @@ def register_linear_dq8ca_q4gsw():
481481
)
482482

483483

484-
@update_features(exir_ops.edge.et_vk.linear_dq8ca_q8csw.default)
485-
def register_linear_dq8ca_q8csw():
486-
return OpFeatures(
487-
inputs_storage=[
488-
utils.CONTIGUOUS_ANY, # input
489-
utils.WIDTH_PACKED_TEXTURE, # input_scale
490-
utils.WIDTH_PACKED_TEXTURE, # input_zero_point
491-
utils.NO_STORAGE, # weight (prepacked)
492-
utils.NO_STORAGE, # weight_sums (prepacked)
493-
utils.NO_STORAGE, # weight_scales (prepacked)
494-
utils.NO_STORAGE, # bias (prepacked)
495-
],
496-
inputs_dtypes=utils.FP_T,
497-
supports_prepacking=True,
498-
)
499-
500-
501484
# =============================================================================
502485
# QuantizeDequantize.cpp
503486
# =============================================================================

backends/vulkan/patterns/quantized_linear.py

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,11 @@ def is_weight_pergroup_quantized(self) -> bool:
227227
def is_weight_perchannel_quantized(self) -> bool:
228228
weight_shape = self.weight_node.meta["val"].shape
229229
scales_shape = self.weight_scales_node.meta["val"].shape
230-
# Standard PT2E per-channel: scales is 1D [N].
231-
if len(scales_shape) == 1:
232-
return scales_shape[0] == weight_shape[-2]
233-
# torchao source-transform with PerAxis(0) produces 2D [N, 1] (a
234-
# single "group" covering the whole row). Treat that as per-channel.
235-
if len(scales_shape) == 2 and scales_shape[-1] == 1:
236-
return scales_shape[-2] == weight_shape[-2]
237-
return False
230+
if len(scales_shape) != 1:
231+
return False
232+
233+
# scales should have same size as weight's output channels dim
234+
return scales_shape[0] == weight_shape[-2]
238235

239236
def is_input_static_per_tensor_quantized(self) -> bool:
240237
if self.dequantize_input_node is None:
@@ -492,85 +489,6 @@ def make_linear_dq8ca_q4gsw_op(
492489
match.output_node.replace_all_uses_with(qlinear_node)
493490

494491

495-
def make_linear_dq8ca_q8csw_op(
496-
ep: ExportedProgram,
497-
graph_module: torch.fx.GraphModule,
498-
match: QuantizedLinearMatch,
499-
weight_tensor: torch.Tensor,
500-
weight_scales_tensor: torch.Tensor,
501-
):
502-
# Per-channel symmetric INT8 weight: no group_size, no nibble packing.
503-
# Align width to 4 so GPU shader reads don't go OOB.
504-
utils.align_width_and_update_state_dict(
505-
ep,
506-
match.weight_node,
507-
weight_tensor,
508-
align_to=1,
509-
force_update=True,
510-
)
511-
512-
# torchao source-transform produces 2D [N, 1] scales; squeeze to 1D [N]
513-
# so the runtime sees the same shape as the standard PT2E per-channel
514-
# path.
515-
if weight_scales_tensor.dim() == 2 and weight_scales_tensor.shape[-1] == 1:
516-
weight_scales_tensor = weight_scales_tensor.squeeze(-1).contiguous()
517-
518-
utils.align_width_and_update_state_dict(
519-
ep,
520-
match.weight_scales_node,
521-
weight_scales_tensor,
522-
align_to=1,
523-
force_update=True,
524-
)
525-
526-
if match.bias_node is not None:
527-
bias_tensor = get_param_tensor(ep, match.bias_node)
528-
if bias_tensor is not None:
529-
utils.align_width_and_update_state_dict(ep, match.bias_node, bias_tensor)
530-
531-
# Pre-compute per-output-channel weight sums for input zero-point
532-
# correction during integer accumulation.
533-
first_graph_node = list(graph_module.graph.nodes)[0]
534-
with graph_module.graph.inserting_before(first_graph_node):
535-
weight_tensor_name = utils.get_tensor_name(ep, match.weight_node)
536-
sum_per_output_channel = weight_tensor.sum(dim=1).to(torch.int32).contiguous()
537-
# Pad OC to multiple of 4 to keep shader loads in-bounds
538-
oc = sum_per_output_channel.shape[0]
539-
if oc % 4 != 0:
540-
num_padding = 4 - (oc % 4)
541-
sum_per_output_channel = F.pad(
542-
sum_per_output_channel, (0, num_padding)
543-
).contiguous()
544-
545-
sums_name = weight_tensor_name + "_sums"
546-
sums_name = sums_name.replace(".", "_")
547-
weight_sums_node = create_constant_placeholder(
548-
exp_program=ep,
549-
graph=graph_module.graph,
550-
kind=InputKind.PARAMETER,
551-
name=sums_name,
552-
data=sum_per_output_channel,
553-
)
554-
555-
with graph_module.graph.inserting_before(match.output_node):
556-
qlinear_node = graph_module.graph.create_node(
557-
"call_function",
558-
exir_ops.edge.et_vk.linear_dq8ca_q8csw.default,
559-
args=(
560-
match.pattern_input_node,
561-
match.input_scales_node,
562-
match.input_zeros_node,
563-
match.weight_node,
564-
weight_sums_node,
565-
match.weight_scales_node,
566-
match.bias_node,
567-
),
568-
)
569-
570-
qlinear_node.meta["val"] = match.output_node.meta["val"]
571-
match.output_node.replace_all_uses_with(qlinear_node)
572-
573-
574492
def make_linear_q8ta_q8csw_custom_op(
575493
ep: ExportedProgram,
576494
graph_module: torch.fx.GraphModule,
@@ -752,13 +670,6 @@ def replace_quantized_linear_patterns(
752670
make_linear_dq8ca_q4gsw_op(
753671
ep, graph_module, match, weight_tensor, weight_scales_tensor
754672
)
755-
elif (
756-
match.is_input_dynamic_perchannel_quantized()
757-
and match.is_weight_perchannel_quantized()
758-
):
759-
make_linear_dq8ca_q8csw_op(
760-
ep, graph_module, match, weight_tensor, weight_scales_tensor
761-
)
762673
elif (
763674
match.is_input_static_per_tensor_quantized()
764675
and match.is_weight_perchannel_quantized()

backends/vulkan/runtime/graph/ops/glsl/linear_dq8ca_q8csw_tiled.glsl

Lines changed: 0 additions & 159 deletions
This file was deleted.

backends/vulkan/runtime/graph/ops/glsl/linear_dq8ca_q8csw_tiled.yaml

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)