Hello all, this day when I try to use dir()
to list all valid attributes of a tvm.ir.container.Array
object, I get a TypeError which tells me Array is not registered via TVM_REGISTER_NODE_TYPE, and I get a similar result of listing tvm.ir.container.Map
.
Here is my script and output:
import tvm
test_array = [1,2,3]
tvm_test_array = tvm.runtime.convert(test_array)
test_key = 'key'
tvm_test_key = tvm.runtime.convert(test_key)
test_dict = {tvm_test_key: 1}
tvm_test_dict = tvm.runtime.convert(test_dict)
try:
print(type(tvm_test_array))
dir(tvm_test_array)
except Exception as e:
print(e)
try:
print(type(tvm_test_dict))
dir(tvm_test_dict)
except Exception as e:
print(e)
python3 test_attr.py
# output of print(type(tvm_test_array))
<class 'tvm.ir.container.Array'>
# Exception of dir(tvm_test_array)
Traceback (most recent call last):
4: TVMFuncCall
3: _ZNSt17_Function_handlerIFvN3tvm7runtime7TVMArgsEPNS1
2: tvm::NodeListAttrNames(tvm::runtime::TVMArgs, tvm::runtime::TVMRetValue*)
1: tvm::ReflectionVTable::ListAttrNames[abi:cxx11](tvm::runtime::Object*) const
0: tvm::ReflectionVTable::VisitAttrs(tvm::runtime::Object*, tvm::AttrVisitor*) const
File "/home/syang/tvm/include/tvm/node/reflection.h", line 390
TypeError: Array is not registered via TVM_REGISTER_NODE_TYPE
# output of print(type(tvm_test_dict))
<class 'tvm.ir.container.Map'>
# Exception of dir(tvm_test_dict)
Traceback (most recent call last):
4: TVMFuncCall
3: _ZNSt17_Function_handlerIFvN3tvm7runtime7TVMArgsEPNS1
2: tvm::NodeListAttrNames(tvm::runtime::TVMArgs, tvm::runtime::TVMRetValue*)
1: tvm::ReflectionVTable::ListAttrNames[abi:cxx11](tvm::runtime::Object*) const
0: tvm::ReflectionVTable::VisitAttrs(tvm::runtime::Object*, tvm::AttrVisitor*) const
File "/home/syang/tvm/include/tvm/node/reflection.h", line 390
TypeError: Map is not registered via TVM_REGISTER_NODE_TYPE
To further explore the root cause, I read the source code and follow call its chains:
First, since tvm.ir.container.Array
extends tvm.runtime.Object
, when we call dir()
, we actually call __dir__(self)
:
class Object(ObjectBase):
"""Base class for all tvm's runtime objects."""
def __dir__(self):
class_names = dir(self.__class__)
fnames = _ffi_node_api.NodeListAttrNames(self)
size = fnames(-1)
return sorted([fnames(i) for i in range(size)] + class_names)
Then, we will execute _ffi_node_api.NodeListAttrNames(self)
, whose backend implementation is tvm::ReflectionVTable::ListAttrNames
in src/node/reflection.cc.
std::vector<std::string> ReflectionVTable::ListAttrNames(Object* self) const {
std::vector<std::string> names;
AttrDir dir;
dir.names = &names;
if (!self->IsInstance<DictAttrsNode>()) {
VisitAttrs(self, &dir);
} else {
// specially handle dict attr
DictAttrsNode* dnode = static_cast<DictAttrsNode*>(self);
for (const auto& kv : dnode->dict) {
names.push_back(kv.first);
}
}
return names;
}
According to its source code we can find that it will execute tvm::ReflectionVTable::VisitAttrs
implemented in include/tvm/node/reflection.h. We can find the error occurs when fvisit_attrs_[tindex] == nullptr
is true.
inline void ReflectionVTable::VisitAttrs(Object* self, AttrVisitor* visitor) const {
uint32_t tindex = self->type_index();
if (tindex >= fvisit_attrs_.size() || fvisit_attrs_[tindex] == nullptr) {
LOG(FATAL) << "TypeError: " << self->GetTypeKey()
<< " is not registered via TVM_REGISTER_NODE_TYPE";
}
fvisit_attrs_[tindex](self, visitor);
}
So does Array
and Map
don’t register themselves? To answer the question, I read their source codes and find that actually they finish their registering in src/runtime/container.cc, Line 38 and Line 123 respectively. And TVM_REGISTER_OBJECT_TYPE
will call ReflectionVTable::Register()
which is defined in include/tvm/node/reflection.h:
template <typename T, typename TraitName>
inline ReflectionVTable::Registry ReflectionVTable::Register() {
uint32_t tindex = T::RuntimeTypeIndex();
if (tindex >= fvisit_attrs_.size()) {
fvisit_attrs_.resize(tindex + 1, nullptr);
fcreate_.resize(tindex + 1, nullptr);
frepr_bytes_.resize(tindex + 1, nullptr);
fsequal_reduce_.resize(tindex + 1, nullptr);
fshash_reduce_.resize(tindex + 1, nullptr);
}
// functor that implements the redirection.
fvisit_attrs_[tindex] = ::tvm::detail::SelectVisitAttrs<T, TraitName>::VisitAttrs;
fsequal_reduce_[tindex] = ::tvm::detail::SelectSEqualReduce<T, TraitName>::SEqualReduce;
fshash_reduce_[tindex] = ::tvm::detail::SelectSHashReduce<T, TraitName>::SHashReduce;
return Registry(this, tindex);
}
Now we find that all functions in fvisit_attrs_
are actually equal to ::tvm::detail::SelectVisitAttrs<T, TraitName>::VisitAttrs
, which is defined in src/node/structural_hash.cc, for Array
and Map
, they are defined in Line 370 and Line 394:
struct ArrayNodeTrait {
static constexpr const std::nullptr_t VisitAttrs = nullptr;
// ignores others
};
TVM_REGISTER_REFLECTION_VTABLE(ArrayNode, ArrayNodeTrait)
.set_creator([](const std::string&) -> ObjectPtr<Object> {
return ::tvm::runtime::make_object<ArrayNode>();
});
struct MapNodeTrait {
static constexpr const std::nullptr_t VisitAttrs = nullptr;
// ignore others
};
TVM_REGISTER_REFLECTION_VTABLE(MapNode, MapNodeTrait)
.set_creator([](const std::string&) -> ObjectPtr<Object> { return MapNode::Empty(); });
Now the root cause is clear since there is a nullptr
as a default value for ArrayNode
and MapNode
, every time when we call tvm::ReflectionVTable::VisitAttrs
, the fvisit_attrs_[tindex] == nullptr
will be always true, and there is no doubt that we will get a type error like XXX is not registered via TVM_REGISTER_NODE_TYPE.