|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Copyright 2023–2025 Google LLC |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | + |
| 17 | +# Script to build and upload the MaxText colocated Python Docker image. |
| 18 | +# This script should be run from the root directory of the MaxText repository. |
| 19 | +# |
| 20 | +# Arguments can be provided as KEY=VALUE pairs, e.g.: |
| 21 | +# bash build_upload_colocated_python_image.sh PROJECT=my-gcp LOCAL_IMAGE_NAME=my-colocated |
| 22 | +# |
| 23 | +# Supported KEYs: |
| 24 | +# PROJECT: GCP project for gcr.io. |
| 25 | +# LOCAL_IMAGE_NAME: Overrides the local tag used during docker build. Defaults to 'maxtext-colocated-python'. |
| 26 | +# IMAGE_LOCATION: The full URL for the final image in the registry. |
| 27 | +# Defaults to gcr.io/${PROJECT}/${USER}_${LOCAL_IMAGE_NAME}:latest. |
| 28 | +# Setting this variable fully overrides PROJECT and LOCAL_IMAGE_NAME. |
| 29 | + |
| 30 | +# Example 1: Provide PROJECT |
| 31 | +# bash src/dependencies/scripts/build_upload_colocated_python_image.sh PROJECT=my-tpu-dev |
| 32 | + |
| 33 | +# Example 2: Specify a custom local image name |
| 34 | +# bash src/dependencies/scripts/build_upload_colocated_python_image.sh PROJECT=my-tpu-dev LOCAL_IMAGE_NAME=maxtext-cp |
| 35 | + |
| 36 | +# Example 3: Provide a complete IMAGE_LOCATION |
| 37 | +# bash src/dependencies/scripts/build_upload_colocated_python_image.sh IMAGE_LOCATION=us-docker.pkg.dev/my-artifact-repo/images/colocated-python:stable |
| 38 | + |
| 39 | +# Example 4: Specify a custom JAX version |
| 40 | +# bash src/dependencies/scripts/build_upload_colocated_python_image.sh PROJECT=my-tpu-dev JAX_VERSION=0.10.0 |
| 41 | + |
| 42 | +for ARGUMENT in "$@"; do |
| 43 | + if [[ "$ARGUMENT" == *"="* ]]; then |
| 44 | + IFS='=' read -r RAW_KEY VALUE <<< "$ARGUMENT" |
| 45 | + KEY=$(echo "$RAW_KEY" | tr '[:lower:]' '[:upper:]') |
| 46 | + export "$KEY"="$VALUE" |
| 47 | + echo " Parsed: $KEY=$VALUE" |
| 48 | + else |
| 49 | + echo "Warning: Ignoring argument '$ARGUMENT'. Arguments should be in KEY=VALUE format (e.g., PROJECT=my-proj)." |
| 50 | + fi |
| 51 | +done |
| 52 | + |
| 53 | +# Set default values using parameter expansion. |
| 54 | +: "${LOCAL_IMAGE_NAME:=maxtext-colocated-python}" |
| 55 | +if [[ -z "${IMAGE_LOCATION}" ]]; then |
| 56 | + # IMAGE_LOCATION was not provided, so PROJECT is required for the default. |
| 57 | + if [[ -z "${PROJECT}" ]]; then |
| 58 | + echo "Error: Either PROJECT or IMAGE_LOCATION must be provided." >&2 |
| 59 | + echo " - To use a default location in GCR, set PROJECT=your-gcp-project-id." >&2 |
| 60 | + echo " - To specify a full custom location, set IMAGE_LOCATION=your/registry/path/image:tag." >&2 |
| 61 | + exit 1 |
| 62 | + fi |
| 63 | + IMAGE_LOCATION="gcr.io/${PROJECT}/${USER}_${LOCAL_IMAGE_NAME}:latest" |
| 64 | +fi |
| 65 | + |
| 66 | +DOCKERFILE_TEMPLATE="src/dependencies/dockerfiles/colocated_python.Dockerfile.template" |
| 67 | +CONTEXT="maxtext/" |
| 68 | + |
| 69 | +echo "$(date): Building and pushing MaxText Colocated Python image..." |
| 70 | +echo " PROJECT: ${PROJECT}" |
| 71 | +echo " LOCAL_IMAGE_NAME: ${LOCAL_IMAGE_NAME}" |
| 72 | +echo " IMAGE_LOCATION: ${IMAGE_LOCATION}" |
| 73 | +echo " Dockerfile Template: ${DOCKERFILE_TEMPLATE}" |
| 74 | +echo " Build Context: ${CONTEXT}" |
| 75 | + |
| 76 | +# --- Step 1: Handle JAX Version --- |
| 77 | +REQ_FILE="src/dependencies/requirements/generated_requirements/tpu-requirements.txt" |
| 78 | +if [[ -z "${JAX_VERSION}" ]]; then |
| 79 | + echo "$(date): Extracting JAX version from requirements..." |
| 80 | + if [[ ! -f "${REQ_FILE}" ]]; then |
| 81 | + echo "Error: Requirements file not found: ${REQ_FILE}" >&2 |
| 82 | + exit 1 |
| 83 | + fi |
| 84 | + # Extracts version like "0.10.0" from lines like "jax>=0.10.0" or "jax==0.10.0" |
| 85 | + JAX_VERSION=$(grep -E "^jax(>=|==)" "${REQ_FILE}" | head -1 | sed -E 's/^jax(>=|==)([0-9.]+).*/\2/') |
| 86 | + if [[ -z "${JAX_VERSION}" ]]; then |
| 87 | + echo "Error: Could not extract jax version from ${REQ_FILE}. Ensure it's in the format 'jax>=X.Y.Z' or 'jax==X.Y.Z'." >&2 |
| 88 | + exit 1 |
| 89 | + fi |
| 90 | + echo " Detected required JAX version: ${JAX_VERSION}" |
| 91 | +else |
| 92 | + TEMP_REQ_FILE=$(mktemp) |
| 93 | + echo "$(date): Generating temporary requirements file: ${TEMP_REQ_FILE} for custom JAX version ${JAX_VERSION}" |
| 94 | + trap 'rm -f "${TEMP_REQ_FILE}"' EXIT |
| 95 | + sed "s/^jax\(>=\|==\).*$/jax==${JAX_VERSION}/" "${REQ_FILE}" > "${TEMP_REQ_FILE}" |
| 96 | + REQ_FILE="${TEMP_REQ_FILE}" |
| 97 | +fi |
| 98 | + |
| 99 | +# --- Step 2: Find the Latest Compatible Base Image Tag --- |
| 100 | +BASE_IMAGE_REPO="us-docker.pkg.dev/cloud-tpu-v2-images/pathways-colocated-python/sidecar" |
| 101 | +TARGET_JAX_TAG_PART="jax_${JAX_VERSION}" |
| 102 | +echo "$(date): Searching for base image tag in '${BASE_IMAGE_REPO}' containing '${TARGET_JAX_TAG_PART}'..." |
| 103 | + |
| 104 | +# Authenticate Docker for the base image registry (us-docker.pkg.dev) |
| 105 | +BASE_REGISTRY=$(echo "${BASE_IMAGE_REPO}" | cut -d/ -f1) |
| 106 | +echo "$(date): Configuring Docker for base image registry: ${BASE_REGISTRY}" |
| 107 | +gcloud auth configure-docker --quiet "${BASE_REGISTRY}" |
| 108 | + |
| 109 | +# List tags, filter by JAX version, sort by date (desc), and take the latest. |
| 110 | +BASE_IMAGE_TAG=$(gcloud artifacts docker images list "${BASE_IMAGE_REPO}" --include-tags --format=json | \ |
| 111 | + jq -r '.[] | .tags[]?' | \ |
| 112 | + grep "${TARGET_JAX_TAG_PART}" | \ |
| 113 | + sort -r | \ |
| 114 | + head -1) |
| 115 | + |
| 116 | +if [[ -z "${BASE_IMAGE_TAG}" ]]; then |
| 117 | + echo "Error: Could not find a suitable base image tag in ${BASE_IMAGE_REPO} for JAX version '${JAX_VERSION}'." >&2 |
| 118 | + echo " Searched for tags containing '${TARGET_JAX_TAG_PART}'." >&2 |
| 119 | + echo " Available matching tags found:" >&2 |
| 120 | + gcloud artifacts docker images list "${BASE_IMAGE_REPO}" --include-tags --format=json | \ |
| 121 | + jq -r '.[] | .tags[]?' | sort -r >&2 |
| 122 | + exit 1 |
| 123 | +fi |
| 124 | +FULL_BASE_IMAGE="${BASE_IMAGE_REPO}:${BASE_IMAGE_TAG}" |
| 125 | +echo " Found latest compatible base image: ${FULL_BASE_IMAGE}" |
| 126 | + |
| 127 | +# --- Step 3: Build the Docker image using --build-arg --- |
| 128 | +echo "$(date): Running docker build with local tag '${LOCAL_IMAGE_NAME}' using ${TMP_DOCKERFILE}..." |
| 129 | +docker build --no-cache \ |
| 130 | + -f ${DOCKERFILE_TEMPLATE} \ |
| 131 | + --build-arg BASE_IMAGE="${FULL_BASE_IMAGE}" \ |
| 132 | + -t "${LOCAL_IMAGE_NAME}" \ |
| 133 | + . |
| 134 | + |
| 135 | +# --- Step 4: Tag and Push the image --- |
| 136 | +echo "$(date): Tagging '${LOCAL_IMAGE_NAME}' as '${IMAGE_LOCATION}'..." |
| 137 | +docker tag "${LOCAL_IMAGE_NAME}" "${IMAGE_LOCATION}" |
| 138 | + |
| 139 | +echo "$(date): Pushing '${IMAGE_LOCATION}'..." |
| 140 | +docker push "${IMAGE_LOCATION}" |
| 141 | + |
| 142 | +# --- Step 5: Cleanup --- |
| 143 | +echo "$(date): Cleaning up local tag '${LOCAL_IMAGE_NAME}'..." |
| 144 | +docker image rm "${LOCAL_IMAGE_NAME}" |
| 145 | + |
| 146 | +echo "$(date): Build and push complete for ${IMAGE_LOCATION}" |
0 commit comments