Skip to content

Commit 2ed31b2

Browse files
ajtullochtqchen
authored andcommitted
{relay,topi}.reinterpret support (#3599)
= Motivation It's useful to expose the tvm::reinterpret functionality to Relay/TOPI users, as this allows them to build (fused) operators leveraging the bitwise reinterpretation of an operator. An example is approximate transcendental functions, which can be implemented similar to: ```.py def C(x): return relay.expr.const(x, "float32") def approx_exp(x): x = relay.minimum(relay.maximum(x, C(-88.0)), C(88.0)) x = C(127.0) + x * C(1.44269504) xf = relay.floor(x) i = relay.cast(xf, "int32") x = x - xf Y = C(0.99992522) + x * (C(0.69583354) + x * (C(0.22606716) + x * C(0.078024523))) exponent = relay.left_shift(i, relay.expr.const(23, "int32")) exponent = relay.reinterpret(exponent, "float32") return exponent * Y def approx_sigmoid(x): # <2.0e-5 absolute error over [-5, 5] y = approx_exp(x) return y / (y + C(1.0)) def approx_tanh(x): # <4.0e-5 absolute error over [-5, 5] x = x * C(2.0) y = approx_exp(x) return (y - C(1.0)) / (y + C(1.0)) ``` See unit tests for implementations of these approximate transendentals.
1 parent 66f3bf8 commit 2ed31b2

File tree

12 files changed

+242
-10
lines changed

12 files changed

+242
-10
lines changed

docs/api/python/topi.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ List of operators
4040
topi.sigmoid
4141
topi.clip
4242
topi.cast
43+
topi.reinterpret
4344
topi.transpose
4445
topi.flip
4546
topi.strided_slice
@@ -133,6 +134,7 @@ topi
133134
.. autofunction:: topi.sigmoid
134135
.. autofunction:: topi.clip
135136
.. autofunction:: topi.cast
137+
.. autofunction:: topi.reinterpret
136138
.. autofunction:: topi.transpose
137139
.. autofunction:: topi.flip
138140
.. autofunction:: topi.strided_slice

docs/langref/relay_op.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ This level enables additional math and transform operators.
114114
tvm.relay.full
115115
tvm.relay.full_like
116116
tvm.relay.cast
117+
tvm.relay.reinterpret
117118
tvm.relay.split
118119
tvm.relay.arange
119120
tvm.relay.stack
@@ -263,6 +264,7 @@ Level 3 Definitions
263264
.. autofunction:: tvm.relay.full
264265
.. autofunction:: tvm.relay.full_like
265266
.. autofunction:: tvm.relay.cast
267+
.. autofunction:: tvm.relay.reinterpret
266268
.. autofunction:: tvm.relay.split
267269
.. autofunction:: tvm.relay.arange
268270
.. autofunction:: tvm.relay.stack

python/tvm/relay/op/_transform.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
_reg.register_schedule("repeat", schedule_broadcast)
4141
_reg.register_schedule("tile", schedule_broadcast)
4242
_reg.register_schedule("cast", schedule_injective)
43+
_reg.register_schedule("reinterpret", schedule_injective)
4344
_reg.register_schedule("strided_slice", schedule_injective)
4445
_reg.register_schedule("slice_like", schedule_injective)
4546
_reg.register_schedule("split", schedule_injective)

python/tvm/relay/op/transform.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,26 @@ def cast(data, dtype):
4040
return _relay_make.cast(data, dtype)
4141

4242

43+
def reinterpret(data, dtype):
44+
"""Reinterpret input tensor to data type.
45+
46+
Parameters
47+
----------
48+
data : relay.Expr
49+
The input data to the operator.
50+
51+
dtype: str
52+
The target data type
53+
54+
Returns
55+
-------
56+
result : relay.Expr
57+
The reinterpreted result.
58+
"""
59+
from .. import _make as _relay_make
60+
return _relay_make.reinterpret(data, dtype)
61+
62+
4363
def expand_dims(data, axis, num_newaxis=1):
4464
"""Insert `num_newaxis` axises at the position given by `axis`.
4565

src/codegen/codegen_c.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* to you under the Apache License, Version 2.0 (the
77
* "License"); you may not use this file except in compliance
88
* with the License. You may obtain a copy of the License at
9-
*
9+
*
1010
* http://www.apache.org/licenses/LICENSE-2.0
11-
*
11+
*
1212
* Unless required by applicable law or agreed to in writing,
1313
* software distributed under the License is distributed on an
1414
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -569,6 +569,13 @@ void CodeGenC::VisitExpr_(const Call *op, std::ostream& os) { // NOLINT(*)
569569
os << "(";
570570
this->PrintExpr(op->args[0], os);
571571
os << " == NULL)";
572+
} else if (op->is_intrinsic(Call::reinterpret)) {
573+
// generate (*( TYPE *)(&(ARG)))
574+
os << "(*(";
575+
this->PrintType(op->type, os);
576+
os << " *)(&(";
577+
this->PrintExpr(op->args[0], os);
578+
os << ")))";
572579
} else {
573580
if (op->call_type == Call::Intrinsic ||
574581
op->call_type == Call::PureIntrinsic) {

src/relay/op/tensor/transform.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,37 @@ RELAY_REGISTER_OP("cast")
9797
.set_attr<TOpPattern>("TOpPattern", kElemWise)
9898
.set_attr<FInferCorrectLayout>("FInferCorrectLayout", ElemwiseArbitraryLayout);
9999

100+
Array<Tensor> ReinterpretCompute(const Attrs& attrs, const Array<Tensor>& inputs,
101+
const Type& out_type, const Target& target) {
102+
const CastAttrs* param = attrs.as<CastAttrs>();
103+
CHECK(param != nullptr);
104+
DataType dtype = param->dtype;
105+
return {topi::reinterpret(inputs[0], dtype)};
106+
}
107+
108+
Expr MakeReinterpret(Expr data, DataType dtype) {
109+
auto attrs = make_node<CastAttrs>();
110+
attrs->dtype = dtype;
111+
static const Op& op = Op::Get("reinterpret");
112+
return CallNode::make(op, {data}, Attrs(attrs), {});
113+
}
114+
115+
TVM_REGISTER_API("relay._make.reinterpret").set_body([](const TVMArgs& args, TVMRetValue* rv) {
116+
runtime::detail::unpack_call<Expr, 2>(MakeReinterpret, args, rv);
117+
});
118+
119+
RELAY_REGISTER_OP("reinterpret")
120+
.describe(R"code(Reinterpret the data into a new data type.
121+
)code" TVM_ADD_FILELINE)
122+
.set_num_inputs(1)
123+
.set_attrs_type_key("relay.attrs.CastAttrs")
124+
.add_argument("data", "Tensor", "The input tensor.")
125+
.set_support_level(3)
126+
.add_type_rel("Reinterpret", CastRel)
127+
.set_attr<FTVMCompute>("FTVMCompute", ReinterpretCompute)
128+
.set_attr<TOpPattern>("TOpPattern", kElemWise)
129+
.set_attr<FInferCorrectLayout>("FInferCorrectLayout", ElemwiseArbitraryLayout);
130+
100131
// relay.expand_dims
101132
TVM_REGISTER_NODE_TYPE(ExpandDimsAttrs);
102133

tests/python/relay/test_op_level3.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def test_cast():
7575
assert "dtype=" in yy.astext()
7676
assert yy.checked_type == relay.TensorType((8, 9, 4), "int32")
7777

78+
7879
def test_clip():
7980
a = relay.var("a", relay.TensorType((10, 4), "float32"))
8081
y = relay.clip(a, 1., 4.)
@@ -88,6 +89,69 @@ def test_clip():
8889
np.testing.assert_allclose(op_res.asnumpy(), ref_res, rtol=0.01)
8990

9091

92+
def test_reinterpret():
93+
a = relay.var("a", relay.TensorType((1000, 4), "float32"))
94+
y = relay.reinterpret(a, "int32")
95+
yy = run_infer_type(y)
96+
assert yy.checked_type == relay.TensorType((1000, 4), "int32")
97+
98+
data = np.random.randn(1000, 4).astype('float32') * 1000
99+
intrp = create_executor()
100+
op_res = intrp.evaluate(y, {a: relay.const(data)})
101+
ref_res = data.view("int32")
102+
np.testing.assert_equal(op_res.asnumpy(), ref_res)
103+
104+
105+
def test_approximate_transcendental():
106+
def C(x):
107+
return relay.expr.const(x, "float32")
108+
109+
def approx_exp(x):
110+
# An approximation derived from Opus,
111+
# https://github.com/xiph/opus/blob/c1c247/celt/mathops.h#L147-L165
112+
x = relay.minimum(relay.maximum(x, C(-88.0)), C(88.0))
113+
x = C(127.0) + x * C(1.44269504)
114+
xf = relay.floor(x)
115+
i = relay.cast(xf, "int32")
116+
x = x - xf
117+
Y = C(0.99992522) + x * (C(0.69583354) + x * (C(0.22606716) + x * C(0.078024523)))
118+
exponent = relay.left_shift(i, relay.expr.const(23, "int32"))
119+
exponent = relay.reinterpret(exponent, "float32")
120+
return exponent * Y
121+
122+
def approximate_sigmoid(x):
123+
y = approx_exp(x)
124+
return y / (y + C(1.0))
125+
126+
def approximate_tanh(x):
127+
x = x * C(2.0)
128+
y = approx_exp(x)
129+
return (y - C(1.0)) / (y + C(1.0))
130+
131+
a = relay.var("a", relay.TensorType((1000,), "float32"))
132+
y = approximate_sigmoid(a)
133+
yy = run_infer_type(y)
134+
assert yy.checked_type == relay.TensorType((1000,), "float32")
135+
data = np.linspace(-5, 5, 1000).astype("float32")
136+
intrp = create_executor()
137+
op_res = intrp.evaluate(y, {a: relay.const(data)})
138+
139+
def reference_sigmoid(x):
140+
return np.exp(-np.logaddexp(0, -x))
141+
np.testing.assert_allclose(op_res.asnumpy(), reference_sigmoid(data), atol=2e-5, rtol=1e-9)
142+
143+
y = approximate_tanh(a)
144+
yy = run_infer_type(y)
145+
assert yy.checked_type == relay.TensorType((1000,), "float32")
146+
data = np.linspace(-5, 5, 1000).astype("float32")
147+
intrp = create_executor()
148+
op_res = intrp.evaluate(y, {a: relay.const(data)})
149+
150+
def reference_tanh(x):
151+
return np.tanh(x)
152+
np.testing.assert_allclose(op_res.asnumpy(), reference_tanh(data), atol=4e-5, rtol=1e-9)
153+
154+
91155
def test_squeeze():
92156
def verify_squeeze(shape, dtype, axis):
93157
x = relay.var("x", relay.TensorType(shape, dtype))

tests/python/unittest/test_codegen_c_host.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,31 @@ def check_c():
9595
with tvm.build_config(offset_factor=4):
9696
check_c()
9797

98+
99+
def test_reinterpret():
100+
nn = 1024
101+
n = tvm.convert(nn)
102+
A = tvm.placeholder((n,), name='A', dtype="int32")
103+
B = tvm.compute(A.shape, lambda *i: tvm.call_pure_intrin("float32", "reinterpret", A(*i)), name='B')
104+
s = tvm.create_schedule(B.op)
105+
106+
def check_c():
107+
mhost = tvm.build(s, [A, B], "c", name="reinterpret")
108+
temp = util.tempdir()
109+
path_dso = temp.relpath("temp.so")
110+
mhost.export_library(path_dso)
111+
m = tvm.module.load(path_dso)
112+
fadd = m['reinterpret']
113+
ctx = tvm.cpu(0)
114+
n = nn
115+
a = tvm.nd.array(np.random.randint(-2 ** 30, 2 ** 30, size=n).astype(A.dtype), ctx)
116+
b = tvm.nd.array(np.zeros(n, dtype=B.dtype), ctx)
117+
fadd(a, b)
118+
tvm.testing.assert_allclose(
119+
b.asnumpy(), a.asnumpy().view('float32'))
120+
check_c()
121+
98122
if __name__ == "__main__":
99123
test_add()
100124
test_add_pipeline()
125+
test_reinterpret()

topi/include/topi/elemwise.h

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,34 @@ inline Tensor cast(const Tensor& x,
269269
}
270270

271271
/*!
272-
* \brief Creates an operation that sum each element of a tensor
273-
*
274-
* \param xs The input tensor array
275-
* \param name The name of the operation
276-
* \param tag The tag to mark the operation
277-
*
278-
* \return A Tensor whose op member is the sum operation
279-
*/
272+
* \brief Reinterpret each element of x to the given type.
273+
274+
* \param x The input tensor
275+
* \param type The type to cast to
276+
* \param name The name of the operation
277+
* \param tag The tag to mark the operation
278+
*
279+
* \return A Tensor whose op member is the reinterpret operation
280+
*/
281+
inline Tensor reinterpret(const Tensor& x, Type type, std::string name = "tensor",
282+
std::string tag = kElementWise) {
283+
return compute(x->shape,
284+
[&](const Array<Var>& i) {
285+
return tvm::ir::Call::make(type, "reinterpret", {x(i)},
286+
tvm::ir::Call::PureIntrinsic);
287+
},
288+
name, tag);
289+
}
290+
291+
/*!
292+
* \brief Creates an operation that sum each element of a tensor
293+
*
294+
* \param xs The input tensor array
295+
* \param name The name of the operation
296+
* \param tag The tag to mark the operation
297+
*
298+
* \return A Tensor whose op member is the sum operation
299+
*/
280300
inline Tensor elemwise_sum(const Array<Tensor>& xs,
281301
std::string name = "T_elemwise_sum",
282302
std::string tag = kElementWise) {

topi/python/topi/math.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,21 @@ def cast(x, dtype):
343343
return tvm.compute(
344344
x.shape, lambda *i: x(*i).astype(dtype), tag=tag.ELEMWISE)
345345
return tvm.make._cast(dtype, x)
346+
347+
def reinterpret(x, dtype):
348+
"""Reinterpret input to specified data type.
349+
350+
Parameters
351+
----------
352+
x : tvm.Tensor
353+
Input argument.
354+
355+
dtype : str
356+
Data type.
357+
358+
Returns
359+
-------
360+
y : tvm.Tensor
361+
The result.
362+
"""
363+
return cpp.reinterpret(x, dtype)

0 commit comments

Comments
 (0)