نحوه میزبانی LLM در سایدکار برای عملکرد Cloud Run

۱. مقدمه

نمای کلی

در این آزمایشگاه کد، یاد خواهید گرفت که چگونه یک مدل gemma3:4b را در یک محفظه کناری برای تابع Cloud Run میزبانی کنید. وقتی یک فایل در یک مخزن ذخیره‌سازی ابری آپلود می‌شود، تابع Cloud Run را فعال می‌کند. این تابع محتویات فایل را برای خلاصه‌سازی به Gemma 3 در محفظه کناری ارسال می‌کند.

آنچه یاد خواهید گرفت

  • چگونه با استفاده از یک تابع Cloud Run و یک LLM که در یک ماشین کناری با استفاده از GPU ها میزبانی شده است، استنتاج انجام دهیم؟
  • نحوه استفاده از پیکربندی خروجی مستقیم VPC برای یک پردازنده گرافیکی Cloud Run برای آپلود و سرویس‌دهی سریع‌تر مدل
  • نحوه استفاده از genkit برای ارتباط با مدل ollama میزبانی شده شما

۲. قبل از شروع

برای استفاده از ویژگی GPUها، باید برای یک منطقه پشتیبانی‌شده درخواست افزایش سهمیه دهید. سهمیه مورد نیاز nvidia_l4_gpu_allocation_no_zonal_redundancy است که تحت API Cloud Run Admin قرار دارد. در اینجا لینک مستقیم درخواست سهمیه آمده است.

۳. تنظیمات و الزامات

متغیرهای محیطی که در سراسر این آزمایشگاه کد استفاده خواهند شد را تنظیم کنید.

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 استفاده می‌شود، به عنوان حساب سرویس برای تریگر eventarc جهت فراخوانی تابع Cloud Run استفاده خواهیم کرد. در صورت تمایل می‌توانید یک SA متفاوت برای Eventarc ایجاد کنید.

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

و سپس به SA دسترسی به سطل را بدهید.

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

حالا یک سطل منطقه‌ای ایجاد کنید که اسنادی را که می‌خواهید خلاصه شوند، ذخیره کند. می‌توانید از یک سطل چند منطقه‌ای نیز استفاده کنید، به شرطی که تریگر Eventarc را متناسب با آن به‌روزرسانی کنید (در انتهای این کد نشان داده شده است).

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

۴. مدل Gemma 3 را دانلود کنید

ابتدا، باید مدل Gemma 3 4b را از ollama دانلود کنید. می‌توانید این کار را با نصب ollama و سپس اجرای مدل gemma3:4b به صورت محلی انجام دهید.

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

اکنون در یک پنجره ترمینال جداگانه، دستور زیر را برای نمایش مدل اجرا کنید. اگر از Cloud Shell استفاده می‌کنید، می‌توانید با کلیک روی نماد به علاوه در نوار منوی بالا سمت راست، یک پنجره ترمینال اضافی باز کنید.

ollama run gemma3:4b

وقتی اولاما اجرا شد، می‌توانید از مدل چند سوال بپرسید، مثلاً

"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

۵. تابع 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
  }
}

۶. تابع را مستقر کنید

در این مرحله، با اجرای دستور زیر، تابع 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

۷. اتاقک کناری را ایجاد کنید

می‌توانید اطلاعات بیشتری در مورد میزبانی 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

۸. تابع را با sidecar به‌روزرسانی کنید

برای افزودن یک sidecar به یک سرویس، کار یا تابع موجود، می‌توانید فایل YAML را به‌روزرسانی کنید تا sidecar را در خود جای دهد.

با اجرای دستور زیر، فایل YAML مربوط به تابع Cloud Run که به تازگی مستقر کرده‌اید را بازیابی کنید:

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

حالا با به‌روزرسانی YAML به صورت زیر، sidecar را به CRf اضافه کنید:

  1. قطعه کد YAML زیر را مستقیماً بالای خط runtimeClassName: run.googleapis.com/linux-base-image-update وارد کنید. آیتم -image ‎ باید با آیتم -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
  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
##############################################

حالا با اجرای دستور زیر، تابع را با sidecar به‌روزرسانی کنید.

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

۹. عملکرد خود را آزمایش کنید

برای خلاصه‌سازی، یک فایل متنی ساده آپلود کنید. نمی‌دانید چه چیزی را خلاصه کنید؟ از Gemini بخواهید توضیحی سریع در حد ۱-۲ صفحه در مورد تاریخچه سگ‌ها به شما بدهد! سپس آن فایل متنی ساده را در باکت $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.

۱۰. عیب‌یابی

در اینجا چند اشتباه تایپی وجود دارد که ممکن است با آنها مواجه شوید:

  1. اگر با خطایی مبنی PORT 8080 is in use مواجه شدید، مطمئن شوید که Dockerfile مربوط به Ollama sidecar شما از پورت ۱۱۴۳۴ استفاده می‌کند. همچنین در صورتی که چندین تصویر Ollama در مخزن AR خود دارید، مطمئن شوید که از تصویر sidecar صحیح استفاده می‌کنید. تابع Cloud Run روی پورت ۸۰۸۰ کار می‌کند و اگر از تصویر Ollama متفاوتی به عنوان sidecar استفاده کرده باشید که روی ۸۰۸۰ نیز کار می‌کند، با این خطا مواجه خواهید شد.
  2. اگر با خطای failed to build: (error ID: 7485c5b6): function.js does not exist مواجه شدید، مطمئن شوید که فایل‌های package.json و tsconfig.json شما در همان سطح دایرکتوری src قرار دارند.
  3. اگر با خطای 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 خود تغییر دهید.