Skip to content

Commit a84676e

Browse files
SS-JIApytorchbot
authored andcommitted
[ET-VK][ops] Consolidate binary scalar shaders behind op_type
Pull Request resolved: #24756 ## Ulterior Motive Make buffer storage behave more like texture storage, so one shader can service both storage types. Earlier commits in this stack gave unary and binary tensor-tensor ops one shader each, selected by an `op_type` specialization constant. This commit does the same for tensor-scalar ops. ## Rationale **What**: The tensor-scalar ops (`pow.Tensor_Scalar`, `mul.Scalar`, and `eq`, `ne`, `lt`, `le`, `gt`, `ge` `.Scalar`) share one arithmetic and one comparison shader variant family. The op is selected at dispatch through an `op_type` specialization constant. **Why**: Each op had its own variant family, which multiplied the number of compiled shaders by the number of ops for no change in the code each one runs. ## Details - Shader variants - **Before**: one family per op, `{op}_scalar_{storage}_{dtype}_{scalar}`, with the op inlined through an `OPERATOR` yaml parameter: 8 ops × 2 storages × 4 dtype pairs = 64 shaders. - **After**: `binary_scalar_op` (`pow`, `mul`) and `binary_scalar_cmp` (comparisons, which write uint8): 2 × 2 × 4 = 16 shaders. A host `dev` build of `etvk_ops_bench` shrinks by 514 KiB. - Op selection - `apply_binary_scalar_op(op_type, X, Y)` in `binary_scalar.glsl` switches on the spec constant, which the compiler folds. Each op computes what its `OPERATOR` expression did, in the same promoted type. - `op_type` takes `BinaryOpType` values, which gains `kNe`. Only the tensor-scalar shader implements `kNe`. - Dispatch - `add_binary_scalar_op_node` takes a `BinaryOpType` instead of an op name and picks the comparison family for every op other than `mul` and `pow`. - `test_vulkan_shader_names.py` checks that every name the tensor-scalar dispatch can build is generated. Authored with Claude Code. ghstack-source-id: 467556317 @exported-using-ghexport Differential Revision: [D130297164](https://our.internmc.facebook.com/intern/diff/D130297164/)
1 parent ae6d3bb commit a84676e

5 files changed

Lines changed: 91 additions & 64 deletions

File tree

‎backends/vulkan/runtime/graph/ops/glsl/binary/binary_scalar.glsl‎

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
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.
9+
// The op is selected by the op_type specialization constant. Comparison ops
10+
// write a bool/uint8 output dtype, which differs from the input dtype, so they
11+
// get their own variants with IS_COMPARISON_OP set.
1212

1313
#version 450 core
1414

@@ -33,9 +33,9 @@ $if IS_COMPARISON_OP:
3333
#define OUT_VEC4_T ${texel_load_type(OUT_DTYPE, STORAGE)}
3434
#define OUT_COMPUTE_VEC4_T ${texel_type(OUT_DTYPE)}
3535

36-
#define op(X, Y) ${OPERATOR}
37-
38-
$if not IS_COMPARISON_OP and DTYPE in ("float", "half"):
36+
$if IS_COMPARISON_OP:
37+
#define BINARY_SCALAR_IS_COMPARISON
38+
$elif DTYPE in ("float", "half"):
3939
#define ROUND_HALF_OUTPUT
4040

4141
${define_active_storage_type(STORAGE)}
@@ -63,12 +63,50 @@ layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
6363
layout(constant_id = 3) const bool input_is_half = false;
6464
layout(constant_id = 4) const int out_layout = CONTIG_LAYOUT_INT;
6565
const int out_packed_dim = get_packed_dim(out_layout);
66+
layout(constant_id = 5) const int op_type = 0;
6667

6768
#include "dispatch.glslh"
6869

6970
$if not IS_COMPARISON_OP:
7071
#include "binary_op_defs.glslh"
7172

73+
// Values of op_type. Must match BinaryOpType in BinaryOp.h.
74+
#define BINARY_OP_MUL 2
75+
#define BINARY_OP_POW 5
76+
#define BINARY_OP_EQ 8
77+
#define BINARY_OP_LT 9
78+
#define BINARY_OP_LE 10
79+
#define BINARY_OP_GT 11
80+
#define BINARY_OP_GE 12
81+
#define BINARY_OP_NE 15
82+
83+
OUT_COMPUTE_VEC4_T apply_binary_scalar_op(
84+
const int op_type,
85+
const COMPUTE_VEC4_T X,
86+
const COMPUTE_VEC4_T Y) {
87+
switch (op_type) {
88+
#ifdef BINARY_SCALAR_IS_COMPARISON
89+
case BINARY_OP_NE:
90+
return OUT_COMPUTE_VEC4_T(notEqual(X, Y));
91+
case BINARY_OP_LT:
92+
return OUT_COMPUTE_VEC4_T(lessThan(X, Y));
93+
case BINARY_OP_LE:
94+
return OUT_COMPUTE_VEC4_T(lessThanEqual(X, Y));
95+
case BINARY_OP_GT:
96+
return OUT_COMPUTE_VEC4_T(greaterThan(X, Y));
97+
case BINARY_OP_GE:
98+
return OUT_COMPUTE_VEC4_T(greaterThanEqual(X, Y));
99+
default: // BINARY_OP_EQ
100+
return OUT_COMPUTE_VEC4_T(equal(X, Y));
101+
#else
102+
case BINARY_OP_POW:
103+
return OUT_COMPUTE_VEC4_T(power_of(X, Y));
104+
default: // BINARY_OP_MUL
105+
return OUT_COMPUTE_VEC4_T(X * Y);
106+
#endif
107+
}
108+
}
109+
72110
void main() {
73111
#ifdef USING_BUFFER
74112
const uint out_idx = linear_idx_from_gid();
@@ -84,8 +122,8 @@ void main() {
84122
const VEC4_T in_texel = texelFetch(t_in, out_idx, 0);
85123
#endif
86124

87-
OUT_COMPUTE_VEC4_T out_texel = OUT_COMPUTE_VEC4_T(
88-
op(COMPUTE_VEC4_T(in_texel), COMPUTE_VEC4_T(scalar_value)));
125+
OUT_COMPUTE_VEC4_T out_texel = apply_binary_scalar_op(
126+
op_type, COMPUTE_VEC4_T(in_texel), COMPUTE_VEC4_T(scalar_value));
89127

90128
#ifdef ROUND_HALF_OUTPUT
91129
if (input_is_half) {

‎backends/vulkan/runtime/graph/ops/glsl/binary/binary_scalar.yaml‎

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7+
# The op is selected at dispatch through the op_type specialization constant.
78
binary_scalar:
89
parameter_names_with_default_values:
9-
OPERATOR: power_of(X, Y)
1010
IS_COMPARISON_OP: false
1111
DTYPE: float
1212
SCALAR_VALUE_TYPE: float
@@ -23,24 +23,6 @@ binary_scalar:
2323
- parameter_values: [int32, int32]
2424
- parameter_values: [int32, float]
2525
shader_variants:
26-
- NAME: pow_scalar
27-
- NAME: mul_scalar
28-
OPERATOR: X * Y
29-
- NAME: eq_scalar
30-
OPERATOR: equal(X, Y)
31-
IS_COMPARISON_OP: true
32-
- NAME: ne_scalar
33-
OPERATOR: notEqual(X, Y)
34-
IS_COMPARISON_OP: true
35-
- NAME: lt_scalar
36-
OPERATOR: lessThan(X, Y)
37-
IS_COMPARISON_OP: true
38-
- NAME: le_scalar
39-
OPERATOR: lessThanEqual(X, Y)
40-
IS_COMPARISON_OP: true
41-
- NAME: gt_scalar
42-
OPERATOR: greaterThan(X, Y)
43-
IS_COMPARISON_OP: true
44-
- NAME: ge_scalar
45-
OPERATOR: greaterThanEqual(X, Y)
26+
- NAME: binary_scalar_op
27+
- NAME: binary_scalar_cmp
4628
IS_COMPARISON_OP: true

‎backends/vulkan/runtime/graph/ops/impl/binary/BinaryOp.h‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
namespace vkcompute {
1414

15-
// Values of the op_type specialization constant of the binary op shaders. Must
16-
// match binary_op.glslh.
15+
// Values of the op_type specialization constant of the binary op and
16+
// tensor-scalar shaders. Must match binary_op.glslh and binary_scalar.glsl.
1717
enum class BinaryOpType : int32_t {
1818
kAdd = 0,
1919
kSub = 1,
@@ -30,6 +30,8 @@ enum class BinaryOpType : int32_t {
3030
kGe = 12,
3131
kBitwiseAnd = 13,
3232
kBitwiseOr = 14,
33+
// Implemented by the tensor-scalar shader only.
34+
kNe = 15,
3335
};
3436

3537
} // namespace vkcompute

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

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <executorch/backends/vulkan/runtime/graph/ops/impl/Common.h>
1212
#include <executorch/backends/vulkan/runtime/graph/ops/impl/Staging.h>
13+
#include <executorch/backends/vulkan/runtime/graph/ops/impl/binary/BinaryOp.h>
1314

1415
#include <executorch/backends/vulkan/runtime/graph/ops/impl/utils/ScalarUtils.h>
1516
#include <executorch/backends/vulkan/runtime/graph/ops/impl/utils/TensorUtils.h>
@@ -75,7 +76,7 @@ void add_binary_scalar_op_node(
7576
const ValueRef in,
7677
const ValueRef scalar,
7778
const ValueRef out,
78-
const std::string& op_name) {
79+
const BinaryOpType op_type) {
7980
ValueRef arg = prepack_standard_like(graph, in, out, true);
8081
VK_CHECK_COND(check_same_packed_dim(graph, arg, out));
8182
if (!graph.is_buffer_storage(out)) {
@@ -97,7 +98,10 @@ void add_binary_scalar_op_node(
9798
VK_THROW("Unsupported tensor-scalar op scalar dtype: ", scalar_dtype);
9899
}
99100

100-
std::string kernel_name = op_name + "_scalar";
101+
const bool is_comparison =
102+
op_type != BinaryOpType::kMul && op_type != BinaryOpType::kPow;
103+
std::string kernel_name(
104+
is_comparison ? "binary_scalar_cmp" : "binary_scalar_op");
101105
kernel_name.reserve(kShaderNameReserve);
102106
add_storage_type_suffix(kernel_name, graph.storage_type_of(out));
103107
add_dtype_suffix(kernel_name, tensor_dtype);
@@ -118,44 +122,28 @@ void add_binary_scalar_op_node(
118122
push_constants,
119123
// Specialization Constants
120124
{int32_t(graph.original_dtype_of(out) == vkapi::kHalf),
121-
graph.hashed_layout_of(out)},
125+
graph.hashed_layout_of(out),
126+
static_cast<int32_t>(op_type)},
122127
// Resize Args
123128
{},
124129
// Resizing Logic
125130
resize_binary_scalar_op_node));
126131
}
127132

128-
void pow_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
129-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "pow");
130-
}
131-
132-
void mul_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
133-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "mul");
134-
}
135-
136-
void eq_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
137-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "eq");
138-
}
139-
140-
void ne_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
141-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "ne");
142-
}
143-
144-
void lt_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
145-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "lt");
146-
}
147-
148-
void le_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
149-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "le");
150-
}
151-
152-
void gt_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
153-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "gt");
154-
}
133+
#define DEFINE_BINARY_SCALAR_OP_FN(op_name, op_type) \
134+
void op_name(ComputeGraph& graph, const std::vector<ValueRef>& args) { \
135+
return add_binary_scalar_op_node( \
136+
graph, args[0], args[1], args[2], BinaryOpType::op_type); \
137+
}
155138

156-
void ge_tensor_scalar(ComputeGraph& graph, const std::vector<ValueRef>& args) {
157-
return add_binary_scalar_op_node(graph, args[0], args[1], args[2], "ge");
158-
}
139+
DEFINE_BINARY_SCALAR_OP_FN(pow_tensor_scalar, kPow);
140+
DEFINE_BINARY_SCALAR_OP_FN(mul_tensor_scalar, kMul);
141+
DEFINE_BINARY_SCALAR_OP_FN(eq_tensor_scalar, kEq);
142+
DEFINE_BINARY_SCALAR_OP_FN(ne_tensor_scalar, kNe);
143+
DEFINE_BINARY_SCALAR_OP_FN(lt_tensor_scalar, kLt);
144+
DEFINE_BINARY_SCALAR_OP_FN(le_tensor_scalar, kLe);
145+
DEFINE_BINARY_SCALAR_OP_FN(gt_tensor_scalar, kGt);
146+
DEFINE_BINARY_SCALAR_OP_FN(ge_tensor_scalar, kGe);
159147

160148
REGISTER_OPERATORS {
161149
VK_REGISTER_OP(aten.pow.Tensor_Scalar, pow_tensor_scalar);

‎backends/vulkan/test/test_vulkan_shader_names.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444

4545
# The yaml template whose variants `add_binary_op_node` dispatches into.
4646
_BINARY_TEMPLATE = os.path.join("binary", "binary")
47+
# The yaml template whose variants `add_binary_scalar_op_node` dispatches into.
48+
_BINARY_SCALAR_TEMPLATE = os.path.join("binary", "binary_scalar")
4749

4850

4951
def _function_body(text: str, name: str) -> str:
@@ -138,6 +140,21 @@ def test_binary_variants_end_in_a_storage_and_dtype_suffix(self) -> None:
138140
"see add_binary_op_node in BinaryOp.cpp",
139141
)
140142

143+
def test_binary_scalar_shaders_cover_every_dispatch(self) -> None:
144+
"""Every name `add_binary_scalar_op_node` can build must be generated.
145+
146+
It appends a storage suffix, the tensor's dtype suffix and the scalar's
147+
dtype suffix. The scalar is read as float for float and half tensors,
148+
and as int32 or float for int32 tensors.
149+
"""
150+
dtype_pairs = ("_half_float", "_float_float", "_int32_int32", "_int32_float")
151+
for prefix in ("binary_scalar_op", "binary_scalar_cmp"):
152+
for storage in ("_buffer", "_texture3d"):
153+
for dtypes in dtype_pairs:
154+
expected = prefix + storage + dtypes
155+
with self.subTest(expected=expected):
156+
self.assertIn(expected, self.names[_BINARY_SCALAR_TEMPLATE])
157+
141158

142159
if __name__ == "__main__":
143160
unittest.main()

0 commit comments

Comments
 (0)