文章目录
团队博客: 汽车电子社区
1. 核心架构模块分析
1.1. MiniCPMO 主控模块
1.1.1. 类设计分析
位置: include/minicpmo.h, src/minicpmo.cpp
class MiniCPMO {
public:
// 构造函数 - 初始化所有子模块
MiniCPMO(minicpmo_params params);
// 核心推理接口
void single_prefill(std::vector<float>& image_embed, std::vector<float>& audio_embed);
void streaming_prefill(image_buf<uint8_t>& image, std::vector<float>& pcmf32, int max_slice_nums = 1);
std::string streaming_generate(std::string user_prompt);
// 高级接口
std::string chat(std::string audio_output_path,
std::vector<image_buf<uint8_t>>& image_bytes_list,
std::vector<float>& pcmf32,
std::string language = "en",
std::string user_prompt = "",
bool stream_out = true,
bool eval_system = true);
// TTS接口
bool text_to_speech(const std::string& text, const std::string& output_wav);
// 状态管理
void reset();
void apm_kv_clear();
void apm_streaming_mode(bool streaming);
private:
// 子模块实例 - 组合模式
Siglip* vpm_; // 视觉投影模型
WhisperEncoder* apm_; // 音频投影模型
Outetts* tts_; // 文本转语音模块
llama_model* llama_model_; // LLaMA语言模型
llama_context* llama_ctx_; // 推理上下文
// 多模态嵌入缓存
std::vector<float> audio_embed_out_;
std::vector<float> image_embd_out_;
std::vector<float> omni_strm_pre_token_; // <unit><image>
std::vector<float> omni_strm_mid_token_; // </image><|audio_start|>
std::vector<float> omni_strm_post_token_; // <|audio_end|>
std::vector<float> omni_strm_embd_inp_;
// 推理状态
int n_past_ = 0;
int omni_strm_n_tokens_ = 0;
bool prefill_finished_ = false;
int n_sample_ = 0;
bool stop_smpl_ = false;
std::string utf8_str_ = "";
struct common_sampler* smpl_ = nullptr;
// 模态token数量常量
int n_image_tokens_ = 64; // 图像token数量
int n_audio_tokens_ = 25; // 音频token数量
const int n_embedding_length_ = 3584; // 嵌入维度
};
1.1.2. 关键算法实现
1.1.2.1. 构造函数初始化流程
MiniCPMO::MiniCPMO(minicpmo_params params) {
// 1. 初始化GGML后端
llama_backend_init();
// 2. OpenMP线程设置
#ifdef USE_OPENMP
const int nthreads = std::max(1U, std::thread::hardware_concurrency() / 2);
omp_set_num_threads(std::getenv("OMP_NUM_THREADS") ?
std::stoi(std::getenv("OMP_NUM_THREADS")) : nthreads);
#endif
// 3. 初始化编码器模块
vpm_ = new Siglip(params.vpm_path, false, 1); // 视觉编码器
apm_ = new WhisperEncoder(params.apm_path, "medium"); // 音频编码器
// 4. 计算嵌入输出大小
size_t audio_embed_size = apm_->get_audio_ctx_length() * n_embedding_length_;
audio_embed_size /= 30; // 30秒默认音频长度
audio_embed_size /= 2; // 池化输出,缩小到1/2
audio_embed_out_.resize(audio_embed_size);
// 5. 初始化LLaMA模型
params_ = params.llm_params;
llama_model_params llm_params = common_model_params_to_llama(params_);
llama_context_params llm_ctx_params = common_context_params_to_llama(params_);
llama_model_ = llama_model_load_from_file(params.llm_path.c_str(), llm_params);
llama_ctx_ = llama_init_from_model(llama_model_, llm_ctx_params);
smpl_ = common_sampler_init(llama_model_, this->params_.sampling);
// 6. 预计算特殊token嵌入
this->token_embed(omni_strm_pre_token_, "<unit><image>");
this->token_embed(omni_strm_mid_token_, "</image><|audio_start|>");
this->token_embed(omni_strm_post_token_, "<|audio_end|>");
// 7. 初始化TTS模块(可选)
if (!params.ttc_model_path.empty() && !params.cts_model_path.empty()) {
tts_ = new Outetts(params.ttc_model_path, params.cts_model_path);
}
}
1.1.2.2. 多模态嵌入融合算法
void MiniCPMO::single_prefill(std::vector<float>& image_embed, std::vector<float>& audio_embed) {
// 上下文管理 - 防止溢出
const int preserve_tokens = (n_image_tokens_ + n_audio_tokens_ + 10);
if (n_past_ + preserve_tokens >= params_.n_ctx) {
const int n_left = n_past_ - params_.n_keep;
const int n_discard = n_left / 2;
// 移除旧token,保留重要信息
llama_kv_self_seq_rm(llama_ctx_, 0, params_.n_keep, params_.n_keep + n_discard);
llama_kv_self_seq_add(llama_ctx_, 0, params_.n_keep + n_discard, n_past_, -n_discard);
n_past_ -= n_discard;
}
// 构建多模态token序列
size_t offset = 0;
// 跳过预token空间
offset += omni_strm_pre_token_.size();
// 填充图像嵌入
std::memcpy(omni_strm_embd_inp_.data() + offset,
image_embed.data(),
image_embed.size() * sizeof(float));
offset += image_embed.size();
// 跳过中间token空间
offset += omni_strm_mid_token_.size();
// 填充音频嵌入
std::memcpy(omni_strm_embd_inp_.data() + offset,
audio_embed.data(),
audio_embed.size() * sizeof(float));
// 批量推理
minicpmo_embd_batch omni_embd_batch = minicpmo_embd_batch(
omni_strm_embd_inp_.data(),
n_image_tokens_ + n_audio_tokens_ + omni_strm_n_tokens_,
n_past_, 0
);
llama_decode(llama_ctx_, omni_embd_batch.batch);
n_past_ += (n_image_tokens_ + n_audio_tokens_ + omni_strm_n_tokens_);
}
1.1.2.3. Token嵌入计算
void MiniCPMO::token_embed(std::vector<float>& out, std::string str, bool add_bos) {
// 1. 文本token化
const llama_vocab* vocab = llama_model_get_vocab(llama_model_);
std::vector<llama_token> embd_inp = common_tokenize(vocab, str, add_bos, true);
int n_tokens = embd_inp.size();
// 2. 分配输出内存
out.resize(llama_model_->tok_embd->ne[0] * n_tokens);
// 3. 构建GGML计算图
ggml_backend_t backend = ggml_backend_cpu_init();
ggml_gallocr_t compute_alloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend));
ggml_context* ctx = ggml_init({
GGML_DEFAULT_GRAPH_SIZE * ggml_tensor_overhead() + ggml_graph_overhead(),
nullptr, true
});
ggml_cgraph* gf = ggml_new_graph(ctx);
ggml_tensor* inp_tokens = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, n_tokens);
ggml_tensor* token_embedding = ggml_get_rows(ctx, llama_model_->tok_embd, inp_tokens);
// 4. 执行计算
ggml_build_forward_expand(gf, token_embedding);
ggml_gallocr_alloc_graph(compute_alloc, gf);
ggml_backend_tensor_set(inp_tokens, embd_inp.data(), 0, ggml_nbytes(inp_tokens));
ggml_backend_graph_compute(backend, gf);
ggml_backend_tensor_get(token_embedding, out.data(), 0, ggml_nbytes(token_embedding));
// 5. 清理资源
ggml_free(ctx);
ggml_backend_free(backend);
}
1.2. Siglip 视觉编码器模块
1.2.1. 类设计分析
位置: include/siglip.h, src/siglip.cpp
class Siglip {
public:
Siglip(std::string path, bool is_q4_1 = false, int32_t device = 0);
~Siglip();
// 核心推理接口
void forward(const std::vector<image_buf<float>>& patches,
int image_width,
int image_height,
std::vector<float>& embeddings);
// 模型信息查询
int _embd_nbytes() { return n_embed_ * sizeof(float); }
int get_n_patches() { return n_patches_; }
int get_patch_size() { return patch_size_; }
std::vector<float> get_image_mean() { return image_mean_; }
std::vector<float> get_image_std() { return image_std_; }
private:
// 模型结构
clip_vision_model model_;
ggml_backend_t backend_;
ggml_gallocr_t compute_alloc_;
// 模型参数
int embed_dim_ = 3584; // 嵌入维度
int image_size_ = 384; // 图像尺寸
int patch_size_ = 14; // 补丁大小
int n_patches_ = 64; // 补丁数量
int n_embed_ = 3584; // 嵌入大小
int n_layer_ = 24; // 层数
int n_head_ = 16; // 注意力头数
// 预处理参数
std::vector<float> image_mean_ = {0.5, 0.5, 0.5}; // ImageNet均值
std::vector<float> image_std_ = {0.5, 0.5, 0.5}; // ImageNet标准差
// 投影类型
std::string projector_type_ = "mlp";
};
1.2.2. 关键算法实现
1.2.2.1. 前向推理核心算法
void Siglip::forward(const std::vector<image_buf<float>>& patches,
int image_width,
int image_height,
std::vector<float>& embeddings) {
// 1. 构建计算图
ggml_cgraph* gf = build_graph(image_width, image_height);
// 2. 设置输入数据
ggml_tensor* input = get_input_tensor(gf);
// 将图像patches转换为连续内存
std::vector<float> input_data;
for (const auto& patch : patches) {
input_data.insert(input_data.end(), patch.buf.begin(), patch.buf.end());
}
ggml_backend_tensor_set(input, input_data.data(), 0, ggml_nbytes(input));
// 3. 分配内存并执行计算
ggml_gallocr_alloc_graph(compute_alloc_, gf);
ggml_backend_graph_compute(backend_, gf);
// 4. 获取输出结果
ggml_tensor* output = get_output_tensor(gf);
embeddings.resize(ggml_nbytes(output) / sizeof(float));
ggml_backend_tensor_get(output, embeddings.data(), 0, ggml_nbytes(output));
// 5. 应用投影层(如果有)
if (projector_type_ == "mlp") {
apply_mlp_projection(embeddings);
}
}
1.2.2.2. GGML计算图构建
ggml_cgraph* Siglip::build_graph(const int image_width, const int image_height) {
ggml_context* ctx = model_.ctx;
ggml_cgraph* gf = ggml_new_graph(ctx);
const int num_patches = (image_width / patch_size_) * (image_height / patch_size_);
// 1. 输入张量 [N_patches, 3, patch_size, patch_size]
struct ggml_tensor* inp_raw = ggml_new_tensor_4d(ctx, GGML_TYPE_F32,
3, patch_size_, patch_size_, num_patches);
// 2. 卷积嵌入 [N_patches, embed_dim]
struct ggml_tensor* cur = ggml_conv_2d(ctx, model_.patch_embeddings_0, inp_raw,
/*stride=*/{patch_size_, patch_size_},
/*padding=*/{0,0,0,0});
cur = ggml_add(ctx, cur, model_.class_embedding);
// 3. 位置编码
cur = ggml_reshape_2d(ctx, cur, num_patches, embed_dim_);
struct ggml_tensor* pos_emb = ggml_get_rows(ctx, model_.position_embedding,
ggml_new_tensor_1d(ctx, GGML_TYPE_I32, num_patches));
cur = ggml_add(ctx, cur, pos_emb);
// 4. Transformer编码器层
for (int il = 0; il < n_layer_; ++il) {
cur = transformer_layer(ctx, cur, il);
}
// 5. CLS token提取和投影
struct ggml_tensor* cls_token = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, embed_dim_);
cls_token = ggml_view_1d(ctx, cur, embed_dim_, 0); // 取第一个token
// 应用投影层
embeddings = ggml_mul_mat(ctx, model_.mm_model_proj, cls_token);
ggml_build_forward_expand(gf, embeddings);
return gf;
}
1.2.2.3. Transformer层实现
ggml_tensor* Siglip::transformer_layer(ggml_context* ctx, ggml_tensor* cur, int layer_idx) {
auto& layer = model_.layers_encoder[layer_idx];
// 1. 层归一化
ggml_tensor* ln_1 = ggml_norm(ctx, cur, 1e-5f);
ln_1 = ggml_mul(ctx, ln_1, layer.layer_norm1_weight);
ln_1 = ggml_add(ctx, ln_1, layer.layer_norm1_bias);
// 2. 自注意力
ggml_tensor* q = ggml_mul_mat(ctx, layer.attn_q_proj, ln_1);
ggml_tensor* k = ggml_mul_mat(ctx, layer.attn_k_proj, ln_1);
ggml_tensor* v = ggml_mul_mat(ctx, layer.attn_v_proj, ln_1);
// 注意力计算 [seq_len, embed_dim]
ggml_tensor* attn_out = ggml_flash_attn(ctx, q, k, v, n_patches_, n_head_, n_ctx_);
attn_out = ggml_mul_mat(ctx, layer.attn_o_proj, attn_out);
// 3. 残差连接
ggml_tensor* residual = ggml_add(ctx, cur, attn_out);
// 4. 前馈网络
ggml_tensor* ln_2 = ggml_norm(ctx, residual, 1e-5f);
ln_2 = ggml_mul(ctx, ln_2, layer.layer_norm2_weight);
ln_2 = ggml_add(ctx, ln_2, layer.layer_norm2_bias);
// MLP层
ggml_tensor* mlp_out = ggml_mul_mat(ctx, layer.fc1, ln_2);
mlp_out = ggml_gelu(ctx, mlp_out);
mlp_out = ggml_mul_mat(ctx, layer.fc2, mlp_out);
// 5. 最终残差连接
ggml_tensor* output = ggml_add(ctx, residual, mlp_out);
return output;
}
1.3. WhisperEncoder 音频编码器模块
1.3.1. 类设计分析
位置: include/whisper_encoder.h, src/whisper_encoder.cpp
class WhisperEncoder {
public:
WhisperEncoder(std::string path, std::string preset = "medium");
~WhisperEncoder();
// 核心推理接口
void forward(const std::vector<float>& pcmf32, std::vector<float>& embeddings);
// 流式处理接口
void set_streaming_mode(bool streaming);
void set_exp_n_audio_ctx(int n_ctx);
int get_audio_ctx_length() const;
// 缓存管理
void kv_cache_clear();
private:
// 模型结构
whisper_encoder_model* model_;
whisper_kv_cache kv_self_;
whisper_sched sched_conv_;
whisper_sched sched_encode_;
// 流式处理状态
bool streaming_ = false;
int iter_ = -1;
int exp_n_audio_ctx_ = 1500;
// 音频处理参数
static constexpr int SAMPLE_RATE = 16000;
static constexpr int N_FFT = 400;
static constexpr int HOP_LENGTH = 160;
static constexpr int N_MELS = 80;
static constexpr int N_CTX = 1500;
// 预计算缓存
static whisper_global_cache global_cache_;
};
1.3.2. 关键算法实现
1.3.2.1. 音频特征提取算法
void WhisperEncoder::forward(const std::vector<float>& pcmf32, std::vector<float>& embeddings) {
// 1. 音频预处理
std::vector<float> pcmf32_processed = preprocess_audio(pcmf32);
// 2. 计算梅尔频谱
std::vector<float> mel_spectrogram;
log_mel_spectrogram(pcmf32_processed, mel_spectrogram);
// 3. 构建编码器计算图
ggml_cgraph* gf = _whisper_build_graph_encoder(mel_spectrogram);
// 4. 执行推理
ggml_gallocr_alloc_graph(compute_alloc_, gf);
ggml_backend_graph_compute(backend_, gf);
// 5. 提取嵌入特征
ggml_tensor* output = get_output_tensor(gf);
embeddings.resize(ggml_nbytes(output) / sizeof(float));
ggml_backend_tensor_get(output, embeddings.data(), 0, ggml_nbytes(output));
}
1.3.2.2. 梅尔频谱计算
void WhisperEncoder::log_mel_spectrogram(const std::vector<float>& pcmf32,
std::vector<float>& mel_spectrogram) {
const int n_samples = pcmf32.size();
const int n_frames = (n_samples - N_FFT) / HOP_LENGTH + 1;
// 1. 应用Hanning窗
std::vector<float> windowed(N_FFT);
for (int i = 0; i < n_frames; ++i) {
for (int j = 0; j < N_FFT; ++j) {
int idx = i * HOP_LENGTH + j;
if (idx < n_samples) {
windowed[j] = pcmf32[idx] * global_cache_.hann_window[j];
} else {
windowed[j] = 0.0f;
}
}
// 2. FFT变换
std::vector<float> fft_out(N_FFT * 2);
fft(windowed.data(), N_FFT, fft_out.data());
// 3. 计算功率谱
std::vector<float> power(N_FFT / 2 + 1);
for (int j = 0; j <= N_FFT / 2; ++j) {
power[j] = fft_out[2*j] * fft_out[2*j] + fft_out[2*j+1] * fft_out[2*j+1];
}
// 4. 应用梅尔滤波器组
for (int m = 0; m < N_MELS; ++m) {
float mel_energy = 0.0f;
for (int k = 0; k < N_FFT / 2 + 1; ++k) {
mel_energy += power[k] * mel_filters[m][k];
}
mel_spectrogram[i * N_MELS + m] = logf(mel_energy + 1e-10f);
}
}
}
1.3.2.3. Whisper编码器图构建
ggml_cgraph* WhisperEncoder::_whisper_build_graph_encoder(const std::vector<float>& mel) {
ggml_context* ctx = model_->ctx;
ggml_cgraph* gf = ggml_new_graph(ctx);
const int n_frames = mel.size() / N_MELS;
// 1. 输入梅尔频谱 [n_frames, n_mels]
struct ggml_tensor* cur = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, N_MELS, n_frames);
ggml_backend_tensor_set(cur, mel.data(), 0, ggml_nbytes(cur));
// 2. 卷积层 (下采样)
cur = ggml_conv_1d(ctx, model_->e_conv_1_w, cur);
cur = ggml_add(ctx, cur, model_->e_conv_1_b);
cur = ggml_gelu(ctx, cur);
// 3. 位置编码
cur = ggml_add(ctx, cur, model_->e_pe);
// 4. Transformer编码器
for (int il = 0; il < n_layer; ++il) {
cur = whisper_encoder_layer(ctx, cur, il);
}
// 5. 全局平均池化
cur = ggml_mean(ctx, cur, 0); // 沿序列维度平均
ggml_build_forward_expand(gf, cur);
return gf;
}
1.4. Outetts 文本转语音模块
1.4.1. 类设计分析
位置: include/outetts.h, src/outetts.cpp
class Outetts {
public:
Outetts(std::string ttc_path, std::string cts_path);
~Outetts();
// 核心TTS接口
bool text_to_speech(const std::string& text, std::vector<float>& audio_data);
bool save_wav(const std::string& filename, const std::vector<float>& audio_data);
private:
// 模型组件
llama_model* ttc_model_; // Text-to-Codes模型
llama_model* cts_model_; // Codes-to-Speech模型
llama_context* ttc_ctx_;
llama_context* cts_ctx_;
// 音频参数
static constexpr int SAMPLE_RATE = 24000;
static constexpr int HOP_LENGTH = 256;
static constexpr int N_MELS = 100;
// 文本预处理
std::string normalize_text(const std::string& text);
std::string expand_numbers(const std::string& text);
};
1.4.2. 关键算法实现
1.4.2.1. 文本到语音主流程
bool Outetts::text_to_speech(const std::string& text, std::vector<float>& audio_data) {
// 1. 文本预处理
std::string normalized_text = normalize_text(text);
normalized_text = expand_numbers(normalized_text);
// 2. 文本到声学特征编码
std::vector<int> acoustic_codes;
if (!text_to_codes(normalized_text, acoustic_codes)) {
return false;
}
// 3. 声学特征到语音合成
if (!codes_to_speech(acoustic_codes, audio_data)) {
return false;
}
return true;
}
1.4.2.2. WAV文件保存
bool Outetts::save_wav(const std::string& filename, const std::vector<float>& audio_data) {
std::ofstream file(filename, std::ios::binary);
if (!file) return false;
// 1. WAV头部
struct wav_header header;
header.sample_rate = SAMPLE_RATE;
header.data_size = audio_data.size() * 2; // 16-bit samples
header.chunk_size = 36 + header.data_size;
header.byte_rate = SAMPLE_RATE * 2;
header.block_align = 2;
file.write(reinterpret_cast<const char*>(&header), sizeof(header));
// 2. 音频数据 (16-bit PCM)
for (float sample : audio_data) {
int16_t pcm_sample = static_cast<int16_t>(std::clamp(sample * 32767.0f, -32768.0f, 32767.0f));
file.write(reinterpret_cast<const char*>(&pcm_sample), sizeof(pcm_sample));
}
return true;
}
2. 接口调用流程图
2.1. 完整推理流程
2.2. 流式推理详细流程
2.3. 内存管理流程
3. 性能优化策略
3.1 并行计算优化
3.1.1. OpenMP并行化
#ifdef USE_OPENMP
#pragma omp parallel for collapse(2)
for (int i = 0; i < target_height; ++i) {
for (int j = 0; j < target_width; ++j) {
// 图像预处理并行计算
bicubic_interpolate(...)
}
}
#endif
3.1.2. SIMD向量化
#ifdef USE_OPENMP
#pragma omp simd
for (int i = 0; i < vector_size; ++i) {
result[i] = a[i] * b[i] + c[i]; // 向量运算
}
#endif
3.2. 内存访问优化
3.2.1. 缓存友好设计
// 数据局部性优化
struct alignas(64) CacheLine {
float data[16]; // 对齐到缓存行
};
3.2.2. 零拷贝优化
// 直接内存映射,避免数据拷贝
ggml_backend_tensor_set(input_tensor,
input_data.data(),
0,
ggml_nbytes(input_tensor));
3.3. 计算图优化
3.3.1. 算子融合
// 合并多个操作为单个核函数
ggml_tensor* fused_op = ggml_add(ctx,
ggml_mul(ctx, input, weight),
bias);
3.3.2. 内存复用
// 重用临时缓冲区
ggml_gallocr_t alloc = ggml_gallocr_new(buffer_type);
ggml_gallocr_alloc_graph(alloc, graph);
4. 错误处理和调试支持
4.1. 异常安全设计
RAII资源管理
class ScopedGGMLContext {
ggml_context* ctx_;
public:
ScopedGGMLContext(ggml_context* ctx) : ctx_(ctx) {}
~ScopedGGMLContext() {
if (ctx_) ggml_free(ctx_);
}
};
4.2. 调试和日志系统
#define LOG_DBG(...) if (log_level_ >= LogLevel::DEBUG) fprintf(stderr, "[DBG] " __VA_ARGS__)
#define LOG_INF(...) if (log_level_ >= LogLevel::INFO) fprintf(stderr, "[INF] " __VA_ARGS__)
#define LOG_ERR(...) if (log_level_ >= LogLevel::ERROR) fprintf(stderr, "[ERR] " __VA_ARGS__)
5. 模块间接口规范
5.1. 统一的编码器接口
template<typename InputType, typename OutputType>
class EncoderInterface {
public:
virtual void forward(const InputType& input, OutputType& output) = 0;
virtual size_t get_output_size() const = 0;
virtual void clear_cache() = 0;
virtual ~EncoderInterface() = default;
};
5.2. 数据转换接口
class DataConverter {
public:
static void numpy_to_tensor(const py::array_t<float>& array, ggml_tensor* tensor);
static void tensor_to_numpy(const ggml_tensor* tensor, py::array_t<float>& array);
static void image_to_buffer(const image_buf<uint8_t>& img, std::vector<float>& buffer);
};
这个深度源码分析展示了MiniCPM-o.cpp项目各模块的详细实现,包括算法细节、内存管理、性能优化和错误处理策略,为理解和扩展该系统提供了全面的技术参考。


被折叠的 条评论
为什么被折叠?



