candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
use super::*;

/// `Alibaba-NLP/gte-multilingual-base`'s published `config.json`, verbatim (1429 bytes).
const MULTILINGUAL: &str = r#"{
  "architectures": [
    "NewModel",
    "NewForTokenClassification"
  ],
  "attention_probs_dropout_prob": 0.0,
  "auto_map": {
    "AutoConfig": "Alibaba-NLP/new-impl--configuration.NewConfig",
    "AutoModelForMaskedLM": "Alibaba-NLP/new-impl--modeling.NewForMaskedLM",
    "AutoModel": "Alibaba-NLP/new-impl--modeling.NewModel",
    "AutoModelForMultipleChoice": "Alibaba-NLP/new-impl--modeling.NewForMultipleChoice",
    "AutoModelForQuestionAnswering": "Alibaba-NLP/new-impl--modeling.NewForQuestionAnswering",
    "AutoModelForSequenceClassification": "Alibaba-NLP/new-impl--modeling.NewForSequenceClassification",
    "AutoModelForTokenClassification": "Alibaba-NLP/new-impl--modeling.NewForTokenClassification"
  },
  "classifier_dropout": 0.0,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "layer_norm_type": "layer_norm",
  "max_position_embeddings": 8192,
  "model_type": "new",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "num_labels": 1,
  "pack_qkv": true,
  "pad_token_id": 1,
  "position_embedding_type": "rope",
  "rope_scaling": {
    "factor": 8.0,
    "type": "ntk"
  },
  "rope_theta": 20000,
  "torch_dtype": "float16",
  "transformers_version": "4.39.1",
  "type_vocab_size": 1,
  "unpad_inputs": false,
  "use_memory_efficient_attention": false,
  "vocab_size": 250048
}"#;

/// `Alibaba-NLP/gte-base-en-v1.5`'s published `config.json`, verbatim (1348 bytes).
const EN_V1_5: &str = r#"{
  "architectures": [
    "NewModel"
  ],
  "attention_probs_dropout_prob": 0.0,
  "auto_map": {
    "AutoConfig": "Alibaba-NLP/new-impl--configuration.NewConfig",
    "AutoModel": "Alibaba-NLP/new-impl--modeling.NewModel",
    "AutoModelForMaskedLM": "Alibaba-NLP/new-impl--modeling.NewForMaskedLM",
    "AutoModelForMultipleChoice": "Alibaba-NLP/new-impl--modeling.NewForMultipleChoice",
    "AutoModelForQuestionAnswering": "Alibaba-NLP/new-impl--modeling.NewForQuestionAnswering",
    "AutoModelForSequenceClassification": "Alibaba-NLP/new-impl--modeling.NewForSequenceClassification",
    "AutoModelForTokenClassification": "Alibaba-NLP/new-impl--modeling.NewForTokenClassification"
  },
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "layer_norm_type": "layer_norm",
  "max_position_embeddings": 8192,
  "model_type": "new",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pack_qkv": true,
  "pad_token_id": 0,
  "position_embedding_type": "rope",
  "rope_scaling": {
    "factor": 2.0,
    "type": "ntk"
  },
  "rope_theta": 500000,
  "torch_dtype": "float32",
  "transformers_version": "4.39.1",
  "type_vocab_size": 0,
  "unpad_inputs": false,
  "use_memory_efficient_attention": false,
  "vocab_size": 30528
}"#;

const EN_V1_5_ROPE_SCALING: &str = r#"  "rope_scaling": {
    "factor": 2.0,
    "type": "ntk"
  },
"#;

fn multilingual() -> GteNewConfig {
  serde_json::from_str(MULTILINGUAL).unwrap()
}

fn en_v1_5() -> GteNewConfig {
  serde_json::from_str(EN_V1_5).unwrap()
}

/// Only `rope_theta`, `rope_scaling.factor`, `type_vocab_size` and `vocab_size` differ between the two.
#[test]
fn reads_both_published_configs() {
  let m = multilingual();
  let e = en_v1_5();
  for config in [&m, &e] {
    assert_eq!(config.hidden_size, 768);
    assert_eq!(config.num_hidden_layers, 12);
    assert_eq!(config.num_attention_heads, 12);
    assert_eq!(config.intermediate_size, 3072);
    assert_eq!(config.hidden_act, "gelu");
    assert_eq!(config.layer_norm_type, "layer_norm");
    assert_eq!(config.layer_norm_eps, 1e-12);
    assert_eq!(config.position_embedding_type, "rope");
    assert_eq!(config.max_position_embeddings, 8192);
    assert!(config.pack_qkv);
    assert!(!config.unpad_inputs);
    assert!(!config.use_memory_efficient_attention);
    assert_eq!(config.head_dim(), 64);
    assert_eq!(config.rope_scaling.as_ref().unwrap().r#type, "ntk");
    config.ensure_supported().unwrap();
  }
  assert_eq!(m.rope_theta, 20_000.0);
  assert_eq!(m.rope_scaling.as_ref().unwrap().factor, 8.0);
  assert_eq!(m.type_vocab_size, 1);
  assert_eq!(m.vocab_size, 250_048);
  assert_eq!(e.rope_theta, 500_000.0);
  assert_eq!(e.rope_scaling.as_ref().unwrap().factor, 2.0);
  assert_eq!(e.type_vocab_size, 0);
  assert_eq!(e.vocab_size, 30_528);
}

/// The reference rescales at construction: `inv_freq[i] = (theta * factor)^(-2i/dim)` times `factor^(-2/dim)`.
#[test]
fn the_rope_arithmetic_matches_the_reference() {
  let m: GteNewConfig = serde_json::from_str(MULTILINGUAL).unwrap();
  assert_eq!(m.rope_theta_effective(), 160_000.0);
  assert!(
    (m.rope_frequency_scale() - 0.937_084).abs() < 1e-6,
    "{}",
    m.rope_frequency_scale()
  );
  let e: GteNewConfig = serde_json::from_str(EN_V1_5).unwrap();
  assert_eq!(e.rope_theta_effective(), 1_000_000.0);
  assert!(
    (e.rope_frequency_scale() - 0.978_572).abs() < 1e-6,
    "{}",
    e.rope_frequency_scale()
  );
}

#[test]
fn an_absent_rope_scaling_leaves_the_table_unscaled() {
  assert!(
    EN_V1_5.contains(EN_V1_5_ROPE_SCALING),
    "{EN_V1_5_ROPE_SCALING}"
  );
  let without_scaling = EN_V1_5.replace(EN_V1_5_ROPE_SCALING, "");
  let config: GteNewConfig = serde_json::from_str(&without_scaling).unwrap();
  assert!(config.rope_scaling.is_none());
  assert_eq!(config.rope_theta_effective(), 500_000.0);
  assert_eq!(config.rope_frequency_scale(), 1.0);
}

/// `type_vocab_size` 0 means no table exists, not a table of size zero; 1 means a real one-row table.
#[test]
fn only_a_positive_type_vocab_size_declares_a_table() {
  assert!(
    serde_json::from_str::<GteNewConfig>(MULTILINGUAL)
      .unwrap()
      .has_token_types()
  );
  assert!(
    !serde_json::from_str::<GteNewConfig>(EN_V1_5)
      .unwrap()
      .has_token_types()
  );
}

#[test]
fn each_refusal_names_the_field_and_the_value() {
  let cases = [
    (
      "\"hidden_act\": \"gelu\"",
      "\"hidden_act\": \"silu\"",
      "hidden_act silu",
    ),
    (
      "\"layer_norm_type\": \"layer_norm\"",
      "\"layer_norm_type\": \"rms_norm\"",
      "layer_norm_type rms_norm",
    ),
    (
      "\"position_embedding_type\": \"rope\"",
      "\"position_embedding_type\": \"absolute\"",
      "position_embedding_type absolute",
    ),
    (
      "\"pack_qkv\": true",
      "\"pack_qkv\": false",
      "pack_qkv false",
    ),
    (
      "\"unpad_inputs\": false",
      "\"unpad_inputs\": true",
      "unpad_inputs true",
    ),
    (
      "\"use_memory_efficient_attention\": false",
      "\"use_memory_efficient_attention\": true",
      "use_memory_efficient_attention true",
    ),
    (
      "\"type\": \"ntk\"",
      "\"type\": \"dynamic\"",
      "rope_scaling.type dynamic",
    ),
    (
      "\"type_vocab_size\": 1",
      "\"type_vocab_size\": 2",
      "type_vocab_size 2",
    ),
  ];
  for (from, to, needle) in cases {
    assert!(MULTILINGUAL.contains(from), "{from}");
    let config: GteNewConfig = serde_json::from_str(&MULTILINGUAL.replace(from, to)).unwrap();
    let err = config.ensure_supported().unwrap_err().to_string();
    assert!(err.contains(needle), "{needle}: {err}");
  }
}