Skip to content
Closed
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
1 change: 1 addition & 0 deletions xla/python/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ cc_library(
"@pybind11_abseil//pybind11_abseil:absl_casters",
] + if_cuda([
"@local_config_cuda//cuda:cuda_headers",
"//xla/stream_executor/cuda:cuda_driver",
]) + if_rocm([
"@local_config_rocm//rocm:rocm_headers",
]),
Expand Down
122 changes: 120 additions & 2 deletions xla/python/py_array.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ limitations under the License.
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "llvm/Support/Casting.h"
#include "pybind11/pytypes.h" // from @pybind11
#include "pybind11/pytypes.h" // from @pybind11
#include "pybind11_abseil/absl_casters.h" // from @pybind11_abseil
#include "xla/layout_util.h"
#include "xla/pjrt/lru_cache.h"
Expand All @@ -52,8 +52,11 @@ limitations under the License.
#include "xla/python/types.h"
#include "xla/python/util.h"
#include "xla/shape.h"
#include "xla/util.h"
#if GOOGLE_CUDA
#include "xla/stream_executor/cuda/cuda_driver.h"
#endif
#include "tsl/platform/statusor.h"
#include "xla/util.h"

namespace xla {
namespace {
Expand Down Expand Up @@ -706,6 +709,121 @@ py::dict PyArray::CudaArrayInterface() {
return result;
}

StatusOr<pybind11::object> CudaArrayInterfaceToBuffer(
const pybind11::dict& cai, std::shared_ptr<PyClient> client) {
#ifndef GOOGLE_CUDA
throw XlaRuntimeError("This operation requires CUDA support.");
#else
if (!cai.contains("data")) {
return absl::InvalidArgumentError(
"CUDA Array Interface does not define `data`");
}
if (!cai.contains("shape")) {
return absl::InvalidArgumentError(
"CUDA Array Interface does not define `shape`");
}
if (!cai.contains("typestr")) {
return absl::InvalidArgumentError(
"CUDA Array Interface does not define `typestr`");
}
if (!cai.contains("version")) {
return absl::InvalidArgumentError(
"CUDA Array Interface does not define `version`");
}
auto version = py::cast<int>(cai["version"]);
if (version < 2 || version > 3) {
LOG(WARNING) << "CUDA Array Interface version " << version
<< " support is undefined";
}
auto data = py::cast<pybind11::tuple>(cai["data"]);
auto data_value = pybind11::cast<std::intptr_t>(data[0]);
void* data_ptr = reinterpret_cast<void*>(data_value);
auto dimensions = pybind11::cast<std::vector<int64_t>>(cai["shape"]);
if (data_value == 0 && absl::c_find(dimensions, 0) == dimensions.end()) {
return absl::InvalidArgumentError(
"CUDA Array Interface `data`(=NULL) and `shape`(no zero-valued "
"dimensions) are inconsistent");
}
auto ndim = dimensions.size();
TF_ASSIGN_OR_RETURN(
PrimitiveType element_type,
DtypeToPrimitiveType(py::dtype::from_args(cai["typestr"])));

// cannot determine device_id/stream when device pointer is NULL.
int device_id =
(data_value == 0
? 0
: stream_executor::gpu::CreatedContexts::GetDeviceOrdinal(data_ptr));
TF_ASSIGN_OR_RETURN(auto device,
client->DeviceFromLocalHardwareId(device_id));
bool is_default_stream =
data_value == 0 || version == 2 ||
(version == 3 && (!cai.contains("stream") || cai["stream"].is_none()));
TF_ASSIGN_OR_RETURN(
std::intptr_t stream,
([is_default_stream, cai, device]() -> StatusOr<std::intptr_t> {
if (is_default_stream) {
return device->GetStreamForExternalReadyEvents();
} else {
auto stream_ = py::cast<std::intptr_t>(cai["stream"]);
if (stream_ == 0) {
return absl::InvalidArgumentError(
"CUDA Array Interface does not allow zero stream value");
}
return stream_;
}
}()));

std::vector<int64_t> minor_to_major(ndim);
if (cai.contains("strides") && !cai["strides"].is_none() && data_value != 0) {
std::iota(minor_to_major.begin(), minor_to_major.end(), 0);
auto strides = pybind11::cast<std::vector<int64_t>>(cai["strides"]);
if (strides.size() != ndim) {
return absl::InvalidArgumentError(
"CUDA Array Interface `shape` and `strides` dimensionalities are "
"inconsistent");
}
absl::c_sort(minor_to_major, [&](int a, int b) {
// If two dimensions have the same stride, prefer the major-to-minor
// interpretation of the ordering, since that's what JAX wants.
return (strides[a] == strides[b] ? b < a : strides[a] < strides[b]);
});
int64_t stride = ShapeUtil::ByteSizeOfPrimitiveType(element_type);
for (int64_t d : minor_to_major) {
if (dimensions[d] > 1 && strides[d] != stride) {
return absl::UnimplementedError(absl::StrCat(
"Only arrays with trivial (compact) striding are supported; "
"i.e., arrays whose striding represents a transposition of the "
"underlying buffer but not broadcasting. Dimensions were: [%s], "
"strides were [%s].",
absl::StrJoin(dimensions, ","), absl::StrJoin(strides, ",")));
}
stride *= dimensions[d];
}
} else {
std::iota(minor_to_major.rbegin(), minor_to_major.rend(), 0);
}
Shape shape = ShapeUtil::MakeShapeWithDenseLayout(element_type, dimensions,
minor_to_major);
std::function<void()> on_delete_callback = []() {};
TF_ASSIGN_OR_RETURN(
auto pjrt_buffer,
device->client()->CreateViewOfDeviceBuffer(
static_cast<char*>(data_ptr), shape, device.get(), on_delete_callback,
stream <= 2 ? std::nullopt : std::make_optional(stream)));
auto* ifrt_client =
llvm::dyn_cast_or_null<ifrt::PjRtCompatibleClient>(client->ifrt_client());
if (ifrt_client == nullptr) {
throw XlaRuntimeError(
"This operation is implemented for a PjRt-compatible backend only.");
}
TF_ASSIGN_OR_RETURN(auto ifrt_array,
ifrt_client->CreatePjRtArray(std::move(pjrt_buffer)));
return PyArray::MakeFromSingleDeviceArray(std::move(client), Traceback::Get(),
std::move(ifrt_array), false, true);
#endif // GOOGLE_CUDA
}

Status PyArray::Delete() {
for (auto& arr : py_arrays()) {
TF_RETURN_IF_ERROR(arr.Delete());
Expand Down
3 changes: 3 additions & 0 deletions xla/python/py_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ class PyArrayResultHandler {
std::vector<int64_t> shape_;
};

StatusOr<pybind11::object> CudaArrayInterfaceToBuffer(
const pybind11::dict& cai, std::shared_ptr<PyClient> cuda_client);

} // namespace xla

#endif // XLA_PYTHON_PY_ARRAY_H_
5 changes: 4 additions & 1 deletion xla/python/xla.cc
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,10 @@ static void Init(py::module_& m) {
},
py::arg("dlpack"), py::arg("cpu_backend") = nullptr,
py::arg("gpu_backend") = nullptr);

m.def("cuda_array_interface_to_buffer",
[](const pybind11::dict& cai, std::shared_ptr<PyClient> cuda_client) {
return xla::ValueOrThrow(CudaArrayInterfaceToBuffer(cai, std::move(cuda_client)));
});
BuildProfilerSubmodule(&m);
BuildOpsSubmodule(&m);
BuildOutfeedReceiverSubmodule(&m);
Expand Down
2 changes: 1 addition & 1 deletion xla/python/xla_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

# Just an internal arbitrary increasing number to help with backward-compatible
# changes. In JAX, reference this via jax._src.lib.xla_extension_version.
_version = 236
_version = 237

# Version number for MLIR:Python components.
mlir_api_version = 55
Expand Down
10 changes: 10 additions & 0 deletions xla/python/xla_extension/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,16 @@ def dlpack_managed_tensor_to_buffer(
tensor: Any, device: Device, stream: int | None
) -> ArrayImpl: ...

def cuda_array_interface_to_buffer(
cai: Dict[str, Union[
str, int, None,
Tuple[int, ...], Tuple[int, bool],
List[Tuple[str, str]],
List[Tuple[str, str, Tuple[int, ...]]]]
],
gpu_backend: Optional[Client] = ...,
) -> ArrayImpl: ...

# Legacy overload
def dlpack_managed_tensor_to_buffer(
tensor: Any,
Expand Down
12 changes: 9 additions & 3 deletions xla/stream_executor/cuda/cuda_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ class CreatedContexts {
}
}

// Return the context associated to that ptr.
static CUcontext GetAnyContext(void* ptr) {
absl::ReaderMutexLock lock(&mu_);
// Find device id from cuda pointer value.
static int GetDeviceOrdinal(void* ptr) {
int device_ordinal;
CUresult result = cuPointerGetAttribute(static_cast<void*>(&device_ordinal),
CU_POINTER_ATTRIBUTE_DEVICE_ORDINAL,
Expand All @@ -120,6 +119,13 @@ class CreatedContexts {
LOG(FATAL) << "Not able to get the device_ordinal for ptr: " << ptr
<< ". Error: " << ToString(result);
}
return device_ordinal;
}

// Return the context associated to that ptr.
static CUcontext GetAnyContext(void* ptr) {
absl::ReaderMutexLock lock(&mu_);
int device_ordinal = GetDeviceOrdinal(ptr);
CHECK_EQ(LiveOrdinal()->count(device_ordinal), 1);
CHECK(!LiveOrdinal()->at(device_ordinal).empty())
<< "Need at least one context.";
Expand Down