diff --git a/include/tvm/node/reflection.h b/include/tvm/node/reflection.h index f547b5a70739..2cfb50e1a283 100644 --- a/include/tvm/node/reflection.h +++ b/include/tvm/node/reflection.h @@ -244,7 +244,7 @@ class ReflectionVTable::Registry { * static constexpr const std::nullptr_t VisitAttrs = nullptr; * * static void SHashReduce(const runtime::StringObj* key, SHashReducer hash_reduce) { - * hash_reduce->SHashReduceHashedValue(runtime::String::HashBytes(key->data, key->size)); + * hash_reduce->SHashReduceHashedValue(runtime::String::StableHashBytes(key->data, key->size)); * } * * static bool SEqualReduce(const runtime::StringObj* lhs, diff --git a/include/tvm/node/structural_hash.h b/include/tvm/node/structural_hash.h index 8b8a403326c4..4d822e68d3d0 100644 --- a/include/tvm/node/structural_hash.h +++ b/include/tvm/node/structural_hash.h @@ -36,52 +36,60 @@ namespace tvm { * \brief Hash definition of base value classes. */ class BaseValueHash { - public: - size_t operator()(const double& key) const { return std::hash()(key); } - - size_t operator()(const int64_t& key) const { return std::hash()(key); } - - size_t operator()(const uint64_t& key) const { return std::hash()(key); } - - size_t operator()(const int& key) const { return std::hash()(key); } - - size_t operator()(const bool& key) const { return std::hash()(key); } - - size_t operator()(const std::string& key) const { return std::hash()(key); } - - size_t operator()(const runtime::DataType& key) const { - return std::hash()(static_cast(key.code()) | - (static_cast(key.bits()) << 8) | - (static_cast(key.lanes()) << 16)); + protected: + template + uint64_t Reinterpret(T value) const { + union Union { + T a; + U b; + } u; + static_assert(sizeof(Union) == sizeof(T), "sizeof(Union) != sizeof(T)"); + static_assert(sizeof(Union) == sizeof(U), "sizeof(Union) != sizeof(U)"); + u.b = 0; + u.a = value; + return u.b; } + public: + uint64_t operator()(const float& key) const { return Reinterpret(key); } + uint64_t operator()(const double& key) const { return Reinterpret(key); } + uint64_t operator()(const int64_t& key) const { return Reinterpret(key); } + uint64_t operator()(const uint64_t& key) const { return key; } + uint64_t operator()(const int& key) const { return Reinterpret(key); } + uint64_t operator()(const bool& key) const { return key; } + uint64_t operator()(const runtime::DataType& key) const { + return Reinterpret(key); + } template ::value>::type> - bool operator()(const ENum& key) const { - return std::hash()(static_cast(key)); + uint64_t operator()(const ENum& key) const { + return Reinterpret(static_cast(key)); + } + uint64_t operator()(const std::string& key) const { + return runtime::String::StableHashBytes(key.data(), key.length()); } }; /*! - * \brief Content-aware structural hasing. + * \brief Content-aware structural hashing. * * The structural hash value is recursively defined in the DAG of IRNodes. * There are two kinds of nodes: * * - Normal node: the hash value is defined by its content and type only. * - Graph node: each graph node will be assigned a unique index ordered by the - * first occurence during the visit. The hash value of a graph node is + * first occurrence during the visit. The hash value of a graph node is * combined from the hash values of its contents and the index. */ class StructuralHash : public BaseValueHash { public: - // inheritate operator() + // inherit operator() using BaseValueHash::operator(); /*! * \brief Compute structural hashing value for an object. * \param key The left operand. * \return The hash value. */ - TVM_DLL size_t operator()(const ObjectRef& key) const; + TVM_DLL uint64_t operator()(const ObjectRef& key) const; }; /*! @@ -109,23 +117,23 @@ class SHashReducer { * * \param hashed_value The hashed value */ - virtual void SHashReduceHashedValue(size_t hashed_value) = 0; + virtual void SHashReduceHashedValue(uint64_t hashed_value) = 0; /*! * \brief Append hash value of key to the current sequence of hashes. * * \param key The object to compute hash from. - * \param map_free_vars Whether to map free variables by their occurence number. + * \param map_free_vars Whether to map free variables by their occurrence number. */ virtual void SHashReduce(const ObjectRef& key, bool map_free_vars) = 0; /*! - * \brief Apppend a hash value of free variable to the current sequence of hashes. + * \brief Append a hash value of free variable to the current sequence of hashes. * * \param var The var of interest. - * \param map_free_vars Whether to map free variables by their occurence number. + * \param map_free_vars Whether to map free variables by their occurrence number. * * \note If map_free_vars is set to be true, * internally the handler can maintain a counter to encode free variables - * by their order of occurence. This helps to resolve variable + * by their order of occurrence. This helps to resolve variable * mapping of function parameters and let binding variables. * * If map_free_vars is set to be false, the address of the variable will be used. @@ -139,7 +147,7 @@ class SHashReducer { * * \return Whether there is already a pre-computed hash value. */ - virtual bool LookupHashedValue(const ObjectRef& key, size_t* hashed_value) = 0; + virtual bool LookupHashedValue(const ObjectRef& key, uint64_t* hashed_value) = 0; /*! * \brief Mark current comparison as graph node in hashing. * Graph node hash will depends on the graph structure. @@ -193,7 +201,7 @@ class SHashReducer { /*! \brief Internal class pointer. */ Handler* handler_; /*! - * \brief Whether or not to map free variables by their occurence + * \brief Whether or not to map free variables by their occurrence * If the flag is false, then free variables will be mapped * by their in-memory address. */ @@ -210,10 +218,10 @@ class SHashHandlerDefault : public SHashReducer::Handler { SHashHandlerDefault(); virtual ~SHashHandlerDefault(); - void SHashReduceHashedValue(size_t hashed_value) override; + void SHashReduceHashedValue(uint64_t hashed_value) override; void SHashReduce(const ObjectRef& key, bool map_free_vars) override; void SHashReduceFreeVar(const runtime::Object* var, bool map_free_vars) override; - bool LookupHashedValue(const ObjectRef& key, size_t* hashed_value) override; + bool LookupHashedValue(const ObjectRef& key, uint64_t* hashed_value) override; void MarkGraphNode() override; /*! @@ -222,7 +230,7 @@ class SHashHandlerDefault : public SHashReducer::Handler { * \param map_free_vars Whether or not to remap variables if possible. * \return The hash result. */ - virtual size_t Hash(const ObjectRef& object, bool map_free_vars); + virtual uint64_t Hash(const ObjectRef& object, bool map_free_vars); protected: /*! diff --git a/include/tvm/runtime/container/string.h b/include/tvm/runtime/container/string.h index 5ecd89e9f56d..c6382506b355 100644 --- a/include/tvm/runtime/container/string.h +++ b/include/tvm/runtime/container/string.h @@ -24,6 +24,7 @@ #ifndef TVM_RUNTIME_CONTAINER_STRING_H_ #define TVM_RUNTIME_CONTAINER_STRING_H_ +#include #include #include #include @@ -247,10 +248,70 @@ class String : public ObjectRef { * \param size The size of the bytes. * \return the hash value. */ - static size_t HashBytes(const char* data, size_t size) { - // This function falls back to string copy with c++11 compiler and is - // recommended to be compiled with c++14 - return std::hash()(std::string_view(data, size)); + static uint64_t StableHashBytes(const char* data, size_t size) { + const constexpr uint64_t kMultiplier = 1099511628211ULL; + const constexpr uint64_t kMod = 2147483647ULL; + union Union { + uint8_t a[8]; + uint64_t b; + } u; + static_assert(sizeof(Union) == sizeof(uint64_t), "sizeof(Union) != sizeof(uint64_t)"); + const char* it = data; + const char* end = it + size; + uint64_t result = 0; + for (; it + 8 <= end; it += 8) { + if (DMLC_IO_NO_ENDIAN_SWAP) { + u.a[0] = it[0]; + u.a[1] = it[1]; + u.a[2] = it[2]; + u.a[3] = it[3]; + u.a[4] = it[4]; + u.a[5] = it[5]; + u.a[6] = it[6]; + u.a[7] = it[7]; + } else { + u.a[0] = it[7]; + u.a[1] = it[6]; + u.a[2] = it[5]; + u.a[3] = it[4]; + u.a[4] = it[3]; + u.a[5] = it[2]; + u.a[6] = it[1]; + u.a[7] = it[0]; + } + result = (result * kMultiplier + u.b) % kMod; + } + if (it < end) { + u.b = 0; + uint8_t* a = u.a; + if (it + 4 <= end) { + a[0] = it[0]; + a[1] = it[1]; + a[2] = it[2]; + a[3] = it[3]; + it += 4; + a += 4; + } + if (it + 2 <= end) { + a[0] = it[0]; + a[1] = it[1]; + it += 2; + a += 2; + } + if (it + 1 <= end) { + a[0] = it[0]; + it += 1; + a += 1; + } + if (!DMLC_IO_NO_ENDIAN_SWAP) { + std::swap(u.a[0], u.a[7]); + std::swap(u.a[1], u.a[6]); + std::swap(u.a[2], u.a[5]); + std::swap(u.a[3], u.a[4]); + } + result = (result * kMultiplier + u.b) % kMod; + } + return result; } TVM_DEFINE_NOTNULLABLE_OBJECT_REF_METHODS(String, ObjectRef, StringObj); @@ -448,7 +509,7 @@ inline int String::memncmp(const char* lhs, const char* rhs, size_t lhs_count, s inline size_t ObjectHash::operator()(const ObjectRef& a) const { if (const auto* str = a.as()) { - return String::HashBytes(str->data, str->size); + return String::StableHashBytes(str->data, str->size); } return ObjectPtrHash()(a); } @@ -476,7 +537,7 @@ namespace std { template <> struct hash<::tvm::runtime::String> { std::size_t operator()(const ::tvm::runtime::String& str) const { - return ::tvm::runtime::String::HashBytes(str.data(), str.size()); + return ::tvm::runtime::String::StableHashBytes(str.data(), str.size()); } }; } // namespace std diff --git a/src/node/structural_hash.cc b/src/node/structural_hash.cc index fa77b47bd284..6cf796d34447 100644 --- a/src/node/structural_hash.cc +++ b/src/node/structural_hash.cc @@ -39,7 +39,7 @@ namespace tvm { -// Define the dispatch functio here since primary user is in this file. +// Define the dispatch function here since primary user is in this file. void ReflectionVTable::SHashReduce(const Object* self, SHashReducer reducer) const { uint32_t tindex = self->type_index(); if (tindex >= fshash_reduce_.size() || fshash_reduce_[tindex] == nullptr) { @@ -50,7 +50,7 @@ void ReflectionVTable::SHashReduce(const Object* self, SHashReducer reducer) con } // Hash handler that handles free vars -// by assigning an unique counter in the order of their ocurrence. +// by assigning an unique counter in the order of their occurrence. // // This algorithm depends on the determinism of the traversal of SHash function. // In particular, when we traverse unordered_map, we should first sort @@ -69,9 +69,9 @@ class SHashHandlerDefault::Impl { */ ObjectRef object; /*! \brief The partially reduce hash value.*/ - size_t reduced_hash; + uint64_t reduced_hash; /*! \brief The expected location in the result stack. */ - size_t result_stack_index = std::numeric_limits::max(); + uint64_t result_stack_index = std::numeric_limits::max(); /*! \brief Whether the children has been expanded via SEqualReduce */ bool children_expanded{false}; /*! \brief Whether the node is graph node. */ @@ -80,7 +80,7 @@ class SHashHandlerDefault::Impl { bool map_free_vars; Task() = default; - explicit Task(ObjectRef object, size_t reduced_hash, bool map_free_vars) + explicit Task(ObjectRef object, uint64_t reduced_hash, bool map_free_vars) : object(object), reduced_hash(reduced_hash), map_free_vars(map_free_vars) {} }; @@ -90,7 +90,7 @@ class SHashHandlerDefault::Impl { task_stack_.back().graph_node_hash = true; } - bool LookupHashedValue(const ObjectRef& key, size_t* hash_value) { + bool LookupHashedValue(const ObjectRef& key, uint64_t* hash_value) { auto it = hash_memo_.find(key); if (it != hash_memo_.end()) { hash_value[0] = it->second; @@ -99,7 +99,7 @@ class SHashHandlerDefault::Impl { return false; } - void SHashReduceHashedValue(size_t hashed_value) { + void SHashReduceHashedValue(uint64_t hashed_value) { pending_tasks_.emplace_back(Task(ObjectRef(nullptr), hashed_value, false)); } @@ -107,18 +107,18 @@ class SHashHandlerDefault::Impl { ICHECK(!hash_memo_.count(GetRef(var))); if (map_free_vars) { // use counter value. - size_t value = std::hash()(free_var_counter_++); + uint64_t value = std::hash()(free_var_counter_++); pending_tasks_.emplace_back(Task(ObjectRef(nullptr), value, false)); } else { // use pointer hash - size_t value = std::hash()(var); + uint64_t value = std::hash()(var); pending_tasks_.emplace_back(Task(ObjectRef(nullptr), value, false)); } } void SHashReduce(const ObjectRef& object, bool map_free_vars) { // Directly push the result - // Note: it is still important to push the result to pendng tasks + // Note: it is still important to push the result to pending tasks // so that the reduction order of hash values stays the same. if (!object.defined()) { pending_tasks_.emplace_back(Task(ObjectRef(nullptr), 0, false)); @@ -133,7 +133,7 @@ class SHashHandlerDefault::Impl { } } - size_t Hash(const ObjectRef& object, bool map_free_vars) { + uint64_t Hash(const ObjectRef& object, bool map_free_vars) { ICHECK_EQ(task_stack_.size(), 0U); ICHECK_EQ(pending_tasks_.size(), 0U); ICHECK_EQ(result_stack_.size(), 0U); @@ -147,7 +147,7 @@ class SHashHandlerDefault::Impl { this->RunTasks(); ICHECK_EQ(result_stack_.size(), 1U); - size_t ret = result_stack_.back(); + uint64_t ret = result_stack_.back(); result_stack_.pop_back(); return ret; } @@ -170,13 +170,13 @@ class SHashHandlerDefault::Impl { * \brief Compute the reduced hash value for the task. * \param task The indicated task. */ - size_t ReduceHash(const Task& task) { - size_t stack_begin = task.result_stack_index; + uint64_t ReduceHash(const Task& task) { + uint64_t stack_begin = task.result_stack_index; ICHECK_LE(stack_begin, result_stack_.size()); // combine in the reverse order of the stack. - size_t reduced_hash = task.reduced_hash; - for (size_t i = result_stack_.size(); i != stack_begin; --i) { + uint64_t reduced_hash = task.reduced_hash; + for (uint32_t i = result_stack_.size(); i != stack_begin; --i) { reduced_hash = support::HashCombine(reduced_hash, result_stack_[i - 1]); } result_stack_.resize(stack_begin); @@ -201,7 +201,7 @@ class SHashHandlerDefault::Impl { // so that we can distinguish DAG from trees. if (entry.graph_node_hash) { entry.reduced_hash = support::HashCombine(entry.reduced_hash, - std::hash()(graph_node_counter_++)); + std::hash()(graph_node_counter_++)); } hash_memo_[entry.object] = entry.reduced_hash; } @@ -241,27 +241,27 @@ class SHashHandlerDefault::Impl { // The owner of this impl SHashHandlerDefault* parent_; // free var counter. - size_t free_var_counter_{0}; + uint32_t free_var_counter_{0}; // graph node counter. - size_t graph_node_counter_{0}; + uint32_t graph_node_counter_{0}; // record current stack top bool allow_push_to_stack_{true}; // list of pending tasks to be pushed to the stack. std::vector pending_tasks_; // Internal task stack to executed the task std::vector task_stack_; - // Internal stack to store the result poped from the task stack. - std::vector result_stack_; + // Internal stack to store the result popped from the task stack. + std::vector result_stack_; // reflection vtable ReflectionVTable* vtable_ = ReflectionVTable::Global(); // map from lhs to rhs - std::unordered_map hash_memo_; + std::unordered_map hash_memo_; }; SHashHandlerDefault::SHashHandlerDefault() { impl = new Impl(this); } SHashHandlerDefault::~SHashHandlerDefault() { delete impl; } -void SHashHandlerDefault::SHashReduceHashedValue(size_t hashed_value) { +void SHashHandlerDefault::SHashReduceHashedValue(uint64_t hashed_value) { return impl->SHashReduceHashedValue(hashed_value); } @@ -273,13 +273,13 @@ void SHashHandlerDefault::SHashReduceFreeVar(const runtime::Object* var, bool ma impl->SHashReduceFreeVar(var, map_free_vars); } -bool SHashHandlerDefault::LookupHashedValue(const ObjectRef& key, size_t* hashed_value) { +bool SHashHandlerDefault::LookupHashedValue(const ObjectRef& key, uint64_t* hashed_value) { return impl->LookupHashedValue(key, hashed_value); } void SHashHandlerDefault::MarkGraphNode() { impl->MarkGraphNode(); } -size_t SHashHandlerDefault::Hash(const ObjectRef& object, bool map_free_vars) { +uint64_t SHashHandlerDefault::Hash(const ObjectRef& object, bool map_free_vars) { return impl->Hash(object, map_free_vars); } @@ -289,11 +289,11 @@ void SHashHandlerDefault::DispatchSHash(const ObjectRef& key, bool map_free_vars TVM_REGISTER_GLOBAL("node.StructuralHash") .set_body_typed([](const ObjectRef& object, bool map_free_vars) -> int64_t { - size_t hashed_value = SHashHandlerDefault().Hash(object, map_free_vars); + uint64_t hashed_value = SHashHandlerDefault().Hash(object, map_free_vars); return static_cast(hashed_value); }); -size_t StructuralHash::operator()(const ObjectRef& object) const { +uint64_t StructuralHash::operator()(const ObjectRef& object) const { return SHashHandlerDefault().Hash(object, false); } @@ -302,7 +302,7 @@ struct StringObjTrait { static constexpr const std::nullptr_t VisitAttrs = nullptr; static void SHashReduce(const runtime::StringObj* key, SHashReducer hash_reduce) { - hash_reduce->SHashReduceHashedValue(runtime::String::HashBytes(key->data, key->size)); + hash_reduce->SHashReduceHashedValue(runtime::String::StableHashBytes(key->data, key->size)); } static bool SEqualReduce(const runtime::StringObj* lhs, const runtime::StringObj* rhs, @@ -371,7 +371,7 @@ void NDArrayHash(const runtime::NDArray::Container* arr, SHashReducer* hash_redu } if (hash_data) { (*hash_reduce) - ->SHashReduceHashedValue(runtime::String::HashBytes( + ->SHashReduceHashedValue(runtime::String::StableHashBytes( static_cast(arr->dl_tensor.data), runtime::GetDataSize(arr->dl_tensor))); } } @@ -405,7 +405,7 @@ struct ArrayNodeTrait { static void SHashReduce(const ArrayNode* key, SHashReducer hash_reduce) { hash_reduce(static_cast(key->size())); - for (size_t i = 0; i < key->size(); ++i) { + for (uint32_t i = 0; i < key->size(); ++i) { hash_reduce(key->at(i)); } } @@ -416,7 +416,7 @@ struct ArrayNodeTrait { } if (lhs->size() != rhs->size()) return false; - for (size_t i = 0; i < lhs->size(); ++i) { + for (uint32_t i = 0; i < lhs->size(); ++i) { if (!equal(lhs->at(i), rhs->at(i))) return false; } return true; @@ -425,10 +425,10 @@ struct ArrayNodeTrait { private: static bool SEqualReduceTraced(const ArrayNode* lhs, const ArrayNode* rhs, const SEqualReducer& equal) { - size_t min_size = std::min(lhs->size(), rhs->size()); + uint32_t min_size = std::min(lhs->size(), rhs->size()); const ObjectPathPair& array_paths = equal.GetCurrentObjectPaths(); - for (size_t index = 0; index < min_size; ++index) { + for (uint32_t index = 0; index < min_size; ++index) { ObjectPathPair element_paths = {array_paths->lhs_path->ArrayIndex(index), array_paths->rhs_path->ArrayIndex(index)}; if (!equal(lhs->at(index), rhs->at(index), element_paths)) { @@ -491,7 +491,7 @@ struct ShapeTupleObjTrait { static void SHashReduce(const ShapeTupleObj* self, SHashReducer hash_reduce) { hash_reduce(self->size); - for (size_t i = 0; i < self->size; ++i) { + for (uint32_t i = 0; i < self->size; ++i) { hash_reduce(self->data[i]); } } @@ -499,7 +499,7 @@ struct ShapeTupleObjTrait { static bool SEqualReduce(const ShapeTupleObj* lhs, const ShapeTupleObj* rhs, SEqualReducer equal) { if (lhs->size != rhs->size) return false; - for (size_t i = 0; i < lhs->size; ++i) { + for (uint32_t i = 0; i < lhs->size; ++i) { if (!equal(lhs->data[i], rhs->data[i])) return false; } return true; @@ -539,10 +539,10 @@ struct MapNodeTrait { // This resolves common use cases where we want to store // Map where Var is defined in the function // parameters. - using KV = std::pair; + using KV = std::pair; std::vector temp; for (const auto& kv : *key) { - size_t hashed_value; + uint64_t hashed_value; if (hash_reduce->LookupHashedValue(kv.first, &hashed_value)) { temp.emplace_back(hashed_value, kv.second); } @@ -553,11 +553,11 @@ struct MapNodeTrait { // add size to the hash hash_reduce(static_cast(key->size())); // hash the content - for (size_t i = 0; i < temp.size();) { - size_t k = i + 1; + for (uint32_t i = 0; i < temp.size();) { + uint32_t k = i + 1; for (; k < temp.size() && temp[k].first == temp[i].first; ++k) { } - // ties are rare, but we need to skip them to make the hash determinsitic + // ties are rare, but we need to skip them to make the hash deterministic if (k == i + 1) { hash_reduce->SHashReduceHashedValue(temp[i].first); hash_reduce(temp[i].second); @@ -583,7 +583,7 @@ struct MapNodeTrait { // add size to the hash after sorting. hash_reduce(static_cast(key->size())); // hash the content - for (size_t i = 0; i < temp.size(); ++i) { + for (uint32_t i = 0; i < temp.size(); ++i) { hash_reduce(temp[i].first); hash_reduce(temp[i].second); } diff --git a/src/relay/backend/utils.h b/src/relay/backend/utils.h index d5cf4baf7243..acaea425d178 100644 --- a/src/relay/backend/utils.h +++ b/src/relay/backend/utils.h @@ -696,7 +696,7 @@ struct TargetStrHash { */ size_t operator()(const Target& target) const { std::string s(target->kind->name); - return String::HashBytes(s.c_str(), s.size()); + return String::StableHashBytes(s.c_str(), s.size()); } };