# GPT 本示例主要通过使用MIGraphX C++ API对GPT2模型进行推理,包括预处理、模型推理以及数据后处理。 ## 模型简介 GPT(Generative Pre-trained Transformer)系列模型以不断堆叠transformer中的decoder模块为特征提取器,提升训练语料的规模和质量、模型的参数量进行迭代更新。GPT-1主要通过在无标签的数据上学习一个通用的语言模型,再根据特定的任务进行微调处理有监督任务;GPT-2在GPT-1的模型结构上使用更多的模型参数和数据集,训练一个泛化能力更强的词向量模型。GPT-3更是采用海量的模型参数和数据集,训练了一个更加强大的语言模型。 | 模型 | 发布时间 | 参数量 | 预训练数据量 | | ----- | ------------ | -------- | ------------ | | GPT-1 | 2018 年 6 月 | 1.17 亿 | 约 5GB | | GPT-2 | 2019 年 2 月 | 15 亿 | 40GB | | GPT-3 | 2020 年 5 月 | 1,750 亿 | 45TB | 本次采用GPT-2模型进行诗词生成任务,模型文件下载链接:https://pan.baidu.com/s/1KWeoUuakCZ5dualK69qCcw, 提取码:4pmh。将GPT2_shici.onnx模型文件保存在Resource/文件夹下。整体模型结构如下图所示,也可以通过netron工具:https://netron.app/ 查看GPT-2的模型结构。 ## 预处理 在将文本输入到模型之前,需要做如下预处理: 1.加载词汇表 2.文本编码 首先,根据提供的词汇表路径,通过cuBERT::FullTokenizer()函数加载词汇表,用于后续对输入文本的编码操作。其次,将词汇表中的内容依次保存到vector容器output中,用于数据后处理中的解码操作。 ```c++ cuBERT::FullTokenizer tokenizer = cuBERT::FullTokenizer("../Resource/vocab_shici.txt"); std::ifstream infile; std::string buf; std::vector output; infile.open("../Resource/vocab_shici.txt"); while (std::getline(infile,buf)) { output.push_back(buf); } ``` 文本编码的实现方法主要封装在GPT2::Preprocessin()函数中,通过输入问题question,进行数据重构,在输入序列中的起始位置处加入起始标志符[CLS],之后拼接问题question的编码信息,从而完成数据预处理过程。 ```c++ ErrorCode GPT2::Preprocessing(cuBERT::FullTokenizer tokenizer, char *question, std::vector &input_id) { // 对问题进行分词操作 int max_seq_length =1000; std::vector tokens_question; tokens_question.reserve(max_seq_length); tokenizer.tokenize(question, &tokens_question, max_seq_length); // 将文本数据转换为数值型数据 input_id.push_back(tokenizer.convert_token_to_id("[CLS]")); for (int i=0;i 具体GPT-2模型的推理,如下代码所示。首先,通过gpt2.Inference()函数实现模型的具体推理细节,推理结果保存在outputs中。其次,对每次推理结果进行判断,当判断为[SEP]结束标志符时,结束循环完成推理,否则就将推理结果outputs加入到输入数据input_id中,继续下一次的模型推理。 ```c++ // 推理 for(int i=0;i<50;++i) { long unsigned int outputs = gpt2.Inference(input_id); if(outputs == 102) // 当outputs等于102时,即[SEP]结束标志符,退出循环。 { break; } input_id.push_back(outputs); } ``` 在GPT2::Inference()函数具体实现了GPT-2模型的推理过程,主要做如下处理: 1.设置输入shape并执行推理 ```c++ long unsigned int GPT2::Inference(const std::vector &input_id) { // 保存预处理后的数据 long unsigned int input[1][input_id.size()]; for (int j=0;j> inputShapes; inputShapes.push_back({1,input_id.size()}); // 输入数据 std::unordered_map inputData; inputData[inputName]=migraphx::argument{migraphx::shape(inputShape.type(),inputShapes[0]),(long unsigned int*)input}; // 推理 std::vector results = net.eval(inputData); // 获取输出节点的属性 migraphx::argument result = results[0]; migraphx::shape outputShape = result.get_shape(); // 输出节点的shape int numberOfOutput=outputShape.elements(); // 输出节点元素的个数 float *data = (float *)result.data(); // 输出节点数据指针 ... } ``` 1.执行推理,GPT-2模型的推理结果results是一个std::vector< migraphx::argument >类型,包含一个输出,所以result = results[0]。result中一共包含了input_id.size() * 22557个概率值,其中,input_id.size()代表输入数据的长度,22557代表了词汇表中词的数量。 ## 数据后处理 得到模型推理结果后,还需要对数据做如下后处理: 1.排序 2.解码 ```c++ long unsigned int GPT2::Inference(const std::vector &input) { ... // 保存模型推理出的概率值 long unsigned int n = 0; std::vector resultsOfPredictions(22557); for(int i=(input.size()-1)*22557; i