Skip to content
Merged

Hdf5 #83

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Legate"
uuid = "1238f2cf-6593-4d60-9aca-2f5364e49909"
version = "0.1.2"
version = "0.1.3"

[workspace]
projects = ["test", "dev"]
Expand Down
4 changes: 2 additions & 2 deletions ext/CUDAExt/CUDAExt.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module CUDAExt

using CUDA
using Legate
# using CUDA
# using Legate

# using CxxWrap: CxxWrap
# import Legate: wrap_task, create_julia_task, SUPPORTED_TYPES, JuliaGPUTask, CxxPtr, Runtime,
Expand Down
2 changes: 1 addition & 1 deletion lib/legate_jl_wrapper/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
25.10.3
25.10.5
27 changes: 27 additions & 0 deletions lib/legate_jl_wrapper/include/wrapper.inl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

#include "legate.h"
#include "legate/io/hdf5/interface.h"
#include "legate/mapping/machine.h"
#include "legate/runtime/runtime.h"
#include "legate/timing/timing.h"
Expand Down Expand Up @@ -64,6 +65,18 @@ inline bool has_started() { return legate::has_started(); }
*/
inline bool has_finished() { return legate::has_finished(); }

/**
* @ingroup legate_wrapper
* @brief Block until all pending Legate tasks have completed.
*/
inline void runtime_sync() {
Runtime::get_runtime()->issue_execution_fence(true);
}

/**
* @ingroup legate_wrapper
* @brief Provide number of runtime processors.
*/
inline int32_t num_procs() {
return legate::Runtime::get_runtime()->get_machine().count();
}
Expand Down Expand Up @@ -416,4 +429,18 @@ inline uint64_t time_nanoseconds() {
}
} // namespace time

namespace hdf5 {
inline LogicalArray read_h5(const std::string& file_path,
const std::string& dataset_name) {
return legate::io::hdf5::from_file(std::filesystem::path(file_path),
dataset_name);
}

inline void write_h5(const LogicalArray& array, const std::string& file_path,
const std::string& dataset_name) {
legate::io::hdf5::to_file(array, std::filesystem::path(file_path),
dataset_name);
}
} // namespace hdf5

} // namespace legate_wrapper
19 changes: 16 additions & 3 deletions lib/legate_jl_wrapper/src/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,11 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
mod.method("slice", [](LogicalStore& s, int32_t dim, legate::Slice sl) {
return s.slice(dim, sl);
});
mod.method("get_physical_store",
[](LogicalStore& s) { return s.get_physical_store(); });
mod.method(
"get_physical_store",
[](LogicalStore& s, std::optional<legate::mapping::StoreTarget> target) {
return s.get_physical_store(target);
});
mod.method("equal_storage", [](LogicalStore& s, LogicalStore& other) {
return s.equal_storage(other);
});
Expand Down Expand Up @@ -228,7 +231,13 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
.method("data", &LogicalArray::data) // returns LogicalStore
.method("get_physical_array",
&LogicalArray::get_physical_array) // return PhysicalArray
.method("unbound", &LogicalArray::unbound);
.method("unbound", &LogicalArray::unbound)
.method("shape", [](const LogicalArray& arr) {
auto s = arr.data().shape();
std::vector<uint64_t> result;
for (int i = 0; i < arr.dim(); i++) result.push_back(s[i]);
return result;
});

mod.add_type<AutoTask>("AutoTask")
.method("add_input", static_cast<Variable (AutoTask::*)(LogicalArray)>(
Expand Down Expand Up @@ -290,6 +299,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
mod.method("get_runtime", &legate_wrapper::runtime::get_runtime);
mod.method("has_started", &legate_wrapper::runtime::has_started);
mod.method("has_finished", &legate_wrapper::runtime::has_finished);
mod.method("runtime_sync", &legate_wrapper::runtime::runtime_sync);
/* tasking */
mod.method("align", &legate_wrapper::tasking::align);
mod.method("domain_from_shape", &legate_wrapper::tasking::domain_from_shape);
Expand Down Expand Up @@ -341,6 +351,9 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) {
mod.method("time_microseconds", &legate_wrapper::time::time_microseconds);
mod.method("time_nanoseconds", &legate_wrapper::time::time_nanoseconds);

/* hdf5 */
mod.method("_read_h5", &legate_wrapper::hdf5::read_h5);
mod.method("_write_h5", &legate_wrapper::hdf5::write_h5);
mod.method("num_procs", &legate_wrapper::runtime::num_procs);
mod.method("num_gpus", &legate_wrapper::runtime::num_gpus);
// `block` is required — do not default at the C++ binding layer.
Expand Down
59 changes: 52 additions & 7 deletions src/api/data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ to_cxx_vector(shape) = CxxWrap.StdVector([UInt64(d) for d in shape])
to_string(ty::LegateType) = code_type_map[code(ty)]

function Base.show(io::IO, ty::LegateType)
println(io, code_type_map[code(ty)])
return println(io, code_type_map[code(ty)])
end

function Base.print(ty::LegateType)
Base.show(stdout, ty)
return Base.show(stdout, ty)
end

"""
Expand Down Expand Up @@ -203,17 +203,17 @@ slice
Return the underlying physical store of this logical store or array.
"""
function get_physical_store(x::LogicalStore)
get_physical_store(x.handle, StoreTargetOptional{StoreTarget}())
return get_physical_store(x.handle, StoreTargetOptional{StoreTarget}())
end
function get_physical_store(x::LogicalStore, target::StoreTarget)
get_physical_store(x.handle, StoreTargetOptional{StoreTarget}(target))
return get_physical_store(x.handle, StoreTargetOptional{StoreTarget}(target))
end

function get_physical_array(x::LogicalArray)
get_physical_array(x.handle, StoreTargetOptional{StoreTarget}())
return get_physical_array(x.handle, StoreTargetOptional{StoreTarget}())
end
function get_physical_array(x::LogicalArray, target::StoreTarget)
get_physical_array(x.handle, StoreTargetOptional{StoreTarget}(target))
return get_physical_array(x.handle, StoreTargetOptional{StoreTarget}(target))
end

"""
Expand Down Expand Up @@ -285,6 +285,51 @@ function get_ptr(arr::PhysicalStore)
return _get_ptr(CxxWrap.CxxPtr(arr)) # cxxwrap call
end

"""
h5read(path::String, name::String; layout::Symbol=:row) -> LogicalArray

Read a dataset from an HDF5 file into a LogicalArray.

# Arguments
- `path`: Path to the HDF5 file.
- `name`: Name of the dataset to read.

# Keywords
- `layout`: On-disk memory order. `:row` (default) for row-major files (numpy/h5py,
cuNumeric, or `h5write`); `:col` for column-major files that stored reversed dimensions
(e.g. HDF5.jl). The layout tags the returned array's `order`, and `Array` uses it to
recover the original shape and values.
"""
function h5read(path::String, name::String; layout::Symbol=:row)
layout in (:row, :col) ||
throw(ArgumentError("layout must be :row or :col, got :$(layout)"))
impl = _read_h5(path, name) # cxxwrap call
ndim = Int(dim(impl))
shp = Tuple(Int.(shape(impl)))
T = code_type_map[Int(code(type(impl)))]
return LogicalArray{T,ndim}(impl, shp, layout)
end

"""
h5write(path::String, name::String, array::LogicalArray)

Write a LogicalArray to an HDF5 dataset directly (no host copy or dimension flip). A `:col`
array reads back transposed to row-major readers; a warning gives the `layout=:col` to
round-trip it.

# Arguments
- `path`: Path to the HDF5 file.
- `name`: Name of the dataset to write.
- `array`: The array to write.
"""
function h5write(path::String, name::String, array::LogicalArray{T,N}) where {T,N}
if array.order === :col && N > 1
@warn "Writing a column-major array; read it back with " *
"`Legate.h5read($(repr(path)), $(repr(name)); layout=:col)`."
end
return _write_h5(array.handle, path, name)
end

function partition_by_tiling(store::LogicalStore{T,N}, tile_shape) where {T,N}
impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape)) # cxxwrap call
return LogicalStorePartition{T,N}(impl)
Expand All @@ -293,4 +338,4 @@ end
function partition_by_tiling(store::LogicalStore{T,N}, tile_shape, color_shape) where {T,N}
impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape), to_cxx_vector(color_shape)) # cxxwrap call
return LogicalStorePartition{T,N}(impl)
end
end
9 changes: 9 additions & 0 deletions src/api/runtime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ Check whether the Legate runtime has finished.
"""
has_finished

"""
runtime_sync()

Block until all pending Legate tasks have completed.

Useful before reading files written by `h5write` or other async operations.
"""
runtime_sync

"""
create_library(name::String) -> Library

Expand Down
11 changes: 8 additions & 3 deletions src/api/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,17 @@ Base.size(s::LogicalStore, i::Integer) = size(s)[i]
LogicalArray{T,N}

A logical view over a physical array. Supports unbound views and nullability checks.
Wraps the underlying C++ `LogicalArrayImpl`.
Wraps the underlying C++ `LogicalArrayImpl`. `order` is the store's buffer layout: `:row`
(C, cuNumeric-native) or `:col` (Fortran); `Array` uses it to convert back to Julia.
"""
struct LogicalArray{T,N}
handle::LogicalArrayImpl
dims::Union{Nothing,NTuple{N,Int}}
order::Symbol
end

function LogicalArray{T,N}(handle::LogicalArrayImpl, dims) where {T,N}
return LogicalArray{T,N}(handle, dims, :row)
end

Base.size(a::LogicalArray) = a.dims
Expand All @@ -130,12 +136,11 @@ Datatype of object within Legate. See `Legate.supported_types()` to see supporte
"""
LegateType


"""
LogicalStorePartition{T,N}
Represents a tiled partition of a `LogicalStore`. Created via `partition_by_tiling`.
Wraps the underlying C++ `LogicalStorePartitionImpl`.
"""
struct LogicalStorePartition{T,N}
handle::CxxWrap.StdLib.SharedPtr{LogicalStorePartitionImpl}
end
end
39 changes: 25 additions & 14 deletions src/utilities/attach.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ function _attach_external_sysmem(
shape::Dims{N},
attach_fn,
) where {T,N}
prod(shape) == length(arr) || throw(DimensionMismatch(
"attach shape $(shape) volume $(prod(shape)) != array length $(length(arr))",
))
prod(shape) == length(arr) || throw(
DimensionMismatch(
"attach shape $(shape) volume $(prod(shape)) != array length $(length(arr))"
),
)
ptr = Base.unsafe_convert(Ptr{Cvoid}, arr)
lshape = Shape(to_cxx_vector(collect(UInt64, shape)))
impl = attach_fn(ptr, lshape, to_legate_type(T))
Expand Down Expand Up @@ -124,50 +126,59 @@ function _row_major_buffer_to_julia(tmp::Array{T,N}, shape::Dims{N}) where {T,N}
return collect(permutedims(tmp, reverse(ntuple(identity, Val(N)))))
end

# conversion from LogicalArray to Base Julia array
function (::Type{<:Array{A}})(arr::LogicalArray{B,0}) where {A,B}
# LogicalArray -> Array. Eltype must match; no implicit cast.
function (::Type{<:Array{A}})(arr::LogicalArray{A,0}) where {A}
out = Array{A}(undef, size(arr))
attached = Legate.attach_external_row_major(out)
copyto!(attached, arr)
return out
end

function (::Type{<:Array{A}})(arr::LogicalArray{B,1}) where {A,B}
function (::Type{<:Array{A}})(arr::LogicalArray{A,1}) where {A}
out = Array{A}(undef, size(arr))
attached = Legate.attach_external_row_major(out)
copyto!(attached, arr)
return out
end

function (::Type{<:Array{A}})(arr::LogicalArray{B,N}) where {A,B,N}
# Allocate F-order buffer whose bytes match C-order for `dims`, then permute back.
function (::Type{<:Array{A}})(arr::LogicalArray{A,N}) where {A,N}
dims = Base.size(arr)
if arr.order === :col
# :col buffer already holds col-major bytes for reverse(dims); copy straight.
out = Array{A}(undef, reverse(dims))
attached = Legate.attach_external_col_major(out; shape=dims)
copyto!(attached, arr)
return out
end
# :row: fill an F-order buffer matching C-order bytes, then permute back.
tmp = Array{A}(undef, reverse(dims))
attached = Legate.attach_external_row_major(tmp; shape=dims)
copyto!(attached, arr)
return _row_major_buffer_to_julia(tmp, dims)
end

function (::Type{<:Array})(arr::LogicalArray{B,N}) where {B,N}
# Bare `Array(arr)` uses the store eltype; `Type{Array}` only so a typed mismatch errors.
function (::Type{Array})(arr::LogicalArray{B,N}) where {B,N}
return Array{B}(arr)
end

# conversion from Base Julia array to LogicalArray
# conversion from Base Julia array to LogicalArray. The Julia buffer is transposed to
# row-major (C-order) before attaching, so the resulting store is row-major (`:row`).
function (::Type{<:LogicalArray{A}})(arr::Array{B}) where {A,B}
dims = Base.size(arr)
out = Legate.create_array(A, dims)
out = Legate.create_array(collect(Int64, dims), A)
src = A === B ? arr : convert(Array{A}, arr)
tmp, shape = _julia_to_row_major_buffer(src)
attached = Legate.attach_external_row_major(tmp; shape)
copyto!(out, attached)
return out
return LogicalArray{A,length(dims)}(out.handle, out.dims, :row)
end

function (::Type{<:LogicalArray})(arr::Array{B}) where {B}
dims = Base.size(arr)
out = Legate.create_array(B, dims)
out = Legate.create_array(collect(Int64, dims), B)
tmp, shape = _julia_to_row_major_buffer(arr)
attached = Legate.attach_external_row_major(tmp; shape)
copyto!(out, attached)
return out
return LogicalArray{B,length(dims)}(out.handle, out.dims, :row)
end
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[deps]
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
Legate = "1238f2cf-6593-4d60-9aca-2f5364e49909"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[extras]
Expand Down
26 changes: 26 additions & 0 deletions test/data/make_row_major.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""Regenerate test/data/row_major.h5 (row-major file for the Julia HDF5 tests).

Datasets hold arange(prod(shape)).reshape(shape); numpy C-order => row-major file.
Run: python3 test/data/make_row_major.py
"""
import os

import h5py
import numpy as np

OUT = os.path.join(os.path.dirname(__file__), "row_major.h5")

DATASETS = {
"vec1d": (np.float64, (10,)),
"mat2d": (np.float64, (4, 5)),
"mat3d": (np.int64, (3, 4, 5)),
}

with h5py.File(OUT, "w") as f:
for name, (dtype, shape) in DATASETS.items():
arr = np.arange(int(np.prod(shape)), dtype=dtype).reshape(shape)
assert arr.flags["C_CONTIGUOUS"]
f.create_dataset(name, data=arr)

print("wrote", OUT)
Binary file added test/data/row_major.h5
Binary file not shown.
4 changes: 4 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Legate
using Test
using HDF5

const VERBOSE = get(ENV, "VERBOSE", "1") != "0"
const run_gpu_tests =
Expand All @@ -16,6 +17,9 @@ if run_gpu_tests
end
end

include("tests/hdf5.jl")
include("tests/stability.jl")

# include("tests/tasking.jl")
# if run_gpu_tests
# include("tests/tasking_gpu.jl")
Expand Down
Loading
Loading