文章目录
团队博客: 汽车电子社区
1. 模块概述
src/ 目录是 llama.cpp 项目的核心推理引擎,负责模型加载、推理执行、状态管理等关键功能。该模块采用模块化设计,将复杂的推理过程分解为多个专门的子模块,每个模块负责特定的功能领域。
1.1 核心职责
- 模型管理:加载、解析和管理各种大语言模型
- 推理执行:构建和执行模型推理计算图
- 状态管理:维护推理上下文和序列状态
- 采样控制:实现多种文本生成采样算法
- 语法约束:支持结构化文本生成约束
- 内存优化:高效的内存分配和缓存管理
1.2 设计原则
- 模块化:清晰的模块边界和职责分离
- 可扩展:易于添加新模型架构和功能
- 高性能:针对推理场景的专门优化
- 跨平台:统一的API接口支持多平台部署
2. 整体架构设计
2.1 目录组织结构
src/
├── 核心API模块
│ ├── llama.cpp # 主入口文件和API实现
│ ├── llama.h # 公共API头文件 (位于include/)
│ └── llama-cpp.cpp # C++接口封装
├── 模型管理模块
│ ├── llama-model.h # 模型数据结构定义
│ ├── llama-model.cpp # 模型加载和处理实现
│ ├── llama-arch.h # 模型架构定义
│ ├── llama-arch.cpp # 架构注册和管理
│ └── models/ # 各种模型架构实现 (100+种)
├── 推理执行模块
│ ├── llama-context.h # 上下文数据结构
│ ├── llama-context.cpp # 推理上下文管理
│ ├── llama-batch.h # 批处理接口
│ ├── llama-batch.cpp # 批处理实现
│ └── llama-kv-cache.* # KV缓存管理
├── 文本处理模块
│ ├── llama-vocab.h # 词汇表接口
│ ├── llama-vocab.cpp # 词汇表和tokenization
│ └── unicode.* # Unicode处理支持
├── 采样算法模块
│ ├── llama-sampling.h # 采样接口定义
│ ├── llama-sampling.cpp # 采样算法实现
│ ├── llama-grammar.h # 语法约束接口
│ └── llama-grammar.cpp # 语法约束实现
├── 量化支持模块
│ ├── llama-quant.h # 量化接口
│ ├── llama-quant.cpp # 量化算法实现
│ └── llama-imatrix.* # 重要性矩阵计算
├── 工具和支持模块
│ ├── llama-chat.* # 聊天模板支持
│ ├── llama-adapter.* # LoRA适配器支持
│ ├── llama-memory.* # 内存抽象层
│ ├── llama-mmap.* # 内存映射支持
│ └── llama-sampling.* # 采样工具函数
└── 构建配置
├── CMakeLists.txt # 模块构建配置
└── ... # 其他构建文件
2.2 模块间依赖关系
3. 核心模块深度分析
3.1 llama.cpp - 主入口和API实现
3.1.1. 核心职责
llama.cpp 是整个推理引擎的主控制中心,负责:
- API统一管理:实现 llama.h 中定义的所有公共API
- 后端初始化:初始化GGML计算后端和硬件设备
- 模型加载协调:协调模型加载的各个阶段
- 资源管理:管理模型、上下文等资源的生命周期
3.1.2. 关键数据结构
// 模型加载参数
struct llama_model_params {
int32_t n_gpu_layers; // GPU层数 (-1表示全部)
int32_t main_gpu; // 主GPU设备
const float * tensor_split; // 张量分割比例
bool progress_callback_user_data; // 进度回调
bool vocab_only; // 仅加载词汇表
bool use_mmap; // 使用内存映射
bool use_mlock; // 锁定内存
bool check_tensors; // 检查张量完整性
};
// 上下文参数
struct llama_context_params {
uint32_t seed; // 随机种子
int32_t n_ctx; // 上下文窗口大小
int32_t n_batch; // 批处理大小
int32_t n_threads; // CPU线程数
int32_t n_threads_batch; // 批处理线程数
float rope_scaling_type; // RoPE缩放类型
int32_t n_gpu_layers_draft; // 草稿模型GPU层数
enum llama_pooling_type pooling; // 池化类型
// ... 其他参数
};
3.1.3. 核心API实现
// 模型加载API实现
LLAMA_API struct llama_model * llama_load_model_from_file(
const char * path_model,
struct llama_model_params params) {
// 1. 初始化后端
llama_backend_init();
// 2. 创建模型加载器
llama_model_loader ml(path_model, params.use_mmap, params.check_tensors);
// 3. 加载模型
llama_model * model = new llama_model();
try {
model->load_arch(ml); // 加载架构
model->load_hparams(ml); // 加载超参数
model->load_vocab(ml); // 加载词汇表
model->load_tensors(ml); // 加载权重张量
} catch (const std::exception & e) {
delete model;
llama_backend_free();
return nullptr;
}
// 4. GPU层分配
if (params.n_gpu_layers != 0) {
model->allocate_backends(params);
}
return model;
}
// 上下文创建API实现
LLAMA_API struct llama_context * llama_new_context_with_model(
struct llama_model * model,
struct llama_context_params params) {
llama_context * ctx = new llama_context(*model, params);
// 初始化组件
ctx->initialize_backends(); // 初始化后端
ctx->initialize_kv_cache(); // 初始化KV缓存
ctx->initialize_memory_pool(); // 初始化内存池
ctx->initialize_thread_pool(); // 初始化线程池
return ctx;
}
3.1.4. 模型加载流程详解
3.2 llama-model.cpp - 模型加载和处理核心
3.2.1. 核心数据结构
// 主模型结构
struct llama_model {
// 模型基本信息
llama_model_arch arch; // 模型架构类型
llama_vocab vocab; // 词汇表
// 模型超参数
int32_t n_embd; // 嵌入维度
int32_t n_head; // 注意力头数
int32_t n_layer; // 层数
int32_t n_ctx_train; // 训练上下文长度
float f_norm_eps; // 归一化epsilon
// 位置编码
enum llama_rope_scaling_type rope_scaling_type; // RoPE缩放
float rope_freq_base; // RoPE频率基
float rope_freq_scale; // RoPE缩放因子
// 量化参数
enum llama_ftype ftype; // 量化类型
std::vector<llama_tensor> tensors; // 模型张量
// 后端管理
std::vector<ggml_backend_t> backends; // 计算后端
std::vector<llama_buffer> buffers; // 缓冲区
};
3.2.2. 模型加载详细流程
// 模型加载的主要实现
void llama_model::load_arch(llama_model_loader & ml) {
// 1. 从元数据读取架构信息
arch = llama_model_arch_from_string(
ml.get_key(LLM_KV_ARCHITECTURE).c_str());
// 2. 验证架构支持
if (!llama_arch_supported(arch)) {
throw std::runtime_error("Unsupported model architecture");
}
// 3. 设置架构特定的构建器
set_arch_builders();
}
void llama_model::load_hparams(llama_model_loader & ml) {
// 加载基础超参数
n_embd = ml.get_key(LLM_KV_EMBD_DIM, 4096);
n_head = ml.get_key(LLM_KV_ATTENTION_HEAD_COUNT, 32);
n_layer = ml.get_key(LLM_KV_BLOCK_COUNT, 32);
n_ctx_train = ml.get_key(LLM_KV_CONTEXT_LENGTH, 2048);
// 加载架构特定参数
switch (arch) {
case LLM_ARCH_LLAMA:
load_llama_hparams(ml);
break;
case LLM_ARCH_MISTRAL:
load_mistral_hparams(ml);
break;
// ... 其他架构
}
}
void llama_model::load_tensors(llama_model_loader & ml) {
// 1. 遍历所有张量
while (ml.tg_exists()) {
std::string name = ml.get_tg_name();
// 2. 创建张量对象
llama_tensor tensor;
tensor.name = name;
tensor.type = ml.get_tg_tensor_type();
tensor.ne = ml.get_tg_ne();
// 3. 分配内存
size_t size = ggml_nbytes(&tensor.ggml_tensor);
tensor.data = ml.get_tg_data();
// 4. 量化处理(如果需要)
if (tensor.type != GGML_TYPE_F32) {
quantize_tensor(tensor);
}
tensors.push_back(tensor);
ml.tg_next();
}
// 5. 验证所有必需张量已加载
validate_tensors();
}
3.2.3. 设备管理和分层策略
// GPU/CPU分层管理
void llama_model::allocate_backends(const llama_model_params & params) {
// 1. 检测可用GPU设备
auto gpu_devices = detect_gpu_devices();
// 2. 确定分层策略
int n_gpu_layers = params.n_gpu_layers;
if (n_gpu_layers < 0) {
n_gpu_layers = n_layer; // 全部层到GPU
}
// 3. 创建后端
backends.clear();
if (!gpu_devices.empty() && n_gpu_layers > 0) {
// GPU后端
auto gpu_backend = ggml_backend_cuda_init(params.main_gpu);
backends.push_back(gpu_backend);
}
// CPU后端(兜底)
auto cpu_backend = ggml_backend_cpu_init();
backends.push_back(cpu_backend);
// 4. 分配张量到后端
allocate_tensors_to_backends(n_gpu_layers);
}
void llama_model::allocate_tensors_to_backends(int n_gpu_layers) {
for (auto & tensor : tensors) {
ggml_backend_t target_backend = nullptr;
// 基于层名和分层策略选择后端
if (is_embedding_tensor(tensor.name) ||
is_output_layer_tensor(tensor.name)) {
// 嵌入层和输出层通常保留在CPU
target_backend = backends.back(); // CPU后端
} else if (extract_layer_number(tensor.name) < n_gpu_layers) {
// 前n层放到GPU
target_backend = backends.front(); // GPU后端
} else {
// 其余层在CPU
target_backend = backends.back(); // CPU后端
}
// 分配张量到目标后端
tensor.backend = target_backend;
tensor.buffer = ggml_backend_alloc_buffer(
ggml_backend_get_default_buffer_type(target_backend),
ggml_nbytes(&tensor.ggml_tensor));
// 复制数据到后端
ggml_backend_tensor_set_async(
target_backend, &tensor.ggml_tensor,
tensor.data, 0, ggml_nbytes(&tensor.ggml_tensor));
}
}
3.3 llama-context.cpp - 推理上下文管理
3.3.1. 核心数据结构
// 推理上下文主结构
struct llama_context {
// 模型关联
const llama_model & model; // 关联的模型
// 上下文参数
llama_cparams cparams; // 上下文参数
// 计算资源
ggml_backend_sched_ptr sched; // 计算调度器
ggml_gallocr_t galloc; // 图分配器
// 内存管理
llama_kv_cache kv_cache; // KV缓存
llama_memory_pool memory_pool; // 内存池
std::vector<uint8_t> buf_compute; // 计算缓冲区
// 批处理管理
llama_batch_allocr balloc; // 批分配器
llama_batch batch; // 当前批次
// 序列管理
std::vector<llama_seq_id> seq_ids; // 活动序列ID
std::vector<int> positions; // 位置编码
std::vector<llama_pos> pos; // 精确位置
// 线程池
std::unique_ptr<thread_pool> thread_pool; // 线程池
// 状态标志
bool model_ready; // 模型就绪标志
bool compute_in_progress; // 计算进行标志
};
3.3.2. 上下文初始化
llama_context::llama_context(const llama_model & model,
const llama_context_params & params)
: model(model), cparams(to_cparams(params)) {
// 1. 验证参数
validate_context_params();
// 2. 初始化调度器
initialize_scheduler();
// 3. 初始化内存管理
initialize_memory();
// 4. 初始化KV缓存
initialize_kv_cache();
// 5. 初始化批处理系统
initialize_batch_system();
// 6. 初始化线程池
initialize_thread_pool();
model_ready = true;
}
void llama_context::initialize_scheduler() {
// 创建多后端调度器
std::vector<ggml_backend_t> backends = model.get_backends();
sched = ggml_backend_sched_new(
backends.data(),
NULL,
backends.size(),
GGML_DEFAULT_GRAPH_SIZE,
true, // 启用并行
true // 启用回调
);
// 设置性能监控
ggml_backend_sched_set_measure_callback(
sched.get(),
[](ggml_backend_sched_t sched, void * user_data) {
// 性能分析回调
},
this);
}
void llama_context::initialize_memory() {
// 创建图分配器
galloc = ggml_gallocr_new(ggml_backend_cpu_buffer_type());
// 预分配计算图内存
auto * graph = model.build_forward_graph();
ggml_gallocr_reserve(galloc, graph);
// 计算缓冲区大小
size_t compute_size = estimate_compute_buffer_size();
buf_compute.resize(compute_size);
// 初始化内存池
memory_pool = llama_memory_pool(compute_size * 2);
}
void llama_context::initialize_kv_cache() {
// 初始化KV缓存
kv_cache = llama_kv_cache(
model.n_layer,
cparams.n_ctx,
model.n_embd,
model.n_kv_head(),
cparams.n_gpu_layers_draft,
model.get_backends()
);
// 设置缓存策略
kv_cache.set_reuse_strategy(cparams.cache_reuse);
kv_cache.set_shift_ratio(cparams.cache_type_k);
}
3.3.3. 批处理和序列管理
// 批处理结构
struct llama_batch {
int32_t n_tokens; // token数量
llama_token * tokens; // token数组
llama_pos * pos; // 位置数组
llama_seq_id * seq_ids; // 序列ID数组
int8_t * logits; // 输出标志
int32_t n_seq_id; // 每个token的序列ID数量
// 构造函数
llama_batch(int32_t n_tokens_, bool all_seq_0 = true)
: n_tokens(n_tokens_) {
tokens = new llama_token[n_tokens_];
pos = new llama_pos[n_tokens_];
seq_ids = new llama_seq_id[n_tokens_];
logits = new int8_t[n_tokens_];
if (all_seq_0) {
for (int i = 0; i < n_tokens_; ++i) {
seq_ids[i] = 0;
}
n_seq_id = 1;
}
}
};
// 批处理管理
void llama_context::prepare_batch(const std::vector<llama_token> & input_tokens,
llama_seq_id seq_id) {
// 1. 创建批处理对象
batch = llama_batch(input_tokens.size());
// 2. 填充token和位置
for (size_t i = 0; i < input_tokens.size(); ++i) {
batch.tokens[i] = input_tokens[i];
batch.pos[i] = get_position_for_token(seq_id, i);
batch.seq_ids[i] = seq_id;
batch.logits[i] = (i == input_tokens.size() - 1) ? 1 : 0;
}
// 3. 更新序列状态
update_sequence_state(seq_id, input_tokens.size());
// 4. 准备KV缓存
prepare_kv_cache_for_batch();
}
3.4 llama-vocab.cpp - 词汇表处理和tokenization
3.4.1. 词汇表数据结构
// 词汇表主结构
struct llama_vocab {
// 基本信息
int n_tokens; // token总数
int token_pad; // padding token ID
int token_bos; // begin of sequence ID
int token_eos; // end of sequence ID
int token_unk; // unknown token ID
// Token到字符串映射
std::unordered_map<llama_token, std::string> token_to_piece;
// 字符串到Token映射
std::unordered_map<std::string, llama_token> piece_to_token;
// 特殊Token映射
std::unordered_map<std::string, llama_token> special_tokens;
// Tokenizer配置
enum llama_vocab_type type; // tokenizer类型
bool add_bos; // 是否添加BOS
bool add_eos; // 是否添加EOS
bool clean_spaces; // 是否清理空格
// Tokenizer特定数据
union {
struct {
std::vector<int> scores; // SPM分数
std::vector<std::string> pieces; // SPM片段
} spm;
struct {
std::vector<std::pair<std::string, int>> merges; // BPE合并对
} bpe;
struct {
std::vector<std::string> vocab; // 词汇表
std::vector<float> scores; // 分数
} wordpiece;
} tokenizer_data;
};
3.4.2. Tokenizer实现
// SentencePiece Tokenizer实现
class sentencepiece_tokenizer {
private:
struct symbol {
std::string piece;
float score;
bool is_unk;
symbol(std::string p, float s, bool u = false)
: piece(std::move(p)), score(s), is_unk(u) {}
};
std::unordered_map<std::string, symbol> symbol_table_;
float unk_score_ = 0.0;
size_t max_piece_length_ = 0;
public:
// 加载SPM模型
bool load_model(const std::string & model_path) {
std::ifstream file(model_path);
if (!file.is_open()) return false;
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue;
// 解析行:piece score
auto parts = split_string(line, '\t');
if (parts.size() < 1) continue;
std::string piece = parts[0];
float score = 0.0f;
if (parts.size() >= 2) {
score = std::stof(parts[1]);
}
bool is_unk = (piece == "<unk>");
symbol_table_[piece] = symbol(piece, score, is_unk);
max_piece_length_ = std::max(max_piece_length_, piece.size());
}
unk_score_ = get_unk_score();
return true;
}
// 编码:字符串 → token序列
std::vector<llama_token> encode(const std::string & text) {
// 1. 预处理:规范化、转UTF-8
std::string normalized = normalize_text(text);
// 2. 分词:初始分割为unicode字符
std::vector<std::string> words = split_into_words(normalized);
// 3. 词内分词:对每个词应用BPE
std::vector<std::string> pieces;
for (const auto & word : words) {
if (is_space(word)) {
pieces.push_back("_"); // 空格特殊处理
continue;
}
// BPE分词
auto word_pieces = bpe_segment(word);
pieces.insert(pieces.end(), word_pieces.begin(), word_pieces.end());
}
// 4. 转换为token ID
std::vector<llama_token> tokens;
for (const auto & piece : pieces) {
auto it = symbol_table_.find(piece);
if (it != symbol_table_.end()) {
tokens.push_back(static_cast<llama_token>(
std::distance(symbol_table_.begin(), it)));
} else {
// 使用<unk> token
tokens.push_back(get_unk_token_id());
}
}
return tokens;
}
// BPE分词算法
std::vector<std::string> bpe_segment(const std::string & word) {
std::vector<std::string> chars = split_to_chars(word);
if (chars.size() == 1) {
return chars;
}
// 使用优先队列管理bigram
using pair_type = std::pair<float, std::pair<int, int>>;
std::priority_queue<
pair_type,
std::vector<pair_type>,
std::greater<pair_type>
> pq;
// 初始符号
std::vector<symbol*> symbols;
for (size_t i = 0; i < chars.size(); ++i) {
std::string key = chars[i];
if (i > 0) {
key = "_" + key; // 非首字符添加下划线
}
auto it = symbol_table_.find(key);
if (it != symbol_table_.end()) {
symbols.push_back(&it->second);
} else {
symbols.push_back(&symbol_table_["<unk>"]);
}
}
// 构建初始bigram
for (size_t i = 0; i < symbols.size() - 1; ++i) {
std::string pair = symbols[i]->piece + symbols[i+1]->piece;
auto it = symbol_table_.find(pair);
if (it != symbol_table_.end()) {
float score = symbols[i]->score + symbols[i+1]->score - it->score;
pq.push({score, {static_cast<int>(i), static_cast<int>(i+1)}});
}
}
// BPE合并过程
while (!pq.empty()) {
auto [score, pos] = pq.top();
pq.pop();
int l = pos.first;
int r = pos.second;
// 检查位置是否仍然有效
if (l < 0 || r >= static_cast<int>(symbols.size()) ||
symbols[l] == nullptr || symbols[r] == nullptr) {
continue;
}
// 合并符号
std::string merged_piece = symbols[l]->piece + symbols[r]->piece;
auto it = symbol_table_.find(merged_piece);
if (it != symbol_table_.end()) {
// 创建新符号
symbol new_symbol(merged_piece, it->score);
// 更新符号链表
symbols[l] = &new_symbol;
symbols[r] = nullptr;
// 添加新的bigram
if (l > 0 && symbols[l-1] != nullptr) {
std::string left_pair = symbols[l-1]->piece + merged_piece;
auto left_it = symbol_table_.find(left_pair);
if (left_it != symbol_table_.end()) {
float left_score = symbols[l-1]->score + it->score -
left_it->second.score;
pq.push({left_score, {l-1, l}});
}
}
if (r + 1 < static_cast<int>(symbols.size()) &&
symbols[r+1] != nullptr) {
std::string right_pair = merged_piece + symbols[r+1]->piece;
auto right_it = symbol_table_.find(right_pair);
if (right_it != symbol_table_.end()) {
float right_score = it->score + symbols[r+1]->score -
right_it->second.score;
pq.push({right_score, {l, r+1}});
}
}
}
}
// 提取最终pieces
std::vector<std::string> result;
for (auto * sym : symbols) {
if (sym != nullptr) {
result.push_back(sym->piece);
}
}
return result;
}
};
3.5 llama-arch.cpp - 模型架构支持
3.5.1. 架构注册机制
// 架构构建器基类
struct llm_graph_context {
virtual ~llm_graph_context() = default;
virtual ggml_tensor * build(struct ggml_context * ctx,
struct llama_model & model) = 0;
virtual const char * get_name() const = 0;
};
// 架构工厂注册
class architecture_registry {
private:
static std::unordered_map<llama_model_arch,
std::function<std::unique_ptr<llm_graph_context>()>>
builders_;
public:
template<typename T>
static void register_architecture(llama_model_arch arch) {
builders_[arch] = []() {
return std::make_unique<T>();
};
}
static std::unique_ptr<llm_graph_context> create(llama_model_arch arch) {
auto it = builders_.find(arch);
if (it != builders_.end()) {
return it->second();
}
return nullptr;
}
static std::vector<llama_model_arch> get_supported_archs() {
std::vector<llama_model_arch> archs;
for (const auto & pair : builders_) {
archs.push_back(pair.first);
}
return archs;
}
};
// 自动注册宏
#define REGISTER_LLAMA_ARCH(arch_enum, arch_class) \
namespace { \
struct arch_class##_registrar { \
arch_class##_registrar() { \
architecture_registry::register_architecture<arch_class>(arch_enum); \
} \
}; \
static arch_class##_registrar arch_class##_instance; \
}
3.5.2. LLaMA架构实现
// LLaMA架构构建器
struct llm_build_llama : public llm_graph_context {
ggml_tensor * build(struct ggml_context * ctx,
struct llama_model & model) override {
// 1. 获取输入张量
ggml_tensor * input_tokens = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, batch.n_tokens);
ggml_set_input(input_tokens);
// 2. Token嵌入
ggml_tensor * embeddings = ggml_get_rows(ctx, model.tok_embeddings, input_tokens);
// 3. 位置编码 (RoPE)
ggml_tensor * pos_emb = build_rope_embeddings(ctx, model, batch.pos);
embeddings = ggml_add(ctx, embeddings, pos_emb);
// 4. Transformer层
for (int il = 0; il < model.n_layer; ++il) {
embeddings = build_llama_layer(ctx, model, embeddings, il);
}
// 5. 最终层归一化
embeddings = ggml_norm(ctx, embeddings, model.f_norm_eps);
embeddings = ggml_mul(ctx, embeddings, model.output_norm);
// 6. 输出投影
ggml_tensor * logits = ggml_mul_mat(ctx, model.output, embeddings);
ggml_set_output(logits);
return logits;
}
const char * get_name() const override {
return "LLaMA";
}
private:
// 构建单个Transformer层
ggml_tensor * build_llama_layer(struct ggml_context * ctx,
struct llama_model & model,
ggml_tensor * x,
int layer_idx) {
// 1. 前馈网络部分
ggml_tensor * h = build_feed_forward(ctx, model, x, layer_idx);
// 2. RMS归一化
ggml_tensor * norm_x = ggml_rms_norm(ctx, x, model.f_norm_eps);
norm_x = ggml_mul(ctx, norm_x, model.layers[layer_idx].attention_norm);
// 3. 多头注意力
ggml_tensor * attn = build_attention(ctx, model, norm_x, layer_idx);
// 4. 残差连接
ggml_tensor * layer_output = ggml_add(ctx, attn, h);
layer_output = ggml_add(ctx, layer_output, x); // 残差连接
return layer_output;
}
// 构建多头注意力
ggml_tensor * build_attention(struct ggml_context * ctx,
struct llama_model & model,
ggml_tensor * x,
int layer_idx) {
int n_heads = model.n_head;
int n_kv_head = model.n_kv_head();
int head_dim = model.n_embd / n_heads;
int n_rep = n_heads / n_kv_head;
// 1. QKV投影
ggml_tensor * q = ggml_mul_mat(ctx, model.layers[layer_idx].wq, x);
ggml_tensor * k = ggml_mul_mat(ctx, model.layers[layer_idx].wk, x);
ggml_tensor * v = ggml_mul_mat(ctx, model.layers[layer_idx].wv, x);
// 2. 重塑为多头格式
q = ggml_reshape_3d(ctx, q, head_dim, n_heads, batch.n_tokens);
k = ggml_reshape_3d(ctx, k, head_dim, n_kv_head, batch.n_tokens);
v = ggml_reshape_3d(ctx, v, head_dim, n_kv_head, batch.n_tokens);
// 3. 应用RoPE
q = apply_rope(ctx, model, q, batch.pos, layer_idx);
k = apply_rope(ctx, model, k, batch.pos, layer_idx);
// 4. KV缓存处理
auto [k_cache, v_cache] = kv_cache.get_kv(layer_idx);
k = ggml_update_kv(ctx, k_cache, k);
v = ggml_update_kv(ctx, v_cache, v);
// 5. 重复K和V头(用于GQA)
k = ggml_repeat_kv(ctx, k, n_rep);
v = ggml_repeat_kv(ctx, v, n_rep);
// 6. 注意力计算
ggml_tensor * attn = compute_scaled_dot_product_attention(ctx, q, k, v);
// 7. 输出投影
ggml_tensor * out = ggml_mul_mat(ctx, model.layers[layer_idx].wo, attn);
return out;
}
// 计算缩放点积注意力
ggml_tensor * compute_scaled_dot_product_attention(
struct ggml_context * ctx,
ggml_tensor * q,
ggml_tensor * k,
ggml_tensor * v) {
// q: [n_tokens, n_heads, head_dim]
// k: [n_ctx, n_kv_head, head_dim] -> [n_ctx, n_heads, head_dim]
// v: [n_ctx, n_kv_head, head_dim] -> [n_ctx, n_heads, head_dim]
// 1. 转置为便于矩阵乘法的格式
q = ggml_transpose(ctx, ggml_cont_3d(ctx, q));
k = ggml_transpose(ctx, ggml_cont_3d(ctx, k));
// 2. 注意力分数计算
ggml_tensor * scores = ggml_mul_mat(ctx, q, k);
scores = ggml_scale(ctx, scores, 1.0f / sqrt(model.n_embd / model.n_head));
// 3. 应用注意力掩码
scores = apply_attention_mask(ctx, scores);
// 4. Softmax
ggml_tensor * attn_weights = ggml_soft_max(ctx, scores);
// 5. 加权和
ggml_tensor * context = ggml_mul_mat(ctx, attn_weights, v);
// 6. 转置回原始格式
context = ggml_transpose(ctx, context);
return context;
}
};
// 注册LLaMA架构
REGISTER_LLAMA_ARCH(LLM_ARCH_LLAMA, llm_build_llama);
3.6 llama-sampling.cpp - 采样算法实现
3.6.1. 采样器接口
// 采样器基类
struct llama_sampler {
virtual ~llama_sampler() = default;
virtual void reset() = 0;
virtual llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) = 0;
virtual void apply(struct llama_context * ctx,
llama_token_data_array * candidates) = 0;
};
// 采样器链
struct llama_sampler_chain {
std::vector<std::unique_ptr<llama_sampler>> samplers;
template<typename T, typename... Args>
void add(Args&&... args) {
samplers.emplace_back(std::make_unique<T>(std::forward<Args>(args)...));
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) {
// 1. 创建候选数组
llama_token_data_array candidates;
candidates.data = new llama_token_data[n_logits];
candidates.size = n_logits;
candidates.sorted = false;
// 2. 初始化候选数据
for (int i = 0; i < n_logits; ++i) {
candidates.data[i].id = i;
candidates.data[i].logit = logits[i];
candidates.data[i].p = 0.0f;
}
// 3. 应用所有采样器
for (auto & sampler : samplers) {
sampler->apply(ctx, &candidates);
}
// 4. 最终采样
llama_token result = samplers.back()->sample(ctx, candidates.data, candidates.size);
delete[] candidates.data;
return result;
}
};
3.6.2. 具体采样算法实现
// Top-K采样器
class top_k_sampler : public llama_sampler {
private:
int k_;
public:
top_k_sampler(int k) : k_(k) {}
void apply(struct llama_context * ctx,
llama_token_data_array * candidates) override {
if (k_ >= candidates->size) {
return; // 不需要处理
}
// 1. 获取前K个候选
std::partial_sort(
candidates->data,
candidates->data + k_,
candidates->data + candidates->size,
[](const llama_token_data & a, const llama_token_data & b) {
return a.logit > b.logit;
}
);
// 2. 截断到K个
candidates->size = k_;
candidates->sorted = true;
// 3. 计算概率
apply_softmax(candidates);
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) override {
// Top-K不直接采样,需要配合其他采样器
return 0;
}
void reset() override {
// 无状态,无需重置
}
};
// Top-P (Nucleus)采样器
class top_p_sampler : public llama_sampler {
private:
float p_;
public:
top_p_sampler(float p) : p_(p) {}
void apply(struct llama_context * ctx,
llama_token_data_array * candidates) override {
if (candidates->size == 0) return;
// 1. 确保已排序
if (!candidates->sorted) {
std::sort(candidates->data, candidates->data + candidates->size,
[](const llama_token_data & a, const llama_token_data & b) {
return a.logit > b.logit;
});
candidates->sorted = true;
}
// 2. 计算累积概率
float cumsum = 0.0f;
int cutoff = candidates->size;
for (int i = 0; i < candidates->size; ++i) {
cumsum += candidates->data[i].p;
if (cumsum > p_) {
cutoff = i + 1;
break;
}
}
// 3. 截断到累积概率p
candidates->size = cutoff;
// 4. 重新归一化
float sum = 0.0f;
for (int i = 0; i < candidates->size; ++i) {
sum += candidates->data[i].p;
}
for (int i = 0; i < candidates->size; ++i) {
candidates->data[i].p /= sum;
}
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) override {
// Top-P不直接采样,需要配合其他采样器
return 0;
}
void reset() override {
// 无状态,无需重置
}
};
// 温度采样器
class temperature_sampler : public llama_sampler {
private:
float temperature_;
std::mt19937 rng_;
public:
temperature_sampler(float temperature, uint32_t seed)
: temperature_(temperature), rng_(seed) {
if (temperature_ <= 0.0f) {
temperature_ = 1.0f;
}
}
void apply(struct llama_context * ctx,
llama_token_data_array * candidates) override {
if (temperature_ == 1.0f) {
// 无需调整
apply_softmax(candidates);
return;
}
// 1. 应用温度缩放
for (int i = 0; i < candidates->size; ++i) {
candidates->data[i].logit /= temperature_;
}
// 2. 计算softmax概率
apply_softmax(candidates);
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) override {
// 温度采样器不直接实现采样逻辑
// 实际采样在采样器链的最后进行
return 0;
}
void reset() override {
rng_.seed(std::random_device{}());
}
};
// 最终采样器(多项式采样)
class multinomial_sampler : public llama_sampler {
private:
std::mt19937 rng_;
public:
multinomial_sampler(uint32_t seed) : rng_(seed) {}
void apply(struct llama_context * ctx,
llama_token_data_array * candidates) override {
// 确保概率已归一化
apply_softmax(candidates);
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) override {
// 多项式分布采样
std::discrete_distribution<> dist;
// 创建概率分布
std::vector<double> probs;
for (int i = 0; i < n_logits; ++i) {
probs.push_back(logits[i]);
}
dist = std::discrete_distribution<>(probs.begin(), probs.end());
return dist(rng_);
}
void reset() override {
rng_.seed(std::random_device{}());
}
};
// Softmax应用函数
void apply_softmax(llama_token_data_array * candidates) {
if (candidates->size == 0) return;
// 1. 数值稳定化:减去最大值
float max_logit = candidates->data[0].logit;
for (int i = 1; i < candidates->size; ++i) {
max_logit = std::max(max_logit, candidates->data[i].logit);
}
// 2. 计算指数和总和
float sum_exp = 0.0f;
for (int i = 0; i < candidates->size; ++i) {
float exp_val = expf(candidates->data[i].logit - max_logit);
candidates->data[i].p = exp_val;
sum_exp += exp_val;
}
// 3. 归一化
for (int i = 0; i < candidates->size; ++i) {
candidates->data[i].p /= sum_exp;
}
}
3.7 llama-grammar.cpp - 语法约束解析
3.7.1. 语法系统数据结构
// 语法元素类型
enum llama_grammar_element_type {
LLM_GRAMMAR_ELEMENT_END = 0, // 结束元素
LLM_GRAMMAR_ELEMENT_NON_TERMINAL = 1, // 非终结符
LLM_GRAMMAR_ELEMENT_TERMINAL = 2, // 终结符
LLM_GRAMMAR_ELEMENT_ALT = 3, // 选择(或)
LLM_GRAMMAR_ELEMENT_OPT = 4, // 可选
LLM_GRAMMAR_ELEMENT_STAR = 5, // 闭包(零或多个)
LLM_GRAMMAR_ELEMENT_PLUS = 6, // 正闭包(一个或多个)
LLM_GRAMMAR_ELEMENT_RULE_REF = 7, // 规则引用
LLM_GRAMMAR_ELEMENT_CHAR_RANGE = 8, // 字符范围
};
// 语法元素
struct llama_grammar_element {
enum llama_grammar_element_type type;
uint32_t value; // 对于不同类型有不同含义
};
// 语法规则(元素序列)
using llama_grammar_rule = std::vector<llama_grammar_element>;
// 语法栈(跟踪解析状态)
using llama_grammar_stack = std::vector<const llama_grammar_element *>;
// 主语法结构
struct llama_grammar {
std::vector<llama_grammar_rule> rules; // 语法规则
std::vector<llama_grammar_stack> stacks; // 解析栈
std::vector<llama_grammar_element> buffer; // 元素缓冲区
// 递归下降解析器状态
std::vector<llama_grammar_stack> partial_stacks;
// 触发条件
std::vector<llama_grammar_trigger> triggers;
};
// 语法触发器
struct llama_grammar_trigger {
std::string pattern; // 触发模式
std::vector<int> triggered_rules; // 触发的规则
bool enabled; // 是否启用
};
3.7.2. BNF解析器实现
// BNF语法解析器
class bnf_parser {
private:
std::string input_;
size_t pos_;
// 规则名映射
std::unordered_map<std::string, int> rule_names_;
std::vector<llama_grammar_rule> rules_;
public:
bnf_parser(const std::string & input) : input_(input), pos_(0) {}
llama_grammar parse() {
// 1. 预扫描,收集所有规则名
scan_rule_names();
// 2. 解析规则
parse_rules();
// 3. 构建语法对象
llama_grammar grammar;
grammar.rules = rules_;
grammar.stacks.push_back({rules_[0].data()}); // 从起始规则开始
return grammar;
}
private:
void scan_rule_names() {
size_t save_pos = pos_;
pos_ = 0;
while (pos_ < input_.size()) {
skip_whitespace();
if (pos_ >= input_.size()) break;
// 读取规则名
std::string name = read_identifier();
if (name.empty()) break;
// 检查是否是规则定义
skip_whitespace();
if (pos_ < input_.size() && input_[pos_] == '=') {
rule_names_[name] = -1; // 标记但暂不分配ID
}
// 跳到下一行
while (pos_ < input_.size() && input_[pos_] != '\n') {
pos_++;
}
}
pos_ = save_pos;
}
void parse_rules() {
int rule_id = 0;
while (pos_ < input_.size()) {
skip_whitespace();
if (pos_ >= input_.size()) break;
// 读取规则名
std::string name = read_identifier();
if (name.empty()) break;
skip_whitespace();
// 期望 '='
if (pos_ >= input_.size() || input_[pos_] != '=') {
throw std::runtime_error("Expected '=' after rule name");
}
pos_++;
// 分配规则ID
if (rule_names_.find(name) != rule_names_.end()) {
rule_names_[name] = rule_id++;
}
// 解析规则体
llama_grammar_rule rule = parse_rule_body();
rules_.push_back(rule);
// 跳到下一规则
skip_whitespace();
}
}
llama_grammar_rule parse_rule_body() {
llama_grammar_rule rule;
skip_whitespace();
// 解析元素序列
while (pos_ < input_.size() && input_[pos_] != '\n') {
llama_grammar_element element = parse_element();
rule.push_back(element);
skip_whitespace();
// 检查操作符
if (pos_ < input_.size()) {
switch (input_[pos_]) {
case '|':
rule.push_back({LLM_GRAMMAR_ELEMENT_ALT, 0});
pos_++;
break;
case '*':
rule.push_back({LLM_GRAMMAR_ELEMENT_STAR, 0});
pos_++;
break;
case '+':
rule.push_back({LLM_GRAMMAR_ELEMENT_PLUS, 0});
pos_++;
break;
case '?':
rule.push_back({LLM_GRAMMAR_ELEMENT_OPT, 0});
pos_++;
break;
}
}
skip_whitespace();
}
// 添加结束标记
rule.push_back({LLM_GRAMMAR_ELEMENT_END, 0});
return rule;
}
llama_grammar_element parse_element() {
llama_grammar_element element;
if (pos_ >= input_.size()) {
throw std::runtime_error("Unexpected end of input");
}
char c = input_[pos_];
if (c == '"') {
// 字符串字面量
pos_++;
std::string literal = read_string_literal();
for (char ch : literal) {
element = {LLM_GRAMMAR_ELEMENT_TERMINAL, static_cast<uint32_t>(ch)};
}
} else if (c == '[') {
// 字符范围
pos_++;
auto [start, end] = parse_char_range();
element = {LLM_GRAMMAR_ELEMENT_CHAR_RANGE,
(static_cast<uint32_t>(end) << 8) | static_cast<uint32_t>(start)};
} else if (c == '(') {
// 分组
pos_++;
llama_grammar_rule group = parse_rule_body();
if (pos_ >= input_.size() || input_[pos_] != ')') {
throw std::runtime_error("Expected ')'");
}
pos_++;
// 将组内容插入到当前规则中
} else if (is_identifier_start(c)) {
// 规则引用
std::string ref_name = read_identifier();
if (rule_names_.find(ref_name) != rule_names_.end()) {
element = {LLM_GRAMMAR_ELEMENT_RULE_REF,
static_cast<uint32_t>(rule_names_[ref_name])};
} else {
throw std::runtime_error("Undefined rule reference: " + ref_name);
}
} else {
throw std::runtime_error(std::string("Unexpected character: ") + c);
}
return element;
}
std::string read_identifier() {
std::string result;
while (pos_ < input_.size() &&
(isalnum(input_[pos_]) || input_[pos_] == '_')) {
result += input_[pos_++];
}
return result;
}
std::string read_string_literal() {
std::string result;
while (pos_ < input_.size() && input_[pos_] != '"') {
if (input_[pos_] == '\\') {
pos_++; // 转义字符
if (pos_ < input_.size()) {
result += input_[pos_++];
}
} else {
result += input_[pos_++];
}
}
if (pos_ >= input_.size() || input_[pos_] != '"') {
throw std::runtime_error("Unterminated string literal");
}
pos_++;
return result;
}
std::pair<char, char> parse_char_range() {
char start = 0, end = 0;
if (pos_ < input_.size()) {
start = input_[pos_++];
}
if (pos_ < input_.size() && input_[pos_] == '-') {
pos_++;
if (pos_ < input_.size()) {
end = input_[pos_++];
}
} else {
end = start; // 单个字符
}
if (pos_ >= input_.size() || input_[pos_] != ']') {
throw std::runtime_error("Expected ']' to close character range");
}
pos_++;
return {start, end};
}
};
3.7.3. 语法约束采样实现
// 语法约束采样器
class grammar_constraining_sampler : public llama_sampler {
private:
llama_grammar grammar_;
std::vector<llama_grammar_stack> partial_stacks_;
public:
grammar_constraining_sampler(const llama_grammar & grammar)
: grammar_(grammar), partial_stacks_({grammar_.stacks}) {}
void apply(struct llama_context * ctx,
llama_token_data_array * candidates) override {
if (candidates->size == 0) return;
// 1. 为每个候选token计算语法接受度
std::vector<float> grammar_scores(candidates->size);
for (int i = 0; i < candidates->size; ++i) {
llama_token token = candidates->data[i].id;
// 2. 检查token是否符合语法
bool accepted = test_token_against_grammar(token);
if (accepted) {
grammar_scores[i] = 1.0f;
} else {
grammar_scores[i] = 0.0f;
}
}
// 3. 应用语法约束:不符合语法的token概率设为0
for (int i = 0; i < candidates->size; ++i) {
if (grammar_scores[i] == 0.0f) {
candidates->data[i].logit = -INFINITY;
candidates->data[i].p = 0.0f;
}
}
// 4. 重新计算概率分布
apply_softmax(candidates);
}
llama_token sample(struct llama_context * ctx,
const float * logits,
int n_logits) override {
// 约束采样器不直接采样
return 0;
}
void reset() override {
partial_stacks_ = {grammar_.stacks};
}
private:
bool test_token_against_grammar(llama_token token) {
std::string token_text = get_token_text(token);
// 对每个部分栈检查
for (auto & stack : partial_stacks_) {
if (test_token_against_stack(token_text, stack)) {
return true;
}
}
return false;
}
bool test_token_against_stack(const std::string & token,
const llama_grammar_stack & stack) {
// 复制栈以避免修改原始状态
llama_grammar_stack test_stack = stack;
for (char c : token) {
bool matched = false;
// 遍历栈中的元素
for (size_t i = 0; i < test_stack.size(); ++i) {
const llama_grammar_element * elem = test_stack[i];
if (elem->type == LLM_GRAMMAR_ELEMENT_TERMINAL) {
if (static_cast<char>(elem->value) == c) {
test_stack.erase(test_stack.begin() + i);
matched = true;
break;
}
} else if (elem->type == LLM_GRAMMAR_ELEMENT_CHAR_RANGE) {
char start = elem->value & 0xFF;
char end = (elem->value >> 8) & 0xFF;
if (c >= start && c <= end) {
test_stack.erase(test_stack.begin() + i);
matched = true;
break;
}
} else if (elem->type == LLM_GRAMMAR_ELEMENT_RULE_REF) {
// 展开规则引用
int rule_id = elem->value;
const llama_grammar_rule & rule = grammar_.rules[rule_id];
test_stack.erase(test_stack.begin() + i);
test_stack.insert(test_stack.begin() + i,
rule.begin(), rule.end());
// 重新检查当前字符
i--;
continue;
}
}
if (!matched) {
return false; // 无法匹配当前字符
}
}
return true; // token完全匹配
}
std::string get_token_text(llama_token token) {
// 从词汇表中获取token的文本表示
// 这里简化处理
return std::to_string(token);
}
};
4. 性能优化策略
4.1 计算图优化
// 计算图缓存和复用
class computation_graph_cache {
private:
std::unordered_map<std::string, ggml_cgraph *> graph_cache_;
public:
ggml_cgraph * get_or_build_graph(const std::string & key,
std::function<ggml_cgraph *()> builder) {
auto it = graph_cache_.find(key);
if (it != graph_cache_.end()) {
return it->second;
}
ggml_cgraph * graph = builder();
graph_cache_[key] = graph;
return graph;
}
void clear_cache() {
for (auto & pair : graph_cache_) {
ggml_free(pair.second);
}
graph_cache_.clear();
}
};
4.2 内存优化
// 智能内存池
class llama_memory_pool {
private:
struct memory_block {
void * ptr;
size_t size;
bool in_use;
};
std::vector<memory_block> blocks_;
size_t total_allocated_;
public:
void * allocate(size_t size) {
// 1. 查找可重用的块
for (auto & block : blocks_) {
if (!block.in_use && block.size >= size) {
block.in_use = true;
return block.ptr;
}
}
// 2. 分配新块
void * ptr = aligned_alloc(32, size); // 32字节对齐
blocks_.push_back({ptr, size, true});
total_allocated_ += size;
return ptr;
}
void deallocate(void * ptr) {
for (auto & block : blocks_) {
if (block.ptr == ptr) {
block.in_use = false;
return;
}
}
}
// 内存碎片整理
void compact() {
// 合并相邻的空闲块
for (size_t i = 0; i < blocks_.size() - 1; ++i) {
if (!blocks_[i].in_use && !blocks_[i+1].in_use) {
// 合并逻辑
// ...
}
}
}
};
4.3 并行优化
// 多线程批处理优化
class parallel_batch_processor {
private:
thread_pool thread_pool_;
public:
void process_batch_parallel(llama_context * ctx,
const llama_batch & batch) {
// 1. 分析数据依赖
auto dependency_graph = analyze_dependencies(batch);
// 2. 并行执行独立任务
std::vector<std::future<void>> futures;
for (const auto & independent_set : dependency_graph) {
for (const auto & task : independent_set) {
futures.push_back(thread_pool_.submit([=]() {
execute_task(ctx, task);
}));
}
// 等待当前批次完成
for (auto & future : futures) {
future.wait();
}
futures.clear();
}
}
private:
std::vector<std::vector<int>> analyze_dependencies(const llama_batch & batch) {
// 分析数据依赖关系,构建任务依赖图
// 返回可并行执行的任务集合
return {};
}
void execute_task(llama_context * ctx, int task_id) {
// 执行具体任务
}
};
5. 错误处理和调试
5.1 错误处理机制
// 统一错误处理
enum llama_error_code {
LLAMA_ERROR_NONE = 0,
LLAMA_ERROR_INVALID_PARAM,
LLAMA_ERROR_FAILED_TO_LOAD_MODEL,
LLAMA_ERROR_INSUFFICIENT_MEMORY,
LLAMA_ERROR_COMPUTE_FAILED,
LLAMA_ERROR_TENSOR_NOT_FOUND,
LLAMA_ERROR_UNSUPPORTED_ARCHITECTURE
};
class llama_exception : public std::exception {
private:
llama_error_code error_code_;
std::string message_;
public:
llama_exception(llama_error_code code, const std::string & message)
: error_code_(code), message_(message) {}
const char * what() const noexcept override {
return message_.c_str();
}
llama_error_code error_code() const noexcept {
return error_code_;
}
};
// RAII资源管理
class llama_context_guard {
private:
llama_context * ctx_;
public:
explicit llama_context_guard(llama_context * ctx) : ctx_(ctx) {}
~llama_context_guard() {
if (ctx_) {
llama_free(ctx_);
}
}
llama_context * get() const noexcept {
return ctx_;
}
llama_context * release() noexcept {
llama_context * tmp = ctx_;
ctx_ = nullptr;
return tmp;
}
};
5.2 调试和日志
// 调试支持
class llama_debug {
private:
std::ofstream log_file_;
bool enabled_;
public:
llama_debug(const std::string & log_path, bool enabled = false)
: enabled_(enabled) {
if (enabled_) {
log_file_.open(log_path, std::ios::app);
}
}
void log_tensor_info(const std::string & name,
const ggml_tensor * tensor) {
if (!enabled_) return;
log_file_ << "Tensor: " << name << std::endl;
log_file_ << " Type: " << ggml_type_name(tensor->type) << std::endl;
log_file_ << " Ne: [" << tensor->ne[0] << ", " << tensor->ne[1]
<< ", " << tensor->ne[2] << ", " << tensor->ne[3] << "]" << std::endl;
log_file_ << " Nb: [" << tensor->nb[0] << ", " << tensor->nb[1]
<< ", " << tensor->nb[2] << ", " << tensor->nb[3] << "]" << std::endl;
log_file_ << std::endl;
}
void log_inference_step(int step, const llama_batch & batch) {
if (!enabled_) return;
log_file_ << "Inference step " << step << std::endl;
log_file_ << " Batch size: " << batch.n_tokens << std::endl;
log_file_ << " Tokens: ";
for (int i = 0; i < batch.n_tokens; ++i) {
log_file_ << batch.tokens[i] << " ";
}
log_file_ << std::endl;
}
void log_performance_metrics(const std::string & operation,
double time_ms) {
if (!enabled_) return;
log_file_ << "Performance: " << operation
<< " took " << time_ms << " ms" << std::endl;
}
};
6. 总结
Llama.cpp 核心推理模块 (src/) 展现了现代C++系统设计的优秀实践:
6.1 架构优势
1. 高度模块化:清晰的职责分离,易于维护和扩展
2. 统一接口:所有模型架构使用相同的API接口
3. 性能导向:针对推理场景的专门优化
4. 资源管理:智能的内存管理和设备调度
6.2 技术创新
1. 多架构支持:100+种模型架构的统一实现
2. 智能分层:GPU/CPU自适应分层策略
3. 语法约束:创新的BNF语法约束采样
4. 高效采样:多种采样算法的灵活组合
6.3 工程实践
1. RAII设计:自动资源管理,避免内存泄漏
2. 错误处理:统一的错误处理机制
3. 调试支持:完善的日志和调试工具
4. 性能监控:内置的性能分析工具
这个核心推理模块为llama.cpp项目提供了坚实的技术基础,使其能够在各种硬件平台上高效运行,成为业界领先的LLM推理引擎。

1233

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



