def ResNet50(framework: Framework, version: str = '1', enable_trt=False): """Export, generate model family and register ResNet50 Arguments: framework (Framework): Framework name. version (str): Model version. enable_trt (bool): Flag for enabling TRT conversion. """ if framework == Framework.TENSORFLOW: model = tf.keras.applications.ResNet50() # converting to trt if not enable_trt: tfs_dir = generate_path(model_name='ResNet50', framework=framework, task=Task.IMAGE_CLASSIFICATION, engine=Engine.TFS, version=str(version)) TFSConverter.from_tensorflow(model, tfs_dir) model = str(tfs_dir.with_suffix('.zip')) register_model( model, dataset='imagenet', metric={Metric.ACC: 0.76}, task=Task.IMAGE_CLASSIFICATION, inputs=[ IOShape([-1, 224, 224, 3], dtype=float, name='input_1', format=ModelInputFormat.FORMAT_NHWC) ], outputs=[IOShape([-1, 1000], dtype=float, name='probs')], architecture='ResNet50', framework=framework, version=ModelVersion(version), convert=enable_trt, ) elif framework == Framework.PYTORCH: model = models.resnet50(pretrained=True) register_model( model, dataset='imagenet', metric={Metric.ACC: 0.76}, task=Task.IMAGE_CLASSIFICATION, inputs=[ IOShape([-1, 3, 224, 224], dtype=float, name='INPUT__0', format=ModelInputFormat.FORMAT_NCHW) ], outputs=[IOShape([-1, 1000], dtype=float, name='probs')], architecture='ResNet50', framework=framework, version=ModelVersion(version), ) else: raise ValueError('Framework not supported.')
def ResNet101(framework: Framework, version: str = "1"): if framework == Framework.TENSORFLOW: model = tf.keras.applications.ResNet101() register_model( model, dataset='imagenet', metric=..., # TODO: to be filled task=Task.IMAGE_CLASSIFICATION, inputs=[ IOShape([-1, 224, 224, 3], dtype=float, name='input_1', format=ModelInputFormat.FORMAT_NHWC) ], outputs=[IOShape([-1, 1000], dtype=float, name='probs')], architecture='ResNet101', framework=framework, version=ModelVersion(version)) elif framework == Framework.PYTORCH: model = models.resnet101(pretrained=True) register_model( model, dataset='imagenet', metric=..., # TODO task=Task.IMAGE_CLASSIFICATION, inputs=[ IOShape([-1, 3, 224, 224], dtype=float, name='INPUT__0', format=ModelInputFormat.FORMAT_NCHW) ], outputs=[IOShape([-1, 1000], dtype=float, name='probs')], architecture='ResNet101', framework=framework, version=ModelVersion(version)) else: raise ValueError('Framework not supported.')
async def publish_model( model: BaseMLModel = Depends(BaseMLModel.as_form), files: List[UploadFile] = File( [], description= 'This field can be set with empty value. In such settings, the publish is a dry run to' 'validate the `ml_model_in_form` field. You are recommend to try a dry run to find input' 'errors before you send the wight file(s) to the server.'), convert: bool = True, profile: bool = False): """Publish model to the model hub. The model weight file(s) as well as its meta information (e.g. architecture, framework, and serving engine) will be stored into the model hub. The publish API will also automatically convert the published model into other deployable format such as TorchScript and ONNX. After successfully converted, original model and its generated models will be profiled on the underlying devices in the clusters, and collects, aggregates, and processes running model performance. Args: model (MLModel): Model meta information. files (List[UploadFile]): A list of model weight files. The files are organized accordingly. Their file name contains relative path to their common parent directory. If the files is empty value, a dry-run to this API is conducted for parameter checks. No information will be saved into model hub in this case. convert (bool): Flag for auto configuration. profile (bool): Flag for auto profiling. Returns: A message response, with IDs of all published model. The format of the return is: ``` { "data": {"id": ["603e6a1f5a62b08bc0a2a7f2", "603e6a383b00cbe9bfee7277"]}, "status": true } ``` Specially, if the dry-run test passed, it will return a status True: ``` { "status": true } ``` """ # save the posted files as local cache loop = asyncio.get_event_loop() saved_path = model.saved_path if len(files) == 0: # conduct dry run for parameter check only. return {'status': True} if len(files) == 1: file = files[0] suffix = Path(file.filename).suffix try: # create directory if len(suffix) == 0: error = ErrorWrapper(ValueError( f'Expect a suffix for file {file.filename}, got None.'), loc='files[0]') raise RequestValidationError([error]) saved_path = saved_path.with_suffix(suffix) saved_path.parent.mkdir(exist_ok=True, parents=True) # save file await file.seek(0) with open(saved_path, 'wb') as buffer: await loop.run_in_executor(None, shutil.copyfileobj, file.file, buffer) finally: await file.close() else: raise NotImplementedError( '`publish_model` not implemented for multiple files upload.') # zip the files model = MLModel(**model.dict(), weight=saved_path) models = register_model(model=model, convert=convert, profile=profile) return { 'data': { 'id': [str(model.id) for model in models], }, 'status': True }
def update_finetune_model_as_new(id: str, updated_layer: Structure, dry_run: bool = False): # noqa """ Temporary function for finetune CV models. The function's functionality is overlapped with `update_model_structure_as_new`. Please use the `update_model_structure_as_new` in next release. Examples: Fine-tune the model by modify the layer with name 'fc' (last layer). The layer has a changed argument out_features = 10. op_='M' indicates the operation to this layer ('fc') is 'Modify'. There is no changes in layer connections. Therefore, the structure change summary is [M] fc: (...) out_features=10 >>> from collections import OrderedDict >>> structure_data = { ... 'layer': OrderedDict({'fc': {'out_features': 10, 'op_': 'M', 'type_': 'torch.nn.Linear'}}) ... } >>> update_finetune_model_as_new(id=..., updated_layer=Structure.parse_obj(structure_data)) Args: id (str): ID of the model to be updated. updated_layer (Structure): Contains layers to be fine-tuned. dry_run (bool): Test run for verify if the provided parameter (i.e. model specified in `id` and updated layers) is valid. Returns: """ if len(updated_layer.layer.items()) == 0: return True model = ModelService.get_model_by_id(id) if model.engine != Engine.PYTORCH: raise ValueError(f'model {id} is not supported for editing. ' f'Currently only support model with engine=PYTORCH') # download model as local cache cache_path = get_remote_model_weight(model=model) net = torch.load(cache_path) for layer_name, layer_param in updated_layer.layer.items(): layer_op = getattr(layer_param, 'op_') # update layer if layer_op == Operation.MODIFY: # check if the layer name exists # TODO check if layer path exists eg."layer1.0.conv1" if not hasattr(net, layer_name): raise ModelStructureError( f'Structure layer name `{layer_name}` not found in model {id}.' ) net_layer = getattr(net, layer_name) # check if the provided type matches the original type layer_type = type(net_layer) layer_type_provided = eval(layer_param.type_.value) # nosec if layer_type is not layer_type_provided: raise ModelStructureError( f'Expect `{layer_name}.type_` to be {layer_type}, ' f'but got {layer_type_provided}') # get layer parameters layer_param_old = layer_param.parse_layer_obj(net_layer) layer_param_data = layer_param_old.dict(exclude_none=True, exclude={'type_', 'op_'}) layer_param_update_data = layer_param.dict( exclude_none=True, exclude={'type_', 'op_'}) # replace 'null' with None. See reason :class:`ModelLayer`. for k, v in layer_param_update_data.items(): if v == 'null': layer_param_update_data[k] = None # update the layer parameters layer_param_data.update(layer_param_update_data) layer = layer_type(**layer_param_data) setattr(net, layer_name, layer) else: # if layer_op is Operation.ADD, # 1. check if the layer name not exists # 2. add a layer # 3. change the `forward` function according to the connections # if layer_op is Operation.DELETE, # 1. check if the layer exists # 2. delete the layer # 3. change the `forward` function raise ValueError( 'Operation not permitted. Please use `update_model_structure_as_new`.' ) input_tensors = list() bs = 1 for input_ in model.inputs: input_tensor = torch.rand(bs, *input_.shape[1:]).type( model_data_type_to_torch(input_.dtype)) input_tensors.append(input_tensor) # parse output tensors output_shapes = list() output_tensors = net(*input_tensors) if not isinstance(output_tensors, (list, tuple)): output_tensors = (output_tensors, ) for output_tensor in output_tensors: output_shape = IOShape(shape=[bs, *output_tensor.shape[1:]], dtype=type_to_data_type(output_tensor.dtype)) output_shapes.append(output_shape) if not dry_run: # TODO return validation result for dry_run mode # TODO apply Semantic Versioning https://semver.org/ # TODO reslove duplicate model version problem in a more efficient way version = ModelVersion(model.version.ver + 1) previous_models = ModelService.get_models( architecture=model.architecture, task=model.task, framework=model.framework, engine=Engine.NONE) if len(previous_models): last_version = max(previous_models, key=lambda k: k.version.ver).version.ver version = ModelVersion(last_version + 1) saved_path = generate_path_plain(architecture=model.architecture, task=model.task, framework=model.framework, engine=Engine.NONE, version=version) saved_path.parent.mkdir(parents=True, exist_ok=True) torch.save(model, saved_path.with_suffix('.pt')) mlmodelin = MLModel(dataset='', metric={key: 0 for key in model.metric.keys()}, task=model.task, inputs=model.inputs, outputs=output_shapes, architecture=model.name, framework=model.framework, engine=Engine.NONE, model_status=[ModelStatus.DRAFT], parent_model_id=model.id, version=version, weight=saved_path) register_model(mlmodelin, convert=False, profile=False) model_bo = ModelService.get_models(architecture=model.architecture, task=model.task, framework=model.framework, engine=Engine.NONE, version=version)[0] return {'id': model_bo.id}