离线推理项目迁移至mindspore(三)——代码迁移
友情链接:
5 代码迁移
在上一篇文章中,模型迁移的部分已经完成,当然,整个迁移工程并没有结束
本篇文章主要进行代码部分的迁移
5.1 x2mindspore
在mindspore中,有一个工具叫做x2mindspore,它可以将torch的代码转为mindspore的代码
不过,不推荐使用x2mindspore进行代码迁移,主要有以下原因:
- x2mindspore给出的等效方法性能可能并不是最佳的等效方法
- 有些时候,使用x2mindspore反而会在项目中引入更多的错误
- 即使未出现错误,也会出现与原模型得到的结果相差甚大
- x2mindspore并不是对所有方法都有效的,部分方法依然需要手动调整
因此,依靠x2mindspore可以转换,但效果不好,所以推荐熟悉mindspore文档,自行书写
5.2 代码迁移总述
既然不能直接使用x2mindspore脚本进行迁移,那么就需要进行等效代码的书写与替换
这里以mindspore1.10为例,来进行代码迁移
对于代码迁移部分,可以参考下面两篇文档:
Tips:2中的文档部分对应的函数并不是最佳方案,仅仅作为参考使用
5.3 等效代码替换
5.3.1 总述
对于torch中的功能,大多数在mindspore上有等效的代码,这时候只需要将代码进行等效就可以了
一般而言,代码的等效有一个规律:
- torch.nn<==>mindspore.nn
- torch.nn.funcational<==>mindspore.ops
- mindspore文档中,如果给出的等效方法是大写字母的类(如ops.MatMul),一般都有小写字母的方法对应(如ops.matmul),只不过在文档中没有列出
- mindspore2.x版本中,大多数方法的名称与torch中等效方法名称一致
5.3.2 常用的代码等效表格
torch | mindspore 1.10.0 |
---|---|
torch.unsqueeze(input, dim) torch.Tensor.unsqueeze(dim) | mindspore.ops.expand_dims(input_x, axis) |
torch.nn.functional.normalize(input, p=2, dim=1, eps=1e-12, out=None) | mindspore.ops.L2Normalize(axis=0, epsilon=1e-4)(input_x)$ ^{[1]}$ |
torch.Tensor.transpose(dim0, dim1) | mindspore.Tensor.transpose(*axes) [ 2 ] ^{[2]} [2] |
torch.mm(input, mat2, out=None) | mindspore.ops.matmul(x1, x2) |
torch.from_numpy(ndarray) | mindspore.Tensor.from_numpy(array) |
torch.stack(tensors, dim=0, out=None) | mindspore.ops.stack(input_x, axis=0) |
torch.topk(input, k, dim=None, largest=True, sorted=True, out=None) | mindspore.ops.top_k(input_x, k, sorted=True) [ 3 ] ^{[3]} [3] |
torch.Tensor.expand(*sizes) | mindspore.ops.broadcast_to(x, shape) |
torch.sum(input, dtype=None) torch.sum(input, dim, keepdim=False, dtype=None) | mindspore.ops.reduce_sum(x, axis) |
torch.Tensor.sum(dim=None, keepdim=False, dtype=None) | mindspore.Tensor.sum(axis=None, dtype=None, keepdims=False, initial=None) |
torch.clamp(input, min, max, out=None) | mindspore.ops.clip_by_value(x, clip_value_min=None, clip_value_max=None) [ 4 ] ^{[4]} [4] |
torch.Tensor.size() | mindspore.Tensor.shape |
torch.nn.Parameter(data=None, requires_grad=True) | mindspore.Parameter(default_input, name=None, requires_grad=True, layerwise_parallel=False, parallel_optimizer=True) |
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) | mindspore.ops.standard_normal(shape, seed=0, seed2=0) |
torch.cat(tensors, dim=0, out=None) | mindspore.ops.concat(input_x, axis=0) |
torch.ones_like(input, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) | mindspore.ops.ones_like(input_x) |
torch.einsum(equation, *operands) | mindspore.ops.Einsum(equation)(input_x) [ 5 ] ^{[5]} [5] |
torch.nn.functional.softmax(input, dim=None, _stacklevel=3, dtype=None) | mindspore.ops.softmax(x, axis=- 1) |
torch.argmax(input, dim, keepdim=False) | mindspore.ops.argmax(x, axis=-1, output_type=mstype.int32) |
注:
[1] torch中,可以指定参数p来使用Lp范式;mindspore中,只能计算L2范式
[2] 该函数的参数与torch中差别较大,请参考官方文档说明
[3] 该函数只能计算每行中最大的k个元素(相当于torch中的dim=1)
[4] 该函数中,clip_value_min,clip_value_max两个参数的数据类型是ms.Tensor,不可以是int、float
[5] 该方法只能在GPU上可以运行,使用CPU、Ascend设备不可使用该方法
5.4 等效代码书写
对于代码,大部分都可以直接进行等效,但在一个项目中,可能会出现以下问题:
-
情况一:存在等效代码,但它的等效代码有设备要求,在当前设备上不能运行
-
情况二:在源项目中,使用了由torch、transformers构建的其它包(如sentence_transformer),但它们在mindspore中没有对应的包
对于上面两种情况,就只能进行等效代码的书写了
5.4.1 情况一
对于情况一,一般来讲,有以下解决办法:
- 借助mindspore内的方法,构建等效方法
- 使用与torch、transformers无关的包进行替代
以torch.einsum方法为例,在mindspore中,它的等效方法是
- ops.Einsum(1.10)
- ops.einsum(2.x)
但是,参考官方文档,这两种等效方法只能在GPU设备上运行
如果要在非GPU设备上运行,就需要书写等效代码
可以用以下方法来解决这个问题:
- 对原来的代码进行拆分分析,将它拆为分步运算,并使用mindspore中的方法替代分步运算,得到结果
- 使用np.einsum方法替代,在转回ms.Tensor
对于方法2, 由于numpy只能在CPU上运行,会存在数据在非GPU非CPU设备与CPU设备之间不断传递,对性能有影响
5.4.2 情况二
对于情况二,一般也只有查阅官方文档、或者查看这个包的源码,找到它的torch写法,再改为mindspore写法
比如下面这段代码:
from sentence_transformers import SentenceTransformer
model_path = 'model/model'
model = SentenceTransformer(model_path)
res = model.encode(...)
显然,这里的model.encode是不能直接替代的,在官方文档中,给出了它的等效方法:
from transformers import AutoTokenizer, AutoModel
import torch
#Mean Pooling - Take attention mask into account for correct averaging
def mean_pooling(model_output, attention_mask):
token_embeddings = model_output[0] #First element of model_output contains all token embeddings
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1)
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
return sum_embeddings / sum_mask
#Sentences we want sentence embeddings for
sentences = ['This framework generates embeddings for each input sentence',
'Sentences are passed as a list of string.',
'The quick brown fox jumps over the lazy dog.']
#Load AutoModel from huggingface model repository
tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
model = AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
#Tokenize sentences
encoded_input = tokenizer(sentences, padding=True, truncation=True, max_length=128, return_tensors='pt')
#Compute token embeddings
with torch.no_grad():
model_output = model(**encoded_input)
#Perform pooling. In this case, mean pooling
sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])
那么,只需要进行将上面代码有mindspore中的代码替代,就可以起到同样的效果
# 以BERT为例
from mindformers import BertTokenizer, BertForPreTraining
from mindspore import ops
import mindspore as ms
def mean_pooling(model_output, attention_mask):
token_embeddings = model_output[0]
input_mask_expanded = ops.broadcast_to(ops.expand_dims(attention_mask, axis=-1), token_embeddings.shape).astype(ms.float32)
sum_embeddings = ops.reduce_sum(token_embeddings * input_mask_expanded, 1)
sum_mask = ops.clip_by_value(input_mask_expanded.sum(1), clip_value_min=ms.Tensor(1e-9, ms.float32))
return sum_embeddings / sum_mask
sentences = ['This framework generates embeddings for each input sentence',
'Sentences are passed as a list of string.',
'The quick brown fox jumps over the lazy dog.']
tokenizer = BertTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
model = BertForPreTraining.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
encoded_input = tokenizer(sentences, padding=True, truncation=True, max_length=128, return_tensors='ms')
model_output = model(**encoded_input)
sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])