swing deploy api

Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/14103985
* tester

* bug fixed: download_file not import

* input example analyzer

* api: get_task_input_examples

* update chat

* 43 task

* fix decode_base64

* json format

* schema
This commit is contained in:
suluyan.sly
2023-09-22 19:18:57 +08:00
committed by wenmeng.zwm
parent 10d1fbe86f
commit d7c2a91e2c
9 changed files with 4444 additions and 71 deletions

View File

@@ -1,2 +1,3 @@
recursive-include modelscope/configs *.py *.cu *.h *.cpp recursive-include modelscope/configs *.py *.cu *.h *.cpp
recursive-include modelscope/cli/template *.tpl recursive-include modelscope/cli/template *.tpl
recursive-include modelscope/utils *.json

View File

@@ -82,6 +82,34 @@ def check_input_type(input_type, input):
TASK_INPUTS = { TASK_INPUTS = {
Tasks.image_text_retrieval: {
InputKeys.IMAGE: InputType.IMAGE,
InputKeys.TEXT: InputType.TEXT
},
Tasks.general_recognition: {
InputKeys.IMAGE: InputType.IMAGE,
InputKeys.TEXT: InputType.TEXT
},
Tasks.video_depth_estimation: {
InputKeys.IMAGE: InputType.IMAGE,
InputKeys.TEXT: InputType.TEXT
},
Tasks.indoor_layout_estimation:
InputType.IMAGE,
Tasks.image_demoireing:
InputType.IMAGE,
Tasks.panorama_depth_estimation:
InputType.IMAGE,
Tasks.video_depth_estimation:
InputType.VIDEO,
Tasks.animal_recognition:
InputType.IMAGE,
Tasks.motion_generation:
InputType.TEXT,
Tasks.video_panoptic_segmentation:
InputType.VIDEO,
Tasks.task_template: { Tasks.task_template: {
'image': InputType.IMAGE, 'image': InputType.IMAGE,

View File

@@ -297,12 +297,11 @@ class ChatGLM6bV2TextGenerationPipeline(Pipeline):
class QWenChatPipeline(Pipeline): class QWenChatPipeline(Pipeline):
def __init__(self, model: Union[Model, str], **kwargs): def __init__(self, model: Union[Model, str], **kwargs):
from modelscope.models.nlp import (QWenConfig, QWenForTextGeneration, from modelscope import AutoModelForCausalLM, AutoTokenizer
QWenTokenizer)
torch_dtype = kwargs.get('torch_dtype', torch.bfloat16) torch_dtype = kwargs.get('torch_dtype', torch.bfloat16)
device_map = kwargs.get('device_map', 'auto') device_map = kwargs.get('device_map', 'auto')
use_max_memory = kwargs.get('use_max_memory', False) use_max_memory = kwargs.get('use_max_memory', False)
quantization_config = kwargs.get('quantization_config', None) revision = kwargs.get('model_revision', 'v.1.0.5')
if use_max_memory: if use_max_memory:
max_memory = f'{int(torch.cuda.mem_get_info()[0] / 1024 ** 3) - 2}GB' max_memory = f'{int(torch.cuda.mem_get_info()[0] / 1024 ** 3) - 2}GB'
@@ -310,31 +309,24 @@ class QWenChatPipeline(Pipeline):
max_memory = {i: max_memory for i in range(n_gpus)} max_memory = {i: max_memory for i in range(n_gpus)}
else: else:
max_memory = None max_memory = None
if torch_dtype == 'bf16' or torch_dtype == torch.bfloat16:
bf16 = True
else:
bf16 = False
if isinstance(model, str): if isinstance(model, str):
model_dir = snapshot_download( self.tokenizer = AutoTokenizer.from_pretrained(
model) if not os.path.exists(model) else model model, revision=revision, trust_remote_code=True)
self.model = AutoModelForCausalLM.from_pretrained(
config = read_config(model_dir) model,
model_config = QWenConfig.from_pretrained(model_dir)
model_config.torch_dtype = torch_dtype
model = QWenForTextGeneration.from_pretrained(
model_dir,
cfg_dict=config,
config=model_config,
device_map=device_map, device_map=device_map,
torch_dtype=torch_dtype, revision=revision,
quantization_config=quantization_config, trust_remote_code=True,
max_memory=max_memory) fp16=bf16).eval()
model.generation_config = GenerationConfig.from_pretrained( self.model.generation_config = GenerationConfig.from_pretrained(
model_dir) model, trust_remote_code=True) # 可指定不同的生成长度、top_p等相关超参
self.model = model super().__init__(model=self.model, **kwargs)
self.model.eval()
self.tokenizer = QWenTokenizer.from_pretrained(self.model.model_dir)
super().__init__(model=model, **kwargs)
# skip pipeline model placement # skip pipeline model placement
self._model_prepare = True self._model_prepare = True
@@ -345,12 +337,19 @@ class QWenChatPipeline(Pipeline):
return inputs return inputs
# define the forward pass # define the forward pass
def forward(self, inputs: str, **forward_params) -> Dict[str, Any]: def forward(self, inputs: Union[Dict, str],
history = forward_params.get('history', None) **forward_params) -> Dict[str, Any]:
if isinstance(inputs, Dict):
text = inputs.get('text', None)
history = inputs.get('history', None)
else:
text = inputs
history = forward_params.get('history', None)
system = forward_params.get('system', 'You are a helpful assistant.') system = forward_params.get('system', 'You are a helpful assistant.')
append_history = forward_params.get('append_history', True) append_history = forward_params.get('append_history', True)
return self.model.chat(self.tokenizer, inputs, history, system, res = self.model.chat(self.tokenizer, text, history, system,
append_history) append_history)
return {'response': res[0], 'history': res[1]}
# format the outputs from pipeline # format the outputs from pipeline
def postprocess(self, input, **kwargs) -> Dict[str, Any]: def postprocess(self, input, **kwargs) -> Dict[str, Any]:
@@ -362,12 +361,11 @@ class QWenChatPipeline(Pipeline):
class QWenTextGenerationPipeline(Pipeline): class QWenTextGenerationPipeline(Pipeline):
def __init__(self, model: Union[Model, str], **kwargs): def __init__(self, model: Union[Model, str], **kwargs):
from modelscope.models.nlp import (QWenConfig, QWenForTextGeneration, from modelscope import AutoModelForCausalLM, AutoTokenizer
QWenTokenizer)
torch_dtype = kwargs.get('torch_dtype', torch.bfloat16) torch_dtype = kwargs.get('torch_dtype', torch.bfloat16)
device_map = kwargs.get('device_map', 'auto') device_map = kwargs.get('device_map', 'auto')
use_max_memory = kwargs.get('use_max_memory', False) use_max_memory = kwargs.get('use_max_memory', False)
quantization_config = kwargs.get('quantization_config', None) revision = kwargs.get('model_revision', 'v.1.0.4')
if use_max_memory: if use_max_memory:
max_memory = f'{int(torch.cuda.mem_get_info()[0] / 1024 ** 3) - 2}GB' max_memory = f'{int(torch.cuda.mem_get_info()[0] / 1024 ** 3) - 2}GB'
@@ -375,31 +373,27 @@ class QWenTextGenerationPipeline(Pipeline):
max_memory = {i: max_memory for i in range(n_gpus)} max_memory = {i: max_memory for i in range(n_gpus)}
else: else:
max_memory = None max_memory = None
if torch_dtype == 'bf16' or torch_dtype == torch.bfloat16:
bf16 = True
else:
bf16 = False
if isinstance(model, str): if isinstance(model, str):
model_dir = snapshot_download( self.model = AutoModelForCausalLM.from_pretrained(
model) if not os.path.exists(model) else model model,
config = read_config(model_dir)
model_config = QWenConfig.from_pretrained(model_dir)
model_config.torch_dtype = torch_dtype
model = QWenForTextGeneration.from_pretrained(
model_dir,
cfg_dict=config,
config=model_config,
device_map=device_map, device_map=device_map,
torch_dtype=torch_dtype, revision=revision,
quantization_config=quantization_config, trust_remote_code=True,
max_memory=max_memory) bf16=bf16).eval()
model.generation_config = GenerationConfig.from_pretrained( self.tokenizer = AutoTokenizer.from_pretrained(
model_dir) model, revision=revision, trust_remote_code=True)
self.model.generation_config = GenerationConfig.from_pretrained(
model)
else:
self.model = model
self.tokenizer = kwargs.get('tokenizer', None)
self.model = model super().__init__(model=self.model, **kwargs)
self.model.eval()
self.tokenizer = QWenTokenizer.from_pretrained(self.model.model_dir)
super().__init__(model=model, **kwargs)
# skip pipeline model placement # skip pipeline model placement
self._model_prepare = True self._model_prepare = True
@@ -411,10 +405,12 @@ class QWenTextGenerationPipeline(Pipeline):
# define the forward pass # define the forward pass
def forward(self, inputs: str, **forward_params) -> Dict[str, Any]: def forward(self, inputs: str, **forward_params) -> Dict[str, Any]:
inputs = self.tokenizer(inputs, return_tensors='pt').to('cuda:0')
return { return {
OutputKeys.TEXT: OutputKeys.TEXT:
self.model.chat(self.tokenizer, inputs, self.tokenizer.decode(
history=None)[OutputKeys.RESPONSE] self.model.generate(**inputs).cpu()[0],
skip_special_tokens=True)
} }
# format the outputs from pipeline # format the outputs from pipeline

View File

@@ -10,6 +10,7 @@ from PIL import Image, ImageOps
from modelscope.fileio import File from modelscope.fileio import File
from modelscope.metainfo import Preprocessors from modelscope.metainfo import Preprocessors
from modelscope.pipeline_inputs import InputKeys
from modelscope.utils.constant import Fields from modelscope.utils.constant import Fields
from modelscope.utils.type_assert import type_assert from modelscope.utils.type_assert import type_assert
from .base import Preprocessor from .base import Preprocessor
@@ -92,6 +93,10 @@ class LoadImage:
if len(input.shape) == 2: if len(input.shape) == 2:
input = cv2.cvtColor(input, cv2.COLOR_GRAY2BGR) input = cv2.cvtColor(input, cv2.COLOR_GRAY2BGR)
img = input[:, :, ::-1] img = input[:, :, ::-1]
elif isinstance(input, Dict):
img = input.get(InputKeys.IMAGE, None)
if img:
img = np.array(load_image(img))
else: else:
raise TypeError(f'input should be either str, PIL.Image,' raise TypeError(f'input should be either str, PIL.Image,'
f' np.array, but got {type(input)}') f' np.array, but got {type(input)}')
@@ -108,6 +113,10 @@ class LoadImage:
img = cv2.cvtColor(input, cv2.COLOR_GRAY2BGR) img = cv2.cvtColor(input, cv2.COLOR_GRAY2BGR)
img = input[:, :, ::-1] img = input[:, :, ::-1]
img = Image.fromarray(img.astype('uint8')).convert('RGB') img = Image.fromarray(img.astype('uint8')).convert('RGB')
elif isinstance(input, Dict):
img = input.get(InputKeys.IMAGE, None)
if img:
img = load_image(img)
else: else:
raise TypeError(f'input should be either str, PIL.Image,' raise TypeError(f'input should be either str, PIL.Image,'
f' np.array, but got {type(input)}') f' np.array, but got {type(input)}')

View File

@@ -3,11 +3,13 @@ import ast
import base64 import base64
import importlib import importlib
import inspect import inspect
import os
from io import BytesIO from io import BytesIO
from typing import Any from typing import Any
from urllib.parse import urlparse from urllib.parse import urlparse
import cv2 import cv2
import json
import numpy as np import numpy as np
from modelscope.hub.api import HubApi from modelscope.hub.api import HubApi
@@ -243,6 +245,33 @@ def process_arg_type_annotation(arg, default_value):
return arg.arg, 'object' return arg.arg, 'object'
def convert_to_value(item):
if isinstance(item, ast.Str):
return item.s
elif hasattr(ast, 'Bytes') and isinstance(item, ast.Bytes):
return item.s
elif isinstance(item, ast.Tuple):
return tuple(convert_to_value(i) for i in item.elts)
elif isinstance(item, ast.Num):
return item.n
elif isinstance(item, ast.Name):
result = VariableKey(item=item)
constants_lookup = {
'True': True,
'False': False,
'None': None,
}
return constants_lookup.get(
result.name,
result,
)
elif isinstance(item, ast.NameConstant):
# None, True, False are nameconstants in python3, but names in 2
return item.value
else:
return UnhandledKeyType()
def process_args(args): def process_args(args):
arguments = [] arguments = []
# name, type, has_default, default # name, type, has_default, default
@@ -259,7 +288,7 @@ def process_args(args):
# process defaults arg. # process defaults arg.
for arg, dft in zip(args.args[n_args - n_args_default:], args.defaults): for arg, dft in zip(args.args[n_args - n_args_default:], args.defaults):
# compatible with python3.7 ast.Num no value. # compatible with python3.7 ast.Num no value.
value = dft.value if hasattr(dft, 'value') else dft.n value = convert_to_value(dft)
arg_name, arg_type = process_arg_type_annotation(arg, value) arg_name, arg_type = process_arg_type_annotation(arg, value)
arguments.append((arg_name, arg_type, True, value)) arguments.append((arg_name, arg_type, True, value))
@@ -398,7 +427,7 @@ meta_type_schema_map = {
def generate_pipeline_parameters_schema(parameters): def generate_pipeline_parameters_schema(parameters):
parameters_schema = {'type': 'object', 'properties': {}} parameters_schema = {'type': 'object', 'properties': {}}
if len(parameters) == 0: if parameters is None or len(parameters) == 0:
return {} return {}
for param in parameters: for param in parameters:
name, param_type, has_default, default_value = param name, param_type, has_default, default_value = param
@@ -523,16 +552,18 @@ def is_url(url: str):
def decode_base64_to_image(content): def decode_base64_to_image(content):
if content.startswith('http') or content.startswith('oss'): if content.startswith('http') or content.startswith(
'oss') or os.path.exists(content):
return content return content
from PIL import Image from PIL import Image
image_file_content = base64.b64decode(content) image_file_content = base64.b64decode(content, '-_')
return Image.open(BytesIO(image_file_content)) return Image.open(BytesIO(image_file_content))
def decode_base64_to_audio(content): def decode_base64_to_audio(content):
if content.startswith('http') or content.startswith('oss'): if content.startswith('http') or content.startswith(
'oss') or os.path.exists(content):
return content return content
file_content = base64.b64decode(content) file_content = base64.b64decode(content)
@@ -540,7 +571,8 @@ def decode_base64_to_audio(content):
def decode_base64_to_video(content): def decode_base64_to_video(content):
if content.startswith('http') or content.startswith('oss'): if content.startswith('http') or content.startswith(
'oss') or os.path.exists(content):
return content return content
file_content = base64.b64decode(content) file_content = base64.b64decode(content)
@@ -594,13 +626,14 @@ def call_pipeline_with_json(pipeline_info: PipelineInfomation,
pipeline (Pipeline): The pipeline object. pipeline (Pipeline): The pipeline object.
body (Dict): The input object, include input and parameters body (Dict): The input object, include input and parameters
""" """
if pipeline_info.is_custom_call: # TODO: is_custom_call misjudgment
pipeline_inputs = body['input'] # if pipeline_info.is_custom_call:
result = pipeline(**pipeline_inputs) # pipeline_inputs = body['input']
else: # result = pipeline(**pipeline_inputs)
pipeline_inputs, parameters = service_base64_input_to_pipeline_input( # else:
pipeline_info.task_name, body) pipeline_inputs, parameters = service_base64_input_to_pipeline_input(
result = pipeline(pipeline_inputs, **parameters) pipeline_info.task_name, body)
result = pipeline(pipeline_inputs, **parameters)
return result return result
@@ -737,6 +770,9 @@ def pipeline_output_to_service_base64_output(task_name, pipeline_output):
task_outputs = [] task_outputs = []
if task_name in TASK_OUTPUTS: if task_name in TASK_OUTPUTS:
task_outputs = TASK_OUTPUTS[task_name] task_outputs = TASK_OUTPUTS[task_name]
# TODO: for batch
if isinstance(pipeline_output, list):
pipeline_output = pipeline_output[0]
for key, value in pipeline_output.items(): for key, value in pipeline_output.items():
if key not in task_outputs: if key not in task_outputs:
continue # skip the output not defined. continue # skip the output not defined.
@@ -768,3 +804,77 @@ def pipeline_output_to_service_base64_output(task_name, pipeline_output):
json_serializable_output[key] = value json_serializable_output[key] = value
return _convert_to_python_type(json_serializable_output) return _convert_to_python_type(json_serializable_output)
def get_task_input_examples(task):
current_work_dir = os.path.dirname(__file__)
with open(current_work_dir + '/pipeline_inputs.json', 'r') as f:
input_examples = json.load(f)
if task in input_examples:
return input_examples[task]
return None
def get_task_schemas(task):
current_work_dir = os.path.dirname(__file__)
with open(current_work_dir + '/pipeline_schema.json', 'r') as f:
schema = json.load(f)
if task in schema:
return schema[task]
return None
if __name__ == '__main__':
from modelscope.utils.ast_utils import load_index
index = load_index()
task_schemas = {}
for key, value in index['index'].items():
reg, task_name, class_name = key
if reg == 'PIPELINES' and task_name != 'default':
print(
f"value['filepath']: {value['filepath']}, class_name: {class_name}"
)
input, parameters = get_pipeline_input_parameters(
value['filepath'], class_name)
try:
if task_name in TASK_INPUTS and task_name in TASK_OUTPUTS:
# delete the first default input which is defined by task.
# parameters.pop(0)
parameters_schema = generate_pipeline_parameters_schema(
parameters)
input_schema = get_input_schema(task_name, None)
output_schema = get_output_schema(task_name)
schema = {
'input': input_schema,
'parameters': parameters_schema,
'output': output_schema
}
else:
logger.warning(
'Task: %s input is defined: %s, output is defined: %s which is not completed'
% (task_name, task_name in TASK_INPUTS, task_name
in TASK_OUTPUTS))
input_schema = None
output_schema = None
if task_name in TASK_INPUTS:
input_schema = get_input_schema(task_name, None)
if task_name in TASK_OUTPUTS:
output_schema = get_output_schema(task_name)
parameters_schema = generate_pipeline_parameters_schema(
parameters)
schema = {
'input': input_schema if input_schema else
parameters_schema, # all parameter is input
'parameters':
parameters_schema if input_schema else {},
'output': output_schema if output_schema else {
'type': 'object',
},
}
except BaseException:
continue
task_schemas[task_name] = schema
s = json.dumps(task_schemas)
with open('./task_schema.json', 'w') as f:
f.write(s)

View File

@@ -0,0 +1,277 @@
{
"action-detection":{
"input":{
"video":"data/test/videos/action_detection_test_video.mp4"
}
},
"action-recognition":{
"input":{
"video":"data/test/videos/action_recognition_test_video.mp4"
}
},
"animal-recognition":{
"input":{
"image":"data/test/images/dogs.jpg"
}
},
"chat":{
"input":{
"text":"你有什么推荐吗?",
"history":[
[
"今天天气真好,",
"今天天气真好,出去走走怎么样?"
]
]
}
},
"domain-specific-object-detection":{
"input":{
"image":"data/test/images/image_traffic_sign.jpg"
}
},
"face-2d-keypoints":{
"input":{
"image":"data/test/images/face_detection.png"
}
},
"face-attribute-recognition":{
"input":{
"image":"data/test/images/face_recognition_1.png"
}
},
"facial-expression-recognition":{
"input":{
"image":"data/test/images/facial_expression_recognition.jpg"
}
},
"general-recognition":{
"input":{
"image":"data/test/images/dogs.jpg"
}
},
"human-detection":{
"input":{
"image":"data/test/images/image_detection.jpg"
}
},
"image-captioning":{
"input":{
"image":"data/test/images/image_captioning.png"
}
},
"image-classification":{
"input":{
"image":"data/test/images/content_check.jpg"
}
},
"image-demoireing":{
"input":{
"image":"data/test/images/shop_segmentation.jpg"
}
},
"image-object-detection":{
"input":{
"image":"data/test/images/image_detection.jpg"
}
},
"image-portrait-stylization":{
"input":{
"image":"https://modelscope.oss-cn-beijing.aliyuncs.com/test/images/image_cartoon.png"
}
},
"image-segmentation":{
"input":{
"image":"data/test/images/image_semantic_segmentation.jpg"
},
"parameters":{
}
},
"image-text-retrieval":{
"input":{
"image":"data/test/images/image_mplug_vqa.jpg",
"text":"What is the woman doing?"
}
},
"indoor-layout-estimation":{
"input":{
"image":"data/test/images/image_traffic_sign.jpg"
}
},
"live-category":{
"input":{
"video":"data/test/videos/live_category_test_video.mp4"
}
},
"motion-generation":{
"input":{
"text":"the person walked forward and is picking up his toolbox"
}
},
"named-entity-recognition":{
"input":{
"text":"这与温岭市新河镇的一个神秘的传说有关。[SEP]地名"
}
},
"nli":{
"input":[
"四川商务职业学院和四川财经职业学院哪个好?",
"四川商务职业学院商务管理在哪个校区?"
],
"parameters":{
}
},
"ocr-recognition":{
"input":{
"image":"data/test/images/image_ocr_recognition.jpg"
}
},
"panorama-depth-estimation":{
"input":{
"image":"data/test/images/panorama_depth_estimation.jpg"
}
},
"semantic-segmentation":{
"input":{
"image":"data/test/images/image_salient_detection.jpg"
}
},
"shop-segmentation":{
"input":{
"image":"data/test/images/shop_segmentation.jpg"
}
},
"text-classification":{
"input":{
"text":"i like this wonderful place"
},
"parameters":{
}
},
"text-driven-segmentation":{
"input":{
"image":"data/test/images/text_driven_segmentation.jpg",
"text":"bear"
}
},
"text-generation":{
"input":{
"text":"蒙古国的首都是乌兰巴托Ulaanbaatar\n冰岛的首都是雷克雅未克Reykjavik\n埃塞俄比亚的首都是"
},
"parameters":{
}
},
"text-ranking":{
"input":{
"source_sentence":[
"how long it take to get a master's degree"
],
"sentences_to_compare":[
"On average, students take about 18 to 24 months to complete a master's degree.",
"On the other hand, some students prefer to go at a slower pace and choose to take several years to complete their studies.",
"It can take anywhere from two semesters"
]
}
},
"text-summarization":{
"input":{
"text":"five-time world champion michelle kwan withdrew from the #### us figure skating championships on wednesday , but will petition us skating officials for the chance to compete at the #### turin olympics ."
}
},
"text-to-video-synthesis":{
"input":{
"text":"A panda eating bamboo on a rock."
}
},
"translation":{
"input":{
"text":"声明补充说,沃伦的同事都深感震惊,并且希望他能够投案自首。"
}
},
"video-captioning":{
"input":{
"video":"data/test/videos/video_caption_and_qa_test.mp4"
}
},
"video-category":{
"input":{
"video":"data/test/videos/video_category_test_video.mp4"
}
},
"video-depth-estimation":{
"input":{
"video":"data/test/videos/video_depth_estimation.mp4"
}
},
"video-embedding":{
"input":{
"video":"data/test/videos/action_recognition_test_video.mp4"
}
},
"video-multi-object-tracking":{
"input":{
"video":"data/test/videos/MOT17-03-partial.mp4"
}
},
"video-panoptic-segmentation":{
"input":{
"video":"data/test/videos/kitti-step_testing_image_02_0000.mp4"
}
},
"video-question-answering":{
"input":{
"video":"data/test/videos/video_caption_and_qa_test.mp4",
"text":"How many people are there?"
}
},
"video-summarization":{
"input":{
"text":"data/test/videos/video_category_test_video.mp4"
}
},
"visual-entailment":{
"input":{
"image":"data/test/images/dogs.jpg",
"text":"there are two birds."
}
},
"visual-grounding":{
"input":{
"image":"data/test/images/visual_grounding.png",
"text":"a blue turtle-like pokemon with round head"
}
},
"visual-question-answering":{
"input":{
"image":"data/test/images/image_mplug_vqa.jpg",
"text":"What is the woman doing?"
}
},
"word-segmentation":{
"input":{
"text":"今天天气不错,适合出去游玩"
}
},
"zero-shot-classification":{
"input":{
"text":"全新突破 解放军运20版空中加油机曝光"
},
"parameters":{
"candidate_labels":[
"文化",
"体育",
"娱乐",
"财经",
"家居",
"汽车",
"教育",
"科技",
"军事"
]
}
}
}

File diff suppressed because it is too large Load Diff

76
tests/json_call_test.py Normal file
View File

@@ -0,0 +1,76 @@
import os
import json
from modelscope.hub.api import HubApi
from modelscope.hub.file_download import model_file_download
from modelscope.hub.utils.utils import get_cache_dir
from modelscope.pipelines import pipeline
from modelscope.utils.config import Config
from modelscope.utils.constant import ModelFile
from modelscope.utils.input_output import (
call_pipeline_with_json, get_pipeline_information_by_pipeline,
get_task_input_examples, pipeline_output_to_service_base64_output)
class ModelJsonTest:
def __init__(self):
self.api = HubApi()
def test_single(self, model_id: str, model_revision=None):
# get model_revision & task info
cache_root = get_cache_dir()
configuration_file = os.path.join(cache_root, model_id,
ModelFile.CONFIGURATION)
if not model_revision:
model_revision = self.api.list_model_revisions(
model_id=model_id)[0]
if not os.path.exists(configuration_file):
configuration_file = model_file_download(
model_id=model_id,
file_path=ModelFile.CONFIGURATION,
revision=model_revision)
cfg = Config.from_file(configuration_file)
task = cfg.safe_get('task')
# init pipeline
ppl = pipeline(
task=task, model=model_id, model_revision=model_revision)
pipeline_info = get_pipeline_information_by_pipeline(ppl)
# call pipeline
data = get_task_input_examples(task)
print(task, data)
infer_result = call_pipeline_with_json(pipeline_info, ppl, data)
result = pipeline_output_to_service_base64_output(task, infer_result)
return result
if __name__ == '__main__':
model_list = [
'damo/nlp_structbert_nli_chinese-base',
'damo/nlp_structbert_word-segmentation_chinese-base',
'damo/nlp_structbert_zero-shot-classification_chinese-base',
'damo/cv_unet_person-image-cartoon_compound-models',
'damo/nlp_structbert_sentiment-classification_chinese-tiny',
'damo/nlp_csanmt_translation_zh2en',
'damo/nlp_rom_passage-ranking_chinese-base',
'damo/ofa_image-caption_muge_base_zh',
'damo/nlp_raner_named-entity-recognition_chinese-base-ecom-50cls',
'damo/nlp_structbert_sentiment-classification_chinese-ecommerce-base',
'damo/text-to-video-synthesis',
'qwen/Qwen-7B',
'qwen/Qwen-7B-Chat',
'ZhipuAI/ChatGLM-6B',
]
tester = ModelJsonTest()
for model in model_list:
try:
res = tester.test_single(model)
print(f'\nmodel_id {model} call_pipeline_with_json run ok.\n')
except BaseException as e:
print(
f'\nmodel_id {model} call_pipeline_with_json run failed: {e}.\n'
)

View File

@@ -62,7 +62,10 @@ class AnalysisTestFile(ast.NodeVisitor):
class AnalysisTestClass(ast.NodeVisitor): class AnalysisTestClass(ast.NodeVisitor):
def __init__(self, test_class_node, builder_function_name) -> None: def __init__(self,
test_class_node,
builder_function_name,
file_analyzer=None) -> None:
super().__init__() super().__init__()
self.test_class_node = test_class_node self.test_class_node = test_class_node
self.builder_function_name = builder_function_name self.builder_function_name = builder_function_name
@@ -72,6 +75,44 @@ class AnalysisTestClass(ast.NodeVisitor):
] # class method trainer builder(call build_trainer) ] # class method trainer builder(call build_trainer)
self.custom_class_method_builder_calls = [ self.custom_class_method_builder_calls = [
] # the builder call statement ] # the builder call statement
self.variables = {}
def get_variables(self, key: str):
if key in self.variables:
return self.variables[key]
return key
def get_ast_value(self, statements):
if not isinstance(statements, list):
statements = [statements]
res = []
for item in statements:
if isinstance(item, ast.Name):
res.append(self.get_variables(item.id))
elif isinstance(item, ast.Attribute):
res.append(self.get_variables(item.value.id))
elif isinstance(item, ast.Str):
res.append(self.get_variables(item.s))
elif isinstance(item, ast.Dict):
keys = [i.s for i in item.keys]
values = self.get_ast_value(item.values)
res.append(dict(zip(keys, values)))
return res
def get_final_variables(self, statement: ast.Assign):
if len(statement.targets) == 1 and \
isinstance(statement.targets[0], ast.Name):
if isinstance(statement.value, ast.Call):
if isinstance(statement.value.func, ast.Attribute) and \
isinstance(statement.value.func.value, ast.Name) and \
statement.value.func.value.id == 'Image':
self.variables[str(
statement.targets[0].id)] = self.get_ast_value(
statement.value.args[0])
else:
self.variables[str(
statement.targets[0].id)] = self.get_ast_value(
statement.value)
def visit_FunctionDef(self, node: ast.FunctionDef) -> Any: def visit_FunctionDef(self, node: ast.FunctionDef) -> Any:
if node.name.startswith('setUp'): if node.name.startswith('setUp'):
@@ -83,6 +124,7 @@ class AnalysisTestClass(ast.NodeVisitor):
self.setup_variables[str( self.setup_variables[str(
statement.targets[0].attr)] = str( statement.targets[0].attr)] = str(
statement.value.attr) statement.value.attr)
self.get_final_variables(statement)
elif node.name.startswith('test_'): elif node.name.startswith('test_'):
self.test_methods.append(node) self.test_methods.append(node)
else: else:
@@ -312,6 +354,48 @@ def analysis_trainer_test_suite(test_file, modified_register_modules):
return tested_trainers return tested_trainers
def get_test_parameters(test_method, analyzer):
for node in ast.walk(test_method):
func = None
if not isinstance(node, ast.FunctionDef):
continue
for statement in node.body:
if isinstance(statement, ast.Assign):
analyzer.get_final_variables(statement)
if not func and isinstance(statement, ast.Assign):
if isinstance(statement.value, ast.Call) and isinstance(
statement.value.func, ast.Name) and ( # noqa W504
'pipeline' in statement.value.func.id
or 'Pipeline' in statement.value.func.id):
func = statement.targets[0].id
if func and isinstance(statement, ast.Assign) and isinstance(
statement.value, ast.Call) and isinstance(
statement.value.func, ast.Name):
if statement.value.func.id == func:
inputs = statement.value.args
return analyzer.get_ast_value(inputs)
def analysis_pipeline_test_examples(test_file):
examples = []
with open(test_file, 'rb') as tsf:
src = tsf.read()
test_root = ast.parse(src, test_file)
test_file_analyzer = AnalysisTestFile(
test_file, SYSTEM_PIPELINE_BUILDER_FUNCTION_NAME)
test_file_analyzer.visit(test_root)
for test_class in test_file_analyzer.test_classes:
test_class_analyzer = AnalysisTestClass(
test_class, SYSTEM_PIPELINE_BUILDER_FUNCTION_NAME,
test_file_analyzer)
test_class_analyzer.visit(test_class)
for test_method in test_class_analyzer.test_methods:
parameters = get_test_parameters(test_method, test_class_analyzer)
examples.append(parameters)
return examples
def analysis_pipeline_test_suite(test_file, modified_register_modules): def analysis_pipeline_test_suite(test_file, modified_register_modules):
tested_tasks = [] tested_tasks = []
with open(test_file, 'rb') as tsf: with open(test_file, 'rb') as tsf:
@@ -413,7 +497,18 @@ def get_pipelines_trainers_test_info(register_modules):
if __name__ == '__main__': if __name__ == '__main__':
test_file = 'tests/pipelines/test_action_detection.py' all_pipeline_cases = [
tasks = analysis_pipeline_test_suite(test_file, None) os.path.join(dp, f) for dp, dn, filenames in os.walk(
os.path.join(os.getcwd(), 'tests', 'pipelines')) for f in filenames
if os.path.splitext(f)[1] == '.py'
]
for test_file in all_pipeline_cases:
print('\n', test_file)
tasks = analysis_pipeline_test_suite(test_file, None)
examples = analysis_pipeline_test_examples(test_file)
print(tasks) from modelsope.metainfo import Tasks
for task, example in zip(tasks, examples):
task_convert = f't = Tasks.{task}'
exec(task_convert)
print(t, example)