I have been working for the past few months on CL-Torch, a Common Lisp equivalent of PyTorch. Like PyTorch, CL-Torch calls LibTorch — the C++ library that does most of the numerics — via FFI.
It's nowhere near done, but I need to set it aside for a few months to work on something else, so I thought I would publish what I have and let people play with it.
Claw
Claw (Common Lisp Auto-Wrap; no relation to OpenClaw, which it preceded by years) is Pavel Korolev's FFI wrapper generator; it includes IFFI, his Intricate Foreign Function Interface, which deals with C++ overloading. This is what I have used to create CL-Torch.
I had initially looked at SWIG, which had had Common Lisp support until its 4.0 release, but on closer examination it didn't look like a good choice; the Common Lisp generation apparently never worked well. I then tried C2FFI, and in retrospect, maybe I could have gotten it to work, but it doesn't have any explicit C++ support; I would have wound up with C++ "mangled names" (encoded function names including namespace and parameter type information) in the CL-Torch sources. Poking around a little more, I found these three blog posts by Pavel introducing Claw. I also noticed that Pavel already had a Claw-Torch project; although he hadn't gotten very far on it and it was years out of date, it was still the best starting point I had found.
But I didn't realize what I was getting into. Pavel describes Claw as "BETA quality", emphasizing that it isn't ready for general use, but actually I think even this description is too generous; in the state I found it in, I would describe it as alpha, and early alpha at that. I spent several weeks fixing and improving it so that it could handle LibTorch, which in fairness, is pretty much a torture test for an FFI generator — it uses features of C++ I didn't even know existed. One of the most problematic was constructor inheritance. Did you know constructors could be inherited? I didn't either, but the feature went in in C++11. Anyway, LibClang, which Claw uses to analyse the C++ code it's wrapping, doesn't expose inherited constructors in a convenient way; the information is there, but you have to dig it out. So I had to learn a bunch about the internals of Claw, including libresect, the C library that interfaces directly with LibClang. This knowledge eventually came in quite handy, though, as I made more fixes and changes to Claw, ultimately dropping 14 PRs on Pavel. — So far, he hasn't merged any of them, and I don't know whether he's going to, so for CL-Torch, if you want to regenerate the FFI bindings, you'll need to use my forks of Claw and its subprojects cl-resect and libresect.
One significant improvement I made to Claw was to add exception handling. Exceptions thrown by LibTorch code are caught and automatically translated to Lisp errors.
At the time Pavel wrote Claw, passing structs by value required libffi, which, he noted in a blog post, is quite slow. So Claw passes all structs by pointer. I see that efficient passing of structs by value has been recently added to SBCL, but I don't think it's worth modifying Claw to use it, as that would change how the generated wrappers have to be called, and thus wouldn't be portable.
What might be worth doing, eventually, is making IFFI allocate temporary objects on the stack; it currently doesn't. But for CL-Torch, the benefit is almost certainly going to be undetectable; LibTorch calls, in normal use, spend the vast majority of their time doing tensor arithmetic; allocating and freeing small objects is negligible by comparison.
Anyway, my overall impression of Claw is that, with my improvements, it works pretty well. If you have another C++ library you'd like to call from CL, I think you should give it a try. It needs quite a bit more documentation, but if you look at what I've done for CL-Torch, that will give you some clues. Beyond that, you'll have to do what I did: read the source 😸
Status
For the purpose of a project like CL-Torch, LibTorch has two major pieces. One is the tensor arithmetic library ATen (with its lower-level component C10). This library has over a thousand operations, although many of these are variants of one another; for instance, many operations have both functional and in-place versions, the latter updating one of its argument tensors rather than allocating a new one. The C++ and Python APIs for these functions are auto-generated from a description file, aten/src/ATen/native/native_functions.yaml.
I have written a generator that produces CL versions of these APIs from the YAML descriptions. It's not finished — there are cases it doesn't yet handle — but it's currently succeeding on 645 of the 1089 candidate functions, so there is a significant amount of working functionality here. (In some cases, not all features of the function are supported yet.) If you just want to do a bunch of tensor arithmetic, there may be enough here to do what you want. It's not heavily tested, but there are enough tests to reasonably assure me that the code generation is being done correctly, at least in most cases.
The second major piece of LibTorch is the high-level neural net API. Here CL-Torch is less far along, but this is also a much easier part to work on. (I think. I haven't tested any of the code I've written for this part.) So if you want to add CL-Torch code for some of this part of the API, I think you should be able to do that. (You could even try using an LLM for this — I haven't, yet.) One thing you should know, if you want to work on that, is that there are two levels within this part of LibTorch: the torch::nn::functional:: code is the slightly lower level, that implements the operations of neural-net layers but without keeping state, and in particular, without maintaining trainable parameters. I have started hand-translating these (they're mostly quite simple) in Code/torch-functional.lisp.
The higher level is the module API, which I have just barely started in Code/torch/modules.lisp. This API implements parameters and training. To actually train a network, you'll also need an optimizer; I haven't started on these.