deep-atomic 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deep_atomic
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: A simple deep learning framework built upon numpy only
5
5
  Author-email: Elliot Zhang <elliot_zh@proton.me>
6
6
  License: MIT
@@ -18,12 +18,21 @@ Requires-Dist: pytest>=7.0; extra == 'dev'
18
18
  Requires-Dist: ruff>=0.1.0; extra == 'dev'
19
19
  Description-Content-Type: text/markdown
20
20
 
21
- # deep-atomic
21
+ # Deep Atomic
22
22
 
23
23
  A simple deep learning framework built upon numpy only. Mainly for practice and learning.
24
24
 
25
25
  ## Usage
26
26
 
27
+ ### Installation
28
+
29
+ ```bash
30
+ pip install deep-atomic
31
+
32
+ # or using uv
33
+ uv add deep-atomic
34
+ ```
35
+
27
36
  ### Import
28
37
 
29
38
  ```python
@@ -48,44 +57,84 @@ Most essential deep‑learning operations are implemented. For those that also e
48
57
  ```python
49
58
  a, b = da.Tensor(np.random.rand(3, 4)), da.Tensor(np.random.rand(3, 4))
50
59
 
51
- # Arithmetic & math
52
60
  c = a + b # addition
53
61
  c = a - b # subtraction
54
62
  c = a * b # element‑wise multiplication
55
63
  c = a / b # element‑wise division
56
64
  c = a ** b # element‑wise power
57
65
  c = a @ b # matrix multiplication
66
+
58
67
  c = da.exp(a)
59
68
  c = da.log(a)
69
+ c = da.sin(a)
70
+ # the same for cos, tan, arcsin, arccos, arctan, sinh, cosh, tanh, arcsinh, arccosh, arctanh
71
+
72
+ d = a < b # element-wise comparison, create a boolean tensor
73
+ e = a <= b
74
+ c = a > b
75
+ c = a >= b
76
+ c = a == b
77
+ c = a != b
78
+ c = da.fmax(a, b) # IMPORTANT: here da.fmax is identical to da.maximum, for simplicity. same for da.fmin / da.minimum
79
+ c = da.maximum(a, b)
80
+ c = da.fmin(a, b)
81
+ c = da.minimum(a, b)
82
+
83
+ c = d & e # element-wise and
84
+ c = d | e # element-wise or
85
+ c = d ^ e # element-wise xor
86
+ c = da.logical_not(d) # element-wise not
87
+ c = d.all(axis=-1) # reduction of and operation
88
+ c = d.any(axis=-1) # reduction of or operation
89
+ c = da.where(d, a, b) # return elements chosen from a or b depending on condition
90
+
91
+ c = da.topk(a, 2, axis=-1, largest=True) # same as pytorch. axis=-1, largest=True by default
60
92
 
61
- # Reductions
62
93
  c = da.sum(a) # shape: (1,)
63
94
  c = da.sum(a, axis=1) # shape: (3,)
64
95
  c = da.sum(a, axis=1, keepdims=True) # shape: (3, 1)
65
96
  # min, max, argmin, argmax follow the same signature
66
97
 
67
- # Softmax
68
- c = da.softmax(a, axis=-1)
69
- c = da.log_softmax(a, axis=-1)
98
+ c = da.softmax(a, axis=-1, temperature=0.6) # support temperature. temperature=1 by default
99
+ c = da.log_softmax(a, axis=-1, temperature=0.6)
100
+
101
+ c = da.sigmoid(a)
102
+ c = da.silu(a)
103
+ c = da.relu(a)
104
+ c = da.gelu(a) # Deep Atomic uses the tanh approximation for speed and convenience
70
105
 
71
- # Shape manipulations
72
106
  c = a.reshape(2, 6)
73
107
  c = a.reshape(1, 12).squeeze(0) # shape: (12,)
74
- c = da.expand_dims(a, -1) # shape: (3, 4, 1)
108
+ c = da.expand_dims(a, -1) # shape: (3, 4, 1)
75
109
  c = a.expand_dims(-1) # method‑style alternative
76
110
  c = a.repeat(2, axis=1) # shape: (3, 8)
77
- c = da.tile(a, (2, 2)) # shape: (6, 8)
111
+ c = da.tile(a, (2, 2)) # shape: (6, 8)
78
112
  c = a.tile(2, 2) # method‑style alternative
79
113
  ```
80
114
 
115
+ ### Autograd
116
+
117
+ Autograd is supported via computational graph.
118
+ _Currently only support scalar source points._
119
+
120
+ ```python
121
+ x = Tensor(np.random.rand(3, 4)) # requires_grad == True by default
122
+ res = ... # some calculation related to x. res is a **scalar** result
123
+ res.backward()
124
+ print(res.grad) # gradient get!
125
+ ```
126
+
81
127
  ## Todo
82
128
 
83
129
  - [ ] more basic operations and their autograd
130
+ - [x] topk
131
+ - [x] boolean operations
132
+ - [x] gather or take_along_axis
133
+ - [ ] scatter
84
134
  - [ ] convolution and 2d convolution
85
- - [ ] topk
86
- - [ ] boolean and masked operation
135
+ - [ ] direct masking and indexing via `[]` syntax
87
136
  - [ ] einsum
88
- - [ ] scatter
137
+ - [ ] support backward with Vector-Jacobian Product like pytorch
89
138
  - [ ] basic neural network classes
90
139
  - [ ] optimizers and loss functions
91
140
  - [ ] full training test
@@ -93,25 +142,23 @@ c = a.tile(2, 2) # method‑style alternative
93
142
  - [ ] attention layers and full LLM training
94
143
  - [ ] finer type annotations, comments and documentation
95
144
 
96
- ## Installation
97
-
98
- ```bash
99
- pip install deep-atomic
100
-
101
- # or with uv
102
- uv add deep-atomic
103
- ```
104
-
105
145
  ## Development
106
146
 
107
- Install in editable mode with development dependencies:
147
+ Recommend manage dependencies using [uv](https://github.com/astral-sh/uv).
108
148
 
109
149
  ```bash
110
- pip install -e ".[dev]"
150
+ uv sync
111
151
  ```
112
152
 
113
153
  Run tests:
114
154
 
115
155
  ```bash
156
+ cd tests
116
157
  pytest
117
158
  ```
159
+
160
+ Build wheels:
161
+
162
+ ```bash
163
+ uv build
164
+ ```
@@ -1,9 +1,18 @@
1
- # deep-atomic
1
+ # Deep Atomic
2
2
 
3
3
  A simple deep learning framework built upon numpy only. Mainly for practice and learning.
4
4
 
5
5
  ## Usage
6
6
 
7
+ ### Installation
8
+
9
+ ```bash
10
+ pip install deep-atomic
11
+
12
+ # or using uv
13
+ uv add deep-atomic
14
+ ```
15
+
7
16
  ### Import
8
17
 
9
18
  ```python
@@ -28,44 +37,84 @@ Most essential deep‑learning operations are implemented. For those that also e
28
37
  ```python
29
38
  a, b = da.Tensor(np.random.rand(3, 4)), da.Tensor(np.random.rand(3, 4))
30
39
 
31
- # Arithmetic & math
32
40
  c = a + b # addition
33
41
  c = a - b # subtraction
34
42
  c = a * b # element‑wise multiplication
35
43
  c = a / b # element‑wise division
36
44
  c = a ** b # element‑wise power
37
45
  c = a @ b # matrix multiplication
46
+
38
47
  c = da.exp(a)
39
48
  c = da.log(a)
49
+ c = da.sin(a)
50
+ # the same for cos, tan, arcsin, arccos, arctan, sinh, cosh, tanh, arcsinh, arccosh, arctanh
51
+
52
+ d = a < b # element-wise comparison, create a boolean tensor
53
+ e = a <= b
54
+ c = a > b
55
+ c = a >= b
56
+ c = a == b
57
+ c = a != b
58
+ c = da.fmax(a, b) # IMPORTANT: here da.fmax is identical to da.maximum, for simplicity. same for da.fmin / da.minimum
59
+ c = da.maximum(a, b)
60
+ c = da.fmin(a, b)
61
+ c = da.minimum(a, b)
62
+
63
+ c = d & e # element-wise and
64
+ c = d | e # element-wise or
65
+ c = d ^ e # element-wise xor
66
+ c = da.logical_not(d) # element-wise not
67
+ c = d.all(axis=-1) # reduction of and operation
68
+ c = d.any(axis=-1) # reduction of or operation
69
+ c = da.where(d, a, b) # return elements chosen from a or b depending on condition
70
+
71
+ c = da.topk(a, 2, axis=-1, largest=True) # same as pytorch. axis=-1, largest=True by default
40
72
 
41
- # Reductions
42
73
  c = da.sum(a) # shape: (1,)
43
74
  c = da.sum(a, axis=1) # shape: (3,)
44
75
  c = da.sum(a, axis=1, keepdims=True) # shape: (3, 1)
45
76
  # min, max, argmin, argmax follow the same signature
46
77
 
47
- # Softmax
48
- c = da.softmax(a, axis=-1)
49
- c = da.log_softmax(a, axis=-1)
78
+ c = da.softmax(a, axis=-1, temperature=0.6) # support temperature. temperature=1 by default
79
+ c = da.log_softmax(a, axis=-1, temperature=0.6)
80
+
81
+ c = da.sigmoid(a)
82
+ c = da.silu(a)
83
+ c = da.relu(a)
84
+ c = da.gelu(a) # Deep Atomic uses the tanh approximation for speed and convenience
50
85
 
51
- # Shape manipulations
52
86
  c = a.reshape(2, 6)
53
87
  c = a.reshape(1, 12).squeeze(0) # shape: (12,)
54
- c = da.expand_dims(a, -1) # shape: (3, 4, 1)
88
+ c = da.expand_dims(a, -1) # shape: (3, 4, 1)
55
89
  c = a.expand_dims(-1) # method‑style alternative
56
90
  c = a.repeat(2, axis=1) # shape: (3, 8)
57
- c = da.tile(a, (2, 2)) # shape: (6, 8)
91
+ c = da.tile(a, (2, 2)) # shape: (6, 8)
58
92
  c = a.tile(2, 2) # method‑style alternative
59
93
  ```
60
94
 
95
+ ### Autograd
96
+
97
+ Autograd is supported via computational graph.
98
+ _Currently only support scalar source points._
99
+
100
+ ```python
101
+ x = Tensor(np.random.rand(3, 4)) # requires_grad == True by default
102
+ res = ... # some calculation related to x. res is a **scalar** result
103
+ res.backward()
104
+ print(res.grad) # gradient get!
105
+ ```
106
+
61
107
  ## Todo
62
108
 
63
109
  - [ ] more basic operations and their autograd
110
+ - [x] topk
111
+ - [x] boolean operations
112
+ - [x] gather or take_along_axis
113
+ - [ ] scatter
64
114
  - [ ] convolution and 2d convolution
65
- - [ ] topk
66
- - [ ] boolean and masked operation
115
+ - [ ] direct masking and indexing via `[]` syntax
67
116
  - [ ] einsum
68
- - [ ] scatter
117
+ - [ ] support backward with Vector-Jacobian Product like pytorch
69
118
  - [ ] basic neural network classes
70
119
  - [ ] optimizers and loss functions
71
120
  - [ ] full training test
@@ -73,25 +122,23 @@ c = a.tile(2, 2) # method‑style alternative
73
122
  - [ ] attention layers and full LLM training
74
123
  - [ ] finer type annotations, comments and documentation
75
124
 
76
- ## Installation
77
-
78
- ```bash
79
- pip install deep-atomic
80
-
81
- # or with uv
82
- uv add deep-atomic
83
- ```
84
-
85
125
  ## Development
86
126
 
87
- Install in editable mode with development dependencies:
127
+ Recommend manage dependencies using [uv](https://github.com/astral-sh/uv).
88
128
 
89
129
  ```bash
90
- pip install -e ".[dev]"
130
+ uv sync
91
131
  ```
92
132
 
93
133
  Run tests:
94
134
 
95
135
  ```bash
136
+ cd tests
96
137
  pytest
97
138
  ```
139
+
140
+ Build wheels:
141
+
142
+ ```bash
143
+ uv build
144
+ ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "deep_atomic"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  description = "A simple deep learning framework built upon numpy only"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  """deep-atomic - A simple deep learning framework built upon numpy only"""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.2.0"
4
4
  __author__ = "elliot_zh@proton.me"
5
5
 
6
6
  from .tensor import *
@@ -97,6 +97,115 @@ class Log(SingleOp):
97
97
  self.input.backward(1 / self.input.to_np() * grad)
98
98
 
99
99
 
100
+ class Sigmoid(SingleOp):
101
+ def __init__(self, input: Tensor, output_np: np.ndarray):
102
+ super().__init__(input)
103
+ self.output_np = output_np
104
+
105
+ def backward(self, grad):
106
+ # TODO: input's actual value is no longer useful during backward, and can be released to increase memory efficiency
107
+ self.input.backward(self.output_np * (1 - self.output_np) * grad)
108
+
109
+
110
+ class Sin(SingleOp):
111
+ def __init__(self, input: Tensor):
112
+ super().__init__(input)
113
+
114
+ def backward(self, grad):
115
+ self.input.backward(np.cos(self.input.to_np()) * grad)
116
+
117
+
118
+ class Cos(SingleOp):
119
+ def __init__(self, input: Tensor):
120
+ super().__init__(input)
121
+
122
+ def backward(self, grad):
123
+ self.input.backward(-np.sin(self.input.to_np()) * grad)
124
+
125
+
126
+ class Tan(SingleOp):
127
+ def __init__(self, input: Tensor, output_np: np.ndarray):
128
+ super().__init__(input)
129
+ self.output_np = output_np
130
+
131
+ def backward(self, grad):
132
+ self.input.backward((1 + self.output_np ** 2) * grad) # faster since only polynomials are concerned
133
+
134
+
135
+ class Arcsin(SingleOp):
136
+ def __init__(self, input: Tensor):
137
+ super().__init__(input)
138
+
139
+ def backward(self, grad):
140
+ self.input.backward(1 / (1 - self.input.to_np() ** 2) ** 0.5 * grad)
141
+
142
+
143
+ class Arccos(SingleOp):
144
+ def __init__(self, input: Tensor):
145
+ super().__init__(input)
146
+
147
+ def backward(self, grad):
148
+ self.input.backward(-1 / (1 - self.input.to_np() ** 2) ** 0.5 * grad)
149
+
150
+
151
+ class Arctan(SingleOp):
152
+ def __init__(self, input: Tensor):
153
+ super().__init__(input)
154
+
155
+ def backward(self, grad):
156
+ self.input.backward(1 / (self.input.to_np() ** 2 + 1) * grad)
157
+
158
+
159
+ class Sinh(SingleOp):
160
+ def __init__(self, input: Tensor, output_np: np.ndarray):
161
+ super().__init__(input)
162
+ self.output_np = output_np
163
+
164
+ def backward(self, grad):
165
+ self.input.backward((1 + self.output_np ** 2) ** 0.5 * grad) # faster since only polynomials are concerned
166
+
167
+
168
+ class Cosh(SingleOp):
169
+ def __init__(self, input: Tensor):
170
+ super().__init__(input)
171
+
172
+ def backward(self, grad):
173
+ self.input.backward(np.sinh(self.input.to_np()) * grad)
174
+
175
+
176
+ class Tanh(SingleOp):
177
+ def __init__(self, input: Tensor, output_np: np.ndarray):
178
+ super().__init__(input)
179
+ self.output_np = output_np
180
+
181
+ def backward(self, grad):
182
+ self.input.backward((1 - self.output_np ** 2) * grad) # faster since only polynomials are concerned
183
+
184
+
185
+ class Arcsinh(SingleOp):
186
+ def __init__(self, input: Tensor):
187
+ super().__init__(input)
188
+
189
+ def backward(self, grad):
190
+ self.input.backward(1 / (self.input.to_np() ** 2 + 1) ** 0.5 * grad)
191
+
192
+
193
+ class Arccosh(SingleOp):
194
+ def __init__(self, input: Tensor):
195
+ super().__init__(input)
196
+
197
+ def backward(self, grad):
198
+ self.input.backward(1 / (self.input.to_np() ** 2 - 1) ** 0.5 * grad)
199
+
200
+
201
+ class Arctanh(SingleOp):
202
+ def __init__(self, input: Tensor):
203
+ super().__init__(input)
204
+
205
+ def backward(self, grad):
206
+ self.input.backward(1 / (1 - self.input.to_np() ** 2) * grad)
207
+
208
+
100
209
  class Pow(TwoOp):
101
210
  def __init__(self, a1, a2):
102
211
  super().__init__(a1, a2)
@@ -111,6 +220,7 @@ class Pow(TwoOp):
111
220
  class Sum(SingleOp):
112
221
  def __init__(self, input: Tensor, axis, keepdims):
113
222
  super().__init__(input)
223
+ # TODO: need to handle when axis is a tuple of int that means reducing mutiple dims at a time
114
224
  self.axis = axis
115
225
  self.keepdims = keepdims
116
226
 
@@ -244,3 +354,28 @@ class Tile(SingleOp):
244
354
  if grad.ndim < len(self.reps):
245
355
  grad = grad.reshape(self.input.shape)
246
356
  self.input.backward(grad)
357
+
358
+ # the condition is not differentiatable, so still two-operanded
359
+ class Where(TwoOp):
360
+ def __init__(self, condition, a1, a2):
361
+ super().__init__(a1, a2)
362
+ if isinstance(condition, Tensor):
363
+ condition = condition.to_np()
364
+ self.condition = condition
365
+
366
+ def backward(self, grad):
367
+ if self.a1 is not None:
368
+ self.a1.backward(np.where(self.condition, grad, 0))
369
+ if self.a2 is not None:
370
+ self.a2.backward(np.where(self.condition, 0, grad))
371
+
372
+
373
+ class TakeAlongAxis(SingleOp):
374
+ def __init__(self, input: Tensor, indices, axis):
375
+ super().__init__(input)
376
+ self.indices, self.axis = indices, axis
377
+
378
+ def backward(self, grad):
379
+ grad_ = np.zeros(self.input.shape)
380
+ np.put_along_axis(grad_, self.indices, grad, self.axis)
381
+ self.input.backward(grad_)
@@ -0,0 +1,230 @@
1
+ import numpy as np
2
+ from .tensor import *
3
+ from .graph import *
4
+ from .utils import *
5
+
6
+
7
+ # implement add, sub, mul, div etc. here
8
+ def add(a1, a2):
9
+ return a1.__array_ufunc__(np.add, "__call__", a1, a2)
10
+
11
+
12
+ def sub(a1, a2):
13
+ return a1.__array_ufunc__(np.subtract, "__call__", a1, a2)
14
+
15
+
16
+ def mul(a1, a2):
17
+ return a1.__array_ufunc__(np.multiply, "__call__", a1, a2)
18
+
19
+
20
+ def div(a1, a2):
21
+ return a1.__array_ufunc__(np.divide, "__call__", a1, a2)
22
+
23
+
24
+ def matmul(a1, a2):
25
+ return a1.__array_ufunc__(np.matmul, "__call__", a1, a2)
26
+
27
+
28
+ def pow(a1, a2):
29
+ return a1.__array_ufunc__(np.pow, "__call__", a1, a2)
30
+
31
+
32
+ def exp(input):
33
+ return input.__array_ufunc__(np.exp, "__call__", input)
34
+
35
+
36
+ def log(input):
37
+ return input.__array_ufunc__(np.log, "__call__", input)
38
+
39
+
40
+ # we do not implement exp2, log2, log10 etc. for that this framework is mainly for neural network training and for simplicity
41
+
42
+
43
+ def logical_not(input):
44
+ return input.__array_ufunc(np.logical_not, "__call__", input)
45
+
46
+ def all(input, axis=None):
47
+ return input.all(axis)
48
+
49
+ def any(input, axis=None):
50
+ return input.any(axis)
51
+
52
+
53
+ def sum(input: Tensor, axis=None, keepdims=False):
54
+ return input.__array_ufunc__(np.add, "reduce", input, axis=axis, keepdims=keepdims)
55
+
56
+
57
+ def max(input: Tensor, axis=None, keepdims=False):
58
+ res = Tensor(
59
+ np.max(input.to_np(), axis=axis, keepdims=keepdims), requires_grad=False
60
+ )
61
+ if input.requires_grad:
62
+ res.requires_grad = True
63
+ if axis == None:
64
+ res.dep = MinMax(input, None, keepdims, full_red_value=res.to_np())
65
+ else:
66
+ indices = np.argmax(input.to_np(), axis=axis, keepdims=True)
67
+ res.dep = MinMax(input, axis, keepdims, indices=indices)
68
+ return res
69
+
70
+
71
+ def min(input: Tensor, axis=None, keepdims=False):
72
+ res = Tensor(
73
+ np.min(input.to_np(), axis=axis, keepdims=keepdims), requires_grad=False
74
+ )
75
+ if input.requires_grad:
76
+ res.requires_grad = True
77
+ if axis == None:
78
+ res.dep = MinMax(input, None, keepdims, full_red_value=res.to_np())
79
+ else:
80
+ indices = np.argmin(input.to_np(), axis=axis, keepdims=True)
81
+ res.dep = MinMax(input, axis, keepdims, indices=indices)
82
+ return res
83
+
84
+
85
+ def argmax(input: Tensor, axis=-1, keepdims=False):
86
+ # no gradient concerned when performing argmax
87
+ return Tensor(
88
+ np.argmax(input.to_np(), axis=axis, keepdims=keepdims), requires_grad=False
89
+ )
90
+
91
+
92
+ def argmin(input: Tensor, axis=-1, keepdims=False):
93
+ # no gradient concerned when performing armin
94
+ return Tensor(
95
+ np.argmin(input.to_np(), axis=axis, keepdims=keepdims), requires_grad=False
96
+ )
97
+
98
+
99
+ def fmax(a1, a2):
100
+ return a1.__array_ufunc__(np.fmax, '__call__', a1, a2)
101
+
102
+
103
+ def fmin(a1, a2):
104
+ return a1.__array_ufunc__(np.fmin, '__call__', a1, a2)
105
+
106
+ # unlike pytorch, fmax and maximum, fmin and minimum are the same since Deep Atomic currently doesn't handle NaN
107
+ maximum = fmax
108
+ minimum = fmin
109
+
110
+
111
+ def softmax(input: Tensor, axis=-1, temperature=1):
112
+ input /= temperature
113
+ input = exp(input - max(input, axis=axis, keepdims=True))
114
+ return input / sum(input, axis=axis, keepdims=True)
115
+
116
+
117
+ def log_softmax(input: Tensor, axis=-1, temperature=1):
118
+ input /= temperature
119
+ input = input - max(input, axis=axis, keepdims=True)
120
+ return input - log(sum(exp(input), axis=axis, keepdims=True))
121
+
122
+
123
+ def sigmoid(input: Tensor):
124
+ res = 1 / (1 + exp(-input))
125
+ if res.requires_grad:
126
+ res.dep = Sigmoid(input, res.to_np())
127
+ return res
128
+
129
+
130
+ def sin(input):
131
+ return input.__array_ufunc__(np.sin, "__call__", input)
132
+
133
+
134
+ def cos(input):
135
+ return input.__array_ufunc__(np.cos, "__call__", input)
136
+
137
+
138
+ def tan(input):
139
+ return input.__array_ufunc__(np.tan, "__call__", input)
140
+
141
+ def arcsin(input):
142
+ return input.__array_ufunc__(np.arcsin, "__call__", input)
143
+
144
+ def arccos(input):
145
+ return input.__array_ufunc__(np.arccos, "__call__", input)
146
+
147
+ def arctan(input):
148
+ return input.__array_ufunc__(np.arctan, "__call__", input)
149
+
150
+ def sinh(input):
151
+ return input.__array_ufunc__(np.sinh, "__call__", input)
152
+
153
+ def cosh(input):
154
+ return input.__array_ufunc__(np.cosh, "__call__", input)
155
+
156
+ def tanh(input):
157
+ return input.__array_ufunc__(np.tanh, "__call__", input)
158
+
159
+ def arcsinh(input):
160
+ return input.__array_ufunc__(np.arcsinh, "__call__", input)
161
+
162
+ def arccosh(input):
163
+ return input.__array_ufunc__(np.arccosh, "__call__", input)
164
+
165
+ def arctanh(input):
166
+ return input.__array_ufunc__(np.arctanh, "__call__", input)
167
+
168
+
169
+ def relu(input: Tensor):
170
+ condition = input > 0
171
+ return where(condition, input, 0)
172
+
173
+
174
+ def silu(input: Tensor):
175
+ return input * sigmoid(input)
176
+
177
+
178
+ def gelu(input: Tensor):
179
+ return 0.5 * input * (1 + tanh(0.7978845608028654 * (input + 0.44715 * input ** 3)))
180
+
181
+
182
+ def reshape(input: Tensor, target_shape):
183
+ return input.reshape(*target_shape)
184
+
185
+
186
+ def squeeze(input: Tensor, axis):
187
+ return input.squeeze(axis)
188
+
189
+
190
+ def expand_dims(input: Tensor, axis):
191
+ return input.expand_dims(axis)
192
+
193
+
194
+ def repeats(input: Tensor, repeats, axis=None):
195
+ return input.repeat(repeats, axis)
196
+
197
+
198
+ def tile(input: Tensor, reps):
199
+ return input.tile(*reps)
200
+
201
+
202
+ def where(condition, a1, a2):
203
+ requires_grad = False
204
+ for i in (a1, a2):
205
+ if isinstance(i, Tensor) and i.requires_grad:
206
+ requires_grad = True
207
+ res = Tensor(np.where(condition, a1, a2), requires_grad=requires_grad)
208
+ if res.requires_grad: res.dep = Where(condition, a1, a2)
209
+ return res
210
+
211
+ # TODO: API design. Should I follow pytorch and add a gather?
212
+ def take_along_axis(input: Tensor, indices, axis=-1):
213
+ if isinstance(indices,Tensor): indices = indices.to_np() # cut off gradient
214
+ res = Tensor(np.take_along_axis(input, indices, axis), requires_grad=input.requires_grad)
215
+ if res.requires_grad: res.dep = TakeAlongAxis(input, indices, axis)
216
+ return res
217
+
218
+ # TODO: Tensor.implement scatter_
219
+
220
+ def topk(input: Tensor, kth, axis=-1, largest=True):
221
+ if largest:
222
+ indices = np.argpartition(input, input.shape[axis] - kth, axis)
223
+ indices_idx = [slice(None)] * input.ndim
224
+ indices_idx[axis] = slice(-kth, None)
225
+ else:
226
+ indices = np.argpartition(input, kth, axis)
227
+ indices_idx = [slice(None)] * input.ndim
228
+ indices_idx[axis] = slice(kth)
229
+ indices = indices[tuple(indices_idx)]
230
+ return take_along_axis(input, indices, axis), Tensor(indices, requires_grad=False)