NumHalide is header only, it's an attempt to have a feature parity with numpy using Halide for a Halide::Func and a Halide::Buffer<>.
2 projects:
- NumHalide: header only
- NumHalide_Examples: use case, currently a naive Visual Studio code, will change for a sharpmake or/and CMake settings.
The point of the project is only simply having the syntaxic sugar of Numpy available for Halide user. It will allow us to have the power of Halide!
Let's take a simple example:
Func xs = func::linspace(Float(32), 0.0f, 1.0f, width, "xs");
Func ys = func::linspace(Float(32), 0.0f, 1.0f, width, "ys");
std::vector<Func> ids = numhalide::func::meshgrid(Float(32), { xs, ys }, "meshgrid");
Func x = ids[0];
Func y = ids[1];
Var u, v, c;
Expr cx = 2.0f * (x(u, v) - 0.5f);
Expr cy = 2.0f * (y(u, v) - 0.5f);
Expr out = exp(-(cx * cx + cy * cy) / 0.25f);
Expr to8bits = cast(UInt(8), round(pow(clamp(out, 0.0f, 1.0f), 1.0f / 2.2f) * 255.0f));
Func result(UInt(8), 3, "image");
result(c, u, v) = select(c < 3, to8bits, Internal::make_const(UInt(8), 255));
That will generate a gaussian centered image in 8 bits. But what matter is the how, Halide did his magic and will produce an elegant code:
image[(((image.stride.1 * t145) + t147) + image.s0.v38.rebased)] =
select(((image.min.0 + image.s0.v38.rebased) < 3),
uint8(round((pow_f32(max(min(exp_f32((((((t140 * 0.003914f) + -1.000000f) * ((t140 * 0.003914f) + -1.000000f)) + (((t146 * 0.003914f) + -1.000000f) * ((t146 * 0.003914f) + -1.000000f))) * -4.000000f)), 1.000000f), 0.000000f), 0.454545f) * 255.000000f))), (uint8)255)
Without the need of storing xs
, ys
, ids
, cx
, cy
, ... everything done properly. Note with get this with a default scheduling of compute_root
or with Adams2019
on CPU.
Incentivise development:
https://www.patreon.com/SoufianeKHIAT
No scheduling is provided. A final design is to split the function and scheduling for a given platform is still open to change.
The NdArray is equivalent to Halide::Buffer<>.
Inspired by NumCpp. Here a modified code of NumCpp.
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
a = np.array([[1, 2], [3, 4], [5, 6]]) | nc::NdArray a = { {1, 2}, {3, 4}, {5, 6} } | |
a.reshape([2, 3]) | a.reshape(2, 3) | reshape(a, {3, 2}, {2, 3}) |
a.astype(np.double) | a.astype() | cast(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.linspace(1, 10, 5) | nc::linspace(1, 10, 5) | // Geneare 2D array, the size is implied at the realize and set_estimate. linspace(Type, 1, 10, 5) |
np.arange(3, 7) | nc::arange(3, 7) | // Geneare 2D array, the size is implied at the realize and set_estimate. arange(Type, 3, 7) |
np.eye(4) | nc::eye(4) | |
np.zeros([3, 4]) | nc::zeros(3, 4) | // Geneare 2D array, the size is implied at the realize and set_estimate. zeros(Type, 2) |
nc::NdArray(3, 4) a = 0 | ||
np.ones([3, 4]) | nc::ones(3, 4) | // Geneare 2D array, the size is implied at the realize and set_estimate. ones(Type, 2) |
nc::NdArray(3, 4) a = 1 | ||
np.nans([3, 4]) | nc::nans(3, 4) | |
nc::NdArray(3, 4) a = nc::constants::nan | ||
np.empty([3, 4]) | nc::empty(3, 4) | // Geneare 2D array, the size is implied at the realize and set_estimate. empty(Type, 2) |
nc::NdArray(3, 4) a |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
a[2, 3] | a(2, 3) | |
a[2:5, 5:8] | a(nc::Slice(2, 5), nc::Slice(5, 8)) | |
a({2, 5}, {5, 8}) | ||
a[:, 7] | a(a.rSlice(), 7) | |
a[a > 5] | a[a > 5] | |
a[a > 5] = 0 | a.putMask(a > 5, 0) |
// TODO
The random module provides simple ways to create random arrays.
NumPy | NumCpp | NumHalide |
---|---|---|
np.random.seed(666) | nc::random::seed(666) | |
np.random.randn(3, 4) | nc::random::randN(nc::Shape(3, 4)) | |
nc::random::randN({3, 4}) | ||
np.random.randint(0, 10, [3, 4]) | nc::random::randInt(nc::Shape(3, 4), 0, 10) | |
nc::random::randInt({3, 4}, 0, 10) | ||
np.random.rand(3, 4) | nc::random::rand(nc::Shape(3,4)) | |
nc::random::rand({3, 4}) | ||
np.random.choice(a, 3) | nc::random::choice(a, 3) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.stack([a, b, c], axis=0) | nc::stack({a, b, c}, nc::Axis::ROW) | |
np.vstack([a, b, c]) | nc::vstack({a, b, c}) | |
np.hstack([a, b, c]) | nc::hstack({a, b, c}) | |
np.append(a, b, axis=1) | nc::append(a, b, nc::Axis::COL) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.diagonal(a) | nc::diagonal(a) | |
np.triu(a) | nc::triu(a) | |
np.tril(a) | nc::tril(a) | |
np.flip(a, axis=0) | nc::flip(a, nc::Axis::ROW) | |
np.flipud(a) | nc::flipud(a) | |
np.fliplr(a) | nc::fliplr(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
for value in a | for(auto it = a.begin(); it < a.end(); ++it) | "a(_)" |
for(auto& value : a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.where(a > 5, a, b) | nc::where(a > 5, a, b) | |
np.any(a) | nc::any(a) | |
np.all(a) | nc::all(a) | |
np.logical_and(a, b) | nc::logical_and(a, b) | |
np.logical_or(a, b) | nc::logical_or(a, b) | |
np.isclose(a, b) | nc::isclose(a, b) | |
np.allclose(a, b) | nc::allclose(a, b) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.equal(a, b) | nc::equal(a, b) | a( _ ) == b( _ ) |
a == b | ||
np.not_equal(a, b) | nc::not_equal(a, b) | a( _ ) != b( _ ) |
a != b | ||
rows, cols = np.nonzero(a) | auto [rows, cols] = nc::nonzero(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.min(a) | nc::min(a) | RDom r( ... ); minimum(a(r)) |
np.max(a) | nc::max(a) | RDom r( ... ); maximum(a(r)) |
np.argmin(a) | nc::argmin(a) | RDom r( ... ); argmin(a(r)) |
np.argmax(a) | nc::argmax(a) | RDom r( ... ); argmax(a(r)) |
np.sort(a, axis=0) | nc::sort(a, nc::Axis::ROW) | |
np.argsort(a, axis=1) | nc::argsort(a, nc::Axis::COL) | |
np.unique(a) | nc::unique(a) | |
np.setdiff1d(a, b) | nc::setdiff1d(a, b) | |
np.diff(a) | nc::diff(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.sum(a) | nc::sum(a) | |
np.sum(a, axis=0) | nc::sum(a, nc::Axis::ROW) | |
np.prod(a) | nc::prod(a) | |
np.prod(a, axis=0) | nc::prod(a, nc::Axis::ROW) | |
np.mean(a) | nc::mean(a) | |
np.mean(a, axis=0) | nc::mean(a, nc::Axis::ROW) | |
np.count_nonzero(a) | nc::count_nonzero(a) | |
np.count_nonzero(a, axis=0) | nc::count_nonzero(a, nc::Axis::ROW) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
print(a) | a.print() | |
std::cout << a | ||
a.tofile(filename, sep=’\n’) | a.tofile(filename, '\n') | |
np.fromfile(filename, sep=’\n’) | nc::fromfile(filename, '\n') | |
np.dump(a, filename) | nc::dump(a, filename) | |
np.load(filename) | nc::load(filename) |
// TODO
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.abs(a) | nc::abs(a) | abs(a(_)) |
np.sign(a) | nc::sign(a) | |
np.remainder(a, b) | nc::remainder(a, b) | |
np.clip(a, 3, 8) | nc::clip(a, 3, 8) | clamp(a(_), 3, 8) |
np.interp(x, xp, fp) | nc::interp(x, xp, fp) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.exp(a) | nc::exp(a) | exp(a(_)) |
np.expm1(a) | nc::expm1(a) | |
np.log(a) | nc::log(a) | log(a(_)) |
np.log1p(a) | nc::log1p(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.power(a, 4) | nc::power(a, 4) | pow(a(_), 4) |
np.sqrt(a) | nc::sqrt(a) | |
np.square(a) | nc::square(a) | |
np.cbrt(a) | nc::cbrt(a) |
NumPy | NumCpp | NumHalide |
---|---|---|
np.sin(a) | nc::sin(a) | sin(a(_)) |
np.cos(a) | nc::cos(a) | cos(a(_)) |
np.tan(a) | nc::tan(a) | tan(a(_)) |
NumPy | NumCpp | NumHalide |
---|---|---|
np.sinh(a) | nc::sinh(a) | sinh(a(_)) |
np.cosh(a) | nc::cosh(a) | cosh(a(_)) |
np.tanh(a) | nc::tanh(a) | tanh(a(_)) |
NumPy | NumCpp | NumHalide |
---|---|---|
np.isnan(a) | nc::isnan(a) | |
np.isinf(a) | nc::isinf(a) |
// TODO
NumPy | NumCpp | NumHalide |
---|---|---|
np.linalg.norm(a) | nc::norm(a) | |
np.dot(a, b) | nc::dot(a, b) | |
np.linalg.det(a) | nc::linalg::det(a) | |
np.linalg.inv(a) | nc::linalg::inv(a) | |
np.linalg.lstsq(a, b) | nc::linalg::lstsq(a, b) | |
np.linalg.matrix_power(a, 3) | nc::linalg::matrix_power(a, 3) | |
Np.linalg.multi_dot(a, b, c) | nc::linalg::multi_dot({a, b, c}) | |
np.linalg.svd(a) | nc::linalg::svd(a) |