Expected behavior
Embedding float32 parameter values as Relax constants, or binding equivalent values with Function.bind_params, should preserve the numerical result of a Relax function compiled for LLVM.
Passing the same values as explicit VM arguments should be numerically equivalent to the embedded-constant form.
Actual behavior
On Windows, a pure Relax program produces an incorrect result when its weight and twelve broadcast bias vectors are embedded as relax.const values.
The same program is correct when those values are passed as explicit VM arguments.
For the minimal reproducer below, the embedded-constant path produces -2.010380268096924, while the NumPy reference is -2.289207935333252.
The explicit-parameter path has a maximum absolute difference of 0 for the same input values.
The minimal reproducer does not involve a model frontend.
Environment
- OS: Windows 11
- Python: 3.11.14
- TVM: 0.25.0
- Build: local Release build with LLVM
22.1.8, target llvm
- Device: CPU
Steps to reproduce
Save the following as repro_relax_const.py and run it with the Python environment that imports the matching TVM source and compiled libraries.
import numpy as np
import tvm
from tvm import relax
from tvm.runtime import tensor as tvm_tensor
def run(embed_as_constants):
rng = np.random.default_rng(0)
x_value = rng.normal(size=(1, 1)).astype("float32")
weight_value = rng.normal(size=(1, 1)).astype("float32")
bias_values = [rng.normal(size=(1,)).astype("float32") for _ in range(12)]
expected = x_value @ weight_value
for bias_value in bias_values:
expected = expected + bias_value
sinfo_x = relax.TensorStructInfo((1, 1), "float32")
sinfo_w = relax.TensorStructInfo((1, 1), "float32")
sinfo_b = relax.TensorStructInfo((1,), "float32")
x = relax.Var("x", sinfo_x)
weight = relax.Var("weight", sinfo_w)
biases = [relax.Var(f"bias_{i}", sinfo_b) for i in range(12)]
if embed_as_constants:
weight_expr = relax.const(weight_value)
bias_exprs = [relax.const(value) for value in bias_values]
function_params = [x]
else:
weight_expr = weight
bias_exprs = biases
function_params = [x, weight, *biases]
bb = relax.BlockBuilder()
with bb.function("main", function_params):
with bb.dataflow():
result = bb.emit(relax.op.matmul(x, weight_expr))
for bias_expr in bias_exprs:
result = bb.emit(relax.op.add(result, bias_expr))
output = bb.emit_output(result)
bb.emit_func_output(output)
mod = bb.finalize()
executable = relax.build(mod, target="llvm")
vm = relax.VirtualMachine(executable, tvm.cpu(0))
arguments = [tvm_tensor(x_value, tvm.cpu(0))]
if not embed_as_constants:
arguments.extend([tvm_tensor(weight_value, tvm.cpu(0))])
arguments.extend(tvm_tensor(value, tvm.cpu(0)) for value in bias_values)
actual = vm["main"](*arguments).numpy()
print(f"embed_as_constants={embed_as_constants}")
print("expected:", expected)
print("actual: ", actual)
np.testing.assert_allclose(actual, expected, rtol=1e-5, atol=1e-5)
run(False) # Passes: maximum absolute difference is 0.
run(True) # Fails: maximum absolute difference is about 0.27882767.
run(False) passes.
run(True) fails with:
ACTUAL: [[-2.0103803]]
DESIRED: [[-2.289208]]
Max absolute difference: 0.27882767
I also reproduced the same parameter-mode distinction with an ONNX DenseNet-121 model on the same Windows environment.
from_onnx(..., keep_params_in_input=False) produced a maximum absolute difference of 0.848955 against ONNX Runtime, while keep_params_in_input=True passed with a maximum absolute difference of 3.58e-06.
This ONNX result is supporting evidence only; the pure Relax script above is the intended minimal reproducer.
Triage
Expected behavior
Embedding
float32parameter values as Relax constants, or binding equivalent values withFunction.bind_params, should preserve the numerical result of a Relax function compiled for LLVM.Passing the same values as explicit VM arguments should be numerically equivalent to the embedded-constant form.
Actual behavior
On Windows, a pure Relax program produces an incorrect result when its weight and twelve broadcast bias vectors are embedded as
relax.constvalues.The same program is correct when those values are passed as explicit VM arguments.
For the minimal reproducer below, the embedded-constant path produces
-2.010380268096924, while the NumPy reference is-2.289207935333252.The explicit-parameter path has a maximum absolute difference of
0for the same input values.The minimal reproducer does not involve a model frontend.
Environment
22.1.8, targetllvmSteps to reproduce
Save the following as
repro_relax_const.pyand run it with the Python environment that imports the matching TVM source and compiled libraries.run(False)passes.run(True)fails with:I also reproduced the same parameter-mode distinction with an ONNX DenseNet-121 model on the same Windows environment.
from_onnx(..., keep_params_in_input=False)produced a maximum absolute difference of0.848955against ONNX Runtime, whilekeep_params_in_input=Truepassed with a maximum absolute difference of3.58e-06.This ONNX result is supporting evidence only; the pure Relax script above is the intended minimal reproducer.
Triage