[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r932591801 ## tests/python/unittest/test_container_structural_equal.py: ## @@ -0,0 +1,150 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import pytest + +import tvm +from tvm.ir.base import get_first_structural_mismatch +from tvm.runtime import ObjectPath + + +def get_first_mismatch_ensure_symmetry(a, b): +mismatch = get_first_structural_mismatch(a, b) +mismatch_swapped = get_first_structural_mismatch(b, a) + +if mismatch is None and mismatch_swapped is None: +return None + +if ( +mismatch is None +or mismatch_swapped is None +or mismatch[0] != mismatch_swapped[1] +or mismatch[1] != mismatch_swapped[0] +): +raise AssertionError( +"get_first_structural_mismatch(a, b) and get_first_structural_mismatch(b, a) returned" +" inconsistent results '{}' and '{}' for a='{}', b='{}'".format( +mismatch, mismatch_swapped, a, b +) +) + +a_path, b_path = mismatch +b_path_swapped, a_path_swapped = mismatch_swapped +assert a_path == a_path_swapped +assert b_path == b_path_swapped + +return mismatch + + +@pytest.mark.parametrize( +"a, b, expected_a_path, expected_b_path", +[ +( +[1, 2, 3], +[1, 4, 3], +ObjectPath.root().array_index(1).attr("value"), +ObjectPath.root().array_index(1).attr("value"), +), +( +[1, 2, 3], +[10, 2, 30], +ObjectPath.root().array_index(0).attr("value"), +ObjectPath.root().array_index(0).attr("value"), +), +( +[1, 3, 4], +[1, 2, 3, 4], +ObjectPath.root().array_index(1).attr("value"), +ObjectPath.root().array_index(1).attr("value"), +), +( +[1, 2, 3], +[1, 2, 3, 4], +ObjectPath.root().missing_array_element(3), +ObjectPath.root().array_index(3), +), +( +[], +[1], +ObjectPath.root().missing_array_element(0), +ObjectPath.root().array_index(0), +), +], +) +def test_array_structural_mismatch(a, b, expected_a_path, expected_b_path): +a = tvm.runtime.convert(a) +b = tvm.runtime.convert(b) +a_path, b_path = get_first_mismatch_ensure_symmetry(a, b) +assert a_path == expected_a_path +assert b_path == expected_b_path + + +@pytest.mark.parametrize( +"contents", +[ +[], +[1], +[1, 2, 3], +], +) +def test_array_structural_equal_to_self(contents): +a = tvm.runtime.convert(list(contents)) +b = tvm.runtime.convert(list(contents)) +assert get_first_mismatch_ensure_symmetry(a, b) is None + + +@pytest.mark.parametrize( +"a, b, expected_a_path, expected_b_path", +[ +( +dict(a=3, b=4), +dict(a=3, b=5), +ObjectPath.root().map_value("b").attr("value"), +ObjectPath.root().map_value("b").attr("value"), +), +( +dict(a=3, b=4), +dict(a=3, b=4, c=5), +ObjectPath.root().missing_map_entry(), +ObjectPath.root().map_value("c"), +), +], +) +def test_string_map_structural_mismatch(a, b, expected_a_path, expected_b_path): +a = tvm.runtime.convert(a) +b = tvm.runtime.convert(b) +a_path, b_path = get_first_mismatch_ensure_symmetry(a, b) +assert a_path == expected_a_path +assert b_path == expected_b_path + + +@pytest.mark.parametrize( +"contents", +[ +dict(), +dict(a=1), +dict(a=3, b=4, c=5), +], +) +def test_string_structural_equal_to_self(contents): +a = tvm.runtime.convert(dict(contents)) +b = tvm.runtime.convert(dict(contents)) +assert get_first_mismatch_ensure_symmetry(a, b) is None + + +# The behavior of structural equality for maps with non-string keys is fairly specific +# to IR variables because it assumes that map keys have been "mapped" using +# `SEqualReducer::FreeVarEqualImpl()`. So we leave this case to TIR tests. Review Com
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r932588181 ## python/tvm/runtime/object_path.py: ## @@ -122,3 +123,14 @@ class MapValuePath(ObjectPath): @tvm._ffi.register_object("MissingMapEntryPath") class MissingMapEntryPath(ObjectPath): pass + + +@tvm._ffi.register_object("ObjectPathPair") +class ObjectPathPair(Object): +@property +def lhs_path(self) -> ObjectPath: +return _ffi_node_api.ObjectPathPairLhsPath(self) + +@property +def rhs_path(self) -> ObjectPath: +return _ffi_node_api.ObjectPathPairRhsPath(self) Review Comment: The problem with this approach is that we can't `#include "reflection.h"` in the `structural_equal.h` because of a circular dependency (`reflection.h` already includes `structural_equal.h`). We could forward-declare the `AttrVisitor` class in the header, and put the implementation of `VisitAttrs` in the cc file, but we would risk getting the two out of sync. I think this is really a special case because this ObjectPathPair is part of the reflection mechanism, so I think it is okay to have the extra boilerplate for the getters. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r932588181 ## python/tvm/runtime/object_path.py: ## @@ -122,3 +123,14 @@ class MapValuePath(ObjectPath): @tvm._ffi.register_object("MissingMapEntryPath") class MissingMapEntryPath(ObjectPath): pass + + +@tvm._ffi.register_object("ObjectPathPair") +class ObjectPathPair(Object): +@property +def lhs_path(self) -> ObjectPath: +return _ffi_node_api.ObjectPathPairLhsPath(self) + +@property +def rhs_path(self) -> ObjectPath: +return _ffi_node_api.ObjectPathPairRhsPath(self) Review Comment: The problem with this approach is that we can't `#include "reflection.h"` in the `structural_equal.h" because of a circular dependency (`reflection.h` already includes `structural_equal.h`). We could forward-declare the `AttrVisitor` class in the header, and put the implementation of `VisitAttrs` in the cc file, but we would risk getting the two out of sync. I think this is really a special case because this ObjectPathPair is part of the reflection mechanism, so I think it is okay to have the extra boilerplate for the getters. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r932584270 ## include/tvm/node/structural_equal.h: ## @@ -198,9 +285,41 @@ class SEqualReducer : public BaseValueEqual { /*! \return Get the internal handler. */ Handler* operator->() const { return handler_; } + /*! \brief Check if this reducer is tracing paths to the first mismatch. */ + bool IsPathTracingEnabled() const { return tracing_data_ != nullptr; } + + /*! + * \brief Get the paths of the currently compared objects. + * + * Can only be called when `IsPathTracingEnabled()` is true. + */ + const ObjectPathPair& GetCurrentObjectPaths() const; + + /*! + * \brief Specify the object paths of a detected mismatch. + * + * Can only be called when `IsPathTracingEnabled()` is true. + */ + void RecordMismatchPaths(const ObjectPathPair& paths) const; + private: + bool EnumAttrsEqual(int lhs, int rhs, const void* lhs_address, const void* rhs_address) const; + + bool ObjectAttrsEqual(const ObjectRef& lhs, const ObjectRef& rhs, bool map_free_vars, +const ObjectPathPair* paths) const; + + static void GetPathsFromAttrAddressesAndStoreMismatch(const void* lhs_address, +const void* rhs_address, +const PathTracingData* tracing_data); + + template + static bool CompareAttributeValues(const T& lhs, const T& rhs, + const PathTracingData* tracing_data); Review Comment: I think I mostly understand your point, but I'm missing one thing: why is this specific to function templates? For example, the non-template helper function `GetPathsFromAttrAddressesAndStoreMismatch` just above is also private but we have to put its declaration in the header file, because it is a static function in our class (which we need because of C++ visibility rules). Even if we go with the `SEqualReducerHelper` approach, we still need to leak some details in the header file because we need to either declare it as a static class or as a friend class. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r931589275 ## include/tvm/node/structural_equal.h: ## @@ -198,9 +285,41 @@ class SEqualReducer : public BaseValueEqual { /*! \return Get the internal handler. */ Handler* operator->() const { return handler_; } + /*! \brief Check if this reducer is tracing paths to the first mismatch. */ + bool IsPathTracingEnabled() const { return tracing_data_ != nullptr; } + + /*! + * \brief Get the paths of the currently compared objects. + * + * Can only be called when `IsPathTracingEnabled()` is true. + */ + const ObjectPathPair& GetCurrentObjectPaths() const; + + /*! + * \brief Specify the object paths of a detected mismatch. + * + * Can only be called when `IsPathTracingEnabled()` is true. + */ + void RecordMismatchPaths(const ObjectPathPair& paths) const; + private: + bool EnumAttrsEqual(int lhs, int rhs, const void* lhs_address, const void* rhs_address) const; + + bool ObjectAttrsEqual(const ObjectRef& lhs, const ObjectRef& rhs, bool map_free_vars, +const ObjectPathPair* paths) const; + + static void GetPathsFromAttrAddressesAndStoreMismatch(const void* lhs_address, +const void* rhs_address, +const PathTracingData* tracing_data); + + template + static bool CompareAttributeValues(const T& lhs, const T& rhs, + const PathTracingData* tracing_data); Review Comment: What is the actual concern about having a function template declared in the header? I don't have a strong opinion, can move these to a helper friend class if you prefer it that way. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r931437519 ## include/tvm/node/structural_equal.h: ## @@ -56,6 +57,31 @@ class BaseValueEqual { } }; +/*! + * \brief Pair of `ObjectPath`s, one for each object being tested for structural equality. + */ +struct ObjectPathPair { + ObjectPath lhs_path; + ObjectPath rhs_path; +}; + +// Could be replaced with std::optional +class OptionalObjectPathPair { + public: + OptionalObjectPathPair() = default; + + OptionalObjectPathPair(const ObjectPathPair& p) // NOLINT(runtime/explicit) + : lhs_path(p.lhs_path), rhs_path(p.rhs_path) {} + + bool defined() const { return lhs_path.defined(); } + + ObjectPathPair value() const { return {lhs_path.value(), rhs_path.value()}; } + + private: + Optional lhs_path; + Optional rhs_path; +}; + Review Comment: That's correct, but making it a TVM object would require heap allocation. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org
[GitHub] [tvm] gbonik commented on a diff in pull request #12101: [TVMScript] Add object path tracing to StructuralEqual
gbonik commented on code in PR #12101: URL: https://github.com/apache/tvm/pull/12101#discussion_r923570472 ## src/node/reflection.cc: ## @@ -281,4 +281,43 @@ TVM_REGISTER_GLOBAL("node.NodeGetAttr").set_body(NodeGetAttr); TVM_REGISTER_GLOBAL("node.NodeListAttrNames").set_body(NodeListAttrNames); TVM_REGISTER_GLOBAL("node.MakeNode").set_body(MakeNode); + +namespace { +// Attribute visitor class for finding the attribute key by its address +class GetAttrKeyByAddressVisitor : public AttrVisitor { + public: + explicit GetAttrKeyByAddressVisitor(const void* attr_address) + : attr_address_(attr_address), key_(nullptr) {} + + void Visit(const char* key, double* value) final { DoVisit(key, value); } + void Visit(const char* key, int64_t* value) final { DoVisit(key, value); } + void Visit(const char* key, uint64_t* value) final { DoVisit(key, value); } + void Visit(const char* key, int* value) final { DoVisit(key, value); } + void Visit(const char* key, bool* value) final { DoVisit(key, value); } + void Visit(const char* key, std::string* value) final { DoVisit(key, value); } + void Visit(const char* key, void** value) final { DoVisit(key, value); } + void Visit(const char* key, DataType* value) final { DoVisit(key, value); } + void Visit(const char* key, runtime::NDArray* value) final { DoVisit(key, value); } + void Visit(const char* key, runtime::ObjectRef* value) final { DoVisit(key, value); } + + const char* GetKey() const { return key_; } + + private: + const void* attr_address_; + const char* key_; + + void DoVisit(const char* key, const void* candidate) { +if (attr_address_ == candidate) { + key_ = key; +} + } +}; +} // anonymous namespace + +const char* GetAttrKeyByAddress(const Object* object, const void* attr_address) { + GetAttrKeyByAddressVisitor visitor(attr_address); + ReflectionVTable::Global()->VisitAttrs(const_cast(object), &visitor); + return visitor.GetKey(); Review Comment: The idea is that it returns `nullptr` when the attribute does not exist. In this case, when tracing paths, we will create a special `UnknownAttributeAccessPath` that doesn't compare equal even to itself. This is to prevent the code from failing hard: if some of the tracing code is incomplete, I'd rather have the printing still work without highlighting, than crash with an exception. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@tvm.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org