happyme531 commited on
Commit
7e8324f
·
verified ·
1 Parent(s): e3e3c5c

Add a better inference script.

Browse files
onnx/post_process.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import numpy as np
3
+ from PIL import Image, ImageDraw, ImageFont
4
+
5
+ colormap = ['blue','orange','green','purple','brown','pink','gray','olive','cyan','red',
6
+ 'lime','indigo','violet','aqua','magenta','coral','gold','tan','skyblue']
7
+
8
+ def plot_bbox(image, data):
9
+ """
10
+ Draws bounding boxes on an image.
11
+
12
+ Parameters:
13
+ - image: PIL Image object.
14
+ - data: Dictionary containing 'bboxes' and 'labels' keys.
15
+
16
+ Returns:
17
+ - Image with bounding boxes drawn.
18
+ """
19
+ draw = ImageDraw.Draw(image)
20
+
21
+ try:
22
+ font = ImageFont.truetype("arial.ttf", 20)
23
+ except IOError:
24
+ font = ImageFont.load_default().font_variant(size=20)
25
+
26
+ labels = data.get('labels', data.get('bboxes_labels', []))
27
+ for bbox, label in zip(data['bboxes'], labels):
28
+ x1, y1, x2, y2 = bbox
29
+ draw.rectangle([x1, y1, x2, y2], outline="red", width=3)
30
+
31
+ # Annotate the label
32
+ left, top, right, bottom = font.getbbox(label)
33
+ text_width = right - left
34
+ text_height = bottom - top
35
+
36
+ padding = 5
37
+ draw.rectangle([x1, y1 - text_height - padding*2, x1 + text_width + padding*2, y1], fill="red")
38
+ draw.text((x1 + padding, y1 - text_height - padding), label, fill="white", font=font)
39
+
40
+ return image
41
+
42
+ def draw_polygons(image, prediction, fill_mask=False):
43
+ """
44
+ Draws segmentation masks with polygons on an image.
45
+
46
+ Parameters:
47
+ - image: PIL Image object.
48
+ - prediction: Dictionary containing 'polygons' and 'labels' keys.
49
+ - fill_mask: Boolean indicating whether to fill the polygons with color.
50
+
51
+ Returns:
52
+ - Image with polygons drawn.
53
+ """
54
+ draw = ImageDraw.Draw(image)
55
+ scale = 1
56
+
57
+ for polygons, label in zip(prediction['polygons'], prediction['labels']):
58
+ color = random.choice(colormap)
59
+ fill_color = random.choice(colormap) if fill_mask else None
60
+
61
+ for _polygon in polygons:
62
+ _polygon = np.array(_polygon).reshape(-1, 2)
63
+ if len(_polygon) < 3:
64
+ print('Invalid polygon:', _polygon)
65
+ continue
66
+
67
+ _polygon = (_polygon * scale).reshape(-1).tolist()
68
+
69
+ if fill_mask:
70
+ draw.polygon(_polygon, outline=color, fill=fill_color)
71
+ else:
72
+ draw.polygon(_polygon, outline=color)
73
+
74
+ draw.text((_polygon[0] + 8, _polygon[1] + 2), label, fill=color)
75
+
76
+ return image
77
+
78
+ def draw_ocr_bboxes(image, prediction, scale=1):
79
+ """
80
+ Draws OCR bounding boxes on an image.
81
+
82
+ Parameters:
83
+ - image: PIL Image object.
84
+ - prediction: Dictionary containing 'quad_boxes' and 'labels' keys.
85
+ - scale: Scale factor for bounding box coordinates.
86
+
87
+ Returns:
88
+ - Image with OCR boxes drawn.
89
+ """
90
+ draw = ImageDraw.Draw(image)
91
+
92
+ try:
93
+ font = ImageFont.truetype("arial.ttf", 18)
94
+ except IOError:
95
+ font = ImageFont.load_default().font_variant(size=18)
96
+
97
+ if 'quad_boxes' not in prediction or 'labels' not in prediction:
98
+ return image
99
+
100
+ bboxes, labels = prediction['quad_boxes'], prediction['labels']
101
+ for box, label in zip(bboxes, labels):
102
+ color = random.choice(colormap)
103
+ new_box = (np.array(box) * scale).tolist()
104
+ draw.polygon(new_box, width=3, outline=color)
105
+ draw.text((new_box[0]+8, new_box[1]+2),
106
+ "{}".format(label),
107
+ align="right",
108
+ fill=color,
109
+ font=font)
110
+
111
+ return image
112
+
113
+ def convert_to_od_format(data):
114
+ """
115
+ Converts a dictionary with 'bboxes' and 'bboxes_labels' into a standard object detection format.
116
+
117
+ Parameters:
118
+ - data: The input dictionary.
119
+
120
+ Returns:
121
+ - A dictionary with 'bboxes' and 'labels' keys.
122
+ """
123
+ return {
124
+ 'bboxes': data.get('bboxes', []),
125
+ 'labels': data.get('bboxes_labels', [])
126
+ }
127
+
128
+ def visualize_results(image, parsed_answer, output_path="result.jpg"):
129
+ """
130
+ Main function to visualize results based on the task.
131
+
132
+ Parameters:
133
+ - image: PIL Image object.
134
+ - parsed_answer: The output from the model's post_process_generation.
135
+ - output_path: Path to save the visualized image.
136
+
137
+ Returns:
138
+ - The visualized PIL Image object, or None if no visualization is available.
139
+ """
140
+ vis_image = image.copy()
141
+
142
+ if not parsed_answer or not isinstance(parsed_answer, dict):
143
+ print("Invalid parsed_answer format.")
144
+ return None
145
+
146
+ task = list(parsed_answer.keys())[0]
147
+ data = parsed_answer[task]
148
+
149
+ if task in ['<OD>', '<DENSE_REGION_CAPTION>', '<REGION_PROPOSAL>', '<CAPTION_TO_PHRASE_GROUNDING>']:
150
+ vis_image = plot_bbox(vis_image, data)
151
+ elif task == '<OPEN_VOCABULARY_DETECTION>':
152
+ bbox_data = convert_to_od_format(data)
153
+ vis_image = plot_bbox(vis_image, bbox_data)
154
+ elif task in ['<REFERRING_EXPRESSION_SEGMENTATION>', '<REGION_TO_SEGMENTATION>']:
155
+ vis_image = draw_polygons(vis_image, data, fill_mask=True)
156
+ elif task == '<OCR_WITH_REGION>':
157
+ vis_image = draw_ocr_bboxes(vis_image, data)
158
+ else:
159
+ print(f"No visualization implemented for task: {task}")
160
+ return None
161
+
162
+ if output_path:
163
+ vis_image.save(output_path)
164
+ print(f"Visualization saved to {output_path}")
165
+
166
+ return vis_image
onnx/run.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoProcessor
2
+ from PIL import Image
3
+ import numpy as np
4
+ import onnxruntime as ort
5
+ import time
6
+ import argparse
7
+ import random
8
+
9
+ from post_process import visualize_results
10
+
11
+ # Use RKNN for some models
12
+ import ztu_somemodelruntime_rknnlite2 as rknnort
13
+ # Uncomment this to use ONNXRuntime for some models
14
+ # import onnxruntime as rknnort
15
+
16
+ # set current working directory to the directory of this file
17
+ import os
18
+ os.chdir(os.path.dirname(os.path.abspath(__file__)))
19
+
20
+ def run(image_path, prompt, max_new_tokens, output_image_path, temperature, seed):
21
+ # set seed for reproducibility
22
+ if seed is not None:
23
+ random.seed(seed)
24
+ np.random.seed(seed)
25
+
26
+ # 初始化总时间计数器
27
+ total_time = 0
28
+
29
+ # Initialize RKNNLite instances
30
+ vision_encoder = rknnort.InferenceSession("vision_encoder.onnx", providers=['CPUExecutionProvider'])
31
+ encoder = rknnort.InferenceSession("encoder_model.onnx", providers=['CPUExecutionProvider'])
32
+ decoder_prefill = rknnort.InferenceSession("decoder_model.onnx", providers=['CPUExecutionProvider'])
33
+
34
+ text_embed = ort.InferenceSession("embed_tokens_fp16.onnx", providers=['CPUExecutionProvider'])
35
+ decoder_decode = ort.InferenceSession("decoder_model_merged_q4.onnx", providers=['CPUExecutionProvider'])
36
+
37
+ prompt_tokens_list = [15, 17, 21, 25]
38
+
39
+ # 1. prepare inputs
40
+ processor = AutoProcessor.from_pretrained("..", trust_remote_code=True)
41
+
42
+ # 2. prepare image
43
+ image = Image.open(image_path)
44
+ original_image = image.copy()
45
+ original_size = image.size
46
+ # resize image to 768x768
47
+ # image = image.resize((768, 768))
48
+ # 3. prepare text
49
+
50
+ ## try tokenize first
51
+ input_tokens_len = processor.tokenizer(prompt, return_tensors="np")["input_ids"].shape[1]
52
+ print("input_tokens_len: ", input_tokens_len)
53
+
54
+ ## select the closest greater value
55
+ pad_to = 0
56
+ for i in prompt_tokens_list:
57
+ if i >= input_tokens_len:
58
+ pad_to = i
59
+ break
60
+ print("pad_to: ", pad_to)
61
+
62
+ inputs = processor(text=prompt, images=image, return_tensors="np", do_resize=True, padding="max_length", max_length=pad_to + 577, truncation=True)
63
+ for k, v in inputs.items():
64
+ print(k, v.shape)
65
+
66
+ # 4. run vision encoder using RKNN
67
+ start_time = time.time()
68
+ image_features = vision_encoder.run(None, {
69
+ "pixel_values": inputs["pixel_values"]
70
+ })[0]
71
+
72
+ end_time = time.time()
73
+ vision_encoder_time = (end_time - start_time) * 1000
74
+ total_time += vision_encoder_time
75
+ print(f"Vision encoder time: {vision_encoder_time:.2f} ms")
76
+ print(image_features.shape)
77
+ np.save("image_features.npy", image_features)
78
+
79
+ # 5. run text embed using RKNN
80
+ start_time = time.time()
81
+ inputs_embeds = text_embed.run(None, {
82
+ "input_ids": inputs["input_ids"]
83
+ })[0]
84
+ end_time = time.time()
85
+ text_embed_time = (end_time - start_time) * 1000
86
+ total_time += text_embed_time
87
+ print(f"Text embed time: {text_embed_time:.2f} ms")
88
+ print(inputs_embeds.shape)
89
+
90
+ # 6. concat image features and text embed
91
+ batch_size, image_token_length = image_features.shape[:-1]
92
+ image_attention_mask = np.ones((batch_size, image_token_length))
93
+ task_prefix_embeds = inputs_embeds
94
+ task_prefix_attention_mask = np.ones((batch_size, task_prefix_embeds.shape[1]))
95
+ # task_prefix_attention_mask = inputs["attention_mask"]
96
+ if len(task_prefix_attention_mask.shape) == 3:
97
+ task_prefix_attention_mask = task_prefix_attention_mask[:, 0]
98
+ inputs_embeds = np.concatenate([image_features, task_prefix_embeds], axis=1)
99
+ attention_mask = np.concatenate([image_attention_mask, task_prefix_attention_mask], axis=1)
100
+
101
+ # 6. run encoder using RKNN
102
+ start_time = time.time()
103
+ encoder_out = encoder.run(None, {
104
+ "inputs_embeds": inputs_embeds,
105
+ "attention_mask": attention_mask.astype(np.int64)
106
+ })
107
+ end_time = time.time()
108
+ encoder_time = (end_time - start_time) * 1000
109
+ total_time += encoder_time
110
+ print(f"Encoder time: {encoder_time:.2f} ms")
111
+ encoder_hidden_states = encoder_out[0]
112
+ print(encoder_hidden_states.shape)
113
+
114
+ # 7. run decoder prefill stage using RKNN
115
+ start_time = time.time()
116
+ next_token = processor.tokenizer.bos_token_id
117
+ decoder_outs = decoder_prefill.run(None, {
118
+ "inputs_embeds": inputs_embeds[:, -1:],
119
+ "encoder_hidden_states": encoder_hidden_states,
120
+ "encoder_attention_mask": attention_mask.astype(np.int64)
121
+ })
122
+ end_time = time.time()
123
+ decoder_prefill_time = (end_time - start_time) * 1000
124
+ total_time += decoder_prefill_time
125
+ print(f"Decoder prefill time: {decoder_prefill_time:.2f} ms")
126
+ # for output in decoder_outs:
127
+ # print(output.shape)
128
+
129
+ encoder_kv = decoder_outs[1:]
130
+
131
+ # 8. run decoder decode stage(autoregressive) (using onnxruntime)
132
+ generated_tokens = []
133
+ decoder_decode_total_time = 0
134
+ while generated_tokens.__len__() < max_new_tokens:
135
+ # 获取上一步的输出
136
+ logits = decoder_outs[0]
137
+ decoder_kv = decoder_outs[1:]
138
+
139
+ # 选择最后一个token的logits
140
+ next_token_logits = logits[:, -1, :]
141
+
142
+ if temperature == 0:
143
+ # Greedy decoding
144
+ next_token = np.argmax(next_token_logits, axis=-1)[0]
145
+ else:
146
+ # Temperature sampling
147
+ # 应用温度
148
+ next_token_logits /= temperature
149
+
150
+ # 从logits中减去最大值以提高数值稳定性
151
+ next_token_logits -= np.max(next_token_logits)
152
+
153
+ # 计算softmax
154
+ probs = np.exp(next_token_logits) / np.sum(np.exp(next_token_logits))
155
+
156
+ # 从概率分布中采样
157
+ next_token = np.random.choice(len(probs[0]), p=probs[0])
158
+
159
+ print("next_token: ", processor.decode([next_token]))
160
+ # 将新生成的token添加到结果中
161
+ generated_tokens.append(next_token)
162
+
163
+ # 如果生成了结束符,则停止生成
164
+ if next_token == 2: # </s>
165
+ break
166
+
167
+ # 准备下一步的输入
168
+ start_time = time.time()
169
+ next_input_embeds = text_embed.run(None, {
170
+ "input_ids": np.array([[next_token]], dtype=np.int64)
171
+ })[0]
172
+ end_time = time.time()
173
+ text_embed_time = (end_time - start_time) * 1000
174
+ decoder_decode_total_time += text_embed_time
175
+
176
+ # 运行decoder的decode阶段
177
+ start_time = time.time()
178
+ decoder_outs = decoder_decode.run(None, {
179
+ "use_cache_branch": np.array([True], dtype=np.bool_),
180
+ "inputs_embeds": next_input_embeds,
181
+ "encoder_hidden_states": encoder_hidden_states,
182
+ "encoder_attention_mask": attention_mask.astype(np.int64),
183
+ "past_key_values.0.decoder.key": decoder_kv[0],
184
+ "past_key_values.0.decoder.value": decoder_kv[1],
185
+ "past_key_values.0.encoder.key": encoder_kv[2],
186
+ "past_key_values.0.encoder.value": encoder_kv[3],
187
+ "past_key_values.1.decoder.key": decoder_kv[4],
188
+ "past_key_values.1.decoder.value": decoder_kv[5],
189
+ "past_key_values.1.encoder.key": encoder_kv[6],
190
+ "past_key_values.1.encoder.value": encoder_kv[7],
191
+ "past_key_values.2.decoder.key": decoder_kv[8],
192
+ "past_key_values.2.decoder.value": decoder_kv[9],
193
+ "past_key_values.2.encoder.key": encoder_kv[10],
194
+ "past_key_values.2.encoder.value": encoder_kv[11],
195
+ "past_key_values.3.decoder.key": decoder_kv[12],
196
+ "past_key_values.3.decoder.value": decoder_kv[13],
197
+ "past_key_values.3.encoder.key": encoder_kv[14],
198
+ "past_key_values.3.encoder.value": encoder_kv[15],
199
+ "past_key_values.4.decoder.key": decoder_kv[16],
200
+ "past_key_values.4.decoder.value": decoder_kv[17],
201
+ "past_key_values.4.encoder.key": encoder_kv[18],
202
+ "past_key_values.4.encoder.value": encoder_kv[19],
203
+ "past_key_values.5.decoder.key": decoder_kv[20],
204
+ "past_key_values.5.decoder.value": decoder_kv[21],
205
+ "past_key_values.5.encoder.key": encoder_kv[22],
206
+ "past_key_values.5.encoder.value": encoder_kv[23],
207
+ })
208
+ end_time = time.time()
209
+ decoder_decode_time = (end_time - start_time) * 1000
210
+ decoder_decode_total_time += decoder_decode_time
211
+
212
+ total_time += decoder_decode_total_time
213
+ print(f"Decoder decode total time: {decoder_decode_total_time:.2f} ms")
214
+
215
+ # 将生成的tokens转换为文本
216
+ print("generated_tokens: ", generated_tokens)
217
+ generated_text = processor.batch_decode([generated_tokens], skip_special_tokens=False)[0]
218
+ print("Generated Text:", generated_text)
219
+ parsed_answer = processor.post_process_generation(generated_text, task=prompt.split(">")[0].strip() + ">", image_size=original_size)
220
+ print("Parsed Answer:", parsed_answer)
221
+
222
+ print(f"Total inference time: {total_time:.2f} ms")
223
+
224
+ visualize_results(original_image, parsed_answer, output_image_path)
225
+
226
+ if __name__ == '__main__':
227
+ parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
228
+ parser.add_argument("image_path", type=str, help="Path to the input image.")
229
+ parser.add_argument(
230
+ "prompt",
231
+ type=str,
232
+ help="""Task prompt for the model. Available tasks:
233
+ --- Tasks without additional inputs ---
234
+ <CAPTION>
235
+ <DETAILED_CAPTION>
236
+ <MORE_DETAILED_CAPTION>
237
+ <OD>
238
+ <DENSE_REGION_CAPTION>
239
+ <REGION_PROPOSAL>
240
+ <OCR>
241
+ <OCR_WITH_REGION>
242
+
243
+ --- Tasks with additional text input ---
244
+ For these tasks, append the text input directly after the task prompt.
245
+ Example: --prompt "<CAPTION_TO_PHRASE_GROUNDING>A green car."
246
+
247
+ <CAPTION_TO_PHRASE_GROUNDING>
248
+ <REFERRING_EXPRESSION_SEGMENTATION>
249
+ <OPEN_VOCABULARY_DETECTION>
250
+
251
+ --- Tasks with location token inputs ---
252
+ For these tasks, append the location tokens after the task prompt.
253
+ Location tokens are in the format <loc_x1><loc_y1><loc_x2><loc_y2> with coordinates quantized to [0, 999].
254
+ Example: --prompt "<REGION_TO_SEGMENTATION><loc_702><loc_575><loc_866><loc_772>"
255
+
256
+ <REGION_TO_SEGMENTATION>
257
+ <REGION_TO_CATEGORY>
258
+ <REGION_TO_DESCRIPTION>
259
+ """
260
+ )
261
+ parser.add_argument("--max_new_tokens", type=int, default=512, help="Maximum number of new tokens to generate.")
262
+ parser.add_argument("--output_image_path", type=str, default="result_image.jpg", help="Path to save the output image with visualizations.")
263
+ parser.add_argument("--temperature", type=float, default=0.6, help="Temperature for sampling. Set to 0 for greedy decoding.")
264
+ parser.add_argument("--seed", type=int, default=None, help="Random seed for reproducibility.")
265
+ args = parser.parse_args()
266
+ run(args.image_path, args.prompt, args.max_new_tokens, args.output_image_path, args.temperature, args.seed)
onnx/ztu_somemodelruntime_rknnlite2.py ADDED
@@ -0,0 +1,569 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 模块级常量和函数
2
+ from rknnlite.api import RKNNLite
3
+ import numpy as np
4
+ import os
5
+ import warnings
6
+ import logging
7
+ from typing import List, Dict, Union, Optional
8
+
9
+ try:
10
+ import onnxruntime as ort
11
+ HAS_ORT = True
12
+ except ImportError:
13
+ HAS_ORT = False
14
+ warnings.warn("onnxruntime未安装,只能使用RKNN后端", ImportWarning)
15
+
16
+ # 配置日志
17
+ logger = logging.getLogger("somemodelruntime_rknnlite2")
18
+ logger.setLevel(logging.ERROR) # 默认只输出错误信息
19
+ if not logger.handlers:
20
+ handler = logging.StreamHandler()
21
+ handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
22
+ logger.addHandler(handler)
23
+
24
+ # ONNX Runtime日志级别到Python logging级别的映射
25
+ _LOGGING_LEVEL_MAP = {
26
+ 0: logging.DEBUG, # Verbose
27
+ 1: logging.INFO, # Info
28
+ 2: logging.WARNING, # Warning
29
+ 3: logging.ERROR, # Error
30
+ 4: logging.CRITICAL # Fatal
31
+ }
32
+
33
+ # 检查环境变量中的日志级别设置
34
+ try:
35
+ env_log_level = os.getenv('ZTU_MODELRT_RKNNL2_LOG_LEVEL')
36
+ if env_log_level is not None:
37
+ log_level = int(env_log_level)
38
+ if log_level in _LOGGING_LEVEL_MAP:
39
+ logger.setLevel(_LOGGING_LEVEL_MAP[log_level])
40
+ logger.info(f"从环境变量设置日志级别: {log_level}")
41
+ else:
42
+ logger.warning(f"环境变量ZTU_MODELRT_RKNNL2_LOG_LEVEL的值无效: {log_level}, 应该是0-4之间的整数")
43
+ except ValueError:
44
+ logger.warning(f"环境变量ZTU_MODELRT_RKNNL2_LOG_LEVEL的值无效: {env_log_level}, 应该是0-4之间的整数")
45
+
46
+
47
+ def set_default_logger_severity(level: int) -> None:
48
+ """
49
+ Sets the default logging severity. 0:Verbose, 1:Info, 2:Warning, 3:Error, 4:Fatal
50
+
51
+ Args:
52
+ level: 日志级别(0-4)
53
+ """
54
+ if level not in _LOGGING_LEVEL_MAP:
55
+ raise ValueError(f"无效的日志级别: {level}, 应该是0-4之间的整数")
56
+ logger.setLevel(_LOGGING_LEVEL_MAP[level])
57
+
58
+ def set_default_logger_verbosity(level: int) -> None:
59
+ """
60
+ Sets the default logging verbosity level. To activate the verbose log,
61
+ you need to set the default logging severity to 0:Verbose level.
62
+
63
+ Args:
64
+ level: 日志级别(0-4)
65
+ """
66
+ set_default_logger_severity(level)
67
+
68
+ # RKNN tensor type到numpy dtype的映射
69
+ RKNN_DTYPE_MAP = {
70
+ 0: np.float32, # RKNN_TENSOR_FLOAT32
71
+ 1: np.float16, # RKNN_TENSOR_FLOAT16
72
+ 2: np.int8, # RKNN_TENSOR_INT8
73
+ 3: np.uint8, # RKNN_TENSOR_UINT8
74
+ 4: np.int16, # RKNN_TENSOR_INT16
75
+ 5: np.uint16, # RKNN_TENSOR_UINT16
76
+ 6: np.int32, # RKNN_TENSOR_INT32
77
+ 7: np.uint32, # RKNN_TENSOR_UINT32
78
+ 8: np.int64, # RKNN_TENSOR_INT64
79
+ 9: bool, # RKNN_TENSOR_BOOL
80
+ 10: np.int8, # RKNN_TENSOR_INT4 (用int8表示)
81
+ }
82
+
83
+ def get_available_providers() -> List[str]:
84
+ """
85
+ 获取可用的设备提供者列表(为保持接口兼容性的占位函数)
86
+
87
+ Returns:
88
+ list: 可用的设备提供者列表,总是返回["CPUExecutionProvider", "somemodelruntime_rknnlite2_ExecutionProvider"]
89
+ """
90
+ return ["CPUExecutionProvider", "somemodelruntime_rknnlite2_ExecutionProvider"]
91
+
92
+
93
+ def get_device() -> str:
94
+ """
95
+ 获取当前设备
96
+
97
+ Returns:
98
+ str: 当前设备
99
+ """
100
+ return "RKNN2"
101
+
102
+ def get_version_info() -> Dict[str, str]:
103
+ """
104
+ 获取版本信息
105
+
106
+ Returns:
107
+ dict: 包含API和驱动版本信息的字典
108
+ """
109
+ runtime = RKNNLite()
110
+ version = runtime.get_sdk_version()
111
+ return {
112
+ "api_version": version.split('\n')[2].split(': ')[1].split(' ')[0],
113
+ "driver_version": version.split('\n')[3].split(': ')[1]
114
+ }
115
+
116
+ class IOTensor:
117
+ """输入/输出张量的信息封装类"""
118
+ def __init__(self, name, shape, type=None):
119
+ self.name = name.decode() if isinstance(name, bytes) else name
120
+ self.shape = shape
121
+ self.type = type
122
+
123
+ def __str__(self):
124
+ return f"IOTensor(name='{self.name}', shape={self.shape}, type={self.type})"
125
+
126
+ class SessionOptions:
127
+ """会话选项类"""
128
+ def __init__(self):
129
+ self.enable_profiling = False # 是否使用性能分析
130
+ self.intra_op_num_threads = 1 # 设置RKNN的线程数, 对应rknn的core_mask
131
+ self.log_severity_level = -1 # 另一个设置日志级别的参数
132
+ self.log_verbosity_level = -1 # 另一个设置日志级别的参数
133
+
134
+
135
+ class InferenceSession:
136
+ """
137
+ RKNNLite运行时封装类,API风格类似ONNX Runtime
138
+ """
139
+
140
+ def __new__(cls, model_path: str, sess_options: Optional[SessionOptions] = None, **kwargs):
141
+ processed_path = InferenceSession._process_model_path(model_path, sess_options)
142
+ if isinstance(processed_path, str) and processed_path.lower().endswith('.onnx'):
143
+ logger.info("使用ONNX Runtime加载模型")
144
+ if not HAS_ORT:
145
+ raise RuntimeError("未安装onnxruntime,无法加载ONNX模型")
146
+ return ort.InferenceSession(processed_path, sess_options=sess_options, **kwargs)
147
+ else:
148
+ # 如果不是 ONNX 模型,则调用父类的 __new__ 创建 InferenceSession 实例
149
+ instance = super().__new__(cls)
150
+ # 保存处理后的路径
151
+ instance._processed_path = processed_path
152
+ return instance
153
+
154
+ def __init__(self, model_path: str, sess_options: Optional[SessionOptions] = None, **kwargs):
155
+ """
156
+ 初始化运行时并加载模型
157
+
158
+ Args:
159
+ model_path: 模型文件路径(.rknn或.onnx)
160
+ sess_options: 会话选项
161
+ **kwargs: 其他初始化参数
162
+ """
163
+ options = sess_options or SessionOptions()
164
+
165
+ # 只在未设置环境变量时使用SessionOptions中的日志级别
166
+ if os.getenv('ZTU_MODELRT_RKNNL2_LOG_LEVEL') is None:
167
+ if options.log_severity_level != -1:
168
+ set_default_logger_severity(options.log_severity_level)
169
+ if options.log_verbosity_level != -1:
170
+ set_default_logger_verbosity(options.log_verbosity_level)
171
+
172
+ # 使用__new__中处理好的路径
173
+ model_path = getattr(self, '_processed_path', model_path)
174
+ if isinstance(model_path, str) and model_path.lower().endswith('.onnx'):
175
+ # 避免重复加载 ONNX 模型
176
+ return
177
+
178
+ # ... 现有的 RKNN 模型加载和初始化代码 ...
179
+ self.model_path = model_path
180
+ if not os.path.exists(self.model_path):
181
+ logger.error(f"模型文件不存在: {self.model_path}")
182
+ raise FileNotFoundError(f"模型文件不存在: {self.model_path}")
183
+
184
+ self.runtime = RKNNLite(verbose=options.enable_profiling)
185
+
186
+ logger.debug(f"正在加载模型: {self.model_path}")
187
+ ret = self.runtime.load_rknn(self.model_path)
188
+ if ret != 0:
189
+ logger.error(f"加载RKNN模型失败: {self.model_path}")
190
+ raise RuntimeError(f'加载RKNN模型失败: {self.model_path}')
191
+ logger.debug("模型加载成功")
192
+
193
+
194
+ if options.intra_op_num_threads == 1:
195
+ core_mask = RKNNLite.NPU_CORE_AUTO
196
+ elif options.intra_op_num_threads == 2:
197
+ core_mask = RKNNLite.NPU_CORE_0_1
198
+ elif options.intra_op_num_threads == 3:
199
+ core_mask = RKNNLite.NPU_CORE_0_1_2
200
+ else:
201
+ raise ValueError(f"intra_op_num_threads的值无效: {options.intra_op_num_threads}, 只能是1,2或3")
202
+
203
+ logger.debug("正在初始化运行时环境")
204
+ ret = self.runtime.init_runtime(core_mask=core_mask)
205
+ if ret != 0:
206
+ logger.error("初始化运行时环境失败")
207
+ raise RuntimeError('初始化运行时环境失败')
208
+ logger.debug("运行时环境初始化成功")
209
+
210
+ self._init_io_info()
211
+ self.options = options
212
+
213
+ def get_performance_info(self) -> Dict[str, float]:
214
+ """
215
+ 获取性能信息
216
+
217
+ Returns:
218
+ dict: 包含性能信息的字典
219
+ """
220
+ if not self.options.perf_debug:
221
+ raise RuntimeError("性能分析未启用,请在SessionOptions中设置perf_debug=True")
222
+
223
+ perf = self.runtime.rknn_runtime.get_run_perf()
224
+ return {
225
+ "run_duration": perf.run_duration / 1000.0 # 转换为毫秒
226
+ }
227
+
228
+ def set_core_mask(self, core_mask: int) -> None:
229
+ """
230
+ 设置NPU核心使用模式
231
+
232
+ Args:
233
+ core_mask: NPU核心掩码,使用NPU_CORE_*常量
234
+ """
235
+ ret = self.runtime.rknn_runtime.set_core_mask(core_mask)
236
+ if ret != 0:
237
+ raise RuntimeError("设置NPU核心模式失败")
238
+
239
+ @staticmethod
240
+ def _process_model_path(model_path, sess_options):
241
+ """
242
+ 处理模型路径,支持.onnx和.rknn文件
243
+
244
+ Args:
245
+ model_path: 模型文件路径
246
+ """
247
+ # 如果是ONNX文件,检查是否需要自动加载RKNN
248
+ if model_path.lower().endswith('.onnx'):
249
+ logger.info("检测到ONNX模型文件")
250
+
251
+ # 获取需要跳过自动加载的模型列表
252
+ skip_models = os.getenv('ZTU_MODELRT_RKNNL2_SKIP', '').strip()
253
+ if skip_models:
254
+ skip_list = [m.strip() for m in skip_models.split(',')]
255
+ # 获取模型文件名(不含路径)用于匹配
256
+ model_name = os.path.basename(model_path)
257
+ if model_name.lower() in [m.lower() for m in skip_list]:
258
+ logger.info(f"模型{model_name}在跳过列表中,将使用ONNX Runtime")
259
+ return model_path
260
+
261
+ # 构造RKNN文件路径
262
+ rknn_path = os.path.splitext(model_path)[0] + '.rknn'
263
+ if os.path.exists(rknn_path):
264
+ logger.info(f"找到对应的RKNN模型,将使用RKNN: {rknn_path}")
265
+ return rknn_path
266
+ else:
267
+ logger.info("未找到对应的RKNN模型,将使用ONNX Runtime")
268
+ return model_path
269
+
270
+ return model_path
271
+
272
+ def _convert_nhwc_to_nchw(self, shape):
273
+ """将NHWC格式的shape转换为NCHW格式"""
274
+ if len(shape) == 4:
275
+ # NHWC -> NCHW
276
+ n, h, w, c = shape
277
+ return [n, c, h, w]
278
+ return shape
279
+
280
+ def _init_io_info(self):
281
+ """初始化模型的输入输出信息"""
282
+ runtime = self.runtime.rknn_runtime
283
+
284
+ # 获取输入输出数量
285
+ n_input, n_output = runtime.get_in_out_num()
286
+
287
+ # 获取输入信息
288
+ self.input_tensors = []
289
+ for i in range(n_input):
290
+ attr = runtime.get_tensor_attr(i)
291
+ shape = [attr.dims[j] for j in range(attr.n_dims)]
292
+ # 对四维输入进行NHWC到NCHW的转换
293
+ shape = self._convert_nhwc_to_nchw(shape)
294
+ # 获取dtype
295
+ dtype = RKNN_DTYPE_MAP.get(attr.type, None)
296
+ tensor = IOTensor(attr.name, shape, dtype)
297
+ self.input_tensors.append(tensor)
298
+
299
+ # 获取输出信息
300
+ self.output_tensors = []
301
+ for i in range(n_output):
302
+ attr = runtime.get_tensor_attr(i, is_output=True)
303
+ shape = runtime.get_output_shape(i)
304
+ # 获取dtype
305
+ dtype = RKNN_DTYPE_MAP.get(attr.type, None)
306
+ tensor = IOTensor(attr.name, shape, dtype)
307
+ self.output_tensors.append(tensor)
308
+
309
+ def get_inputs(self):
310
+ """
311
+ 获取模型输入信息
312
+
313
+ Returns:
314
+ list: 包含输入信息的列表
315
+ """
316
+ return self.input_tensors
317
+
318
+ def get_outputs(self):
319
+ """
320
+ 获取模型输出信息
321
+
322
+ Returns:
323
+ list: 包含输出信息的列表
324
+ """
325
+ return self.output_tensors
326
+
327
+ def run(self, output_names=None, input_feed=None, data_format="nchw", **kwargs):
328
+ """
329
+ 执行模型推理
330
+
331
+ Args:
332
+ output_names: 输出节点名称列表,指定需要返回哪些输出
333
+ input_feed: 输入数据字典或列表
334
+ data_format: 输入数据格式,"nchw"或"nhwc"
335
+ **kwargs: 其他运行时参数
336
+
337
+ Returns:
338
+ list: 模型输出结果列表,如果指定了output_names则只返回指定的输出
339
+ """
340
+ if input_feed is None:
341
+ logger.error("input_feed不能为None")
342
+ raise ValueError("input_feed不能为None")
343
+
344
+ # 准备输入数据
345
+ if isinstance(input_feed, dict):
346
+ # 如果是字典,按照模型输入顺序排列
347
+ inputs = []
348
+ input_map = {tensor.name: i for i, tensor in enumerate(self.input_tensors)}
349
+ for tensor in self.input_tensors:
350
+ if tensor.name not in input_feed:
351
+ raise ValueError(f"缺少输入: {tensor.name}")
352
+ inputs.append(input_feed[tensor.name])
353
+ elif isinstance(input_feed, (list, tuple)):
354
+ # 如果是列表,确保长度匹配
355
+ if len(input_feed) != len(self.input_tensors):
356
+ raise ValueError(f"输入数量不匹配: 期望{len(self.input_tensors)}, 实际{len(input_feed)}")
357
+ inputs = list(input_feed)
358
+ else:
359
+ logger.error("input_feed必须是字典或列表类型")
360
+ raise ValueError("input_feed必须是字典或列表类型")
361
+
362
+ # 执行推理
363
+ try:
364
+ logger.debug("开始执行推理")
365
+ all_outputs = self.runtime.inference(inputs=inputs, data_format=data_format)
366
+
367
+ # 如果没有指定output_names,返回所有输出
368
+ if output_names is None:
369
+ return all_outputs
370
+
371
+ # 获取指定的输出
372
+ output_map = {tensor.name: i for i, tensor in enumerate(self.output_tensors)}
373
+ selected_outputs = []
374
+ for name in output_names:
375
+ if name not in output_map:
376
+ raise ValueError(f"未找到输出节点: {name}")
377
+ selected_outputs.append(all_outputs[output_map[name]])
378
+
379
+ return selected_outputs
380
+
381
+ except Exception as e:
382
+ logger.error(f"推理执行失败: {str(e)}")
383
+ raise RuntimeError(f"推理执行失败: {str(e)}")
384
+
385
+ def close(self):
386
+ """
387
+ 关闭会话,释放资源
388
+ """
389
+ if self.runtime is not None:
390
+ logger.info("正在释放运行时资源")
391
+ self.runtime.release()
392
+ self.runtime = None
393
+
394
+ def __enter__(self):
395
+ return self
396
+
397
+ def __exit__(self, exc_type, exc_val, exc_tb):
398
+ self.close()
399
+
400
+ def end_profiling(self) -> Optional[str]:
401
+ """
402
+ 结束性能分析的存根方法
403
+
404
+ Returns:
405
+ Optional[str]: None
406
+ """
407
+ warnings.warn("end_profiling()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
408
+ return None
409
+
410
+ def get_profiling_start_time_ns(self) -> int:
411
+ """
412
+ 获取性能分析开始时间的存根方法
413
+
414
+ Returns:
415
+ int: 0
416
+ """
417
+ warnings.warn("get_profiling_start_time_ns()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
418
+ return 0
419
+
420
+ def get_modelmeta(self) -> Dict[str, str]:
421
+ """
422
+ 获取模型元数据的存根方法
423
+
424
+ Returns:
425
+ Dict[str, str]: 空字典
426
+ """
427
+ warnings.warn("get_modelmeta()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
428
+ return {}
429
+
430
+ def get_session_options(self) -> SessionOptions:
431
+ """
432
+ 获取会话选项
433
+
434
+ Returns:
435
+ SessionOptions: 当前会话选项
436
+ """
437
+ return self.options
438
+
439
+ def get_providers(self) -> List[str]:
440
+ """
441
+ 获取当前使用的providers的存根方法
442
+
443
+ Returns:
444
+ List[str]: ["CPUExecutionProvider"]
445
+ """
446
+ warnings.warn("get_providers()是存根方法,始终返回CPUExecutionProvider", RuntimeWarning, stacklevel=2)
447
+ return ["CPUExecutionProvider"]
448
+
449
+ def get_provider_options(self) -> Dict[str, Dict[str, str]]:
450
+ """
451
+ 获取provider选项的存根方法
452
+
453
+ Returns:
454
+ Dict[str, Dict[str, str]]: 空字典
455
+ """
456
+ warnings.warn("get_provider_options()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
457
+ return {}
458
+
459
+ def get_session_config(self) -> Dict[str, str]:
460
+ """
461
+ 获取会话配置的存根方法
462
+
463
+ Returns:
464
+ Dict[str, str]: 空字典
465
+ """
466
+ warnings.warn("get_session_config()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
467
+ return {}
468
+
469
+ def get_session_state(self) -> Dict[str, str]:
470
+ """
471
+ 获取会话状态的存根方法
472
+
473
+ Returns:
474
+ Dict[str, str]: 空字典
475
+ """
476
+ warnings.warn("get_session_state()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
477
+ return {}
478
+
479
+ def set_session_config(self, config: Dict[str, str]) -> None:
480
+ """
481
+ 设置会话配置的存根方法
482
+
483
+ Args:
484
+ config: 会话配置字典
485
+ """
486
+ warnings.warn("set_session_config()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
487
+
488
+ def get_memory_info(self) -> Dict[str, int]:
489
+ """
490
+ 获取内存使用信息的存根方法
491
+
492
+ Returns:
493
+ Dict[str, int]: 空字典
494
+ """
495
+ warnings.warn("get_memory_info()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
496
+ return {}
497
+
498
+ def set_memory_pattern(self, enable: bool) -> None:
499
+ """
500
+ 设置内存模式的存根方法
501
+
502
+ Args:
503
+ enable: 是否启用内存模式
504
+ """
505
+ warnings.warn("set_memory_pattern()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
506
+
507
+ def disable_memory_pattern(self) -> None:
508
+ """
509
+ 禁用内存模式的存根方法
510
+ """
511
+ warnings.warn("disable_memory_pattern()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
512
+
513
+ def get_optimization_level(self) -> int:
514
+ """
515
+ 获取优化级别的存根方法
516
+
517
+ Returns:
518
+ int: 0
519
+ """
520
+ warnings.warn("get_optimization_level()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
521
+ return 0
522
+
523
+ def set_optimization_level(self, level: int) -> None:
524
+ """
525
+ 设置优化级别的存根方法
526
+
527
+ Args:
528
+ level: 优化级别
529
+ """
530
+ warnings.warn("set_optimization_level()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
531
+
532
+ def get_model_metadata(self) -> Dict[str, str]:
533
+ """
534
+ 获取模型元数据的存根方法(与get_modelmeta不同的接口)
535
+
536
+ Returns:
537
+ Dict[str, str]: 空字典
538
+ """
539
+ warnings.warn("get_model_metadata()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
540
+ return {}
541
+
542
+ def get_model_path(self) -> str:
543
+ """
544
+ 获取模型路径
545
+
546
+ Returns:
547
+ str: 模型文件路径
548
+ """
549
+ return self.model_path
550
+
551
+ def get_input_type_info(self) -> List[Dict[str, str]]:
552
+ """
553
+ 获取输入类型信息的存根方法
554
+
555
+ Returns:
556
+ List[Dict[str, str]]: 空列表
557
+ """
558
+ warnings.warn("get_input_type_info()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
559
+ return []
560
+
561
+ def get_output_type_info(self) -> List[Dict[str, str]]:
562
+ """
563
+ 获取输出类型信息的存根方法
564
+
565
+ Returns:
566
+ List[Dict[str, str]]: 空列表
567
+ """
568
+ warnings.warn("get_output_type_info()是存根方法,不提供实际功能", RuntimeWarning, stacklevel=2)
569
+ return []