HanLP2.1中mtl的各个task并行predict问题

请教下大神,


目前看到mtl每次predict时,多个task是for顺序执行的,
看到代码里有说到“We could parallelize this in the future”
请问如何并行起来?有什么思路吗?感谢!
  1. CUDA是异步的,所以GPU上的运算已经并行了。
  2. 拖后腿的是Python的post-processing,由于GIL无法并行。解决方案有两个:
    • 用Python的多进程。但进程通讯开销很大,可能得不偿失。
    • 用C++写extension。会增加编译安装复杂度,但彻底释放服务器多核算力,是企业大规模部署的首选。

感谢大佬回复!hanlp model是不是也不能转onnx?我自己尝试了下,各种坑很麻烦。方案1确实开销很大,比for慢多了。对于大佬说的方案2,有什么tutorial可以参考的吗?我想继续尝试下,但不知道应该搜哪方面的资料

理论上是可以转的,有如下难点:

  1. MTL中的Python控制流无法trace,导致丢失如下功能:
    • task调度功能
    • 处理长度超过512的序列的功能
  2. 除了tokenizer输入subword外,后续task接受的hidden states都是token level的。必须有一段逻辑在分词之后完成这种转换,这也是torch.onnx无法trace的。
  3. 每个task对batch的要求不尽相同,有的task需要BOS/EOS有的不需要,光这个问题就有4种组合。每种组合执行时batch中的token、hidden states和mask都要进行相应的转换。更何况,有的task依赖另一个task的输出,这些输出也是需要在CPU上转成Tensor的。

总之,如果只关注一个固定的task set自然可以硬编码并且转ONNX;否则的话,一个通用的框架则很难。虽然MTL整体难以trace,不过一旦任务和batch确定,Transformer和每个decoder都是可以导出ONNX的。

我们线上服务器对Trie实现C++双数组trie树的时候,用的是swig,现在似乎更推荐Cython了。
每个task的prediction包括三个步骤:feed_batch、decode_output和prediction_to_result。大致思路是,每个task的feed_batch保持原样放在for loop里,因为主要是CUDA运算,decode_output看情况,有的是CUDA上的算法比如eisner,有的是CPU上的。然后在整个for loop结束后用Cython在C++里面开一个线程池并行执行每个task的prediction_to_result。当然,这些prediction_to_result都要用C++写。对于大部分任务都比较简单,基本就是把id转为string。

虽然没有用hanlp做部署,但我为了让自有的框架的MTL在onnx上部署是这么做的: 预测模型从训练模型拷贝weights, 切分成主干和branches, task调度放在cpu里

onnx处理变长序列应该只能bucketing, 对于pytorch来说好像有点棘手

的确是个好办法。

HanLP是支持将相似长度的句子归入同一个batch的,对 O(n^3) 的self-attention来讲是很省时间空间的。如果ONNX只支持512 padding的话,会不会比Python端更慢呢

应该会吧,没具体测过onnx对比pytorch的overhead,但padding是影响更大的
在我的代码上,基于tf的bucketing, 序列长度和耗时呈线性关系

综合上面的讨论,Python目前在部署上暂时还行。更何况根据《Deep Learning with PyTorch》作者的经验,即使你把推理从Python迁移到C++,性能也顶多提高10%。

面向未来,我觉得Python不是最佳选择。毕竟NLP不像CV那样基本全部依赖GPU运算,还是有相当一部分运算(预处理)是在CPU上的,而Python整个的内存管理和并行都太弱了。就像huggingface的tokenizer一样,可能把这部分逻辑用Rust重写一遍才是又快又经济的。

1 Like

确实大部分耗时都是预处理和后处理,viterbi解码这些东西。

百度开源的分词工具LAC,使用的是GRU,在cpu端的推理优化做的很到位(好像用的是mkldnn),crf解码用的avx指令,分词速度还不错

HanLP的Viterbi是矩阵并行的,在CPU上PyTorch应该也有相应的MKL加速或本地指令集。