Add a better inference script.
Browse files- onnx/post_process.py +166 -0
- onnx/run.py +266 -0
- onnx/ztu_somemodelruntime_rknnlite2.py +569 -0
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 []
|