red-candle 1.8.1 → 1.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2d45880b82de259bacdef9c2ce582a3d98da006966e3c211b5f2c63a76a474a
4
- data.tar.gz: 2f239dbfc74f6c924fba41b7ed53437416459ceed6bfa2b6d5501d82a778c071
3
+ metadata.gz: e7b7ecd1fe8f4551498e553e005c09ffb75f858b107525cbc8bd77948a040ff6
4
+ data.tar.gz: cf84d7690cfdc13eaf210745b38e73bbf72806a4f94be2c1a0315d1d3827ff8d
5
5
  SHA512:
6
- metadata.gz: 18f03813074b73b797a82f9d2917441b701e920a3e2a1afef8b7fb4e863ebbf2acbda06decb90a9014766fbaef4554f0e88e66744fe28e86d4e489e5a7db8dcf
7
- data.tar.gz: 97482fdd896803f4bb3a4a8142ad04995f9a25b53c8876979c77e392881ae43b72f497797501f5fc24323108b1edb5a6206933e0a8eb30ba02879f393df4a507
6
+ metadata.gz: 8f33e7cad2a4678a77f54c6997b31ebd3880a5916a473f8f15bdb58b8bfe78960077efa713ebab265dfa25acc0bd28bd952bb8a7cd48549593f6aee107cbc887
7
+ data.tar.gz: f4ba719c4f721fa23155bf11df9bd779d423eba03676ba5c3aa1c92c29e0551455267b07bc6f15b2996cd2a8592cdf7c471aed6d11d1706c0f5371de054f739d
data/README.md CHANGED
@@ -107,6 +107,10 @@ device = Candle::Device.cpu # or Candle::Device.metal, Candle::Device.cuda
107
107
  model = Candle::EmbeddingModel.from_pretrained("jinaai/jina-embeddings-v2-base-en", device: device)
108
108
  embedding = model.embedding("Hi there!")
109
109
 
110
+ # Cap input length (in tokens) to cut memory; longer inputs are truncated.
111
+ # JinaBERT otherwise reserves ~3.2 GB for its full 8192-token context.
112
+ model = Candle::EmbeddingModel.from_pretrained(max_length: 512)
113
+
110
114
  # Reranker also supports device selection
111
115
  reranker = Candle::Reranker.from_pretrained("cross-encoder/ms-marco-MiniLM-L-12-v2", device: device)
112
116
  results = reranker.rerank("query", ["doc1", "doc2", "doc3"])
@@ -72,10 +72,11 @@ pub struct EmbeddingModelInner {
72
72
  model: Option<EmbeddingModelVariant>,
73
73
  tokenizer: Option<TokenizerWrapper>,
74
74
  embedding_size: Option<usize>,
75
+ max_length: Option<usize>,
75
76
  }
76
77
 
77
78
  impl EmbeddingModel {
78
- pub fn new(model_id: Option<String>, tokenizer: Option<String>, device: Option<Device>, model_type: Option<String>, embedding_size: Option<usize>) -> Result<Self> {
79
+ pub fn new(model_id: Option<String>, tokenizer: Option<String>, device: Option<Device>, model_type: Option<String>, embedding_size: Option<usize>, max_length: Option<usize>) -> Result<Self> {
79
80
  let device = device.unwrap_or(Device::best()).as_device()?;
80
81
  let model_type = model_type
81
82
  .and_then(|mt| EmbeddingModelType::from_string(&mt))
@@ -86,14 +87,15 @@ impl EmbeddingModel {
86
87
  tokenizer_id: tokenizer.clone(),
87
88
  model_type: Some(model_type),
88
89
  model: match model_id.as_ref() {
89
- Some(id) => Some(Self::build_embedding_model(id, device, model_type, embedding_size)?),
90
+ Some(id) => Some(Self::build_embedding_model(id, device, model_type, embedding_size, max_length)?),
90
91
  None => None
91
92
  },
92
93
  tokenizer: match tokenizer {
93
- Some(tid) => Some(Self::build_tokenizer(tid)?),
94
+ Some(tid) => Some(Self::build_tokenizer(tid, max_length)?),
94
95
  None => None
95
96
  },
96
97
  embedding_size,
98
+ max_length,
97
99
  }))
98
100
  }
99
101
 
@@ -177,7 +179,7 @@ impl EmbeddingModel {
177
179
  }
178
180
  }
179
181
 
180
- fn build_embedding_model(model_id: &str, device: CoreDevice, model_type: EmbeddingModelType, embedding_size: Option<usize>) -> Result<EmbeddingModelVariant> {
182
+ fn build_embedding_model(model_id: &str, device: CoreDevice, model_type: EmbeddingModelType, embedding_size: Option<usize>, max_length: Option<usize>) -> Result<EmbeddingModelVariant> {
181
183
  use hf_hub::{api::sync::Api, Repo, RepoType};
182
184
  let api = Api::new().map_err(wrap_hf_err)?;
183
185
  let repo = Repo::new(model_id.to_string(), RepoType::Model);
@@ -194,6 +196,11 @@ impl EmbeddingModel {
194
196
  let final_emb_dim = Self::resolve_embedding_size(Path::new(&model_path), embedding_size)?;
195
197
  let mut config = JinaConfig::v2_base();
196
198
  config.hidden_size = final_emb_dim;
199
+ // JinaBERT precomputes an ALiBi bias of heads x len x len floats
200
+ // (3.2 GB at the default 8192), so size it to the inputs we accept.
201
+ if let Some(max_length) = max_length {
202
+ config.max_position_embeddings = max_length;
203
+ }
197
204
  let vb = unsafe {
198
205
  VarBuilder::from_mmaped_safetensors(&[model_path], CoreDType::F32, &device)
199
206
  .map_err(wrap_candle_err)?
@@ -268,7 +275,7 @@ impl EmbeddingModel {
268
275
  }
269
276
  }
270
277
 
271
- fn build_tokenizer(tokenizer_id: String) -> Result<TokenizerWrapper> {
278
+ fn build_tokenizer(tokenizer_id: String, max_length: Option<usize>) -> Result<TokenizerWrapper> {
272
279
  use hf_hub::{api::sync::Api, Repo, RepoType};
273
280
  let tokenizer_path = Api::new()
274
281
  .map_err(wrap_hf_err)?
@@ -282,6 +289,10 @@ impl EmbeddingModel {
282
289
  .map_err(wrap_std_err)?;
283
290
 
284
291
  let tokenizer = TokenizerLoader::with_padding(tokenizer, None);
292
+ let tokenizer = match max_length {
293
+ Some(max_length) => TokenizerLoader::with_truncation(tokenizer, max_length),
294
+ None => tokenizer,
295
+ };
285
296
  Ok(TokenizerWrapper::new(tokenizer))
286
297
  }
287
298
 
@@ -451,6 +462,11 @@ impl EmbeddingModel {
451
462
  if let Some(size) = self.0.embedding_size {
452
463
  hash.aset("embedding_size", size)?;
453
464
  }
465
+
466
+ // Add max_length
467
+ if let Some(max_length) = self.0.max_length {
468
+ hash.aset("max_length", max_length)?;
469
+ }
454
470
 
455
471
  Ok(hash)
456
472
  }
@@ -459,7 +475,7 @@ impl EmbeddingModel {
459
475
  pub fn init(rb_candle: RModule) -> Result<()> {
460
476
  let ruby = Ruby::get().unwrap();
461
477
  let rb_embedding_model = rb_candle.define_class("EmbeddingModel", ruby.class_object())?;
462
- rb_embedding_model.define_singleton_method("_create", function!(EmbeddingModel::new, 5))?;
478
+ rb_embedding_model.define_singleton_method("_create", function!(EmbeddingModel::new, 6))?;
463
479
  // Expose embedding with an optional pooling_method argument (default: "pooled")
464
480
  rb_embedding_model.define_method("_embedding", method!(EmbeddingModel::embedding, 2))?;
465
481
  rb_embedding_model.define_method("embeddings", method!(EmbeddingModel::embeddings, 1))?;
@@ -15,8 +15,9 @@ module Candle
15
15
  # @param tokenizer [String, nil] The tokenizer to use (defaults to using the model's tokenizer)
16
16
  # @param model_type [String, nil] The type of embedding model (auto-detected if nil)
17
17
  # @param embedding_size [Integer, nil] Override for the embedding size (optional)
18
+ # @param max_length [Integer, nil] Longest input in tokens; longer inputs are truncated (optional)
18
19
  # @return [EmbeddingModel] A new EmbeddingModel instance
19
- def self.from_pretrained(model_id = DEFAULT_MODEL_PATH, device: Candle::Device.best, tokenizer: nil, model_type: nil, embedding_size: nil)
20
+ def self.from_pretrained(model_id = DEFAULT_MODEL_PATH, device: Candle::Device.best, tokenizer: nil, model_type: nil, embedding_size: nil, max_length: nil)
20
21
  # Auto-detect model type based on model_id if not provided
21
22
  if model_type.nil?
22
23
  model_type = case model_id.downcase
@@ -34,7 +35,7 @@ module Candle
34
35
  # Use model_id as tokenizer if not specified (usually what you want)
35
36
  tokenizer_id = tokenizer || model_id
36
37
 
37
- _create(model_id, tokenizer_id, device, model_type, embedding_size)
38
+ _create(model_id, tokenizer_id, device, model_type, embedding_size, max_length)
38
39
  end
39
40
 
40
41
  # Constructor for creating a new EmbeddingModel with optional parameters
@@ -44,13 +45,15 @@ module Candle
44
45
  # @param device [Candle::Device, Candle::Device.cpu] The device to use for computation
45
46
  # @param model_type [String, nil] The type of embedding model to use
46
47
  # @param embedding_size [Integer, nil] Override for the embedding size (optional)
48
+ # @param max_length [Integer, nil] Longest input in tokens; longer inputs are truncated (optional)
47
49
  def self.new(model_path: DEFAULT_MODEL_PATH,
48
50
  tokenizer_path: DEFAULT_TOKENIZER_PATH,
49
51
  device: Candle::Device.best,
50
52
  model_type: DEFAULT_EMBEDDING_MODEL_TYPE,
51
- embedding_size: nil)
53
+ embedding_size: nil,
54
+ max_length: nil)
52
55
  $stderr.puts "[DEPRECATION] `EmbeddingModel.new` is deprecated. Please use `EmbeddingModel.from_pretrained` instead."
53
- _create(model_path, tokenizer_path, device, model_type, embedding_size)
56
+ _create(model_path, tokenizer_path, device, model_type, embedding_size, max_length)
54
57
  end
55
58
  # Returns the embedding for a string using the specified pooling method.
56
59
  # @param str [String] The input text
@@ -1,5 +1,5 @@
1
1
  # :nocov:
2
2
  module Candle
3
- VERSION = "1.8.1"
3
+ VERSION = "1.8.2"
4
4
  end
5
5
  # :nocov:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red-candle
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.1
4
+ version: 1.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christopher Petersen