TLDR;
We have various use cases that want to attach external non-IR data alongside IR when serializing, and we’d like to standardize a mechanism to do it.
Background
The MLIR textual format currently only contains operations (technically attribute/type aliases too, but those are immaterial), meaning that any external configurations used to process the IR must either be attached directly to the IR, or serialized in some other often hacky format. This generally establishes a good practice of encouraging users to encode the information they need directly in the IR, but in certain cases this just isn’t practical or desirable. For example, MLIR’s pass crash reproducer currently encodes the reproducer configuration as a comment in the output IR file, of which mlir-opt magically tries to detect and parse. Putting aside how extremely hacky this is(I’ll take fault here), this solution obviously won’t work when mlir has a bitcode format (stay tuned in the next week or so ).
Another important use case for us (at Modular AI) is the representation of constants/weights in ML programs. Right now we (and I suspect many others in the ecosystem) generally encode these as DenseElementsAttr, which is extremely undesirable for a large variety of reasons. This data is often many mb/gb/etc., and we don’t want MLIR to own/unique/allocate/copy this data. We’d ideally like to keep the data adjacent to the IR, and have very controlled access to it. The problem with moving data out of the IR is that once you want to serialize, whether that be for generating reproducers/writing tests/<insert-flow-specific-thing>
/etc, you’ll have to get creative with how you interact with the rest of the ecosystem.
There are various other potential use cases that could be enumerated here, but IMO these all boil down to a general desire to encode external “configurations” alongside the IR.
Pitch: A generalized “config” section in MLIR files
I’d like to propose extending the MLIR serialization format with a “config” section to capture the use cases described in the background above.
This section is designed to be a mechanism by which dialects, and external clients, can attach additional information when parsing/printing IR without that information being encoded in the IR itself. Configurations are not unique’d within the MLIR context, are not attached directly to any operation, and are solely intended to live and be processed outside of the immediate IR. Configurations are encoded using a key-value pair nested within dictionaries anchored either on a dialect, or an externally registered entity. Dictionaries anchored on dialects use the dialect namespace directly. Dictionaries anchored on external entities use a provided identifier wrapped within <>
(to differentiate them from dialects). The configuration key is an identifier used to uniquely identify and disambiguate the data. The configuration value can be encoded in a few limited forms (for now either a bool, string (human readable), or blob format (binary)). We can expand these as necessary, but the intention isn’t to have this be super open, we want to be able to optimally encode these as we see fit (in both the textual and our inevitable bitcode format). Within the textual format, an example may be of the form:
{-#
config: {
// Here is a dictionary anchored on "mlir_reproducer", which is an
// external entity representing MLIR's crash reproducer functionality.
// External entity anchors are wrapped in `<>` to differentiate them
// from dialect names.
<mlir_reproducer>: {
// `pipeline` is an entry that holds a crash reproducer pipeline
// configuration.
pipeline: "func.func(canonicalize,cse)"
},
// Here is a dictionary anchored on "foo_dialect", which is a dialect
// namespace.
foo_dialect: {
// `some_dialect_config` is a key to be interpreted by the dialect,
// and used to initialize/configure/etc.
some_dialect_config: "Some important config value"
}
}
#-}
The wrapping {-#
/#-}
is intended to represent a new top-level “file metadata dictionary” section within the MLIR file that holds all of the non-IR extensions that we may want to add in the future (and easily differentiate them from operations/other IR constructs). The design of this was influenced by some of the discussion on the semi-recent proposal for IR versioning.
I’ve uploaded a few commits that illustrate what the design could look like:
-
D126446
- Adds support for the config section described above, with an example “external constants” attribute added to the test dialect.
-
D126447
- Updates the Pass Crash Reproducer to use a config section instead of the
hackymagic comment encoding.
- Updates the Pass Crash Reproducer to use a config section instead of the
Note: I’m not attached to any of the naming or formatting of the “config” section, happy to take any better suggestions here.