Skip to content

Commit ef603eb

Browse files
author
pengcheng888
committed
issue/892-添加infini.tensor函数; 移除from_numpy函数的dtype参数
1 parent 3b5afff commit ef603eb

File tree

5 files changed

+223
-46
lines changed

5 files changed

+223
-46
lines changed

python/infinicore/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
ones,
6363
strided_empty,
6464
strided_from_blob,
65+
tensor,
6566
zeros,
6667
)
6768

@@ -127,6 +128,7 @@
127128
"ones",
128129
"strided_empty",
129130
"strided_from_blob",
131+
"tensor",
130132
"zeros",
131133
]
132134

python/infinicore/nn/modules/rope.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from infinicore.nn import functional as F
55

66
from ...tensor import Tensor
7+
from ...utils import infinicore_to_numpy_dtype
78
from ..functional import RopeAlgo
89
from .module import InfiniCoreModule as Module
910

@@ -31,8 +32,13 @@ def create_sin_cos_table(
3132
max_position, head_dim, theta
3233
)
3334

34-
sin_table_infini = infinicore.from_numpy(sin_table_np, dtype=dtype, device=device)
35-
cos_table_infini = infinicore.from_numpy(cos_table_np, dtype=dtype, device=device)
35+
if dtype is not None:
36+
np_dtype = infinicore_to_numpy_dtype(dtype)
37+
sin_table_np = sin_table_np.astype(np_dtype)
38+
cos_table_np = cos_table_np.astype(np_dtype)
39+
40+
sin_table_infini = infinicore.from_numpy(sin_table_np, device=device)
41+
cos_table_infini = infinicore.from_numpy(cos_table_np, device=device)
3642

3743
return sin_table_infini, cos_table_infini
3844

python/infinicore/tensor.py

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ctypes
2+
from typing import Any, Union
23

34
import numpy as np
45

@@ -17,15 +18,18 @@ class Tensor:
1718
# Public attributes describing the Tensor
1819
_underlying: _infinicore.Tensor
1920
_torch_ref: "torch.Tensor" # noqa: F821
21+
_numpy_ref: np.ndarray # noqa: F821
22+
2023
shape: list[int]
2124
dtype: infinicore.dtype
2225
device: infinicore.device
2326

24-
def __init__(self, underlying, *, _torch_ref=None):
27+
def __init__(self, underlying, *, _torch_ref=None, _numpy_ref=None):
2528
"""An internal method. Please do not use this directly."""
2629

2730
self._underlying = underlying
2831
self._torch_ref = _torch_ref
32+
self._numpy_ref = _numpy_ref
2933

3034
def __getattr__(self, name):
3135
# Lazily construct and cache an attribute.
@@ -131,6 +135,38 @@ def narrow(self, dim, start, length):
131135
return infinicore.narrow(self, dim, start, length)
132136

133137

138+
def tensor(
139+
data: Any,
140+
*,
141+
dtype: Union[infinicore.dtype, None] = None,
142+
device: Union[infinicore.device, str, int, None] = None,
143+
pin_memory: bool = False,
144+
) -> Tensor:
145+
r"""
146+
Constructs a tensor by copying `data`.
147+
148+
Args:
149+
data (array_like): Initial data for the tensor. Can be a list, tuple, NumPy, scalar.
150+
151+
Keyword args:
152+
dtype (infinicore.dtype, optional): the desired data type of returned tensor.
153+
Default: if ``None``, infers data type from :attr:`data`.
154+
device (infinicore.device, optional): the device of the constructed tensor.
155+
"""
156+
157+
if isinstance(data, (list, tuple)):
158+
return from_list(data, dtype=dtype, device=device)
159+
elif isinstance(data, np.ndarray):
160+
if dtype is not None:
161+
np_dtype = infinicore_to_numpy_dtype(dtype)
162+
data = data.astype(np_dtype)
163+
return from_numpy(data, device=device)
164+
elif isinstance(data, (int, float)):
165+
return from_list([data], dtype=dtype, device=device).squeeze(0)
166+
167+
raise ValueError(f" Can not convert data type: {type(data)} to Tensor")
168+
169+
134170
def empty(size, *, dtype=None, device=None, pin_memory=False):
135171
return Tensor(
136172
_infinicore.empty(size, dtype._underlying, device._underlying, pin_memory)
@@ -198,28 +234,27 @@ def from_torch(torch_tensor) -> Tensor:
198234
def from_numpy(
199235
np_array,
200236
*,
201-
dtype: infinicore.dtype = None,
202237
device: infinicore.device = None,
203238
) -> Tensor:
204-
"""Convert a NumPy ndarray to an infinicore Tensor.
239+
"""
240+
Creates a Tensor from a numpy.ndarray.
205241
206242
Args:
207243
np_array: NumPy ndarray to convert to tensor
208-
dtype: Optional infinicore dtype. If None, inferred from numpy array
209244
device: Optional infinicore device. If None, defaults to CPU device
210245
211246
Returns:
212247
Tensor: An infinicore tensor created from the numpy array
213248
214249
Raises:
215250
TypeError: If input data is not a numpy ndarray
216-
ValueError: If input array is empty
251+
ValueError: If input array is empty or not C-contiguous
217252
218253
Note:
219254
NumPy arrays can only be created on CPU. For CUDA devices, data is first
220255
created on CPU, then copied to the target device.
221256
"""
222-
# Input validation
257+
223258
if not isinstance(np_array, np.ndarray):
224259
raise TypeError(
225260
f"Input data must be a np.ndarray, got {type(np_array).__name__}"
@@ -228,55 +263,29 @@ def from_numpy(
228263
if np_array.size == 0:
229264
raise ValueError("Input array cannot be empty")
230265

231-
# Determine target numpy dtype
232-
# If dtype is specified, convert it to numpy dtype first
233-
if dtype is not None:
234-
np_dtype = infinicore_to_numpy_dtype(dtype)
235-
# Create a copy with the target dtype if dtype doesn't match
236-
# Use copy=True to ensure we don't modify the original array
237-
if np_dtype != np_array.dtype:
238-
np_array = np_array.astype(np_dtype, copy=True)
239-
# Ensure C-contiguous layout
240-
elif not np_array.flags.c_contiguous:
241-
np_array = np.ascontiguousarray(np_array)
242-
else:
243-
# Ensure C-contiguous layout
244-
if not np_array.flags.c_contiguous:
245-
np_array = np.ascontiguousarray(np_array)
246-
247-
# Infer infinicore dtype if not provided
248-
infini_type = (
249-
dtype if dtype is not None else numpy_to_infinicore_dtype(np_array.dtype)
250-
)
266+
if not np_array.flags.c_contiguous:
267+
raise ValueError("Input array must be C-contiguous")
251268

252-
# Default to CPU device if not provided
253-
infini_device = device if device is not None else infinicore.device("cpu", 0)
269+
infini_type = numpy_to_infinicore_dtype(np_array.dtype)
254270
cpu_device = infinicore.device("cpu", 0)
255271

256-
# Create a temporary tensor on CPU using from_blob to reference numpy array
257-
# This allows us to copy data without keeping numpy array reference
272+
# Create a infinicore.Tensor on CPU using from_blob to reference numpy array
258273
data_ptr = np_array.ctypes.data_as(ctypes.c_void_p).value
259-
temp_tensor = Tensor(
274+
infini_tensor = Tensor(
260275
_infinicore.from_blob(
261276
data_ptr,
262277
list(np_array.shape),
263278
dtype=infini_type._underlying,
264279
device=cpu_device._underlying,
265-
)
280+
),
281+
_numpy_ref=np_array,
266282
)
267283

268-
# Always create the result tensor on CPU first, then copy data
269-
# This ensures we have a proper copy of the data
270-
result = empty(list(np_array.shape), dtype=infini_type, device=cpu_device)
271-
result.copy_(temp_tensor)
272-
273284
# If target device is not CPU, move the tensor to the target device
274-
# The temporary tensor and numpy array will be garbage collected
275-
# since we don't keep references to them
276-
if infini_device.type != "cpu":
277-
result = result.to(infini_device)
285+
if device is not None and device.type != "cpu":
286+
infini_tensor = infini_tensor.to(device)
278287

279-
return result
288+
return infini_tensor
280289

281290

282291
def from_list(data, *, dtype=None, device=None) -> Tensor:
@@ -329,4 +338,4 @@ def from_list(data, *, dtype=None, device=None) -> Tensor:
329338

330339
# Reuse from_numpy to create the tensor
331340
# This avoids code duplication and ensures consistent behavior
332-
return from_numpy(np_array, dtype=dtype, device=device)
341+
return from_numpy(np_array, device=device)

python/infinicore/utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
import numpy as np
33
import torch
44

5+
try:
6+
import torch
7+
except ImportError:
8+
torch = None
9+
print("warning: torch not available, some functions may not be available")
10+
511
import infinicore
612

713

@@ -94,4 +100,6 @@ def infinicore_to_numpy_dtype(infini_dtype):
94100
elif infini_dtype == infinicore.uint8:
95101
return np.uint8
96102
else:
97-
raise ValueError(f"Unsupported infinicore dtype: {infini_dtype}")
103+
raise ValueError(
104+
f"Cannot convert infinicore dtype: {infini_dtype} to numpy dtype"
105+
)

test/infinicore/test.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import torch
2+
import numpy as np
23
from infinicore.lib import _infinicore
34

45
import infinicore
@@ -265,10 +266,161 @@ def func6_initialize_device_relationship():
265266
z_infini.debug()
266267

267268

269+
def test7_infinicore_tensor_function():
270+
"""
271+
测试 infinicore.tensor 函数,能够传入 list, tuple, NumPy, scalar,得到一个InfiniCore.Tensor的对象
272+
"""
273+
print("\n" + "=" * 60)
274+
print("测试 infinicore.tensor 函数")
275+
print("=" * 60)
276+
277+
# 定义测试用例列表
278+
case_list = [
279+
{
280+
"name": "从 list 创建 tensor",
281+
"data": [[1.0, 2.0, 3.0, 4.0]],
282+
"kwargs": {},
283+
"expected_shape": [1, 4],
284+
"expected_dtype": None,
285+
"expected_device_type": None,
286+
},
287+
{
288+
"name": "从 tuple 创建 tensor",
289+
"data": (1.0, 2.0, 3.0, 4.0),
290+
"kwargs": {},
291+
"expected_shape": [4],
292+
"expected_dtype": None,
293+
"expected_device_type": None,
294+
},
295+
{
296+
"name": "从 NumPy.ndarray 创建 tensor",
297+
"data": np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32),
298+
"kwargs": {},
299+
"expected_shape": [2, 2],
300+
"expected_dtype": None,
301+
"expected_device_type": None,
302+
},
303+
{
304+
"name": "从 scalar (int) 创建 tensor",
305+
"data": 42,
306+
"kwargs": {},
307+
"expected_shape": [],
308+
"expected_dtype": None,
309+
"expected_device_type": None,
310+
},
311+
{
312+
"name": "从 scalar (float) 创建 tensor",
313+
"data": 3.14,
314+
"kwargs": {},
315+
"expected_shape": [],
316+
"expected_dtype": None,
317+
"expected_device_type": None,
318+
},
319+
{
320+
"name": "多维 list",
321+
"data": [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]],
322+
"kwargs": {},
323+
"expected_shape": [3, 2],
324+
"expected_dtype": None,
325+
"expected_device_type": None,
326+
},
327+
{
328+
"name": "指定 dtype (float32)",
329+
"data": [1, 2, 3],
330+
"kwargs": {"dtype": infinicore.float32},
331+
"expected_shape": [3],
332+
"expected_dtype": infinicore.float32,
333+
"expected_device_type": None,
334+
},
335+
{
336+
"name": "指定 dtype (float64)",
337+
"data": [1, 2, 3],
338+
"kwargs": {"dtype": infinicore.float64},
339+
"expected_shape": [3],
340+
"expected_dtype": infinicore.float64,
341+
"expected_device_type": None,
342+
},
343+
{
344+
"name": "指定 device (cuda)",
345+
"data": [1.0, 2.0, 3.0],
346+
"kwargs": {"device": infinicore.device("cuda", 0)},
347+
"expected_shape": [3],
348+
"expected_dtype": None,
349+
"expected_device_type": "cuda",
350+
},
351+
]
352+
353+
# 循环测试每个用例
354+
for i, case in enumerate(case_list, 1):
355+
print(f"\n{i}. 测试{case['name']}:")
356+
print("-" * 40)
357+
358+
# 准备输入数据描述
359+
if isinstance(case["data"], np.ndarray):
360+
input_desc = f"shape={case['data'].shape}, dtype={case['data'].dtype}"
361+
else:
362+
input_desc = str(case["data"])
363+
364+
print(f" 输入: {input_desc}")
365+
366+
# 创建 tensor
367+
tensor = infinicore.tensor(case["data"], **case["kwargs"])
368+
369+
# 打印输出信息
370+
print(
371+
f" 输出: shape={tensor.shape}, dtype={tensor.dtype}, device={tensor.device}"
372+
)
373+
374+
# 验证结果
375+
if case["expected_shape"] is not None:
376+
assert tensor.shape == case["expected_shape"], (
377+
f"期望shape {case['expected_shape']}, 实际 {tensor.shape}"
378+
)
379+
380+
if case["expected_dtype"] is not None:
381+
assert tensor.dtype == case["expected_dtype"], (
382+
f"期望dtype {case['expected_dtype']}, 实际 {tensor.dtype}"
383+
)
384+
385+
if case["expected_device_type"] is not None:
386+
assert tensor.device.type == case["expected_device_type"], (
387+
f"期望device类型 {case['expected_device_type']}, 实际 {tensor.device.type}"
388+
)
389+
390+
print(f" ✓ {case['name']} 测试通过")
391+
392+
# 特殊测试:数据正确性验证(与 NumPy/Torch 对比)
393+
print("\n10. 测试数据正确性验证(与 NumPy 对比):")
394+
print("-" * 40)
395+
test_data = [[1.5, 2.5], [3.5, 4.5]]
396+
infini_tensor = infinicore.tensor(test_data, dtype=infinicore.float32)
397+
398+
# 转换为 torch tensor 进行验证
399+
torch_ref = torch.tensor(test_data, dtype=torch.float32)
400+
torch_result = torch.zeros(infini_tensor.shape, dtype=torch.float32)
401+
infini_blob = infinicore.from_blob(
402+
torch_result.data_ptr(),
403+
infini_tensor.shape,
404+
dtype=infinicore.float32,
405+
device=infinicore.device("cpu", 0),
406+
)
407+
infini_blob.copy_(infini_tensor)
408+
409+
max_error = torch.abs(torch_ref - torch_result).max().item()
410+
print(f" 最大误差: {max_error}")
411+
assert max_error < 1e-6, f"数据不匹配,最大误差: {max_error}"
412+
print(" ✓ 数据正确性验证通过")
413+
414+
print("\n" + "=" * 60)
415+
print("所有 infinicore.tensor 测试通过!")
416+
print("=" * 60 + "\n")
417+
418+
268419
if __name__ == "__main__":
269420
test()
270421
test2()
271422
test3()
272423
test4_to()
273424
test5_bf16()
274425
func6_initialize_device_relationship()
426+
test7_infinicore_tensor_function()

0 commit comments

Comments
 (0)