chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
unittest
|
||||
*.d
|
||||
*_test
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--- 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/cpp
|
||||
|
||||
This folder contains some unit tests for C++ utilities in the codebase.
|
||||
|
||||
In principle we aim to do most compiler related tests in the python to
|
||||
bring more development velocity, and only use this folder for low-level unit-tests.
|
||||
All tests should finish fast and not dependent on a presence of an accelerator device.
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/arith/analyzer.h>
|
||||
#include <tvm/ffi/extra/structural_equal.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/te/operation.h>
|
||||
|
||||
TEST(Simplify, MinMax) {
|
||||
tvm::arith::Analyzer ana;
|
||||
auto x = tvm::te::var("x");
|
||||
auto e1 = (tvm::max(x, 1) - tvm::max(x, 1));
|
||||
auto e1s = ana->canonical_simplify(e1);
|
||||
TVM_FFI_ICHECK(tvm::tirx::is_zero(e1s));
|
||||
|
||||
auto e2 = (x * tvm::min(x, 1)) - (x * tvm::min(x, 1));
|
||||
auto e2s = ana->canonical_simplify(e2);
|
||||
TVM_FFI_ICHECK(tvm::tirx::is_zero(e2s));
|
||||
}
|
||||
|
||||
TEST(Simplify, Mul) {
|
||||
tvm::arith::Analyzer ana;
|
||||
auto x = tvm::te::var("x");
|
||||
auto e = (x * x) - (x * x);
|
||||
auto es = ana->canonical_simplify(e);
|
||||
TVM_FFI_ICHECK(tvm::tirx::is_zero(es));
|
||||
}
|
||||
|
||||
TEST(Simplify, Mod) {
|
||||
tvm::arith::Analyzer ana;
|
||||
auto x = tvm::IntImm::Int32(10);
|
||||
auto y = tvm::IntImm::Int32(12);
|
||||
// Mod::make is used instead of % to avoid constant folding during
|
||||
// calling operator%(x,y). Mod::make doesn't try constant folding,
|
||||
// and therefore, the constant folding will be attempted in CanonicalSimplify
|
||||
auto mod = ana->canonical_simplify(tvm::tirx::Mod(x, y));
|
||||
auto es = ana->canonical_simplify(mod - x);
|
||||
TVM_FFI_ICHECK(tvm::tirx::is_zero(es));
|
||||
}
|
||||
|
||||
TEST(AnalyzerObjectRef, CopySharesMutableState) {
|
||||
tvm::arith::Analyzer analyzer;
|
||||
tvm::arith::Analyzer copy = analyzer;
|
||||
auto x = tvm::te::var("x");
|
||||
|
||||
copy->Bind(x, tvm::Range::FromMinExtent(0, 8));
|
||||
|
||||
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
|
||||
}
|
||||
|
||||
TEST(AnalyzerObjectRef, ConstHandleRefCanMutateAnalyzerState) {
|
||||
tvm::arith::Analyzer analyzer;
|
||||
const tvm::arith::Analyzer& analyzer_ref = analyzer;
|
||||
auto x = tvm::te::var("x");
|
||||
|
||||
analyzer_ref->Bind(x, tvm::Range::FromMinExtent(0, 8));
|
||||
|
||||
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
|
||||
}
|
||||
|
||||
TEST(AnalyzerObjectRef, CloneIsIndependent) {
|
||||
tvm::arith::Analyzer analyzer;
|
||||
auto x = tvm::te::var("x");
|
||||
auto y = tvm::te::var("y");
|
||||
|
||||
analyzer->Bind(x, tvm::Range::FromMinExtent(0, 8));
|
||||
analyzer->modular_set.Update(x, tvm::arith::ModularSet(4, 0));
|
||||
|
||||
tvm::arith::Analyzer clone = analyzer->Clone();
|
||||
TVM_FFI_ICHECK(clone->CanProve(x < 8));
|
||||
TVM_FFI_ICHECK(clone->modular_set(x)->coeff == 4);
|
||||
|
||||
clone->Bind(y, tvm::Range::FromMinExtent(0, 4));
|
||||
clone->modular_set.Update(x, tvm::arith::ModularSet(8, 0), true);
|
||||
TVM_FFI_ICHECK(clone->CanProve(y < 4));
|
||||
TVM_FFI_ICHECK(!analyzer->CanProve(y < 4));
|
||||
TVM_FFI_ICHECK(analyzer->CanProve(x < 8));
|
||||
TVM_FFI_ICHECK(analyzer->modular_set(x)->coeff == 4);
|
||||
TVM_FFI_ICHECK(clone->modular_set(x)->coeff == 8);
|
||||
}
|
||||
|
||||
TEST(ConstantFold, Broadcast) {
|
||||
tvm::ffi::StructuralEqual checker;
|
||||
auto i32x4 = tvm::tirx::Broadcast(tvm::IntImm::Int32(10), 4);
|
||||
auto i64x4 = tvm::cast(i32x4.ty().WithBits(64), i32x4);
|
||||
auto i64x4_expected = tvm::tirx::Broadcast(tvm::IntImm::Int64(10), 4);
|
||||
ASSERT_TRUE(checker(i64x4, i64x4_expected));
|
||||
}
|
||||
|
||||
TEST(ConstantFold, Ramp) {
|
||||
tvm::ffi::StructuralEqual checker;
|
||||
auto i32x4 = tvm::tirx::Ramp(tvm::IntImm::Int32(10), tvm::IntImm::Int32(1), 4);
|
||||
auto i64x4 = tvm::cast(i32x4.ty().WithBits(64), i32x4);
|
||||
auto i64x4_expected = tvm::tirx::Ramp(tvm::IntImm::Int64(10), tvm::IntImm::Int64(1), 4);
|
||||
ASSERT_TRUE(checker(i64x4, i64x4_expected));
|
||||
|
||||
auto f32x4 = tvm::cast(tvm::PrimType::Float(32, 4), i32x4);
|
||||
auto f32x4_expected = tvm::tirx::Cast(tvm::PrimType::Float(32, 4), i32x4);
|
||||
ASSERT_TRUE(checker(f32x4, f32x4_expected));
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ffi/container/array.h>
|
||||
#include <tvm/ffi/container/map.h>
|
||||
#include <tvm/ffi/string.h>
|
||||
#include <tvm/ir/config_schema.h>
|
||||
|
||||
using namespace tvm;
|
||||
using namespace tvm::ir;
|
||||
namespace refl = tvm::ffi::reflection;
|
||||
|
||||
// Basic int/bool/string option resolution
|
||||
TEST(ConfigSchema, BasicTypes) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("num_threads");
|
||||
schema.def_option<bool>("verbose");
|
||||
schema.def_option<ffi::String>("name");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"num_threads", int64_t(8)},
|
||||
{"verbose", true},
|
||||
{"name", ffi::String("test")},
|
||||
};
|
||||
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("num_threads").cast<int64_t>(), 8);
|
||||
ASSERT_EQ(resolved.at("verbose").cast<bool>(), true);
|
||||
ASSERT_EQ(resolved.at("name").cast<ffi::String>(), "test");
|
||||
}
|
||||
|
||||
// Type coercion: correct type passes
|
||||
TEST(ConfigSchema, TypeCoercionPasses) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("count");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"count", int64_t(42)}};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("count").cast<int64_t>(), 42);
|
||||
}
|
||||
|
||||
// Type mismatch rejection (TypeError)
|
||||
TEST(ConfigSchema, TypeMismatchRejection) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("count");
|
||||
|
||||
// Pass a string where int64_t is expected
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"count", ffi::String("not_a_number")}};
|
||||
try {
|
||||
schema.Resolve(config);
|
||||
FAIL() << "Expected TypeError";
|
||||
} catch (const ffi::Error& e) {
|
||||
EXPECT_EQ(e.kind(), "TypeError");
|
||||
EXPECT_NE(std::string(e.message()).find("count"), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ConfigSchema, TypeMismatchBoolForInt) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<bool>("flag");
|
||||
|
||||
// Pass a string where bool is expected
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"flag", ffi::String("true")}};
|
||||
try {
|
||||
schema.Resolve(config);
|
||||
FAIL() << "Expected TypeError";
|
||||
} catch (const ffi::Error& e) {
|
||||
EXPECT_EQ(e.kind(), "TypeError");
|
||||
EXPECT_NE(std::string(e.message()).find("flag"), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// Default value materialization via refl::DefaultValue
|
||||
TEST(ConfigSchema, DefaultValueMaterialization) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(256)));
|
||||
schema.def_option<bool>("debug", refl::DefaultValue(false));
|
||||
|
||||
// Empty config -> defaults are applied
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 256);
|
||||
ASSERT_EQ(resolved.at("debug").cast<bool>(), false);
|
||||
}
|
||||
|
||||
// Default value materialization via refl::DefaultValue
|
||||
TEST(ConfigSchema, DefaultValueViaTrait) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(128)));
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 128);
|
||||
}
|
||||
|
||||
// Explicit value overrides default
|
||||
TEST(ConfigSchema, ExplicitOverridesDefault) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("max_threads", refl::DefaultValue(int64_t(256)));
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"max_threads", int64_t(512)}};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("max_threads").cast<int64_t>(), 512);
|
||||
}
|
||||
|
||||
// Missing non-required option stays absent
|
||||
TEST(ConfigSchema, MissingNonRequired) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<ffi::String>("optional_name"); // no default
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.find("optional_name"), resolved.end());
|
||||
}
|
||||
|
||||
// Unknown key rejection (ValueError)
|
||||
TEST(ConfigSchema, UnknownKeyRejection) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("known_key");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"known_key", int64_t(1)},
|
||||
{"unknown_key", ffi::String("surprise")},
|
||||
};
|
||||
try {
|
||||
schema.Resolve(config);
|
||||
FAIL() << "Expected ValueError";
|
||||
} catch (const ffi::Error& e) {
|
||||
EXPECT_EQ(e.kind(), "ValueError");
|
||||
EXPECT_NE(std::string(e.message()).find("unknown_key"), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown key allowed when error_on_unknown is false
|
||||
TEST(ConfigSchema, UnknownKeyAllowed) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("known_key");
|
||||
schema.set_error_on_unknown(false);
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"known_key", int64_t(1)},
|
||||
{"unknown_key", ffi::String("allowed")},
|
||||
};
|
||||
auto resolved = schema.Resolve(config);
|
||||
// Unknown keys are silently dropped (not validated, not forwarded)
|
||||
ASSERT_EQ(resolved.at("known_key").cast<int64_t>(), 1);
|
||||
}
|
||||
|
||||
// Canonicalizer runs as final step
|
||||
TEST(ConfigSchema, CanonicalizerRunsLast) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<ffi::String>("mcpu");
|
||||
|
||||
auto canonicalizer = [](ffi::Map<ffi::String, ffi::Any> config) {
|
||||
ffi::String mcpu = config.at("mcpu").cast<ffi::String>();
|
||||
config.Set("mcpu", ffi::String("canonical_") + mcpu);
|
||||
// Canonicalizer can add new keys
|
||||
config.Set("feature.is_fast", true);
|
||||
return config;
|
||||
};
|
||||
schema.set_canonicalizer(canonicalizer);
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"mcpu", ffi::String("arm")}};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("mcpu").cast<ffi::String>(), "canonical_arm");
|
||||
ASSERT_EQ(resolved.at("feature.is_fast").cast<bool>(), true);
|
||||
}
|
||||
|
||||
// Option declaration order is preserved in output
|
||||
TEST(ConfigSchema, DeclarationOrderPreserved) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<ffi::String>("zebra");
|
||||
schema.def_option<ffi::String>("alpha");
|
||||
schema.def_option<ffi::String>("middle");
|
||||
|
||||
auto& options = schema.ListOptions();
|
||||
ASSERT_EQ(options.size(), 3);
|
||||
ASSERT_EQ(std::string(options[0].key), "zebra");
|
||||
ASSERT_EQ(std::string(options[1].key), "alpha");
|
||||
ASSERT_EQ(std::string(options[2].key), "middle");
|
||||
}
|
||||
|
||||
// Doc string trait accepted without affecting behavior
|
||||
TEST(ConfigSchema, DocStringTrait) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("count", "Number of items to process");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"count", int64_t(5)}};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("count").cast<int64_t>(), 5);
|
||||
}
|
||||
|
||||
// Array option type
|
||||
TEST(ConfigSchema, ArrayOptionType) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<ffi::Array<ffi::String>>("keys");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"keys", ffi::Array<ffi::String>{"cpu", "gpu"}},
|
||||
};
|
||||
auto resolved = schema.Resolve(config);
|
||||
auto keys = resolved.at("keys").cast<ffi::Array<ffi::String>>();
|
||||
ASSERT_EQ(keys.size(), 2);
|
||||
ASSERT_EQ(keys[0], "cpu");
|
||||
ASSERT_EQ(keys[1], "gpu");
|
||||
}
|
||||
|
||||
// Map option type
|
||||
TEST(ConfigSchema, MapOptionType) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<ffi::Map<ffi::String, int64_t>>("params");
|
||||
|
||||
ffi::Map<ffi::String, int64_t> params = {{"a", 1}, {"b", 2}};
|
||||
ffi::Map<ffi::String, ffi::Any> config = {{"params", params}};
|
||||
auto resolved = schema.Resolve(config);
|
||||
auto result = resolved.at("params").cast<ffi::Map<ffi::String, int64_t>>();
|
||||
ASSERT_EQ(result.size(), 2);
|
||||
ASSERT_EQ(result["a"], 1);
|
||||
ASSERT_EQ(result["b"], 2);
|
||||
}
|
||||
|
||||
// Duplicate option key rejected
|
||||
TEST(ConfigSchema, DuplicateKeyRejected) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("key1");
|
||||
try {
|
||||
schema.def_option<int64_t>("key1");
|
||||
FAIL() << "Expected ValueError";
|
||||
} catch (const ffi::Error& e) {
|
||||
EXPECT_EQ(e.kind(), "ValueError");
|
||||
EXPECT_NE(std::string(e.message()).find("key1"), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
// ListOptions returns option entries in declaration order
|
||||
TEST(ConfigSchema, ListOptionsNames) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("no_default");
|
||||
schema.def_option<bool>("with_default", refl::DefaultValue(true));
|
||||
|
||||
auto& options = schema.ListOptions();
|
||||
ASSERT_EQ(options.size(), 2);
|
||||
ASSERT_EQ(std::string(options[0].key), "no_default");
|
||||
ASSERT_EQ(std::string(options[1].key), "with_default");
|
||||
ASSERT_EQ(std::string(options[0].type_str), "int");
|
||||
ASSERT_EQ(std::string(options[1].type_str), "bool");
|
||||
// Check default_value
|
||||
ASSERT_FALSE(options[0].has_default);
|
||||
ASSERT_TRUE(options[1].has_default);
|
||||
ASSERT_EQ(options[1].default_value.cast<bool>(), true);
|
||||
}
|
||||
|
||||
// OptionEntry stores correct type strings
|
||||
TEST(ConfigSchema, OptionEntryTypeStr) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("count");
|
||||
schema.def_option<bool>("flag");
|
||||
|
||||
auto& options = schema.ListOptions();
|
||||
ASSERT_EQ(std::string(options[0].type_str), "int");
|
||||
ASSERT_EQ(std::string(options[1].type_str), "bool");
|
||||
}
|
||||
|
||||
// HasOption query
|
||||
TEST(ConfigSchema, HasOption) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("exists");
|
||||
ASSERT_TRUE(schema.HasOption("exists"));
|
||||
ASSERT_FALSE(schema.HasOption("does_not_exist"));
|
||||
}
|
||||
|
||||
// Empty config with no options
|
||||
TEST(ConfigSchema, EmptySchemaEmptyConfig) {
|
||||
ConfigSchema schema;
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.size(), 0);
|
||||
}
|
||||
|
||||
// Canonicalizer with defaults
|
||||
TEST(ConfigSchema, CanonicalizerWithDefaults) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("threads", refl::DefaultValue(int64_t(4)));
|
||||
|
||||
auto canonicalizer = [](ffi::Map<ffi::String, ffi::Any> config) {
|
||||
int64_t threads = config.at("threads").cast<int64_t>();
|
||||
config.Set("threads", threads * 2);
|
||||
return config;
|
||||
};
|
||||
schema.set_canonicalizer(canonicalizer);
|
||||
|
||||
// Empty config: default 4 applied, then canonicalizer doubles it
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("threads").cast<int64_t>(), 8);
|
||||
}
|
||||
|
||||
// Combined doc + default traits
|
||||
TEST(ConfigSchema, DocAndDefaultTraits) {
|
||||
ConfigSchema schema;
|
||||
schema.def_option<int64_t>("num_cores", "CPU core count", refl::DefaultValue(int64_t(4)));
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> config = {};
|
||||
auto resolved = schema.Resolve(config);
|
||||
ASSERT_EQ(resolved.at("num_cores").cast<int64_t>(), 4);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ffi/cast.h>
|
||||
#include <tvm/ffi/extra/structural_equal.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/te/operation.h>
|
||||
|
||||
TEST(Expr, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
auto z = max(x + 1 + 2, 100);
|
||||
ffi::ObjectRef tmp = z;
|
||||
PrimExpr zz = tmp.as_or_throw<PrimExpr>();
|
||||
std::ostringstream os;
|
||||
os << z;
|
||||
TVM_FFI_ICHECK(zz.same_as(z));
|
||||
TVM_FFI_ICHECK(os.str() == "T.max(x + 1 + 2, 100)");
|
||||
}
|
||||
|
||||
TEST(Expr, VarTypeAnnotation) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x", PrimType::Float(32));
|
||||
PrimVar y("y", PrimType::Float(32));
|
||||
tvm::ffi::StructuralEqual checker;
|
||||
TVM_FFI_ICHECK(checker(x.ty(), y.ty()));
|
||||
TVM_FFI_ICHECK(checker(x->ty, y->ty));
|
||||
}
|
||||
|
||||
TEST(Expr, PrimTypeBoolLanes) {
|
||||
using namespace tvm;
|
||||
PrimType boolx4 = PrimType::Bool(4);
|
||||
TVM_FFI_ICHECK(boolx4.IsFixedLengthVector());
|
||||
TVM_FFI_ICHECK(boolx4.MatchesCode(DLDataTypeCode::kDLBool));
|
||||
TVM_FFI_ICHECK_EQ(boolx4.lanes(), 4);
|
||||
TVM_FFI_ICHECK(boolx4.MatchesElementType(DLDataTypeCode::kDLBool, 8));
|
||||
}
|
||||
|
||||
TEST(ExprNodeRef, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
PrimExpr z = max(x + 1 + 2, 100);
|
||||
const tirx::MaxNode* op = z.as<tirx::MaxNode>();
|
||||
TVM_FFI_ICHECK(ffi::GetRef<ffi::ObjectRef>(op).same_as(z));
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ir/module.h>
|
||||
#include <tvm/ir/node_functor.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/tirx/analysis.h>
|
||||
#include <tvm/tirx/builtin.h>
|
||||
#include <tvm/tirx/expr.h>
|
||||
#include <tvm/tirx/expr_functor.h>
|
||||
#include <tvm/tirx/function.h>
|
||||
#include <tvm/tirx/op.h>
|
||||
#include <tvm/tirx/stmt_functor.h>
|
||||
|
||||
TEST(IRF, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
auto z = x + 1;
|
||||
|
||||
NodeFunctor<int(const ffi::ObjectRef& n, int b)> f;
|
||||
f.set_dispatch<VarNode>([](const ffi::ObjectRef& n, int b) { return b; });
|
||||
f.set_dispatch<AddNode>([](const ffi::ObjectRef& n, int b) { return b + 2; });
|
||||
TVM_FFI_ICHECK_EQ(f(x, 2), 2);
|
||||
TVM_FFI_ICHECK_EQ(f(z, 2), 4);
|
||||
}
|
||||
|
||||
TEST(IRF, CountVar) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
int n_var = 0;
|
||||
PrimVar x("x"), y("y");
|
||||
|
||||
auto z = x + 1 + y + y;
|
||||
tirx::PostOrderVisit(z, [&n_var](const ffi::ObjectRef& n) {
|
||||
if (n.as<VarNode>()) ++n_var;
|
||||
});
|
||||
TVM_FFI_ICHECK_EQ(n_var, 2);
|
||||
}
|
||||
|
||||
TEST(IRF, PreOrderVisit) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
Stmt init =
|
||||
IfThenElse(IntImm::Bool(true), Evaluate(IntImm::Int32(0)), Evaluate(IntImm::Int32(0)));
|
||||
Stmt body = Evaluate(IntImm::Int32(1));
|
||||
SBlock block(/*iter_vars=*/{}, /*reads=*/{},
|
||||
/*writes=*/{}, /*name_hint=*/"block", /*body=*/body,
|
||||
/*init=*/init);
|
||||
bool init_visited = false;
|
||||
bool stopped_at_if = true;
|
||||
bool body_visited = false;
|
||||
PreOrderVisit(block, [&](const ffi::ObjectRef& n) -> bool {
|
||||
if (n->IsInstance<IfThenElseNode>()) {
|
||||
init_visited = true;
|
||||
return false;
|
||||
}
|
||||
if (const auto* eval = n.as<EvaluateNode>()) {
|
||||
if (const auto* int_imm = eval->value.as<IntImmNode>()) {
|
||||
if (int_imm->value == 0) {
|
||||
stopped_at_if = false;
|
||||
} else if (int_imm->value == 1) {
|
||||
body_visited = true;
|
||||
} else {
|
||||
TVM_FFI_THROW(InternalError) << "Unreachable";
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
ASSERT_EQ(init_visited, true);
|
||||
ASSERT_EQ(stopped_at_if, true);
|
||||
ASSERT_EQ(body_visited, true);
|
||||
}
|
||||
|
||||
TEST(IRF, ExprTransform) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
auto z = x + 1;
|
||||
|
||||
class MyExprFunctor : public tirx::ExprFunctor<int(const Expr&, int)> {
|
||||
public:
|
||||
int VisitExpr_(const VarNode* op, int b) final { return b; }
|
||||
int VisitExpr_(const IntImmNode* op, int b) final { return op->value; }
|
||||
int VisitExpr_(const AddNode* op, int b) final {
|
||||
return VisitExpr(op->a, b) + VisitExpr(op->b, b);
|
||||
}
|
||||
};
|
||||
MyExprFunctor f;
|
||||
TVM_FFI_ICHECK_EQ(f(x, 2), 2);
|
||||
TVM_FFI_ICHECK_EQ(f(z, 2), 3);
|
||||
try {
|
||||
f(z - 1, 2);
|
||||
TVM_FFI_THROW(InternalError) << "should fail";
|
||||
} catch (tvm::ffi::Error&) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IRF, ExprVisit) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
auto z = x + 1;
|
||||
|
||||
class MyVisitor : public tirx::ExprFunctor<void(const Expr&)>,
|
||||
public tirx::StmtFunctor<void(const Stmt&)> {
|
||||
public:
|
||||
int count = 0;
|
||||
// implementation
|
||||
void VisitExpr_(const VarNode* op) final { ++count; }
|
||||
void VisitExpr_(const IntImmNode* op) final {}
|
||||
void VisitExpr_(const AddNode* op) final {
|
||||
VisitExpr(op->a);
|
||||
VisitExpr(op->b);
|
||||
}
|
||||
void VisitStmt_(const EvaluateNode* op) final { VisitExpr(op->value); }
|
||||
};
|
||||
MyVisitor v;
|
||||
v.VisitStmt(Evaluate(z));
|
||||
TVM_FFI_ICHECK_EQ(v.count, 1);
|
||||
}
|
||||
|
||||
TEST(IRF, StmtVisitor) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
class MyVisitor : public StmtExprVisitor {
|
||||
public:
|
||||
int count = 0;
|
||||
// implementation
|
||||
void VisitExpr_(const VarNode* op) final { ++count; }
|
||||
};
|
||||
MyVisitor v;
|
||||
auto fmaketest = [&]() {
|
||||
auto z = x + 1;
|
||||
Stmt eval_body = Evaluate(z);
|
||||
PrimType dtype = PrimType::Float(32);
|
||||
Var data_var("b", PointerType(dtype));
|
||||
Buffer buf(data_var, dtype, {z, z}, {}, PrimExpr(), "b", 0, 0, BufferType::kDefault);
|
||||
// AllocBuffer is flat (no body). Return as SeqStmt with eval.
|
||||
return SeqStmt({AllocBuffer(buf), eval_body});
|
||||
};
|
||||
v(fmaketest());
|
||||
// AllocBuffer visits buffer shape via VisitBufferDef.
|
||||
// shape = {z, z} where z = x + 1, so x is visited twice from shape + once from eval = 3
|
||||
TVM_FFI_ICHECK_EQ(v.count, 3);
|
||||
|
||||
{
|
||||
// tests for block and block_realize
|
||||
Stmt body = fmaketest();
|
||||
PrimType dtype = PrimType::Float(32);
|
||||
Var buf_var("b", PointerType(dtype));
|
||||
Buffer buffer = decl_buffer({16});
|
||||
body = SeqStmt({DeclBuffer(buffer), std::move(body)});
|
||||
BufferRegion buffer_region(buffer, {Range::FromMinExtent(x + 1, 1)});
|
||||
MatchBufferRegion match_buffer_region(decl_buffer({1}), buffer_region);
|
||||
|
||||
// construct block and block_realize
|
||||
SBlock block = SBlock({}, {buffer_region}, {buffer_region}, "block", body, body, {},
|
||||
{match_buffer_region});
|
||||
Stmt block_realize = SBlockRealize({}, IntImm::Bool(true), block);
|
||||
|
||||
v.count = 0;
|
||||
v(block_realize);
|
||||
// x visited in: reads range (1), writes range (1), match_buffers range (1),
|
||||
// init DeclBuffer(0) + AllocBuffer shape(2) + Evaluate(1) = 3,
|
||||
// body DeclBuffer(0) + AllocBuffer shape(2) + Evaluate(1) = 3.
|
||||
// Total: 1 + 1 + 1 + 3 + 3 = 9.
|
||||
TVM_FFI_ICHECK_EQ(v.count, 9);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IRF, StmtMutator) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimVar x("x");
|
||||
|
||||
class MyVisitor : public tirx::StmtMutator, public tirx::ExprMutator {
|
||||
public:
|
||||
using StmtMutator::operator();
|
||||
using ExprMutator::operator();
|
||||
|
||||
protected:
|
||||
// implementation
|
||||
Expr VisitExpr_(const AddNode* op) final { return op->a; }
|
||||
Stmt VisitStmt_(const SeqStmtNode* op) final { return StmtMutator::VisitSeqStmt_(op, true); }
|
||||
Expr VisitExpr(const Expr& expr) final { return ExprMutator::VisitExpr(expr); }
|
||||
};
|
||||
auto fmakealloc = [&]() {
|
||||
auto z = x + 1;
|
||||
PrimType dtype = PrimType::Float(32);
|
||||
Var data_var("b", PointerType(dtype));
|
||||
Buffer buf(data_var, dtype, {1, z}, {}, PrimExpr(), "b", 0, 0, BufferType::kDefault);
|
||||
return AllocBuffer(buf);
|
||||
};
|
||||
|
||||
auto fmakeif = [&]() {
|
||||
auto z = x + 1;
|
||||
Stmt body = Evaluate(z);
|
||||
return IfThenElse(x, Evaluate(0), body);
|
||||
};
|
||||
|
||||
MyVisitor v;
|
||||
{
|
||||
auto alloc = fmakealloc();
|
||||
Stmt body2 = Evaluate(1);
|
||||
auto* bufptr = alloc.as<AllocBufferNode>()->buffer.get();
|
||||
ffi::Array<Stmt> arr{std::move(alloc), body2, body2};
|
||||
auto* arrptr = arr.get();
|
||||
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
|
||||
TVM_FFI_ICHECK(arr.get() == arrptr);
|
||||
// buffer IS mutated now (AllocBuffer mutator visits buffer shape via VisitBufferDef)
|
||||
// shape was {1, x+1}, mutator transforms x+1 -> x, so buffer changes
|
||||
TVM_FFI_ICHECK(arr[0].as<AllocBufferNode>()->buffer.get() != bufptr);
|
||||
}
|
||||
{
|
||||
ffi::Array<Stmt> arr{fmakealloc()};
|
||||
// mutate array get reference by another one, trigger copy.
|
||||
ffi::Array<Stmt> arr2 = arr;
|
||||
auto* arrptr = arr.get();
|
||||
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
|
||||
TVM_FFI_ICHECK(arr.get() != arrptr);
|
||||
// buffer is mutated in arr but not in arr2
|
||||
TVM_FFI_ICHECK(arr[0].as<AllocBufferNode>()->buffer.get() !=
|
||||
arr2[0].as<AllocBufferNode>()->buffer.get());
|
||||
// mutate but no content change.
|
||||
arr2 = arr;
|
||||
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
|
||||
TVM_FFI_ICHECK(arr2.get() == arr.get());
|
||||
}
|
||||
{
|
||||
ffi::Array<Stmt> arr{fmakeif()};
|
||||
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
|
||||
TVM_FFI_ICHECK(arr[0].as<IfThenElseNode>()->else_case.as<EvaluateNode>()->value.same_as(x));
|
||||
// mutate but no content change.
|
||||
auto arr2 = arr;
|
||||
arr.MutateByApply([&](Stmt s) { return v(std::move(s)); });
|
||||
TVM_FFI_ICHECK(arr2.get() == arr.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto body =
|
||||
Evaluate(Call(PrimType::Int(32), builtin::call_extern(), {StringImm("xyz"), x + 1}));
|
||||
auto res = v(std::move(body));
|
||||
TVM_FFI_ICHECK(res.as<EvaluateNode>()->value.as<CallNode>()->args[1].same_as(x));
|
||||
}
|
||||
{
|
||||
Stmt body = fmakealloc();
|
||||
Stmt body2 = Evaluate(1);
|
||||
auto* ref2 = body2.get();
|
||||
auto* bufptr = body.as<AllocBufferNode>()->buffer.get();
|
||||
// construct a recursive SeqStmt.
|
||||
body = SeqStmt({body, body2});
|
||||
body = SeqStmt({body, body2});
|
||||
body = v(std::move(body));
|
||||
// the seq get flattened
|
||||
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->size() == 3);
|
||||
// buffer is now mutated (shape x+1 -> x via VisitBufferDef)
|
||||
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[0].as<AllocBufferNode>()->buffer.get() != bufptr);
|
||||
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[1].get() == ref2);
|
||||
}
|
||||
|
||||
{
|
||||
// Cannot cow because of bref
|
||||
Stmt body = fmakealloc();
|
||||
Stmt body2 = Evaluate(1);
|
||||
// construct a recursive SeqStmt.
|
||||
body = SeqStmt({body, body2});
|
||||
auto bref = body;
|
||||
body = SeqStmt({body, body2});
|
||||
body = v(std::move(body));
|
||||
// the seq get flattened
|
||||
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->size() == 3);
|
||||
// buffer is mutated (shape x+1 -> x via VisitBufferDef)
|
||||
TVM_FFI_ICHECK(body.as<SeqStmtNode>()->seq[0].as<AllocBufferNode>() != nullptr);
|
||||
// bref still holds the old SeqStmt (not shared with new one due to copy)
|
||||
TVM_FFI_ICHECK(!bref.same_as(body));
|
||||
}
|
||||
|
||||
{
|
||||
// tests for block and block_realize
|
||||
// AllocBuffer and DeclBuffer are flat (no body), placed as siblings in SeqStmt
|
||||
Stmt eval_body = Evaluate(x + 1);
|
||||
Buffer buffer = decl_buffer({16});
|
||||
Stmt decl = DeclBuffer(buffer);
|
||||
Stmt alloc = fmakealloc();
|
||||
// body is: DeclBuffer, AllocBuffer, Evaluate
|
||||
Stmt body = SeqStmt({decl, alloc, eval_body});
|
||||
BufferRegion buffer_region(buffer, {Range::FromMinExtent(x + 1, 1)});
|
||||
MatchBufferRegion match_buffer_region(decl_buffer({1}), buffer_region);
|
||||
// construct block and block_realize
|
||||
SBlock block = SBlock({}, {buffer_region}, {buffer_region}, "block", body, body, {},
|
||||
{match_buffer_region});
|
||||
Stmt block_realize = SBlockRealize({}, IntImm::Bool(true), block);
|
||||
body = v(std::move(block_realize));
|
||||
// the body should be changed
|
||||
SBlock new_block = body.as<SBlockRealizeNode>()->block;
|
||||
// body is a SeqStmt; the Evaluate(x+1) -> Evaluate(x)
|
||||
auto* seq = new_block->body.as<SeqStmtNode>();
|
||||
TVM_FFI_ICHECK(seq != nullptr);
|
||||
TVM_FFI_ICHECK(seq->seq[2].as<EvaluateNode>()->value.same_as(x));
|
||||
auto* init_seq = new_block->init.value().as<SeqStmtNode>();
|
||||
TVM_FFI_ICHECK(init_seq != nullptr);
|
||||
TVM_FFI_ICHECK(init_seq->seq[2].as<EvaluateNode>()->value.same_as(x));
|
||||
// buffer region min is mutated: x+1 -> x
|
||||
TVM_FFI_ICHECK(new_block->reads[0]->region[0]->min.same_as(x));
|
||||
TVM_FFI_ICHECK(new_block->writes[0]->region[0]->min.same_as(x));
|
||||
TVM_FFI_ICHECK(new_block->match_buffers[0]->source->region[0]->min.same_as(x));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(IRF, Substitute) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
PrimType dtype = PrimType::Float(32);
|
||||
Var x("x", PointerType(dtype, ""));
|
||||
PrimVar n("n", PrimType::Int(32));
|
||||
|
||||
auto fmakebuffer = [&]() {
|
||||
return Buffer{/*data=*/x,
|
||||
/*dtype=*/PrimType::Float(32),
|
||||
/*shape=*/{n},
|
||||
/*strides=*/{},
|
||||
/*elem_offset=*/PrimExpr(),
|
||||
/*name=*/"buf",
|
||||
/*data_alignment=*/1,
|
||||
/*offset_factor=*/1,
|
||||
/*buffer_type=*/BufferType::kDefault};
|
||||
};
|
||||
|
||||
{
|
||||
// test substitute buffer data var and shape var via DeclBuffer
|
||||
Var y = x.CopyWithSuffix("subst");
|
||||
PrimVar m("m", PrimType::Int(32));
|
||||
Buffer buffer = fmakebuffer();
|
||||
Stmt store = BufferStore(buffer, FloatImm(dtype, 0), {IntImm::Int32(0)});
|
||||
Stmt decl = SeqStmt({DeclBuffer(buffer), store});
|
||||
auto f_subst = [&](const Var& var) -> ffi::Optional<Expr> {
|
||||
if (var.same_as(x)) return Expr(y);
|
||||
if (var.same_as(n)) return Expr(m);
|
||||
return std::nullopt;
|
||||
};
|
||||
Stmt new_decl = Substitute(decl, f_subst);
|
||||
auto* seq_node = new_decl.as<SeqStmtNode>();
|
||||
TVM_FFI_ICHECK(seq_node != nullptr);
|
||||
auto* decl_node = seq_node->seq[0].as<DeclBufferNode>();
|
||||
TVM_FFI_ICHECK(decl_node != nullptr);
|
||||
TVM_FFI_ICHECK(decl_node->buffer->data.same_as(y));
|
||||
TVM_FFI_ICHECK(decl_node->buffer->shape[0].same_as(m));
|
||||
}
|
||||
|
||||
{
|
||||
// test identity substitution on expression
|
||||
Buffer buffer = fmakebuffer();
|
||||
PrimExpr expr = BufferLoad(buffer, {IntImm::Int32(0)});
|
||||
auto f_subst = [&](const Var& var) -> ffi::Optional<Expr> { return Expr(var); };
|
||||
PrimExpr new_expr = Substitute(expr, f_subst);
|
||||
// the expression is not changed
|
||||
TVM_FFI_ICHECK(new_expr.same_as(expr));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/runtime/tensor.h>
|
||||
|
||||
using namespace tvm;
|
||||
|
||||
TEST(TensorTest, IsContiguous_ContiguousStride) {
|
||||
auto array = runtime::Tensor::Empty({5, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
|
||||
DLManagedTensor* managed_tensor = array.ToDLPack();
|
||||
|
||||
int64_t strides[] = {10, 1};
|
||||
managed_tensor->dl_tensor.strides = strides;
|
||||
|
||||
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
|
||||
|
||||
managed_tensor->deleter(managed_tensor);
|
||||
}
|
||||
|
||||
TEST(TensorTest, IsContiguous_NullStride) {
|
||||
auto array = runtime::Tensor::Empty({5, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
|
||||
DLManagedTensor* managed_tensor = array.ToDLPack();
|
||||
|
||||
managed_tensor->dl_tensor.strides = nullptr;
|
||||
|
||||
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
|
||||
|
||||
managed_tensor->deleter(managed_tensor);
|
||||
}
|
||||
|
||||
TEST(TensorTest, IsContiguous_AnyStrideForSingular) {
|
||||
auto array = runtime::Tensor::Empty({5, 1, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
|
||||
DLManagedTensor* managed_tensor = array.ToDLPack();
|
||||
|
||||
int64_t strides[] = {10, 1, 1}; // strides[1] is normalized to 1 because shape[1] == 1.
|
||||
managed_tensor->dl_tensor.strides = strides;
|
||||
|
||||
TVM_FFI_ICHECK(ffi::IsContiguous(managed_tensor->dl_tensor));
|
||||
|
||||
managed_tensor->dl_tensor.strides = nullptr;
|
||||
managed_tensor->deleter(managed_tensor);
|
||||
}
|
||||
|
||||
TEST(TensorTest, IsContiguous_UncontiguousStride) {
|
||||
auto array = runtime::Tensor::Empty({5, 1, 10}, DLDataType{kDLFloat, 32, 1}, {kDLCPU});
|
||||
DLManagedTensor* managed_tensor = array.ToDLPack();
|
||||
|
||||
int64_t strides[] = {1, 1, 1};
|
||||
managed_tensor->dl_tensor.strides = strides;
|
||||
|
||||
TVM_FFI_ICHECK(!ffi::IsContiguous(managed_tensor->dl_tensor));
|
||||
|
||||
managed_tensor->dl_tensor.strides = nullptr;
|
||||
managed_tensor->deleter(managed_tensor);
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ffi/dtype.h>
|
||||
#include <tvm/ffi/extra/structural_equal.h>
|
||||
#include <tvm/relax/block_builder.h>
|
||||
#include <tvm/relax/nested_msg.h>
|
||||
#include <tvm/relax/type.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/tirx/expr.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace tvm;
|
||||
using namespace tvm::relax;
|
||||
|
||||
namespace {
|
||||
|
||||
TensorType ScalarTensorType(PrimType dtype) {
|
||||
auto n = tvm::ffi::make_object<TensorTypeNode>();
|
||||
n->dtype = std::move(dtype);
|
||||
n->ndim = 0;
|
||||
return TensorType(n);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(NestedMsg, Basic) {
|
||||
// start with no annotation
|
||||
relax::Var x("x", std::nullopt), y("y", std::nullopt);
|
||||
|
||||
// constructor from array, T and nullopt.
|
||||
NestedMsg<relax::Expr> msg({x, std::nullopt, x});
|
||||
|
||||
EXPECT_TRUE(msg.IsNested());
|
||||
EXPECT_FALSE(msg.IsLeaf());
|
||||
EXPECT_TRUE(msg != nullptr);
|
||||
|
||||
EXPECT_ANY_THROW(msg.LeafValue());
|
||||
|
||||
auto arr = msg.NestedArray();
|
||||
EXPECT_TRUE(arr[0].LeafValue().same_as(x));
|
||||
EXPECT_TRUE(arr[1] == nullptr);
|
||||
EXPECT_TRUE(arr[1].IsNull());
|
||||
|
||||
EXPECT_TRUE(arr[2].LeafValue().same_as(x));
|
||||
|
||||
auto a0 = arr[0];
|
||||
EXPECT_TRUE(a0.IsLeaf());
|
||||
|
||||
// assignment
|
||||
// assign null
|
||||
a0 = std::nullopt;
|
||||
EXPECT_TRUE(a0 == nullptr);
|
||||
|
||||
// assign array
|
||||
a0 = {x, {x, std::nullopt, y}};
|
||||
EXPECT_TRUE(a0.IsNested());
|
||||
auto t0 = a0.NestedArray()[1];
|
||||
EXPECT_TRUE(t0.IsNested());
|
||||
EXPECT_TRUE(t0.NestedArray()[2].LeafValue().same_as(y));
|
||||
|
||||
// assign leaf
|
||||
a0 = x;
|
||||
|
||||
EXPECT_TRUE(a0.IsLeaf());
|
||||
EXPECT_TRUE(a0.LeafValue().same_as(x));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, IntAndAny) {
|
||||
NestedMsg<int64_t> msg({1, std::nullopt, 2});
|
||||
Any any_msg = msg;
|
||||
NestedMsg<int64_t> msg2 = any_msg.cast<NestedMsg<int64_t>>();
|
||||
|
||||
EXPECT_TRUE(msg2.IsNested());
|
||||
EXPECT_EQ(msg2.NestedArray()[0].LeafValue(), 1);
|
||||
EXPECT_TRUE(msg2.NestedArray()[1].IsNull());
|
||||
EXPECT_EQ(msg2.NestedArray()[2].LeafValue(), 2);
|
||||
}
|
||||
|
||||
TEST(NestedMsg, ForEachLeaf) {
|
||||
relax::Var x("x", std::nullopt), y("y", std::nullopt);
|
||||
NestedMsg<Expr> msg = {x, {x, y}, std::nullopt, {x, {x, y}}};
|
||||
|
||||
int x_count = 0, y_count = 0;
|
||||
|
||||
ForEachLeaf(msg, [&](const Expr& v) {
|
||||
if (v.same_as(x)) ++x_count;
|
||||
if (v.same_as(y)) ++y_count;
|
||||
});
|
||||
EXPECT_EQ(x_count, 4);
|
||||
EXPECT_EQ(y_count, 2);
|
||||
}
|
||||
|
||||
TEST(NestedMsg, Equal) {
|
||||
relax::Var x("x", std::nullopt), y("y", std::nullopt);
|
||||
relax::Var z("z", std::nullopt);
|
||||
|
||||
auto fequal = [](Expr lhs, Expr rhs) { return lhs.same_as(rhs); };
|
||||
|
||||
using M = NestedMsg<relax::Expr>;
|
||||
|
||||
EXPECT_TRUE(Equal(M(std::nullopt), M(std::nullopt), fequal));
|
||||
|
||||
EXPECT_TRUE(Equal(M(x), M(x), fequal));
|
||||
|
||||
EXPECT_TRUE(Equal(M({x, y}), M({x, y}), fequal));
|
||||
|
||||
EXPECT_TRUE(Equal(M({x, std::nullopt}), M({x, std::nullopt}), fequal));
|
||||
|
||||
EXPECT_TRUE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}}), fequal));
|
||||
|
||||
EXPECT_TRUE(Equal(M({x, {std::nullopt, y}, {x, z}}), M({x, {std::nullopt, y}, {x, z}}), fequal));
|
||||
|
||||
// type mismatch
|
||||
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, x}), M({x, {std::nullopt, y}, {x, z}}), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}, {x, std::nullopt}}),
|
||||
M({x, {std::nullopt, y}, {x, z}}), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M({x, {std::nullopt, y}}), M({x, {std::nullopt, y}, {x, z}}), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M(x), M(std::nullopt), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M(std::nullopt), M(x), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M(x), M(ffi::Array<M>({x})), fequal));
|
||||
|
||||
EXPECT_FALSE(Equal(M(ffi::Array<M>({x})), M(x), fequal));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, MapAndDecompose) {
|
||||
relax::Var x("x", PrimType::Int(16));
|
||||
relax::Var y("y", PrimType::Int(32));
|
||||
relax::Var z("z", PrimType::Int(64));
|
||||
|
||||
BlockBuilder bb = BlockBuilder::Create(std::nullopt);
|
||||
relax::Expr t0 = bb->Normalize(Tuple({x, y}));
|
||||
relax::Expr t1 = bb->Normalize(Tuple({t0, x, z, t0}));
|
||||
|
||||
auto c0 = IntImm::Int32(0);
|
||||
auto c1 = IntImm::Int32(1);
|
||||
auto c2 = IntImm::Int32(2);
|
||||
|
||||
auto output = MapToNestedMsg<IntImm>(t1, [&](Expr value) {
|
||||
if (value.same_as(x)) return c0;
|
||||
if (value.same_as(y)) return c1;
|
||||
return c2;
|
||||
});
|
||||
|
||||
NestedMsg<IntImm> expected = {{c0, c1}, c0, c2, {c0, c1}};
|
||||
|
||||
EXPECT_TRUE(Equal(output, expected,
|
||||
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
|
||||
|
||||
auto output2 = MapToNestedMsg<IntImm>(GetType(t1), [&](Type ty) -> NestedMsg<IntImm> {
|
||||
const auto* prim_ty = ty.as<PrimTypeNode>();
|
||||
if (prim_ty == nullptr) return std::nullopt;
|
||||
int bits = prim_ty->dtype.bits;
|
||||
if (bits == 16) return c0;
|
||||
if (bits == 32) return c1;
|
||||
if (bits == 64) return c2;
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_TRUE(Equal(output2, expected,
|
||||
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
|
||||
|
||||
int x_count = 0, y_count = 0, z_count = 0;
|
||||
|
||||
DecomposeNestedMsg(t1, expected, [&](Expr value, NestedMsg<IntImm> msg) {
|
||||
if (value.same_as(x)) {
|
||||
EXPECT_TRUE(msg.LeafValue().same_as(c0));
|
||||
++x_count;
|
||||
} else if (value.same_as(y)) {
|
||||
EXPECT_TRUE(msg.LeafValue().same_as(c1));
|
||||
++y_count;
|
||||
} else {
|
||||
EXPECT_TRUE(msg.LeafValue().same_as(c2));
|
||||
++z_count;
|
||||
}
|
||||
});
|
||||
EXPECT_EQ(x_count, 3);
|
||||
EXPECT_EQ(y_count, 2);
|
||||
EXPECT_EQ(z_count, 1);
|
||||
}
|
||||
|
||||
TEST(NestedMsg, MapToNestedMsgByType) {
|
||||
auto sf0 = ScalarTensorType(PrimType::Float(32));
|
||||
auto sf1 = TupleType({sf0, sf0});
|
||||
auto sf2 = TupleType({sf0, sf0});
|
||||
auto x = relax::Var("x", TupleType({sf1, sf2, sf0}));
|
||||
|
||||
auto msg = MapToNestedMsgByType<Expr>(x, [](Expr value) { return value; });
|
||||
|
||||
EXPECT_TRUE(msg.IsNested());
|
||||
auto arr = msg.NestedArray();
|
||||
|
||||
EXPECT_TRUE(arr[1].IsNested());
|
||||
auto arr1 = arr[1].NestedArray();
|
||||
|
||||
EXPECT_TRUE(arr1[0].IsLeaf());
|
||||
EXPECT_TRUE(
|
||||
tvm::ffi::StructuralEqual()(arr1[0].LeafValue(), TupleGetItem(TupleGetItem(x, 1), 0)));
|
||||
|
||||
EXPECT_TRUE(arr[2].IsLeaf());
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(arr[2].LeafValue(), TupleGetItem(x, 2)));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, NestedMsgToExpr) {
|
||||
auto sf0 = ScalarTensorType(PrimType::Float(32));
|
||||
auto sf1 = TupleType({sf0, sf0});
|
||||
|
||||
auto c0 = IntImm::Int32(0);
|
||||
auto c1 = IntImm::Int32(1);
|
||||
auto c2 = IntImm::Int32(2);
|
||||
|
||||
relax::Var x("x", sf0), y("y", sf0), z("z", sf0);
|
||||
|
||||
NestedMsg<IntImm> msg = {c0, {c0, c1}, {c0, {c1, c2}}};
|
||||
auto expr = NestedMsgToExpr<IntImm>(msg, [&](ffi::Optional<IntImm> leaf) {
|
||||
TVM_FFI_ICHECK(leaf.has_value());
|
||||
int value = leaf.value()->value;
|
||||
switch (value) {
|
||||
case 0:
|
||||
return x;
|
||||
case 1:
|
||||
return y;
|
||||
default:
|
||||
return z;
|
||||
}
|
||||
});
|
||||
|
||||
Expr expected = Tuple({x, Tuple({x, y}), Tuple({x, Tuple({y, z})})});
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr, expected));
|
||||
|
||||
// test simplified
|
||||
relax::Var t("t", sf1);
|
||||
NestedMsg<Expr> msg1 = {TupleGetItem(t, 0), TupleGetItem(t, 1)};
|
||||
auto expr1 = NestedMsgToExpr<Expr>(msg1, [](ffi::Optional<Expr> leaf) { return leaf.value(); });
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(expr1, t));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, CombineNestedMsg) {
|
||||
auto c0 = IntImm::Int32(0);
|
||||
auto c1 = IntImm::Int32(1);
|
||||
auto c2 = IntImm::Int32(2);
|
||||
|
||||
NestedMsg<IntImm> lhs = {c0, {c0, c1}, std::nullopt, {c0, {c1, c2}}};
|
||||
NestedMsg<IntImm> rhs = {c1, {c2, std::nullopt}, std::nullopt, {c1, {c2, c2}}};
|
||||
NestedMsg<IntImm> expected = {c1, {c2, c1}, std::nullopt, {c1, {c2, c2}}};
|
||||
|
||||
auto output = CombineNestedMsg(lhs, rhs, [](IntImm x, IntImm y) {
|
||||
if (x->value > y->value) return x;
|
||||
return y;
|
||||
});
|
||||
|
||||
EXPECT_TRUE(Equal(output, expected,
|
||||
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, MapNestedMsg) {
|
||||
auto c0 = IntImm::Int32(0);
|
||||
auto c1 = IntImm::Int32(1);
|
||||
auto c2 = IntImm::Int32(2);
|
||||
auto c3 = IntImm::Int32(3);
|
||||
|
||||
NestedMsg<IntImm> msg = {c0, {c0, c1}, std::nullopt, {c0, {c2, c1}}};
|
||||
NestedMsg<IntImm> expected = {c3, {c3, std::nullopt}, std::nullopt, {c3, {c2, std::nullopt}}};
|
||||
|
||||
auto output = MapNestedMsg(msg, [](IntImm x) {
|
||||
if (x->value == 0) {
|
||||
return NestedMsg<IntImm>(IntImm::Int32(3));
|
||||
} else if (x->value == 1) {
|
||||
return NestedMsg<IntImm>();
|
||||
} else {
|
||||
return NestedMsg<IntImm>(x);
|
||||
}
|
||||
});
|
||||
|
||||
EXPECT_TRUE(Equal(output, expected,
|
||||
[](IntImm lhs, IntImm rhs) -> bool { return lhs->value == rhs->value; }));
|
||||
}
|
||||
|
||||
TEST(NestedMsg, TransformTupleLeaf) {
|
||||
auto c0 = IntImm::Int32(0);
|
||||
auto c1 = IntImm::Int32(1);
|
||||
auto c2 = IntImm::Int32(2);
|
||||
using NInt = NestedMsg<IntImm>;
|
||||
|
||||
NInt msg1 = {c0, {c0, c1}, c2, {c0, {c1, c2}}};
|
||||
NInt msg2 = {c1, {c2, c0}, c2, {c1, {c2, c0}}};
|
||||
|
||||
PrimType s = PrimType::Int(32);
|
||||
relax::Var x("x", s), y("y", s), z("z", s);
|
||||
BlockBuilder bb = BlockBuilder::Create(std::nullopt);
|
||||
Expr expr = bb->Normalize(Tuple({x, Tuple({x, x}), x, Tuple({x, Tuple({x, x})})}));
|
||||
|
||||
auto ftransleaf = [&](Expr value, std::array<NInt, 2> msgs) -> Expr {
|
||||
int lhs = msgs[0].LeafValue().as_or_throw<IntImm>()->value;
|
||||
int rhs = msgs[1].LeafValue().as_or_throw<IntImm>()->value;
|
||||
if (lhs > rhs)
|
||||
return z;
|
||||
else if (lhs == rhs)
|
||||
return value;
|
||||
else
|
||||
return y;
|
||||
};
|
||||
|
||||
Expr expected = Tuple({y, Tuple({y, z}), x, Tuple({y, Tuple({y, z})})});
|
||||
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(
|
||||
TransformTupleLeaf(expr, std::array<NInt, 2>({msg1, msg2}), ftransleaf), expected));
|
||||
|
||||
EXPECT_TRUE(
|
||||
expr.same_as(TransformTupleLeaf(expr, std::array<NInt, 2>({msg1, msg1}), ftransleaf)));
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ffi/memory.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
|
||||
namespace tvm {
|
||||
namespace test {
|
||||
|
||||
using namespace tvm::runtime;
|
||||
|
||||
class ObjBase : public ffi::Object {
|
||||
public:
|
||||
// dynamically allocate slow
|
||||
static constexpr const uint32_t _type_child_slots = 1;
|
||||
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjBase", ObjBase, ffi::Object);
|
||||
};
|
||||
|
||||
class ObjA : public ObjBase {
|
||||
public:
|
||||
static constexpr const uint32_t _type_child_slots = 0;
|
||||
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjA", ObjA, ObjBase);
|
||||
};
|
||||
|
||||
class ObjB : public ObjBase {
|
||||
public:
|
||||
static constexpr const uint32_t _type_child_slots = 0;
|
||||
TVM_FFI_DECLARE_OBJECT_INFO("test.ObjB", ObjB, ObjBase);
|
||||
};
|
||||
|
||||
class ObjAA : public ObjA {
|
||||
public:
|
||||
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.ObjAA", ObjAA, ObjA);
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace tvm
|
||||
|
||||
TEST(ObjectHierachy, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::runtime;
|
||||
using namespace tvm::test;
|
||||
using namespace tvm::ffi;
|
||||
|
||||
ffi::ObjectRef refA(ffi::make_object<ObjA>());
|
||||
TVM_FFI_ICHECK_EQ(refA->type_index(), ObjA::RuntimeTypeIndex());
|
||||
TVM_FFI_ICHECK(refA.as<ffi::Object>() != nullptr);
|
||||
TVM_FFI_ICHECK(refA.as<ObjA>() != nullptr);
|
||||
TVM_FFI_ICHECK(refA.as<ObjBase>() != nullptr);
|
||||
TVM_FFI_ICHECK(refA.as<ObjB>() == nullptr);
|
||||
TVM_FFI_ICHECK(refA.as<ObjAA>() == nullptr);
|
||||
|
||||
ffi::ObjectRef refAA(ffi::make_object<ObjAA>());
|
||||
TVM_FFI_ICHECK_EQ(refAA->type_index(), ObjAA::RuntimeTypeIndex());
|
||||
TVM_FFI_ICHECK(refAA.as<ffi::Object>() != nullptr);
|
||||
TVM_FFI_ICHECK(refAA.as<ObjBase>() != nullptr);
|
||||
TVM_FFI_ICHECK(refAA.as<ObjA>() != nullptr);
|
||||
TVM_FFI_ICHECK(refAA.as<ObjAA>() != nullptr);
|
||||
TVM_FFI_ICHECK(refAA.as<ObjB>() == nullptr);
|
||||
|
||||
ffi::ObjectRef refB(ffi::make_object<ObjB>());
|
||||
TVM_FFI_ICHECK_EQ(refB->type_index(), ObjB::RuntimeTypeIndex());
|
||||
TVM_FFI_ICHECK(refB.as<ffi::Object>() != nullptr);
|
||||
TVM_FFI_ICHECK(refB.as<ObjBase>() != nullptr);
|
||||
TVM_FFI_ICHECK(refB.as<ObjA>() == nullptr);
|
||||
TVM_FFI_ICHECK(refB.as<ObjAA>() == nullptr);
|
||||
TVM_FFI_ICHECK(refB.as<ObjB>() != nullptr);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "../src/arith/pattern_match.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/tirx/analysis.h>
|
||||
|
||||
TEST(Pattern, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::tirx;
|
||||
using namespace tvm::arith;
|
||||
tvm::tirx::PrimVar x("x"), y("y"), z("z");
|
||||
PrimExpr scalable_lanes = Mul(Call(PrimType::Int(32), builtin::vscale(), {}), 4);
|
||||
arith::PVar<PrimExpr> px, py, pz;
|
||||
arith::PVar<DLDataType> pt;
|
||||
arith::PVar<PrimExpr> planes;
|
||||
arith::PCallExpr<PVscaleOp> vscale;
|
||||
|
||||
// arithmetics
|
||||
auto r = 1 + (y + 1);
|
||||
TVM_FFI_ICHECK(!(px + (px + px)).Match(r));
|
||||
TVM_FFI_ICHECK(!(px + (py + py)).Match(r));
|
||||
TVM_FFI_ICHECK((px + (py + pz)).Match(r));
|
||||
auto pattern = px + (py + pz);
|
||||
TVM_FFI_ICHECK(pattern.Match(r));
|
||||
{
|
||||
TVM_FFI_ICHECK((px + (py + px)).Match(r));
|
||||
auto rr = (px + py).Eval();
|
||||
|
||||
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(rr, 1 + y));
|
||||
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval() + py.Eval(), 1 + y));
|
||||
}
|
||||
{
|
||||
TVM_FFI_ICHECK((px + max(py, px)).Match((x + 1) + max(y, (x + 1))));
|
||||
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
|
||||
}
|
||||
TVM_FFI_ICHECK(!(px + min(py, px)).Match((x + 1) + max(y, (x + 1))));
|
||||
|
||||
TVM_FFI_ICHECK((px + min(py, px)).Match(z + min(y, z)));
|
||||
TVM_FFI_ICHECK((px + truncdiv(py, px * py)).Match(x + truncdiv(2, x * 2)));
|
||||
TVM_FFI_ICHECK((px - truncmod(py, px * pz)).Match(x - truncmod(2, x * 2)));
|
||||
TVM_FFI_ICHECK((px - floormod(py, px * PConst<PrimExpr>(2))).Match(x - floormod(2, x * 2)));
|
||||
|
||||
// logicals
|
||||
TVM_FFI_ICHECK((px == pz).Match(x == 1));
|
||||
TVM_FFI_ICHECK((px != pz).Match(x != 1));
|
||||
TVM_FFI_ICHECK((px > py).Match(x > y));
|
||||
TVM_FFI_ICHECK((px < py).Match(x < y));
|
||||
TVM_FFI_ICHECK((px <= py).Match(x <= y));
|
||||
TVM_FFI_ICHECK((px >= py).Match(x >= y));
|
||||
TVM_FFI_ICHECK((px >= py && px < pz).Match(x >= y && x < z));
|
||||
TVM_FFI_ICHECK((!(px > py || px != py)).Match(!(x > y || x != y)));
|
||||
{
|
||||
TVM_FFI_ICHECK(select(px >= pz, py, py + pz).Match(tirx::Select((x + 1) >= 1, y, y + 1)));
|
||||
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(px.Eval(), x + 1));
|
||||
}
|
||||
// bit intrinsics
|
||||
{
|
||||
TVM_FFI_ICHECK((px >> pz).Match(x >> 1));
|
||||
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
||||
}
|
||||
TVM_FFI_ICHECK(!(px >> pz).Match(x << 1));
|
||||
TVM_FFI_ICHECK((px << pz).Match(x << 1));
|
||||
TVM_FFI_ICHECK((px & pz).Match(x & 1));
|
||||
TVM_FFI_ICHECK((px | pz).Match(x | 1));
|
||||
TVM_FFI_ICHECK((px ^ pz).Match(x ^ 1));
|
||||
TVM_FFI_ICHECK((px - (~(py | (px * pz)))).Match(x - (~(2 | (x * 2)))));
|
||||
// select
|
||||
{
|
||||
TVM_FFI_ICHECK(select(px > pz, py, py + pz).Match(tirx::Select(x > 1, y, y + 1)));
|
||||
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
||||
}
|
||||
TVM_FFI_ICHECK(!select(px > pz, py, py + pz).Match(tirx::Select(x > 2, y, y + 1)));
|
||||
TVM_FFI_ICHECK(!select(px > pz, py, py).Match(tirx::Select(x > 2, y, y + 1)));
|
||||
{
|
||||
TVM_FFI_ICHECK(select(px, py, pz).Match(tirx::Select(x > 2, y, y + 1)));
|
||||
TVM_FFI_ICHECK(tirx::ExprDeepEqual()(pz.Eval(), y + 1));
|
||||
}
|
||||
// if_then_else
|
||||
{
|
||||
TVM_FFI_ICHECK(if_then_else(px > pz, py, py + pz).Match(if_then_else(x > 1, y, y + 1)));
|
||||
TVM_FFI_ICHECK(is_const_int(pz.Eval(), 1));
|
||||
}
|
||||
// cast pattern
|
||||
{
|
||||
TVM_FFI_ICHECK(!cast(PConst<DLDataType>(DLDataType{kDLInt, 32, 1}), px)
|
||||
.Match(tirx::Cast(PrimType::Float(64), x)));
|
||||
TVM_FFI_ICHECK(cast(pt, px).Match(tirx::Cast(PrimType::Float(64), x)));
|
||||
TVM_FFI_ICHECK((pt.Eval() == DLDataType{kDLFloat, 64, 1}));
|
||||
auto zz = cast(pt, px).Eval();
|
||||
TVM_FFI_ICHECK(
|
||||
(cast(pt, px) - cast(pt, py))
|
||||
.Match(tirx::Cast(PrimType::Float(64), x) - tirx::Cast(PrimType::Int(64), x)));
|
||||
auto expr = tirx::Cast(PrimType::Int(32), tirx::Cast(PrimType::Float(64), x));
|
||||
TVM_FFI_ICHECK(!(cast(pt, cast(pt, px))).Match(expr));
|
||||
}
|
||||
// ramp pattern
|
||||
{
|
||||
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, 10)));
|
||||
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
|
||||
TVM_FFI_ICHECK(ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 1, scalable_lanes)));
|
||||
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
|
||||
TVM_FFI_ICHECK(!ramp(px, PConst<PrimExpr>(1), planes).Match(tirx::Ramp(x, 2, 10)));
|
||||
}
|
||||
// broadcast pattern
|
||||
{
|
||||
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, 10)));
|
||||
TVM_FFI_ICHECK(planes.Eval().as<IntImmNode>()->value == 10);
|
||||
TVM_FFI_ICHECK(broadcast(px * py, planes).Match(tirx::Broadcast(x * 10, 10)));
|
||||
TVM_FFI_ICHECK(broadcast(px, planes).Match(tirx::Broadcast(x, scalable_lanes)));
|
||||
TVM_FFI_ICHECK((vscale * PConst<PrimExpr>(4)).Match(planes.Eval()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Pattern, IntImm) {
|
||||
using namespace tvm;
|
||||
tirx::PrimVar tx("tx"), ty("ty");
|
||||
arith::PVar<IntImm> c;
|
||||
arith::PVar<tirx::Var> v;
|
||||
{
|
||||
// We can match integer and Var, both of which are
|
||||
// special case container of Expr
|
||||
TVM_FFI_ICHECK((v * c).Match(tx * 3));
|
||||
TVM_FFI_ICHECK_EQ(c.Eval()->value, 3);
|
||||
TVM_FFI_ICHECK((v * 3).Match(tx * 3));
|
||||
}
|
||||
// cannot match c to ty
|
||||
TVM_FFI_ICHECK(!(v * c).Match(tx * ty));
|
||||
// cannot match tx + 1 to v
|
||||
TVM_FFI_ICHECK(!(v * c).Match((tx + 1) * 3));
|
||||
}
|
||||
|
||||
TEST(Pattern, MatchWithType) {
|
||||
using namespace tvm;
|
||||
// match expr with specified dtype
|
||||
arith::PVarWithDataType<PrimExpr, arith::PConst<DLDataType>> pat(DLDataType{kDLFloat, 32, 1});
|
||||
tirx::PrimVar x("x", PrimType::Float(32));
|
||||
tirx::PrimVar y("y", PrimType::Float(32));
|
||||
tirx::PrimVar x_int("x", PrimType::Int(32));
|
||||
tirx::PrimVar y_int("y", PrimType::Int(32));
|
||||
TVM_FFI_ICHECK(pat.Match(x + y * 2.0f));
|
||||
TVM_FFI_ICHECK(!pat.Match(x_int + y_int * 2));
|
||||
|
||||
// match vectorized expr with specified element dtype
|
||||
arith::PVecDataType vec_ty(DLDataType{kDLFloat, 32, 1});
|
||||
arith::PVarWithDataType<PrimExpr, arith::PVecDataType> vpat(vec_ty);
|
||||
tirx::PrimVar vx("x", PrimType::Float(32, 8));
|
||||
tirx::PrimVar vy("y", PrimType::Float(32, 8));
|
||||
tirx::PrimVar vx_int("x", PrimType::Int(32, 8));
|
||||
tirx::PrimVar vy_int("y", PrimType::Int(32, 8));
|
||||
TVM_FFI_ICHECK(vpat.Match(vx + vy * tirx::Broadcast(2.0f, 8)));
|
||||
TVM_FFI_ICHECK(!vpat.Match(vx_int + vy_int * tirx::Broadcast(2, 8)));
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/s_tir/random_engine.h>
|
||||
|
||||
TEST(RandomEngine, Randomness) {
|
||||
int64_t rand_state = 0;
|
||||
|
||||
tvm::s_tir::LinearCongruentialEngine rng(&rand_state);
|
||||
rng.Seed(0x114514);
|
||||
|
||||
bool covered[100];
|
||||
memset(covered, 0, sizeof(covered));
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
covered[rng() % 100] = true;
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
TVM_FFI_ICHECK(covered[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RandomEngine, Reproducibility) {
|
||||
int64_t rand_state_a = 0, rand_state_b = 0;
|
||||
tvm::s_tir::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b);
|
||||
|
||||
rng_a.Seed(0x23456789);
|
||||
rng_b.Seed(0x23456789);
|
||||
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RandomEngine, Serialization) {
|
||||
int64_t rand_state_a = 0, rand_state_b = 0;
|
||||
tvm::s_tir::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b);
|
||||
|
||||
rng_a.Seed(0x56728);
|
||||
|
||||
rand_state_b = rand_state_a;
|
||||
for (int i = 0; i < 100000; i++) TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
|
||||
|
||||
for (int i = 0; i < 123456; i++) rng_a();
|
||||
|
||||
rand_state_b = rand_state_a;
|
||||
for (int i = 0; i < 100000; i++) TVM_FFI_ICHECK_EQ(rng_a(), rng_b());
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
|
||||
namespace tvm {
|
||||
namespace runtime {
|
||||
namespace detail {
|
||||
namespace {
|
||||
|
||||
TEST(TvmLogDebugSettings, Disabled) {
|
||||
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec(nullptr);
|
||||
EXPECT_FALSE(settings.dlog_enabled());
|
||||
|
||||
settings = TvmLogDebugSettings::ParseSpec("");
|
||||
EXPECT_FALSE(settings.dlog_enabled());
|
||||
|
||||
settings = TvmLogDebugSettings::ParseSpec("0");
|
||||
EXPECT_FALSE(settings.dlog_enabled());
|
||||
}
|
||||
|
||||
TEST(TvmLogDebugSettings, DlogOnly) {
|
||||
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec("1");
|
||||
EXPECT_TRUE(settings.dlog_enabled());
|
||||
EXPECT_FALSE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 0));
|
||||
}
|
||||
|
||||
TEST(TvmLogDebugSettings, VLogEnabledDefault) {
|
||||
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec("DEFAULT=3");
|
||||
EXPECT_TRUE(settings.dlog_enabled());
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 3));
|
||||
EXPECT_FALSE(settings.VerboseEnabled("my/filesytem/src/foo/bar.cc", 4));
|
||||
}
|
||||
|
||||
TEST(TvmLogDebugSettings, VLogEnabledComplex) {
|
||||
TvmLogDebugSettings settings =
|
||||
TvmLogDebugSettings::ParseSpec("foo/bar.cc=3,baz.cc=-1,DEFAULT=2,another/file.cc=4");
|
||||
EXPECT_TRUE(settings.dlog_enabled());
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 3));
|
||||
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 4));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/other.cc", 2));
|
||||
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/foo/other.cc", 3));
|
||||
EXPECT_FALSE(settings.VerboseEnabled("my/filesystem/src/baz.cc", 0));
|
||||
}
|
||||
|
||||
#define MATCH_THROW(stmt, err_type, matcher) \
|
||||
try { \
|
||||
stmt; \
|
||||
} catch (const err_type& e) { \
|
||||
EXPECT_THAT(e.what(), matcher); \
|
||||
} catch (...) { \
|
||||
EXPECT_FALSE("stmt threw an unexpected exception"); \
|
||||
}
|
||||
|
||||
TEST(TvmLogDebugSettings, IllFormed) {
|
||||
MATCH_THROW(
|
||||
TvmLogDebugSettings::ParseSpec("foo/bar.cc=bogus;"), tvm::ffi::Error,
|
||||
::testing::HasSubstr("TVM_LOG_DEBUG ill-formed at position 11: invalid level: \"bogus;\""));
|
||||
|
||||
MATCH_THROW(TvmLogDebugSettings::ParseSpec("DEFAULT=2;bar/baz.cc=2"), tvm::ffi::Error,
|
||||
::testing::HasSubstr(
|
||||
"TVM_LOG_DEBUG ill-formed at position 8: invalid level: \"2;bar/baz.cc=2\""));
|
||||
|
||||
MATCH_THROW(TvmLogDebugSettings::ParseSpec("DEFAULT=2,bar/baz.cc+2"), tvm::ffi::Error,
|
||||
::testing::HasSubstr("TVM_LOG_DEBUG ill-formed at position 22: expecting "
|
||||
"\"=<level>\" after \"bar/baz.cc+2\""));
|
||||
}
|
||||
|
||||
TEST(TvmLogDebugSettings, SpecPrefix) {
|
||||
TvmLogDebugSettings settings = TvmLogDebugSettings::ParseSpec(
|
||||
"../src/foo/bar.cc=3,src/baz.cc=3,foo/bar/src/another/file.cc=4");
|
||||
EXPECT_TRUE(settings.dlog_enabled());
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/foo/bar.cc", 3));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("foo/bar.cc", 3));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/baz.cc", 3));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("baz.cc", 3));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("my/filesystem/src/another/file.cc", 4));
|
||||
EXPECT_TRUE(settings.VerboseEnabled("another/file.cc", 4));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace detail
|
||||
} // namespace runtime
|
||||
} // namespace tvm
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "../../../src/support/ring_buffer.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace tvm {
|
||||
namespace support {
|
||||
namespace {
|
||||
|
||||
TEST(RingBuffer, ReadWrite) {
|
||||
RingBuffer buffer;
|
||||
std::vector<int> data = {1, 2, 3, 4};
|
||||
std::vector<int> output;
|
||||
|
||||
buffer.Write(data.data(), data.size() * 4);
|
||||
ASSERT_EQ(buffer.bytes_available(), data.size() * 4);
|
||||
|
||||
output.resize(4);
|
||||
buffer.Read(output.data(), data.size() * 4);
|
||||
|
||||
for (size_t i = 0; i < output.size(); ++i) {
|
||||
ASSERT_EQ(output[i], data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RingBuffer, ReadWithCallback) {
|
||||
RingBuffer buffer;
|
||||
std::vector<int> data = {1, 2, 3, 4};
|
||||
std::vector<int> output;
|
||||
|
||||
buffer.Write(data.data(), data.size() * 4);
|
||||
|
||||
auto callback0 = [](const char* data, size_t size) -> size_t {
|
||||
const int* iptr = reinterpret_cast<const int*>(data);
|
||||
TVM_FFI_ICHECK_EQ(iptr[0], 1);
|
||||
TVM_FFI_ICHECK_EQ(iptr[1], 2);
|
||||
return size;
|
||||
};
|
||||
buffer.ReadWithCallback(callback0, 2 * sizeof(int));
|
||||
auto callback1 = [](const char* data, size_t size) -> size_t {
|
||||
const int* iptr = reinterpret_cast<const int*>(data);
|
||||
TVM_FFI_ICHECK_EQ(iptr[0], 3);
|
||||
TVM_FFI_ICHECK_EQ(iptr[1], 4);
|
||||
return size;
|
||||
};
|
||||
buffer.ReadWithCallback(callback1, 2 * sizeof(int));
|
||||
}
|
||||
} // namespace
|
||||
} // namespace support
|
||||
} // namespace tvm
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
|
||||
#include "../../src/support/utils.h"
|
||||
|
||||
namespace tvm {
|
||||
namespace test {
|
||||
|
||||
TEST(HashTests, HashStability) {
|
||||
size_t a = 345292;
|
||||
int b = 795620;
|
||||
EXPECT_EQ(::tvm::support::HashCombine(a, b), 2677237020);
|
||||
uint64_t c = 12345;
|
||||
int d = 987654432;
|
||||
EXPECT_EQ(::tvm::support::HashCombine(c, d), 3642871070);
|
||||
size_t e = 1010101;
|
||||
size_t f = 3030303;
|
||||
EXPECT_EQ(::tvm::support::HashCombine(e, f), 2722928432);
|
||||
}
|
||||
|
||||
TEST(StartsWithTests, Basic) {
|
||||
EXPECT_TRUE(::tvm::support::StartsWith("abc", "abc"));
|
||||
EXPECT_TRUE(::tvm::support::StartsWith("abcd", "abc"));
|
||||
EXPECT_FALSE(::tvm::support::StartsWith("abc", "abcd"));
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace tvm
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/target/target.h>
|
||||
#include <tvm/target/virtual_device.h>
|
||||
|
||||
namespace tvm {
|
||||
namespace {
|
||||
|
||||
TEST(VirtualDevice, Join_Defined) {
|
||||
{
|
||||
Target target_a = Target("cuda");
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3);
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, -1, target_a, "global");
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_TRUE(actual.operator bool());
|
||||
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
|
||||
}
|
||||
{
|
||||
Target target_a = Target("cuda");
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, -1, target_a, "global");
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3);
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_TRUE(actual.operator bool());
|
||||
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
|
||||
}
|
||||
{
|
||||
Target target_a = Target("cuda");
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA);
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 2, target_a);
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_TRUE(actual.operator bool());
|
||||
VirtualDevice expected = VirtualDevice(kDLCUDA, 2, target_a);
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
|
||||
}
|
||||
{
|
||||
Target target_a = Target("cuda");
|
||||
VirtualDevice lhs = VirtualDevice();
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, target_a, "global");
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_TRUE(actual.operator bool());
|
||||
VirtualDevice expected = rhs;
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual.value(), expected));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VirtualDevice, Join_Undefined) {
|
||||
{
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA);
|
||||
VirtualDevice rhs = VirtualDevice(kDLCPU);
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_FALSE(actual);
|
||||
}
|
||||
{
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3);
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 4);
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_FALSE(actual);
|
||||
}
|
||||
{
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3, Target("cuda"));
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, Target("cuda"));
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_FALSE(actual);
|
||||
}
|
||||
{
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, 3, Target("cuda"), "local");
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, Target("cuda"), "global");
|
||||
ffi::Optional<VirtualDevice> actual = VirtualDevice::Join(lhs, rhs);
|
||||
EXPECT_FALSE(actual);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(VirtualDevice, Default) {
|
||||
Target target_a = Target("cuda");
|
||||
VirtualDevice lhs = VirtualDevice(kDLCUDA, -1, Target(), "global");
|
||||
VirtualDevice rhs = VirtualDevice(kDLCUDA, 3, target_a, "local");
|
||||
VirtualDevice actual = VirtualDevice::Default(lhs, rhs);
|
||||
VirtualDevice expected = VirtualDevice(kDLCUDA, 3, target_a, "global");
|
||||
EXPECT_TRUE(tvm::ffi::StructuralEqual()(actual, expected));
|
||||
}
|
||||
|
||||
TEST(VirtualDevice, Constructor_Invalid) {
|
||||
EXPECT_ANY_THROW(VirtualDevice(kDLCPU, -1, Target("cuda")));
|
||||
}
|
||||
|
||||
TEST(VirtualDeviceCache, Memoized) {
|
||||
VirtualDeviceCache cache;
|
||||
Target target_a = Target("cuda");
|
||||
Target target_b = Target("llvm");
|
||||
Target target_c = Target("cuda");
|
||||
VirtualDevice virtual_device_a = cache.Make(kDLCUDA, 3, target_a, "local");
|
||||
VirtualDevice virtual_device_b = cache.Make(kDLCPU, 1, target_b, "global");
|
||||
|
||||
EXPECT_EQ(cache.Make(kDLCUDA, 3, target_a, "local"), virtual_device_a);
|
||||
EXPECT_EQ(cache.Make(kDLCPU, 1, target_b, "global"), virtual_device_b);
|
||||
EXPECT_NE(cache.Make(kDLCUDA, 2, target_a, "local"), virtual_device_a);
|
||||
EXPECT_NE(cache.Make(kDLCPU, 3, target_b, "local"), virtual_device_a);
|
||||
EXPECT_NE(cache.Make(kDLCUDA, 3, target_a, "global"), virtual_device_a);
|
||||
EXPECT_EQ(cache.Make(kDLCUDA, 3, Target("cuda"), "local"), virtual_device_a);
|
||||
EXPECT_NE(
|
||||
cache.Make(kDLCUDA, 3,
|
||||
Target(ffi::Map<ffi::String, ffi::Any>{{"kind", ffi::String("cuda")},
|
||||
{"max_threads_per_block", int64_t(4096)}}),
|
||||
"local"),
|
||||
virtual_device_a);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tvm
|
||||
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ir/expr.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/target/target.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
using namespace tvm;
|
||||
|
||||
TVM_REGISTER_TARGET_KIND("TestTargetKind", kDLCPU)
|
||||
.set_attr<std::string>("Attr1", "Value1")
|
||||
.add_attr_option<bool>("my_bool")
|
||||
.add_attr_option<ffi::Array<ffi::String>>("your_names")
|
||||
.add_attr_option<ffi::Map<ffi::String, int64_t>>("her_maps");
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> TestTargetParser(ffi::Map<ffi::String, ffi::Any> target) {
|
||||
ffi::String mcpu = target.at("mcpu").as_or_throw<ffi::String>();
|
||||
target.Set("mcpu", ffi::String("super_") + mcpu);
|
||||
target.Set("keys", ffi::Array<ffi::String>({"super"}));
|
||||
target.Set("feature.test", true);
|
||||
return target;
|
||||
}
|
||||
|
||||
ffi::Map<ffi::String, ffi::Any> TestAttrsPreProcessor(ffi::Map<ffi::String, ffi::Any> attrs) {
|
||||
attrs.Set("mattr", ffi::String("woof"));
|
||||
return attrs;
|
||||
}
|
||||
|
||||
TVM_REGISTER_TARGET_KIND("TestTargetParser", kDLCPU)
|
||||
.add_attr_option<ffi::String>("mattr")
|
||||
.add_attr_option<ffi::String>("mcpu")
|
||||
.set_default_keys({"cpu"})
|
||||
.set_target_canonicalizer(TestTargetParser);
|
||||
|
||||
TVM_REGISTER_TARGET_KIND("TestAttrsPreprocessor", kDLCPU)
|
||||
.add_attr_option<ffi::String>("mattr")
|
||||
.set_default_keys({"cpu"})
|
||||
.set_target_canonicalizer(TestAttrsPreProcessor);
|
||||
|
||||
TVM_REGISTER_TARGET_KIND("TestClashingPreprocessor", kDLCPU)
|
||||
.add_attr_option<ffi::String>("mattr")
|
||||
.add_attr_option<ffi::String>("mcpu")
|
||||
.set_default_keys({"cpu"})
|
||||
.set_target_canonicalizer(TestTargetParser);
|
||||
|
||||
TEST(TargetKind, GetAttrMap) {
|
||||
auto map = tvm::TargetKind::GetAttrMap<std::string>("Attr1");
|
||||
auto target_kind = tvm::TargetKind::Get("TestTargetKind").value();
|
||||
std::string result = map[target_kind];
|
||||
TVM_FFI_ICHECK_EQ(result, "Value1");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, NestedConfig) {
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"my_bool", true},
|
||||
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
|
||||
{"kind", ffi::String("TestTargetKind")},
|
||||
{
|
||||
"her_maps",
|
||||
ffi::Map<ffi::String, int64_t>{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
},
|
||||
},
|
||||
};
|
||||
Target target = Target(config);
|
||||
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("TestTargetKind").value());
|
||||
TVM_FFI_ICHECK_EQ(target->tag, "");
|
||||
TVM_FFI_ICHECK(target->keys.empty());
|
||||
bool my_bool = target->GetAttr<bool>("my_bool").value();
|
||||
TVM_FFI_ICHECK_EQ(my_bool, true);
|
||||
ffi::Array<ffi::String> your_names =
|
||||
target->GetAttr<ffi::Array<ffi::String>>("your_names").value();
|
||||
TVM_FFI_ICHECK_EQ(your_names.size(), 2U);
|
||||
TVM_FFI_ICHECK_EQ(your_names[0], "junru");
|
||||
TVM_FFI_ICHECK_EQ(your_names[1], "jian");
|
||||
ffi::Map<ffi::String, int64_t> her_maps =
|
||||
target->GetAttr<ffi::Map<ffi::String, int64_t>>("her_maps").value();
|
||||
TVM_FFI_ICHECK_EQ(her_maps.size(), 2U);
|
||||
TVM_FFI_ICHECK_EQ(her_maps["a"], 1);
|
||||
TVM_FFI_ICHECK_EQ(her_maps["b"], 2);
|
||||
}
|
||||
|
||||
TEST(TargetCreationFail, UnrecognizedConfigOption) {
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"my_bool", true},
|
||||
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
|
||||
{"kind", ffi::String("TestTargetKind")},
|
||||
{"bad", ffi::ObjectRef(nullptr)},
|
||||
{
|
||||
"her_maps",
|
||||
ffi::Map<ffi::String, int64_t>{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
},
|
||||
},
|
||||
};
|
||||
bool failed = false;
|
||||
try {
|
||||
Target tgt(config);
|
||||
} catch (...) {
|
||||
failed = true;
|
||||
}
|
||||
ASSERT_EQ(failed, true);
|
||||
}
|
||||
|
||||
TEST(TargetCreationFail, TypeMismatch) {
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"my_bool", ffi::String("true")},
|
||||
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
|
||||
{"kind", ffi::String("TestTargetKind")},
|
||||
{
|
||||
"her_maps",
|
||||
ffi::Map<ffi::String, int64_t>{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
},
|
||||
},
|
||||
};
|
||||
bool failed = false;
|
||||
try {
|
||||
Target tgt(config);
|
||||
} catch (...) {
|
||||
failed = true;
|
||||
}
|
||||
ASSERT_EQ(failed, true);
|
||||
}
|
||||
|
||||
TEST(TargetCreationFail, TargetKindNotFound) {
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"my_bool", "true"},
|
||||
{"your_names", ffi::Array<ffi::String>{"junru", "jian"}},
|
||||
{
|
||||
"her_maps",
|
||||
ffi::Map<ffi::String, int64_t>{
|
||||
{"a", 1},
|
||||
{"b", 2},
|
||||
},
|
||||
},
|
||||
};
|
||||
bool failed = false;
|
||||
try {
|
||||
Target tgt(config);
|
||||
} catch (...) {
|
||||
failed = true;
|
||||
}
|
||||
ASSERT_EQ(failed, true);
|
||||
}
|
||||
|
||||
TEST(TargetCreation, TargetParser) {
|
||||
Target test_target(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
});
|
||||
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
|
||||
ASSERT_EQ(test_target->keys.size(), 1);
|
||||
ASSERT_EQ(test_target->keys[0], "super");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, TargetFeatures) {
|
||||
Target test_target_with_parser(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
});
|
||||
// Features are stored as "feature.xxx" keys in attrs
|
||||
ASSERT_EQ(test_target_with_parser->GetAttr<bool>("feature.test").value(), true);
|
||||
|
||||
Target test_target_no_parser("TestTargetKind");
|
||||
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test"), std::nullopt);
|
||||
ASSERT_EQ(test_target_no_parser->GetAttr<bool>("feature.test", true).value(), true);
|
||||
}
|
||||
|
||||
TEST(TargetCreation, TargetFeaturesSetByCanonicalizer) {
|
||||
// feature.* keys are set by the canonicalizer, not by user input.
|
||||
Target test_target(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
});
|
||||
// TestTargetParser sets "feature.test" = true
|
||||
ASSERT_EQ(test_target->GetAttr<bool>("feature.test").value(), true);
|
||||
// Non-existent features return nullopt
|
||||
ASSERT_EQ(test_target->GetAttr<bool>("feature.nonexistent"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(TargetCreation, TargetAttrsPreProcessor) {
|
||||
Target test_target(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestAttrsPreprocessor")},
|
||||
{"mattr", ffi::String("cake")},
|
||||
});
|
||||
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "woof");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, TargetParserProcessing) {
|
||||
Target test_target(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestClashingPreprocessor")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
{"mattr", ffi::String("cake")},
|
||||
});
|
||||
ASSERT_EQ(test_target->GetAttr<ffi::String>("mcpu").value(), "super_woof");
|
||||
ASSERT_EQ(test_target->GetAttr<ffi::String>("mattr").value(), "cake");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, RoundTripCanonicalizerFeatures) {
|
||||
// Construct a target whose canonicalizer sets feature.test and transforms mcpu
|
||||
Target original(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
});
|
||||
ASSERT_EQ(original->GetAttr<ffi::String>("mcpu").value(), "super_woof");
|
||||
ASSERT_EQ(original->GetAttr<bool>("feature.test").value(), true);
|
||||
|
||||
// Export to config and reconstruct
|
||||
ffi::Map<ffi::String, ffi::Any> exported = original->ToConfig();
|
||||
Target reconstructed(exported);
|
||||
|
||||
// Canonicalized attrs must survive the round-trip
|
||||
// Note: mcpu gets canonicalized again (super_super_woof) because the canonicalizer runs
|
||||
ASSERT_TRUE(reconstructed->GetAttr<ffi::String>("mcpu").has_value());
|
||||
ASSERT_EQ(reconstructed->GetAttr<bool>("feature.test").value(), true);
|
||||
ASSERT_EQ(reconstructed->keys.size(), 1);
|
||||
ASSERT_EQ(reconstructed->keys[0], "super");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, RoundTripCanonicalizerFeaturesNestedHost) {
|
||||
// Construct a host target whose canonicalizer sets feature.test
|
||||
Target host(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
});
|
||||
ASSERT_EQ(host->GetAttr<bool>("feature.test").value(), true);
|
||||
|
||||
// Attach it as host to another target
|
||||
Target outer(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestTargetKind")},
|
||||
{"my_bool", true},
|
||||
});
|
||||
Target combined(outer, host);
|
||||
|
||||
// Export the outer target (includes nested host) and reconstruct
|
||||
ffi::Map<ffi::String, ffi::Any> exported = combined->ToConfig();
|
||||
Target reconstructed(exported);
|
||||
|
||||
// The nested host must reconstruct successfully with feature.* preserved
|
||||
ffi::Optional<Target> reconstructed_host = reconstructed->GetHost();
|
||||
ASSERT_TRUE(reconstructed_host.has_value());
|
||||
ASSERT_EQ(reconstructed_host.value()->GetAttr<bool>("feature.test").value(), true);
|
||||
ASSERT_TRUE(reconstructed_host.value()->GetAttr<ffi::String>("mcpu").has_value());
|
||||
}
|
||||
|
||||
TEST(TargetCreationFail, UnknownNonFeatureKeyStillFails) {
|
||||
// Verify that unknown non-feature.* keys still fail schema validation
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"kind", ffi::String("TestTargetParser")},
|
||||
{"mcpu", ffi::String("woof")},
|
||||
{"unknown_key", ffi::String("bad")},
|
||||
};
|
||||
ASSERT_THROW({ Target{config}; }, tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TVM_REGISTER_TARGET_KIND("TestStringKind", kDLCPU)
|
||||
.add_attr_option<ffi::String>("single")
|
||||
.add_attr_option<ffi::Array<ffi::String>>("array")
|
||||
.add_attr_option<ffi::Array<ffi::Array<ffi::String>>>("nested-array")
|
||||
.add_attr_option<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array");
|
||||
|
||||
TEST(TargetCreation, ProcessStrings) {
|
||||
// Test single string attribute
|
||||
Target test_target1(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestStringKind")},
|
||||
{"single", ffi::String("'string with single quote")},
|
||||
});
|
||||
ASSERT_TRUE(test_target1->GetAttr<ffi::String>("single"));
|
||||
ffi::String string1 = test_target1->GetAttr<ffi::String>("single").value();
|
||||
ASSERT_EQ(string1, "'string with single quote");
|
||||
|
||||
// Test array of strings
|
||||
Target test_target3(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestStringKind")},
|
||||
{"array", ffi::Array<ffi::String>{"-danny", "-sammy=1", "-kirby=string with space"}},
|
||||
});
|
||||
ASSERT_TRUE(test_target3->GetAttr<ffi::Array<ffi::String>>("array"));
|
||||
ffi::Array<ffi::String> array3 = test_target3->GetAttr<ffi::Array<ffi::String>>("array").value();
|
||||
ASSERT_EQ(array3[0], "-danny");
|
||||
ASSERT_EQ(array3[1], "-sammy=1");
|
||||
ASSERT_EQ(array3[2], "-kirby=string with space");
|
||||
|
||||
// Test nested array of strings
|
||||
Target test_target6(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestStringKind")},
|
||||
{"nested-array",
|
||||
ffi::Array<ffi::Array<ffi::String>>{
|
||||
ffi::Array<ffi::String>{"foo0", "foo1", "foo2"},
|
||||
ffi::Array<ffi::String>{"bar0", "bar1", "bar2"},
|
||||
ffi::Array<ffi::String>{"baz0", "baz1"},
|
||||
}},
|
||||
});
|
||||
ASSERT_TRUE(test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array"));
|
||||
ffi::Array<ffi::Array<ffi::String>> array6 =
|
||||
test_target6->GetAttr<ffi::Array<ffi::Array<ffi::String>>>("nested-array").value();
|
||||
ASSERT_EQ(array6[0][0], "foo0");
|
||||
ASSERT_EQ(array6[0][1], "foo1");
|
||||
ASSERT_EQ(array6[0][2], "foo2");
|
||||
ASSERT_EQ(array6[1][0], "bar0");
|
||||
ASSERT_EQ(array6[1][1], "bar1");
|
||||
ASSERT_EQ(array6[1][2], "bar2");
|
||||
ASSERT_EQ(array6[2][0], "baz0");
|
||||
ASSERT_EQ(array6[2][1], "baz1");
|
||||
|
||||
// Test doubly-nested array of strings
|
||||
Target test_target7(ffi::Map<ffi::String, ffi::Any>{
|
||||
{"kind", ffi::String("TestStringKind")},
|
||||
{"nested2-array",
|
||||
ffi::Array<ffi::Array<ffi::Array<ffi::String>>>{
|
||||
ffi::Array<ffi::Array<ffi::String>>{
|
||||
ffi::Array<ffi::String>{"foo0", "foo1"},
|
||||
ffi::Array<ffi::String>{"bar0", "bar1"},
|
||||
ffi::Array<ffi::String>{"baz0", "baz1"},
|
||||
},
|
||||
ffi::Array<ffi::Array<ffi::String>>{
|
||||
ffi::Array<ffi::String>{"zing0", "zing1"},
|
||||
ffi::Array<ffi::String>{"fred"},
|
||||
},
|
||||
}},
|
||||
});
|
||||
ASSERT_TRUE(
|
||||
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array"));
|
||||
ffi::Array<ffi::Array<ffi::Array<ffi::String>>> array7 =
|
||||
test_target7->GetAttr<ffi::Array<ffi::Array<ffi::Array<ffi::String>>>>("nested2-array")
|
||||
.value();
|
||||
ASSERT_EQ(array7.size(), 2);
|
||||
ASSERT_EQ(array7[0].size(), 3);
|
||||
ASSERT_EQ(array7[0][0].size(), 2);
|
||||
ASSERT_EQ(array7[0][1].size(), 2);
|
||||
ASSERT_EQ(array7[0][2].size(), 2);
|
||||
ASSERT_EQ(array7[1].size(), 2);
|
||||
ASSERT_EQ(array7[1][0].size(), 2);
|
||||
ASSERT_EQ(array7[1][1].size(), 1);
|
||||
|
||||
ASSERT_EQ(array7[0][0][0], "foo0");
|
||||
ASSERT_EQ(array7[0][0][1], "foo1");
|
||||
ASSERT_EQ(array7[0][1][0], "bar0");
|
||||
ASSERT_EQ(array7[0][1][1], "bar1");
|
||||
ASSERT_EQ(array7[0][2][0], "baz0");
|
||||
ASSERT_EQ(array7[0][2][1], "baz1");
|
||||
ASSERT_EQ(array7[1][0][0], "zing0");
|
||||
ASSERT_EQ(array7[1][0][1], "zing1");
|
||||
ASSERT_EQ(array7[1][1][0], "fred");
|
||||
}
|
||||
|
||||
TEST(TargetCreation, DeduplicateKeys) {
|
||||
ffi::Map<ffi::String, ffi::Any> config = {
|
||||
{"kind", ffi::String("llvm")},
|
||||
{"keys", ffi::Array<ffi::String>{"cpu", "arm_cpu"}},
|
||||
{"device", ffi::String("arm_cpu")},
|
||||
};
|
||||
Target target = Target(config);
|
||||
TVM_FFI_ICHECK_EQ(target->kind, TargetKind::Get("llvm").value());
|
||||
TVM_FFI_ICHECK_EQ(target->tag, "");
|
||||
TVM_FFI_ICHECK_EQ(target->keys.size(), 2U);
|
||||
TVM_FFI_ICHECK_EQ(target->keys[0], "cpu");
|
||||
TVM_FFI_ICHECK_EQ(target->keys[1], "arm_cpu");
|
||||
TVM_FFI_ICHECK_EQ(target->attrs.count("keys"), 0U);
|
||||
TVM_FFI_ICHECK_EQ(target->GetAttr<ffi::String>("device"), "arm_cpu");
|
||||
}
|
||||
|
||||
TEST(TargetKindRegistry, ListTargetKinds) {
|
||||
ffi::Array<ffi::String> names = TargetKindRegEntry::ListTargetKinds();
|
||||
TVM_FFI_ICHECK_EQ(names.empty(), false);
|
||||
TVM_FFI_ICHECK_EQ(std::count(std::begin(names), std::end(names), "llvm"), 1);
|
||||
}
|
||||
|
||||
TEST(TargetKindRegistry, ListTargetOptions) {
|
||||
TargetKind llvm = TargetKind::Get("llvm").value();
|
||||
ffi::Map<ffi::String, ffi::String> attrs = TargetKindRegEntry::ListTargetKindOptions(llvm);
|
||||
TVM_FFI_ICHECK_EQ(attrs.empty(), false);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/te/operation.h>
|
||||
|
||||
TEST(Tensor, Basic) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::te;
|
||||
|
||||
PrimVar m("m"), n("n"), l("l");
|
||||
|
||||
Tensor A = placeholder({m, l}, PrimType::Float(32), "A");
|
||||
Tensor B = placeholder({n, l}, PrimType::Float(32), "B");
|
||||
|
||||
auto C = compute({m, n}, [&](PrimVar i, PrimVar j) { return A[i][j]; }, "C");
|
||||
|
||||
Tensor::Slice x = A[n];
|
||||
}
|
||||
|
||||
TEST(Tensor, Reduce) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::te;
|
||||
|
||||
PrimVar m("m"), n("n"), l("l");
|
||||
te::Tensor A = te::placeholder({m, l}, PrimType::Float(32), "A");
|
||||
te::Tensor B = te::placeholder({n, l}, PrimType::Float(32), "B");
|
||||
IterVar rv = reduce_axis(Range{0, l}, "k");
|
||||
|
||||
auto C = te::compute(
|
||||
{m, n}, [&](PrimVar i, PrimVar j) { return sum(max(1 + A[i][rv] + 1, B[j][rv]), {rv}); },
|
||||
"C");
|
||||
}
|
||||
|
||||
TEST(Tensor, Indexing) {
|
||||
using namespace tvm;
|
||||
using namespace tvm::te;
|
||||
|
||||
PrimVar x("x"), y("y");
|
||||
te::Tensor A = te::placeholder({x, y}, PrimType::Float(32), "A");
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "../../src/runtime/threading_backend.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/c_backend_api.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
constexpr size_t N = 128;
|
||||
void AtomicCompute(int task_id, size_t n, std::atomic<size_t>* acc, TVMParallelGroupEnv* penv) {
|
||||
const size_t N_per_task = (n + penv->num_task - 1) / penv->num_task;
|
||||
for (size_t i = task_id * N_per_task; i < n && i < (task_id + 1) * N_per_task; ++i) {
|
||||
acc->fetch_add(i, std::memory_order_relaxed);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
class AffinityCheck {
|
||||
public:
|
||||
AffinityCheck(uint32_t parent_id, int max_concurrency, std::atomic<size_t>* acc)
|
||||
: id_(parent_id), max_concurrency_(max_concurrency), acc_(acc) {}
|
||||
|
||||
void Compute(int task_id, size_t n, TVMParallelGroupEnv* penv) {
|
||||
AtomicCompute(task_id, n, acc_, penv);
|
||||
}
|
||||
|
||||
int GetComputeResult() { return acc_->load(std::memory_order_relaxed); }
|
||||
|
||||
void GetAffinity(int task_id) {
|
||||
#if defined(__linux__)
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
thread_affinity_[task_id] = cpuset;
|
||||
// Printing the current thread CPU affinity.
|
||||
std::ostringstream str;
|
||||
for (int i = 0; i < max_concurrency_; i++) {
|
||||
if (CPU_ISSET(i, &cpuset)) {
|
||||
str << i << ",";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VerifyAffinity(const std::vector<uint32_t>& cpus) {
|
||||
#if defined(__linux__)
|
||||
std::unordered_set<uint32_t> uset;
|
||||
cpu_set_t cpu_mask;
|
||||
CPU_ZERO(&cpu_mask);
|
||||
for (auto x : cpus) {
|
||||
CPU_SET(x, &cpu_mask);
|
||||
uset.insert(x);
|
||||
}
|
||||
|
||||
for (auto x : thread_affinity_) {
|
||||
if (!CPU_EQUAL(&cpu_mask, &x.second)) {
|
||||
bool cpu_find = false;
|
||||
for (auto cpu : uset) {
|
||||
CPU_ISSET(cpu, &x.second);
|
||||
uset.erase(cpu);
|
||||
cpu_find = true;
|
||||
break;
|
||||
}
|
||||
if (!cpu_find) return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t id_;
|
||||
int max_concurrency_;
|
||||
std::atomic<size_t>* acc_;
|
||||
std::mutex mutex_;
|
||||
#if defined(__linux__)
|
||||
std::unordered_map<int, cpu_set_t> thread_affinity_;
|
||||
#endif
|
||||
};
|
||||
|
||||
static FTVMParallelLambda atomic_add_task_id = [](int task_id, TVMParallelGroupEnv* penv,
|
||||
void* cdata) -> int {
|
||||
auto* data = reinterpret_cast<std::atomic<size_t>*>(cdata);
|
||||
AtomicCompute(task_id, N, data, penv);
|
||||
return 0;
|
||||
};
|
||||
|
||||
static FTVMParallelLambda affinity_check_task_id = [](int task_id, TVMParallelGroupEnv* penv,
|
||||
void* cdata) -> int {
|
||||
auto* data = reinterpret_cast<AffinityCheck*>(cdata);
|
||||
data->Compute(task_id, N, penv);
|
||||
data->GetAffinity(task_id);
|
||||
return 0;
|
||||
};
|
||||
|
||||
TEST(ThreadingBackend, TVMBackendParallelLaunch) {
|
||||
std::atomic<size_t> acc(0);
|
||||
TVMBackendParallelLaunch(atomic_add_task_id, &acc, 0);
|
||||
EXPECT_EQ(acc.load(std::memory_order_relaxed), N * (N - 1) / 2);
|
||||
}
|
||||
|
||||
TEST(ThreadingBackend, TVMBackendParallelLaunchMultipleThreads) {
|
||||
// TODO(tulloch) use parameterised tests when available.
|
||||
size_t num_jobs_per_thread = 3;
|
||||
size_t max_num_threads = 2;
|
||||
|
||||
for (size_t num_threads = 1; num_threads < max_num_threads; ++num_threads) {
|
||||
std::vector<std::unique_ptr<std::thread>> ts;
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
ts.emplace_back(new std::thread([&]() {
|
||||
for (size_t j = 0; j < num_jobs_per_thread; ++j) {
|
||||
std::atomic<size_t> acc(0);
|
||||
TVMBackendParallelLaunch(atomic_add_task_id, &acc, 0);
|
||||
EXPECT_EQ(acc.load(std::memory_order_relaxed), N * (N - 1) / 2);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for (auto& t : ts) {
|
||||
t->join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThreadingBackend, TVMBackendAffinityConfigure) {
|
||||
int max_concurrency = tvm::runtime::threading::MaxConcurrency();
|
||||
std::vector<std::unique_ptr<std::thread>> ts;
|
||||
// Returning as there is only one CPU available.
|
||||
if (max_concurrency <= 1) {
|
||||
return;
|
||||
}
|
||||
// Creating two threads to test the 'CPU list affinity' feature.
|
||||
const int threads_num = 2;
|
||||
// Getting the maximum number of CPUs which are available to each thread.
|
||||
const int cpus_num_per_thread = max_concurrency / threads_num;
|
||||
// Testing two mode of affinity.,
|
||||
std::vector<tvm::runtime::threading::ThreadGroup::AffinityMode> modes = {
|
||||
tvm::runtime::threading::ThreadGroup::kSpecifyOneCorePerThread,
|
||||
tvm::runtime::threading::ThreadGroup::kSpecifyThreadShareAllCore};
|
||||
for (auto mode : modes) {
|
||||
for (int thread_pool_idx = 0; thread_pool_idx < threads_num; thread_pool_idx++) {
|
||||
ts.emplace_back(new std::thread(
|
||||
[&](int thread_pool_index, int sys_max_concurrency,
|
||||
tvm::runtime::threading::ThreadGroup::AffinityMode affinity_mode) {
|
||||
std::atomic<size_t> acc(0);
|
||||
AffinityCheck ac(thread_pool_index, sys_max_concurrency, &acc);
|
||||
std::vector<unsigned int> cpus;
|
||||
for (int k = 0; k < cpus_num_per_thread; k++) {
|
||||
cpus.push_back(thread_pool_index * cpus_num_per_thread + k);
|
||||
}
|
||||
tvm::runtime::threading ::Configure(affinity_mode, 0, cpus);
|
||||
TVMBackendParallelLaunch(affinity_check_task_id, &ac, 0);
|
||||
EXPECT_EQ(ac.GetComputeResult(), N * (N - 1) / 2);
|
||||
EXPECT_EQ(ac.VerifyAffinity(cpus), true);
|
||||
},
|
||||
thread_pool_idx, max_concurrency, mode));
|
||||
}
|
||||
}
|
||||
for (auto& t : ts) {
|
||||
t->join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ThreadingBackend, TVMBackendParallelForWithThreadingBackend) {
|
||||
int n = 100;
|
||||
std::vector<int> vec(/*size=*/n, /*value=*/0);
|
||||
tvm::runtime::parallel_for_with_threading_backend([&vec](int i) { vec[i] = i; }, 0, n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
EXPECT_EQ(vec[i], i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/runtime/logging.h>
|
||||
#include <tvm/te/operation.h>
|
||||
#include <tvm/tirx/analysis.h>
|
||||
#include <tvm/tirx/builtin.h>
|
||||
|
||||
TEST(SimplePasses, SideEffect) {
|
||||
using namespace tvm;
|
||||
auto buf = tirx::decl_buffer({16}, PrimType::Float(32));
|
||||
auto i = tirx::PrimVar("i", PrimType::Int(32));
|
||||
TVM_FFI_ICHECK(tirx::SideEffect(tirx::BufferLoad(buf, {i})) == tirx::CallEffectKind::kReadState);
|
||||
TVM_FFI_ICHECK(tirx::SideEffect(exp(tirx::Cast(PrimType::Float(32), i + 1))) ==
|
||||
tirx::CallEffectKind::kPure);
|
||||
TVM_FFI_ICHECK(tirx::SideEffect(tvm::Call(PrimType::Void(), tirx::builtin::tvm_storage_sync(), {})
|
||||
.as_or_throw<PrimExpr>()) ==
|
||||
tirx::CallEffectKind::kUpdateState);
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/ffi/dtype.h>
|
||||
#include <tvm/script/printer/printer.h>
|
||||
#include <tvm/tirx/builtin.h>
|
||||
#include <tvm/tirx/expr.h>
|
||||
|
||||
#ifdef TVM_LLVM_VERSION
|
||||
#include <llvm/IR/Intrinsics.h>
|
||||
#endif
|
||||
|
||||
#include "../../src/script/printer/utils.h"
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
// ---------
|
||||
// Prim Type
|
||||
// ---------
|
||||
TEST(ScalablePrimType, TestCreateScalableType) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
|
||||
ASSERT_EQ(scalable_type.code(), kDLInt);
|
||||
ASSERT_EQ(scalable_type.bits(), 32);
|
||||
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector());
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalableWithBits) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 1, 8);
|
||||
scalable_type = scalable_type.WithBits(32);
|
||||
ASSERT_EQ(scalable_type.bits(), 32);
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector());
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalableWithVscaleFactor) {
|
||||
tvm::PrimType type = tvm::PrimType::Int(32);
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(type.code(), type.bits(), 4);
|
||||
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector());
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector() || scalable_type.IsFixedLengthVector());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestAssignScalablePrimType) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 2);
|
||||
tvm::PrimType scalable_type_copy = scalable_type;
|
||||
ASSERT_TRUE(scalable_type_copy.IsScalableVector());
|
||||
ASSERT_TRUE(scalable_type_copy.IsScalableVector() || scalable_type_copy.IsFixedLengthVector());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeEquality) {
|
||||
ASSERT_TRUE(tvm::PrimType::ScalableVector(kDLInt, 32, 4) ==
|
||||
tvm::PrimType::ScalableVector(kDLInt, 32, 4));
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeAndNonScalablePrimTypeInequality) {
|
||||
ASSERT_FALSE(tvm::PrimType::ScalableVector(kDLInt, 32, 4) == tvm::PrimType::Int(32, 4));
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestIsScalar) {
|
||||
ASSERT_FALSE(tvm::PrimType::ScalableVector(kDLInt, 32, 4).IsScalar());
|
||||
ASSERT_TRUE(tvm::PrimType::Int(32).IsScalar());
|
||||
ASSERT_FALSE(tvm::PrimType::Int(32, 4).IsScalar());
|
||||
ASSERT_FALSE(tvm::PrimType::Void().IsScalar());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeToString) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
|
||||
EXPECT_EQ(tvm::ffi::DLDataTypeToString(scalable_type->dtype), "int32xvscalex4");
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestStringToScalablePrimType) {
|
||||
std::string scalable_type_str = "int32xvscalex4";
|
||||
EXPECT_EQ(tvm::PrimType(tvm::ffi::StringToDLDataType(scalable_type_str)),
|
||||
tvm::PrimType::ScalableVector(kDLInt, 32, 4));
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestInvalidStringToScalablePrimType) {
|
||||
std::string scalable_type_str = "int32x4xvscale";
|
||||
EXPECT_THROW(
|
||||
{
|
||||
try {
|
||||
tvm::ffi::StringToDLDataType(scalable_type_str);
|
||||
} catch (const tvm::ffi::Error& e) {
|
||||
EXPECT_THAT(e.what(), HasSubstr("unknown dtype `int32x4xvscale`"));
|
||||
throw;
|
||||
}
|
||||
},
|
||||
tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestGetScalableVectorBytes) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
|
||||
EXPECT_THROW(
|
||||
{
|
||||
try {
|
||||
int bytes = (scalable_type.bits() * scalable_type.lanes() + 7) / 8;
|
||||
static_cast<void>(bytes);
|
||||
} catch (const tvm::ffi::Error& e) {
|
||||
EXPECT_THAT(e.what(),
|
||||
HasSubstr("Can't fetch the lanes of a scalable vector at a compile time"));
|
||||
throw;
|
||||
}
|
||||
},
|
||||
tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeInvalidLanesError) {
|
||||
EXPECT_THROW(
|
||||
{
|
||||
try {
|
||||
tvm::PrimType::ScalableVector(kDLFloat, 62, 1);
|
||||
} catch (const tvm::ffi::Error& e) {
|
||||
EXPECT_THAT(e.what(), HasSubstr("Invalid value for vscale factor"));
|
||||
throw;
|
||||
}
|
||||
},
|
||||
tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeInvalidVscaleFactorAccess) {
|
||||
tvm::PrimType fixed_length_type = tvm::PrimType::Float(32, 4);
|
||||
ASSERT_TRUE(fixed_length_type.IsFixedLengthVector());
|
||||
ASSERT_TRUE(fixed_length_type.IsScalableVector() || fixed_length_type.IsFixedLengthVector());
|
||||
EXPECT_THROW(
|
||||
{
|
||||
try {
|
||||
fixed_length_type.VScaleFactor();
|
||||
} catch (const tvm::ffi::Error& e) {
|
||||
EXPECT_THAT(e.what(), HasSubstr("A fixed length vector doesn't have a vscale factor"));
|
||||
throw;
|
||||
}
|
||||
},
|
||||
tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalablePrimTypeInvalidLanesAccess) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLFloat, 32, 4);
|
||||
EXPECT_THROW(
|
||||
{
|
||||
try {
|
||||
scalable_type.lanes();
|
||||
} catch (const tvm::ffi::Error& e) {
|
||||
EXPECT_THAT(e.what(),
|
||||
HasSubstr("Can't fetch the lanes of a scalable vector at a compile time"));
|
||||
throw;
|
||||
}
|
||||
},
|
||||
tvm::ffi::Error);
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalableBool) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLBool, 8, 4);
|
||||
ASSERT_EQ(scalable_type.code(), kDLBool);
|
||||
ASSERT_EQ(scalable_type.bits(), 8);
|
||||
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector());
|
||||
}
|
||||
|
||||
TEST(ScalablePrimType, TestScalableUInt) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLUInt, 1, 4);
|
||||
ASSERT_EQ(scalable_type.code(), kDLUInt);
|
||||
ASSERT_EQ(scalable_type.bits(), 1);
|
||||
ASSERT_EQ(scalable_type.VScaleFactor(), 4);
|
||||
ASSERT_TRUE(scalable_type.IsScalableVector());
|
||||
}
|
||||
|
||||
// -----------
|
||||
// Integration
|
||||
// -----------
|
||||
#ifdef TVM_LLVM_VERSION
|
||||
TEST(ScalablePrimType, TestScalableIntrinCall) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
|
||||
tvm::Call call = tvm::Call(scalable_type, tvm::tirx::builtin::call_llvm_intrin(),
|
||||
#if TVM_LLVM_VERSION >= 200
|
||||
{tvm::IntImm::Int32(::llvm::Intrinsic::stepvector)});
|
||||
#else
|
||||
{tvm::IntImm::Int32(::llvm::Intrinsic::experimental_stepvector)});
|
||||
#endif
|
||||
ASSERT_EQ(call->ty.as_or_throw<tvm::PrimType>(), scalable_type);
|
||||
ASSERT_EQ(tvm::Script(call),
|
||||
#if TVM_LLVM_VERSION >= 200
|
||||
"T.call_llvm_intrin(\"int32xvscalex4\", \"llvm.stepvector\")");
|
||||
#else
|
||||
"T.call_llvm_intrin(\"int32xvscalex4\", \"llvm.experimental.stepvector\")");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(ScalablePrimType, TestTIRScriptScalableDtype2Str) {
|
||||
tvm::PrimType scalable_type = tvm::PrimType::ScalableVector(kDLInt, 32, 4);
|
||||
ASSERT_EQ(tvm::script::printer::DType2Str(scalable_type->dtype), "int32xvscalex4");
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <tvm/te/operation.h>
|
||||
#include <tvm/topi/elemwise.h>
|
||||
|
||||
namespace tvm {
|
||||
namespace topi {
|
||||
TEST(Tensor, Basic) {
|
||||
using namespace tvm;
|
||||
PrimVar m("m"), l("l");
|
||||
Tensor A = placeholder({m, l}, PrimType::Float(32), "A");
|
||||
auto C = topi::exp(A);
|
||||
}
|
||||
} // namespace topi
|
||||
} // namespace tvm
|
||||
Reference in New Issue
Block a user