CTranslate2:在 AMD GPU 上高效推理 Transformer 模型

CTranslate2: Efficient Inference with Transformer Models on AMD GPUs — ROCm Blogs

Transformer 模型通过在机器翻译、文本摘要、文本生成和语音识别等任务中提供高性能结果,彻底改变了自然语言处理(NLP)的领域。然而,由于这些模型对计算和内存的高需求,在生产环境中部署它们可能会面临挑战。CTranslate2 通过提供一个自定义运行时来应对这些挑战,该运行时实现了各种优化技术,以加速 Transformer 模型的推理。

在这篇博客中,您将了解到如何使用 CTranslate2 这一强大的 C++ 和 Python 库,在 AMD 硬件上优化和加速 Transformer 模型的推理。我们将探讨它的关键特性、安装过程,以及如何将其集成到您的 NLP 应用中,以实现显著的性能提升和内存使用的减少。

OpenNMT 和CTranslate2是什么?

OpenNMT是一个开源工具包,专为机器翻译设计。机器翻译是自动将文本从一种语言转换为另一种语言的过程。它还支持如文本摘要等相关任务,涉及对单词序列的理解和生成,通常被称为“神经序列学习”。OpenNMT 提供了一个综合工具包,用于训练和部署翻译模型。CTranslate2 是一个优化的推理引擎,支持在 OpenNMT 中训练的模型,在生产环境中部署这些模型时可以提供显著的速度提升和效率改进。它是 OpenNMT 生态系统的一部分,可以作为一种专为高性能 Transformer 模型推理而量身定制的解决方案。

CTranslate2 库包括强大的功能,如将模型嵌入到 C++ 应用程序中,并减少模型在磁盘和内存中的占用。作为框架无关的库,它可以处理从 PyTorch 或 TensorFlow 等框架导出的模型,同时保持轻量级执行。

用 CTranslate2 训练的模型会转换为一种特殊的“CTranslate2 格式”,该格式使得模型能够进行高效推理优化。这种格式设计用于加速模型的使用,同时减少内存占用,使其更适合生产环境。

CTranslate2的关键功能

CTranslate2 提供了一系列设计用来优化 Transformer 模型推理的功能,包括:

  • 快速高效的执行:针对 CPU 和 GPU 进行了优化,提供快速高效的 Transformer 模型推理

  • 量化和降低精度:支持量化到 INT8、INT16 和 FP16,在保持精度的同时减少内存使用

  • 并行和异步执行:允许批量和异步推理,以最大限度提高吞吐量

  • 动态内存使用:高效利用内存以进行大规模部署

  • 磁盘占用轻量:减少模型尺寸,有助于可扩展性

  • 简单集成:易于嵌入 C++ 或 Python 应用程序

  • 张量并行:支持大型模型的分布式推理

支持的模型和框架

CTranslate2 目前支持以下模型:

  • 编码器-解码器模型(Encoder-Decoder Models):Transformer base/big,BART,mBART,Pegasus,T5,Whisper

  • 仅解码器模型(Decoder-Only Models):GPT-2,GPT-J,GPT-NeoX,OPT,BLOOM,MPT,Llama,Mistral,CodeGen,Falcon

  • 仅编码器模型(Encoder-Only Models):BERT,DistilBERT,XLM-RoBERTa

兼容的模型应首先转换为优化的模型格式。该库包含多个框架的转换器:

安装和设置 

要在 AMD GPU 上运行 CTranslate2,您将需要:

以下是开始的步骤:

  1. 克隆 ROCm CTranslate2 仓库:

    git clone https://github.com/ROCm/CTranslate2.git
    
  2. 为 ROCm AMD GPU 构建 Docker 镜像:

    cd CTranslate2/docker_rocm
    docker build -t rocm_ct2_v3.23.0 -f Dockerfile.rocm .
    
  3. 启动 Docker 容器:

    docker run -it --ipc=host --cap-add=SYS_PTRACE --network=host --device=/dev/kfd --device=/dev/dri --security-opt seccomp=unconfined --group-add video --privileged -w /workspace rocm_ct2_v3.23.0
    
  4. 设置 Docker 容器后,克隆本博客的仓库:

    git clone https://github.com/ROCm/rocm-blogs.git
    cd rocm-blogs/blogs/artificial-intelligence/ctranslate2
    

文本翻译快速开始

要在Python中开始使用CTranslate2,请按照以下步骤操作。将预训练模型转换为CTranslate2格式,然后使用转换后的模型运行您的第一次翻译。

  1. 首先,在Docker容器中安装所需的Python包:

    pip install OpenNMT-py==2.* sentencepiece
    
  2. 然后,下载并解压OpenNMT-py模型,例如使用OpenNMT-py训练的英德Transformer模型:

    wget https://s3.amazonaws.com/opennmt-models/transformer-ende-wmt-pyOnmt.tar.gz
    tar xf transformer-ende-wmt-pyOnmt.tar.gz
    ls  # 检查是否有`averaged-10-epoch.pt`文件存在
    
  3. 将模型转换为CTranslate2格式:

    ct2-opennmt-py-converter --model_path averaged-10-epoch.pt --output_dir ende_ctranslate2
    ls  #  检查是否存在`ende_ctranslate2`文件夹
    

    预期的输出文件夹应如下所示:

    # ende_ctranslate2/
    # ├── config.json
    # ├── model.bin
    # └── shared_vocabulary.json
    

要翻译文本,请使用Python API:

python3 src/translate.py

以下是 translate.py 文件内容:

import ctranslate2
import sentencepiece as spm

translator = ctranslate2.Translator("ende_ctranslate2/", device="cuda")
sp = spm.SentencePieceProcessor("sentencepiece.model")

input_text = "Good Morning!"
input_tokens = sp.encode(input_text, out_type=str)

results = translator.translate_batch([input_tokens])

output_tokens = results[0].hypotheses[0]
output_text = sp.decode(output_tokens)

print(output_text)

程序应打印以下翻译结果:

Guten Morgen!

如果您看到此输出,则说明您已成功转换并使用CTranslate2执行了翻译模型!

量化

量化是一种可以在不显著降低准确度的情况下减少模型大小并加速其执行的技术。目前,CTranslate2 支持在 AMD GPU 上使用以下数据类型进行量化:

  • 8位整数 (INT8)

  • 16位整数 (INT16)

  • 16位浮点数 (FP16)

通过应用量化,您可以显著减少模型的内存占用并提高推理速度,这对于在资源受限环境中部署模型或追求更高吞吐量特别有益。有关量化的更多信息,请参见 documentation.

将模型转换为量化数据类型

在模型转换过程中启用量化有助于减少磁盘上的模型大小并提高推理速度。转换器提供了 --quantization 选项,该选项接受以下值:

  • int8

  • int16

  • float16

  • float32 (默认值)

例如,要将模型转换并量化为 INT8,请使用以下命令:

ct2-opennmt-py-converter --model_path averaged-10-epoch.pt --quantization int8 --output_dir ende_ctranslate2_int8
ls  # 检查 `ende_ctranslate2_int8` 文件夹是否存在

预期的输出文件夹如下所示:

# ende_ctranslate2_int8/
# ├── config.json
# ├── model.bin
# └── shared_vocabulary.json

在模型加载时进行量化

量化也可以在加载模型时启用或更改,通过设置`compute_type`参数:

  • default: 保持模型转换期间使用的相同量化类型。

  • auto: 使用系统和设备支持的最快计算类型。

  • int8int16float16float32

例如,要使用 INT8 计算类型加载模型,请运行以下命令:

translator = ctranslate2.Translator("ende_ctranslate2_int8/", device="cuda", compute_type="int8")

translate.py代码中展示了翻译器的用法。 

性能比较:INT8量化效果

为了说明INT8量化对性能的影响,我们将对比默认(float32)模型和INT8量化模型在AMD GPU上的翻译延迟和模型大小。

1. 模型转换

使用INT8量化转换模型:

ct2-opennmt-py-converter --model_path averaged-10-epoch.pt --quantization int8 --output_dir ende_ctranslate2_int8
2. 带有延迟测量的翻译脚本

运行代码测量并比较延迟:

python3 src/translate_compare.py

以下是 translate_compare.py的源代码:

import ctranslate2
import sentencepiece as spm
import time

# 加载SentencePiece模型
sp = spm.SentencePieceProcessor(model_file="sentencepiece.model")

# 要翻译的输入文本
input_text = "Hello world!"
input_tokens = sp.encode(input_text, out_type=str)

# 翻译并测量延迟和每秒处理的tokens数的函数
def translate_and_time(translator):
    start_time = time.time()
    results = translator.translate_batch([input_tokens])
    end_time = time.time()
    latency = end_time - start_time

    # 解码翻译后的tokens
    output_tokens = results[0].hypotheses[0]
    output_text = sp.decode(output_tokens)

    # 计算每秒处理的tokens数
    num_output_tokens = len(output_tokens)
    tokens_per_second = num_output_tokens / latency

    return output_text, latency, tokens_per_second

# 加载默认(float32)模型
translator_float32 = ctranslate2.Translator(
    "ende_ctranslate2/", device="cuda", compute_type="float32"
)
output_text_float32, latency_float32, tps_float32 = translate_and_time(translator_float32)

# 加载int8量化模型
translator_int8 = ctranslate2.Translator(
    "ende_ctranslate2_int8/", device="cuda", compute_type="int8"
)
output_text_int8, latency_int8, tps_int8 = translate_and_time(translator_int8)

# 打印结果
print("Default (float32) model translation:")
print(f"Output: {output_text_float32}")
print(f"Latency: {latency_float32:.4f} seconds")
print(f"Tokens per second: {tps_float32:.2f}\n")

print("Int8 quantized model translation:")
print(f"Output: {output_text_int8}")
print(f"Latency: {latency_int8:.4f} seconds")
print(f"Tokens per second: {tps_int8:.2f}\n")

# 计算每秒处理tokens数的加速比
speedup_tps = tps_int8 / tps_float32
print(f"Speedup in tokens per second with int8 quantization: {speedup_tps:.2f}x faster")
3.  示例输出和分析
Default (float32) model translation:
Output: Hallo Welt!
Latency: 0.1510 seconds
Tokens per second: 19.86

Int8 quantized model translation:
Output: Hallo Welt!
Latency: 0.0428 seconds
Tokens per second: 70.14

Speedup in tokens per second with int8 quantization: 3.53x faster

分析:

在这个例子中,两个模型都产生了类似的翻译结果,表明量化对翻译质量的影响较小。然而,需要对更大样本集进行进一步测试以确认总体趋势。INT8模型的速度约为float32模型的3.53倍,显示出了显著的性能提升。

文本生成

文本生成是NLP中的一项基本任务,目标是根据提示生成连贯且上下文相关的文本。CTranslate2提供了对文本生成模型高效推理的支持,允许你在生产环境中部署经过优化性能的模型。在这个例子中,我们将使用GPT-2模型。

1. 转换GPT-2模型

首先,使用ct2-transformers-converter脚本将预训练的Hugging Face Transformers中的GPT-2模型转换为CTranslate2格式:

ct2-transformers-converter --model gpt2 --output_dir gpt2_ct2

2.  文本生成脚本

为了更好地理解无条件生成和有条件生成之间的区别,我们应该先解释这些概念,然后再提供代码:

无条件生成
  • 是什么: 模型在没有任何先验上下文或输入提示的情况下生成文本。

  • 如何工作: 生成从一个特殊标记开始(例如,GPT-2的<|endoftext|>),完全依赖训练期间学习的模式。

  • 使用案例: 创意写作,生成随机文本,探索模型的固有知识。

有条件生成
  • 是什么: 模型生成基于给定输入或提示上下文的延续文本。

  • 如何工作: 生成从用户提供的标记开始,模型根据提示的上下文预测后续的标记。

  • 使用案例: 文本补全,语言建模,聊天机器人生成响应,基于起始句子自动生成代码或文章。

现在,你可以运行以下脚本来生成文本:

python3 src/gpt2.py

以下是 gpt2.py 脚本,用于使用转换后的GPT-2模型执行无条件和有条件文本生成:

import ctranslate2
import transformers

generator = ctranslate2.Generator("gpt2_ct2", device="cuda")
tokenizer = transformers.AutoTokenizer.from_pretrained("gpt2")

# 无条件生成
start_tokens = [tokenizer.bos_token]
results = generator.generate_batch([start_tokens], max_length=30, sampling_topk=10)
print(tokenizer.decode(results[0].sequences_ids[0]))

# 有条件生成
start_tokens = tokenizer.convert_ids_to_tokens(tokenizer.encode("It is"))
results = generator.generate_batch([start_tokens], max_length=30, sampling_topk=10)
print(tokenizer.decode(results[0].sequences_ids[0]))

以下是`generate_batch`方法的参数:

  • max_length: 要生成的最大标记数。在这个例子中,设置为30。

  • sampling_topk: 通过从最有可能的前`k`个标记中采样下一个标记来控制生成文本的随机性。较小的`k`值会使输出更加确定。

3. 样例输出

This is a very nice and simple tutorial on how the game should work, but the first couple of steps are pretty straightforward: first, open up
It is true that in my opinion, if there were a large number of people in Europe who were involved in the development, it would be very difficult to

语音识别

Whisper 

是由OpenAI开发的一种多语言语音识别模型。它旨在将音频文件转录为多种语言的文本。CTranslate2 提供对 Whisper 模型高效推理的支持,从而实现更快速的转录和减少资源使用。

重要注意事项

  • Transformers 版本: 转换 Whisper 模型需要 transformers 库版本 4.23.0 或更高。

  •  模型大小: 下面的例子使用的是最小的模型,即 whisper-tiny,该模型有3900万参数。为了获得更好的转录准确性,可以考虑使用更大模型,如 whisper-base、`whisper-small`、`whisper-medium` 或 whisper-large

转换 Whisper 模型

首先,使用 ct2-transformers-converter 脚本将 Hugging Face Transformers 中的预训练 Whisper 模型转换为 CTranslate2 格式:

ct2-transformers-converter --model openai/whisper-tiny --output_dir whisper-tiny-ct2

此命令从 Hugging Face 下载 whisper-tiny 模型,将其转换为针对 CTranslate2 优化的推理格式,并保存到 whisper-tiny-ct2 目录中。

使用CTranslate2进行音频转录

以下是一个示例,展示如何使用转换后的Whisper模型通过CTranslate2转录音频文件。示例音频文件 (sample2.flac) 和Python代码位于本博客的GitHub存储库的 src 文件夹下:

python3 src/speech_recognition.py

这是 speech_recognition.py用于转录音频的代码:

import ctranslate2
import librosa
import transformers

# 加载并重采样音频文件.
audio, _ = librosa.load("src/sample2.flac", sr=16000, mono=True)

# 计算前30秒音频的特征。
processor = transformers.WhisperProcessor.from_pretrained("openai/whisper-tiny")
inputs = processor(audio, return_tensors="np", sampling_rate=16000)
features = ctranslate2.StorageView.from_array(inputs.input_features)

# 在GPU上加载模型。
model = ctranslate2.models.Whisper("whisper-tiny-ct2", device="cuda")

# 检测语言。
results = model.detect_language(features)
language, probability = results[0][0]
print("Detected language %s with probability %f" % (language, probability))

# 在提示中描述任务。
# 查看https://github.com/openai/whisper中的提示格式。
prompt = processor.tokenizer.convert_tokens_to_ids(
    [
        "<|startoftranscript|>",
        language,
        "<|transcribe|>",
        "<|notimestamps|>",  # Remove this token to generate timestamps.
    ]
)

# 在30秒窗口内运行生成。
results = model.generate(features, [prompt])
transcription = processor.decode(results[0].sequences_ids[0])
print(transcription)

程序生成以下示例输出:

Detected language <|en|> with probability 0.981871
 Before he had time to answer, a much encumbered Vera burst into the wrong with the question, I say, can I leave these here? These were a small black pig and a lusty specimen of black red game cock.

以下是程序工作原理的说明:

  • 音频加载:脚本使用 librosa 加载并重采样音频文件 audio.wav 到16 kHz的采样率并转换为单声道。

  • 特征提取:它使用 transformers 库中的 WhisperProcessor 计算前30秒音频的输入特征。

  • 模型加载:脚本使用CTranslate2的 Whisper 类加载Whisper模型,并将其放置在GPU上进行推理(`device="cuda"`)。

  • 语言检测:使用 detect_language 方法识别音频片段中的语言。它返回语言代码和概率。

  • 提示准备:脚本创建一个描述转录任务的提示。提示包括指定转录开始、语言和无时间戳的转录模式的特殊标记。

  • 转录生成:`generate` 方法使用准备好的提示转录音频特征。 

处理更长的音频文件

前面的例子仅处理了音频文件的前30秒。要转录更长的音频录音,可以按照以下步骤:

  • 音频分割:将音频文件分成30秒(或更短)的片段。

  • 顺序处理:循环处理每个片段,进行特征提取和转录。

  • 结果汇总:将所有片段的转录结果汇总,形成完整的转录文本。

结论

CTranslate2在生产环境中为高效部署Transformer模型提供了一种强大的解决方案。通过利用量化和与C++和Python的无缝集成等功能,CTranslate2使得在AMD GPU上实现更快、更高效的性能成为可能。这使得它在文本翻译、文本生成和语音识别等任务中成为理想的选择。

其他资源

        

猜你喜欢

转载自blog.csdn.net/eidolon_foot/article/details/144121989#comments_36828575
今日推荐