-
-
Notifications
You must be signed in to change notification settings - Fork 4
Description
I’m willing to put some effort in the ONNX story if there exist some interest.
TBH I don’t really know what fastAI is about so if there is some special meaning to ONNX + fastAI then please let me know.
Non exhaustive rundown of the current status Flux is that @opus111 has created BaseOnnx and I have made a branch of ONNXmutable which makes use of it (replacing ONNX.jl as the source of protos).
Afaik, ONNXmutable is fully functional and verified and the main gripe I have with it is that it is a bit of a monolith. It depends on NaiveNASflux for the model DAG which in turn has the somewhat big dependencies Flux (arguably not a big deal), LightGraphs and MetaGraphs (both which could be removed though with a little effort), JuMP and Cbc. ONNXmutable also has onnx and onnxruntime as test-only dependencies as well as PyCall and Conda to be able to use and depend on them.
As such, I’m mainly focused on breaking down that monolith into smaller and more reusable components which don’t force all those dependencies on to people. Please let me know if this is not what you think is needed here.
Here are some of my thoughts:
Import
In addition to the primitives discussed below, one needs some kind of runnable representation of the computation graph.
Julias fantastic autodiff capabilities have made it obsolete to have a special DAG format for the models (e.g. Tensorflow) as you can just write the DAG as a normal Julia function. While it is indeed possible to translate an ONNX model into a Julia expression which evaluates to a function representing the model (this is the approach taken by ONNX.jl) it has the limitation that the expression (at least in practice) has to be evaluated at the top level.
Flux today does not have a typical DAG. Flux’s built in Chain can represent many DAGs through the usage of SkipConnection but I don’t think it can represent any DAG (without user written functions).
I think that the method used in ONNXmutable can be generalized without too much effort to work for any typical DAG and perhaps this is something which is useful to put in BasicOnnx. I opened an issue about it here.
If there is interest, I think I can make a functor compatible import-onnxmodel-as-a-function macro in BaseOnnx. Drawback is perhaps that it might give people a janky experience due to it only working from the top level, but perhaps this can be solved by generating a better error message than “incorrect world age”.
To reach the end goal of having a useable package, there still needs to be a DAG format to use. One option is that I extract the non-mutation stuff from NaiveNASlib into some AbstractMlDag package. It is already separated from the mutation stuff so it is trivial to move to a separate package.
Export
The method in ONNXmutable uses dispatch for tracing and I think this is good enough for most typical/traditional DL models. It will however fail if it encounters
- (Non primitive) functions with type annotations (ie
function model(x::AbstractArray)) - Non-function expressions, e.g. if/else and for
I think IRTools can be used without too much effort to circumvent 1, but I dread to think about how to make use of it for 2. Mjolnir seems to have the perfect abstraction but I don’t know if it is effectively maintained and ready for use.
For exporting, I’m not as certain that there exist some simple and universal enough solution which is worthwhile to put in BaseOnnx. Should we try to make a generic package or two for this or just mash it together with the framework specific stuff?
Primitives
Primitives are the functions which have a one to one correspondence with an operator defined in ONNX, for example Add, sin, Conv, RNN etc. In other words, this is the part which knows how to transform e.g. a Flux.Conv into an ONNX NodeProto and vice versa.
I don’t think this can be done without manually typing out the mapping for each OP so in any moderately well designed ONNX package this will be the by far biggest effort to create and maintain, especially considering opset versions. To me this makes it quite important that adding OPs can be done easily so that users of the package can contribute with the OPs they need or else it will be a thankless and soul crushing effort to support the whole spec.
To me, this makes it pretty useful to have a package with only the primitives to attract contributions. Obviously one package per ML framework is needed as the primitive package has to depend on the ML framework package (e.g. Flux, KNet, Lilith etc).
Here we can start with the modest set of primitives from ONNXmutable as a start of the Flux package.
Furthermore, import and export have basically nothing to do with each other and I don’t see a way to make use of import primitives for export and vice versa. This opens up for having separate packages for import and export primitives, but it is also kinda nice to be able to test the import functionality with the export functionality and vice versa. Thoughts on this?
Another thought is whether it makes sense to have a package with OPs from Base (e.g. Add, tan, sin, max, reshape etc)?
Testing
Testing numerical computations is always annoying. In ONNXmutable I used 1) test vectors from onnx and 2) comparison with output from onnxruntime. Nothing says onnxruntime is the golden standard ofc, but it seems like a lot of work is being put into it so I think it makes for a pretty good reference.
There are a couple of lines of code to set all of this up and perhaps this can also be made a package which others can make use of (e.g. primitive packages for other ML frameworks).
Package homes
The packages which are specific to a particular ML framework are best served to sit in the same org as their parent frameworks, right? What about the reminders?
Assuming the above, the reminders are basically BaseOnnx, the testing package, the Base OPs and maybe a generic export package or two if it makes sense to create it.
I guess that creating a new org in github is no effort, but perhaps it is good to try to put it into some more well known org like JuliaML.
Another option is to just give up on components which are not tied to any framework and perhaps just create another monolith. I think the advantage of this over what ONNXmutable offers today then is basically that one does not need to depend on JuMP and Cbc. I can't imagine that this is the reason why people state that julia has no functional ONNX import/export though.