add LFM2-1.2B, LFM2.5-1.2B-Instruct

This commit is contained in:
jhqxxx
2026-03-23 22:07:41 +08:00
parent 77b244e53e
commit 9224593b78
26 changed files with 1001 additions and 40 deletions
+3
View File
@@ -247,6 +247,8 @@ struct ErrorResponse {
fn which_model_to_id(which_model: WhichModel) -> &'static str {
match which_model {
WhichModel::MiniCPM4_0_5B => "minicpm4-0.5b",
WhichModel::LFM2_1_2B => "lfm2-1.2b",
WhichModel::LFM2_5_1_2BInstruct => "lfm2.5-1.2b-instruct",
WhichModel::Qwen2_5vl3B => "qwen2.5vl-3b",
WhichModel::Qwen2_5vl7B => "qwen2.5vl-7b",
WhichModel::Qwen3_0_6B => "qwen3-0.6b",
@@ -297,6 +299,7 @@ fn which_model_to_owner(which_model: WhichModel) -> &'static str {
WhichModel::VoxCPM | WhichModel::VoxCPM1_5 => "OpenBMB",
WhichModel::GlmASRNano2512 | WhichModel::GlmOCR => "ZhipuAI",
WhichModel::FunASRNano2512 => "FunAudioLLM",
WhichModel::LFM2_1_2B | WhichModel::LFM2_5_1_2BInstruct => "LiquidAI",
}
}
+61
View File
@@ -0,0 +1,61 @@
//! MiniCPM4-0.5B exec implementation for CLI `run` subcommand
use std::time::Instant;
use anyhow::{Ok, Result};
use crate::exec::ExecModel;
use crate::models::GenerateModel;
use crate::models::lfm2::generate::Lfm2GenerateModel;
use crate::utils::get_file_path;
pub struct Lfm2Exec;
impl ExecModel for Lfm2Exec {
fn run(input: &[String], output: Option<&str>, weight_path: &str) -> Result<()> {
let input_text = &input[0];
let target_text = if input_text.starts_with("file://") {
// let path = &input[7..];
let path = get_file_path(input_text)?;
std::fs::read_to_string(path)?
} else {
input_text.to_string()
};
let i_start = Instant::now();
let mut model = Lfm2GenerateModel::init(weight_path, None, None)?;
let i_duration = i_start.elapsed();
println!("Time elapsed in load model is: {:?}", i_duration);
let message = format!(
r#"{{
"temperature": 0.3,
"top_p": 0.8,
"model": "lfm2",
"messages": [
{{
"role": "user",
"content": "{}"
}}
]
}}"#,
target_text.replace('"', "\\\"")
);
let mes = serde_json::from_str(&message)?;
let i_start = Instant::now();
let result = model.generate(mes)?;
let i_duration = i_start.elapsed();
println!("Time elapsed in generate is: {:?}", i_duration);
// Print result
println!("Result: {:?}", result);
if let Some(out) = output {
std::fs::write(out, format!("{:?}", result))?;
println!("Output saved to: {}", out);
}
Ok(())
}
}
+1
View File
@@ -18,6 +18,7 @@ pub mod qwen3vl;
pub mod rmbg2_0;
pub mod voxcpm;
pub mod voxcpm1_5;
pub mod lfm2;
use anyhow::Result;
+10
View File
@@ -244,6 +244,8 @@ struct ModelInfo {
fn run_list(args: ListArgs) -> anyhow::Result<()> {
let models = [
WhichModel::MiniCPM4_0_5B,
WhichModel::LFM2_1_2B,
WhichModel::LFM2_5_1_2BInstruct,
WhichModel::Qwen2_5vl3B,
WhichModel::Qwen2_5vl7B,
WhichModel::Qwen3_0_6B,
@@ -461,6 +463,14 @@ fn run_run(args: RunArgs) -> anyhow::Result<()> {
use aha::exec::minicpm4::MiniCPM4Exec;
MiniCPM4Exec::run(&input, output.as_deref(), &weight_path)?;
}
WhichModel::LFM2_1_2B => {
use aha::exec::lfm2::Lfm2Exec;
Lfm2Exec::run(&input, output.as_deref(), &weight_path)?;
}
WhichModel::LFM2_5_1_2BInstruct => {
use aha::exec::lfm2::Lfm2Exec;
Lfm2Exec::run(&input, output.as_deref(), &weight_path)?;
}
WhichModel::Qwen2_5vl3B => {
use aha::exec::qwen2_5vl::Qwen2_5vlExec;
Qwen2_5vlExec::run(&input, output.as_deref(), &weight_path)?;
+139
View File
@@ -411,6 +411,145 @@ impl QKVCatAttention {
}
}
pub struct QKNormAttention {
q_proj: Linear,
k_proj: Linear,
v_proj: Linear,
o_proj: Linear,
q_norm: RmsNorm,
k_norm: RmsNorm,
num_attention_heads: usize,
num_key_value_heads: usize,
num_kv_groups: usize,
head_dim: usize,
scaling: f64,
kv_cache: Option<(Tensor, Tensor)>,
}
impl QKNormAttention {
pub fn new(
vb: VarBuilder,
hidden_size: usize,
num_attention_heads: usize,
head_dim: Option<usize>,
num_key_value_heads: Option<usize>,
attention_bias: bool,
rms_norm_eps: f64,
q_proj_pp_name: Option<&str>,
k_proj_pp_name: Option<&str>,
v_proj_pp_name: Option<&str>,
o_proj_pp_name: Option<&str>,
q_norm_pp_name: Option<&str>,
k_norm_pp_name: Option<&str>,
) -> Result<Self> {
let head_dim = head_dim.unwrap_or(hidden_size / num_attention_heads);
let num_key_value_heads = num_key_value_heads.unwrap_or(num_attention_heads);
let num_kv_groups = num_attention_heads / num_key_value_heads;
let scaling = 1f64 / f64::sqrt(head_dim as f64);
let q_proj_pp_name = q_proj_pp_name.unwrap_or("q_proj");
let k_proj_pp_name = k_proj_pp_name.unwrap_or("k_proj");
let v_proj_pp_name = v_proj_pp_name.unwrap_or("v_proj");
let o_proj_pp_name = o_proj_pp_name.unwrap_or("o_proj");
let q_norm_pp_name = q_norm_pp_name.unwrap_or("q_norm");
let k_norm_pp_name = k_norm_pp_name.unwrap_or("k_norm");
let q_proj = linear_b(
hidden_size,
num_attention_heads * head_dim,
attention_bias,
vb.pp(q_proj_pp_name),
)?;
let k_proj = linear_b(
hidden_size,
num_key_value_heads * head_dim,
attention_bias,
vb.pp(k_proj_pp_name),
)?;
let v_proj = linear_b(
hidden_size,
num_key_value_heads * head_dim,
attention_bias,
vb.pp(v_proj_pp_name),
)?;
let o_proj = linear_b(
num_attention_heads * head_dim,
hidden_size,
attention_bias,
vb.pp(o_proj_pp_name),
)?;
let q_norm = rms_norm(head_dim, rms_norm_eps, vb.pp(q_norm_pp_name))?;
let k_norm = rms_norm(head_dim, rms_norm_eps, vb.pp(k_norm_pp_name))?;
Ok(Self {
q_proj,
k_proj,
v_proj,
o_proj,
q_norm,
k_norm,
num_attention_heads,
num_key_value_heads,
num_kv_groups,
head_dim,
scaling,
kv_cache: None,
})
}
pub fn forward(
&mut self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
attention_mask: Option<&Tensor>,
) -> Result<Tensor> {
let (b_sz, q_len, _) = xs.dims3()?;
let query_states = self.q_proj.forward(xs)?.reshape((
b_sz,
q_len,
self.num_attention_heads,
self.head_dim,
))?;
let query_states = self.q_norm.forward(&query_states)?.transpose(1, 2)?;
let key_states = self.k_proj.forward(xs)?.reshape((
b_sz,
q_len,
self.num_key_value_heads,
self.head_dim,
))?;
let key_states = self.k_norm.forward(&key_states)?.transpose(1, 2)?;
let value_states = self.v_proj.forward(xs)?;
let value_states = value_states
.reshape((b_sz, q_len, self.num_key_value_heads, self.head_dim))?
.transpose(1, 2)?;
let (query_states, key_states) =
apply_rotary_pos_emb(&query_states, &key_states, cos, sin, false)?;
let (key_states, value_states) = match &self.kv_cache {
None => (key_states, value_states),
Some((prev_k, prev_v)) => {
let key_states = Tensor::cat(&[prev_k, &key_states], 2)?;
let value_states = Tensor::cat(&[prev_v, &value_states], 2)?;
(key_states, value_states)
}
};
self.kv_cache = Some((key_states.clone(), value_states.clone()));
let attn_output = eager_attention_forward(
&query_states,
&key_states,
&value_states,
Some(self.num_kv_groups),
attention_mask,
self.scaling,
)?;
let attn_output =
attn_output.reshape((b_sz, q_len, self.num_attention_heads * self.head_dim))?;
let attn_output = attn_output.apply(&self.o_proj)?;
Ok(attn_output)
}
pub fn clear_kv_cache(&mut self) {
self.kv_cache = None
}
}
pub struct NaiveAttnTwoLinearMLPBlock {
self_attn: NaiveAttention,
mlp: TwoLinearMLP,
+88
View File
@@ -0,0 +1,88 @@
use serde::{Deserialize, Serialize};
use anyhow::{anyhow, Result};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Lfm2Config {
pub architectures: Vec<String>,
pub block_auto_adjust_ff_dim: bool,
pub block_dim: usize,
pub block_ff_dim: usize,
pub block_ffn_dim_multiplier: f64,
pub block_mlp_init_scale: f64,
pub block_multiple_of: usize,
pub block_norm_eps: f64,
pub block_out_init_scale: f64,
pub block_use_swiglu: bool,
pub block_use_xavier_init: bool,
pub bos_token_id: u32,
#[serde[rename="conv_L_cache"]]
pub conv_l_cache: usize,
pub conv_bias: bool,
pub conv_dim: usize,
pub conv_dim_out: Option<usize>,
pub conv_use_xavier_init: bool,
pub eos_token_id: u32,
pub full_attn_idxs: Option<Vec<usize>>,
pub layer_types: Option<Vec<String>>,
pub hidden_size: usize,
pub initializer_range: f64,
pub intermediate_size: Option<usize>,
pub max_position_embeddings: usize,
pub model_type: String,
pub norm_eps: f64,
pub num_attention_heads: usize,
pub num_heads: usize,
pub num_hidden_layers: usize,
pub num_key_value_heads: usize,
pub pad_token_id: u32,
pub rope_theta: f32,
pub torch_dtype: Option<String>,
pub dtype: Option<String>,
pub use_cache: bool,
pub use_pos_enc: bool,
pub vocab_size: usize,
pub tie_embedding: Option<bool>,
}
impl Lfm2Config {
pub fn full_attn_idx2layer_type(&mut self) {
if self.layer_types.is_none()
&& let Some(idx) = &self.full_attn_idxs
{
let mut layer_types = vec![];
for i in 0..self.num_hidden_layers {
if idx.contains(&i) {
layer_types.push("full_attention".to_string());
} else {
layer_types.push("conv".to_string());
}
}
self.layer_types = Some(layer_types);
}
}
pub fn get_layer_types(&self) -> Result<Vec<String>> {
if let Some(types) = &self.layer_types {
Ok(types.clone())
} else if let Some(idx) = &self.full_attn_idxs {
let mut layer_types = vec![];
for i in 0..self.num_hidden_layers {
if idx.contains(&i) {
layer_types.push("full_attention".to_string());
} else {
layer_types.push("conv".to_string());
}
}
Ok(layer_types)
} else {
Err(anyhow!("layer_types full_attn_idxs cannot be none at the same time"))
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct Lfm2GenerateConfig {
pub bos_token_id: u32,
pub eos_token_id: u32,
pub pad_token_id: u32
}
+163
View File
@@ -0,0 +1,163 @@
use crate::{
chat_template::ChatTemplate,
models::{GenerateModel, lfm2::{
config::{Lfm2Config, Lfm2GenerateConfig},
model::Lfm2Model,
}},
tokenizer::TokenizerModel,
utils::{
build_completion_response, find_type_files, get_device, get_dtype, get_logit_processor,
},
};
use crate::utils::build_completion_chunk_response;
use aha_openai_dive::v1::resources::chat::{ChatCompletionParameters, ChatCompletionResponse};
use anyhow::Result;
use candle_core::{DType, Device, Tensor};
use candle_nn::VarBuilder;
use rocket::async_stream::stream;
pub struct Lfm2GenerateModel<'a> {
chat_template: ChatTemplate<'a>,
tokenizer: TokenizerModel,
device: Device,
model: Lfm2Model,
eos_token_id: u32,
model_name: String,
}
impl<'a> Lfm2GenerateModel<'a> {
pub fn init(path: &str, device: Option<&Device>, dtype: Option<DType>) -> Result<Self> {
let chat_template = ChatTemplate::init(path)?;
let tokenizer = TokenizerModel::init(path)?;
let device = get_device(device);
let gen_cfg_path = path.to_string() + "/generation_config.json";
let gen_cfg: Lfm2GenerateConfig = serde_json::from_slice(&std::fs::read(gen_cfg_path)?)?;
let cfg_path = path.to_string() + "/config.json";
let cfg: Lfm2Config = serde_json::from_slice(&std::fs::read(cfg_path)?)?;
let model_path = find_type_files(path, "safetensors")?;
let cfg_dtype = if let Some(dtype) = &cfg.dtype {
dtype.clone()
} else if let Some(dtype) = &cfg.torch_dtype {
dtype.clone()
} else {
"bfloat16".to_string()
};
let dtype = get_dtype(dtype, &cfg_dtype);
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_path, dtype, &device)? };
let model = Lfm2Model::new(vb, &cfg)?;
let eos_token_id = gen_cfg.eos_token_id;
let model_name = std::path::Path::new(path)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("lfm2")
.to_string();
Ok(Self {
chat_template,
tokenizer,
device,
model,
eos_token_id,
model_name,
})
}
}
impl<'a> GenerateModel for Lfm2GenerateModel<'a> {
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
let mes_render = self.chat_template.apply_chat_template(&mes)?;
let mut logits = get_logit_processor(
mes.temperature,
mes.top_p,
None,
mes.seed.unwrap_or(34562) as u64,
);
let mut input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
let mut seq_len = input_ids.dim(1)?;
let prompt_tokens = seq_len as u32;
let mut seqlen_offset = 0;
let mut generate = vec![];
let sample_len = mes.max_tokens.unwrap_or(1024);
for _ in 0..sample_len {
let logit = self.model.forward(&input_ids, seqlen_offset)?;
let logit = logit.squeeze(0)?.squeeze(0)?;
let next_token = logits.sample(&logit)?;
generate.push(next_token);
if next_token == self.eos_token_id {
break;
}
input_ids = Tensor::new(vec![next_token], &self.device)?.unsqueeze(0)?;
seqlen_offset += seq_len;
seq_len = 1;
}
self.model.clear_cache();
let completion_tokens = generate.len() as u32;
let decode = self.tokenizer.token_decode(generate)?;
let mes = build_completion_response(
decode,
&self.model_name,
Some(completion_tokens),
Some(prompt_tokens),
);
Ok(mes)
}
fn generate_stream(
&mut self,
mes: ChatCompletionParameters,
) -> Result<
Box<
dyn rocket::futures::Stream<Item = Result<aha_openai_dive::v1::resources::chat::ChatCompletionChunkResponse, anyhow::Error>>
+ Send
+ Unpin
+ '_,
>,
> {
let mes_render = self.chat_template.apply_chat_template(&mes)?;
let mut logits = get_logit_processor(
mes.temperature,
mes.top_p,
None,
mes.seed.unwrap_or(34562) as u64,
);
let mut input_ids = self.tokenizer.text_encode(mes_render, &self.device)?;
let mut seq_len = input_ids.dim(1)?;
let mut seqlen_offset = 0;
let sample_len = mes.max_tokens.unwrap_or(1024);
let stream = stream! {
let mut err_tokens = vec![];
for _ in 0..sample_len {
let logit = self.model.forward(&input_ids, seqlen_offset)?;
let logit = logit.squeeze(0)?.squeeze(0)?;
let next_token = logits.sample(&logit)?;
let mut decode_ids = vec![];
if !err_tokens.is_empty() {
decode_ids.extend_from_slice(&err_tokens);
}
decode_ids.push(next_token);
let decode = self.tokenizer.token_decode(decode_ids)?;
if decode.contains("") {
err_tokens.push(next_token);
if err_tokens.len() > 3 {
err_tokens.clear();
}
input_ids = Tensor::new(vec![next_token], &self.device)?.unsqueeze(0)?;
seqlen_offset += seq_len;
seq_len = 1;
continue;
}
err_tokens.clear();
let chunk = build_completion_chunk_response(decode, &self.model_name, None, None);
yield Ok(chunk);
if next_token == self.eos_token_id {
break;
}
input_ids = Tensor::new(vec![next_token], &self.device)?.unsqueeze(0)?;
seqlen_offset += seq_len;
seq_len = 1;
}
self.model.clear_cache();
};
Ok(Box::new(Box::pin(stream)))
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod config;
pub mod model;
pub mod generate;
+313
View File
@@ -0,0 +1,313 @@
use crate::{
models::{
common::{GateUpDownMLP, QKNormAttention, conv1d_depthwise, get_conv1d},
lfm2::config::Lfm2Config,
},
position_embed::rope::RoPE,
utils::tensor_utils::prepare_causal_attention_mask,
};
use anyhow::Result;
use candle_core::{D, Tensor};
use candle_nn::{
Conv1d, Embedding, Linear, Module, RmsNorm, VarBuilder, embedding, linear_b, rms_norm,
};
pub struct Lfm2ShortConv {
l_cache: usize,
conv: Conv1d,
in_proj: Linear,
out_proj: Linear,
cache: Option<Tensor>,
}
impl Lfm2ShortConv {
pub fn new(vb: VarBuilder, config: &Lfm2Config) -> Result<Self> {
let l_cache = config.conv_l_cache;
let bias = config.conv_bias;
let conv = get_conv1d(
vb.pp("conv"),
config.hidden_size,
config.hidden_size,
l_cache,
l_cache - 1,
1,
1,
config.hidden_size,
bias,
)?;
let in_proj = linear_b(
config.hidden_size,
config.hidden_size * 3,
bias,
vb.pp("in_proj"),
)?;
let out_proj = linear_b(
config.hidden_size,
config.hidden_size,
bias,
vb.pp("out_proj"),
)?;
Ok(Self {
l_cache,
conv,
in_proj,
out_proj,
cache: None,
})
}
pub fn forward(&mut self, xs: &Tensor) -> Result<Tensor> {
let seq_len = xs.dim(1)?;
let bc_x = self.in_proj.forward(xs)?.transpose(D::Minus1, D::Minus2)?;
let chunk = bc_x.chunk(3, D::Minus2)?;
let bx = chunk[0].mul(&chunk[2])?;
let c: &Tensor = &chunk[1];
let conv_out = if self.cache.is_none() && seq_len > 1 {
let pad_num = self.l_cache as isize - seq_len as isize;
let conv_state = if pad_num > 0 {
bx.pad_with_zeros(D::Minus1, pad_num as usize, 0)?
} else {
bx.narrow(D::Minus1, pad_num.unsigned_abs(), self.l_cache)?
};
self.cache = Some(conv_state);
let bx = bx.pad_with_zeros(D::Minus1, self.l_cache-1, self.l_cache-1)?;
let bx = conv1d_depthwise(&bx, self.conv.weight(), self.conv.bias())?;
bx.narrow(D::Minus1, 0, seq_len)?
} else {
let conv_state = self.cache.as_ref().unwrap();
let conv_state = Tensor::cat(&[conv_state, &bx], D::Minus1)?;
let conv_state = conv_state.narrow(D::Minus1, 1, self.l_cache)?;
let conv_out = conv1d_depthwise(&conv_state, self.conv.weight(), self.conv.bias())?;
self.cache = Some(conv_state);
conv_out
};
let y = c.mul(&conv_out)?;
let y = y.transpose(D::Minus1, D::Minus2)?.contiguous()?;
let y = self.out_proj.forward(&y)?;
Ok(y)
}
pub fn clear_cache(&mut self) {
self.cache = None;
}
}
enum LayerKind {
SelfAttn(QKNormAttention),
Conv(Lfm2ShortConv),
}
impl LayerKind {
pub fn forward(
&mut self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
attention_mask: Option<&Tensor>,
) -> Result<Tensor> {
match self {
LayerKind::SelfAttn(attn) => attn.forward(xs, cos, sin, attention_mask),
LayerKind::Conv(conv) => conv.forward(xs),
}
}
}
pub struct Lfm2DecoderLayer {
layer: LayerKind,
feed_forward: GateUpDownMLP,
operator_norm: RmsNorm,
ffn_norm: RmsNorm,
}
impl Lfm2DecoderLayer {
pub fn new(vb: VarBuilder, config: &Lfm2Config, layer_type: &str) -> Result<Self> {
let layer = if layer_type.eq("full_attention") {
let attn = QKNormAttention::new(
vb.pp("self_attn"),
config.hidden_size,
config.num_attention_heads,
None,
Some(config.num_key_value_heads),
false,
config.block_norm_eps,
Some("q_proj"),
Some("k_proj"),
Some("v_proj"),
Some("out_proj"),
Some("q_layernorm"),
Some("k_layernorm"),
)?;
LayerKind::SelfAttn(attn)
} else {
let conv = Lfm2ShortConv::new(vb.pp("conv"), config)?;
LayerKind::Conv(conv)
};
let intermediate_size = if config.block_auto_adjust_ff_dim {
let inter_size = 2 * config.block_ff_dim / 3;
let inter_size = (config.block_ffn_dim_multiplier * inter_size as f64) as usize;
let inter_size = config.block_multiple_of
* ((inter_size + config.block_multiple_of - 1) / config.block_multiple_of);
inter_size
} else {
config.block_ff_dim
};
let feed_forward = GateUpDownMLP::new(
vb.pp("feed_forward"),
config.hidden_size,
intermediate_size,
candle_nn::Activation::Silu,
false,
Some("w1"),
Some("w3"),
Some("w2"),
)?;
let operator_norm = rms_norm(config.hidden_size, config.norm_eps, vb.pp("operator_norm"))?;
let ffn_norm = rms_norm(config.hidden_size, config.norm_eps, vb.pp("ffn_norm"))?;
Ok(Self {
layer,
feed_forward,
operator_norm,
ffn_norm,
})
}
pub fn forward(
&mut self,
xs: &Tensor,
cos: &Tensor,
sin: &Tensor,
attention_mask: Option<&Tensor>,
) -> Result<Tensor> {
let res = xs.clone();
let xs = self.operator_norm.forward(xs)?;
let xs = self.layer.forward(&xs, cos, sin, attention_mask)?;
let res = xs.add(&res)?;
let xs = self.ffn_norm.forward(&res)?;
let xs = self.feed_forward.forward(&xs)?;
let xs = xs.add(&res)?;
Ok(xs)
}
pub fn clear_cache(&mut self) {
match &mut self.layer {
LayerKind::SelfAttn(attn) => attn.clear_kv_cache(),
LayerKind::Conv(conv) => conv.clear_cache(),
}
}
}
pub struct Lfm2Decoder {
embed_tokens: Embedding,
layers: Vec<Lfm2DecoderLayer>,
// rotary_emb: RoPE,
pos_emb: RoPE,
embedding_norm: RmsNorm,
}
impl Lfm2Decoder {
pub fn new(vb: VarBuilder, config: &Lfm2Config) -> Result<Self> {
let embed_tokens = embedding(config.vocab_size, config.hidden_size, vb.pp("embed_tokens"))?;
let mut layers = vec![];
let vb_layers = vb.pp("layers");
// let layer_types = config.layer_types.as_ref().unwrap();
let layer_types = config.get_layer_types()?;
for i in 0..config.num_hidden_layers {
let layer_type = layer_types.get(i).unwrap();
let layer = Lfm2DecoderLayer::new(vb_layers.pp(i), config, layer_type)?;
layers.push(layer);
}
let dim = config.hidden_size / config.num_attention_heads;
let pos_emb = RoPE::new(dim, config.rope_theta, vb.device())?;
let embedding_norm =
rms_norm(config.hidden_size, config.norm_eps, vb.pp("embedding_norm"))?;
Ok(Self {
embed_tokens,
layers,
pos_emb,
embedding_norm,
})
}
pub fn forward(
&mut self,
input_ids: &Tensor,
inputs_embeds: Option<&Tensor>,
seqlen_offset: usize,
) -> Result<Tensor> {
let inputs_embeds = if let Some(embed) = inputs_embeds {
embed.clone()
} else {
self.embed_tokens.forward(input_ids)?
};
let (bs, seq_len, _) = inputs_embeds.dims3()?;
let attention_mask = if seq_len > 1 {
Some(prepare_causal_attention_mask(
bs,
seq_len,
seqlen_offset,
inputs_embeds.device(),
)?)
} else {
None
};
let (cos, sin) = self
.pos_emb
.forward(seqlen_offset, seq_len, inputs_embeds.device())?;
let mut xs = inputs_embeds;
for layer in &mut self.layers {
xs = layer.forward(&xs, &cos, &sin, attention_mask.as_ref())?;
}
let xs = self.embedding_norm.forward(&xs)?;
Ok(xs)
}
pub fn clear_cache(&mut self) {
for layer in &mut self.layers {
layer.clear_cache()
}
}
}
pub struct Lfm2Model {
model: Lfm2Decoder,
lm_head: Linear,
}
impl Lfm2Model {
pub fn new(vb: VarBuilder, config: &Lfm2Config) -> Result<Self> {
let model = Lfm2Decoder::new(vb.pp("model"), config)?;
let lm_head = if let Some(flag) = config.tie_embedding
&& flag
{
Linear::new(model.embed_tokens.embeddings().clone(), None)
} else {
let linear = linear_b(
config.hidden_size,
config.vocab_size,
false,
vb.pp("lm_head"),
);
match linear {
Ok(linear) => linear,
Err(_) => {
Linear::new(model.embed_tokens.embeddings().clone(), None)
}
}
};
Ok(Self { model, lm_head })
}
pub fn forward(&mut self, input_ids: &Tensor, seqlen_offset: usize) -> Result<Tensor> {
let xs = self.model.forward(input_ids, None, seqlen_offset)?;
let seq_len = xs.dim(1)?;
let last_xs = xs.narrow(1, seq_len - 1, 1)?;
let xs = self.lm_head.forward(&last_xs)?;
Ok(xs)
}
pub fn clear_cache(&mut self) {
self.model.clear_cache();
}
}
+22 -9
View File
@@ -19,6 +19,7 @@ pub mod qwen3vl;
pub mod rmbg2_0;
pub mod voxcpm;
pub mod w2v_bert_2_0;
pub mod lfm2;
use aha_openai_dive::v1::resources::chat::{
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
@@ -27,18 +28,15 @@ use anyhow::{Result, anyhow};
use rocket::futures::Stream;
use crate::models::{
deepseek_ocr::generate::DeepseekOCRGenerateModel,
fun_asr_nano::generate::FunAsrNanoGenerateModel,
glm_asr_nano::generate::GlmAsrNanoGenerateModel, glm_ocr::generate::GlmOcrGenerateModel,
hunyuan_ocr::generate::HunyuanOCRGenerateModel, minicpm4::generate::MiniCPMGenerateModel,
paddleocr_vl::generate::PaddleOCRVLGenerateModel, qwen2_5vl::generate::Qwen2_5VLGenerateModel,
qwen3::generate::Qwen3GenerateModel, qwen3_5::generate::Qwen3_5GenerateModel,
qwen3_asr::generate::Qwen3AsrGenerateModel, qwen3vl::generate::Qwen3VLGenerateModel,
rmbg2_0::generate::RMBG2_0Model, voxcpm::generate::VoxCPMGenerate,
deepseek_ocr::generate::DeepseekOCRGenerateModel, fun_asr_nano::generate::FunAsrNanoGenerateModel, glm_asr_nano::generate::GlmAsrNanoGenerateModel, glm_ocr::generate::GlmOcrGenerateModel, hunyuan_ocr::generate::HunyuanOCRGenerateModel, lfm2::generate::Lfm2GenerateModel, minicpm4::generate::MiniCPMGenerateModel, paddleocr_vl::generate::PaddleOCRVLGenerateModel, qwen2_5vl::generate::Qwen2_5VLGenerateModel, qwen3::generate::Qwen3GenerateModel, qwen3_5::generate::Qwen3_5GenerateModel, qwen3_asr::generate::Qwen3AsrGenerateModel, qwen3vl::generate::Qwen3VLGenerateModel, rmbg2_0::generate::RMBG2_0Model, voxcpm::generate::VoxCPMGenerate
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum WhichModel {
#[value(name = "lfm2-1.2b", hide = true)]
LFM2_1_2B,
#[value(name = "lfm2.5-1.2b-instruct", hide = true)]
LFM2_5_1_2BInstruct,
#[value(name = "minicpm4-0.5b", hide = true)]
MiniCPM4_0_5B,
#[value(name = "qwen2.5vl-3b", hide = true)]
@@ -97,6 +95,8 @@ impl WhichModel {
/// Get the ModelScope model ID for this model variant
pub fn model_id(self) -> &'static str {
match self {
WhichModel::LFM2_1_2B => "LiquidAI/LFM2-1.2B",
WhichModel::LFM2_5_1_2BInstruct => "LiquidAI/LFM2.5-1.2B-Instruct",
WhichModel::MiniCPM4_0_5B => "OpenBMB/MiniCPM4-0.5B",
WhichModel::Qwen2_5vl3B => "Qwen/Qwen2.5-VL-3B-Instruct",
WhichModel::Qwen2_5vl7B => "Qwen/Qwen2.5-VL-7B-Instruct",
@@ -130,7 +130,9 @@ impl WhichModel {
pub fn model_type(self) -> &'static str {
match self {
// LLM models
WhichModel::MiniCPM4_0_5B | WhichModel::Qwen3_0_6B => "llm",
WhichModel::MiniCPM4_0_5B | WhichModel::Qwen3_0_6B
| WhichModel::LFM2_1_2B
| WhichModel::LFM2_5_1_2BInstruct => "llm",
WhichModel::Qwen2_5vl3B
| WhichModel::Qwen2_5vl7B
| WhichModel::Qwen3vl2B
@@ -177,6 +179,7 @@ pub trait GenerateModel {
pub enum ModelInstance<'a> {
MiniCPM4(MiniCPMGenerateModel<'a>),
Lfm2(Lfm2GenerateModel<'a>),
Qwen2_5VL(Qwen2_5VLGenerateModel<'a>),
Qwen3(Qwen3GenerateModel<'a>),
Qwen3_5(Qwen3_5GenerateModel<'a>),
@@ -196,6 +199,7 @@ impl<'a> GenerateModel for ModelInstance<'a> {
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
match self {
ModelInstance::MiniCPM4(model) => model.generate(mes),
ModelInstance::Lfm2(model) => model.generate(mes),
ModelInstance::Qwen2_5VL(model) => model.generate(mes),
ModelInstance::Qwen3(model) => model.generate(mes),
ModelInstance::Qwen3_5(model) => model.generate(mes),
@@ -225,6 +229,7 @@ impl<'a> GenerateModel for ModelInstance<'a> {
> {
match self {
ModelInstance::MiniCPM4(model) => model.generate_stream(mes),
ModelInstance::Lfm2(model) => model.generate_stream(mes),
ModelInstance::Qwen2_5VL(model) => model.generate_stream(mes),
ModelInstance::Qwen3(model) => model.generate_stream(mes),
ModelInstance::Qwen3_5(model) => model.generate_stream(mes),
@@ -253,6 +258,14 @@ pub fn load_model<'a>(
let model = MiniCPMGenerateModel::init(path, None, None)?;
ModelInstance::MiniCPM4(model)
}
WhichModel::LFM2_1_2B => {
let model = Lfm2GenerateModel::init(path, None, None)?;
ModelInstance::Lfm2(model)
}
WhichModel::LFM2_5_1_2BInstruct => {
let model = Lfm2GenerateModel::init(path, None, None)?;
ModelInstance::Lfm2(model)
}
WhichModel::Qwen2_5vl3B => {
let model = Qwen2_5VLGenerateModel::init(path, None, None)?;
ModelInstance::Qwen2_5VL(model)
+19 -3
View File
@@ -6,7 +6,7 @@ use candle_nn::{
use crate::{
models::{
common::{GateUpDownMLP, eager_attention_forward},
common::{GateUpDownMLP, QKNormAttention, eager_attention_forward},
qwen3::config::Qwen3Config,
},
position_embed::rope::{RoPE, apply_rotary_pos_emb},
@@ -135,7 +135,8 @@ impl Qwen3Attention {
}
pub struct Qwen3DecoderLayer {
self_attn: Qwen3Attention,
// self_attn: Qwen3Attention,
self_attn: QKNormAttention,
mlp: GateUpDownMLP,
input_layernorm: RmsNorm,
post_attention_layernorm: RmsNorm,
@@ -143,7 +144,22 @@ pub struct Qwen3DecoderLayer {
impl Qwen3DecoderLayer {
pub fn new(config: &Qwen3Config, vb: VarBuilder) -> Result<Self> {
let self_attn = Qwen3Attention::new(config, vb.pp("self_attn"))?;
// let self_attn = Qwen3Attention::new(config, vb.pp("self_attn"))?;
let self_attn = QKNormAttention::new(
vb.pp("self_attn"),
config.hidden_size,
config.num_attention_heads,
Some(config.head_dim),
Some(config.num_key_value_heads),
config.attention_bias,
config.rms_norm_eps,
None,
None,
None,
None,
None,
None,
)?;
let mlp = GateUpDownMLP::new(
vb.pp("mlp"),
config.hidden_size,
+3 -4
View File
@@ -36,10 +36,9 @@ pub struct Qwen3_5GenerateModel<'a> {
impl<'a> Qwen3_5GenerateModel<'a> {
pub fn init(path: &str, device: Option<&Device>, dtype: Option<DType>) -> Result<Self> {
let model_name = path
.split("/")
.collect::<Vec<&str>>()
.pop()
let model_name = std::path::Path::new(path)
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("qwen3.5");
let chat_template = ChatTemplate::init(path)?;
let tokenizer = TokenizerModel::init(path)?;
+8
View File
@@ -110,6 +110,14 @@ impl TokenizerModel {
.map_err(|e| anyhow!(format!("tokenizer encode error{}", e)))?;
Ok(decode)
}
pub fn token_decode_with_special(&self, tokens: Vec<u32>) -> Result<String> {
let decode = self
.tokenizer
.decode(&tokens, false)
.map_err(|e| anyhow!(format!("tokenizer encode error{}", e)))?;
Ok(decode)
}
}
pub fn sentencepiece_encode(