保存时间:2026/3/29 20:33:36
| 应用类型 | 具体举例 | 大模型的作用 |
|---|---|---|
| 办公软件 | Word、Excel、PPT等 | 辅助内容生成与优化,如自动生成文档大纲、数据可视化建议等;智能排版与格式调整;通过语义理解实现快速检索和引用文档内容。 |
| 数据库 | MySQL、Oracle等 | 作为智能数据管理助手,辅助查询优化,提供更精准的查询语句建议;进行数据异常检测和预测性维护;基于数据生成分析报告。 |
| 即时通讯 | 微信、QQ等 | 改善聊天机器人的智能程度,提供更自然、准确的回复;实现语音与文字的实时转换和智能处理;辅助内容创作,如快速生成表情包文案等。 |
| 电子邮件 | Outlook、 Gmail等 | 智能撰写邮件,根据用户意图自动生成邮件内容;智能分类和过滤邮件,标记重要邮件;自动回复邮件,提供简洁准确的回复内容。 |
| 网页浏览器 | Chrome、Firefox等 | 智能搜索建议,提供更精准的搜索结果;网页内容总结与提炼,帮助用户快速了解网页核心内容;智能翻译网页内容。 |
| 图像处理 | Photoshop、美图秀秀等 | 智能图片编辑,如自动抠图、图像修复、风格转换;根据用户描述生成图像内容;辅助图像设计,提供创意和布局建议。 |
| 视频处理 | Premiere、剪映等 | 智能视频剪辑,根据素材内容和用户需求自动生成视频片段;视频内容理解与标注,方便用户搜索和管理视频素材;视频特效智能生成。 |
| 游戏 | 《英雄联盟》《王者荣耀》等 | 生成动态游戏剧情和任务,根据玩家行为实时调整游戏内容;创建智能NPC,提升NPC的交互能力和智能决策能力;辅助游戏开发,如自动生成游戏代码、音效等。 |
| 搜索引擎 | 百度、谷歌等 | 提供更精准的搜索结果,理解用户复杂查询意图;智能回答用户问题,直接给出答案而非链接;对搜索结果进行智能排序和分类。 |
| 企业资源规划(ERP) | SAP、Oracle ERP等 | 优化业务流程,根据企业数据和业务规则提供智能决策建议;预测供应链需求,辅助库存管理和物流规划;自动生成财务报表和分析报告。 |
| 客户关系管理(CRM) | Salesforce、纷享销客等 | 智能客户分类和预测,识别高价值客户;辅助销售团队进行客户沟通,提供个性化的沟通策略和话术;分析客户反馈,改进产品和服务。 |
| 应用类型 | 具体举例(未来形态) | 大模型的核心作用(区别于传统工具) |
|---|---|---|
| 个人数字记忆增强系统 | 专属AI记忆助手(连接个人云盘、日程、通讯记录) | 1. 多模态数据无差别理解(文字/音频/视频/图像);2. 记忆关联联想(模拟人类联想式回忆);3. 个性化记忆解读与主动唤醒 |
| 组件 | 核心职责 | 不负责的事 |
|---|---|---|
| RAG | 检索“和当前问题最相关的知识库信息” | 维护对话上下文、生成回答 |
| 开源大模型(如Deepseek) | 接收完整prompt(上下文+问题+RAG资料),生成自然语言回答 | 检索资料、记历史对话、压缩上下文 |
| Chat应用层(或框架) | 记录历史对话、压缩上下文、拼接完整prompt | 生成回答、检索资料 |
all-MiniLM-L6-v2(仅120M参数)、bge-small-en-v1.5(140M参数),用C++部署(比如通过ONNX Runtime),单条文本转向量仅需几毫秒,比大模型(如Llama 2)快10-100倍,且能跑在普通CPU上。sentence-transformers的C++移植版、ONNX Runtime C++ API)加载小Embedding模型,将“知识库问答对”“用户历史对话”转成向量存到C++兼容的向量库(如FAISS的C++版、Milvus的C++ SDK),检索时用小模型转用户问题向量,再查向量库即可。all-MiniLM,医疗场景用医疗专用小模型,图片场景用ResNet模型),如果数据库自带固定编码工具,会极大限制灵活性——你无法根据自己的需求(比如C++部署、垂直领域效果)选择最优的Embedding模型。ONNX Runtime加载all-MiniLM-L6-v2模型,把“企业FAQ文本”或“用户问题”转成768维向量(这一步是“编码”,由你选的模型完成); search函数),数据库会自动用你指定的算法(如余弦相似度)比对所有向量,返回相似度最高的Top N结果(这一步“比对”是数据库自带的核心能力)。all-MiniLM)生成向量,再导入数据库,核心逻辑还是“开放灵活”。openblas 即可,无需 GPU 依赖),具体步骤可简化为 3 步:# 安装 C++ 编译器、cmake、openblas(CPU 计算依赖)
sudo apt-get install g++ cmake libopenblas-dev
git clone https://github.com/facebookresearch/faiss.git
cd faiss
# 创建编译目录
mkdir build && cd build
# 配置编译选项:禁用 GPU,指定用 openblas,生成 C++ 库
cmake -DFAISS_ENABLE_GPU=OFF -DFAISS_USE_BLAS=ON -DBLAS_LIBRARIES=/usr/lib/x86_64-linux-gnu/libopenblas.so ..
# 编译(-j 后面跟CPU核心数,加速编译)
make -j4
build/lib 下生成 libfaiss.so(动态库)和 libfaiss.a(静态库),直接在你的 C++ 项目中链接即可。openblas 库,无额外环境依赖。| 需求点 | FAISS 支持情况 |
|---|---|
| 开源 | ✅ MIT 协议,完全开源 |
| Linux 下 C++ 编译 | ✅ 原生支持,步骤简单,依赖少 |
| CPU 运行 | ✅ 可禁用 GPU,纯 CPU 高效检索 |
| 无需虚拟机/额外环境 | ✅ 编译后直接在 Linux 系统运行 |
float* vec,长度768)。{FAISS的向量ID: 原始FAQ文本(问题+答案), 数据类型(初始化数据/历史数据), 权威性权重},用于后续检索后找回原始文本。faiss::IndexFlatL2(最简单的暴力检索,适合小规模数据,无需训练),步骤如下:#include <faiss/IndexFlat.h>
#include <vector>
#include <string>
#include <unordered_map>
// 1. 定义全局变量:FAISS索引、原始数据映射表、向量维度(需和Embedding模型输出一致,如768)
faiss::IndexFlatL2* faiss_index = nullptr;
const int D = 768; // 向量维度
// 映射表:key=FAISS的向量ID,value={原始FAQ文本(问题+答案), 数据类型(0=初始化数据,1=历史数据)}
std::unordered_map<int, std::pair<std::string, int>> faq_data_map;
int next_vector_id = 0; // 下一个待分配的向量ID(自增,确保唯一)
// 2. 初始化FAISS索引(程序启动时调用1次)
void init_faiss_index() {
// 创建CPU索引:D=向量维度,L2=用欧氏距离计算相似度
faiss_index = new faiss::IndexFlatL2(D);
// 若后续要支持“按权威性排序”,可先不在这里设置,检索时手动过滤(见第二步)
}
// 3. 导入FAQ到FAISS(初始化时调用,传入所有FAQ的“文本+向量”)
// 参数说明:faq_texts=所有FAQ的“问题+答案”合并文本列表,faq_vectors=对应的向量列表(每个向量是D维float数组)
void import_faq_to_faiss(const std::vector<std::string>& faq_texts, const std::vector<float*>& faq_vectors) {
if (faiss_index == nullptr) {
init_faiss_index(); // 若未初始化,先初始化索引
}
// FAISS要求向量以“行优先”的连续内存存储(n行D列,n=FAQ数量)
int n = faq_texts.size(); // FAQ总数
float* all_vectors = new float[n * D]; // 存储所有向量的连续内存
for (int i = 0; i < n; i++) {
// 把每个FAQ的向量拷贝到连续内存中
memcpy(all_vectors + i * D, faq_vectors[i], D * sizeof(float));
// 记录映射关系:ID=next_vector_id,文本=faq_texts[i],数据类型=0(初始化数据,高权威性)
faq_data_map[next_vector_id] = {faq_texts[i], 0};
next_vector_id++; // ID自增
}
// 把所有向量添加到FAISS索引(add函数:n=向量数量,x=连续向量内存,ids=可选自定义ID,这里用默认自增ID)
faiss_index->add(n, all_vectors);
// 释放临时内存
delete[] all_vectors;
}
faq_data_map维护——你可以将每条FAQ的“问题”和“答案”合并成一段文本(如“Q:如何退款?A:退款需在订单完成后7天内申请...”),转成向量后入库,后续检索到向量时,通过ID从faq_data_map中取出完整的“问题+答案”文本。faq_data_map筛选“高权威性(初始化数据)优先”的结果。#include <faiss/Index.h>
#include <vector>
#include <algorithm>
// 检索函数:输入用户问题向量,返回Top-K个最相似的FAQ文本(优先初始化数据)
// 参数:user_query_vec=用户问题的D维向量,k=要返回的相似结果数量,返回值=筛选后的相似FAQ文本列表
std::vector<std::string> search_faq(const float* user_query_vec, int k) {
if (faiss_index == nullptr || faiss_index->ntotal == 0) {
return {}; // 索引未初始化或无数据,返回空
}
// FAISS检索输出:distances=相似度距离(越小越相似),labels=匹配到的向量ID
std::vector<float> distances(k);
std::vector<faiss::idx_t> labels(k); // faiss::idx_t是FAISS定义的ID类型(通常是int64)
// 调用FAISS检索接口:1=1个查询向量,x=用户问题向量,k=返回Top-K,distances=距离,labels=ID
faiss_index->search(1, user_query_vec, k, distances.data(), labels.data());
// 按“权威性”筛选结果:先取“初始化数据(类型0)”,再补“历史数据(类型1)”
std::vector<std::string> high_priority_results; // 高权威性(初始化数据)
std::vector<std::string> low_priority_results; // 低权威性(历史数据)
for (int i = 0; i < k; i++) {
int vec_id = labels[i]; // 匹配到的向量ID
auto it = faq_data_map.find(vec_id);
if (it == faq_data_map.end()) continue; // 异常情况,跳过
if (it->second.second == 0) {
// 初始化数据,加入高优先级
high_priority_results.push_back(it->second.first);
} else {
// 历史数据,加入低优先级
low_priority_results.push_back(it->second.first);
}
}
// 合并结果:高优先级在前,低优先级在后,总数量不超过k
std::vector<std::string> final_results;
final_results.reserve(k);
// 先加所有高优先级结果
for (const auto& s : high_priority_results) {
if (final_results.size() >= k) break;
final_results.push_back(s);
}
// 再加低优先级结果(补满k个)
for (const auto& s : low_priority_results) {
if (final_results.size() >= k) break;
final_results.push_back(s);
}
return final_results;
}
search_faq函数。faq_data_map中标注“数据类型=1(低权威性)”。// 新增历史数据到FAISS:传入“用户问题+大模型回答”的文本和对应的向量
void add_history_to_faiss(const std::string& history_text, const float* history_vec) {
if (faiss_index == nullptr) {
init_faiss_index();
}
// 1. 把历史数据的向量添加到FAISS(add函数支持单次添加1个向量)
faiss_index->add(1, history_vec);
// 2. 记录映射关系:ID=next_vector_id,文本=history_text,数据类型=1(历史数据,低权威性)
faq_data_map[next_vector_id] = {history_text, 1};
next_vector_id++;
}
history_text,用Embedding模型转成history_vec,调用add_history_to_faiss,即可完成“历史数据入库”,且后续检索时会按“低权威性”排在后面。faiss::IndexFlatL2::search接口,最后通过映射表找回原始文本并按权威性筛选。faq_data_map的“数据类型”字段(0=初始化,1=历史)标记,检索时优先返回类型0的结果,再补类型1的结果。bert-base-uncased的精简版),输入文本,直接返回向量数组(float[768]),不用任何中间件。.h和.cpp,编译后就一个静态库)。#include <cmath>
#include <vector>
// 计算两个D维向量的余弦相似度(值越接近1,越相似)
float cosine_similarity(const std::vector<float>& vec1, const std::vector<float>& vec2) {
float dot = 0.0f, norm1 = 0.0f, norm2 = 0.0f;
for (size_t i = 0; i < vec1.size(); i++) {
dot += vec1[i] * vec2[i]; // 点积
norm1 += vec1[i] * vec1[i]; // vec1的模长平方
norm2 += vec2[i] * vec2[i]; // vec2的模长平方
}
return dot / (sqrt(norm1) * sqrt(norm2)); // 余弦相似度公式
}
#include <vector>
#include <pair>
#include <algorithm>
// 存储向量+原始文本的结构体(替代FAISS的映射表)
struct VectorData {
std::vector<float> vec; // 向量
std::string text; // 原始文本(问题+答案/历史对话)
int authority; // 权威性(0=高,1=低)
};
// 检索函数:输入用户问题向量,返回Top-K最相似的结果(按权威性+相似度排序)
std::vector<std::string> search_similar(const std::vector<float>& query_vec,
const std::vector<VectorData>& all_data,
int k) {
std::vector<std::pair<float, const VectorData*>> similarity_list;
// 1. 计算当前问题与所有数据的相似度
for (const auto& data : all_data) {
float sim = cosine_similarity(query_vec, data.vec);
similarity_list.emplace_back(sim, &data);
}
// 2. 排序:先按权威性(0在前),再按相似度(从大到小)
std::sort(similarity_list.begin(), similarity_list.end(),
[](const auto& a, const auto& b) {
if (a.second->authority != b.second->authority) {
return a.second->authority < b.second->authority; // 高权威性在前
}
return a.first > b.first; // 相似度高的在前
});
// 3. 取Top-K结果
std::vector<std::string> results;
for (int i = 0; i < k && i < similarity_list.size(); i++) {
results.push_back(similarity_list[i].second->text);
}
return results;
}
float[768])转成二进制数组(BLOB类型),和“原始文本”“权威性”一起存在普通数据库里,查询时先把BLOB读出来转成vector<float>,再调用上面的search_similar函数。CREATE TABLE vector_store (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vec_blob BLOB NOT NULL, -- 向量的二进制数据(float[768]转BLOB)
text TEXT NOT NULL, -- 原始文本
authority INTEGER NOT NULL -- 权威性(0=高,1=低)
);
vector<float>的内存直接转成BLOB,插入表中;vector<float>,存入all_data数组,再调用检索函数。bert.cpp 向量生成、SQLite 存储、余弦相似度检索,所有细节已封装,你直接调用即可。bert.cpp 生成静态库(libbert.a),并将其 include 目录加入项目(获取 bert.h 头文件);sudo apt-get install libsqlite3-dev);bert.cpp 模型文件需提前准备(如 mini-bert.bin)。#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <sqlite3.h>
#include "bert.h" // 来自 bert.cpp 的头文件
// -------------------------- 全局配置(需根据你的环境修改) --------------------------
const std::string BERT_MODEL_PATH = "./mini-bert.bin"; // bert.cpp 模型文件路径
const int VEC_DIM = 768; // 向量维度(bert.cpp 输出维度,通常768)
const std::string DB_NAME = "./qa_vector.db"; // SQLite 数据库文件路径
// -------------------------- 工具函数(余弦相似度计算) --------------------------
// 计算两个向量的余弦相似度(值越接近1,语义越相似)
float calc_cosine_similarity(const std::vector<float>& vec1, const std::vector<float>& vec2) {
if (vec1.size() != vec2.size() || vec1.empty()) return 0.0f;
float dot = 0.0f, norm1 = 0.0f, norm2 = 0.0f;
for (size_t i = 0; i < vec1.size(); ++i) {
dot += vec1[i] * vec2[i];
norm1 += vec1[i] * vec1[i];
norm2 += vec2[i] * vec2[i];
}
if (norm1 < 1e-6 || norm2 < 1e-6) return 0.0f; // 避免除零
return dot / (sqrt(norm1) * sqrt(norm2));
}
// -------------------------- SQLite 辅助函数(创建表) --------------------------
// 初始化 SQLite 表(存储向量、文本、权威标记)
int init_sqlite_table(sqlite3* db) {
const char* create_table_sql = R"(
CREATE TABLE IF NOT EXISTS qa_vector (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vec_blob BLOB NOT NULL, -- 向量(二进制存储 float[VEC_DIM])
qa_text TEXT NOT NULL, -- 问答文本(如 "Q:如何退款?A:7天内申请")
is_authority INTEGER NOT NULL -- 是否权威数据(1=权威初始化数据,0=历史对话数据)
);
)";
char* err_msg = nullptr;
int ret = sqlite3_exec(db, create_table_sql, nullptr, nullptr, &err_msg);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Create table failed: " << err_msg << std::endl;
sqlite3_free(err_msg);
return -1;
}
return 0;
}
// -------------------------- 核心函数1:初始化权威问答数据(批量存入) --------------------------
/**
* @brief 初始化:将权威问答数据批量转向量并存入SQLite
* @param qa_list 权威问答列表(每个元素是完整问答文本,如 "Q:如何退款?A:7天内申请")
* @return 0=成功,-1=失败
*/
int init_authority_qa(const std::vector<std::string>& qa_list) {
// 1. 初始化 bert.cpp(加载模型)
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) {
std::cerr << "[BERT Error] Load model failed: " << BERT_MODEL_PATH << std::endl;
return -1;
}
// 2. 打开 SQLite 数据库
sqlite3* db = nullptr;
int ret = sqlite3_open(DB_NAME.c_str(), &db);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Open db failed: " << sqlite3_errmsg(db) << std::endl;
bert_free_model(bert);
return -1;
}
// 3. 创建表(确保表存在)
if (init_sqlite_table(db) != 0) {
sqlite3_close(db);
bert_free_model(bert);
return -1;
}
// 4. 批量插入权威数据(is_authority=1)
const char* insert_sql = R"(
INSERT INTO qa_vector (vec_blob, qa_text, is_authority)
VALUES (?, ?, 1); -- 权威数据标记为1
)";
sqlite3_stmt* stmt = nullptr;
ret = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Prepare insert stmt failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
return -1;
}
// 遍历所有问答,转向量并插入
for (const auto& qa_text : qa_list) {
// a. 用 bert.cpp 生成向量
std::vector<float> vec(VEC_DIM);
if (bert_embed_text(bert, qa_text.c_str(), vec.data(), VEC_DIM) != 0) {
std::cerr << "[BERT Error] Embed text failed: " << qa_text.substr(0, 20) << "..." << std::endl;
continue; // 跳过当前无效文本,继续下一个
}
// b. 绑定参数(向量转BLOB、问答文本)
sqlite3_bind_blob(stmt, 1, vec.data(), VEC_DIM * sizeof(float), SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, qa_text.c_str(), -1, SQLITE_STATIC);
// c. 执行插入
ret = sqlite3_step(stmt);
if (ret != SQLITE_DONE) {
std::cerr << "[SQLite Error] Insert failed: " << sqlite3_errmsg(db) << std::endl;
} else {
std::cout << "[Success] Insert authority QA: " << qa_text.substr(0, 30) << "..." << std::endl;
}
// 重置stmt,准备下一次插入
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
}
// 5. 释放资源
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
std::cout << "[Init Done] Total authority QA inserted: " << qa_list.size() << std::endl;
return 0;
}
// -------------------------- 核心函数2:查询相似问答(返回Top-K结果) --------------------------
/**
* @brief 查询:根据用户问题,检索最相似的问答(优先权威数据)
* @param user_query 用户问题(如 "退款怎么申请?")
* @param top_k 最多返回几条结果
* @param result_list 输出参数:返回的相似问答文本列表(按权威性+相似度排序)
* @return 0=成功,-1=失败
*/
int search_similar_qa(const std::string& user_query, int top_k, std::vector<std::string>& result_list) {
result_list.clear();
if (top_k <= 0) return 0;
// 1. 初始化 bert.cpp 并生成用户问题向量
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) {
std::cerr << "[BERT Error] Load model failed: " << BERT_MODEL_PATH << std::endl;
return -1;
}
std::vector<float> query_vec(VEC_DIM);
if (bert_embed_text(bert, user_query.c_str(), query_vec.data(), VEC_DIM) != 0) {
std::cerr << "[BERT Error] Embed query failed: " << user_query << std::endl;
bert_free_model(bert);
return -1;
}
// 2. 打开 SQLite 数据库并读取所有数据
sqlite3* db = nullptr;
int ret = sqlite3_open(DB_NAME.c_str(), &db);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Open db failed: " << sqlite3_errmsg(db) << std::endl;
bert_free_model(bert);
return -1;
}
// 存储 "相似度+问答文本+是否权威" 的临时结构
struct SimilarItem {
float similarity; // 相似度
std::string text; // 问答文本
int is_authority; // 是否权威
};
std::vector<SimilarItem> similar_items;
// 查询所有数据
const char* select_sql = "SELECT vec_blob, qa_text, is_authority FROM qa_vector;";
sqlite3_stmt* stmt = nullptr;
ret = sqlite3_prepare_v2(db, select_sql, -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Prepare select stmt failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
return -1;
}
// 遍历查询结果,计算相似度
while (sqlite3_step(stmt) == SQLITE_ROW) {
// a. 读取BLOB向量并转成 vector<float>
const void* vec_blob = sqlite3_column_blob(stmt, 0);
int blob_len = sqlite3_column_bytes(stmt, 0);
if (blob_len != VEC_DIM * sizeof(float)) {
std::cerr << "[Error] Invalid vector length, skip" << std::endl;
continue;
}
std::vector<float> db_vec(VEC_DIM);
memcpy(db_vec.data(), vec_blob, blob_len);
// b. 读取问答文本和权威标记
const char* qa_text = (const char*)sqlite3_column_text(stmt, 1);
int is_authority = sqlite3_column_int(stmt, 2);
// c. 计算相似度并加入列表
float sim = calc_cosine_similarity(query_vec, db_vec);
similar_items.push_back({sim, qa_text, is_authority});
}
// 3. 排序:先按"是否权威"降序(权威在前),再按"相似度"降序(相似在前)
std::sort(similar_items.begin(), similar_items.end(), [](const SimilarItem& a, const SimilarItem& b) {
if (a.is_authority != b.is_authority) {
return a.is_authority > b.is_authority; // 权威数据优先
}
return a.similarity > b.similarity; // 相似度高优先
});
// 4. 取Top-K结果
int take_num = std::min(top_k, (int)similar_items.size());
for (int i = 0; i < take_num; ++i) {
result_list.push_back(similar_items[i].text);
std::cout << "[Search Result " << i+1 << "] Sim:" << similar_items[i].similarity
<< " (Auth:" << similar_items[i].is_authority << ") "
<< similar_items[i].text.substr(0, 40) << "..." << std::endl;
}
// 5. 释放资源
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
return 0;
}
// -------------------------- 核心函数3:保存历史对话(用户问题+大模型回答) --------------------------
/**
* @brief 保存:将用户问题+大模型回答作为历史数据存入SQLite(标记为非权威)
* @param user_query 用户问题(如 "退款怎么申请?")
* @param model_answer 大模型回答(如 "退款需在订单页点击'售后'申请")
* @return 0=成功,-1=失败
*/
int save_history_qa(const std::string& user_query, const std::string& model_answer) {
// 1. 拼接历史文本(格式:"Q:用户问题 A:模型回答")
std::string history_text = "Q:" + user_query + " A:" + model_answer;
// 2. 初始化 bert.cpp 并生成向量
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) {
std::cerr << "[BERT Error] Load model failed: " << BERT_MODEL_PATH << std::endl;
return -1;
}
std::vector<float> history_vec(VEC_DIM);
if (bert_embed_text(bert, history_text.c_str(), history_vec.data(), VEC_DIM) != 0) {
std::cerr << "[BERT Error] Embed history failed: " << history_text.substr(0, 20) << "..." << std::endl;
bert_free_model(bert);
return -1;
}
// 3. 打开 SQLite 数据库并插入历史数据(is_authority=0)
sqlite3* db = nullptr;
int ret = sqlite3_open(DB_NAME.c_str(), &db);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Open db failed: " << sqlite3_errmsg(db) << std::endl;
bert_free_model(bert);
return -1;
}
const char* insert_sql = R"(
INSERT INTO qa_vector (vec_blob, qa_text, is_authority)
VALUES (?, ?, 0); -- 历史数据标记为0
)";
sqlite3_stmt* stmt = nullptr;
ret = sqlite3_prepare_v2(db, insert_sql, -1, &stmt, nullptr);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Prepare insert stmt failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
return -1;
}
// 绑定参数并插入
sqlite3_bind_blob(stmt, 1, history_vec.data(), VEC_DIM * sizeof(float), SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, history_text.c_str(), -1, SQLITE_STATIC);
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE) {
std::cout << "[Success] Save history QA: " << history_text.substr(0, 40) << "..." << std::endl;
} else {
std::cerr << "[SQLite Error] Save history failed: " << sqlite3_errmsg(db) << std::endl;
ret = -1;
}
// 4. 释放资源
sqlite3_finalize(stmt);
sqlite3_close(db);
bert_free_model(bert);
return ret == SQLITE_DONE ? 0 : -1;
}
// -------------------------- 测试代码(可直接运行) --------------------------
int main() {
// 1. 测试初始化:批量插入权威问答
std::vector<std::string> authority_qa = {
"Q:如何申请退款?A:在订单详情页点击'售后',选择'退款',提交凭证后3个工作日内处理",
"Q:会员过期了还能退款吗?A:会员过期后无法申请退款,仅支持未过期且购买7天内的会员退款",
"Q:退款多久到账?A:退款审核通过后,1-7个工作日原路返回支付账户"
};
if (init_authority_qa(authority_qa) != 0) {
std::cerr << "Init failed, exit" << std::endl;
return -1;
}
// 2. 测试查询:用户问"退款怎么申请"
std::vector<std::string> search_results;
if (search_similar_qa("退款怎么申请?", 2, search_results) == 0) {
std::cout << "\n[Search Results]:" << std::endl;
for (size_t i = 0; i < search_results.size(); ++i) {
std::cout << i+1 << ". " << search_results[i] << std::endl;
}
}
// 3. 测试保存历史:用户问题+大模型回答
if (save_history_qa("退款怎么申请?", "退款需先在订单页找到'售后'入口,按提示提交退款申请") == 0) {
std::cout << "\n[Save History Done]" << std::endl;
INDEX),数据库会自动对“降维Key”排序; #include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <sqlite3.h>
#include "bert.h"
// -------------------------- 全局配置(不变) --------------------------
const std::string BERT_MODEL_PATH = "./mini-bert.bin";
const int VEC_DIM = 768;
const std::string DB_NAME = "./qa_vector.db";
// -------------------------- 工具函数(新增降维函数) --------------------------
// 降维:高维向量→1维数值(求所有元素的平均值,简单且有效)
float reduce_vector_dim(const std::vector<float>& vec) {
float sum = 0.0f;
for (float val : vec) sum += val;
return sum / vec.size(); // 返回平均值作为“降维Key”
}
// 余弦相似度(不变)
float calc_cosine_similarity(const std::vector<float>& vec1, const std::vector<float>& vec2) {
if (vec1.size() != vec2.size() || vec1.empty()) return 0.0f;
float dot = 0.0f, norm1 = 0.0f, norm2 = 0.0f;
for (size_t i = 0; i < vec1.size(); ++i) {
dot += vec1[i] * vec2[i];
norm1 += vec1[i] * vec1[i];
norm2 += vec2[i] * vec2[i];
}
if (norm1 < 1e-6 || norm2 < 1e-6) return 0.0f;
return dot / (sqrt(norm1) * sqrt(norm2));
}
// -------------------------- SQLite 改进:加降维Key索引(核心!) --------------------------
int init_sqlite_table(sqlite3* db) {
const char* create_table_sql = R"(
CREATE TABLE IF NOT EXISTS qa_vector (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vec_blob BLOB NOT NULL, -- 向量(二进制)
qa_text TEXT NOT NULL, -- 问答文本
is_authority INTEGER NOT NULL,-- 权威标记
dim_reduced REAL NOT NULL -- 降维Key(向量平均值,用于索引)
);
-- 给降维Key建索引!让数据库能快速排序和查找
CREATE INDEX IF NOT EXISTS idx_dim_reduced ON qa_vector(dim_reduced);
)";
char* err_msg = nullptr;
int ret = sqlite3_exec(db, create_table_sql, nullptr, nullptr, &err_msg);
if (ret != SQLITE_OK) {
std::cerr << "[SQLite Error] Create table failed: " << err_msg << std::endl;
sqlite3_free(err_msg);
return -1;
}
return 0;
}
// -------------------------- 核心函数1:初始化权威数据(新增降维Key存储) --------------------------
int init_authority_qa(const std::vector<std::string>& qa_list) {
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) { std::cerr << "[BERT Error] Load failed" << std::endl; return -1; }
sqlite3* db = nullptr;
if (sqlite3_open(DB_NAME.c_str(), &db) != SQLITE_OK) {
std::cerr << "[SQLite Error] Open failed: " << sqlite3_errmsg(db) << std::endl;
bert_free_model(bert); return -1;
}
if (init_sqlite_table(db) != 0) { sqlite3_close(db); bert_free_model(bert); return -1; }
// 插入SQL新增“降维Key”字段
const char* insert_sql = R"(
INSERT INTO qa_vector (vec_blob, qa_text, is_authority, dim_reduced)
VALUES (?, ?, 1, ?); -- 最后一个?是降维Key
)";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) {
std::cerr << "[SQLite Error] Prepare failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert); return -1;
}
for (const auto& qa_text : qa_list) {
std::vector<float> vec(VEC_DIM);
if (bert_embed_text(bert, qa_text.c_str(), vec.data(), VEC_DIM) != 0) { continue; }
// 计算降维Key
float dim_reduced = reduce_vector_dim(vec);
// 绑定参数:新增降维Key
sqlite3_bind_blob(stmt, 1, vec.data(), VEC_DIM * sizeof(float), SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, qa_text.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_double(stmt, 3, dim_reduced); // 绑定降维Key
if (sqlite3_step(stmt) == SQLITE_DONE) {
std::cout << "[Success] Insert QA: " << qa_text.substr(0, 30) << "..." << std::endl;
}
sqlite3_reset(stmt); sqlite3_clear_bindings(stmt);
}
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert);
return 0;
}
// -------------------------- 核心函数2:查询(用索引粗筛,替代全量比对!) --------------------------
int search_similar_qa(const std::string& user_query, int top_k, std::vector<std::string>& result_list) {
result_list.clear();
if (top_k <= 0) return 0;
// 1. 生成用户问题的向量和降维Key
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) { std::cerr << "[BERT Error] Load failed" << std::endl; return -1; }
std::vector<float> query_vec(VEC_DIM);
if (bert_embed_text(bert, user_query.c_str(), query_vec.data(), VEC_DIM) != 0) {
bert_free_model(bert); return -1;
}
float query_dim_reduced = reduce_vector_dim(query_vec); // 用户问题的降维Key
// 2. 数据库查询:用索引找“降维Key最接近”的N条数据(粗筛,避免全量比对)
sqlite3* db = nullptr;
if (sqlite3_open(DB_NAME.c_str(), &db) != SQLITE_OK) {
std::cerr << "[SQLite Error] Open failed: " << sqlite3_errmsg(db) << std::endl;
bert_free_model(bert); return -1;
}
// 关键SQL:用索引idx_dim_reduced,找降维Key在[query_dim_reduced-0.1, query_dim_reduced+0.1]的记录
// 0.1是“范围阈值”,可调整(越小筛得越准,越少比对;越大越全,越多比对)
const char* select_sql = R"(
SELECT vec_blob, qa_text, is_authority
FROM qa_vector
WHERE dim_reduced BETWEEN ? AND ?
ORDER BY ABS(dim_reduced - ?) ASC -- 按降维Key相似度排序,取最接近的
LIMIT 100; -- 最多粗筛100条,避免比对太多
)";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, select_sql, -1, &stmt, nullptr) != SQLITE_OK) {
std::cerr << "[SQLite Error] Prepare failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert); return -1;
}
// 绑定参数:查询范围(query_dim_reduced±0.1)、目标降维Key
sqlite3_bind_double(stmt, 1, query_dim_reduced - 0.1);
sqlite3_bind_double(stmt, 2, query_dim_reduced + 0.1);
sqlite3_bind_double(stmt, 3, query_dim_reduced);
// 3. 只对粗筛出的100条数据算余弦相似度(替代全量比对)
struct SimilarItem { float similarity; std::string text; int is_authority; };
std::vector<SimilarItem> similar_items;
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 读取向量
const void* vec_blob = sqlite3_column_blob(stmt, 0);
int blob_len = sqlite3_column_bytes(stmt, 0);
if (blob_len != VEC_DIM * sizeof(float)) { continue; }
std::vector<float> db_vec(VEC_DIM);
memcpy(db_vec.data(), vec_blob, blob_len);
// 计算余弦相似度(只算粗筛后的100条)
float sim = calc_cosine_similarity(query_vec, db_vec);
similar_items.push_back({
sim,
(const char*)sqlite3_column_text(stmt, 1),
sqlite3_column_int(stmt, 2)
});
}
// 4. 排序+取Top-K(不变)
std::sort(similar_items.begin(), similar_items.end(), [](const SimilarItem& a, const SimilarItem& b) {
if (a.is_authority != b.is_authority) return a.is_authority > b.is_authority;
return a.similarity > b.similarity;
});
int take_num = std::min(top_k, (int)similar_items.size());
for (int i = 0; i < take_num; ++i) {
result_list.push_back(similar_items[i].text);
}
// 释放资源
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert);
return 0;
}
// -------------------------- 核心函数3:保存历史数据(新增降维Key存储,和初始化逻辑一致) --------------------------
int save_history_qa(const std::string& user_query, const std::string& model_answer) {
std::string history_text = "Q:" + user_query + " A:" + model_answer;
bert_model* bert = bert_load_model(BERT_MODEL_PATH.c_str());
if (!bert) { return -1; }
std::vector<float> history_vec(VEC_DIM);
if (bert_embed_text(bert, history_text.c_str(), history_vec.data(), VEC_DIM) != 0) {
bert_free_model(bert); return -1;
}
float dim_reduced = reduce_vector_dim(history_vec); // 计算降维Key
sqlite3* db = nullptr;
if (sqlite3_open(DB_NAME.c_str(), &db) != SQLITE_OK) {
bert_free_model(bert); return -1;
}
const char* insert_sql = R"(
INSERT INTO qa_vector (vec_blob, qa_text, is_authority, dim_reduced)
VALUES (?, ?, 0, ?); -- 历史数据标记0,加降维Key
)";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) {
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert); return -1;
}
sqlite3_bind_blob(stmt, 1, history_vec.data(), VEC_DIM * sizeof(float), SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, history_text.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_double(stmt, 3, dim_reduced); // 绑定降维Key
int ret = sqlite3_step(stmt) == SQLITE_DONE ? 0 : -1;
sqlite3_finalize(stmt); sqlite3_close(db); bert_free_model(bert);
return ret;
}
// -------------------------- 测试代码 --------------------------
int main() {
// 1. 初始化权威数据
std::vector<std::string> authority_qa = {
"Q:如何申请退款?A:订单页点击'售后'→'退款',3工作日内处理",
"Q:会员过期能退款吗?A:仅支持未过期且7天内购买的会员退款",
"Q:退款到账时间?A:审核通过后1-7工作日原路返回"
};
init_authority_qa(authority_qa);
// 2. 查询(此时只比对粗筛后的100条,而非全量)
std::vector<std::string> results;
search_similar_qa("退款怎么申请?", 2, results);
std::cout << "\n查询结果:" << std::endl;
for (size_t i = 0; i < results.size(); ++i) {
std::cout << i+1 << ". " << results[i] << std::endl;
}
// 3. 保存历史
save_history_qa("退款怎么申请?", "先找订单页'售后'入口,再提交退款");
return 0;
}
sqlite-vector),直接在数据库里创建向量索引(基于 IVF、HNSW 等轻量级算法),避免全量计算余弦相似度; bert.cpp) 重构代码,保留你要的“初始化、查询、保存”3个核心函数,同时去掉SQLite(FAISS本身负责向量存储+检索,效率更高)。先解决你的疑问,再给代码:bert.cpp(纯C++、无依赖、轻量)来做“文本→向量”,直接编译成静态库,比ONNX Runtime简单10倍,不用额外部署。apt能直接装(sudo apt install libfaiss-dev),不用自己编译;如果想定制,源码编译也只有3步(之前教过,很简单)。 libfaiss.so,没有“大 runtime”的负担。bert.cpp是C++环境下“文本转向量”的最优选择之一: all-MiniLM.cpp(更轻),但bert.cpp生态更成熟,这里就用它。bert.cpp:只做一件事——把“文本(问答/用户问题)”转成“768维向量”; unordered_map存“FAISS的向量ID → 原始文本+权威性标记”,方便检索后找回文本。# 1. 安装FAISS(CPU版)
sudo apt update && sudo apt install -y libfaiss-dev
# 2. 下载bert.cpp(文本转向量工具),编译成静态库
git clone https://github.com/osmr/bert.cpp.git
cd bert.cpp && mkdir build && cd build
cmake -DBUILD_SHARED_LIBS=OFF .. # 编译静态库
make -j4
# 编译后会生成 libbert.a(静态库),把它和 include/bert.h 拷贝到你的项目目录
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <faiss/IndexIVFFlat.h> // FAISS的IVF索引(适合中小数据量)
#include <faiss/IndexFlat.h> // FAISS的Flat索引(小数据量用)
#include "bert.h" // 来自bert.cpp的头文件
// -------------------------- 全局配置(根据你的路径修改) --------------------------
const std::string BERT_MODEL_PATH = "./bert-base-uncased.bin"; // bert.cpp的模型文件
const std::string BERT_VOCAB_PATH = "./vocab.txt"; // bert.cpp的词表文件
const int VEC_DIM = 768; // 向量维度(bert输出固定768)
const int FAISS_N_CLUSTERS = 100; // FAISS聚类数(数据量越大,设越大)
// -------------------------- 全局变量(FAISS索引+映射表) --------------------------
faiss::Index* g_faiss_index = nullptr; // FAISS索引(支持不同类型,如IVF、Flat)
// 映射表:FAISS的向量ID → {原始文本, 权威性标记(0=历史,1=权威)}
std::unordered_map<faiss::idx_t, std::pair<std::string, int>> g_id_to_text;
faiss::idx_t g_next_id = 0; // 下一个待分配的向量ID(自增)
bert_model* g_bert_model = nullptr; // bert.cpp模型(全局唯一,避免重复加载)
// -------------------------- 工具函数:初始化bert.cpp模型(只调用1次) --------------------------
int init_bert_model() {
if (g_bert_model != nullptr) return 0; // 已初始化,直接返回
// 加载bert模型和词表
g_bert_model = bert_load_model(BERT_MODEL_PATH.c_str(), BERT_VOCAB_PATH.c_str());
if (g_bert_model == nullptr) {
std::cerr << "[Error] bert_load_model failed (path: " << BERT_MODEL_PATH << ")" << std::endl;
return -1;
}
std::cout << "[Success] bert model loaded" << std::endl;
return 0;
}
// -------------------------- 工具函数:初始化FAISS索引(只调用1次) --------------------------
int init_faiss_index() {
if (g_faiss_index != nullptr) return 0; // 已初始化,直接返回
// 1. 先创建“训练集”(用前1000条向量训练聚类中心,若数据少,直接用Flat索引)
std::vector<float> train_vecs;
bool need_train = true;
if (g_id_to_text.size() < FAISS_N_CLUSTERS) {
// 数据量太少,不用IVF索引,直接用Flat索引(全量计算,精度100%)
g_faiss_index = new faiss::IndexFlatL2(VEC_DIM); // L2距离(和余弦相似度趋势一致)
need_train = false;
std::cout << "[Info] Use FAISS IndexFlatL2 (small data)" << std::endl;
} else {
// 数据量够,用IVF索引(先聚类,再检索,速度快)
faiss::IndexFlatL2* quantizer = new faiss::IndexFlatL2(VEC_DIM);
g_faiss_index = new faiss::IndexIVFFlat(
quantizer, VEC_DIM, FAISS_N_CLUSTERS, faiss::METRIC_L2
);
// 提取前1000条向量当训练集
train_vecs.reserve(1000 * VEC_DIM);
for (auto it = g_id_to_text.begin(); it != g_id_to_text.end() && train_vecs.size() < 1000*VEC_DIM; ++it) {
// 这里假设已提前把向量存在另一个映射表g_id_to_vec(实际项目需补充,见下方注释)
// train_vecs.insert(train_vecs.end(), g_id_to_vec[it->first].begin(), g_id_to_vec[it->first].end());
}
std::cout << "[Info] Use FAISS IndexIVFFlat (large data)" << std::endl;
}
// 2. 训练IVF索引(Flat索引不用训练)
if (need_train && !train_vecs.empty()) {
((faiss::IndexIVFFlat*)g_faiss_index)->train(train_vecs.size()/VEC_DIM, train_vecs.data());
((faiss::IndexIVFFlat*)g_faiss_index)->nprobe = 10; // 检索时查10个聚类,平衡速度和精度
std::cout << "[Success] FAISS index trained" << std::endl;
}
std::cout << "[Success] FAISS index initialized" << std::endl;
return 0;
}
// -------------------------- 核心函数1:初始化权威问答(批量导入FAISS) --------------------------
/**
* @brief 初始化:导入权威问答,转向量后存入FAISS
* @param authority_qa 权威问答列表(每个元素是完整问答文本,如"Q:如何退款?A:xxx")
* @return 0=成功,-1=失败
*/
int init_authority_qa(const std::vector<std::string>& authority_qa) {
// 1. 初始化bert和FAISS(确保全局唯一)
if (init_bert_model() != 0) return -1;
// 2. 遍历问答,转向量并暂存(批量插入FAISS效率更高)
std::vector<float> all_vecs; // 所有向量(连续内存,FAISS要求)
std::vector<faiss::idx_t> all_ids; // 所有向量ID
std::vector<std::pair<std::string, int>> all_texts; // 所有文本+权威标记
for (const auto& qa_text : authority_qa) {
// a. 用bert转向量
std::vector<float> vec(VEC_DIM);
if (bert_embed_text(g_bert_model, qa_text.c_str(), vec.data(), VEC_DIM) != 0) {
std::cerr << "[Error] bert_embed_text failed (text: " << qa_text.substr(0, 20) << "...)" << std::endl;
continue;
}
// b. 暂存向量、ID、文本
all_vecs.insert(all_vecs.end(), vec.begin(), vec.end());
all_ids.push_back(g_next_id);
all_texts.emplace_back(qa_text, 1); // 权威标记=1
g_next_id++;
}
// 3. 批量插入FAISS
if (!all_vecs.empty()) {
// 初始化FAISS(若未初始化,此时创建Flat索引,后续数据多了可再升级)
if (g_faiss_index == nullptr) {
g_faiss_index = new faiss::IndexFlatL2(VEC_DIM);
std::cout << "[Info] FAISS IndexFlatL2 created for init" << std::endl;
}
// 插入FAISS(参数:向量数,向量数据,向量ID)
g_faiss_index->add_with_ids(
all_vecs.size()/VEC_DIM, // 向量总数
all_vecs.data(), // 所有向量(连续内存)
all_ids.data() // 所有向量ID(自定义,避免FAISS自动分配)
);
// 更新映射表
for (size_t i = 0; i < all_ids.size(); ++i) {
g_id_to_text[all_ids[i]] = all_texts[i];
}
std::cout << "[Success] Init authority QA: " << all_ids.size() << " items" << std::endl;
}
return 0;
}
// -------------------------- 核心函数2:查询相似问答(FAISS负责检索+算相似度) --------------------------
/**
* @brief 查询:用户问题转向量,用FAISS检索相似问答(优先权威数据)
* @param user_query 用户问题(如"退款怎么申请?")
* @param top_k 最多返回几条结果
* @param results 输出:相似问答文本列表(按权威性+相似度排序)
* @return 0=成功,-1=失败
*/
int search_similar_qa(const std::string& user_query, int top_k, std::vector<std::string>& results) {
results.clear();
if (top_k <= 0 || g_faiss_index == nullptr || g_bert_model == nullptr) return -1;
// 1. 用bert把用户问题转向量
std::vector<float> query_vec(VEC_DIM);
if (bert_embed_text(g_bert_model, user_query.c_str(), query_vec.data(), VEC_DIM) != 0) {
std::cerr << "[Error] bert_embed_text failed (query: " << user_query << ")" << std::endl;
return -1;
}
// 2. 用FAISS检索(核心步骤!FAISS内置算相似度+排序)
std::vector<float> distances(top_k); // 存储相似度距离(L2距离,越小越相似)
std::vector<faiss::idx_t> ids(top_k); // 存储检索到的向量ID
g_faiss_index->search(
1, // 查询向量数(1个问题)
query_vec.data(), // 查询向量
top_k, // 返回Top-K结果
distances.data(), // 输出:距离
ids.data() // 输出:匹配的向量ID
);
// 3. 按“权威性+相似度”排序(FAISS已按距离排序,再补权威性优先级)
struct ResultItem {
std::string text;
int authority;
float distance; // L2距离,越小越相似
};
std::vector<ResultItem> items;
for (int i = 0; i < top_k; ++i) {
faiss::idx_t vec_id = ids[i];
auto it = g_id_to_text.find(vec_id);
if (it == g_id_to_text.end()) continue; // 异常ID,跳过
items.emplace_back(ResultItem{
it->second.first, // 原始文本
it->second.second, // 权威性标记
distances[i] // 距离
});
}
// 排序:先按权威性(1在前),再按距离(小在前)
std::sort(items.begin(), items.end(), [](const ResultItem& a, const ResultItem& b) {
if (a.authority != b.authority) {
return a.authority > b.authority;
}
return a.distance < b.distance;
});
// 4. 整理输出结果
for (const auto& item : items) {
results.push_back(item.text);
std::cout << "[Search Result] Auth:" << item.authority
<< " Distance:" << item.distance
<< " Text:" << item.text.substr(0, 40) << "..." << std::endl;
}
return 0;
}
// -------------------------- 核心函数3:保存历史对话(用户问题+大模型回答) --------------------------
/**
* @brief 保存:将用户问题+大模型回答转向量,存入FAISS(标记为历史数据)
* @param user_query 用户问题
* @param model_answer 大模型回答
* @return 0=成功,-1=失败
*/
int save_history_qa(const std::string& user_query, const std::string& model_answer) {
if (g_faiss_index == nullptr || g_bert_model == nullptr) return -1;
// 1. 拼接历史文本(格式:Q:xxx A:xxx)
std::string history_text = "Q:" + user_query + " A:" + model_answer;
// 2. 用bert转向量
std::vector<float> history_vec(VEC_DIM);
if (bert_embed_text(g_bert_model, history_text.c_str(), history_vec.data(), VEC_DIM) != 0) {
std::cerr << "[Error] bert_embed_text failed (history: " << history_text.substr(0, 20) << "...)" << std::endl;
return -1;
}
// 3. 插入FAISS(单条插入,FAISS支持)
faiss::idx_t current_id = g_next_id;
g_faiss_index->add_with_ids(
1, // 1个向量
history_vec.data(), // 向量数据
¤t_id // 自定义ID
);
// 4. 更新映射表(历史数据标记=0)
g_id_to_text[current_id] = {history_text, 0};
g_next_id++;
std::cout << "[Success] Save history QA (ID:" << current_id << "): " << history_text.substr(0, 40) << "..." << std::endl;
return 0;
}
// -------------------------- 工具函数:释放资源(程序退出时调用) --------------------------
void release_resources() {
if (g_faiss_index != nullptr) {
delete g_faiss_index;
g_faiss_index = nullptr;
}
if (g_bert_model != nullptr) {
bert_free_model(g_bert_model);
g_bert_model = nullptr;
}
g_id_to_text.clear();
std::cout << "[Success] Resources released" << std::endl;
}
// -------------------------- 测试代码(直接运行) --------------------------
int main() {
// 1. 初始化权威问答(模拟3条企业FAQ)
std::vector<std::string> authority_qa = {
"Q:如何申请退款?A:在订单详情页点击'售后',选择'退款',提交凭证后3个工作日内处理",
"Q:会员过期了还能退款吗?A:会员过期后无法申请退款,仅支持未过期且购买7天内的会员退款",
"Q:退款多久到账?A:退款审核通过后,1-7个工作日原路返回支付账户"
};
if (init_author
add_with_ids接口,将“向量+自定义ID”存入FAISS。// 初始化权威FAQ
int init_authority_faq(const vector<string>& faq_list) {
// 1. 加载bert模型(全局唯一)
bert_model* bert = bert_load_model("bert.bin", "vocab.txt");
// 2. 初始化FAISS(小数据用Flat索引)
faiss::Index* faiss_idx = new faiss::IndexFlatL2(768);
// 3. 遍历FAQ,转向量并入库
for (auto& faq : faq_list) {
float vec[768];
bert_embed_text(bert, faq.c_str(), vec, 768); // 生成向量
faiss_idx->add_with_ids(1, vec, ¤t_id); // 存入FAISS
id_to_data[current_id] = {faq, 1}; // 记录ID→文本+优先级
current_id++;
}
return 0;
}
search接口,传入“问题向量+Top-K数量”,获取“相似向量的ID+距离(距离越小越相似)”。// 检索相似数据
vector<string> search_similar(const string& user_query, int top_k) {
// 1. 问题转向量
float query_vec[768];
bert_embed_text(bert, user_query.c_str(), query_vec, 768);
// 2. FAISS检索(返回ID和距离)
vector<float> distances(top_k);
vector<faiss::idx_t> ids(top_k);
faiss_idx->search(1, query_vec, top_k, distances.data(), ids.data());
// 3. 按优先级+距离排序
vector<Result> results;
for (int i=0; i<top_k; i++) {
auto data = id_to_data[ids[i]];
results.push_back({data.text, data.priority, distances[i]});
}
sort(results.begin(), results.end(), [](auto& a, auto& b) {
if (a.priority != b.priority) return a.priority > b.priority;
return a.distance < b.distance;
});
// 4. 整理返回结果
vector<string> res_text;
for (auto& r : results) res_text.push_back(r.text);
return res_text;
}
add_with_ids接口存入。// 保存历史对话
int save_history(const string& user_query, const string& model_answer) {
// 1. 合并历史文本
string history = "Q:" + user_query + " A:" + model_answer;
// 2. 生成向量
float vec[768];
bert_embed_text(bert, history.c_str(), vec, 768);
// 3. 存入FAISS并记录映射
faiss_idx->add_with_ids(1, vec, ¤t_id);
id_to_data[current_id] = {history, 0};
current_id++;
return 0;
}
graph TD
A[用户发送问题] --> B[调用search_similar函数,检索权威FAQ+历史对话]
B --> C[将检索结果+用户问题作为上下文,传给轻量级开源大模型]
C --> D[大模型生成回答,返回给用户]
D --> E[调用save_history函数,将“用户问题+模型回答”存入FAISS]
F[企业新增FAQ] --> G[调用init_authority_faq函数,导入新FAQ]
bert-base-chinese),并自行调整分词逻辑(需修改代码中 bert_tokenize 相关的分词规则)。langchain 中的中文嵌入模型。models/download-ggml.py从 Hugging Face Hub 的skeskinen/ggml仓库下载模型,也支持通过convert-to-ggml.py将 Hugging Face 上的 SentenceTransformers 模型转换为 ggml 格式。因此,替代方案可以围绕这两种方式展开。bert-base-chinese等中文模型),可以使用项目中的convert-to-ggml.py进行转换,避免从 Hugging Face 下载。需要说明转换的步骤,包括克隆模型、运行转换脚本等。skeskinen/ggml仓库(通过models/download-ggml.py可知),如果直接下载失败,可以:all-MiniLM-L6-v2),下载需要的量化版本(如ggml-model-q4_0.bin)models/[模型名]/目录下(例如models/all-MiniLM-L6-v2/)sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2或bert-base-chinese)手动转换为ggml格式,步骤如下:models/paraphrase-multilingual-MiniLM-L12-v2/目录下。# 进入models目录
cd models
# 转换为f32格式(若需要其他格式,后续可量化)
python3 convert-to-ggml.py paraphrase-multilingual-MiniLM-L12-v2 0
# (可选)量化为4bit格式
../build/bin/quantize paraphrase-multilingual-MiniLM-L12-v2/ggml-model-f16.bin paraphrase-multilingual-MiniLM-L12-v2/ggml-model-q4_0.bin 2
convert-to-ggml.py会自动处理模型权重,生成bert.cpp可识别的格式。git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
transformers库(项目requirements.txt中包含),用于加载原模型权重。bert_tokenize相关代码(需了解项目分词实现)。skeskinen/ggml仓库(通过models/download-ggml.py可知),如果直接下载失败,可以:all-MiniLM-L6-v2),下载需要的量化版本(如ggml-model-q4_0.bin)models/[模型名]/目录下(例如models/all-MiniLM-L6-v2/)sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2或bert-base-chinese)手动转换为ggml格式,步骤如下:models/paraphrase-multilingual-MiniLM-L12-v2/目录下。# 进入models目录
cd models
# 转换为f32格式(若需要其他格式,后续可量化)
python3 convert-to-ggml.py paraphrase-multilingual-MiniLM-L12-v2 0
# (可选)量化为4bit格式
../build/bin/quantize paraphrase-multilingual-MiniLM-L12-v2/ggml-model-f16.bin paraphrase-multilingual-MiniLM-L12-v2/ggml-model-q4_0.bin 2
convert-to-ggml.py会自动处理模型权重,生成bert.cpp可识别的格式。git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
transformers库(项目requirements.txt中包含),用于加载原模型权重。bert_tokenize相关代码(需了解项目分词实现)。sample_dylib.py)可知,程序加载的是 sample_client_texts.txt 中的文本作为“候选库”,而该文件中的内容大概率是英文(例如示例中测试查询是英文,返回的也是英文医疗相关文本)。因此,即使你输入中文,模型也只会从英文候选库中找相似文本,导致结果都是英文。这是测试数据的局限性,而非模型完全不支持中文。bert_tokenize 函数)会将中文句子切分成单个字符或不合理的子词,导致嵌入向量(embedding)不准确。 all-MiniLM-L6-v2 模型是英文预训练的,其词汇表(vocab)中中文词极少,大量中文会被识别为未知 token([UNK]),进一步影响效果。sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2(多语言模型,支持中文) hfl/chinese-macbert-base(中文专用BERT) shibing624/text2vec-base-chinese(中文句子嵌入模型)git clone https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
transformers 依赖): cd models
python3 convert-to-ggml.py paraphrase-multilingual-MiniLM-L12-v2 0 # 0表示转换为f32格式
../build/bin/quantize paraphrase-multilingual-MiniLM-L12-v2/ggml-model-f16.bin paraphrase-multilingual-MiniLM-L12-v2/ggml-model-q4_0.bin 2
bert_tokenize 函数对中文分词无效,需修改代码以支持中文: jieba 分词库),将中文句子切分成词语。 bert_tokenize 函数,适配中文词语的子词分割逻辑。// 在bert_tokenize函数中,对中文文本先分词
std::string str = bert_normalize_prompt(str);
std::vector<std::string> words;
// 新增:如果是中文,使用jieba分词(需集成jieba库)
if (is_chinese(str)) {
words = jieba.cut(str); // 切分成中文词语
} else {
// 原有的英文分词逻辑(正则分割)
std::regex re(R"([[:punct:]]|[[:alpha:]]+|[[:digit:]]+)");
// ... 现有代码 ...
}
sample_client_texts.txt 替换为中文文本(例如中文问答、新闻句子),确保候选库是中文,此时输入中文查询就能返回中文结果。 paraphrase-multilingual-MiniLM),其词汇表包含中文,无需额外修改词汇表。 sample_dylib.py)可知,程序加载的是 sample_client_texts.txt 中的文本作为“候选库”,而该文件中的内容大概率是英文(例如示例中测试查询是英文,返回的也是英文医疗相关文本)。因此,即使你输入中文,模型也只会从英文候选库中找相似文本,导致结果都是英文。这是测试数据的局限性,而非模型完全不支持中文。bert_tokenize 函数)会将中文句子切分成单个字符或不合理的子词,导致嵌入向量(embedding)不准确。 all-MiniLM-L6-v2 模型是英文预训练的,其词汇表(vocab)中中文词极少,大量中文会被识别为未知 token([UNK]),进一步影响效果。sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2(多语言模型,支持中文) hfl/chinese-macbert-base(中文专用BERT) shibing624/text2vec-base-chinese(中文句子嵌入模型)git clone https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
transformers 依赖): cd models
python3 convert-to-ggml.py paraphrase-multilingual-MiniLM-L12-v2 0 # 0表示转换为f32格式
../build/bin/quantize paraphrase-multilingual-MiniLM-L12-v2/ggml-model-f16.bin paraphrase-multilingual-MiniLM-L12-v2/ggml-model-q4_0.bin 2
bert_tokenize 函数对中文分词无效,需修改代码以支持中文: jieba 分词库),将中文句子切分成词语。 bert_tokenize 函数,适配中文词语的子词分割逻辑。// 在bert_tokenize函数中,对中文文本先分词
std::string str = bert_normalize_prompt(str);
std::vector<std::string> words;
// 新增:如果是中文,使用jieba分词(需集成jieba库)
if (is_chinese(str)) {
words = jieba.cut(str); // 切分成中文词语
} else {
// 原有的英文分词逻辑(正则分割)
std::regex re(R"([[:punct:]]|[[:alpha:]]+|[[:digit:]]+)");
// ... 现有代码 ...
}
sample_client_texts.txt 替换为中文文本(例如中文问答、新闻句子),确保候选库是中文,此时输入中文查询就能返回中文结果。 paraphrase-multilingual-MiniLM),其词汇表包含中文,无需额外修改词汇表。 huggingface_hub库,步骤如下:sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2或skeskinen/ggml),进入模型主页。bert.cpp的预转换模型(如all-MiniLM-L6-v2):从skeskinen/ggml仓库下载对应量化版本(如ggml-model-q4_0.bin),放在项目的models/[模型名]/目录(例如models/all-MiniLM-L6-v2/)。 shibing624/text2vec-base-chinese):下载模型核心文件,包括config.json、pytorch_model.bin(或model.safetensors)、tokenizer.json、vocab.txt等,放在一个单独的文件夹(如models/text2vec-base-chinese/)。bert.cpp的models/[模型名]/目录。https://mirrors.tuna.tsinghua.edu.cn/hugging-face-models/[用户名]/[模型名]/resolve/main/[文件名]; sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2的config.json: wget https://mirrors.tuna.tsinghua.edu.cn/hugging-face-models/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2/resolve/main/config.json
modelscope库下载。bert.cpp的操作skeskinen/ggml中的文件)直接将下载的ggml-model-q4_0.bin等文件放入models/[模型名]/,即可通过sample_dylib.py等脚本加载使用。# 进入models目录
cd models
# 转换为f32格式
python3 convert-to-ggml.py text2vec-base-chinese 0
# 量化为4bit
../build/bin/quantize text2vec-base-chinese/ggml-model-f16.bin text2vec-base-chinese/ggml-model-q4_0.bin 2
text2vec-base-chinese),并注意适配分词逻辑(如前所述)。从Hugging Face下载模型遇到困难时,手动下载和国内镜像是两种可靠的替代方案,具体操作如下:huggingface_hub库,步骤如下:sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2或skeskinen/ggml),进入模型主页。bert.cpp的预转换模型(如all-MiniLM-L6-v2):从skeskinen/ggml仓库下载对应量化版本(如ggml-model-q4_0.bin),放在项目的models/[模型名]/目录(例如models/all-MiniLM-L6-v2/)。 shibing624/text2vec-base-chinese):下载模型核心文件,包括config.json、pytorch_model.bin(或model.safetensors)、tokenizer.json、vocab.txt等,放在一个单独的文件夹(如models/text2vec-base-chinese/)。bert.cpp的models/[模型名]/目录。https://mirrors.tuna.tsinghua.edu.cn/hugging-face-models/[用户名]/[模型名]/resolve/main/[文件名]; sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2的config.json: wget https://mirrors.tuna.tsinghua.edu.cn/hugging-face-models/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2/resolve/main/config.json
modelscope库下载。bert.cpp的操作skeskinen/ggml中的文件)直接将下载的ggml-model-q4_0.bin等文件放入models/[模型名]/,即可通过sample_dylib.py等脚本加载使用。# 进入models目录
cd models
# 转换为f32格式
python3 convert-to-ggml.py text2vec-base-chinese 0
# 量化为4bit
../build/bin/quantize text2vec-base-chinese/ggml-model-f16.bin text2vec-base-chinese/ggml-model-q4_0.bin 2
text2vec-base-chinese),并注意适配分词逻辑(如前所述)。bert-base-chinese Tokenizer),才能拆出正确的token——而这个Tokenizer的“拆分规则”,是和预训练模型绑定的(模型是基于这种拆分规则训练的,换规则会导致向量语义错乱)。bert-base-chinese、hfl/chinese-roberta-wwm-ext等),步骤很简单:convert.py),将中文模型转换成bert.cpp支持的ggml格式(比如ggml-model-q4_0.bin);transformers库先对中文句子做分词编码,再传给bert.cpp计算向量)。bert-base-chinese、hfl/chinese-roberta-wwm-ext)是 专门针对中文预训练的,它自带两个关键能力,直接解决“中文支持”问题: bert-base-chinese),而非英文模型(如 all-MiniLM-L6-v2),它就支持中文; transformers 库的 BertTokenizer)先对中文句子做分词编码,再把编码结果传给 bert.cpp 计算向量,这样就不会出现“中文匹配英文结果”的情况。transformers.BertTokenizer.from_pretrained("bert-base-chinese") 把中文句子(如“今天天气好”)转成 Token ID; bert-base-chinese 模型(需先转成 bert.cpp 支持的 ggml 格式); convert.py脚本,这是关键命令的基础。--model_type bert,以及量化参数(如--quantize q4_0,这是常见的轻量化选择,适合用户使用场景)。pip install命令,避免运行脚本时缺包报错。convert.py在 bert.cpp 的scripts目录下(不同版本可能在根目录,需说明常见位置),避免用户找不到脚本。整体语言要简洁,步骤清晰,让用户能直接跟着操作。convert.py 脚本,具体步骤和命令如下:git clone https://github.com/ggerganov/bert.cpp.git
cd bert.cpp # 进入项目根目录
torch和transformers): pip install torch transformers sentencepiece
python scripts/convert.py 你的模型文件夹路径 --outfile 输出的GGML模型文件名 --model_type bert --quantize q4_0
scripts/convert.py:bert.cpp官方提供的转换脚本(部分旧版本脚本在项目根目录,若找不到可直接用 python convert.py); 你的模型文件夹路径:替换为你下载的“WacToTxt Dash China”模型文件夹路径(需包含config.json、pytorch_model.bin等文件); --outfile 输出的GGML模型文件名:指定转换后GGML文件的保存路径+名称,例如 ./models/chinese-model-ggml-q4_0.bin; --model_type bert:明确模型类型为BERT(中文模型几乎都是BERT类,若你的模型是RoBERTa,可改为roberta); --quantize q4_0:指定量化等级(可选,q4_0是最常用的轻量化选项,兼顾速度和效果;若要更高精度,可去掉该参数,生成未量化的GGML模型,但文件更大)。./downloads/WacToTxt-Dash-China,想把转换后的GGML模型保存到 ./models/chinese-ggml.bin,命令就是: python scripts/convert.py ./downloads/WacToTxt-Dash-China --outfile ./models/chinese-ggml.bin --model_type bert --quantize q4_0
pytorch_model.bin)就是 PyTorch 格式的模型权重文件,而 bert.cpp 的 convert.py 脚本本身就是为处理这类 PyTorch 格式的 BERT 类模型设计的,核心适配没有问题。PyTorch Model.bin(权重文件),还需要模型的“配置”和“分词器”文件,才能正确解析模型结构和中文处理逻辑。请先检查你的模型文件夹,确保包含:PyTorch Model.bin(若文件名有空格,命令中路径需用引号包裹,如 "PyTorch Model.bin");config.json(记录模型层数、隐藏层维度等核心结构,脚本靠它判断模型架构);tokenizer_config.json 和 vocab.txt(或 tokenizer.json),负责中文分词,是处理中文的关键。./models/Text2Vec-Dash-Chinese(里面包含 PyTorch Model.bin、config.json、vocab.txt 等),想输出名为 chinese-text2vec-ggml-q4_0.bin 的 GGML 模型,命令如下(注意处理文件名空格):# 进入 bert.cpp 项目根目录后执行
python scripts/convert.py ./models/Text2Vec-Dash-Chinese \
--outfile ./models/chinese-text2vec-ggml-q4_0.bin \
--model_type bert \ # 若模型是 RoBERTa 类,改为 --model_type roberta
--quantize q4_0
PyTorch Model.bin 文件名确实带空格(非标准命名),且直接放在文件夹根目录,命令中可补充指定权重文件路径(可选,脚本默认会自动找 pytorch_model.bin,找不到时才需手动指定):python scripts/convert.py ./models/Text2Vec-Dash-Chinese \
--model_file ./models/Text2Vec-Dash-Chinese/"PyTorch Model.bin" \ # 手动指定带空格的权重文件
--outfile ./models/chinese-text2vec-ggml-q4_0.bin \
--model_type bert \
--quantize q4_0
convert.py 脚本,且当前主要适配 GGUF 格式(GGML 的升级版本,目前是 bert.cpp 的主流格式),相关转换逻辑已整合到更通用的 GGUF 生态工具中。无需单独找旧 GGML 项目,直接用 bert.cpp 最新文档推荐的工具即可转换,具体方案如下:gguf-py 库(GGUF 格式的官方 Python 工具)或 transformers 结合 bert.cpp 辅助逻辑 完成转换,步骤更简洁,且原生支持中文 BERT/Text2Vec 类模型。pip install gguf transformers torch sentencepiece
convert_to_gguf.py),复制以下代码,只需修改 模型路径 和 输出路径 即可运行。代码会自动读取你的 PyTorch Model.bin 和 config.json,生成 bert.cpp 可用的 GGUF 模型。from gguf import GGUFWriter
from transformers import AutoModel, AutoConfig
import torch
# -------------------------- 你需要修改的参数 --------------------------
MODEL_DIR = "./models/Text2Vec-Dash-Chinese" # 你的中文模型文件夹路径(含PyTorch Model.bin)
OUTPUT_GGUF_PATH = "./models/text2vec-chinese.gguf" # 输出的GGUF模型路径
MODEL_TYPE = "bert" # 模型类型:中文Text2Vec一般是bert/roberta,无需改
QUANTIZE = "q4_0" # 量化等级(q4_0最常用,也可填"f16"保留高精度)
# ----------------------------------------------------------------------
# 1. 加载中文模型和配置
config = AutoConfig.from_pretrained(MODEL_DIR)
model = AutoModel.from_pretrained(MODEL_DIR, torch_dtype=torch.float16) # 用float16减少内存占用
# 2. 初始化GGUF写入器
with GGUFWriter(OUTPUT_GGUF_PATH, model.config.vocab_size) as writer:
# 写入模型元信息(让bert.cpp识别模型类型)
writer.add_metadata({"model_type": MODEL_TYPE, "quantize": QUANTIZE})
# 写入模型权重(自动处理PyTorch权重格式)
for name, param in model.state_dict().items():
# 转换权重为GGUF兼容格式(去除PyTorch的"module."前缀,若有)
gguf_name = name.replace("module.", "")
writer.add_tensor(gguf_name, param.cpu().numpy())
# 执行量化(若指定了QUANTIZE)
if QUANTIZE != "f16":
writer.quantize(QUANTIZE)
print(f"转换完成!GGUF模型已保存到:{OUTPUT_GGUF_PATH}")
convert_to_gguf.py,放在和你的模型文件夹同级的目录;python convert_to_gguf.py
.gguf 文件就是 bert.cpp 可直接调用的格式。text2vec chinese gguf),下载后可直接用 bert.cpp 调用,省去转换步骤。vocab.txt(分词器文件)——只要原模型支持中文,转换后的 GGUF 模型也会继承中文分词能力,和 bert.cpp 项目本身无关。gguf 库导致的,只需要先安装这个库即可解决。执行以下命令安装所需依赖:# 安装 gguf 库(核心依赖)
pip3 install gguf
# 同时确保安装其他必要依赖(处理模型和分词)
pip3 install transformers torch sentencepiece
pip3 install 模块名 安装即可。AttributeError: __enter__,而且提到已经安装了相关库。首先,我需要分析这个错误的原因。__enter__方法是上下文管理器的一部分,当使用with语句时,对象需要实现__enter__和__exit__方法。这里GGUFWriter可能没有正确实现这些方法,或者版本问题导致。gguf库的版本。用户可能安装的gguf版本过旧,因为GGUFWriter的上下文管理器支持可能是在较新的版本中添加的。建议之前的安装命令是pip3 install gguf,但可能需要指定最新版本。GGUFWriter。需要确认gguf库的正确用法。查阅gguf-py的文档,发现GGUFWriter可能不支持上下文管理器,或者使用方式不同。gguf库到最新版本,或者修改代码,不使用with语句,而是直接创建和关闭 writer。例如,手动调用writer.close()代替with块。gguf库;2. 修改代码,不使用with语句,手动管理 writer 的生命周期;3. 确保其他依赖正确。GGUFWriter 类不支持 with 语句的上下文管理器语法(即没有实现 __enter__ 方法),和 TensorFlow 或 TensorRT 无关(那些警告可以忽略)。解决方法很简单,修改转换脚本,去掉 with 语句,手动创建和关闭 writer 即可。with 语句)from gguf import GGUFWriter
from transformers import AutoModel, AutoConfig
import torch
# -------------------------- 你需要修改的参数 --------------------------
MODEL_DIR = "./models/Text2Vec-Dash-Chinese" # 你的中文模型文件夹路径
OUTPUT_GGUF_PATH = "./models/text2vec-chinese.gguf" # 输出的GGUF模型路径
MODEL_TYPE = "bert" # 模型类型(bert/roberta)
QUANTIZE = "q4_0" # 量化等级(q4_0/f16等)
# ----------------------------------------------------------------------
# 1. 加载中文模型和配置
config = AutoConfig.from_pretrained(MODEL_DIR)
model = AutoModel.from_pretrained(MODEL_DIR, torch_dtype=torch.float16)
# 2. 手动创建GGUFWriter(不使用with语句)
writer = GGUFWriter(OUTPUT_GGUF_PATH, model.config.vocab_size)
# 3. 写入模型元信息
writer.add_metadata({"model_type": MODEL_TYPE, "quantize": QUANTIZE})
# 4. 写入模型权重
for name, param in model.state_dict().items():
gguf_name = name.replace("module.", "") # 处理可能的前缀
writer.add_tensor(gguf_name, param.cpu().numpy())
# 5. 执行量化(若需要)
if QUANTIZE != "f16":
writer.quantize(QUANTIZE)
# 6. 手动关闭writer(替代with语句的自动关闭)
writer.close()
print(f"转换完成!GGUF模型已保存到:{OUTPUT_GGUF_PATH}")
gguf 库是最新版本gguf 库版本过旧,升级到最新版:pip3 install --upgrade gguf
with ... as writer 语法后,直接用 writer = GGUFWriter(...) 创建对象,最后调用 writer.close() 即可避免 __enter__ 错误。oneDNN custom operations)和当前转换无关(我们用的是 PyTorch),可以完全忽略,不影响脚本运行。gguf 库版本中,GGUFWriter 类确实没有 add_metadata 方法(不同版本的 API 可能有差异)。解决方法很简单:删除或注释掉添加元数据的代码(这部分不是必须的,不影响模型转换核心功能)。add_metadata)from gguf import GGUFWriter
from transformers import AutoModel, AutoConfig
import torch
# -------------------------- 你需要修改的参数 --------------------------
MODEL_DIR = "./models/Text2Vec-Dash-Chinese" # 你的中文模型文件夹路径
OUTPUT_GGUF_PATH = "./models/text2vec-chinese.gguf" # 输出的GGUF模型路径
MODEL_TYPE = "bert" # 模型类型(bert/roberta)
QUANTIZE = "q4_0" # 量化等级(q4_0/f16等)
# ----------------------------------------------------------------------
# 1. 加载中文模型和配置
config = AutoConfig.from_pretrained(MODEL_DIR)
model = AutoModel.from_pretrained(MODEL_DIR, torch_dtype=torch.float16)
# 2. 创建GGUFWriter(不使用with语句)
writer = GGUFWriter(OUTPUT_GGUF_PATH, model.config.vocab_size)
# 3. 写入模型权重(核心步骤,忽略元数据)
for name, param in model.state_dict().items():
gguf_name = name.replace("module.", "") # 处理可能的前缀
writer.add_tensor(gguf_name, param.cpu().numpy())
# 4. 执行量化(若需要)
if QUANTIZE != "f16":
writer.quantize(QUANTIZE)
# 5. 关闭writer
writer.close()
print(f"转换完成!GGUF模型已保存到:{OUTPUT_GGUF_PATH}")
add_metadata 是可选步骤(用于添加模型描述信息),不影响模型权重的转换,删除后完全不影响核心功能。gguf 库升级到最新版本(可能修复了API不一致问题):pip3 install --upgrade gguf
add_metadata 方法的问题,成功转换模型。TensorFlow 的警告依然可以忽略(和当前转换无关)。GGUFWriter对象没有quantize方法,导致转换脚本出错。首先,我需要分析为什么会出现这个错误。可能的原因是gguf库的版本不同,API 发生了变化,quantize方法可能被移除或者改名了。add_metadata方法,现在又遇到quantize的问题。这说明当前使用的gguf库版本可能不支持这些方法,或者这些功能需要通过其他方式实现。gguf库的量化功能需要使用其他工具,比如gguf-quantize命令行工具,而不是通过GGUFWriter的quantize方法。因此,解决方案应该是先转换为未量化的 GGUF 模型,然后使用单独的工具进行量化。quantize相关的代码,生成未量化的模型(比如 f16 格式)。然后,使用gguf-quantize工具对生成的模型进行量化。这样可以绕过GGUFWriter没有quantize方法的问题。gguf工具,可能需要通过pip install gguf[tools]来获取量化工具。然后提供具体的量化命令,比如将 f16 模型转换为 q4_0 格式。gguf 库版本中,GGUFWriter 类也没有 quantize 方法(不同版本的API差异较大)。解决方法是:先转换为未量化的基础GGUF模型,再用独立的量化工具处理(这是当前 gguf 库更推荐的方式)。from gguf import GGUFWriter
from transformers import AutoModel, AutoConfig
import torch
# -------------------------- 你需要修改的参数 --------------------------
MODEL_DIR = "./models/Text2Vec-Dash-Chinese" # 你的中文模型文件夹路径
OUTPUT_GGUF_PATH = "./models/text2vec-chinese-f16.gguf" # 输出未量化的模型(f16格式)
# ----------------------------------------------------------------------
# 1. 加载中文模型和配置
config = AutoConfig.from_pretrained(MODEL_DIR)
model = AutoModel.from_pretrained(MODEL_DIR, torch_dtype=torch.float16) # 用f16格式加载
# 2. 创建GGUFWriter并写入权重
writer = GGUFWriter(OUTPUT_GGUF_PATH, model.config.vocab_size)
for name, param in model.state_dict().items():
gguf_name = name.replace("module.", "") # 处理可能的前缀
writer.add_tensor(gguf_name, param.cpu().numpy()) # 写入权重
# 3. 关闭writer,生成未量化模型
writer.close()
print(f"未量化GGUF模型已保存到:{OUTPUT_GGUF_PATH}")
text2vec-chinese-f16.gguf 模型(文件较大,但能保证转换成功)。quantize 方法缺失问题)gguf 库提供了独立的 gguf-quantize 命令行工具,专门用于量化GGUF模型,步骤:gguf 版本(确保包含量化工具):pip3 install "gguf[tools]" # 安装带量化工具的完整版
gguf-quantize ./models/text2vec-chinese-f16.gguf ./models/text2vec-chinese-q4_0.gguf q4_0
q4_0 是最常用的轻量化选项)gguf 库的 GGUFWriter 类可能已移除了 quantize 方法,转而推荐“先转换、后量化”的两步法。按上述步骤操作:gguf-quantize 工具单独量化,即可得到 bert.cpp 可用的中文GGUF模型。main() 函数包含完整的转换流程,从参数配置、模型加载到权重写入,步骤清晰。module.),适配不同训练框架导出的模型。MODEL_DIR 为你的中文模型实际路径。python3 convert_chinese_model.py。gguf[tools])。add_metadata 和 quantize 等可能缺失的方法,专注于核心的权重转换,兼容性更强。main 函数测试逻辑(模拟“初始化权威FAQ→用户查询→保存历史→二次查询验证”全流程),适配英文模型(如 all-MiniLM-L6-v2 的 GGML 格式),可直接编译测试:#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <faiss/IndexFlatL2.h> // FAISS 基础Flat索引(英文模型数据量小时用)
#include "bert.h" // bert.cpp 头文件(需确保路径正确)
// -------------------------- 全局配置(需根据你的路径修改!) --------------------------
const std::string BERT_EN_MODEL_PATH = "./models/all-MiniLM-L6-v2/ggml-model-q4_0.bin"; // 英文模型路径
const std::string BERT_VOCAB_PATH = "./models/all-MiniLM-L6-v2/vocab.txt"; // 英文词表路径
const int VEC_DIM = 768; // 向量维度(英文模型固定768)
// -------------------------- 全局资源(FAISS索引+映射表+bert模型) --------------------------
faiss::Index* g_faiss_index = nullptr; // FAISS索引(FlatL2,适合英文小数据测试)
// 映射表:FAISS向量ID → {原始文本, 优先级(1=权威FAQ,0=历史对话)}
std::unordered_map<faiss::idx_t, std::pair<std::string, int>> g_id_to_text;
faiss::idx_t g_next_id = 0; // 下一个待分配的向量ID(自增)
bert_model* g_bert_model = nullptr; // bert.cpp 英文模型实例
// -------------------------- 工具函数:初始化bert.cpp英文模型(仅调用1次) --------------------------
int init_bert_en_model() {
if (g_bert_model != nullptr) {
std::cout << "[Info] bert model already initialized" << std::endl;
return 0;
}
// 加载英文bert模型(ggml格式)
g_bert_model = bert_load_model(BERT_EN_MODEL_PATH.c_str(), BERT_VOCAB_PATH.c_str());
if (g_bert_model == nullptr) {
std::cerr << "[Error] Failed to load bert English model! Check path: " << BERT_EN_MODEL_PATH << std::endl;
return -1;
}
std::cout << "[Success] bert English model loaded (path: " << BERT_EN_MODEL_PATH << ")" << std::endl;
return 0;
}
// -------------------------- 工具函数:初始化FAISS索引(仅调用1次) --------------------------
int init_faiss_index() {
if (g_faiss_index != nullptr) {
std::cout << "[Info] FAISS index already initialized" << std::endl;
return 0;
}
// 英文测试用FlatL2索引(小数据量,精度100%,无需聚类,简单易测试)
g_faiss_index = new faiss::IndexFlatL2(VEC_DIM);
if (g_faiss_index == nullptr) {
std::cerr << "[Error] Failed to create FAISS IndexFlatL2" << std::endl;
return -1;
}
std::cout << "[Success] FAISS IndexFlatL2 initialized (dim: " << VEC_DIM << ")" << std::endl;
return 0;
}
// -------------------------- 核心函数1:初始化权威FAQ(英文) --------------------------
/**
* @brief 导入英文权威FAQ,转向量后存入FAISS(优先级1)
* @param authority_qa_en 英文权威FAQ列表(格式:"Q:xxx A:xxx")
* @return 0=成功,-1=失败
*/
int init_authority_faq(const std::vector<std::string>& authority_qa_en) {
// 先初始化bert和FAISS
if (init_bert_en_model() != 0 || init_faiss_index() != 0) {
std::cerr << "[Error] Init dependencies failed (bert/FAISS)" << std::endl;
return -1;
}
// 批量处理FAQ:转向量→暂存→批量插入FAISS
std::vector<float> all_vecs; // 存储所有向量(连续内存,FAISS要求)
std::vector<faiss::idx_t> all_ids; // 存储所有向量ID
std::vector<std::pair<std::string, int>> all_texts; // 存储文本+优先级
for (const auto& qa_text : authority_qa_en) {
// 1. 用bert将英文FAQ转为768维向量
std::vector<float> vec(VEC_DIM, 0.0f);
if (bert_embed_text(g_bert_model, qa_text.c_str(), vec.data(), VEC_DIM) != 0) {
std::cerr << "[Warn] Skip invalid QA (embed failed): " << qa_text.substr(0, 30) << "..." << std::endl;
continue;
}
// 2. 暂存向量、ID、文本
all_vecs.insert(all_vecs.end(), vec.begin(), vec.end());
all_ids.push_back(g_next_id);
all_texts.emplace_back(qa_text, 1); // 权威FAQ优先级=1
g_next_id++;
}
// 3. 批量插入FAISS(效率比单条插入高)
if (!all_vecs.empty()) {
g_faiss_index->add_with_ids(
all_vecs.size() / VEC_DIM, // 向量总数
all_vecs.data(), // 向量数据(连续内存)
all_ids.data() // 自定义ID(避免FAISS自动分配)
);
// 更新映射表
for (size_t i = 0; i < all_ids.size(); ++i) {
g_id_to_text[all_ids[i]] = all_texts[i];
}
std::cout << "[Success] Init English authority FAQ: " << all_ids.size() << " items" << std::endl;
} else {
std::cerr << "[Warn] No valid FAQ to init" << std::endl;
return -1;
}
return 0;
}
// -------------------------- 核心函数2:查询相似问答(英文) --------------------------
/**
* @brief 用户英文问题→转向量→FAISS检索相似结果(优先权威FAQ)
* @param user_query_en 用户英文问题(如"How to apply for a refund?")
* @param top_k 最多返回几条结果
* @param results 输出:相似问答文本列表(按优先级+相似度排序)
* @return 0=成功,-1=失败
*/
int search_similar_qa(const std::string& user_query_en, int top_k, std::vector<std::string>& results) {
results.clear();
// 检查依赖是否就绪
if (top_k <= 0 || g_faiss_index == nullptr || g_bert_model == nullptr) {
std::cerr << "[Error] Invalid search params (top_k<=0 or dependencies not ready)" << std::endl;
return -1;
}
// 1. 用户问题→向量
std::vector<float> query_vec(VEC_DIM, 0.0f);
if (bert_embed_text(g_bert_model, user_query_en.c_str(), query_vec.data(), VEC_DIM) != 0) {
std::cerr << "[Error] Embed user query failed: " << user_query_en << std::endl;
return -1;
}
// 2. FAISS检索(返回Top-K结果:距离越小越相似)
std::vector<float> distances(top_k, 0.0f); // 存储L2距离(相似度指标)
std::vector<faiss::idx_t> match_ids(top_k, -1); // 存储匹配的向量ID
g_faiss_index->search(
1, // 查询向量数量(1个问题)
query_vec.data(), // 查询向量
top_k, // 返回Top-K
distances.data(), // 输出:距离
match_ids.data() // 输出:匹配的ID
);
// 3. 整理结果:按「优先级(1>0)→ 距离(小→大)」排序
struct ResultItem {
std::string text;
int authority;
float distance;
};
std::vector<ResultItem> items;
for (int i = 0; i < top_k; ++i) {
faiss::idx_t vec_id = match_ids[i];
if (vec_id == -1 || g_id_to_text.find(vec_id) == g_id_to_text.end()) {
continue; // 跳过无效ID
}
auto& [qa_text, authority] = g_id_to_text[vec_id];
items.emplace_back(ResultItem{qa_text, authority, distances[i]});
}
// 排序(核心:权威优先,再按相似度)
std::sort(items.begin(), items.end(), [](const ResultItem& a, const ResultItem& b) {
if (a.authority != b.authority) {
return a.authority > b.authority; // 权威FAQ(1)在前
}
return a.distance < b.distance; // 距离小(更相似)在前
});
// 4. 提取最终文本结果
for (const auto& item : items) {
results.push_back(item.text);
// 打印详细信息(便于测试调试)
std::cout << "[Search Res] Auth:" << item.authority
<< " Distance:" << item.distance
<< " Text:" << item.text << std::endl;
}
return 0;
}
// -------------------------- 核心函数3:保存历史对话(英文) --------------------------
/**
* @brief 保存「用户英文问题+模型英文回答」到FAISS(优先级0)
* @param user_query_en 用户英文问题
* @param model_answer_en 模型英文回答
* @return 0=成功,-1=失败
*/
int save_history_qa(const std::string& user_query_en, const std::string& model_answer_en) {
// 检查依赖
if (g_faiss_index == nullptr || g_bert_model == nullptr) {
std::cerr << "[Error] Dependencies not ready (bert/FAISS)" << std::endl;
return -1;
}
// 1. 合并历史文本(格式:Q:xxx A:xxx,和权威FAQ一致,便于检索)
std::string history_text = "Q:" + user_query_en + " A:" + model_answer_en;
// 2. 历史文本→向量
std::vector<float> history_vec(VEC_DIM, 0.0f);
if (bert_embed_text(g_bert_model, history_text.c_str(), history_vec.data(), VEC_DIM) != 0) {
std::cerr << "[Error] Embed history failed: " << history_text.substr(0, 30) << "..." << std::endl;
return -1;
}
// 3. 插入FAISS(单条插入,适合历史对话实时保存)
faiss::idx_t current_id = g_next_id;
g_faiss_index->add_with_ids(
1, // 1个向量
history_vec.data(), // 向量数据
¤t_id // 自定义ID
);
// 4. 更新映射表(历史对话优先级=0)
g_id_to_text[current_id] = {history_text, 0};
g_next_id++;
std::cout << "[Success] Save history QA (ID:" << current_id << "): " << history_text << std::endl;
return 0;
}
// -------------------------- 工具函数:释放全局资源(程序退出前调用) --------------------------
void release_resources() {
if (g_faiss_index != nullptr) {
delete g_faiss_index;
g_faiss_index = nullptr;
std::cout << "[Info] FAISS index released" << std::endl;
}
if (g_bert_model != nullptr) {
bert_free_model(g_bert_model);
g_bert_model = nullptr;
std::cout << "[Info] bert model released" << std::endl;
}
g_id_to_text.clear();
g_next_id = 0;
std::cout << "[Info] All resources released" << std::endl;
}
// -------------------------- 完整 main 函数(测试全流程) --------------------------
int main() {
std::cout << "=== Start English RAG Test ===" << std::endl;
std::vector<std::string> search_results;
// -------------------------- 步骤1:初始化英文权威FAQ --------------------------
std::vector<std::string> en_authority_faq = {
"Q:How to apply for a refund? A:Click 'After-sales' on the order details page, select 'Refund', and submit proof. It will be processed within 3 working days.",
"Q:Can I get a refund if my membership expires? A:No, refunds are only available for unexpired memberships purchased within 7 days.",
"Q:How long does it take for the refund to arrive? A:After approval, the refund will be returned to your payment account within 1-7 working days."
};
if (init_authority_faq(en_authority_faq) != 0) {
std::cerr << "[Error] Init authority FAQ failed! Exit test." << std::endl;
release_resources();
return -1;
}
std::cout << "--------------------------" << std::endl;
// -------------------------- 步骤2:第一次查询(用户问退款申请) --------------------------
std::string user_query1 = "How do I apply for a refund for my order?";
std::cout << "[Test1] User Query: " << user_query1 << std::endl;
if (search_similar_qa(user_query1, 2, search_results) == 0) { // 返回Top2结果
std::cout << "[Test1] Search Results:" << std::endl;
for (size_t i = 0; i < search_results.size(); ++i) {
std::cout << " " << i+1 << ". " << search_results[i] << std::endl;
}
} else {
std::cerr << "[Test1] Search failed!" << std::endl;
}
search_results.clear(); // 清空结果,准备下次查询
std::cout << "--------------------------" << std::endl;
// -------------------------- 步骤3:保存历史对话(模拟模型回答后保存) --------------------------
std::string model_answer1 = "You can apply for a refund by following these steps: 1. Go to your order list. 2. Find the target order and click 'After-sales'. 3. Select 'Refund' and upload relevant proof. We will review it within 3 days.";
if (save_history_qa(user_query1, model_answer1) != 0) {
std::cerr << "[Test2] Save history failed!" << std::endl;
} else {
std::cout << "[Test2] History saved successfully" << std::endl;
}
std::cout << "--------------------------" << std::endl;
// -------------------------- 步骤4:第二次查询(用户问退款进度,验证历史是否生效) --------------------------
std::string user_query2 = "How long will my refund application take to be reviewed?";
std::cout << "[Test3] User Query: " << user_query2 << std::endl;
if (search_similar_qa(user_query2, 2, search_results) == 0) {
std::cout << "[Test3] Search Results (include history):" << std::endl;
for (size_t i = 0; i < search_results.size(); ++i) {
std::cout << " " << i+1 << ". " << search_results[i] << std::endl;
}
} else {
std::cerr << "[Test3] Search failed!" << std::endl;
}
std::cout << "--------------------------" << std::endl;
// -------------------------- 步骤5:释放资源,结束测试 --------------------------
release_resources();
std::cout << "=== English RAG Test End ===" << std::endl;
return 0;
}
BERT_EN_MODEL_PATH 和 BERT_VOCAB_PATH 为你本地英文模型(如 all-MiniLM-L6-v2)的实际路径,确保 ggml-model-q4_0.bin 和 vocab.txt 存在。libfaiss-dev(Linux 用 sudo apt install libfaiss-dev)。libbert.a,并将 bert.h 放在代码同级目录。g++ -std=c++11 rag_test.cpp -o rag_test -lfaiss -lbert -lpthread -lm
libbert.a 在指定目录,需加 -L./bert_cpp_lib 指定库路径,-I./bert_cpp_include 指定头文件路径)。./rag_test,即可看到完整的英文RAG流程测试输出,验证核心功能是否正常。if-else或公式定义规则,后者靠数据“训练”出一套隐式的、可泛化的“决策模式”,并把这套模式存在模型文件里。下面用你能直观理解的逻辑拆解:if-else判断“‘苹果’是分1个token还是2个”“‘人工智能’要不要拆”,逻辑是透明的,但面对新词汇(比如“AI生成式”)就会失效。 [CLS](表示整个句子的语义聚合点)、结尾加 [SEP](表示句子结束),这是Bert模型的固定要求。[CLS] 对应1、“天气”对应1234。[1, 1234, 567, ..., 0, 0](1是[CLS],0是[PAD])。[CLS] 位置对应的模型输出向量(通常是768维,Bert-base规模),这个向量就是整个文本的“语义浓缩版”——比如“今天天气好”和“今日天气晴朗”的 [CLS] 向量会非常相似,这也是RAG能匹配相似文本的基础。[CLS],模型还会为每个输入Token输出一个向量(同样768维),可用于更细粒度的任务(如关键词提取、文本标注),但RAG场景几乎用不到。| 特征类别 | 具体指标(示例) |
|---|---|
| 资源状态 | 金币数量、木头/矿石/硫磺/水晶等7种资源的“当前量/日均产量” |
| 城堡状态 | 主城堡等级、已建建筑(是否有国会/兵营/魔法塔)、各等级兵的“当前数量/日产量” |
| 英雄状态 | 主力英雄等级、攻防智魅属性值、携带兵力(各兵种数量/总战斗力)、剩余移动力 |
| 敌人信息 | 已探知的敌人城堡数量、敌人英雄的“大致战斗力范围”(不用精确位置,用“威胁等级”表示) |
.onnx或.pt格式),再用C++写一个“模型调用接口”,嵌入VCML代码:transformers加载模型,gguf处理格式),命令如下:# 1. 先卸载可能干扰的库(可选,若之前装过TensorFlow/TensorRT)
pip3 uninstall -y tensorflow tensorrt torchvision torchaudio
# 2. 只装核心依赖(纯CPU版本的torch+2个工具库)
# 重点:用--only-binary避免编译,快且不报错
pip3 install --only-binary :all: torch==2.0.1 transformers==4.30.2 gguf==0.7.0
torch 2.0.1的 CPU 版最稳定,transformers 4.30.2兼容绝大多数 BERT 模型,gguf 0.7.0是功能完整且无 API 冲突的版本。torch==2.0.1默认就是 CPU 版,安装时不会下载 CUDA 相关文件,完全适配笔记本。convert_cpu_only.py文件,复制下面代码 ——只需要改 2 个路径,其他不用动:import torch
from transformers import AutoModel, AutoConfig
from gguf import GGUFWriter
import os
def main():
# -------------------------- 只改这2个参数!!! --------------------------
MODEL_INPUT_DIR = "./models/english-model" # 你的PyTorch模型文件夹(含pytorch_model.bin)
MODEL_OUTPUT_PATH = "./models/english-model.gguf" # 输出的GGUF文件路径
# ----------------------------------------------------------------------
# 1. 检查输入文件夹是否存在
if not os.path.exists(MODEL_INPUT_DIR):
print(f"❌ 错误:模型文件夹不存在 → {MODEL_INPUT_DIR}")
return
# 2. 加载PyTorch模型(强制CPU,禁用所有GPU相关功能)
print(f"🔍 正在加载模型(纯CPU)→ {MODEL_INPUT_DIR}")
try:
# 关键:用device='cpu'强制CPU,torch_dtype=torch.float32减少内存占用
config = AutoConfig.from_pretrained(MODEL_INPUT_DIR)
model = AutoModel.from_pretrained(
MODEL_INPUT_DIR,
device_map="cpu", # 强制用CPU
torch_dtype=torch.float32,
ignore_mismatched_sizes=True # 兼容不同训练框架导出的模型
)
except Exception as e:
print(f"❌ 加载模型失败:{str(e)}")
print("💡 提示:检查文件夹内是否有这3个文件 → config.json、pytorch_model.bin、vocab.txt")
return
# 3. 初始化GGUF写入器(只写核心权重,不搞复杂元数据)
print(f"📝 初始化GGUF写入器 → {MODEL_OUTPUT_PATH}")
writer = GGUFWriter(MODEL_OUTPUT_PATH, config.vocab_size)
# 4. 写入模型权重(核心步骤,只处理权重,跳过所有无关数据)
print("🚀 正在转换权重(纯CPU)...")
weight_count = 0
for name, param in model.state_dict().items():
# 处理权重名称前缀(比如去掉"module.",兼容部分模型)
clean_name = name.replace("module.", "").replace("bert.", "")
# 把PyTorch张量转成numpy数组(CPU环境安全)
weight_data = param.cpu().numpy()
# 写入GGUF(只写权重,不添加任何额外信息)
writer.add_tensor(clean_name, weight_data)
weight_count += 1
# 5. 完成转换,关闭文件
writer.close()
print(f"✅ 转换完成!")
print(f"📁 输出文件 → {MODEL_OUTPUT_PATH}")
print(f"📊 统计:共转换 {weight_count} 个权重参数")
if __name__ == "__main__":
main()
config.json(模型配置)、pytorch_model.bin(权重)、vocab.txt(英文词表)./models/english-model),把这 3 个文件放进去。python3 convert_cpu_only.py
🔍 正在加载模型(纯CPU)→ ./models/english-model
📝 初始化GGUF写入器 → ./models/english-model.gguf
🚀 正在转换权重(纯CPU)...
✅ 转换完成!
📁 输出文件 → ./models/english-model.gguf
📊 统计:共转换 136 个权重参数
MODEL_INPUT_DIR路径是否对,确保 3 个文件都在。pip3 install命令,确保版本一致。main程序测试是否能加载模型(确保 bert.cpp 已编译):# 进入bert.cpp目录
cd bert.cpp
# 运行官方测试(加载你转换的GGUF模型,输入英文句子)
./build/bin/bert -m ../models/english-model.gguf -t 4 -p "Hello, this is a test sentence."