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
Generated
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies = [
[[package]]
name = "aha"
version = "0.2.3"
version = "0.2.4"
dependencies = [
"aha_openai_dive",
"ahash",
+2 -2
View File
@@ -1,10 +1,10 @@
[package]
name = "aha"
version = "0.2.3"
version = "0.2.4"
edition = "2024"
repository = "https://github.com/jhqxxx/aha"
license = "Apache-2.0"
description = "aha model inference library, now supports Qwen(2.5VL/3/3VL/3.5/ASR), MiniCPM4, VoxCPM/1.5, DeepSeek-OCR/2, Hunyuan-OCR, PaddleOCR-VL/1.5, RMBG2.0, GLM(ASR-Nano-2512/OCR), Fun-ASR-Nano-2512"
description = "aha model inference library, now supports Qwen(2.5VL/3/3VL/3.5/ASR), MiniCPM4, VoxCPM/1.5, DeepSeek-OCR/2, Hunyuan-OCR, PaddleOCR-VL/1.5, RMBG2.0, GLM(ASR-Nano-2512/OCR), Fun-ASR-Nano-2512, LFM(2/2.5)"
[dependencies]
candle-core = { version = "0.9.2" }
+7 -6
View File
@@ -25,6 +25,10 @@
aha is a high-performance, cross-platform AI inference engine built with Rust and the Candle framework. It brings state-of-the-art AI models to your local machine—no API keys, no cloud dependencies, just pure, fast AI running directly on your hardware.
## Changelog
### v0.2.4 (2026-03-23)
- add LFM2.5-1.2B-Instruct
- add LFM2-1.2B
### v0.2.3 (2026-03-18)
- add DeepSeek-OCR-2
@@ -49,9 +53,6 @@ aha is a high-performance, cross-platform AI inference engine built with Rust an
### v0.2.1 (2026-03-05)
- Added Qwen3.5 model
### 2026-03-01
- update interpolate.rs
**[View full changelog](docs/changelog.md)** →
@@ -125,9 +126,9 @@ curl http://localhost:10100/chat/completions \
| Category | Models |
|----------|--------|
| **Text** | Qwen3, MiniCPM4 |
| **Vision** | Qwen2.5-VL, Qwen3-VL |
| **OCR** | DeepSeek-OCR, Hunyuan-OCR, PaddleOCR-VL |
| **Text** | Qwen3, MiniCPM4, <br> LFM2-1.2B, LFM2.5-1.2B-Instruct |
| **Vision** | Qwen2.5-VL, Qwen3-VL, Qwen3.5 |
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2 , <br> Hunyuan-OCR, PaddleOCR-VL, PaddleOCR-VL1.5 |
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano, Qwen3-ASR |
| **Audio** | VoxCPM, VoxCPM1.5 |
| **Image** | RMBG-2.0 (background removal) |
+6 -2
View File
@@ -25,6 +25,10 @@
aha 是一款基于 Rust 和 Candle 框架构建的高性能跨平台 AI 推理引擎。将最先进的 AI 模型带到您的本地机器——无需 API 密钥,无需云依赖,纯粹、快速的 AI,直接在您的硬件上运行。
## 更新日志
### v0.2.4 (2026-03-23)
- 新增 LFM2.5-1.2B-Instruct
- 新增 LFM2-1.2B
### v0.2.3 (2026-03-18)
- 新增 DeepSeek-OCR-2
@@ -124,9 +128,9 @@ curl http://localhost:10100/chat/completions \
| 类别 | 模型 |
|------|------|
| **文本** | Qwen3, MiniCPM4 |
| **文本** | Qwen3, MiniCPM4, <br> LFM2-1.2B, LFM2.5-1.2B-Instruct |
| **视觉** | Qwen2.5-VL, Qwen3-VL |
| **OCR** | DeepSeek-OCR, Hunyuan-OCR, PaddleOCR-VL |
| **OCR** | DeepSeek-OCR, DeepSeek-OCR-2 , <br> Hunyuan-OCR, PaddleOCR-VL, PaddleOCR-VL1.5 |
| **ASR** | GLM-ASR-Nano, Fun-ASR-Nano,Qwen3-ASR |
| **音频** | VoxCPM, VoxCPM1.5 |
| **图像** | RMBG-2.0 (背景移除) |
+4
View File
@@ -5,6 +5,10 @@ All notable changes to aha will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### v0.2.4 (2026-03-23)
- add LFM2.5-1.2B-Instruct
- add LFM2-1.2B
### v0.2.3 (2026-03-18)
- add DeepSeek-OCR-2
+4
View File
@@ -5,6 +5,10 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/spec/v2.0.0.html)。
### v0.2.4 (2026-03-23)
- 新增 LFM2.5-1.2B-Instruct
- 新增 LFM2-1.2B
### v0.2.3 (2026-03-18)
- 新增 DeepSeek-OCR-2
+2
View File
@@ -8,6 +8,8 @@ aha supports a growing collection of state-of-the-art AI models across multiple
|-------|-----------|-------------|----------|---------|
| **Qwen3-0.6B** | 0.6B | Latest generation | Advanced reasoning | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
| **MiniCPM4-0.5B** | 0.5B | Efficient lightweight | Edge deployment | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
| **LFM2-1.2B** | 1.2B | Efficient lightweight | Edge deployment | [lfm1.0](https://huggingface.co/LiquidAI/LFM2-1.2B/blob/main/LICENSE) |
| **LFM2.5-1.2B-Instruct** | 1.2B | Efficient lightweight | Edge deployment | [lfm1.0](https://huggingface.co/LiquidAI/LFM2.5-1.2B-Instruct/blob/main/LICENSE) |
## Vision & Multimodal
+2
View File
@@ -8,6 +8,8 @@ aha 支持多个领域的最先进 AI 模型集合。
|------|--------|------|----------|---------|
| **Qwen3-0.6B** | 0.6B | 最新一代 | 高级推理 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
| **MiniCPM4-0.5B** | 0.5B | 高效轻量级 | 边缘部署 | [Apache 2.0](https://huggingface.co/datasets/choosealicense/licenses/blob/main/markdown/apache-2.0.md) |
| **LFM2-1.2B** | 1.2B | 高效轻量级 | 边缘部署 | [lfm1.0](https://huggingface.co/LiquidAI/LFM2-1.2B/blob/main/LICENSE) |
| **LFM2.5-1.2B-Instruct** | 1.2B | 高效轻量级 | 边缘部署 | [lfm1.0](https://huggingface.co/LiquidAI/LFM2.5-1.2B-Instruct/blob/main/LICENSE) |
## 视觉与多模态
+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(
+14 -4
View File
@@ -1,8 +1,5 @@
use aha::models::{
deepseek_ocr::config::DeepseekOCRConfig, hunyuan_ocr::config::HunYuanVLConfig,
minicpm4::config::MiniCPM4Config, paddleocr_vl::config::PaddleOCRVLConfig,
qwen2_5vl::config::Qwen2_5VLConfig, qwen3vl::config::Qwen3VLConfig,
voxcpm::config::VoxCPMConfig,
deepseek_ocr::config::DeepseekOCRConfig, hunyuan_ocr::config::HunYuanVLConfig, lfm2::config::Lfm2Config, minicpm4::config::MiniCPM4Config, paddleocr_vl::config::PaddleOCRVLConfig, qwen2_5vl::config::Qwen2_5VLConfig, qwen3vl::config::Qwen3VLConfig, voxcpm::config::VoxCPMConfig
};
use anyhow::Result;
@@ -85,3 +82,16 @@ fn paddleocr_vl_config() -> Result<()> {
println!("{:?}", config);
Ok(())
}
#[test]
fn lfm2_config() -> Result<()> {
// cargo test -F cuda --test config_tests lfm2_config -r -- --nocapture
let model_path = "/home/jhq/.aha/LiquidAI/LFM2-1.2B/";
// let model_path = "/home/jhq/.aha/LiquidAI/LFM2.5-1.2B-Instruct/";
let config_path = model_path.to_string() + "/config.json";
let mut config: Lfm2Config = serde_json::from_slice(&std::fs::read(config_path)?)?;
println!("{:?}", config);
config.full_attn_idx2layer_type();
println!("{:?}", config);
Ok(())
}
+12 -8
View File
@@ -35,14 +35,18 @@ async fn download_test() -> Result<()> {
#[test]
fn messy_test() -> Result<()> {
// RUST_BACKTRACE=1 cargo test -F cuda --test messy_test messy_test -r -- --nocapture
let save_dir =
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
let model_path = format!("{}/deepseek-ai/DeepSeek-OCR-2/", save_dir);
let stem = std::path::Path::new(&model_path)
.file_stem() // 获取文件名主干(不含扩展名)
.and_then(|s| s.to_str())
.unwrap_or("qwen3.5");
println!("stem: {:?}", stem);
// let t1 = Tensor::randn(0.0, 1.0, (1, 2, 6), device)?;
// println!(" t1: {}", t1);
// let t2 = t1.pad_with_zeros(D::Minus1, -3, 0)?;
// println!(" t2: {}", t);
// let save_dir =
// aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
// let model_path = format!("{}/deepseek-ai/DeepSeek-OCR-2/", save_dir);
// let stem = std::path::Path::new(&model_path)
// .file_name()
// .and_then(|s| s.to_str())
// .unwrap_or("qwen3.5");
// println!("stem: {:?}", stem);
// let device = &candle_core::Device::Cpu;
// let t1 = Tensor::randn(0.0, 1.0, (16, 9, 64, 128), device)?;
// let t2 = Tensor::randn(0.0, 1.0, (16, 9, 128, 64), device)?;
+91
View File
@@ -0,0 +1,91 @@
use std::{pin::pin, time::Instant};
use anyhow::Result;
use aha::{chat::ChatCompletionParameters, models::{GenerateModel, lfm2::generate::Lfm2GenerateModel}};
use rocket::futures::StreamExt;
#[test]
fn lfm2_generate() -> Result<()> {
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_lfm2 lfm2_generate -r -- --nocapture
// test with cuda+flash-attn: RUST_BACKTRACE=1 cargo test -F cuda,flash-attn qwen3_0_6b_generate -r -- --nocapture
let save_dir =
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
// let model_path = format!("{}/LiquidAI/LFM2-1.2B/", save_dir);
let model_path = format!("{}/LiquidAI/LFM2.5-1.2B-Instruct/", save_dir);
let message = r#"
{
"model": "lfm2",
"messages": [
{
"role": "user",
"content": "你如何看待AI"
}
]
}
"#;
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
let i_start = Instant::now();
let mut model = Lfm2GenerateModel::init(&model_path, None, None)?;
let i_duration = i_start.elapsed();
println!("Time elapsed in load model is: {:?}", i_duration);
let i_start = Instant::now();
let result = model.generate(mes)?;
let i_duration = i_start.elapsed();
println!("generate: \n {:?}", result);
if let Some(usage) = &result.usage {
let num_token = usage.total_tokens;
let duration_secs = i_duration.as_secs_f64();
let tps = num_token as f64 / duration_secs;
println!("Tokens per second (TPS): {:.2}", tps);
}
println!("Time elapsed in generate is: {:?}", i_duration);
Ok(())
}
#[tokio::test]
async fn lfm2_stream() -> Result<()> {
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_lfm2 lfm2_stream -r -- --nocapture
// test with cuda+flash-attn: RUST_BACKTRACE=1 cargo test -F cuda,flash-attn qwen3_0_6b_generate -r -- --nocapture
let save_dir =
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
// let model_path = format!("{}/LiquidAI/LFM2-1.2B/", save_dir);
let model_path = format!("{}/LiquidAI/LFM2.5-1.2B-Instruct/", save_dir);
let message = r#"
{
"model": "lfm2",
"messages": [
{
"role": "user",
"content": "你如何看待AI"
}
]
}
"#;
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
let i_start = Instant::now();
let mut model = Lfm2GenerateModel::init(&model_path, None, None)?;
let i_duration = i_start.elapsed();
println!("Time elapsed in load model is: {:?}", i_duration);
let i_start = Instant::now();
// let result = model.generate(mes)?;
let mut stream = pin!(model.generate_stream(mes)?);
let i_duration = i_start.elapsed();
while let Some(token) = stream.next().await {
println!("generate: \n {:?}", token);
}
// println!("generate: \n {:?}", result);
// if let Some(usage) = &result.usage {
// let num_token = usage.total_tokens;
// let duration_secs = i_duration.as_secs_f64();
// let tps = num_token as f64 / duration_secs;
// println!("Tokens per second (TPS): {:.2}", tps);
// }
println!("Time elapsed in generate is: {:?}", i_duration);
Ok(())
}
+1 -1
View File
@@ -7,7 +7,7 @@ use rocket::futures::StreamExt;
#[test]
fn qwen3_0_6b_generate() -> Result<()> {
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda qwen3_0_6b_generate -r -- --nocapture
// test with cuda: RUST_BACKTRACE=1 cargo test -F cuda --test test_qwen3 qwen3_0_6b_generate -r -- --nocapture
// test with cuda+flash-attn: RUST_BACKTRACE=1 cargo test -F cuda,flash-attn qwen3_0_6b_generate -r -- --nocapture
let save_dir =
+22
View File
@@ -265,3 +265,25 @@ fn deepseekocrv2_weight() -> Result<()> {
println!("model_list: {:?}", model_list);
Ok(())
}
#[test]
fn lfm2_weight() -> Result<()> {
// cargo test -F cuda --test weight_test lfm2_weight -r -- --nocapture
let save_dir =
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
let model_path = format!("{}/LiquidAI/LFM2-1.2B/", save_dir);
let model_list = find_type_files(&model_path, "safetensors")?;
let device = Device::Cpu;
for m in &model_list {
let weights = safetensors::load(m, &device)?;
for (key, tensor) in weights.iter() {
// if key.contains("lm_head") {
// println!("=== {} === {:?}", key, tensor.shape());
// }
println!("=== {} === {:?}", key, tensor.shape());
}
}
println!("model_list: {:?}", model_list);
Ok(())
}