Skip to content

Commit 092c027

Browse files
authored
[New Feature] Add paddle2onnx component (#1228)
* add paddle2onnx component * add comments * fix * supplement failure judgement * fix format * fix format
1 parent e368bb7 commit 092c027

File tree

3 files changed

+245
-102
lines changed

3 files changed

+245
-102
lines changed

requirements.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ six >= 1.14.0
99
matplotlib
1010
pandas
1111
packaging
12-
x2paddle
12+
x2paddle >= 1.4.0
13+
paddle2onnx >= 1.0.5
1314
rarfile
1415
gradio
1516
tritonclient[all]

visualdl/component/inference/model_convert_server.py

+214-89
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# flake8: noqa
12
# Copyright (c) 2022 VisualDL Authors. All Rights Reserve.
23
#
34
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,20 +14,18 @@
1314
# limitations under the License.
1415
# =======================================================================
1516
import base64
16-
import glob
1717
import hashlib
1818
import json
1919
import os
2020
import shutil
2121
import tempfile
22-
from threading import Lock
2322

23+
import paddle2onnx
2424
from flask import request
25-
from x2paddle.convert import caffe2paddle
2625
from x2paddle.convert import onnx2paddle
2726

2827
from .xarfile import archive
29-
from .xarfile import unarchive
28+
from visualdl.io.bfile import BosFileSystem
3029
from visualdl.server.api import gen_result
3130
from visualdl.server.api import result
3231
from visualdl.utils.dir import X2PADDLE_CACHE_PATH
@@ -35,126 +34,252 @@
3534

3635

3736
class ModelConvertApi(object):
37+
'''!
38+
Integrate multiple model convertion tools, and provide convertion service for users.
39+
When user uploads a model to this server, convert model and upload the results to VDL Bos.
40+
When user downloads the model, we get the data from Bos and send it to client.
41+
Maybe users can download from bos directy if frontend can achieve it.
42+
'''
43+
3844
def __init__(self):
39-
self.supported_formats = {'onnx', 'caffe'}
40-
self.lock = Lock()
41-
self.server_count = 0 # we use this variable to count requests handled,
42-
# and check the number of files every 100 requests.
43-
# If more than _max_cache_numbers files in cache, we delete the last recent used 50 files.
45+
'''
46+
Initialize a object to provide service. Need a BosFileSystem client to write data.
47+
'''
48+
try:
49+
self.bos_client = BosFileSystem()
50+
self.bucket_name = os.getenv("BOS_BUCKET_NAME")
51+
except Exception:
52+
# When BOS_HOST, BOS_AK, BOS_SK, BOS_STS are not set in the environment variables.
53+
# We use VDL BOS by default
54+
self.bos_client = BosFileSystem(write_flag=False)
55+
self.bos_client.renew_bos_client_from_server()
56+
self.bucket_name = 'visualdl-server'
4457

4558
@result()
46-
def convert_model(self, format):
47-
file_handle = request.files['file']
48-
data = file_handle.stream.read()
49-
if format not in self.supported_formats:
50-
raise RuntimeError('Model format {} is not supported. "\
51-
"Only onnx and caffe models are supported now.'.format(format))
59+
def onnx2paddle_model_convert(self, convert_to_lite, lite_valid_places,
60+
lite_model_type): # noqa:C901
61+
'''
62+
Convert onnx model to paddle model.
63+
'''
64+
model_handle = request.files['model']
65+
data = model_handle.stream.read()
5266
result = {}
53-
result['from'] = format
54-
result['to'] = 'paddle'
67+
# Do a simple data verification
68+
if convert_to_lite in ['true', 'True', 'yes', 'Yes', 'y']:
69+
convert_to_lite = True
70+
else:
71+
convert_to_lite = False
72+
73+
if lite_valid_places not in [
74+
'arm', 'opencl', 'x86', 'metal', 'xpu', 'bm', 'mlu',
75+
'intel_fpga', 'huawei_ascend_npu', 'imagination_nna',
76+
'rockchip_npu', 'mediatek_apu', 'huawei_kirin_npu',
77+
'amlogic_npu'
78+
]:
79+
lite_valid_places = 'arm'
80+
if lite_model_type not in ['protobuf', 'naive_buffer']:
81+
lite_model_type = 'naive_buffer'
82+
5583
# call x2paddle to convert models
5684
hl = hashlib.md5()
5785
hl.update(data)
5886
identity = hl.hexdigest()
5987
result['request_id'] = identity
60-
target_path = os.path.join(X2PADDLE_CACHE_PATH, identity)
61-
if os.path.exists(target_path):
62-
if os.path.exists(
63-
os.path.join(target_path, 'inference_model',
64-
'model.pdmodel')): # if data in cache
65-
with open(
66-
os.path.join(target_path, 'inference_model',
67-
'model.pdmodel'), 'rb') as model_fp:
68-
model_encoded = base64.b64encode(
69-
model_fp.read()).decode('utf-8')
70-
result['pdmodel'] = model_encoded
88+
# check whether model has been transfromed before
89+
# if model has been transformed before, data is stored at bos
90+
pdmodel_filename = 'bos://{}/onnx2paddle/{}/model.pdmodel'.format(
91+
self.bucket_name, identity)
92+
if self.bos_client.exists(pdmodel_filename):
93+
remote_data = self.bos_client.read_file(pdmodel_filename)
94+
if remote_data: # we should check data is not empty,
95+
# in case convertion failed but empty data is still uploaded before due to unknown reasons
96+
model_encoded = base64.b64encode(remote_data).decode('utf-8')
97+
result['model'] = model_encoded
7198
return result
72-
else:
99+
target_path = os.path.join(X2PADDLE_CACHE_PATH, 'onnx2paddle',
100+
identity)
101+
if not os.path.exists(target_path):
73102
os.makedirs(target_path, exist_ok=True)
74103
with tempfile.NamedTemporaryFile() as fp:
75104
fp.write(data)
76105
fp.flush()
77106
try:
78-
if format == 'onnx':
79-
try:
80-
import onnx # noqa: F401
81-
except Exception:
82-
raise RuntimeError(
83-
"[ERROR] onnx is not installed, use \"pip install onnx>=1.6.0\"."
84-
)
85-
onnx2paddle(fp.name, target_path)
86-
elif format == 'caffe':
87-
with tempfile.TemporaryDirectory() as unarchivedir:
88-
unarchive(fp.name, unarchivedir)
89-
prototxt_path = None
90-
weight_path = None
91-
for dirname, subdirs, filenames in os.walk(
92-
unarchivedir):
93-
for filename in filenames:
94-
if '.prototxt' in filename:
95-
prototxt_path = os.path.join(
96-
dirname, filename)
97-
if '.caffemodel' in filename:
98-
weight_path = os.path.join(
99-
dirname, filename)
100-
if prototxt_path is None or weight_path is None:
101-
raise RuntimeError(
102-
".prototxt or .caffemodel file is missing in your archive file, "
103-
"please check files uploaded.")
104-
caffe2paddle(prototxt_path, weight_path, target_path,
105-
None)
107+
import onnx # noqa: F401
108+
except Exception:
109+
raise RuntimeError(
110+
"[ERROR] onnx is not installed, use \"pip install onnx>=1.6.0\"."
111+
)
112+
try:
113+
if convert_to_lite is False:
114+
onnx2paddle(
115+
fp.name, target_path, convert_to_lite=convert_to_lite)
116+
else:
117+
onnx2paddle(
118+
fp.name,
119+
target_path,
120+
convert_to_lite=convert_to_lite,
121+
lite_valid_places=lite_valid_places,
122+
lite_model_type=lite_model_type)
106123
except Exception as e:
107124
raise RuntimeError(
108125
"[Convertion error] {}.\n Please open an issue at "
109126
"https://github.com/PaddlePaddle/X2Paddle/issues to report your problem."
110127
.format(e))
111-
with self.lock: # we need to enter dirname(target_path) to archive,
112-
# in case unneccessary directory added in archive.
128+
113129
origin_dir = os.getcwd()
114130
os.chdir(os.path.dirname(target_path))
115131
archive(os.path.basename(target_path))
116132
os.chdir(origin_dir)
117-
self.server_count += 1
133+
with open(
134+
os.path.join(X2PADDLE_CACHE_PATH, 'onnx2paddle',
135+
'{}.tar'.format(identity)), 'rb') as f:
136+
# upload archived transformed model to vdl bos
137+
data = f.read()
138+
filename = 'bos://{}/onnx2paddle/{}.tar'.format(
139+
self.bucket_name, identity)
140+
try:
141+
self.bos_client.write(filename, data)
142+
except Exception as e:
143+
print(
144+
"Exception: Write file {}.tar to bos failed, due to {}"
145+
.format(identity, e))
118146
with open(
119147
os.path.join(target_path, 'inference_model', 'model.pdmodel'),
120148
'rb') as model_fp:
121-
model_encoded = base64.b64encode(model_fp.read()).decode('utf-8')
122-
result['pdmodel'] = model_encoded
149+
# upload pdmodel file to bos, if some model has been transformed before, we can directly download from bos
150+
filename = 'bos://{}/onnx2paddle/{}/model.pdmodel'.format(
151+
self.bucket_name, identity)
152+
data = model_fp.read()
153+
try:
154+
self.bos_client.write(filename, data)
155+
except Exception as e:
156+
print(
157+
"Exception: Write file {}/model.pdmodel to bos failed, due to {}"
158+
.format(identity, e))
159+
# return transformed pdmodel file to frontend to show model structure graph
160+
model_encoded = base64.b64encode(data).decode('utf-8')
161+
# delete target_path
162+
shutil.rmtree(target_path)
163+
result['model'] = model_encoded
123164
return result
124165

125166
@result('application/octet-stream')
126-
def download_model(self, request_id):
127-
if os.path.exists(
128-
os.path.join(X2PADDLE_CACHE_PATH,
129-
'{}.tar'.format(request_id))):
130-
with open(
131-
os.path.join(X2PADDLE_CACHE_PATH,
132-
'{}.tar'.format(request_id)), 'rb') as f:
133-
data = f.read()
134-
if self.server_count % 100 == 0: # we check number of files every 100 request
135-
file_paths = glob.glob(
136-
os.path.join(X2PADDLE_CACHE_PATH, '*.tar'))
137-
if len(file_paths) >= _max_cache_numbers:
138-
file_paths = sorted(
139-
file_paths, key=os.path.getctime, reverse=True)
140-
for file_path in file_paths:
141-
try:
142-
os.remove(file_path)
143-
shutil.rmtree(
144-
os.path.join(
145-
os.path.dirname(file_path),
146-
os.path.splitext(
147-
os.path.basename(file_path))[0]))
148-
except Exception:
149-
pass
150-
return data
167+
def onnx2paddle_model_download(self, request_id):
168+
'''
169+
Download converted paddle model from bos.
170+
'''
171+
filename = 'bos://{}/onnx2paddle/{}.tar'.format(
172+
self.bucket_name, request_id)
173+
data = None
174+
if self.bos_client.exists(filename):
175+
data = self.bos_client.read_file(filename)
176+
if not data:
177+
raise RuntimeError(
178+
"The requested model can not be downloaded due to not existing or convertion failed."
179+
)
180+
return data
181+
182+
@result()
183+
def paddle2onnx_convert(self, opset_version, deploy_backend):
184+
'''
185+
Convert paddle model to onnx model.
186+
'''
187+
model_handle = request.files['model']
188+
params_handle = request.files['param']
189+
model_data = model_handle.stream.read()
190+
param_data = params_handle.stream.read()
191+
result = {}
192+
# Do a simple data verification
193+
try:
194+
opset_version = int(opset_version)
195+
except Exception:
196+
opset_version = 11
197+
if deploy_backend not in ['onnxruntime', 'tensorrt', 'others']:
198+
deploy_backend = 'onnxruntime'
199+
200+
# call paddle2onnx to convert models
201+
hl = hashlib.md5()
202+
hl.update(model_data + param_data)
203+
identity = hl.hexdigest()
204+
result['request_id'] = identity
205+
# check whether model has been transfromed before
206+
# if model has been transformed before, data is stored at bos
207+
model_filename = 'bos://{}/paddle2onnx/{}/model.onnx'.format(
208+
self.bucket_name, identity)
209+
if self.bos_client.exists(model_filename):
210+
remote_data = self.bos_client.read_file(model_filename)
211+
if remote_data: # we should check data is not empty,
212+
# in case convertion failed but empty data is still uploaded before due to unknown reasons
213+
model_encoded = base64.b64encode(remote_data).decode('utf-8')
214+
result['model'] = model_encoded
215+
return result
216+
217+
with tempfile.NamedTemporaryFile() as model_fp:
218+
with tempfile.NamedTemporaryFile() as param_fp:
219+
model_fp.write(model_data)
220+
param_fp.write(param_data)
221+
model_fp.flush()
222+
param_fp.flush()
223+
try:
224+
onnx_model = paddle2onnx.export(
225+
model_fp.name,
226+
param_fp.name,
227+
opset_version=opset_version,
228+
deploy_backend=deploy_backend)
229+
except Exception as e:
230+
raise RuntimeError(
231+
"[Convertion error] {}.\n Please open an issue at "
232+
"https://github.com/PaddlePaddle/Paddle2ONNX/issues to report your problem."
233+
.format(e))
234+
if not onnx_model:
235+
raise RuntimeError(
236+
"[Convertion error] Please check your input model and param files."
237+
)
238+
239+
# upload transformed model to vdl bos
240+
filename = 'bos://{}/paddle2onnx/{}/model.onnx'.format(
241+
self.bucket_name, identity)
242+
model_encoded = None
243+
if onnx_model:
244+
try:
245+
self.bos_client.write(filename, onnx_model)
246+
except Exception as e:
247+
print(
248+
"Exception: Write file {}/model.onnx to bos failed, due to {}"
249+
.format(identity, e))
250+
model_encoded = base64.b64encode(onnx_model).decode(
251+
'utf-8')
252+
result['model'] = model_encoded
253+
return result
254+
255+
@result('application/octet-stream')
256+
def paddle2onnx_download(self, request_id):
257+
'''
258+
Download converted onnx model from bos.
259+
'''
260+
filename = 'bos://{}/paddle2onnx/{}/model.onnx'.format(
261+
self.bucket_name, request_id)
262+
data = None
263+
if self.bos_client.exists(filename):
264+
data = self.bos_client.read_file(filename)
265+
if not data:
266+
raise RuntimeError(
267+
"The requested model can not be downloaded due to not existing or convertion failed."
268+
)
269+
return data
151270

152271

153272
def create_model_convert_api_call():
154273
api = ModelConvertApi()
155274
routes = {
156-
'convert': (api.convert_model, ['format']),
157-
'download': (api.download_model, ['request_id'])
275+
'paddle2onnx/convert': (api.paddle2onnx_convert,
276+
['opset_version', 'deploy_backend']),
277+
'paddle2onnx/download': (api.paddle2onnx_download, ['request_id']),
278+
'onnx2paddle/convert':
279+
(api.onnx2paddle_model_convert,
280+
['convert_to_lite', 'lite_valid_places', 'lite_model_type']),
281+
'onnx2paddle/download': (api.onnx2paddle_model_download,
282+
['request_id'])
158283
}
159284

160285
def call(path: str, args):

0 commit comments

Comments
 (0)