OCANNL is sponsored by Ahrefs! Visit the Ahrefs website.
- A from-scratch, compiled Deep Learning framework.
- Implements backpropagation (i.e. first-order reverse mode autodiff) and shape inference.
- The long-term goal is to provide several "low-level" backends, aiming to seek inspiration from projects such as tinygrad, TVM, Luminal.
- OCANNL starts with a high-level representation, but can compile everything down to
forloops.
- OCANNL starts with a high-level representation, but can compile everything down to
- The library users can compile any amount of code into a routine (i.e. a compilation unit). The user decides explicitly what the scope of a compilation unit is, by putting together the corresponding code. Depending on the use case:
- the whole training update step can be a single routine,
- or the step can be composed of a gradient update routine (a forward pass and a backprop pass) and a params update routine (e.g. SGD with momentum, ADAM, etc.),
- or the user can compile parts of a model separately, manually composing the corresponding forward pass code and the backprop code.
- Tensor axes are split into kinds: batch, input and output. Tensor dimensions have an optional basis.
- The basis (aka dimension units) ensures a more precise semantics for dimension matching. It's not an axis selection mechanism.
- OCANNL has full support for a significantly extended
einsumnotation, integrated with shape inference. See comparison with einops for how this relates to the popular einops library. Supports static indexing, with a built-in operation to take a slice of the batch axes, integrated with shape inference. Extensible to more static indexing patterns as needs arise.- OCANNL does not have dynamic indexing (using the last axis of one tensor as indices into another tensor). If it's needed, it can be added (we had a prototype once, removed to reduce complexity). Then it would also be integrated with shape inference.
- OCANNL offers two main levels of abstraction.
- Tensor expressions as differentiable computations, centered around the
%opsyntax extension.%opstands for "operation", it's meant to express tensors:Tensor.t, and tensor functions.
- Plain computations, centered around the
%cdsyntax extension. It integrates thearrayjitbackend library with shape inference.%cdstands for "code", it's meant to express assignment computations:Assignments.comp.
- Tensor expressions as differentiable computations, centered around the
- Fully supports mixed-precision computations, with bidirectional precision inference.
- E.g. higher-precision network components, or gradients at a higher precision than values.
- Should be easily extensible.
- Model surgery should be straightforward (not sure if we are there yet).
The CUDA backend requires at least CUDA version 12.8. The Metal backend requires at least MSL version 3.1. The HIP backend (AMD GPUs) requires ROCm / the AMD HIP SDK, via the hipjit bindings (opam install hipjit).
API documentation entry point.
A possible route to learning OCANNL:
- Read the introductory slides.
- Read: shapes and the generalized einsum beginner-to-advanced slides.
- Read Tensors and Contexts and, for the runtime API,
Context. - Read the migration guide.
- Read the syntax extensions documentation docs/syntax_extensions.md.
- Read the NN building blocks file lib/nn_blocks.ml and the training recipes lib/train.ml.
- Work through the makemore tutorial — a character-level language-model progression mirroring Andrej Karpathy's Neural Networks: Zero to Hero lectures.
- Read the introductory part of the shape inference documentation docs/shape_inference.md.
- For the paper-facing account, read the workshop article docs/ocannl_workshop_article_human.md, the formal core technical report ocannl-formal-core-technical-report.pdf (LaTeX source in docs/), and the constraint-generation notes docs/shape-constraint-generation.md.
- Skim the configuration documentation ocannl_config.reference.
- Improve your understanding by reading or skimming the framework internals: tensor/shape.mli, tensor/tensor.mli, tensor/operation.ml, arrayjit/lib/context.mli.
- Read the implementation overview:
- The various tests.
- Shape inference details docs/shape_inference.md.
- Backend-independent optimizations docs/lowering_and_inlining.md -- lowering means translating (compiling) from the high-level representation (as assignments) to the low-level representation.
- Schedules and autotuning docs/schedules_and_autotuning.md -- the loop-nest transform layer (parallelization, tiling, staging, tensor cores) and the empirical search over it.
To use debugging as provided by configuring Utils.settings.debug_log_from_routines <- true with the cuda or hip backend, wrap the code that schedules work and synchronizes the GPU with Utils.capture_stdout_logs. Both GPU APIs expose device-side printf, but not fprintf; the runtime drains the device printing buffer to process stdout around synchronization. Synchronize the context inside the capture window so all device output is available before stdout is restored.
NOTE: debug logging from CUDA or HIP in complex settings is a bit tricky, as it involves another thread (domain) intercepting and filtering stdout. If facing issues, try the setting never_capture_stdout=true (see ocannl_config.reference).
See ROADMAP.md for the detailed schedule. GitHub issue assignments are the source of truth for release scope. Headline target: ICFP 2026 week (August 24, 2026).
Note (July 2026): v0.7 shipped on July 3, 2026 as the consolidated paper-ready release. v0.6.4 was skipped as a release — its work (concatenation, RoPE, transformer toy) shipped inside v0.7 — and v0.7.2 was consolidated into v0.7. v0.7.1 was dissolved: its AMD HIP backend (#411) shipped in v0.8; completed examples and tokenizer work landed subsequently, while remaining work follows the current GitHub milestone assignments. The sequence is now
0.7 → 0.8 → 0.9 → 1.0 → 1.1.
- 0.7 (Jul 3, 2026, released): Frontend finalization + compiler optimizations. The consolidated paper-ready release for workshop submissions (OCaml Workshop, FProPer). Absorbs the former v0.6.4/v0.6.5/v0.7.0 frontend work and the former v0.7.2 optimization work.
- Migrate from the "hosted tensor" idea to always requiring a context when accessing tensors and dealing with devices directly; remove the
arrayfield ofTnode.tand the hosted memory mode (#333). - Tensor saving, loading, and restoring (#373).
- Axis concatenation in the einsum syntax (
a^b), generalizing tensor stacking; shifting (1^i=>i) and padding (i=>1^i) as fixed-index special cases (#49). - RoPE and other non-learned position embeddings (#398); decoder-only autoregressive transformer toy (#57).
- Ternary einsum notation (#305); loop-invariant hoisting (#350) and common subexpression elimination (#351).
- Universal Pool Allocator across backends (#344): per-context-delta working pools, per-device constant pools, reserved merge pool, and pooled Metal bindings.
- Sharding primitives, data-parallel training driver, and zero-copy leading-axis slice views (#293).
- Workshop article, formal core technical report, and shape-constraint-generation notes.
- Migrate from the "hosted tensor" idea to always requiring a context when accessing tensors and dealing with devices directly; remove the
- 0.8 (Jul 13, 2026, released): Parallel schedules, autotuning, tensor cores, and AMD HIP.
- Schedule transforms and generated CPU/GPU kernels harvested from the Böhm CPU/CUDA matmul articles and llm.c (#412).
- Kernel fission, hardware-mapped loop axes, shared staging, packed/register-tiled
Tile_mma, and explicit SIMD codegen. - Measured schedule autotuning with caches, sketch seeds, and per-segment candidates.
- CUDA WMMA/inline-PTX, Metal simdgroup-matrix, and HIP rocWMMA tensor-core paths.
- HIP backend for AMD hardware via the independent hipjit bindings (#411).
- Native Windows support via mingw-w64; an additional MSVC toolchain was evaluated and closed as not planned (#313).
- 0.9 (Aug 24, 2026 — ICFP week): Schedule quality, deterministic parallelism, and convolution performance.
- Cross-machine benchmark/tuning sweep, an analytic default-schedule cost model, and constraint-based schedule legality (#476, #491, #494).
- Deterministic split reductions, CUDA/HIP graph capture, and a mixed-precision training recipe (#484, #488, #492).
- Convolution schedule families and boundary handling: tiled sketches, epilogue twins, compact strided staging, and clamped windows (#500, #501, #502, #504).
- Fix overlapping-window tropical/einmax1 gradients (#512).
- Tensor-core hardening delivered tf32 policy, CUDA 13 support, pad-to-tile scheduling, static partitioning, and packed-uniform tails (#478, #482, #485, #508, #509).
- CNN classifiers (#54), GPT-2 inference (#377), and the TVM, Tiramisu, and superoptimizer studies (#242, #267, #261).
- 1.0 (Sep 30, 2026): Release completeness, training/deployment utilities, and advanced compiler tiers.
- Training and deployment: resumable checkpoints, inference binaries, experiment tracking, training-loop utilities, mmap checkpoints, and tracing design (#96, #97, #122, #465, #467, #160).
- User-facing library study: implications of Simply/NanoDO for
lib/(#435). - Advanced schedules and algorithms: CUDA tensor-core completeness, fused attention, software pipelining, rematerialization, remaining convolution tiers, and branch-and-bound schedule inference (#481, #483, #487, #498, #503, #505, #514).
- Frontend and diagnostics:
%opinline-initializer scoping and routine-name collision policy (#511, #513). - Roadmap-only ergonomics: concise merge-buffer transfer composition and execution-dependency tracking.
- 1.1 (no target date): Shape design, model examples, integrations, and deferred backend experiments.
- Shape schemes and the axis-label design direction (#404).
- Model surgery, LSTM and Bonsai RNN examples, digit addition, BERT/ModernBERT, and DisTrO (#33, #60, #182, #427, #297, #278).
- Plot polish, local
%cdlets, Polars integration, and external-framework study (#103, #80, #219, #277). - CUDA pinned/constant host-memory experiments, PoPE, and HIP CDNA tensor cores (#170, #195, #444, #477).
For more details, see CHANGES.
- 0.7: Frontend finalization, compiler optimizations, and paper-ready formal docs.
- Removed hosted tensors in favor of explicit context-mediated access.
- Added axis concatenation/block tensors, RoPE, the decoder-only transformer toy, ternary einsum, sharding primitives, and zero-copy leading-axis slice views.
- Added loop hoisting, CSE, broader virtual-node inlining, and the universal pool allocator across backends.
- Added the workshop article, formal core technical report, and shape-constraint-generation notes.
- 0.6.3: Padding inference for convolutions.
- Padding inference during shape inference.
- Toy CNN example: circle counting.
- 0.6.2: "you forgot to specify a hidden dimension".
- Menhir einsum parser.
- Detection of user errors where there is missing information about a hidden dimension: disables guessing "no axes" or "dimension 1" for shapes of parameters.
- 0.6.1: Syntax extension improvements, transformer building blocks.
- Heterogeneous precision operations.
- Counter-based randomness via threefry, second pass (pointwise and weak-but-efficient variants); normal distribution operation.
- New syntax for inline parameter definitions; record-based syntax instead of string-based.
- Add transformer and convnet building blocks.
- Better shape error messages.
- 0.6: more precisions, initialization, counter-based randomness, strided iteration.
- BF16, FP8.
- Extended expressivity of projections and the generalized einsum notation to cover strided iteration and convolution.
- Parameter initialization on devices.
- Counter-based randomness via threefry, first pass (vectorized and cryptographic strength).
- Better precision inference, including top-down propagation.
- 0.5.3: Apple Metal backend.
- Also, CUDA backend works on native Windows.
- 0.5.2: More primitive operations.
- Supports a lot of primitive operations (including ternary ops), and ternary tensor operations.
%cdand%opsupport both curried and uncurried operator application syntax.- More flexible gradient construction via the
%cdsyntax (better projections inference). - Works on Native Windows with the C compiler backend (but CUDA backend blocked by cudajit still).
- 0.5.1: Automatic synchronization and transfers between host and devices.
- 0.5.0: Stream-to-stream synchronization at the buffer level.
- Support for CUDA events, and
Condition-based events for CPU backends. - Overhaul of the backend interfaces, both user-facing but especially internal: full code sharing.
- Automatic stream-to-stream synchronization on a per-tensor-node basis.
- Support for CUDA events, and
- 0.4.1 Half precision, mixed precision, CUDA virtual devices (virtual devices renamed to streams in 0.5.0)
- Half precision. Maybe improvements for mixed-precision computations.
- Resolve remaining issues with the new scheduler.
- Initial version of lib/nn_blocks.ml.
- v0.4 Merge buffers, C-syntax backend builder: a significant refactoring of the API.
- v0.3 Shape inference, jitted routines: a major rewrite of the whole project.
- v0.3.3: continuous integration and opam release.
- v0.3.2: new shape inference feature: tracking leftmost axes -- complete inference for splicing, ellipsis-in-the-middle allowed in einsum notation.
- v0.3.1: sanitizing code inclusion (rootness checks).
- v0.3.0: declarative shape inference; replaced the session interface with a "jitted code routines" API. Cuda defunct.
- v0.2 Inching toward GPU:
- v0.2.1 naive-cuda: a Cuda backend where blocks and threads are exposed via dedicated axis types.
- v0.2.0 stack-as-device: treating the C function stack as the "device memory".
- v0.1 GCCJIT backend:
- v0.1.2: multicore computations using a thread-local "task id" index.
- v0.1.1: inlining scalar constants, improved inlining for virtual nodes.
- v0.1.0: a
Gccjitbackend, single and double precision floats, code compiled as a monolithic update step function.
- v0.0 Untagged: basic design around shape inference, high-level and low-level code representation. Now-abandoned Meta-OCaml and OCaml backends.
Why not just use OWL?
OCANNL follows different design choices than OWL. For example:
- OCANNL is not functorized, except that it uses first-class modules for backends.
- OCANNL has fewer abstraction layers.
- OCANNL has a more powerful shape inference.
- OCANNL only supports backpropagation, while OWL supports full forward and backward auto-diff.
- Some aspects are more centralized in OCANNL than in OWL and form the "infrastructure":
- Tensor indexing mechanisms are not extensible, other than changing OCANNL code.
- Shape inference is fully handled by OCANNL and not extensible, other than changing OCANNL code.
Tensorimplements "putting pieces together".Trainhas the optimization "frontend" and utilities.arrayjit, which may one day become a standalone library: generates the code, performs backend-agnostic optimizations (virtual nodes whose computation is inlined), implements the backends.
- Some aspects that are more core to OWL are less encapsulated in OCANNL, so it should be more natural to extend them.
- OCANNL provides lower-level compilation backends than OWL, it is more self-contained in this sense.
Although the project is called ocannl, the main package is called neural_nets_lib, to avoid the (opam linter's) complaint that the name can be confused with other packages. This also clarifies that ocannl is composed of arrayjit and neural_nets_lib.
The dependency on cudajit is optional so you have to install it first to enable the CUDA backend. The dependency on metal is MacOS-specific but automatic.
The codebase is organized to separate user-facing recipes from framework internals:
-
lib/: User-facing recipes and utilitiestrain.ml- Training utilities and optimizersnn_blocks.ml- Neural network building blocks (transformers, attention, convolution, etc.)ocannl.ml- Re-exports for backward compatibility
-
tensor/: Framework internals (separate libraryocannl_tensor)tensor.ml/mli- Core tensor type and operationsshape.ml/mli- Shape inference systemoperation.ml- Tensor operations and DSL modulesppx_*.ml- Syntax extensions implementation
-
arrayjit/: Low-level optimizing compiler with multiple backends
NOTE TO POTENTIAL CONTRIBUTORS: while I am might be slowly starting to work with PRs in separate branches rather than just a stream of commits on the main branch, design migrations will be broken into small PRs to avoid main (master) branch staleness; and many changes will still be commits on the main branch. We allow for failing tests on the main branch, although going forward this would hopefully be happening less. Tagged i.e. released versions of the code are guaranteed to work as well as the given stage of the project permitted, the policy is that all tests must pass for releases with the backend sync_cc and must have the behavior expected of a backend with all other backends. We try to minimize discrepancy across backends but prefer more stringent tests even if some backends only pass them "in spirit" rather than with exact expectations of the sync_cc backend.
OCANNL uses ppx_minidebug for debugging. Currently, we migrated to a per-file opt-in scheme for enabling ppx_minidebug at compile time (via environment variables, see the top of .ml files in question), and then a unified log level configuration (ocannl_log_level) for tuning logging at runtime. Due to the compile-time nature of the per-file settings, run dune clean after setting/exporting one of these environment variables.