From a6e192e98bd86adc9921c857474b9b9c31f434c9 Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 20:33:11 +0100 Subject: [PATCH 1/8] chg: Add ruff to pre-commit --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bb9463d..e90abaf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,3 +9,8 @@ repos: rev: 22.3.0 hooks: - id: black +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.254 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] From 00a7be9a1ddec55bb9d9971703e486fbddfb85e9 Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 22:45:27 +0100 Subject: [PATCH 2/8] chg: Rename InferenceEndpointConfig to EndpointConfig --- hugie/config.py | 4 ++-- hugie/endpoint.py | 6 +++--- hugie/models.py | 9 ++++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/hugie/config.py b/hugie/config.py index 6d0a296..55c0242 100644 --- a/hugie/config.py +++ b/hugie/config.py @@ -1,7 +1,7 @@ import srsly import typer -from hugie.models import InferenceEndpointConfig +from hugie.models import EndpointConfig app = typer.Typer() @@ -41,7 +41,7 @@ def modify( Modify an existing endpoint config file """ - config = InferenceEndpointConfig.from_json(path) + config = EndpointConfig.from_json(path) # Standard configs diff --git a/hugie/endpoint.py b/hugie/endpoint.py index ca7cbb4..a62a3a8 100644 --- a/hugie/endpoint.py +++ b/hugie/endpoint.py @@ -3,7 +3,7 @@ import requests import typer -from hugie.models import InferenceEndpointConfig +from hugie.models import EndpointConfig from hugie.settings import Settings from hugie.utils import format_table, load_json @@ -88,7 +88,7 @@ def create( data (str): Path to JSON data to create the endpoint """ - data = InferenceEndpointConfig.from_json(data).dict() + data = EndpointConfig.from_json(data).dict() try: response = requests.post(settings.endpoint_url, headers=headers, json=data) @@ -126,7 +126,7 @@ def update( """ Update an endpoint """ - data = dict(InferenceEndpointConfig.from_json(data)) + data = dict(EndpointConfig.from_json(data)) try: response = requests.put( diff --git a/hugie/models.py b/hugie/models.py index eb41aaf..04b5e3f 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -1,4 +1,7 @@ -from pydantic import BaseModel, BaseSettings +""" +These models are based on the openapi specification of the Hugging Face +Inference Endpoints API: https://api.endpoints.huggingface.cloud/ +""" from hugie.utils import load_json @@ -30,7 +33,7 @@ class ProviderModel(BaseModel): region: str = None -class InferenceEndpointConfig(BaseSettings): +class EndpointConfig(BaseSettings): """ Config for the inference endpoint """ @@ -74,7 +77,7 @@ def from_json(self, path: str): region=config["provider"]["region"], ) - config = InferenceEndpointConfig( + config = EndpointConfig( accountId=config["accountId"], type=config["type"], compute=compute, From 2f79b63a2e78ed444b48c96461b76f5580dca54b Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 22:48:21 +0100 Subject: [PATCH 3/8] chg: linting --- hugie/config.py | 6 +++++- hugie/endpoint.py | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hugie/config.py b/hugie/config.py index 55c0242..2677e84 100644 --- a/hugie/config.py +++ b/hugie/config.py @@ -26,7 +26,11 @@ def modify( framework: str = typer.Option("huggingface", help="Framework to use"), image: str = typer.Option( None, - help="Image to use when deploying model endppint. Must be string representing a valid JSON, e.g. '{'huggingface': {}}'", + help=( + "Image to use when deploying model endpoint." + "Must be string representing a valid JSON," + "e.g. '{'huggingface': {}}'" + ), ), repository: str = typer.Option(None, help="Name of the hf model repository"), revision: str = typer.Option(None, help="Revision of the hf model repository"), diff --git a/hugie/endpoint.py b/hugie/endpoint.py index a62a3a8..1bcb58f 100644 --- a/hugie/endpoint.py +++ b/hugie/endpoint.py @@ -76,7 +76,7 @@ def list( @app.command() def create( - data: str = typer.Argument(..., help="Path JSON data to create the endpoint"), + data: str = typer.Argument(..., help="Path to JSON data to create the endpoint"), json: Optional[bool] = typer.Option( None, "--json", help="Prints the full output in JSON." ), @@ -105,7 +105,7 @@ def create( elif response.status_code == 401: typer.secho("Invalid token", fg=typer.colors.YELLOW) elif response.status_code == 409: - typer.secho(f"Endpoint {name} already exists", fg=typer.colors.YELLOW) + typer.secho(f"Endpoint {data['name']} already exists", fg=typer.colors.YELLOW) else: typer.secho( f"Endpoint {data['name']} created successfully on {data['provider']['vendor']} using {data['model']['repository']}", From 0f36c60c3ce47d218538b1324bce52aa19944f44 Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 22:49:13 +0100 Subject: [PATCH 4/8] chg: Use Field for Endpoint.name --- hugie/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hugie/models.py b/hugie/models.py index 04b5e3f..fcfedc4 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -3,6 +3,8 @@ Inference Endpoints API: https://api.endpoints.huggingface.cloud/ """ +from pydantic import BaseModel, BaseSettings, Field + from hugie.utils import load_json @@ -42,7 +44,9 @@ class EndpointConfig(BaseSettings): type: str = None compute: ComputeModel = ComputeModel() model: ModelModel = ModelModel() - name: str = None + name: str = Field( + ..., description="Name of the endpoint", max_length=32, regex="^[a-z0-9-]+$" + ) provider: ProviderModel = ProviderModel() @classmethod From d6d257a78507d91bb45de01dba4401b10171fb8a Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 22:52:38 +0100 Subject: [PATCH 5/8] chg: Use Field for Endpoint.type --- hugie/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hugie/models.py b/hugie/models.py index fcfedc4..27575b0 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -41,7 +41,11 @@ class EndpointConfig(BaseSettings): """ accountId: str = None - type: str = None + type: str = Field( + ..., + description="Type of the endpoint, must be one of ['public', 'protected', 'private']", + regex="^(public|protected|private)$", + ) compute: ComputeModel = ComputeModel() model: ModelModel = ModelModel() name: str = Field( From 4c4ac10a7679ac84bb392f7795308d389a46b90f Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 22:56:30 +0100 Subject: [PATCH 6/8] chg: Rename ScalingModel to EndpointScaling --- hugie/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hugie/models.py b/hugie/models.py index 27575b0..5aba91b 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -8,9 +8,9 @@ from hugie.utils import load_json -class ScalingModel(BaseModel): - minReplica: int = 1 - maxReplica: int = 1 +class EndpointScaling(BaseModel): + minReplica: int = Field(..., alias="Minimum number of endpoint replicas") + maxReplica: int = Field(..., alias="Maximum number of endpoint replicas") class ComputeModel(BaseModel): @@ -18,7 +18,7 @@ class ComputeModel(BaseModel): accelerator: str = None instanceSize: str = None instanceType: str = None - scaling: ScalingModel = ScalingModel() + scaling: EndpointScaling = EndpointScaling() class ModelModel(BaseModel): @@ -68,7 +68,7 @@ def from_json(self, path: str): task=config["model"]["task"], ) - scaling = ScalingModel( + scaling = EndpointScaling( minReplica=config["compute"]["scaling"]["minReplica"], maxReplica=config["compute"]["scaling"]["maxReplica"], ) From a49d70f04a72b62a708fda9b8a70e1e1366daf7e Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Thu, 9 Mar 2023 23:05:00 +0100 Subject: [PATCH 7/8] chg: Rename ComputeModel to EndpointCompute --- hugie/models.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hugie/models.py b/hugie/models.py index 5aba91b..90863df 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -13,11 +13,13 @@ class EndpointScaling(BaseModel): maxReplica: int = Field(..., alias="Maximum number of endpoint replicas") -class ComputeModel(BaseModel): +class EndpointCompute(BaseModel): - accelerator: str = None - instanceSize: str = None - instanceType: str = None + accelerator: str = Field( + ..., alias="Accelerator type, one of [cpu, gpu]", regex="^(cpu|gpu)$" + ) + instanceSize: str = Field(..., alias="Instance size, e.g. large") + instanceType: str = Field(..., alias="Instance type, e.g. c6i") scaling: EndpointScaling = EndpointScaling() @@ -46,7 +48,7 @@ class EndpointConfig(BaseSettings): description="Type of the endpoint, must be one of ['public', 'protected', 'private']", regex="^(public|protected|private)$", ) - compute: ComputeModel = ComputeModel() + compute: EndpointCompute = EndpointCompute() model: ModelModel = ModelModel() name: str = Field( ..., description="Name of the endpoint", max_length=32, regex="^[a-z0-9-]+$" @@ -73,7 +75,7 @@ def from_json(self, path: str): maxReplica=config["compute"]["scaling"]["maxReplica"], ) - compute = ComputeModel( + compute = EndpointCompute( accelerator=config["compute"]["accelerator"], instanceSize=config["compute"]["instanceSize"], instanceType=config["compute"]["instanceType"], From f51535b388eea3dc80d57d8928bd80c9b41c5492 Mon Sep 17 00:00:00 2001 From: Matthew Upson Date: Fri, 10 Mar 2023 18:26:13 +0100 Subject: [PATCH 8/8] wip --- hugie/endpoint.py | 6 ++++-- hugie/models.py | 43 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/hugie/endpoint.py b/hugie/endpoint.py index 1bcb58f..8f16067 100644 --- a/hugie/endpoint.py +++ b/hugie/endpoint.py @@ -76,7 +76,9 @@ def list( @app.command() def create( - data: str = typer.Argument(..., help="Path to JSON data to create the endpoint"), + data: str = typer.Argument( + ..., help="Path or url of a JSON from which to create the endpoint" + ), json: Optional[bool] = typer.Option( None, "--json", help="Prints the full output in JSON." ), @@ -85,7 +87,7 @@ def create( Create an endpoint Args: - data (str): Path to JSON data to create the endpoint + data (str): Path or url of a JSON from which to create the endpoint. """ data = EndpointConfig.from_json(data).dict() diff --git a/hugie/models.py b/hugie/models.py index 90863df..185e34a 100644 --- a/hugie/models.py +++ b/hugie/models.py @@ -14,7 +14,6 @@ class EndpointScaling(BaseModel): class EndpointCompute(BaseModel): - accelerator: str = Field( ..., alias="Accelerator type, one of [cpu, gpu]", regex="^(cpu|gpu)$" ) @@ -23,12 +22,40 @@ class EndpointCompute(BaseModel): scaling: EndpointScaling = EndpointScaling() -class ModelModel(BaseModel): +class EndpointImageCredentials(BaseModel): + username: str = Field(..., alias="Username for private registry") + password: str = Field(..., alias="Password for private registry") + + +class EndpointModelImageConfig(BaseModel): + credentials: EndpointImageCredentials = EndpointImageCredentials() + env: dict = Field({}, alias="Environment variables") + health_route: str = Field("/health", alias="Health route") + port: int = Field(80, alias="Port", description="Endpoint API port") + url: str = Field( + ..., alias="URL for the container", example="https://host/image:tag" + ) + + +class EndpointModelImage(BaseModel): + image: str = Field( + "huggingface", + description="One of ['huggingface', 'custom']", + regex="^(huggingface|custom)$", + ) + config: dict = {} + + def __call__(self, **kwargs): + return {self.image: self.config} - framework: str = None - image: dict = {"huggingface": {}} - repository: str = None - revision: str = None + +class EndpointModel(BaseModel): + framework: str = Field(..., alias="Framework, one of [custom, pytorch, tensorflow]") + image: dict = Field({"huggingface": {}}) + repository: str = Field(..., alias="Repository name, e.g. gpt2") + revision: str = Field( + ..., description="Model commit hash, if not set, the latest commit will be used" + ) task: str = None @@ -49,7 +76,7 @@ class EndpointConfig(BaseSettings): regex="^(public|protected|private)$", ) compute: EndpointCompute = EndpointCompute() - model: ModelModel = ModelModel() + model: EndpointModel = EndpointModel() name: str = Field( ..., description="Name of the endpoint", max_length=32, regex="^[a-z0-9-]+$" ) @@ -62,7 +89,7 @@ def from_json(self, path: str): """ config = load_json(path) - model = ModelModel( + model = EndpointModel( framework=config["model"]["framework"], image=config["model"]["image"], repository=config["model"]["repository"],