| 301 | } |
| 302 | |
| 303 | nb::object PyTensor::numpy(bool copy) const { |
| 304 | validate(); |
| 305 | Tensor cpu_tensor = tensor_.device() == Device::CUDA ? tensor_.cpu() : tensor_; |
| 306 | |
| 307 | // Ensure contiguous |
| 308 | if (!cpu_tensor.is_contiguous()) { |
| 309 | cpu_tensor = cpu_tensor.contiguous(); |
| 310 | } |
| 311 | |
| 312 | const auto& dims = cpu_tensor.shape().dims(); |
| 313 | size_t elem_size = 4; |
| 314 | switch (cpu_tensor.dtype()) { |
| 315 | case DataType::Float32: elem_size = 4; break; |
| 316 | case DataType::Float16: elem_size = 2; break; |
| 317 | case DataType::Int32: elem_size = 4; break; |
| 318 | case DataType::Int64: elem_size = 8; break; |
| 319 | case DataType::UInt8: |
| 320 | case DataType::Bool: elem_size = 1; break; |
| 321 | } |
| 322 | |
| 323 | if (copy) { |
| 324 | // Copy mode: allocate new buffer and copy data |
| 325 | const size_t total_bytes = cpu_tensor.numel() * elem_size; |
| 326 | void* const buffer = std::malloc(total_bytes); |
| 327 | if (!buffer) { |
| 328 | throw std::bad_alloc(); |
| 329 | } |
| 330 | std::memcpy(buffer, cpu_tensor.data_ptr(), total_bytes); |
| 331 | |
| 332 | // Create owner capsule for memory management |
| 333 | nb::capsule owner(buffer, [](void* p) noexcept { std::free(p); }); |
| 334 | |
| 335 | // Use nb::shape to create proper shape object |
| 336 | switch (cpu_tensor.dtype()) { |
| 337 | case DataType::Float32: { |
| 338 | if (dims.size() == 1) { |
| 339 | return nb::cast(nb::ndarray<nb::numpy, float, nb::shape<-1>>( |
| 340 | buffer, {dims[0]}, owner)); |
| 341 | } else if (dims.size() == 2) { |
| 342 | return nb::cast(nb::ndarray<nb::numpy, float, nb::shape<-1, -1>>( |
| 343 | buffer, {dims[0], dims[1]}, owner)); |
| 344 | } else if (dims.size() == 3) { |
| 345 | return nb::cast(nb::ndarray<nb::numpy, float, nb::shape<-1, -1, -1>>( |
| 346 | buffer, {dims[0], dims[1], dims[2]}, owner)); |
| 347 | } else { |
| 348 | // Fallback for higher dimensions - use dynamic ndarray |
| 349 | std::vector<size_t> shape_vec(dims.begin(), dims.end()); |
| 350 | return nb::cast(nb::ndarray<nb::numpy, float>( |
| 351 | buffer, dims.size(), shape_vec.data(), owner)); |
| 352 | } |
| 353 | } |
| 354 | case DataType::Int32: { |
| 355 | if (dims.size() == 1) { |
| 356 | return nb::cast(nb::ndarray<nb::numpy, int32_t, nb::shape<-1>>( |
| 357 | buffer, {dims[0]}, owner)); |
| 358 | } else if (dims.size() == 2) { |
| 359 | return nb::cast(nb::ndarray<nb::numpy, int32_t, nb::shape<-1, -1>>( |
| 360 | buffer, {dims[0], dims[1]}, owner)); |