Skip to content

Commit 1d6ceb4

Browse files
feat: support pplx-embed-v1 (#824)
Co-authored-by: Alvaro Bartolome <36760800+alvarobartt@users.noreply.github.com>
1 parent c9902fd commit 1d6ceb4

11 files changed

Lines changed: 4431 additions & 18 deletions

File tree

backends/candle/src/lib.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use crate::models::{
2525
BertConfig, BertModel, DebertaV2Config, DebertaV2Model, Dense, DenseConfig, DenseLayer,
2626
DistilBertConfig, DistilBertModel, GTEConfig, GTEModel, Gemma3Config, Gemma3Model,
2727
JinaBertModel, JinaCodeBertModel, LlamaConfig, MPNetConfig, MPNetModel, MistralConfig, Model,
28-
ModernBertConfig, ModernBertModel, NomicBertModel, NomicConfig, Qwen2Config, Qwen3Config,
29-
Qwen3Model,
28+
ModernBertConfig, ModernBertModel, NomicBertModel, NomicConfig, Pplx1Config, Pplx1Model,
29+
Qwen2Config, Qwen3Config, Qwen3Model,
3030
};
3131
#[cfg(feature = "cuda")]
3232
use crate::models::{
@@ -135,16 +135,17 @@ enum Config {
135135
Gte(GTEConfig),
136136
#[serde(rename = "mpnet")]
137137
MPNet(MPNetConfig),
138-
#[allow(dead_code)]
138+
#[allow(dead_code)] // NOTE: As it's only used when `cuda` feature is enabled
139139
Mistral(MistralConfig),
140140
#[serde(rename(deserialize = "modernbert"))]
141141
ModernBert(ModernBertConfig),
142142
#[serde(rename(deserialize = "nomic_bert"))]
143143
NomicBert(NomicConfig),
144-
#[allow(dead_code)]
144+
#[allow(dead_code)] // NOTE: As it's only used when `cuda` feature is enabled
145145
Qwen2(Qwen2Config),
146-
#[allow(dead_code)]
147146
Qwen3(Qwen3Config),
147+
#[serde(rename(deserialize = "bidirectional_pplx_qwen3"))]
148+
Pplx1(Pplx1Config),
148149
Roberta(BertConfig),
149150
XlmRoberta(BertConfig),
150151
#[allow(dead_code)]
@@ -319,6 +320,7 @@ impl CandleBackend {
319320
))
320321
}
321322
(Config::Gemma3(config), Device::Cpu | Device::Metal(_)) => {
323+
// TODO(alvarobartt): Enable Flash Attention with BF16 once supported on Metal
322324
if dtype != DType::F32 {
323325
Err(BackendError::Start(
324326
"Gemma3 is only supported in fp32 precision".to_string(),
@@ -362,6 +364,17 @@ impl CandleBackend {
362364
tracing::info!("Starting Qwen3 model on {:?}", device);
363365
Ok(Box::new(Qwen3Model::load(vb, &config, model_type).s()?))
364366
}
367+
(Config::Pplx1(config), Device::Cpu | Device::Metal(_)) => {
368+
// TODO(alvarobartt): Enable Flash Attention with BF16 once supported on Metal
369+
if dtype != DType::F32 {
370+
Err(BackendError::Start(
371+
"Pplx1 is only supported in fp32 precision".to_string(),
372+
))
373+
} else {
374+
tracing::info!("Starting Pplx1 model on {:?}", device);
375+
Ok(Box::new(Pplx1Model::load(vb, &config, model_type).s()?))
376+
}
377+
}
365378
#[cfg(feature = "cuda")]
366379
(Config::Bert(config), Device::Cuda(_)) => {
367380
if dtype == DType::F16 && use_flash_attn(&[FlashAttn::V1, FlashAttn::V2]) {
@@ -440,6 +453,7 @@ impl CandleBackend {
440453
}
441454
#[cfg(feature = "cuda")]
442455
(Config::Gemma3(config), Device::Cuda(_)) => {
456+
// TODO(alvarobartt): Enable Flash Attention with BF16 once supported on CUDA
443457
if dtype != DType::F32 {
444458
Err(BackendError::Start(
445459
"Gemma3 is only supported in fp32 precision".to_string(),
@@ -520,6 +534,18 @@ impl CandleBackend {
520534
}
521535
}
522536
#[cfg(feature = "cuda")]
537+
(Config::Pplx1(config), Device::Cuda(_)) => {
538+
// TODO(alvarobartt): Enable Flash Attention with BF16 once supported on CUDA
539+
if dtype != DType::F32 {
540+
Err(BackendError::Start(
541+
"Pplx1 is only supported in fp32 precision".to_string(),
542+
))
543+
} else {
544+
tracing::info!("Starting Pplx1 model on {:?}", device);
545+
Ok(Box::new(Pplx1Model::load(vb, &config, model_type).s()?))
546+
}
547+
}
548+
#[cfg(feature = "cuda")]
523549
(Config::Llama(config), Device::Cuda(_)) => match config.rope_scaling {
524550
Some(_) => Err(BackendError::Start(
525551
"Rope scaling is not supported for FlashLlama yet".to_string(),

backends/candle/src/models/flash_mistral.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ struct MistralAttention {
2424

2525
impl MistralAttention {
2626
pub fn load(vb: VarBuilder, config: &MistralConfig) -> Result<Self> {
27-
let window_size_left = config.sliding_window;
28-
let use_bidirectional_attention = config.use_bidirectional_attention.unwrap_or(false);
2927
let num_attention_heads = config.num_attention_heads;
3028
let attention_head_size = config.hidden_size / config.num_attention_heads;
3129
let num_key_value_heads = config.num_key_value_heads;
@@ -55,8 +53,8 @@ impl MistralAttention {
5553
Ok(Self {
5654
qkv_linear,
5755
o_proj,
58-
window_size_left,
59-
use_bidirectional_attention,
56+
window_size_left: config.sliding_window,
57+
use_bidirectional_attention: config.use_bidirectional_attention.unwrap_or(false),
6058
num_attention_heads,
6159
num_key_value_heads,
6260
attention_head_size,

backends/candle/src/models/flash_qwen3.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ impl Qwen3Attention {
162162
max_s,
163163
max_s,
164164
self.softmax_scale,
165+
// NOTE: When `use_bidirectional_attention=true` then that implies that `causal=false`
165166
!self.use_bidirectional_attention,
166167
None,
167168
None,

backends/candle/src/models/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod mistral;
2020
mod modernbert;
2121
mod mpnet;
2222
mod nomic;
23+
mod pplx1;
2324
mod qwen2;
2425
mod qwen3;
2526

@@ -66,6 +67,7 @@ pub use mistral::MistralConfig;
6667
pub use modernbert::{ModernBertConfig, ModernBertModel};
6768
pub use mpnet::{MPNetConfig, MPNetModel};
6869
pub use nomic::{NomicBertModel, NomicConfig};
70+
pub use pplx1::{Pplx1Config, Pplx1Model};
6971
pub use qwen2::Qwen2Config;
7072
pub use qwen3::{Qwen3Config, Qwen3Model};
7173

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use crate::models::{Model, Qwen3Config, Qwen3Model};
2+
use candle::{Result, Tensor};
3+
use candle_nn::VarBuilder;
4+
use text_embeddings_backend_core::{Batch, ModelType, Pool};
5+
6+
pub type Pplx1Config = Qwen3Config;
7+
8+
pub struct Pplx1Model {
9+
inner: Qwen3Model,
10+
}
11+
12+
impl Pplx1Model {
13+
pub fn load(vb: VarBuilder, config: &Pplx1Config, model_type: ModelType) -> Result<Self> {
14+
match model_type {
15+
ModelType::Classifier => {
16+
candle::bail!("`classifier` model type is not supported for Pplx1")
17+
}
18+
ModelType::Embedding(ref pool) => {
19+
if pool != &Pool::Mean {
20+
candle::bail!("Pplx1 only supports mean pooling, got {:?}", pool);
21+
}
22+
}
23+
};
24+
25+
// NOTE: Qwen3 but the `config` contains `use_bidirectional_attention=true`
26+
let inner = Qwen3Model::load(vb, config, model_type)?;
27+
28+
Ok(Self { inner })
29+
}
30+
31+
pub fn forward(&self, batch: Batch) -> Result<(Option<Tensor>, Option<Tensor>)> {
32+
let (pooled, raw) = self.inner.forward(batch)?;
33+
34+
// NOTE: Apply Pplx1-specific quantization to pooled embeddings
35+
let pooled = pooled
36+
.map(|embeddings| {
37+
embeddings
38+
.tanh() // Apply tanh: [-1, 1]
39+
// NOTE: To benefit form the INT8 quantization / scaling, the `normalize`
40+
// parameter when generating embeddings should be set to `false`, otherwise the
41+
// quantization is "lost"
42+
.and_then(|t| t.affine(127.0, 0.0)) // INT8 scale: [-127, 127]
43+
.and_then(|t| t.round()) // Round to integers
44+
})
45+
.transpose()?;
46+
47+
Ok((pooled, raw))
48+
}
49+
}
50+
51+
impl Model for Pplx1Model {
52+
fn is_padded(&self) -> bool {
53+
self.inner.is_padded()
54+
}
55+
56+
fn embed(&self, batch: Batch) -> Result<(Option<Tensor>, Option<Tensor>)> {
57+
self.forward(batch)
58+
}
59+
}

backends/candle/src/models/qwen3.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ pub struct Qwen3Model {
396396
pool: Pool,
397397
num_attention_heads: usize,
398398
pad_token_id: u32,
399+
399400
use_bidirectional_attention: bool,
400401

401402
dtype: DType,
@@ -452,8 +453,6 @@ impl Qwen3Model {
452453
None
453454
};
454455

455-
let use_bidirectional_attention = config.use_bidirectional_attention.unwrap_or(false);
456-
457456
let rotary_dim = config
458457
.head_dim
459458
.unwrap_or(config.hidden_size / config.num_attention_heads);
@@ -482,7 +481,7 @@ impl Qwen3Model {
482481
pool,
483482
pad_token_id: config.eos_token_id as u32,
484483
num_attention_heads: config.num_attention_heads,
485-
use_bidirectional_attention,
484+
use_bidirectional_attention: config.use_bidirectional_attention.unwrap_or(false),
486485
dtype: vb.dtype(),
487486
device: vb.device().clone(),
488487
span: tracing::span!(tracing::Level::TRACE, "model"),

0 commit comments

Comments
 (0)