微信机器翻译服务向Ctranslate2框架的迁移过程
简介
Ctranslate2是维护OpenNMT模型的公司新出的一种专门针对OpenNMT-py和OpenNMT-tf模型的优化推理引擎,同时支持CPU和GPU。这里的优化主要指两个方面,一是模型的压缩,二是推理的加速。
Ctranslate2具有以下关键特性:
- 高效运行
- 交互式解码
- 模型量化
- 并行翻译
- 动态内存使用
- 自动化的指令集调度
- 轻量的磁盘占用
- 易于使用的翻译API
我们之前机器翻译服务是使用的OpenNMT模型,因此可以很简单的迁移到Ctranslate2上。Ctranslate2的使用方式也很简单,主要分为两步:
转模型,将OpenNMTPy或者OpenNMTf模型转换为该框架支持模型的二进制形式,量化参数支持int8和int16,也可不做量化操作。
以下是将一个OpenNMTPy模型转换成Ctranslate2 int8量化模型的示例:
1
2
3
4
5
6import ctranslate2
converter = ctranslate2.converters.OpenNMTPyConverter(cfg.model)
output_dir = converter.convert(output_dir="./vx_model",
model_spec=transformer_spec,
quantization=int8,
force=True)使用转换后的模型进行翻译。
1
2
3import ctranslate2
translator = ctranslate2.Translator("vx_model/")
translator.translate_batch([["你好", "世界", "!"]])
分析
CTranslate2的核心实现与框架无关。特定于框架的逻辑移至转换步骤,该步骤将训练好的模型序列化为简单的二进制格式。它是以这种方式来兼容不同机器学习框架的model。
其次我觉得需要回答这样一个问题,Ctranslate2的翻译效果为什么快?我觉得关键之处在于”定制”,普遍意义上的神经网络框架比如Pytorch和TensorFlow是针对所有任务的,但是Ctranslate2只是针对机器翻译服务的,可以做一些针对性的优化。
CTranslate2的整体架构可以分为这么几个部分:
通用层
模型格式:模型格式定义了每个model中variable的表示
模型序列化:转二进制格式
C++ engine
- 存储:Ctranslate2使用行优先的存储方式,定义在StorageView Class
- 抽象层
- primitives:底层计算函数
- ops
- layers
- models
- translators:使用model实现文本翻译逻辑的高阶类
- translators pool:并行计算的translator池,共享同一个model
- Ops
主要影响Ctranslate2运行速度的有两个参数,分别是intra_threads
和inter_threads
:
- intra_threads是每次转换使用的线程数:增加此值可减少延迟。
- inter_threads是并行执行的最大翻译引擎的数量:增加此值以增加吞吐量(由于线程内部的某些内部缓冲区被复制,这也会增加内存使用量)。
我们可以从源码角度看一下这两个参数是在哪个地方起作用的:
1 | TranslatorPool(size_t num_replicas, size_t num_threads_per_replica, Args&&... args) { |
num_replicas即inter_threads,num_threads_per_replica即intra_threads。
所以inter_threads其实决定了TranslatorPool的大小,即翻译引擎的个数。
1 | void set_num_threads(size_t num_threads) { |
而intra_threads决定了同一个翻译任务起多少个线程去翻译。
因此在实际部署中,我们采用了inter_threads数为1,intra_threads数等于核数的方案。
实现
机器翻译服务现在可以分为这么几个阶段:
- 中英翻译流程:分词,bpe,翻译,delbpe,detruecase,detokenize
- 英中翻译流程:normalize, tokenize, subEntity,转小写,bpe,翻译,delbpe,去空格
替换过程只需要将翻译阶段中的predictor替换成ctranslator2的实现即可,但要注意Ctranslate2框架下的输入和之前的机器翻译服务有些许不同,需要改动一下bpe阶段的输出。
1 | def load_predictor(config_file): |
上线
目前还未正式上线,在TKE集群上部署进行小流量测试。因为量化模型会对翻译效果造成一定的影响,因此针对Ctranslate2框架训练的model非常重要。未来会在测试充分的情况下,使用auto-serve正式上线。
性能
我们对CTranslate2机器翻译框架替换前后的机器翻译成本做了对比:
测试的机器是2核的机器,每个model测试10个句子,token数目统一是3834。
时间 | QPS | 处理1M token的时间(h) | 处理1M token的成本(元) | |
---|---|---|---|---|
长句子(V2[onmt,无量化]) | 75.33 | 0.1327 | 5.4579 | 0.5542 |
长句子(V2[onmt,有量化]) | 38.46 | 0.2600 | 2.7865 | 0.2829 |
长句子(V3[onmt,无量化]) | 181.69 | 0.0550 | 13.1640 | 1.3367 |
长句子(V3[onmt,有量化]) | 95.07 | 0.1051 | 6.8879 | 0.6994 |
长句子(V4[onmt,无量化]) | 149.83 | 0.0667 | 10.8557 | 1.1023 |
长句子(V4[onmt,有量化]) | 77.40 | 0.1291 | 5.6082 | 0.5695 |
长句子(V4[ctrans2,fp32]) | 139.53 | 0.0716 | 10.1094 | 1.0265 |
长句子(V4[ctrans2,int16]) | 102.13 | 0.0979 | 7.4001 | 0.7514 |
长句子(V4[ctrans2,int8]) | 70.26 | 0.1423 | 5.0910 | 0.5169 |
可以看出替换成Ctranslate2的机器翻译框架,在int8的量化情况下要比ONMT的量化模型节省了9.6%的成本。
总结和展望
我们基于Ctranslate2这种全新的机器翻译框架实现了机器翻译服务上的predict模块,翻译的性能有了一些提升,可以节省下一些机器的成本。未来会对model进行针对Ctranslate2框架进一步的调优,并且会进行更加充分的测试。
关于进一步的调优工作,如果是在GPU上进行的机器翻译服务,可以对CUDA caching allocator的一些参数进行针对性的调优,如bin_growth,min_bin,max_bin和max_cached_bytes等参数。
这个工作在josephyu和florianzhao指导下进行,感谢他们。