fastembed 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/.yardopts +6 -0
- data/BENCHMARKS.md +124 -1
- data/CHANGELOG.md +14 -0
- data/README.md +395 -74
- data/benchmark/compare_all.rb +167 -0
- data/benchmark/compare_python.py +60 -0
- data/benchmark/memory_profile.rb +70 -0
- data/benchmark/profile.rb +198 -0
- data/benchmark/reranker_benchmark.rb +158 -0
- data/exe/fastembed +6 -0
- data/fastembed.gemspec +3 -0
- data/lib/fastembed/async.rb +193 -0
- data/lib/fastembed/base_model.rb +247 -0
- data/lib/fastembed/base_model_info.rb +61 -0
- data/lib/fastembed/cli.rb +745 -0
- data/lib/fastembed/custom_model_registry.rb +255 -0
- data/lib/fastembed/image_embedding.rb +313 -0
- data/lib/fastembed/late_interaction_embedding.rb +260 -0
- data/lib/fastembed/late_interaction_model_info.rb +91 -0
- data/lib/fastembed/model_info.rb +59 -19
- data/lib/fastembed/model_management.rb +82 -23
- data/lib/fastembed/onnx_embedding_model.rb +25 -4
- data/lib/fastembed/pooling.rb +39 -3
- data/lib/fastembed/progress.rb +52 -0
- data/lib/fastembed/quantization.rb +75 -0
- data/lib/fastembed/reranker_model_info.rb +91 -0
- data/lib/fastembed/sparse_embedding.rb +261 -0
- data/lib/fastembed/sparse_model_info.rb +80 -0
- data/lib/fastembed/text_cross_encoder.rb +217 -0
- data/lib/fastembed/text_embedding.rb +161 -28
- data/lib/fastembed/validators.rb +59 -0
- data/lib/fastembed/version.rb +1 -1
- data/lib/fastembed.rb +42 -1
- data/plan.md +257 -0
- data/scripts/verify_models.rb +229 -0
- metadata +70 -3
data/plan.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# FastEmbed-rb Roadmap
|
|
2
|
+
|
|
3
|
+
This document outlines features from the original [FastEmbed Python library](https://github.com/qdrant/fastembed) that are not yet implemented in fastembed-rb.
|
|
4
|
+
|
|
5
|
+
## Current Status (v1.0.0)
|
|
6
|
+
|
|
7
|
+
### Implemented
|
|
8
|
+
- Dense text embeddings with 12 models
|
|
9
|
+
- Automatic model downloading from HuggingFace
|
|
10
|
+
- Lazy evaluation via `Enumerator`
|
|
11
|
+
- Query/passage prefixes for retrieval models
|
|
12
|
+
- Mean pooling and L2 normalization
|
|
13
|
+
- Configurable batch size and threading
|
|
14
|
+
- CoreML execution provider support
|
|
15
|
+
- CLI tool (`fastembed`)
|
|
16
|
+
- **Reranking / Cross-Encoder models** (5 models)
|
|
17
|
+
|
|
18
|
+
## Feature Gap Analysis
|
|
19
|
+
|
|
20
|
+
### High Priority
|
|
21
|
+
|
|
22
|
+
#### 1. Sparse Text Embeddings
|
|
23
|
+
The Python library supports sparse embedding models that return indices and values rather than dense vectors. These are useful for hybrid search combining keyword and semantic matching.
|
|
24
|
+
|
|
25
|
+
**Models to support:**
|
|
26
|
+
- `Qdrant/bm25` - Classic BM25 (0.010 GB)
|
|
27
|
+
- `Qdrant/bm42-all-minilm-l6-v2-attentions` - Attention-based sparse (0.090 GB)
|
|
28
|
+
- `prithivida/Splade_PP_en_v1` - SPLADE++ (0.532 GB)
|
|
29
|
+
|
|
30
|
+
**API design:**
|
|
31
|
+
```ruby
|
|
32
|
+
sparse = Fastembed::SparseTextEmbedding.new
|
|
33
|
+
result = sparse.embed(["hello world"]).first
|
|
34
|
+
# => { indices: [123, 456, 789], values: [0.5, 0.3, 0.2] }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Implementation notes:**
|
|
38
|
+
- Need new `SparseTextEmbedding` class
|
|
39
|
+
- Different output format (sparse vectors instead of dense)
|
|
40
|
+
- May require different tokenization approach for BM25
|
|
41
|
+
|
|
42
|
+
#### 2. Late Interaction (ColBERT) Models
|
|
43
|
+
ColBERT-style models produce token-level embeddings rather than a single vector per document. This enables more fine-grained matching.
|
|
44
|
+
|
|
45
|
+
**Models to support:**
|
|
46
|
+
- `answerdotai/answerai-colbert-small-v1` (96 dim)
|
|
47
|
+
- `colbert-ir/colbertv2.0` (128 dim)
|
|
48
|
+
- `jinaai/jina-colbert-v2` (128 dim)
|
|
49
|
+
|
|
50
|
+
**API design:**
|
|
51
|
+
```ruby
|
|
52
|
+
colbert = Fastembed::LateInteractionTextEmbedding.new
|
|
53
|
+
result = colbert.embed(["hello world"]).first
|
|
54
|
+
# => Array of token embeddings, shape: [num_tokens, dim]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Implementation notes:**
|
|
58
|
+
- Returns 2D array per document (tokens × dimensions)
|
|
59
|
+
- Different pooling strategy (no pooling, keep all tokens)
|
|
60
|
+
- Scoring requires MaxSim operation between query and document tokens
|
|
61
|
+
|
|
62
|
+
#### ~~3. Reranking / Cross-Encoder Models~~ ✅ IMPLEMENTED
|
|
63
|
+
|
|
64
|
+
See `Fastembed::TextCrossEncoder` class.
|
|
65
|
+
|
|
66
|
+
### Medium Priority
|
|
67
|
+
|
|
68
|
+
#### ~~4. Image Embeddings~~ ✅ IMPLEMENTED
|
|
69
|
+
|
|
70
|
+
Vision models for converting images to vectors. Requires `mini_magick` gem.
|
|
71
|
+
|
|
72
|
+
**Supported models:**
|
|
73
|
+
- `Qdrant/resnet50-onnx` (2048 dim)
|
|
74
|
+
- `Qdrant/clip-ViT-B-32-vision` (512 dim)
|
|
75
|
+
- `jinaai/jina-clip-v1` (768 dim)
|
|
76
|
+
|
|
77
|
+
**Usage:**
|
|
78
|
+
```ruby
|
|
79
|
+
# Add to Gemfile: gem "mini_magick"
|
|
80
|
+
image_embed = Fastembed::ImageEmbedding.new
|
|
81
|
+
vector = image_embed.embed(["path/to/image.jpg"]).first
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### ~~5. Custom Model Support~~ ✅ IMPLEMENTED
|
|
85
|
+
|
|
86
|
+
Implemented via `CustomModelRegistry` module. Users can register custom models:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
Fastembed.register_model(
|
|
90
|
+
model_name: "my-org/my-model",
|
|
91
|
+
dim: 768,
|
|
92
|
+
sources: { hf: "my-org/my-model" }
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
embed = Fastembed::TextEmbedding.new(model_name: "my-org/my-model")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Also supports local model loading via `local_model_dir` parameter.
|
|
99
|
+
|
|
100
|
+
### Low Priority
|
|
101
|
+
|
|
102
|
+
#### 6. Multimodal Late Interaction (ColPali)
|
|
103
|
+
ColPali models that can embed both images and text for document retrieval.
|
|
104
|
+
|
|
105
|
+
**Models to support:**
|
|
106
|
+
- `vidore/colpali-v1.2`
|
|
107
|
+
- `vidore/colqwen2-v1.0`
|
|
108
|
+
|
|
109
|
+
**Implementation notes:**
|
|
110
|
+
- Combines image and text embedding
|
|
111
|
+
- Requires vision preprocessing
|
|
112
|
+
- Complex architecture, lower priority
|
|
113
|
+
|
|
114
|
+
#### 7. Quantized Models
|
|
115
|
+
Support for INT8/INT4 quantized models for faster inference and lower memory usage.
|
|
116
|
+
|
|
117
|
+
**Implementation notes:**
|
|
118
|
+
- ONNX Runtime supports quantized models natively
|
|
119
|
+
- Need to add quantized model variants to registry
|
|
120
|
+
- Trade-off between speed and accuracy
|
|
121
|
+
|
|
122
|
+
## ~~CLI Enhancements~~ ✅ IMPLEMENTED
|
|
123
|
+
|
|
124
|
+
All planned CLI features have been implemented:
|
|
125
|
+
|
|
126
|
+
- ✅ `fastembed download <model>` - Pre-download models for offline use
|
|
127
|
+
- ✅ `fastembed benchmark` - Run performance benchmarks with configurable iterations
|
|
128
|
+
- ✅ `fastembed info <model>` - Show detailed model information including cache status
|
|
129
|
+
- ✅ `-i input.txt` - Read texts from file (one per line)
|
|
130
|
+
- ✅ `-p` / `--progress` - Show progress bar during embedding
|
|
131
|
+
- ✅ `-q` / `--quiet` - Suppress progress output for scripting
|
|
132
|
+
|
|
133
|
+
## Breaking Changes for v2.0
|
|
134
|
+
|
|
135
|
+
If we do a major version bump:
|
|
136
|
+
|
|
137
|
+
1. Consider making `embed()` return an Array instead of Enumerator by default
|
|
138
|
+
2. Rename `query_embed`/`passage_embed` to `embed_query`/`embed_passage` for consistency
|
|
139
|
+
3. Use keyword arguments consistently throughout
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Refactoring Plan
|
|
144
|
+
|
|
145
|
+
### Completed: Phase 1 - Extract Shared Helpers
|
|
146
|
+
|
|
147
|
+
- [x] Create `Validators` module for document validation
|
|
148
|
+
- [x] Extract `prepare_model_inputs` to BaseModel
|
|
149
|
+
- [x] Extract `setup_model_and_tokenizer` to BaseModel
|
|
150
|
+
- [x] Update all model classes to use shared helpers
|
|
151
|
+
|
|
152
|
+
**Result:** Reduced ~60 lines of duplicated code across 4 model classes.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Completed: Phase 2 - Add Missing Features (Medium Risk)
|
|
157
|
+
|
|
158
|
+
Goal: Achieve API consistency across all model types.
|
|
159
|
+
|
|
160
|
+
#### 2.1 Add `passage_embed` to TextSparseEmbedding ✅ IMPLEMENTED
|
|
161
|
+
|
|
162
|
+
Added to TextSparseEmbedding.
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# lib/fastembed/sparse_embedding.rb
|
|
166
|
+
def passage_embed(passages, batch_size: 32)
|
|
167
|
+
passages = [passages] if passages.is_a?(String)
|
|
168
|
+
embed(passages, batch_size: batch_size)
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### 2.2 Add async methods to all embedding classes ✅ IMPLEMENTED
|
|
173
|
+
|
|
174
|
+
Added async methods to all model classes:
|
|
175
|
+
- TextSparseEmbedding: embed_async, query_embed_async, passage_embed_async
|
|
176
|
+
- LateInteractionTextEmbedding: embed_async, query_embed_async, passage_embed_async
|
|
177
|
+
- TextCrossEncoder: rerank_async, rerank_with_scores_async
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# Add to TextSparseEmbedding
|
|
181
|
+
def embed_async(documents, batch_size: 32)
|
|
182
|
+
Async::Future.new { embed(documents, batch_size: batch_size).to_a }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def query_embed_async(queries, batch_size: 32)
|
|
186
|
+
Async::Future.new { query_embed(queries, batch_size: batch_size).to_a }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def passage_embed_async(passages, batch_size: 32)
|
|
190
|
+
Async::Future.new { passage_embed(passages, batch_size: batch_size).to_a }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Add to TextCrossEncoder
|
|
194
|
+
def rerank_async(query:, documents:, batch_size: 64)
|
|
195
|
+
Async::Future.new { rerank(query: query, documents: documents, batch_size: batch_size) }
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### 2.3 Add progress callback support to all embedding classes ✅ IMPLEMENTED
|
|
200
|
+
|
|
201
|
+
Added progress callback support to TextSparseEmbedding and LateInteractionTextEmbedding.
|
|
202
|
+
|
|
203
|
+
#### 2.4 Add `show_progress` parameter to TextCrossEncoder ✅ IMPLEMENTED
|
|
204
|
+
|
|
205
|
+
Made configurable (was hardcoded to true).
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### Completed: Phase 3 - Unify Initialization (Higher Risk)
|
|
210
|
+
|
|
211
|
+
Goal: Consistent initialization API across all model types.
|
|
212
|
+
|
|
213
|
+
#### 3.1 Add quantization support to all models ✅ IMPLEMENTED
|
|
214
|
+
|
|
215
|
+
Added quantization parameter to all model classes (TextSparseEmbedding, LateInteractionTextEmbedding, TextCrossEncoder).
|
|
216
|
+
|
|
217
|
+
#### 3.2 Add local_model_dir support to all models ✅ IMPLEMENTED
|
|
218
|
+
|
|
219
|
+
Added local_model_dir, model_file, and tokenizer_file parameters to all model classes. Shared logic extracted to BaseModel (initialize_from_local, create_local_model_info).
|
|
220
|
+
|
|
221
|
+
#### 3.3 Document batch size rationale ✅ DOCUMENTED
|
|
222
|
+
|
|
223
|
+
Default batch sizes vary by model type based on memory requirements:
|
|
224
|
+
|
|
225
|
+
| Model Type | Default Batch Size | Rationale |
|
|
226
|
+
|------------|-------------------|-----------|
|
|
227
|
+
| TextEmbedding | 256 | Dense embeddings have fixed output size (e.g., 384 floats). Memory is predictable and efficient. |
|
|
228
|
+
| TextSparseEmbedding | 32 | SPLADE models output logits for entire vocabulary (~30k tokens) per sequence position. Much higher memory per document. |
|
|
229
|
+
| LateInteractionTextEmbedding | 32 | ColBERT keeps per-token embeddings (not pooled), so output size scales with sequence length × embedding dim. |
|
|
230
|
+
| TextCrossEncoder | 64 | Processes query-document pairs together. Each pair requires more memory than single documents, but less than sparse/late interaction. |
|
|
231
|
+
|
|
232
|
+
Users can override these defaults via the `batch_size` parameter if they have different memory constraints.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### Implementation Priority
|
|
237
|
+
|
|
238
|
+
| Task | Risk | Effort | Value |
|
|
239
|
+
|------|------|--------|-------|
|
|
240
|
+
| 2.1 Add passage_embed to Sparse | Low | Small | Medium |
|
|
241
|
+
| 2.2 Add async to all classes | Low | Medium | High |
|
|
242
|
+
| 2.3 Add progress to all classes | Medium | Medium | Medium |
|
|
243
|
+
| 2.4 Add show_progress to CrossEncoder | Low | Small | Low |
|
|
244
|
+
| 3.1 Add quantization to all | Medium | Medium | Medium |
|
|
245
|
+
| 3.2 Add local_model_dir to all | Medium | Large | Medium |
|
|
246
|
+
| 3.3 Document batch size rationale | Low | Small | Low |
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Contributing
|
|
251
|
+
|
|
252
|
+
Contributions are welcome! If you'd like to implement any of these features:
|
|
253
|
+
|
|
254
|
+
1. Open an issue to discuss the approach
|
|
255
|
+
2. Follow the existing code style (run `bundle exec rubocop`)
|
|
256
|
+
3. Add tests for new functionality
|
|
257
|
+
4. Update the README and CHANGELOG
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Model Verification Script
|
|
5
|
+
# Downloads and tests all supported models to ensure they work correctly.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# ruby scripts/verify_models.rb # Run all model tests
|
|
9
|
+
# ruby scripts/verify_models.rb --quick # Quick test (first model of each type only)
|
|
10
|
+
# ruby scripts/verify_models.rb --type embedding # Test only embedding models
|
|
11
|
+
|
|
12
|
+
require 'bundler/setup'
|
|
13
|
+
require 'fastembed'
|
|
14
|
+
require 'optparse'
|
|
15
|
+
|
|
16
|
+
class ModelVerifier
|
|
17
|
+
COLORS = {
|
|
18
|
+
green: "\e[32m",
|
|
19
|
+
red: "\e[31m",
|
|
20
|
+
yellow: "\e[33m",
|
|
21
|
+
cyan: "\e[36m",
|
|
22
|
+
reset: "\e[0m"
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def initialize(quick: false, type: nil)
|
|
26
|
+
@quick = quick
|
|
27
|
+
@type = type
|
|
28
|
+
@results = { passed: [], failed: [], skipped: [] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run
|
|
32
|
+
puts "#{COLORS[:cyan]}=== FastEmbed Model Verification ==#{COLORS[:reset]}"
|
|
33
|
+
puts "Mode: #{@quick ? 'Quick' : 'Full'}"
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
verify_embedding_models if should_test?(:embedding)
|
|
37
|
+
verify_sparse_models if should_test?(:sparse)
|
|
38
|
+
verify_late_interaction_models if should_test?(:late_interaction)
|
|
39
|
+
verify_reranker_models if should_test?(:reranker)
|
|
40
|
+
verify_image_models if should_test?(:image)
|
|
41
|
+
|
|
42
|
+
print_summary
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def should_test?(model_type)
|
|
48
|
+
@type.nil? || @type.to_sym == model_type
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def verify_embedding_models
|
|
52
|
+
section("Text Embedding Models")
|
|
53
|
+
models = Fastembed::SUPPORTED_MODELS.keys
|
|
54
|
+
models = [models.first] if @quick
|
|
55
|
+
|
|
56
|
+
models.each do |model_name|
|
|
57
|
+
verify_model(model_name, :embedding) do
|
|
58
|
+
embedding = Fastembed::TextEmbedding.new(model_name: model_name, show_progress: false)
|
|
59
|
+
vectors = embedding.embed(['Hello world', 'Test document']).to_a
|
|
60
|
+
|
|
61
|
+
raise "Expected 2 vectors, got #{vectors.length}" unless vectors.length == 2
|
|
62
|
+
raise "Vector dimension mismatch" unless vectors.first.length == embedding.dim
|
|
63
|
+
|
|
64
|
+
"dim=#{embedding.dim}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def verify_sparse_models
|
|
70
|
+
section("Sparse Embedding Models")
|
|
71
|
+
models = Fastembed::SUPPORTED_SPARSE_MODELS.keys
|
|
72
|
+
models = [models.first] if @quick
|
|
73
|
+
|
|
74
|
+
models.each do |model_name|
|
|
75
|
+
verify_model(model_name, :sparse) do
|
|
76
|
+
embedding = Fastembed::TextSparseEmbedding.new(model_name: model_name, show_progress: false)
|
|
77
|
+
vectors = embedding.embed(['Hello world']).to_a
|
|
78
|
+
|
|
79
|
+
raise "Expected sparse vector with indices" unless vectors.first[:indices].is_a?(Array)
|
|
80
|
+
raise "Expected sparse vector with values" unless vectors.first[:values].is_a?(Array)
|
|
81
|
+
|
|
82
|
+
"nnz=#{vectors.first[:indices].length}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def verify_late_interaction_models
|
|
88
|
+
section("Late Interaction Models")
|
|
89
|
+
models = Fastembed::SUPPORTED_LATE_INTERACTION_MODELS.keys
|
|
90
|
+
models = [models.first] if @quick
|
|
91
|
+
|
|
92
|
+
models.each do |model_name|
|
|
93
|
+
verify_model(model_name, :late_interaction) do
|
|
94
|
+
embedding = Fastembed::LateInteractionTextEmbedding.new(model_name: model_name, show_progress: false)
|
|
95
|
+
vectors = embedding.embed(['Hello world']).to_a
|
|
96
|
+
|
|
97
|
+
raise "Expected token embeddings array" unless vectors.first.is_a?(Array)
|
|
98
|
+
raise "Token embedding dimension mismatch" unless vectors.first.first.length == embedding.dim
|
|
99
|
+
|
|
100
|
+
"dim=#{embedding.dim}, tokens=#{vectors.first.length}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def verify_reranker_models
|
|
106
|
+
section("Reranker Models")
|
|
107
|
+
models = Fastembed::SUPPORTED_RERANKER_MODELS.keys
|
|
108
|
+
models = [models.first] if @quick
|
|
109
|
+
|
|
110
|
+
models.each do |model_name|
|
|
111
|
+
verify_model(model_name, :reranker) do
|
|
112
|
+
encoder = Fastembed::TextCrossEncoder.new(model_name: model_name, show_progress: false)
|
|
113
|
+
results = encoder.rerank_with_scores(
|
|
114
|
+
query: 'What is Ruby?',
|
|
115
|
+
documents: ['Ruby is a programming language', 'The sky is blue']
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
raise "Expected ranked results" unless results.is_a?(Array)
|
|
119
|
+
raise "Expected results with scores" unless results.first.key?(:score)
|
|
120
|
+
|
|
121
|
+
"top_score=#{results.first[:score].round(4)}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def verify_image_models
|
|
127
|
+
section("Image Embedding Models")
|
|
128
|
+
|
|
129
|
+
begin
|
|
130
|
+
require 'mini_magick'
|
|
131
|
+
rescue LoadError
|
|
132
|
+
puts "#{COLORS[:yellow]} Skipped (mini_magick not installed)#{COLORS[:reset]}"
|
|
133
|
+
Fastembed::SUPPORTED_IMAGE_MODELS.keys.each do |model_name|
|
|
134
|
+
@results[:skipped] << { name: model_name, type: :image, reason: 'mini_magick not installed' }
|
|
135
|
+
end
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
models = Fastembed::SUPPORTED_IMAGE_MODELS.keys
|
|
140
|
+
models = [models.first] if @quick
|
|
141
|
+
|
|
142
|
+
models.each do |model_name|
|
|
143
|
+
verify_model(model_name, :image) do
|
|
144
|
+
# Create a test image
|
|
145
|
+
require 'tempfile'
|
|
146
|
+
path = Tempfile.new(['test', '.png']).path
|
|
147
|
+
MiniMagick::Tool::Convert.new do |convert|
|
|
148
|
+
convert.size '224x224'
|
|
149
|
+
convert.xc 'white'
|
|
150
|
+
convert << path
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
begin
|
|
154
|
+
embedding = Fastembed::ImageEmbedding.new(model_name: model_name, show_progress: false)
|
|
155
|
+
vectors = embedding.embed(path).to_a
|
|
156
|
+
|
|
157
|
+
raise "Expected image embedding" unless vectors.first.is_a?(Array)
|
|
158
|
+
raise "Dimension mismatch" unless vectors.first.length == embedding.dim
|
|
159
|
+
|
|
160
|
+
"dim=#{embedding.dim}"
|
|
161
|
+
ensure
|
|
162
|
+
File.delete(path) if File.exist?(path)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def verify_model(model_name, type)
|
|
169
|
+
print " #{model_name}... "
|
|
170
|
+
$stdout.flush
|
|
171
|
+
|
|
172
|
+
start_time = Time.now
|
|
173
|
+
begin
|
|
174
|
+
result = yield
|
|
175
|
+
elapsed = (Time.now - start_time).round(2)
|
|
176
|
+
puts "#{COLORS[:green]}PASS#{COLORS[:reset]} (#{elapsed}s) [#{result}]"
|
|
177
|
+
@results[:passed] << { name: model_name, type: type, time: elapsed }
|
|
178
|
+
rescue => e
|
|
179
|
+
elapsed = (Time.now - start_time).round(2)
|
|
180
|
+
puts "#{COLORS[:red]}FAIL#{COLORS[:reset]} (#{elapsed}s)"
|
|
181
|
+
puts " Error: #{e.message}"
|
|
182
|
+
@results[:failed] << { name: model_name, type: type, error: e.message }
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def section(title)
|
|
187
|
+
puts "#{COLORS[:cyan]}#{title}:#{COLORS[:reset]}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def print_summary
|
|
191
|
+
puts
|
|
192
|
+
puts "#{COLORS[:cyan]}=== Summary ==#{COLORS[:reset]}"
|
|
193
|
+
puts "Passed: #{COLORS[:green]}#{@results[:passed].length}#{COLORS[:reset]}"
|
|
194
|
+
puts "Failed: #{COLORS[:red]}#{@results[:failed].length}#{COLORS[:reset]}"
|
|
195
|
+
puts "Skipped: #{COLORS[:yellow]}#{@results[:skipped].length}#{COLORS[:reset]}"
|
|
196
|
+
puts
|
|
197
|
+
|
|
198
|
+
unless @results[:failed].empty?
|
|
199
|
+
puts "#{COLORS[:red]}Failed models:#{COLORS[:reset]}"
|
|
200
|
+
@results[:failed].each do |r|
|
|
201
|
+
puts " - #{r[:name]} (#{r[:type]}): #{r[:error]}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
exit(@results[:failed].empty? ? 0 : 1)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Parse options
|
|
210
|
+
options = { quick: false, type: nil }
|
|
211
|
+
OptionParser.new do |opts|
|
|
212
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
|
213
|
+
|
|
214
|
+
opts.on('-q', '--quick', 'Quick mode (first model of each type only)') do
|
|
215
|
+
options[:quick] = true
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
opts.on('-t', '--type TYPE', %w[embedding sparse late_interaction reranker image],
|
|
219
|
+
'Test only models of this type') do |type|
|
|
220
|
+
options[:type] = type
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
opts.on('-h', '--help', 'Show this help') do
|
|
224
|
+
puts opts
|
|
225
|
+
exit
|
|
226
|
+
end
|
|
227
|
+
end.parse!
|
|
228
|
+
|
|
229
|
+
ModelVerifier.new(**options).run
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastembed
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Hasinski
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: onnxruntime
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0.5'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: mini_magick
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '4.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '4.0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: rake
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -94,30 +108,83 @@ dependencies:
|
|
|
94
108
|
- - "~>"
|
|
95
109
|
- !ruby/object:Gem::Version
|
|
96
110
|
version: '3.0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: webmock
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '3.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: yard
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0.9'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0.9'
|
|
97
139
|
description: A Ruby port of FastEmbed - fast text embeddings using ONNX Runtime
|
|
98
140
|
email:
|
|
99
141
|
- krzysztof.hasinski@gmail.com
|
|
100
|
-
executables:
|
|
142
|
+
executables:
|
|
143
|
+
- fastembed
|
|
101
144
|
extensions: []
|
|
102
145
|
extra_rdoc_files: []
|
|
103
146
|
files:
|
|
104
147
|
- ".mise.toml"
|
|
105
148
|
- ".rspec"
|
|
106
149
|
- ".rubocop.yml"
|
|
150
|
+
- ".yardopts"
|
|
107
151
|
- BENCHMARKS.md
|
|
108
152
|
- CHANGELOG.md
|
|
109
153
|
- Gemfile
|
|
110
154
|
- LICENSE
|
|
111
155
|
- README.md
|
|
112
156
|
- Rakefile
|
|
157
|
+
- benchmark/compare_all.rb
|
|
158
|
+
- benchmark/compare_python.py
|
|
159
|
+
- benchmark/memory_profile.rb
|
|
160
|
+
- benchmark/profile.rb
|
|
161
|
+
- benchmark/reranker_benchmark.rb
|
|
162
|
+
- exe/fastembed
|
|
113
163
|
- fastembed.gemspec
|
|
114
164
|
- lib/fastembed.rb
|
|
165
|
+
- lib/fastembed/async.rb
|
|
166
|
+
- lib/fastembed/base_model.rb
|
|
167
|
+
- lib/fastembed/base_model_info.rb
|
|
168
|
+
- lib/fastembed/cli.rb
|
|
169
|
+
- lib/fastembed/custom_model_registry.rb
|
|
170
|
+
- lib/fastembed/image_embedding.rb
|
|
171
|
+
- lib/fastembed/late_interaction_embedding.rb
|
|
172
|
+
- lib/fastembed/late_interaction_model_info.rb
|
|
115
173
|
- lib/fastembed/model_info.rb
|
|
116
174
|
- lib/fastembed/model_management.rb
|
|
117
175
|
- lib/fastembed/onnx_embedding_model.rb
|
|
118
176
|
- lib/fastembed/pooling.rb
|
|
177
|
+
- lib/fastembed/progress.rb
|
|
178
|
+
- lib/fastembed/quantization.rb
|
|
179
|
+
- lib/fastembed/reranker_model_info.rb
|
|
180
|
+
- lib/fastembed/sparse_embedding.rb
|
|
181
|
+
- lib/fastembed/sparse_model_info.rb
|
|
182
|
+
- lib/fastembed/text_cross_encoder.rb
|
|
119
183
|
- lib/fastembed/text_embedding.rb
|
|
184
|
+
- lib/fastembed/validators.rb
|
|
120
185
|
- lib/fastembed/version.rb
|
|
186
|
+
- plan.md
|
|
187
|
+
- scripts/verify_models.rb
|
|
121
188
|
homepage: https://github.com/khasinski/fastembed-rb
|
|
122
189
|
licenses:
|
|
123
190
|
- MIT
|