Skip to content

Commit 2c1da32

Browse files
committed
[ET-VK][ops] Extend arange, clamp, and index.Tensor support
Pull Request resolved: pytorch#22481 Support runtime-varying `arange` bounds, fractional and negative steps, and symbolic `clamp` bounds for integer and floating-point tensors. Extend buffer `index.Tensor` to gather on any axis while preserving multidimensional index shapes. Authored with Codex. ghstack-source-id: 423931774 @exported-using-ghexport Differential Revision: [D118507314](https://our.internmc.facebook.com/intern/diff/D118507314/)
1 parent 7e9ac9b commit 2c1da32

11 files changed

Lines changed: 398 additions & 119 deletions

File tree

‎backends/vulkan/op_registry.py‎

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,58 +1435,56 @@ def register_where():
14351435
# =============================================================================
14361436

14371437

1438-
@update_features(exir_ops.edge.aten.index.Tensor)
1439-
def register_index_tensor():
1440-
def _index_tensor_shapes(node: torch.fx.Node):
1441-
"""(self_val, index_val) for the supported single-index form, else None."""
1442-
self_arg = node.args[0]
1443-
indices = node.args[1]
1444-
1445-
if not isinstance(self_arg, torch.fx.Node):
1446-
return None
1447-
self_val = self_arg.meta.get("val", None)
1448-
if self_val is None:
1449-
return None
1450-
1451-
# Only support exactly one non-None index tensor, applied to dim 0.
1452-
if not isinstance(indices, (list, tuple)):
1453-
return None
1454-
non_none = [idx for idx in indices if idx is not None]
1455-
if len(non_none) != 1 or indices[0] is None:
1456-
return None
1457-
index_arg = non_none[0]
1458-
if not isinstance(index_arg, torch.fx.Node):
1459-
return None
1460-
index_val = index_arg.meta.get("val", None)
1461-
if index_val is None:
1462-
return None
1438+
def _index_tensor_shapes(node: torch.fx.Node):
1439+
"""Return self, index, and axis for the supported form, else None."""
1440+
self_arg = node.args[0]
1441+
indices = node.args[1]
1442+
1443+
if not isinstance(self_arg, torch.fx.Node):
1444+
return None
1445+
self_val = self_arg.meta.get("val", None)
1446+
if self_val is None or not isinstance(indices, (list, tuple)):
1447+
return None
1448+
1449+
non_none = [(dim, index) for dim, index in enumerate(indices) if index is not None]
1450+
if len(non_none) != 1:
1451+
return None
1452+
index_dim, index_arg = non_none[0]
1453+
if index_dim >= len(self_val.size()) or not isinstance(index_arg, torch.fx.Node):
1454+
return None
1455+
index_val = index_arg.meta.get("val", None)
1456+
if index_val is None:
1457+
return None
1458+
1459+
return self_val, index_val, index_dim
1460+
1461+
1462+
def _check_index_tensor_node(node: torch.fx.Node) -> bool:
1463+
shapes = _index_tensor_shapes(node)
1464+
if shapes is None:
1465+
return False
1466+
_, index_val, _ = shapes
1467+
# The gather is expressed as "one index position per output slice", so
1468+
# the index must be 1-D. `self` may be any rank.
1469+
return len(index_val.size()) == 1
14631470

1464-
return self_val, index_val
14651471

1466-
def check_index_tensor_node(node: torch.fx.Node) -> bool:
1467-
shapes = _index_tensor_shapes(node)
1468-
if shapes is None:
1469-
return False
1470-
_, index_val = shapes
1471-
# The gather is expressed as "one index position per output slice", so
1472-
# the index must be 1-D. `self` may be any rank: the buffer shader
1473-
# copies self's trailing dims through unchanged.
1474-
return len(index_val.size()) == 1
1472+
def _pick_index_tensor_storage(node: torch.fx.Node):
1473+
shapes = _index_tensor_shapes(node)
1474+
# Only the buffer shader handles a higher-rank `self`.
1475+
if shapes is not None and len(shapes[0].size()) > 1:
1476+
return utils.CONTIGUOUS_BUFFER, utils.CONTIGUOUS_BUFFER
1477+
return utils.ANY_STORAGE, utils.ANY_STORAGE
14751478

1476-
def pick_index_tensor_storage(node: torch.fx.Node):
1477-
shapes = _index_tensor_shapes(node)
1478-
# Only the buffer shader handles a higher-rank `self`; the texture
1479-
# variant still assumes the 1-D form (it reads self[idx, 0, 0, 0]).
1480-
if shapes is not None and len(shapes[0].size()) > 1:
1481-
return utils.CONTIGUOUS_BUFFER, utils.CONTIGUOUS_BUFFER
1482-
return utils.ANY_STORAGE, utils.ANY_STORAGE
14831479

1480+
@update_features(exir_ops.edge.aten.index.Tensor)
1481+
def register_index_tensor():
14841482
return OpFeatures(
14851483
inputs_storage=utils.ANY_STORAGE,
14861484
inputs_dtypes=utils.FP_INT_T,
14871485
supports_resize=True,
1488-
are_node_inputs_supported_fn=check_index_tensor_node,
1489-
pick_io_storage_fn=pick_index_tensor_storage,
1486+
are_node_inputs_supported_fn=_check_index_tensor_node,
1487+
pick_io_storage_fn=_pick_index_tensor_storage,
14901488
)
14911489

14921490

@@ -1500,6 +1498,7 @@ def register_arange():
15001498
return OpFeatures(
15011499
inputs_storage=utils.ANY_STORAGE,
15021500
inputs_dtypes=utils.FP_INT_T,
1501+
supports_resize=True,
15031502
)
15041503

15051504

‎backends/vulkan/runtime/graph/ops/glsl/arange_buffer.glsl‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,28 @@ layout(std430) buffer;
2323
${layout_declare_tensor(B, "w", "t_out", DTYPE, "buffer")}
2424

2525
${layout_declare_ubo(B, "BufferMetadata", "outp")}
26-
${layout_declare_ubo(B, "float", "start")}
27-
${layout_declare_ubo(B, "float", "step")}
26+
${layout_declare_ubo(B, "uint", "start")}
27+
${layout_declare_ubo(B, "uint", "step")}
28+
29+
layout(push_constant) uniform restrict Block {
30+
ivec2 params_are_int;
31+
};
2832

2933
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3034

3135
#include "dispatch.glslh"
3236

37+
float decode_param(const uint value, const int is_int) {
38+
return is_int != 0 ? float(int(value)) : uintBitsToFloat(value);
39+
}
40+
3341
void main() {
3442
const uint out_bufi = linear_idx_from_gid();
3543
if (out_of_bounds(out_bufi, outp)) {
3644
return;
3745
}
3846

39-
t_out[out_bufi] = T(start + out_bufi * step);
47+
const float start_val = decode_param(start, params_are_int.x);
48+
const float step_val = decode_param(step, params_are_int.y);
49+
t_out[out_bufi] = T(start_val + out_bufi * step_val);
4050
}

‎backends/vulkan/runtime/graph/ops/glsl/arange_texture.glsl‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,22 @@ layout(std430) buffer;
2323
${layout_declare_tensor(B, "w", "t_out", DTYPE, "texture3d")}
2424

2525
${layout_declare_ubo(B, "TextureMetadata", "outp")}
26-
${layout_declare_ubo(B, "float", "start")}
27-
${layout_declare_ubo(B, "float", "step")}
26+
${layout_declare_ubo(B, "uint", "start")}
27+
${layout_declare_ubo(B, "uint", "step")}
28+
29+
layout(push_constant) uniform restrict Block {
30+
ivec2 params_are_int;
31+
};
2832

2933
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3034

3135
${layout_declare_spec_const(C, "int", "out_layout", "CONTIG_LAYOUT_INT")}
3236
const int packed_dim = get_packed_dim(out_layout);
3337

38+
float decode_param(const uint value, const int is_int) {
39+
return is_int != 0 ? float(int(value)) : uintBitsToFloat(value);
40+
}
41+
3442
void main() {
3543
const ivec3 out_pos = ivec3(gl_GlobalInvocationID);
3644

@@ -44,11 +52,13 @@ void main() {
4452
// arange output is 1D, so the W dimension holds the element index.
4553
// Compute the value for each element in the texel along the packed dim.
4654
VEC4_T outtex = VEC4_T(0);
55+
const float start_val = decode_param(start, params_are_int.x);
56+
const float step_val = decode_param(step, params_are_int.y);
4757
int limit = min(
4858
4, safe_idx(outp.sizes, packed_dim) - out_tidx.data[packed_dim]);
4959
for (int comp = 0; comp < limit; comp++) {
5060
int elem_idx = out_tidx.data[0]; // W index is the linear element index
51-
outtex[comp] = VEC4_T(start + elem_idx * step).x;
61+
outtex[comp] = VEC4_T(start_val + elem_idx * step_val).x;
5262
out_tidx.data[packed_dim]++;
5363
}
5464

‎backends/vulkan/runtime/graph/ops/glsl/index_tensor_buffer.glsl‎

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#version 450 core
1010

1111
${define_required_extensions("buffer", DTYPE)}
12+
${define_required_extensions(INDEX_STORAGE, "int")}
1213

1314
#define PRECISION ${PRECISION}
1415

@@ -22,19 +23,68 @@ layout(std430) buffer;
2223

2324
${layout_declare_tensor(B, "w", "t_out", DTYPE, "buffer")}
2425
${layout_declare_tensor(B, "r", "t_self", DTYPE, "buffer")}
25-
${layout_declare_tensor(B, "r", "t_index", "int", "buffer")}
26+
${layout_declare_tensor(B, "r", "t_index", "int", INDEX_STORAGE)}
2627

2728
${layout_declare_ubo(B, "BufferMetadata", "outp")}
2829
${layout_declare_ubo(B, "BufferMetadata", "inp")}
29-
${layout_declare_ubo(B, "BufferMetadata", "index")}
30+
$if INDEX_STORAGE == "buffer":
31+
${layout_declare_ubo(B, "BufferMetadata", "index")}
32+
$else:
33+
${layout_declare_ubo(B, "TextureMetadata", "index")}
34+
35+
layout(push_constant) uniform restrict Block {
36+
ivec2 index_params;
37+
};
3038

3139
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3240

3341
#include "dispatch.glslh"
3442

35-
// Implements aten.index.Tensor for the case where self is 1D and there is
36-
// exactly one index tensor. Each output element is:
43+
// Implements aten.index.Tensor with exactly one index tensor. Each output
44+
// element is:
3745
// output[...] = self[index[...]]
46+
${layout_declare_spec_const(C, "int", "out_layout", "CONTIG_LAYOUT_INT")}
47+
${layout_declare_spec_const(C, "int", "inp_layout", "CONTIG_LAYOUT_INT")}
48+
${layout_declare_spec_const(C, "int", "index_layout", "CONTIG_LAYOUT_INT")}
49+
50+
int load_index(const TensorIndex out_tidx) {
51+
$if INDEX_STORAGE == "buffer":
52+
uint index_bufi = 0;
53+
for (int d = 0; d < index_params.y; ++d) {
54+
index_bufi +=
55+
stride_at(index, d) * idx_at(out_tidx, index_params.x + d);
56+
}
57+
return t_index[index_bufi];
58+
$else:
59+
TensorIndex4D index_tidx = zero_tensor4d_idx();
60+
index_tidx.data.x = int(idx_at(out_tidx, index_params.x));
61+
if (index_params.y > 1) {
62+
index_tidx.data.y = int(idx_at(out_tidx, index_params.x + 1));
63+
}
64+
if (index_params.y > 2) {
65+
index_tidx.data.z = int(idx_at(out_tidx, index_params.x + 2));
66+
}
67+
if (index_params.y > 3) {
68+
index_tidx.data.w = int(idx_at(out_tidx, index_params.x + 3));
69+
}
70+
const TextureElementIndex index_elem =
71+
tensor4d_idx_to_texture_element_idx_simple(
72+
index, index_tidx, index_layout);
73+
return texelFetch(t_index, index_elem.pos, 0)[index_elem.comp];
74+
}
75+
76+
uint self_idx_at(
77+
const TensorIndex out_tidx,
78+
const int self_axis,
79+
const uint index_value) {
80+
if (self_axis == index_params.x) {
81+
return index_value;
82+
}
83+
const int out_axis = self_axis < index_params.x
84+
? self_axis
85+
: self_axis + index_params.y - 1;
86+
return idx_at(out_tidx, out_axis);
87+
}
3888

3989
void main() {
4090
const uint out_bufi = linear_idx_from_gid();
@@ -45,22 +95,20 @@ void main() {
4595
// Convert output buffer index to tensor index
4696
TensorIndex out_tidx = linear_idx_to_tensor_idx(outp, out_bufi);
4797

48-
const uint self_rank = ndim(inp);
49-
const uint index_rank = ndim(index);
50-
// WHCN order places self's trailing axes before the index axes.
51-
const uint index_axis_offset = self_rank - 1;
52-
53-
uint index_bufi = 0;
54-
for (uint d = 0; d < index_rank; ++d) {
55-
index_bufi +=
56-
stride_at(index, d) * idx_at(out_tidx, index_axis_offset + d);
57-
}
58-
const int idx = t_index[index_bufi];
59-
60-
uint self_bufi = stride_at(inp, self_rank - 1) * uint(idx);
61-
for (uint d = 0; d + 1 < self_rank; ++d) {
62-
self_bufi += stride_at(inp, d) * idx_at(out_tidx, d);
63-
}
98+
const int idx = load_index(out_tidx);
99+
100+
TensorIndex self_tidx;
101+
initialize(self_tidx);
102+
const int self_rank = int_ndim(inp);
103+
if (self_rank > 0) self_tidx.data[0].x = self_idx_at(out_tidx, 0, uint(idx));
104+
if (self_rank > 1) self_tidx.data[0].y = self_idx_at(out_tidx, 1, uint(idx));
105+
if (self_rank > 2) self_tidx.data[0].z = self_idx_at(out_tidx, 2, uint(idx));
106+
if (self_rank > 3) self_tidx.data[0].w = self_idx_at(out_tidx, 3, uint(idx));
107+
if (self_rank > 4) self_tidx.data[1].x = self_idx_at(out_tidx, 4, uint(idx));
108+
if (self_rank > 5) self_tidx.data[1].y = self_idx_at(out_tidx, 5, uint(idx));
109+
if (self_rank > 6) self_tidx.data[1].z = self_idx_at(out_tidx, 6, uint(idx));
110+
if (self_rank > 7) self_tidx.data[1].w = self_idx_at(out_tidx, 7, uint(idx));
111+
const uint self_bufi = tensor_idx_to_linear_idx(inp, self_tidx);
64112

65113
t_out[out_bufi] = t_self[self_bufi];
66114
}

‎backends/vulkan/runtime/graph/ops/glsl/index_tensor_buffer.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ index_tensor_buffer:
88
parameter_names_with_default_values:
99
DTYPE: float
1010
STORAGE: buffer
11+
INDEX_STORAGE: buffer
1112
generate_variant_forall:
13+
INDEX_STORAGE:
14+
- VALUE: buffer
15+
- VALUE: texture3d
1216
DTYPE:
1317
- VALUE: half
1418
- VALUE: float

‎backends/vulkan/runtime/graph/ops/glsl/unary_op.glsl‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,35 @@ ${define_active_storage_type(STORAGE)}
2323

2424
layout(std430) buffer;
2525

26-
${layout_declare_tensor(0, "w", "t_out", DTYPE, STORAGE)}
27-
${layout_declare_tensor(1, "r", "t_in", DTYPE, STORAGE)}
26+
${layout_declare_tensor(B, "w", "t_out", DTYPE, STORAGE)}
27+
${layout_declare_tensor(B, "r", "t_in", DTYPE, STORAGE)}
28+
29+
$if DYNAMIC_PARAMS:
30+
${layout_declare_ubo(B, "uint", "minimum")}
31+
${layout_declare_ubo(B, "uint", "maximum")}
2832

2933
layout(push_constant) uniform restrict Block {
3034
$if STORAGE == "buffer":
3135
int numel;
3236
$else:
3337
ivec4 out_limits;
34-
float minimum;
35-
float maximum;
38+
$if DYNAMIC_PARAMS:
39+
ivec2 bounds_are_int;
40+
$else:
41+
float minimum;
42+
float maximum;
3643
};
3744

3845
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
3946

4047
#include "dispatch.glslh"
4148
#include "activations.h"
4249

50+
$if DYNAMIC_PARAMS:
51+
float decode_bound(const uint value, const int is_int) {
52+
return is_int != 0 ? float(int(value)) : uintBitsToFloat(value);
53+
}
54+
4355
#ifdef USING_BUFFER
4456

4557
void main() {
@@ -48,7 +60,13 @@ void main() {
4860
return;
4961
}
5062

51-
float in_val = float(t_in[i]);
63+
$if DYNAMIC_PARAMS:
64+
const T in_val = T(t_in[i]);
65+
const T minimum_val = T(decode_bound(minimum, bounds_are_int.x));
66+
const T maximum_val = T(decode_bound(maximum, bounds_are_int.y));
67+
t_out[i] = T(op(in_val, minimum_val, maximum_val));
68+
$else:
69+
const float in_val = float(t_in[i]);
5270
t_out[i] = T(op(in_val, minimum, maximum));
5371
}
5472

@@ -62,6 +80,11 @@ void main() {
6280
}
6381

6482
VEC4_T in_texel = texelFetch(t_in, pos, 0);
83+
$if DYNAMIC_PARAMS:
84+
const VEC4_T minimum_val = VEC4_T(decode_bound(minimum, bounds_are_int.x));
85+
const VEC4_T maximum_val = VEC4_T(decode_bound(maximum, bounds_are_int.y));
86+
imageStore(t_out, pos, op(in_texel, minimum_val, maximum_val));
87+
$else:
6588
imageStore(t_out, pos, VEC4_T(op(in_texel, minimum, maximum)));
6689
}
6790

‎backends/vulkan/runtime/graph/ops/glsl/unary_op.yaml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ unary_op:
33
OPERATOR: clamp(X, A, B)
44
DTYPE: float
55
STORAGE: texture3d
6+
DYNAMIC_PARAMS: false
67
generate_variant_forall:
78
DTYPE:
89
- VALUE: half
@@ -18,6 +19,13 @@ unary_op:
1819
- NAME: clamp_int32
1920
OPERATOR: clamp(X, A, B)
2021
DTYPE: int32
22+
- NAME: clamp_dynamic_int32
23+
OPERATOR: clamp(X, A, B)
24+
DTYPE: int32
25+
DYNAMIC_PARAMS: true
26+
- NAME: clamp_dynamic
27+
OPERATOR: clamp(X, A, B)
28+
DYNAMIC_PARAMS: true
2129
- NAME: cos
2230
OPERATOR: cos(X)
2331
- NAME: exp

0 commit comments

Comments
 (0)