Repository navigation
Add support for nanobind? #284
Description
Activity
I believe that currently this is the blocker : xtensor-stack/xtensor#2366 . It would be amazing if we could get that out of the way. Any help is greatly appreciated!
@benbovy I've been using C++17 fine. I would be very much open to support this. Have you had time to experiment?
@tdegeus good to know C++17 works well. I haven't had the time yet to experiment with this, unfortunately.
Understood. Open to a contribution if you are up to it (I guess that ideas could be borrowed from https://github.com/wjakob/nanobind/blob/master/include/nanobind/eigen/dense.h ). Otherwise it will come when one of us finds the time or sufficient urgency
Just for the record: I did some experimenting with
nanobind'sndarraywhich should be very similar to what we want, and things like allocation and function calls are indeed way faster (at least a factor two in what I experimented with), and compilation much much faster. I hope to get some time soon to work on this (and I hope even more that someone beats me to it ;))!Some off-topic discussion: Is it possible to provide ctypes support? Since numpy has ctypeslib, I believe it should be very easy to provide a wrapper? And according to many benchmarks, ctypes performs better than pybind: taichi-dev/taichi#4830
Just for the record: I did some experimenting with
nanobind'sndarraywhich should be very similar to what we want, and things like allocation and function calls are indeed way faster (at least a factor two in what I experimented with), and compilation much much faster. I hope to get some time soon to work on this (and I hope even more that someone beats me to it ;))!I did a bit on this for my work. Didn't test for xarray, only xtensor and xtensor_fixed. Maybe a starting point for you?
SebastianThiede/nanobind_xtensorSome off-topic discussion: Is it possible to provide ctypes support? Since numpy has ctypeslib, I believe it should be very easy to provide a wrapper? And according to many benchmarks, ctypes performs better than pybind: taichi-dev/taichi#4830
Btw. Nanobind can also be very fast compared to pybind11.
Just for the record: I did some experimenting with
nanobind'sndarraywhich should be very similar to what we want, and things like allocation and function calls are indeed way faster (at least a factor two in what I experimented with), and compilation much much faster. I hope to get some time soon to work on this (and I hope even more that someone beats me to it ;))!I did a bit on this for my work. Didn't test for xarray, only xtensor and xtensor_fixed. Maybe a starting point for you? SebastianThiede/nanobind_xtensor
Some off-topic discussion: Is it possible to provide ctypes support? Since numpy has ctypeslib, I believe it should be very easy to provide a wrapper? And according to many benchmarks, ctypes performs better than pybind: taichi-dev/taichi#4830
Btw. Nanobind can also be very fast compared to pybind11.
https://github.com/yanto77/cpp-python-binding-benchmark This is another benchmark I found recently
Any updates on this?
Currently, I'm using https://github.com/SebastianThiede/nanobind_xtensor, but nativenanobindsupport would be greatly appreciated!Hi, is anyone still working on this?
If not, I'd like to give it a try!
I can start by mimicking the pybind11 implementation approach.@Roy-Kid I am not associated with this project, but I'd greatly appreciate if you'd give it a try :-)
Nanobind support would be awesome!I vibe-coded a
pytensorclass (nopyarrayyet) that enables xtensor-based in-place operations on NumPy arrays:
https://github.com/peter-urban/xtensor_nanobind) Requires C++20 (tested with GCC, Clang, clang-cl; no MSVC yet).#include "pytensor_nanobind.hpp" ... m.def("double_inplace", [](xt::nanobind::pytensor<double, 2>& arr) { arr *= 2.0; });
It’s still a bit of an AI-generated mess (possibly buggy and possibly incomplete), but works for my use cases at similar speed to the original
pytensor.Anyways, it is a proof of concept that might already fit your project.
I’d be willing to make it more useful or integrate it here, but I’m unsure how — the sollution is quite different from the original and e.g. uses
xtensor_adaptoras base and does not deriv fromnb::object.
My attempts to bring it closer to the original structure ended in segfaults that i could not resolve. Maybe someone who is more familiar with xtensor/pybind/nanobind internals could achieve this though.Anybody here interested in giving some lead/guidance on how to proceed from here?
@peter-urban thanks for working on this. I think it would be easier to start from the existing code and adapt it to nanobind rather than starting from scratch (that would probably make the review process easier).
xtensor_adatoris not meant to be a base class (I don't remember why we did not make it final); the issue is that it ill be passed as a reference to one of its CRTP base class at smoe point, and downcasting it will give anxtenosr_adaptor, and not your inheriting class. That breaks the assignment mechanism.If you don't need to inherit from py::object, you can keep the inheritance from pycontainer, and remove the inheritance form py::object in this class.
I'm not familiar to nanobind at all, so I don't know what would be required to get it work. But the changes should at least pass the test suite of xtensor-python.
@JohanMabille thx for the response and the info on xtensor_adaptor. I'll try to rebase on xcontainer and xcontainer_semantics in the future.
About deriving from pycontainer: The pycontainer class does not only derive from py::object. It seems that it is, for a large part, written to implement pybind11 specifc numpy handling. While the nanobind interface is made to be similar to pybind overall, specifically the numpy array handling is different.
Similarily, also pytensor includes pybind11 specific things.
namespace pybind11 { namespace detail { #ifdef PYBIND11_DESCR // The macro is removed from pybind11 since 2.3 template <class T, std::size_t N, xt::layout_type L> struct handle_type_name<xt::pytensor<T, N, L>> { static PYBIND11_DESCR name() { return _("numpy.ndarray[") + npy_format_descriptor<T>::name() + _("]");This tight integration makes it difficult to derive from- or adapt these classes to nanobind. So I guess it would be necessary to untie this a bit.
About using the existing code and adapting it to nanobind: This was my first approach but I simply did not manage to create a working solution. (My attempts ended in segfaults on module import that i could not resolve). I think adapting this somewhat working solution step by step to fit within the xtensor_python framework is easier then trying to create a working solution within the xtensor_python interface directly. (happy to be disproven if somebody wants to do this)
I guess a good first step would be to split my file into the same structure (e.g. pycontainer pytensor) and try to mimic/copy as much code from xtensor_python as possible. When this is done it will be easier to understand what needs to be done to merge the approaches.
I guess a good first step would be to split my file into the same structure (e.g. pycontainer pytensor) and try to mimic/copy as much code from xtensor_python as possible.
Yes, that is what I meant in my first answer. If the handling of numpy array is very different in nanobind, writing a new pycontainer-like file from scratch (rather than trying to modify the existing one) is totally acceptable, but trying to keep a similar structure / architecture would definitely help to review the changes.
Also, even if the changes must pass the test-suite of xtensor-python in the end, do not hesitate to open an early preview PR so that it is easier to us to comment and follow your work on this. And again, thanks a lot for working on this ;)
Reacted by Peter UrbanI prototAIped a proof of concept for a nanobind addition that works well for my project (tests are passing, performance is comparable as far as I can see it).
https://github.com/peter-urban/xtensor-python/tree/xtensor-nanobindThe idea is convert this into digestible commits over the coming weeks or months and clean the code up while doing this.
The reason is that my solution currently requires significant changes to the project structure that need correction or adoption step by step.My first question is the main mode of integration/separation.
Pybind11 and nanobind integrate quite deeply in the main classes (e.g. pycontainer, pytensor, pyarray).
My implementation separates the pybind11 and the nanobind implementations in different subfolders with seperate namespaces. (xt::pybind11::pytensor and xt::nanobind::pytensor).Shared implementations/interface definitions (e.g. for slicing) would then go into the 'detail' folder/namespace.
As a note on this: The nanobind implementation is currently mostly independend on purpose. This simplifies moving around files and namespaces. Once the final structure is clear and implemented we can work towards more code sharing between the implementations.@JohanMabille It would be nice to get some comments on the general approach and the suggested namespace structure. If you are happy, I could work on a pull request that moves the current implementation into a separate pybind11 namespace. For backwards compatibility we could probably create an alias xt::pytensor = xt::pybind11::pytensor
hey @peter-urban, I'm currently working on xtensor wrapper based on nanobind (as a course project at university), I'm trying to use more idiomatic and nanobind-specific approach than just translating the xtensor-python code. I will release the binding code in a several weeks
@keltecc cool! Thanks for letting me know. Some questions:
- Will your bindings become part of xtensor-python or do you plan it as seperate project?
- My current AI version uses nanobinds DLPack interface. Because of this, does not support arrays with python objects (only numeric dtypes). Is this something you are planning to work around?
Anyways happy to hear more about your work, let me know if you could do with some help testing performance etc.
Will your bindings become part of xtensor-python or do you plan it as seperate project?
I plan to release it as a part of nanobind project, the same way as Eigen bindings (include/nanobind/eigen). Hope that nanobind maintainer will accept the PR.
My current AI version uses nanobinds DLPack interface. Because of this, does not support arrays with python objects (only numeric dtypes). Is this something you are planning to work around?
Currently I'm working on the support of primitive types (ints, floats, etc) that are natively supported by nanobind. It seems that DLPack supports several other types, for example
std::complex. I will also try to add a support for general python objects, but I didn't explored that direction yet.Anyways happy to hear more about your work, let me know if you could do with some help testing performance etc.
Thank you! I've planned to do some performance testing after the development stage, will compare the implementation with the existing
xtensor-pythonbindings.Reacted by Peter UrbanHi, I asked this question several month ago. I think best way is to hold it under xtensor org instead of part of nanobind. wjakob/nanobind#1046. If @keltecc can publish the code we can work together. @JohanMabille How do you think?
@peter-urban @Roy-Kid I've published the casters
I will try to make a PR in nanobind, hope wjakob will accept it. now xtensor casters have ~330 lines without tests, for example the existing Eigen casters have ~745 lines (and xtensor-python has ~2032 lines), so the new codebase is relatively small
there's also a some benchmark repository that measures the implementation
Reacted by Peter Urban@keltecc, nice work! I created a benchmark against my ai implementation.
On my machine your implementation is ~5% faster than mine (and of cause much cleaner).
Whether your PR is accepted or whether it becomes it's own repo, you can be sure I'll switch to your version eventually :-)Hi there,
Apologies for the very late reply. It seems that Wenzel changed his mind and would be happy to support these bindings in nanobind itself. However, if the C++20 requirements becomes a blocker for future versions of xtensor, I'm happy to create a new repo and have them uder the xtensor org.Reacted by Jichen Li and Peter Urban
After a quick inspection of xtensor-python's internals, it looks like it shouldn't be too hard supporting both pybind11 and nanobind via some sort of minimal compatibility layer? Both have a pretty similar API for the basic features, and unless I'm missing something xtensor-python doesn't seem to depend much on pybind11's numpy and/or other advanced features?
Nanobind is more performant than pybind11 and also offers more control via its low-level interface, which would make things much easier for my use case (*). However, nanobind's numpy support is currently limited, e.g., it doesn't provide a
vectorizehelper (not sure it will anytime soon?) which is something that I also need. Xtensor-python would nicely fill this gap I guess.(*) More context: I'm working on a Python/Numpy library (https://github.com/benbovy/s2shapely) providing bindings for the s2geometry / s2geography libraries, via vectorized functions (ufuncs) operating on Geography objects (C++ wrapped classes) referenced in numpy arrays with the
numpy.objectdtype.