Skip to content

Commit fd00fa7

Browse files
author
ssjia
committed
[ET-VK][ops] Add eq.Scalar operator
Pull Request resolved: #20383 Adds Vulkan support for `aten.eq.Scalar`. This is the second of two ops needed to collapse the Llama4-mini TISO en_US backbone export to a single Vulkan partition after `bitwise_or`: the discrete-speech mask compares the int token-id tensor against scalar constants via `aten.eq.Scalar`, which previously had no Vulkan implementation and forced a CPU fallback that split the delegated graph. Implemented by extending the existing tensor-scalar binary-op path with a comparison-output variant: `binary_scalar_buffer.glsl` / `binary_scalar_texture.glsl` gain an `IS_COMPARISON_OP` code path that writes a `uint8` (bool) output while leaving the existing arithmetic path unchanged; `binary_scalar_buffer.yaml` / `binary_scalar_texture.yaml` add compatible dtype/scalar pairs only (`half`/`float`, `float`/`float`, `int32`/`int32`) so unsupported mixed pairs fail instead of silently truncating scalar values; `BinaryScalarOp.cpp` adds an `eq_tensor_scalar` dispatch and registers `aten.eq.Scalar`; `op_registry.py` registers `aten.eq.Scalar` features with FP/INT tensor input and bool output. The generated op-test graph builders now preserve `at::Scalar` tags when adding graph scalars, so integer, boolean, and floating scalar literals exercise the correct graph scalar type instead of all being converted through `double`. The int64 token tensor is serialized to int32 via the existing `downcast_64_bit` path, so the dispatch resolves to the int32 shader variant; no dtype-conversion pass is added. This change was authored with Claude. ghstack-source-id: 397279869 @exported-using-ghexport Differential Revision: [D108457791](https://our.internmc.facebook.com/intern/diff/D108457791/)
1 parent 197fbce commit fd00fa7

10 files changed

Lines changed: 194 additions & 27 deletions

File tree

backends/vulkan/op_registry.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,17 @@ def register_pow_tensor_scalar():
327327
)
328328

329329

330+
@update_features(exir_ops.edge.aten.eq.Scalar)
331+
def register_eq_scalar():
332+
return OpFeatures(
333+
inputs_storage=utils.ANY_STORAGE,
334+
inputs_dtypes=utils.FP_INT_T,
335+
outputs_dtypes=utils.BOOL_T,
336+
supports_resize=True,
337+
supports_highdim=True,
338+
)
339+
340+
330341
# =============================================================================
331342
# ToCopy.cpp
332343
# =============================================================================

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,27 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
// Binary comparison ops write a bool/uint8 output dtype, which differs from
10+
// the input dtype. IS_COMPARISON_OP is set explicitly per shader variant in the
11+
// .yaml.
12+
913
#version 450 core
1014

1115
${define_required_extensions(STORAGE, DTYPE)}
16+
${define_explicit_type_extensions(SCALAR_VALUE_TYPE)}
17+
$if IS_COMPARISON_OP:
18+
${define_required_extensions(STORAGE, "uint8")}
1219

1320
#define PRECISION ${PRECISION}
1421

1522
#define NAME ${VARIANT_NAME}
1623

1724
#define T ${buffer_scalar_type(DTYPE)}
25+
#define SCALAR_T ${buffer_scalar_type(SCALAR_VALUE_TYPE)}
26+
$if IS_COMPARISON_OP:
27+
#define OUT_T ${buffer_scalar_type("uint8")}
28+
$else:
29+
#define OUT_T ${buffer_scalar_type(DTYPE)}
1830

1931
#define op(X, Y) ${OPERATOR}
2032

@@ -24,25 +36,30 @@ layout(std430) buffer;
2436

2537
#include "indexing.glslh"
2638

27-
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
39+
$if IS_COMPARISON_OP:
40+
${layout_declare_tensor(B, "w", "t_out", "uint8", STORAGE)}
41+
$else:
42+
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
43+
2844
${layout_declare_tensor(B, "r", "t_in", DTYPE, STORAGE)}
2945

3046
${layout_declare_ubo(B, "BufferMetadata", "outp")}
3147
${layout_declare_ubo(B, "BufferMetadata", "inp")}
3248

3349
layout(push_constant) uniform restrict Block {
34-
float scalar_value;
50+
SCALAR_T scalar_value;
3551
};
3652

3753
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3854

39-
#include "binary_op_defs.glslh"
55+
$if not IS_COMPARISON_OP:
56+
#include "binary_op_defs.glslh"
4057

4158
void main() {
4259
const uint out_bufi = gl_GlobalInvocationID.x;
4360
if (out_of_bounds(out_bufi, outp)) {
4461
return;
4562
}
4663

47-
t_out[out_bufi] = T(op(t_in[out_bufi], T(scalar_value)));
64+
t_out[out_bufi] = OUT_T(op(t_in[out_bufi], T(scalar_value)));
4865
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77
binary_scalar_buffer:
88
parameter_names_with_default_values:
99
OPERATOR: power_of(X, Y)
10-
NDIM: 3
10+
IS_COMPARISON_OP: false
1111
DTYPE: float
12-
PACKING: C_packed
12+
SCALAR_VALUE_TYPE: float
1313
STORAGE: buffer
1414
generate_variant_forall:
15-
DTYPE:
16-
- VALUE: half
17-
- VALUE: float
18-
- VALUE: int32
15+
combination:
16+
parameter_names: [DTYPE, SCALAR_VALUE_TYPE]
17+
combos:
18+
- parameter_values: [half, float]
19+
- parameter_values: [float, float]
20+
- parameter_values: [int32, int32]
1921
shader_variants:
2022
- NAME: pow_scalar_buffer
23+
- NAME: eq_scalar_buffer
24+
OPERATOR: X == Y
25+
IS_COMPARISON_OP: true

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,28 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9+
// Binary comparison ops write a bool/uint8 output dtype, which differs from
10+
// the input dtype. IS_COMPARISON_OP is set explicitly per shader variant in the
11+
// .yaml.
12+
913
#version 450 core
1014

1115
${define_required_extensions(STORAGE, DTYPE)}
16+
${define_explicit_type_extensions(SCALAR_VALUE_TYPE)}
17+
$if IS_COMPARISON_OP:
18+
${define_required_extensions(STORAGE, "uint8")}
1219

1320
#define PRECISION ${PRECISION}
1421

1522
#define NAME ${VARIANT_NAME}
1623

1724
#define VEC4_T ${texel_load_type(DTYPE, STORAGE)}
1825
#define T ${texel_load_component_type(DTYPE, STORAGE)}
26+
#define SCALAR_T ${buffer_scalar_type(SCALAR_VALUE_TYPE)}
27+
$if IS_COMPARISON_OP:
28+
#define VEC4_OUT_T ${texel_load_type("uint8", STORAGE)}
29+
$else:
30+
#define VEC4_OUT_T VEC4_T
1931

2032
#define op(X, Y) ${OPERATOR}
2133

@@ -25,19 +37,24 @@ layout(std430) buffer;
2537

2638
#include "indexing.glslh"
2739

28-
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
40+
$if IS_COMPARISON_OP:
41+
${layout_declare_tensor(B, "w", "t_out", "uint8", STORAGE)}
42+
$else:
43+
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
44+
2945
${layout_declare_tensor(B, "r", "t_in", DTYPE, STORAGE)}
3046

3147
${layout_declare_ubo(B, "TextureMetadata", "outp")}
3248
${layout_declare_ubo(B, "TextureMetadata", "inp")}
3349

3450
layout(push_constant) uniform restrict Block {
35-
float scalar_value;
51+
SCALAR_T scalar_value;
3652
};
3753

3854
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3955

40-
#include "binary_op_defs.glslh"
56+
$if not IS_COMPARISON_OP:
57+
#include "binary_op_defs.glslh"
4158

4259
void main() {
4360
const ivec3 pos = ivec3(gl_GlobalInvocationID);
@@ -47,7 +64,7 @@ void main() {
4764
}
4865

4966
VEC4_T in_texel = texelFetch(t_in, pos, 0);
50-
VEC4_T out_texel = VEC4_T(op(in_texel, VEC4_T(scalar_value)));
67+
VEC4_OUT_T out_texel = VEC4_OUT_T(op(in_texel, VEC4_T(scalar_value)));
5168

5269
imageStore(t_out, pos, out_texel);
5370
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77
binary_scalar_texture:
88
parameter_names_with_default_values:
99
OPERATOR: power_of(X, Y)
10-
NDIM: 3
10+
IS_COMPARISON_OP: false
1111
DTYPE: float
12-
PACKING: C_packed
12+
SCALAR_VALUE_TYPE: float
1313
STORAGE: texture3d
1414
generate_variant_forall:
15-
DTYPE:
16-
- VALUE: half
17-
- VALUE: float
18-
- VALUE: int32
15+
combination:
16+
parameter_names: [DTYPE, SCALAR_VALUE_TYPE]
17+
combos:
18+
- parameter_values: [half, float]
19+
- parameter_values: [float, float]
20+
- parameter_values: [int32, int32]
1921
shader_variants:
2022
- NAME: pow_scalar_texture3d
23+
- NAME: eq_scalar_texture3d
24+
OPERATOR: equal(X, Y)
25+
IS_COMPARISON_OP: true

backends/vulkan/runtime/graph/ops/impl/BinaryScalarOp.cpp

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,42 @@
1616

1717
#include <executorch/backends/vulkan/runtime/graph/ops/utils/ShaderNameUtils.h>
1818

19+
#include <executorch/backends/vulkan/runtime/utils/VecUtils.h>
20+
21+
#include <vector>
22+
1923
namespace vkcompute {
2024

25+
namespace {
26+
27+
vkapi::ScalarType scalar_value_dtype(
28+
ComputeGraph& graph,
29+
const ValueRef scalar) {
30+
// Use a 32-bit integer push constant for bool scalars. Push constants in this
31+
// file otherwise use 32-bit lanes, and T(scalar_value) preserves bool as 0/1.
32+
if (graph.val_is_bool(scalar) || graph.val_is_symint(scalar)) {
33+
return vkapi::kInt;
34+
}
35+
return graph.dtype_of(scalar);
36+
}
37+
38+
int32_t extract_int32_scalar(ComputeGraph& graph, const ValueRef scalar) {
39+
if (graph.val_is_int(scalar)) {
40+
return utils::safe_downcast<int32_t>(graph.get_int(scalar));
41+
}
42+
if (graph.val_is_bool(scalar)) {
43+
return graph.get_bool(scalar) ? 1 : 0;
44+
}
45+
if (graph.val_is_symint(scalar)) {
46+
return graph.read_symint(scalar);
47+
}
48+
VK_THROW(
49+
"Expected int, bool, or SymInt scalar, got: ",
50+
graph.get_val_type(scalar));
51+
}
52+
53+
} // namespace
54+
2155
void resize_binary_scalar_op_node(
2256
ComputeGraph* graph,
2357
const std::vector<ArgGroup>& args,
@@ -39,14 +73,32 @@ void add_binary_scalar_op_node(
3973
const std::string& op_name) {
4074
ValueRef arg = prepack_standard_like(graph, in, out, true);
4175

42-
// Extract scalar value
43-
float scalar_val = graph.extract_scalar<float>(scalar);
44-
45-
// Pick shader
76+
const vkapi::ScalarType scalar_dtype = scalar_value_dtype(graph, scalar);
77+
std::vector<PushConstantDataInfo> push_constants;
78+
if (scalar_dtype == vkapi::kInt) {
79+
const int32_t scalar_val = extract_int32_scalar(graph, scalar);
80+
push_constants.emplace_back(&scalar_val, sizeof(scalar_val));
81+
} else if (scalar_dtype == vkapi::kFloat) {
82+
const float scalar_val = graph.extract_scalar<float>(scalar);
83+
push_constants.emplace_back(&scalar_val, sizeof(scalar_val));
84+
} else {
85+
VK_THROW("Unsupported tensor-scalar op scalar dtype: ", scalar_dtype);
86+
}
87+
88+
// Pick shader -- note that currently, only a few shader variants are
89+
// generated for "compatible" tensor dtype / scalar dtype pairs. In particular
90+
// float/half tensor + float scalar, and int32 tensor + int32 scalar. This
91+
// decision is to prevent combinations like int tensor + float scalar, which
92+
// would currently produce wrong values with the way the shader is currently
93+
// written. The current combinations should be sufficient for practical
94+
// applications, but in the future if "mixed precision" combinations are
95+
// needed, then the shaders would need to be updated with more robust dtype
96+
// handling.
4697
std::string kernel_name = op_name + "_scalar";
4798
kernel_name.reserve(kShaderNameReserve);
4899
add_storage_type_suffix(kernel_name, graph.storage_type_of(out));
49100
add_dtype_suffix(kernel_name, graph.dtype_of(in));
101+
add_dtype_suffix(kernel_name, scalar_dtype);
50102

51103
vkapi::ParamsBindList param_ubos = {graph.meta_ubo(out), graph.meta_ubo(in)};
52104

@@ -60,7 +112,7 @@ void add_binary_scalar_op_node(
60112
// Shader params buffers
61113
param_ubos,
62114
// Push Constants
63-
{PushConstantDataInfo(&scalar_val, sizeof(scalar_val))},
115+
push_constants,
64116
// Specialization Constants
65117
{},
66118
// Resize Args
@@ -73,8 +125,13 @@ void pow_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
73125
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "pow");
74126
}
75127

128+
void eq_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
129+
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "eq");
130+
}
131+
76132
REGISTER_OPERATORS {
77133
VK_REGISTER_OP(aten.pow.Tensor_Scalar, pow_tensor_scalar);
134+
VK_REGISTER_OP(aten.eq.Scalar, eq_tensor_scalar);
78135
}
79136

80137
} // namespace vkcompute

backends/vulkan/test/op_tests/cases.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,3 +2217,32 @@ def get_pow_tensor_scalar_inputs():
22172217
]
22182218
test_suite.dtypes = ["at::kFloat"]
22192219
return test_suite
2220+
2221+
2222+
@register_test_suite("aten.eq.Scalar")
2223+
def get_eq_scalar_inputs():
2224+
# Scalars are chosen to fall within the make_seq_tensor range (1..numel),
2225+
# so each case exercises a genuine mix of equal / not-equal elements rather
2226+
# than a trivially all-false comparison.
2227+
test_suite = VkTestSuite(
2228+
[
2229+
((M1,), 5),
2230+
((M2, M1), 100),
2231+
((S1, M1, M2), 1000),
2232+
((S1, S2, S2, M2), 2000),
2233+
((S, S1, S2), 50),
2234+
((M1, M2), 700),
2235+
((S1, S2), 20),
2236+
]
2237+
)
2238+
test_suite.storage_types = [
2239+
"utils::kBuffer",
2240+
"utils::kTexture3D",
2241+
]
2242+
test_suite.layouts = [
2243+
"utils::kWidthPacked",
2244+
"utils::kChannelsPacked",
2245+
]
2246+
test_suite.dtypes = ["at::kInt"]
2247+
test_suite.data_gen = "make_seq_tensor"
2248+
return test_suite

backends/vulkan/test/op_tests/utils/gen_benchmark_vk.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,19 @@ def generate_benchmark_fixture(self) -> str:
198198
}}
199199
}}
200200
201+
ValueRef add_scalar_to_graph(ComputeGraph& graph, const at::Scalar& scalar) {{
202+
if (scalar.isBoolean()) {{
203+
return graph.add_scalar<bool>(scalar.toBool());
204+
}}
205+
if (scalar.isIntegral(/*includeBool=*/false)) {{
206+
return graph.add_scalar<int64_t>(scalar.toLong());
207+
}}
208+
if (scalar.isFloatingPoint()) {{
209+
return graph.add_scalar<double>(scalar.toDouble());
210+
}}
211+
VK_THROW("Unsupported at::Scalar!");
212+
}}
213+
201214
at::Tensor make_casted_randint_tensor(
202215
std::vector<int64_t> sizes,
203216
at::ScalarType dtype = at::kFloat,

backends/vulkan/test/op_tests/utils/gen_computegraph.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,8 +476,8 @@ def create_value_for( # noqa: C901
476476
ret_str += f"from_at_scalartype({ref.src_cpp_name}.scalar_type()), "
477477
ret_str += f"{ref.src_cpp_name}.const_data_ptr()); \n"
478478
elif ref.src_cpp_type == AT_SCALAR:
479-
# TODO(ssjia): generalize this to work with all scalar types
480-
ret_str += f"add_scalar<double>({ref.src_cpp_name}.toDouble()); \n"
479+
ret_str = f"{cpp_type} {ref.name} = "
480+
ret_str += f"add_scalar_to_graph(*{self.graph}, {ref.src_cpp_name}); \n"
481481
elif ref.src_cpp_type == AT_INT_ARRAY_REF:
482482
ret_str += f"add_scalar_list({ref.src_cpp_name}.vec()); \n"
483483
elif ref.src_cpp_type == BOOL:

backends/vulkan/test/op_tests/utils/gen_correctness_vk.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ def gen_parameterization(self) -> str:
129129
}
130130
}
131131
132+
ValueRef add_scalar_to_graph(ComputeGraph& graph, const at::Scalar& scalar) {
133+
if (scalar.isBoolean()) {
134+
return graph.add_scalar<bool>(scalar.toBool());
135+
}
136+
if (scalar.isIntegral(/*includeBool=*/false)) {
137+
return graph.add_scalar<int64_t>(scalar.toLong());
138+
}
139+
if (scalar.isFloatingPoint()) {
140+
return graph.add_scalar<double>(scalar.toDouble());
141+
}
142+
VK_THROW("Unsupported at::Scalar!");
143+
}
144+
132145
#ifdef USE_VULKAN_FP16_INFERENCE
133146
bool check_close(at::Tensor& t1, at::Tensor& t2, float rtol=1e-2, float atol=1e-2) {
134147
#else

0 commit comments

Comments
 (0)