保存时间:2026/4/2 09:32:19
MoELayer类,在类的初始化函数中会设置相关参数,如专家层的维度、专家的数量、激活策略等。对于闸门参数,可能会作为类的属性进行存储和管理,通过特定的函数来控制其开关和激活状态。run.c (准确来说在 LLaMA.cpp 项目里代码逻辑实现分散在多个源文件中)的代码实现可能会因不同版本和修改有所差异,并且其代码量相对较多,以下是简化后展示其关键数据结构部分示例(仅为示意,实际代码更复杂且完整项目有更多相关逻辑和处理):#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义一些基础参数,比如模型的维度等,这里只是示例
#define N_CTX 2048 // 上下文长度
#define N_EMBD 4096 // 嵌入维度
// 定义一个简单的张量结构来表示数据
typedef struct {
float *data; // 数据指针,指向实际的浮点数数据
int n_dims; // 维度数量
int *shape; // 各维度的大小
} Tensor;
// 定义注意力头相关的结构
typedef struct {
Tensor *query;
Tensor *key;
Tensor *value;
} AttentionHead;
// 定义多头注意力层的结构
typedef struct {
int n_heads;
AttentionHead **heads;
Tensor *output;
} MultiHeadAttention;
// 定义前馈神经网络层的结构
typedef struct {
Tensor *fc1;
Tensor *fc2;
} FeedForwardNetwork;
// 定义Transformer层的结构
typedef struct {
MultiHeadAttention *attention;
FeedForwardNetwork *ffn;
Tensor *input;
Tensor *output;
} TransformerLayer;
// 定义整个模型的结构
typedef struct {
int n_layers;
TransformerLayer **layers;
Tensor *input_embeddings;
Tensor *output_layer;
} LLaMA_Model;
run.c 代码具体的细节(实际的 llama.cpp 代码中函数众多且结构复杂),以下是从常见的在 llama.cpp 中可能出现的基础函数示例来进行解析,以初始化张量相关的函数为例:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义一个简单的张量结构来表示数据
typedef struct {
float *data; // 数据指针,指向实际的浮点数数据
int n_dims; // 维度数量
int *shape; // 各维度的大小
} Tensor;
// 初始化张量函数
Tensor* tensor_init(int n_dims, int *shape) {
Tensor *tensor = (Tensor *)malloc(sizeof(Tensor));
if (tensor == NULL) {
fprintf(stderr, "Failed to allocate memory for tensor.\n");
return NULL;
}
tensor->n_dims = n_dims;
tensor->shape = (int *)malloc(n_dims * sizeof(int));
if (tensor->shape == NULL) {
fprintf(stderr, "Failed to allocate memory for tensor shape.\n");
free(tensor);
return NULL;
}
// 复制传入的形状信息到张量的形状数组中
for (int i = 0; i < n_dims; ++i) {
tensor->shape[i] = shape[i];
}
// 计算张量元素的总数
int total_elements = 1;
for (int i = 0; i < n_dims; ++i) {
total_elements *= shape[i];
}
// 分配数据内存
tensor->data = (float *)malloc(total_elements * sizeof(float));
if (tensor->data == NULL) {
fprintf(stderr, "Failed to allocate memory for tensor data.\n");
free(tensor->shape);
free(tensor);
return NULL;
}
return tensor;
}
tensor_init 函数的作用是:Tensor 结构体分配内存空间,如果分配失败则打印错误信息并返回 NULL。NULL;如果所有步骤都成功,则返回初始化好的 Tensor 结构体指针。run.c 代码情况告诉我更多信息,以便我更准确地分析其他函数。llama.c 项目的 run.c 代码内容,以便进行分析。不过从你之前的描述来看,推测你可能说的是 Andrej Karpathy 的 llama2.c 项目,其run.c代码可在https://github.com/karpathy/llama2.c/blob/master/run.c查看。如果你说的不是这个项目,提供链接后能更精准地满足你的需求。// build the Transformer via the model.bin file
Transformer transformer;
build_transformer(&transformer, checkpoint_path);
if (steps == 0 || steps > transformer.config.seq_len) steps = transformer.config.seq_len; // override to ~max length
Transformer 类型的变量 transformer,用于表示 Transformer 模型。build_transformer 函数,传入 transformer 的地址和 checkpoint_path(模型检查点文件路径,这里应该是 model.bin 文件的路径)。这个函数的作用是根据指定的检查点文件来构建 Transformer 模型,将模型的相关参数和配置等信息加载到 transformer 结构体中。if 语句检查 steps(应该是指定的生成步骤数量)的值,如果 steps 为 0 或者大于 transformer.config.seq_len(模型配置中的序列长度),则将 steps 的值设置为 transformer.config.seq_len,这样做是为了将 steps 限制在模型允许的最大序列长度范围内。// build the Tokenizer via the tokenizer.bin file
Tokenizer tokenizer;
build_tokenizer(&tokenizer, tokenizer_path, transformer.config.vocab_size);
Tokenizer 类型的变量 tokenizer,用于表示分词器。build_tokenizer 函数,传入 tokenizer 的地址、tokenizer_path(分词器文件路径,这里是 tokenizer.bin 文件的路径)以及 transformer.config.vocab_size(模型的词汇表大小)。这个函数的作用是根据指定的分词器文件和词汇表大小来构建分词器,用于将输入文本转换为模型能够处理的 token 序列。// build the Sampler
Sampler sampler;
build_sampler(&sampler, transformer.config.vocab_size, temperature, topp, rng_seed);
Sampler 类型的变量 sampler,用于表示采样器。build_sampler 函数,传入 sampler 的地址、transformer.config.vocab_size(词汇表大小)、temperature(温度参数,用于调整采样的随机性)、topp(核采样的阈值参数)和 rng_seed(随机数生成器种子)。这个函数的作用是根据这些参数来构建采样器,在生成文本时用于从模型预测的概率分布中采样得到下一个 token。// run!
if (strcmp(mode, "generate") == 0) {
generate(&transformer, &tokenizer, &sampler, prompt, steps);
} else if (strcmp(mode, "chat") == 0) {
chat(&transformer, &tokenizer, &sampler, prompt, system_prompt, steps);
} else {
fprintf(stderr, "unknown mode: %s\n", mode);
error_usage();
}
mode 变量的值来决定执行的操作。mode 应该是一个表示运行模式的字符串。mode 等于 "generate",则调用 generate 函数,传入 transformer、tokenizer、sampler 的地址,以及 prompt(输入的文本提示)和 steps(生成的步骤数量)。这个函数的作用是使用构建好的模型、分词器和采样器,根据输入的提示文本生成指定长度的文本。mode 等于 "chat",则调用 chat 函数,传入 transformer、tokenizer、sampler 的地址,以及 prompt(输入的对话文本)、system_prompt(系统提示文本)和 steps(生成的步骤数量)。这个函数的作用是进行对话模式的文本生成。mode 不是 "generate" 或 "chat",则向标准错误输出流打印错误信息,提示未知的运行模式,并调用 error_usage 函数(推测这个函数用于打印程序的正确使用方法)。// memory and file handles cleanup
free_sampler(&sampler);
free_tokenizer(&tokenizer);
free_transformer(&transformer);
return 0;
free_sampler、free_tokenizer 和 free_transformer 函数,传入对应的结构体地址,用于释放之前分配的内存,包括采样器、分词器和 Transformer 模型相关的内存。malloc_run_state(RunState* s, Config* p):为RunState结构体分配内存,用于存储模型运行时的激活状态数据。free_run_state(RunState* s):释放RunState结构体中分配的内存。memory_map_weights(TransformerWeights *w, Config* p, float* ptr, int shared_weights):将模型权重内存映射到TransformerWeights结构体中。read_checkpoint(char* checkpoint, Config* config, TransformerWeights* weights, int* fd, float** data, ssize_t* file_size):读取模型检查点文件,获取模型配置和权重信息,并进行内存映射。build_transformer(Transformer *t, char* checkpoint_path):构建Transformer模型,包括读取检查点文件和分配RunState内存。free_transformer(Transformer* t):释放Transformer模型占用的内存,包括关闭内存映射和释放RunState内存。rmsnorm(float* o, float* x, float* weight, int size):对输入数据进行均方根归一化(RMSNorm)操作。softmax(float* x, int size):对输入数据应用 Softmax 函数,将其转换为概率分布。matmul(float* xout, float* x, float* w, int n, int d):执行矩阵乘法运算。forward(Transformer* transformer, int token, int pos):执行 Transformer 模型的前向传播计算,返回模型的输出 logits。build_tokenizer(Tokenizer* t, char* tokenizer_path, int vocab_size):构建分词器,从文件中读取词汇表和相关信息。free_tokenizer(Tokenizer* t):释放分词器占用的内存。decode(Tokenizer* t, int prev_token, int token):将 token 解码为字符串。safe_printf(char *piece):安全打印字符串,避免打印不可见字符。str_lookup(char *str, TokenIndex *sorted_vocab, int vocab_size):在排序后的词汇表中查找字符串对应的索引。encode(Tokenizer* t, char *text, int8_t bos, int8_t eos, int *tokens, int *n_tokens):将字符串编码为 token 序列。sample_argmax(float* probabilities, int n):返回概率最高的索引,用于贪婪采样。sample_mult(float* probabilities, int n, float coin):根据概率分布进行采样。compare(const void* a, const void* b):用于对ProbIndex结构体进行排序的比较函数。sample_topp(float* probabilities, int n, float topp, ProbIndex* probindex, float coin):执行核采样(top-p 采样)。build_sampler(Sampler* sampler, int vocab_size, float temperature, float topp, unsigned long long rng_seed):构建采样器,设置相关参数。free_sampler(Sampler* sampler):释放采样器占用的内存。random_u32(unsigned long long *state):生成一个 32 位随机无符号整数。random_f32(unsigned long long *state):生成一个在 [0, 1) 范围内的随机浮点数。sample(Sampler* sampler, float* logits):根据模型输出的 logits 和采样器参数进行采样,返回采样得到的 token。generate(Transformer *transformer, Tokenizer *tokenizer, Sampler *sampler, char *prompt, int steps):根据输入提示生成文本。read_stdin(const char* guide, char* buffer, size_t bufsize):从标准输入读取一行数据。chat(Transformer *transformer, Tokenizer *tokenizer, Sampler *sampler, char *cli_user_prompt, char *cli_system_prompt, int steps):实现聊天功能,处理用户输入并生成回复。error_usage():打印程序使用方法的错误信息并退出程序。main(int argc, char *argv[]):程序入口,解析命令行参数,构建模型、分词器和采样器,并根据模式调用生成或聊天函数。char* decode(Tokenizer* t, int prev_token, int token) {
char *piece = t->vocab[token];
// following BOS (1) token, sentencepiece decoder strips any leading whitespace (see PR #89)
if (prev_token == 1 && piece[0] == ' ') { piece++; }
// careful, some tokens designate raw bytes, and look like e.g. '<0x01>'
// parse this and convert and return the actual byte
unsigned char byte_val;
if (sscanf(piece, "<0x%02hhX>", &byte_val) == 1) {
piece = (char*)t->byte_pieces + byte_val * 2;
}
return piece;
}
Tokenizer的词汇表vocab中取出索引为token的字符串,将其赋值给piece。这一步是基于模型输出的token,找到其在词汇表中对应的文本表示。prev_token是否为 1(句首标记BOS) ,并且piece的首字符是否为空格。如果满足条件,说明需要去掉这个句首空格,于是将piece指针向后移动一位,跳过这个空格字符。piece是否是表示原始字节的特殊格式,即是否符合<0xXX>这种格式。sscanf函数尝试从piece中按照<0x%02hhX>的格式解析出一个无符号字符byte_val。如果解析成功(sscanf返回 1),说明piece表示一个原始字节,此时将piece重新赋值为tokenizer的byte_pieces数组中对应byte_val的位置。byte_pieces数组存储了所有单字节字符串,每个字节对应两个字符的位置(如byte_val对应的是byte_val * 2和byte_val * 2 + 1)。piece字符串,这个字符串就是token解码后的文本内容。