diff --git a/hacks/genai-intro/artifacts/function/main.py b/hacks/genai-intro/artifacts/function/main.py index 4f8198f3..9b5bce3d 100644 --- a/hacks/genai-intro/artifacts/function/main.py +++ b/hacks/genai-intro/artifacts/function/main.py @@ -19,14 +19,12 @@ from typing import Iterator import functions_framework -import vertexai +from google import genai from google.cloud import bigquery from google.cloud import storage from google.cloud import vision -from vertexai.generative_models import GenerativeModel - PROJECT_ID=os.getenv("GCP_PROJECT_ID") REGION=os.getenv("GCP_REGION") @@ -35,9 +33,9 @@ BQ_DATASET="articles" BQ_TABLE="summaries" -MODEL_NAME="gemini-2.0-flash" +MODEL_NAME="gemini-2.5-flash" -vertexai.init(project=PROJECT_ID, location=REGION) +genai_client = genai.Client(vertexai=True, project=PROJECT_ID, location=REGION) def extract_text_from_document(src_bucket: str, file_name: str, dst_bucket: str) -> str: @@ -137,17 +135,16 @@ def extract_title_from_text(text: str) -> str: Returns: title of the PDF document """ - model = GenerativeModel(MODEL_NAME) prompt_template = get_prompt_for_title_extraction() prompt = prompt_template.format() # TODO Challenge 2, set placeholder values in format if not prompt: return "" # return empty title for empty prompt - if model.count_tokens(prompt).total_tokens > 2500: + if genai_client.models.count_tokens(model=MODEL_NAME, contents=prompt).total_tokens > 2500: raise ValueError("Too many tokens used") - response = model.generate_content(prompt) + response = genai_client.models.generate_content(model=MODEL_NAME, contents=prompt) return response.text @@ -192,7 +189,6 @@ def extract_summary_from_text(text: str) -> str: Returns: summary of the PDF document """ - model = GenerativeModel(MODEL_NAME) rolling_prompt_template = get_prompt_for_page_summary_with_context() if not rolling_prompt_template: @@ -201,7 +197,7 @@ def extract_summary_from_text(text: str) -> str: summary = "" for page in pages(text, 16000): prompt = rolling_prompt_template.format() # TODO Challenge 3, set placeholder values in format - summary = model.generate_content(prompt).text + summary = genai_client.models.generate_content(model=MODEL_NAME, contents=prompt).text return summary diff --git a/hacks/genai-intro/artifacts/function/requirements.txt b/hacks/genai-intro/artifacts/function/requirements.txt index 918cbc51..2f9ad740 100644 --- a/hacks/genai-intro/artifacts/function/requirements.txt +++ b/hacks/genai-intro/artifacts/function/requirements.txt @@ -1,5 +1,5 @@ functions-framework==3.* -google-cloud-aiplatform==1.74.0 +google-genai==1.75.0 google-cloud-storage==2.19.0 google-cloud-vision==3.8.1 google-cloud-bigquery==3.27.0 \ No newline at end of file diff --git a/hacks/genai-intro/images/genai-intro-arch.png b/hacks/genai-intro/images/genai-intro-arch.png index 2664c5d2..3197704e 100644 Binary files a/hacks/genai-intro/images/genai-intro-arch.png and b/hacks/genai-intro/images/genai-intro-arch.png differ diff --git a/hacks/mlops-on-gcp/README.md b/hacks/mlops-on-gcp/README.md index 93f768ff..f724ec93 100644 --- a/hacks/mlops-on-gcp/README.md +++ b/hacks/mlops-on-gcp/README.md @@ -76,14 +76,14 @@ As depicted in the overview diagram, the first step of any ML project is data an ### Description -Create a *Workbench Instance*. Pick a region close to you and choose the **single user only** option. +Create a *Workbench Instance*. Pick a region close to you and choose the **single user only** option for *IAM and security*. It's a good practice to have isolated virtual environments for experiments, so create a new virtual environment and install that as a kernel. See this [gist](https://gist.github.com/meken/e6c7430997de9b3f2cf7721f8ecffc04) for the instructions. > [!WARNING] > Not using a dedicated and isolated environment/kernel might cause dependency conflicts as Workbench Instances come pre-installed with some versions of the required libraries. -We've prepared a [sample project on Github](https://github.com/meken/gcp-mlops-demo/archive/refs/heads/main.zip), navigate there and download the project as a **zip** file and extract the contents of the zip file onto your Notebook instance. Open the notebook `01-tip-toe-vertex-ai.ipynb`, make sure that you've selected the newly created kernel. You should now be able to run the first notebook and get familiar with some of the Agent Platform concepts. +We've prepared a [sample project on Github](https://github.com/meken/gcp-mlops-demo/archive/refs/heads/main.zip), navigate there and download the project as a **zip** file and extract the contents of the zip file onto your Notebook instance. Open the notebook `01-getting-started.ipynb`, make sure that you've selected the newly created kernel. You should now be able to run the first notebook and get familiar with some of the Agent Platform concepts. > [!NOTE] > As we're installing packages in the first cell of the sample notebook and restarting the kernel in one of the following cells, *Run All* will not work. Run the cells one-by-one also to understand what's going on in every cell. @@ -91,10 +91,10 @@ We've prepared a [sample project on Github](https://github.com/meken/gcp-mlops-d ### Success Criteria 1. There's a new single-user Workbench Instance. -2. The sample notebook `01-tip-toe-vertex-ai.ipynb` is successfully run (using the newly generated kernel) and a model file is generated/stored in Google Cloud Storage. +2. The sample notebook `01-getting-started.ipynb` is successfully run (using the newly generated kernel) and a model file is generated/stored in Google Cloud Storage. 3. No code was modified. -### Tips +### Tips - Some of the required settings can be found in the *Advanced Settings* section when you're creating a new Workbench Instance. - If there's nothing mentioned in the instructions about a parameter, stick to the defaults (this applies to all of the challenges). @@ -147,7 +147,7 @@ This task is all about automating things using Cloud Build. When multiple people ### Description -Once things look fine locally, set up a *Cloud Build Trigger* that's run when code is *pushed* to the repository. The code base already includes a build configuration (`cloudbuild.yaml`), have a look at it to understand what it does. Make sure that the trigger uses that build configuration and the `Compute Engine Default Service Account` as the service account. Name the trigger `CI` (or `continuous-integration`). +Once things look fine locally, set up a *Cloud Build Trigger* that's run when code is *pushed* to the repository. The code base already includes a build configuration (`cloudbuild.yaml`), have a look at it to understand what it does. Make sure that the trigger uses that build configuration and the `sa-mlops-build` (MLOps Build Service Account) as the service account. Name the trigger `CI` (or `continuous-integration`). > [!IMPORTANT] > The qwiklabs environment only has quota in the *global* region, make sure that you pick that when you're creating the trigger. @@ -177,7 +177,7 @@ The previous challenge introduced the concept of build pipelines. But there are If you've successfully completed the previous challenge, your training code has been packaged and can be run from a Agent Platform Pipeline. -The provided project has a `pipeline.py` file that can generate a pipeline definition. Run that to generate a pipeline definition file in `YML` format. Use the generated pipeline definition file to create a new *Pipeline Run* through the GCP Console. Fill in the required pipeline parameters in the next step (you can look up the Python package location). Do not set/override the `endpoint` and `monitoring_job` parameters (keep the default values). +The provided project has a `pipeline.py` file that can generate a pipeline definition. Run that to generate a pipeline definition file in `YML` format. Use the generated pipeline definition file to create a new *Pipeline Run* through the GCP Console. Make sure to use the service account `sa-mlops-kfp` (Kubeflow Pipelines Service Account). In the *Runtime configuration* step fill in the required pipeline parameters. Do not set/override the `endpoint` and `monitoring_job` parameters (keep the default values). > [!NOTE] > Once the pipeline is triggered, it will take ~10 minutes to complete. @@ -192,6 +192,7 @@ The provided project has a `pipeline.py` file that can generate a pipeline defin - Read the `pipeline.py` to understand what it does. - Note that the `pipeline.py` can generate `JSON` and `YML` pipeline definition files based on the extension of the output file name. - You can either upload the pipeline definition from a local machine, or put it on GCS and refer to its location. +- The service account can be configured in the *Run details* phase when you expand the *Advanced options*. - You have already created a bucket, you can use that as the pipeline root (optionally add `pipelines` folder in it). - For the parameter *location* look up the *region* of the storage bucket created in the first challenge. - And for the *python_pkg* parameter check the Cloud Build pipeline to find out where the created Python package is stored and browse to that location to get the name of the package. @@ -221,9 +222,9 @@ From this challenge onwards you'll have the option to either do online inferenci ### Description -So, you've chosen for online inferencing. In order to use the model to serve predictions in an online fashion it has to be deployed to an endpoint. Luckily Agent Platform provides exactly what we need, a managed service for serving predictions, called Online Prediction. +So, you've chosen for online inferencing. In order to use the model to serve predictions in an online fashion it has to be deployed to an endpoint. Luckily Agent Platform provides exactly what we need, a managed service for serving predictions, called *Endpoints*. -Create a new Agent Platform Endpoint and deploy the freshly trained model. Use the smallest machine type but make sure that it can scale to more than 1 node by configuring *autoscaling*. +Create a new Agent Platform Endpoint and deploy the freshly trained model. Use the smallest machine type but make sure that it can scale to more than 1 node by configuring *autoscaling*. Stick to the defaults for everything else. > [!NOTE] > The deployment of the model will take ~10 minutes to complete. @@ -239,6 +240,7 @@ Create a new Agent Platform Endpoint and deploy the freshly trained model. Use t ### Tips +- Read the requirements for *Autoscaling* before you complete your configuration. - Verify first that you're getting predictions from the endpoint before generating load (for example using cURL) - In order to generate load you can use any tool you want, but we recommend [oha](https://github.com/hatoo/oha) on Cloud Shell or your notebook environment. You can download the latest version from [here](https://github.com/hatoo/oha/releases) (you'll need the `oha-linux-amd64` version for Cloud Shell or your notebook environment). @@ -251,7 +253,7 @@ Create a new Agent Platform Endpoint and deploy the freshly trained model. Use t ### Description -So, you've chosen for the batch inferencing path. We're going to use Agent Platform Batch Predictions to get predictions for data in a BigQuery table. First, go ahead and create a new table with at most 10K rows that's going to be used for generating the predictions. Once the table is created, create a new Batch Prediction job with that table as the input and another BigQuery table as the output, using the previously created model. Choose a small machine type and 2 compute nodes. Don't turn on Model Monitoring yet as that's for the next challenge. +So, you've chosen for the batch inferencing path. We're going to use Agent Platform Batch Inference to get predictions for data in a BigQuery table. First, go ahead and create a new table with at most 10K rows that's going to be used for generating the predictions. Once the table is created, create a new Batch Inference job with that table as the input and a new BigQuery table as the output, using the previously created model. Choose a small machine type and 2 compute nodes. Don't turn on Model Monitoring yet as that's for the next challenge. > [!NOTE] > The batch inferencing will take roughly ~10 minutes, most of that is the overhead of starting the cluster, so increasing the number of instances won't help with the small table we're using. @@ -259,13 +261,14 @@ So, you've chosen for the batch inferencing path. We're going to use Agent Platf ### Success Criteria 1. There's a properly structured input table in BigQuery with 10K rows. -2. There's a succesful Batch Prediction job. +2. There's a succesful Batch Inference job. 3. There are predictions in a new BigQuery table. 4. No code was modified. ### Tips - The pipeline that we've used in the previous challenge contains a task to prepare the data using BigQuery, have a look at that for inspiration. +- As output you can also choose a BigQuery dataset, in which case a new output table with the right schema will be created for you automatically. - Make sure that the input table has the exact same number of input columns as required by the model. Remember, for training extra data is needed which is not an input for the model at inferencing time ;) ### Learning Resources @@ -282,7 +285,7 @@ So, you've chosen for the batch inferencing path. We're going to use Agent Platf There are times when the training data becomes not representative anymore because of changing demographics, trends etc. To catch any skew or drift in feature distributions or even in predictions, it is necessary to monitor your model performance continuously. > [!NOTE] -> We'll be using **Model Monitoring v1** for this challenge, which is configured during *Online Prediction Endpoint configuration* for online models, and *Batch Prediction Run configuration* for batch execution. +> We'll be using **Model Monitoring v1** for this challenge, which is configured during *Endpoint configuration* for online models, and *Batch Inference configuration* for batch execution. If you've chosen the online inferencing path, continue with [Online Monitoring](#online-monitoring), otherwise please skip to the [Batch Monitoring](#batch-monitoring) section. @@ -353,6 +356,9 @@ Just like the previous challenges, if you've chosen the online inferencing path, Use the provided build pipeline (`clouddeploy.yaml`) to create a new build configuration. Configure it to be triggered in response to the messages received in the Pub/Sub topic that's used to configure the Model Monitoring notifications. Also provide the necessary variables, such as the model training code version, endpoint name etc. Name this trigger `CT-CD` (or `continuous-training-and-delivery`). +> [!NOTE] +> Keep in mind that you need to choose the correct service accounts for Cloud Build and for the Agent Platform Pipelines. + ### Success Criteria 1. There's a correctly configured build pipeline that can be triggered through Pub/Sub messages, named `CT-CD` (or `continuous-training-and-delivery`). @@ -363,6 +369,7 @@ Use the provided build pipeline (`clouddeploy.yaml`) to create a new build confi ### Tips - If you create the topic before you create the notification channel you can copy its fully qualified name and paste when configuring the notification channel. +- Read the `clouddeploy.yaml` file to understand how variables are used and what you need to provide. ### Learning Resources @@ -376,6 +383,9 @@ Typically Batch Predictions are asynchronous and are scheduled to run periodical Running the batch predictions periodically will only get us half way. We need to monitor any Model Monitoring alerts and act on that. There's another Cloud Build pipeline definition provided by `clouddeploy.yaml` that's responsible for retraining. Configure that in a new Cloud Build trigger, call it `CT` (or `continuous-training`) set the required variables (remember to set *ENDPOINT* to `[none]`, the others should be familiar, when in doubt have a look at the yaml file). Use Pub/Sub messages as the trigger event and pick the topic that's configured for Model Monitoring Pub/Sub notification channel. +> [!NOTE] +> Keep in mind that you need to choose the correct service accounts for Cloud Build and for the Agent Platform Pipelines. + ### Success Criteria 1. There's a correctly configured build pipeline for *batch predictions* that can be triggered with webhooks, called `CD` (or `continuous-delivery`). @@ -387,6 +397,7 @@ Running the batch predictions periodically will only get us half way. We need to ### Tips - If you create the topic before you create the notification channel you can copy its name and paste when configuring the notification channel. +- Read the `batchdeploy.yaml` file to understand how variables are used and what you need to provide. - The webhook URL configuration in Cloud Scheduler requires the header `Content-Type` to be set to `application/json` otherwise the things won't work. - You can *force run* a Cloud Scheduler job, no need to wait until Sunday :). diff --git a/hacks/mlops-on-gcp/artifacts/main.tf b/hacks/mlops-on-gcp/artifacts/main.tf index c2d8158b..39e11b86 100644 --- a/hacks/mlops-on-gcp/artifacts/main.tf +++ b/hacks/mlops-on-gcp/artifacts/main.tf @@ -2,19 +2,15 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = "4.63.1" + version = "7.24.0" } google-beta = { source = "hashicorp/google-beta" - version = "4.63.1" + version = "7.24.0" } } } -locals { - build_default_sa = "${data.google_project.project.number}@cloudbuild.gserviceaccount.com" -} - provider "google" { project = var.gcp_project_id region = var.gcp_region @@ -25,60 +21,46 @@ provider "google-beta" { region = var.gcp_region } -resource "google_project_service" "compute_api" { - service = "compute.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "notebooks_api" { - service = "notebooks.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "vertex_api" { - service = "aiplatform.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "build_api" { - service = "cloudbuild.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "source_repository_api" { - service = "sourcerepo.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "scheduler_api" { - service = "cloudscheduler.googleapis.com" - disable_on_destroy = false -} - -resource "google_project_service" "pubsub_api" { - service = "pubsub.googleapis.com" - disable_on_destroy = false -} +resource "google_project_service" "default" { + project = var.gcp_project_id + for_each = toset([ + "compute.googleapis.com", + "notebooks.googleapis.com", + "aiplatform.googleapis.com", + "cloudbuild.googleapis.com", + "sourcerepo.googleapis.com", + "cloudscheduler.googleapis.com", + "pubsub.googleapis.com", + "cloudresourcemanager.googleapis.com", + "iam.googleapis.com", + "artifactregistry.googleapis.com" + ]) + service = each.key -resource "google_project_service" "resource_manager_api" { - service = "cloudresourcemanager.googleapis.com" disable_on_destroy = false } -resource "google_project_service" "iam_api" { - service = "iam.googleapis.com" - disable_on_destroy = false +data "google_project" "project" { + depends_on = [ + google_project_service.default + ] } -data "google_project" "project" { +resource "google_service_account" "mlops_build_sa" { + account_id = "sa-mlops-build" + display_name = "MLOps Build Service Account" + project = var.gcp_project_id depends_on = [ - google_project_service.resource_manager_api + google_project_service.default ] } -data "google_compute_default_service_account" "gce_default" { +resource "google_service_account" "mlops_kfp_sa" { + account_id = "sa-mlops-kfp" + display_name = "Kubeflow Pipelines Service Account" + project = var.gcp_project_id depends_on = [ - google_project_service.compute_api + google_project_service.default ] } @@ -88,7 +70,7 @@ resource "google_compute_network" "default_network_created" { auto_create_subnetworks = true count = var.create_default_network ? 1 : 0 depends_on = [ - google_project_service.compute_api + google_project_service.default ] } @@ -111,39 +93,33 @@ data "google_compute_network" "default_network" { ] } -resource "google_project_iam_member" "gce_default_iam" { +resource "google_project_iam_member" "mlops_build_sa_iam" { project = var.gcp_project_id for_each = toset([ "roles/aiplatform.admin", - "roles/bigquery.admin", - "roles/storage.admin", - "roles/monitoring.notificationChannelViewer", + "roles/storage.objectAdmin", "roles/source.reader", "roles/logging.logWriter" ]) role = each.key - member = "serviceAccount:${data.google_compute_default_service_account.gce_default.email}" + member = google_service_account.mlops_build_sa.member depends_on = [ - google_project_service.iam_api + google_project_service.default ] } -resource "google_project_iam_member" "build_default_iam" { +resource "google_project_iam_member" "mlops_kfp_sa_iam" { project = var.gcp_project_id - role = "roles/aiplatform.admin" - member = "serviceAccount:${local.build_default_sa}" - depends_on = [ - google_project_service.build_api, - google_project_service.iam_api - ] -} - -resource "google_service_account_iam_member" "gce_default_account_user_iam" { - service_account_id = data.google_compute_default_service_account.gce_default.name - role = "roles/iam.serviceAccountUser" - member = "serviceAccount:${local.build_default_sa}" + for_each = toset([ + "roles/bigquery.admin", + "roles/aiplatform.admin", + "roles/storage.admin", + "roles/monitoring.notificationChannelViewer" + ]) + role = each.key + member = google_service_account.mlops_kfp_sa.member depends_on = [ - google_project_service.iam_api + google_project_service.default ] } @@ -161,9 +137,45 @@ resource "google_project_iam_member" "monitoring_default_iam" { "roles/monitoring.notificationServiceAgent" ]) role = each.key - member = "serviceAccount:${google_project_service_identity.monitoring_default_sa.email}" + member = google_project_service_identity.monitoring_default_sa.member + depends_on = [ + google_project_service.default + ] +} + +resource "google_project_service_identity" "cloudbuild_sa" { + provider = google-beta + + project = data.google_project.project.project_id + service = "cloudbuild.googleapis.com" + depends_on = [ + google_project_service.default + ] +} + +resource "google_project_iam_member" "cloudbuild_sa_pubsub" { + project = var.gcp_project_id + role = "roles/pubsub.subscriber" + member = google_project_service_identity.cloudbuild_sa.member + depends_on = [ + google_project_service.default + ] +} + +resource "google_service_account_iam_member" "mlops_build_use_mlops_kfp" { + service_account_id = google_service_account.mlops_kfp_sa.name + role = "roles/iam.serviceAccountUser" + member = google_service_account.mlops_build_sa.member +} + +# This is an optional resource in case participants want to try it out as an alternative way to manage their +# templates +resource "google_artifact_registry_repository" "kfp_artifacts" { + location = var.gcp_region + repository_id = "kfp-artifacts" + description = "Repository for Kubeflow Pipelines templates" + format = "KFP" depends_on = [ - google_project_service.pubsub_api, - google_project_service.iam_api + google_project_service.default ] } diff --git a/hacks/mlops-on-gcp/solutions.md b/hacks/mlops-on-gcp/solutions.md index 5cf79c4f..5330fe3b 100644 --- a/hacks/mlops-on-gcp/solutions.md +++ b/hacks/mlops-on-gcp/solutions.md @@ -20,7 +20,7 @@ We'll be assuming that all necessary services have been enabled and the (default ### Notes & Guidance -The Workbench Instance can run anywhere, but a region close to the participants is preferred. And the *Permissions*→*Single user only* option must be chosen, which requires to enter the Advanced Setting section for Workbench Instance. +The Workbench Instance can run anywhere, but a region close to the participants is preferred. And the *IAM and security*→*Single user only* option must be chosen, which requires to enter the Advanced Setting section for Workbench Instance. Creating a virtual environment is essential otherwise things might break due to dependency conflicts. The instructions point to a gist that works with `pip` and Workbench Instances have that installed. However, `conda` virtual environments would work fine too. Make sure to run these from a *Terminal* in Jupyter (not from a *Notebook*)! @@ -249,11 +249,12 @@ Setting this up through the UI should be trivial, the training sample data uri s The *retraining* (`clouddeploy.yaml`) build pipeline requires the following variables to be set. -| Variable | Value | -| --- | --- | -| `_PYTHON_PKG` | `gcp_mlops_demo-0.8.0.dev0` | -| `_ENDPOINT` | `ep-taxi-tips` | -| `_LOCATION` | `us-central1` | +| Variable | Value | +| --- | --- | +| `_PYTHON_PKG` | `gcp_mlops_demo-0.8.0.dev0` | +| `_ENDPOINT` | `ep-taxi-tips` | +| `_LOCATION` | `us-central1` | +| `_KFP_SERVICE_ACCOUNT` | `sa-mlops-kfp` | The *retraining* pipeline must respond to a *Pub/Sub message* using the topic created for the notification channel. @@ -268,6 +269,7 @@ The main challenge is to configure the build pipelines properly. You'll need the | `_SOURCE_TABLE_URI` | `bq://{PROJECT_ID}.{DATASET}.{TABLE}` | | `_TRAINING_SAMPLE_URI` | `gs://{PROJECT_ID}/data/sample/sample.csv` | | `_LOCATION` | `us-central1` | +| `_KFP_SERVICE_ACCOUNT` | `sa-mlops-kfp` | The Cloud Scheduler cron job configuration should be: `30 3 * * 7` with HTTP target type and webhook URL from the previous build configuration, using the POST method (see the note around the webhook URL in [Online Loop](#online-loop) section if you get 404s). One thing to remember is to set the *Content-Type* header to *application/json* otherwise the execution will fail. @@ -275,10 +277,11 @@ You can verify the Cloud Scheduler job by a *Force run*. Similarly the *retraining* (`clouddeploy.yaml`) build pipeline requires the following variables to be set. -| Variable | Value | -| --- | --- | -| `_PYTHON_PKG` | `gcp_mlops_demo-0.8.0.dev0` | -| `_ENDPOINT` | `[none]` | -| `_LOCATION` | `us-central1` | +| Variable | Value | +| --- | --- | +| `_PYTHON_PKG` | `gcp_mlops_demo-0.8.0.dev0` | +| `_ENDPOINT` | `[none]` | +| `_LOCATION` | `us-central1` | +| `_KFP_SERVICE_ACCOUNT` | `sa-mlops-kfp` | The *retraining* pipeline must respond to a *Pub/Sub message* using the topic created for the notification channel.