基于TensorRT的BERT实时自然语言理解(下)
BERT Inference with TensorRT
请参阅Python脚本bert_inference.py还有详细的Jupyter notebook BERT_TRT.ipynb
在sample文件夹中进行推理过程的逐步描述和演练。在本节中,让我们回顾几个关键参数和概念,以便使用TensorRT进行推理。
BERT(更具体地说是编码器层)使用以下参数来控制其操作:
- Batch size
- Sequence Length
- Number of attention heads
这些参数的值取决于所选的BERT模型,用于设置TensorRT计划文件(执行引擎)的配置参数。
对于每个编码器,还要指定隐藏层的数量和注意头的大小。您也可以从Tensorflow检查点文件中读取上述所有参数。
由于我们所使用的BERT模型已经针对SQuAD数据集上的问答下游任务进行了微调,网络的输出(即输出完全连接层)将是一段文本,答案出现在文章中(在示例中称为h_output)。一旦我们生成了TensorRT引擎,我们就可以对其进行序列化,然后在TensorRT运行时中使用它。
在推理过程中,我们异步地执行从CPU到GPU的内存复制,反之亦然,从而分别将张量导入和移出GPU内存。异步内存复制操作通过与设备和主机之间的内存复制操作重叠计算来隐藏内存传输的延迟。异步内存复制和内核执行如图3所示。
Figure 3: TensorRT Runtime process
The inputs to the BERT model, which are shown in Figure 3, include:
- input_ids: tensor with token ids of paragraph concatenated along with question that is used as input for inference
- segment_ids: distinguishes between passage and question
- input_mask: indicates which elements in the sequence are tokens, and which ones are padding elements
输出(start_logits)和end_logits结束逻辑表示答案的范围,网络根据问题在文章中预测答案的范围。
在Jupyter笔记本中,我们使用了作为TensorRT插件实现的层的加速和高效实现。请参阅TensorRT文档以了解有关TensorRT插件以及如何在TensorRT中导入自定义层的更多信息。
Optimizing BERT for Inference
让我们浏览一下在TensorRT优化的BERT中实现的关键优化。
BERT架构基于Transformer,由12个用于BERT基的Transformer单元和24个用于BERT大型单元的Transformer单元组成。在被Transformer处理之前,输入标记通过一个嵌入层传递,该层查找它们的向量表示并对它们在句子中的位置进行编码。每个Transformer单元由两个连续的剩余块组成,每个剩余块随后进行层标准化。第一残差块代替第一完全连通层并通过多头自注意机制激活,第二剩余块使用高斯误差线性单元(GELU6)激活。
图4说明了Transformer单元的结构。
为了用TensorRT优化BERT,我们重点研究了Transformer单元的优化。由于在BERT中堆叠了几个Transformer单元,因此我们能够通过这组优化实现显著的性能提升。
为了在TensorRT中使用这些优化,我们使用定制插件来加速BERT模型中Transformer编码器元素中的关键操作。插件将多个操作融合到单个CUDA内核中的子图中。每个子图由几个基本计算组成,每个计算都需要对GPU的全局内存(即最慢的设备内存)进行读写。通过将基本操作融合到单个CUDA内核中,我们允许计算在更大的子图上进行,同时访问全局内存的次数最少。让我们更详细地看看这些插件。
Figure 4: Optimizations through TensorRT for BERT encoder cell of Transformer
The Gelu Activation performs the following elementwise computation where a, b and c are some scalar constants:
gelu(x) = a * x * (1 + tanh( b * (x + c * x^3) ))
A naive implementation using elementary layers in TensorRT would require the following operations:
Result = x^3
Result = c * Result
Result = x + Result
Result = b * Result
Result = tanh(Result)
Result = x * Result
Result = a * Result
对于k层,朴素的实现将需要。k-1不必要的全局内存往返,我们将其合并到单个CUDA内核中的元素计算中。参考geluPlugin.cu目录中获取更多详细信息。
跳过层和层规范化(LN)层在每个转换器层出现两次,并在单个内核中融合。有关此融合操作的实现,请参见中的skipLayerNormPlugin.cu在plugins目录中。
Transformer中的自我注意机制(图5)基于使用完全连接(FC)层的输入嵌入计算查询(Q)、键(K)和值(V)的表示。所有这些FC层的输入和输出维都是B x S x(N*H),其中B是批大小,S是序列长度,N是注意头数,H是隐藏层大小。然后将每个FC层的输出转置以形成大小为B x N x S x H的结果矩阵。将3个FC层合并到单个较大的层中可得到B x S x(3*N*H)的输出维度。
在融合的FC层之后,有三个转置操作可以被融合到单个更大的转置中,从而产生3xb×N×S×H的输出维。通过融合FC层,然后在较大张量上执行单个转置,Q、K和V表示被连续地放置在存储器中,以便进行以下操作他们。这将导致更快的内存访问,提高模型的吞吐量。
我们还融合了元素级缩放和softmax层(图5a和5b的右侧)。
参考qkvToContextPlugin.cu在自我注意实现的plugins目录中。
Figure 5a: Self-Attention Layer before Optimization
Figure 5b: Self-attention Layer after Optimization
Benchmarking BERT Inference Performance
BERT可以应用于在线和离线用例。在线NLU应用程序,如会话式人工智能,在推理过程中会有较紧的延迟预算。为了响应单个用户的查询,需要按顺序执行多个模型。当作为服务使用时,客户体验的总时间包括计算时间以及输入和输出网络延迟。时间越长,性能就越差,客户体验也就越差。
虽然单个模型可用的确切延迟可能因应用程序而异,但多个实时应用程序需要语言模型在10毫秒内执行。使用Tesla T4 GPU,使用TensorRT优化的BERT可以在2.2毫秒内对批处理大小=1、序列长度=128的QA任务执行推断。使用TensorRT优化的示例,对于BERT base,您可以执行高达8的批处理大小,对于在10毫秒延迟预算内具有较少Transformer层的模型,可以执行更高的批处理大小。对于批处理大小=1,在仅CPU平台上用高度优化的代码执行相同的任务需要40毫秒,而更高的批处理大小不会运行到完成并出现错误退出。
Figure 6: Compute latency in milliseconds for executing BERT-base on an NVIDIA T4 GPU versus a CPU-only server
该基准测试测量在将张量作为输入传递和将逻辑集作为输出之间执行QA任务时仅计算的延迟时间。您可以在中找到用于对示例进行基准测试的代码sampleBERT.cpp.
Conclusion
NVIDIA是开放源码的几个优化,使它能够在2.2ms内使用TensorRT在T4 gpu上执行BERT推断。优化的代码可以在TensorRT开源repo中作为开源示例提供。要在GCP上运行这个示例,可以从google cloud ai Hub访问它。repo展示了如何优化Transformer层,它是BERT和其他几种语言模型的核心构建块。我们希望您能够轻松地定制这些构建块,以适合您的定制模型和应用程序。本文概述了如何使用TensorRT示例、关键优化和性能结果。我们进一步描述了如何使用BERT示例作为简单应用程序和Jupyter笔记本的一部分的工作流程,在这里您可以传递一个段落并询问与之相关的问题。
新的优化和可实现的性能使得首次将BERT用于延迟预算较紧的应用程序(如会话式AI)成为现实。在接下来的几个月里,我们希望分享更多的例子,展示如何将BERT用作其他工作流的一部分。
我们一直在寻找新的想法来分享新的例子和应用程序。您使用BERT进行哪些NLP应用程序?您将来希望从我们这里看到哪些示例?
If you have questions regarding the TensorRT sample repo, check the NVIDIA TensorRT Developer Forum to see if other members of the TensorRT community have a resolution first. NVIDIA Registered Developer Program can also file bugs at https://developer.nvidia.com/nvidia-developer-program.
- CPU-only specifications: Gold 6240@2.60GHz 3.9GHz Turbo (Cascade Lake) HT Off, Single node, Single Socket, Number of CPU Threads = 18, Data=Real, Batch Size=1; Sequence Length=128; nireq=1; Precision=FP32; Data=Real; OpenVINO 2019 R2
GPU-server specification: Gold 6140@2GHz 3.7GHz Turbo (Skylake) HT On, Single node, Dual Socket, Number of CPU Threads = 72, Tesla T4 16GB, Driver Version 418.67 (r418_00), BERT-base, Batch Size=1; Number of heads = 12, Size per head = 64; 12 layers; Sequence Length=128; Precision=FP16; XLA=Yes; Data=Real; TensorRT 5.1
References:
- [Devlin et al 2018] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova
- [Y Liu 2019] RoBERTa: A Robustly Optimized BERT Pretraining Approach
- SQuAD leaderboard
- [Vaswani et al 2017] Attention Is All You Need Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin
- [Jinhyuk Lee et al 2019] BioBERT: a pre-trained biomedical language representation model for biomedical text mining Jinhyuk Lee, Wonjin Yoon, Sungdong Kim2, Donghyeon Kim1, Sunkyu Kim1, Chan Ho So3 and Jaewoo Kang
- [Dan et al 2016] Gaussian Error Linear Units (GELUs) Dan Hendrycks, Kevin Gimpel