chore: import upstream snapshot with attribution
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
# 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 sys
import tvm
@tvm.contrib.pickle_memoize.memoize("test_memoize_save_data", save_at_exit=True)
def get_data_saved():
return 42
@tvm.contrib.pickle_memoize.memoize("test_memoize_transient_data", save_at_exit=False)
def get_data_transient():
return 42
def main():
assert len(sys.argv) == 3, "Expect arguments SCRIPT NUM_SAVED NUM_TRANSIENT"
num_iter_saved = int(sys.argv[1])
num_iter_transient = int(sys.argv[2])
for _ in range(num_iter_saved):
get_data_saved()
for _ in range(num_iter_transient):
get_data_transient()
if __name__ == "__main__":
main()
@@ -0,0 +1,19 @@
# isort: skip_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.
"""Testing infrastructure for Android"""
@@ -0,0 +1,58 @@
# 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.
# pylint: disable=invalid-name
"""Android testing infrastructure"""
import os
import tvm
from tvm.s_tir.meta_schedule.runner import EvaluatorConfig, RPCConfig, RPCRunner
def get_rpc_runner() -> tvm.s_tir.meta_schedule.runner.RPCRunner:
if (
"TVM_TRACKER_HOST" in os.environ
and "TVM_TRACKER_PORT" in os.environ
and "RPC_DEVICE_KEY" in os.environ
):
rpc_host = os.environ["TVM_TRACKER_HOST"]
rpc_port = int(os.environ["TVM_TRACKER_PORT"])
rpc_key = os.environ["RPC_DEVICE_KEY"]
else:
raise Exception("Please initialize environment variables for using RPC tracker")
rpc_config = RPCConfig(
tracker_host=rpc_host,
tracker_port=rpc_port,
tracker_key=rpc_key,
session_priority=1,
session_timeout_sec=100,
)
evaluator_config = EvaluatorConfig(
number=1,
repeat=1,
min_repeat_ms=0,
)
return RPCRunner(rpc_config, evaluator_config)
def get_android_gpu_target() -> tvm.target.Target:
"""Creates a Android GPU target"""
target_c = "opencl"
target_h = {"kind": "llvm", "mtriple": "arm64-linux-android"}
return tvm.target.Target(target_c, host=target_h)
@@ -0,0 +1,78 @@
# 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.
# ruff: noqa: F401
"""Test rpc based launcher for Android"""
import tempfile
import numpy as np
import pytest
pytest.importorskip("scipy") # tvm.topi.testing imports scipy
import tvm.testing
import tvm.topi.testing
from tvm.s_tir import meta_schedule as ms
from tvm.s_tir.meta_schedule.builder import LocalBuilder
from tvm.script import tirx as T
from .infrastructure import get_android_gpu_target, get_rpc_runner
@T.prim_func(s_tir=True)
def matmul(a: T.handle, b: T.handle, c: T.handle) -> None:
A = T.match_buffer(a, [128, 128])
B = T.match_buffer(b, [128, 128])
C = T.match_buffer(c, [128, 128])
for i, j, k in T.grid(128, 128, 128):
with T.sblock("update"):
vi, vj, vk = T.axis.remap("SSR", [i, j, k])
with T.init():
C[vi, vj] = 0.0
C[vi, vj] = C[vi, vj] + A[vi, vk] * B[vj, vk]
@pytest.mark.skip("Integration test")
def test_tune_tir_on_android():
"""Test tune_tir on Android through RPC."""
max_workers = 4
builder = LocalBuilder(
f_export="s_tir.meta_schedule.builder.export_ndk", max_workers=max_workers
)
runner = get_rpc_runner()
target = get_android_gpu_target()
with tempfile.TemporaryDirectory() as work_dir:
database = ms.tir_integration.tune_tir(
mod=matmul,
target=target,
work_dir=work_dir,
max_trials_global=32,
num_trials_per_iter=16,
builder=builder,
runner=runner,
)
sch = ms.tir_integration.compile_tir(database, matmul, target)
if sch is None:
print("No valid schedule found!")
else:
sch.mod.show()
sch.trace.show()
if __name__ == "__main__":
tvm.testing.main()
+301
View File
@@ -0,0 +1,301 @@
# 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.
"""Configure pytest"""
import numpy as np
import pytest
pytest.importorskip("scipy") # tvm.topi.testing imports scipy
import tvm
import tvm.testing
import tvm.topi.testing
from tvm import te
from tvm.contrib import cblas, dnnl, mkl
def verify_matmul_add(
matrix_m, matrix_l, matrix_n, lib, transa=False, transb=False, dtype="float32"
):
"""Tests matmul+add op"""
bias = te.var("bias", dtype=dtype)
ashape = (matrix_l, matrix_n) if transa else (matrix_n, matrix_l)
bshape = (matrix_m, matrix_l) if transb else (matrix_l, matrix_m)
input1_data = te.placeholder(ashape, name="input1_data", dtype=dtype)
input2_data = te.placeholder(bshape, name="input2_data", dtype=dtype)
matmul_result = lib.matmul(input1_data, input2_data, transa, transb)
final_result = te.compute(
matmul_result.shape, lambda i, j: matmul_result[i, j] + bias, name="final_result"
)
def get_numpy(a, b, matrix_bias, transa, transb):
if transa:
a = a.transpose()
if transb:
b = b.transpose()
return np.dot(a, b) + matrix_bias
def compiling(f, name="test_matmul_add", ext=".so"):
path = name + ext
f.export_library(path)
mod = tvm.runtime.load_module(path)
f = mod[name]
return f
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func(lib.__name__ + ".matmul", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
name = "test_matmul_add"
f = tvm.compile(
te.create_prim_func([input1_data, input2_data, final_result, bias]).with_attr(
"global_symbol", name
),
target=target,
)
if target == "c":
f = compiling(f, name)
matrix_input1 = tvm.runtime.tensor(
np.random.uniform(size=ashape).astype(input1_data.dtype), dev
)
matrix_input2 = tvm.runtime.tensor(
np.random.uniform(size=bshape).astype(input2_data.dtype), dev
)
matrix_result = tvm.runtime.tensor(
np.zeros((matrix_n, matrix_m), dtype=final_result.dtype), dev
)
matrix_bias = 10.0
f(matrix_input1, matrix_input2, matrix_result, matrix_bias)
tvm.testing.assert_allclose(
matrix_result.numpy(),
get_numpy(matrix_input1.numpy(), matrix_input2.numpy(), matrix_bias, transa, transb),
rtol=1e-5,
)
verify("llvm")
verify("c")
def test_matmul_add():
"""Tests of matmul+add op"""
verify_matmul_add(235, 128, 1024, cblas)
verify_matmul_add(235, 128, 1024, cblas, True, False)
verify_matmul_add(235, 128, 1024, cblas, False, True)
verify_matmul_add(235, 128, 1024, cblas, True, True)
verify_matmul_add(235, 128, 1024, mkl)
verify_matmul_add(235, 128, 1024, mkl, True, False)
verify_matmul_add(235, 128, 1024, mkl, False, True)
verify_matmul_add(235, 128, 1024, mkl, True, True)
verify_matmul_add(235, 128, 1024, dnnl)
verify_matmul_add(235, 128, 1024, dnnl, True, False)
verify_matmul_add(235, 128, 1024, dnnl, False, True)
verify_matmul_add(235, 128, 1024, dnnl, True, True)
verify_matmul_add(1, 16, 4, cblas)
verify_matmul_add(1, 16, 3, cblas, True, False)
verify_matmul_add(1, 16, 3, cblas, False, False)
verify_matmul_add(1, 16, 3, cblas, True, True)
verify_matmul_add(1, 16, 4, mkl)
verify_matmul_add(1, 16, 3, mkl, True, False)
verify_matmul_add(1, 16, 3, mkl, False, False)
verify_matmul_add(1, 16, 3, mkl, True, True)
verify_matmul_add(1, 16, 4, dnnl)
verify_matmul_add(1, 16, 3, dnnl, True, False)
verify_matmul_add(1, 16, 3, dnnl, False, False)
verify_matmul_add(1, 16, 3, dnnl, True, True)
def verify_quantized_matmul_add(matrix_m, matrix_l, matrix_n, transa=False, transb=False):
"""Tests quantized matmul+add op"""
if not tvm.get_global_func("tvm.contrib.mkl.matmul_u8s8s32", True):
pytest.skip("Quantized dense is supported only for MKL. TVM GPU CI uses openblas")
data_dtype = "uint8"
kernel_dtype = "int8"
out_dtype = "int32"
bias = te.var("bias", dtype=out_dtype)
ashape = (matrix_l, matrix_n) if transa else (matrix_n, matrix_l)
bshape = (matrix_m, matrix_l) if transb else (matrix_l, matrix_m)
input1_data = te.placeholder(ashape, name="input1_data", dtype=data_dtype)
input2_data = te.placeholder(bshape, name="input2_data", dtype=kernel_dtype)
matmul_result = mkl.matmul_u8s8s32(input1_data, input2_data, transa, transb, dtype=out_dtype)
final_result = te.compute(
matmul_result.shape, lambda i, j: matmul_result[i, j] + bias, name="final_result"
)
def get_numpy(a, b, matrix_bias, transa, transb):
if transa:
a = a.transpose()
if transb:
b = b.transpose()
return np.dot(a, b) + matrix_bias
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func("tvm.contrib.mkl.matmul_u8s8s32", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
f = tvm.compile(
te.create_prim_func([input1_data, input2_data, final_result, bias]), target=target
)
matrix_input1 = tvm.runtime.tensor(
np.random.randint(low=0, high=50, size=ashape).astype(input1_data.dtype), dev
)
matrix_input2 = tvm.runtime.tensor(
np.random.randint(low=0, high=50, size=bshape).astype(input2_data.dtype), dev
)
matrix_result = tvm.runtime.tensor(
np.zeros((matrix_n, matrix_m), dtype=final_result.dtype), dev
)
matrix_bias = 10
f(matrix_input1, matrix_input2, matrix_result, matrix_bias)
tvm.testing.assert_allclose(
matrix_result.numpy(),
get_numpy(
matrix_input1.numpy().astype("int32"),
matrix_input2.numpy().astype("int32"),
matrix_bias,
transa,
transb,
),
rtol=1e-5,
)
verify()
def test_quantized_matmul_add():
"""Tests of quantized matmul+add op"""
verify_quantized_matmul_add(235, 128, 1024)
verify_quantized_matmul_add(235, 128, 1024, True, False)
verify_quantized_matmul_add(235, 128, 1024, False, True)
verify_quantized_matmul_add(235, 128, 1024, True, True)
verify_quantized_matmul_add(1, 16, 4)
verify_quantized_matmul_add(1, 16, 3, True, False)
verify_quantized_matmul_add(1, 16, 3, False, True)
verify_quantized_matmul_add(1, 16, 3, True, True)
def verify_batch_matmul(
batch_a,
batch_b,
matrix_m,
matrix_l,
matrix_n,
lib,
transa=False,
transb=False,
dtype="float32",
):
"""Tests matmul op where matrices are in batch"""
batch = max(batch_a, batch_b)
ashape = (batch_a, matrix_l, matrix_n) if transa else (batch_a, matrix_n, matrix_l)
bshape = (batch_b, matrix_m, matrix_l) if transb else (batch_b, matrix_l, matrix_m)
input1_data = te.placeholder(ashape, name="input1_data", dtype=dtype)
input2_data = te.placeholder(bshape, name="input2_data", dtype=dtype)
matmul_result = lib.batch_matmul(input1_data, input2_data, transa, transb)
final_result = te.compute(
matmul_result.shape, lambda k, i, j: matmul_result[k, i, j], name="final_result"
)
def get_numpy(a, b, transa, transb):
if transa:
a = a.transpose(0, 2, 1)
if not transb:
b = b.transpose(0, 2, 1)
return tvm.topi.testing.batch_matmul(a, b)
def compiling(f, name="test_batch_matmul", ext=".so"):
path = name + ext
f.export_library(path)
mod = tvm.runtime.load_module(path)
f = mod[name]
return f
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func(lib.__name__ + ".matmul", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
name = "test_batch_matmul"
f = tvm.compile(
te.create_prim_func([input1_data, input2_data, final_result]), target=target
)
if target == "c":
f = compiling(f, name)
matrix_input1 = tvm.runtime.tensor(
np.random.uniform(size=ashape).astype(input1_data.dtype), dev
)
matrix_input2 = tvm.runtime.tensor(
np.random.uniform(size=bshape).astype(input2_data.dtype), dev
)
matrix_result = tvm.runtime.tensor(
np.zeros((batch, matrix_n, matrix_m), dtype=final_result.dtype), dev
)
f(matrix_input1, matrix_input2, matrix_result)
tvm.testing.assert_allclose(
matrix_result.numpy(),
get_numpy(matrix_input1.numpy(), matrix_input2.numpy(), transa, transb),
rtol=1e-5,
)
verify("llvm")
verify("c")
def test_batch_matmul():
"""Tests of matmul op where matrices are in batch"""
verify_batch_matmul(16, 16, 235, 128, 1024, cblas)
verify_batch_matmul(16, 16, 235, 128, 1024, cblas, True, False)
verify_batch_matmul(16, 16, 235, 128, 1024, cblas, False, True)
verify_batch_matmul(16, 16, 235, 128, 1024, cblas, True, True)
verify_batch_matmul(16, 16, 235, 128, 1024, mkl)
verify_batch_matmul(16, 16, 235, 128, 1024, mkl, True, False)
verify_batch_matmul(16, 16, 235, 128, 1024, mkl, False, True)
verify_batch_matmul(16, 16, 235, 128, 1024, mkl, True, True)
verify_batch_matmul(16, 1, 235, 128, 1024, cblas)
verify_batch_matmul(1, 16, 235, 128, 1024, cblas)
verify_batch_matmul(16, 1, 235, 128, 1024, cblas)
verify_batch_matmul(1, 16, 235, 128, 1024, cblas)
verify_batch_matmul(16, 1, 235, 128, 1024, mkl)
verify_batch_matmul(1, 16, 235, 128, 1024, mkl)
verify_batch_matmul(16, 1, 235, 128, 1024, mkl)
verify_batch_matmul(1, 16, 235, 128, 1024, mkl)
verify_batch_matmul(1, 1, 1, 16, 3, cblas)
verify_batch_matmul(1, 1, 1, 16, 3, cblas, True, False)
verify_batch_matmul(1, 1, 1, 16, 3, cblas, False, False)
verify_batch_matmul(1, 1, 1, 16, 3, cblas, True, True)
verify_batch_matmul(1, 1, 1, 16, 3, cblas)
verify_batch_matmul(1, 1, 1, 16, 3, mkl)
verify_batch_matmul(1, 1, 1, 16, 3, mkl, True, False)
verify_batch_matmul(1, 1, 1, 16, 3, mkl, False, False)
verify_batch_matmul(1, 1, 1, 16, 3, mkl, True, True)
verify_batch_matmul(1, 1, 1, 16, 3, mkl)
if __name__ == "__main__":
test_matmul_add()
test_quantized_matmul_add()
test_batch_matmul()
+108
View File
@@ -0,0 +1,108 @@
# 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.
# ruff: noqa: F401
import os
import numpy as np
import pytest
import tvm
from tvm import rpc, te
from tvm.contrib import coreml_runtime
from tvm.support import utils, xcode
proxy_host = os.environ.get("TVM_IOS_RPC_PROXY_HOST", "127.0.0.1")
proxy_port = os.environ.get("TVM_IOS_RPC_PROXY_PORT", 9090)
destination = os.environ.get("TVM_IOS_RPC_DESTINATION", "")
key = "iphone"
@pytest.mark.skip("skip because coremltools is not available in CI")
def test_coreml_runtime():
import coremltools
from coremltools.models.neural_network import NeuralNetworkBuilder
def create_coreml_model():
shape = (2,)
alpha = 2
inputs = [
("input0", coremltools.models.datatypes.Array(*shape)),
("input1", coremltools.models.datatypes.Array(*shape)),
]
outputs = [
("output0", coremltools.models.datatypes.Array(*shape)),
("output1", coremltools.models.datatypes.Array(*shape)),
]
builder = NeuralNetworkBuilder(inputs, outputs)
builder.add_elementwise(
name="Add", input_names=["input0", "input1"], output_name="output0", mode="ADD"
)
builder.add_elementwise(
name="Mul", alpha=alpha, input_names=["input0"], output_name="output1", mode="MULTIPLY"
)
return coremltools.models.MLModel(builder.spec)
def verify(coreml_model, model_path, dev):
coreml_model = create_coreml_model()
out_spec = coreml_model.output_description._fd_spec
out_names = [spec.name for spec in out_spec]
# inference via coremltools
inputs = {}
for in_spec in coreml_model.input_description._fd_spec:
name = in_spec.name
shape = in_spec.type.multiArrayType.shape
inputs[name] = np.random.random_sample(shape)
coreml_outputs = [coreml_model.predict(inputs)[name] for name in out_names]
# inference via tvm coreml runtime
runtime = coreml_runtime.create("main", model_path, dev)
for name in inputs:
runtime.set_input(name, tvm.runtime.tensor(inputs[name], dev))
runtime.invoke()
tvm_outputs = [runtime.get_output(i).numpy() for i in range(runtime.get_num_outputs())]
for c_out, t_out in zip(coreml_outputs, tvm_outputs):
np.testing.assert_almost_equal(c_out, t_out, 3)
def check_remote(coreml_model):
temp = utils.tempdir()
compiled_model = xcode.compile_coreml(coreml_model, out_dir=temp.temp_dir)
xcode.popen_test_rpc(
proxy_host, proxy_port, key, destination=destination, libs=[compiled_model]
)
compiled_model = os.path.basename(compiled_model)
remote = rpc.connect(proxy_host, proxy_port, key=key)
dev = remote.cpu(0)
verify(coreml_model, compiled_model, dev)
def check_local(coreml_model):
temp = utils.tempdir()
compiled_model = xcode.compile_coreml(coreml_model, out_dir=temp.temp_dir)
dev = tvm.cpu(0)
verify(coreml_model, compiled_model, dev)
coreml_model = create_coreml_model()
check_remote(coreml_model)
check_local(coreml_model)
if __name__ == "__main__":
test_coreml_runtime()
+399
View File
@@ -0,0 +1,399 @@
# 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 ml_dtypes
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm.contrib.pickle_memoize import memoize
from tvm.testing import env
def get_random_tensor(shape, dtype):
if dtype == "int8":
return np.random.randint(-128, 128, shape).astype(dtype)
elif dtype == "uint8":
return np.random.randint(0, 256, shape).astype(dtype)
return np.random.uniform(-1, 1, shape).astype(dtype)
def verify_group_gemm(
func_name, M, N, K, num_groups, x_dtype, weight_dtype, out_dtype, use_scale, rtol, atol
):
group_gemm_func = tvm.get_global_func(func_name, allow_missing=True)
if group_gemm_func is None:
print(f"Skipped as {func_name} is not available")
return
@memoize("tvm.contrib.cutlass.test_group_gemm_sm90")
def get_ref_data():
assert M % num_groups == 0
M_per_group = M // num_groups
a_np = get_random_tensor((M, K), x_dtype)
b_np = get_random_tensor((num_groups, N, K), weight_dtype)
indptr_np = np.arange(1, num_groups + 1).astype("int64") * M_per_group
c_np = np.concatenate(
[a_np[i * M_per_group : (i + 1) * M_per_group] @ b_np[i].T for i in range(num_groups)],
axis=0,
)
return a_np, b_np, indptr_np, c_np
def to_numpy_dtype(dtype):
mapping = {"float8_e5m2": ml_dtypes.float8_e5m2, "float8_e4m3fn": ml_dtypes.float8_e4m3fn}
return mapping.get(dtype, dtype)
a_np, b_np, indptr_np, c_np = get_ref_data()
def run_and_check():
dev = tvm.cuda(0)
a_nd = tvm.runtime.tensor(a_np.astype(to_numpy_dtype(x_dtype)), device=dev)
b_nd = tvm.runtime.tensor(b_np.astype(to_numpy_dtype(weight_dtype)), device=dev)
c_nd = tvm.runtime.empty(c_np.shape, dtype=out_dtype, device=dev)
indptr_nd = tvm.runtime.tensor(indptr_np, device=dev)
workspace = tvm.runtime.empty((4096 * 1024,), dtype="uint8", device=dev)
if use_scale:
scale = tvm.runtime.tensor(np.array([1.0], dtype="float32"), device=dev)
group_gemm_func(a_nd, b_nd, indptr_nd, workspace, scale, c_nd)
else:
group_gemm_func(a_nd, b_nd, indptr_nd, workspace, c_nd)
tvm.testing.assert_allclose(c_nd.numpy(), c_np, rtol=rtol, atol=atol)
tvm.testing.run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(not env.build_flag_enabled("USE_CUTLASS"), reason="need cutlass")
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(9), reason="need cuda compute >= 9.0")
def test_group_gemm_sm90():
verify_group_gemm(
"cutlass.group_gemm",
8,
128,
128,
4,
"float16",
"float16",
"float16",
False,
rtol=1e-3,
atol=1e-3,
)
verify_group_gemm(
"cutlass.group_gemm_e5m2_e5m2_fp16",
8,
16,
16,
4,
"float8_e5m2",
"float8_e5m2",
"float16",
True,
rtol=1e-1,
atol=1,
)
verify_group_gemm(
"cutlass.group_gemm_e4m3_e4m3_fp16",
8,
16,
16,
4,
"float8_e4m3fn",
"float8_e4m3fn",
"float16",
True,
rtol=1e-1,
atol=1,
)
@pytest.mark.skipif(not env.build_flag_enabled("USE_CUTLASS"), reason="need cutlass")
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(10), reason="need cuda compute >= 10.0")
def test_group_gemm_sm100():
verify_group_gemm(
"cutlass.group_gemm",
8,
128,
128,
4,
"bfloat16",
"bfloat16",
"bfloat16",
False,
rtol=1e-2,
atol=1e-3,
)
def rowwise_quant_fp8_e4m3(shape: tuple[int, int], block_size: tuple[int, int], dtype: str):
x_full_np = (np.random.rand(*shape) * 2 - 1).astype(dtype)
x_scale_shape = (
*shape[:-1],
(shape[-1] + block_size[1] - 1) // block_size[1],
)
# For each (block_size[1]) block, compute the max abs value of `w_full_np`
x_max_abs_np = np.zeros(x_scale_shape, dtype="float32")
for i in range(x_scale_shape[-1]):
x_max_abs_np[..., i] = np.max(
np.abs(x_full_np[..., i * block_size[1] : min((i + 1) * block_size[1], shape[-1])]),
axis=-1,
)[0]
# Scale is the `x_max_abs_np` divided by the max value of quant_dtype in ml_dtypes
fp8_max = float(ml_dtypes.finfo("float8_e4m3fn").max)
x_scale_np = x_max_abs_np / fp8_max
# `x_np` is the `x_full_np` divided by the `x_scale_np` (with block awareness),
# clamped to (-fp8_max, fp8_max), and cast to `quant_dtype`
x_np = np.zeros_like(x_full_np, dtype="float8_e4m3fn")
for i in range(x_scale_shape[-1]):
x_np[..., i * block_size[1] : min((i + 1) * block_size[1], shape[-1])] = np.clip(
x_full_np[..., i * block_size[1] : min((i + 1) * block_size[1], shape[-1])]
/ x_scale_np[..., i : i + 1],
-fp8_max,
fp8_max,
)
x_scale_np = np.random.rand(*x_scale_np.shape).astype("float32") / fp8_max
for i in range(x_scale_shape[-1]):
x_full_np[..., i * block_size[1] : min((i + 1) * block_size[1], shape[-1])] = (
x_np[..., i * block_size[1] : min((i + 1) * block_size[1], shape[-1])].astype(
x_scale_np.dtype
)
* x_scale_np[..., i : i + 1]
)
return x_np, x_scale_np
def blockwise_quant_fp8_e4m3(shape: tuple[int, int], block_size: tuple[int, int], dtype: str):
w_full_np = (np.random.rand(*shape) * 2 - 1).astype(dtype)
w_scale_shape = (
*shape[:-2],
(shape[-2] + block_size[0] - 1) // block_size[0],
(shape[-1] + block_size[1] - 1) // block_size[1],
)
# For each (block_size[0], block_size[1]) block, compute the max abs value of `w_full_np`
w_max_abs_np = np.zeros(w_scale_shape, dtype="float32")
for i in range(w_scale_shape[-2]):
for j in range(w_scale_shape[-1]):
block_shape = (
*shape[:-2],
min(block_size[0], shape[-2] - i * block_size[0]),
min(block_size[1], shape[-1] - j * block_size[1]),
)
w_max_abs_np[..., i, j] = np.max(
np.abs(
w_full_np[
...,
i * block_size[0] : min((i + 1) * block_size[0], shape[-2]),
j * block_size[1] : min((j + 1) * block_size[1], shape[-1]),
]
).reshape(*shape[:-2], block_shape[-2] * block_shape[-1]),
axis=-1,
)
# Scale is the `w_max_abs_np` divided by the max value of quant_dtype in ml_dtypes
fp8_max = float(ml_dtypes.finfo("float8_e4m3fn").max)
w_scale_np = w_max_abs_np / fp8_max
# `w_np` is the `w_full_np` divided by the `w_scale_np` (with block awareness),
# clamped to (-fp8_max, fp8_max), and cast to `quant_dtype`
w_np = np.zeros_like(w_full_np, dtype="float8_e4m3fn")
if len(w_scale_shape) == 2:
for i in range(w_scale_shape[-2]):
for j in range(w_scale_shape[-1]):
w_np[
i * block_size[0] : min((i + 1) * block_size[0], shape[-2]),
j * block_size[1] : min((j + 1) * block_size[1], shape[-1]),
] = np.clip(
w_full_np[
i * block_size[0] : min((i + 1) * block_size[0], shape[-2]),
j * block_size[1] : min((j + 1) * block_size[1], shape[-1]),
]
/ w_scale_np[..., i, j],
-fp8_max,
fp8_max,
)
else:
for e in range(w_scale_shape[0]):
for i in range(w_scale_shape[-2]):
for j in range(w_scale_shape[-1]):
w_np[
e,
i * block_size[0] : min((i + 1) * block_size[0], shape[-2]),
j * block_size[1] : min((j + 1) * block_size[1], shape[-1]),
] = np.clip(
w_full_np[
e,
i * block_size[0] : min((i + 1) * block_size[0], shape[-2]),
j * block_size[1] : min((j + 1) * block_size[1], shape[-1]),
]
/ w_scale_np[e, i, j],
-fp8_max,
fp8_max,
)
w_scale_np = np.random.rand(*w_scale_np.shape).astype("float32") / fp8_max
return w_np, w_scale_np
def blockwise_matmul(
x_fp8_np: np.ndarray,
x_scale_np: np.ndarray,
w_np: np.ndarray,
w_scale_np: np.ndarray,
block_size: tuple[int, int],
dtype: str,
):
o_np = np.zeros((x_fp8_np.shape[0], w_np.shape[0]), dtype=dtype)
for j in range(w_scale_np.shape[0]):
for k in range(w_scale_np.shape[1]):
o_np[:, j * block_size[0] : min((j + 1) * block_size[0], w_np.shape[0])] += (
np.matmul(
x_fp8_np[
:, k * block_size[1] : min((k + 1) * block_size[1], x_fp8_np.shape[1])
].astype(dtype),
w_np[
j * block_size[0] : min((j + 1) * block_size[0], w_np.shape[0]),
k * block_size[1] : min((k + 1) * block_size[1], w_np.shape[1]),
].T.astype(dtype),
)
* x_scale_np[:, k : k + 1]
* w_scale_np[j, k]
)
return o_np
def blockwise_bmm(
x_fp8_np: np.ndarray,
x_scale_np: np.ndarray,
w_np: np.ndarray,
w_scale_np: np.ndarray,
block_size: tuple[int, int],
dtype: str,
):
o_np = np.zeros((x_fp8_np.shape[0], x_fp8_np.shape[1], w_np.shape[1]), dtype=dtype)
for j in range(w_scale_np.shape[1]):
for k in range(w_scale_np.shape[2]):
o_np[..., j * block_size[0] : min((j + 1) * block_size[0], w_np.shape[1])] += (
np.matmul(
x_fp8_np[
..., k * block_size[1] : min((k + 1) * block_size[1], x_fp8_np.shape[2])
].astype(dtype),
w_np[
...,
j * block_size[0] : min((j + 1) * block_size[0], w_np.shape[1]),
k * block_size[1] : min((k + 1) * block_size[1], w_np.shape[2]),
]
.transpose(0, 2, 1)
.astype(dtype),
)
* x_scale_np[..., k : k + 1]
* w_scale_np[..., j : j + 1, k : k + 1]
)
return o_np
@pytest.mark.skipif(not env.build_flag_enabled("USE_CUTLASS"), reason="need cutlass")
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(9), reason="need cuda compute >= 9.0")
def test_fp8_e4m3_groupwise_scaled_gemm():
M = 16
N = 4608
K = 896
block_size = (128, 128)
assert N % 128 == 0 and K % 128 == 0 # Only support N/K are multiple of 128
func_name = "cutlass.groupwise_scaled_gemm_e4m3fn_e4m3fn"
gemm_func = tvm.get_global_func(func_name, allow_missing=True)
if gemm_func is None:
print(f"Skipped as {func_name} is not available")
return
dtype = "bfloat16"
x_np, x_scale_np = rowwise_quant_fp8_e4m3((M, K), block_size, dtype)
w_np, w_scale_np = blockwise_quant_fp8_e4m3((N, K), block_size, dtype)
o_np = blockwise_matmul(x_np, x_scale_np, w_np, w_scale_np, block_size, dtype)
def run_and_check():
device = tvm.cuda(0)
x_tvm = tvm.runtime.tensor(x_np, device=device)
x_scale_tvm = tvm.runtime.tensor(x_scale_np.T, device=device)
w_tvm = tvm.runtime.tensor(w_np, device=device)
w_scale_tvm = tvm.runtime.tensor(w_scale_np, device=device)
workspace = tvm.runtime.empty((4096 * 1024,), dtype="uint8", device=device)
o_tvm = tvm.runtime.empty((M, N), dtype=dtype, device=device)
gemm_func(
x_tvm,
w_tvm,
x_scale_tvm,
w_scale_tvm,
workspace,
block_size[0],
block_size[1],
o_tvm,
)
tvm.testing.assert_allclose(o_tvm.numpy(), o_np, rtol=1e-4, atol=0.5)
tvm.testing.run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(not env.build_flag_enabled("USE_CUTLASS"), reason="need cutlass")
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda_compute(9), reason="need cuda compute >= 9.0")
def test_fp8_e4m3_groupwise_scaled_bmm():
B = 16
M = 40
N = 512
K = 128
block_size = (128, 128)
assert N % 128 == 0 and K % 128 == 0 # Only support N/K are multiple of 128
func_name = "cutlass.groupwise_scaled_bmm_e4m3fn_e4m3fn"
gemm_func = tvm.get_global_func(func_name, allow_missing=True)
if gemm_func is None:
print(f"Skipped as {func_name} is not available")
return
dtype = "bfloat16"
x_np, x_scale_np = rowwise_quant_fp8_e4m3((B, M, K), block_size, dtype)
w_np, w_scale_np = blockwise_quant_fp8_e4m3((B, N, K), block_size, dtype)
o_np = blockwise_bmm(x_np, x_scale_np, w_np, w_scale_np, block_size, dtype)
def run_and_check():
device = tvm.cuda(0)
x_tvm = tvm.runtime.tensor(x_np, device=device)
x_scale_tvm = tvm.runtime.tensor(x_scale_np.transpose(0, 2, 1), device=device)
w_tvm = tvm.runtime.tensor(w_np, device=device)
w_scale_tvm = tvm.runtime.tensor(w_scale_np, device=device)
workspace = tvm.runtime.empty((4096 * 1024,), dtype="uint8", device=device)
o_tvm = tvm.runtime.empty((B, M, N), dtype=dtype, device=device)
gemm_func(
x_tvm,
w_tvm,
x_scale_tvm,
w_scale_tvm,
workspace,
block_size[0],
block_size[1],
o_tvm,
)
tvm.testing.assert_allclose(o_tvm.numpy(), o_np, rtol=1e-4, atol=0.5)
tvm.testing.run_with_gpu_lock(run_and_check)
if __name__ == "__main__":
tvm.testing.main()
+71
View File
@@ -0,0 +1,71 @@
# 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 numpy as np
import tvm
import tvm.testing
from tvm import te
from tvm.contrib.dlpack import to_pytorch_func
def verify_torch_dlpack():
a = np.random.randn(1337)
tvm_a = tvm.runtime.tensor(a)
np.testing.assert_equal(tvm.runtime.from_dlpack(tvm_a).numpy(), a)
try:
import torch
import torch.utils.dlpack
x = torch.rand(56, 56)
tvm_x = tvm.runtime.from_dlpack(torch.utils.dlpack.to_dlpack(x))
np.testing.assert_equal(x.numpy(), tvm_x.numpy())
y = tvm.runtime.from_dlpack(tvm_x)
np.testing.assert_equal(y.numpy(), tvm_x.numpy())
np.testing.assert_equal(torch.utils.dlpack.from_dlpack(y).numpy(), tvm_x.numpy())
n = tvm.runtime.convert(137)
xx = torch.rand(137, 137)
yy = torch.rand(137, 137)
zz2 = torch.empty(137, 137)
zz = xx.mm(yy)
XX = te.placeholder((n, n), name="X")
YY = te.placeholder((n, n), name="Y")
k = te.reduce_axis((0, n), name="k")
ZZ = te.compute((n, n), lambda i, j: te.sum(XX[i, k] * YY[k, j], axis=k))
# No need to speficy target_host if it's llvm
# Otherwise you will need to specify the target and target_host
f = tvm.compile(te.create_prim_func([XX, YY, ZZ]))
f_pytorch = to_pytorch_func(f)
zz2 = torch.empty(137, 137)
f_pytorch(xx, yy, zz2)
tvm.testing.assert_allclose(zz.numpy(), zz2.numpy(), rtol=1e-4, atol=1e-4)
except ImportError:
pass
def test_torch_dlpack():
# Run dlpack interoperability test a few times to make sure it's stable.
for i in range(5):
verify_torch_dlpack()
if __name__ == "__main__":
test_torch_dlpack()
+277
View File
@@ -0,0 +1,277 @@
# 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.
"""
Tests for Example NPU Backend
This test file demonstrates how to test a custom NPU backend
implementation using TVM's testing infrastructure.
"""
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import relax
from tvm.relax.backend.pattern_registry import get_patterns_with_prefix
from tvm.relax.transform import FuseOpsByPattern, MergeCompositeFunctions, RunCodegen
from tvm.script import relax as R
@tvm.script.ir_module
class MatmulReLU:
"""Example module with matrix multiplication and ReLU"""
@R.function
def main(
x: R.Tensor((2, 4), "float32"),
w: R.Tensor((4, 8), "float32"),
) -> R.Tensor((2, 8), "float32"):
with R.dataflow():
y = relax.op.matmul(x, w)
z = relax.op.nn.relu(y)
R.output(z)
return z
@tvm.script.ir_module
class Conv2dReLU:
"""Example module with 2D convolution and ReLU"""
@R.function
def main(
x: R.Tensor((1, 3, 32, 32), "float32"),
w: R.Tensor((16, 3, 3, 3), "float32"),
) -> R.Tensor((1, 16, 30, 30), "float32"):
with R.dataflow():
y = relax.op.nn.conv2d(x, w)
z = relax.op.nn.relu(y)
R.output(z)
return z
@tvm.script.ir_module
class MultipleOps:
"""Example module with multiple operations that can be fused"""
@R.function
def main(
x: R.Tensor((1, 16, 32, 32), "float32"),
) -> R.Tensor((1, 16, 16, 16), "float32"):
with R.dataflow():
# First ReLU
y = relax.op.nn.relu(x)
# Max pooling
z = relax.op.nn.max_pool2d(y, pool_size=(2, 2), strides=(2, 2))
# Second ReLU
out = relax.op.nn.relu(z)
R.output(out)
return out
@tvm.script.ir_module
class Softmax:
"""Example module with softmax"""
@R.function
def main(x: R.Tensor((2, 8), "float32")) -> R.Tensor((2, 8), "float32"):
with R.dataflow():
z = relax.op.nn.softmax(x)
R.output(z)
return z
# Check if the example NPU runtime is available
has_example_npu_codegen = tvm.get_global_func("relax.ext.example_npu", True)
has_example_npu_runtime = tvm.get_global_func("runtime.ExampleNPUJSONRuntimeCreate", True)
has_example_npu = has_example_npu_codegen and has_example_npu_runtime
example_npu_enabled = pytest.mark.skipif(
not has_example_npu,
reason="Example NPU backend not enabled. Compile with the example NPU runtime.",
)
def test_example_npu_patterns_registered():
"""Test that all expected patterns are registered"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
patterns = get_patterns_with_prefix("example_npu")
pattern_names = {p.name for p in patterns}
# Core patterns that should always be available
core_patterns = {
"example_npu.dense",
"example_npu.matmul",
"example_npu.conv1d",
"example_npu.conv2d",
"example_npu.max_pool2d",
}
assert core_patterns.issubset(pattern_names), (
f"Missing core patterns: {core_patterns - pattern_names}"
)
# Check that at least some activation patterns are available
activation_patterns = {name for name in pattern_names if "relu" in name or "sigmoid" in name}
assert len(activation_patterns) > 0, "No activation patterns found"
@example_npu_enabled
def test_example_npu_matmul_relu_partitioning():
"""Test graph partitioning for MatMul + ReLU pattern"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
mod = MatmulReLU
patterns = get_patterns_with_prefix("example_npu")
# Partition the graph
partitioned_mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=False)(mod)
partitioned_mod = MergeCompositeFunctions()(partitioned_mod)
# Verify partitioning happened
assert partitioned_mod is not None
# Check that composite functions were created
for gvar, func in partitioned_mod.functions.items():
if gvar.name_hint != "main":
# This should be a composite function
assert "Composite" in str(func)
@example_npu_enabled
def test_example_npu_conv2d_relu_partitioning():
"""Test graph partitioning for Conv2D + ReLU pattern"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
mod = Conv2dReLU
patterns = get_patterns_with_prefix("example_npu")
# Partition the graph
partitioned_mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=False)(mod)
partitioned_mod = MergeCompositeFunctions()(partitioned_mod)
assert partitioned_mod is not None
@example_npu_enabled
def test_example_npu_multiple_ops():
"""Test partitioning with multiple fusable operations"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
mod = MultipleOps
patterns = get_patterns_with_prefix("example_npu")
# Partition the graph
partitioned_mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=False)(mod)
partitioned_mod = MergeCompositeFunctions()(partitioned_mod)
assert partitioned_mod is not None
@example_npu_enabled
def test_example_npu_softmax_partitioning():
"""Test graph partitioning for softmax pattern"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
mod = Softmax
patterns = get_patterns_with_prefix("example_npu")
partitioned_mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=False)(mod)
partitioned_mod = MergeCompositeFunctions()(partitioned_mod)
assert partitioned_mod is not None
for gvar, func in partitioned_mod.functions.items():
if gvar.name_hint != "main":
assert "Composite" in str(func)
@example_npu_enabled
def test_example_npu_codegen():
"""Test code generation for the example NPU backend"""
import tvm.relax.backend.contrib.example_npu # noqa: F401
mod = MatmulReLU
patterns = get_patterns_with_prefix("example_npu")
# Partition and generate code
partitioned_mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=True)(mod)
partitioned_mod = MergeCompositeFunctions()(partitioned_mod)
partitioned_mod = RunCodegen()(partitioned_mod)
assert partitioned_mod is not None
# The module should now contain external function calls
main_func = partitioned_mod["main"]
assert main_func is not None
@example_npu_enabled
def test_example_npu_runtime_execution():
"""Test end-to-end execution with the example NPU runtime"""
import tvm.relax.backend.contrib.example_npu
# Create simple test inputs
np.random.seed(42)
x_np = np.random.randn(2, 4).astype("float32")
w_np = np.random.randn(4, 8).astype("float32")
# Expected output (computed with NumPy)
expected = np.maximum(0, np.matmul(x_np, w_np))
# Build and run with example NPU backend
mod = MatmulReLU
patterns = get_patterns_with_prefix("example_npu")
# Apply transformations
mod = FuseOpsByPattern(patterns, bind_constants=False, annotate_codegen=True)(mod)
mod = MergeCompositeFunctions()(mod)
mod = RunCodegen()(mod)
# Build the module
target = tvm.target.Target("llvm")
with tvm.transform.PassContext(opt_level=3):
built = relax.build(mod, target)
# Create VM and run
vm = relax.VirtualMachine(built, tvm.cpu())
x_tvm = tvm.runtime.tensor(x_np, tvm.cpu())
w_tvm = tvm.runtime.tensor(w_np, tvm.cpu())
result = vm["main"](x_tvm, w_tvm)
# Verify the result shape is correct (the runtime is a stub and does not compute numerically)
assert result.numpy().shape == expected.shape
if __name__ == "__main__":
# Run tests locally for debugging
test_example_npu_patterns_registered()
if has_example_npu:
print("Example NPU backend is available, running tests...")
test_example_npu_matmul_relu_partitioning()
test_example_npu_conv2d_relu_partitioning()
test_example_npu_softmax_partitioning()
test_example_npu_multiple_ops()
test_example_npu_codegen()
test_example_npu_runtime_execution()
print("All tests passed!")
else:
print("Example NPU backend not available. Compile with example NPU runtime to run tests.")
+121
View File
@@ -0,0 +1,121 @@
# 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.
# ruff: noqa: E741
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import te
from tvm.contrib import hipblas
from tvm.testing import env
def verify_matmul_add(in_dtype, out_dtype, rtol=1e-5):
n = 1024
l = 128
m = 236
A = te.placeholder((n, l), name="A", dtype=in_dtype)
B = te.placeholder((l, m), name="B", dtype=in_dtype)
C = hipblas.matmul(A, B, dtype=out_dtype)
def verify(target="rocm"):
if not tvm.get_global_func("tvm.contrib.hipblas.matmul", True):
print("skip because extern function is not available")
return
f = tvm.compile(te.create_prim_func([A, B, C]), target=target)
def run_and_check():
dev = tvm.rocm(0)
a = tvm.runtime.tensor(np.random.uniform(0, 128, size=(n, l)).astype(A.dtype), dev)
b = tvm.runtime.tensor(np.random.uniform(0, 128, size=(l, m)).astype(B.dtype), dev)
c = tvm.runtime.tensor(np.zeros((n, m), dtype=C.dtype), dev)
f(a, b, c)
tvm.testing.assert_allclose(
c.numpy(),
np.dot(a.numpy().astype(C.dtype), b.numpy().astype(C.dtype)),
rtol=rtol,
)
tvm.testing.run_with_gpu_lock(run_and_check)
verify()
def roundoff(v, d):
return int(np.floor((v + d - 1) / d) * d)
def verify_batch_matmul(Ashape, Bshape, Cshape, in_dtype, out_dtype, rtol=1e-5):
A = te.placeholder(Ashape, name="A", dtype=in_dtype)
B = te.placeholder(Bshape, name="B", dtype=in_dtype)
C = hipblas.batch_matmul(A, B, dtype=out_dtype)
f = tvm.compile(te.create_prim_func([A, B, C]), target="rocm")
def run_and_check():
dev = tvm.rocm(0)
if "int" in in_dtype:
a = tvm.runtime.tensor(np.random.uniform(1, 10, size=Ashape).astype(in_dtype), dev)
b = tvm.runtime.tensor(np.random.uniform(1, 10, size=Bshape).astype(in_dtype), dev)
else:
a = tvm.runtime.tensor(np.random.uniform(size=Ashape).astype(A.dtype), dev)
b = tvm.runtime.tensor(np.random.uniform(size=Bshape).astype(B.dtype), dev)
c = tvm.runtime.tensor(np.zeros(Cshape, dtype=C.dtype), dev)
f(a, b, c)
tvm.testing.assert_allclose(
c.numpy(),
np.matmul(a.numpy().astype(C.dtype), b.numpy().astype(C.dtype)).astype(C.dtype),
rtol=rtol,
)
tvm.testing.run_with_gpu_lock(run_and_check)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_rocm(), reason="need rocm")
def test_matmul_add():
verify_matmul_add("float", "float", rtol=1e-3)
verify_matmul_add("float16", "float")
verify_matmul_add("float16", "float16", rtol=1e-2)
verify_matmul_add("int8", "int32")
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_rocm(), reason="need rocm")
def test_batch_matmul():
if not tvm.get_global_func("tvm.contrib.hipblas.batch_matmul", True):
print("skip because extern function is not available")
return
verify_batch_matmul((16, 1024, 128), (16, 128, 236), (16, 1024, 236), "float", "float")
verify_batch_matmul((16, 1024, 128), (1, 128, 236), (16, 1024, 236), "float", "float")
verify_batch_matmul((16, 1024, 128), (16, 128, 236), (16, 1024, 236), "float16", "float")
verify_batch_matmul((16, 1024, 128), (1, 128, 236), (16, 1024, 236), "float16", "float")
verify_batch_matmul(
(16, 1024, 128), (16, 128, 236), (16, 1024, 236), "float16", "float16", rtol=1e-2
)
verify_batch_matmul(
(16, 1024, 128), (1, 128, 236), (16, 1024, 236), "float16", "float16", rtol=1e-2
)
verify_batch_matmul((16, 1024, 128), (16, 128, 236), (16, 1024, 236), "int8", "int32")
if __name__ == "__main__":
tvm.testing.main()
+128
View File
@@ -0,0 +1,128 @@
# 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.
"""Tests for tvm.contrib.pickle_memoize"""
import os
import pathlib
import subprocess
import sys
import tempfile
import tvm.testing
TEST_SCRIPT_FILE = pathlib.Path(__file__).with_name("pickle_memoize_script.py").resolve()
def test_cache_dir_not_in_current_working_dir():
with tempfile.TemporaryDirectory(prefix="tvm_") as temp_dir:
temp_dir = pathlib.Path(temp_dir)
subprocess.check_call([sys.executable, str(TEST_SCRIPT_FILE), "1", "1"], cwd=temp_dir)
new_files = list(temp_dir.iterdir())
assert not new_files, (
"Use of tvm.contrib.pickle_memorize may not write to current directory."
)
def test_current_directory_is_not_required_to_be_writable():
"""TVM may be imported without directory permissions
This is a regression test. In previous implementations, the
`tvm.contrib.pickle_memoize.memoize` function would write to the
current directory when importing TVM. Import of a Python module
should not write to any directory.
"""
with tempfile.TemporaryDirectory(prefix="tvm_") as temp_dir:
temp_dir = pathlib.Path(temp_dir)
# User may read/cd into the temp dir, nobody may write to temp
# dir.
temp_dir.chmod(0o500)
subprocess.check_call([sys.executable, "-c", "import tvm"], cwd=temp_dir)
def test_cache_dir_defaults_to_home_config_cache():
with tempfile.TemporaryDirectory(prefix="tvm_") as temp_dir:
temp_dir = pathlib.Path(temp_dir)
subprocess.check_call([sys.executable, str(TEST_SCRIPT_FILE), "1", "0"], cwd=temp_dir)
new_files = list(temp_dir.iterdir())
assert not new_files, (
"Use of tvm.contrib.pickle_memorize may not write to current directory."
)
cache_dir = pathlib.Path.home().joinpath(".cache", "tvm", "pkl_memoize_py3")
assert cache_dir.exists()
cache_files = list(cache_dir.iterdir())
assert len(cache_files) >= 1
def test_cache_dir_respects_xdg_cache_home():
with (
tempfile.TemporaryDirectory(prefix="tvm_") as temp_working_dir,
tempfile.TemporaryDirectory(prefix="tvm_") as temp_cache_dir,
):
temp_cache_dir = pathlib.Path(temp_cache_dir)
temp_working_dir = pathlib.Path(temp_working_dir)
subprocess.check_call(
[sys.executable, str(TEST_SCRIPT_FILE), "1", "0"],
cwd=temp_working_dir,
env={
**os.environ,
"XDG_CACHE_HOME": temp_cache_dir.as_posix(),
},
)
new_files = list(temp_working_dir.iterdir())
assert not new_files, (
"Use of tvm.contrib.pickle_memorize may not write to current directory."
)
cache_dir = temp_cache_dir.joinpath("tvm", "pkl_memoize_py3")
assert cache_dir.exists()
cache_files = list(cache_dir.iterdir())
assert len(cache_files) == 1
def test_cache_dir_only_created_when_used():
with (
tempfile.TemporaryDirectory(prefix="tvm_") as temp_working_dir,
tempfile.TemporaryDirectory(prefix="tvm_") as temp_cache_dir,
):
temp_cache_dir = pathlib.Path(temp_cache_dir)
temp_working_dir = pathlib.Path(temp_working_dir)
subprocess.check_call(
[sys.executable, str(TEST_SCRIPT_FILE), "0", "1"],
cwd=temp_working_dir,
env={
**os.environ,
"XDG_CACHE_HOME": temp_cache_dir.as_posix(),
},
)
cache_dir = temp_cache_dir.joinpath("tvm", "pkl_memoize_py3")
assert not cache_dir.exists()
if __name__ == "__main__":
tvm.testing.main()
+200
View File
@@ -0,0 +1,200 @@
# 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.
# ruff: noqa: E722
"""Configure pytest"""
# pylint: disable=invalid-name
import threading
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import rpc, te
from tvm.contrib import random
def test_randint():
"""Tests randint function"""
m = 10240
n = 10240
A = random.randint(-127, 128, size=(m, n), dtype="int32")
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func("tvm.contrib.random.randint", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
f = tvm.compile(te.create_prim_func([A]), target=target)
a = tvm.runtime.tensor(np.zeros((m, n), dtype=A.dtype), dev)
f(a)
na = a.numpy()
assert abs(np.mean(na)) < 0.3
assert np.min(na) == -127
assert np.max(na) == 127
verify()
def test_uniform():
"""Tests uniform function"""
m = 10240
n = 10240
A = random.uniform(0, 1, size=(m, n))
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func("tvm.contrib.random.uniform", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
f = tvm.compile(te.create_prim_func([A]), target=target)
a = tvm.runtime.tensor(np.zeros((m, n), dtype=A.dtype), dev)
f(a)
na = a.numpy()
assert abs(np.mean(na) - 0.5) < 1e-1
assert abs(np.min(na) - 0.0) < 1e-3
assert abs(np.max(na) - 1.0) < 1e-3
verify()
def test_normal():
"""Tests normal function"""
m = 10240
n = 10240
A = random.normal(3, 4, size=(m, n))
def verify(target="llvm"):
if not tvm.testing.device_enabled(target):
print(f"skip because {target} is not enabled...")
return
if not tvm.get_global_func("tvm.contrib.random.normal", True):
print("skip because extern function is not available")
return
dev = tvm.cpu(0)
f = tvm.compile(te.create_prim_func([A]), target=target)
a = tvm.runtime.tensor(np.zeros((m, n), dtype=A.dtype), dev)
f(a)
na = a.numpy()
assert abs(np.mean(na) - 3) < 1e-1
assert abs(np.std(na) - 4) < 1e-2
verify()
@pytest.mark.gpu
def test_random_fill():
"""Tests random_fill function"""
def test_local(dev, dtype):
if not tvm.get_global_func("tvm.contrib.random.random_fill", True):
print("skip because extern function is not available")
return
value = tvm.runtime.empty((512, 512), dtype, dev)
random_fill = tvm.get_global_func("tvm.contrib.random.random_fill")
random_fill(value)
assert np.count_nonzero(value.numpy()) == 512 * 512
# make sure arithmentic doesn't overflow too
np_values = value.numpy()
assert np.isfinite(np_values * np_values + np_values).any()
def test_rpc(dtype):
if not tvm.get_global_func("tvm.contrib.random.random_fill", True):
print("skip because extern function is not available")
return
if not tvm.testing.device_enabled("rpc") or not tvm.runtime.enabled("llvm"):
return
def check_remote(server):
remote = rpc.connect(server.host, server.port)
value = tvm.runtime.empty((512, 512), dtype, remote.cpu())
random_fill = remote.get_function("tvm.contrib.random.random_fill")
random_fill(value)
assert np.count_nonzero(value.numpy()) == 512 * 512
# make sure arithmentic doesn't overflow too
np_values = value.numpy()
assert np.isfinite(np_values * np_values + np_values).any()
check_remote(rpc.Server("127.0.0.1"))
# Packed sub-byte dtypes (e.g. int4) are intentionally unsupported by
# random_fill since #19714 and raise an error instead.
for dtype in [
"bool",
"int8",
"uint8",
"int16",
"uint16",
"int32",
"int32",
"int64",
"uint64",
"float16",
"float32",
"float64",
]:
for target, dev in tvm.testing.enabled_targets():
if tvm.target.Target(target).kind.name == "llvm":
test_local(dev, dtype)
else:
tvm.testing.run_with_gpu_lock(test_local, dev, dtype)
test_rpc(dtype)
def test_random_fill_mt():
"""Check random filler applicability in case of nontrivial thread pool configuration.
Particularly when MaxConcurrency != num_workers_used_ which is actual for big-little systems.
"""
no_exception_happened = True
def test_body():
try:
num_thread_used = 1
configure_threads = tvm.get_global_func("runtime.config_threadpool")
configure_threads(1, num_thread_used)
test_input = tvm.runtime.empty((10, 10))
random_fill = tvm.get_global_func("tvm.contrib.random.random_fill_for_measure")
random_fill(test_input)
except: # pylint: disable=bare-except
nonlocal no_exception_happened
no_exception_happened = False
# ThreadPool object is thread local. To eliminate effect on other test cases put it into thread
x = threading.Thread(target=test_body)
x.start()
x.join()
assert no_exception_happened
if __name__ == "__main__":
test_randint()
test_uniform()
test_normal()
test_random_fill()
test_random_fill_mt()
+69
View File
@@ -0,0 +1,69 @@
# 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.
"""Configure pytest"""
# pylint: disable=invalid-name
import logging
import multiprocessing
import time
import tvm
from tvm import rpc
def rpc_proxy_check():
"""This is a simple test function for RPC Proxy
It is not included as pytests, because:
- It depends on tornado
- It relies on the fact that Proxy starts before client and server connects,
which is often the case but not always
User can directly run this script to verify correctness.
"""
try:
# pylint: disable=import-outside-toplevel
from tvm.rpc import proxy
web_port = 8888
prox = proxy.Proxy("127.0.0.1", web_port=web_port)
def check():
if not tvm.runtime.enabled("rpc"):
return
server = multiprocessing.Process(
target=proxy.websocket_proxy_server, args=(f"ws://localhost:{web_port}/ws", "x1")
)
# Need to make sure that the connection start after proxy comes up
time.sleep(0.1)
server.deamon = True
server.start()
client = rpc.connect(prox.host, prox.port, key="x1")
f1 = client.get_function("testing.echo")
assert f1(10) == 10
assert f1("xyz") == "xyz"
check()
except ImportError:
print("Skipping because tornado is not avaliable...")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
rpc_proxy_check()
+152
View File
@@ -0,0 +1,152 @@
# 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.
"""Configure pytest"""
# pylint: disable=invalid-name
import logging
import time
import tvm
from tvm import rpc
def check_server_drop():
"""test when server drops"""
try:
# pylint: disable=import-outside-toplevel
from tvm.rpc import base, proxy, tracker
# pylint: disable=import-outside-toplevel
from tvm.rpc.base import TrackerCode
@tvm.register_global_func("rpc.test2.addone")
def addone(x):
return x + 1
def _put(tclient, value):
base.sendjson(tclient._sock, value)
base.recvjson(tclient._sock)
tserver = tracker.Tracker("127.0.0.1", 8888)
tproxy = proxy.Proxy("127.0.0.1", 8881, tracker_addr=("127.0.0.1", tserver.port))
tclient = rpc.connect_tracker("127.0.0.1", tserver.port)
server0 = rpc.Server(
"127.0.0.1", port=9099, tracker_addr=("127.0.0.1", tserver.port), key="abc"
)
server1 = rpc.Server(
"127.0.0.1", port=9099, tracker_addr=("127.0.0.1", tserver.port), key="xyz"
)
server2 = rpc.Server("127.0.0.1", tproxy.port, is_proxy=True, key="xyz")
server3 = rpc.Server("127.0.0.1", tproxy.port, is_proxy=True, key="xyz1")
# Fault tolerence to un-handled requested value
_put(tclient, [TrackerCode.REQUEST, "abc", "", 1])
_put(tclient, [TrackerCode.REQUEST, "xyz1", "", 1])
# Fault tolerence to stale worker value
_put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abc")])
_put(tclient, [TrackerCode.PUT, "xyz", (server1.port, "abcxxx")])
_put(tclient, [TrackerCode.PUT, "xyz", (tproxy.port, "abcxxx11")])
# Fault tolerence server timeout
def check_timeout(timeout, sleeptime):
def myfunc(remote):
time.sleep(sleeptime)
f1 = remote.get_function("rpc.test2.addone")
assert f1(10) == 11
try:
tclient.request_and_run("xyz", myfunc, session_timeout=timeout)
except RuntimeError:
pass
print(tclient.text_summary())
try:
remote = tclient.request("xyz", priority=0, session_timeout=timeout)
remote2 = tclient.request("xyz", session_timeout=timeout)
time.sleep(sleeptime)
f1 = remote.get_function("rpc.test2.addone")
assert f1(10) == 11
f1 = remote2.get_function("rpc.test2.addone")
assert f1(10) == 11
except RuntimeError:
pass
remote3 = tclient.request("abc")
f1 = remote3.get_function("rpc.test2.addone")
assert f1(10) == 11
remote3 = tclient.request("xyz1")
f1 = remote3.get_function("rpc.test2.addone")
assert f1(10) == 11
check_timeout(0.01, 0.1)
check_timeout(2, 0)
tserver.terminate()
server0.terminate()
server1.terminate()
server2.terminate()
server3.terminate()
tproxy.terminate()
except ImportError:
print("Skip because tornado is not available")
def check_tracker_rejects_oversized_msg_size():
"""Tracker must reject an oversized msg_size header and close the connection
instead of buffering an unbounded amount of data on a single TCP connection.
Regression test for the unbounded buffer growth defect in
TCPEventHandler.on_message. See MAX_TRACKER_MSG_BYTES in tracker.py.
"""
try:
# pylint: disable=import-outside-toplevel
import socket
import struct
from tvm.rpc import base, tracker
tserver = tracker.Tracker(port=9180, port_end=9290, silent=True)
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
sock.connect(("127.0.0.1", tserver.port))
# complete the 4-byte magic handshake
sock.sendall(struct.pack("<i", base.RPC_TRACKER_MAGIC))
magic_reply = sock.recv(4)
assert struct.unpack("<i", magic_reply)[0] == base.RPC_TRACKER_MAGIC
# send an oversized msg_size header (2 GiB)
sock.sendall(struct.pack("<i", 0x7FFFFFFF))
# server must close the connection (no payload buffering)
for _ in range(20):
chunk = sock.recv(4096)
if chunk == b"":
break
time.sleep(0.05)
else:
raise AssertionError("tracker did not close connection after oversized msg_size")
finally:
tserver.terminate()
except ImportError:
print("Skip because tornado is not available")
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
check_server_drop()
check_tracker_rejects_oversized_msg_size()
+102
View File
@@ -0,0 +1,102 @@
# 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.
# ruff: noqa: E741
"""Configure pytest"""
# pylint: disable=invalid-name
import numpy as np
import tvm
import tvm.testing
from tvm import te
def test_sort():
"""Tests sort function"""
n = 2
l = 5
m = 3
data = te.placeholder((n, l, m), name="data")
sort_num = te.placeholder((n, m), name="sort_num", dtype="int32")
axis = 1
is_ascend = False
out = te.extern(
data.shape,
[data, sort_num],
lambda ins, outs: tvm.tirx.call_packed(
"tvm.contrib.sort.argsort_nms", ins[0], ins[1], outs[0], axis, is_ascend
),
dtype="int32",
name="sort_tensor",
)
input_data = [
[[1, 2, 3], [2, 4.5, 3.5], [1.1, 0.5, 1], [3.2, -5, 0.5], [1.5, 0, 0]],
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]],
]
sort_num_input = [[1, 2, 3], [4, 5, 5]]
sorted_index = [
[[0, 1, 1], [1, 0, 0], [2, 2, 2], [3, 3, 3], [4, 4, 4]],
[[3, 4, 4], [2, 3, 3], [1, 2, 2], [0, 1, 1], [4, 0, 0]],
]
dev = tvm.cpu(0)
target = "llvm"
f = tvm.compile(te.create_prim_func([data, sort_num, out]), target=target)
a = tvm.runtime.tensor(np.array(input_data).astype(data.dtype.dtype), dev)
b = tvm.runtime.tensor(np.array(sort_num_input).astype(sort_num.dtype.dtype), dev)
c = tvm.runtime.tensor(np.zeros(a.shape, dtype=out.dtype.dtype), dev)
f(a, b, c)
tvm.testing.assert_allclose(
c.numpy(), np.array(sorted_index).astype(out.dtype.dtype), rtol=1e-5
)
def test_sort_np():
"""Tests sort function using numpy"""
dshape = (1, 2, 3, 4, 5, 6)
axis = 4
reduced_shape = (1, 2, 3, 4, 6)
is_ascend = True
data = te.placeholder(dshape, name="data")
sort_num = te.placeholder(reduced_shape, name="sort_num", dtype="int32")
out = te.extern(
data.shape,
[data, sort_num],
lambda ins, outs: tvm.tirx.call_packed(
"tvm.contrib.sort.argsort_nms", ins[0], ins[1], outs[0], axis, is_ascend
),
dtype="int32",
name="sort_tensor",
)
dev = tvm.cpu(0)
target = "llvm"
f = tvm.compile(te.create_prim_func([data, sort_num, out]), target=target)
np_data = np.random.uniform(size=dshape)
np_out = np.argsort(np_data, axis=axis)
sort_num_input = np.full(reduced_shape, dshape[axis])
a = tvm.runtime.tensor(np.array(np_data).astype(data.dtype.dtype), dev)
b = tvm.runtime.tensor(np.array(sort_num_input).astype(sort_num.dtype.dtype), dev)
c = tvm.runtime.tensor(np.zeros(a.shape, dtype=out.dtype.dtype), dev)
f(a, b, c)
tvm.testing.assert_allclose(c.numpy(), np_out, rtol=1e-5)
if __name__ == "__main__":
test_sort()
test_sort_np()
@@ -0,0 +1,134 @@
# 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.
# ruff: noqa: F401
import sys
import numpy as np
import pytest
import tvm
import tvm.testing
from tvm import relax
from tvm.relax.frontend import nn
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T
from tvm.testing import env
try:
import triton
import triton.language as tl
from packaging import version
except ImportError:
pytestmark = pytest.skip("Triton is not available", allow_module_level=True)
else:
if version.parse(triton.__version__) < version.parse("3.3.0"):
pytestmark = pytest.skip("Triton >= 3.3.0 is required", allow_module_level=True)
@pytest.mark.gpu
@pytest.mark.skipif(not env.has_cuda(), reason="need cuda")
def test_tir_triton_integration():
@triton.jit
def add_kernel(
x_ptr, # *Pointer* to first input vector.
y_ptr, # *Pointer* to second input vector.
output_ptr, # *Pointer* to output vector.
n_elements, # Size of the vector.
BLOCK_SIZE: tl.constexpr, # Number of elements each program should process.
):
"""Triton vector add kernel from its tutorial."""
pid = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
output = x + y
tl.store(output_ptr + offsets, output, mask=mask)
@I.ir_module(s_tir=True)
class Module:
@T.prim_func(s_tir=True)
def add(x_handle: T.handle, y_handle: T.handle, output_handle: T.handle) -> None:
T.func_attr({"global_symbol": "add"})
m = T.int64()
x = T.match_buffer(x_handle, (m,), "float32")
y = T.match_buffer(y_handle, (m,), "float32")
output = T.match_buffer(output_handle, (m,), "float32")
with T.sblock("root"):
T.reads(x[0:m], y[0:m])
T.writes(output[0:m])
BLOCK_SIZE = T.meta_var(64)
T.call_kernel(
add_kernel,
(T.ceildiv(m, BLOCK_SIZE),),
x.data,
y.data,
output.data,
m,
BLOCK_SIZE,
num_warps=8,
)
@R.function
def main(x: R.Tensor(("m",), "float32"), y: R.Tensor(("m",), "float32")):
m = T.int64()
with R.dataflow():
output = R.call_tir(Module.add, [x, y], relax.TensorType((m,), "float32"))
R.output(output)
return output
# Constexpr parameters (BLOCK_SIZE) stay in the kernel arguments, and the
# thread extent is 256 because the kernel is compiled with num_warps=8.
@I.ir_module(s_tir=True)
class Parsed:
@T.prim_func(s_tir=True)
def add(x_handle: T.handle, y_handle: T.handle, output_handle: T.handle):
m = T.int64()
x = T.match_buffer(x_handle, (m,))
y = T.match_buffer(y_handle, (m,))
output = T.match_buffer(output_handle, (m,))
with T.sblock("root"):
T.reads(x[0:m], y[0:m])
T.writes(output[0:m])
T.call_packed(
"add_kernel",
x.data,
y.data,
output.data,
m,
64,
256,
(m + T.int64(64) - T.int64(1)) // T.int64(64),
)
tvm.ir.assert_structural_equal(Module["add"], Parsed["add"])
assert len(Module.get_attr("external_mods")) == 1
with tvm.target.Target("cuda"):
lib = tvm.compile(Module)
def run_and_check():
device = tvm.cuda(0)
x_nd = tvm.runtime.tensor(np.random.rand(256).astype(np.float32), device)
y_nd = tvm.runtime.tensor(np.random.rand(256).astype(np.float32), device)
output_np = x_nd.numpy() + y_nd.numpy()
output_nd = tvm.runtime.vm.VirtualMachine(lib, device)["main"](x_nd, y_nd)
tvm.testing.assert_allclose(output_nd.numpy(), output_np, rtol=1e-5)
tvm.testing.run_with_gpu_lock(run_and_check)
+64
View File
@@ -0,0 +1,64 @@
# 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.
"""Test contrib.tvmjs"""
import tempfile
import numpy as np
import pytest
import tvm.testing
from tvm.contrib import tvmjs
dtype = tvm.testing.parameter(
"int8",
"int16",
"int32",
"int64",
"uint8",
"uint16",
"uint32",
"uint64",
"float16",
"float32",
"float64",
"float8_e4m3fn",
"float8_e5m2",
)
def test_save_load_float8(dtype):
if "float8" in dtype or "bfloat16" in dtype:
ml_dtypes = pytest.importorskip("ml_dtypes")
np_dtype = np.dtype(getattr(ml_dtypes, dtype))
else:
np_dtype = np.dtype(dtype)
arr = np.arange(16, dtype=np_dtype)
with tempfile.TemporaryDirectory(prefix="tvm_") as temp_dir:
tvmjs.dump_tensor_cache({"arr": arr}, temp_dir)
cache, _ = tvmjs.load_tensor_cache(temp_dir, tvm.cpu())
after_roundtrip = cache["arr"].numpy()
np.testing.assert_array_equal(arr, after_roundtrip)
if __name__ == "__main__":
tvm.testing.main()