56.md 20.4 KB
Newer Older
W
wizardforcel 已提交
1
# BERT 上的动态量化(Beta)
W
wizardforcel 已提交
2

W
wizardforcel 已提交
3
> 原文:<https://pytorch.org/tutorials/intermediate/dynamic_quantization_bert_tutorial.html>
W
wizardforcel 已提交
4 5 6 7 8

小费

为了充分利用本教程,我们建议使用此 [Colab 版本](https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/dynamic_quantization_bert_tutorial.ipynb)。 这将使您可以尝试以下信息。

W
wizardforcel 已提交
9
**作者**[Jianyu Huang](https://github.com/jianyuh)
W
wizardforcel 已提交
10

W
wizardforcel 已提交
11
**审核**[Raghuraman Krishnamoorthi](https://github.com/raghuramank100)
W
wizardforcel 已提交
12

W
wizardforcel 已提交
13
**编辑**[Jessica Lin](https://github.com/jlin27)
W
wizardforcel 已提交
14

W
wizardforcel 已提交
15
## 简介
W
wizardforcel 已提交
16

W
wizardforcel 已提交
17
在本教程中,我们将动态量化应用在 BERT 模型上,紧跟 [HuggingFace 转换器示例](https://github.com/huggingface/transformers)中的 BERT 模型。 通过这一循序渐进的旅程,我们将演示如何将著名的 BERT 等最新模型转换为动态量化模型。
W
wizardforcel 已提交
18

W
wizardforcel 已提交
19 20
*   BERT,或者说转换器的双向嵌入表示,是一种预训练语言表示的新方法,它可以在许多常见的自然语言处理(NLP)任务(例如问题解答,文本分类, 和别的。 [可以在此处找到](https://arxiv.org/pdf/1810.04805.pdf)
*   PyTorch 中的动态量化支持将浮点模型转换为具有静态`int8``float16`数据类型的权重和动态量化激活的量化模型。 当权重量化为`int8`时,激活(每批)动态量化为`int8`。 在 PyTorch 中,我们有[`torch.quantization.quantize_dynamic` API](https://pytorch.org/docs/stable/quantization.html#torch.quantization.quantize_dynamic),该 API 用仅动态权重的量化版本替换了指定的模块,并输出了量化模型。
W
wizardforcel 已提交
21
*   我们在[通用语言理解评估基准(GLUE)](https://gluebenchmark.com/)中演示了 [Microsoft Research Paraphrase 语料库(MRPC)任务](https://www.microsoft.com/en-us/download/details.aspx?id=52398)的准确率和推理表现结果。 MRPC(Dolan 和 Brockett,2005 年)是从在线新闻源中自动提取的句子对的语料库,带有人工标注,说明句子中的句子在语义上是否等效。 由于类别不平衡(正向为 68%,负向为 32%),我们遵循常规做法并报告 [F1 得分](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html)。 MRPC 是用于语言对分类的常见 NLP 任务,如下所示。
W
wizardforcel 已提交
22 23 24

![../_img/bert.png](img/b43b70d8a6eef9ea4f75867b5e83b483.png)

W
wizardforcel 已提交
25
## 1.设置
W
wizardforcel 已提交
26

W
wizardforcel 已提交
27
### 1.1 安装 PyTorch 和 HuggingFace 转换器
W
wizardforcel 已提交
28

W
wizardforcel 已提交
29
要开始本教程,首先请遵循 [PyTorch](https://github.com/pytorch/pytorch/#installation)[HuggingFace Github 仓库](https://github.com/huggingface/transformers#installation)中的安装说明。 此外,我们还将安装 [scikit-learn](https://github.com/scikit-learn/scikit-learn) 包,因为我们将重复使用其内置的 F1 分数计算助手函数。
W
wizardforcel 已提交
30 31 32 33 34 35 36

```py
pip install sklearn
pip install transformers

```

W
wizardforcel 已提交
37
由于我们将使用 PyTorch 的 Beta 版部分,因此建议安装最新版本的 Torch 和`tochvision`[ 您可以在此处找到有关本地安装的最新说明](https://pytorch.org/get-started/locally/)。 例如,要在 Mac 上安装:
W
wizardforcel 已提交
38 39 40 41 42 43 44

```py
yes y | pip uninstall torch tochvision
yes y | pip install --pre torch -f https://download.pytorch.org/whl/nightly/cu101/torch_nightly.html

```

W
wizardforcel 已提交
45
### 1.2 导入必要的模块
W
wizardforcel 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

在这一步中,我们将导入本教程所需的 Python 模块。

```py
from __future__ import absolute_import, division, print_function

import logging
import numpy as np
import os
import random
import sys
import time
import torch

from argparse import Namespace
from torch.utils.data import (DataLoader, RandomSampler, SequentialSampler,
                              TensorDataset)
from tqdm import tqdm
from transformers import (BertConfig, BertForSequenceClassification, BertTokenizer,)
from transformers import glue_compute_metrics as compute_metrics
from transformers import glue_output_modes as output_modes
from transformers import glue_processors as processors
from transformers import glue_convert_examples_to_features as convert_examples_to_features

# Setup logging
logger = logging.getLogger(__name__)
logging.basicConfig(format = '%(asctime)s - %(levelname)s - %(name)s -   %(message)s',
                    datefmt = '%m/%d/%Y %H:%M:%S',
                    level = logging.WARN)

logging.getLogger("transformers.modeling_utils").setLevel(
   logging.WARN)  # Reduce logging

print(torch.__version__)

```

我们设置线程数以比较 FP32 和 INT8 性能之间的单线程性能。 在本教程的最后,用户可以通过使用右侧并行后端构建 PyTorch 来设置其他线程数量。

```py
torch.set_num_threads(1)
print(torch.__config__.parallel_info())

```

W
wizardforcel 已提交
91
### 1.3 了解辅助函数
W
wizardforcel 已提交
92

W
wizardforcel 已提交
93
助手函数内置在转换器库中。 我们主要使用以下辅助函数:一个用于将文本示例转换为特征向量的函数; 另一个用于测量预测结果的 F1 分数。
W
wizardforcel 已提交
94

W
wizardforcel 已提交
95
[`gum_convert_examples_to_features`](https://github.com/huggingface/transformers/blob/master/transformers/data/processors/glue.py)函数将文本转换为输入特征:
W
wizardforcel 已提交
96 97

*   标记输入序列;
W
wizardforcel 已提交
98 99
*   在开头插入`[CLS]`
*   在第一句和第二句之间并在最后插入`[SEP]`
W
wizardforcel 已提交
100
*   生成标记类型 ID,以指示标记是属于第一序列还是第二序列。
W
wizardforcel 已提交
101

W
wizardforcel 已提交
102
[`gum_compute_metrics`](https://github.com/huggingface/transformers/blob/master/transformers/data/processors/glue.py)函数的计算指标为 [F1 得分](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.f1_score.html),可以将其解释为精度和召回率的加权平均值,其中 F1 得分最佳值为 1,最差值为 0。精度和召回率对 F1 得分的相对贡献相等。
W
wizardforcel 已提交
103 104 105

*   F1 分数的公式为:

W
wizardforcel 已提交
106
![](img/tex56-1.gif)
W
wizardforcel 已提交
107

W
wizardforcel 已提交
108
### 1.4 下载数据集
W
wizardforcel 已提交
109 110 111 112 113 114 115 116

在运行 MRPC 任务之前,我们通过运行[此脚本](https://gist.github.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e)并下载 [GLUE 数据](https://gluebenchmark.com/tasks)并将其解压缩到目录`glue_data`中。

```py
python download_glue_data.py --data_dir='glue_data' --tasks='MRPC'

```

W
wizardforcel 已提交
117
## 2.微调 BERT 模型
W
wizardforcel 已提交
118 119 120

BERT 的精神是预训练语言表示形式,然后以最小的任务相关参数微调各种任务上的深层双向表示形式,并获得最新的结果。 在本教程中,我们将专注于对预训练的 BERT 模型进行微调,以对 MRPC 任务上的语义等效句子对进行分类。

W
wizardforcel 已提交
121
要为 MRPC 任务微调预训练的 BERT 模型(HuggingFace 转换器中的`bert-base-uncased`模型),可以按照[示例](https://github.com/huggingface/transformers/tree/master/examples#mrpc)中的命令进行操作:
W
wizardforcel 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

```py
export GLUE_DIR=./glue_data
export TASK_NAME=MRPC
export OUT_DIR=./$TASK_NAME/
python ./run_glue.py \
    --model_type bert \
    --model_name_or_path bert-base-uncased \
    --task_name $TASK_NAME \
    --do_train \
    --do_eval \
    --do_lower_case \
    --data_dir $GLUE_DIR/$TASK_NAME \
    --max_seq_length 128 \
    --per_gpu_eval_batch_size=8   \
    --per_gpu_train_batch_size=8   \
    --learning_rate 2e-5 \
    --num_train_epochs 3.0 \
    --save_steps 100000 \
    --output_dir $OUT_DIR

```

W
wizardforcel 已提交
145
[我们在此处为 MRPC 任务提供了经过微调的 BERT 模型](https://download.pytorch.org/tutorial/MRPC.zip)。 为了节省时间,您可以将模型文件(约 400 MB)直接下载到本地文件夹`$OUT_DIR`中。
W
wizardforcel 已提交
146

W
wizardforcel 已提交
147
### 2.1 设置全局配置
W
wizardforcel 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

在这里,我们设置了用于在动态量化之前和之后评估微调 BERT 模型的全局配置。

```py
configs = Namespace()

# The output directory for the fine-tuned model, $OUT_DIR.
configs.output_dir = "./MRPC/"

# The data directory for the MRPC task in the GLUE benchmark, $GLUE_DIR/$TASK_NAME.
configs.data_dir = "./glue_data/MRPC"

# The model name or path for the pre-trained model.
configs.model_name_or_path = "bert-base-uncased"
# The maximum length of an input sequence
configs.max_seq_length = 128

# Prepare GLUE task.
configs.task_name = "MRPC".lower()
configs.processor = processors[configs.task_name]()
configs.output_mode = output_modes[configs.task_name]
configs.label_list = configs.processor.get_labels()
configs.model_type = "bert".lower()
configs.do_lower_case = True

# Set the device, batch size, topology, and caching flags.
configs.device = "cpu"
configs.per_gpu_eval_batch_size = 8
configs.n_gpu = 0
configs.local_rank = -1
configs.overwrite_cache = False

# Set random seed for reproducibility.
def set_seed(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
set_seed(42)

```

W
wizardforcel 已提交
189
### 2.2 加载经过微调的 BERT 模型
W
wizardforcel 已提交
190 191 192 193 194 195 196 197 198 199 200 201

我们从`configs.output_dir`加载标记器和经过微调的 BERT 序列分类器模型(FP32)。

```py
tokenizer = BertTokenizer.from_pretrained(
    configs.output_dir, do_lower_case=configs.do_lower_case)

model = BertForSequenceClassification.from_pretrained(configs.output_dir)
model.to(configs.device)

```

W
wizardforcel 已提交
202
### 2.3 定义分词和评估函数
W
wizardforcel 已提交
203

W
wizardforcel 已提交
204
我们重用了 [Huggingface](https://github.com/huggingface/transformers/blob/master/examples/run_glue.py) 中的分词和评估函数。
W
wizardforcel 已提交
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

```py
# coding=utf-8
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
# Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

def evaluate(args, model, tokenizer, prefix=""):
    # Loop to handle MNLI double evaluation (matched, mis-matched)
    eval_task_names = ("mnli", "mnli-mm") if args.task_name == "mnli" else (args.task_name,)
    eval_outputs_dirs = (args.output_dir, args.output_dir + '-MM') if args.task_name == "mnli" else (args.output_dir,)

    results = {}
    for eval_task, eval_output_dir in zip(eval_task_names, eval_outputs_dirs):
        eval_dataset = load_and_cache_examples(args, eval_task, tokenizer, evaluate=True)

        if not os.path.exists(eval_output_dir) and args.local_rank in [-1, 0]:
            os.makedirs(eval_output_dir)

        args.eval_batch_size = args.per_gpu_eval_batch_size * max(1, args.n_gpu)
        # Note that DistributedSampler samples randomly
        eval_sampler = SequentialSampler(eval_dataset) if args.local_rank == -1 else DistributedSampler(eval_dataset)
        eval_dataloader = DataLoader(eval_dataset, sampler=eval_sampler, batch_size=args.eval_batch_size)

        # multi-gpu eval
        if args.n_gpu > 1:
            model = torch.nn.DataParallel(model)

        # Eval!
        logger.info("***** Running evaluation {} *****".format(prefix))
        logger.info("  Num examples = %d", len(eval_dataset))
        logger.info("  Batch size = %d", args.eval_batch_size)
        eval_loss = 0.0
        nb_eval_steps = 0
        preds = None
        out_label_ids = None
        for batch in tqdm(eval_dataloader, desc="Evaluating"):
            model.eval()
            batch = tuple(t.to(args.device) for t in batch)

            with torch.no_grad():
                inputs = {'input_ids':      batch[0],
                          'attention_mask': batch[1],
                          'labels':         batch[3]}
                if args.model_type != 'distilbert':
                    inputs['token_type_ids'] = batch[2] if args.model_type in ['bert', 'xlnet'] else None  # XLM, DistilBERT and RoBERTa don't use segment_ids
                outputs = model(**inputs)
                tmp_eval_loss, logits = outputs[:2]

                eval_loss += tmp_eval_loss.mean().item()
            nb_eval_steps += 1
            if preds is None:
                preds = logits.detach().cpu().numpy()
                out_label_ids = inputs['labels'].detach().cpu().numpy()
            else:
                preds = np.append(preds, logits.detach().cpu().numpy(), axis=0)
                out_label_ids = np.append(out_label_ids, inputs['labels'].detach().cpu().numpy(), axis=0)

        eval_loss = eval_loss / nb_eval_steps
        if args.output_mode == "classification":
            preds = np.argmax(preds, axis=1)
        elif args.output_mode == "regression":
            preds = np.squeeze(preds)
        result = compute_metrics(eval_task, preds, out_label_ids)
        results.update(result)

        output_eval_file = os.path.join(eval_output_dir, prefix, "eval_results.txt")
        with open(output_eval_file, "w") as writer:
            logger.info("***** Eval results {} *****".format(prefix))
            for key in sorted(result.keys()):
                logger.info("  %s = %s", key, str(result[key]))
                writer.write("%s = %s\n" % (key, str(result[key])))

    return results

def load_and_cache_examples(args, task, tokenizer, evaluate=False):
    if args.local_rank not in [-1, 0] and not evaluate:
        torch.distributed.barrier()  # Make sure only the first process in distributed training process the dataset, and the others will use the cache

    processor = processors[task]()
    output_mode = output_modes[task]
    # Load data features from cache or dataset file
    cached_features_file = os.path.join(args.data_dir, 'cached_{}_{}_{}_{}'.format(
        'dev' if evaluate else 'train',
        list(filter(None, args.model_name_or_path.split('/'))).pop(),
        str(args.max_seq_length),
        str(task)))
    if os.path.exists(cached_features_file) and not args.overwrite_cache:
        logger.info("Loading features from cached file %s", cached_features_file)
        features = torch.load(cached_features_file)
    else:
        logger.info("Creating features from dataset file at %s", args.data_dir)
        label_list = processor.get_labels()
        if task in ['mnli', 'mnli-mm'] and args.model_type in ['roberta']:
            # HACK(label indices are swapped in RoBERTa pretrained model)
            label_list[1], label_list[2] = label_list[2], label_list[1]
        examples = processor.get_dev_examples(args.data_dir) if evaluate else processor.get_train_examples(args.data_dir)
        features = convert_examples_to_features(examples,
                                                tokenizer,
                                                label_list=label_list,
                                                max_length=args.max_seq_length,
                                                output_mode=output_mode,
                                                pad_on_left=bool(args.model_type in ['xlnet']),                 # pad on the left for xlnet
                                                pad_token=tokenizer.convert_tokens_to_ids([tokenizer.pad_token])[0],
                                                pad_token_segment_id=4 if args.model_type in ['xlnet'] else 0,
        )
        if args.local_rank in [-1, 0]:
            logger.info("Saving features into cached file %s", cached_features_file)
            torch.save(features, cached_features_file)

    if args.local_rank == 0 and not evaluate:
        torch.distributed.barrier()  # Make sure only the first process in distributed training process the dataset, and the others will use the cache

    # Convert to Tensors and build dataset
    all_input_ids = torch.tensor([f.input_ids for f in features], dtype=torch.long)
    all_attention_mask = torch.tensor([f.attention_mask for f in features], dtype=torch.long)
    all_token_type_ids = torch.tensor([f.token_type_ids for f in features], dtype=torch.long)
    if output_mode == "classification":
        all_labels = torch.tensor([f.label for f in features], dtype=torch.long)
    elif output_mode == "regression":
        all_labels = torch.tensor([f.label for f in features], dtype=torch.float)

    dataset = TensorDataset(all_input_ids, all_attention_mask, all_token_type_ids, all_labels)
    return dataset

```

W
wizardforcel 已提交
343
## 3.应用动态量化
W
wizardforcel 已提交
344 345 346

我们在模型上调用`torch.quantization.quantize_dynamic`,将动态量化应用于 HuggingFace BERT 模型。 特别,

W
wizardforcel 已提交
347 348
*   我们指定要对模型中的`torch.nn.Linear`模块进行量化;
*   我们指定希望将权重转换为量化的`int8`值。
W
wizardforcel 已提交
349 350 351 352 353 354 355 356 357

```py
quantized_model = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear}, dtype=torch.qint8
)
print(quantized_model)

```

W
wizardforcel 已提交
358
### 3.1 检查模型大小
W
wizardforcel 已提交
359 360 361 362 363 364 365 366 367 368 369 370 371 372

首先,检查模型尺寸。 我们可以观察到模型大小的显着减小(FP32 总大小:438 MB; INT8 总大小:181 MB):

```py
def print_size_of_model(model):
    torch.save(model.state_dict(), "temp.p")
    print('Size (MB):', os.path.getsize("temp.p")/1e6)
    os.remove('temp.p')

print_size_of_model(model)
print_size_of_model(quantized_model)

```

W
wizardforcel 已提交
373
本教程中使用的 BERT 模型(`bert-base-uncased`)的词汇量`V`为 30522。在嵌入量为 768 的情况下,单词嵌入表的总大小为`~4 (Bytes/FP32) * 30522 * 768 = 90 MB` 。 因此,借助量化,非嵌入表部分的模型大小从 350 MB(FP32 模型)减少到 90 MB(INT8 模型)。
W
wizardforcel 已提交
374

W
wizardforcel 已提交
375
### 3.2 评估推理准确率和时间
W
wizardforcel 已提交
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404

接下来,我们比较一下动态量化后原始 FP32 模型和 INT8 模型之间的推断时间以及评估精度。

```py
def time_model_evaluation(model, configs, tokenizer):
    eval_start_time = time.time()
    result = evaluate(configs, model, tokenizer, prefix="")
    eval_end_time = time.time()
    eval_duration_time = eval_end_time - eval_start_time
    print(result)
    print("Evaluate total time (seconds): {0:.1f}".format(eval_duration_time))

# Evaluate the original FP32 BERT model
time_model_evaluation(model, configs, tokenizer)

# Evaluate the INT8 BERT model after the dynamic quantization
time_model_evaluation(quantized_model, configs, tokenizer)

```

在 MacBook Pro 上本地运行此程序,无需进行量化,推理(对于 MRPC 数据集中的所有 408 个示例)大约需要 160 秒,而进行量化则只需大约 90 秒。 我们总结了在 Macbook Pro 上运行量化 BERT 模型推断的结果,如下所示:

```py
| Prec | F1 score | Model Size | 1 thread | 4 threads |
| FP32 |  0.9019  |   438 MB   | 160 sec  | 85 sec    |
| INT8 |  0.902   |   181 MB   |  90 sec  | 46 sec    |

```

W
wizardforcel 已提交
405
在 MRPC 任务的微调 BERT 模型上应用训练后动态量化后,我们的 F1 分数准确率为 0.6%。 作为比较,在[最新论文](https://arxiv.org/pdf/1910.06188.pdf)(表 1)中,通过应用训练后动态量化,可以达到 0.8788;通过应用量化感知训练,可以达到 0.8956。 主要区别在于我们在 PyTorch 中支持非对称量化,而该论文仅支持对称量化。
W
wizardforcel 已提交
406 407 408

请注意,在本教程中,为了进行单线程比较,我们将线程数设置为 1。 我们还为这些量化的 INT8 运算符支持运算内并行化。 用户现在可以通过`torch.set_num_threads(N)`设置多线程(`N`是内部运算并行线程的数量)。 启用帧内并行支持的一项初步要求是使用正确的[后端](https://pytorch.org/docs/stable/notes/cpu_threading_torchscript_inference.html#build-options)(例如 OpenMP,Native 或 TBB)构建 PyTorch。 您可以使用`torch.__config__.parallel_info()`检查并行化设置。 在使用 PyTorch 和本机后端进行并行化的同一台 MacBook Pro 上,我们可以花大约 46 秒的时间来处理 MRPC 数据集的评估。

W
wizardforcel 已提交
409
### 3.3 序列化量化模型
W
wizardforcel 已提交
410

W
wizardforcel 已提交
411
跟踪模型后,我们可以使用`torch.jit.save`序列化并保存量化模型,以备将来使用。
W
wizardforcel 已提交
412 413 414 415 416 417 418 419 420 421 422

```py
input_ids = ids_tensor([8, 128], 2)
token_type_ids = ids_tensor([8, 128], 2)
attention_mask = ids_tensor([8, 128], vocab_size=2)
dummy_input = (input_ids, attention_mask, token_type_ids)
traced_model = torch.jit.trace(quantized_model, dummy_input)
torch.jit.save(traced_model, "bert_traced_eager_quant.pt")

```

W
wizardforcel 已提交
423
要加载量化模型,我们可以使用`torch.jit.load`
W
wizardforcel 已提交
424 425 426 427 428 429

```py
loaded_quantized_model = torch.jit.load("bert_traced_eager_quant.pt")

```

W
wizardforcel 已提交
430
## 总结
W
wizardforcel 已提交
431

W
wizardforcel 已提交
432
在本教程中,我们演示了如何演示如何将 BERT 等著名的最新 NLP 模型转换为动态量化模型。 动态量化可以减小模型的大小,而对准确率的影响有限。
W
wizardforcel 已提交
433

W
wizardforcel 已提交
434
谢谢阅读! 与往常一样,我们欢迎您提供反馈,因此,如果有任何问题,[请在这里创建一个 ISSUE](https://github.com/pytorch/pytorch/issues)
W
wizardforcel 已提交
435

W
wizardforcel 已提交
436
## 参考文献
W
wizardforcel 已提交
437

W
wizardforcel 已提交
438 439
```py
[1] J.Devlin, M. Chang, K. Lee and K. Toutanova, BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding (2018).
W
wizardforcel 已提交
440

W
wizardforcel 已提交
441
[2] HuggingFace Transformers.
W
wizardforcel 已提交
442

W
wizardforcel 已提交
443 444
[3] O. Zafrir, G. Boudoukh, P. Izsak, and M. Wasserblat (2019). Q8BERT: Quantized 8bit BERT.
```