Skip to content

Batch per-slice device_put in transfer_arrays_to_host to reduce D2H dispatch overhead #3493

Description

@anveshjayakrishna

Summary

In replica_slices.transfer_arrays_to_host, the pinned-host transfer path issues one jax.device_put per replica slice (replica_slices.py#L451). Each device_put carries a fixed per-call dispatch cost independent of slice size, so when saving many arrays the dispatch count can dominate the transfer step.

Proposal

jax.device_put accepts a list of arrays plus a matching list of shardings and issues them in a single dispatch. Batching all pinned-host slices into one device_put pays the dispatch cost once. Non-pinned slices keep the existing copy_to_host_async() path. This is behavior-preserving: the same slices/data are transferred with the same replica-parallel semantics, and peak host memory is unchanged (all buffers were already held until the final await). e.g.:

# Current: one device_put per slice
for rslice in rslices:
    data = rslice.data()
    if use_pinned_host_transfer(data.device):
        data = jax.device_put(
            data,
            jax.sharding.SingleDeviceSharding(data.device, memory_kind='pinned_host'),
        )
    else:
        data.copy_to_host_async()

# Proposed: batch all pinned slices into a single device_put
pinned = [s.data() for s in rslices if use_pinned_host_transfer(s.data().device)]
on_host = jax.device_put(
    pinned,
    [jax.sharding.SingleDeviceSharding(d.device, memory_kind='pinned_host') for d in pinned],
)

Question for maintainers

Is a single batched device_put across all pinned slices acceptable, or was the per-slice form chosen for a specific reason (ordering, memory, or backend constraints)?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions