CLI tool
openxla/stablehlo avatar
openxla/stablehlo

StableHLO: the operation set that sits between ML frameworks and compilers

Backward compatible ML compute opset inspired by HLO/MHLO

711 stars229 forksMLIRApache-2.0

At a glance

What is it?
StableHLO is an MLIR dialect that gives machine learning frameworks and machine learning compilers a common intermediate vocabulary, and its distinguishing feature is not the operations themselves but the versioning and serialization guarantees wrapped around them. Building it means building MLIR first, which is the part of the setup that most people underestimate.
Who is it for?
Adopt StableHLO if you are building a compiler or a runtime that has to consume models from more than one framework, because the versioning and serialization guarantees are the whole point and the operation set alone would not be worth adopting. Do not adopt it if you only need one framework to one compiler path, since the portability benefit is zero there and the build cost is a full MLIR build.
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 1 day ago.
What is it written in?
Mainly MLIR, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A portability layer, described in the terms of who produces it and who consumes it

StableHLO is an operation set for high-level operations in machine learning models. The README puts it plainly: essentially, it is a portability layer between different ML frameworks and ML compilers, and ML frameworks that produce StableHLO programs are compatible with ML compilers that consume StableHLO programs.

That is a narrow definition and it is the useful one. StableHLO is not a framework, not a compiler, and not a runtime. It is the contract in the middle, and everything interesting about the project follows from the fact that both sides of the contract have to accept it.

The goal is stated as simplifying and accelerating ML development by creating more interoperability between various ML frameworks, such as TensorFlow, JAX and PyTorch, and ML compilers, such as XLA and IREE. That pairing of names is the whole strategy. A framework exports a StableHLO program, a compiler imports it, and neither has to know about the other's internals.

The technical base is the MHLO dialect, enhanced with additional functionality including serialization and versioning. MLIR bytecode is used as the serialization format, and the project provides backward and forward compatibility guarantees. The README states the consequence directly: this ensures compatibility between frameworks and compilers, even as StableHLO continues to evolve.

That last clause is the hard promise. Backward and forward compatibility across an evolving operation set is difficult, and the versioning scheme is what makes it tractable.

The repository contains the StableHLO specification along with an MLIR-based implementation in C++ and Python, usable to define StableHLO programs for consumption by compilers such as XLA and IREE, as well as on-device use via Google AI Edge.

Building it, and the MLIR prerequisite that dominates the setup

The build instructions are seven numbered steps and the shape of them is worth understanding before you start, because step five is the long pole.

CMake is the primary build tool, so you need CMake and Ninja installed. On Linux the guide recommends installing lld as well, having observed it to be noticeably faster than alternatives on their typical software and hardware configurations:

sh
# On Linux
sudo apt install cmake ninja-build lld ccache

# On macOS
brew install cmake ninja ccache

Then a shell variable decides whether the linker is lld:

sh
[ "$(uname)" != 'Darwin' ] && LLVM_ENABLE_LLD='ON' || LLVM_ENABLE_LLD='OFF'

The recommendation is ON on Linux and OFF on macOS, and the two-clause shell test is how that is expressed in a form you can paste.

The next three steps are about getting LLVM at exactly the right commit. You clone the StableHLO repo and then the LLVM repository, both with --depth=1 --single-branch, and then you check out the commit named in build_tools/llvm_version.txt. The checkout command is a single line that reads the hash, fetches it and checks it out, and the guide is explicit that you need to do this every time llvm_version.txt changes.

That is the mechanism that makes the build reproducible, and it is also a recurring cost, because a pinned LLVM commit is a moving target that follows upstream.

Then MLIR is built, with the Python bindings switched off:

sh
MLIR_ENABLE_BINDINGS_PYTHON=OFF build_tools/build_mlir.sh "${PWD}"/llvm-project/ "${PWD}"/llvm-build

The guide says this will take a considerable amount of time, and gives a concrete figure: on a MacBook Pro with an M1 Pro chip, building MLIR took around 10 minutes at the moment of writing. And again, you need to do this every time llvm_version.txt changes.

So the honest cost of building StableHLO is not the StableHLO build, it is the MLIR build, and it is not a one-off if you follow the pinned LLVM commit.

The two CMake configurations, and what the developer one adds

Building StableHLO as a standalone library is a configure and a build, and the two configurations the guide describes differ only in the flags.

The release configuration sets the build type to Release, turns LLVM assertions on, disables the Python bindings and points MLIR_DIR at the MLIR you just built:

sh
mkdir -p build && cd build

cmake .. -GNinja \
  -DLLVM_ENABLE_LLD="$LLVM_ENABLE_LLD" \
  -DCMAKE_BUILD_TYPE='Release' \
  -DLLVM_ENABLE_ASSERTIONS='ON' \
  -DSTABLEHLO_ENABLE_BINDINGS_PYTHON='OFF' \
  -DMLIR_DIR="${PWD}"/../llvm-build/lib/cmake/mlir

cmake --build .

The developer configuration replaces the build type with RelWithDebInfo and adds a set of flags that are worth reading individually.

STABLEHLO_ENABLE_LLD=ON links with lld rather than the default linker. STABLEHLO_ENABLE_SPLIT_DWARF=ON splits the debug information, and the guide explains the effect: it enables debug symbols and ccache, which can speed up incremental builds, and it creates a GDB index file in the binary to speed up debugging. CMAKE_CXX_COMPILER_LAUNCHER and CMAKE_C_COMPILER_LAUNCHER are set to ccache, and CMAKE_EXPORT_COMPILE_COMMANDS=ON produces a compilation database, which is what a language server or an editor integration needs to index the C++ sources.

STABLEHLO_ENABLE_SANITIZER=address turns on the address sanitizer, which is the one flag on this list that changes what the code does rather than how it is built.

There is one more note, and it is the kind of detail that saves an hour. If you build MLIR using the script above it should also set by default LLVM_USE_SPLIT_DWARF, which does the majority of the size saving for the binary and should also be set.

Assertions being on in both configurations is worth noticing. For a compiler infrastructure project, assertion failures in a release build are a deliberate choice, because the alternative is silently miscompiling a model.

The Python bindings, and the nightly wheels that skip the build entirely

There are two ways to get Python bindings, and the second one is the reason most people will never build MLIR.

To build them yourself you need a few additional dependencies:

sh
pip install -r ./llvm-project/mlir/python/requirements.txt

And then build StableHLO with the bindings enabled, using the CI script rather than the manual sequence:

sh
STABLEHLO_ENABLE_BINDINGS_PYTHON=ON ./build_tools/github_actions/ci_build_cmake.sh "${PWD}"/llvm-build "${PWD}"/build

That reuse of the CI script is a small convenience with a real effect, because it means the local build path and the continuous integration path are the same code, so a local build is not a different configuration from what CI tested.

After building, you point Python at the built package:

shell
$ PYTHONPATH='./build/python_packages/stablehlo' python3
Python 3.11.6 (main, Oct  8 2023, 05:06:43) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import mlir.dialects.stablehlo
>>> from mlir.ir import Context, Location
>>> import mlir.dialects.arith

The imports are the point. You import the dialect as mlir.dialects.stablehlo, alongside the core MLIR IR types and the arith dialect, which is what tells you the design: StableHLO is a dialect in a dialect-based IR, not a standalone library with its own object model.

But you can also build a wheel yourself using the setup.py file, and the project makes nightly wheels available on its GitHub Releases page:

shell
pip install stablehlo -f https://github.com/openxla/stablehlo/releases/expanded_assets/dev-wheels

That last line is the pragmatic path for evaluation. A nightly wheel means you can try the Python bindings on a laptop in a minute, and if the idea does not work for you, you have not spent an hour on an MLIR build.

The repository also offers tooling for converting a StableHLO program, including its metadata representing trained weights and biases, into a TensorFlow SavedModel, with the details in a README under the Python integrations directory.

The release history, and a project whose pace slowed after 1.0

The release list is short in a way that tells you something. v1.0.0, titled the StableHLO 1.0 Release, came on 2024-05-14, before that a dev-wheels release on 2024-01-26 and v0.9.0 on 2023-03-02. The last push to the repository was on 2026-10-20, and the repository is not archived.

So the source is being worked on daily while the tagged releases have not moved past 1.0 since 2024. That is the opposite of what the release cadence suggests, and it is worth understanding rather than guessing at.

The likely explanation is in the nature of the project. A portability layer that guarantees backward and forward compatibility cannot ship a new operation set in a way that breaks that guarantee, so changes that alter the operation set land on the development branch and on nightly wheels, while the tagged 1.0 release remains the reference point that existing producers and consumers are compatible with. The dev-wheels release in the list is the mechanism for that, and it is the same nightly wheel stream the pip install line above points at.

The licence is Apache-2.0, and the project describes itself as a community effort: building an amazing portability layer between ML frameworks and ML compilers requires collaboration across the whole ML industry. Development is organised through GitHub issues and pull requests, with openxla-discuss for longer discussions and a #stablehlo channel on the OpenXLA Discord server.

The repository layout supports that community framing. There is a docs/ directory with the specification, the bytecode format description and the compatibility guarantees as separate documents, an rfcs/ directory, an examples/ directory with C++ and Python variants, build_tools/ holding the scripts the build instructions reference, a cmake/ directory, and third_party/ for vendored dependencies. The build system is a mix of Bazel and CMake, with BUILD.bazel, WORKSPACE.bazel, MODULE.bazel and a .bazelversion, alongside the CMakeLists.txt and CMakePresets.json, which reflects a project that supports both toolchains while migrating.

The test command is one line, ninja check-stablehlo-tests, and the guide shows what a healthy run looks like: 246 tests discovered and 246 passed, in a little over six seconds.

Editorial conclusion

Adopt StableHLO if you are building a compiler or a runtime that has to consume models from more than one framework, because the versioning and serialization guarantees are the whole point and the operation set alone would not be worth adopting. Do not adopt it if you only need one framework to one compiler path, since the portability benefit is zero there and the build cost is a full MLIR build. Before you build, read the compatibility document, and budget for the LLVM clone and MLIR build, which the guide itself describes as taking a considerable amount of time.

Frequently asked questions

What is StableHLO and where does it fit?

It is an operation set for high-level operations in machine learning models, essentially a portability layer between ML frameworks and ML compilers. Frameworks such as TensorFlow, JAX and PyTorch produce StableHLO programs, and compilers such as XLA and IREE consume them, so neither side needs to know about the other.

What makes StableHLO different from the MHLO dialect it is based on?

StableHLO is based on the MHLO dialect and enhances it with additional functionality, including serialization and versioning. MLIR bytecode is the serialization format, and the project provides backward and forward compatibility guarantees so compatibility holds even as StableHLO evolves.

What do I need before building StableHLO from source?

CMake and Ninja, and on Linux the guide also recommends lld and ccache. You then clone the LLVM repository at the commit named in build_tools/llvm_version.txt and build MLIR from it, which the guide says takes a considerable amount of time and has to be redone whenever that file changes.

How do I install the Python bindings without building everything?

Nightly wheels are published on the GitHub Releases page, and the documented install is pip install stablehlo with the dev-wheels expanded assets URL. You can also build a wheel yourself with the setup.py file, or build with Python bindings enabled using the CI cmake script.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. openxla/stablehlo on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openxla-stablehlo.svg)](https://hysenlabs.com/projects/openxla-stablehlo)

Was this useful?