Commit 4a7839a8 authored by zhuyue's avatar zhuyue Committed by zhuyue
Browse files

Convert Python list to InfiniCore Tensor

parent 74934cdf
...@@ -38,6 +38,8 @@ from infinicore.tensor import ( ...@@ -38,6 +38,8 @@ from infinicore.tensor import (
empty, empty,
empty_like, empty_like,
from_blob, from_blob,
from_list,
from_numpy,
from_torch, from_torch,
ones, ones,
strided_empty, strided_empty,
...@@ -85,6 +87,8 @@ __all__ = [ ...@@ -85,6 +87,8 @@ __all__ = [
"empty", "empty",
"empty_like", "empty_like",
"from_blob", "from_blob",
"from_list",
"from_numpy",
"from_torch", "from_torch",
"ones", "ones",
"strided_empty", "strided_empty",
......
import ctypes
import numpy as np
import infinicore.device import infinicore.device
import infinicore.dtype import infinicore.dtype
from infinicore.lib import _infinicore from infinicore.lib import _infinicore
from .utils import to_infinicore_dtype from .utils import (
infinicore_to_numpy_dtype,
numpy_to_infinicore_dtype,
to_infinicore_dtype,
)
class Tensor: class Tensor:
...@@ -157,5 +165,142 @@ def from_torch(torch_tensor) -> Tensor: ...@@ -157,5 +165,142 @@ def from_torch(torch_tensor) -> Tensor:
dtype=infini_type._underlying, dtype=infini_type._underlying,
device=infini_device._underlying, device=infini_device._underlying,
), ),
torch_ref=torch_tensor, _torch_ref=torch_tensor,
)
def from_numpy(
np_array,
*,
dtype: infinicore.dtype = None,
device: infinicore.device = None,
) -> Tensor:
"""Convert a NumPy ndarray to an infinicore Tensor.
Args:
np_array: NumPy ndarray to convert to tensor
dtype: Optional infinicore dtype. If None, inferred from numpy array
device: Optional infinicore device. If None, defaults to CPU device
Returns:
Tensor: An infinicore tensor created from the numpy array
Raises:
TypeError: If input data is not a numpy ndarray
ValueError: If input array is empty
Note:
NumPy arrays can only be created on CPU. For CUDA devices, data is first
created on CPU, then copied to the target device.
"""
# Input validation
if not isinstance(np_array, np.ndarray):
raise TypeError(
f"Input data must be a np.ndarray, got {type(np_array).__name__}"
)
if np_array.size == 0:
raise ValueError("Input array cannot be empty")
# Determine target numpy dtype
# If dtype is specified, convert it to numpy dtype first
if dtype is not None:
np_dtype = infinicore_to_numpy_dtype(dtype)
# Create a copy with the target dtype if dtype doesn't match
# Use copy=True to ensure we don't modify the original array
if np_dtype != np_array.dtype:
np_array = np_array.astype(np_dtype, copy=True)
# Ensure C-contiguous layout
elif not np_array.flags.c_contiguous:
np_array = np.ascontiguousarray(np_array)
else:
# Ensure C-contiguous layout
if not np_array.flags.c_contiguous:
np_array = np.ascontiguousarray(np_array)
# Infer infinicore dtype if not provided
infini_type = (
dtype if dtype is not None else numpy_to_infinicore_dtype(np_array.dtype)
)
# Default to CPU device if not provided
infini_device = device if device is not None else infinicore.device("cpu", 0)
cpu_device = infinicore.device("cpu", 0)
# Create a temporary tensor on CPU using from_blob to reference numpy array
# This allows us to copy data without keeping numpy array reference
data_ptr = np_array.ctypes.data_as(ctypes.c_void_p).value
temp_tensor = Tensor(
_infinicore.from_blob(
data_ptr,
list(np_array.shape),
dtype=infini_type._underlying,
device=cpu_device._underlying,
)
) )
# Always create the result tensor on CPU first, then copy data
# This ensures we have a proper copy of the data
result = empty(list(np_array.shape), dtype=infini_type, device=cpu_device)
result.copy_(temp_tensor)
# If target device is not CPU, move the tensor to the target device
# The temporary tensor and numpy array will be garbage collected
# since we don't keep references to them
if infini_device.type != "cpu":
result = result.to(infini_device)
return result
def from_list(data, *, dtype=None, device=None) -> Tensor:
"""Convert a Python list to an infinicore Tensor.
Args:
data: Python list or nested list to convert to tensor
dtype: Optional infinicore dtype. If None, inferred from numpy array
device: Optional infinicore device. If None, defaults to CPU device
Returns:
Tensor: An infinicore tensor created from the list data
Raises:
TypeError: If input data is not a list or tuple
ValueError: If input data is empty
Note:
NumPy arrays can only be created on CPU. For CUDA devices, data is first
created on CPU, then copied to the target device.
This function internally converts the list to a numpy array and calls from_numpy.
"""
# Input validation
if not isinstance(data, (list, tuple)):
raise TypeError(
f"Input data must be a list or tuple, got {type(data).__name__}"
)
if not data:
raise ValueError("Input data cannot be empty")
# Determine target numpy dtype
# If dtype is specified, convert it to numpy dtype first
# This ensures the numpy array has the correct dtype from the start
if dtype is not None:
np_dtype = infinicore_to_numpy_dtype(dtype)
else:
np_dtype = None # Let numpy infer
# Convert Python list to numpy array with correct dtype
# NumPy arrays can only be created on CPU
# Use np.array(..., copy=True, order='C') to efficiently:
# - Convert data type (if dtype is specified)
# - Create a copy (ensuring data ownership)
# - Ensure C-contiguous memory layout
if np_dtype is not None:
np_array = np.array(data, dtype=np_dtype, copy=True, order="C")
else:
np_array = np.array(data, copy=True, order="C")
# Reuse from_numpy to create the tensor
# This avoids code duplication and ensures consistent behavior
return from_numpy(np_array, dtype=dtype, device=device)
import numpy as np
import torch import torch
import infinicore import infinicore
...@@ -45,3 +46,47 @@ def to_infinicore_dtype(torch_dtype): ...@@ -45,3 +46,47 @@ def to_infinicore_dtype(torch_dtype):
return infinicore.uint8 return infinicore.uint8
else: else:
raise ValueError(f"Unsupported torch dtype: {torch_dtype}") raise ValueError(f"Unsupported torch dtype: {torch_dtype}")
def numpy_to_infinicore_dtype(numpy_dtype):
"""Convert numpy data type to infinicore data type"""
if numpy_dtype == np.float32:
return infinicore.float32
elif numpy_dtype == np.float64:
return infinicore.float64
elif numpy_dtype == np.float16:
return infinicore.float16
elif numpy_dtype == np.int8:
return infinicore.int8
elif numpy_dtype == np.int16:
return infinicore.int16
elif numpy_dtype == np.int32:
return infinicore.int32
elif numpy_dtype == np.int64:
return infinicore.int64
elif numpy_dtype == np.uint8:
return infinicore.uint8
else:
raise ValueError(f"Unsupported numpy dtype: {numpy_dtype}")
def infinicore_to_numpy_dtype(infini_dtype):
"""Convert infinicore data type to numpy data type"""
if infini_dtype == infinicore.float32:
return np.float32
elif infini_dtype == infinicore.float64:
return np.float64
elif infini_dtype == infinicore.float16:
return np.float16
elif infini_dtype == infinicore.int8:
return np.int8
elif infini_dtype == infinicore.int16:
return np.int16
elif infini_dtype == infinicore.int32:
return np.int32
elif infini_dtype == infinicore.int64:
return np.int64
elif infini_dtype == infinicore.uint8:
return np.uint8
else:
raise ValueError(f"Unsupported infinicore dtype: {infini_dtype}")
import torch
import infinicore
def _copy_infinicore_to_torch(infinicore_tensor, torch_result_tensor):
"""Helper function: Copy infinicore tensor to torch tensor
Args:
infinicore_tensor: Source infinicore tensor
torch_result_tensor: Target torch tensor (to receive data)
Returns:
torch.Tensor: Torch tensor containing copied data
"""
# Determine the device from torch tensor
torch_device = torch_result_tensor.device
if torch_device.type == "cuda":
infini_device = infinicore.device("cuda", torch_device.index or 0)
else:
infini_device = infinicore.device("cpu", 0)
infini_result = infinicore.from_blob(
torch_result_tensor.data_ptr(),
list(torch_result_tensor.shape),
dtype=infinicore_tensor.dtype,
device=infini_device,
)
# Ensure tensor is on the same device as target
tensor_to_copy = infinicore_tensor
if tensor_to_copy.device.type != infini_device.type or \
(infini_device.type == "cuda" and tensor_to_copy.device.index != infini_device.index):
tensor_to_copy = tensor_to_copy.to(infini_device)
infini_result.copy_(tensor_to_copy)
return torch_result_tensor
def compare_with_torch(infinicore_tensor, expected_list, dtype=torch.float32, atol=1e-6):
"""Helper function: Compare infinicore tensor computation results with PyTorch expected results
Uses unified addition verification: tensor + zero_tensor == tensor
Converts all data to float32 for verification to avoid different verification paths for different data types
Args:
infinicore_tensor: infinicore tensor object
expected_list: Expected data (Python list, can be nested)
dtype: torch dtype (for compatibility, actually uses float32 for verification)
atol: Absolute tolerance for floating point comparison
Returns:
bool: Whether data matches
"""
# Verify basic attributes
expected_shape = list(torch.tensor(expected_list).shape)
assert list(infinicore_tensor.shape) == expected_shape, \
f"Shape mismatch: expected {expected_shape}, got {list(infinicore_tensor.shape)}"
# Flatten nested list and convert to float
def flatten(data):
result = []
for item in data:
if isinstance(item, (list, tuple)):
result.extend(flatten(item))
else:
result.append(float(item))
return result
flat_expected = flatten(expected_list)
# Unified verification through addition: create float32 version of tensor for verification
# This unifies verification logic and avoids different verification paths for different data types
tensor_f32 = infinicore.from_list(flat_expected, dtype=infinicore.float32)
expected_f32 = torch.tensor(flat_expected, dtype=torch.float32)
# Add with zero tensor to verify data: tensor + zero == tensor
zero_f32 = infinicore.from_list([0.0] * tensor_f32.numel(), dtype=infinicore.float32)
result = tensor_f32 + zero_f32
# Verify result
torch_result = _copy_infinicore_to_torch(result, torch.zeros_like(expected_f32))
return torch.allclose(expected_f32, torch_result, atol=atol)
# Parameterized test data: test cases for different dimensions
_TEST_SHAPES = [
([1, 2, 3, 4, 5], [5], "1D"),
([[1, 2, 3], [4, 5, 6]], [2, 3], "2D"),
([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], [2, 2, 2], "3D"),
]
def test_from_list_basic_shapes():
"""Test converting lists of different dimensions to tensor (parameterized test)"""
for data, expected_shape, dim_name in _TEST_SHAPES:
print(f"Testing {dim_name} list to tensor conversion")
# Create float tensor for addition verification
tensor_f32 = infinicore.from_list(data, dtype=infinicore.float32)
# Verify shape and data type
assert list(tensor_f32.shape) == expected_shape, \
f"{dim_name}: Shape mismatch: expected {expected_shape}, got {list(tensor_f32.shape)}"
assert tensor_f32.dtype == infinicore.float32, \
f"{dim_name}: Expected float32, got {tensor_f32.dtype}"
# Verify data correctness through addition
assert compare_with_torch(tensor_f32, data, dtype=torch.float32), \
f"{dim_name}: Data mismatch"
print(f"✓ {dim_name} list test passed")
def test_from_list_float():
"""Test converting float list to tensor"""
print("=" * 50)
print("Testing float list to tensor conversion")
data = [[1.0, 2.5, 3.7], [4.2, 5.9, 6.1]]
tensor = infinicore.from_list(data)
# Verify dtype (should be float64, as Python float defaults to float64)
assert tensor.dtype == infinicore.float64, f"Expected float64, got {tensor.dtype}"
# Use unified verification method
assert compare_with_torch(tensor, data, dtype=torch.float64), "Data mismatch"
print("✓ Float list test passed")
def test_from_list_with_dtype():
"""Test converting list to tensor with specified dtype"""
print("=" * 50)
print("Testing list to tensor conversion with specified dtype")
data = [1, 2, 3, 4, 5]
# Specify as float32
tensor = infinicore.from_list(data, dtype=infinicore.float32)
# Verify dtype
assert tensor.dtype == infinicore.float32, f"Expected float32, got {tensor.dtype}"
# Use unified verification method
assert compare_with_torch(tensor, data, dtype=torch.float32), "Data mismatch"
print("✓ Specified dtype test passed")
def test_from_list_with_device():
"""Test converting list to tensor with specified device"""
print("=" * 50)
print("Testing list to tensor conversion with specified device")
data = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
# Test CPU device
tensor_cpu = infinicore.from_list(data, dtype=infinicore.float32, device=infinicore.device("cpu", 0))
assert tensor_cpu.device.type == "cpu", "Expected CPU device"
# Verify data correctness on CPU
assert compare_with_torch(tensor_cpu, data, dtype=torch.float32), "CPU data mismatch"
# Test CUDA device (if available)
try:
# Check if CUDA is available in PyTorch
if not torch.cuda.is_available():
print("⚠ CUDA not available in PyTorch, skipping CUDA test")
else:
tensor_cuda = infinicore.from_list(data, dtype=infinicore.float32, device=infinicore.device("cuda", 0))
assert tensor_cuda.device.type == "cuda", "Expected CUDA device"
# Create PyTorch CUDA tensor for comparison
torch_expected_cuda = torch.tensor(data, dtype=torch.float32, device="cuda:0")
torch_result_cuda = torch.zeros_like(torch_expected_cuda)
# Copy infinicore CUDA tensor to PyTorch CUDA tensor and compare
_copy_infinicore_to_torch(tensor_cuda, torch_result_cuda)
assert torch.allclose(torch_expected_cuda, torch_result_cuda), "CUDA data mismatch"
print("✓ CUDA device test passed (device type and data correctness verified on CUDA)")
except Exception as e:
print(f"⚠ CUDA device not available, skipping CUDA test: {e}")
print("✓ Specified device test passed")
def test_from_list_operations():
"""Test operations on tensors created from lists"""
print("=" * 50)
print("Testing operations on tensors created from lists")
data1 = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
data2 = [[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]
t1 = infinicore.from_list(data1, dtype=infinicore.float32)
t2 = infinicore.from_list(data2, dtype=infinicore.float32)
# Test addition and multiplication
result_add = t1 + t2
result_mul = t1 * t2
# Compare with torch
torch_t1 = torch.tensor(data1, dtype=torch.float32)
torch_t2 = torch.tensor(data2, dtype=torch.float32)
torch_add = torch_t1 + torch_t2
torch_mul = torch_t1 * torch_t2
# Verify results
torch_add_result = _copy_infinicore_to_torch(result_add, torch.zeros_like(torch_add))
assert torch.allclose(torch_add, torch_add_result), "Addition result mismatch"
torch_mul_result = _copy_infinicore_to_torch(result_mul, torch.zeros_like(torch_mul))
assert torch.allclose(torch_mul, torch_mul_result), "Multiplication result mismatch"
print("✓ Operations test passed")
def test_from_list_single_element():
"""Test converting single element list to tensor"""
print("=" * 50)
print("Testing single element list to tensor conversion")
data = [42]
tensor = infinicore.from_list(data, dtype=infinicore.float32)
assert list(tensor.shape) == [1], f"Expected shape [1], got {tensor.shape}"
assert tensor.dtype == infinicore.float32, f"Expected float32, got {tensor.dtype}"
# Use unified verification method
assert compare_with_torch(tensor, data, dtype=torch.float32), "Data mismatch"
print("✓ Single element test passed")
def test_from_list_edge_cases():
"""Test edge cases"""
print("=" * 50)
print("Testing edge cases")
# Test empty list (should raise exception)
try:
infinicore.from_list([])
assert False, "Expected ValueError for empty list"
except ValueError:
pass # Expected exception
# Test non-list input (should raise exception)
try:
infinicore.from_list("not a list")
assert False, "Expected TypeError for non-list input"
except TypeError:
pass # Expected exception
# Test single scalar (wrapped in list)
data = 42
tensor = infinicore.from_list([data], dtype=infinicore.float32)
assert list(tensor.shape) == [1], f"Expected shape [1], got {tensor.shape}"
assert compare_with_torch(tensor, [data], dtype=torch.float32), "Data mismatch"
print("✓ Edge cases test passed")
if __name__ == "__main__":
print("\nStarting from_list functionality tests...\n")
try:
test_from_list_basic_shapes()
test_from_list_float()
test_from_list_with_dtype()
test_from_list_with_device()
test_from_list_operations()
test_from_list_single_element()
test_from_list_edge_cases()
print("\n" + "=" * 50)
print("✅ All tests passed!")
print("=" * 50)
except Exception as e:
print(f"\n❌ Test failed: {e}")
import traceback
traceback.print_exc()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment