This is an archive of the discontinued LLVM Phabricator instance.

Add option to emit stateful functions to the emitc backend.
Needs ReviewPublic

Authored by jacobhegna on Apr 4 2023, 1:55 PM.

Details

Reviewers
marbre
simon-camp
Summary

The usual emitc backend emits stateless cpp functions which take the
model inputs as function arguments and returns the resulting tensor.
However, it does not provide meaningful names to the arguments of the
function. Moreover, the order of the arguments is not stable in a
variety of applications (anything which comes from a tflite file, namely
MLGO models), so switching to a model where arguments are set via setter
methods avoids issues of tracking down which anonymous argument
corresponds to which model input.

Moreover, this change emits the stateful functions in two files: a
header and cpp file. This is useful for MLGO as we intend to deploy the
emitc runtime code in an anonymous namespace inside of the .cpp file of
the generated model to allow each generated model to have its own
version of the runtime (to prevent future changes to the runtime to
break previously deployed models).

Diff Detail

Event Timeline

jacobhegna created this revision.Apr 4 2023, 1:55 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 4 2023, 1:55 PM
jacobhegna requested review of this revision.Apr 4 2023, 1:55 PM

Currently no reviewers are specified. Would you like @simon-camp and me to review? We initially pushed the code and mostly maintain it.

Ah sorry, I got sidetracked with something else. Let me update the patch with some tests then it will be ready for review.

I added myself and especially @simon-camp as reviewer, since I might be oof when this is ready for review.

Updating to include test and to update the generated code.

The delay was due to making sure the generated API was acceptable for use in
D146483.

Ok, should be good to review.

jacobhegna updated this revision to Diff 520016.May 5 2023, 6:50 PM

Including definitions of some enums.

phosek added a subscriber: phosek.Jun 22 2023, 12:20 AM
jpienaar added inline comments.
mlir/include/mlir/Target/Cpp/CppEmitter.h
27

This is related only to the variable initialization right? (e.g., stateless won't be stateless if the ops being lowered mutate some global state which is not what this is controlling).

37

Could you describe these above the function? (I almost wonder if we are at point where we want a translate configuration struct for these)

mlir/lib/Target/Cpp/TranslateRegistration.cpp
41

Other descriptions here don't end with period

mlir/lib/Target/Cpp/TranslateToCpp.cpp
94

Comment?

194

Doxygen comments?

691

Prints the declaration of the stateful function class. The output of this method is something that looks like: ?

692

pseudocode

706

Could we give one concrete reason here instead of this? This feels a little bit mysterious and O(5) months down the line someone will wonder why.

711

Is this tensor specific? What happens if stateful is set and non-tensors used? (e.g., scalars)

759

pseudocode

791

API

793

This is generally useful though, I'd prefer the choices being laid out here. I don't think this is controversial, but should just be documented.

891

Nit: MLIR style is to elide trivial braces. (unrelaetd, but you can combine this into 1 conditional without loss of readability)

897

Why not switch given enum class?

1160

ml_program.identifier ?

1248

MLIR style switched to

auto tType = dyn_cast<TensorType>(type);

1250

Nit: error style is sentence fragment starting with lower case.

To me this looks like a lot of additional code that targets a very specific use case. Additionally this hardcodes the assumption that the Tensor class exposes a get method into the emitter.

Could you think of a representation that is more suitable for codegen'ing through dialect conversions? For example by lowering the model to loops we would end up with a mix of memref, arith and scf dialects. This would both be easier to model in the EmitC dialect and deal with the problem of version skew in an external reference implementation.
The issue with the anonymous function arguments would of course persist though, as well as the question about memory ownership of the arguments and results.

To me this looks like a lot of additional code that targets a very specific use case. Additionally this hardcodes the assumption that the Tensor class exposes a get method into the emitter.

Could you think of a representation that is more suitable for codegen'ing through dialect conversions? For example by lowering the model to loops we would end up with a mix of memref, arith and scf dialects. This would both be easier to model in the EmitC dialect and deal with the problem of version skew in an external reference implementation.
The issue with the anonymous function arguments would of course persist though, as well as the question about memory ownership of the arguments and results.

I agree it's too specialized, but I was thinking slightly different: the above should be able to remain as is with a transform pass on Emitc that creates classes etc. So this hardcoding would all be in a pass before translate rather than in translate.

Class construct would be needed and we'd still need a way to specify header file or body content. I think being able to not encode this into translate becomes a target then to further work here. E.g., being able to get this same functionality but as a pass with new ops ("transformToStatefulAccessors")

What do you think of the interim state where this is usable until one could do it as pass?

Can you add an error message when the module contains multiple func ops, paired with a test in test/Target/Cpp/invalid.mlir. Currently the translation succeeds but generates code that contains repeated class definitions with the same name.

If emit-cpp-only-one-fn is set and the dedicated function contains func.call ops, these are emitted as calls to free functions whose generation is skipped. This should also raise an error.

mlir/lib/Target/Cpp/TranslateToCpp.cpp
108–110

This method seems to be unused.

688

Can you call os.unindent() here so that the indentation level is not changed by the function.

783
858

This should be moved into printFuncOpBody.

1250

Please add a test for this in test/Target/Cpp/invalid.mlir.