@@ -275,12 +275,19 @@ desired Python type.
275275- :cpp:class: `nb::tensorflow <tensorflow> `: create a ``tensorflow.python.framework.ops.EagerTensor ``.
276276- :cpp:class: `nb::jax <jax> `: create a ``jaxlib.xla_extension.DeviceArray ``.
277277- :cpp:class: `nb::cupy <cupy> `: create a ``cupy.ndarray ``.
278+ - :cpp:class: `nb::memview <memview> `: create a Python ``memoryview ``.
279+ - :cpp:class: `nb::arrayapi <arrayapi> `: create an object that supports the
280+ Python buffer protocol (i.e., is accepted as an argument to ``memoryview() ``)
281+ and also has the DLPack attributes ``__dlpack__ `` and ``_dlpack_device__ ``
282+ (i.e., it is accepted as an argument to a framework's ``from_dlpack() ``
283+ function).
278284- No framework annotation. In this case, nanobind will create a raw Python
279285 ``dltensor `` `capsule <https://docs.python.org/3/c-api/capsule.html >`__
280- representing the `DLPack <https://github.com/dmlc/dlpack >`__ metadata.
286+ representing the `DLPack <https://github.com/dmlc/dlpack >`__ metadata of
287+ a ``DLManagedTensor ``.
281288
282289This annotation also affects the auto-generated docstring of the function,
283- which in this case becomes:
290+ which in this example's case becomes:
284291
285292.. code-block :: python
286293
@@ -458,6 +465,21 @@ interpreted as follows:
458465- :cpp:enumerator: `rv_policy::move ` is unsupported and demoted to
459466 :cpp:enumerator: `rv_policy::copy `.
460467
468+ Note that when a copy is returned, the copy is made by the framework, not by
469+ nanobind itself.
470+ For example, ``numpy.array() `` is passed the keyword argument ``copy `` with
471+ value ``True ``, or the PyTorch tensor's ``clone() `` method is immediately
472+ called to create the copy.
473+ This design has a couple of advantages.
474+ First, nanobind does not have a build-time dependency on the libraries and
475+ frameworks (NumPy, PyTorch, CUDA, etc.) that would otherwise be necessary
476+ to perform the copy.
477+ Second, frameworks have the opportunity to optimize how the copy is created.
478+ The copy is owned by framework, so the framework can choose to use a custom
479+ memory allocator, over-align the data, etc. based on the nd-array's size,
480+ the specific CPU, GPU, or memory types detected, etc.
481+
482+
461483.. _ndarray-temporaries :
462484
463485Returning temporaries
@@ -643,26 +665,92 @@ support inter-framework data exchange, custom array types should implement the
643665- `__dlpack__ <https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack__.html#array_api.array.__dlpack_ _>`__ and
644666- `__dlpack_device__ <https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__dlpack_device__.html#array_api.array.__dlpack_device_ _>`__
645667
646- methods. This is easy thanks to the nd-array integration in nanobind. An example is shown below:
668+ methods.
669+ These, as well as the buffer protocol, are implemented in the object returned
670+ by nanobind when specifying :cpp:class: `nb::arrayapi <arrayapi> ` as the
671+ framework template parameter.
672+ For example:
647673
648674.. code-block :: cpp
649675
650- nb::class_<MyArray>(m, "MyArray")
651- // ...
652- .def("__dlpack__", [](nb::kwargs kwargs) {
653- return nb::ndarray<>( /* ... */);
654- })
655- .def("__dlpack_device__", []() {
656- return std::make_pair(nb::device::cpu::value, 0);
657- });
676+ class MyArray {
677+ double* d;
678+ public:
679+ MyArray() { d = new double[5] { 0.0, 1.0, 2.0, 3.0, 4.0 }; }
680+ ~MyArray() { delete[] d; }
681+ double* data() const { return d; }
682+ };
683+
684+ nb::class_<MyArray>(m, "MyArray")
685+ .def(nb::init<>())
686+ .def("arrayapi", [](const MyArray& self) {
687+ return nb::ndarray<nb::arrayapi, double>(self.data(), {5});
688+ }, nb::rv_policy::reference_internal);
689+
690+ which can be used as follows:
691+
692+ .. code-block :: pycon
658693
659- Returning a raw :cpp:class: `nb::ndarray <ndarray> ` without framework annotation
660- will produce a DLPack capsule, which is what the interface expects.
694+ >>> import my_extension
695+ >>> ma = my_extension.MyArray()
696+ >>> aa = ma.arrayapi()
697+ >>> aa.__dlpack_device__()
698+ (1, 0)
699+ >>> import numpy as np
700+ >>> x = np.from_dlpack(aa)
701+ >>> x
702+ array([0., 1., 2., 3., 4.])
703+
704+ The DLPack methods can also be provided for the class itself, by implementing
705+ ``__dlpack__() `` as a wrapper function.
706+ For example, by adding the following lines to the binding:
707+
708+ .. code-block :: cpp
709+
710+ .def("__dlpack__", [](nb::pointer_and_handle<MyArray> self,
711+ nb::kwargs kwargs) {
712+ using arrayapi_t = nb::ndarray<nb::arrayapi, double>;
713+ nb::object aa = nb::cast(arrayapi_t(self.p->data(), {5}),
714+ nb::rv_policy::reference_internal,
715+ self.h);
716+ nb::object max = kwargs.get("max_version", nb::none());
717+ return aa.attr("__dlpack__")(nb::arg("max_version") = max);
718+ })
719+ .def("__dlpack_device__", [](nb::handle /*self*/) {
720+ return std::make_pair(nb::device::cpu::value, 0);
721+ })
722+
723+ the class can be used as follows:
724+
725+ .. code-block :: pycon
726+
727+ >>> import my_extension
728+ >>> ma = my_extension.MyArray()
729+ >>> ma.__dlpack_device__()
730+ (1, 0)
731+ >>> import numpy as np
732+ >>> y = np.from_dlpack(ma)
733+ >>> y
734+ array([0., 1., 2., 3., 4.])
735+
736+
737+ The ``kwargs `` argument in the implementation of ``__dlpack__ `` above can be
738+ used to support additional parameters (e.g., to allow the caller to request a
739+ copy). Please see the DLPack documentation for details.
740+
741+ The caller may or may not supply the keyword argument ``max_version ``.
742+ If it is not supplied or has the value ``None ``, nanobind will return an
743+ unversioned ``DLManagedTensor `` in a capsule named ``dltensor ``.
744+ If its value is a tuple of integers ``(major_version, minor_version) `` and the
745+ major version is at least 1, nanobind will return a ``DLManagedTensorVersioned ``
746+ in a capsule named ``dltensor_versioned ``.
747+ Nanobind ignores other keyword arguments.
748+ In particular, it cannot transfer the array's data to another device (such as
749+ a GPU), nor can it make a copy of the data.
750+ A custom class (such as ``MyArray `` above) could provide such functionality.
751+ Often, the caller framework takes care of copying and inter-device data
752+ transfer and does not ask the producer, ``MyArray ``, to perform them.
661753
662- The ``kwargs `` argument can be used to provide additional parameters (for
663- example to request a copy), please see the DLPack documentation for details.
664- Note that nanobind does not yet implement the versioned DLPack protocol. The
665- version number should be ignored for now.
666754
667755Frequently asked questions
668756--------------------------
@@ -708,7 +796,3 @@ be more restrictive. Presently supported dtypes include signed/unsigned
708796integers, floating point values, complex numbers, and boolean values. Some
709797:ref: `nonstandard arithmetic types <ndarray-nonstandard >` can be supported as
710798well.
711-
712- Nanobind can receive and return *read-only * arrays via the buffer protocol when
713- exhanging data with NumPy. The DLPack interface currently ignores this
714- annotation.
0 commit comments