Как разместить LLM в коляске для функции Cloud Run

1. Введение

Обзор

In this codelab, you'll learn how to host a gemma3:4b model in a sidecar for a Cloud Run function. When a file is uploaded to a Cloud Storage bucket, it will trigger the Cloud Run function. The function will send the contents of the file to Gemma 3 in the sidecar for summarization.

Что вы узнаете

  • Как выполнить вывод данных с помощью функции Cloud Run и LLM, размещенной в сайдкаре, используя графические процессоры.
  • Как использовать конфигурацию прямого исходящего трафика VPC для облачного графического процессора (GPU) для более быстрой загрузки и обслуживания модели.
  • Как использовать Genkit для взаимодействия с вашей размещенной моделью Ollama

2. Прежде чем начать

To use the GPUs feature, you must request a quota increase for a supported region. The quota needed is nvidia_l4_gpu_allocation_no_zonal_redundancy, which is under Cloud Run Admin API. Here is the direct link to request quota .

3. Настройка и требования

Установите переменные окружения, которые будут использоваться на протяжении всего этого практического занятия.

PROJECT_ID=<YOUR_PROJECT_ID>
REGION=<YOUR_REGION>

AR_REPO=codelab-crf-sidecar-gpu
FUNCTION_NAME=crf-sidecar-gpu
BUCKET_GEMMA_NAME=$PROJECT_ID-codelab-crf-sidecar-gpu-gemma3
BUCKET_DOCS_NAME=$PROJECT_ID-codelab-crf-sidecar-gpu-docs
SERVICE_ACCOUNT="crf-sidecar-gpu"
SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
IMAGE_SIDECAR=$REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/ollama-gemma3

Создайте учетную запись службы, выполнив следующую команду:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="SA for codelab crf sidecar with gpu"

We'll use this same service account being used as the Cloud Run function's identity as the service account for the eventarc trigger to invoke the Cloud Run function. You can create a different SA for Eventarc if you prefer.

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
    --role=roles/run.invoker

Также предоставьте учетной записи службы доступ на получение событий Eventarc.

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
    --role="roles/eventarc.eventReceiver"

Создайте хранилище (bucket), в котором будет размещена ваша доработанная модель. В этом практическом задании используется региональное хранилище. Вы также можете использовать многорегиональное хранилище.

gsutil mb -l $REGION gs://$BUCKET_GEMMA_NAME

А затем предоставьте сотруднику службы безопасности доступ к ведру.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_GEMMA_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

Now create a regional bucket that will store the docs you want summarized. You can use a multi-regional bucket as well, provided you update the Eventarc trigger accordingly (shown at the end of this codelab).

gsutil mb -l $REGION gs://$BUCKET_DOCS_NAME

А затем предоставьте SA доступ к хранилищу Gemma 3.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_GEMMA_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

и ведро с документами.

gcloud storage buckets add-iam-policy-binding gs://$BUCKET_DOCS_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin

Создайте репозиторий реестра артефактов для образа Ollama, который будет использоваться в сайдкаре.

gcloud artifacts repositories create $AR_REPO \
    --repository-format=docker \
    --location=$REGION \
    --description="codelab for CR function and gpu sidecar" \
    --project=$PROJECT_ID

4. Скачайте модель Gemma 3.

Для начала вам нужно будет загрузить модель Gemma 3 4b с сайта ollama. Это можно сделать, установив ollama, а затем запустив модель gemma3:4b локально.

curl -fsSL https://ollama.com/install.sh | sh
ollama serve

Now in a separate terminal window, run the following command to pull down the model. If you are using Cloud Shell, you can open an additional terminal window by clicking the plus icon in the upper right menu bar.

ollama run gemma3:4b

После запуска Ollama вы можете задать модели несколько вопросов, например:

"why is the sky blue?"

После завершения общения с олламой вы можете выйти из чата, выполнив команду.

/bye

Затем в первом окне терминала выполните следующую команду, чтобы остановить локальную работу Ollama.

# on Linux / Cloud Shell press Ctrl^C or equivalent for your shell

Здесь вы можете найти, куда Ollama загружает модели в зависимости от вашей операционной системы.

https://github.com/ollama/ollama/blob/main/docs/faq.md#where-are-models-stored

Если вы используете облачные рабочие станции, вы можете найти загруженные модели Ollama здесь: /home/$USER/.ollama/models

Убедитесь, что ваши модели размещены здесь:

ls /home/$USER/.ollama/models

Теперь переместите модель gemma3:4b в свой контейнер GCS.

gsutil cp -r /home/$USER/.ollama/models gs://$BUCKET_GEMMA_NAME

5. Создайте функцию Cloud Run.

Создайте корневую папку для исходного кода.

mkdir codelab-crf-sidecar-gpu &&
cd codelab-crf-sidecar-gpu &&
mkdir cr-function &&
mkdir ollama-gemma3 &&
cd cr-function

Создайте подпапку с именем src. Внутри этой папки создайте файл с именем index.ts.

mkdir src &&
touch src/index.ts

Обновите файл index.ts, добавив следующий код:

//import util from 'util';
import { cloudEvent, CloudEvent } from "@google-cloud/functions-framework";
import { StorageObjectData } from "@google/events/cloud/storage/v1/StorageObjectData";
import { Storage } from "@google-cloud/storage";

// Initialize the Cloud Storage client
const storage = new Storage();

import { genkit } from 'genkit';
import { ollama } from 'genkitx-ollama';

const ai = genkit({
    plugins: [
        ollama({
            models: [
                {
                    name: 'gemma3:4b',
                    type: 'generate', // type: 'chat' | 'generate' | undefined
                },
            ],
            serverAddress: 'http://127.0.0.1:11434', // default local address
        }),
    ],
});


// Register a CloudEvent callback with the Functions Framework that will
// be triggered by Cloud Storage.

//functions.cloudEvent('helloGCS', await cloudEvent => {
cloudEvent("gcs-cloudevent", async (cloudevent: CloudEvent<StorageObjectData>) => {
    console.log("---------------\nProcessing for ", cloudevent.subject, "\n---------------");

    if (cloudevent.data) {

        const data = cloudevent.data;

        if (data && data.bucket && data.name) {
            const bucketName = cloudevent.data.bucket;
            const fileName = cloudevent.data.name;
            const filePath = `${cloudevent.data.bucket}/${cloudevent.data.name}`;

            console.log(`Attempting to download: ${filePath}`);

            try {
                // Get a reference to the bucket
                const bucket = storage.bucket(bucketName!);

                // Get a reference to the file
                const file = bucket.file(fileName!);

                // Download the file's contents
                const [content] = await file.download();

                // 'content' is a Buffer. Convert it to a string.
                const fileContent = content.toString('utf8');

                console.log(`Sending file to Gemma 3 for summarization`);
                const { text } = await ai.generate({
                    model: 'ollama/gemma3:4b',
                    prompt: `Summarize the following document in just a few sentences ${fileContent}`,
                });

                console.log(text);

            } catch (error: any) {

                console.error('An error occurred:', error.message);
            }
        } else {
            console.warn("CloudEvent bucket name is missing!", cloudevent);
        }
    } else {
        console.warn("CloudEvent data is missing!", cloudevent);
    }
});

Теперь в корневом каталоге crf-sidecar-gpu создайте файл package.json со следующим содержимым:

{
    "main": "lib/index.js",
    "name": "ingress-crf-genkit",
    "version": "1.0.0",
    "scripts": {
        "build": "tsc"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
        "@google-cloud/functions-framework": "^3.4.0",
        "@google-cloud/storage": "^7.0.0",
        "genkit": "^1.1.0",
        "genkitx-ollama": "^1.1.0",
        "@google/events": "^5.4.0"
    },
    "devDependencies": {
        "typescript": "^5.5.2"
    }
}

Создайте файл tsconfig.json также в корневом каталоге со следующим содержимым:

{
  "compileOnSave": true,
  "include": [
    "src"
  ],
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017",
    "skipLibCheck": true,
    "esModuleInterop": true
  }
}

6. Разверните функцию.

На этом шаге вы развернете функцию Cloud Run, выполнив следующую команду.

Примечание: максимальное количество экземпляров следует установить равным числу, меньшему или равному вашей квоте на использование графического процессора.

gcloud beta run deploy $FUNCTION_NAME \
  --region $REGION \
  --function gcs-cloudevent \
  --base-image nodejs22 \
  --source . \
  --no-allow-unauthenticated \
  --max-instances 2 # this should be less than or equal to your GPU quota

7. Создайте коляску.

Подробнее о размещении Ollama в сервисе Cloud Run можно узнать по ссылке: https://cloud.google.com/run/docs/tutorials/gpu-gemma-with-ollama

Перейдите в каталог вашего колясочного мотоцикла:

cd ../ollama-gemma3

Создайте файл Dockerfile со следующим содержимым:

FROM ollama/ollama:latest

# Listen on all interfaces, port 11434
ENV OLLAMA_HOST 0.0.0.0:11434

# Store model weight files in /models
ENV OLLAMA_MODELS /models

# Reduce logging verbosity
ENV OLLAMA_DEBUG false

# Never unload model weights from the GPU
ENV OLLAMA_KEEP_ALIVE -1

# Store the model weights in the container image
ENV MODEL gemma3:4b
RUN ollama serve & sleep 5 && ollama pull $MODEL

# Start Ollama
ENTRYPOINT ["ollama", "serve"]

Создайте образ

gcloud builds submit \
   --tag $REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO/ollama-gemma3 \
   --machine-type e2-highcpu-32

8. Обновите функцию с помощью сайдкара.

Чтобы добавить вспомогательный компонент (sidecar) к существующей службе, заданию или функции, вы можете обновить YAML-файл, указав в нем этот компонент.

Получите YAML-файл для только что развернутой функции Cloud Run, выполнив команду:

gcloud run services describe $FUNCTION_NAME --format=export > add-sidecar-service.yaml

Теперь добавьте сайдкар к CRf, обновив YAML-файл следующим образом:

  1. Вставьте следующий фрагмент YAML непосредственно перед строкой runtimeClassName: run.googleapis.com/linux-base-image-update . Параметр -image должен совпадать с параметром -image элемента контейнера ingress.
    - image: YOUR_IMAGE_SIDECAR:latest
        name: gemma-sidecar
        env:
        - name: OLLAMA_FLASH_ATTENTION
          value: '1'
        resources:
          limits:
            cpu: 6000m
            nvidia.com/gpu: '1'
            memory: 16Gi
        volumeMounts:
        - name: gcs-1
          mountPath: /root/.ollama
        startupProbe:
          failureThreshold: 2
          httpGet:
            path: /
            port: 11434
          initialDelaySeconds: 60
          periodSeconds: 60
          timeoutSeconds: 60
      nodeSelector:
        run.googleapis.com/accelerator: nvidia-l4
      volumes:
        - csi:
            driver: gcsfuse.run.googleapis.com
            volumeAttributes:
              bucketName: YOUR_BUCKET_GEMMA_NAME
          name: gcs-1
  1. Выполните следующую команду, чтобы обновить фрагмент YAML, добавив в него ваши переменные окружения:
sed -i "s|YOUR_IMAGE_SIDECAR|$IMAGE_SIDECAR|; s|YOUR_BUCKET_GEMMA_NAME|$BUCKET_GEMMA_NAME|" add-sidecar-service.yaml

В итоге ваш YAML-файл должен выглядеть примерно так:

##############################################
# DO NOT COPY - For illustration purposes only
##############################################

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  annotations:    
    run.googleapis.com/build-base-image: us-central1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/nodejs22
    run.googleapis.com/build-enable-automatic-updates: 'true'
    run.googleapis.com/build-function-target: gcs-cloudevent
    run.googleapis.com/build-id: f0122905-a556-4000-ace4-5c004a9f9ec6
    run.googleapis.com/build-image-uri:<YOUR_IMAGE_CRF>
    run.googleapis.com/build-name: <YOUR_BUILD_NAME>
    run.googleapis.com/build-source-location: <YOUR_SOURCE_LOCATION>
    run.googleapis.com/ingress: all
    run.googleapis.com/ingress-status: all
    run.googleapis.com/urls: '["<YOUR_CLOUD_RUN_FUNCTION_URLS"]'
  labels:
    cloud.googleapis.com/location: <YOUR_REGION>
  name: <YOUR_FUNCTION_NAME>
  namespace: '392295011265'
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/maxScale: '4'
        run.googleapis.com/base-images: '{"":"us-central1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/nodejs22"}'
        run.googleapis.com/client-name: gcloud
        run.googleapis.com/client-version: 514.0.0
        run.googleapis.com/startup-cpu-boost: 'true'
      labels:
        client.knative.dev/nonce: hzhhrhheyd
        run.googleapis.com/startupProbeType: Default
    spec:
      containerConcurrency: 80
      containers:
      - image: <YOUR_FUNCTION_IMAGE>
        ports:
        - containerPort: 8080
          name: http1
        resources:
          limits:
            cpu: 1000m
            memory: 512Mi
        startupProbe:
          failureThreshold: 1
          periodSeconds: 240
          tcpSocket:
            port: 8080
          timeoutSeconds: 240
      - image: <YOUR_SIDECAR_IMAGE>:latest
        name: gemma-sidecar
        env:
        - name: OLLAMA_FLASH_ATTENTION
          value: '1'
        resources:
          limits:
            cpu: 6000m
            nvidia.com/gpu: '1'
            memory: 16Gi
        volumeMounts:
        - name: gcs-1
          mountPath: /root/.ollama
        startupProbe:
          failureThreshold: 2
          httpGet:
            path: /
            port: 11434
          initialDelaySeconds: 60
          periodSeconds: 60
          timeoutSeconds: 60
      nodeSelector:
        run.googleapis.com/accelerator: nvidia-l4
      volumes:
        - csi:
            driver: gcsfuse.run.googleapis.com
            volumeAttributes:
              bucketName: <YOUR_BUCKET_NAME>
          name: gcs-1
      runtimeClassName: run.googleapis.com/linux-base-image-update
      serviceAccountName: <YOUR_SA_ADDRESS>
      timeoutSeconds: 300
  traffic:
  - latestRevision: true
    percent: 100

##############################################
# DO NOT COPY - For illustration purposes only
##############################################

Теперь обновите функцию, добавив в нее сайдкар, выполнив следующую команду.

gcloud run services replace add-sidecar-service.yaml

Наконец, создайте триггер EventArc для функции. Эта команда также добавит его к функции.

Примечание: если вы создали многорегиональный сегмент, вам потребуется изменить параметр --location

gcloud eventarc triggers create my-crf-summary-trigger  \
    --location=$REGION \
    --destination-run-service=$FUNCTION_NAME  \
    --destination-run-region=$REGION \
    --event-filters="type=google.cloud.storage.object.v1.finalized" \
    --event-filters="bucket=$BUCKET_DOCS_NAME" \
    --service-account=$SERVICE_ACCOUNT_ADDRESS

9. Проверьте свою функцию.

Upload a plain text file for summarization. Don't know what to summarize? Ask Gemini for a quick 1-2 page description of the history of dogs! Then upload that plain text file to your $BUCKET_DOCS_NAME bucket for the Gemma3:4b model to write a summary to the function logs.

В логах вы увидите нечто подобное следующему:

---------------
Processing for objects/dogs.txt
---------------
Attempting to download: <YOUR_PROJECT_ID>-codelab-crf-sidecar-gpu-docs/dogs.txt
Sending file to Gemma 3 for summarization
...
Here's a concise summary of the document "Humanity's Best Friend":
The dog's domestication, beginning roughly 20,000-40,000 years ago, represents a unique, deeply intertwined evolutionary partnership with humans, predating the domestication of any other animal
<...>
solidifying their long-standing role as humanity's best friend.

10. Устранение неполадок

Вот некоторые примеры опечаток, с которыми вы можете столкнуться:

  1. Если вы получаете ошибку о том, что PORT 8080 is in use , убедитесь, что ваш Dockerfile для вашего Ollama sidecar использует порт 11434. Также убедитесь, что вы используете правильный образ sidecar, если у вас в репозитории AR несколько образов Ollama. Функция Cloud Run работает на порту 8080, и если вы использовали другой образ Ollama в качестве sidecar, который также работает на порту 8080, вы столкнетесь с этой ошибкой.
  2. Если вы получаете ошибку " failed to build: (error ID: 7485c5b6): function.js does not exist , убедитесь, что файлы package.json и tsconfig.json находятся на том же уровне, что и каталог src.
  3. If you get error ERROR: (gcloud.run.services.replace) spec.template.spec.node_selector: Max instances must be set to 4 or fewer in order to set GPU requirements. , in your YAML file, change autoscaling.knative.dev/maxScale: '100' to 1 or to something less than or equal to your GPU quota.