mirror of
https://github.com/GoogleCloudPlatform/vertex-ai-samples.git
synced 2026-09-26 14:42:04 +00:00
Increases notebook concurrency and linted CI files (#512)
* Fixed private pool issues * Ran linter * Added worker timeouts * Tweaked timeout * Removed gcloud requirement * Removed unneeded file
This commit is contained in:
@@ -1,45 +1,44 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from resource_cleanup_manager import (
|
|
||||||
ResourceCleanupManager,
|
from resource_cleanup_manager import (DatasetResourceCleanupManager,
|
||||||
DatasetResourceCleanupManager,
|
EndpointResourceCleanupManager,
|
||||||
EndpointResourceCleanupManager,
|
ModelResourceCleanupManager,
|
||||||
ModelResourceCleanupManager,
|
ResourceCleanupManager)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_cleanup_managers(managers: List[ResourceCleanupManager], is_dry_run: bool):
|
def run_cleanup_managers(managers: List[ResourceCleanupManager], is_dry_run: bool):
|
||||||
for manager in managers:
|
for manager in managers:
|
||||||
type_name = manager.type_name
|
type_name = manager.type_name
|
||||||
|
|
||||||
print(f"Fetching {type_name}'s...")
|
print(f"Fetching {type_name}'s...")
|
||||||
resources = manager.list()
|
resources = manager.list()
|
||||||
print(f"Found {len(resources)} {type_name}'s")
|
print(f"Found {len(resources)} {type_name}'s")
|
||||||
for resource in resources:
|
for resource in resources:
|
||||||
if not manager.is_deletable(resource):
|
if not manager.is_deletable(resource):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if is_dry_run:
|
if is_dry_run:
|
||||||
resource_name = manager.resource_name(resource)
|
resource_name = manager.resource_name(resource)
|
||||||
print(f"Will delete '{type_name}': {resource_name}")
|
print(f"Will delete '{type_name}': {resource_name}")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
manager.delete(resource)
|
manager.delete(resource)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
print(exception)
|
print(exception)
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
|
|
||||||
is_dry_run = False
|
is_dry_run = False
|
||||||
|
|
||||||
if is_dry_run:
|
if is_dry_run:
|
||||||
print("Starting cleanup in dry run mode...")
|
print("Starting cleanup in dry run mode...")
|
||||||
|
|
||||||
# List of all cleanup managers
|
# List of all cleanup managers
|
||||||
managers = [
|
managers = [
|
||||||
DatasetResourceCleanupManager(),
|
DatasetResourceCleanupManager(),
|
||||||
EndpointResourceCleanupManager(),
|
EndpointResourceCleanupManager(),
|
||||||
ModelResourceCleanupManager(),
|
ModelResourceCleanupManager(),
|
||||||
]
|
]
|
||||||
|
|
||||||
run_cleanup_managers(managers=managers, is_dry_run=is_dry_run)
|
run_cleanup_managers(managers=managers, is_dry_run=is_dry_run)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import abc
|
import abc
|
||||||
from google.cloud import aiplatform
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from proto.datetime_helpers import DatetimeWithNanoseconds
|
|
||||||
|
from google.cloud import aiplatform
|
||||||
from google.cloud.aiplatform import base
|
from google.cloud.aiplatform import base
|
||||||
|
from proto.datetime_helpers import DatetimeWithNanoseconds
|
||||||
|
|
||||||
# If a resource was updated within this number of seconds, do not delete.
|
# If a resource was updated within this number of seconds, do not delete.
|
||||||
RESOURCE_UPDATE_BUFFER_IN_SECONDS = 60 * 60 * 8
|
RESOURCE_UPDATE_BUFFER_IN_SECONDS = 60 * 60 * 8
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
import execute_changed_notebooks_helper
|
import execute_changed_notebooks_helper
|
||||||
|
|
||||||
|
|
||||||
@@ -73,6 +74,13 @@ parser.add_argument(
|
|||||||
help="The GCP directory for storing executed notebooks.",
|
help="The GCP directory for storing executed notebooks.",
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timeout",
|
||||||
|
type=int,
|
||||||
|
help="Timeout in seconds",
|
||||||
|
default=86400,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--private_pool_id",
|
"--private_pool_id",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -102,6 +110,7 @@ execute_changed_notebooks_helper.process_and_execute_notebooks(
|
|||||||
artifacts_bucket=args.artifacts_bucket,
|
artifacts_bucket=args.artifacts_bucket,
|
||||||
variable_project_id=args.variable_project_id,
|
variable_project_id=args.variable_project_id,
|
||||||
variable_region=args.variable_region,
|
variable_region=args.variable_region,
|
||||||
private_pool_id=args.private_pool_id if not "default" else None,
|
private_pool_id=args.private_pool_id,
|
||||||
should_parallelize=args.should_parallelize,
|
should_parallelize=args.should_parallelize,
|
||||||
|
timeout=args.timeout,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,18 +17,22 @@ import concurrent
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
|
import operator
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import nbformat
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from tabulate import tabulate
|
|
||||||
import operator
|
|
||||||
|
|
||||||
import execute_notebook_remote
|
import execute_notebook_remote
|
||||||
from utils import util, NotebookProcessors
|
import nbformat
|
||||||
from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata
|
from google.cloud.devtools.cloudbuild_v1.types import BuildOperationMetadata
|
||||||
|
from ratemate import RateLimit
|
||||||
|
from tabulate import tabulate
|
||||||
|
from utils import NotebookProcessors, util
|
||||||
|
|
||||||
|
# A buffer so that workers finish before the orchestrating job
|
||||||
|
WORKER_TIMEOUT_BUFFER_IN_SECONDS: int = 60 * 60
|
||||||
|
|
||||||
|
|
||||||
def format_timedelta(delta: datetime.timedelta) -> str:
|
def format_timedelta(delta: datetime.timedelta) -> str:
|
||||||
@@ -103,6 +107,9 @@ def _create_tag(filepath: str) -> str:
|
|||||||
return tag
|
return tag
|
||||||
|
|
||||||
|
|
||||||
|
rate_limit = RateLimit(max_count=50, per=60, greedy=True)
|
||||||
|
|
||||||
|
|
||||||
def process_and_execute_notebook(
|
def process_and_execute_notebook(
|
||||||
container_uri: str,
|
container_uri: str,
|
||||||
staging_bucket: str,
|
staging_bucket: str,
|
||||||
@@ -110,9 +117,12 @@ def process_and_execute_notebook(
|
|||||||
variable_project_id: str,
|
variable_project_id: str,
|
||||||
variable_region: str,
|
variable_region: str,
|
||||||
private_pool_id: Optional[str],
|
private_pool_id: Optional[str],
|
||||||
|
deadline: datetime,
|
||||||
notebook: str,
|
notebook: str,
|
||||||
should_get_tail_logs: bool = False,
|
should_get_tail_logs: bool = False,
|
||||||
) -> NotebookExecutionResult:
|
) -> NotebookExecutionResult:
|
||||||
|
rate_limit.wait() # wait before creating the task
|
||||||
|
|
||||||
print(f"Running notebook: {notebook}")
|
print(f"Running notebook: {notebook}")
|
||||||
|
|
||||||
# Create paths
|
# Create paths
|
||||||
@@ -145,14 +155,20 @@ def process_and_execute_notebook(
|
|||||||
# Upload the pre-processed code to a GCS bucket
|
# Upload the pre-processed code to a GCS bucket
|
||||||
code_archive_uri = util.archive_code_and_upload(staging_bucket=staging_bucket)
|
code_archive_uri = util.archive_code_and_upload(staging_bucket=staging_bucket)
|
||||||
|
|
||||||
|
# Calculate timeout in seconds
|
||||||
|
timeout_in_seconds = max(
|
||||||
|
int((deadline - datetime.datetime.now()).total_seconds()), 1
|
||||||
|
)
|
||||||
|
|
||||||
operation = execute_notebook_remote.execute_notebook_remote(
|
operation = execute_notebook_remote.execute_notebook_remote(
|
||||||
code_archive_uri=code_archive_uri,
|
code_archive_uri=code_archive_uri,
|
||||||
notebook_uri=notebook,
|
notebook_uri=notebook,
|
||||||
notebook_output_uri=notebook_output_uri,
|
notebook_output_uri=notebook_output_uri,
|
||||||
container_uri=container_uri,
|
container_uri=container_uri,
|
||||||
tag=tag,
|
tag=tag,
|
||||||
region=variable_region,
|
|
||||||
private_pool_id=private_pool_id,
|
private_pool_id=private_pool_id,
|
||||||
|
private_pool_region=variable_region,
|
||||||
|
timeout_in_seconds=timeout_in_seconds,
|
||||||
)
|
)
|
||||||
|
|
||||||
operation_metadata = BuildOperationMetadata(mapping=operation.metadata)
|
operation_metadata = BuildOperationMetadata(mapping=operation.metadata)
|
||||||
@@ -241,6 +257,7 @@ def process_and_execute_notebooks(
|
|||||||
variable_region: str,
|
variable_region: str,
|
||||||
private_pool_id: Optional[str],
|
private_pool_id: Optional[str],
|
||||||
should_parallelize: bool,
|
should_parallelize: bool,
|
||||||
|
timeout: int,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the notebooks that exist under the folders defined in the test_paths_file.
|
Run the notebooks that exist under the folders defined in the test_paths_file.
|
||||||
@@ -267,9 +284,16 @@ def process_and_execute_notebooks(
|
|||||||
Required. The value for REGION to inject into notebooks.
|
Required. The value for REGION to inject into notebooks.
|
||||||
should_parallelize (bool):
|
should_parallelize (bool):
|
||||||
Required. Should run notebooks in parallel using a thread pool as opposed to in sequence.
|
Required. Should run notebooks in parallel using a thread pool as opposed to in sequence.
|
||||||
|
timeout (str):
|
||||||
|
Required. Timeout string according to https://cloud.google.com/build/docs/build-config-file-schema#timeout.
|
||||||
"""
|
"""
|
||||||
notebook_execution_results: List[NotebookExecutionResult] = []
|
notebook_execution_results: List[NotebookExecutionResult] = []
|
||||||
|
|
||||||
|
# Calculate deadline
|
||||||
|
deadline = datetime.datetime.now() + datetime.timedelta(
|
||||||
|
seconds=max(timeout - WORKER_TIMEOUT_BUFFER_IN_SECONDS, 0)
|
||||||
|
)
|
||||||
|
|
||||||
if len(notebooks) > 0:
|
if len(notebooks) > 0:
|
||||||
print(f"Found {len(notebooks)} modified notebooks: {notebooks}")
|
print(f"Found {len(notebooks)} modified notebooks: {notebooks}")
|
||||||
|
|
||||||
@@ -277,7 +301,9 @@ def process_and_execute_notebooks(
|
|||||||
print(
|
print(
|
||||||
"Running notebooks in parallel, so no logs will be displayed. Please wait..."
|
"Running notebooks in parallel, so no logs will be displayed. Please wait..."
|
||||||
)
|
)
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
|
||||||
|
print(f"Max workers: {executor._max_workers}")
|
||||||
|
|
||||||
notebook_execution_results = list(
|
notebook_execution_results = list(
|
||||||
executor.map(
|
executor.map(
|
||||||
functools.partial(
|
functools.partial(
|
||||||
@@ -288,6 +314,7 @@ def process_and_execute_notebooks(
|
|||||||
variable_project_id,
|
variable_project_id,
|
||||||
variable_region,
|
variable_region,
|
||||||
private_pool_id,
|
private_pool_id,
|
||||||
|
deadline,
|
||||||
),
|
),
|
||||||
notebooks,
|
notebooks,
|
||||||
)
|
)
|
||||||
@@ -301,6 +328,7 @@ def process_and_execute_notebooks(
|
|||||||
variable_project_id=variable_project_id,
|
variable_project_id=variable_project_id,
|
||||||
variable_region=variable_region,
|
variable_region=variable_region,
|
||||||
private_pool_id=private_pool_id,
|
private_pool_id=private_pool_id,
|
||||||
|
deadline=deadline,
|
||||||
notebook=notebook,
|
notebook=notebook,
|
||||||
)
|
)
|
||||||
for notebook in notebooks
|
for notebook in notebooks
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"""A CLI to download (optional) and run a single notebook locally"""
|
"""A CLI to download (optional) and run a single notebook locally"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import execute_notebook_helper
|
import execute_notebook_helper
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Run a single notebook locally.")
|
parser = argparse.ArgumentParser(description="Run a single notebook locally.")
|
||||||
|
|||||||
@@ -15,14 +15,14 @@
|
|||||||
|
|
||||||
"""Methods to run a notebook locally"""
|
"""Methods to run a notebook locally"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import errno
|
import errno
|
||||||
import papermill as pm
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
from utils import util
|
import papermill as pm
|
||||||
from google.cloud.aiplatform import utils
|
from google.cloud.aiplatform import utils
|
||||||
|
from utils import util
|
||||||
|
|
||||||
# This script is used to execute a notebook and write out the output notebook.
|
# This script is used to execute a notebook and write out the output notebook.
|
||||||
|
|
||||||
|
|||||||
@@ -16,22 +16,18 @@
|
|||||||
"""Methods to run a notebook on Google Cloud Build"""
|
"""Methods to run a notebook on Google Cloud Build"""
|
||||||
|
|
||||||
from re import sub
|
from re import sub
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import google.auth
|
||||||
|
import yaml
|
||||||
|
from google.api_core import client_options, operation
|
||||||
|
from google.cloud.aiplatform import utils
|
||||||
|
from google.cloud.devtools import cloudbuild_v1
|
||||||
|
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
|
||||||
from google.protobuf import duration_pb2
|
from google.protobuf import duration_pb2
|
||||||
from yaml.loader import FullLoader
|
from yaml.loader import FullLoader
|
||||||
|
|
||||||
import google.auth
|
|
||||||
from google.cloud.devtools import cloudbuild_v1
|
|
||||||
from google.cloud.devtools.cloudbuild_v1.types import Source, StorageSource
|
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from google.cloud.aiplatform import utils
|
|
||||||
from google.api_core import operation, client_options
|
|
||||||
|
|
||||||
|
|
||||||
CLOUD_BUILD_FILEPATH = ".cloud-build/notebook-execution-test-cloudbuild-single.yaml"
|
CLOUD_BUILD_FILEPATH = ".cloud-build/notebook-execution-test-cloudbuild-single.yaml"
|
||||||
TIMEOUT_IN_SECONDS = 86400
|
|
||||||
SERVICE_BASE_PATH = "cloudbuild.googleapis.com"
|
SERVICE_BASE_PATH = "cloudbuild.googleapis.com"
|
||||||
|
|
||||||
|
|
||||||
@@ -40,12 +36,14 @@ def execute_notebook_remote(
|
|||||||
notebook_uri: str,
|
notebook_uri: str,
|
||||||
notebook_output_uri: str,
|
notebook_output_uri: str,
|
||||||
container_uri: str,
|
container_uri: str,
|
||||||
region: str,
|
|
||||||
private_pool_id: Optional[str],
|
private_pool_id: Optional[str],
|
||||||
|
private_pool_region: Optional[str],
|
||||||
tag: Optional[str],
|
tag: Optional[str],
|
||||||
|
timeout_in_seconds: Optional[int] = None,
|
||||||
) -> operation.Operation:
|
) -> operation.Operation:
|
||||||
"""Create and execute a single notebook on Google Cloud Build"""
|
"""Create and execute a single notebook on Google Cloud Build"""
|
||||||
# Load build steps from YAML
|
# Load build steps from YAML
|
||||||
|
|
||||||
cloudbuild_config = yaml.load(open(CLOUD_BUILD_FILEPATH), Loader=FullLoader)
|
cloudbuild_config = yaml.load(open(CLOUD_BUILD_FILEPATH), Loader=FullLoader)
|
||||||
|
|
||||||
substitutions = {
|
substitutions = {
|
||||||
@@ -57,13 +55,14 @@ def execute_notebook_remote(
|
|||||||
build = cloudbuild_v1.Build()
|
build = cloudbuild_v1.Build()
|
||||||
|
|
||||||
options: Optional[client_options.ClientOptions] = None
|
options: Optional[client_options.ClientOptions] = None
|
||||||
if private_pool_id:
|
if private_pool_id and private_pool_region:
|
||||||
substitutions["_PRIVATE_POOL_NAME"] = private_pool_id
|
# substitutions["_PRIVATE_POOL_NAME"] = private_pool_id
|
||||||
build.options = cloudbuild_config["options"]
|
build.options = cloudbuild_config.get("options")
|
||||||
|
build.options.pool = {"name": private_pool_id}
|
||||||
|
|
||||||
# Switch to the regional endpoint of the pool
|
# Switch to the regional endpoint of the pool
|
||||||
options = client_options.ClientOptions(
|
options = client_options.ClientOptions(
|
||||||
api_endpoint=f"{region}-{SERVICE_BASE_PATH}"
|
api_endpoint=f"{private_pool_region}-{SERVICE_BASE_PATH}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Authorize the client with Google defaults
|
# Authorize the client with Google defaults
|
||||||
@@ -85,8 +84,8 @@ def execute_notebook_remote(
|
|||||||
|
|
||||||
build.steps = cloudbuild_config["steps"]
|
build.steps = cloudbuild_config["steps"]
|
||||||
build.substitutions = substitutions
|
build.substitutions = substitutions
|
||||||
build.timeout = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS)
|
build.timeout = duration_pb2.Duration(seconds=timeout_in_seconds)
|
||||||
build.queue_ttl = duration_pb2.Duration(seconds=TIMEOUT_IN_SECONDS)
|
build.queue_ttl = duration_pb2.Duration(seconds=timeout_in_seconds)
|
||||||
|
|
||||||
if tag:
|
if tag:
|
||||||
build.tags = [tag]
|
build.tags = [tag]
|
||||||
|
|||||||
@@ -25,7 +25,4 @@ steps:
|
|||||||
- 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"'
|
- 'python3 -m pip install -U pip && python3 -m pip freeze && python3 .cloud-build/execute_notebook_cli.py --notebook_source "${_NOTEBOOK_GCS_URI}" --output_file_or_uri "${_NOTEBOOK_OUTPUT_GCS_URI}"'
|
||||||
env:
|
env:
|
||||||
- 'IS_TESTING=1'
|
- 'IS_TESTING=1'
|
||||||
timeout: 86400s
|
timeout: 86400s
|
||||||
options:
|
|
||||||
pool:
|
|
||||||
name: ${_PRIVATE_POOL_NAME}
|
|
||||||
@@ -9,4 +9,4 @@ tabulate
|
|||||||
google-cloud-aiplatform
|
google-cloud-aiplatform
|
||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
google-cloud-build
|
google-cloud-build
|
||||||
gcloud
|
ratemate
|
||||||
|
|||||||
@@ -13,8 +13,10 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from nbconvert.preprocessors import Preprocessor
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
from nbconvert.preprocessors import Preprocessor
|
||||||
|
|
||||||
from . import UpdateNotebookVariables as update_notebook_variables
|
from . import UpdateNotebookVariables as update_notebook_variables
|
||||||
|
|
||||||
|
|
||||||
@@ -60,4 +62,4 @@ class UpdateVariablesPreprocessor(Preprocessor):
|
|||||||
|
|
||||||
executable_cells.append(cell)
|
executable_cells.append(cell)
|
||||||
notebook.cells = executable_cells
|
notebook.cells = executable_cells
|
||||||
return notebook, resources
|
return notebook, resources
|
||||||
|
|||||||
@@ -78,4 +78,4 @@ def test_region():
|
|||||||
variable_name="REGION",
|
variable_name="REGION",
|
||||||
variable_value="us-central1",
|
variable_value="us-central1",
|
||||||
)
|
)
|
||||||
assert new_content == 'REGION = "us-central1" # @param {type:"string"}'
|
assert new_content == 'REGION = "us-central1" # @param {type:"string"}'
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
from google.cloud import storage
|
|
||||||
from google.cloud.aiplatform import utils
|
|
||||||
from google.auth import credentials as auth_credentials
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tarfile
|
import tarfile
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from google.auth import credentials as auth_credentials
|
||||||
|
from google.cloud import storage
|
||||||
|
from google.cloud.aiplatform import utils
|
||||||
|
|
||||||
|
|
||||||
def download_file(bucket_name: str, blob_name: str, destination_file: str) -> str:
|
def download_file(bucket_name: str, blob_name: str, destination_file: str) -> str:
|
||||||
@@ -57,4 +57,4 @@ def archive_code_and_upload(staging_bucket: str):
|
|||||||
|
|
||||||
print(f"Uploaded source code archive to {source_archived_file_gcs}")
|
print(f"Uploaded source code archive to {source_archived_file_gcs}")
|
||||||
|
|
||||||
return source_archived_file_gcs
|
return source_archived_file_gcs
|
||||||
|
|||||||
Reference in New Issue
Block a user