12 KiB
12 KiB
开发指南
本指南涵盖为 AHA 做贡献,包括开发设置、添加新模型和提交贡献。
目录
开发设置
前置要求
- Rust 1.85+ (edition 2024)
- Git
- 可选:CUDA Toolkit、Metal、FFmpeg(用于功能开发)
克隆和构建
# 克隆仓库
git clone https://github.com/jhqxxx/aha.git
cd aha
# 调试模式构建(编译更快)
cargo build
# 发布模式构建(性能更好)
cargo build --release
# 使用功能构建
cargo build --features cuda,flash-attn
cargo build --features metal
cargo build --features ffmpeg
开发工作流程
# 运行 CLI
cargo run -- -m qwen3-0.6b
# 运行测试
cargo test
# 运行特定测试
cargo test test_qwen3vl_generate
# 启用日志运行
RUST_LOG=debug cargo run -- -m qwen3-0.6b
# 检查代码而不构建
cargo check
# 格式化代码
cargo fmt
# 运行 linter
cargo clippy
项目结构
aha/
├── Cargo.toml # 构建配置
├── src/
│ ├── main.rs # CLI 入口点
│ ├── lib.rs # 库导出
│ ├── api.rs # HTTP API 处理程序
│ ├── exec/ # CLI 命令实现
│ │ ├── mod.rs
│ │ ├── cli.rs
│ │ ├── serv.rs
│ │ ├── download.rs
│ │ └── run.rs
│ ├── models/ # 模型实现
│ │ ├── mod.rs # 模型工厂和导出
│ │ ├── common/ # 共享模型工具
│ │ │ ├── config.rs
│ │ │ └── mod.rs
│ │ ├── qwen3vl/ # Qwen3-VL 模型
│ │ │ ├── config.rs
│ │ │ ├── model.rs
│ │ │ ├── generate.rs
│ │ │ ├── processor.rs
│ │ │ └── mod.rs
│ │ ├── voxcpm/ # VoxCPM 模型
│ │ └── ... # 其他模型
│ ├── tokenizer/ # 分词工具
│ ├── chat_template/ # 聊天模板处理
│ ├── position_embed/ # 位置编码
│ └── utils/ # 工具函数
│ ├── audio_utils.rs
│ ├── image_utils.rs
│ ├── tensor_utils.rs
│ └── mod.rs
├── tests/ # 集成测试
│ ├── test_qwen2_5vl.rs
│ ├── test_qwen3vl.rs
│ └── ...
└── docs/ # 文档
添加新模型
本节提供了向 AHA 添加新模型的分步指南。
步骤 1:创建模型目录
在 src/models/ 下创建新目录:
mkdir -p src/models/newmodel
步骤 2:实现模型文件
在 src/models/newmodel/ 中创建以下文件:
config.rs
定义模型配置:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NewModelConfig {
pub vocab_size: usize,
pub hidden_size: usize,
pub num_hidden_layers: usize,
pub num_attention_heads: usize,
// ... 其他配置字段
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationConfig {
pub temperature: f64,
pub top_p: f64,
pub max_tokens: usize,
}
model.rs
实现模型架构:
use candle::{Result, Tensor};
use candle_nn::{Module, VarBuilder};
use super::config::NewModelConfig;
pub struct NewModel {
// 定义模型组件
config: NewModelConfig,
}
impl NewModel {
pub fn load(vb: VarBuilder, config: &NewModelConfig) -> Result<Self> {
// 加载模型权重
Ok(Self {
config: config.clone(),
})
}
pub fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
// 实现前向传播
Ok(input_ids.clone())
}
}
generate.rs
实现 GenerateModel trait:
use std::path::Path;
use anyhow::Result;
use candle::Device;
use super::config::{GenerationConfig, NewModelConfig};
use aha::models::common::GenerateModel;
pub struct NewModelGenerate {
// 定义生成状态
model: super::model::NewModel,
config: GenerationConfig,
device: Device,
}
impl GenerateModel for NewModelGenerate {
type Config = NewModelConfig;
type GenerateConfig = GenerationConfig;
fn init(
model_path: &str,
config: Option<Self::Config>,
generate_config: Option<Self::GenerateConfig>,
) -> Result<Self> {
// 从磁盘加载模型
let device = Device::Cpu;
let config = config.unwrap_or_default();
let generate_config = generate_config.unwrap_or_default();
// 加载权重
let model_path = Path::new(model_path);
// ... 加载模型实现
Ok(Self {
model: todo!(),
config: generate_config,
device,
})
}
fn generate(&mut self, prompt: &str) -> Result<String> {
// 分词提示
// 运行推理
// 解码输出
Ok(prompt.to_string())
}
}
processor.rs(可选)
对于具有多模态输入的复杂模型:
use anyhow::Result;
pub struct NewModelProcessor {
// 处理状态
}
impl NewModelProcessor {
pub fn new() -> Self {
Self {}
}
pub fn process_text(&self, text: &str) -> Result<Vec<String>> {
// 处理文本输入
Ok(vec![text.to_string()])
}
pub fn process_image(&self, image_path: &str) -> Result<Tensor> {
// 处理图像输入
todo!()
}
}
mod.rs
导出模型:
mod config;
mod model;
mod generate;
pub mod processor;
pub use generate::NewModelGenerate;
pub use config::{GenerationConfig, NewModelConfig};
步骤 3:注册模型
更新 src/models/mod.rs:
// 添加到导入
pub mod newmodel;
// 添加到 WhichModel 枚举
#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
pub enum WhichModel {
// ... 现有模型
NewModel,
}
// 添加到模型加载
pub fn load_model(
model_type: &WhichModel,
model_path: &str,
device: &Device,
) -> Result<Box<dyn common::GenerateModel>> {
match model_type {
// ... 现有模型
WhichModel::NewModel => {
Ok(Box::new(newmodel::NewModelGenerate::init(model_path, None, None)?))
}
}
}
步骤 4:更新 CLI
更新 src/main.rs 以在帮助文本和 CLI 选项中包含新模型(如果需要)。
步骤 5:添加测试
创建 tests/test_newmodel.rs:
use anyhow::Result;
#[test]
fn test_newmodel_generate() -> Result<()> {
let model_path = "path/to/test/model";
let mut model = aha::models::newmodel::NewModelGenerate::init(model_path, None, None)?;
let result = model.generate("测试提示")?;
assert!(!result.is_empty());
Ok(())
}
步骤 6:更新文档
更新以下文件:
README.md- 将模型添加到支持的模型列表docs/cli.md- 将模型 ID 添加到模型列表docs/api.md- 将模型添加到支持的模型部分CHANGELOG.md- 为新模型添加条目
多制品接入规则
为模型新增或扩展加载后端时,请遵循当前制品架构:
- 统一使用
LoadSpec(src/models/core/artifact.rs)作为加载输入契约。 - 保持模型身份(
WhichModel)与制品格式(ArtifactKind)解耦。 - 在
supported_artifacts(model)中声明支持矩阵,不要提前暴露未实现格式。 - 通过 family loader registry(
src/models/core/registry.rs)分发初始化,不再扩展load_model的大match。 - GGUF/ONNX 保持本地路径输入(
gguf_path/onnx_path/tokenizer_dir),不引入远程下载管理逻辑。 - 新后端接入时保持
ModelInstance能力接口语义兼容(generate、generate_stream、embedding、rerank)。
测试
单元测试
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_newmodel_generate
# 启用输出运行
cargo test -- --nocapture
# 并行运行测试
cargo test -- --test-threads=4
集成测试
集成测试位于 tests/ 中:
# 运行特定集成测试
cargo test --test test_qwen3vl
手动测试
手动测试 CLI 和 API:
# 测试 CLI
cargo run -- -m newmodel
# 测试 API
cargo run -- -m newmodel -p 8080
curl http://127.0.0.1:8080/chat/completions -d '{...}'
模型特定测试
每个模型都应该有涵盖以下内容的测试:
- 模型加载
- 基本推理
- 边缘情况
- 错误处理
代码风格
格式化
# 格式化所有代码
cargo fmt
# 检查格式化而不进行更改
cargo fmt --check
Linting
# 运行 clippy
cargo clippy
# 修复 clippy 警告
cargo clippy --fix
约定
- 使用 4 个空格缩进
- 对于错误,优先使用
Result<T>而不是Option<T> - 对于应用程序错误使用
anyhow::Result - 使用 rustdoc 注释记录公共 API
- 保持函数专注和小型
- 使用有意义的变量名
文档
/// 使用 NewModel 生成文本。
///
/// # 参数
///
/// * `prompt` - 输入提示文本
/// * `max_tokens` - 要生成的最大令牌数
///
/// # 返回
///
/// 生成的文本作为字符串
///
/// # 示例
///
/// ```no_run
/// let result = model.generate("你好")?;
/// ```
pub fn generate(&mut self, prompt: &str) -> Result<String> {
// 实现
}
提交贡献
Pull Request 流程
- Fork 仓库
- 创建功能分支:
git checkout -b feature/new-model - 进行更改
- 添加测试
- 更新文档
- 确保所有测试通过:
cargo test cargo clippy cargo fmt --check - 提交并推送:
git commit -m "添加 NewModel 支持" git push origin feature/new-model - 在 GitHub 上创建 pull request
Pull Request 清单
- 已添加/更新测试
- 已更新文档
- 已更新 CHANGELOG.md
- 代码已格式化(
cargo fmt) - 已修复 Clippy 警告(
cargo clippy) - 所有测试通过(
cargo test) - 提交消息遵循约定
提交消息约定
遵循约定式提交:
feat: 添加 NewModel 支持
fix: 修正 Qwen3VL 中的张量维度
docs: 更新安装指南
test: 添加 VoxCPM 集成测试
refactor: 简化模型加载逻辑
perf: 将推理速度提高 20%
发布流程
版本升级
更新 Cargo.toml:
[package]
version = "0.1.9" # 升级版本
更新变更日志
将条目添加到 CHANGELOG.md:
## [0.1.9] - 2025-02-06
### 新增
- NewModel 支持
### 修复
- 图像处理中的内存泄漏
### 更改
- 改进错误消息
创建发布
# 标记发布
git tag -a v0.1.9 -m "Release v0.1.9"
git push origin v0.1.9
# 构建发布产物
cargo build --release
# 发布到 crates.io(如果适用)
cargo publish
调试
日志
启用调试日志:
RUST_LOG=debug cargo run -- -m qwen3-0.6b
设置特定模块日志:
RUST_LOG=aha::models::qwen3vl=debug cargo run -- -m qwen3-0.6b
调试测试
# 打印测试输出
cargo test -- --nocapture
# 显示回溯
RUST_BACKTRACE=1 cargo test
常见问题
构建错误
- 链接错误:安装所需的系统依赖
- CUDA 错误:确保已安装 CUDA toolkit
- Metal 错误:检查您是否在 Apple Silicon 上
运行时错误
- 未找到模型:检查模型路径和下载
- 内存不足:使用更小的模型或启用 GPU
- 推理速度慢:启用 GPU 加速