ONNXMLTools: A Conversion Layer for Models That Never Shipped an ONNX Exporter
ONNXMLTools enables conversion of models to ONNX
At a glance
- What is it?
- ONNXMLTools collects converters from Keras, Core ML, H2O, LightGBM, libsvm, XGBoost, CatBoost, Spark ML and scikit-learn into one Python package. The useful part is knowing which of those paths is a real implementation and which is a thin wrapper around someone else's project.
- Who is it for?
- Adopt ONNXMLTools when your trained artefact lives in Core ML, H2O MOJO, LightGBM, libsvm, XGBoost or CatBoost and you need an ONNX file without writing an operator mapping yourself. Skip it if you are converting PyTorch (use the built-in exporter) or scikit-learn and TensorFlow, where the README itself points at skl2onnx and tf2onnx and you would only be adding a layer.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 14 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Gap ONNXMLTools Fills, and the One It Does Not
Training frameworks and inference runtimes do not agree on a file format. ONNXMLTools exists to close that gap for toolkits that never shipped their own ONNX exporter. The README lists nine supported sources: TensorFlow, scikit-learn, Apple Core ML, Spark ML (marked experimental), LightGBM, libsvm, XGBoost, H2O and CatBoost. PyTorch is deliberately absent from that list, and the README says so directly, pointing readers at the built-in PyTorch ONNX exporter instead. That omission is a useful signal about the project's self-image. It is not trying to be the single door into ONNX. It is trying to be the door for the frameworks that forgot to build one.
The audience follows from that. If you trained a gradient boosted model in LightGBM or XGBoost and want to serve it through an ONNX runtime, or if you have a Core ML spec from an iOS pipeline and need it in a server-side graph, this package is aimed at you. The conversion is done in Python, on a machine that has the source framework installed, and the output is a protobuf file. There is no training involved and no runtime dependency on the source framework once the file is written.
Two Kinds of Converter Live in This Repository
The supported list is not homogeneous, and the README is explicit about it. TensorFlow support is described as "a wrapper of tf2onnx converter" and scikit-learn as "a wrapper of skl2onnx converter". Those two entries are re-exports, not independent implementations. When you call into them you are running code maintained in a different repository, with its own release cadence and its own issue tracker. That matters for debugging: a conversion failure on a scikit-learn pipeline is a skl2onnx problem, and pinning onnxmltools will not pin the behaviour you are actually exercising.
The remaining converters are the ones this project owns. Core ML, H2O, LightGBM, libsvm, XGBoost and CatBoost conversions are implemented here, and the README describes the underlying mechanism plainly. The converter walks the source model operator by operator, maps each one to its ONNX equivalent, and records the opset version in which that ONNX operator was most recently updated. The final model carries the maximum opset across all of its operators. This is a per-operator mapping strategy, not a whole-graph translation, and it explains most of the surprising behaviour users hit.
Why target_opset Is a Ceiling, Not a Setting
The README's explanation of target_opset is the single most important paragraph in the documentation, and it is easy to skim past. The parameter is optional and is passed to the convert function, for example onnxmltools.convert_keras(keras_model, target_opset=7). The natural reading is that you are choosing the opset of the output. That reading is wrong.
The README states that if the opset of your converted model comes back smaller than the target_opset you requested, this is likely intended behaviour, and it gives a worked example. A model containing Abs and Add, as of December 2018, would resolve to opset 7, because Add was most recently updated in opset 7 even though Abs was last touched in opset 6. Request target_opset=8 and you still get 7. The converter takes the maximum opset among the operators it actually emitted, not the number you asked for. The stated reason is backwards compatibility.
The practical consequence: target_opset constrains the converter, it does not command it. If you are targeting a runtime that only supports opset 9 and your model's operators top out at 7, you get 7 and that is fine. If you need a specific opset because a downstream tool requires it, this design will not get you there, and you would be looking at an ONNX version conversion step after the fact rather than a parameter on this call. The README offers a check for this: read onnx_model.opset_import[0].version, or open the file in Netron.
Installation and the Environment Variable That Trips People Up
The install path is ordinary. From PyPI: pip install onnxmltools. From source: pip install git+https://github.com/onnx/onnxmltools. The README attaches a condition to the source route that is worth reading twice: if you install onnxmltools from source, you must set the environment variable ONNX_ML=1 before installing the onnx package. That ordering is the trap. Setting the variable after onnx is already installed does not help, and the failure mode is a build that looks successful but is missing the ML operator set.
Base dependencies are ONNX, NumPy and ProtoBuf. Everything else is conditional on which converter you use, and the README enumerates them: scikit-learn, CoreMLTools (version 3.1 or lower), Keras (version 2.0.8 or higher) with the matching TensorFlow, LightGBM, SparkML, XGBoost, libsvm, H2O, CatBoost. Note the CoreMLTools ceiling. Version 3.1 or lower is a hard upper bound in the documentation, not a floor, which means a modern coremltools install is outside the supported range. If your Core ML model was produced by a recent toolchain, verify that constraint before assuming this path works.
Python 3.7 or higher is the stated floor. There is no stated ceiling, and the README does not claim testing against any specific newer interpreter.
Conversion Calls and Saving the Result
Each converter has its own entry point, and they are not uniform in signature. The Keras example builds a model with two sub-models joined by an Add layer and calls onnxmltools.convert_keras(keras_model, target_opset=7). The Core ML path takes a loaded spec and a name string: onnxmltools.convert_coreml(coreml_model, 'Example Model'), where the spec comes from coremltools.utils.load_spec('example.mlmodel'). The H2O path takes a filesystem path to a MOJO archive: onnxmltools.convert_h2o('/path/to/h2o/gbm_mojo.zip'), with the README noting the only prerequisite is a saved MOJO on the local filesystem.
Writing the result is separate from converting it. The README uses onnxmltools.utils.save_model(onnx_model, 'example.onnx') for that. The Core ML and H2O examples both include this step; the Keras example stops at the in-memory model and does not show a save call, so if you follow only that snippet you will have a model object and no file.
The test suite is run with pytest. The README gives python -m pytest --ignore .\tests\ as the command line, which is a Windows-style path separator, and notes that the converters' unit tests generate both the original and converted model and check them against onnxruntime or onnxruntime-gpu. Extra test dependencies are onnxruntime, numpy for most models, pandas for text-feature transforms, scipy for sparse features, and keras for one custom-operator test.
Where This Breaks Down
The per-operator mapping strategy is the main source of limitations. Operators that have no ONNX equivalent cannot be mapped, and the README does not provide a fallback for them. Custom layers are the obvious case, and the test suite's mention of a single keras test for a custom operator suggests the supported surface for custom operators is narrow rather than general. If your model contains a layer someone on your team wrote, assume you will be writing the mapping yourself.
The Spark ML entry is labelled experimental in the README. That label is the project's own, and it should be read as a warning rather than a maturity rating. Treat Spark ML conversion as unproven for anything you intend to put in production.
The CoreMLTools version cap is a second hard boundary. Documentation that specifies 3.1 or lower is documentation written against an older toolchain, and the README does not describe how the converter behaves against newer coremltools output. There is no statement about whether newer specs fail loudly or produce a subtly wrong graph. Verify with a model whose predictions you can check against the original.
Finally, the wrapper entries cut both ways. Using onnxmltools for TensorFlow or scikit-learn adds an indirection layer without adding capability. The README says as much by naming tf2onnx and skl2onnx as the underlying converters.
The Alternative: Going Straight to skl2onnx or tf2onnx
For scikit-learn and TensorFlow, the alternative is not a different format, it is one fewer dependency. skl2onnx and tf2onnx are the projects that onnxmltools wraps for those two frameworks. Calling them directly means you read their documentation, pin their version, and file issues in their tracker, and the conversion behaviour is identical because it is the same code. The difference is version resolution: with onnxmltools in the middle, the version of skl2onnx you get is determined by onnxmltools' dependency constraints, and it may lag what skl2onnx has released. That is a real cost for no gain if scikit-learn is your only source.
The trade-off flips for the frameworks onnxmltools implements itself. There is no separate LightGBM-to-ONNX project to go to, so the comparison is between onnxmltools and writing operator mappings by hand against the ONNX spec. For H2O MOJO the package is doing something you would otherwise have to build from the model archive format up. That is where the project earns its place: not as a universal front door, but as the only maintained front door for several specific frameworks.
Licence, Maintenance and What to Check Before You Commit
The licence is Apache-2.0, declared in the README's SPDX header and in the repository metadata. Apache-2.0 permits commercial use and modification and includes an express patent grant. It also requires that you preserve copyright and licence notices for the parts you redistribute. If you vendor the converter into a product, that notice obligation travels with it. This is a description of the licence text, not legal advice; have counsel review anything you redistribute.
The repository is not archived, the default branch is main, and the most recent release listed is 1.16.0 from January 2026, following v1.15.0 and a v1.15.0rc1 in the same month. Release candidates appearing alongside stable releases is normal for this project and means you should pin to the stable tags rather than tracking main. Maintenance cost is dominated by the source frameworks, not by onnxmltools itself: every time Core ML, LightGBM, XGBoost or CatBoost changes its serialisation format, the corresponding converter has to follow, and your pin has to move with it. Budget for that, and keep a regression test that runs the original and converted models side by side, which is exactly what the project's own test suite does.
What to verify first, concretely: pick one representative model per framework you depend on, convert it, and print onnx_model.opset_import[0].version to see what the per-operator maximum actually produced. Then run both models over the same input and compare outputs. If your source is Core ML, confirm your coremltools version against the documented 3.1 or lower bound before writing any integration code. If your source is Spark ML, remember the README calls it experimental.
Editorial conclusion
Adopt ONNXMLTools when your trained artefact lives in Core ML, H2O MOJO, LightGBM, libsvm, XGBoost or CatBoost and you need an ONNX file without writing an operator mapping yourself. Skip it if you are converting PyTorch (use the built-in exporter) or scikit-learn and TensorFlow, where the README itself points at skl2onnx and tf2onnx and you would only be adding a layer. Before committing, convert one representative model and print onnx_model.opset_import[0].version, because the converter resolves each operator to the opset where it was last updated and can return a lower number than the target_opset you passed.
Community notes