Skip to content

Commit

Permalink
[mlir][sparse] Renaming "pointer/index" to "position/coordinate"
Browse files Browse the repository at this point in the history
The old "pointer/index" names often cause confusion since these names clash with names of unrelated things in MLIR; so this change rectifies this by changing everything to use "position/coordinate" terminology instead.

In addition to the basic terminology, there have also been various conventions for making certain distinctions like: (1) the overall storage for coordinates in the sparse-tensor, vs the particular collection of coordinates of a given element; and (2) particular coordinates given as a `Value` or `TypedValue<MemRefType>`, vs particular coordinates given as `ValueRange` or similar.  I have striven to maintain these distinctions
as follows:

  * "p/c" are used for individual position/coordinate values, when there is no risk of confusion.  (Just like we use "d/l" to abbreviate "dim/lvl".)

  * "pos/crd" are used for individual position/coordinate values, when a longer name is helpful to avoid ambiguity or to form compound names (e.g., "parentPos").  (Just like we use "dim/lvl" when we need a longer form of "d/l".)

    I have also used these forms for a handful of compound names where the old name had been using a three-letter form previously, even though a longer form would be more appropriate.  I've avoided renaming these to use a longer form purely for expediency sake, since changing them would require a cascade of other renamings.  They should be updated to follow the new naming scheme, but that can be done in future patches.

  * "coords" is used for the complete collection of crd values associated with a single element.  In the runtime library this includes both `std::vector` and raw pointer representations.  In the compiler, this is used specifically for buffer variables with C++ type `Value`, `TypedValue<MemRefType>`, etc.

    The bare form "coords" is discouraged, since it fails to make the dim/lvl distinction; so the compound names "dimCoords/lvlCoords" should be used instead.  (Though there may exist a rare few cases where is is appropriate to be intentionally ambiguous about what coordinate-space the coords live in; in which case the bare "coords" is appropriate.)

    There is seldom the need for the pos variant of this notion.  In most circumstances we use the term "cursor", since the same buffer is reused for a 'moving' pos-collection.

  * "dcvs/lcvs" is used in the compiler as the `ValueRange` analogue of "dimCoords/lvlCoords".  (The "vs" stands for "`Value`s".)  I haven't found the need for it, but "pvs" would be the obvious name for a pos-`ValueRange`.

    The old "ind"-vs-"ivs" naming scheme does not seem to have been sustained in more recent code, which instead prefers other mnemonics (e.g., adding "Buf" to the end of the names for `TypeValue<MemRefType>`).  I have cleaned up a lot of these to follow the "coords"-vs-"cvs" naming scheme, though haven't done an exhaustive cleanup.

  * "positions/coordinates" are used for larger collections of pos/crd values; in particular, these are used when referring to the complete sparse-tensor storage components.

    I also prefer to use these unabbreviated names in the documentation, unless there is some specific reason why using the abbreviated forms helps resolve ambiguity.

In addition to making this terminology change, this change also does some cleanup along the way:
  * correcting the dim/lvl terminology in certain places.
  * adding `const` when it requires no other code changes.
  * miscellaneous cleanup that was entailed in order to make the proper distinctions.  Most of these are in CodegenUtils.{h,cpp}

Reviewed By: aartbik

Differential Revision: https://reviews.llvm.org/D144773
  • Loading branch information
wrengr committed Mar 6, 2023
1 parent 3e00f24 commit 84cd51b
Show file tree
Hide file tree
Showing 92 changed files with 2,919 additions and 2,824 deletions.
30 changes: 15 additions & 15 deletions mlir/include/mlir-c/Dialect/SparseTensor.h
Expand Up @@ -41,40 +41,40 @@ enum MlirSparseTensorDimLevelType {
// SparseTensorEncodingAttr
//===----------------------------------------------------------------------===//

/// Checks whether the given attribute is a sparse_tensor.encoding attribute.
/// Checks whether the given attribute is a `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED bool
mlirAttributeIsASparseTensorEncodingAttr(MlirAttribute attr);

/// Creates a sparse_tensor.encoding attribute with the given parameters.
/// Creates a `sparse_tensor.encoding` attribute with the given parameters.
MLIR_CAPI_EXPORTED MlirAttribute mlirSparseTensorEncodingAttrGet(
MlirContext ctx, intptr_t numDimLevelTypes,
MlirContext ctx, intptr_t lvlRank,
enum MlirSparseTensorDimLevelType const *dimLevelTypes,
MlirAffineMap dimOrdering, MlirAffineMap higherOrdering,
int pointerBitWidth, int indexBitWidth);
MlirAffineMap dimOrdering, MlirAffineMap higherOrdering, int posWidth,
int crdWidth);

/// Returns the number of dim level types in a sparse_tensor.encoding attribute.
/// Returns the level-rank of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED intptr_t
mlirSparseTensorEncodingGetNumDimLevelTypes(MlirAttribute attr);
mlirSparseTensorEncodingGetLvlRank(MlirAttribute attr);

/// Returns a specified dim level type in a sparse_tensor.encoding attribute.
/// Returns a specified level-type of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED enum MlirSparseTensorDimLevelType
mlirSparseTensorEncodingAttrGetDimLevelType(MlirAttribute attr, intptr_t pos);
mlirSparseTensorEncodingAttrGetDimLevelType(MlirAttribute attr, intptr_t lvl);

/// Returns the dimension ordering in a sparse_tensor.encoding attribute.
/// Returns the dimension-ordering of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED MlirAffineMap
mlirSparseTensorEncodingAttrGetDimOrdering(MlirAttribute attr);

/// Returns the higher ordering in a sparse_tensor.encoding attribute.
/// Returns the higher-ordering of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED MlirAffineMap
mlirSparseTensorEncodingAttrGetHigherOrdering(MlirAttribute attr);

/// Returns the pointer bit width in a sparse_tensor.encoding attribute.
/// Returns the position bitwidth of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED int
mlirSparseTensorEncodingAttrGetPointerBitWidth(MlirAttribute attr);
mlirSparseTensorEncodingAttrGetPosWidth(MlirAttribute attr);

/// Returns the index bit width in a sparse_tensor.encoding attribute.
/// Returns the coordinate bitwidth of the `sparse_tensor.encoding` attribute.
MLIR_CAPI_EXPORTED int
mlirSparseTensorEncodingAttrGetIndexBitWidth(MlirAttribute attr);
mlirSparseTensorEncodingAttrGetCrdWidth(MlirAttribute attr);

#ifdef __cplusplus
}
Expand Down
4 changes: 2 additions & 2 deletions mlir/include/mlir/Dialect/SparseTensor/IR/Enums.h
Expand Up @@ -40,12 +40,12 @@ namespace sparse_tensor {

/// This type is used in the public API at all places where MLIR expects
/// values with the built-in type "index". For now, we simply assume that
/// type is 64-bit, but targets with different "index" bit widths should
/// type is 64-bit, but targets with different "index" bitwidths should
/// link with an alternatively built runtime support library.
// TODO: support such targets?
using index_type = uint64_t;

/// Encoding of overhead types (both pointer overhead and indices
/// Encoding of overhead types (both position overhead and coordinate
/// overhead), for "overloading" @newSparseTensor.
enum class OverheadType : uint32_t {
kIndex = 0,
Expand Down
222 changes: 135 additions & 87 deletions mlir/include/mlir/Dialect/SparseTensor/IR/SparseTensorAttrDefs.td
Expand Up @@ -19,6 +19,41 @@ class SparseTensor_Attr<string name,
list<Trait> traits = []>
: AttrDef<SparseTensor_Dialect, name, traits>;

//===----------------------------------------------------------------------===//
// Type aliases.
//
// These attributes are just like `IndexAttr` (include/mlir/IR/OpBase.td),
// except that:
// (1) the `summary` is more specific (i.e., the fourth parameter to
// `TypedAttrBase`), which helps tablegen provide better error messages.
// (2) tablegen-generated getters will have the given `returnType`, in
// lieu of the `APInt` that `IndexAttr` uses. This avoids the boilerplate
// of needing to say `get{FOO}().getZExtValue()`, as well as using
// C++ types which better document intent.
//===----------------------------------------------------------------------===//

def DimensionAttr :
TypedAttrBase<
Index, "IntegerAttr",
And<[CPred<"$_self.isa<::mlir::IntegerAttr>()">,
CPred<"$_self.cast<::mlir::IntegerAttr>().getType()"
".isa<::mlir::IndexType>()">]>,
"dimension attribute"> {
let returnType = [{::mlir::sparse_tensor::Dimension}];
let convertFromStorage = [{$_self.getValue().getZExtValue()}];
}

def LevelAttr :
TypedAttrBase<
Index, "IntegerAttr",
And<[CPred<"$_self.isa<::mlir::IntegerAttr>()">,
CPred<"$_self.cast<::mlir::IntegerAttr>().getType()"
".isa<::mlir::IndexType>()">]>,
"level attribute"> {
let returnType = [{::mlir::sparse_tensor::Level}];
let convertFromStorage = [{$_self.getValue().getZExtValue()}];
}

//===----------------------------------------------------------------------===//
// Sparse Tensor Dimension Slice Attribute.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -92,73 +127,86 @@ def SparseTensorEncodingAttr : SparseTensor_Attr<"SparseTensorEncoding",

The attribute consists of the following fields.

- Dimension level type for each dimension of a tensor type:
- **dense** : dimension is dense, all entries along this dimension
are stored
- **compressed** : dimension is sparse, only nonzeros along this dimensions
are stored
- **singleton** : dimension stores individual indices with no siblings
By default, each dimension level types has the property of being unique
(no duplicates at that level) and ordered (indices appear sorted at that
level). The following two suffixes can be used to make the last two
dimension level types not-unique (duplicates may appear) and not-ordered
(indices may appear unsorted).
- Level-type for each level of a tensor type:
- **dense** : all entries along this level are stored.
- **compressed** : only nonzeros along this level are stored.
- **singleton** : a variant of the compressed level-format,
for when coordinates are guaranteed to have no siblings at this level.
By default, each level-type has the property of being unique (no
duplicates at that level) and ordered (coordinates appear sorted
at that level). The following two suffixes can be used to specify
that the level should instead be non-unique (duplicates may appear)
and/or non-ordered (coordinates may appear unsorted).
- **-nu** : not unique
- **-no** : not ordered
Currently, these suffixes, is present, should appear in this order.
In the future, we may introduce many more dimension level types and
properties, and separate specifying the two completely rather than
using this suffix mechanism.

- An optional dimension ordering on the indices of this tensor type. Unlike
dense storage, most sparse storage schemes do not provide fast random
access. This affine map specifies the order of dimensions that should be
supported by the sparse storage scheme. For example, for a 2-d tensor,
`(i, j) -> (i, j)` requests row-wise storage and `(i, j) -> (j, i)`
requests column-wise storage. By default, an identify mapping is used,
which implies that the original indices directly correspond to stored
indices.

- An optional higher-ordering mapping from the original index space of
the tensor to a higher-order index space, used to define block-sparse
storage or ELL (jagged diagonal) storage. For example, for a 2-d tensor,
the mapping `(i, j) -> (i floordiv 2, j floordiv 3, i mod 2, j mod 3)`
imposes an higher-order partitioning into 2x3 blocks along the matrix
layout. A dimension ordering can be used to define a desired ordering
on this higher-order index space. Likewise, the dimension level types
define dense or compressed storage along this higher-order index space.
For block-sparse, blocks are typically stored with compression while
dense storage is used within each block (although hybrid schemes are
possible as well). The higher-order mapping also provides a notion of
"counting a dimension", where every stored element with the same index
is mapped to a new slice. For instance, ELL storage of a 2-d tensor can
be defined with the mapping `(i, j) -> (#i, i, j)` using the notation
of [Chou20]. Lacking the `#` symbol in MLIR's affine mapping, we use
a free symbol `c` to define such counting, together with a constant
that denotes the number of resulting slices. For example, the mapping
`(i, j)[c] -> (c * 3 * i, i, j)` with the first two higher-order indices
stored dense and the innermost compressed denotes ELL storage with
three jagged diagonals that count the dimension `i`.

TODO: introduce a real counting symbol to MLIR's mapping, since an
expression like 3*c*i has no direct interpretation?

- The required bit width for "pointer" storage (integral offsets into
the sparse storage scheme). A narrow width reduces the memory footprint
of overhead storage, as long as the width suffices to define the total
required range (viz. the maximum number of stored entries over all indirection
dimensions). The choices are `8`, `16`, `32`, `64`, or, the default, `0` to
indicate the native bit width.

- The required bit width for "index" storage (elements of the coordinates of
stored entries). A narrow width reduces the memory footprint of overhead
storage, as long as the width suffices to define the total required range
(viz. the maximum value of each tensor index over all dimensions). The
choices are `8`, `16`, `32`, `64`, or, the default, `0` to indicate a
native bit width.

- An optional array of SparseTensorDimSliceAttr, which specifies how the sparse
tensor is partitioned on each level.
Currently, these suffixes (if present) must appear in this order.
In the future, we may introduce additional level-types and
properties, and split up how the level-format and properties are
specified rather than using this suffix mechanism.

TODO: This field is called "dimLevelType" for historical reasons,
even though the types are per-level rather than per-dimension.
(This will be corrected in an upcoming change that completely
overhauls the syntax of this attribute.)

- An optional permutation which maps (higher-ordering)-coordinates
to level-coordinates; defaulting to the identity permutation.
For example, given a 2-d tensor with the default higher-ordering,
`(i, j) -> (i, j)` specifies row-wise storage and `(i, j) ->
(j, i)` specifies column-wise storage.

TODO: this field is called "dimOrdering" for historical reasons,
even though it actually operates on level-coordinates rather than
dimension-coordinates.
(This will be corrected in an upcoming change that completely
overhauls the syntax of this attribute.)

- An optional higher-order mapping from dimension-coordinates to
a higher-order coordinate space; defaulting to the identity map.
This is applied before the `dimOrdering`, thus we have the composite:
dimCoords --higherOrdering--> hoCoords --dimOrdering--> lvlCoords.
The higher-order mapping is used to define block-sparse storage,
jagged-diagonal (JDS/ELL/ITPACK) storage, etc.

For example, given a 2-d tensor, the mapping
`(i, j) -> (i floordiv 2, j floordiv 3, i mod 2, j mod 3)`
imposes an higher-order partitioning into 2x3 blocks along the
matrix layout. For block-sparsity, blocks are typically stored
with compression while dense storage is used within each block
(although hybrid schemes are possible as well).

TODO: the following example is out-of-date and will be implemented
in a different manner than described here.
(This will be corrected in an upcoming change that completely
overhauls the syntax of this attribute.)

The higher-order mapping also provides a notion of "counting a
dimension", where every stored element with the same coordinate
is mapped to a new slice. For instance, ELL storage of a 2-d
tensor can be defined with the mapping `(i, j) -> (#i, i, j)`
using the notation of [Chou20]. Lacking the `#` symbol in MLIR's
affine mapping, we use a free symbol `c` to define such counting,
together with a constant that denotes the number of resulting
slices. For example, the mapping `(i, j)[c] -> (c * 3 * i, i, j)`
with the level-types `["dense", "dense", "compressed"]` denotes ELL
storage with three jagged diagonals that count the dimension `i`.

- The required bitwidth for "position" storage (integral offsets
into the sparse storage scheme). A narrow width reduces the memory
footprint of overhead storage, as long as the width suffices to
define the total required range (viz. the maximum number of stored
entries over all indirection levels). The choices are `8`, `16`,
`32`, `64`, or, the default, `0` to indicate the native bitwidth.

- The required bitwidth for "coordinate" storage (the coordinates
of stored entries). A narrow width reduces the memory footprint
of overhead storage, as long as the width suffices to define
the total required range (viz. the maximum value of each tensor
coordinate over all levels). The choices are `8`, `16`, `32`,
`64`, or, the default, `0` to indicate a native bitwidth.

- An optional array of `SparseTensorDimSliceAttr`, which specifies
how the sparse tensor is partitioned on each dimension.

Examples:

Expand All @@ -179,8 +227,8 @@ def SparseTensorEncodingAttr : SparseTensor_Attr<"SparseTensorEncoding",
#DCSC = #sparse_tensor.encoding<{
dimLevelType = [ "compressed", "compressed" ],
dimOrdering = affine_map<(i, j) -> (j, i)>,
pointerBitWidth = 32,
indexBitWidth = 8
posWidth = 32,
crdWidth = 8
}>
... tensor<8x8xf64, #DCSC> ...

Expand Down Expand Up @@ -214,20 +262,20 @@ def SparseTensorEncodingAttr : SparseTensor_Attr<"SparseTensorEncoding",
// Data in sparse tensor encoding.
let parameters = (
ins
// A dimension level type for each dimension of the tensor type.
// A level-type for each level of the sparse storage.
ArrayRefParameter<
"::mlir::sparse_tensor::DimLevelType",
"per dimension level type"
"level-types"
>: $dimLevelType,
// A dimension order on the indices of this tensor type.
// A permutation from (higher-ordering)-coordinates to level-coordinates.
"AffineMap":$dimOrdering,
// A mapping between the original and higher-ordering index space.
// A mapping from dimension-coordinates to (higher-ordering)-coordinates.
"AffineMap":$higherOrdering,
// The required bit width for pointer storage.
"unsigned":$pointerBitWidth,
// The required bit width for index storage.
"unsigned":$indexBitWidth,
// A dimension level type for each dimension of the tensor type.
// The required bitwidth for position storage.
"unsigned":$posWidth,
// The required bitwidth for coordinate storage.
"unsigned":$crdWidth,
// A slice attribute for each dimension of the tensor type.
ArrayRefParameter<
"::mlir::sparse_tensor::SparseTensorDimSliceAttr",
"per dimension slice metadata"
Expand All @@ -238,23 +286,23 @@ def SparseTensorEncodingAttr : SparseTensor_Attr<"SparseTensorEncoding",
AttrBuilder<(ins "ArrayRef<::mlir::sparse_tensor::DimLevelType>":$dimLevelType,
"AffineMap":$dimOrdering,
"AffineMap":$higherOrdering,
"unsigned":$pointerBitWidth,
"unsigned":$indexBitWidth), [{
"unsigned":$posWidth,
"unsigned":$crdWidth), [{
return $_get($_ctxt, dimLevelType,
dimOrdering,
higherOrdering,
pointerBitWidth,
indexBitWidth,
posWidth,
crdWidth,
ArrayRef<::mlir::sparse_tensor::SparseTensorDimSliceAttr>{});
}]>
];

let extraClassDeclaration = [{
/// Returns the type for pointer storage based on pointerBitWidth
Type getPointerType() const;
/// Returns the type for position storage based on posWidth
Type getPosType() const;

/// Returns the type for index storage based on indexBitWidth
Type getIndexType() const;
/// Returns the type for coordinate storage based on crdWidth
Type getCrdType() const;

/// Constructs a new encoding with the dimOrdering and higherOrdering
/// reset to the default/identity.
Expand Down Expand Up @@ -316,9 +364,9 @@ def SparseTensorEncodingAttr : SparseTensor_Attr<"SparseTensorEncoding",
// The C++ enum for Storage Specifier kind.
def SparseTensorStorageSpecifierKindEnum
: I32EnumAttr<"StorageSpecifierKind", "sparse tensor storage specifier kind", [
I32EnumAttrCase<"DimSize", 0, "dim_sz">,
I32EnumAttrCase<"PtrMemSize", 1, "ptr_mem_sz">,
I32EnumAttrCase<"IdxMemSize", 2, "idx_mem_sz">,
I32EnumAttrCase<"LvlSize", 0, "lvl_sz">,
I32EnumAttrCase<"PosMemSize", 1, "pos_mem_sz">,
I32EnumAttrCase<"CrdMemSize", 2, "crd_mem_sz">,
I32EnumAttrCase<"ValMemSize", 3, "val_mem_sz">,
]> {
let genSpecializedAttr = 0;
Expand Down

0 comments on commit 84cd51b

Please sign in to comment.