grx-tensor 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/GUIA_PRINCIPIANTES.md +763 -0
- data/README.es.md +270 -0
- data/README.md +178 -375
- data/grx-tensor.gemspec +4 -2
- data/lib/grx/c_api.rb +41 -37
- data/lib/grx/data.rb +87 -0
- data/lib/grx/loss.rb +55 -25
- data/lib/grx/nn.rb +153 -36
- data/lib/grx/optim.rb +9 -9
- data/lib/grx/serialization.rb +66 -0
- data/lib/grx/storage.rb +15 -24
- data/lib/grx/tensor.rb +198 -65
- data/lib/grx/utils.rb +28 -0
- data/lib/grx/version.rb +1 -1
- data/lib/grx.rb +14 -3
- metadata +8 -3
data/lib/grx/tensor.rb
CHANGED
|
@@ -40,7 +40,7 @@ module GRX
|
|
|
40
40
|
ones(t.shape, requires_grad: requires_grad)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# Xavier uniform initialization (optimal for linear layers with tanh/sigmoid)
|
|
44
44
|
def self.xavier_uniform(shape, requires_grad: false)
|
|
45
45
|
fan_in, fan_out = shape[-2] || 1, shape[-1] || 1
|
|
46
46
|
n = shape.reduce(1, :*)
|
|
@@ -49,9 +49,9 @@ module GRX
|
|
|
49
49
|
new(s, shape, requires_grad: requires_grad)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
#
|
|
52
|
+
# He normal initialization (optimal for layers with ReLU)
|
|
53
53
|
def self.he_normal(shape, requires_grad: false)
|
|
54
|
-
# fan_in =
|
|
54
|
+
# fan_in = number of inputs = last dim or penultimate if 2D
|
|
55
55
|
fan_in = shape.size >= 2 ? shape[-1] : shape[0]
|
|
56
56
|
n = shape.reduce(1, :*)
|
|
57
57
|
s = _alloc_raw(n)
|
|
@@ -60,13 +60,13 @@ module GRX
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
# ----------------------------------------------------------------
|
|
63
|
-
#
|
|
63
|
+
# ARITHMETIC OPERATIONS (with autograd)
|
|
64
64
|
# ----------------------------------------------------------------
|
|
65
65
|
|
|
66
66
|
def +(other)
|
|
67
67
|
case other
|
|
68
68
|
when Tensor
|
|
69
|
-
raise ShapeError, "
|
|
69
|
+
raise ShapeError, "Incompatible shapes: #{@shape} vs #{other.shape}" if @shape != other.shape
|
|
70
70
|
r = Tensor.new(_binop(:grx_add, other), @shape)
|
|
71
71
|
if requires_grad || other.requires_grad
|
|
72
72
|
r.requires_grad = true
|
|
@@ -80,14 +80,14 @@ module GRX
|
|
|
80
80
|
when Numeric
|
|
81
81
|
add_scalar(other.to_f)
|
|
82
82
|
else
|
|
83
|
-
raise TypeError, "
|
|
83
|
+
raise TypeError, "Cannot add Tensor with #{other.class}"
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def -(other)
|
|
88
88
|
case other
|
|
89
89
|
when Tensor
|
|
90
|
-
raise ShapeError, "
|
|
90
|
+
raise ShapeError, "Incompatible shapes: #{@shape} vs #{other.shape}" if @shape != other.shape
|
|
91
91
|
r = Tensor.new(_binop(:grx_sub, other), @shape)
|
|
92
92
|
if requires_grad || other.requires_grad
|
|
93
93
|
r.requires_grad = true
|
|
@@ -101,14 +101,14 @@ module GRX
|
|
|
101
101
|
when Numeric
|
|
102
102
|
add_scalar(-other.to_f)
|
|
103
103
|
else
|
|
104
|
-
raise TypeError, "
|
|
104
|
+
raise TypeError, "Cannot subtract Tensor with #{other.class}"
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def *(other)
|
|
109
109
|
case other
|
|
110
110
|
when Tensor
|
|
111
|
-
raise ShapeError, "
|
|
111
|
+
raise ShapeError, "Incompatible shapes: #{@shape} vs #{other.shape}" if @shape != other.shape
|
|
112
112
|
r = Tensor.new(_binop(:grx_mul, other), @shape)
|
|
113
113
|
if requires_grad || other.requires_grad
|
|
114
114
|
r.requires_grad = true
|
|
@@ -123,14 +123,14 @@ module GRX
|
|
|
123
123
|
when Numeric
|
|
124
124
|
scale(other.to_f)
|
|
125
125
|
else
|
|
126
|
-
raise TypeError, "
|
|
126
|
+
raise TypeError, "Cannot multiply Tensor with #{other.class}"
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def /(other)
|
|
131
131
|
case other
|
|
132
132
|
when Tensor
|
|
133
|
-
raise ShapeError, "
|
|
133
|
+
raise ShapeError, "Incompatible shapes: #{@shape} vs #{other.shape}" if @shape != other.shape
|
|
134
134
|
r = Tensor.new(_binop(:grx_div, other), @shape)
|
|
135
135
|
if requires_grad || other.requires_grad
|
|
136
136
|
r.requires_grad = true
|
|
@@ -146,7 +146,7 @@ module GRX
|
|
|
146
146
|
when Numeric
|
|
147
147
|
scale(1.0 / other.to_f)
|
|
148
148
|
else
|
|
149
|
-
raise TypeError, "
|
|
149
|
+
raise TypeError, "Cannot divide Tensor with #{other.class}"
|
|
150
150
|
end
|
|
151
151
|
end
|
|
152
152
|
|
|
@@ -155,23 +155,51 @@ module GRX
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
# ----------------------------------------------------------------
|
|
158
|
-
#
|
|
158
|
+
# SCALAR OPERATIONS
|
|
159
159
|
# ----------------------------------------------------------------
|
|
160
160
|
|
|
161
|
+
def coerce(other)
|
|
162
|
+
case other
|
|
163
|
+
when Numeric
|
|
164
|
+
# Returns reversed [self, other] wrapper to enable 2.0 * tensor
|
|
165
|
+
[Tensor.new(Storage.new(Array.new(numel, other.to_f)), @shape), self]
|
|
166
|
+
else
|
|
167
|
+
raise TypeError, "#{self.class} cannot be coerced with #{other.class}"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
161
171
|
def scale(s)
|
|
162
|
-
_unary_c(:grx_scale, s) { |v| v * s }
|
|
172
|
+
r = _unary_c(:grx_scale, s) { |v| v * s }
|
|
173
|
+
if requires_grad
|
|
174
|
+
r.requires_grad = true; r._grafo_hijos << self
|
|
175
|
+
src = self; factor = s.to_f
|
|
176
|
+
r.backward_fn = ->(g) { src.agregar_gradiente(g.scale(factor)) }
|
|
177
|
+
end
|
|
178
|
+
r
|
|
163
179
|
end
|
|
164
180
|
|
|
165
181
|
def add_scalar(s)
|
|
166
|
-
_unary_c(:grx_add_scalar, s) { |v| v + s }
|
|
182
|
+
r = _unary_c(:grx_add_scalar, s) { |v| v + s }
|
|
183
|
+
if requires_grad
|
|
184
|
+
r.requires_grad = true; r._grafo_hijos << self
|
|
185
|
+
src = self
|
|
186
|
+
r.backward_fn = ->(g) { src.agregar_gradiente(g) }
|
|
187
|
+
end
|
|
188
|
+
r
|
|
167
189
|
end
|
|
168
190
|
|
|
169
191
|
def negate
|
|
170
|
-
_unary_c(:grx_negate) { |v| -v }
|
|
192
|
+
r = _unary_c(:grx_negate) { |v| -v }
|
|
193
|
+
if requires_grad
|
|
194
|
+
r.requires_grad = true; r._grafo_hijos << self
|
|
195
|
+
src = self
|
|
196
|
+
r.backward_fn = ->(g) { src.agregar_gradiente(g.negate) }
|
|
197
|
+
end
|
|
198
|
+
r
|
|
171
199
|
end
|
|
172
200
|
|
|
173
201
|
# ----------------------------------------------------------------
|
|
174
|
-
#
|
|
202
|
+
# ELEMENT-WISE MATH (with autograd)
|
|
175
203
|
# ----------------------------------------------------------------
|
|
176
204
|
|
|
177
205
|
def abs
|
|
@@ -249,29 +277,56 @@ module GRX
|
|
|
249
277
|
CAPI.grx_clip(@storage.ptr, lo.to_f, hi.to_f, out.ptr, numel)
|
|
250
278
|
else
|
|
251
279
|
data = to_a.map { |v| v < lo ? lo : (v > hi ? hi : v) }
|
|
252
|
-
return Tensor.create(data, @shape)
|
|
280
|
+
return Tensor.create(data, @shape, requires_grad: @requires_grad)
|
|
253
281
|
end
|
|
254
|
-
Tensor.new(out, @shape)
|
|
282
|
+
r = Tensor.new(out, @shape)
|
|
283
|
+
if @requires_grad
|
|
284
|
+
r.requires_grad = true; r._grafo_hijos << self
|
|
285
|
+
src = self; l = lo.to_f; h = hi.to_f
|
|
286
|
+
r.backward_fn = ->(g) {
|
|
287
|
+
mask = Tensor.create(src.to_a.map { |v| (v >= l && v <= h) ? 1.0 : 0.0 }, src.shape)
|
|
288
|
+
src.agregar_gradiente(g * mask)
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
r
|
|
255
292
|
end
|
|
256
293
|
|
|
257
294
|
# ----------------------------------------------------------------
|
|
258
|
-
#
|
|
295
|
+
# REDUCTIONS (return differentiable scalar Tensor with autograd)
|
|
259
296
|
# ----------------------------------------------------------------
|
|
260
297
|
|
|
261
298
|
def sum
|
|
262
|
-
if CAPI::LOADED
|
|
299
|
+
val = if CAPI::LOADED
|
|
263
300
|
CAPI.grx_sum(@storage.ptr, numel)
|
|
264
301
|
else
|
|
265
302
|
to_a.sum
|
|
266
303
|
end
|
|
304
|
+
r = Tensor.create([val], [1], requires_grad: @requires_grad)
|
|
305
|
+
if @requires_grad
|
|
306
|
+
r._grafo_hijos << self
|
|
307
|
+
src = self
|
|
308
|
+
r.backward_fn = ->(g) {
|
|
309
|
+
src.agregar_gradiente(Tensor.create(Array.new(src.numel, g.item), src.shape))
|
|
310
|
+
}
|
|
311
|
+
end
|
|
312
|
+
r
|
|
267
313
|
end
|
|
268
314
|
|
|
269
315
|
def mean
|
|
270
|
-
if CAPI::LOADED
|
|
316
|
+
val = if CAPI::LOADED
|
|
271
317
|
CAPI.grx_mean(@storage.ptr, numel)
|
|
272
318
|
else
|
|
273
319
|
to_a.sum.to_f / numel
|
|
274
320
|
end
|
|
321
|
+
r = Tensor.create([val], [1], requires_grad: @requires_grad)
|
|
322
|
+
if @requires_grad
|
|
323
|
+
r._grafo_hijos << self
|
|
324
|
+
src = self; n = numel.to_f
|
|
325
|
+
r.backward_fn = ->(g) {
|
|
326
|
+
src.agregar_gradiente(Tensor.create(Array.new(src.numel, g.item / n), src.shape))
|
|
327
|
+
}
|
|
328
|
+
end
|
|
329
|
+
r
|
|
275
330
|
end
|
|
276
331
|
|
|
277
332
|
def max
|
|
@@ -291,11 +346,11 @@ module GRX
|
|
|
291
346
|
end
|
|
292
347
|
|
|
293
348
|
# ----------------------------------------------------------------
|
|
294
|
-
#
|
|
349
|
+
# LINEAR ALGEBRA
|
|
295
350
|
# ----------------------------------------------------------------
|
|
296
351
|
|
|
297
352
|
def dot(other)
|
|
298
|
-
raise ShapeError, "dot
|
|
353
|
+
raise ShapeError, "dot requires matching shape" if @shape != other.shape
|
|
299
354
|
if CAPI::LOADED
|
|
300
355
|
CAPI.grx_dot(@storage.ptr, other.storage.ptr, numel)
|
|
301
356
|
else
|
|
@@ -304,9 +359,9 @@ module GRX
|
|
|
304
359
|
end
|
|
305
360
|
|
|
306
361
|
def matmul(other)
|
|
307
|
-
raise DimensionError, "matmul
|
|
362
|
+
raise DimensionError, "matmul requires 2D tensors" unless @shape.size == 2 && other.shape.size == 2
|
|
308
363
|
m, k = @shape; k2, n = other.shape
|
|
309
|
-
raise ShapeError, "
|
|
364
|
+
raise ShapeError, "Incompatible dimensions: #{@shape} × #{other.shape}" if k != k2
|
|
310
365
|
out = _alloc_storage(m * n)
|
|
311
366
|
if CAPI::LOADED
|
|
312
367
|
CAPI.grx_matmul(@storage.ptr, other.storage.ptr, out.ptr, m, k, n)
|
|
@@ -323,7 +378,7 @@ module GRX
|
|
|
323
378
|
r._grafo_hijos.push(a, b)
|
|
324
379
|
r.backward_fn = ->(g) {
|
|
325
380
|
# dL/dA = dL/dC × B^T, dL/dB = A^T × dL/dC
|
|
326
|
-
#
|
|
381
|
+
# Uses _matmul_no_grad and _transpose_view to avoid graph recursion
|
|
327
382
|
a.agregar_gradiente(g._matmul_no_grad(b._transpose_view)) if a.requires_grad
|
|
328
383
|
b.agregar_gradiente(a._transpose_view._matmul_no_grad(g)) if b.requires_grad
|
|
329
384
|
}
|
|
@@ -332,7 +387,7 @@ module GRX
|
|
|
332
387
|
end
|
|
333
388
|
|
|
334
389
|
# ----------------------------------------------------------------
|
|
335
|
-
#
|
|
390
|
+
# ACTIVATIONS (with autograd)
|
|
336
391
|
# ----------------------------------------------------------------
|
|
337
392
|
|
|
338
393
|
def relu
|
|
@@ -388,10 +443,39 @@ module GRX
|
|
|
388
443
|
end
|
|
389
444
|
|
|
390
445
|
def softmax
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
446
|
+
dim = @shape[-1]
|
|
447
|
+
batch = numel / dim
|
|
448
|
+
raw = to_a
|
|
449
|
+
out_vals = Array.new(numel)
|
|
450
|
+
|
|
451
|
+
batch.times do |b|
|
|
452
|
+
slice = raw.slice(b * dim, dim)
|
|
453
|
+
max_v = slice.max
|
|
454
|
+
exps = slice.map { |v| Math.exp(v - max_v) }
|
|
455
|
+
sum_e = exps.sum
|
|
456
|
+
dim.times { |j| out_vals[b * dim + j] = exps[j] / sum_e }
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
r = Tensor.create(out_vals, @shape, requires_grad: @requires_grad)
|
|
460
|
+
if @requires_grad
|
|
461
|
+
r._grafo_hijos << self
|
|
462
|
+
res = r; src = self
|
|
463
|
+
r.backward_fn = ->(g) {
|
|
464
|
+
s_data = res.to_a
|
|
465
|
+
g_data = g.to_a
|
|
466
|
+
grad_x = Array.new(src.numel, 0.0)
|
|
467
|
+
|
|
468
|
+
batch.times do |b|
|
|
469
|
+
s_row = s_data.slice(b * dim, dim)
|
|
470
|
+
g_row = g_data.slice(b * dim, dim)
|
|
471
|
+
dot = s_row.zip(g_row).sum { |s_val, g_val| s_val * g_val }
|
|
472
|
+
dim.times do |j|
|
|
473
|
+
grad_x[b * dim + j] = s_row[j] * (g_row[j] - dot)
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
src.agregar_gradiente(Tensor.create(grad_x, src.shape))
|
|
478
|
+
}
|
|
395
479
|
end
|
|
396
480
|
r
|
|
397
481
|
end
|
|
@@ -411,7 +495,7 @@ module GRX
|
|
|
411
495
|
agregar_gradiente(grad_inicial)
|
|
412
496
|
end
|
|
413
497
|
|
|
414
|
-
#
|
|
498
|
+
# Topological sorting via iterative post-order DFS (prevents stack overflow on deep graphs)
|
|
415
499
|
orden = []
|
|
416
500
|
visitados = {}
|
|
417
501
|
stack = [[self, false]]
|
|
@@ -428,7 +512,7 @@ module GRX
|
|
|
428
512
|
end
|
|
429
513
|
end
|
|
430
514
|
|
|
431
|
-
#
|
|
515
|
+
# Topological order in post-order: reverse traverses root first down to leaves
|
|
432
516
|
orden.reverse_each do |nodo|
|
|
433
517
|
next unless nodo.grad && nodo.backward_fn
|
|
434
518
|
nodo.backward_fn.call(nodo.grad)
|
|
@@ -447,20 +531,37 @@ module GRX
|
|
|
447
531
|
end
|
|
448
532
|
|
|
449
533
|
# ----------------------------------------------------------------
|
|
450
|
-
#
|
|
534
|
+
# GEOMETRY (zero-copy)
|
|
451
535
|
# ----------------------------------------------------------------
|
|
452
536
|
|
|
453
537
|
def get(*coords)
|
|
454
538
|
@storage.read(_calc_flat_index(coords))
|
|
455
539
|
end
|
|
456
540
|
|
|
541
|
+
def contiguous
|
|
542
|
+
return self if _contiguous?
|
|
543
|
+
c = Tensor.create(to_a, @shape, requires_grad: @requires_grad)
|
|
544
|
+
if @requires_grad
|
|
545
|
+
c._grafo_hijos << self
|
|
546
|
+
src = self
|
|
547
|
+
c.backward_fn = ->(g) { src.agregar_gradiente(g) }
|
|
548
|
+
end
|
|
549
|
+
c
|
|
550
|
+
end
|
|
551
|
+
|
|
457
552
|
def reshape(nueva_forma)
|
|
458
|
-
raise ArgumentError, "
|
|
459
|
-
Tensor.new(@storage, nueva_forma, offset: @offset, requires_grad: @requires_grad)
|
|
553
|
+
raise ArgumentError, "Incompatible reshape" if numel != nueva_forma.reduce(1,:*)
|
|
554
|
+
r = Tensor.new(@storage, nueva_forma, offset: @offset, requires_grad: @requires_grad)
|
|
555
|
+
if @requires_grad
|
|
556
|
+
r._grafo_hijos << self
|
|
557
|
+
src = self; orig_shape = @shape
|
|
558
|
+
r.backward_fn = ->(g) { src.agregar_gradiente(g.reshape(orig_shape)) }
|
|
559
|
+
end
|
|
560
|
+
r
|
|
460
561
|
end
|
|
461
562
|
|
|
462
563
|
def transpose
|
|
463
|
-
raise DimensionError, "transpose
|
|
564
|
+
raise DimensionError, "transpose only supports 2D tensors" if @shape.size != 2
|
|
464
565
|
t = Tensor.new(@storage, [@shape[1], @shape[0]],
|
|
465
566
|
strides: [@strides[1], @strides[0]],
|
|
466
567
|
offset: @offset, requires_grad: @requires_grad)
|
|
@@ -468,32 +569,34 @@ module GRX
|
|
|
468
569
|
t._grafo_hijos << self
|
|
469
570
|
src = self
|
|
470
571
|
t.backward_fn = ->(g) {
|
|
471
|
-
src.agregar_gradiente(g.
|
|
572
|
+
src.agregar_gradiente(g.transpose)
|
|
472
573
|
}
|
|
473
574
|
end
|
|
474
575
|
t
|
|
475
576
|
end
|
|
476
577
|
|
|
477
|
-
# Transpose
|
|
578
|
+
# Transpose view without autograd — for internal backward pass
|
|
478
579
|
def _transpose_view
|
|
479
|
-
raise DimensionError, "transpose
|
|
580
|
+
raise DimensionError, "transpose only supports 2D tensors" if @shape.size != 2
|
|
480
581
|
Tensor.new(@storage, [@shape[1], @shape[0]],
|
|
481
582
|
strides: [@strides[1], @strides[0]],
|
|
482
583
|
offset: @offset, requires_grad: false)
|
|
483
584
|
end
|
|
484
585
|
|
|
485
|
-
# Matmul
|
|
586
|
+
# Matmul without autograd — for internal backward_fn usage
|
|
486
587
|
def _matmul_no_grad(other)
|
|
487
|
-
raise DimensionError, "matmul
|
|
588
|
+
raise DimensionError, "matmul requires 2D tensors" unless @shape.size == 2 && other.shape.size == 2
|
|
488
589
|
m, k = @shape; k2, n = other.shape
|
|
489
|
-
raise ShapeError, "
|
|
590
|
+
raise ShapeError, "Incompatible dimensions" if k != k2
|
|
591
|
+
a_c = _contiguous? ? self : contiguous
|
|
592
|
+
b_c = other._contiguous? ? other : other.contiguous
|
|
490
593
|
out = _alloc_storage(m * n)
|
|
491
594
|
if CAPI::LOADED
|
|
492
|
-
CAPI.grx_matmul(
|
|
595
|
+
CAPI.grx_matmul(a_c.storage.ptr, b_c.storage.ptr, out.ptr, m, k, n)
|
|
493
596
|
else
|
|
494
597
|
result = Array.new(m * n, 0.0)
|
|
495
|
-
m.times { |i| k.times { |kk| aik =
|
|
496
|
-
n.times { |j| result[i*n+j] += aik *
|
|
598
|
+
m.times { |i| k.times { |kk| aik = a_c.storage.read(i*k+kk)
|
|
599
|
+
n.times { |j| result[i*n+j] += aik * b_c.storage.read(kk*n+j) } } }
|
|
497
600
|
return Tensor.new(Storage.new(result), [m, n])
|
|
498
601
|
end
|
|
499
602
|
Tensor.new(out, [m, n])
|
|
@@ -504,7 +607,7 @@ module GRX
|
|
|
504
607
|
end
|
|
505
608
|
|
|
506
609
|
# ----------------------------------------------------------------
|
|
507
|
-
#
|
|
610
|
+
# UTILITIES
|
|
508
611
|
# ----------------------------------------------------------------
|
|
509
612
|
|
|
510
613
|
def numel
|
|
@@ -512,8 +615,8 @@ module GRX
|
|
|
512
615
|
end
|
|
513
616
|
|
|
514
617
|
def to_a
|
|
515
|
-
#
|
|
516
|
-
#
|
|
618
|
+
# If strides are contiguous (normal tensor, reshape), read buffer directly.
|
|
619
|
+
# Otherwise (transpose, strided views), traverse with custom strides.
|
|
517
620
|
if _contiguous?
|
|
518
621
|
@storage.to_ruby_array
|
|
519
622
|
else
|
|
@@ -521,13 +624,14 @@ module GRX
|
|
|
521
624
|
end
|
|
522
625
|
end
|
|
523
626
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
# Un tensor es contiguo si sus strides coinciden con los strides row-major estándar
|
|
527
|
-
def _contiguous?
|
|
627
|
+
# A tensor is contiguous if its strides match standard row-major order
|
|
628
|
+
def contiguous?
|
|
528
629
|
expected = _calc_strides(@shape)
|
|
529
630
|
@strides == expected && @offset == 0
|
|
530
631
|
end
|
|
632
|
+
alias _contiguous? contiguous?
|
|
633
|
+
|
|
634
|
+
private
|
|
531
635
|
|
|
532
636
|
def _collect_elements(shape, strides, offset)
|
|
533
637
|
if shape.size == 1
|
|
@@ -541,18 +645,46 @@ module GRX
|
|
|
541
645
|
|
|
542
646
|
public
|
|
543
647
|
|
|
648
|
+
include Comparable
|
|
649
|
+
|
|
650
|
+
def <=>(other)
|
|
651
|
+
case other
|
|
652
|
+
when Tensor
|
|
653
|
+
(numel == 1 && other.numel == 1) ? item <=> other.item : nil
|
|
654
|
+
when Numeric
|
|
655
|
+
numel == 1 ? item <=> other.to_f : nil
|
|
656
|
+
else
|
|
657
|
+
nil
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
|
|
544
661
|
def item
|
|
545
|
-
raise "item()
|
|
662
|
+
raise "item() only supported for 1-element tensors" if numel != 1
|
|
663
|
+
to_a[0]
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def to_f
|
|
667
|
+
raise "to_f only supported for 1-element tensors" if numel != 1
|
|
546
668
|
to_a[0]
|
|
547
669
|
end
|
|
548
670
|
|
|
671
|
+
def to_i
|
|
672
|
+
raise "to_i only supported for 1-element tensors" if numel != 1
|
|
673
|
+
to_a[0].to_i
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def nan?
|
|
677
|
+
raise "nan? only supported for 1-element tensors" if numel != 1
|
|
678
|
+
to_a[0].nan?
|
|
679
|
+
end
|
|
680
|
+
|
|
549
681
|
def to_s
|
|
550
682
|
"#<GRX::Tensor shape=#{@shape} data=#{to_a}>"
|
|
551
683
|
end
|
|
552
684
|
alias inspect to_s
|
|
553
685
|
|
|
554
686
|
# ----------------------------------------------------------------
|
|
555
|
-
#
|
|
687
|
+
# PRIVATE
|
|
556
688
|
# ----------------------------------------------------------------
|
|
557
689
|
|
|
558
690
|
private
|
|
@@ -575,33 +707,34 @@ module GRX
|
|
|
575
707
|
end
|
|
576
708
|
end
|
|
577
709
|
|
|
578
|
-
#
|
|
710
|
+
# Element-wise binary op: delegates to CAPI or Ruby fallback
|
|
579
711
|
def _binop(op, other)
|
|
712
|
+
a_c = _contiguous? ? self : contiguous
|
|
713
|
+
b_c = other._contiguous? ? other : other.contiguous
|
|
580
714
|
out = _alloc_storage(numel)
|
|
581
715
|
if CAPI::LOADED
|
|
582
|
-
CAPI.public_send(op,
|
|
716
|
+
CAPI.public_send(op, a_c.storage.ptr, b_c.storage.ptr, out.ptr, numel)
|
|
583
717
|
else
|
|
584
718
|
rb = { grx_add: :+, grx_sub: :-, grx_mul: :*, grx_div: :/ }[op]
|
|
585
719
|
data = (0...numel).map { |i|
|
|
586
|
-
|
|
720
|
+
a_c.storage.read(a_c.offset + i).public_send(rb, b_c.storage.read(b_c.offset + i))
|
|
587
721
|
}
|
|
588
722
|
return Storage.new(data)
|
|
589
723
|
end
|
|
590
724
|
out
|
|
591
725
|
end
|
|
592
726
|
|
|
593
|
-
#
|
|
594
|
-
# Si el bloque acepta un elemento (arity == 1) → map element-wise
|
|
595
|
-
# Si el bloque no acepta argumentos (arity == 0) → lo llama una vez (para softmax, etc.)
|
|
727
|
+
# Unary op: delegates to CAPI with optional args or Ruby fallback block
|
|
596
728
|
def _unary_c(op, *args, &fallback)
|
|
729
|
+
a_c = _contiguous? ? self : contiguous
|
|
597
730
|
out = _alloc_storage(numel)
|
|
598
731
|
if CAPI::LOADED
|
|
599
|
-
CAPI.public_send(op,
|
|
732
|
+
CAPI.public_send(op, a_c.storage.ptr, *args, out.ptr, numel)
|
|
600
733
|
else
|
|
601
734
|
vals = if fallback
|
|
602
|
-
fallback.arity == 0 ? fallback.call : to_a.map(&fallback)
|
|
735
|
+
fallback.arity == 0 ? fallback.call : a_c.to_a.map(&fallback)
|
|
603
736
|
else
|
|
604
|
-
to_a
|
|
737
|
+
a_c.to_a
|
|
605
738
|
end
|
|
606
739
|
return Tensor.create(vals, @shape)
|
|
607
740
|
end
|
data/lib/grx/utils.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GRX
|
|
4
|
+
module Utils
|
|
5
|
+
# ================================================================
|
|
6
|
+
# clip_grad_norm! — Clips gradients of parameter collection so their
|
|
7
|
+
# combined L2 norm does not exceed max_norm.
|
|
8
|
+
# Prevents exploding gradient problems during deep network training.
|
|
9
|
+
# ================================================================
|
|
10
|
+
def self.clip_grad_norm!(parameters, max_norm)
|
|
11
|
+
max_norm = max_norm.to_f
|
|
12
|
+
total_norm_sq = 0.0
|
|
13
|
+
parameters.each do |p|
|
|
14
|
+
next unless p.grad
|
|
15
|
+
total_norm_sq += p.grad.square.to_a.sum
|
|
16
|
+
end
|
|
17
|
+
total_norm = Math.sqrt(total_norm_sq)
|
|
18
|
+
clip_coef = max_norm / (total_norm + 1e-6)
|
|
19
|
+
if clip_coef < 1.0
|
|
20
|
+
parameters.each do |p|
|
|
21
|
+
next unless p.grad
|
|
22
|
+
p.grad = p.grad.scale(clip_coef)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
total_norm
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/grx/version.rb
CHANGED
data/lib/grx.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# =====================================================================
|
|
4
|
-
# GRX —
|
|
4
|
+
# GRX — Tensor framework with autograd and C+SIMD compute core
|
|
5
5
|
# require "grx"
|
|
6
6
|
# =====================================================================
|
|
7
7
|
|
|
@@ -10,12 +10,15 @@ require_relative "grx/errors"
|
|
|
10
10
|
require_relative "grx/c_api"
|
|
11
11
|
require_relative "grx/storage"
|
|
12
12
|
require_relative "grx/tensor"
|
|
13
|
+
require_relative "grx/serialization"
|
|
13
14
|
require_relative "grx/nn"
|
|
14
15
|
require_relative "grx/optim"
|
|
15
16
|
require_relative "grx/loss"
|
|
17
|
+
require_relative "grx/data"
|
|
18
|
+
require_relative "grx/utils"
|
|
16
19
|
|
|
17
20
|
module GRX
|
|
18
|
-
#
|
|
21
|
+
# Quick factory helpers
|
|
19
22
|
def self.tensor(data, shape, requires_grad: false)
|
|
20
23
|
Tensor.create(data, shape, requires_grad: requires_grad)
|
|
21
24
|
end
|
|
@@ -34,7 +37,7 @@ module GRX
|
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def self.randn(shape, requires_grad: false)
|
|
37
|
-
# Box-Muller
|
|
40
|
+
# Box-Muller from Ruby (C backend executes faster via he_normal)
|
|
38
41
|
n = shape.reduce(1, :*)
|
|
39
42
|
data = []
|
|
40
43
|
(n / 2.0).ceil.times do
|
|
@@ -46,4 +49,12 @@ module GRX
|
|
|
46
49
|
end
|
|
47
50
|
Tensor.create(data.first(n), shape, requires_grad: requires_grad)
|
|
48
51
|
end
|
|
52
|
+
|
|
53
|
+
def self.c_loaded?
|
|
54
|
+
CAPI::LOADED
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.mode
|
|
58
|
+
CAPI::LOADED ? :c : :ruby
|
|
59
|
+
end
|
|
49
60
|
end
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: grx-tensor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Razo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -81,7 +81,9 @@ extensions:
|
|
|
81
81
|
extra_rdoc_files: []
|
|
82
82
|
files:
|
|
83
83
|
- CHANGELOG.md
|
|
84
|
+
- GUIA_PRINCIPIANTES.md
|
|
84
85
|
- LICENSE.txt
|
|
86
|
+
- README.es.md
|
|
85
87
|
- README.md
|
|
86
88
|
- ext/grx/extconf.rb
|
|
87
89
|
- ext/grx/grx_core.c
|
|
@@ -91,12 +93,15 @@ files:
|
|
|
91
93
|
- grx-tensor.gemspec
|
|
92
94
|
- lib/grx.rb
|
|
93
95
|
- lib/grx/c_api.rb
|
|
96
|
+
- lib/grx/data.rb
|
|
94
97
|
- lib/grx/errors.rb
|
|
95
98
|
- lib/grx/loss.rb
|
|
96
99
|
- lib/grx/nn.rb
|
|
97
100
|
- lib/grx/optim.rb
|
|
101
|
+
- lib/grx/serialization.rb
|
|
98
102
|
- lib/grx/storage.rb
|
|
99
103
|
- lib/grx/tensor.rb
|
|
104
|
+
- lib/grx/utils.rb
|
|
100
105
|
- lib/grx/version.rb
|
|
101
106
|
homepage: https://github.com/Gabo-Razo/grx-tensor
|
|
102
107
|
licenses:
|
|
@@ -108,7 +113,7 @@ metadata:
|
|
|
108
113
|
post_install_message: |2+
|
|
109
114
|
|
|
110
115
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
111
|
-
GRX-Tensor 0.
|
|
116
|
+
GRX-Tensor 0.2.0 installed
|
|
112
117
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
113
118
|
|
|
114
119
|
Compile the C extension to enable AVX2+FMA SIMD:
|