Skip to content

Commit 52b1f9b

Browse files
committed
PODVector::to_device()
Mirror existing `to_host()`
1 parent fcd4d6c commit 52b1f9b

2 files changed

Lines changed: 135 additions & 19 deletions

File tree

src/Base/PODVector.cpp

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,8 @@ namespace
162162
}
163163

164164
template <class T, class Allocator = std::allocator<T> >
165-
void make_PODVector(py::module &m, std::string typestr, std::string allocstr)
166-
{
165+
py::class_<PODVector<T, Allocator> >
166+
make_PODVector(py::module &m, std::string typestr, std::string allocstr) {
167167
using namespace amrex;
168168

169169
using PODVector_type = PODVector<T, Allocator>;
@@ -176,7 +176,7 @@ void make_PODVector(py::module &m, std::string typestr, std::string allocstr)
176176
.append(allocstr)
177177
.append("' allocation.");
178178

179-
py::class_<PODVector_type>(m, podv_name.c_str(), podv_doc.c_str())
179+
auto cl = py::class_<PODVector_type>(m, podv_name.c_str(), podv_doc.c_str())
180180
.def("__repr__",
181181
[typestr](PODVector_type const & pv) {
182182
std::stringstream s, rs;
@@ -224,15 +224,6 @@ void make_PODVector(py::module &m, std::string typestr, std::string allocstr)
224224
py::arg("strategy") = GrowthStrategy::Poisson
225225
)
226226
.def("shrink_to_fit", &PODVector_type::shrink_to_fit)
227-
.def("to_host", [](PODVector_type const & pv) {
228-
PODVector<T, amrex::PinnedArenaAllocator<T>> h_data(pv.size());
229-
amrex::Gpu::copyAsync(amrex::Gpu::deviceToHost,
230-
pv.begin(), pv.end(),
231-
h_data.begin()
232-
);
233-
Gpu::streamSynchronize();
234-
return h_data;
235-
}, py::return_value_policy::move)
236227

237228
// front
238229
// back
@@ -290,21 +281,102 @@ PODVector
290281
A new PODVector with a copy of the data.
291282
)")
292283
;
284+
285+
return cl;
286+
}
287+
288+
/** Bind the host/device copy helpers ``to_host`` and ``to_device`` on a
289+
* single PODVector class.
290+
*
291+
* Both return a different PODVector allocator type than ``cl`` (``to_host`` a
292+
* pinned vector, ``to_device`` a ``Gpu::DeviceVector`` = ``arena`` on GPU,
293+
* ``std`` on CPU). Binding them only after every allocator class exists lets
294+
* pybind11 stubgen resolve those return types to registered Python types.
295+
*/
296+
template <class T, class Allocator>
297+
void bind_host_device(py::class_<PODVector<T, Allocator> > & cl)
298+
{
299+
using namespace amrex;
300+
301+
cl.def("to_host",
302+
[](PODVector<T, Allocator> const & src) {
303+
PODVector<T, amrex::PinnedArenaAllocator<T>> h_data(src.size());
304+
amrex::Gpu::copyAsync(amrex::Gpu::deviceToHost,
305+
src.begin(), src.end(),
306+
h_data.begin()
307+
);
308+
Gpu::streamSynchronize();
309+
return h_data;
310+
},
311+
py::return_value_policy::move,
312+
"Copy this vector into a new pinned (host) PODVector. Mirrors to_device().")
313+
314+
.def("to_device",
315+
[](PODVector<T, Allocator> const & src) -> amrex::Gpu::DeviceVector<T> {
316+
amrex::Gpu::DeviceVector<T> dst(src.size());
317+
if (src.empty()) {
318+
return dst;
319+
}
320+
#ifdef AMREX_USE_GPU
321+
bool const src_host = is_host_accessible(src);
322+
bool const dst_host = is_host_accessible(dst);
323+
if (src_host && !dst_host) {
324+
Gpu::copyAsync(Gpu::hostToDevice, src.begin(), src.end(), dst.begin());
325+
Gpu::streamSynchronize();
326+
return dst;
327+
} else if (!src_host && dst_host) {
328+
Gpu::copyAsync(Gpu::deviceToHost, src.begin(), src.end(), dst.begin());
329+
Gpu::streamSynchronize();
330+
return dst;
331+
} else if (!src_host && !dst_host) {
332+
Gpu::copyAsync(Gpu::deviceToDevice, src.begin(), src.end(), dst.begin());
333+
Gpu::streamSynchronize();
334+
return dst;
335+
}
336+
#endif
337+
std::copy(src.begin(), src.end(), dst.begin());
338+
return dst;
339+
},
340+
py::return_value_policy::move,
341+
"Copy this vector into a new amrex Gpu::DeviceVector (the arena "
342+
"allocator on GPU, std on CPU), transferring across memory spaces "
343+
"as needed. Mirrors to_host().");
344+
}
345+
346+
/** Bind ``to_host``/``to_device`` on each of the given PODVector classes. */
347+
template <class... PODVectorClass>
348+
void add_host_device(PODVectorClass &... cls)
349+
{
350+
(bind_host_device(cls), ...);
293351
}
294352

295353
template <class T>
296354
void make_PODVector(py::module &m, std::string typestr)
297355
{
298356
// see Src/Base/AMReX_GpuContainers.H
299-
make_PODVector<T, amrex::PinnedArenaAllocator<T>> (m, typestr, "pinned");
300-
make_PODVector<T, amrex::ArenaAllocator<T>> (m, typestr, "arena");
301-
make_PODVector<T, std::allocator<T>> (m, typestr, "std");
357+
auto pv_pinned = make_PODVector<T, amrex::PinnedArenaAllocator<T>> (m, typestr, "pinned");
358+
auto pv_arena = make_PODVector<T, amrex::ArenaAllocator<T>> (m, typestr, "arena");
359+
auto pv_std = make_PODVector<T, std::allocator<T>> (m, typestr, "std");
360+
#ifdef AMREX_USE_GPU
361+
auto pv_device = make_PODVector<T, amrex::DeviceArenaAllocator<T>> (m, typestr, "device");
362+
auto pv_managed = make_PODVector<T, amrex::ManagedArenaAllocator<T>> (m, typestr, "managed");
363+
auto pv_async = make_PODVector<T, amrex::AsyncArenaAllocator<T>> (m, typestr, "async");
364+
#endif
365+
auto pv_polymorphic = make_PODVector<T, amrex::PolymorphicArenaAllocator<T>> (m, typestr, "polymorphic");
366+
367+
// bind to_host/to_device now that every PODVector allocator class is
368+
// registered, so their PODVector return types resolve to known Python types
369+
add_host_device(
370+
pv_pinned,
371+
pv_arena,
372+
pv_std,
302373
#ifdef AMREX_USE_GPU
303-
make_PODVector<T, amrex::DeviceArenaAllocator<T>> (m, typestr, "device");
304-
make_PODVector<T, amrex::ManagedArenaAllocator<T>> (m, typestr, "managed");
305-
make_PODVector<T, amrex::AsyncArenaAllocator<T>> (m, typestr, "async");
374+
pv_device,
375+
pv_managed,
376+
pv_async,
306377
#endif
307-
make_PODVector<T, amrex::PolymorphicArenaAllocator<T>> (m, typestr, "polymorphic");
378+
pv_polymorphic
379+
);
308380

309381
// Implement AMReX_GpuContainers.H
310382
// Alias matching Gpu::DeviceVector<T> etc. — resolves per platform:

tests/test_podvector.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,50 @@ def test_from_numpy_normalizes_input():
9090
np.testing.assert_array_equal(result2, np.array([4.0, 5.0, 6.0], result2.dtype))
9191

9292

93+
def test_to_device_empty():
94+
podv = amr.PODVector_int_std()
95+
device = podv.to_device()
96+
assert isinstance(device, amr.DeviceVector_int)
97+
assert device.size() == 0
98+
assert device.empty()
99+
100+
101+
def test_to_device_from_host_vector():
102+
import numpy as np
103+
104+
values = np.array([1, -2, 5, 8], dtype=np.int32)
105+
podv = amr.PODVector_int_std.from_numpy(values)
106+
device = podv.to_device()
107+
108+
assert isinstance(device, amr.DeviceVector_int)
109+
assert device.size() == values.size
110+
result = device.to_numpy(copy=True)
111+
np.testing.assert_array_equal(result, values.astype(result.dtype))
112+
113+
podv[0] = 99
114+
np.testing.assert_array_equal(
115+
device.to_numpy(copy=True), values.astype(result.dtype)
116+
)
117+
118+
119+
def test_to_device_from_device_vector():
120+
import numpy as np
121+
122+
values = np.array([1.0, 2.5, -3.0], dtype=np.float64)
123+
podv = amr.DeviceVector_real.from_numpy(values)
124+
device = podv.to_device()
125+
126+
assert isinstance(device, amr.DeviceVector_real)
127+
assert device.size() == values.size
128+
result = device.to_numpy(copy=True)
129+
np.testing.assert_array_equal(result, values.astype(result.dtype))
130+
131+
podv[1] = 7.0
132+
np.testing.assert_array_equal(
133+
device.to_numpy(copy=True), values.astype(result.dtype)
134+
)
135+
136+
93137
@pytest.mark.skipif(not amr.Config.have_gpu, reason="requires AMReX GPU support")
94138
def test_from_numpy_device_only():
95139
# device-only allocator: the host-to-device copy must work without CuPy

0 commit comments

Comments
 (0)