解读:https://github.com/NVIDIA/FasterTransformer/blob/main/docs/gpt_guide.md
这篇文档讲了一下 FasterTransformer 为 GPT 模型提供了什么,解释了工作流程和优化。并且还提供了在 FastTransformer 上运行 GPT 模型的指南。最后还提供了 Benchmark 测试来说明 FastTransformer 在 GPT 模型上的性能。我这里只针对 FasterTransformer GPT 的工作流程和做的优化进行讲解。
下面的 FasterTransformer GPT 介绍 和 FasterTransformer GPT 结构是简单翻了下文档。
GPT 是 Decooding 模型的一种变体,没有 Encoder 模块,没有交叉多头注意力模块,使用 GeLU 作为激活函数。2020 年,OpenAI 在他们的论文中表明,使用非常庞大的模型和大量的训练数据可以显著提高 GPT 模型的容量。 但是,不可能将这样的模型放入单个 GPU 中。 例如,最大的模型 GPT-3 有 1750 亿个参数,half 数据类型下大约需要 350 GB显存。 因此,多GPU,甚至多节点,是很有必要的。 为了解决模型大小导致的延迟和内存瓶颈,FasterTransformer 提供了高性能、低内存占用的 kernel,并使用了模型并行技术。
Fig 1展示了 FasterTransformer GPT 的工作流程。 与 BERT 和编码器-解码器结构不同,GPT 接收一些输入 id 作为上下文,并生成相应的输出 id 作为响应。在这个工作流中,主要的瓶颈是 GptDecoderLayer (Transformer块),因为当我们增加层数的时候耗时也是线性增加的。在GPT-3中,GptDecoderLayer占用了大约95%的时间。
FasterTransformer把整个工作流分成了两个部分。第一个部分是:“根据上下文context(也就是输入ids)计算k/v cache”。第二个部分是:“自回归的生成输出ids”。这两部分的操作类似,但是selfAttention部分的输入tensors的形状是不一样的。所以FasterTransformer提供了2种计算方式,如Fig2所示。在DecoderSelfAttention
里面,query的序列长度总是1,所以我们使用自定义的fused masked multi-head attention kernel 进行处理。另一方面,在ContextSelfAttention
中,query的序列长度最大时输入的长度,所以我们使用cuBLAS来利用TensorCore。
这个地方没有理解为什么要分成2个Attention,因为自回归的解码也是需要把输入的句子 padding 到最大的长度吧。这里的seq_len为1的情况是什么时候发生呢?我看了一下hugging face的GPT,似乎没有找到对应的位置。然后在FasterTransformer的GPT C++实现中也没有找到这个DecoderSelfAttention的实现:https://github.com/NVIDIA/FasterTransformer/blob/main/src/fastertransformer/models/multi_gpu_gpt/ParallelGptContextDecoder.cc 。不过本文主要是在后面介绍下 FasterTransformer 的优化点以及优缺点,这个暂时不影响解读。
以下示例演示如何运行多 GPU 和多节点 GPT 模型。
examples/cpp/multi_gpu_gpt_example.cc
: 它使用 MPI 来组织所有 GPU。examples/cpp/multi_gpu_gpt_triton_example.cc
: 它在节点内使用多线程,节点间使用 MPI。此示例还演示了如何使用 FasterTransformer 的 Triton 后端 API 来运行 GPT 模型。examples/pytorch/gpt/multi_gpu_gpt_example.py
: 这个例子和 examples/cpp/multi_gpu_gpt_example.cc
很类似, 但是通过 PyTorch OP 封装了 FasterTransformer 的实例。总之,运行 GPT 模型的工作流程是:
ParalelGpt
实例。在c++示例代码中,我们跳过第4步和第6步,通过examples/cpp/multi_gpu_gpt/start_ids.csv
加载请求。 在 PyTorch 示例代码中,请求来自 PyTorch 端。 在 Triton 示例代码中,我们有从步骤 1 到步骤 6 的完整示例。
源代码放在 src/fastertransformer/models/multi_gpu_gpt/ParallelGpt.cc
中。 GPT的参数、输入张量和输出张量:
Classification | Name | Data Type | Description |
---|---|---|---|
[0] | max_batch_size | size_t | Deprecated, move to input |
[1] | max_seq_len | size_t | Deprecated, move to input |
[2] | max_input_len | size_t | Deprecated, move to input |
[3] | beam_width | size_t | Deprecated, move to input |
[4] | head_num | size_t | Head number for model configuration |
[5] | size_per_head | size_t | Size per head for model configuration |
[6] | inter_size | size_t | The inter size of feed forward network. It is often set to 4 * head_num * size_per_head. |
[7] | num_layer | size_t | Number of transformer layers for model configuration |
[8] | vocab_size | int | Vocabulary size for model configuration |
[9] | start_id | int | Start id for vocabulary |
[18] | temperature | float | Deprecated, move to input |
[19] | len_penalty | float | Deprecated, move to input |
[20] | repetition_penalty | float | Deprecated, move to input |
[21] | tensor_para | NcclParam | Tensor Parallel information, which is declared in src/fastertransformer/utils/nccl_utils.h |
[22] | pipeline_para | NcclParam | Pipeline Parallel information, which is declared in src/fastertransformer/utils/nccl_utils.h |
[23] | stream | cudaStream_t | CUDA stream |
[24] | cublas_wrapper | cublasMMWrapper* | Pointer of cuBLAS wrapper, which is declared in src/fastertransformer/utils/cublasMMWrapper.h |
[26] | is_free_buffer_after_forward | bool | 如果设置为 true ,FasterTransformer 将在 forward 前分配缓冲区,并在 forward 后释放缓冲区。 当分配器基于内存池时,设置为“true”可能有助于减少推理期间的内存使用。 |
[27] | cuda_device_prop | cudaDeviceProp* | CUDA 设备属性指针,用于获取共享内存大小等硬件属性 |
[28] | sparse | bool | Is using sparsity. Experimental feature |
[29] | int8_mode | int | 0 means no quantization. 1 means use weight-only PTQ Experimental feature. 2 for weight and activation quantization Experimental feature. |
[30] | custom_all_reduce_comm | AbstractCustomComm | Custom all reduction communication for custom all reduction in model parallelism. It is only supported in 8-way tensor parallelism |
[31] | enable_custom_all_reduce | int | Flag of enabling custom all reduction or not |
[32] | remove_padding | bool | Remove the padding of input ids or not in context phase. |
[33] | shared_contexts_ratio | float | 控制共享上下文优化使用的比率。If the compact size (that accounts only for unique prompts) is less than ratio * batch size,使用优化的实现 。 设置 shared_contexts_ratio=0 停用优化。 |
Name | Tensor/Parameter Shape | Location | Data Type | Description |
---|---|---|---|---|
input_ids | [batch_size, max_input_length] | GPU | int | The input ids (context) |
input_lengths | [batch_size] | GPU | int | The lengths of input ids |
prompt_learning_task_name_ids | [batch_size] | CPU | int | Optional. Task name ids for prompt learning. |
output_seq_len | [batch_size] | CPU | uint32_t | The largest number of tokens you hope for results. Note that it contains the input length |
stop_words_list | [batch_size, 2, stop_words_length] | GPU | int | Optional. When FT generates words in this list, it will stop the generation. An extension of stop id |
bad_words_list | [batch_size, 2, bad_words_length] | GPU | int | Optional. The words in the list will never be sampled. |
repetition_penalty | [1] or [batch_size] | CPU | float | Optional. Repetition penalty applied to logits for both beam search and sampling. Exclusive with presence_penalty. |
presence_penalty | [1] or [batch_size] | CPU | float | Optional. Presence penalty - additive type of repetition penalty - applied to logits for both beam search and sampling. Exclusive with repetition_penalty. |
min_length | [1] or [batch_size] | CPU | int | Optional. Minimum number of tokens to generate |
random_seed | [1] or [batch_size] | CPU | unsigned long long int | Optional. Random seed to initialize the random table in sampling. |
request_prompt_lengths | [batch_size], | GPU | int | Optional. Length of prefix soft prompt embedding. This describes how many tokens of soft prompt embedding in each sentence. |
request_prompt_embedding | [batch_size, max_prompt_length, hidden_units] | GPU | float/half/bfloat16 | Optional. FT will concat them with results of embedding lookup kernel. For prefix soft prompt embedding, the type must be float; for p/prompt tuning, the type is same to weight. |
request_prompt_type | [batch_size] | CPU | int | Optional. Prompt type of request. This is necessary when user pass the prompt embedding by input |
is_return_context_cum_log_probs | [1] | CPU | bool | Optional. Return the cumulative log probability of context or not |
is_return_context_embeddings | [1] | CPU | bool | Optional. Return the sum of context tokens encodings or not |
session_len | [1] | CPU | uint32 | Optional. The maximum time length allowed during the whole interactive generation. Only used for interactive generation feature |
continue_gen | [1] | CPU | bool | Optional. A flag to tell FasterTransformer to not discard previous tokens and continue producing token based on previous generations. Only used for interactive generation feature |
memory_len | [1] | CPU | uint32 | Optional. The maximum time memory used in attention modules. Reduces the memory footprint but quality of generation might degrades. |
top_p_decay | [batch_size] | GPU | float | Optional. decay values for top_p sampling |
top_p_min | [batch_size] | GPU | float | Optional. min top_p values for top p sampling |
top_p_reset_ids | [batch_size] | GPU | uint32 | Optional. reset ids for resetting top_p values for top p sampling |
Name | Tensor/Parameter Shape | Location | Data Type | Description |
---|---|---|---|---|
output_ids | [batch_size, beam_width, max_output_seq_len] | GPU | int | The output ids. It contains the input_ids and generated ids |
sequence_length | [batch_size, beam_width] | GPU | int | The lengths of output ids |
output_log_probs | [batch_size, beam_width, request_output_seq_len] | GPU | float | Optional. It records the log probability of logits at each step for sampling. |
cum_log_probs | [batch_size, beam_width] | GPU | float | Optional. Cumulative log probability of generated sentences |
context_embeddings | [batch_size, beam_width, hidden_units] | GPU | float | Optional. Sum of context tokens encodings. |
beam_width
值直接由输出形状设置。 当output_ids
的beam_width
大于1时,FT会使用beam search来生成token; 否则,FT 将使用 topk 或 topp 采样。 当 beam search 和 sampling 的输入无效时,比如 beam width 1,top k 0,top p 0.0,FT 会自动运行 greedy search。·
is_context_qk_buf_float_
(是否对 GPT context QK GEMM 使用浮点累加)默认设置为 false。 如果您遇到与 GPT Context注意力模块相关的准确性问题,请尝试在 ParallelGpt.h 中启用它。和通信相关的实现以及shared_context相关的优化这里就不提了,代码的可读性比较差,我个人建议有需要的读者学习下某些kernel的实现即可。
从之前对 BERT 的优化点介绍以及这里对 GPT 的优化点介绍,我们可以发现
FasterTransformer集成了大量针对Transformer架构的优化,并且实现了各种Transformer架构中常见的各种fuse pattern对应的kernel。并且较为完整的支持了Transformer架构的int8推理,整体的性能始终保持在一个SOTA水平。对于我这种入门CUDA优化的学习者来说有一定的学习意义。此外,FasterTransformer也将实现的这些组件注册到TensorFlow,PyTorch等框架中使得读者可以对常见的Transformer架构进行推理。
CUDA Kernel之外的代码写得很抽象,特别对于多卡模式来说需要用户手动管理通信和模型切分,这个门槛是很高的。如果用户想基于FasterTreansformer这个框架实现新的Transformer架构的网络会非常困难,必须要非常了解FasterTransformer才可以。除了要手动管理通信以及模型切分之外,如果用户的新模型中出现了新的组件不仅要实现CUDA Kernel还需要手动管理内存的申请和释放,比如GPT的内存申请和释放:https://github.com/NVIDIA/FasterTransformer/blob/main/src/fastertransformer/models/multi_gpu_gpt/ParallelGpt.cc#L96-L270 稍微不注意就可能内存泄露。最近试跑了一个第三方模型的FasterTransformer实现,就出现了类似的问题。
个人认为 FasterTransformer 的整体架构实现的用户体验类似于 “九转大肠”,易用性方面我还是比较看好 PyTorch ,OneFlow 等将内存管理,通信集成到框架底层用户新增模型只需要关心自定义 CUDA Kernel 的传统深度学习框架。个人建议可以学习下 FasterTransformer 某些 CUDA Kernel 实现,但基于这个框架来搭建应用要慎重。如果基于 PyTorch,OneFlow 等框架能将大量的 Transformer 架构性能追平甚至超越 FasterTransformer 就完全没必要折磨自己。
这里总结了一下 FasterTransformer 里面和 CUDA Kernel相关的优化技巧,并且给出了Kernel实现的位置,并从易用性,性能多方便对比了 FasterTransformer 和 PyTorch/OneFlow 等框架的优缺点,供大家参考学习。