Skip to content

Conversation

@alexreinking
Copy link
Member

@alexreinking alexreinking commented Jul 10, 2025

The buffer protocol (PyBuffer) does not support all the same metadata that Halide::Runtime::Buffer does. In particular, it does not support coordinate offsets (as in set_min).

When an object is passed to an AOT Python pipeline, it is now checked for a _get_raw_halide_runtime_buffer method. If it exists, it should return a pointer to a halide_buffer_t whose lifetime is at least that of the object.

I don't love this solution as passing around a uintptr_t smells fishy (security implications?). But my first few ideas didn't pan out:

  1. I wanted to use pybind11's API to call .cast<Halide::Buffer<>*> to get a reference to the underlying C++ object. This has some more safety checks implemented. But the generated extension doesn't link to pybind11. Changing this would mean adding a dependency.
  2. Then I wanted to return a pointer to the Halide::Buffer object and use dynamic_cast to check type-safety. But the generated extensions don't have access to Halide.h, either.
  3. Then I wanted to do the same for Halide::Runtime::Buffer, since the generated extensions definitely have access to that. But it's not polymorphic (no virtual functions), so it's not compatible with dynamic_cast or typeid.

Tagging dev_meeting to advance discussion.

Fixes #8652

@alexreinking alexreinking added python Issues related to Halide/Python interop dev_meeting Topic to be discussed at the next dev meeting labels Jul 10, 2025
@alexreinking
Copy link
Member Author

In the abstract, this approach might resolve #6849 as our buffers do support bfloat16.

@alexreinking
Copy link
Member Author

I'm not totally seeing the logic behind the many layers of wrapping:

  1. The pybind11 type wrapping Halide::Buffer (Python reference counting)
  2. Halide::Buffer wrapping Halide::Runtime::Buffer (our own reference counting)
  3. Halide::Runtime::Buffer wrapping halide_buffer_t (for C++ convenience, but no virtual dtor)
  4. halide_buffer_t as a POD struct.

I wonder if adding a virtual dtor to (3) and having (1) wrap (3) directly is a better approach. It feels analogous: (2) is a reference-counted wrapper for C++, while (1) is a reference-counted wrapper for Python. But, again, maybe I'm missing something.

@alexreinking
Copy link
Member Author

alexreinking commented Jul 10, 2025

I'm not even sure how to extract the code from PythonExtensionGen.cpp as we don't target the Python Stable ABI. I don't think we even can until our minimum Python version is 3.11 since that's when PyBuffer was added to the ABI. As an aside, moving to the Stable ABI would greatly simplify our distribution of binary wheels.

On this subject, the latest Ubuntu LTS (24.04/noble) packages Python 3.12... at the latest, we can drop Python <3.11 in October 2026 (in a year and a half) when it goes EOL.

@alexreinking
Copy link
Member Author

Furthermore, we should probably find a way to throw an exception if a Halide::Buffer that is wrapped by pybind11 is attempted to be used in a Buffer Protocol context and the mins are not all 0.

@steven-johnson
Copy link
Contributor

In the abstract, this approach might resolve #6849 as our buffers do support bfloat16.

Yes, but it's not clear if the standard numpy buffer decriptors support bfloat16 -- they didn't last time I checked (but that was probably ~1 year ago)

@alexreinking
Copy link
Member Author

Yes, but it's not clear if the standard numpy buffer decriptors support bfloat16 -- they didn't last time I checked (but that was probably ~1 year ago)

They still don't, but using Halide buffers directly provides an escape hatch.

@abadams
Copy link
Member

abadams commented Jul 10, 2025

Furthermore, we should probably find a way to throw an exception if a Halide::Buffer that is wrapped by pybind11 is attempted to be used in a Buffer Protocol context and the mins are not all 0.

This could be tricky because there are going to be contexts where the mins don't matter and it would be annoying to have to explicitly zero them. E.g. saving a buffer to a png file using a routine that expects the buffer protocol, or displaying it in a window.

The buffer protocol (PyBuffer) does support all the same metadata that
Halide::Runtime::Buffer does. In particular, it does not support
coordinate offsets (as in set_min).

When an object is passed to an AOT Python pipeline, it is now checked
for a _get_raw_halide_runtime_buffer method. If it exists, it should
return a pointer to a halide_buffer_t whose lifetime is at least that
of the object.

Fixes #8652
@alexreinking
Copy link
Member Author

After discussion at the dev meeting, we will move ahead with merging this. The name _get_raw_halide_runtime_buffer was replaced with _get_raw_halide_buffer_t.

@alexreinking alexreinking merged commit c0b5b11 into main Jul 21, 2025
19 checks passed
@alexreinking alexreinking removed the dev_meeting Topic to be discussed at the next dev meeting label Aug 13, 2025
@alexreinking alexreinking deleted the python/buffer-mins branch September 12, 2025 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Issues related to Halide/Python interop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hl.Buffer mins ignored when passing one to an aot-compiled pipeline

4 participants