blob: 270de831e5546c7172ae34e8972978d786ab3d14 [file]
# 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.
from unittest.mock import patch
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import IRModule, relay, rpc, runtime
from tvm.contrib import utils
from tvm.relay import testing
from tvm.relay.backend import vm
from tvm.relay.backend.vm import VMCompiler
from tvm.relay.dataflow_pattern import is_op, wildcard
from tvm.relay.loops import while_loop
from tvm.relay.prelude import Prelude
from tvm.relay.scope_builder import ScopeBuilder
from tvm.relay.testing import mlp
from tvm.relay.transform import InferType
def check_result(target, dev, args, expected_result, mod):
"""
Check that evaluating `expr` applied to the arguments produces
`result` on Relay VM.
Parameters
----------
args: list of Expr
The arguments to supply the expr.
expected_result:
The expected result of running the expression.
"""
rts_result = relay.create_executor("vm", device=dev, target=target, mod=mod).evaluate()(*args)
tvm.testing.assert_allclose(expected_result, rts_result.numpy())
def veval(f, *args, device=tvm.cpu(), target="llvm"):
if isinstance(f, relay.Expr):
mod = tvm.IRModule()
mod["main"] = f
else:
assert isinstance(f, tvm.IRModule), "expected expression or module"
mod = f
exe = relay.vm.compile(mod, target)
vm = runtime.vm.VirtualMachine(exe, device)
return vm.invoke("main", *args)
def vmobj_to_list(o):
if isinstance(o, tvm.nd.NDArray):
return [o.numpy().tolist()]
elif isinstance(o, tvm.runtime.container.ADT):
result = []
for f in o:
result.extend(vmobj_to_list(f))
return result
else:
raise RuntimeError("Unknown object type: %s" % type(o))
def test_split(target, dev):
x = relay.var("x", shape=(12,))
y = relay.split(x, 3, axis=0).astuple()
f = relay.Function([x], y)
x_data = np.random.rand(
12,
).astype("float32")
ref_res = np.split(x_data, 3, axis=0)
res = veval(f, x_data, device=dev, target=target)
for i in range(3):
tvm.testing.assert_allclose(res[i].numpy(), ref_res[i])
def test_split_no_fuse(target, dev):
x = relay.var("x", shape=(12,))
y = relay.split(x, 3, axis=0).astuple()
z = relay.concatenate([relay.TupleGetItem(y, 0)], axis=0)
z = relay.annotation.stop_fusion(z)
f = relay.Function([x], z)
x_data = np.random.rand(
12,
).astype("float32")
res = veval(f, x_data, device=dev, target=target)
tvm.testing.assert_allclose(res.numpy(), np.split(x_data, 3, axis=0)[0])
def test_id(target, dev):
x = relay.var("x", shape=(10, 10), dtype="float64")
f = relay.Function([x], x)
x_data = np.random.rand(10, 10).astype("float64")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [x_data], x_data, mod)
def test_op(target, dev):
x = relay.var("x", shape=(10, 10))
f = relay.Function([x], x + x)
x_data = np.random.rand(10, 10).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [x_data], 2 * x_data, mod)
def any(x):
x = relay.op.nn.batch_flatten(x)
return relay.op.min(x, axis=[0, 1])
@tvm.testing.known_failing_targets("vulkan")
def test_cond(target, dev):
x = relay.var("x", shape=(10, 10))
y = relay.var("y", shape=(10, 10))
# f = relay.Function([x, y], relay.op.equal(x, y))
f = relay.Function([x, y], any(relay.op.equal(x, y)))
x_data = np.random.rand(10, 10).astype("float32")
y_data = np.random.rand(10, 10).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
# same
check_result(target, dev, [x_data, x_data], True, mod)
# diff
check_result(target, dev, [x_data, y_data], False, mod)
@tvm.testing.known_failing_targets("vulkan")
def test_simple_if(target, dev):
x = relay.var("x", shape=(10, 10))
y = relay.var("y", shape=(10, 10))
f = relay.Function([x, y], relay.If(any(relay.op.equal(x, y)), x, y))
x_data = np.random.rand(10, 10).astype("float32")
y_data = np.random.rand(10, 10).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
# same
check_result(target, dev, [x_data, x_data], x_data, mod)
# diff
check_result(target, dev, [x_data, y_data], y_data, mod)
@tvm.testing.parametrize_targets("llvm")
def test_multiple_ifs(target, dev):
mod = tvm.IRModule({})
b = relay.var("b")
v0 = relay.var("v0")
v1 = relay.var("v1")
v2 = relay.var("v2")
v3 = relay.var("v3")
out = relay.Tuple([v2, v3])
out = relay.Let(v3, relay.If(b, v1, v0), out)
out = relay.Let(v2, relay.If(b, v0, v1), out)
out = relay.Let(v1, relay.Tuple([relay.const(1)]), out)
out = relay.Let(v0, relay.Tuple([relay.const(0)]), out)
fn = relay.Function([b], out)
mod["main"] = fn
func = relay.create_executor(device=dev, mod=mod, kind="vm").evaluate()
res = vmobj_to_list(func(False))
assert res == [1, 0]
def test_unused_function(target, dev):
cond = relay.const(True)
mod = tvm.IRModule()
then_name = relay.GlobalVar("times_2")
# define unused function
else_name = relay.GlobalVar("times_3")
t1 = relay.TensorType((2, 2), dtype="float32")
x1 = relay.var("x1", t1, dtype="float32")
x2 = relay.var("x2", t1, dtype="float32")
f2 = relay.multiply(x1, relay.const(2.0))
f3 = relay.multiply(x2, relay.const(3.0))
mod[then_name] = relay.Function([x1], f2)
mod[else_name] = relay.Function([x2], f3)
mod = InferType()(mod)
x3 = relay.var("x3", t1, dtype="float32")
# put unused function in else branch
f = relay.If(cond, then_name(x3), else_name(x3))
mod["main"] = relay.Function([x3], f)
x_data = np.random.rand(2, 2).astype("float32")
y_data = x_data * 2
check_result(target, dev, [x_data], y_data, mod)
def test_simple_call(target, dev):
mod = tvm.IRModule({})
sum_up = relay.GlobalVar("sum_up")
i = relay.var("i", shape=[], dtype="int32")
sb = ScopeBuilder()
sb.ret(i)
func = relay.Function([i], sb.get(), ret_type=relay.TensorType([], "int32"))
mod[sum_up] = func
i_data = np.array(0, dtype="int32")
iarg = relay.var("iarg", shape=[], dtype="int32")
mod["main"] = relay.Function([iarg], sum_up(iarg))
check_result(target, dev, [i_data], i_data, mod)
def test_count_loop(target, dev):
mod = tvm.IRModule({})
sum_up = relay.GlobalVar("sum_up")
i = relay.var("i", shape=[], dtype="int32")
sb = ScopeBuilder()
with sb.if_scope(relay.equal(i, relay.const(0, dtype="int32"))):
sb.ret(i)
with sb.else_scope():
one_less = relay.subtract(i, relay.const(1, dtype="int32"))
rec_call = relay.Call(sum_up, [one_less])
sb.ret(relay.add(rec_call, i))
func = relay.Function([i], sb.get(), ret_type=relay.TensorType([], "int32"))
mod[sum_up] = func
i_data = np.array(0, dtype="int32")
iarg = relay.var("i", shape=[], dtype="int32")
mod["main"] = relay.Function([iarg], sum_up(iarg))
result = veval(mod, i_data, device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), i_data)
check_result(target, dev, [i_data], i_data, mod)
def test_sum_loop(target, dev):
mod = tvm.IRModule({})
sum_up = relay.GlobalVar("sum_up")
i = relay.var("i", shape=[], dtype="int32")
accum = relay.var("accum", shape=[], dtype="int32")
sb = ScopeBuilder()
with sb.if_scope(relay.equal(i, relay.const(0, "int32"))):
sb.ret(accum)
with sb.else_scope():
one_less = relay.subtract(i, relay.const(1, "int32"))
new_accum = relay.add(accum, i)
sb.ret(relay.Call(sum_up, [one_less, new_accum]))
func = relay.Function([i, accum], sb.get())
mod[sum_up] = func
mod = relay.transform.InferType()(mod)
loop_bound = 0
i_data = np.array(loop_bound, dtype="int32")
accum_data = np.array(0, dtype="int32")
iarg = relay.var("i", shape=[], dtype="int32")
aarg = relay.var("accum", shape=[], dtype="int32")
mod["main"] = relay.Function([iarg, aarg], sum_up(iarg, aarg))
check_result(target, dev, [i_data, accum_data], sum(range(1, loop_bound + 1)), mod)
def test_tuple_fst(target, dev):
ttype = relay.TupleType([relay.TensorType((1,)), relay.TensorType((10,))])
tup = relay.var("tup", type_annotation=ttype)
f = relay.Function([tup], relay.TupleGetItem(tup, 0))
i_data = np.random.rand(41).astype("float32")
j_data = np.random.rand(10).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [(i_data, j_data)], i_data, mod)
def test_tuple_second(target, dev):
ttype = relay.TupleType([relay.TensorType((1,)), relay.TensorType((10,))])
tup = relay.var("tup", type_annotation=ttype)
f = relay.Function([tup], relay.TupleGetItem(tup, 1))
i_data = np.random.rand(41).astype("float32")
j_data = np.random.rand(10).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [(i_data, j_data)], j_data, mod)
def test_list_constructor(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
l, cons, nil = mod.get_type("List")
one2 = cons(relay.const(1), nil())
one3 = cons(relay.const(2), one2)
one4 = cons(relay.const(3), one3)
f = relay.Function([], one4)
mod["main"] = f
result = veval(mod, device=dev, target=target)
assert len(result) == 2
assert len(result[1]) == 2
obj = vmobj_to_list(result)
tvm.testing.assert_allclose(obj, np.array([3, 2, 1]))
def test_let_tensor(target, dev):
sb = relay.ScopeBuilder()
shape = (1,)
x = relay.var("x", shape=shape, dtype="float32")
x1 = relay.var("x1", shape=shape, dtype="float32")
x1 = sb.let(x1, x)
xplusone = x1 + relay.const(42.0, "float32")
sb.ret(xplusone)
body = sb.get()
f = relay.Function([x], body)
x_data = np.random.rand(*shape).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [x_data], x_data + 42.0, mod)
def test_let_scalar(target, dev):
sb = relay.ScopeBuilder()
x = relay.var("x", "float32")
x1 = sb.let("x1", x)
xplusone = x1 + relay.const(42.0, "float32")
sb.ret(xplusone)
body = sb.get()
f = relay.Function([x], body)
x_data = np.array(np.random.rand()).astype("float32")
mod = tvm.IRModule()
mod["main"] = f
check_result(target, dev, [x_data], x_data + 42.0, mod)
def test_compose(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
compose = p.compose
# add_one = fun x -> x + 1
sb = relay.ScopeBuilder()
x = relay.var("x", "float32")
x1 = sb.let("x1", x)
xplusone = x1 + relay.const(1.0, "float32")
sb.ret(xplusone)
body = sb.get()
add_one = relay.GlobalVar("add_one")
add_one_func = relay.Function([x], body)
# add_two = compose(add_one, add_one)
sb = relay.ScopeBuilder()
y = relay.var("y", "float32")
add_two_func = sb.let("add_two", compose(add_one_func, add_one_func))
add_two_res = add_two_func(y)
sb.ret(add_two_res)
add_two_body = sb.get()
mod[add_one] = add_one_func
f = relay.Function([y], add_two_body)
mod["main"] = f
x_data = np.array(np.random.rand()).astype("float32")
result = veval(mod, [x_data], device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), x_data + 2.0)
def test_list_hd(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
l, cons, nil = mod.get_type("List")
hd = mod.get_global_var("hd")
one2 = cons(relay.const(1), nil())
one3 = cons(relay.const(2), one2)
one4 = cons(relay.const(3), one3)
three = hd(one4)
f = relay.Function([], three)
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), 3)
def test_list_tl_empty_list(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
l, cons, nil = mod.get_type("List")
tl = mod.get_global_var("tl")
f = relay.Function([], tl(nil()))
mod["main"] = f
with pytest.raises(tvm.error.TVMError):
result = veval(mod, device=dev, target=target)
def test_list_tl(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
l, cons, nil = mod.get_type("List")
tl = mod.get_global_var("tl")
one2 = cons(relay.const(1), nil())
one3 = cons(relay.const(2), one2)
one4 = cons(relay.const(3), one3)
f = relay.Function([], tl(one4))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array([2, 1]))
def test_list_nth(target, dev):
expected = list(range(10))
for i in range(len(expected)):
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
nth = mod.get_global_var("nth")
l = nil()
for i in reversed(expected):
l = cons(relay.const(i), l)
f = relay.Function([], nth(l, relay.const(i)))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), expected[i])
def test_list_update(target, dev):
expected = list(range(10))
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
update = mod.get_global_var("update")
l = nil()
# create zero initialized list
for i in range(len(expected)):
l = cons(relay.const(0), l)
# set value
for i, v in enumerate(expected):
l = update(l, relay.const(i), relay.const(v))
f = relay.Function([], l)
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array(expected))
def test_list_length(target, dev):
expected = list(range(10))
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
length = mod.get_global_var("length")
l = nil()
# create zero initialized list
for _ in range(len(expected)):
l = cons(relay.const(0), l)
l = length(l)
f = relay.Function([], l)
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), 10)
def test_list_map(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
x = relay.var("x", "int32")
add_one_func = relay.Function([x], relay.const(1) + x)
_, cons, nil = mod.get_type("List")
map = mod.get_global_var("map")
l = cons(relay.const(2), cons(relay.const(1), nil()))
f = relay.Function([], map(add_one_func, l))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array([3, 2]))
def test_list_foldl(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
foldl = mod.get_global_var("foldl")
x = relay.var("x")
y = relay.var("y")
rev_dup_func = relay.Function([y, x], cons(x, cons(x, y)))
l = cons(relay.const(1), cons(relay.const(2), cons(relay.const(3), nil())))
f = relay.Function([], foldl(rev_dup_func, nil(), l))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array([3, 3, 2, 2, 1, 1]))
def test_list_foldr(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
foldr = mod.get_global_var("foldr")
x = relay.var("x")
y = relay.var("y")
identity_func = relay.Function([x, y], cons(x, y))
l = cons(relay.const(1), cons(relay.const(2), cons(relay.const(3), nil())))
f = relay.Function([], foldr(identity_func, nil(), l))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array([1, 2, 3]))
def test_list_sum(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
sum = mod.get_global_var("sum")
l = cons(relay.const(1), cons(relay.const(2), cons(relay.const(3), nil())))
f = relay.Function([], sum(l))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(result.numpy(), 6)
def test_list_filter(target, dev):
mod = tvm.IRModule()
p = Prelude(mod)
_, cons, nil = mod.get_type("List")
filter = mod.get_global_var("filter")
x = relay.var("x", "int32")
greater_than_one = relay.Function([x], x > relay.const(1))
l = cons(
relay.const(1),
cons(
relay.const(3), cons(relay.const(1), cons(relay.const(5), cons(relay.const(1), nil())))
),
)
f = relay.Function([], filter(greater_than_one, l))
mod["main"] = f
result = veval(mod, device=dev, target=target)
tvm.testing.assert_allclose(vmobj_to_list(result), np.array([3, 5]))
def test_closure(target, dev):
x = relay.var("x", shape=())
y = relay.var("y", shape=())
f = relay.Function([x], x + y)
ff = relay.Function([y], f)
clo = ff(relay.const(1.0))
main = clo(relay.const(2.0))
res = veval(main, device=dev, target=target)
tvm.testing.assert_allclose(res.numpy(), 3.0)
def test_add_op_scalar(target, dev):
"""
test_add_op_scalar:
fn (x, y) {
return x + y;
}
"""
mod = tvm.IRModule()
x = relay.var("x", shape=()) # Default to float32
y = relay.var("y", shape=()) # Default to float32
func = relay.Function([x, y], relay.op.add(x, y))
x_y_data = [
(np.array(10.0, dtype="float32"), np.array(1.0, dtype="float32")),
(np.float32(10.0), np.float32(1.0)),
(10.0, 1.0),
]
for (x_data, y_data) in x_y_data:
mod["main"] = func
check_result(target, dev, [x_data, y_data], x_data + y_data, mod)
def test_add_op_scalar_float16(target, dev):
"""
test_add_op_scalar_float16:
fn (x, y) {
return x + y;
}
"""
mod = tvm.IRModule()
x = relay.var("x", shape=(), dtype="float16") # Default to float16
y = relay.var("y", shape=(), dtype="float16") # Default to float16
func = relay.Function([x, y], relay.op.add(x, y))
x_y_data = [
(np.array(10.0, dtype="float16"), np.array(1.0, dtype="float16")),
(np.float16(10.0), np.float16(1.0)),
]
for (x_data, y_data) in x_y_data:
mod["main"] = func
check_result(target, dev, [x_data, y_data], x_data + y_data, mod)
def test_add_op_scalar_int(target, dev):
"""
test_add_op_scalar_int:
fn (x, y) {
return x + y;
}
"""
mod = tvm.IRModule()
x = relay.var("x", shape=(), dtype="int32")
y = relay.var("y", shape=(), dtype="int32")
func = relay.Function([x, y], relay.op.add(x, y))
x_y_data = [
(np.array(10.0, dtype="int32"), np.array(1.0, dtype="int32")),
(np.int32(10), np.int32(1)),
(10, 1),
]
for (x_data, y_data) in x_y_data:
mod["main"] = func
check_result(target, dev, [x_data, y_data], x_data + y_data, mod)
def test_add_op_tensor(target, dev):
"""
test_add_op_tensor:
fn (x, y) {
return x + y;
}
"""
mod = tvm.IRModule()
x = relay.var("x", shape=(10, 5))
y = relay.var("y", shape=(10, 5))
func = relay.Function([x, y], relay.op.add(x, y))
x_data = np.random.rand(10, 5).astype("float32")
y_data = np.random.rand(10, 5).astype("float32")
mod["main"] = func
check_result(target, dev, [x_data, y_data], x_data + y_data, mod)
def test_add_op_broadcast(target, dev):
"""
test_add_op_broadcast:
fn (x, y) {
return x + y;
}
"""
mod = tvm.IRModule()
x = relay.var("x", shape=(10, 5))
y = relay.var("y", shape=(1, 5))
func = relay.Function([x, y], relay.op.add(x, y))
x_data = np.random.rand(10, 5).astype("float32")
y_data = np.random.rand(1, 5).astype("float32")
mod["main"] = func
check_result(target, dev, [x_data, y_data], x_data + y_data, mod)
def test_vm_optimize_dynamic():
dtype = "float32"
x = relay.var("x", shape=(relay.Any(), relay.Any()), dtype=dtype)
y = relay.var("y", shape=(relay.Any(), relay.Any()), dtype=dtype)
mod = tvm.IRModule()
mod["main"] = relay.Function([x, y], relay.add(x, y))
comp = relay.vm.VMCompiler()
opt_mod, _ = comp.optimize(mod, target="llvm")
assert "shape_func" in opt_mod.astext(False)
def test_vm_optimize():
mod, params = testing.synthetic.get_workload()
comp = relay.vm.VMCompiler()
opt_mod, _ = comp.optimize(mod, target="llvm", params=params)
free_vars = relay.analysis.free_vars(opt_mod["main"].body)
# Paremeters should all be bound, so the only free var is data
assert len(free_vars) == 1
def test_loop_free_var(target, dev):
x = relay.var("x", shape=(), dtype="int32")
i = relay.var("i", shape=(), dtype="int32")
s = relay.var("s", shape=(), dtype="int32")
def cond(i, _):
return i < relay.const(10, dtype="int32")
def body_no_free_var(i, acc):
incr = relay.const(1, "int32")
return i + incr, acc + i
def body_with_free_var(i, acc):
incr = relay.const(1, "int32")
return i + incr, acc + x
for args, body, expected in zip([[], [1]], [body_no_free_var, body_with_free_var], [45, 10]):
loop = while_loop(cond, [i, s], body)
tup = loop(relay.const(0, dtype="int32"), relay.zeros(shape=(), dtype="int32"))
ret = relay.TupleGetItem(tup, 1)
mod = tvm.IRModule()
mod["main"] = relay.Function(relay.analysis.free_vars(ret), ret)
check_result(target, dev, args, expected, mod)
def test_vm_reshape_tensor(target, dev):
x_np = np.random.uniform(size=(8, 16)).astype("float32")
x = relay.var("x", shape=(8, 16), dtype="float32")
y = relay.reshape(x, [-1, 4, 8])
mod = tvm.IRModule()
mod["main"] = relay.Function([x], y)
with tvm.transform.PassContext(opt_level=3):
exec = relay.vm.compile(mod, "llvm")
assert "reshape_tensor" in exec.bytecode
check_result(target, dev, [x_np], x_np.reshape([4, 4, 8]), mod)
x = relay.var("x", shape=(8, 16), dtype="float32")
y = relay.reshape(x, [16, -1])
y = relay.reverse_reshape(y, [-1, 4, 0])
mod = tvm.IRModule()
mod["main"] = relay.Function([x], y)
with tvm.transform.PassContext(opt_level=3):
exec = relay.vm.compile(mod, "llvm")
assert exec.bytecode.count("reshape_tensor") == 1
check_result(target, dev, [x_np], x_np.reshape([4, 4, 8]), mod)
# reshape with symbolic/any shape
for n in [tvm.tir.Any(), tvm.te.size_var("n")]:
x = relay.var("x", shape=(n, 16), dtype="float32")
y = relay.reshape(x, [-1, 4])
y = relay.reshape(y, [0, 2, -1])
mod = tvm.IRModule()
mod["main"] = relay.Function([x], y)
with tvm.transform.PassContext(opt_level=3):
exec = relay.vm.compile(mod, "llvm")
assert exec.bytecode.count("reshape_tensor") == 1
check_result(target, dev, [x_np], x_np.reshape([32, 2, 2]), mod)
# dyn.reshape
x = relay.var("x", shape=(8, 16), dtype="float32")
y = relay.var("y", shape=(3,), dtype="int32")
z = relay.reshape(x, [-1, 4, 8])
z = relay.reshape(z, y)
mod = tvm.IRModule()
mod["main"] = relay.Function([x, y], z)
with tvm.transform.PassContext(opt_level=3):
exec = relay.vm.compile(mod, "llvm")
assert exec.bytecode.count("reshape_tensor") == 2
assert "reshape_tensor" in exec.bytecode
y_np = np.array([8, 2, 8]).astype("int32")
check_result(target, dev, [x_np, y_np], x_np.reshape([8, 2, 8]), mod)
def test_vm_reshape_and_copy(target, dev):
"""Make sure the compiler notices the reshape result shape is a literal and can use
the immediate-mode alloc_tensor instruction instead of alloc_tensor_reg."""
x_np = np.random.uniform(size=(1, 1)).astype("float32")
x = relay.var("x", shape=(1, 1), dtype="float32")
mod = tvm.IRModule.from_expr(relay.Function([x], relay.copy(relay.reshape(x, [0, 1]))))
with tvm.transform.PassContext(opt_level=3):
exec = relay.vm.compile(mod, "llvm")
assert "alloc_tensor" in exec.bytecode
assert not "alloc_tensor_reg" in exec.bytecode
check_result(target, dev, [x_np], x_np.reshape([1, 1]), mod)
def test_vm_reshape_tuple(target, dev, x_shape=(1, 4, 2), y_shape=(1, 2, 10)):
tup = relay.var(
"tup",
type_annotation=relay.TupleType([relay.TensorType(x_shape), relay.TensorType(y_shape)]),
)
out = relay.reshape(relay.TupleGetItem(tup, 0), (1, -1))
f = relay.Function([tup], out)
x_data = np.random.uniform(size=x_shape).astype("float32")
y_data = np.random.uniform(size=y_shape).astype("float32")
res = veval(f, (x_data, y_data), device=dev, target=target)
tvm.testing.assert_allclose(res.numpy(), np.reshape(x_data, (1, -1)))
def test_constant_shape_with_external_codegen():
@tvm.register_func("relay.ext.test1")
def relay_ext_test(func):
return None
mod = tvm.IRModule()
shape = (relay.Any(), 25)
dtype = "float32"
# external function
x = relay.var("x", shape=shape, dtype=dtype)
weight = relay.const(np.random.rand(5, 25).astype("float32"), dtype="float32")
out = relay.nn.dense(x, weight)
f1 = relay.Function([x], out)
f1 = f1.with_attr("Primitive", tvm.tir.IntImm("int32", 1))
f1 = f1.with_attr("Inline", tvm.tir.IntImm("int32", 1))
f1 = f1.with_attr("Compiler", "test1")
f1 = f1.with_attr("global_symbol", "f1")
glb_f1 = relay.GlobalVar("f1")
mod[glb_f1] = f1
mod = relay.transform.InferType()(mod)
# Main function
x = relay.var("x", shape=shape, dtype=dtype)
mod["main"] = relay.Function([x], glb_f1(x))
comp = relay.vm.VMCompiler()
opt_mod, _ = comp.optimize(mod, target="llvm")
assert "shape_func" in opt_mod.astext(False)
def prepare_vm_model(path, tensor_shape):
"""
Virtual Machine is compiled for simple topology and
exported as library to given path
"""
target = tvm.target.Target("llvm --host=llvm")
# Build a IRModule.
x = relay.var("x", shape=tensor_shape)
f = relay.Function([x], x + x)
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
# Export to Disk
vm_exec.mod.export_library(path)
def test_vm_rpc():
"""
This test checks to make sure you can export a VMExecutable,
upload it to a remote machine using RPC and then execute it
on the other machine.
"""
# Shape for input and output tensors
shape = (10, 1)
# Export to Disk
temp = utils.tempdir()
path = temp.relpath("vm_library.so")
prepare_vm_model(path, shape)
# Use local rpc server for testing.
# Server must use popen so it doesn't inherit the current process state. It
# will crash otherwise.
def check_remote(server):
remote = rpc.connect(server.host, server.port, session_timeout=10)
# Upload the serialized Executable.
remote.upload(path)
# Get a handle to remote Executable.
rexec = remote.load_module("vm_library.so")
device = remote.cpu()
# Build a VM out of the executable and context.
vm_factory = runtime.vm.VirtualMachine(rexec, device)
np_input = np.random.uniform(size=shape).astype("float32")
input_tensor = tvm.nd.array(np_input, device)
# Invoke its "main" function.
out = vm_factory.invoke("main", input_tensor)
# Check the result.
np.testing.assert_allclose(out.numpy(), np_input + np_input)
check_remote(rpc.Server("127.0.0.1"))
def test_vm_invoke_with_outputs_rpc():
"""
This test checks to make sure you can export a VMExecutable,
upload it to a remote machine using RPC and then execute it
on the other machine with preallocated outputs.
"""
# Shape for input and output tensors
shape = (3, 2)
# Export to Disk
temp = utils.tempdir()
path = temp.relpath("vm_library.so")
prepare_vm_model(path, shape)
# Use local rpc server for testing.
# Server must use popen so it doesn't inherit the current process state. It
# will crash otherwise.
def check_remote_invoke_with_outputs(server):
remote = rpc.connect(server.host, server.port, session_timeout=10)
# Upload the serialized Executable.
remote.upload(path)
# Get a handle to remote Executable.
rexec = remote.load_module("vm_library.so")
device = remote.cpu()
# Build a VM out of the executable and context.
vm_factory = runtime.vm.VirtualMachine(rexec, device)
np_input = np.random.uniform(size=shape).astype("float32")
input_tensor = tvm.nd.array(np_input, device)
np_output = np.empty(shape, dtype="float32")
output_tensor = tvm.nd.array(np_output, device)
# Invoke its "main" function.
vm_factory.invoke_with_outputs(
"main", input_args={"x": input_tensor}, output_args=[output_tensor]
)
# Check the result.
np.testing.assert_allclose(output_tensor.numpy(), np_input + np_input)
check_remote_invoke_with_outputs(rpc.Server("127.0.0.1"))
def test_vm_invoke_with_outputs():
target = tvm.target.Target("llvm")
shape = (3, 2)
# Build a IRModule.
x = relay.var("x", shape=shape)
f = relay.Function([x], x + x)
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
vm_factory = runtime.vm.VirtualMachine(vm_exec, tvm.cpu())
np_input = np.random.uniform(size=shape).astype("float32")
input_tensor = tvm.nd.array(np_input)
np_output = np.empty(shape, dtype="float32")
output_tensor = tvm.nd.array(np_output)
# Invoke
vm_factory.invoke_with_outputs(
"main", input_args={"x": input_tensor}, output_args=[output_tensor]
)
# Check the result.
np.testing.assert_allclose(output_tensor.numpy(), np_input + np_input)
def test_get_output_single():
target = tvm.target.Target("llvm")
# Build a IRModule.
x = relay.var("x", shape=(10,))
f = relay.Function([x], x + x)
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
vm_factory = runtime.vm.VirtualMachine(vm_exec, tvm.cpu())
inp = np.ones(10, dtype="float32")
vm_factory.invoke_stateful("main", inp)
outputs = vm_factory.get_outputs()
assert len(outputs) == 1
np.testing.assert_allclose(outputs[0].numpy(), inp + inp)
@tvm.testing.parametrize_targets("llvm")
def test_get_output_multiple(target, dev):
# Build a IRModule.
x = relay.var("x", shape=(10,))
f = relay.Function([x], relay.Tuple([x + x, x]))
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
vm_factory = runtime.vm.VirtualMachine(vm_exec, dev)
inp = np.ones(10, dtype="float32")
vm_factory.invoke_stateful("main", inp)
outputs = vm_factory.get_outputs()
assert len(outputs) == 2
np.testing.assert_allclose(outputs[0].numpy(), inp + inp)
np.testing.assert_allclose(outputs[1].numpy(), inp)
@tvm.testing.parametrize_targets("llvm")
def test_get_input_index(target, dev):
# Build a IRModule.
data_0, data_1 = ["d1", "d2"]
x, y = [relay.var(c, shape=(10,)) for c in [data_0, data_1]]
f = relay.Function([x, y], x + y)
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
vm_factory = runtime.vm.VirtualMachine(vm_exec, dev)
assert vm_factory.get_input_index(data_1) == 1
assert vm_factory.get_input_index(data_0) == 0
assert vm_factory.get_input_index("invalid") == -1
def get_one_input_relay_mod(tensor_type, shape, data_name):
x = relay.var(data_name, shape=shape, dtype=tensor_type)
y = relay.exp(x)
f = relay.Function([x], y)
return IRModule.from_expr(f)
@tvm.testing.parametrize_targets("llvm")
def test_one_set_input(target, dev):
dtype = "float32"
in_shape = [1, 2, 3, 3]
in_data_name_0 = "d0"
mod = get_one_input_relay_mod(dtype, in_shape, in_data_name_0)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
exe = runtime.vm.VirtualMachine(vm_exec, dev)
data0_core = np.random.uniform(size=in_shape).astype(dtype)
data0 = tvm.nd.array(data0_core)
ref_res_core = np.exp(data0_core)
ref_res = tvm.nd.array(ref_res_core)
exe.set_input("main", data0)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
data_dict = {in_data_name_0: data0}
exe.set_input("main", **data_dict)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
def get_multiple_input_relay_mod(tensor_type, shape, data_name0, data_name1):
x, y = [relay.var(c, shape=shape, dtype=tensor_type) for c in [data_name0, data_name1]]
f = relay.Function([x, y], x + y)
return IRModule.from_expr(f)
@tvm.testing.parametrize_targets("llvm")
def test_multiple_set_input(target, dev):
dtype = "float32"
in_shape = [1, 2, 3, 3]
in_data_name_0 = "d0"
in_data_name_1 = "d1"
mod = get_multiple_input_relay_mod(dtype, in_shape, in_data_name_0, in_data_name_1)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
exe = runtime.vm.VirtualMachine(vm_exec, dev)
data0_core = np.random.uniform(size=in_shape).astype(dtype)
data0 = tvm.nd.array(data0_core)
data1_core = np.random.uniform(size=in_shape).astype(dtype)
data1 = tvm.nd.array(data1_core)
ref_res_core = data0_core + data1_core
ref_res = tvm.nd.array(ref_res_core)
exe.set_input("main", data0, data1)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
data_dict = {in_data_name_1: data1, in_data_name_0: data0}
exe.set_input("main", **data_dict)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
@tvm.testing.parametrize_targets("llvm")
def test_one_set_one_input(target, dev):
dtype = "float32"
in_shape = [1, 2, 3, 3]
in_data_name_0 = "d0"
mod = get_one_input_relay_mod(dtype, in_shape, in_data_name_0)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
exe = runtime.vm.VirtualMachine(vm_exec, dev)
data0_core = np.random.uniform(size=in_shape).astype(dtype)
data0 = tvm.nd.array(data0_core)
ref_res_core = np.exp(data0_core)
ref_res = tvm.nd.array(ref_res_core)
exe.set_one_input("main", 0, data0)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
exe.set_one_input("main", in_data_name_0, data0)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
data_dict = {in_data_name_0: data0}
exe.set_one_input("main", **data_dict)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
@tvm.testing.parametrize_targets("llvm")
def test_multiple_set_one_input(target, dev):
dtype = "float32"
in_shape = [1, 2, 3, 3]
in_data_name_0 = "d0"
in_data_name_1 = "d1"
mod = get_multiple_input_relay_mod(dtype, in_shape, in_data_name_0, in_data_name_1)
# Compile to VMExecutable.
vm_exec = vm.compile(mod, target=target)
exe = runtime.vm.VirtualMachine(vm_exec, dev)
data0_core = np.random.uniform(size=in_shape).astype(dtype)
data0 = tvm.nd.array(data0_core)
data1_core = np.random.uniform(size=in_shape).astype(dtype)
data1 = tvm.nd.array(data1_core)
ref_res_core = data0_core + data1_core
ref_res = tvm.nd.array(ref_res_core)
exe.set_one_input("main", 1, data1)
exe.set_one_input("main", 0, data0)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
exe.set_one_input("main", in_data_name_1, data1)
exe.set_one_input("main", in_data_name_0, data0)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
data_dict = {in_data_name_1: data1}
exe.set_one_input("main", **data_dict)
data_dict = {in_data_name_0: data0}
exe.set_one_input("main", **data_dict)
output = exe.invoke("main")
assert output.dtype == ref_res.dtype
tvm.testing.assert_allclose(ref_res_core, output.numpy())
@tvm.testing.parametrize_targets("llvm")
def test_benchmark(target, dev):
mod, params = mlp.get_workload(1)
lib = vm.compile(mod, target=target, params=params)
exe = runtime.vm.VirtualMachine(lib, tvm.cpu())
data = tvm.nd.array(np.random.rand(1, 1, 28, 28).astype("float32"))
result = exe.benchmark(tvm.cpu(), data, func_name="main", repeat=2, number=1)
assert result.mean == result.median
assert result.mean > 0
assert len(result.results) == 2
with patch.object(
tvm.runtime.module.Module,
"time_evaluator",
return_value=lambda x: tvm.runtime.module.BenchmarkResult([1, 2, 2, 5]),
) as method:
result = exe.benchmark(dev, data, func_name="main", repeat=2, number=1)
assert result.mean == 2.5
assert result.median == 2.0
assert result.max == 5
assert result.min == 1
assert result.std == 1.5
def test_benchmark_end_to_end(target, dev):
mod, params = mlp.get_workload(1)
lib = vm.compile(mod, target=target, params=params)
exe = runtime.vm.VirtualMachine(lib, dev)
data = tvm.nd.array(np.random.rand(1, 1, 28, 28).astype("float32"), device=dev)
result = exe.benchmark(dev, data, func_name="main", repeat=2, number=1, end_to_end=True)
assert result.mean > 0
@tvm.testing.requires_cuda
def test_benchmark_end_to_end_rpc():
server = rpc.Server("127.0.0.1")
remote = rpc.connect(server.host, server.port)
mod, params = mlp.get_workload(1)
lib = vm.compile(mod, target="cuda", params=params)
temp = utils.tempdir()
path = temp.relpath("vm_library.so")
lib.mod.export_library(path)
remote.upload(path)
rlib = remote.load_module("vm_library.so")
exe = runtime.vm.VirtualMachine(rlib, remote.device("cuda"))
data = tvm.nd.array(
np.random.rand(1, 1, 28, 28).astype("float32"), device=remote.device("cuda")
)
result = exe.benchmark(
remote.device("cuda"), data=data, func_name="main", repeat=2, number=1, end_to_end=True
)
assert result.mean > 0
def test_shape_func_nested_function():
@tvm.register_func("relay.ext.test2")
def relay_ext_test(func):
return None
data_shape = (relay.Any(), 16)
weight_shape = (relay.Any(), 16)
dense = relay.nn.dense(
relay.var("data", shape=data_shape), relay.var("weight", shape=weight_shape)
)
mod = tvm.IRModule.from_expr(dense)
patterns = [("test.dense", is_op("nn.dense")(wildcard(), wildcard()))]
passes = tvm.transform.Sequential(
[
relay.transform.MergeComposite(patterns),
relay.transform.AnnotateTarget(["test2"]),
relay.transform.PartitionGraph(),
]
)
mod = passes(mod)
compiler = VMCompiler()
compiler.lower(mod, "llvm")
@tvm.testing.requires_cuda
def test_storage_size_and_offset_on_cpu():
"""Tests allocations place sizes and offsets on the CPU host even if the rest
of the computation is on a different device type."""
# TODO(mbs): Better would be to test ManifestAlloc independently.
# And/or move this to C++ and test the VM executable in it's C++ instead of
# pretty-printed form.
# CPU = device type 1
# GPU = device type 2
def input():
return tvm.relay.fromtext(
"""
#[version = "0.0.5"]
def @main(%a: Tensor[(5, 7), float32],
param_device_types=[2], result_device_type=2) {
add(%a, %a)
}
"""
)
exe = relay.vm.compile(
input(),
tvm.target.Target("cuda"),
)
# This program needs two constants:
# - The size of the tensor's storage (first arg) to alloc_storage
# - The offset of the tensor within the storage (second arg) to alloc_tensor
# Both should be on the CPU
assert "VirtualDevice[0]: device type 1" in exe.virtual_devices
assert "VM Const[0]: NDArray[(),int64,(1,0)]=[140] on device index 0" in exe.constants
assert "VM Const[1]: NDArray[(),int64,(1,0)]=[0] on device index 0" in exe.constants
@tvm.testing.requires_cuda
def test_reshape_shape_on_cpu():
"""Tests the argument to a reshape places the shape on the CPU host even if the rest
of the computation is on a different device type."""
# TODO(mbs): Better would be to test ManifestAlloc independently.
# And/or move this to C++ and test the VM executable in it's C++ instead of
# pretty-printed form.
# CPU = device type 1
# GPU = device type 2
def input():
return tvm.relay.fromtext(
"""
#[version = "0.0.5"]
def @main(%x: Tensor[(2, 8), float32],
param_device_types=[2], result_device_type=2) {
reshape(%x, newshape=[2, 4, 2])
}
"""
)
exe = relay.vm.compile(
input(),
tvm.target.Target("cuda"),
)
# The newshape annotation should have been turned into a constant on the CPU.
assert "VirtualDevice[0]: device type 1" in exe.virtual_devices
assert "VM Const[0]: NDArray[(3),int64,(1,0)]=[2,4,2] on device index 0" in exe.constants
@tvm.testing.requires_cuda
def test_multi_targets():
# Build an IRModule.
n = 10
x = relay.var("x", shape=(n,))
y = relay.var("y", shape=(n,))
z = relay.var("z", shape=(n,))
f = relay.Function([x, y, z], x + relay.op.annotation.on_device(y + z, tvm.cpu()))
mod = IRModule.from_expr(f)
# Compile to VMExecutable.
with tvm.transform.PassContext(
opt_level=3, config={"relay.fallback_device_type": tvm.cuda().device_type}
):
exe = relay.vm.compile(
mod, target={"cpu": tvm.target.Target("llvm"), "cuda": tvm.target.Target("cuda")}
)
# Run
vm = runtime.vm.VirtualMachine(exe, [tvm.cuda(), tvm.cpu()])
x_data = np.random.rand(
n,
).astype("float32")
y_data = np.random.rand(
n,
).astype("float32")
z_data = np.random.rand(
n,
).astype("float32")
actual_result = vm.invoke("main", x_data, y_data, z_data)
# Test
expected_result = x_data + y_data + z_data
tvm.testing.assert_allclose(actual_result.numpy(), expected_result)
def test_let_bound_constants():
"""This tests for an ICHECK failure for ill-formed IR with let-bound constants"""
x = relay.var("x", shape=(3,), dtype="int32")
y = relay.take(x, relay.const(0))
z = relay.const(1)
f = relay.Function([x], relay.stack((z, y), axis=0))
mod = IRModule.from_expr(f)
compiler = VMCompiler()
compiler.optimize(mod, target="llvm")
def test_large_constants():
"""Large constants can be serialized outside of executable"""
target = tvm.target.Target("llvm")
dev = tvm.cpu()
# fn(x) { add(x, <large constant>) }
x = relay.var("x", shape=(1000, 1000))
const_data = np.random.rand(1000, 1000).astype("float32")
const = relay.const(const_data, dtype="float32")
func = relay.Function([x], relay.op.add(x, const))
mod = tvm.IRModule.from_expr(func)
# Compile to executable.
vm_exec = vm.compile(mod, target=target)
# Save to constants and library files
temp = utils.tempdir()
path_consts = temp.relpath("consts")
vm_exec.move_late_bound_consts(path_consts, byte_limit=256)
path_dso = temp.relpath("lib.so")
vm_exec.mod.export_library(path_dso)
# Load library files and constants
mod = runtime.load_module(path_dso)
mod["load_late_bound_consts"](path_consts)
# Test main
x_data = np.random.rand(1000, 1000).astype("float32")
the_vm = runtime.vm.VirtualMachine(mod, dev)
actual = the_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
# We load the mod again so it's missing the consts.
mod = runtime.load_module(path_dso)
exe = runtime.vm.Executable(mod)
# Also test loading consts via the VM's wrapper API.
exe.load_late_bound_consts(path_consts)
# Test main again with consts now loaded via the above API.
x_data = np.random.rand(1000, 1000).astype("float32")
the_vm = runtime.vm.VirtualMachine(exe, dev)
actual = the_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
def test_load_late_bound_consts_with_no_late_bound_consts():
"""Check that load_late_bound_consts handles a model with no late bound consts."""
target = tvm.target.Target("llvm")
dev = tvm.cpu()
const_data = np.random.rand(1).astype("float64")
x = relay.var("x", shape=(1,), dtype="float64")
const = relay.const(const_data, dtype="float64")
func = relay.Function([x], relay.op.add(x, const))
mod = tvm.IRModule.from_expr(func)
vm_exec = vm.compile(mod, target=target)
temp = utils.tempdir()
path_consts = temp.relpath("consts")
path_dso = temp.relpath("lib.so")
# Ensure const_data is below the byte threshold for a late-bound const.
byte_limit = len(const_data.tobytes()) + 1
vm_exec.move_late_bound_consts(path_consts, byte_limit=byte_limit)
vm_exec.mod.export_library(path_dso)
mod = runtime.load_module(path_dso)
mod["load_late_bound_consts"](path_consts)
x_data = np.random.rand(1).astype("float64")
loaded_vm = runtime.vm.VirtualMachine(mod, dev)
actual = loaded_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
def test_vm_save_and_load_without_designating_late_bound_consts():
"""Check that a VM can be saved and loaded without late-bound consts in play.
Specifically, this test ensures that the machinery behind late-bound const
loading does not assume the need to load late-bound consts (and cause an error)
when the user did not choose to designate any consts as such.
"""
target = tvm.target.Target("llvm")
dev = tvm.cpu()
const_data = np.random.rand(1).astype("float64")
x = relay.var("x", shape=(1,), dtype="float64")
const = relay.const(const_data, dtype="float64")
func = relay.Function([x], relay.op.add(x, const))
mod = tvm.IRModule.from_expr(func)
vm_exec = vm.compile(mod, target=target)
code, lib = vm_exec.save()
exe = runtime.vm.Executable.load_exec(code, lib)
x_data = np.random.rand(1).astype("float64")
loaded_vm = runtime.vm.VirtualMachine(exe, dev)
actual = loaded_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
def test_load_and_save_constants_via_map():
"""Large constants can be serialized outside of executable"""
target = tvm.target.Target("llvm")
dev = tvm.cpu()
# fn(x) { add(x, <large constant>) }
x = relay.var("x", shape=(1000, 1000))
const_data = np.random.rand(1000, 1000).astype("float32")
const = relay.const(const_data, dtype="float32")
func = relay.Function([x], relay.op.add(x, const))
mod = tvm.IRModule.from_expr(func)
# Compile to executable.
vm_exec = vm.compile(mod, target=target)
consts_map = vm_exec.get_late_bound_consts(byte_limit=256)
# Save to constants and library files
temp = utils.tempdir()
path_dso = temp.relpath("lib.so")
vm_exec.mod.export_library(path_dso)
# Load library files and constants
mod = runtime.load_module(path_dso)
mod["load_late_bound_consts_from_map"](consts_map)
# Test main
x_data = np.random.rand(1000, 1000).astype("float32")
the_vm = runtime.vm.VirtualMachine(mod, dev)
actual = the_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
# We load the mod again so it's missing the consts.
mod = runtime.load_module(path_dso)
exe = runtime.vm.Executable(mod)
# Also test loading consts via the VM's wrapper API.
exe.load_late_bound_consts_from_map(consts_map)
# Test main again with consts now loaded via the above API.
x_data = np.random.rand(1000, 1000).astype("float32")
the_vm = runtime.vm.VirtualMachine(exe, dev)
actual = the_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
def test_load_late_bound_consts_via_map_with_no_late_bound_consts():
"""Check that load_late_bound_consts handles a model with no late bound consts."""
target = tvm.target.Target("llvm")
dev = tvm.cpu()
const_data = np.random.rand(1).astype("float64")
x = relay.var("x", shape=(1,), dtype="float64")
const = relay.const(const_data, dtype="float64")
func = relay.Function([x], relay.op.add(x, const))
mod = tvm.IRModule.from_expr(func)
vm_exec = vm.compile(mod, target=target)
temp = utils.tempdir()
path_dso = temp.relpath("lib.so")
# Ensure const_data is below the byte threshold for a late-bound const.
byte_limit = len(const_data.tobytes()) + 1
consts_map = vm_exec.get_late_bound_consts(byte_limit=byte_limit)
vm_exec.mod.export_library(path_dso)
mod = runtime.load_module(path_dso)
mod["load_late_bound_consts_from_map"](consts_map)
x_data = np.random.rand(1).astype("float64")
loaded_vm = runtime.vm.VirtualMachine(mod, dev)
actual = loaded_vm.invoke("main", x_data)
expected = x_data + const_data
tvm.testing.assert_allclose(expected, actual.numpy())
if __name__ == "__main__":
tvm.testing.main()