Library / SDK
hasktorch/hasktorch avatar
hasktorch/hasktorch

Hasktorch: tensors and neural networks in Haskell on top of libtorch

Tensors and neural networks in Haskell

1,216 stars127 forksHaskellNOASSERTION

At a glance

What is it?
Hasktorch is a community Haskell library for tensors and neural networks that binds to the C++ libraries shared by PyTorch, and its build downloads the official libtorch binaries for you and caches them. The 0.2 release is on Hackage and nixpkgs, the API is explicitly allowed to change, and the README is candid that the project is an experiment in active development.
Who is it for?
Adopt Hasktorch if you want to write machine learning code in Haskell and you would rather reuse the same libtorch runtime and C++ kernels that PyTorch uses than implement tensors yourself, since the library handles the FFI, the automatic tensor conversion and the libtorch acquisition.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Haskell, 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 Haskell face on the PyTorch runtime

Hasktorch is a library for tensors and neural networks in Haskell. It is an independent open source community project which leverages the core C++ libraries shared by PyTorch.

That last clause is the whole design decision. Hasktorch is not a Haskell reimplementation of a tensor library with its own kernels, and it is not a binding to a Python process. It wraps libtorch, the C++ library that PyTorch itself is built on, so the numerics, the autodiff engine and the operator set are the same ones PyTorch uses, reached through a Haskell API.

That has an obvious appeal for a Haskell shop. A pure Haskell tensor library would need its own implementation of matrix multiplication, convolution, autodiff and a dozen device backends, and the result would be slower than the well optimised C++ underneath PyTorch. Binding to libtorch gets the performance and the operator coverage for free, at the cost of writing and maintaining the FFI layer, which is exactly what Hasktorch exists to do.

The intended user is a Haskell developer doing numerical or machine learning work who wants the type system and the functional style of Haskell over a mature numerical runtime, and who is comfortable with a project where the API surface is still moving.

The project states its own status plainly. This project is in active development, so expect changes to the library API as it evolves, and the README invites new users to a Discord space for questions and discussions, with contributions and pull requests encouraged. The second major release of Hasktorch, version 0.2, is available on Hackage and in nixpkgs.

The documentation is split into introductory videos, a getting started section, a known issues section, contributing notes, and notes for library developers. The three videos are worth knowing about, because they cover the three levels at which this library can be approached: a high-level MuniHac talk, a hands-on live-coding demo, and a low-level FFI talk for people who want to write bindings themselves.

The build fetches libtorch and caches it

The first thing that distinguishes Hasktorch from a typical Haskell binding is that you do not install libtorch by hand. The README is emphatic about it: you do not need to install libtorch, or anything else, by hand.

You add the library to your project's `build-depends`:

cabal
build-depends: base, hasktorch

And then the first time you run `cabal build`, the build downloads the official libtorch binaries from `download.pytorch.org` automatically and caches them under `~/.cache/libtorch/<version>/<platform>/<flavor>`. Later builds, and every other project on the machine, reuse that cache, so nothing is downloaded twice. The linker rpath is set for you, so the built binaries find the libraries at runtime without any environment variables.

That last point is more than a convenience. A binding library that requires a hand-set `LD_LIBRARY_PATH` is a binding library that breaks the moment a build runs in a different container, and the rpath handling means the same source tree builds in a developer shell, in CI, and in a deployment image without per-environment configuration.

The four environment variables are all optional, and each solves a specific problem. `LIBTORCH_VERSION` pins a libtorch version. `LIBTORCH_HOME` changes the cache location, which is what you need when the default path is not writable. `LIBTORCH_CUDA_VERSION` selects the build flavour, with `cpu`, `cu118` and `cu121` among the values listed. And `LIBTORCH_SKIP_DOWNLOAD` tells the build to use a system-provided libtorch instead of fetching one, which is the escape hatch for an air-gapped environment or a machine that already has a libtorch from another tool.

Nix builds skip the download entirely, because the flake provides libtorch itself. That is a meaningful difference for a Nix-based CI, where the hermetic build is the point.

Platforms, and the Apple MPS backend

The supported platform table is short, and the backends are the interesting column.

Linux x86-64 gets CPU by default, or CUDA if you export `LIBTORCH_CUDA_VERSION=cu118` or a similar value before building, in which case the needed NVIDIA libraries are downloaded automatically too. macOS on Apple Silicon gets CPU, and the binary also includes Apple's MPS backend, shown in the table as `Device MPS 0`. macOS on Intel gets CPU.

The MPS entry is the one to notice. Apple's Metal Performance Shaders backend is a genuinely different device from the CPU, and having it available in the same binary means a model can run on the GPU of the laptop it is running on rather than falling back to CPU. The table's phrasing, that the binary also includes the backend, suggests it is present rather than exclusive, which is the arrangement you want: the same build works on a machine with and without a usable MPS device.

The practical constraint is that Linux ARM is not in the table, neither is Windows, and macOS on Intel is on a CPU-only path that will be of limited interest to anyone with a discrete GPU. The CUDA story on Linux is the one that requires planning, because the flavour has to be chosen at build time through an environment variable, and choosing the wrong CUDA version means the binaries you download are not the ones your driver supports.

The default is CPU everywhere it is offered, which is the right default for a library that people use for learning and experimentation, and the reason the CUDA instructions come as a separate variant in the build documentation rather than as a note.

Building the library and running the MNIST example

Working on Hasktorch itself has a setup step that the README describes for several platform and toolchain combinations, including linux with cabal and CPU, linux with cabal and CUDA 11, macOS with cabal, linux with stack, macOS with stack, NixOS with cabal in both CPU and CUDA flavours, and docker with JupyterLab and CUDA 11.

The common first step creates a cabal project file:

sh
$ ./setup-cabal.sh # Create a cabal project file

Then the library and its test suite:

sh
$ cabal build hasktorch  # Build the Hasktorch library.
$ cabal test hasktorch   # Build and run the Hasktorch library test suite.

And the example executables, which are a separate cabal target:

sh
$ cabal build examples  # Build the Hasktorch examples.
$ cabal test examples   # Build and run the Hasktorch example test suites.

The MNIST example is the one the documentation walks through in full, and it is a good shape for a first real use because it exercises a dataset download, a model definition and a training loop. On CPU:

sh
$ cd examples                            # Change to the examples directory.
$ ./datasets/download-mnist.sh           # Download the MNIST dataset.
$ export DEVICE=cpu                      # Set device to CPU for the MNIST CNN example.
$ cabal run static-mnist-cnn -- ./mnist/ # Run the MNIST CNN example.

On a CUDA machine, the only difference is the device selection:

sh
$ export DEVICE="cuda:0"                 # Set device to CUDA for the MNIST CNN example.

Note that the device is chosen at run time through the `DEVICE` environment variable while the CUDA flavour of libtorch is chosen at build time through `LIBTORCH_CUDA_VERSION`. Two different mechanisms for two different decisions, and it is easy to confuse them the first time.

The Stack variants use `stack build` and `stack test` in place of the cabal commands, and the repository carries `stack.yaml` and `stack-macos.yaml` for them.

Code generation, the FFI layer and the examples as a map

The repository layout shows how the work is divided, and the division is the interesting part for anyone extending the library.

There is a `libtorch-ffi/` directory and a `libtorch-ffi-helper/` directory, which are the hand written binding layers, and a `codegen/` directory, which is where the generated bindings come from. The `hasktorch/` directory is the user facing library, `spec/` holds the specification-driven tests, `experimental/` holds the parts that are not yet stable, `deps/` holds the dependency fetch scripts, and `nix/` plus `flake.nix` and `flake.lock` provide the Nix path.

The separation of `codegen/` from `libtorch-ffi/` is the mechanism behind the library's reach. libtorch's C++ API is enormous and it changes with every PyTorch release, so a binding that is written by hand for each function does not scale. A generator that reads the libtorch headers and emits the Haskell foreign import declarations can cover the surface, and the hand written helper layer then provides the ergonomics, the automatic tensor conversion and the parts that need care.

The `examples/` directory is the best documentation of the library's scope, and there are more than twenty of them: `alexNet`, `autograd`, `distill`, `gaussian-process`, `gd-field`, `image-processing`, `iris-classification`, `kmeans`, `load-torchscript`, `matrix-factorization`, `minimal-text-example`, `mnist-mixed-precision`, `mnist-mlp`, `model-serving`, `optimizers`, `optimizers-cpp`, `optimizers-cpp-typed`, `regression`, `rnn`, `serialization`, `static-mnist-cnn` and `static-mnist-mlp`.

Reading that list tells you what the library can do. Autograd, optimizers, TorchScript loading, model serving, mixed precision, matrix factorisation and Gaussian processes are all covered, which is a wide surface for a 0.2 release. The `notebooks/` directory and the `website/` directory indicate that the project also has a documentation site and interactive material, and the `pyproject.toml` with its Poetry configuration and its `torch` and `transformers` dependencies belongs to that tooling side rather than to the library itself.

The Dockerfile in the root shows the older, more manual path: a `nvidia/cuda:10.1-devel-ubuntu18.04` base with GHC 8.6.5 and cabal-install 3.0, a call to `deps/get-deps.sh -a cu101`, and a manual `LD_LIBRARY_PATH` pointing at the libtorch, MKLML and libtokenizers directories. It is worth reading as a contrast, because it shows exactly the setup that the automatic download and rpath handling in the current build removes.

Incubation status, licence, and the honest boundary

Two things should be settled before anyone builds on this.

The first is the API stability. The README says the project is in active development and asks you to expect changes to the library API as it evolves. The `experimental/` directory in the tree is a second signal, since it separates the parts that are not yet settled from the rest. Version 0.2 is on Hackage and in nixpkgs, which is a reasonable way to distribute a fast-moving library, but it is not a promise of compatibility.

The second is the licence. The project metadata records the licence as other, a custom licence that GitHub cannot classify, and the repository root carries a `LICENSE` file. Since this is a library you link into your application, the terms are worth reading directly rather than assuming a standard permissive licence, and this is a description of what the file list says rather than legal advice.

There is also no GitHub release for the project, so the version to depend on is the Hackage version, and the last push to the default branch was on 2026-08-19.

The honest boundary is the language ecosystem. Hasktorch gives you libtorch from Haskell, and libtorch is the PyTorch runtime, which means the models you train are PyTorch models. That is a feature if you plan to hand the result to a Python inference service, to a TorchScript artefact, or to a C++ deployment, and `load-torchscript` and `model-serving` are examples in the tree that point that way. It is a limitation if you wanted a self-contained Haskell machine learning stack with its own package index, its own model zoo and its own tooling, because none of that comes with this project. The equivalent of the Python ecosystem is one `pip install` away, and the question is whether you want to cross that line in your build.

The alternative approach, for a team that wants tensors in Haskell today, is to use a Haskell-native numerical library and keep the neural network parts in a Python service behind an interface. The difference in approach is that Hasktorch keeps the whole computation in one process with one runtime and one set of semantics, at the cost of an FFI boundary and an evolving API, while the split approach keeps Haskell on the parts of your system that are already Haskell and puts the modelling work where the tooling is.

Editorial conclusion

Adopt Hasktorch if you want to write machine learning code in Haskell and you would rather reuse the same libtorch runtime and C++ kernels that PyTorch uses than implement tensors yourself, since the library handles the FFI, the automatic tensor conversion and the libtorch acquisition. Do not adopt it if you need a stable API to build a product on, since the README asks you to expect changes as the library evolves, or if you want the wider tooling ecosystem that comes with Python. Verify three things first: that your target platform is in the supported table, Linux x86-64, Apple Silicon or Intel macOS, that the libtorch download works in your build environment, including behind a proxy or in an air-gapped CI runner, and that the version of Hasktorch on Hackage exposes the operations you need before you commit to it.

Frequently asked questions

What is Hasktorch and what does it build on?

It is a Haskell library for tensors and neural networks, and an independent open source community project that leverages the core C++ libraries shared by PyTorch. The 0.2 release is available on Hackage and in nixpkgs.

Do I need to install libtorch manually for Hasktorch?

No. The first `cabal build` downloads the official libtorch binaries from `download.pytorch.org` and caches them under `~/.cache/libtorch/<version>/<platform>/<flavor>`, later builds reuse that cache, and the linker rpath is set so the binaries find the libraries at runtime.

Which platforms and backends does Hasktorch support?

Linux x86-64 with CPU by default or CUDA selected through `LIBTORCH_CUDA_VERSION`, macOS on Apple Silicon with CPU and the Apple MPS backend, and macOS on Intel with CPU. The optional environment variables are `LIBTORCH_VERSION`, `LIBTORCH_HOME`, `LIBTORCH_CUDA_VERSION` and `LIBTORCH_SKIP_DOWNLOAD`.

How do I run the Hasktorch MNIST example?

From the repository, run `./setup-cabal.sh` to create the cabal project file, then `cabal build hasktorch` and `cabal test hasktorch`. For the example itself, change into the examples directory, run `./datasets/download-mnist.sh`, set `export DEVICE=cpu` or `export DEVICE="cuda:0"`, and run `cabal run static-mnist-cnn -- ./mnist/`.

Is Hasktorch's API stable?

The README states the project is in active development and asks users to expect changes to the library API as it evolves, and the repository keeps an `experimental/` directory for the parts that are not yet settled.

Official sources

  1. hasktorch/hasktorch on GitHub
  2. Issues
  3. Project website
  4. README
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/hasktorch-hasktorch.svg)](https://hysenlabs.com/projects/hasktorch-hasktorch)

Was this useful?