Also, Zygote is much more ambitious than Jax. Zygote aims to support all of the Julia language whereas Jax is limited to a subset of Python. I wonder if the Zygote folks are biting off too much here? Though currently Zygote doesn't support mutation.
Since Python is not compiled like Julia, the approach usually involves using custom types that store a separate intermediate representation from python (a custom one or the MLIR for example) and overloading functions (and perhaps other more sophisticated ways of metaprogramming) to map all the required operations while interpreting the Python code before compiling. That's why Zygote says it works on Zygote unaware libraries, as it doesn't store the graph within any of the types or directly overload any of the methods (it does need to know how to reverse them though), and also why it can directly operate on control structures since they are part of the full IR even when you can't overload them.
And while Zygote aims to fully support the Julia language, they'll certainly work on getting the most important operations working well enough before focusing on the more complicated stuff like mutation.
you want to work with the code reduced down to Single assignment form, and work with code blocks and the control flow graph. for a number of reasons: 1. all control flow looks the same now (no 2 different kinds of loops + GOTOs) 2. one expression per line, no need to untangle larger experessions 3. Host of techniques from compiler world can come out to be used.
This is how Zgyote works, it used code that has been lowered into this form
A year or two ago, it at least seemed to be a much more vibrant situation where there was more effort going into lots of different approaches, but then Zygote really picked up steam and it seemed like it was going to solve all our problems and cure cancer so development on the other packages slowed down, but now the 'finish line' for Zygote doesn't seem to be as close as we initially thought.
Also, I think performance is very important because Zygote is used in Flux (Julia's main deep learning framework at this point.) and if it's keeping Flux's performance from matching, say, PyTorch's then that's going to limit adoption of Flux and Julia. One of Julia's main claims to fame is performance so people coming to kick the tires are going to be disappointed if it's actually slower for this domain.
Besides, any implementation of AD is going to have downsides. Having many different, hot swappable implementations is quite nice because you can tailor towards your needs.
One of my on going projects is ChainRules (http://www.juliadiff.org/ChainRulesCore.jl/dev/) which will unite them under one set of custom senstitivities and more generally make it easier to mix and match them
I need to update the JuliaDiff website, I want to list all of them in a table with some some key points.
Forward mode:
- [ForwardDiff](https://github.com/JuliaDiff/ForwardDiff.jl)
- [ForwardDiff2](https://github.com/YingboMa//ForwardDiff2.jl)
Reverse Mode:
- [Nabla](https://github.com/invenia/Nabla.jl/)
- [Tracker](https://github.com/FluxML/Tracker.jl)
- [Yota](https://github.com/dfdx/Yota.jl)
- [Zygote](https://github.com/FluxML/Zygote.jl)
- [ReverseDiff](https://github.com/JuliaDiff/ReverseDiff.jl)
- [AutoGrad.jl](https://github.com/denizyuret/AutoGrad.jl)
- [NiLang](https://github.com/GiggleLiu/NiLang.jl) (arguably not reverse mode)
Symbolic:
- [ModelingToolKit](https://github.com/JuliaDiffEq/ModelingToolkit.jl)
- [XGrad.jl](https://github.com/dfdx/XGrad.jl)
Finite Differencing:
- [Calculus](https://github.com/JuliaMath/Calculus.jl) (please stop)
- [FiniteDifferences](https://github.com/JuliaDiff/FiniteDifferences.jl)
- [FiniteDiff](https://github.com/JuliaDiff/FiniteDiff.jl)