Skip to content

Commit 8965e51

Browse files
authored
Sym ite prim op (pytorch#20681)
Adds Python op registration, mapping entry, and C++ kernel handling with tests
1 parent 40f6d18 commit 8965e51

4 files changed

Lines changed: 131 additions & 0 deletions

File tree

exir/emit/test/test_emit.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,46 @@ def forward(self, x):
24902490
self.assertTrue(expected.shape == et_result.shape)
24912491
self.assertTrue(torch.allclose(expected, et_result))
24922492

2493+
def test_emit_sym_ite(self) -> None:
2494+
class SymIteModel(nn.Module):
2495+
def forward(self, x):
2496+
n = x.shape[0]
2497+
m = x.shape[1]
2498+
cond = n > 5
2499+
val = torch.sym_ite(cond, n, m)
2500+
return torch.zeros(val, dtype=x.dtype, device=x.device)
2501+
2502+
model = SymIteModel()
2503+
model.eval()
2504+
test_inputs = [
2505+
torch.randn(3, 6), # n<=5: ite(False,3,6)=6
2506+
torch.randn(8, 4), # n>5: ite(True,8,4)=8
2507+
]
2508+
reference_outputs = []
2509+
with torch.no_grad():
2510+
for inp in test_inputs:
2511+
reference_outputs.append(model(inp))
2512+
2513+
batch_dim = Dim("batch", min=1, max=20)
2514+
feat_dim = Dim("feat", min=1, max=20)
2515+
dynamic_shapes = {"x": {0: batch_dim, 1: feat_dim}}
2516+
exported_program = torch.export.export(
2517+
model, (test_inputs[0],), dynamic_shapes=dynamic_shapes
2518+
)
2519+
2520+
edge_program = to_edge(
2521+
exported_program,
2522+
compile_config=exir.EdgeCompileConfig(_check_ir_validity=False),
2523+
)
2524+
et_program = edge_program.to_executorch()
2525+
program_buffer = et_program.buffer
2526+
et_module = _load_for_executorch_from_buffer(program_buffer)
2527+
for inp, expected in zip(test_inputs, reference_outputs):
2528+
et_output = et_module.forward([inp])
2529+
et_result = et_output[0]
2530+
self.assertTrue(expected.shape == et_result.shape)
2531+
self.assertTrue(torch.allclose(expected, et_result))
2532+
24932533
def test_emit_channels_last_constant(self) -> None:
24942534
"""Test that channels-last constant tensors are emitted correctly.
24952535

exir/passes/executorch_prim_ops_registry.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,13 @@ def sym_not(a: _SymScalar) -> bool:
134134
return not a # pyre-ignore
135135

136136

137+
@bind_pattern_to_op(
138+
executorch_prims_lib, "sym_ite.Scalar(Scalar b, Scalar t, Scalar f) -> Scalar"
139+
)
140+
def sym_ite(b: _SymScalar, t: _SymScalar, f: _SymScalar) -> _SymScalar:
141+
return t if b else f # pyre-ignore
142+
143+
137144
_PYTHON_SYM_OPS_TO_EXECUTORCH_SYM_OPS: Dict[Any, OpOverload] = {
138145
builtins.round: ops.backend.executorch_prim.round.Scalar,
139146
math.ceil: ops.backend.executorch_prim.ceil.Scalar,
@@ -155,6 +162,7 @@ def sym_not(a: _SymScalar) -> bool:
155162
torch.sym_max: ops.backend.executorch_prim.sym_max.Scalar,
156163
torch.sym_min: ops.backend.executorch_prim.sym_min.Scalar,
157164
torch.sym_not: ops.backend.executorch_prim.sym_not.Scalar,
165+
torch.sym_ite: ops.backend.executorch_prim.sym_ite.Scalar,
158166
}
159167

160168

kernels/prim_ops/register_prim_ops.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,48 @@ static Kernel prim_ops[] = {
541541
}),
542542
#endif
543543

544+
#if !defined(EXECUTORCH_ENABLE_PRIM_OPS_SELECTIVE_BUILD) || \
545+
defined(INCLUDE_EXECUTORCH_PRIM_SYM_ITE_SCALAR)
546+
// executorch_prim::sym_ite.Scalar(bool b, Scalar t, Scalar f) -> Scalar
547+
Kernel(
548+
"executorch_prim::sym_ite.Scalar",
549+
[](KernelRuntimeContext& context, Span<EValue*> stack) {
550+
ET_KERNEL_CHECK_MSG(
551+
context,
552+
stack.size() == 4,
553+
InvalidProgram,
554+
/* void */,
555+
"Expected %zu args, got %zu",
556+
(size_t)4,
557+
stack.size());
558+
EValue& b = *stack[0];
559+
EValue& out = *stack[3];
560+
ET_KERNEL_CHECK_MSG(
561+
context,
562+
b.isBool(),
563+
InvalidType,
564+
/* void */,
565+
"sym_ite condition must be bool, got %zu",
566+
(size_t)b.tag);
567+
EValue& selected = b.toBool() ? *stack[1] : *stack[2];
568+
if (selected.isInt()) {
569+
out = EValue(selected.toInt());
570+
} else if (selected.isDouble()) {
571+
out = EValue(selected.toDouble());
572+
} else if (selected.isBool()) {
573+
out = EValue(selected.toBool());
574+
} else {
575+
ET_KERNEL_CHECK_MSG(
576+
context,
577+
false,
578+
InvalidType,
579+
/* void */,
580+
"sym_ite value must be int, double, or bool, got %zu",
581+
(size_t)selected.tag);
582+
}
583+
}),
584+
#endif
585+
544586
#if !defined(EXECUTORCH_ENABLE_PRIM_OPS_SELECTIVE_BUILD) || \
545587
defined(INCLUDE_EXECUTORCH_PRIM_FLOORDIV_INT)
546588
// executorch_prim::floordiv.int(int, int) -> int

kernels/prim_ops/test/prim_ops_test.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ TEST_F(RegisterPrimOpsTest, OpRegistered) {
4242
EXPECT_TRUE(hasOpsFn("executorch_prim::sym_min.Scalar"));
4343
EXPECT_TRUE(hasOpsFn("executorch_prim::sym_not.Scalar"));
4444
EXPECT_TRUE(hasOpsFn("executorch_prim::sym_int.Scalar"));
45+
EXPECT_TRUE(hasOpsFn("executorch_prim::sym_ite.Scalar"));
4546
}
4647

4748
TEST_F(RegisterPrimOpsTest, SymSizeReturnsCorrectValue) {
@@ -241,6 +242,46 @@ TEST_F(RegisterPrimOpsTest, SymIntReturnsCorrectValue) {
241242
EXPECT_EQ(stack[1]->toInt(), 0);
242243
}
243244

245+
TEST_F(RegisterPrimOpsTest, SymIteReturnsCorrectValue) {
246+
EValue values[4];
247+
EValue* stack[4];
248+
for (size_t i = 0; i < 4; i++) {
249+
stack[i] = &values[i];
250+
}
251+
252+
// true branch selects t (int)
253+
values[0] = EValue(true);
254+
values[1] = EValue((int64_t)42);
255+
values[2] = EValue((int64_t)99);
256+
values[3] = EValue((int64_t)0);
257+
getOpsFn("executorch_prim::sym_ite.Scalar")(context_, Span<EValue*>(stack));
258+
EXPECT_EQ(stack[3]->toInt(), 42);
259+
260+
// false branch selects f (int)
261+
values[0] = EValue(false);
262+
values[1] = EValue((int64_t)42);
263+
values[2] = EValue((int64_t)99);
264+
values[3] = EValue((int64_t)0);
265+
getOpsFn("executorch_prim::sym_ite.Scalar")(context_, Span<EValue*>(stack));
266+
EXPECT_EQ(stack[3]->toInt(), 99);
267+
268+
// true branch selects t (double)
269+
values[0] = EValue(true);
270+
values[1] = EValue(3.14);
271+
values[2] = EValue(2.72);
272+
values[3] = EValue(0.0);
273+
getOpsFn("executorch_prim::sym_ite.Scalar")(context_, Span<EValue*>(stack));
274+
EXPECT_FLOAT_EQ(stack[3]->toDouble(), 3.14);
275+
276+
// false branch selects f (bool)
277+
values[0] = EValue(false);
278+
values[1] = EValue(true);
279+
values[2] = EValue(false);
280+
values[3] = EValue(false);
281+
getOpsFn("executorch_prim::sym_ite.Scalar")(context_, Span<EValue*>(stack));
282+
EXPECT_EQ(stack[3]->toBool(), false);
283+
}
284+
244285
TEST_F(RegisterPrimOpsTest, TestAlgebraOps) {
245286
EValue values[3];
246287
int64_t a = 3;

0 commit comments

Comments
 (0)