LLM推理端实现

文摘   科技   2024-08-23 14:40   山东  

2024年 第17篇


推理端实现了本地环境中部署大语言模型。可以实现LLM的基本功能,包括生成文本、自动摘要、语言翻译、代码生成、问答系统等。


  • LLM推理端是什么

  • 工作流程

  • 数学计算流程

  • 工程优化


01 LLM推理端是什么



Large Language Model,大语言模型。典型代表ChatGPT。

推理端:模型训练出来后,用于模型应用和部署的interface。

推理端实现了本地环境中部署大语言模型。可以实现LLM的基本功能,包括生成文本、自动摘要、语言翻译、代码生成、问答系统等。

Llamma.cpp是Llama推理端的C++实现库。https://github.com/ggerganov/llama.cpp ,MIT开源协议

Llama(Large Language Model Meta AI)是Meta2023年2月发布的开源大语言模型。

论文:https://arxiv.org/pdf/2302.13971.pdf

Gemma.cpp是Gemma推理端的C++实现库。https://github.com/google/gemma.cpp  ,Apache开源协议

Gemma是Google基于Gemini推出的开源LLM。https://blog.google/technology/developers/gemma-open-models/

论1b-212-215.above.com:2181

LLAMA.cpp用于在各种硬件平台上高效地运行大型语言模型(LLMs)。可以使大型语言模型能够在资源受限的环境中运行,例如在个人电脑、智能手机甚至树莓派等设备上。

Gemma.cpp用于在各种硬件平台上高效运行Gemma模型。


02 工作流程



LLM推理端的工作流程大致相同,我们以Llama.cpp为例。

Llama.cpp 工程目录

|-- example
| |-- main
| |-- main.cpp # 推理llama 2的主函数
|-- ggml-alloc.c #内存分配管理
|-- ggml-alloc.h
|-- llama.cpp # 整个llama 2的核心文件,包含所有重要interface
|-- llama.h
|-- ggml.c # 机器学习计算库.定义一些框架的基础数据结构和函数等
|-- ggml.h
|-- ggml-cuda.cu #cuda版本的llama2 中的kernel实现与调用
|-- ggml-cuda.h
|-- ggml-opencl.cpp #opencl版本的llama2 中的kernel实现与调用
|-- ggml-opencl.h
|-- ... #其他

Llama.cpp 的工作流程

步骤一:解析和设置参数

参数可以控制推理过程的行为。例如,可以设置批量大小来提高推理速度,或者设置温度来控制生成文本的多样性。

  1. 解析参数gpt_params_parse()

  2. 可设置的用户参数:

    1. --model 模型地址

    2. --color 颜色区分生成和输入

    3. --interactive 启用交互模式

    4. --prompt 输入的提示词

    5. --file 提示词文件

步骤二:加载模型

加载模型是 LLAMA.cpp 工作流程的第一步。模型文件包含模型的参数和权重,是推理过程所必需的。

  1. 根据输入参数生成模型参数,llama_model_params

  2. 使用 llama_load_model() 函数加载模型文件。

步骤三:创建llama_context

llama_context是整个llama.cpp 重要数据结构。存储了各种参数,模型,prompt,backend(cpu/gpu),推理的计算结果。

llama_new_context_with_model()

步骤四:处理输入的prompt

将输入的文本数据转换为模型可以理解的格式。也就是文本转化为词向量。

  1. Tokenization:

    1. 使用llama_tokenize()对输入文本进行分词(Tokenization),将文本拆分为token(词元)。

    2. 再输出为一个整数类型token_id

  2. Embedding:

    1. 将分词后的文本转换为词嵌入向量(Embeddings),这通常涉及到查找预训练的词嵌入矩阵(词汇表)。词向量的长度为hidden_dim,是LLM模型训练后固定的。把每个词元token_id转化为一个向量。

    2. 对于某些模型,可能还需要进行位置编码(Positional Encoding)以保留序列中单词的顺序信息。

    3. 输出为(词元数 * hidden_dim)的矩阵。

步骤五:推理

这是LLAMA.cpp 工作流程的核心步骤。在这个步骤中,模型会根据输入数据生成预测结果。

推理包括两个部分逻辑

Prompt:将转化后的所有词向量进行Transformer-decode计算。

Generate:根据Prompt的结果和前一次生成的结果,进行Transformer-decode计算。最后返回预测结果

下面比较重要的函数

  1. llama_decode()开始推理

  2. llama_build_graph()构建计算图

  3. llama_graph_compute()根据计算图调用对应算子,开始计算。

计算的结果是一组概率数组,表示预测的结果,存放在llama_context中。

步骤六:输出结果

对预测结果进行进一步处理。

  1. 根据需要对预测结果进行采样,获取到要输出的token_id。采样(sample)是从模型预测的概率分布中生成下一个词或标记的过程。

  2. 将token_id 转换回文本字符,llama_token_to_piece()

  3. 输出结果给用户。

步骤七:释放资源

释放资源是 LLAMA.cpp 工作流程的最后一步。释放模型句柄可以回收内存和计算资源。

调用 llama_free_model() 等函数释放模型资源。

以下是一些 LLAMA.cpp 工作流程的示例代码:

//解析参数
gpt_params params;
gpt_params_parse(argc, argv, params);

// 加载模型
llama_model_t *model = llama_load_model("llama-2-7b-chat");

//创建llama_context
llama_context * ctx = llama_new_context_with_model(model, params);

// 处理输入prompt
const char *input = "This is an example input.";
vector<llama_token> *embd_input = llama_tokenize(ctx, input);

// 推理
llama_decode(ctx, embd_input);

// 输出结果
cllama_token id = llama_sampling_sample(llama_sampling_context, ctx);
string token_str = llama_token_to_piece(ctx, id)
printf("%s\n", token_str);

// 释放资源
llama_free(ctx);
llama_free_model(model);



03 数学计算流程



「 3.1 模型架构 」

Llama的基础模型架构是Transformer,出自Attention Is All You Need。Transformer目前是大部分生成式人工智能的基础模型架构。

Llama的实现 - LLaMA: Open and Efficient Foundation Language Models

Llama相比基础Transformer,主要优化工作有下面几点:

  • 使用RMSNorm作为标准函数

  • 使用SwiGLU作为激活函数

  • 使用RoPE作为位置编码函数

LLM的核心是Attention的计算。

Attention是计算每个token相对其他token的重要程度,结果是一组权重向量。

"算子"(Operator)通常指的是执行特定数学运算的函数或指令。类似于c/c++的operator关键字,

在机器学习中算子的对象是张量(Tensor)。

张量是用于数学计算的数据结构,它的基本构成是多维数组。

下面是Llama.cpp定义的算子

enum ggml_op {
GGML_OP_NONE = 0,

GGML_OP_DUP,
GGML_OP_ADD,
GGML_OP_ADD1,
GGML_OP_ACC,
GGML_OP_SUB,
GGML_OP_MUL,
GGML_OP_DIV,
GGML_OP_SQR,
GGML_OP_SQRT,
GGML_OP_LOG,
GGML_OP_SUM,
GGML_OP_SUM_ROWS,
GGML_OP_MEAN,
GGML_OP_ARGMAX,
GGML_OP_REPEAT,
GGML_OP_REPEAT_BACK,
GGML_OP_CONCAT,
GGML_OP_SILU_BACK,
GGML_OP_NORM, // normalize
GGML_OP_RMS_NORM,
GGML_OP_RMS_NORM_BACK,
GGML_OP_GROUP_NORM,

GGML_OP_MUL_MAT,
GGML_OP_MUL_MAT_ID,
GGML_OP_OUT_PROD,

GGML_OP_SCALE,
GGML_OP_SET,
GGML_OP_CPY,
GGML_OP_CONT,
GGML_OP_RESHAPE,
GGML_OP_VIEW,
GGML_OP_PERMUTE,
GGML_OP_TRANSPOSE,
GGML_OP_GET_ROWS,
GGML_OP_GET_ROWS_BACK,
GGML_OP_DIAG,
GGML_OP_DIAG_MASK_INF,
GGML_OP_DIAG_MASK_ZERO,
GGML_OP_SOFT_MAX,
GGML_OP_SOFT_MAX_BACK,
GGML_OP_ROPE,
GGML_OP_ROPE_BACK,
GGML_OP_ALIBI,
GGML_OP_CLAMP,
GGML_OP_CONV_TRANSPOSE_1D,
GGML_OP_IM2COL,
GGML_OP_CONV_TRANSPOSE_2D,
GGML_OP_POOL_1D,
GGML_OP_POOL_2D,
GGML_OP_UPSCALE, // nearest interpolate
GGML_OP_PAD,
GGML_OP_ARGSORT,
GGML_OP_LEAKY_RELU,

GGML_OP_FLASH_ATTN,
GGML_OP_FLASH_FF,
GGML_OP_FLASH_ATTN_BACK,
GGML_OP_WIN_PART,
GGML_OP_WIN_UNPART,
GGML_OP_GET_REL_POS,
GGML_OP_ADD_REL_POS,

GGML_OP_UNARY,

GGML_OP_MAP_UNARY,
GGML_OP_MAP_BINARY,

GGML_OP_MAP_CUSTOM1_F32,
GGML_OP_MAP_CUSTOM2_F32,
GGML_OP_MAP_CUSTOM3_F32,

GGML_OP_MAP_CUSTOM1,
GGML_OP_MAP_CUSTOM2,
GGML_OP_MAP_CUSTOM3,

GGML_OP_CROSS_ENTROPY_LOSS,
GGML_OP_CROSS_ENTROPY_LOSS_BACK,

GGML_OP_COUNT,
};

标记为红色的算子是计算Attention中会用到的。

「 3.2 Attention计算步骤和对应的算子 」

CUDA是LLM计算中最常采用的计算平台,我们以CUDA算子举例。

CUDA是NVIDIA推出的基于GPU的科学计算软件平台和代码模型,大范围用于机器学习领域的训练和推理的计算工作。它提供了一套API和工具,使得程序员可以编写能够在GPU上执行的并行程序。

Llama.cpp的所有CUDA核函数实现全部在ggml-cuda.cu

算法流程

把上面算法流程进行拆分

Q*K - MatMul - 矩阵乘法

CUDA算子:mul_mat_p021_f16_f32

static __global__ void mul_mat_p021_f16_f32(
const void * __restrict__ vx, const float * __restrict__ y, float * __restrict__ dst,
const int ncols_x, const int nrows_x, const int nchannels_x, const int nchannels_y)
{
}
除以sqrt(dk) -  Scale - 缩放

CUDA算子:scale_f32

static __global__ void scale_f32(const float * x, float * dst, const float scale, const int k) {
const int i = blockDim.x*blockIdx.x + threadIdx.x;

if (i >= k) {
return;
}

dst[i] = scale * x[i];
}
Mask - Mask

CUDA算子:diag_mask_inf_f32

static __global__ void diag_mask_inf_f32(const float * x, float * dst, const int ncols, const int rows_per_channel, const int n_past) {
const int col = blockDim.y*blockIdx.y + threadIdx.y;
const int row = blockDim.x*blockIdx.x + threadIdx.x;

if (col >= ncols) {
return;
}

const int i = row*ncols + col;
//dst[i] = col > (n_past + row % rows_per_channel) ? -INFINITY : x[i];
//dst[i] = x[i] - (col > n_past + row % rows_per_channel) * INT_MAX; // equivalent within rounding error but slightly faster on GPU
dst[i] = x[i] - (col > n_past + row % rows_per_channel) * FLT_MAX;
}
softmax - SoftMax

CUDA算子:soft_max_f32

static __global__ void soft_max_f32(const float * x, const float * mask, const float * pos, float * dst, const int ncols_par, const int nrows_y, const float scale, const float max_bias, const float m0, const float m1, uint32_t n_head_log2) 
{
}
* V  - MatMul - 矩阵乘法

CUDA算子:mul_mat_vec_nc_f16_f32

static __global__ void mul_mat_vec_nc_f16_f32( // nc == non-contiguous
const void * __restrict__ vx, const float * __restrict__ y, float * __restrict__ dst, const int ncols_x, const int nrows_x,
const int row_stride_x, const int channel_stride_x, const int channel_x_divisor)
{
}



04 工程优化



LLAMA.cpp使LLM能够在资源受限的环境中运行,例如在个人电脑、智能手机甚至树莓派等设备上。

已经对各种硬件环境进行了优化。

主要包括:

宏定义

计算优化项

GGML_USE_MPI

MPI并行计算优化

GGML_USE_METAL

METAL GPU计算优化

GGML_USE_CUBLAS

CUDA GPU计算优化

GGML_USE_VULKAN

VULKAN GPU计算优化

GGML_USE_SYCL

SYCL 并行计算优化

GGML_USE_KOMPUTE

KOMPUTE GPU计算优化

LLAMA_AVX,LLAMA_AVX2,

LLAMA_AVX512

CPU指令集优化

推理过程中llama_graph_compute()等函数中调用不同平台优化算子。

static void ggml_compute_forward(struct ggml_compute_params * params, struct ggml_tensor * tensor) {
GGML_ASSERT(params);

if (tensor->op == GGML_OP_NONE) {
return;
}

#ifdef GGML_USE_CUBLAS
bool skip_cpu = ggml_cuda_compute_forward(params, tensor);//调用ggml-cuda.cu的CUDA算子
if (skip_cpu) {
return;
}
GGML_ASSERT(tensor->src[0] == NULL || tensor->src[0]->backend == GGML_BACKEND_TYPE_CPU);
GGML_ASSERT(tensor->src[1] == NULL || tensor->src[1]->backend == GGML_BACKEND_TYPE_CPU);
#elif defined(GGML_USE_VULKAN)
const bool skip_cpu = ggml_vk_compute_forward_cpu_assist(params, tensor);
#ifdef GGML_VULKAN_CHECK_RESULTS
if (skip_cpu) {
ggml_vk_check_results_1_cpu_assist(params, tensor);
}
#endif
if (skip_cpu) {
return;
}
GGML_ASSERT(tensor->src[0] == NULL || tensor->src[0]->backend == GGML_BACKEND_TYPE_CPU);
GGML_ASSERT(tensor->src[1] == NULL || tensor->src[1]->backend == GGML_BACKEND_TYPE_CPU);
#endif // GGML_USE_CUBLAS

#ifdef GGML_USE_SYCL
bool skip_cpu = ggml_sycl_compute_forward(params, tensor);
if (skip_cpu) {
return;
}
#endif // GGML_USE_SYCL
Gemma.cpp使用Hightway高性能cpp库进行针对CPU的计算优化。

算法优化 Lora - (Low-Rank Adaptation)优化和调整大型预训练语言模型的技术。能够减少计算资源和内存需求。

KVcache - 缓存下Attention计算中产生的Key和Value张量,减少计算量。

量化 - 量化通常涉及将浮点数(如32位的FP32)转换为低精度的表示,如8位的整数(INT8)或其他低精度格式。这种转换可以在不显著影响模型性能的情况下,显著降低模型的内存占用和计算需求。(这段由moonshot 生成)

Ps. 推理过程需要的显存

Llama 7B模型为例,使用FP16数据类型,每个FP16占2个字节。hidden_dim为4096,每个K和V 都要储存。一共32个Transformer  Layer。

FP16 * hidden_dim * K,V * 32Layer * context_length =  2 * 4096 * 2 * 32 * context_length(取1024) = 512M

如果输入有1024个token就需要512M显存。



05 团队介绍



三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。

    _________________ END__________________

三翼鸟数字化科技
三翼鸟数字化技术团队官方订阅号,提供技术前沿洞察、技术实践分享、最佳实践整合、技术规范发布、团队文化输出。