Open-source project
EnzymeAD/Enzyme.jl avatar
EnzymeAD/Enzyme.jl

Enzyme.jl: LLVM-level automatic differentiation called from Julia

Julia bindings for the Enzyme automatic differentiator

586 stars108 forksJuliaMIT

At a glance

What is it?
Enzyme.jl is the Julia binding layer for Enzyme, an LLVM plugin that differentiates statically analyzable LLVM. It is a work in progress by its own description, and the README's single worked example is a scalar reverse-mode gradient.
Who is it for?
Adopt Enzyme.jl if your differentiation target is ordinary Julia or LLVM code that you want differentiated after optimization, and if you can tolerate a package the README itself labels a work in progress. Do not adopt it as a drop-in replacement for a mature pure-Julia AD package if your code relies on constructs that resist static analysis, or if you need a stable API surface across versions.
Can I use it commercially?
Yes. MIT 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 Julia, 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 problem Enzyme.jl addresses: differentiating code you did not write for AD

Most automatic differentiation in Julia happens at the level of the language. A tool traces or transforms Julia source, which means the differentiation machinery has to understand every construct the language allows. Enzyme takes the opposite route. The README describes Enzyme as "a plugin that performs automatic differentiation (AD) of statically analyzable LLVM," and Enzyme.jl as "a package containing the Julia bindings" for it. The target of differentiation is not your Julia function as written. It is the LLVM that Julia's compiler produced from it.

That distinction matters for who this is for. If you are differentiating a function whose body is a few array operations and you are happy with an existing Julia AD package, Enzyme.jl offers you little you cannot already get. The case it is built for is different: code that has already been optimized by the compiler before differentiation runs, and code that reaches outside pure Julia semantics. The README makes the performance argument explicitly, stating that Enzyme is "highly-efficient and its ability perform AD on optimized code allows Enzyme to meet or exceed the performance of state-of-the-art AD tools." Treat that as the project's claim about its own design, not as a measured result.

The second paper cited in the README, from SC '21, is titled "Reverse-Mode Automatic Differentiation and Optimization of GPU Kernels via Enzyme," and its keyword list includes CUDA, ROCm, LLVM, HPC and GPU. That is the strongest signal in the supplied material about the intended audience: people differentiating kernels and numeric code where the boundary between Julia and generated machine code is where the interesting work happens.

How the mechanism works: Julia IR becomes LLVM, and the plugin differentiates that

The data flow implied by the README is a three-stage pipeline. You write Julia. Julia's own compiler lowers that to LLVM IR. Enzyme, running as an LLVM plugin, differentiates the IR. Enzyme.jl is the surface that lets you start that pipeline from Julia and get results back.

That ordering is why the README stresses "statically analyzable" LLVM. The plugin needs to see the code it is differentiating. Anything that produces LLVM the plugin cannot inspect, or that only becomes known at runtime, sits outside what the mechanism can reach. This is a real architectural boundary, not a documentation caveat. A language-level AD tool can sometimes fall back to an interpreter or a tracing runtime when it hits something it cannot transform. An LLVM plugin has no such fallback in the description given here; the constraint is a property of where in the compilation stack the differentiation happens.

The payoff is that differentiation sees the optimized form. Constant folding, inlining and other transformations have already happened when the plugin runs. The README's phrasing is that performing AD on optimized code is what allows Enzyme to meet or exceed other tools. The cost is that the plugin must handle whatever the optimizer emitted, which is a harder and less predictable input than the source you originally wrote.

The API reflects this. The README's example calls autodiff with a mode argument (Reverse), a function, and an Active wrapper around the input value. Active marks which arguments participate in differentiation, which is necessary because the plugin is differentiating a compiled function whose argument types are fixed. The return is described as "a tuple of active returns," and the example indexes into it twice: first(autodiff(Reverse, f1, Active(1.0))[1]). The outer [1] selects the first return group, and first selects the first active return within it. For f1(x) = x*x at 1.0 the expected value is 2.0, and the README writes the check as a test assertion rather than a printed result.

Installing and calling it: the commands and the one documented API shape

Installation uses the standard Julia package workflow. The README gives it as a REPL snippet:

] add Enzyme

The leading bracket enters Pkg mode, so the equivalent non-REPL form is using Pkg; Pkg.add("Enzyme"), though the README does not spell that out. The package name is Enzyme, not Enzyme.jl; the .jl suffix is the repository convention.

Usage starts with using Enzyme. The README's example also does using Test, because the demonstration is written as a test assertion:

using Enzyme, Test f1(x) = x*x @test first(autodiff(Reverse, f1, Active(1.0))[1]) ≈ 2.0

Three API elements are visible here and nothing more. First, the mode: Reverse, passed as the first positional argument. The README does not document the other modes in the supplied text, though the existence of a mode argument implies there is at least one alternative. Second, Active, a wrapper applied to the input 1.0. Third, the indexing pattern on the result, which the README describes as a tuple of active returns. If you are evaluating this package, that indexing is the part most likely to trip you up, because the tuple has an extra layer compared with what a plain gradient call would return.

The README points to https://enzyme.mit.edu/julia for full documentation and distinguishes stable from dev builds with two badges. It also points to https://enzyme.mit.edu for information on "installing and using Enzyme directly (not through Julia)." That second link is where you would look if the Julia binding is not the layer you need. For questions, the README directs readers to a Google Group mailing list at groups.google.com/d/forum/enzyme-dev rather than to a chat channel.

The work-in-progress label is the main limitation to plan around

The README opens with a sentence that should shape any adoption decision: "This is very much a work in progress and bug reports/discussion is greatly appreciated!" That is the project describing itself, and it is consistent with the release history in the supplied material. Three releases landed in September 2026: v0.13.200 on the 4th, v0.13.201 on the 5th, and v0.13.202 on the 8th. A patch number that advances by one, three times in four days, is a cadence you should read as active bug-fixing rather than as a stable, frozen API.

The version numbering reinforces this. The package is in the 0.13.x line. Under semantic versioning conventions, a 0.x version carries no compatibility promise for minor releases, and here even the patch releases are moving quickly. If you pin Enzyme.jl in a project that must build reproducibly months later, pin the exact version and expect to re-test when you move it.

The second limitation follows from the mechanism. Differentiation targets statically analyzable LLVM. Code that resists static analysis is where this approach is the wrong tool, and the README gives no fallback story for that case. If your function dispatches dynamically in ways the compiler cannot resolve, or reaches into runtime behaviour that does not exist at the IR level, the plugin has nothing to differentiate. The supplied material does not enumerate which Julia constructs fall on which side of that line, so the honest position is that you must test your own code rather than assume coverage.

A third constraint is environmental. Enzyme is an LLVM plugin. Enzyme.jl is bindings to it. Nothing in the README explains how the plugin binary is delivered to your machine, whether it ships as a Julia artifact, or what happens if your Julia build uses an LLVM version the plugin was not compiled against. That is exactly the kind of thing that produces an install that succeeds and an autodiff call that fails, and the README does not address it.

How this differs from source-level AD packages such as Zygote

The natural comparison in Julia is Zygote, which differentiates Julia code by transforming it at the source and IR level within the language, rather than by operating on LLVM after the compiler has finished. The difference in approach has consequences on both ends.

Zygote's model means the differentiation rules are expressed in terms of Julia constructs, and users extend it by writing adjoints for their own functions in Julia. Enzyme.jl's model means the differentiation happens below that layer, on the optimized IR. The README's claim that AD on optimized code is what lets Enzyme "meet or exceed the performance of state-of-the-art AD tools" is the project's stated reason for choosing the lower layer. Whether that holds for your workload is something you would have to measure; the supplied material contains no benchmark numbers.

The mutation question is where the two approaches diverge most sharply in practice. A source-level tool has to reason about Julia semantics to decide whether a mutation is safe to differentiate. An LLVM-level plugin sees the mutation after it has been lowered to memory operations, which is a different and in some respects more tractable problem. The README does not make this argument, and I am not attributing it to the project. What the README does support is the general point: differentiating optimized LLVM is a different problem from differentiating Julia source, and the two tools are not interchangeable implementations of the same idea.

The practical difference for a user is extension. With a source-level package you write Julia adjoints when the tool cannot handle something. With Enzyme.jl, the escape hatch, if one exists, lives at a different layer, and the README does not describe it. That asymmetry is worth knowing before you commit to either.

Maintenance cost, licence, and what the citation requirement means in practice

Enzyme.jl is MIT licensed, and the repository is not archived. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and permission notice are retained. That is the substance of the licence, and it is not legal advice; if you are shipping this in a commercial product, have someone qualified read the actual LICENSE file in the repository rather than this summary.

The README adds a request that is separate from the licence: "If using this code in an academic setting, please cite the following two papers." It supplies full BibTeX for both, the NeurIPS 2020 paper on synthesizing fast gradients and the SC '21 paper on reverse-mode AD and optimization of GPU kernels. A citation request is a norm of the academic community, not a licence condition, and the two should not be confused. Nothing in the MIT licence obliges you to cite. But if you are publishing research that uses Enzyme.jl, the README's authors have asked for both citations, and the second one specifically covers GPU and optimization work, so citing only the first would understate what you used.

Maintenance cost on your side has two components visible in the material. The first is version churn. With three patch releases in four days in September 2026, you should expect to re-run your differentiation tests when you upgrade, and you should not assume a patch bump is inert. The second is the work-in-progress status. A project that describes itself that way is telling you that you may need to file bugs and follow discussion rather than wait for a fix. The README routes that through the enzyme-dev mailing list, which is a slower channel than an issue tracker but is where the project points you.

Who should adopt Enzyme.jl, and what to check before you do

The fit is narrow and specific. You should look at Enzyme.jl if you are differentiating Julia code where the optimized form is what you care about, if you are working on GPU kernels or numeric code in the CUDA or ROCm space that the SC '21 paper covers, or if you need differentiation to happen below the language layer for reasons your own profiling has established. You should not reach for it if you want a stable API you can set and forget, if your differentiation targets are ordinary Julia functions that an existing source-level package already handles, or if your code depends on dynamic behaviour that static analysis cannot resolve.

The README's own framing should carry weight in that decision. "This is very much a work in progress" is not marketing modesty; it is the maintainers telling you what to expect.

Before you build on it, verify four things against the live documentation at enzyme.mit.edu/julia rather than against this article. Check the exact return shape of autodiff for your function's arity and argument types, since the README's example only shows a single scalar input and the double indexing may not generalize the way you assume. Check which Julia and LLVM versions the current release supports. Check how the plugin is delivered, because the README does not say and that is the most likely source of an install that appears to work and then fails at the first call. And check the release notes for v0.13.200 through v0.13.202 to see whether the recent patches touched the API surface you plan to use. If all four come back clean for your workload, the LLVM-level approach is worth the setup cost. If any of them is unclear, that ambiguity is itself the answer.

Editorial conclusion

Adopt Enzyme.jl if your differentiation target is ordinary Julia or LLVM code that you want differentiated after optimization, and if you can tolerate a package the README itself labels a work in progress. Do not adopt it as a drop-in replacement for a mature pure-Julia AD package if your code relies on constructs that resist static analysis, or if you need a stable API surface across versions. Before committing, verify three things against the current docs: that autodiff(Reverse, ...) returns what you expect for your function's signature, that your Julia version is one the package supports, and whether the LLVM plugin is bundled by the artifact or must be supplied by your environment. The release cadence (v0.13.200, v0.13.201, v0.13.202 within four days in September 2026) is the maintenance signal to weigh.

Official sources

  1. EnzymeAD/Enzyme.jl on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes