Skip to content

Commit 1f517e1

Browse files
authored
[Vulkan] Pass typed fill values to full and make it resizable (pytorch#23250)
The full shaders always received the fill value as a float, which loses int32 fills above 2^24 and is ill-defined for bools. Full.cpp now passes the fill value in the output's accumulator type. Both full shaders explicitly declare the matching fill UBO type for each output variant, independent of the accumulator-type helper. full, full_like, zeros, ones and their _like variants are marked resizable so they can live inside dynamic-shape delegates, and fill values the output dtype cannot represent stay on CPU. Part 11/15 of the Vulkan transformer and operator-conformance stack. Depends on pytorch#23249; review against the selected base branch. Integration PR: pytorch#23254. Validation: The earlier MoltenVK/portable-kernel run had 6 passes and 2 warnings in 113.26s (0:01:53). For this follow-up, all 8 full shader variants compiled with glslc; commit-hook lint and git diff --check pass. Native tests have not been rerun since this edit; hardware and SwiftShader CI are pending. Recreates pytorch#23211 through ghstack. Prior review discussion remains on that PR. Authored with OpenAI Codex; split planned with Claude Code. cc @SS-JIA @manuelcandales @digantdesai @cbilgin
1 parent 8f67ff7 commit 1f517e1

5 files changed

Lines changed: 194 additions & 4 deletions

File tree

‎backends/vulkan/op_registry.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,10 @@ def register_full_cpp_ops():
16121612
return OpFeatures(
16131613
inputs_storage=utils.ANY_STORAGE,
16141614
inputs_dtypes=utils.FP_INT_BOOL_T,
1615+
supports_resize=True,
1616+
are_node_inputs_supported_fn=lambda node: node.target
1617+
not in (exir_ops.edge.aten.full.default, exir_ops.edge.aten.full_like.default)
1618+
or is_scalar_value_supported(node.args[1], node.meta["val"].dtype),
16151619
)
16161620

16171621

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ ${define_required_extensions("buffer", DTYPE)}
1313
#define PRECISION ${PRECISION}
1414

1515
#define T ${buffer_scalar_type(DTYPE)}
16+
$if DTYPE == "half" or DTYPE == "float":
17+
#define FILL_DTYPE float
18+
$elif DTYPE == "int32":
19+
#define FILL_DTYPE int
20+
$elif DTYPE == "uint8":
21+
#define FILL_DTYPE uint
22+
$else:
23+
#error Unsupported full dtype
1624

1725
${define_active_storage_type("buffer")}
1826

@@ -22,7 +30,7 @@ layout(std430) buffer;
2230

2331
${layout_declare_tensor(B, "w", "t_out", DTYPE, "buffer")}
2432
${layout_declare_ubo(B, "BufferMetadata", "outp")}
25-
${layout_declare_ubo(B, "float", "fill_value")}
33+
${layout_declare_ubo(B, "FILL_DTYPE", "fill_value")}
2634

2735
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
2836

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,25 @@ ${define_required_extensions("texture3d", DTYPE)}
1313
#define PRECISION ${PRECISION}
1414

1515
#define VEC4_T ${texel_load_type(DTYPE, "texture3d")}
16+
$if DTYPE == "half" or DTYPE == "float":
17+
#define FILL_DTYPE float
18+
$elif DTYPE == "int32":
19+
#define FILL_DTYPE int
20+
$elif DTYPE == "uint8":
21+
#define FILL_DTYPE uint
22+
$else:
23+
#error Unsupported full dtype
1624

1725
${define_active_storage_type("texture3d")}
1826

1927
layout(std430) buffer;
2028

2129
#include "indexing.glslh"
30+
#include "convert.glslh"
2231

2332
${layout_declare_tensor(B, "w", "t_out", DTYPE, "texture3d")}
2433
${layout_declare_ubo(B, "TextureMetadata", "outp")}
25-
${layout_declare_ubo(B, "float", "fill_value")}
34+
${layout_declare_ubo(B, "FILL_DTYPE", "fill_value")}
2635

2736
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
2837

@@ -37,6 +46,8 @@ void main() {
3746
}
3847

3948
VEC4_T outtex = VEC4_T(fill_value);
49+
$if DTYPE == "half":
50+
outtex = round_to_half_rte(outtex);
4051

4152
TensorIndex4D tidx =
4253
texture_pos_to_tensor4d_idx_simple(outp, pos, out_layout);

‎backends/vulkan/runtime/graph/ops/impl/Full.cpp‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ void add_full_node(
3636
const ValueRef size_or_in,
3737
const ValueRef fill_value,
3838
const ValueRef out) {
39-
float fill_value_val = graph.extract_scalar<float>(fill_value);
39+
vkapi::BufferBindInfo fill_value_buffer;
40+
if (graph.dtype_of(out) == vkapi::kInt) {
41+
fill_value_buffer =
42+
graph.create_params_buffer(graph.extract_scalar<int32_t>(fill_value));
43+
} else if (graph.dtype_of(out) == vkapi::kBool) {
44+
const uint32_t value = graph.extract_scalar<bool>(fill_value);
45+
fill_value_buffer = graph.create_params_buffer(value);
46+
} else {
47+
fill_value_buffer =
48+
graph.create_params_buffer(graph.extract_scalar<float>(fill_value));
49+
}
4050

4151
std::string kernel_name("full");
4252
kernel_name.reserve(kShaderNameReserve);
@@ -52,7 +62,7 @@ void add_full_node(
5262
// Inputs and Outputs
5363
{{out, vkapi::kWrite}},
5464
// Shader params buffers
55-
{graph.meta_ubo(out), graph.create_params_buffer(fill_value_val)},
65+
{graph.meta_ubo(out), fill_value_buffer},
5666
// Push Constants
5767
{},
5868
// Specialization Constants

‎backends/vulkan/test/test_vulkan_dynamic.py‎

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,27 @@ def forward(self, x):
233233
edge = self._lower(model, inputs[0])
234234
self._run(edge, model, inputs, atol=0, rtol=0)
235235

236+
def test_dynamic_full(self):
237+
class Full(torch.nn.Module):
238+
def forward(self, x):
239+
return (
240+
torch.full(x.shape, 2.5),
241+
torch.zeros(x.shape),
242+
torch.ones(x.shape),
243+
torch.full_like(x, -1.5),
244+
torch.zeros_like(x),
245+
torch.ones_like(x),
246+
)
247+
248+
for storage in (VkStorageType.TEXTURE_3D, VkStorageType.BUFFER):
249+
with self.subTest(storage=storage):
250+
model = Full()
251+
inputs = [(torch.randn(2, s, 3),) for s in (16, 3, 31, 2, 16)]
252+
edge = self._lower(
253+
model, inputs[0], ({1: Dim("s", min=2, max=32)},), storage
254+
)
255+
self._run(edge, model, inputs, atol=0, rtol=0)
256+
236257
def test_dynamic_logical_not(self):
237258
class LogicalNot(torch.nn.Module):
238259
def forward(self, x):
@@ -308,6 +329,29 @@ def forward(self, x):
308329
edge = self._lower(model, inputs[0], fully_delegated=False)
309330
self._run(edge, model, inputs, atol=0, rtol=0, equal_nan=True)
310331

332+
def test_integer_fill_values(self):
333+
class IntegerFill(torch.nn.Module):
334+
def __init__(self, dtype):
335+
super().__init__()
336+
self.dtype = dtype
337+
338+
def forward(self, x):
339+
return (
340+
torch.full(x.shape, 16777217, dtype=self.dtype),
341+
torch.full_like(x, -(2**31), dtype=self.dtype),
342+
torch.full(x.shape, 2**31 - 1, dtype=self.dtype),
343+
)
344+
345+
inputs = [(torch.randn(2, s, 3),) for s in (7, 2, 15, 7)]
346+
for dtype in (torch.int32, torch.int64):
347+
for storage in (VkStorageType.TEXTURE_3D, VkStorageType.BUFFER):
348+
with self.subTest(dtype=dtype, storage=storage):
349+
model = IntegerFill(dtype)
350+
edge = self._lower(
351+
model, inputs[0], ({1: Dim("s", min=2, max=16)},), storage
352+
)
353+
self._run(edge, model, inputs, atol=0, rtol=0)
354+
311355
def test_integer_scalar_range_fallback(self):
312356
class LargeScalar(torch.nn.Module):
313357
def __init__(self, kind, value):
@@ -367,6 +411,85 @@ def forward(self, x):
367411
)
368412
self._run(edge, model, inputs, atol=0, rtol=0)
369413

414+
def test_integer_factories_without_downcasting(self):
415+
class IntegerFactories(torch.nn.Module):
416+
def __init__(self, dtype):
417+
super().__init__()
418+
self.dtype = dtype
419+
420+
def forward(self, x):
421+
return (
422+
torch.scalar_tensor(16777217, dtype=self.dtype),
423+
torch.full(x.shape, 2**31 - 1, dtype=self.dtype),
424+
torch.full_like(x, -(2**31), dtype=self.dtype),
425+
)
426+
427+
inputs = [(torch.zeros(3, s),) for s in (7, 2, 15, 7)]
428+
for dtype in (torch.int32, torch.int64):
429+
for downcast in (True, False):
430+
with self.subTest(dtype=dtype, downcast=downcast):
431+
model = IntegerFactories(dtype)
432+
delegated = dtype == torch.int32 or downcast
433+
edge = self._lower(
434+
model,
435+
inputs[0],
436+
({1: Dim("s", min=2, max=16)},),
437+
fully_delegated=delegated,
438+
downcast_64_bit=downcast,
439+
)
440+
self.assertEqual(bool(_vulkan_graphs(edge)), delegated)
441+
self._run(edge, model, inputs, atol=0, rtol=0)
442+
443+
def test_64_bit_inputs_without_downcasting(self):
444+
class Input64Bit(torch.nn.Module):
445+
def forward(self, x):
446+
return (
447+
x + x,
448+
torch.full_like(x, 3, dtype=torch.int32),
449+
torch.ones(x.shape, dtype=torch.float32),
450+
)
451+
452+
model = Input64Bit()
453+
for dtype in (torch.int64, torch.float64):
454+
with self.subTest(dtype=dtype):
455+
inputs = [
456+
(torch.arange(3 * s, dtype=dtype).reshape(3, s),)
457+
for s in (7, 2, 15, 7)
458+
]
459+
edge = self._lower(
460+
model,
461+
inputs[0],
462+
({1: Dim("s", min=2, max=16)},),
463+
fully_delegated=False,
464+
downcast_64_bit=False,
465+
)
466+
graphs = _vulkan_graphs(edge)
467+
self.assertTrue(graphs)
468+
for graph in graphs:
469+
for value in graph.values:
470+
if isinstance(value.value, VkTensor):
471+
self.assertNotIn(
472+
value.value.datatype,
473+
(VkDataType.INT64, VkDataType.FLOAT64),
474+
)
475+
self._run(edge, model, inputs, atol=0, rtol=0)
476+
477+
def test_bool_fill_values(self):
478+
class BoolFill(torch.nn.Module):
479+
def forward(self, x):
480+
return (
481+
torch.full_like(x, 0.5, dtype=torch.bool),
482+
torch.full_like(x, -1.5, dtype=torch.bool),
483+
torch.full(x.shape, 0, dtype=torch.bool),
484+
)
485+
486+
model = BoolFill()
487+
inputs = [(torch.zeros(3, 7),)]
488+
for storage in (VkStorageType.TEXTURE_3D, VkStorageType.BUFFER):
489+
with self.subTest(storage=storage):
490+
edge = self._lower(model, inputs[0], storage=storage)
491+
self._run(edge, model, inputs, atol=0, rtol=0)
492+
370493
def test_64_bit_fusion_inputs_without_downcasting(self):
371494
class SelectScalar(torch.nn.Module):
372495
def __init__(self, narrow):
@@ -636,6 +759,40 @@ def forward(self, x):
636759
self.assertEqual(_vulkan_graphs(edge), [])
637760
self._run(edge, model, [(x,)])
638761

762+
def test_fp16_scalar_rounding(self):
763+
class CreateTensor(torch.nn.Module):
764+
def __init__(self, value, scalar):
765+
super().__init__()
766+
self.value = value
767+
self.scalar = scalar
768+
769+
def forward(self, x):
770+
if self.scalar:
771+
return torch.scalar_tensor(self.value, dtype=x.dtype)
772+
return torch.full_like(x, self.value)
773+
774+
x = torch.ones(3, 7, dtype=torch.float16)
775+
for value in (
776+
0.3,
777+
-1.00075,
778+
2**-24,
779+
-(2**-24),
780+
2**-25,
781+
3 * 2**-25,
782+
65519.0,
783+
65520.0,
784+
1e5,
785+
-1e5,
786+
):
787+
for scalar in (False, True):
788+
if not scalar and abs(value) > 65504:
789+
continue
790+
for storage in (VkStorageType.TEXTURE_3D, VkStorageType.BUFFER):
791+
with self.subTest(value=value, scalar=scalar, storage=storage):
792+
model = CreateTensor(value, scalar)
793+
edge = self._lower(model, (x,), storage=storage)
794+
self._run(edge, model, [(x,)], atol=0, rtol=0)
795+
639796
def test_int32_buffer_reduction_shader_range(self):
640797
from executorch.extension.pybindings.portable_lib import (
641798
_load_for_executorch_from_buffer,

0 commit comments

Comments
 (0)