diff --git a/samples/model-builder/conftest.py b/samples/model-builder/conftest.py index 3f60573613..718f4645ff 100644 --- a/samples/model-builder/conftest.py +++ b/samples/model-builder/conftest.py @@ -220,7 +220,7 @@ def mock_model(): @pytest.fixture -def mock_get_model(mock_model): +def mock_init_model(mock_model): with patch.object(aiplatform, "Model") as mock: mock.return_value = mock_model yield mock @@ -232,6 +232,19 @@ def mock_batch_predict_model(mock_model): yield mock +@pytest.fixture +def mock_upload_model(): + with patch.object(aiplatform.models.Model, "upload") as mock: + yield mock + + +@pytest.fixture +def mock_deploy_model(mock_model, mock_endpoint): + with patch.object(mock_model, "deploy") as mock: + mock.return_value = mock_endpoint + yield mock + + """ ---------------------------------------------------------------------------- Job Fixtures diff --git a/samples/model-builder/create_batch_prediction_job_sample_test.py b/samples/model-builder/create_batch_prediction_job_sample_test.py index 6d3c68e77e..a3eb5ed085 100644 --- a/samples/model-builder/create_batch_prediction_job_sample_test.py +++ b/samples/model-builder/create_batch_prediction_job_sample_test.py @@ -18,7 +18,7 @@ def test_create_batch_prediction_job_sample( - mock_sdk_init, mock_get_model, mock_batch_predict_model + mock_sdk_init, mock_model, mock_init_model, mock_batch_predict_model ): create_batch_prediction_job_sample.create_batch_prediction_job_sample( @@ -33,7 +33,7 @@ def test_create_batch_prediction_job_sample( mock_sdk_init.assert_called_once_with( project=constants.PROJECT, location=constants.LOCATION ) - mock_get_model.assert_called_once_with(constants.MODEL_NAME) + mock_init_model.assert_called_once_with(constants.MODEL_NAME) mock_batch_predict_model.assert_called_once_with( job_display_name=constants.DISPLAY_NAME, gcs_source=constants.GCS_SOURCES, diff --git a/samples/model-builder/deploy_model_with_automatic_resources_sample.py b/samples/model-builder/deploy_model_with_automatic_resources_sample.py new file mode 100644 index 0000000000..27976ae652 --- /dev/null +++ b/samples/model-builder/deploy_model_with_automatic_resources_sample.py @@ -0,0 +1,57 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Optional, Sequence, Tuple + +from google.cloud import aiplatform + + +# [START aiplatform_sdk_deploy_model_with_automatic_resources_sample] +def deploy_model_with_automatic_resources_sample( + project, + location, + model_name: str, + endpoint: Optional[aiplatform.Endpoint] = None, + deployed_model_display_name: Optional[str] = None, + traffic_percentage: Optional[int] = 0, + traffic_split: Optional[Dict[str, int]] = None, + min_replica_count: int = 1, + max_replica_count: int = 1, + metadata: Optional[Sequence[Tuple[str, str]]] = (), + sync: bool = True, +): + + aiplatform.init(project=project, location=location) + + model = aiplatform.Model(model_name=model_name) + + model.deploy( + endpoint=endpoint, + deployed_model_display_name=deployed_model_display_name, + traffic_percentage=traffic_percentage, + traffic_split=traffic_split, + min_replica_count=min_replica_count, + max_replica_count=max_replica_count, + metadata=metadata, + sync=sync, + ) + + model.wait() + + print(model.display_name) + print(model.resource_name) + return model + + +# [END aiplatform_sdk_deploy_model_with_automatic_resources_sample] diff --git a/samples/model-builder/deploy_model_with_automatic_resources_test.py b/samples/model-builder/deploy_model_with_automatic_resources_test.py new file mode 100644 index 0000000000..fff08b6e7e --- /dev/null +++ b/samples/model-builder/deploy_model_with_automatic_resources_test.py @@ -0,0 +1,52 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import deploy_model_with_automatic_resources_sample +import test_constants as constants + + +def test_deploy_model_with_automatic_resources_sample( + mock_sdk_init, mock_model, mock_init_model, mock_deploy_model, +): + + deploy_model_with_automatic_resources_sample.deploy_model_with_automatic_resources_sample( + project=constants.PROJECT, + location=constants.LOCATION, + model_name=constants.MODEL_NAME, + endpoint=constants.ENDPOINT_NAME, + deployed_model_display_name=constants.DEPLOYED_MODEL_DISPLAY_NAME, + traffic_percentage=constants.TRAFFIC_PERCENTAGE, + traffic_split=constants.TRAFFIC_SPLIT, + min_replica_count=constants.MIN_REPLICA_COUNT, + max_replica_count=constants.MAX_REPLICA_COUNT, + metadata=constants.ENDPOINT_DEPLOY_METADATA, + ) + + mock_sdk_init.assert_called_once_with( + project=constants.PROJECT, location=constants.LOCATION + ) + + mock_init_model.assert_called_once_with(model_name=constants.MODEL_NAME) + + mock_deploy_model.assert_called_once_with( + endpoint=constants.ENDPOINT_NAME, + deployed_model_display_name=constants.DEPLOYED_MODEL_DISPLAY_NAME, + traffic_percentage=constants.TRAFFIC_PERCENTAGE, + traffic_split=constants.TRAFFIC_SPLIT, + min_replica_count=constants.MIN_REPLICA_COUNT, + max_replica_count=constants.MAX_REPLICA_COUNT, + metadata=constants.ENDPOINT_DEPLOY_METADATA, + sync=True, + ) diff --git a/samples/model-builder/deploy_model_with_dedicated_resources_sample.py b/samples/model-builder/deploy_model_with_dedicated_resources_sample.py new file mode 100644 index 0000000000..093dfae805 --- /dev/null +++ b/samples/model-builder/deploy_model_with_dedicated_resources_sample.py @@ -0,0 +1,70 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Optional, Sequence, Tuple + +from google.cloud import aiplatform +from google.cloud.aiplatform import explain + + +# [START aiplatform_sdk_deploy_model_with_dedicated_resources_sample] +def deploy_model_with_dedicated_resources_sample( + project, + location, + model_name: str, + machine_type: str, + endpoint: Optional[aiplatform.Endpoint] = None, + deployed_model_display_name: Optional[str] = None, + traffic_percentage: Optional[int] = 0, + traffic_split: Optional[Dict[str, int]] = None, + min_replica_count: int = 1, + max_replica_count: int = 1, + accelerator_type: Optional[str] = None, + accelerator_count: Optional[int] = None, + explanation_metadata: Optional[explain.ExplanationMetadata] = None, + explanation_parameters: Optional[explain.ExplanationParameters] = None, + metadata: Optional[Sequence[Tuple[str, str]]] = (), + sync: bool = True, +): + + aiplatform.init(project=project, location=location) + + model = aiplatform.Model(model_name=model_name) + + # The explanation_metadata and explanation_parameters should only be + # provided for a custom trained model and not an AutoML model. + model.deploy( + endpoint=endpoint, + deployed_model_display_name=deployed_model_display_name, + traffic_percentage=traffic_percentage, + traffic_split=traffic_split, + machine_type=machine_type, + min_replica_count=min_replica_count, + max_replica_count=max_replica_count, + accelerator_type=accelerator_type, + accelerator_count=accelerator_count, + explanation_metadata=explanation_metadata, + explanation_parameters=explanation_parameters, + metadata=metadata, + sync=sync, + ) + + model.wait() + + print(model.display_name) + print(model.resource_name) + return model + + +# [END aiplatform_sdk_deploy_model_with_dedicated_resources_sample] diff --git a/samples/model-builder/deploy_model_with_dedicated_resources_test.py b/samples/model-builder/deploy_model_with_dedicated_resources_test.py new file mode 100644 index 0000000000..6dac9ad6e3 --- /dev/null +++ b/samples/model-builder/deploy_model_with_dedicated_resources_test.py @@ -0,0 +1,62 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import deploy_model_with_dedicated_resources_sample +import test_constants as constants + + +def test_deploy_model_with_dedicated_resources_sample( + mock_sdk_init, mock_model, mock_init_model, mock_deploy_model +): + + deploy_model_with_dedicated_resources_sample.deploy_model_with_dedicated_resources_sample( + project=constants.PROJECT, + location=constants.LOCATION, + machine_type=constants.MACHINE_TYPE, + model_name=constants.MODEL_NAME, + endpoint=constants.ENDPOINT_NAME, + deployed_model_display_name=constants.DEPLOYED_MODEL_DISPLAY_NAME, + traffic_percentage=constants.TRAFFIC_PERCENTAGE, + traffic_split=constants.TRAFFIC_SPLIT, + min_replica_count=constants.MIN_REPLICA_COUNT, + max_replica_count=constants.MAX_REPLICA_COUNT, + accelerator_type=constants.ACCELERATOR_TYPE, + accelerator_count=constants.ACCELERATOR_COUNT, + explanation_metadata=constants.EXPLANATION_METADATA, + explanation_parameters=constants.EXPLANATION_PARAMETERS, + metadata=constants.ENDPOINT_DEPLOY_METADATA, + ) + + mock_sdk_init.assert_called_once_with( + project=constants.PROJECT, location=constants.LOCATION + ) + + mock_init_model.assert_called_once_with(model_name=constants.MODEL_NAME) + + mock_deploy_model.assert_called_once_with( + endpoint=constants.ENDPOINT_NAME, + deployed_model_display_name=constants.DEPLOYED_MODEL_DISPLAY_NAME, + traffic_percentage=constants.TRAFFIC_PERCENTAGE, + traffic_split=constants.TRAFFIC_SPLIT, + machine_type=constants.MACHINE_TYPE, + min_replica_count=constants.MIN_REPLICA_COUNT, + max_replica_count=constants.MAX_REPLICA_COUNT, + accelerator_type=constants.ACCELERATOR_TYPE, + accelerator_count=constants.ACCELERATOR_COUNT, + explanation_metadata=constants.EXPLANATION_METADATA, + explanation_parameters=constants.EXPLANATION_PARAMETERS, + metadata=constants.ENDPOINT_DEPLOY_METADATA, + sync=True, + ) diff --git a/samples/model-builder/get_model_sample.py b/samples/model-builder/get_model_sample.py new file mode 100644 index 0000000000..e5dff928ac --- /dev/null +++ b/samples/model-builder/get_model_sample.py @@ -0,0 +1,31 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from google.cloud import aiplatform + + +# [START aiplatform_sdk_get_model_sample] +def get_model_sample(project: str, location: str, model_name: str): + + aiplatform.init(project=project, location=location) + + model = aiplatform.Model(model_name=model_name) + + print(model.display_name) + print(model.resource_name) + return model + + +# [END aiplatform_sdk_get_model_sample] diff --git a/samples/model-builder/get_model_test.py b/samples/model-builder/get_model_test.py new file mode 100644 index 0000000000..4bb5f5fddb --- /dev/null +++ b/samples/model-builder/get_model_test.py @@ -0,0 +1,32 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import get_model_sample +import test_constants as constants + + +def test_get_model_sample(mock_sdk_init, mock_init_model): + + get_model_sample.get_model_sample( + project=constants.PROJECT, + location=constants.LOCATION, + model_name=constants.MODEL_NAME, + ) + + mock_sdk_init.assert_called_once_with( + project=constants.PROJECT, location=constants.LOCATION + ) + + mock_init_model.assert_called_once_with(model_name=constants.MODEL_NAME) diff --git a/samples/model-builder/test_constants.py b/samples/model-builder/test_constants.py index aabd4f9637..994a8724ee 100644 --- a/samples/model-builder/test_constants.py +++ b/samples/model-builder/test_constants.py @@ -16,6 +16,7 @@ from uuid import uuid4 from google.auth import credentials +from google.cloud import aiplatform PROJECT = "abc" LOCATION = "us-central1" @@ -97,3 +98,54 @@ MACHINE_TYPE = "n1-standard-4" ACCELERATOR_TYPE = "ACCELERATOR_TYPE_UNSPECIFIED" ACCELERATOR_COUNT = 0 + +# Model constants +MODEL_RESOURCE_NAME = f"{PARENT}/models/1234" +MODEL_ARTIFACT_URI = "gs://bucket3/output-dir/" +SERVING_CONTAINER_IMAGE_URI = "http://gcr.io/test/test:latest" +SERVING_CONTAINER_IMAGE = "gcr.io/test-serving/container:image" +SERVING_CONTAINER_PREDICT_ROUTE = "predict" +SERVING_CONTAINER_HEALTH_ROUTE = "metadata" +DESCRIPTION = "test description" +SERVING_CONTAINER_COMMAND = ["python3", "run_my_model.py"] +SERVING_CONTAINER_ARGS = ["--test", "arg"] +SERVING_CONTAINER_ENVIRONMENT_VARIABLES = { + "learning_rate": 0.01, + "loss_fn": "mse", +} +SERVING_CONTAINER_PORTS = [8888, 10000] +INSTANCE_SCHEMA_URI = "gs://test/schema/instance.yaml" +PARAMETERS_SCHEMA_URI = "gs://test/schema/parameters.yaml" +PREDICTION_SCHEMA_URI = "gs://test/schema/predictions.yaml" + +MODEL_DESCRIPTION = "This is a model" +SERVING_CONTAINER_COMMAND = ["python3", "run_my_model.py"] +SERVING_CONTAINER_ARGS = ["--test", "arg"] +SERVING_CONTAINER_ENVIRONMENT_VARIABLES = { + "learning_rate": 0.01, + "loss_fn": "mse", +} + +SERVING_CONTAINER_PORTS = [8888, 10000] +EXPLANATION_METADATA = aiplatform.explain.ExplanationMetadata( + inputs={ + "features": { + "input_tensor_name": "dense_input", + "encoding": "BAG_OF_FEATURES", + "modality": "numeric", + "index_feature_mapping": ["abc", "def", "ghj"], + } + }, + outputs={"medv": {"output_tensor_name": "dense_2"}}, +) +EXPLANATION_PARAMETERS = aiplatform.explain.ExplanationParameters( + {"sampled_shapley_attribution": {"path_count": 10}} +) + +# Endpoint constants +DEPLOYED_MODEL_DISPLAY_NAME = "model_name" +TRAFFIC_PERCENTAGE = 80 +TRAFFIC_SPLIT = {"a": 99, "b": 1} +MIN_REPLICA_COUNT = 1 +MAX_REPLICA_COUNT = 1 +ENDPOINT_DEPLOY_METADATA = () diff --git a/samples/model-builder/upload_model_sample.py b/samples/model-builder/upload_model_sample.py new file mode 100644 index 0000000000..05cb910b12 --- /dev/null +++ b/samples/model-builder/upload_model_sample.py @@ -0,0 +1,71 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict, Optional, Sequence + +from google.cloud import aiplatform +from google.cloud.aiplatform import explain + + +# [START aiplatform_sdk_upload_model_sample] +def upload_model_sample( + project: str, + location: str, + display_name: str, + serving_container_image_uri: str, + artifact_uri: Optional[str] = None, + serving_container_predict_route: Optional[str] = None, + serving_container_health_route: Optional[str] = None, + description: Optional[str] = None, + serving_container_command: Optional[Sequence[str]] = None, + serving_container_args: Optional[Sequence[str]] = None, + serving_container_environment_variables: Optional[Dict[str, str]] = None, + serving_container_ports: Optional[Sequence[int]] = None, + instance_schema_uri: Optional[str] = None, + parameters_schema_uri: Optional[str] = None, + prediction_schema_uri: Optional[str] = None, + explanation_metadata: Optional[explain.ExplanationMetadata] = None, + explanation_parameters: Optional[explain.ExplanationParameters] = None, + sync: bool = True, +): + + aiplatform.init(project=project, location=location) + + model = aiplatform.Model.upload( + display_name=display_name, + artifact_uri=artifact_uri, + serving_container_image_uri=serving_container_image_uri, + serving_container_predict_route=serving_container_predict_route, + serving_container_health_route=serving_container_health_route, + instance_schema_uri=instance_schema_uri, + parameters_schema_uri=parameters_schema_uri, + prediction_schema_uri=prediction_schema_uri, + description=description, + serving_container_command=serving_container_command, + serving_container_args=serving_container_args, + serving_container_environment_variables=serving_container_environment_variables, + serving_container_ports=serving_container_ports, + explanation_metadata=explanation_metadata, + explanation_parameters=explanation_parameters, + sync=sync, + ) + + model.wait() + + print(model.display_name) + print(model.resource_name) + return model + + +# [END aiplatform_sdk_upload_model_sample] diff --git a/samples/model-builder/upload_model_test.py b/samples/model-builder/upload_model_test.py new file mode 100644 index 0000000000..ea00051c1a --- /dev/null +++ b/samples/model-builder/upload_model_test.py @@ -0,0 +1,62 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import test_constants as constants +import upload_model_sample + + +def test_upload_model_sample(mock_sdk_init, mock_upload_model): + + upload_model_sample.upload_model_sample( + project=constants.PROJECT, + location=constants.LOCATION, + display_name=constants.MODEL_NAME, + artifact_uri=constants.MODEL_ARTIFACT_URI, + serving_container_image_uri=constants.SERVING_CONTAINER_IMAGE_URI, + serving_container_predict_route=constants.SERVING_CONTAINER_PREDICT_ROUTE, + serving_container_health_route=constants.SERVING_CONTAINER_HEALTH_ROUTE, + instance_schema_uri=constants.INSTANCE_SCHEMA_URI, + parameters_schema_uri=constants.PARAMETERS_SCHEMA_URI, + prediction_schema_uri=constants.PREDICTION_SCHEMA_URI, + description=constants.MODEL_DESCRIPTION, + serving_container_command=constants.SERVING_CONTAINER_COMMAND, + serving_container_args=constants.SERVING_CONTAINER_ARGS, + serving_container_environment_variables=constants.SERVING_CONTAINER_ENVIRONMENT_VARIABLES, + serving_container_ports=constants.SERVING_CONTAINER_PORTS, + explanation_metadata=constants.EXPLANATION_METADATA, + explanation_parameters=constants.EXPLANATION_PARAMETERS, + ) + + mock_sdk_init.assert_called_once_with( + project=constants.PROJECT, location=constants.LOCATION + ) + + mock_upload_model.assert_called_once_with( + display_name=constants.MODEL_NAME, + artifact_uri=constants.MODEL_ARTIFACT_URI, + serving_container_image_uri=constants.SERVING_CONTAINER_IMAGE_URI, + serving_container_predict_route=constants.SERVING_CONTAINER_PREDICT_ROUTE, + serving_container_health_route=constants.SERVING_CONTAINER_HEALTH_ROUTE, + instance_schema_uri=constants.INSTANCE_SCHEMA_URI, + parameters_schema_uri=constants.PARAMETERS_SCHEMA_URI, + prediction_schema_uri=constants.PREDICTION_SCHEMA_URI, + description=constants.MODEL_DESCRIPTION, + serving_container_command=constants.SERVING_CONTAINER_COMMAND, + serving_container_args=constants.SERVING_CONTAINER_ARGS, + serving_container_environment_variables=constants.SERVING_CONTAINER_ENVIRONMENT_VARIABLES, + serving_container_ports=constants.SERVING_CONTAINER_PORTS, + explanation_metadata=constants.EXPLANATION_METADATA, + explanation_parameters=constants.EXPLANATION_PARAMETERS, + sync=True, + )