From cca717680dd4b06c8f935699d65b5da4a2af8a86 Mon Sep 17 00:00:00 2001 From: Chris Sidebottom Date: Tue, 7 Sep 2021 15:14:39 +0000 Subject: [PATCH] Introduce centralised name transformation functions To address some of the concerns raised in https://github.com/apache/tvm/pull/8280 and https://github.com/apache/tvm/pull/8720 I've put together a series of functions to combine together names to re-use between these areas. These are meant to be a starting point to fix up the name generation to use the TVM C conventions and port the interface API header to C++. These functions will also be used for constructing the names in the C Device API (https://github.com/apache/tvm-rfcs/pull/31). --- python/tvm/relay/backend/name_transforms.py | 86 ++++++++++++++++ src/relay/backend/name_transforms.cc | 102 +++++++++++++++++++ src/relay/backend/name_transforms.h | 105 ++++++++++++++++++++ tests/cpp/name_transforms_test.cc | 85 ++++++++++++++++ tests/python/relay/test_name_transforms.py | 96 ++++++++++++++++++ 5 files changed, 474 insertions(+) create mode 100644 python/tvm/relay/backend/name_transforms.py create mode 100644 src/relay/backend/name_transforms.cc create mode 100644 src/relay/backend/name_transforms.h create mode 100644 tests/cpp/name_transforms_test.cc create mode 100644 tests/python/relay/test_name_transforms.py diff --git a/python/tvm/relay/backend/name_transforms.py b/python/tvm/relay/backend/name_transforms.py new file mode 100644 index 0000000000000..a7bfa9847edd3 --- /dev/null +++ b/python/tvm/relay/backend/name_transforms.py @@ -0,0 +1,86 @@ +# 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. +""" +Name transformation functions for use in code generation +""" + +from typing import List, Union + +from . import _backend + + +def to_c_function_style(original_name: str): + """Transform a name to the C function style assuming it is + appropriately constructed using the prefixing functions + + Parameters + ---------- + original_name : str + Original name to transform + """ + return _backend.ToCFunctionStyle(original_name) + + +def to_c_variable_style(original_name: str): + """Transform a name to the C variable style assuming it is + appropriately constructed using the prefixing functions + + Parameters + ---------- + original_name : str + Original name to transform + """ + return _backend.ToCVariableStyle(original_name) + + +def prefix_name(names: Union[List[str], str]): + """Apply TVM-specific prefix to a function name + + Parameters + ---------- + names : Union[List[str], str] + List of names to combine to form a combined name or the name itself + """ + if isinstance(names, str): + names = [names] + + return _backend.PrefixName(names) + + +def prefix_generated_name(names: Union[List[str], str]): + """Apply generated TVM-specific prefix to a function name + + Parameters + ---------- + names : Union[List[str], str] + List of names to combine to form a combined name or the name itself + """ + if isinstance(names, str): + names = [names] + + return _backend.PrefixGeneratedName(names) + + +def sanitise_name(original_name: str): + """Sanitise name for output into compiler artifacts + + Parameters + ---------- + original_name : str + Original name to sanitise + """ + return _backend.SanitiseName(original_name) diff --git a/src/relay/backend/name_transforms.cc b/src/relay/backend/name_transforms.cc new file mode 100644 index 0000000000000..f09e6a6346a58 --- /dev/null +++ b/src/relay/backend/name_transforms.cc @@ -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 "name_transforms.h" + +#include + +#include +#include + +namespace tvm { +namespace relay { +namespace backend { + +std::string ToCFunctionStyle(const std::string& original_name) { + ICHECK(!original_name.empty()); + + int tvm_prefix_length = 3; + std::string function_name("TVM"); + + bool new_block = true; + for (const char& symbol : original_name.substr(tvm_prefix_length)) { + if (std::isalpha(symbol)) { + if (new_block) { + function_name.push_back(std::toupper(symbol)); + new_block = false; + } else { + function_name.push_back(std::tolower(symbol)); + } + } else if (symbol == '_') { + new_block = true; + } + } + return function_name; +} + +std::string ToCVariableStyle(const std::string& original_name) { + ICHECK(!original_name.empty()); + + std::string variable_name; + variable_name.resize(original_name.size()); + + std::transform(original_name.begin(), original_name.end(), variable_name.begin(), ::tolower); + return variable_name; +} + +std::string CombineNames(const Array& names) { + std::stringstream combine_stream; + ICHECK(!names.empty()); + + for (const String& name : names) { + ICHECK(!name.empty()); + combine_stream << name << "_"; + } + + std::string combined_name = combine_stream.str(); + combined_name.pop_back(); + return combined_name; +} + +std::string SanitiseName(const std::string& name) { + ICHECK(!name.empty()); + + auto multipleSeparators = [](char before, char after) { + return before == '_' && before == after; + }; + auto isNotAlnum = [](char c) { return !std::isalnum(c); }; + std::string sanitised_input = name; + std::replace_if(sanitised_input.begin(), sanitised_input.end(), isNotAlnum, '_'); + + sanitised_input.erase( + std::unique(sanitised_input.begin(), sanitised_input.end(), multipleSeparators), + sanitised_input.end()); + + return sanitised_input; +} + +TVM_REGISTER_GLOBAL("relay.backend.ToCFunctionStyle").set_body_typed(ToCFunctionStyle); +TVM_REGISTER_GLOBAL("relay.backend.ToCVariableStyle").set_body_typed(ToCVariableStyle); +TVM_REGISTER_GLOBAL("relay.backend.PrefixName").set_body_typed(PrefixName); +TVM_REGISTER_GLOBAL("relay.backend.PrefixGeneratedName").set_body_typed(PrefixGeneratedName); +TVM_REGISTER_GLOBAL("relay.backend.SanitiseName").set_body_typed(SanitiseName); + +} // namespace backend +} // namespace relay +} // namespace tvm diff --git a/src/relay/backend/name_transforms.h b/src/relay/backend/name_transforms.h new file mode 100644 index 0000000000000..f94b472f88c88 --- /dev/null +++ b/src/relay/backend/name_transforms.h @@ -0,0 +1,105 @@ +/* + * 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. + */ + +/*! + * \file relay/backend/name_transforms.h + * \brief Transformations which are applied on names to generate appropriately named compiler + * artifacts + * + * Example: + * ToCFunctionStyle(PrefixName(CombineNames({"Device", "target", "Invoke"}))) + * // TVMDeviceTargetInvoke + * + * ToCFunctionStyle(PrefixGeneratedName(CombineNames({"model", "Run"}))) + * // TVMGenModelRun + * + * ToCVariableStyle(PrefixName(CombineNames({"Device", "target", "t"}))) + * // tvm_device_target_t + * + * ToCVariableStyle(PrefixGeneratedName(CombineNames({"model", "Devices"}))) + * // tvmgen_model_devices + * + */ + +#include +#include +#include + +#include +#include +#include + +#ifndef TVM_RELAY_BACKEND_NAME_TRANSFORMS_H_ +#define TVM_RELAY_BACKEND_NAME_TRANSFORMS_H_ + +namespace tvm { +namespace relay { +namespace backend { + +/*! + * \brief Transform a name to the C variable style assuming it is + * appropriately constructed using the prefixing functions + * \param original_name Original name + * \return Transformed function in the C function style + */ +std::string ToCFunctionStyle(const std::string& original_name); + +/*! + * \brief Transform a name to the C variable style assuming it is + * appropriately constructed using the prefixing functions + * \param name Original name + * \return Transformed function in the C variable style + */ +std::string ToCVariableStyle(const std::string& original_name); + +/*! + * \brief Combine names together for use as a generated name + * \param names Vector of strings to combine + * \return Combined together names + */ +std::string CombineNames(const Array& names); + +/*! + * \brief Apply TVM-specific prefix to a name + * \param names Vector of names to combine to form a combined name + * \return Name with prefix applied or prefix-only if no name passed + */ +inline std::string PrefixName(const Array& names) { return "TVM_" + CombineNames(names); } + +/*! + * \brief Apply generated TVM-specific prefix to a name + * \param names Vector of names to combine to form a combined name + * \return Name with prefix applied or prefix-only if no name passed + */ +inline std::string PrefixGeneratedName(const Array& names) { + return "TVMGen_" + CombineNames(names); +} + +/*! + * \brief Sanitise name for output into compiler artifacts + * \param name Original name + * \return Sanitised name + */ +std::string SanitiseName(const std::string& name); + +} // namespace backend +} // namespace relay +} // namespace tvm + +#endif // TVM_RELAY_BACKEND_NAME_TRANSFORMS_H_ diff --git a/tests/cpp/name_transforms_test.cc b/tests/cpp/name_transforms_test.cc new file mode 100644 index 0000000000000..bcad3f9db0916 --- /dev/null +++ b/tests/cpp/name_transforms_test.cc @@ -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 "../src/relay/backend/name_transforms.h" + +#include +#include + +using namespace tvm::relay::backend; +using namespace tvm::runtime; + +TEST(NameTransforms, ToCFunctionStyle) { + ASSERT_EQ(ToCFunctionStyle("TVM_Woof"), "TVMWoof"); + ASSERT_EQ(ToCFunctionStyle("TVM_woof"), "TVMWoof"); + ASSERT_EQ(ToCFunctionStyle("TVM_woof_woof"), "TVMWoofWoof"); + ASSERT_EQ(ToCFunctionStyle("TVMGen_woof_woof"), "TVMGenWoofWoof"); + EXPECT_THROW(ToCFunctionStyle(""), InternalError); +} + +TEST(NameTransforms, ToCVariableStyle) { + ASSERT_EQ(ToCVariableStyle("TVM_Woof"), "tvm_woof"); + ASSERT_EQ(ToCVariableStyle("TVM_woof"), "tvm_woof"); + ASSERT_EQ(ToCVariableStyle("TVM_woof_Woof"), "tvm_woof_woof"); + EXPECT_THROW(ToCVariableStyle(""), InternalError); +} + +TEST(NameTransforms, PrefixName) { + ASSERT_EQ(PrefixName({"Woof"}), "TVM_Woof"); + ASSERT_EQ(PrefixName({"woof"}), "TVM_woof"); + ASSERT_EQ(PrefixName({"woof", "moo"}), "TVM_woof_moo"); + EXPECT_THROW(PrefixName({}), InternalError); + EXPECT_THROW(PrefixName({""}), InternalError); +} + +TEST(NameTransforms, PrefixGeneratedName) { + ASSERT_EQ(PrefixGeneratedName({"Woof"}), "TVMGen_Woof"); + ASSERT_EQ(PrefixGeneratedName({"woof"}), "TVMGen_woof"); + ASSERT_EQ(PrefixGeneratedName({"woof", "moo"}), "TVMGen_woof_moo"); + EXPECT_THROW(PrefixGeneratedName({}), InternalError); + EXPECT_THROW(PrefixGeneratedName({""}), InternalError); +} + +TEST(NameTransforms, CombineNames) { + ASSERT_EQ(CombineNames({"woof"}), "woof"); + ASSERT_EQ(CombineNames({"Woof", "woof"}), "Woof_woof"); + ASSERT_EQ(CombineNames({"Woof", "woof", "woof"}), "Woof_woof_woof"); + ASSERT_EQ(CombineNames({"Woof", "moo", "t"}), "Woof_moo_t"); + + EXPECT_THROW(CombineNames({}), InternalError); + EXPECT_THROW(CombineNames({""}), InternalError); + EXPECT_THROW(CombineNames({"Woof", ""}), InternalError); + EXPECT_THROW(CombineNames({"", "Woof"}), InternalError); +} + +TEST(NameTransforms, SanitiseName) { + ASSERT_EQ(SanitiseName("+_+ "), "_"); + ASSERT_EQ(SanitiseName("input+"), "input_"); + ASSERT_EQ(SanitiseName("input-"), "input_"); + ASSERT_EQ(SanitiseName("input++"), "input_"); + ASSERT_EQ(SanitiseName("woof:1"), "woof_1"); + EXPECT_THROW(SanitiseName(""), InternalError); +} + +TEST(NameTransforms, CombinedLogic) { + ASSERT_EQ(ToCFunctionStyle(PrefixName({"Device", "target", "Invoke"})), "TVMDeviceTargetInvoke"); + ASSERT_EQ(ToCFunctionStyle(PrefixGeneratedName({"model", "Run"})), "TVMGenModelRun"); + ASSERT_EQ(ToCVariableStyle(PrefixName({"Device", "target", "t"})), "tvm_device_target_t"); + ASSERT_EQ(ToCVariableStyle(PrefixGeneratedName({"model", "Devices"})), "tvmgen_model_devices"); +} diff --git a/tests/python/relay/test_name_transforms.py b/tests/python/relay/test_name_transforms.py new file mode 100644 index 0000000000000..34ea100392f5a --- /dev/null +++ b/tests/python/relay/test_name_transforms.py @@ -0,0 +1,96 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License" you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from tvm import TVMError +from tvm.relay.backend.name_transforms import ( + to_c_function_style, + to_c_variable_style, + prefix_name, + prefix_generated_name, + sanitise_name, +) +import pytest + + +def test_to_c_function_style(): + assert to_c_function_style("TVM_Woof") == "TVMWoof" + assert to_c_function_style("TVM_woof") == "TVMWoof" + assert to_c_function_style("TVM_woof_woof") == "TVMWoofWoof" + assert to_c_function_style("TVMGen_woof_woof") == "TVMGenWoofWoof" + + with pytest.raises(TVMError): + to_c_function_style("") + + +def test_to_c_variable_style(): + assert to_c_variable_style("TVM_Woof") == "tvm_woof" + assert to_c_variable_style("TVM_woof") == "tvm_woof" + assert to_c_variable_style("TVM_woof_Woof") == "tvm_woof_woof" + + with pytest.raises(TVMError): + to_c_variable_style("") + + +def test_prefix_name(): + assert prefix_name("Woof") == "TVM_Woof" + assert prefix_name(["Woof"]) == "TVM_Woof" + assert prefix_name(["woof"]) == "TVM_woof" + assert prefix_name(["woof", "moo"]) == "TVM_woof_moo" + + with pytest.raises(TVMError): + prefix_name("") + with pytest.raises(TVMError): + prefix_name([]) + with pytest.raises(TVMError): + prefix_name([""]) + + +def test_prefix_generated_name(): + assert prefix_generated_name("Woof") == "TVMGen_Woof" + assert prefix_generated_name(["Woof"]) == "TVMGen_Woof" + assert prefix_generated_name(["Woof"]) == "TVMGen_Woof" + assert prefix_generated_name(["woof"]) == "TVMGen_woof" + assert prefix_generated_name(["woof", "moo"]) == "TVMGen_woof_moo" + + with pytest.raises(TVMError): + prefix_generated_name("") + with pytest.raises(TVMError): + prefix_generated_name([]) + with pytest.raises(TVMError): + prefix_generated_name([""]) + + +def test_sanitise_name(): + assert sanitise_name("+_+ ") == "_" + assert sanitise_name("input+") == "input_" + assert sanitise_name("input-") == "input_" + assert sanitise_name("input++") == "input_" + assert sanitise_name("woof:1") == "woof_1" + + with pytest.raises(TVMError): + sanitise_name("") + + +def test_combined_logic(): + assert ( + to_c_function_style(prefix_name(["Device", "target", "Invoke"])) == "TVMDeviceTargetInvoke" + ) + assert to_c_function_style(prefix_generated_name(["model", "Run"])) == "TVMGenModelRun" + assert to_c_variable_style(prefix_name(["Device", "target", "t"])) == "tvm_device_target_t" + assert ( + to_c_variable_style(prefix_generated_name(["model", "Devices"])) == "tvmgen_model_devices" + )