Introduce `Client::CopyArrays()` for batched device-to-device copy
Introduce `Client::CopyArrays()` for batched device-to-device copy

This CL introduces an IFRT client API that transfers a list of IFRT arrays to a set of new devices and memory kind.

The API is intentionally designed to impose a constraint that all arrays in a single call must share the same destination devices and memory kind. The rationale is that we don't expect batching benefit across transfers that do not share the same set of source/destination devices. Callers can always invoke `CopyArrays` multiple times if they have multiple sets of destination devices.

This API is intended as a batched version of `Array::Reshard()` (now marked as deprecated), but with a narrower interface that allows just device and memory kind changes and not "resharding". Since none of the existing IFRT implementations currently implement true resharding, we expect `Client::CopyArrays()` to be a sufficient replacement of `Array::Reshard()` for the current use case. The actual resharding is a much more involved operation that may deserve an API that is closer to execution than transfer and can be reintroduced with a separate design.

Since `CopyArrays` takes a device list instead of a sharding object, `Sharding` objects of the returned arrays must be created internally by `CopyArrays`. This makes it impossible for the caller to control the instance of `Sharding` objects to be used for returned arrays, unlike `Array::Reshard` which takes `std::shared_ptr<const Sharding>`. We may revisit the design once `DeviceList` virtualization happens.

PjRT-IFRT and IFRT Proxy implement `CopyArrays` by calling `Array::Reshard`. In future, we may implement a more efficient `CopyArrays` for IFRT Proxy.
