Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]Remove obsolete modelservice #303

Closed
Closed
43 changes: 22 additions & 21 deletions docs/tutorial/housekeeper.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ Users can search for a model and obtain its detailed information.


```python
from modelci.hub.manager import retrieve_model, retrieve_model_by_task
from modelci.types.bo import Framework, Engine, Task
from modelci.hub.manager import retrieve_model
from modelci.types.models import Framework, Engine, Task

# By model name and optionally filtered by model framework and(or) model engine
model_bo = retrieve_model(
architecture_name='ResNet50', framework=Framework.PYTORCH, engine=Engine.TORCHSCRIPT
ml_model = retrieve_model(
architecture='ResNet50', framework=Framework.PyTorch, engine=Engine.TORCHSCRIPT
)
# By task
model_bo2 = retrieve_model_by_task(task=Task.IMAGE_CLASSIFICATION)
ml_model_by_task = retrieve_model(task=Task.Image_Classification)
```

The returned tuple contains the path where the model is cached and model meta information (e.g. model name, model framework).
Expand All @@ -40,30 +40,31 @@ You can update the model information manually using the update API.

Here is an example for updating the information of a ResNet50 model. The return value of the function `update_model(model)` is a boolean that indicates the status.

Since many models share a name, the function `get_models_by_name` will return a model list. You should specify the model version to get a model object.
Since many models share a name, the function `get_models` will return a model list. You should specify the model version to get a model object.

```python
from modelci.persistence.service import ModelService
from modelci.persistence.service import get_models, update_model
from modelci.types.models import ModelUpdateSchema, Metric
from pathlib import Path

# get_models_by_name will return a list of all matched results.
# get_models will return a list of all matched results.

model = ModelService.get_models('ResNet50')[0]
model.acc = 0.9
model.weight.weight = bytes([123, 255])
model = get_models(architecture='ResNet50')[0]

# check if update success
assert ModelService.update_model(model)
assert update_model(str(model.id), ModelUpdateSchema(metric={Metric.acc: 0.9}, weight=Path('path-to-new-model-file')))
```


MLModelCI allows you to get the model object by id, task and name.
MLModelCI allows you to get the model object by id, task, architecture, framework, engine and version.

```python
from modelci.persistence.service import ModelService
from modelci.persistence.service import get_models, get_by_id
from modelci.types.models import Task

model_bo = ModelService.get_models('ResNet50')[0] # get model by name
models = ModelService.get_models_by_task('image classification') # get model by task
model_bo2 = ModelService.get_models('ResNet50')[0] # get model by id
model_by_architecture = get_models(architecture='ResNet50')[0] # get model by name
model_by_task = get_models(task=Task.Image_Classification) # get model by task
model_by_id = get_by_id(str(model_by_architecture.id)) # get model by id
```

Getting by name or task may return more than one model objects.
Expand All @@ -73,11 +74,11 @@ Getting by name or task may return more than one model objects.
You can delete a model record easily using MLModelCI.

```python
from modelci.persistence.service import ModelService
from modelci.persistence.service import delete_model, get_models

model = ModelService.get_models('ResNet50')[0]

assert ModelService.delete_model_by_id(model.id) # delete the model record
model_list = get_models(architecture='ResNet50')
model = model_list[0]
assert delete_model(str(model.id)) # delete the model record
```

Currently, we only support deleting model by `model.id`.
2 changes: 1 addition & 1 deletion example/resnet50_explicit_path.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
weight: "~/.modelci/ResNet50/pytorch-pytorch/image_classification/1.pth"
architecture: ResNet18
architecture: ResNet50
framework: PYTORCH
engine: PYTORCH
version: 1
Expand Down
29 changes: 14 additions & 15 deletions modelci/app/experimental/endpoints/cv_tuner.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@

from modelci.experimental.model.model_structure import Structure, Operation
from modelci.hub.registrar import register_model
from modelci.hub.manager import get_remote_model_weight
from modelci.hub.cache_manager import get_remote_model_weight
from modelci.hub.utils import generate_path_plain
from modelci.persistence.service import ModelService
from modelci.types.bo import ModelVersion, Engine, IOShape, ModelStatus
from modelci.types.models import MLModel
from modelci.persistence.service import get_by_id, get_models
from modelci.types.models import MLModel, Engine, IOShape, ModelStatus
from modelci.types.type_conversion import model_data_type_to_torch, type_to_data_type
from modelci.utils.exceptions import ModelStructureError

Expand Down Expand Up @@ -52,7 +51,7 @@ def update_finetune_model_as_new(id: str, updated_layer: Structure, dry_run: boo
"""
if len(updated_layer.layer.items()) == 0:
return True
model = ModelService.get_model_by_id(id)
model = get_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')
Expand Down Expand Up @@ -117,23 +116,23 @@ def update_finetune_model_as_new(id: str, updated_layer: Structure, dry_run: boo
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_shape = IOShape(name="output", 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(
version = model.version + 1
previous_models = 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)
last_version = max(previous_models, key=lambda k: k.version.ver).version
version = last_version + 1

saved_path = generate_path_plain(
architecture=model.architecture,
Expand All @@ -143,32 +142,32 @@ def update_finetune_model_as_new(id: str, updated_layer: Structure, dry_run: boo
version=version
)
saved_path.parent.mkdir(parents=True, exist_ok=True)
torch.save(model,saved_path.with_suffix('.pt') )
torch.save(net, 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,
architecture=model.architecture,
framework=model.framework,
engine=Engine.NONE,
model_status=[ModelStatus.DRAFT],
parent_model_id=model.id,
version=version,
weight=saved_path
weight=saved_path.with_suffix('.pt')
)
register_model(
mlmodelin,
convert=False, profile=False
)

model_bo = ModelService.get_models(
ml_model = get_models(
architecture=model.architecture,
task=model.task,
framework=model.framework,
engine=Engine.NONE,
version=version
)[0]

return {'id': model_bo.id}
return {'id': str(ml_model.id)}
6 changes: 0 additions & 6 deletions modelci/app/experimental/endpoints/model_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@

ML model structure related API
"""
import torch
from fastapi import APIRouter
from modelci.hub.manager import get_remote_model_weight

from modelci.types.bo import Engine

from modelci.persistence.service import ModelService

from modelci.experimental.model.model_structure import Structure

Expand Down
8 changes: 4 additions & 4 deletions modelci/app/experimental/endpoints/trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""
from typing import List

from fastapi import APIRouter
from fastapi import APIRouter, BackgroundTasks
from starlette.responses import JSONResponse

from modelci.experimental.curd.model_train import save, get_by_id, get_all, delete_by_id, delete_all
Expand All @@ -18,13 +18,14 @@


@router.post('/', status_code=201)
def create_training_job(training_job: TrainingJobIn):
def create_training_job(training_job: TrainingJobIn, background_tasks: BackgroundTasks):
YuanmingLeee marked this conversation as resolved.
Show resolved Hide resolved
"""
Create a training job data object, and save it into the database. Then, submit the created training job
(with job ID generated by database) to the training job coordinator.
TODO return training job as soon as created

Args:
background_tasks:
training_job (TrainingJobIn): Training job to be submitted.

Returns:
Expand All @@ -34,8 +35,7 @@ def create_training_job(training_job: TrainingJobIn):
if id_ is not None:
training_job = get_by_id(id_)
trainer = PyTorchTrainer.from_training_job(training_job)
trainer.start()
trainer.join()
background_tasks.add_task(lambda trainer: trainer.start(), trainer)
return {'id': str(id_)}


Expand Down
7 changes: 3 additions & 4 deletions modelci/app/v1/endpoints/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
Date: 6/20/2020
"""
import asyncio
import http
import json
import shutil
from pathlib import Path
Expand All @@ -15,10 +14,10 @@
from fastapi import APIRouter, File, UploadFile, Depends
from fastapi.exceptions import RequestValidationError, HTTPException
from pydantic.error_wrappers import ErrorWrapper
from starlette.responses import JSONResponse
from starlette.responses import JSONResponse, Response

from modelci.hub.registrar import register_model
from modelci.persistence.service_ import get_by_id, get_models, update_model, delete_model, exists_by_id
from modelci.persistence.service import get_by_id, get_models, update_model, delete_model, exists_by_id
from modelci.types.models import MLModel, BaseMLModel, ModelUpdateSchema, Framework, Engine, Task

router = APIRouter()
Expand Down Expand Up @@ -52,7 +51,7 @@ def update(id: str, schema: ModelUpdateSchema):
return update_model(id, schema)


@router.delete('/{id}', status_code=http.HTTPStatus.NO_CONTENT)
@router.delete('/{id}', status_code=204, response_class=Response)
def delete(id: str):
if not exists_by_id(id):
raise HTTPException(
Expand Down
13 changes: 7 additions & 6 deletions modelci/app/v1/endpoints/visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"""

from fastapi import APIRouter
from modelci.persistence.service import ModelService
from modelci.types.bo.model_objects import Engine

from modelci.persistence.service import get_by_id
from modelci.types.models import Engine
from torchviz import make_dot
import torch

Expand All @@ -18,11 +19,11 @@

@router.get('/{id}')
def generate_model_graph(*, id: str): # noqa
model_bo = ModelService.get_model_by_id(id)
ml_model = get_by_id(id)
dot_graph = ''
if model_bo.engine == Engine.PYTORCH:
pytorch_model = torch.load(model_bo.saved_path)
sample_data = torch.zeros(1, *model_bo.inputs[0].shape[1:], dtype=torch.float, requires_grad=False)
if ml_model.engine == Engine.PYTORCH:
pytorch_model = torch.load(ml_model.saved_path)
sample_data = torch.zeros(1, *ml_model.inputs[0].shape[1:], dtype=torch.float, requires_grad=False)
out = pytorch_model(sample_data)
dot_graph = make_dot(out, params=dict(list(pytorch_model.named_parameters()) + [('x', sample_data)]))

Expand Down
2 changes: 1 addition & 1 deletion modelci/cli/modelhub.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import typer
import yaml
from pydantic import ValidationError
import modelci.persistence.service_ as ModelDB
import modelci.persistence.service as ModelDB
from modelci.hub.converter import generate_model_family

from modelci.config import app_settings
Expand Down
15 changes: 7 additions & 8 deletions modelci/controller/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@

from modelci.hub.profiler import Profiler
from modelci.metrics.benchmark.metric import BaseModelInspector
from modelci.persistence.service import ModelService
from modelci.types.bo import ModelBO, Status
from modelci.persistence.service import update_model, register_dynamic_profiling_result
from modelci.types.bo import Status
from modelci.types.models import MLModel, ModelUpdateSchema


class Job(object):
def __init__(
self,
client: BaseModelInspector,
device: str,
model_info: ModelBO,
model_info: MLModel,
container_name: str = None
):
self.client = client
Expand Down Expand Up @@ -81,16 +82,14 @@ def run(self) -> None:
else:
container_name = job.container_name
# change model status
job.model.status = Status.RUNNING
ModelService.update_model(job.model)
update_model(str(job.model.id), ModelUpdateSchema(model_status=Status.RUNNING))

profiler = Profiler(model_info=job.model, server_name=container_name, inspector=job.client)
dpr = profiler.diagnose(device=job.device)
ModelService.append_dynamic_profiling_result(job.model.id, dynamic_result=dpr)
register_dynamic_profiling_result(str(job.model.id), dynamic_result=dpr)

# set model status to pass
job.model.status = Status.PASS
ModelService.update_model(job.model)
update_model(str(job.model.id), ModelUpdateSchema(model_status=Status.PASS))

if job.container_name is None:
# get holding container
Expand Down
6 changes: 3 additions & 3 deletions modelci/experimental/curd/model_train.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from modelci.config import db_settings
from modelci.experimental.model.model_train import TrainingJob, TrainingJobIn, TrainingJobUpdate
from modelci.experimental.mongo_client import MongoClient
from modelci.persistence import service
from modelci.persistence.exceptions import ServiceException
from modelci.persistence.model_dao import ModelDAO

_db = MongoClient()[db_settings.mongo_db]
_collection = _db['training_job']
Expand Down Expand Up @@ -42,7 +42,7 @@ def get_all() -> List[TrainingJob]:

def save(training_job_in: TrainingJobIn) -> str:
model_id = training_job_in.model
if not ModelDAO.exists_by_id(ObjectId(model_id)):
if not service.exists_by_id(str(model_id)):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is exists_by_id accept ObjectID as model id input? If no, you may add this ObjectId support

raise ServiceException(f'Model with ID {model_id} not exist.')

training_job = TrainingJob(**training_job_in.dict(exclude_none=True))
Expand All @@ -55,7 +55,7 @@ def update(training_job: TrainingJobUpdate) -> int:
raise ValueError(f'id {training_job.id} not found.')

# check model ID
if training_job.model and not ModelDAO.exists_by_id(ObjectId(training_job.model)):
if training_job.model and not service.exists_by_id(str(training_job.model)):
raise ServiceException(f'Model with ID {training_job.model} not exist.')

# save update
Expand Down
Loading