# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. import importlib.util import numpy as np import pytest import tvm import tvm.testing from tvm import relax target, dev = "llvm", tvm.cpu() def _has_xcode(): try: import tvm.support.xcode tvm.support.xcode.xcrun([]) return True except FileNotFoundError: pass return False requires_coreml_runtime = pytest.mark.skipif( not (importlib.util.find_spec("coremltools") and _has_xcode()), reason="coreml is not enabled.", ) def test_partition_for_coreml_uses_current_relax_passes(): from tvm.relax.backend.metal.coreml import partition_for_coreml x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): lv0 = bb.emit(relax.op.add(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) partitioned = partition_for_coreml(bb.get()) relax.analysis.well_formed(partitioned) assert any( getattr(func, "attrs", None) is not None and "Codegen" in func.attrs and str(func.attrs["Codegen"]) == "coreml" for func in partitioned.functions.values() ) def verify(mod, inputs): from tvm.relax.backend.metal.coreml import partition_for_coreml mod1 = partition_for_coreml(mod) mod1 = relax.transform.RunCodegen()(mod1) relax.analysis.well_formed(mod1) assert mod1.attrs, "Should exist if offloaded successfully." assert "external_mods" in mod1.attrs, "Should exist if offloaded successfully." mod1 = relax.transform.LegalizeOps()(mod1) relax.analysis.well_formed(mod1) ex1 = tvm.compile(mod1, target=target) vm1 = relax.VirtualMachine(ex1, dev) out1 = vm1["main"](*inputs) mod2 = relax.transform.LegalizeOps()(mod) ex2 = tvm.compile(mod2, target=target) vm2 = relax.VirtualMachine(ex2, dev) out2 = vm2["main"](*inputs) tvm.testing.assert_allclose(out1.numpy(), out2.numpy(), rtol=1e-3, atol=1e-3) @requires_coreml_runtime def test_add(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): lv0 = bb.emit(relax.op.add(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) y_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data, y_data]) @requires_coreml_runtime def test_add_const(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.const(np.ones([10, 10]), "float32") bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.add(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_multiply(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): lv0 = bb.emit(relax.op.multiply(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) y_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data, y_data]) @requires_coreml_runtime def test_matmul(): x = relax.Var("x", relax.TensorType([8, 10], "float32")) y = relax.Constant(tvm.runtime.tensor(np.random.rand(10, 8).astype("float32"), dev)) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.matmul(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(8, 10).astype("float32"), dev) verify(mod, [x_data]) x = relax.Var("x", relax.TensorType([8, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 8], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): lv0 = bb.emit(relax.op.matmul(x, y)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(8, 10).astype("float32"), dev) y_data = tvm.runtime.tensor(np.random.rand(10, 8).astype("float32"), dev) verify(mod, [x_data, y_data]) @requires_coreml_runtime def test_clip(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.clip(x, 0, 4)) gv0 = bb.emit_output(lv0) bb.emit_func_output(gv0) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data]) x = relax.Var("x", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.clip(x, 0, 4)) lv1 = bb.emit(relax.op.clip(x, 1, 3)) gv0 = bb.emit_output(lv0) gv1 = bb.emit_output(lv1) bb.emit_func_output([gv0, gv1]) x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_expand_dims(): def get_mod(axis): x = relax.Var("x", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.expand_dims(x, axis=axis)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) return bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(get_mod(axis=0), [x_data]) verify(get_mod(axis=1), [x_data]) @requires_coreml_runtime def test_relu(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.nn.relu(x)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_batch_flatten(): x = relax.Var("x", relax.TensorType([10, 10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.nn.batch_flatten(x)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_softmax(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.nn.softmax(x)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_conv2d(): x = relax.Var("x", relax.TensorType([1, 3, 224, 224], "float32")) w = relax.const(np.zeros((16, 3, 3, 3), dtype="float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.nn.conv2d(x, w, strides=[2, 2], padding=[1, 1, 1, 1])) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(1, 3, 224, 224).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_global_avg_pool2d(): x = relax.Var("x", relax.TensorType([1, 1, 10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x]): with bb.dataflow(): lv0 = bb.emit(relax.op.nn.avg_pool2d(x)) gv = bb.emit_output(lv0) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(1, 1, 10, 10).astype("float32"), dev) verify(mod, [x_data]) @requires_coreml_runtime def test_subgraph1(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): lv0 = bb.emit(relax.op.multiply(x, y)) lv1 = bb.emit(relax.op.nn.softmax(lv0)) gv = bb.emit_output(lv1) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) y_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data, y_data]) @requires_coreml_runtime def test_subgraph2(): x = relax.Var("x", relax.TensorType([10, 10], "float32")) y = relax.Var("y", relax.TensorType([10, 10], "float32")) bb = relax.BlockBuilder() with bb.function("main", [x, y]): with bb.dataflow(): # multiply+relu will be offloaded to coreml lv0 = bb.emit(relax.op.multiply(x, y)) lv1 = bb.emit(relax.op.nn.relu(lv0)) # gelu wouldn't be offloaded to coreml lv2 = bb.emit(relax.op.nn.gelu(lv1)) # relu would be offloaded to coreml lv3 = bb.emit(relax.op.nn.relu(lv2)) gv = bb.emit_output(lv3) bb.emit_func_output(gv) mod = bb.get() x_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) y_data = tvm.runtime.tensor(np.random.rand(10, 10).astype("float32"), dev) verify(mod, [x_data, y_data]) if __name__ == "__main__": pytest.main([__file__])