欢迎阅读「 [InferLLM大模型推理框架项目](15)Llama系列模型代码实现(src/graph/ggml_llama.h+.cpp) 」
音乐播放器
sola的小屋
 
文章 标签
52 20

Powered by Gridea | Theme: Fog
本站已安全运行 1775 天
04 小时 25 分 13 秒
总访问量:2143  |   访问人数:1704

[InferLLM大模型推理框架项目](15)Llama系列模型代码实现(src/graph/ggml_llama.h+.cpp)

GGML Llama 模型代码结构与功能实现分析

ggml_llama.hggml_llama.cpp 文件实现了 InferLLM 框架中对 GGML 格式的 Llama 模型的支持。这些文件定义了 GgmlLlamaGraph 类,该类继承自基础的 Graph 类,并实现了特定于 GGML Llama 模型的加载和计算图构建逻辑。

1. ggml_llama.h 文件分析

1.1 文件结构

ggml_llama.h 文件包含以下几个部分:

  1. 头文件引入

    #include <unordered_map>
    #include "core/graph.h"
    #include "core/kvstorage.h"
    #include "core/op.h"
    #include "core/tensor.h"
    

    引入了必要的头文件,包括图、KV存储、算子和张量等核心组件。

  2. 命名空间声明

    namespace inferllm {
    

    所有代码都在 inferllm 命名空间中。

  3. 模型类型枚举

    enum class LlamaModelType {
        LLAMA_FILE_VERSION_GGML = 0,
        LLAMA_FILE_VERSION_GGMF_V1,  // added version field and scores in vocab
        LLAMA_FILE_VERSION_GGJT_V1
    };
    

    定义了三种 GGML 格式的 Llama 模型类型:

    • LLAMA_FILE_VERSION_GGML:原始 GGML 格式
    • LLAMA_FILE_VERSION_GGMF_V1:添加了版本字段和词汇表分数的格式
    • LLAMA_FILE_VERSION_GGJT_V1:另一种扩展格式
  4. GgmlLlamaGraph 类定义

    class GgmlLlamaGraph : public Graph {
        using Graph::Graph;
    
    public:
        void set_weights_alias() override;
        void construct_llm() override;
    
        void load(
                std::shared_ptr<InputFile> fin, LlmParams& param,
                std::shared_ptr<Vocab> vocab) override;
    };
    

    定义了 GgmlLlamaGraph 类,继承自 Graph 类,并重写了三个关键方法:

    • set_weights_alias:设置权重别名映射
    • construct_llm:构建模型计算图
    • load:加载模型参数和权重

2. ggml_llama.cpp 文件分析

2.1 权重别名设置

void GgmlLlamaGraph::set_weights_alias() {
    m_weights_name_aliases.clear();
    m_weights_name_aliases = {
            {"norm.weight", "head.norm.weight"},
            {"output.weight", "head.output.weight"},
            {"layers.x.feed_forward.w3.weight", "layers.x.ffn.w3.weight"},
            {"layers.x.feed_forward.w2.weight", "layers.x.ffn.w2.weight"},
            {"layers.x.feed_forward.w1.weight", "layers.x.ffn.w1.weight"},
            {"layers.x.ffn_norm.weight", "layers.x.ffn.norm.weight"},
            {"layers.x.attention_norm.weight", "layers.x.attention.norm.weight"},
    };
}

这个方法建立了原始权重名称和内部使用的权重名称之间的映射关系,使得模型可以正确加载预训练权重。

2.2 模型加载

void GgmlLlamaGraph::load(
        std::shared_ptr<InputFile> fin, LlmParams& param,
        std::shared_ptr<Vocab> vocab) {
    // 验证魔数
    uint32_t magic;
    uint32_t version = 0;
    fin->read_raw((char*)&magic, sizeof(magic));
    if (magic != 'ggml') {
        fin->read_raw((char*)&version, sizeof(version));
    }
    LlamaModelType model_type;
    if (magic == 'ggml' && version == 0) {
        model_type = LlamaModelType::LLAMA_FILE_VERSION_GGML;
    } else if (magic == 'ggmf' && version == 1) {
        model_type = LlamaModelType::LLAMA_FILE_VERSION_GGMF_V1;
    } else if (magic == 'ggjt' && version == 1) {
        model_type = LlamaModelType::LLAMA_FILE_VERSION_GGJT_V1;
    } else {
        INFER_ASSERT(0, "unsupported model type.");
    }
    
    // 加载参数
    fin->read_raw((char*)&param.n_vocab, sizeof(param.n_vocab));
    // ... 读取其他参数
    
    // 加载词汇表
    if (model_type == LlamaModelType::LLAMA_FILE_VERSION_GGJT_V1) {
        vocab->load_vocab_with_score(fin, param.n_vocab);
    } else {
        vocab->load_vocab(fin, param.n_vocab);
    }
    
    m_param = param;
    
    // 构建计算图
    construct_llm();
    collect_weights();
    
    // 设置权重别名并加载权重
    set_weights_alias();
    // ... 读取权重
}

这个方法从模型文件中加载参数和权重,包括:

  1. 验证模型格式

    • 读取魔数和版本号
    • 根据魔数和版本号确定模型类型
  2. 加载模型参数

    • 读取词汇表大小、嵌入维度、头数、层数等参数
    • 输出参数信息
  3. 加载词汇表

    • 根据模型类型选择不同的词汇表加载方法
  4. 构建计算图

    • 调用 construct_llm 构建计算图
    • 调用 collect_weights 收集权重
  5. 加载权重

    • 设置权重别名
    • 循环读取权重数据
    • 将权重数据与计算图中的权重关联

2.3 计算图构建

void GgmlLlamaGraph::construct_llm() {
    uint32_t embd = m_param.n_embd;
    uint32_t mult = m_param.n_mult;
    uint32_t head = m_param.n_head;
    uint32_t rot = m_param.n_rot;
    uint32_t ctx = m_param.n_ctx;
    uint32_t n_vocab = m_param.n_vocab;

    size_t nff = ((2 * (4 * embd) / 3 + mult - 1) / mult) * mult;
    m_input = std::make_shared<Tensor>(device(), name() + ":input");
    std::shared_ptr<Tensor> input = m_input;
    //! embd
    input = add_module<EmbdModule>(
            this, input, embd, n_vocab, model_config(), device(), "");

    int nr_layer = m_param.n_layer;
    for (int i = 0; i < nr_layer; i++) {
        std::string name = "layers." + std::to_string(i);
        //! layer norm
        std::shared_ptr<Tensor> attention_input = input;

        auto norm_out_attention = add_one_opr_module<LayerNorm>(
                                          this, OpIOs{attention_input}, device(),
                                          name + ".attention.norm")
                                          ->add_opr(embd);
        //! attentin
        auto attention_output = add_module<AttentionModule<LlamaAttention>>(
                this, norm_out_attention, embd, head, rot, ctx, model_config(),
                device(), name + ".attention", i);
        //! add
        auto add_output = add_one_opr_module<Elemwise>(
                                  this, OpIOs{attention_input, attention_output},
                                  device(), name + ".attention_add")
                                  ->add_opr(ElemMode::Add);

        std::shared_ptr<Tensor> feed_forward_input = add_output;
        //! layer normal
        auto ffn_norm_out =
                add_one_opr_module<LayerNorm>(
                        this, OpIOs{feed_forward_input}, device(), name + ".ffn.norm")
                        ->add_opr(embd);
        //! feed forward
        auto ffn_output = add_module<LlamaFFNModule>(
                this, ffn_norm_out, embd, nff, model_config(), device(), name);
        //! add
        input = add_one_opr_module<Elemwise>(
                        this, OpIOs{feed_forward_input, ffn_output}, device(),
                        name + ".ffn_add")
                        ->add_opr(ElemMode::Add);
    }
    //! the last layer
    m_output = add_module<HeadModule>(
            this, input, embd, n_vocab, model_config(), device(), "head");
}

这个方法构建了 Llama 模型的计算图,包括:

  1. 初始化参数

    • 从模型参数中获取嵌入维度、头数、层数等参数
    • 计算前馈网络的隐藏层大小
  2. 创建输入张量和嵌入层

    • 创建输入张量
    • 添加嵌入层,将 token ID 转换为嵌入向量
  3. 循环添加 Transformer 层
    每个 Transformer 层包括:

    • 注意力层前的层归一化
    • 注意力层 (LlamaAttention)
    • 残差连接
    • 前馈网络前的层归一化
    • 前馈网络 (LlamaFFNModule)
    • 残差连接
  4. 添加输出层

    • 添加 HeadModule,将隐藏状态转换为词汇表概率分布

3. GGML Llama 模型的特点

通过分析 ggml_llama.hggml_llama.cpp 文件,可以看出 GGML Llama 模型的以下特点:

3.1 模型架构

  1. Pre-LayerNorm 架构

    • 在注意力层和前馈网络前进行层归一化
    • 使用 RMSNorm(没有偏置项)
  2. 旋转位置编码

    • 使用旋转位置编码 (RoPE),通过 rot 参数指定旋转维度
  3. SwiGLU 激活函数

    • 在前馈网络中使用 SwiGLU 激活函数,通过 LlamaFFNModule 实现
  4. 残差连接

    • 在注意力层和前馈网络后使用残差连接

3.2 模型格式

GGML Llama 模型支持三种格式:

  1. LLAMA_FILE_VERSION_GGML:原始 GGML 格式
  2. LLAMA_FILE_VERSION_GGMF_V1:添加了版本字段和词汇表分数的格式
  3. LLAMA_FILE_VERSION_GGJT_V1:另一种扩展格式,支持带分数的词汇表

3.3 权重加载

模型权重的加载过程包括:

  1. 读取权重的维度、名称和数据类型
  2. 通过权重别名映射找到对应的内部权重
  3. 将权重数据与内部权重关联
  4. 对于 GGJT_V1 格式,需要进行 32 字节对齐

4. 与其他模型的比较

与 ChatGLM 系列模型相比,GGML Llama 模型有以下区别:

  1. 模型格式

    • GGML Llama 使用 GGML 格式,而 ChatGLM 使用自定义格式
  2. 注意力机制

    • GGML Llama 使用 LlamaAttention,而 ChatGLM 使用 GlmAttention
  3. 前馈网络

    • GGML Llama 使用 LlamaFFNModule,而 ChatGLM 使用 GlmFFNModule
  4. 层归一化

    • GGML Llama 使用 RMSNorm(没有偏置项),而 ChatGLM 使用传统的层归一化(有偏置项)
  5. 残差连接

    • GGML Llama 使用普通的残差连接,而 ChatGLM 使用带缩放因子的残差连接

总结

ggml_llama.hggml_llama.cpp 文件实现了 InferLLM 框架中对 GGML 格式的 Llama 模型的支持。它们定义了 GgmlLlamaGraph 类,该类继承自基础的 Graph 类,并实现了特定于 GGML Llama 模型的加载和计算图构建逻辑。

通过分析这些文件,我们可以了解 GGML Llama 模型的结构特点和实现细节,以及 InferLLM 框架如何支持不同格式的模型。这种设计使得 InferLLM 可以灵活地支持多种模型格式,而不需要修改框架的核心代码。