use crate::candle::fast_index::gather_rows;
use crate::candle::lora::LoraFactors;
use crate::candle::nn::layers::sparsemax;
use candle_core::{Result, Tensor};
use candle_nn::{VarBuilder, VarMap};
const INIT_LOGIT_JITTER: f64 = 0.01;
pub const FREE_VAR_NAME: &str = "feature.embeddings";
pub const LOGITS_VAR_NAME: &str = "modules.logits";
pub const MU_VAR_NAME: &str = "modules.mu";
pub const LORA_PREFIX: &str = "feature";
pub enum FeatureEmbedding {
Free(Tensor),
Composed { logits: Tensor, mu: Tensor },
Lora { base: Tensor, lora: LoraFactors },
}
impl FeatureEmbedding {
pub fn new(
n_features: usize,
n_modules: usize,
embedding_dim: usize,
vs: VarBuilder,
) -> Result<Self> {
if n_modules == 0 {
return Ok(Self::Free(vs.get_with_hints(
(n_features, embedding_dim),
FREE_VAR_NAME,
candle_nn::init::DEFAULT_KAIMING_NORMAL,
)?));
}
Ok(Self::Composed {
logits: vs.get_with_hints(
(n_features, n_modules),
LOGITS_VAR_NAME,
candle_nn::Init::Randn {
mean: 0.0,
stdev: INIT_LOGIT_JITTER,
},
)?,
mu: vs.get_with_hints(
(n_modules, embedding_dim),
MU_VAR_NAME,
candle_nn::init::DEFAULT_KAIMING_NORMAL,
)?,
})
}
pub fn new_lora(
n_features: usize,
embedding_dim: usize,
rank: usize,
vs: VarBuilder,
) -> Result<Self> {
Ok(Self::Lora {
base: vs.get_with_hints(
(n_features, embedding_dim),
FREE_VAR_NAME,
candle_nn::init::DEFAULT_KAIMING_NORMAL,
)?,
lora: LoraFactors::new(n_features, embedding_dim, rank, vs.pp(LORA_PREFIX))?,
})
}
pub fn membership(&self) -> Result<Option<Tensor>> {
match self {
Self::Free(_) | Self::Lora { .. } => Ok(None),
Self::Composed { logits, .. } => Ok(Some(sparsemax(logits)?)),
}
}
pub fn map_rows_linear(&self, f: impl FnOnce(&Tensor) -> Result<Tensor>) -> Result<Tensor> {
match self {
Self::Free(rho) => f(rho),
Self::Composed { logits, mu } => f(&sparsemax(logits)?)?.matmul(mu),
Self::Lora { base, lora } => f(&(base + lora.residual()?)?),
}
}
pub fn project_dims(&self, v_hc: &Tensor) -> Result<Tensor> {
match self {
Self::Free(rho) => rho.matmul(v_hc),
Self::Composed { logits, mu } => sparsemax(logits)?.matmul(&mu.matmul(v_hc)?),
Self::Lora { base, lora } => base.matmul(v_hc)? + lora.project_dims(v_hc)?,
}
}
pub fn gather(&self, ids: &Tensor) -> Result<Tensor> {
match self {
Self::Free(rho) => gather_rows(rho, ids),
Self::Composed { logits, mu } => sparsemax(&gather_rows(logits, ids)?)?.matmul(mu),
Self::Lora { base, lora } => gather_rows(base, ids)? + lora.residual_rows(ids)?,
}
}
pub fn gather_membership(&self, ids: &Tensor) -> Result<Option<Tensor>> {
match self {
Self::Free(_) | Self::Lora { .. } => Ok(None),
Self::Composed { logits, .. } => Ok(Some(sparsemax(&gather_rows(logits, ids)?)?)),
}
}
pub fn full(&self) -> Result<Tensor> {
self.map_rows_linear(|rows| Ok(rows.clone()))
}
#[must_use]
pub fn n_features(&self) -> usize {
match self {
Self::Free(rho) | Self::Lora { base: rho, .. } => rho.dims()[0],
Self::Composed { logits, .. } => logits.dims()[0],
}
}
#[must_use]
pub fn embedding_dim(&self) -> usize {
match self {
Self::Free(rho) | Self::Lora { base: rho, .. } => rho.dims()[1],
Self::Composed { mu, .. } => mu.dims()[1],
}
}
#[must_use]
pub fn fixed(rho: Tensor) -> std::sync::Arc<Self> {
assert_eq!(
rho.rank(),
2,
"a feature table must be 2-D [D, H], got {:?}",
rho.dims()
);
std::sync::Arc::new(Self::Free(rho))
}
pub fn lora_ridge(&self) -> Result<Option<Tensor>> {
match self {
Self::Lora { lora, .. } => Ok(Some(lora.ridge()?)),
_ => Ok(None),
}
}
#[must_use]
pub fn ridge_table(&self) -> &Tensor {
match self {
Self::Free(rho) => rho,
Self::Composed { mu, .. } => mu,
Self::Lora { lora, .. } => &lora.v,
}
}
#[must_use]
pub fn device(&self) -> &candle_core::Device {
match self {
Self::Free(rho) | Self::Lora { base: rho, .. } => rho.device(),
Self::Composed { logits, .. } => logits.device(),
}
}
pub fn n_modules(&self) -> usize {
match self {
Self::Free(_) | Self::Lora { .. } => 0,
Self::Composed { logits, .. } => logits.dims()[1],
}
}
#[must_use]
pub fn dictionary(&self) -> Option<&Tensor> {
match self {
Self::Free(_) | Self::Lora { .. } => None,
Self::Composed { mu, .. } => Some(mu),
}
}
}
pub fn fold_lora(varmap: &VarMap, prefix: &str) -> Result<()> {
use crate::candle::lora::join;
crate::candle::lora::fold(
varmap,
&join(prefix, FREE_VAR_NAME),
&join(prefix, LORA_PREFIX),
)
}
#[cfg(test)]
#[path = "feature_embedding_tests.rs"]
mod feature_embedding_tests;