Skip to content

Commit e6db71b

Browse files
committed
add test and debug
1 parent da83af3 commit e6db71b

7 files changed

Lines changed: 194 additions & 66 deletions

File tree

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
[![Downloads](https://pepy.tech/badge/pydynet)](https://pepy.tech/project/pydynet)
88
[![Downloads](https://static.pepy.tech/personalized-badge/pydynet?period=month&units=international_system&left_color=grey&right_color=orange&left_text=downloads/month)](https://pepy.tech/project/pydynet)
9-
![](https://img.shields.io/pypi/l/pydynet)
10-
![](https://img.shields.io/pypi/implementation/numpy)
11-
![](https://img.shields.io/github/stars/Kaslanarian/PyDyNet?style=social)
12-
![](https://img.shields.io/github/forks/Kaslanarian/PyDyNet?style=social)
9+
![x](https://img.shields.io/pypi/l/pydynet)
10+
![x](https://img.shields.io/pypi/implementation/numpy)
11+
![x](https://img.shields.io/github/stars/Kaslanarian/PyDyNet?style=social)
12+
![x](https://img.shields.io/github/forks/Kaslanarian/PyDyNet?style=social)
1313

1414
## Towards Large Language Model
1515

1616
**2025.8.12**: 实现了纯推理的llama3 (6-layer Transformer, vocab-size=32000). 参考了[这里](https://github.com/likejazz/llama3.np)的NumPy实现和数据集. 将数据集下载到`llama`文件夹即可运行:
17+
1718
```bash
1819
>>> python .\llama\infer.py
1920
There was a boy named Timmy. He loved to play with hi toy and run around outside. One day, Timmy' mom asked him to help her with the laundry. Timmy didn't want to help because he wanted to play. But hi mom said, "Timmy, you need to help me. It' important to help out."
@@ -53,7 +54,7 @@ python setup.py install
5354

5455
## Example
5556

56-
[tests](./tests)中是一些例子。运行`python examples/XXX.py`即可:
57+
[examples](./examples/)中是一些例子。运行`python examples/XXX.py`即可:
5758

5859
### AutoDiff
5960

@@ -65,7 +66,6 @@ python setup.py install
6566

6667
<img src="imgs/ad2d.png" alt="ad2" style="zoom:67%;" />
6768

68-
6969
### MLP & LeNet
7070

7171
[mlp_cnn.py](examples/mlp_cnn.py)使用全连接网络(三层+残差)和LeNet对MNIST进行分类. 训练准确率和测试准确率:

pydynet/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .tensor import (Tensor, add, sub, mul, div, pow, matmul, abs, sum, mean,
22
min, max, min, argmax, argmin, maximum, minimum, exp, log,
3-
sign, reshape, transpose, swapaxes, concat)
3+
sign, reshape, transpose, swapaxes, concat, sigmoid, tanh)
44
from .function import sqrt, square, vsplit, hsplit, dsplit, split, unsqueeze, squeeze

pydynet/core/tensor.py

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ class Graph:
1111
size = 0
1212

1313
@classmethod
14-
def add_node(cls, node):
14+
def _add_node(cls, node):
1515
'''添加图节点'''
1616
cls.node_list.append(node)
1717
cls.size += 1
1818

1919
@classmethod
20-
def free_node(cls, node):
20+
def _free_node(cls, node):
2121
node.last.clear()
2222

2323
index = cls.node_list.index(node)
@@ -87,7 +87,7 @@ def __init__(
8787
with self.device:
8888
self.grad = self.xp.zeros(self.shape, dtype=dtype)
8989
self.last: list[Tensor] = list()
90-
Graph.add_node(self)
90+
Graph._add_node(self)
9191
else:
9292
self.grad = None
9393

@@ -249,72 +249,66 @@ def __abs__(self):
249249
def __getitem__(self, key):
250250
return _get_slice(self, key)
251251

252-
def __setitem__(self, key, value):
253-
if is_grad_enable() and self.requires_grad:
252+
def _inplace(self, *others: Tensor, func):
253+
if self.requires_grad and is_grad_enable():
254254
raise ValueError(
255255
"In-place operation is forbidden in node requires grad.")
256-
if isinstance(key, Tensor):
257-
key = key.data
258256

259-
with self.device:
260-
self.data[key] = value.data if isinstance(value, Tensor) else value
257+
others = tuple(other.data if isinstance(other, Tensor) else other
258+
for other in others)
261259

262-
def __inplace(self, other, func):
263-
if self.requires_grad:
264-
raise ValueError(
265-
"In-place operation is forbidden in node requires grad.")
266-
if isinstance(other, Tensor):
267-
other = other.data
268260
with self.device:
269-
self.data[...] = func(self.data, other)
261+
func(*others)
270262
return self
271263

264+
def __setitem__(self, key, value):
265+
return self._inplace(key, value, func=self.data.__setitem__)
266+
272267
def __iadd__(self, other):
273-
return self.__inplace(other, lambda x, y: x + y)
268+
return self._inplace(other, func=self.data.__iadd__)
274269

275270
def __isub__(self, other):
276-
return self.__inplace(other, lambda x, y: x - y)
271+
return self._inplace(other, func=self.data.__isub__)
277272

278273
def __imul__(self, other):
279-
return self.__inplace(other, lambda x, y: x * y)
274+
return self._inplace(other, func=self.data.__imul__)
280275

281276
def __itruediv__(self, other):
282-
return self.__inplace(other, lambda x, y: x / y)
277+
return self._inplace(other, func=self.data.__itruediv__)
283278

284279
def __imatmul__(self, other):
285-
return self.__inplace(other, lambda x, y: x @ y)
286-
287-
def __compare(self, other, func):
288-
if isinstance(other, Tensor):
289-
other = other.data
280+
return self._inplace(other, func=self.data.__imatmul__)
290281

282+
def _compare(self, other, func):
291283
with self.device:
292-
return Tensor(func(self.data, other), self.xp.bool_, None,
293-
self.device, False)
284+
return Tensor(
285+
func(self.data,
286+
other.data if isinstance(other, Tensor) else other),
287+
self.xp.bool_, None, self.device, False)
294288

295289
@no_grad()
296290
def eq(self, other):
297-
return self.__compare(other, lambda x, y: x == y)
291+
return self._compare(other, lambda x, y: x == y)
298292

299293
@no_grad()
300294
def ne(self, other):
301-
return self.__compare(other, lambda x, y: x != y)
295+
return self._compare(other, lambda x, y: x != y)
302296

303297
@no_grad()
304298
def __lt__(self, other):
305-
return self.__compare(other, lambda x, y: x < y)
299+
return self._compare(other, lambda x, y: x < y)
306300

307301
@no_grad()
308302
def __le__(self, other):
309-
return self.__compare(other, lambda x, y: x <= y)
303+
return self._compare(other, lambda x, y: x <= y)
310304

311305
@no_grad()
312306
def __gt__(self, other):
313-
return not self.__le__(other)
307+
return self._compare(other, lambda x, y: x > y)
314308

315309
@no_grad()
316310
def __ge__(self, other):
317-
return not self.__lt__(other)
311+
return self._compare(other, lambda x, y: x >= y)
318312

319313
def backward(self, retain_graph: bool = False):
320314
'''
@@ -364,7 +358,7 @@ def backward(self, retain_graph: bool = False):
364358

365359
# if not retain graph and node is not leaf, free it
366360
if not retain_graph and not node.is_leaf:
367-
Graph.free_node(node)
361+
Graph._free_node(node)
368362

369363
def _build_edge(self, node: Tensor):
370364
node.last.append(self)
@@ -397,8 +391,8 @@ def to(self, device):
397391
def cpu(self):
398392
return self.to("cpu")
399393

400-
def cuda(self):
401-
return self.to("cuda:0")
394+
def cuda(self, id: int = 0):
395+
return self.to(f"cuda:{id}")
402396

403397
@property
404398
def xp(self):
@@ -809,7 +803,7 @@ def grad_fn(self, x: Tensor, grad) -> np.ndarray:
809803
class minimum(_BinaryOperator):
810804

811805
def forward_(self, x: Tensor, y: Tensor) -> np.ndarray:
812-
return self.xp.minimum(x, y)
806+
return self.xp.minimum(x.data, y.data)
813807

814808
def grad_fn(self, x: Tensor, grad) -> np.ndarray:
815809
return (self.data == x) * grad
@@ -983,3 +977,29 @@ def grad_fn(self, x, grad: np.ndarray):
983977
slc = [slice(None)] * grad.ndim
984978
slc[self.axis] = slice(start, end)
985979
return grad[tuple(slc)]
980+
981+
982+
class sigmoid(_UnaryOperator):
983+
'''Sigmoid运算, 我们前向传播避免了溢出问题'''
984+
985+
def forward_(self, x: Tensor) -> np.ndarray:
986+
sigmoid = self.xp.zeros(x.shape, dtype=x.dtype)
987+
sigmoid[x.data > 0] = 1 / (1 + self.xp.exp(-x.data[x.data > 0]))
988+
sigmoid[x.data <= 0] = 1 - 1 / (1 + self.xp.exp(x.data[x.data <= 0]))
989+
return sigmoid
990+
991+
def grad_fn(self, x: Tensor, grad: np.ndarray) -> np.ndarray:
992+
return self.data * (1 - self.data) * grad
993+
994+
995+
class tanh(_UnaryOperator):
996+
'''Tanh运算, 我们前向传播避免了溢出问题'''
997+
998+
def forward_(self, x: Tensor) -> np.ndarray:
999+
tanh = self.xp.zeros(x.shape, dtype=x.dtype)
1000+
tanh[x.data > 0] = 2 / (1 + self.xp.exp(-2 * x.data[x.data > 0])) - 1
1001+
tanh[x.data <= 0] = 1 - 2 / (1 + self.xp.exp(2 * x.data[x.data <= 0]))
1002+
return tanh
1003+
1004+
def grad_fn(self, x: Tensor, grad: np.ndarray) -> np.ndarray:
1005+
return (1 - self.data**2) * grad

pydynet/nn/functional.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,12 @@ def embedding(x: tensor.Tensor, weight: tensor.Tensor, padding_idx: int):
2020
return query
2121

2222

23-
class sigmoid(tensor._UnaryOperator):
24-
'''Sigmoid运算, 我们前向传播避免了溢出问题'''
25-
26-
def forward_(self, x: tensor.Tensor) -> np.ndarray:
27-
sigmoid = self.xp.zeros(x.shape, dtype=x.dtype)
28-
sigmoid[x.data > 0] = 1 / (1 + self.xp.exp(-x.data[x.data > 0]))
29-
sigmoid[x.data <= 0] = 1 - 1 / (1 + self.xp.exp(x.data[x.data <= 0]))
30-
return sigmoid
31-
32-
def grad_fn(self, x: tensor.Tensor, grad: np.ndarray) -> np.ndarray:
33-
return self.data * (1 - self.data) * grad
23+
def sigmoid(x: tensor.Tensor):
24+
return tensor.sigmoid(x)
3425

3526

36-
class tanh(tensor._UnaryOperator):
37-
'''Tanh运算, 我们前向传播避免了溢出问题'''
38-
39-
def forward_(self, x: tensor.Tensor) -> np.ndarray:
40-
tanh = self.xp.zeros(x.shape, dtype=x.dtype)
41-
tanh[x.data > 0] = 2 / (1 + self.xp.exp(-2 * x.data[x.data > 0])) - 1
42-
tanh[x.data <= 0] = 1 - 2 / (1 + self.xp.exp(2 * x.data[x.data <= 0]))
43-
return tanh
44-
45-
def grad_fn(self, x: tensor.Tensor, grad: np.ndarray) -> np.ndarray:
46-
return (1 - self.data**2) * grad
27+
def tanh(x: tensor.Tensor):
28+
return tensor.tanh(x)
4729

4830

4931
def relu(x: tensor.Tensor):

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[pytest]
2+
filterwarnings =
3+
ignore::UserWarning:pydynet

tests/test_backward.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import sys, pytest, random
2+
import numpy as np
3+
4+
sys.path.append('../pydynet')
5+
6+
np.random.seed(0)
7+
random.seed(0)
8+
9+
type_list = [np.float16, np.float32, np.float64]

tests/test_tensor_basic.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import sys, pytest, random
2+
import numpy as np
3+
from itertools import product
4+
5+
sys.path.append('../pydynet')
6+
7+
import pydynet as pdn
8+
9+
np.random.seed(0)
10+
random.seed(0)
11+
12+
type_list = [np.float16, np.float32, np.float64]
13+
14+
15+
def matmul_shape_pair(max_dim=4, max_size=5):
16+
ndim = random.randint(0, max_dim)
17+
18+
shape1 = []
19+
shape2 = []
20+
for _ in range(ndim):
21+
if random.random() < 0.5:
22+
# 50% 概率设置为 1, 确保广播可能
23+
s1, s2 = random.choice([(1, random.randint(1, max_size)),
24+
(random.randint(1, max_size), 1)])
25+
else:
26+
# 否则两边相同
27+
val = random.randint(1, max_size)
28+
s1, s2 = val, val
29+
shape1.append(s1)
30+
shape2.append(s2)
31+
shape1, shape2 = tuple(shape1), tuple(shape2)
32+
33+
m = random.randint(1, max_size)
34+
n = random.randint(1, max_size)
35+
p = random.randint(1, max_size)
36+
37+
shape1 = shape1 + (m, n)
38+
shape2 = shape2 + (n, p)
39+
40+
shape1 = shape1[random.randint(0, len(shape1) - 2):]
41+
42+
return shape1, shape2
43+
44+
45+
def broadcastable_shape_pair(max_dim=4, max_size=5):
46+
ndim = random.randint(0, max_dim) # 随机维数
47+
shape1 = []
48+
shape2 = []
49+
for _ in range(ndim):
50+
if random.random() < 0.5:
51+
# 50% 概率设置为 1, 确保广播可能
52+
s1, s2 = random.choice([(1, random.randint(1, max_size)),
53+
(random.randint(1, max_size), 1)])
54+
else:
55+
# 否则两边相同
56+
val = random.randint(1, max_size)
57+
s1, s2 = val, val
58+
shape1.append(s1)
59+
shape2.append(s2)
60+
shape1, shape2 = tuple(shape1), tuple(shape2)
61+
62+
# 随机缺失维度
63+
shape1 = shape1[random.randint(0, len(shape1)):]
64+
return shape1, shape2
65+
66+
67+
def array_pair_generator(pair_gen_func,
68+
max_dim=4,
69+
max_size=5,
70+
n_iter=4,
71+
seed=None):
72+
rng = np.random.default_rng(seed)
73+
count = 0
74+
while n_iter is None or count < n_iter:
75+
shape1, shape2 = pair_gen_func(max_dim, max_size)
76+
a = rng.standard_normal(size=shape1).astype(rng.choice(type_list))
77+
b = rng.standard_normal(size=shape2).astype(rng.choice(type_list))
78+
yield a, b
79+
count += 1
80+
81+
82+
test_list = array_pair_generator(broadcastable_shape_pair, 4, 5, 8, seed=42)
83+
func_list = [(pdn.add, np.add), (pdn.sub, np.subtract), (pdn.mul, np.multiply),
84+
(pdn.div, np.divide), (pdn.pow, np.power),
85+
(pdn.maximum, np.maximum), (pdn.minimum, np.minimum)]
86+
test_list = [(*array, *funcs)
87+
for (array, funcs) in product(test_list, func_list)]
88+
89+
90+
@pytest.mark.parametrize("operand1, operand2, pdn_func, np_func", test_list)
91+
@pytest.mark.filterwarnings("ignore:invalid value")
92+
@pytest.mark.filterwarnings("ignore:divide by zero")
93+
def test_binary_operator(operand1: np.ndarray, operand2: np.ndarray,
94+
pdn_func: callable, np_func: callable):
95+
pdn_operand1, pdn_operand2 = pdn.Tensor(operand1), pdn.Tensor(operand2)
96+
pdn_output: pdn.Tensor = pdn_func(pdn_operand1, pdn_operand2)
97+
np_output: np.ndarray = np_func(operand1, operand2)
98+
assert pdn_output.shape == np_output.shape
99+
assert pdn_output.dtype == np_output.dtype
100+
assert np.allclose(pdn_output.data, np_output, equal_nan=True)
101+
102+
103+
test_list = array_pair_generator(matmul_shape_pair, 4, 5, 8, seed=42)
104+
105+
106+
@pytest.mark.parametrize("operand1, operand2", test_list)
107+
def test_matmul(operand1: np.ndarray, operand2: np.ndarray):
108+
pdn_operand1, pdn_operand2 = pdn.Tensor(operand1), pdn.Tensor(operand2)
109+
pdn_output: pdn.Tensor = pdn.matmul(pdn_operand1, pdn_operand2)
110+
np_output: np.ndarray = np.matmul(operand1, operand2)
111+
assert pdn_output.shape == np_output.shape
112+
assert pdn_output.dtype == np_output.dtype
113+
assert np.allclose(pdn_output.data, np_output, equal_nan=True)
114+

0 commit comments

Comments
 (0)