Skip to content

Commit 3e1d315

Browse files
committed
[ExecuTorch][Vulkan] Tests for et_vk.linear_q4gsw_backward
Pull Request resolved: #20944 **Correctness tests for the Vulkan `et_vk.linear_q4gsw_backward` kernel** (stacked above the op diff). **Coverage:** the golden `d_x = d_out @ dequant(W)` is computed with ATen (`matmul` over the library-dequantized weight), mirroring the CPU-eager `linear_q4gsw_backward_impl` in `custom_ops_lib.py` — never a hand-rolled matmul. The packed weight is fed as a constant `tensorref` so the op exercises the real `prepack_q4_w_4x8_nc_buffer` W_4X8 path. Cases: - `test_tile_aligned` — single group, tile-aligned M/N/K. - `test_grouped` — multiple quantization groups along K. - `test_odd_n4_partial_m` — `N % 8 != 0` (odd N4 -> padded W_4X8 stride) plus a partial-M tile (exercises the `min()` clamps). Also wires `quantized_linear_backward_test` into `targets.bzl` + `CMakeLists.txt` (mirrors the sibling `linear_q4gsw_dw_test`). ghstack-source-id: 405059117 @exported-using-ghexport Differential Revision: [D111797530](https://our.internmc.facebook.com/intern/diff/D111797530/)
1 parent be93f5f commit 3e1d315

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

backends/vulkan/test/op_tests/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ if(TARGET vulkan_backend AND LIB_TORCH)
125125
vulkan_op_test(
126126
fused_ce_test ${CMAKE_CURRENT_SOURCE_DIR}/fused_ce_test.cpp test_utils
127127
)
128+
vulkan_op_test(
129+
quantized_linear_backward_test
130+
${CMAKE_CURRENT_SOURCE_DIR}/quantized_linear_backward_test.cpp test_utils
131+
)
128132

129133
# Only build generated op tests if a path to tags.yaml and
130134
# native_functions.yaml is provided. These files are required for codegen.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <gtest/gtest.h>
10+
11+
#include <ATen/ATen.h>
12+
13+
#include <executorch/backends/vulkan/runtime/api/api.h>
14+
#include <executorch/backends/vulkan/runtime/graph/ComputeGraph.h>
15+
#include <executorch/backends/vulkan/runtime/graph/ops/OperatorRegistry.h>
16+
17+
#include "test_utils.h"
18+
19+
//
20+
// Reference Implementation
21+
//
22+
23+
// Pack unpacked [N, K] codes (0..15) into the flat [N, K/2] uint8 weight the
24+
// forward's prepack consumes: even-K in the low nibble, odd-K in the high.
25+
at::Tensor pack_codes_flat(const at::Tensor& codes) {
26+
const int64_t N = codes.size(0);
27+
const int64_t K = codes.size(1);
28+
at::Tensor packed =
29+
at::empty({N, K / 2}, at::device(at::kCPU).dtype(at::kByte));
30+
auto ca = codes.accessor<int, 2>();
31+
auto pa = packed.accessor<uint8_t, 2>();
32+
for (int64_t n = 0; n < N; ++n) {
33+
for (int64_t kb = 0; kb < K / 2; ++kb) {
34+
const int lo = ca[n][2 * kb] & 0xF;
35+
const int hi = ca[n][2 * kb + 1] & 0xF;
36+
pa[n][kb] = static_cast<uint8_t>(lo | (hi << 4));
37+
}
38+
}
39+
return packed;
40+
}
41+
42+
// Golden d_x[M, K] = d_out[M, N] @ dequant(W)[N, K], with
43+
// dequant(W[n, k]) = (code(n, k) - 8) * scales[k / group_size, n].
44+
// Mirrors the CPU-eager linear_q4gsw_backward_impl in custom_ops_lib.py.
45+
at::Tensor linear_q4gsw_backward_reference_impl(
46+
const at::Tensor& d_out,
47+
const at::Tensor& codes,
48+
const at::Tensor& scales,
49+
const int64_t group_size) {
50+
const int64_t N = codes.size(0);
51+
const int64_t K = codes.size(1);
52+
const at::Tensor group_idx =
53+
at::arange(K, at::device(at::kCPU).dtype(at::kLong))
54+
.div(group_size, "floor");
55+
const at::Tensor scale_full =
56+
scales.t().contiguous().index_select(1, group_idx); // [N, K]
57+
const at::Tensor dequant_w =
58+
(codes.to(at::kFloat) - 8.0) * scale_full; // [N, K]
59+
const at::Tensor d_x_flat = d_out.reshape({-1, N}).matmul(dequant_w);
60+
std::vector<int64_t> out_shape = d_out.sizes().vec();
61+
out_shape.back() = K;
62+
return d_x_flat.reshape(out_shape).contiguous(); // d_out[..., :-1] + [K]
63+
}
64+
65+
//
66+
// Test function
67+
//
68+
69+
void test_vulkan_linear_q4gsw_backward_impl(
70+
const std::vector<int64_t>& d_out_sizes,
71+
const int64_t K,
72+
const int64_t group_size) {
73+
const int64_t N = d_out_sizes.back();
74+
const int64_t num_groups = K / group_size;
75+
76+
at::Tensor codes =
77+
at::randint(0, 16, {N, K}, at::device(at::kCPU).dtype(at::kInt));
78+
at::Tensor scales =
79+
at::rand({num_groups, N}, at::device(at::kCPU).dtype(at::kFloat)) + 0.5;
80+
at::Tensor packed = pack_codes_flat(codes);
81+
at::Tensor d_out =
82+
at::rand(d_out_sizes, at::device(at::kCPU).dtype(at::kFloat));
83+
84+
at::Tensor d_x_ref =
85+
linear_q4gsw_backward_reference_impl(d_out, codes, scales, group_size);
86+
87+
using namespace vkcompute;
88+
89+
GraphConfig config;
90+
ComputeGraph graph(config);
91+
92+
ValueRef r_weights = graph.add_tensorref(
93+
packed.sizes().vec(),
94+
from_at_scalartype(packed.scalar_type()),
95+
packed.const_data_ptr());
96+
ValueRef r_scales = graph.add_tensorref(
97+
scales.sizes().vec(),
98+
from_at_scalartype(scales.scalar_type()),
99+
scales.const_data_ptr());
100+
101+
IOValueRef r_d_out = graph.add_input_tensor(
102+
d_out.sizes().vec(),
103+
from_at_scalartype(d_out.scalar_type()),
104+
utils::kBuffer);
105+
const ValueRef r_group_size = graph.add_scalar<int64_t>(group_size);
106+
const ValueRef r_d_x = graph.add_tensor(
107+
d_x_ref.sizes().vec(),
108+
from_at_scalartype(d_x_ref.scalar_type()),
109+
utils::kBuffer);
110+
111+
VK_GET_OP_FN("et_vk.linear_q4gsw_backward.default")
112+
(graph, {r_d_out.value, r_weights, r_scales, r_group_size, r_d_x});
113+
114+
ValueRef staging_out = graph.set_output_tensor(r_d_x);
115+
116+
graph.prepare();
117+
graph.prepack();
118+
graph.propagate_resize();
119+
120+
graph.maybe_cast_and_copy_into_staging(
121+
r_d_out.staging,
122+
d_out.const_data_ptr(),
123+
d_out.numel(),
124+
from_at_scalartype(d_out.scalar_type()));
125+
126+
graph.execute();
127+
128+
at::Tensor vk_d_x = at::empty_like(d_x_ref);
129+
graph.maybe_cast_and_copy_from_staging(
130+
staging_out,
131+
vk_d_x.mutable_data_ptr(),
132+
vk_d_x.numel(),
133+
from_at_scalartype(vk_d_x.scalar_type()));
134+
135+
ASSERT_TRUE(at::allclose(vk_d_x, d_x_ref, 1e-3, 1e-3));
136+
}
137+
138+
// Tile-aligned single-group shapes.
139+
TEST(VulkanLinearQ4gswBackwardTest, test_tile_aligned) {
140+
test_vulkan_linear_q4gsw_backward_impl(
141+
/*d_out_sizes=*/{8, 16}, /*K=*/32, /*group_size=*/32);
142+
}
143+
144+
// Multiple quantization groups along K.
145+
TEST(VulkanLinearQ4gswBackwardTest, test_grouped) {
146+
test_vulkan_linear_q4gsw_backward_impl(
147+
/*d_out_sizes=*/{8, 32}, /*K=*/64, /*group_size=*/32);
148+
}
149+
150+
// N not a multiple of 8 (odd N4 -> padded W_4X8 stride) plus partial-M tile.
151+
TEST(VulkanLinearQ4gswBackwardTest, test_odd_n4_partial_m) {
152+
test_vulkan_linear_q4gsw_backward_impl(
153+
/*d_out_sizes=*/{5, 12}, /*K=*/16, /*group_size=*/16);
154+
}
155+
156+
// Leading dims > 2D: M is the flattened product of all leading dims.
157+
TEST(VulkanLinearQ4gswBackwardTest, test_leading_dims_flatten) {
158+
test_vulkan_linear_q4gsw_backward_impl(
159+
/*d_out_sizes=*/{2, 3, 16}, /*K=*/32, /*group_size=*/32);
160+
}

backends/vulkan/test/op_tests/targets.bzl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ def define_common_targets(is_fbcode = False):
198198
":test_utils",
199199
]
200200
)
201+
define_test_targets(
202+
"quantized_linear_backward_test",
203+
extra_deps = [
204+
":test_utils",
205+
]
206+
)
201207
define_test_targets(
202208
"rms_norm_test",
203209
extra_deps = [

0 commit comments

Comments
 (0)