Skip to content

Commit 76d6b1e

Browse files
ssjiaSS-JIA
authored andcommitted
[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` generate variants for the `half`/`float`, `float`/`float`, `int32`/`int32`, and `int32`/`float` tensor/scalar dtype pairs. Mixed tensor/scalar dtypes are computed in a promoted type rather than narrowing the scalar to the tensor dtype: a new `get_higher_precision_dtype` helper in `gen_vulkan_spv.py` picks the higher-precision of the two dtypes, and `binary_op_defs.glslh` evaluates operands in that promoted `COMPUTE_T` before narrowing to the output dtype (so e.g. an `int32` tensor compared against a `float` scalar compares in `float`). `BinaryScalarOp.cpp` adds an `eq_tensor_scalar` dispatch, registers `aten.eq.Scalar`, and coerces the scalar extract dtype to match a generated variant; `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: 397529325 @exported-using-ghexport Differential Revision: [D108457791](https://our.internmc.facebook.com/intern/diff/D108457791/)
1 parent 3719228 commit 76d6b1e

12 files changed

Lines changed: 283 additions & 37 deletions

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/gen_vulkan_spv.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,61 @@ def texel_load_component_type(dtype: str, storage_type: str) -> str:
255255
return texel_component_type(dtype)
256256

257257

258+
def get_higher_precision_dtype(dtype_a: str, dtype_b: str) -> str:
259+
"""Return the higher-precision of two dtypes, intended for picking the type
260+
in which to perform a mixed-dtype computation (e.g. a tensor of one dtype
261+
combined with a scalar of another). The result is the operand dtype that can
262+
represent the other without loss for the value ranges these shaders handle.
263+
264+
Ranking rule (highest first):
265+
- The float family ALWAYS outranks the int family. So any float dtype
266+
beats any int dtype (e.g. int32 + float -> float), because integer
267+
values in these shaders are small enough to be exactly representable in
268+
the float compute type.
269+
- Within the float family: double > float > half.
270+
- Within the int family: rank by bit width
271+
(int64/uint64 > int32/uint32 > int16/uint16 > int8/uint8/bool).
272+
- Signed-vs-unsigned tie at the same bit width resolves to the SIGNED
273+
dtype (matches PyTorch's same-width promotion preference and keeps the
274+
compute type able to hold negative operands).
275+
276+
Dtype aliases are normalized first: int<->int32, uint<->uint32, and bool is
277+
treated as the lowest-ranked 8-bit integer.
278+
"""
279+
alias_map = {"int": "int32", "uint": "uint32"}
280+
a = alias_map.get(dtype_a, dtype_a)
281+
b = alias_map.get(dtype_b, dtype_b)
282+
283+
if a == b:
284+
return a
285+
286+
# (family_rank, bit_width, signed_rank). Higher tuple wins. family_rank 1 for
287+
# the float family ensures it always outranks the int family (family_rank 0).
288+
# signed_rank breaks same-width int ties in favor of signed.
289+
float_ranks = {"half": 16, "float": 32, "double": 64}
290+
int_ranks = {
291+
"bool": 8,
292+
"uint8": 8,
293+
"int8": 8,
294+
"uint16": 16,
295+
"int16": 16,
296+
"uint32": 32,
297+
"int32": 32,
298+
"uint64": 64,
299+
"int64": 64,
300+
}
301+
302+
def rank(dtype: str) -> tuple:
303+
if dtype in float_ranks:
304+
return (1, float_ranks[dtype], 0)
305+
if dtype in int_ranks:
306+
signed_rank = 0 if dtype in ("bool",) or dtype.startswith("uint") else 1
307+
return (0, int_ranks[dtype], signed_rank)
308+
raise AssertionError(f"Cannot rank precision of dtype: {dtype}")
309+
310+
return a if rank(a) >= rank(b) else b
311+
312+
258313
def get_access_qualifier(access_type: Optional[str]) -> str:
259314
if access_type is None:
260315
return ""
@@ -497,6 +552,7 @@ def define_required_extensions(storage_type: str, dtypes: Union[str, List[str]])
497552
"texel_component_type": texel_component_type,
498553
"texel_load_type": texel_load_type,
499554
"texel_load_component_type": texel_load_component_type,
555+
"get_higher_precision_dtype": get_higher_precision_dtype,
500556
"layout_declare_buffer": layout_declare_buffer,
501557
"layout_declare_image": layout_declare_image,
502558
"layout_declare_sampler": layout_declare_sampler,

backends/vulkan/runtime/graph/ops/glsl/binary_op_defs.glslh

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,44 @@
1919
// - Uses standard pow() for x > 0
2020
//
2121

22+
// Operands are evaluated in the promoted compute type COMPUTE_T (and its vector
23+
// form COMPUTE_VEC4_T), which the including shader sets from
24+
// get_higher_precision_dtype(DTYPE, SCALAR_VALUE_TYPE) so mixed tensor/scalar
25+
// dtype combinations compute without loss before narrowing back to the output
26+
// dtype.
27+
2228
// Scalar overload
23-
T power_of(T x, T y) {
29+
COMPUTE_T power_of(COMPUTE_T x, COMPUTE_T y) {
2430
if (x == 0.0) {
2531
// Handle 0^y: 0^0 = 1, 0^y = 0 for y > 0
26-
return (y == 0.0) ? T(1.0) : T(0.0);
32+
return (y == 0.0) ? COMPUTE_T(1.0) : COMPUTE_T(0.0);
2733
}
2834

2935
// Use absolute value to avoid undefined behavior
30-
float result = pow(abs(x), y);
36+
float result = pow(abs(float(x)), float(y));
3137

3238
// For negative bases with odd integer exponents, preserve the negative sign
3339
if (x < 0.0) {
34-
float int_y = round(y);
35-
if (abs(y - int_y) < 1e-5 && int(int_y) % 2 == 1) {
40+
float int_y = round(float(y));
41+
if (abs(float(y) - int_y) < 1e-5 && int(int_y) % 2 == 1) {
3642
result = -result;
3743
}
3844
}
3945

40-
return T(result);
46+
return COMPUTE_T(result);
4147
}
4248

43-
#ifdef VEC4_T
49+
#ifdef COMPUTE_VEC4_T
4450

4551
// Vector overload
46-
VEC4_T power_of(VEC4_T x, VEC4_T y) {
47-
VEC4_T result;
52+
COMPUTE_VEC4_T power_of(COMPUTE_VEC4_T x, COMPUTE_VEC4_T y) {
53+
COMPUTE_VEC4_T result;
4854
for (int i = 0; i < 4; i++) {
4955
result[i] = power_of(x[i], y[i]);
5056
}
5157
return result;
5258
}
5359

54-
#endif // VEC4_T
60+
#endif // COMPUTE_VEC4_T
5561

5662
#endif // BINARY_OP_DEFS_GLSLH

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,32 @@
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

15+
$PROMOTED_DTYPE = get_higher_precision_dtype(DTYPE, SCALAR_VALUE_TYPE)
16+
1117
${define_required_extensions(STORAGE, DTYPE)}
18+
${define_required_extensions(STORAGE, PROMOTED_DTYPE)}
19+
${define_explicit_type_extensions(SCALAR_VALUE_TYPE)}
20+
${define_explicit_type_extensions(PROMOTED_DTYPE)}
21+
$if IS_COMPARISON_OP:
22+
${define_required_extensions(STORAGE, "uint8")}
1223

1324
#define PRECISION ${PRECISION}
1425

1526
#define NAME ${VARIANT_NAME}
1627

1728
#define T ${buffer_scalar_type(DTYPE)}
29+
#define SCALAR_T ${buffer_scalar_type(SCALAR_VALUE_TYPE)}
30+
#define COMPUTE_T ${buffer_scalar_type(PROMOTED_DTYPE)}
31+
$if IS_COMPARISON_OP:
32+
#define OUT_T ${buffer_scalar_type("uint8")}
33+
$else:
34+
#define OUT_T ${buffer_scalar_type(DTYPE)}
1835

1936
#define op(X, Y) ${OPERATOR}
2037

@@ -24,25 +41,31 @@ layout(std430) buffer;
2441

2542
#include "indexing.glslh"
2643

27-
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
44+
$if IS_COMPARISON_OP:
45+
${layout_declare_tensor(B, "w", "t_out", "uint8", STORAGE)}
46+
$else:
47+
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
48+
2849
${layout_declare_tensor(B, "r", "t_in", DTYPE, STORAGE)}
2950

3051
${layout_declare_ubo(B, "BufferMetadata", "outp")}
3152
${layout_declare_ubo(B, "BufferMetadata", "inp")}
3253

3354
layout(push_constant) uniform restrict Block {
34-
float scalar_value;
55+
SCALAR_T scalar_value;
3556
};
3657

3758
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3859

39-
#include "binary_op_defs.glslh"
60+
$if not IS_COMPARISON_OP:
61+
#include "binary_op_defs.glslh"
4062

4163
void main() {
4264
const uint out_bufi = gl_GlobalInvocationID.x;
4365
if (out_of_bounds(out_bufi, outp)) {
4466
return;
4567
}
4668

47-
t_out[out_bufi] = T(op(t_in[out_bufi], T(scalar_value)));
69+
t_out[out_bufi] =
70+
OUT_T(op(COMPUTE_T(t_in[out_bufi]), COMPUTE_T(scalar_value)));
4871
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
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]
21+
- parameter_values: [int32, float]
1922
shader_variants:
2023
- NAME: pow_scalar_buffer
24+
- NAME: eq_scalar_buffer
25+
OPERATOR: X == Y
26+
IS_COMPARISON_OP: true

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,34 @@
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

15+
$PROMOTED_DTYPE = get_higher_precision_dtype(DTYPE, SCALAR_VALUE_TYPE)
16+
1117
${define_required_extensions(STORAGE, DTYPE)}
18+
${define_required_extensions(STORAGE, PROMOTED_DTYPE)}
19+
${define_explicit_type_extensions(SCALAR_VALUE_TYPE)}
20+
${define_explicit_type_extensions(PROMOTED_DTYPE)}
21+
$if IS_COMPARISON_OP:
22+
${define_required_extensions(STORAGE, "uint8")}
1223

1324
#define PRECISION ${PRECISION}
1425

1526
#define NAME ${VARIANT_NAME}
1627

1728
#define VEC4_T ${texel_load_type(DTYPE, STORAGE)}
1829
#define T ${texel_load_component_type(DTYPE, STORAGE)}
30+
#define SCALAR_T ${buffer_scalar_type(SCALAR_VALUE_TYPE)}
31+
#define COMPUTE_VEC4_T ${texel_load_type(PROMOTED_DTYPE, STORAGE)}
32+
#define COMPUTE_T ${texel_load_component_type(PROMOTED_DTYPE, STORAGE)}
33+
$if IS_COMPARISON_OP:
34+
#define VEC4_OUT_T ${texel_load_type("uint8", STORAGE)}
35+
$else:
36+
#define VEC4_OUT_T VEC4_T
1937

2038
#define op(X, Y) ${OPERATOR}
2139

@@ -25,19 +43,24 @@ layout(std430) buffer;
2543

2644
#include "indexing.glslh"
2745

28-
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
46+
$if IS_COMPARISON_OP:
47+
${layout_declare_tensor(B, "w", "t_out", "uint8", STORAGE)}
48+
$else:
49+
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
50+
2951
${layout_declare_tensor(B, "r", "t_in", DTYPE, STORAGE)}
3052

3153
${layout_declare_ubo(B, "TextureMetadata", "outp")}
3254
${layout_declare_ubo(B, "TextureMetadata", "inp")}
3355

3456
layout(push_constant) uniform restrict Block {
35-
float scalar_value;
57+
SCALAR_T scalar_value;
3658
};
3759

3860
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3961

40-
#include "binary_op_defs.glslh"
62+
$if not IS_COMPARISON_OP:
63+
#include "binary_op_defs.glslh"
4164

4265
void main() {
4366
const ivec3 pos = ivec3(gl_GlobalInvocationID);
@@ -47,7 +70,8 @@ void main() {
4770
}
4871

4972
VEC4_T in_texel = texelFetch(t_in, pos, 0);
50-
VEC4_T out_texel = VEC4_T(op(in_texel, VEC4_T(scalar_value)));
73+
VEC4_OUT_T out_texel = VEC4_OUT_T(
74+
op(COMPUTE_VEC4_T(in_texel), COMPUTE_VEC4_T(scalar_value)));
5175

5276
imageStore(t_out, pos, out_texel);
5377
}

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
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]
21+
- parameter_values: [int32, float]
1922
shader_variants:
2023
- NAME: pow_scalar_texture3d
24+
- NAME: eq_scalar_texture3d
25+
OPERATOR: equal(X, Y)
26+
IS_COMPARISON_OP: true

0 commit comments

Comments
 (0)