From 6a00ed79252c97f3e5f5eef5492d5d7ef12c969c Mon Sep 17 00:00:00 2001 From: A Vertex SDK engineer Date: Tue, 12 Dec 2023 15:04:02 -0800 Subject: [PATCH 1/9] feat: adding `serving_container_grpc_ports` parameter to Model.upload() method PiperOrigin-RevId: 590350209 --- google/cloud/aiplatform/models.py | 16 ++++++++++++ .../aiplatform/prediction/local_model.py | 16 ++++++++++++ tests/unit/aiplatform/test_models.py | 8 ++++++ tests/unit/aiplatform/test_prediction.py | 25 +++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/google/cloud/aiplatform/models.py b/google/cloud/aiplatform/models.py index 7a3002faa4..1dac4a118d 100644 --- a/google/cloud/aiplatform/models.py +++ b/google/cloud/aiplatform/models.py @@ -2977,6 +2977,7 @@ def upload( serving_container_args: Optional[Sequence[str]] = None, serving_container_environment_variables: Optional[Dict[str, str]] = None, serving_container_ports: Optional[Sequence[int]] = None, + serving_container_grpc_ports: Optional[Sequence[int]] = None, local_model: Optional["LocalModel"] = None, instance_schema_uri: Optional[str] = None, parameters_schema_uri: Optional[str] = None, @@ -3083,6 +3084,14 @@ def upload( no impact on whether the port is actually exposed, any port listening on the default "0.0.0.0" address inside a container will be accessible from the network. + serving_container_grpc_ports: Optional[Sequence[int]]=None, + Declaration of ports that are exposed by the container. Vertex AI sends gRPC + prediction requests that it receives to the first port on this list. Vertex + AI also sends liveness and health checks to this port. + If you do not specify this field, gRPC requests to the container will be + disabled. + Vertex AI does not use ports other than the first one listed. This field + corresponds to the `ports` field of the Kubernetes Containers v1 core API. local_model (Optional[LocalModel]): Optional. A LocalModel instance that includes a `serving_container_spec`. If provided, the `serving_container_spec` of the LocalModel instance @@ -3238,6 +3247,7 @@ def upload( env = None ports = None + grpc_ports = None deployment_timeout = ( duration_pb2.Duration(seconds=serving_container_deployment_timeout) if serving_container_deployment_timeout @@ -3256,6 +3266,11 @@ def upload( gca_model_compat.Port(container_port=port) for port in serving_container_ports ] + if serving_container_grpc_ports: + grpc_ports = [ + gca_model_compat.Port(container_port=port) + for port in serving_container_grpc_ports + ] if ( serving_container_startup_probe_exec or serving_container_startup_probe_period_seconds @@ -3293,6 +3308,7 @@ def upload( args=serving_container_args, env=env, ports=ports, + grpc_ports=grpc_ports, predict_route=serving_container_predict_route, health_route=serving_container_health_route, deployment_timeout=deployment_timeout, diff --git a/google/cloud/aiplatform/prediction/local_model.py b/google/cloud/aiplatform/prediction/local_model.py index 20b52527e3..313c68c58b 100644 --- a/google/cloud/aiplatform/prediction/local_model.py +++ b/google/cloud/aiplatform/prediction/local_model.py @@ -60,6 +60,7 @@ def __init__( serving_container_args: Optional[Sequence[str]] = None, serving_container_environment_variables: Optional[Dict[str, str]] = None, serving_container_ports: Optional[Sequence[int]] = None, + serving_container_grpc_ports: Optional[Sequence[int]] = None, serving_container_deployment_timeout: Optional[int] = None, serving_container_shared_memory_size_mb: Optional[int] = None, serving_container_startup_probe_exec: Optional[Sequence[str]] = None, @@ -110,6 +111,14 @@ def __init__( no impact on whether the port is actually exposed, any port listening on the default "0.0.0.0" address inside a container will be accessible from the network. + serving_container_grpc_ports: Optional[Sequence[int]]=None, + Declaration of ports that are exposed by the container. Vertex AI sends gRPC + prediction requests that it receives to the first port on this list. Vertex + AI also sends liveness and health checks to this port. + If you do not specify this field, gRPC requests to the container will be + disabled. + Vertex AI does not use ports other than the first one listed. This field + corresponds to the `ports` field of the Kubernetes Containers v1 core API. serving_container_deployment_timeout (int): Optional. Deployment timeout in seconds. serving_container_shared_memory_size_mb (int): @@ -156,6 +165,7 @@ def __init__( env = None ports = None + grpc_ports = None deployment_timeout = ( duration_pb2.Duration(seconds=serving_container_deployment_timeout) if serving_container_deployment_timeout @@ -174,6 +184,11 @@ def __init__( gca_model_compat.Port(container_port=port) for port in serving_container_ports ] + if serving_container_grpc_ports: + grpc_ports = [ + gca_model_compat.Port(container_port=port) + for port in serving_container_grpc_ports + ] if ( serving_container_startup_probe_exec or serving_container_startup_probe_period_seconds @@ -211,6 +226,7 @@ def __init__( args=serving_container_args, env=env, ports=ports, + grpc_ports=grpc_ports, predict_route=serving_container_predict_route, health_route=serving_container_health_route, deployment_timeout=deployment_timeout, diff --git a/tests/unit/aiplatform/test_models.py b/tests/unit/aiplatform/test_models.py index af771962af..3031562eb4 100644 --- a/tests/unit/aiplatform/test_models.py +++ b/tests/unit/aiplatform/test_models.py @@ -113,6 +113,7 @@ "loss_fn": "mse", } _TEST_SERVING_CONTAINER_PORTS = [8888, 10000] +_TEST_SERVING_CONTAINER_GRPC_PORTS = [7777, 7000] _TEST_SERVING_CONTAINER_DEPLOYMENT_TIMEOUT = 100 _TEST_SERVING_CONTAINER_SHARED_MEMORY_SIZE_MB = 1000 _TEST_SERVING_CONTAINER_STARTUP_PROBE_EXEC = ["a", "b"] @@ -1606,6 +1607,7 @@ def test_upload_uploads_and_gets_model_with_all_args( serving_container_args=_TEST_SERVING_CONTAINER_ARGS, serving_container_environment_variables=_TEST_SERVING_CONTAINER_ENVIRONMENT_VARIABLES, serving_container_ports=_TEST_SERVING_CONTAINER_PORTS, + serving_container_grpc_ports=_TEST_SERVING_CONTAINER_GRPC_PORTS, explanation_metadata=_TEST_EXPLANATION_METADATA, explanation_parameters=_TEST_EXPLANATION_PARAMETERS, labels=_TEST_LABEL, @@ -1634,6 +1636,11 @@ def test_upload_uploads_and_gets_model_with_all_args( for port in _TEST_SERVING_CONTAINER_PORTS ] + grpc_ports = [ + gca_model.Port(container_port=port) + for port in _TEST_SERVING_CONTAINER_GRPC_PORTS + ] + deployment_timeout = duration_pb2.Duration( seconds=_TEST_SERVING_CONTAINER_DEPLOYMENT_TIMEOUT ) @@ -1662,6 +1669,7 @@ def test_upload_uploads_and_gets_model_with_all_args( args=_TEST_SERVING_CONTAINER_ARGS, env=env, ports=ports, + grpc_ports=grpc_ports, deployment_timeout=deployment_timeout, shared_memory_size_mb=_TEST_SERVING_CONTAINER_SHARED_MEMORY_SIZE_MB, startup_probe=startup_probe, diff --git a/tests/unit/aiplatform/test_prediction.py b/tests/unit/aiplatform/test_prediction.py index 9f27c81cb8..1cb6ca5875 100644 --- a/tests/unit/aiplatform/test_prediction.py +++ b/tests/unit/aiplatform/test_prediction.py @@ -111,6 +111,7 @@ "loss_fn": "mse", } _TEST_SERVING_CONTAINER_PORTS = [8888, 10000] +_TEST_SERVING_CONTAINER_GRPC_PORTS = [7777, 7000] _TEST_ID = "1028944691210842416" _TEST_LABEL = {"team": "experimentation", "trial_id": "x435"} _TEST_APPENDED_USER_AGENT = ["fake_user_agent"] @@ -1112,6 +1113,10 @@ def test_init_with_serving_container_spec(self): gca_model_compat.Port(container_port=port) for port in _TEST_SERVING_CONTAINER_PORTS ] + grpc_ports = [ + gca_model_compat.Port(container_port=port) + for port in _TEST_SERVING_CONTAINER_GRPC_PORTS + ] container_spec = gca_model_compat.ModelContainerSpec( image_uri=_TEST_SERVING_CONTAINER_IMAGE, predict_route=_TEST_SERVING_CONTAINER_PREDICTION_ROUTE, @@ -1120,6 +1125,7 @@ def test_init_with_serving_container_spec(self): args=_TEST_SERVING_CONTAINER_ARGS, env=env, ports=ports, + grpc_ports=grpc_ports, ) local_model = LocalModel( @@ -1139,6 +1145,9 @@ def test_init_with_serving_container_spec(self): assert local_model.serving_container_spec.args == container_spec.args assert local_model.serving_container_spec.env == container_spec.env assert local_model.serving_container_spec.ports == container_spec.ports + assert ( + local_model.serving_container_spec.grpc_ports == container_spec.grpc_ports + ) def test_init_with_serving_container_spec_but_not_image_uri_throws_exception(self): env = [ @@ -1149,6 +1158,10 @@ def test_init_with_serving_container_spec_but_not_image_uri_throws_exception(sel gca_model_compat.Port(container_port=port) for port in _TEST_SERVING_CONTAINER_PORTS ] + grpc_ports = [ + gca_model_compat.Port(container_port=port) + for port in _TEST_SERVING_CONTAINER_GRPC_PORTS + ] container_spec = gca_model_compat.ModelContainerSpec( predict_route=_TEST_SERVING_CONTAINER_PREDICTION_ROUTE, health_route=_TEST_SERVING_CONTAINER_HEALTH_ROUTE, @@ -1156,6 +1169,7 @@ def test_init_with_serving_container_spec_but_not_image_uri_throws_exception(sel args=_TEST_SERVING_CONTAINER_ARGS, env=env, ports=ports, + grpc_ports=grpc_ports, ) expected_message = "Image uri is required for the serving container spec to initialize a LocalModel instance." @@ -1175,6 +1189,7 @@ def test_init_with_separate_args(self): serving_container_args=_TEST_SERVING_CONTAINER_ARGS, serving_container_environment_variables=_TEST_SERVING_CONTAINER_ENVIRONMENT_VARIABLES, serving_container_ports=_TEST_SERVING_CONTAINER_PORTS, + serving_container_grpc_ports=_TEST_SERVING_CONTAINER_GRPC_PORTS, ) env = [ @@ -1187,6 +1202,11 @@ def test_init_with_separate_args(self): for port in _TEST_SERVING_CONTAINER_PORTS ] + grpc_ports = [ + gca_model_compat.Port(container_port=port) + for port in _TEST_SERVING_CONTAINER_GRPC_PORTS + ] + container_spec = gca_model_compat.ModelContainerSpec( image_uri=_TEST_SERVING_CONTAINER_IMAGE, predict_route=_TEST_SERVING_CONTAINER_PREDICTION_ROUTE, @@ -1195,6 +1215,7 @@ def test_init_with_separate_args(self): args=_TEST_SERVING_CONTAINER_ARGS, env=env, ports=ports, + grpc_ports=grpc_ports, ) assert local_model.serving_container_spec.image_uri == container_spec.image_uri @@ -1210,6 +1231,9 @@ def test_init_with_separate_args(self): assert local_model.serving_container_spec.args == container_spec.args assert local_model.serving_container_spec.env == container_spec.env assert local_model.serving_container_spec.ports == container_spec.ports + assert ( + local_model.serving_container_spec.grpc_ports == container_spec.grpc_ports + ) def test_init_with_separate_args_but_not_image_uri_throws_exception(self): expected_message = "Serving container image uri is required to initialize a LocalModel instance." @@ -1222,6 +1246,7 @@ def test_init_with_separate_args_but_not_image_uri_throws_exception(self): serving_container_args=_TEST_SERVING_CONTAINER_ARGS, serving_container_environment_variables=_TEST_SERVING_CONTAINER_ENVIRONMENT_VARIABLES, serving_container_ports=_TEST_SERVING_CONTAINER_PORTS, + serving_container_grpc_ports=_TEST_SERVING_CONTAINER_GRPC_PORTS, ) assert str(exception.value) == expected_message From 406595dd78896d3c3fcec8975baccdabef468849 Mon Sep 17 00:00:00 2001 From: A Vertex SDK engineer Date: Tue, 12 Dec 2023 17:55:06 -0800 Subject: [PATCH 2/9] feat: Support CMEK for scheduled pipeline jobs. PiperOrigin-RevId: 590396834 --- .../aiplatform/pipeline_job_schedules.py | 4 + .../aiplatform/test_pipeline_job_schedules.py | 87 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/google/cloud/aiplatform/pipeline_job_schedules.py b/google/cloud/aiplatform/pipeline_job_schedules.py index 00f9b86746..6bbe1f41ca 100644 --- a/google/cloud/aiplatform/pipeline_job_schedules.py +++ b/google/cloud/aiplatform/pipeline_job_schedules.py @@ -106,6 +106,10 @@ def __init__( create_pipeline_job_request["pipeline_job"][ "labels" ] = pipeline_job._gca_resource.labels + if "encryption_spec" in pipeline_job._gca_resource: + create_pipeline_job_request["pipeline_job"][ + "encryption_spec" + ] = pipeline_job._gca_resource.encryption_spec pipeline_job_schedule_args = { "display_name": display_name, "create_pipeline_job_request": create_pipeline_job_request, diff --git a/tests/unit/aiplatform/test_pipeline_job_schedules.py b/tests/unit/aiplatform/test_pipeline_job_schedules.py index 0021ded3ba..c27b7b6086 100644 --- a/tests/unit/aiplatform/test_pipeline_job_schedules.py +++ b/tests/unit/aiplatform/test_pipeline_job_schedules.py @@ -36,6 +36,7 @@ ) from google.cloud.aiplatform.compat.types import ( context as gca_context, + encryption_spec as gca_encryption_spec_compat, pipeline_job as gca_pipeline_job, pipeline_state as gca_pipeline_state, schedule as gca_schedule, @@ -722,6 +723,92 @@ def test_call_schedule_service_create_uses_pipeline_job_labels( timeout=None, ) + @pytest.mark.parametrize( + "job_spec", + [_TEST_PIPELINE_SPEC_JSON, _TEST_PIPELINE_SPEC_YAML, _TEST_PIPELINE_JOB], + ) + def test_call_schedule_service_create_uses_pipeline_job_encryption_spec_key_name( + self, + mock_schedule_service_create, + mock_pipeline_service_list, + mock_schedule_service_get, + mock_schedule_bucket_exists, + job_spec, + mock_load_yaml_and_json, + ): + """Creates a PipelineJobSchedule. + + Tests that PipelineJobs created through PipelineJobSchedule inherit the encryption_spec_key_name of the init PipelineJob. + """ + TEST_PIPELINE_JOB_ENCRYPTION_SPEC_KEY_NAME = "encryption_spec_key_name" + + aiplatform.init( + project=_TEST_PROJECT, + staging_bucket=_TEST_GCS_BUCKET_NAME, + location=_TEST_LOCATION, + credentials=_TEST_CREDENTIALS, + ) + + job = pipeline_jobs.PipelineJob( + display_name=_TEST_PIPELINE_JOB_DISPLAY_NAME, + template_path=_TEST_TEMPLATE_PATH, + parameter_values=_TEST_PIPELINE_PARAMETER_VALUES, + input_artifacts=_TEST_PIPELINE_INPUT_ARTIFACTS, + enable_caching=True, + encryption_spec_key_name=TEST_PIPELINE_JOB_ENCRYPTION_SPEC_KEY_NAME, + ) + + pipeline_job_schedule = pipeline_job_schedules.PipelineJobSchedule( + pipeline_job=job, + display_name=_TEST_PIPELINE_JOB_SCHEDULE_DISPLAY_NAME, + ) + + pipeline_job_schedule.create( + cron=_TEST_PIPELINE_JOB_SCHEDULE_CRON, + max_concurrent_run_count=_TEST_PIPELINE_JOB_SCHEDULE_MAX_CONCURRENT_RUN_COUNT, + max_run_count=_TEST_PIPELINE_JOB_SCHEDULE_MAX_RUN_COUNT, + service_account=_TEST_SERVICE_ACCOUNT, + network=_TEST_NETWORK, + create_request_timeout=None, + ) + + expected_runtime_config_dict = { + "gcsOutputDirectory": _TEST_GCS_BUCKET_NAME, + "parameterValues": _TEST_PIPELINE_PARAMETER_VALUES, + "inputArtifacts": {"vertex_model": {"artifactId": "456"}}, + } + runtime_config = gca_pipeline_job.PipelineJob.RuntimeConfig()._pb + json_format.ParseDict(expected_runtime_config_dict, runtime_config) + + job_spec = yaml.safe_load(job_spec) + pipeline_spec = job_spec.get("pipelineSpec") or job_spec + + # Construct expected request + expected_gapic_pipeline_job_schedule = gca_schedule.Schedule( + display_name=_TEST_PIPELINE_JOB_SCHEDULE_DISPLAY_NAME, + cron=_TEST_PIPELINE_JOB_SCHEDULE_CRON, + max_concurrent_run_count=_TEST_PIPELINE_JOB_SCHEDULE_MAX_CONCURRENT_RUN_COUNT, + max_run_count=_TEST_PIPELINE_JOB_SCHEDULE_MAX_RUN_COUNT, + create_pipeline_job_request={ + "parent": _TEST_PARENT, + "pipeline_job": { + "runtime_config": runtime_config, + "pipeline_spec": dict_to_struct(pipeline_spec), + "encryption_spec": gca_encryption_spec_compat.EncryptionSpec( + kms_key_name=TEST_PIPELINE_JOB_ENCRYPTION_SPEC_KEY_NAME + ), + "service_account": _TEST_SERVICE_ACCOUNT, + "network": _TEST_NETWORK, + }, + }, + ) + + mock_schedule_service_create.assert_called_once_with( + parent=_TEST_PARENT, + schedule=expected_gapic_pipeline_job_schedule, + timeout=None, + ) + @pytest.mark.parametrize( "job_spec", [_TEST_PIPELINE_SPEC_JSON, _TEST_PIPELINE_SPEC_YAML, _TEST_PIPELINE_JOB], From 25e381e9ae8c01b8e25069faaacaa8ac31ed0207 Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Wed, 13 Dec 2023 05:39:09 -0800 Subject: [PATCH 3/9] chore: GenAI - Updated the README PiperOrigin-RevId: 590565781 --- vertexai/generative_models/README.md | 133 +++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/vertexai/generative_models/README.md b/vertexai/generative_models/README.md index afba64b704..3f51b40acb 100644 --- a/vertexai/generative_models/README.md +++ b/vertexai/generative_models/README.md @@ -1,13 +1,132 @@ -# Vertex GenAI Python SDK +# Vertex Generative AI SDK for Python +The Vertex Generative AI SDK helps developers use Google's generative AI +[Gemini models](http://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/overview) +and [PaLM language models](http://cloud.google.com/vertex-ai/docs/generative-ai/language-model-overview) +to build AI-powered features and applications. +The SDKs support use cases like the following: -> [!IMPORTANT] -> Thanks for your interest in the Vertex AI SDKs! **You can start using this SDK and its samples on December 13, 2023.** Until then, check out our [blog post](https://blog.google/technology/ai/google-gemini-ai/) to learn more about Google's Gemini multimodal model. +- Generate text from texts, images and videos (multimodal generation) +- Build stateful multi-turn conversations (chat) +- Function calling -Vertex GenAI Python SDK enables developers to use Google's state-of-the-art generative AI models (like Gemini) to build AI-powered features and applications. +## Installation -*More details and information coming soon!* +To install the +[google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) +Python package, run the following command: + +```shell +pip3 install --upgrade --user google-cloud-aiplatform +``` + +## Usage + +For detailed instructions, see [Introduction to multimodal classes in the Vertex AI SDK](http://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/sdk-for-gemini/gemini-sdk-overview-reference). + +#### Imports: +```python +from vertexai.preview.generative_models import GenerativeModel, Image, Content, Part, Tool, FunctionDeclaration, GenerationConfig +``` + +#### Basic generation: +```python +from vertexai.preview.generative_models import GenerativeModel +model = GenerativeModel("gemini-pro") +print(model.generate_content("Why is sky blue?")) +``` + +#### Using images and videos +```python +from vertexai.preview.generative_models import GenerativeModel, Image +print(vision_model = GenerativeModel("gemini-pro-vision")) + +# Local image +image = Image.load_from_file("image.jpg") +print(vision_model.generate_content(image)) + +# Image from Cloud Storage +image_part = generative_models.Part.from_uri("gs://download.tensorflow.org/example_images/320px-Felis_catus-cat_on_snow.jpg", mime_type="image/jpeg") +print(vision_model.generate_content(image_part)) + +# Text and image +print(vision_model.generate_content(["What is shown in this image?", image])) + +# Text and video +video_part = Part.from_uri("gs://cloud-samples-data/video/animals.mp4", mime_type="video/mp4") +print(vision_model.generate_content(["What is in the video? ", video_part])) +``` + +#### Chat +``` +from vertexai.preview.generative_models import GenerativeModel, Image +vision_model = GenerativeModel("gemini-ultra-vision") +vision_chat = vision_model.start_chat() +image = Image.load_from_file("image.jpg") +print(vision_chat.send_message(["I like this image.", image])) +print(vision_chat.send_message("What things do I like?.")) +``` + +#### Function calling + +``` +# First, create tools that the model is can use to answer your questions. +# Describe a function by specifying it's schema (JsonSchema format) +get_current_weather_func = generative_models.FunctionDeclaration( + name="get_current_weather", + description="Get the current weather in a given location", + parameters={ + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": [ + "celsius", + "fahrenheit", + ] + } + }, + "required": [ + "location" + ] + }, +) +# Tool is a collection of related functions +weather_tool = generative_models.Tool( + function_declarations=[get_current_weather_func], +) + +# Use tools in chat: +model = GenerativeModel( + "gemini-pro", + # You can specify tools when creating a model to avoid having to send them with every request. + tools=[weather_tool], +) +chat = model.start_chat() +# Send a message to the model. The model will respond with a function call. +print(chat.send_message("What is the weather like in Boston?")) +# Then send a function response to the model. The model will use it to answer. +print(chat.send_message( + Part.from_function_response( + name="get_current_weather", + response={ + "content": {"weather": "super nice"}, + } + ), +)) +``` + +## Documentation + +You can find complete documentation for the Vertex AI SDKs and the Gemini model in the Google Cloud [documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/overview) + +## Contributing + +See [Contributing](https://github.com/googleapis/python-aiplatform/blob/main/CONTRIBUTING.rst) for more information on contributing to the Vertex AI Python SDK. ## License -The contents of this repository are licensed under the -[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +The contents of this repository are licensed under the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0). From cfc5cba01536d6364609f5757fcfa4e3ee5383a2 Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Wed, 13 Dec 2023 05:58:17 -0800 Subject: [PATCH 4/9] chore: Skipping the flaky `TestPipelineBasedService.test_create_and_submit_pipeline_job` test PiperOrigin-RevId: 590569706 --- tests/unit/aiplatform/test_pipeline_based_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/aiplatform/test_pipeline_based_service.py b/tests/unit/aiplatform/test_pipeline_based_service.py index f751671462..8969686be1 100644 --- a/tests/unit/aiplatform/test_pipeline_based_service.py +++ b/tests/unit/aiplatform/test_pipeline_based_service.py @@ -517,6 +517,7 @@ def test_init_pipeline_based_service_with_invalid_pipeline_run_id_raises( pipeline_job_name=_TEST_INVALID_PIPELINE_JOB_NAME, ) + @pytest.mark.skip("Flaky") @pytest.mark.parametrize( "job_spec_json", [_TEST_PIPELINE_JOB], From 28925e9464254e9768ceab845001aa0e3d46bbbf Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Wed, 13 Dec 2023 06:35:04 -0800 Subject: [PATCH 5/9] feat: LLM - Added support for model distillation PiperOrigin-RevId: 590578502 --- tests/unit/aiplatform/test_language_models.py | 234 ++++++++++++++++++ vertexai/language_models/_distillation.py | 111 +++++++++ vertexai/language_models/_language_models.py | 62 +++-- 3 files changed, 386 insertions(+), 21 deletions(-) create mode 100644 vertexai/language_models/_distillation.py diff --git a/tests/unit/aiplatform/test_language_models.py b/tests/unit/aiplatform/test_language_models.py index 3571e2a216..406a4bd730 100644 --- a/tests/unit/aiplatform/test_language_models.py +++ b/tests/unit/aiplatform/test_language_models.py @@ -758,6 +758,124 @@ def reverse_string_2(s):""", "pipelineSpec": json.loads(_TEST_EVAL_PIPELINE_SPEC_JSON), } ) +_TEST_DISTILLATION_PIPELINE_SPEC = { + "components": {}, + "pipelineInfo": { + "description": "Vertex kfp pipeline for distillation.", + "name": "distillation", + }, + "root": { + "dag": {"tasks": {}}, + "inputDefinitions": { + "parameters": { + "accelerator_type": { + "defaultValue": "GPU", + "isOptional": True, + "parameterType": "STRING", + }, + "api_endpoint": { + "defaultValue": "aiplatform.googleapis.com/ui", + "isOptional": True, + "parameterType": "STRING", + }, + "dataset_uri": {"parameterType": "STRING"}, + "enable_checkpoint_selection": { + "defaultValue": "default", + "isOptional": True, + "parameterType": "STRING", + }, + "enable_early_stopping": { + "defaultValue": True, + "isOptional": True, + "parameterType": "BOOLEAN", + }, + "encryption_spec_key_name": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "evaluation_data_uri": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "evaluation_interval": { + "defaultValue": 100, + "isOptional": True, + "parameterType": "NUMBER_INTEGER", + }, + "evaluation_output_root_dir": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "learning_rate_multiplier": { + "defaultValue": 1, + "isOptional": True, + "parameterType": "NUMBER_DOUBLE", + }, + "location": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "max_context_length": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "model_display_name": { + "defaultValue": "distilled-student-model", + "isOptional": True, + "parameterType": "STRING", + }, + "project": {"parameterType": "STRING"}, + "student_model_reference": { + "defaultValue": "text-bison@002", + "isOptional": True, + "parameterType": "STRING", + }, + "teacher_model_reference": { + "defaultValue": "text-unicorn@001", + "isOptional": True, + "parameterType": "STRING", + }, + "temperature": { + "defaultValue": 0, + "isOptional": True, + "parameterType": "NUMBER_DOUBLE", + }, + "tensorboard_resource_id": { + "defaultValue": "", + "isOptional": True, + "parameterType": "STRING", + }, + "tpu_training_skip_cmek": { + "defaultValue": False, + "isOptional": True, + "parameterType": "BOOLEAN", + }, + "train_steps": { + "defaultValue": 300, + "isOptional": True, + "parameterType": "NUMBER_INTEGER", + }, + "version": { + "defaultValue": "latest", + "isOptional": True, + "parameterType": "STRING", + }, + } + }, + }, + "schemaVersion": "2.1.0", + "sdkVersion": "kfp-2.4.0", +} + +_TEST_DISTILLATION_PIPELINE_SPEC_JSON = json.dumps( + _TEST_DISTILLATION_PIPELINE_SPEC, +) + # Eval classification spec @@ -875,6 +993,10 @@ def reverse_string_2(s):""", } ) +_URL_DATA = { + "https://us-kfp.pkg.dev/ml-pipeline/research/distillation/v1.0.0": _TEST_DISTILLATION_PIPELINE_SPEC_JSON, +} + @pytest.fixture def mock_pipeline_bucket_exists(): @@ -1225,6 +1347,19 @@ def mock_request_urlopen_eval_classification( yield request.param, mock_urlopen +@pytest.fixture +def mock_urllib_request_urlopen(request: str) -> Tuple[str, mock.MagicMock]: + url = request.param + data = _URL_DATA[url] + with mock.patch.object(urllib_request, "urlopen") as mock_urlopen: + mock_read_response = mock.MagicMock() + mock_decode_response = mock.MagicMock() + mock_decode_response.return_value = data + mock_read_response.return_value.decode = mock_decode_response + mock_urlopen.return_value.read = mock_read_response + yield url, mock_urlopen + + @pytest.fixture def get_endpoint_mock(): with mock.patch.object( @@ -4251,3 +4386,102 @@ def test_model_evaluation_text_classification_base_model_only_summary_metrics( ) assert eval_metrics.confidenceMetrics is None assert eval_metrics.auPrc == _TEST_TEXT_CLASSIFICATION_METRICS["auPrc"] + + @pytest.mark.parametrize( + "job_spec", + [ + _TEST_DISTILLATION_PIPELINE_SPEC_JSON, + ], + ) + @pytest.mark.parametrize( + "mock_urllib_request_urlopen", + ["https://us-kfp.pkg.dev/ml-pipeline/research/distillation/v1.0.0"], + indirect=True, + ) + def test_text_generation_model_distill_from( + self, + mock_pipeline_service_create, + mock_pipeline_job_get, + mock_pipeline_bucket_exists, + job_spec, + mock_load_yaml_and_json, + mock_gcs_from_string, + mock_gcs_upload, + mock_urllib_request_urlopen, + mock_get_tuned_model, + ): + """Tests distilling the text generation model.""" + aiplatform.init( + project=_TEST_PROJECT, + location=_TEST_LOCATION, + encryption_spec_key_name=_TEST_ENCRYPTION_KEY_NAME, + ) + with mock.patch.object( + target=model_garden_service_client.ModelGardenServiceClient, + attribute="get_publisher_model", + return_value=gca_publisher_model.PublisherModel( + _TEXT_BISON_PUBLISHER_MODEL_DICT + ), + ): + model = preview_language_models.TextGenerationModel.from_pretrained( + "text-bison@001" + ) + + dataset_uri = "gs://bucket/distillation.training_data.jsonl" + evaluation_data_uri = "gs://bucket/eval.jsonl" + evaluation_interval = 37 + enable_early_stopping = True + enable_checkpoint_selection = True + tensorboard_name = ( + f"projects/{_TEST_PROJECT}/locations/{_TEST_LOCATION}/tensorboards/123" + ) + + tuning_job = model.distill_from( + dataset=dataset_uri, + teacher_model="text-unicorn@001", + learning_rate_multiplier=2.0, + train_steps=10, + evaluation_spec=preview_language_models.TuningEvaluationSpec( + evaluation_data=evaluation_data_uri, + evaluation_interval=evaluation_interval, + enable_early_stopping=enable_early_stopping, + enable_checkpoint_selection=enable_checkpoint_selection, + tensorboard=tensorboard_name, + ), + accelerator_type="TPU", + ) + call_kwargs = mock_pipeline_service_create.call_args[1] + pipeline_arguments = call_kwargs[ + "pipeline_job" + ].runtime_config.parameter_values + assert pipeline_arguments["teacher_model_reference"] == "text-unicorn@001" + assert pipeline_arguments["student_model_reference"] == "text-bison@001" + assert pipeline_arguments["dataset_uri"] == dataset_uri + assert pipeline_arguments["project"] == _TEST_PROJECT + assert pipeline_arguments["location"] == _TEST_LOCATION + assert pipeline_arguments["train_steps"] == 10 + assert pipeline_arguments["learning_rate_multiplier"] == 2.0 + assert pipeline_arguments["evaluation_data_uri"] == evaluation_data_uri + assert pipeline_arguments["evaluation_interval"] == evaluation_interval + assert pipeline_arguments["enable_early_stopping"] == enable_early_stopping + assert ( + pipeline_arguments["enable_checkpoint_selection"] + == enable_checkpoint_selection + ) + assert pipeline_arguments["tensorboard_resource_id"] == tensorboard_name + assert pipeline_arguments["accelerator_type"] == "TPU" + assert ( + pipeline_arguments["encryption_spec_key_name"] + == _TEST_ENCRYPTION_KEY_NAME + ) + assert ( + call_kwargs["pipeline_job"].encryption_spec.kms_key_name + == _TEST_ENCRYPTION_KEY_NAME + ) + + # Testing the tuned model + tuned_model = tuning_job.get_tuned_model() + assert ( + tuned_model._endpoint_name + == test_constants.EndpointConstants._TEST_ENDPOINT_NAME + ) diff --git a/vertexai/language_models/_distillation.py b/vertexai/language_models/_distillation.py new file mode 100644 index 0000000000..643ae102f2 --- /dev/null +++ b/vertexai/language_models/_distillation.py @@ -0,0 +1,111 @@ +from typing import Optional, Union + +from google.cloud import aiplatform +from google.cloud.aiplatform import initializer as aiplatform_initializer +from vertexai.language_models import _language_models +from vertexai.language_models import _language_models as tuning + + +class DistillationMixin: + _DISTILLATION_PIPELINE_URI = ( + "https://us-kfp.pkg.dev/ml-pipeline/research/distillation/v1.0.0" + ) + + def distill_from( + self, + *, + dataset: str, + teacher_model: Union[str, _language_models._TextGenerationModel], + train_steps: Optional[int] = None, + learning_rate_multiplier: Optional[float] = None, + evaluation_spec: Optional[tuning.TuningEvaluationSpec] = None, + accelerator_type: Optional[tuning._ACCELERATOR_TYPE_TYPE] = None, + model_display_name: Optional[str] = None, + ): + """Tunes a smaller model with help from another bigger model. + + Args: + dataset: A URI pointing to data in JSON lines format. + teacher_model: The teacher model to use for distillation. + train_steps: Number of training batches to use (batch size is 8 samples). + learning_rate_multiplier: Learning rate multiplier to use in tuning. + evaluation_spec: Specification for the model evaluation during tuning. + accelerator_type: Type of accelerator to use. Can be "TPU" or "GPU". + model_display_name: Custom display name for the tuned model. + + Returns: + A tuning job for distillation. + + Raises: + RuntimeError: If the model does not support distillation. + """ + if "/models/" not in self._endpoint_name: + raise RuntimeError( + f"Model does not support distillation: {self._endpoint_name}" + ) + student_short_model_id = self._endpoint_name.split("/")[-1] + + if isinstance(teacher_model, str): + teacher_short_model_id = teacher_model + elif isinstance(teacher_model, _language_models._LanguageModel): + if "/models/" not in teacher_model._endpoint_name: + raise RuntimeError( + f"Teacher model does not support distillation: {teacher_model._endpoint_name}" + ) + teacher_short_model_id = teacher_model._endpoint_name.split("/")[-1] + else: + raise RuntimeError(f"Unsupported teacher model type: {teacher_model}") + + pipeline_arguments = { + "teacher_model_reference": teacher_short_model_id, + "student_model_reference": student_short_model_id, + "dataset_uri": dataset, + "project": aiplatform_initializer.global_config.project, + "location": aiplatform_initializer.global_config.location, + } + if train_steps is not None: + pipeline_arguments["train_steps"] = train_steps + if learning_rate_multiplier is not None: + pipeline_arguments["learning_rate_multiplier"] = learning_rate_multiplier + if evaluation_spec is not None: + pipeline_arguments["evaluation_data_uri"] = evaluation_spec.evaluation_data + pipeline_arguments[ + "evaluation_interval" + ] = evaluation_spec.evaluation_interval + pipeline_arguments[ + "enable_early_stopping" + ] = evaluation_spec.enable_early_stopping + pipeline_arguments[ + "enable_checkpoint_selection" + ] = evaluation_spec.enable_checkpoint_selection + pipeline_arguments["tensorboard_resource_id"] = evaluation_spec.tensorboard + # pipeline_parameter_values["evaluation_output_root_dir"] = ... + if accelerator_type is not None: + pipeline_arguments["accelerator_type"] = accelerator_type + if aiplatform_initializer.global_config.encryption_spec_key_name is not None: + pipeline_arguments[ + "encryption_spec_key_name" + ] = aiplatform_initializer.global_config.encryption_spec_key_name + if model_display_name is None: + model_display_name = ( + f"{student_short_model_id}" + f" distilled from {teacher_short_model_id}" + ) + pipeline_arguments["model_display_name"] = model_display_name + # # Not exposing these parameters: + # temperature: Optional[float] = None, + # max_context_length: Optional[int] = None, + # tpu_training_skip_cmek: Optional[bool] = None, + # api_endpoint: Optional[str] = None, + # version: Optional[str] = None, + pipeline_job = aiplatform.PipelineJob( + template_path=self._DISTILLATION_PIPELINE_URI, + display_name=None, + parameter_values=pipeline_arguments, + ) + pipeline_job.submit() + tuning_job = tuning._LanguageModelTuningJob( + base_model=self, + job=pipeline_job, + ) + return tuning_job diff --git a/vertexai/language_models/_language_models.py b/vertexai/language_models/_language_models.py index 30f43f86dd..2ae7a29e9c 100644 --- a/vertexai/language_models/_language_models.py +++ b/vertexai/language_models/_language_models.py @@ -1533,20 +1533,6 @@ class TextGenerationModel( __module__ = "vertexai.language_models" -class _PreviewTextGenerationModel( - _TextGenerationModel, - _PreviewTunableTextModelMixin, - _PreviewModelWithBatchPredict, - _evaluatable_language_models._EvaluatableLanguageModel, - _CountTokensMixin, -): - # Do not add docstring so that it's inherited from the base class. - __name__ = "TextGenerationModel" - __module__ = "vertexai.preview.language_models" - - _LAUNCH_STAGE = _model_garden_models._SDK_PUBLIC_PREVIEW_LAUNCH_STAGE - - class _ChatModel(_TextGenerationModel): """ChatModel represents a language model that is capable of chat. @@ -3143,19 +3129,33 @@ def get_tuned_model(self) -> "_LanguageModel": if self._model: return self._model self._job.wait() - root_pipeline_tasks = [ - task_detail - for task_detail in self._job.gca_resource.job_detail.task_details - if task_detail.execution.schema_title == "system.Run" + + # Getting tuned model from the pipeline. + model_task = None + # Searching for the model uploading task first. + # Note: Distillation does not have pipeline outputs yet. + upload_model_tasks = [ + task_info + for task_info in self._job.gca_resource.job_detail.task_details + if task_info.task_name == "upload-llm-model" ] - if len(root_pipeline_tasks) != 1: + if len(upload_model_tasks) == 1: + model_task = upload_model_tasks[0] + if not model_task: + root_pipeline_tasks = [ + task_detail + for task_detail in self._job.gca_resource.job_detail.task_details + if task_detail.execution.schema_title == "system.Run" + ] + if len(root_pipeline_tasks) == 1: + model_task = root_pipeline_tasks[0] + if not model_task: raise RuntimeError( f"Failed to get the model name from the tuning pipeline: {self._job.name}" ) - root_pipeline_task = root_pipeline_tasks[0] # Trying to get model name from output parameter - vertex_model_name = root_pipeline_task.execution.metadata[ + vertex_model_name = model_task.execution.metadata[ "output:model_resource_name" ].strip() _LOGGER.info(f"Tuning has completed. Created Vertex Model: {vertex_model_name}") @@ -3292,3 +3292,23 @@ def _uri_join(uri: str, path_fragment: str) -> str: """ return uri.rstrip("/") + "/" + path_fragment.lstrip("/") + + +# Importing here to prevent issues caused by circular references +# pylint: disable=g-import-not-at-top,g-bad-import-order +from vertexai.language_models import _distillation + + +class _PreviewTextGenerationModel( + _TextGenerationModel, + _PreviewTunableTextModelMixin, + _PreviewModelWithBatchPredict, + _evaluatable_language_models._EvaluatableLanguageModel, + _CountTokensMixin, + _distillation.DistillationMixin, +): + # Do not add docstring so that it's inherited from the base class. + __name__ = "TextGenerationModel" + __module__ = "vertexai.preview.language_models" + + _LAUNCH_STAGE = _model_garden_models._SDK_PUBLIC_PREVIEW_LAUNCH_STAGE From b647ef9f3d470b29c388b283f5864cd1e31f88da Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Wed, 13 Dec 2023 06:42:39 -0800 Subject: [PATCH 6/9] chore: GenAI - Improved error message when accessing `GenerationResponse.text` PiperOrigin-RevId: 590580220 --- vertexai/generative_models/_generative_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vertexai/generative_models/_generative_models.py b/vertexai/generative_models/_generative_models.py index 1a59b3c2bb..e5e79ffbcc 100644 --- a/vertexai/generative_models/_generative_models.py +++ b/vertexai/generative_models/_generative_models.py @@ -1306,7 +1306,7 @@ def candidates(self) -> List["Candidate"]: def text(self) -> str: if len(self.candidates) > 1: raise ValueError("Multiple candidates are not supported") - return self.candidates[0].content.parts[0].text + return self.candidates[0].text class Candidate: From 0dccd83e4d8f5b44a169765f70bc4f96645c0776 Mon Sep 17 00:00:00 2001 From: Alexey Volkov Date: Wed, 13 Dec 2023 07:16:48 -0800 Subject: [PATCH 7/9] chore: GenAI - Validating `history` in the `ChatSession` constructor PiperOrigin-RevId: 590588154 --- vertexai/generative_models/_generative_models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vertexai/generative_models/_generative_models.py b/vertexai/generative_models/_generative_models.py index e5e79ffbcc..52e26a68be 100644 --- a/vertexai/generative_models/_generative_models.py +++ b/vertexai/generative_models/_generative_models.py @@ -655,6 +655,10 @@ def __init__( history: Optional[List["Content"]] = None, raise_on_blocked: bool = True, ): + if history: + if not all(isinstance(item, Content) for item in history): + raise ValueError("history must be a list of Content objects.") + self._model = model self._history = history or [] self._raise_on_blocked = raise_on_blocked From 537d00e185df593f6c718859cbc92f8dfef67512 Mon Sep 17 00:00:00 2001 From: Sasha Sobran Date: Wed, 13 Dec 2023 13:32:27 -0800 Subject: [PATCH 8/9] chore: release 1.38.1 Release-As: 1.38.1 PiperOrigin-RevId: 590697558 --- google/cloud/aiplatform/releases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/cloud/aiplatform/releases.txt b/google/cloud/aiplatform/releases.txt index 74a8fb5d84..f4db9d1034 100644 --- a/google/cloud/aiplatform/releases.txt +++ b/google/cloud/aiplatform/releases.txt @@ -1,4 +1,4 @@ Use this file when you need to force a patch release with release-please. Edit line 4 below with the version for the release. -1.36.4 \ No newline at end of file +1.38.1 \ No newline at end of file From 014fe0b0af33c0beed392f9eaeccf661222a709a Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:16:11 -0500 Subject: [PATCH 9/9] chore(main): release 1.38.1 (#3089) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ google/cloud/aiplatform/gapic_version.py | 2 +- .../v1/schema/predict/instance/gapic_version.py | 2 +- .../v1/schema/predict/instance_v1/gapic_version.py | 2 +- .../v1/schema/predict/params/gapic_version.py | 2 +- .../v1/schema/predict/params_v1/gapic_version.py | 2 +- .../v1/schema/predict/prediction/gapic_version.py | 2 +- .../schema/predict/prediction_v1/gapic_version.py | 2 +- .../schema/trainingjob/definition/gapic_version.py | 2 +- .../trainingjob/definition_v1/gapic_version.py | 2 +- .../schema/predict/instance/gapic_version.py | 2 +- .../predict/instance_v1beta1/gapic_version.py | 2 +- .../v1beta1/schema/predict/params/gapic_version.py | 2 +- .../schema/predict/params_v1beta1/gapic_version.py | 2 +- .../schema/predict/prediction/gapic_version.py | 2 +- .../predict/prediction_v1beta1/gapic_version.py | 2 +- .../schema/trainingjob/definition/gapic_version.py | 2 +- .../definition_v1beta1/gapic_version.py | 2 +- google/cloud/aiplatform/version.py | 2 +- google/cloud/aiplatform_v1/gapic_version.py | 2 +- google/cloud/aiplatform_v1beta1/gapic_version.py | 2 +- ...nippet_metadata_google.cloud.aiplatform.v1.json | 2 +- ...t_metadata_google.cloud.aiplatform.v1beta1.json | 2 +- 24 files changed, 37 insertions(+), 23 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2b02a05903..7b4c7c907d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.38.0" + ".": "1.38.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca1b68f58..b1eb45c6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.38.1](https://github.com/googleapis/python-aiplatform/compare/v1.38.0...v1.38.1) (2023-12-13) + + +### Features + +* Adding `serving_container_grpc_ports` parameter to Model.upload() method ([6a00ed7](https://github.com/googleapis/python-aiplatform/commit/6a00ed79252c97f3e5f5eef5492d5d7ef12c969c)) +* LLM - Added support for model distillation ([28925e9](https://github.com/googleapis/python-aiplatform/commit/28925e9464254e9768ceab845001aa0e3d46bbbf)) +* Support CMEK for scheduled pipeline jobs. ([406595d](https://github.com/googleapis/python-aiplatform/commit/406595dd78896d3c3fcec8975baccdabef468849)) + + +### Miscellaneous Chores + +* Release 1.38.1 ([537d00e](https://github.com/googleapis/python-aiplatform/commit/537d00e185df593f6c718859cbc92f8dfef67512)) + ## [1.38.0](https://github.com/googleapis/python-aiplatform/compare/v1.37.0...v1.38.0) (2023-12-11) diff --git a/google/cloud/aiplatform/gapic_version.py b/google/cloud/aiplatform/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/gapic_version.py +++ b/google/cloud/aiplatform/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/instance/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/instance_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/params/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/params_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/prediction/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/predict/prediction_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py b/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/trainingjob/definition/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py b/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py +++ b/google/cloud/aiplatform/v1/schema/trainingjob/definition_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/instance/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/instance_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/params/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/params_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/prediction/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/predict/prediction_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform/v1beta1/schema/trainingjob/definition_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform/version.py b/google/cloud/aiplatform/version.py index b40cf2958f..6ef544bacc 100644 --- a/google/cloud/aiplatform/version.py +++ b/google/cloud/aiplatform/version.py @@ -15,4 +15,4 @@ # limitations under the License. # -__version__ = "1.38.0" +__version__ = "1.38.1" diff --git a/google/cloud/aiplatform_v1/gapic_version.py b/google/cloud/aiplatform_v1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform_v1/gapic_version.py +++ b/google/cloud/aiplatform_v1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/google/cloud/aiplatform_v1beta1/gapic_version.py b/google/cloud/aiplatform_v1beta1/gapic_version.py index d350308c03..5095c854d8 100644 --- a/google/cloud/aiplatform_v1beta1/gapic_version.py +++ b/google/cloud/aiplatform_v1beta1/gapic_version.py @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -__version__ = "1.38.0" # {x-release-please-version} +__version__ = "1.38.1" # {x-release-please-version} diff --git a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json index 95ba44f359..e018f76f55 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-aiplatform", - "version": "1.38.0" + "version": "1.38.1" }, "snippets": [ { diff --git a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json index 112ed821eb..318eac2542 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.aiplatform.v1beta1.json @@ -8,7 +8,7 @@ ], "language": "PYTHON", "name": "google-cloud-aiplatform", - "version": "1.38.0" + "version": "1.38.1" }, "snippets": [ {