Open runenordmo opened 2 months ago
Here is a more relevant minimal example as well
import halide as hl
import numpy as np
x = hl.Var("x")
result = hl.Func("result")
result[x] = x + hl.Expr(np.float32(np.pi))
output = result.realize([1])
# Fails: ACTUAL: 1 (or ACTUAL 3 with halide-13)
np.testing.assert_almost_equal(output[0], 3.1416, decimal=2)
If I rearrange the following
https://github.com/halide/Halide/blob/f658eec68e047c0e57214615ca840c8c9e374bb0/python_bindings/src/halide/halide_/PyExpr.cpp#L26-L37
into moving the double
overload before bool
overload
// Python float is implemented by double
// But Halide prohibits implicitly construct by double.
.def(py::init([](double v) {
return double_to_expr_check(v);
}))
.def(py::init([](bool b) {
return Internal::make_bool(b);
}))
// PyBind11 searches in declared order,
// int should be tried before float conversion
.def(py::init<int>())
.def(py::init<int64_t>())
then the issue goes away. I rebuilt the Python bindings and ran the above minimal example.
Based on this comment, it seems like the order is intentional:
// int should be tried before float conversion
Reading the pybind11 docs it seems like PyExpr
is missing an explicit overload for np.float32
, but I have not yet figured out what that explicit overload should be. Attempting to add py::float_
failed with a compiler error.
I am also a bit confused by the comment in the code when I compare that to what the pybind11 docs states. According to the docs, pybind11 runs resolution order in two passes. One where it attempts to match explicitly, then a second where it chooses the first one that matches implicitly.
It seems like we are hitting this issue in the second pass, where bool
is preferred as an implicit target over double
. But I do not understand why bool
and int
would be preferred over double
in an implicit pass. It sounds like it would easily be a narrowing conversion. Perhaps except in the case of something that is int64
that does not trigger the explicit conversion.
@steven-johnson: It seems like you wrote the initial comment. Do you remember why the integers are preferred? The comment is from 2017, so I completely understand if you do not remember :smiley:
This PR for pybind11 seems related: https://github.com/pybind/pybind11/pull/2060
@dragly Does this link give enough info on how we can resolve the issue?
Do you remember why the integers are preferred? The comment is from 2017, so I completely understand if you do not remember
Sadly, yeah, I spun down those brain cells a while ago. The current behavior definitely seems like a bug that needs fixing though! I wonder if just adding an overload for float
would work?
@alexreinking FYI we should try to fix this prior to the Halide 19 release
LLVM 19.1.0 is due tomorrow and we normally trail them by a week or two anyway
This PR for pybind11 seems related: pybind/pybind11#2060
@dragly Does this link give enough info on how we can resolve the issue?
I have not been able to find a solution based on that link yet. The linked PR seems to add new types to pybind11 that could help solve the issue, but I am not sure it helps if those are not in pybind11 already. Maybe there is something in there if we try to mimick the behavior the PR implements, though.
Do you remember why the integers are preferred? The comment is from 2017, so I completely understand if you do not remember
Sadly, yeah, I spun down those brain cells a while ago. The current behavior definitely seems like a bug that needs fixing though! I wonder if just adding an overload for
float
would work?
I tried that, but for some reason float
does not seem to be picked up as an explicit overload equivalent to np.float32
.
Reordering to have double
before bool
and int
fixes the issue, but I am not sure if that also changes behavior elsewhere. It seems to fix it by catching np.float32
as an implicit conversion to double
in the second pass. The same goes for float
if it comes before bool
or int
in the chain of overloads.
PTAL at https://github.com/halide/Halide/pull/8420 -- I very much want feedback from @dragly and @runenordmo before landing that change
With halide-19.0.0.dev36, a
numpy.float32(1.2)
as an argument tohalide.Expr
's constructor will give a resultingExpr
of typehl.Bool()
:Earlier versions like halide 13 also give unexpected results:
<halide.Expr of type int32: 4>
.I'm not sure if this is an unintended use case or unintended behavior. I initially discovered it because
np.float32(np.pi)
was part of a calculation and it ended up being treated as an integer, giving wrong results.I would be greatful if you have any ideas to fix or avoid this type of behavior. (Short term, we are keeping away from numpy.float32)
Here is a minimal example/test: