fine 0.1.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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +38 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +167 -0
  6. data/LICENSE +21 -0
  7. data/README.md +212 -0
  8. data/Rakefile +6 -0
  9. data/docs/installation.md +151 -0
  10. data/docs/tutorials/llm-fine-tuning.md +246 -0
  11. data/docs/tutorials/model-export.md +200 -0
  12. data/docs/tutorials/siglip2-image-classification.md +130 -0
  13. data/docs/tutorials/siglip2-object-recognition.md +203 -0
  14. data/docs/tutorials/siglip2-similarity-search.md +152 -0
  15. data/docs/tutorials/text-classification.md +233 -0
  16. data/docs/tutorials/text-embeddings.md +211 -0
  17. data/examples/basic_classification.rb +70 -0
  18. data/examples/data/tool_calls.jsonl +30 -0
  19. data/examples/demo_training.rb +78 -0
  20. data/examples/finetune_gemma3_tools.rb +135 -0
  21. data/examples/real_llm_test.rb +128 -0
  22. data/examples/real_text_classification_test.rb +90 -0
  23. data/examples/real_text_embedder_test.rb +110 -0
  24. data/examples/real_training_test.rb +88 -0
  25. data/examples/test_export.rb +28 -0
  26. data/examples/test_image_classifier.rb +79 -0
  27. data/examples/test_llm.rb +100 -0
  28. data/examples/test_text_classifier.rb +59 -0
  29. data/lib/fine/callbacks/base.rb +140 -0
  30. data/lib/fine/callbacks/progress_bar.rb +66 -0
  31. data/lib/fine/configuration.rb +106 -0
  32. data/lib/fine/datasets/data_loader.rb +63 -0
  33. data/lib/fine/datasets/image_dataset.rb +203 -0
  34. data/lib/fine/datasets/instruction_dataset.rb +226 -0
  35. data/lib/fine/datasets/text_data_loader.rb +88 -0
  36. data/lib/fine/datasets/text_dataset.rb +266 -0
  37. data/lib/fine/error.rb +49 -0
  38. data/lib/fine/export/gguf_exporter.rb +424 -0
  39. data/lib/fine/export/onnx_exporter.rb +249 -0
  40. data/lib/fine/export.rb +53 -0
  41. data/lib/fine/hub/config_loader.rb +145 -0
  42. data/lib/fine/hub/model_downloader.rb +136 -0
  43. data/lib/fine/hub/safetensors_loader.rb +108 -0
  44. data/lib/fine/image_classifier.rb +256 -0
  45. data/lib/fine/llm.rb +336 -0
  46. data/lib/fine/models/base.rb +48 -0
  47. data/lib/fine/models/bert_encoder.rb +202 -0
  48. data/lib/fine/models/bert_for_sequence_classification.rb +226 -0
  49. data/lib/fine/models/causal_lm.rb +279 -0
  50. data/lib/fine/models/classification_head.rb +24 -0
  51. data/lib/fine/models/gemma3_decoder.rb +244 -0
  52. data/lib/fine/models/llama_decoder.rb +297 -0
  53. data/lib/fine/models/sentence_transformer.rb +202 -0
  54. data/lib/fine/models/siglip2_for_image_classification.rb +155 -0
  55. data/lib/fine/models/siglip2_vision_encoder.rb +190 -0
  56. data/lib/fine/text_classifier.rb +250 -0
  57. data/lib/fine/text_embedder.rb +221 -0
  58. data/lib/fine/tokenizers/auto_tokenizer.rb +208 -0
  59. data/lib/fine/training/llm_trainer.rb +212 -0
  60. data/lib/fine/training/text_trainer.rb +275 -0
  61. data/lib/fine/training/trainer.rb +194 -0
  62. data/lib/fine/transforms/compose.rb +28 -0
  63. data/lib/fine/transforms/normalize.rb +33 -0
  64. data/lib/fine/transforms/resize.rb +35 -0
  65. data/lib/fine/transforms/to_tensor.rb +53 -0
  66. data/lib/fine/version.rb +3 -0
  67. data/lib/fine.rb +112 -0
  68. data/mise.toml +2 -0
  69. metadata +240 -0
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fine
4
+ module Transforms
5
+ # Normalize a tensor with mean and standard deviation
6
+ class Normalize
7
+ # ImageNet normalization values (commonly used)
8
+ IMAGENET_MEAN = [0.485, 0.456, 0.406].freeze
9
+ IMAGENET_STD = [0.229, 0.224, 0.225].freeze
10
+
11
+ attr_reader :mean, :std
12
+
13
+ # @param mean [Array<Float>] Mean values for each channel
14
+ # @param std [Array<Float>] Standard deviation for each channel
15
+ def initialize(mean: IMAGENET_MEAN, std: IMAGENET_STD)
16
+ @mean = mean
17
+ @std = std
18
+ end
19
+
20
+ def call(tensor)
21
+ # Expect tensor shape: (C, H, W)
22
+ raise ArgumentError, "Expected tensor, got #{tensor.class}" unless tensor.is_a?(Torch::Tensor)
23
+
24
+ # Convert mean and std to tensors with shape (C, 1, 1)
25
+ mean_tensor = Torch.tensor(@mean).view(-1, 1, 1)
26
+ std_tensor = Torch.tensor(@std).view(-1, 1, 1)
27
+
28
+ # Normalize: (x - mean) / std
29
+ (tensor - mean_tensor) / std_tensor
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fine
4
+ module Transforms
5
+ # Resize image to specified dimensions
6
+ class Resize
7
+ attr_reader :width, :height, :interpolation
8
+
9
+ # @param width [Integer] Target width
10
+ # @param height [Integer, nil] Target height (defaults to width for square)
11
+ # @param interpolation [Symbol] Interpolation method (:bilinear, :nearest, :bicubic)
12
+ def initialize(width, height = nil, interpolation: :bilinear)
13
+ @width = width
14
+ @height = height || width
15
+ @interpolation = interpolation
16
+ end
17
+
18
+ def call(image)
19
+ # Map interpolation to vips kernel names
20
+ vips_kernel = case @interpolation
21
+ when :nearest then :nearest
22
+ when :bilinear then :linear
23
+ when :bicubic then :cubic
24
+ else :linear
25
+ end
26
+
27
+ # Calculate scale factors
28
+ h_scale = @width.to_f / image.width
29
+ v_scale = @height.to_f / image.height
30
+
31
+ image.resize(h_scale, vscale: v_scale, kernel: vips_kernel)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fine
4
+ module Transforms
5
+ # Convert a Vips::Image to a Torch::Tensor
6
+ class ToTensor
7
+ # @param scale [Boolean] If true, scale pixel values to [0, 1]
8
+ def initialize(scale: true)
9
+ @scale = scale
10
+ end
11
+
12
+ def call(image)
13
+ # Get image as array of bytes
14
+ # Vips images are (H, W, C) format
15
+
16
+ # Ensure image is in RGB format
17
+ image = ensure_rgb(image)
18
+
19
+ # Get raw pixel data as a flat array
20
+ width = image.width
21
+ height = image.height
22
+ bands = image.bands
23
+
24
+ # Convert to array of floats
25
+ data = image.write_to_memory.unpack("C*")
26
+
27
+ # Create tensor with shape (H, W, C)
28
+ tensor = Torch.tensor(data, dtype: :float32).reshape([height, width, bands])
29
+
30
+ # Scale to [0, 1] if requested
31
+ tensor = tensor / 255.0 if @scale
32
+
33
+ # Permute to (C, H, W) format expected by PyTorch
34
+ tensor.permute([2, 0, 1])
35
+ end
36
+
37
+ private
38
+
39
+ def ensure_rgb(image)
40
+ case image.bands
41
+ when 1
42
+ # Grayscale to RGB
43
+ image.bandjoin([image, image])
44
+ when 4
45
+ # RGBA to RGB (drop alpha)
46
+ image.extract_band(0, n: 3)
47
+ else
48
+ image
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Fine
2
+ VERSION = "0.1.0"
3
+ end
data/lib/fine.rb ADDED
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "torch"
4
+ require "safetensors"
5
+ require "vips"
6
+ require "tokenizers"
7
+ require "tty-progressbar"
8
+ require "down"
9
+ require "json"
10
+ require "fileutils"
11
+
12
+ require_relative "fine/version"
13
+ require_relative "fine/error"
14
+ require_relative "fine/configuration"
15
+
16
+ # Hub
17
+ require_relative "fine/hub/config_loader"
18
+ require_relative "fine/hub/model_downloader"
19
+ require_relative "fine/hub/safetensors_loader"
20
+
21
+ # Tokenizers
22
+ require_relative "fine/tokenizers/auto_tokenizer"
23
+
24
+ # Transforms (Image)
25
+ require_relative "fine/transforms/compose"
26
+ require_relative "fine/transforms/resize"
27
+ require_relative "fine/transforms/normalize"
28
+ require_relative "fine/transforms/to_tensor"
29
+
30
+ # Datasets
31
+ require_relative "fine/datasets/image_dataset"
32
+ require_relative "fine/datasets/data_loader"
33
+ require_relative "fine/datasets/text_dataset"
34
+ require_relative "fine/datasets/text_data_loader"
35
+ require_relative "fine/datasets/instruction_dataset"
36
+
37
+ # Models - Vision
38
+ require_relative "fine/models/base"
39
+ require_relative "fine/models/siglip2_vision_encoder"
40
+ require_relative "fine/models/classification_head"
41
+ require_relative "fine/models/siglip2_for_image_classification"
42
+
43
+ # Models - Text
44
+ require_relative "fine/models/bert_encoder"
45
+ require_relative "fine/models/bert_for_sequence_classification"
46
+ require_relative "fine/models/sentence_transformer"
47
+
48
+ # Models - LLM
49
+ require_relative "fine/models/llama_decoder"
50
+ require_relative "fine/models/gemma3_decoder"
51
+ require_relative "fine/models/causal_lm"
52
+
53
+ # Training
54
+ require_relative "fine/training/trainer"
55
+ require_relative "fine/training/text_trainer"
56
+ require_relative "fine/training/llm_trainer"
57
+
58
+ # Callbacks
59
+ require_relative "fine/callbacks/base"
60
+ require_relative "fine/callbacks/progress_bar"
61
+
62
+ # High-level API
63
+ require_relative "fine/image_classifier"
64
+ require_relative "fine/text_classifier"
65
+ require_relative "fine/text_embedder"
66
+ require_relative "fine/llm"
67
+
68
+ # Export
69
+ require_relative "fine/export"
70
+
71
+ module Fine
72
+ class << self
73
+ attr_accessor :configuration
74
+
75
+ def configure
76
+ self.configuration ||= GlobalConfiguration.new
77
+ yield(configuration) if block_given?
78
+ configuration
79
+ end
80
+
81
+ def cache_dir
82
+ configuration&.cache_dir || File.expand_path("~/.cache/fine")
83
+ end
84
+
85
+ def device
86
+ configuration&.device || detect_device
87
+ end
88
+
89
+ private
90
+
91
+ def detect_device
92
+ if Torch::CUDA.available?
93
+ "cuda"
94
+ elsif defined?(Torch::Backends::MPS) && Torch::Backends::MPS.available?
95
+ "mps"
96
+ else
97
+ "cpu"
98
+ end
99
+ end
100
+ end
101
+
102
+ class GlobalConfiguration
103
+ attr_accessor :cache_dir, :device, :log_level, :progress_bar
104
+
105
+ def initialize
106
+ @cache_dir = File.expand_path("~/.cache/fine")
107
+ @device = nil # auto-detect
108
+ @log_level = :info
109
+ @progress_bar = true
110
+ end
111
+ end
112
+ end
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "3.3"
metadata ADDED
@@ -0,0 +1,240 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Hasinski
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: torch-rb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: safetensors
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tokenizers
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby-vips
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '2.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '2.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-progressbar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0.18'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0.18'
83
+ - !ruby/object:Gem::Dependency
84
+ name: down
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '5.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '5.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.12'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.12'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.50'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.50'
139
+ description: A Ruby-native interface for fine-tuning machine learning models, starting
140
+ with image classification using SigLIP2
141
+ email:
142
+ - krzysztof.hasinski@gmail.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".rspec"
148
+ - CHANGELOG.md
149
+ - Gemfile
150
+ - Gemfile.lock
151
+ - LICENSE
152
+ - README.md
153
+ - Rakefile
154
+ - docs/installation.md
155
+ - docs/tutorials/llm-fine-tuning.md
156
+ - docs/tutorials/model-export.md
157
+ - docs/tutorials/siglip2-image-classification.md
158
+ - docs/tutorials/siglip2-object-recognition.md
159
+ - docs/tutorials/siglip2-similarity-search.md
160
+ - docs/tutorials/text-classification.md
161
+ - docs/tutorials/text-embeddings.md
162
+ - examples/basic_classification.rb
163
+ - examples/data/tool_calls.jsonl
164
+ - examples/demo_training.rb
165
+ - examples/finetune_gemma3_tools.rb
166
+ - examples/real_llm_test.rb
167
+ - examples/real_text_classification_test.rb
168
+ - examples/real_text_embedder_test.rb
169
+ - examples/real_training_test.rb
170
+ - examples/test_export.rb
171
+ - examples/test_image_classifier.rb
172
+ - examples/test_llm.rb
173
+ - examples/test_text_classifier.rb
174
+ - lib/fine.rb
175
+ - lib/fine/callbacks/base.rb
176
+ - lib/fine/callbacks/progress_bar.rb
177
+ - lib/fine/configuration.rb
178
+ - lib/fine/datasets/data_loader.rb
179
+ - lib/fine/datasets/image_dataset.rb
180
+ - lib/fine/datasets/instruction_dataset.rb
181
+ - lib/fine/datasets/text_data_loader.rb
182
+ - lib/fine/datasets/text_dataset.rb
183
+ - lib/fine/error.rb
184
+ - lib/fine/export.rb
185
+ - lib/fine/export/gguf_exporter.rb
186
+ - lib/fine/export/onnx_exporter.rb
187
+ - lib/fine/hub/config_loader.rb
188
+ - lib/fine/hub/model_downloader.rb
189
+ - lib/fine/hub/safetensors_loader.rb
190
+ - lib/fine/image_classifier.rb
191
+ - lib/fine/llm.rb
192
+ - lib/fine/models/base.rb
193
+ - lib/fine/models/bert_encoder.rb
194
+ - lib/fine/models/bert_for_sequence_classification.rb
195
+ - lib/fine/models/causal_lm.rb
196
+ - lib/fine/models/classification_head.rb
197
+ - lib/fine/models/gemma3_decoder.rb
198
+ - lib/fine/models/llama_decoder.rb
199
+ - lib/fine/models/sentence_transformer.rb
200
+ - lib/fine/models/siglip2_for_image_classification.rb
201
+ - lib/fine/models/siglip2_vision_encoder.rb
202
+ - lib/fine/text_classifier.rb
203
+ - lib/fine/text_embedder.rb
204
+ - lib/fine/tokenizers/auto_tokenizer.rb
205
+ - lib/fine/training/llm_trainer.rb
206
+ - lib/fine/training/text_trainer.rb
207
+ - lib/fine/training/trainer.rb
208
+ - lib/fine/transforms/compose.rb
209
+ - lib/fine/transforms/normalize.rb
210
+ - lib/fine/transforms/resize.rb
211
+ - lib/fine/transforms/to_tensor.rb
212
+ - lib/fine/version.rb
213
+ - mise.toml
214
+ homepage: https://github.com/khasinski/fine
215
+ licenses:
216
+ - MIT
217
+ metadata:
218
+ homepage_uri: https://github.com/khasinski/fine
219
+ source_code_uri: https://github.com/khasinski/fine
220
+ changelog_uri: https://github.com/khasinski/fine/blob/main/CHANGELOG.md
221
+ post_install_message:
222
+ rdoc_options: []
223
+ require_paths:
224
+ - lib
225
+ required_ruby_version: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '3.1'
230
+ required_rubygems_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ requirements: []
236
+ rubygems_version: 3.5.22
237
+ signing_key:
238
+ specification_version: 4
239
+ summary: Fine-tune ML models with Ruby
240
+ test_files: []