Exemple #1
0
    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_tf_model(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.')
Exemple #2
0
 def ResNet101(framework: Framework, version: str = "1"):
     if framework == Framework.TENSORFLOW:
         model = tf.keras.applications.ResNet101()
         register_model(
             model,
             dataset='imagenet',
             acc=...,  # TODO: to be filled
             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',
             acc=...,  # TODO
             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.')
Exemple #3
0
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
    }
Exemple #4
0
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}