tldr: DenseElementsAttr is currently hard-coded to certain builtin types. Make DenseElementsAttr extensible, so that it can be used with custom element types.
Background
DenseElementsAttr (e.g., dense<[1, 2, 3, 4]>: tensor<4xi32>) is an attribute that stores elements of shaped values in a dense storage format: instead of storing one Attribute per element, it stores the data in an ArrayRef<char>. This is more efficient for large shaped values.
DenseElementsAttr is currently hard-coded to IntegerType, IndexType, FloatType, ComplexType and strings. (Strings are a bit special, let’s not get into that…)
Here are some examples:
DenseIntOrFPElementsAttr::getRaw(ShapedType, ArrayRef<char>)verifies that the size of the buffer matches the expected size based on the specified shaped type. Custom types are not supported here.DenseElementsAttr::get(ShapedType, ArrayRef<Attribute>)is hard-coded to the same builtin types.- There are convenience iterators for the supported builtin types, but these cannot be used with custom types.
Proposal
Users have expressed interest in a more extensible DenseElementsAttr infrastructure. Most recently on this PR. This RFC proposes to:
Add a newTensorElementTypeInterface, so that custom tensor element types are possible. This interface is similar to theVectorElementTypeInterface.- Add a new
DenseElementTypeInterfacefor element types that are supported byDenseElementsAttr. The interface has the following interface methods:
2.1.size_t getDenseElementByteSize(): Return the storage size of a single element. For verification purposes and to compute where a certain element begins and ends (needed for iterators + random access).
2.2.Attribute convertToAttribute(ArrayRef<char>): Deserialize the bytes of a single element into an MLIR attribute.
2.3.LogicalResult convertFromAttribute(Attribute, SmallVectorImpl<char> &): Serialize an MLIR attribute into bytes. - Update the assembly format of
DenseElementsAttr: the shaped type should come first, so that you know what you’re about to parse. (The existing format can also remain in place.)
The conversion functions from/to MLIR attributes allow users to iterate over a DenseElementsAttr in terms of Attribute. For efficiency reasons, there should also be a way to iterate over elements in a “dense” way, i.e., without constructing an Attribute for each scalar. This is not shown here, but in essence, users will receive a void * / char * for each element and can then interpret the data as needed.
Another benefit of the conversion from/to attributes is that the existing parser/printer can be utilized for the assembly format of the DenseElementsAttr. Note: This assumes that there is a corresponding attribute for each supported element type. E.g., IntegerType -> IntegerAttr or MyCustomIntegerType -> IntegerAttr. (Two different types can share the same attribute class.)
Assembly Format
Current assembly format: dense<[[1, 3], [0, 7]]> : tensor<2x2xi32>
The existing assembly format does not work for custom DenseElementsAttr element types. In order to parse an element, we first have to know what we are going to parse.
Custom element types must be written in this format: dense<tensor<2x2x!test.my_int_type> : [[1 : i32, 2 : i32], [3 : i32, 4 : i32]]>. Here’s a breakdown of what happens when parsing this attribute:
- The ranked tensor type with element type
!test.my_int_typeis parsed. 2x2element attributes are parsed. These could be arbitrary attributes.- For each element attribute,
MyIntType::convertFromAttributeis called. This interface function expectsIntegerAttrand will fail when other attributes are passed, triggering a parser error. The serialized bytes are stored in theDenseElementsAttr.
Both the current assembly format and the new assembly format can exist side-by-side. However, for consistency reasons, it would be great if we could eventually deprecated the current assembly format.
Future Extensions
Parsing/printing is a bit inefficient because it materializes temporary attributes for each element. This could be avoided by adding additional interface methods for parsing/printing the entire dense elements attr data. This would also allow for denser notations such as dense<tensor<2x2x!test.my_int_type> : [[1, 2], [3, 4]]>.
Note that while the “parsing/printing via scalar attribute” approach as presented here is inefficient, it does not slow down attribute creation / access via the C++ API.
Prototype
Here’s a Cursor-generated prototype: [mlir][IR] Generalize`DenseElementsAttr` to custom element types by matthias-springer · Pull Request #179122 · llvm/llvm-project · GitHub. I’d like to gather some feedback before cleaning up that PR and splitting it up into smaller pieces.