无服务器 AI:使用 Cloud Run 嵌入 Gemma

1. 简介

在此 Codelab 中,您将学习如何使用 GPU 在 Cloud Run 上部署 EmbeddingGemma(一种强大的多语言文本嵌入模型)。然后,您将使用此已部署的服务为语义搜索应用生成嵌入。

与生成文本的传统大语言模型 (LLM) 不同,嵌入模型会将文本转换为数值向量。这些向量对于构建检索增强生成 (RAG) 系统至关重要,可让您找到与用户查询最相关的文档。

您将执行的操作

  • 使用 OllamaEmbeddingGemma 模型容器化。
  • 将容器部署到具有 GPU 加速功能的 Cloud Run
  • 通过为示例文本生成嵌入来测试已部署的模型。
  • 使用已部署的服务构建轻量级语义搜索系统。

所需条件

  • 启用了结算功能的 Google Cloud 项目。
  • 基本熟悉 Docker 和命令行。

2. 准备工作

项目设置

  1. 如果您还没有 Google 账号,则必须先创建一个 Google 账号
    • 请改用个人账号,而非工作账号或学校账号。工作账号和学校账号可能存在限制,导致您无法启用本实验所需的 API。
  2. 登录 Google Cloud 控制台
  3. 在 Cloud 控制台中启用结算功能
    • 完成本实验的 Cloud 资源费用应低于 1 美元。
    • 您可以按照本实验末尾的步骤删除资源,以避免产生更多费用。
    • 新用户符合参与 $300 USD 免费试用计划的条件。
  4. 创建新项目或选择重复使用现有项目。
    • 如果您看到有关项目配额的错误,请重复使用现有项目或删除现有项目以创建新项目。

启动 Cloud Shell

Cloud Shell 是在 Google Cloud 中运行的命令行环境,预加载了必要的工具。

  1. 点击 Google Cloud 控制台顶部的激活 Cloud Shell
  2. 连接到 Cloud Shell 后,验证您的身份验证:
    gcloud auth list
    
  3. 确认已选择您的项目:
    gcloud config get project
    
  4. 根据需要进行设置:
    gcloud config set project <YOUR_PROJECT_ID>
    

启用 API

运行以下命令可启用所有必需的 API:

gcloud services enable \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com

3. 将模型容器化

为了以无服务器方式运行 EmbeddingGemma,我们需要将其打包到容器中。我们将使用用于运行 LLM 的轻量级框架 OllamaDocker

创建 Dockerfile

在 Cloud Shell 中,为您的项目创建一个新目录并进入该目录:

mkdir embedding-gemma-codelab
cd embedding-gemma-codelab

创建一个名为 Dockerfile 的文件,其中包含以下内容:

FROM ollama/ollama:latest

# Listen on all interfaces, port 8080
ENV OLLAMA_HOST=0.0.0.0:8080

# 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=embeddinggemma:latest
RUN ollama serve & sleep 5 && ollama pull $MODEL

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

此 Dockerfile 执行以下操作:

  1. 从官方 Ollama 基础映像开始。
  2. 将 Ollama 配置为在端口 8080(Cloud Run 的默认端口)上监听。
  3. RUN 命令会启动 ollama 服务器,并在构建流程中下载 embeddinggemma 模型,以便将其烘焙到映像中。
  4. OLLAMA_KEEP_ALIVE=-1 设置为 true,以确保模型始终加载在 GPU 内存中,从而加快后续请求的速度。

4. 构建和部署

我们将使用 Cloud Run 源代码部署功能,通过一个步骤构建和部署容器。此命令使用 Cloud Build 构建映像,将其存储在 Artifact Registry 中,并将其部署到 Cloud Run。

运行以下命令以进行部署:

gcloud run deploy embedding-gemma \
  --source . \
  --region europe-west1 \
  --concurrency 4 \
  --cpu 8 \
  --set-env-vars OLLAMA_NUM_PARALLEL=4 \
  --gpu 1 \
  --gpu-type nvidia-l4 \
  --max-instances 1 \
  --memory 32Gi \
  --no-allow-unauthenticated \
  --no-cpu-throttling \
  --no-gpu-zonal-redundancy \
  --timeout=600 \
  --labels dev-tutorial=codelab-embedding-gemma

了解配置

  • --source . 指定当前目录作为 build 的来源。
  • --region europe-west1 我们使用支持 Cloud Run 上 GPU 的区域。
  • --concurrency 4 设置为与环境变量 OLLAMA_NUM_PARALLEL 的值匹配。
  • 具有 --gpu-type nvidia-l4--gpu 1 会为服务中的每个 Cloud Run 实例分配 1 个 NVIDIA L4 GPU。
  • --max-instances 1 用于指定扩缩到的最大实例数。该值必须小于或等于项目的 NVIDIA L4 GPU 配额。
  • --no-allow-unauthenticated 限制对服务进行未经身份验证的访问。通过将服务保持不公开状态,您可以依赖 Cloud Run 的内置 Identity and Access Management (IAM) 身份验证来实现服务到服务通信。
  • --no-cpu-throttling 对于启用 GPU 是必需的。
  • --no-gpu-zonal-redundancy 根据您的可用区故障切换要求和可用配额设置区域冗余选项。

地区注意事项

Cloud Run 上的 GPU 仅在特定区域可用。您可以在文档中查看支持的地区。

部署输出

几分钟后,部署将完成,您会看到类似如下的消息:

Service [embedding-gemma] revision [embedding-gemma-12345-abc] has been deployed and is serving 100 percent of traffic.
Service URL: https://embedding-gemma-123456789012.europe-west1.run.app

5. 测试部署

由于我们使用 --no-allow-unauthenticated 部署了服务,因此无法直接 curl 公开网址。我们首先需要授予自己访问该服务并在请求中使用身份验证令牌的权限。

  1. 向您的用户账号授予调用服务的权限:
    gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
        --member=user:$(gcloud config get-value account) \
        --role='roles/run.invoker'
    
  2. 使用以下命令,将您的 Google Cloud 凭证和项目编号保存在环境变量中,以便在请求中使用:
    export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
    export ID_TOKEN=$(gcloud auth print-identity-token)
    
  3. 运行以下命令,为“示例文本”生成嵌入内容:
    curl -X POST "https://embedding-gemma-$PROJECT_NUMBER.europe-west1.run.app/api/embed" \
        -H "Authorization: Bearer $ID_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
            "model": "embeddinggemma",
            "input": "Sample text"
        }'
    

您应该会看到一个 JSON 响应,其中包含 embedding 字段下的向量(一个很长的数字列表)。这表明您的无服务器 GPU 支持的嵌入模型正在正常运行!

响应将类似如下所示:EmbeddingGemma Curl 输出

Python 客户端

您还可以使用 Python 与该服务进行交互。创建一个名为 test_client.py 的文件:

import urllib.request
import urllib.parse
import json
import os

# 1. Setup the URL and Payload
url = f"https://embedding-gemma-{os.environ['PROJECT_NUMBER']}.europe-west1.run.app/api/embed"
payload = {
    "model": "embeddinggemma",
    "input": "Sample text"
}

# 2. Create the Request object
# Note: Providing 'data' automatically makes this a POST request
req = urllib.request.Request(
    url,
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {os.environ['ID_TOKEN']}",
        "Content-Type": "application/json"
    }
)

# 3. Execute and print the response
response = urllib.request.urlopen(req)
result = json.loads(response.read().decode("utf-8"))
print(result)

运行该文件:

python test_client.py

6. 构建语义搜索应用

现在,我们已经有了一个可正常运行的嵌入服务,接下来我们来构建一个简单的语义搜索应用。我们将使用生成的嵌入来查找与给定查询最相关的文档。

依赖项

我们将使用 chromadb 作为矢量数据库和 ollama 客户端库。

uv init semantic-search --description "Semantic Search Application"
cd semantic-search
uv add chromadb ollama

创建搜索应用

创建一个名为 semantic_search.py 且包含以下代码的文件:

import ollama
import chromadb
import os

# 1. Define our knowledge base
documents = [
    "Poland is a country located in Central Europe.",
    "The capital and largest city of Poland is Warsaw.",
    "Poland's official language is Polish, which is a West Slavic language.",
    "Marie Curie, the pioneering scientist who conducted groundbreaking research on radioactivity, was born in Warsaw, Poland.",
    "Poland is famous for its traditional dish called pierogi, which are filled dumplings.",
    "The Białowieża Forest in Poland is one of the last and largest remaining parts of the immense primeval forest that once stretched across the European Plain.",
]

print("Initializing Vector Database...")
client = chromadb.Client()
collection = client.create_collection(name="docs")

# Configure the client to point to our Cloud Run proxy
ollama_client = ollama.Client(
    host=f"https://embedding-gemma-{os.environ['PROJECT_NUMBER']}.europe-west1.run.app",
    headers={'Authorization': 'Bearer ' + os.environ['ID_TOKEN']}
)

print("Generating embeddings and indexing documents...")
# 2. Store each document in the vector database
for i, d in enumerate(documents):
    # This calls our Cloud Run service to get the embedding
    response = ollama_client.embed(model="embeddinggemma", input=d)
    embeddings = response["embeddings"]
    collection.add(ids=[str(i)], embeddings=embeddings, documents=[d])

print("Indexing complete.\n")

# 3. Perform a Semantic Search
question = "What is Poland's official language?"
print(f"Query: {question}")

# Generate an embedding for the question
response = ollama_client.embed(model="embeddinggemma", input=question)

# Query the database for the most similar document
results = collection.query(
    query_embeddings=[response["embeddings"][0]],
    n_results=1
)

best_match = results["documents"][0][0]
print(f"Best Match Document: {best_match}")

运行应用

执行脚本:

uv run semantic_search.py

您会看到如下所示的输出:

Initializing Vector Database...
Generating embeddings and indexing documents...
Indexing complete.

Query: What is Poland's official language?
Best Match Document: Poland's official language is Polish, which is a West Slavic language.

此脚本演示了 RAG 系统的核心:使用无服务器 EmbeddingGemma 服务将文档和查询都转换为向量,从而让您能够找到回答用户问题所需的准确信息。

7. 清理

为避免系统向您的 Google Cloud 账号持续收取费用,请删除本 Codelab 中创建的资源。

删除 Cloud Run 服务

gcloud run services delete embedding-gemma --region europe-west1 --quiet

删除容器映像

gcloud artifacts docker images delete \
    europe-west1-docker.pkg.dev/${GOOGLE_CLOUD_PROJECT}/cloud-run-source-deploy/embedding-gemma \
    --quiet

8. 恭喜

恭喜!您已成功在 Cloud Run 上部署了 EmbeddingGemma(使用 GPU),并使用它来支持语义搜索应用。

现在,您已拥有可扩缩的无服务器基础,可用于构建需要理解文本含义的 AI 应用。

您学到的内容

  • 如何使用 Docker 将 Ollama 模型容器化。
  • 如何将支持 GPU 的服务部署到 Cloud Run。
  • 如何使用已部署的模型进行语义搜索 (RAG)。

后续步骤

参考文档