grx-tensor 0.1.0 → 0.2.1

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.
data/ext/grx/grx_core.h CHANGED
@@ -1,5 +1,8 @@
1
1
  /*
2
- * grx_core.h — API pública del núcleo C de GRX
2
+ * grx_core.h — API publica del nucleo C de GRX
3
+ * =============================================================
4
+ * Compatible con compilacion universal y despacho dinamico SIMD
5
+ * (AVX2 + FMA / SSE / Escalar C)
3
6
  * =============================================================
4
7
  */
5
8
 
@@ -18,20 +21,24 @@
18
21
  extern "C" {
19
22
  #endif
20
23
 
24
+ /* ---- Diagnostico y nivel SIMD ----------------------------------- */
25
+ /* Retorna 2 para AVX2+FMA, 1 para SSE, 0 para Escalar portable C */
26
+ GRX_API int grx_simd_level(void);
27
+
21
28
  /* ---- Memoria alineada -------------------------------------------- */
22
29
  GRX_API double* grx_alloc(size_t n);
23
30
  GRX_API void grx_free(double *ptr);
24
31
 
25
- /* ---- Element-wise aritmética ------------------------------------- */
26
- GRX_API void grx_add (const double *a, const double *b, double *out, size_t n);
27
- GRX_API void grx_sub (const double *a, const double *b, double *out, size_t n);
28
- GRX_API void grx_mul (const double *a, const double *b, double *out, size_t n);
29
- GRX_API void grx_div (const double *a, const double *b, double *out, size_t n);
30
- GRX_API void grx_scale (const double *a, double s, double *out, size_t n);
31
- GRX_API void grx_negate(const double *a, double *out, size_t n);
32
- GRX_API void grx_add_scalar(const double *a, double s, double *out, size_t n);
32
+ /* ---- Element-wise aritmetica ------------------------------------- */
33
+ GRX_API void grx_add (const double *a, const double *b, double *out, size_t n);
34
+ GRX_API void grx_sub (const double *a, const double *b, double *out, size_t n);
35
+ GRX_API void grx_mul (const double *a, const double *b, double *out, size_t n);
36
+ GRX_API void grx_div (const double *a, const double *b, double *out, size_t n);
37
+ GRX_API void grx_scale (const double *a, double s, double *out, size_t n);
38
+ GRX_API void grx_negate (const double *a, double *out, size_t n);
39
+ GRX_API void grx_add_scalar(const double *a, double s, double *out, size_t n);
33
40
 
34
- /* ---- Element-wise matemáticas ------------------------------------ */
41
+ /* ---- Element-wise matematicas ------------------------------------ */
35
42
  GRX_API void grx_abs (const double *a, double *out, size_t n);
36
43
  GRX_API void grx_sqrt (const double *a, double *out, size_t n);
37
44
  GRX_API void grx_log (const double *a, double *out, size_t n);
@@ -46,7 +53,7 @@ GRX_API double grx_mean(const double *a, size_t n);
46
53
  GRX_API double grx_max (const double *a, size_t n);
47
54
  GRX_API double grx_min (const double *a, size_t n);
48
55
 
49
- /* ---- Álgebra lineal ---------------------------------------------- */
56
+ /* ---- Algebra lineal ---------------------------------------------- */
50
57
  GRX_API double grx_dot (const double *a, const double *b, size_t n);
51
58
  GRX_API void grx_matmul (const double *a, const double *b, double *out,
52
59
  size_t M, size_t K, size_t N);
@@ -63,7 +70,7 @@ GRX_API void grx_softmax (const double *a, double *out, size_t n);
63
70
  GRX_API void grx_sgd_step(double *param, const double *grad,
64
71
  double lr, size_t n);
65
72
 
66
- /* Adam: actualiza param, m, v in-place */
73
+ /* Adam: actualiza param, m, v in-place con aceleracion FMA */
67
74
  GRX_API void grx_adam_step(double *param,
68
75
  double *m, double *v,
69
76
  const double *grad,
@@ -71,13 +78,16 @@ GRX_API void grx_adam_step(double *param,
71
78
  double epsilon, double beta1t, double beta2t,
72
79
  size_t n);
73
80
 
74
- /* ---- Inicialización de pesos ------------------------------------- */
81
+ /* ---- Inicializacion de pesos ------------------------------------- */
75
82
  /* Xavier uniform: U(-limit, limit), limit = sqrt(6 / (fan_in + fan_out)) */
76
83
  GRX_API void grx_init_xavier_uniform(double *out, size_t n,
77
84
  size_t fan_in, size_t fan_out);
78
85
  /* He normal: N(0, sqrt(2/fan_in)) */
79
86
  GRX_API void grx_init_he_normal(double *out, size_t n, size_t fan_in);
80
87
 
88
+ /* Extension init for Ruby */
89
+ GRX_API void Init_grx_core(void);
90
+
81
91
  #ifdef __cplusplus
82
92
  }
83
93
  #endif
data/ext/unix/Makefile CHANGED
@@ -1,17 +1,9 @@
1
1
  # =============================================================
2
2
  # Makefile — Linux / macOS
3
- #
4
- # Compila grx_core.c DIRECTAMENTE en lib/grx/ (sin archivo intermedio).
5
- # No hay .so en ext/unix/ — el único binario vive en lib/grx/.
6
- #
7
- # Uso:
8
- # make → compila (detecta OS y SIMD automáticamente)
9
- # make clean → elimina el binario de lib/grx/
10
- # make bench → compila y corre el benchmark
11
3
  # =============================================================
12
4
 
13
- CC = gcc
14
- CFLAGS = -O3 -march=native -ffast-math -funroll-loops \
5
+ CC ?= gcc
6
+ CFLAGS = -O3 -ffast-math -funroll-loops \
15
7
  -fPIC -fvisibility=hidden \
16
8
  -Wall -Wextra -std=c11
17
9
  LDFLAGS = -lm
@@ -19,7 +11,6 @@ SRC_DIR = ../grx
19
11
  SRC = $(SRC_DIR)/grx_core.c
20
12
  HEADER = $(SRC_DIR)/grx_core.h
21
13
 
22
- # Destino final — directamente en lib/grx/
23
14
  OUT_DIR = ../../lib/grx
24
15
 
25
16
  UNAME := $(shell uname -s)
@@ -33,21 +24,6 @@ endif
33
24
 
34
25
  TARGET = $(OUT_DIR)/$(LIB)
35
26
 
36
- # Detección de SIMD
37
- AVX2_TEST := $(shell echo 'int main(){}' | $(CC) -mavx2 -mfma -x c - -o /dev/null 2>&1)
38
- ifeq ($(AVX2_TEST),)
39
- CFLAGS += -mavx2 -mfma
40
- $(info [GRX] AVX2 + FMA habilitados — máxima velocidad SIMD)
41
- else
42
- SSE2_TEST := $(shell echo 'int main(){}' | $(CC) -msse2 -x c - -o /dev/null 2>&1)
43
- ifeq ($(SSE2_TEST),)
44
- CFLAGS += -msse2
45
- $(info [GRX] SSE2 habilitado)
46
- else
47
- $(info [GRX] Sin SIMD — modo escalar)
48
- endif
49
- endif
50
-
51
27
  .PHONY: all clean bench
52
28
 
53
29
  all: $(TARGET)
@@ -55,7 +31,7 @@ all: $(TARGET)
55
31
  $(TARGET): $(SRC) $(HEADER)
56
32
  @mkdir -p $(OUT_DIR)
57
33
  $(CC) $(CFLAGS) $(SHARED) $(SRC) -o $(TARGET) $(LDFLAGS)
58
- @echo "[GRX] Compilado → $(TARGET)"
34
+ @echo "[GRX] Compilado exitosamente → $(TARGET)"
59
35
 
60
36
  bench: all
61
37
  @echo "[GRX] Corriendo benchmark..."
@@ -1,41 +1,21 @@
1
1
  # =============================================================
2
2
  # Makefile.mingw — Windows (MinGW-w64 / MSYS2)
3
- #
4
- # Compila grx_core.c DIRECTAMENTE en lib/grx/ (sin archivo intermedio).
5
- # El único .dll vive en lib/grx/grx_core.dll
6
- #
7
- # Requisitos:
8
- # MSYS2: pacman -S mingw-w64-x86_64-gcc
9
- #
10
- # Uso:
11
- # make -f Makefile.mingw → compila
12
- # make -f Makefile.mingw clean → elimina el .dll de lib/grx/
13
3
  # =============================================================
14
4
 
15
- CC = x86_64-w64-mingw32-gcc
16
- CFLAGS = -O3 -march=native -ffast-math -funroll-loops \
5
+ CC ?= x86_64-w64-mingw32-gcc
6
+ CFLAGS = -O3 -ffast-math -funroll-loops \
17
7
  -Wall -Wextra -std=c11
18
8
  LDFLAGS = -lm
19
9
  SRC_DIR = ../grx
20
10
  SRC = $(SRC_DIR)/grx_core.c
21
11
  HEADER = $(SRC_DIR)/grx_core.h
22
12
 
23
- # Destino final — directamente en lib/grx/
24
13
  OUT_DIR = ../../lib/grx
25
14
  LIB = grx_core.dll
26
15
  TARGET = $(OUT_DIR)/$(LIB)
27
16
 
28
- # __declspec(dllexport) ya está en el header vía GRX_API
29
17
  SHARED = -shared -Wl,--out-implib,$(OUT_DIR)/libgrx_core.a
30
18
 
31
- AVX2_TEST := $(shell echo 'int main(){}' | $(CC) -mavx2 -mfma -x c - -o NUL 2>&1)
32
- ifeq ($(AVX2_TEST),)
33
- CFLAGS += -mavx2 -mfma
34
- $(info [GRX] AVX2 + FMA habilitados)
35
- else
36
- $(info [GRX] Sin AVX2 — modo escalar)
37
- endif
38
-
39
19
  .PHONY: all clean
40
20
 
41
21
  all: $(TARGET)
@@ -43,7 +23,7 @@ all: $(TARGET)
43
23
  $(TARGET): $(SRC) $(HEADER)
44
24
  mkdir -p $(OUT_DIR)
45
25
  $(CC) $(CFLAGS) $(SHARED) $(SRC) -o $(TARGET) $(LDFLAGS)
46
- @echo [GRX] Compilado → $(TARGET)
26
+ @echo [GRX] Compilado exitosamente → $(TARGET)
47
27
 
48
28
  clean:
49
29
  rm -f "$(TARGET)" "$(OUT_DIR)/libgrx_core.a" 2>/dev/null || true
data/grx-tensor.gemspec CHANGED
@@ -5,14 +5,15 @@ require_relative "lib/grx/version"
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "grx-tensor"
7
7
  spec.version = GRX::VERSION
8
- spec.authors = ["Angel Gabriel Garcia Razo"]
8
+ spec.authors = ["Razo"]
9
9
  spec.email = ["garabatoangelopolis@gmail.com"]
10
10
 
11
11
  spec.summary = "Tensor framework for Ruby with autograd and a C+SIMD compute core"
12
12
  spec.description = <<~DESC
13
13
  GRX brings PyTorch-style tensor operations to Ruby. Every arithmetic op,
14
- activation, and optimizer step runs through a native C library compiled
15
- with AVX2+FMA SIMD. Ruby is the interface C does the work.
14
+ activation, and optimizer step runs through a native C library with
15
+ dynamic multi-target SIMD dispatch (AVX2+FMA, SSE, and scalar fallback).
16
+ Ruby is the interface — C does the work.
16
17
 
17
18
  Features: autograd, SGD/Adam optimizers, Linear/Sequential/Dropout/BatchNorm
18
19
  layers, MSE/BCE/CrossEntropy loss functions, Xavier and He weight init.
@@ -37,47 +38,52 @@ Gem::Specification.new do |spec|
37
38
  "ext/windows/Makefile.mingw",
38
39
  "*.gemspec",
39
40
  "README.md",
41
+ "README.es.md",
42
+ "GUIA_PRINCIPIANTES.md",
40
43
  "LICENSE.txt",
41
44
  "CHANGELOG.md"
42
- ]
45
+ ].reject { |f| f.match?(/\.(so|dll|dylib|bundle|a)$/) }
43
46
 
44
47
  spec.require_paths = ["lib"]
45
48
 
46
- # rake-compiler compiles ext/grx/extconf.rb on `gem install`
49
+ # rake-compiler / rubygems compiles ext/grx/extconf.rb on `gem install`
47
50
  spec.extensions = ["ext/grx/extconf.rb"]
48
51
 
49
52
  spec.post_install_message = <<~MSG
50
53
 
51
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52
- GRX-Tensor #{GRX::VERSION} installed
53
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
54
-
55
- Compile the C extension to enable AVX2+FMA SIMD:
56
-
57
- Linux / macOS: make -C ext/unix
58
- Windows: make -C ext/windows -f Makefile.mingw
59
-
60
- Without it, GRX runs in pure Ruby fallback mode (slower but correct).
61
-
62
- Quick start:
63
-
64
- require "grx"
65
-
66
- a = GRX.tensor([1.0, 2.0, 3.0], [3], requires_grad: true)
67
- b = GRX.tensor([4.0, 5.0, 6.0], [3], requires_grad: true)
68
- c = a + b
69
- c.backward
70
- puts a.grad.to_a # [1.0, 1.0, 1.0]
71
-
72
- net = GRX::NN::Sequential.new(
73
- GRX::NN::Linear.new(4, 16),
74
- GRX::NN::ReLU.new,
75
- GRX::NN::Linear.new(16, 1)
76
- )
77
- opt = GRX::Optim::Adam.new(net.parameters, lr: 0.001)
78
-
79
- Docs: https://github.com/Gabo-Razo/grx-tensor
80
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
54
+ ============================================================
55
+ GRX-Tensor #{GRX::VERSION}
56
+ ============================================================
57
+
58
+ [ENGLISH]
59
+ Thank you for choosing and using GRX-Tensor!
60
+
61
+ * Native C acceleration with dynamic SIMD dispatch
62
+ (AVX2+FMA, SSE, and portable scalar C) is built and
63
+ configured automatically.
64
+ * Windows Note: Native C acceleration is supported when
65
+ using RubyInstaller with DevKit (MSYS2 / MinGW-w64).
66
+ Standalone pre-compiled Windows binaries are currently
67
+ in active development.
68
+ * Documentation, guides, and tutorials:
69
+ https://github.com/Gabo-Razo/grx-tensor
70
+
71
+ ----------------------------------------------------------
72
+
73
+ [ESPAÑOL]
74
+ Muchas gracias por elegir y utilizar GRX-Tensor!
75
+
76
+ * La aceleracion nativa en C con despacho dinamico SIMD
77
+ (AVX2+FMA, SSE y C escalar portable) se compila y
78
+ configura de forma totalmente automatica.
79
+ * Nota para Windows: La aceleracion nativa esta soportada
80
+ al utilizar RubyInstaller con DevKit (MSYS2 / MinGW-w64).
81
+ Los binarios pre-compilados independientes para Windows
82
+ se encuentran actualmente en desarrollo activo.
83
+ * Documentacion, guias y tutoriales:
84
+ https://github.com/Gabo-Razo/grx-tensor
85
+
86
+ ============================================================
81
87
 
82
88
  MSG
83
89
 
data/lib/grx/c_api.rb CHANGED
@@ -2,54 +2,78 @@
2
2
 
3
3
  require "fiddle"
4
4
  require "fiddle/import"
5
+ require "rbconfig"
5
6
 
6
7
  module GRX
7
8
  module CAPI
8
9
  extend Fiddle::Importer
9
10
 
10
- LIB_NAME = case RUBY_PLATFORM
11
- when /mingw|mswin|windows/i then "grx_core.dll"
12
- when /darwin/i then "libgrx_core.dylib"
13
- else "libgrx_core.so"
14
- end
15
-
16
- # rake-compiler siempre genera el archivo como "grx_core.so" / "grx_core.bundle" / "grx_core.dll"
17
- # (sin el prefijo "lib"), y lo pone un nivel arriba de lib/grx/
18
- RAKE_COMPILER_NAME = case RUBY_PLATFORM
19
- when /mingw|mswin|windows/i then "grx_core.dll"
20
- when /darwin/i then "grx_core.bundle"
21
- else "grx_core.so"
22
- end
23
-
24
- LIB_PATHS = [
25
- # 1. make -C ext/unix → lib/grx/libgrx_core.so
26
- File.expand_path(LIB_NAME, __dir__),
27
- # 2. gem install (rake-compiler) → lib/grx_core.so (un nivel arriba)
28
- File.expand_path("../#{RAKE_COMPILER_NAME}", __dir__),
29
- # 3. gem install en Ruby versioned path → lib/ruby/X.X.X/grx_core.so
30
- File.expand_path("../../#{RAKE_COMPILER_NAME}", __dir__),
31
- # 4. desarrollo local sin instalar
32
- File.expand_path("../../ext/grx/#{LIB_NAME}", __dir__),
33
- ].freeze
11
+ CANDIDATE_NAMES = case RUBY_PLATFORM
12
+ when /mingw|mswin|windows/i
13
+ ["grx_core.dll", "libgrx_core.dll", "grx_core.so", "libgrx_core.so"]
14
+ when /darwin/i
15
+ ["libgrx_core.dylib", "grx_core.bundle", "libgrx_core.so", "grx_core.so"]
16
+ else
17
+ ["libgrx_core.so", "grx_core.so", "libgrx_core.dylib", "grx_core.dll"]
18
+ end
19
+
20
+ # Search directories including standard Gem extension build paths
21
+ SEARCH_DIRS = begin
22
+ dirs = [
23
+ File.expand_path(__dir__), # lib/grx/
24
+ File.expand_path("..", __dir__), # lib/
25
+ File.expand_path("../../ext/grx", __dir__), # ext/grx/
26
+ File.expand_path("../../ext/unix", __dir__), # ext/unix/
27
+ File.expand_path("../../ext/windows", __dir__) # ext/windows/
28
+ ]
29
+
30
+ # Gem extensions build directory (RubyGems standard)
31
+ if defined?(Gem) && Gem.loaded_specs["grx-tensor"]
32
+ ext_dir = Gem.loaded_specs["grx-tensor"].extension_dir
33
+ dirs << ext_dir if ext_dir && File.directory?(ext_dir)
34
+ dirs << File.join(ext_dir, "grx") if ext_dir && File.directory?(File.join(ext_dir, "grx"))
35
+ end
36
+
37
+ # Ruby sitearch / vendorarch directories
38
+ sitearch = RbConfig::CONFIG["sitearchdir"]
39
+ dirs << sitearch if sitearch && File.directory?(sitearch)
40
+
41
+ dirs.uniq.freeze
42
+ end
43
+
44
+ LIB_PATHS = SEARCH_DIRS.flat_map do |dir|
45
+ CANDIDATE_NAMES.flat_map do |name|
46
+ [
47
+ File.join(dir, name),
48
+ File.join(dir, name).tr("/", "\\")
49
+ ]
50
+ end
51
+ end.uniq.freeze
52
+
53
+ LOADED_PATH = LIB_PATHS.find { |p| File.file?(p) && File.exist?(p) }
34
54
 
35
55
  LOADED = begin
36
- path = LIB_PATHS.find { |p| File.exist?(p) }
37
- raise Fiddle::DLError, "No se encontró #{LIB_NAME} en #{LIB_PATHS.inspect}" unless path
38
- dlload path
39
- true
56
+ if LOADED_PATH
57
+ dlload LOADED_PATH
58
+ true
59
+ else
60
+ false
61
+ end
40
62
  rescue Fiddle::DLError => e
41
- warn "[GRX] Extensión C no disponible: #{e.message}\n" \
42
- " Ejecuta: make -C ext/unix install\n" \
43
- " → Corriendo en modo Ruby puro (sin SIMD)."
63
+ warn "[GRX] C extension load error: #{e.message}\n" \
64
+ " -> Running in pure Ruby fallback mode."
44
65
  false
45
66
  end
46
67
 
47
68
  if LOADED
48
- # Memoria
69
+ # CPU SIMD Level
70
+ extern "int grx_simd_level(void)"
71
+
72
+ # Memory management
49
73
  extern "double* grx_alloc(size_t)"
50
74
  extern "void grx_free(double*)"
51
75
 
52
- # Aritmética element-wise
76
+ # Element-wise arithmetic
53
77
  extern "void grx_add (double*, double*, double*, size_t)"
54
78
  extern "void grx_sub (double*, double*, double*, size_t)"
55
79
  extern "void grx_mul (double*, double*, double*, size_t)"
@@ -58,7 +82,7 @@ module GRX
58
82
  extern "void grx_add_scalar(double*, double, double*, size_t)"
59
83
  extern "void grx_negate (double*, double*, size_t)"
60
84
 
61
- # Matemáticas element-wise
85
+ # Element-wise math
62
86
  extern "void grx_abs (double*, double*, size_t)"
63
87
  extern "void grx_sqrt (double*, double*, size_t)"
64
88
  extern "void grx_square(double*, double*, size_t)"
@@ -67,30 +91,42 @@ module GRX
67
91
  extern "void grx_pow (double*, double, double*, size_t)"
68
92
  extern "void grx_clip (double*, double, double, double*, size_t)"
69
93
 
70
- # Reducciones
94
+ # Reductions
71
95
  extern "double grx_sum (double*, size_t)"
72
96
  extern "double grx_mean(double*, size_t)"
73
97
  extern "double grx_max (double*, size_t)"
74
98
  extern "double grx_min (double*, size_t)"
75
99
 
76
- # Álgebra lineal
100
+ # Linear algebra
77
101
  extern "double grx_dot (double*, double*, size_t)"
78
102
  extern "void grx_matmul (double*, double*, double*, size_t, size_t, size_t)"
79
103
 
80
- # Activaciones
104
+ # Activations
81
105
  extern "void grx_relu (double*, double*, size_t)"
82
106
  extern "void grx_leaky_relu (double*, double, double*, size_t)"
83
107
  extern "void grx_tanh_act (double*, double*, size_t)"
84
108
  extern "void grx_sigmoid (double*, double*, size_t)"
85
109
  extern "void grx_softmax (double*, double*, size_t)"
86
110
 
87
- # Optimizadores
111
+ # Optimizers
88
112
  extern "void grx_sgd_step (double*, double*, double, size_t)"
89
113
  extern "void grx_adam_step(double*, double*, double*, double*, double, double, double, double, double, double, size_t)"
90
114
 
91
- # Inicialización de pesos
115
+ # Weight initialization
92
116
  extern "void grx_init_xavier_uniform(double*, size_t, size_t, size_t)"
93
117
  extern "void grx_init_he_normal (double*, size_t, size_t)"
94
118
  end
119
+
120
+ def self.simd_mode
121
+ return :ruby unless LOADED
122
+
123
+ case grx_simd_level
124
+ when 2 then :avx2
125
+ when 1 then :sse
126
+ else :scalar
127
+ end
128
+ rescue StandardError
129
+ :scalar
130
+ end
95
131
  end
96
132
  end
data/lib/grx/data.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GRX
4
+ module Data
5
+ # ================================================================
6
+ # Dataset — Base class for data collections
7
+ # ================================================================
8
+ class Dataset
9
+ def size
10
+ raise NotImplementedError, "#{self.class}#size must be implemented"
11
+ end
12
+
13
+ def [](index)
14
+ raise NotImplementedError, "#{self.class}#[] must be implemented"
15
+ end
16
+ end
17
+
18
+ # ================================================================
19
+ # TensorDataset — Dataset wrapping parallel tensors (e.g. X and Y)
20
+ # ================================================================
21
+ class TensorDataset < Dataset
22
+ attr_reader :tensors, :size
23
+
24
+ def initialize(*tensors)
25
+ raise ArgumentError, "Must provide at least one tensor" if tensors.empty?
26
+ first_dim = tensors.first.shape[0]
27
+ unless tensors.all? { |t| t.shape[0] == first_dim }
28
+ raise ArgumentError, "All tensors must have the same size in batch dimension (dimension 0)"
29
+ end
30
+ @tensors = tensors
31
+ @size = first_dim
32
+ end
33
+
34
+ def [](index)
35
+ @tensors.map do |t|
36
+ cols = t.numel / @size
37
+ offset = index * cols
38
+ data = t.to_a.slice(offset, cols)
39
+ new_shape = t.shape.size == 1 ? [1] : [1] + t.shape[1..]
40
+ Tensor.create(data, new_shape)
41
+ end
42
+ end
43
+ end
44
+
45
+ # ================================================================
46
+ # DataLoader — Mini-batch iterator with optional shuffling
47
+ # ================================================================
48
+ class DataLoader
49
+ include Enumerable
50
+
51
+ attr_reader :dataset, :batch_size, :shuffle
52
+
53
+ def initialize(dataset, batch_size: 32, shuffle: true)
54
+ @dataset = dataset
55
+ @batch_size = batch_size
56
+ @shuffle = shuffle
57
+ end
58
+
59
+ def each
60
+ return to_enum(:each) unless block_given?
61
+
62
+ indices = (0...@dataset.size).to_a
63
+ indices.shuffle! if @shuffle
64
+
65
+ indices.each_slice(@batch_size) do |batch_indices|
66
+ batch_samples = batch_indices.map { |i| @dataset[i] }
67
+ num_tensors = batch_samples.first.size
68
+
69
+ batched = (0...num_tensors).map do |t_idx|
70
+ slices = batch_samples.map { |sample| sample[t_idx].to_a }
71
+ flat_data = slices.flatten
72
+ sample_shape = batch_samples.first[t_idx].shape
73
+ batch_dim = batch_indices.size
74
+ target_shape = [batch_dim] + sample_shape[1..]
75
+ Tensor.create(flat_data, target_shape)
76
+ end
77
+
78
+ yield(*batched)
79
+ end
80
+ end
81
+
82
+ def size
83
+ (@dataset.size.to_f / @batch_size).ceil
84
+ end
85
+ end
86
+ end
87
+ end