1. はじめに
概要
この Codelab では、Cloud Run 関数のサイドカーで gemma3:4b モデルをホストする方法について説明します。ファイルが Cloud Storage バケットにアップロードされると、Cloud Run functions の関数がトリガーされます。この関数は、要約のためにサイドカー内の Gemma 3 にファイルの内容を送信します。
学習内容
- GPU を使用してサイドカーでホストされている LLM と Cloud Run 関数を使用して推論を行う方法
- モデルのアップロードとサービングを高速化するために Cloud Run GPU でダイレクト VPC 下り(外向き)構成を使用する方法
- genkit を使用してホストされている ollama モデルとインターフェースする方法
2. 始める前に
GPU 機能を使用するには、サポートされているリージョンの割り当ての増加をリクエストする必要があります。必要な割り当ては nvidia_l4_gpu_allocation_no_zonal_redundancy で、Cloud Run Admin API でリクエストします。割り当てをリクエストするための直接リンクはこちらです。
3. 設定と要件
この Codelab 全体で使用する環境変数を設定します。
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"
Cloud Run 関数の ID として使用されているのと同じサービス アカウントを、Cloud Run 関数を呼び出す eventarc トリガーのサービス アカウントとして使用します。必要に応じて、Eventarc 用に別の SA を作成できます。
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"
ファインチューニングされたモデルをホストするバケットを作成します。この Codelab では、リージョン バケットを使用します。マルチリージョン バケットを使用することもできます。
gsutil mb -l $REGION gs://$BUCKET_GEMMA_NAME
次に、SA にバケットへのアクセス権を付与します。
gcloud storage buckets add-iam-policy-binding gs://$BUCKET_GEMMA_NAME \
--member=serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
--role=roles/storage.objectAdmin
要約するドキュメントを保存するリージョン バケットを作成します。マルチリージョン バケットも使用できます。ただし、それに応じて Eventarc トリガーを更新する必要があります(この 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 イメージ用の Artifact Registry リポジトリを作成する
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 モデルをダウンロードする
まず、ollama から Gemma 3 4b モデルをダウンロードします。これを行うには、ollama をインストールしてから、gemma3:4b モデルをローカルで実行します。
curl -fsSL https://ollama.com/install.sh | sh
ollama serve
別のターミナル ウィンドウで、次のコマンドを実行してモデルをプルダウンします。Cloud Shell を使用している場合は、右上にあるメニューバーのプラスアイコンをクリックして、追加のターミナル ウィンドウを開くことができます。
ollama run gemma3:4b
ollama が実行されたら、モデルに質問してみましょう。たとえば、次のようにします。
"why is the sky blue?"
ollama とのチャットが完了したら、次のコマンドを実行してチャットを終了できます。
/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
Cloud Workstations を使用している場合は、ダウンロードした 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 functions を作成する
ソースコードのルートフォルダを作成します。
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 関数をデプロイします。
注: インスタンスの最大数は、GPU 割り当て以下の数に設定する必要があります。
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. サイドカーを作成する
Cloud Run サービス内での Ollama のホスティングの詳細については、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. サイドカーを使用して関数を更新する
既存のサービス、ジョブ、関数にサイドカーを追加するには、サイドカーを含むように YAML ファイルを更新します。
次のコマンドを実行して、デプロイした Cloud Run functions の YAML を取得します。
gcloud run services describe $FUNCTION_NAME --format=export > add-sidecar-service.yaml
次に、次のように YAML を更新して、サイドカーを CRf に追加します。
- 次の YAML フラグメントを
runtimeClassName: run.googleapis.com/linux-base-image-update行の直前に挿入します。-imageは、Ingress コンテナ アイテムの-imageと一致する必要があります。
- 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
- 次のコマンドを実行して、環境変数で 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. 関数をテストする
要約するプレーン テキスト ファイルをアップロードします。要約する内容がわからない場合は、犬の歴史について 1 ~ 2 ページで説明して。次に、そのプレーン テキスト ファイルを $BUCKET_DOCS_NAME バケットにアップロードして、Gemma3:4b モデルが関数ログに要約を書き込むようにします。
ログには、次のような内容が表示されます。
---------------
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. トラブルシューティング
発生する可能性のある誤字脱字のエラーを次に示します。
PORT 8080 is in useというエラーが表示された場合は、Ollama サイドカーの Dockerfile でポート 11434 が使用されていることを確認してください。AR リポジトリに複数の Ollama イメージがある場合は、正しいサイドカー イメージを使用していることを確認してください。Cloud Run 関数はポート 8080 で提供されます。8080 で提供されるサイドカーとして別の Ollama イメージを使用した場合、このエラーが発生します。- エラー
failed to build: (error ID: 7485c5b6): function.js does not existが発生した場合は、package.json ファイルと tsconfig.json ファイルが src ディレクトリと同じレベルにあることを確認してください。 - エラー
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.が発生した場合は、YAML ファイルでautoscaling.knative.dev/maxScale: '100'を 1 に変更するか、GPU 割り当て以下の値に変更します。