Inspired by the work of @mbs-octoml, I give you a new RFC for CompilationConfig!
# Summary [summary]: #summary This RFC supersedes [Migrating IRModules to Attributes](https://github.com/apache/tvm-rfcs/blob/main/rfcs/0029-migrating-to-irmodule-attributes.md) by replacing the various attributes associated with the `Runtime` and `Executor` with a single `CompilationConfig` object that encapsulates the configuration of the compiler for a given `IRModule`. By collecting this together, it introduces on object which can be coupled to the `IRModule` and used through-out compilation with guarantees about the properties of the configuration. # Motivation [motivation]: #motivation ## Argument Duplication When implementing [Migrating IRModules to Attributes](https://github.com/apache/tvm-rfcs/blob/main/rfcs/0029-migrating-to-irmodule-attributes.md), it became clear that the arguments were getting duplicated in many places across the codebase and never collated. Such as the following `tvmc` CLI: ``` tvmc --target=c --executor=aot --runtime=crt ``` Which populates the arguments `executor`, `runtime` and `target` all the way into the compilation flow, rather than collating them and passing the pre-processed representation. This introduces places we can make errors due to missing one of the three and always having to replicate the signature. ## Single Point of Setup Futher to this, many areas of the code required access to the compiler configurations non-optionally, this introduces uncertainty in later passes which should be able to guarantee the compiler has been configured - thus making the dynamic attributes less ideal for this use-case: ```cpp // Hopefully this is set! ir_mod->GetAttr<Executor>(kExecutor).value()->name; ``` As can be seen by [adding SEScope](https://github.com/apache/tvm/pull/9313), there's also need for a single place to collect and define configuration rather than cause duplication of effort, with multiple calls to setup various parts of the configuration, such as `Target` environment which should be known when the compilation flow begins: ```cpp CheckAndUpdateHostConsistency(&target, &target_host); ``` By moving these to a single property of the `IRModule` which is required to exist before entry into the compiler flow, this reduces both the cognitive overhead of accessing the configuration and provides a place to encapsulate the logic of setup early in the compilation flow. This is a fixed set of configuration alongside the existing `PassContext::Global()` to solidify the configuration requirements of the TVM Compiler. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation ## User API A user creates and passes a configuration to `relay.build(mod, config)`, leading to something similar to: ```python ir_mod = IRModule.from_expr(function) config = CompilationConfig( target_host=target, targets=[target], executor=Executor("aot", {"interface-api": "c", "unpacked-api": True}), runtime=Runtime("crt", {"system-lib": True}) ) relay.build(ir_mod, config) ``` To easily create a default `CompilationConfig` from a single `Target`, the `from_target` option is provided - this can be used to add syntactic ease in `relay.build`: ```python def relay.build(ir_mod, config, ...): if isinstance(config, Target): config = CompilationConfig.from_target(config) ``` The C++ API is then amended to require configuration: ```cpp /*! * \brief Build relay IRModule * * \param mod Relay IRModule * \param config Compilation Config for this compiler run * \param mod_name Name of the module */ void Build(IRModule mod, const CompilationConfig& config, const String mod_name) ``` This means a user can continue to easily create and pass an `IRModule` to `relay.build` as an opaque object whilst providing the configuration alongside. ## Developer API The developer facing API for the `IRModule` changes for internal passes to easily access the configuration: ```cpp ir_mod->GetConfig()->GetExecutor() ``` And functionality related to inspecting this configuration can be added to `CompilationConfig` which can see all available configuration properties: ```cpp ir_mod->GetConfig()->ShouldLinkParams() ``` Importantly, for ad-hoc information passed alongside with the `IRModule`, attributes continue to be available: ```cpp WithAttr<String>(ir_mod, "woof", "woof"); ``` # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## IRModule Configuration To incorporate this into the `IRModule`, a property of type `CompilationConfig` will be added and exposed via a function `GetConfig()` in line with [the Google C++ guidelines](https://google.github.io/styleguide/cppguide.html) except for being a public property for TVMs internal node structures: ```cpp class IRModuleNode { CompilationConfig config; const CompilationConfig& GetConfig() { return config; } } ``` The actual `CompilationConfig` class will represent the current and future shape of configuration, such as `Executor` and `Runtime`, alongside `Target` and `Target` host (example illustrative not carved into stone): ```cpp class CompilationConfigNode { Target target_host; Array<Target> targets; Runtime runtime; Executor executor; } ``` >From Python the module will be passed into C++ with the `CompilationConfig` to >start the compilation flow: ```python def relay.build(ir_mod, config: Union[CompilationConfig, Target], ...): if isinstance(config, Target): config = CompilationConfig.from_target(config) mod["build"](ir_mod, config) ``` Which is then connected within C++: ```cpp void Build(IRModule mod, const CompilationConfig& config, const String mod_name) { mod->config = config; ... } ``` When creating `IRModule` -> `IRModule` passes within the compiler this property should now be guaranteed to exist in the main Relay flow. ## Non-Relay Flows There are a number of ways of accessing the internals of TVM which have been exposed to the user, for these to continue to function the `CompilationConfig` will be settable from Python such that: ```python ir_mod = ir_mod.with_configuration(config) ``` The above will attach the configuration within the alternative pathways, it is the opinion of the author that these should be eventually deprecated in favour of a single standard compilation path. ## Compilation Configuration Creators Within `python/tvm/target/target.py` there are a number of functions which returned `Target`s with configuration built in, such as: https://github.com/apache/tvm/blob/e8077439efbe021d73cf27018cd83653f964255f/python/tvm/target/target.py#L305-L328 These are user-facing configurations, and will be ported to `tvm/target/config.py` to match the `include/tvm/target/compilation_config.h` and provide the same logic but returning a `CompilationConfig`: ```python def micro(model="unknown", options=None, executor="graph"): opts = _merge_opts( MICRO_SUPPORTED_MODELS[model] + ["-runtime=c", f"-model={model}"], options, ) target = Target(" ".join(["c"] + opts)) return CompilationConfig( target_host=[target], targets=[target], executor=Executor(executor), runtime=Runtime("crt", { "system-lib": executor == "graph" }) ) ``` # Drawbacks [drawbacks]: #drawbacks - This is more information on the `IRModule` alongside existing attributes and pass information - The entry contract for `relay.build` and the TVM Compiler changes to require configuration of the initial `IRModule` # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives Continue to use `PassContext::Global()` and general purpose attributes for fixed compilation configuration, using the general purpose options here hides the details of the compilation configuration and makes it harder for us to provide a robust representation of none-`Optional` configuration. If all things are considered `Optional` then they have to be considered as none-configured and requiring multiple passes of reconfiguration to provide safety to the users. # Prior art [prior-art]: #prior-art - [Migrating IRModules to Attributes](https://github.com/apache/tvm-rfcs/blob/main/rfcs/0029-migrating-to-irmodule-attributes.md) split the settings from `Target` into `Executor` and `Runtime` attributes - `CompilationConfig` was introduced in [Add SEScope PR #9313](https://github.com/apache/tvm/pull/9313#issuecomment-955732036) - `PassContext` already exists in TVM as a way of passing general configuration # Unresolved questions [unresolved-questions]: #unresolved-questions - Do we want to fully deprecate passing a `target` to `relay.build`? The assumption is made that we don't want to fully deprecate it and a sugar has been added above for populating a `CompilationConfig` straight from a `Target` - Similarly, do we want to provide a similar helper for `target` and `target_host`: ```python def relay.build(ir_mod, config, target_host=None): if isinstance(config, Target): config = CompilationConfig.from_target(config, target_host=target_host) ``` - What is the future for `IRModule` attributes and `PassContext` - this RFC aims to provide one piece of concrete configuration which we know can be fixed at the start of compilation but there should be a default choice for ad-hoc configuration within the compilation flow. # Future possibilities [future-possibilities]: #future-possibilities By codifying the properties of the compilation flow in `CompilationConfig` and providing `IRModule` attributes we may be able to deprecate `PassContext::Global()` and only use the passed state in TVM. @areusch @manupa-arm @jroesch @tqchen --- [Visit Topic](https://discuss.tvm.apache.org/t/pre-rfc-compilation-configuration-representation/11372/1) to respond. You are receiving this because you enabled mailing list mode. To unsubscribe from these emails, [click here](https://discuss.tvm.apache.org/email/unsubscribe/c291481dae0f3b187d191b56089daddd031577e452a2a3568b1ad586f99a425f).