AlloyDB Omni et modèle d'IA local sur Kubernetes

1. Introduction

Dans cet atelier de programmation, vous allez apprendre à déployer AlloyDB Omni sur GKE et à l'utiliser avec un modèle d'encapsulation ouvert déployé dans le même cluster Kubernetes. Le déploiement d'un modèle à côté de l'instance de base de données dans le même cluster GKE réduit la latence et les dépendances vis-à-vis des services tiers. De plus, des exigences de sécurité peuvent l'exiger lorsque les données ne doivent pas quitter l'organisation et que l'utilisation de services tiers n'est pas autorisée.

391e4244b25a7db0.png

Prérequis

  • Connaissances de base de la console Google Cloud
  • Compétences de base concernant l'interface de ligne de commande et Cloud Shell

Points abordés

  • Déployer AlloyDB Omni sur un cluster Google Kubernetes
  • Se connecter à AlloyDB Omni
  • Charger des données dans AlloyDB Omni
  • Déployer un modèle d'encapsulation ouvert sur GKE
  • Enregistrer un modèle d'encapsulation dans AlloyDB Omni
  • Générer des représentations vectorielles continues pour la recherche sémantique
  • Utiliser des représentations vectorielles continues générées pour la recherche sémantique dans AlloyDB Omni
  • Créer et utiliser des index vectoriels dans AlloyDB

Prérequis

  • Un compte Google Cloud et un projet Google Cloud
  • Un navigateur Web tel que Chrome compatible avec la console Google Cloud et Cloud Shell

2. Préparation

Configuration de l'environnement au rythme de chacun

  1. Connectez-vous à la console Google Cloud, puis créez un projet ou réutilisez un projet existant. Si vous n'avez pas encore de compte Gmail ou Google Workspace, vous devez en créer un.

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • Le nom du projet est le nom à afficher pour les participants au projet. Il s'agit d'une chaîne de caractères non utilisée par les API Google. Vous pourrez toujours le modifier.
  • L'ID du projet est unique parmi tous les projets Google Cloud et non modifiable une fois défini. La console Cloud génère automatiquement une chaîne unique (en général, vous n'y accordez d'importance particulière). Dans la plupart des ateliers de programmation, vous devrez indiquer l'ID de votre projet (généralement identifié par PROJECT_ID). Si l'ID généré ne vous convient pas, vous pouvez en générer un autre de manière aléatoire. Vous pouvez également en spécifier un et voir s'il est disponible. Après cette étape, l'ID n'est plus modifiable et restera donc le même pour toute la durée du projet.
  • Pour information, il existe une troisième valeur (le numéro de projet) que certaines API utilisent. Pour en savoir plus sur ces trois valeurs, consultez la documentation.
  1. Vous devez ensuite activer la facturation dans la console Cloud pour utiliser les ressources/API Cloud. L'exécution de cet atelier de programmation est très peu coûteuse, voire sans frais. Pour désactiver les ressources et éviter ainsi que des frais ne vous soient facturés après ce tutoriel, vous pouvez supprimer le projet ou les ressources que vous avez créées. Les nouveaux utilisateurs de Google Cloud peuvent participer au programme d'essai sans frais pour bénéficier d'un crédit de 300 $.

Démarrer Cloud Shell

Bien que Google Cloud puisse être utilisé à distance depuis votre ordinateur portable, nous allons nous servir de Google Cloud Shell pour cet atelier de programmation, un environnement de ligne de commande exécuté dans le cloud.

Dans la console Google Cloud, cliquez sur l'icône Cloud Shell dans la barre d'outils supérieure :

55efc1aaa7a4d3ad.png

Le provisionnement et la connexion à l'environnement prennent quelques instants seulement. Une fois l'opération terminée, le résultat devrait ressembler à ceci :

7ffe5cbb04455448.png

Cette machine virtuelle contient tous les outils de développement nécessaires. Elle comprend un répertoire d'accueil persistant de 5 Go et s'exécute sur Google Cloud, ce qui améliore nettement les performances du réseau et l'authentification. Vous pouvez effectuer toutes les tâches de cet atelier de programmation dans un navigateur. Vous n'avez rien à installer.

3. Avant de commencer

Activer l'API

Résultat :

Dans Cloud Shell, assurez-vous que l'ID de votre projet est configuré :

PROJECT_ID=$(gcloud config get-value project)
echo $PROJECT_ID

Si ce n'est pas le cas dans la configuration de Cloud Shell, configurez-le à l'aide des commandes suivantes.

export PROJECT_ID=<your project>
gcloud config set project $PROJECT_ID

Activez tous les services nécessaires :

gcloud services enable compute.googleapis.com
gcloud services enable container.googleapis.com

Résultat attendu

student@cloudshell:~ (test-project-001-402417)$ PROJECT_ID=test-project-001-402417
student@cloudshell:~ (test-project-001-402417)$ gcloud config set project test-project-001-402417
Updated property [core/project].
student@cloudshell:~ (test-project-001-402417)$ gcloud services enable compute.googleapis.com
gcloud services enable container.googleapis.com
Operation "operations/acat.p2-4470404856-1f44ebd8-894e-4356-bea7-b84165a57442" finished successfully.

4. Déployer AlloyDB Omni sur GKE

Pour déployer AlloyDB Omni sur GKE, nous devons préparer un cluster Kubernetes en suivant les exigences listées dans les Exigences concernant l'opérateur AlloyDB Omni.

Créer un cluster GKE

Nous devons déployer un cluster GKE standard avec une configuration de pool suffisante pour déployer un pod avec une instance AlloyDB Omni. Omni nécessite au moins deux processeurs et 8 Go de RAM, avec un peu d'espace pour les services d'opérateur et de surveillance.

Configurez les variables d'environnement pour votre déploiement.

export PROJECT_ID=$(gcloud config get project)
export LOCATION=us-central1
export CLUSTER_NAME=alloydb-ai-gke
export MACHINE_TYPE=e2-standard-4

Nous utilisons ensuite gcloud pour créer le cluster GKE standard.

gcloud container clusters create ${CLUSTER_NAME} \
  --project=${PROJECT_ID} \
  --region=${LOCATION} \
  --workload-pool=${PROJECT_ID}.svc.id.goog \
  --release-channel=rapid \
  --machine-type=${MACHINE_TYPE} \
  --num-nodes=1

Résultat attendu sur la console :

student@cloudshell:~ (gleb-test-short-001-415614)$ export PROJECT_ID=$(gcloud config get project)
export LOCATION=us-central1
export CLUSTER_NAME=alloydb-ai-gke
export MACHINE_TYPE=n2-highmem-2
Your active configuration is: [gleb-test-short-001-415614]
student@cloudshell:~ (gleb-test-short-001-415614)$ gcloud container clusters create ${CLUSTER_NAME} \
  --project=${PROJECT_ID} \
  --region=${LOCATION} \
  --workload-pool=${PROJECT_ID}.svc.id.goog \
  --release-channel=rapid \
  --machine-type=${MACHINE_TYPE} \
  --num-nodes=1
Note: The Kubelet readonly port (10255) is now deprecated. Please update your workloads to use the recommended alternatives. See https://cloud.google.com/kubernetes-engine/docs/how-to/disable-kubelet-readonly-port for ways to check usage and for migration instructions.
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s).
Creating cluster alloydb-ai-gke in us-central1..


NAME: omni01
ZONE: us-central1-a
MACHINE_TYPE: e2-standard-4
PREEMPTIBLE: 
INTERNAL_IP: 10.128.0.3
EXTERNAL_IP: 35.232.157.123
STATUS: RUNNING
student@cloudshell:~ (gleb-test-short-001-415614)$ 

Préparer le cluster

Nous devons installer les composants requis, tels que le service cert-manager. Nous pouvons suivre les étapes de la documentation pour installer cert-manager.

Nous utilisons l'outil de ligne de commande Kubernetes, kubectl, qui est déjà installé dans Cloud Shell. Avant d'utiliser l'utilitaire, nous devons obtenir les identifiants de notre cluster.

gcloud container clusters get-credentials ${CLUSTER_NAME} --region=${LOCATION}

Nous pouvons maintenant utiliser kubectl pour installer cert-manager:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml

Résultat attendu sur la console (masqué) :

student@cloudshell:~$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.2/cert-manager.yaml
namespace/cert-manager created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
...
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created

Installer AlloyDB Omni

Installer l'opérateur AlloyDB Omni peut être installé à l'aide de l'utilitaire Helm.

Exécutez la commande suivante pour installer l'opérateur AlloyDB Omni:

export GCS_BUCKET=alloydb-omni-operator
export HELM_PATH=$(gcloud storage cat gs://$GCS_BUCKET/latest)
export OPERATOR_VERSION="${HELM_PATH%%/*}"
gcloud storage cp gs://$GCS_BUCKET/$HELM_PATH ./ --recursive
helm install alloydbomni-operator alloydbomni-operator-${OPERATOR_VERSION}.tgz \
--create-namespace \
--namespace alloydb-omni-system \
--atomic \
--timeout 5m

Résultat attendu sur la console (masqué) :

student@cloudshell:~$ gcloud storage cp gs://$GCS_BUCKET/$HELM_PATH ./ --recursive
Copying gs://alloydb-omni-operator/1.2.0/alloydbomni-operator-1.2.0.tgz to file://./alloydbomni-operator-1.2.0.tgz
  Completed files 1/1 | 126.5kiB/126.5kiB
student@cloudshell:~$ helm install alloydbomni-operator alloydbomni-operator-${OPERATOR_VERSION}.tgz \
> --create-namespace \
> --namespace alloydb-omni-system \
> --atomic \
> --timeout 5m
NAME: alloydbomni-operator
LAST DEPLOYED: Mon Jan 20 13:13:20 2025
NAMESPACE: alloydb-omni-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
student@cloudshell:~$

Une fois l'opérateur AlloyDB Omni installé, nous pourrons suivre le déploiement de notre cluster de base de données.

Voici un exemple de fichier manifeste de déploiement avec le paramètre googleMLExtension activé et un équilibreur de charge interne (privé) :

apiVersion: v1
kind: Secret
metadata:
  name: db-pw-my-omni
type: Opaque
data:
  my-omni: "VmVyeVN0cm9uZ1Bhc3N3b3Jk"
---
apiVersion: alloydbomni.dbadmin.goog/v1
kind: DBCluster
metadata:
  name: my-omni
spec:
  databaseVersion: "15.7.0"
  primarySpec:
    adminUser:
      passwordRef:
        name: db-pw-my-omni
    features:
      googleMLExtension:
        enabled: true
    resources:
      cpu: 1
      memory: 8Gi
      disks:
      - name: DataDisk
        size: 20Gi
        storageClass: standard
    dbLoadBalancerOptions:
      annotations:
        networking.gke.io/load-balancer-type: "internal"
  allowExternalIncomingTraffic: true

La valeur secrète du mot de passe est une représentation Base64 du mot de passe "VeryStrongPassword". Le moyen le plus fiable consiste à utiliser Secret Manager de Google pour stocker la valeur du mot de passe. Pour en savoir plus, consultez la documentation.

Enregistrez le fichier manifeste sous le nom my-omni.yaml pour qu'il soit appliqué à l'étape suivante. Si vous êtes dans Cloud Shell, vous pouvez le faire à l'aide de l'éditeur en appuyant sur le bouton "Ouvrir l'éditeur" en haut à droite du terminal.

47ab85dad9afdff7.png

Après avoir enregistré le fichier sous le nom my-omni.yaml, revenez au terminal en appuyant sur le bouton "Ouvrir le terminal".

b9b7747b39dbe8c7.png

Appliquez le fichier manifeste my-omni.yaml au cluster à l'aide de l'utilitaire kubectl:

kubectl apply -f my-omni.yaml

Résultat attendu sur la console :

secret/db-pw-my-omni created
dbcluster.alloydbomni.dbadmin.goog/my-omni created

Vérifiez l'état de votre cluster my-omni à l'aide de l'utilitaire kubectl:

kubectl get dbclusters.alloydbomni.dbadmin.goog my-omni -n default

Pendant le déploiement, le cluster passe par différentes phases et doit finalement se terminer par l'état DBClusterReady.

Résultat attendu sur la console :

$ kubectl get dbclusters.alloydbomni.dbadmin.goog my-omni -n default
NAME      PRIMARYENDPOINT   PRIMARYPHASE   DBCLUSTERPHASE   HAREADYSTATUS   HAREADYREASON
my-omni   10.131.0.33        Ready          DBClusterReady

Se connecter à AlloyDB Omni

Se connecter à l'aide d'un pod Kubernetes

Lorsque le cluster est prêt, nous pouvons utiliser les binaires du client PostgreSQL sur le pod de l'instance AlloyDB Omni. Nous trouvons l'ID du pod, puis utilisons kubectl pour nous connecter directement au pod et exécuter le logiciel client. Le mot de passe est "VeryStrongPassword", comme défini via le hachage dans my-omni.yaml:

DB_CLUSTER_NAME=my-omni
DB_CLUSTER_NAMESPACE=default
DBPOD=`kubectl get pod --selector=alloydbomni.internal.dbadmin.goog/dbcluster=$DB_CLUSTER_NAME,alloydbomni.internal.dbadmin.goog/task-type=database -n $DB_CLUSTER_NAMESPACE -o jsonpath='{.items[0].metadata.name}'`
kubectl exec -ti $DBPOD -n $DB_CLUSTER_NAMESPACE -c database -- psql -h localhost -U postgres

Exemple de sortie de console:

DB_CLUSTER_NAME=my-omni
DB_CLUSTER_NAMESPACE=default
DBPOD=`kubectl get pod --selector=alloydbomni.internal.dbadmin.goog/dbcluster=$DB_CLUSTER_NAME,alloydbomni.internal.dbadmin.goog/task-type=database -n $DB_CLUSTER_NAMESPACE -o jsonpath='{.items[0].metadata.name}'`
kubectl exec -ti $DBPOD -n $DB_CLUSTER_NAMESPACE -c database -- psql -h localhost -U postgres
Password for user postgres: 
psql (15.7)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_128_GCM_SHA256, compression: off)
Type "help" for help.

postgres=#

5. Déployer un modèle d'IA sur GKE

Pour tester l'intégration de l'IA AlloyDB Omni avec des modèles locaux, nous devons déployer un modèle sur le cluster.

Créer un pool de nœuds pour le modèle

Pour exécuter le modèle, nous devons préparer un pool de nœuds pour exécuter l'inférence. Du point de vue des performances, la meilleure approche consiste à utiliser un pool avec des accélérateurs graphiques utilisant une configuration de nœud telle que g2-standard-8 avec un accélérateur Nvidia L4.

Créez le pool de nœuds avec l'accélérateur L4:

export PROJECT_ID=$(gcloud config get project)
export LOCATION=us-central1
export CLUSTER_NAME=alloydb-ai-gke
gcloud container node-pools create gpupool \
  --accelerator type=nvidia-l4,count=1,gpu-driver-version=latest \
  --project=${PROJECT_ID} \
  --location=${LOCATION} \
  --node-locations=${LOCATION}-a \
  --cluster=${CLUSTER_NAME} \
  --machine-type=g2-standard-8 \
  --num-nodes=1

Résultat attendu

student@cloudshell$ export PROJECT_ID=$(gcloud config get project)
Your active configuration is: [pant]
export LOCATION=us-central1
export CLUSTER_NAME=alloydb-ai-gke
student@cloudshell$ gcloud container node-pools create gpupool \
>   --accelerator type=nvidia-l4,count=1,gpu-driver-version=latest \
>   --project=${PROJECT_ID} \
>   --location=${LOCATION} \
>   --node-locations=${LOCATION}-a \
>   --cluster=${CLUSTER_NAME} \
>   --machine-type=g2-standard-8 \
>   --num-nodes=1
Note: Machines with GPUs have certain limitations which may affect your workflow. Learn more at https://cloud.google.com/kubernetes-engine/docs/how-to/gpus
Note: Starting in GKE 1.30.1-gke.115600, if you don't specify a driver version, GKE installs the default GPU driver for your node's GKE version.
Creating node pool gpupool...done.
Created [https://container.googleapis.com/v1/projects/student-test-001/zones/us-central1/clusters/alloydb-ai-gke/nodePools/gpupool].
NAME     MACHINE_TYPE   DISK_SIZE_GB  NODE_VERSION
gpupool  g2-standard-8  100           1.31.4-gke.1183000

Préparer le fichier manifeste de déploiement

Pour déployer le modèle, nous devons préparer un fichier manifeste de déploiement.

Nous utilisons le modèle d'embedding BGE Base v1.5 de Hugging Face. Pour consulter la fiche du modèle, cliquez ici. Pour déployer le modèle, nous pouvons utiliser les instructions déjà préparées de Hugging Face et le package de déploiement de GitHub.

Cloner le package

git clone https://github.com/huggingface/Google-Cloud-Containers

Modifiez le fichier manifeste en remplaçant la valeur cloud.google.com/gke-accelerator par nvidia-l4 et en ajoutant des limites aux ressources.

vi Google-Cloud-Containers/examples/gke/tei-deployment/gpu-config/deployment.yaml

Voici un fichier manifeste corrigé.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: tei-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tei-server
  template:
    metadata:
      labels:
        app: tei-server
        hf.co/model: Snowflake--snowflake-arctic-embed-m
        hf.co/task: text-embeddings
    spec:
      containers:
        - name: tei-container
          image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-embeddings-inference-cu122.1-4.ubuntu2204:latest
          resources:
            requests:
              nvidia.com/gpu: 1
            limits:
              nvidia.com/gpu: 1
          env:
            - name: MODEL_ID
              value: Snowflake/snowflake-arctic-embed-m
            - name: NUM_SHARD
              value: "1"
            - name: PORT
              value: "8080"
          volumeMounts:
            - mountPath: /dev/shm
              name: dshm
            - mountPath: /data
              name: data
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 1Gi
        - name: data
          emptyDir: {}
      nodeSelector:
        cloud.google.com/gke-accelerator: nvidia-l4

Déployer le modèle

Nous devons préparer un compte de service et un espace de noms pour le déploiement.

Créez un espace de noms Kubernetes hf-gke-namespace.

export NAMESPACE=hf-gke-namespace
kubectl create namespace $NAMESPACE

Créer un compte de service Kubernetes

export SERVICE_ACCOUNT=hf-gke-service-account
kubectl create serviceaccount $SERVICE_ACCOUNT --namespace $NAMESPACE

Déployer le modèle

kubectl apply -f Google-Cloud-Containers/examples/gke/tei-deployment/gpu-config

Vérifier les déploiements

kubectl get pods

Vérifier le service de modèle

kubectl get service tei-service

Il est censé afficher le type de service en cours d'exécution ClusterIP.

Exemple de résultat :

student@cloudshell$ kubectl get service tei-service
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
tei-service   ClusterIP   34.118.233.48   <none>        8080/TCP   10m

Nous allons utiliser l'adresse CLUSTER-IP du service comme adresse de point de terminaison. L'encapsulation du modèle peut répondre par l'URI http://34.118.233.48:8080/embed. Il sera utilisé plus tard lorsque vous enregistrerez le modèle dans AlloyDB Omni.

Nous pouvons le tester en l'exposant à l'aide de la commande kubectl port-forward.

kubectl port-forward service/tei-service 8080:8080

Le transfert de port s'exécute dans une session Cloud Shell. Nous avons besoin d'une autre session pour le tester.

Ouvrez un autre onglet Cloud Shell à l'aide du signe "+" situé en haut.

4ca978f5142bb6ce.png

Exécutez une commande curl dans la nouvelle session shell.

curl http://localhost:8080/embed \
    -X POST \
    -d '{"inputs":"Test"}' \
    -H 'Content-Type: application/json'

Elle doit renvoyer un tableau de vecteurs semblable à l'exemple de résultat suivant (masqué):

curl http://localhost:8080/embed \
>     -X POST \
>     -d '{"inputs":"Test"}' \
>     -H 'Content-Type: application/json'
[[-0.018975832,0.0071419072,0.06347208,0.022992613,0.014205903
...
-0.03677433,0.01636146,0.06731572]]

6. Enregistrer le modèle dans AlloyDB Omni

Pour tester le fonctionnement d'AlloyDB Omni avec le modèle déployé, nous devons créer une base de données et enregistrer le modèle.

Créer une base de données

Créez une VM GCE en tant que jump box, connectez-vous à AlloyDB Omni depuis votre VM cliente et créez une base de données.

Nous avons besoin de la passerelle de transfert, car l'équilibreur de charge externe GKE pour Omni vous permet d'accéder au VPC à l'aide d'une adresse IP privée, mais ne vous permet pas de vous connecter en dehors du VPC. Il est plus sécurisé en général et n'expose pas votre instance de base de données à Internet. Veuillez vérifier le schéma pour plus de clarté.

391e4244b25a7db0.png

Pour créer une VM dans la session Cloud Shell, exécutez:

export ZONE=us-central1-a
gcloud compute instances create instance-1 \
    --zone=$ZONE 

Recherchez l'adresse IP du point de terminaison AlloyDB Omni à l'aide de kubectl dans Cloud Shell:

kubectl get dbclusters.alloydbomni.dbadmin.goog my-omni -n default

Notez PRIMARYENDPOINT. Voici un exemple :

sortie :

student@cloudshell:~$ kubectl get dbclusters.alloydbomni.dbadmin.goog my-omni -n default
NAME      PRIMARYENDPOINT   PRIMARYPHASE   DBCLUSTERPHASE   HAREADYSTATUS   HAREADYREASON
my-omni   10.131.0.33        Ready          DBClusterReady
student@cloudshell:~$

L'adresse IP 10.131.0.33 est celle que nous utiliserons dans nos exemples pour nous connecter à l'instance AlloyDB Omni.

Connectez-vous à la VM à l'aide de gcloud:

gcloud compute ssh instance-1 --zone=$ZONE 

Si vous êtes invité à générer une clé SSH, suivez les instructions. Pour en savoir plus sur la connexion SSH, consultez la documentation.

Dans la session SSH de la VM, installez le client PostgreSQL:

sudo apt-get update
sudo apt-get install --yes postgresql-client

Exportez l'adresse IP de l'équilibreur de charge AlloyDB Omni comme dans l'exemple suivant (remplacez l'adresse IP par celle de votre équilibreur de charge):

export INSTANCE_IP=10.131.0.33

Connectez-vous à AlloyDB Omni. Le mot de passe est "VeryStrongPassword", comme défini via le hachage dans my-omni.yaml:

psql "host=$INSTANCE_IP user=postgres sslmode=require"

Dans la session psql établie, exécutez:

create database demo;

Quittez la session et connectez-vous à la démonstration de la base de données (ou exécutez simplement "\c demo" dans la même session).

psql "host=$INSTANCE_IP user=postgres sslmode=require dbname=demo"

Créer des fonctions de transformation

Pour les modèles d'encapsulation tiers, nous devons créer des fonctions de transformation qui formatent l'entrée et la sortie au format attendu par le modèle et nos fonctions internes.

Voici la fonction de transformation qui gère l'entrée:

-- Input Transform Function corresponding to the custom model endpoint
CREATE OR REPLACE FUNCTION tei_text_input_transform(model_id VARCHAR(100), input_text TEXT)
RETURNS JSON
LANGUAGE plpgsql
AS $$
DECLARE
  transformed_input JSON;
  model_qualified_name TEXT;
BEGIN
  SELECT json_build_object('inputs', input_text, 'truncate', true)::JSON INTO transformed_input;
  RETURN transformed_input;
END;
$$;

Exécutez le code fourni lorsque vous êtes connecté à la base de données de démonstration, comme indiqué dans l'exemple de sortie:

demo=# -- Input Transform Function corresponding to the custom model endpoint
CREATE OR REPLACE FUNCTION tei_text_input_transform(model_id VARCHAR(100), input_text TEXT)
RETURNS JSON
LANGUAGE plpgsql
AS $$
DECLARE
  transformed_input JSON;
  model_qualified_name TEXT;
BEGIN
  SELECT json_build_object('inputs', input_text, 'truncate', true)::JSON INTO transformed_input;
  RETURN transformed_input;
END;
$$;
CREATE FUNCTION
demo=#

Voici la fonction de sortie qui transforme la réponse du modèle en tableau de nombres réels:

-- Output Transform Function corresponding to the custom model endpoint
CREATE OR REPLACE FUNCTION tei_text_output_transform(model_id VARCHAR(100), response_json JSON)
RETURNS REAL[]
LANGUAGE plpgsql
AS $$
DECLARE
  transformed_output REAL[];
BEGIN
  SELECT ARRAY(SELECT json_array_elements_text(response_json->0)) INTO transformed_output;
  RETURN transformed_output;
END;
$$;

Exécutez-le dans la même session:

demo=# -- Output Transform Function corresponding to the custom model endpoint
CREATE OR REPLACE FUNCTION tei_text_output_transform(model_id VARCHAR(100), response_json JSON)
RETURNS REAL[]
LANGUAGE plpgsql
AS $$
DECLARE
  transformed_output REAL[];
BEGIN
  SELECT ARRAY(SELECT json_array_elements_text(response_json->0)) INTO transformed_output;
  RETURN transformed_output;
END;
$$;
CREATE FUNCTION
demo=#

Enregistrer le modèle

Nous pouvons maintenant enregistrer le modèle dans la base de données.

Voici l'appel de procédure pour enregistrer le modèle avec le nom bge-base-1.5. Remplacez l'adresse IP 34.118.233.48 par l'adresse IP de votre service de modèle (sortie de kubectl get service tei-service):

CALL
  google_ml.create_model(
    model_id => 'bge-base-1.5',
    model_request_url => 'http://34.118.233.48:8080/embed',
    model_provider => 'custom',
    model_type => 'text_embedding',
    model_in_transform_fn => 'tei_text_input_transform',
    model_out_transform_fn => 'tei_text_output_transform');

Exécutez le code fourni lorsque vous êtes connecté à la base de données de démonstration:

demo=# CALL
  google_ml.create_model(
    model_id => 'bge-base-1.5',
    model_request_url => 'http://34.118.233.48:8080/embed',
    model_provider => 'custom',
    model_type => 'text_embedding',
    model_in_transform_fn => 'tei_text_input_transform',
    model_out_transform_fn => 'tei_text_output_transform');
CALL
demo=#

Nous pouvons tester le modèle de registre à l'aide de la requête de test suivante, qui devrait renvoyer un tableau de nombres réels.

select google_ml.embedding('bge-base-1.5','What is AlloyDB Omni?');

7. Tester le modèle dans AlloyDB Omni

Charger des données

Pour tester le fonctionnement d'AlloyDB Omni avec le modèle déployé, nous devons charger des données. J'ai utilisé les mêmes données que dans l'un des autres ateliers de programmation pour la recherche vectorielle dans AlloyDB.

Pour charger les données, vous pouvez utiliser le SDK Google Cloud et le logiciel client PostgreSQL. Nous pouvons utiliser la même VM cliente que celle utilisée pour créer la base de données de démonstration. Le SDK Google Cloud devrait déjà y être installé si vous avez utilisé les valeurs par défaut pour l'image de VM. Toutefois, si vous avez utilisé une image personnalisée sans SDK Google, vous pouvez l'ajouter en suivant la documentation.

Exportez l'adresse IP de l'équilibreur de charge AlloyDB Omni comme dans l'exemple suivant (remplacez l'adresse IP par celle de votre équilibreur de charge):

export INSTANCE_IP=10.131.0.33

Connectez-vous à la base de données et activez l'extension pgvector.

psql "host=$INSTANCE_IP user=postgres sslmode=require dbname=demo"

Dans la session psql:

CREATE EXTENSION IF NOT EXISTS vector;

Quittez la session psql, puis dans la session de ligne de commande, exécutez des commandes pour charger les données dans la base de données de démonstration.

Créez les tables:

gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_demo_schema.sql |psql "host=$INSTANCE_IP user=postgres dbname=demo"

Résultat attendu sur la console :

student@cloudshell:~$ gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_demo_schema.sql |psql "host=$INSTANCE_IP user=postgres dbname=demo"
Password for user postgres:
SET
SET
SET
SET
SET
 set_config
------------

(1 row)

SET
SET
SET
SET
SET
SET
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
CREATE TABLE
ALTER TABLE
CREATE SEQUENCE
ALTER TABLE
ALTER SEQUENCE
ALTER TABLE
ALTER TABLE
ALTER TABLE
student@cloudshell:~$ 

Voici la liste des tables créées:

psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\dt+"

Sortie :

student@cloudshell:~$ psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\dt+"
Password for user postgres: 
                                           List of relations
 Schema |       Name       | Type  |  Owner   | Persistence | Access method |    Size    | Description 
--------+------------------+-------+----------+-------------+---------------+------------+-------------
 public | cymbal_embedding | table | postgres | permanent   | heap          | 8192 bytes | 
 public | cymbal_inventory | table | postgres | permanent   | heap          | 8192 bytes | 
 public | cymbal_products  | table | postgres | permanent   | heap          | 8192 bytes | 
 public | cymbal_stores    | table | postgres | permanent   | heap          | 8192 bytes | 
(4 rows)
student@cloudshell:~$ 

Chargez des données dans la table cymbal_products:

gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_products.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_products from stdin csv header"

Résultat attendu sur la console :

student@cloudshell:~$ gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_products.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_products from stdin csv header"
COPY 941
student@cloudshell:~$ 

Voici quelques exemples de lignes du tableau cymbal_products.

psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT uniq_id,left(product_name,30),left(product_description,50),sale_price FROM cymbal_products limit 3"

Sortie :

student@cloudshell:~$ psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT uniq_id,left(product_name,30),left(product_description,50),sale_price FROM cymbal_products limit 3"
Password for user postgres: 
             uniq_id              |              left              |                        left                        | sale_price 
----------------------------------+--------------------------------+----------------------------------------------------+------------
 a73d5f754f225ecb9fdc64232a57bc37 | Laundry Tub Strainer Cup       |   Laundry tub strainer cup Chrome For 1-.50, drain |      11.74
 41b8993891aa7d39352f092ace8f3a86 | LED Starry Star Night Light La |  LED Starry Star Night Light Laser Projector 3D Oc |      46.97
 ed4a5c1b02990a1bebec908d416fe801 | Surya Horizon HRZ-1060 Area Ru |  The 100% polypropylene construction of the Surya  |       77.4
(3 rows)
student@cloudshell:~$ 

Chargez des données dans la table cymbal_inventory:

gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_inventory.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_inventory from stdin csv header"

Résultat attendu sur la console :

student@cloudshell:~$ gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_inventory.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_inventory from stdin csv header"
Password for user postgres: 
COPY 263861
student@cloudshell:~$ 

Voici quelques exemples de lignes du tableau cymbal_inventory.

psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT * FROM cymbal_inventory LIMIT 3"

Sortie :

student@cloudshell:~$ psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT * FROM cymbal_inventory LIMIT 3"
Password for user postgres: 
 store_id |             uniq_id              | inventory 
----------+----------------------------------+-----------
     1583 | adc4964a6138d1148b1d98c557546695 |         5
     1490 | adc4964a6138d1148b1d98c557546695 |         4
     1492 | adc4964a6138d1148b1d98c557546695 |         3
(3 rows)
student@cloudshell:~$ 

Chargez des données dans la table cymbal_stores:

gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_stores.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_stores from stdin csv header"

Résultat attendu sur la console :

student@cloudshell:~$ gcloud storage cat gs://cloud-training/gcc/gcc-tech-004/cymbal_stores.csv |psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "\copy cymbal_stores from stdin csv header"
Password for user postgres: 
COPY 4654
student@cloudshell:~$

Voici quelques exemples de lignes du tableau cymbal_stores.

psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT store_id, name, zip_code FROM cymbal_stores limit 3"

Sortie :

student@cloudshell:~$ psql "host=$INSTANCE_IP user=postgres dbname=demo" -c "SELECT store_id, name, zip_code FROM cymbal_stores limit 3"
Password for user postgres: 
 store_id |       name        | zip_code 
----------+-------------------+----------
     1990 | Mayaguez Store    |      680
     2267 | Ware Supercenter  |     1082
     4359 | Ponce Supercenter |      780
(3 rows)
student@cloudshell:~$ 

Créer des représentations vectorielles continues

Connectez-vous à la base de données de démonstration à l'aide de psql et créez des représentations vectorielles continues pour les produits décrits dans la table cymbal_products en fonction de leurs noms et descriptions.

Connectez-vous à la base de données de démonstration:

psql "host=$INSTANCE_IP user=postgres sslmode=require dbname=demo"

Nous utilisons une table cymbal_embedding avec un embedding de colonne pour stocker nos embeddings et nous utilisons la description du produit comme entrée textuelle de la fonction.

Activez le chronométrage de vos requêtes pour les comparer ultérieurement avec des modèles distants :

\timing

Exécutez la requête pour créer les représentations vectorielles continues:

INSERT INTO cymbal_embedding(uniq_id,embedding)  SELECT uniq_id, google_ml.embedding('bge-base-1.5',product_description)::vector FROM cymbal_products;

Résultat attendu sur la console :

demo=#  INSERT INTO cymbal_embedding(uniq_id,embedding)  SELECT uniq_id, google_ml.embedding('bge-base-1.5',product_description)::vector FROM cymbal_products;
INSERT 0 941
Time: 11069.762 ms (00:11.070)
demo=#

Dans cet exemple, la création d'embeddings pour 941 enregistrements a pris environ 11 secondes.

Exécuter des requêtes de test

Connectez-vous à la base de données de démonstration à l'aide de psql et activez le chronométrage pour mesurer le temps d'exécution de nos requêtes, comme nous l'avons fait pour créer des représentations vectorielles continues.

Trouvons les cinq premiers produits correspondant à une requête telle que "Quels arbres fruitiers poussent bien ici ?" en utilisant la distance cosinus comme algorithme pour la recherche vectorielle.

Dans la session psql, exécutez:

SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        (ce.embedding <=> google_ml.embedding('bge-base-1.5','What kind of fruit trees grow well here?')::vector) as distance
FROM
        cymbal_products cp
JOIN cymbal_embedding ce on
        ce.uniq_id=cp.uniq_id
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        distance ASC
LIMIT 5;

Résultat attendu sur la console :

demo=# SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        (ce.embedding <=> google_ml.embedding('bge-base-1.5','What kind of fruit trees grow well here?')::vector) as distance
FROM
        cymbal_products cp
JOIN cymbal_embedding ce on
        ce.uniq_id=cp.uniq_id
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        distance ASC
LIMIT 5;
     product_name      |                                   description                                    | sale_price | zip_code |      distance
-----------------------+----------------------------------------------------------------------------------+------------+----------+---------------------
 California Sycamore   | This is a beautiful sycamore tree that can grow to be over 100 feet tall. It is  |     300.00 |    93230 | 0.22753925487632942
 Toyon                 | This is a beautiful toyon tree that can grow to be over 20 feet tall. It is an e |      10.00 |    93230 | 0.23497374266229387
 California Peppertree | This is a beautiful peppertree that can grow to be over 30 feet tall. It is an e |      25.00 |    93230 | 0.24215884459965364
 California Redwood    | This is a beautiful redwood tree that can grow to be over 300 feet tall. It is a |    1000.00 |    93230 | 0.24564130578287147
 Cherry Tree           | This is a beautiful cherry tree that will produce delicious cherries. It is an d |      75.00 |    93230 | 0.24846117929767153
(5 rows)

Time: 28.724 ms
demo=#

La requête a été exécutée en 28 ms et a renvoyé une liste d'arbres de la table "cymbal_products" correspondant à la requête et dont l'inventaire est disponible dans le magasin numéro 1583.

Créer un index ANN

Lorsque nous disposons d'un petit ensemble de données, il est facile d'utiliser la recherche exacte qui analyse tous les représentations vectorielles continues, mais lorsque les données augmentent, la charge et le temps de réponse augmentent également. Pour améliorer les performances, vous pouvez créer des index sur vos données d'embedding. Voici un exemple d'utilisation de l'index Google ScaNN pour les données vectorielles.

Si vous avez perdu la connexion à la base de données de démonstration, reconnectez-vous:

psql "host=$INSTANCE_IP user=postgres sslmode=require dbname=demo"

Activez l'extension alloydb_scann:

CREATE EXTENSION IF NOT EXISTS alloydb_scann;

Créez l'index:

CREATE INDEX cymbal_embedding_scann ON cymbal_embedding USING scann (embedding cosine);

Essayez la même requête qu'avant et comparez les résultats:

demo=# SELECT
        cp.product_name,
        left(cp.product_description,80) as description,
        cp.sale_price,
        cs.zip_code,
        (ce.embedding <=> google_ml.embedding('bge-base-1.5','What kind of fruit trees grow well here?')::vector) as distance
FROM
        cymbal_products cp
JOIN cymbal_embedding ce on
        ce.uniq_id=cp.uniq_id
JOIN cymbal_inventory ci on
        ci.uniq_id=cp.uniq_id
JOIN cymbal_stores cs on
        cs.store_id=ci.store_id
        AND ci.inventory>0
        AND cs.store_id = 1583
ORDER BY
        distance ASC
LIMIT 5;
     product_name      |                                   description                                    | sale_price | zip_code |      distance
-----------------------+----------------------------------------------------------------------------------+------------+----------+---------------------
 California Sycamore   | This is a beautiful sycamore tree that can grow to be over 100 feet tall. It is  |     300.00 |    93230 | 0.22753925487632942
 Toyon                 | This is a beautiful toyon tree that can grow to be over 20 feet tall. It is an e |      10.00 |    93230 | 0.23497374266229387
 California Peppertree | This is a beautiful peppertree that can grow to be over 30 feet tall. It is an e |      25.00 |    93230 | 0.24215884459965364
 California Redwood    | This is a beautiful redwood tree that can grow to be over 300 feet tall. It is a |    1000.00 |    93230 | 0.24564130578287147
 Fremont Cottonwood    | This is a beautiful cottonwood tree that can grow to be over 100 feet tall. It i |     200.00 |    93230 |  0.2533482837690365
(5 rows)

Time: 14.665 ms
demo=#

Le temps d'exécution des requêtes a légèrement diminué, et ce gain serait plus visible avec des ensembles de données plus volumineux. Les résultats sont assez similaires, et seul Cherry a été remplacé par Fremont Cottonwood.

Essayez d'autres requêtes et découvrez comment choisir un indice vectoriel dans la documentation.

N'oubliez pas qu'AlloyDB Omni propose plus de fonctionnalités et de labs.

8. Nettoyer l'environnement

Nous pouvons maintenant supprimer notre cluster GKE avec AlloyDB Omni et un modèle d'IA.

Supprimer le cluster GKE

Dans Cloud Shell, exécutez :

export PROJECT_ID=$(gcloud config get project)
export LOCATION=us-central1
export CLUSTER_NAME=alloydb-ai-gke
gcloud container clusters delete ${CLUSTER_NAME} \
  --project=${PROJECT_ID} \
  --region=${LOCATION}

Résultat attendu sur la console :

student@cloudshell:~$ gcloud container clusters delete ${CLUSTER_NAME} \
>   --project=${PROJECT_ID} \
>   --region=${LOCATION}
The following clusters will be deleted.
 - [alloydb-ai-gke] in [us-central1]

Do you want to continue (Y/n)?  Y

Deleting cluster alloydb-ai-gke...done.
Deleted

Supprimer la VM

Dans Cloud Shell, exécutez :

export PROJECT_ID=$(gcloud config get project)
export ZONE=us-central1-a
gcloud compute instances delete instance-1 \
  --project=${PROJECT_ID} \
  --zone=${ZONE}

Résultat attendu sur la console :

student@cloudshell:~$ export PROJECT_ID=$(gcloud config get project)
export ZONE=us-central1-a
gcloud compute instances delete instance-1 \
  --project=${PROJECT_ID} \
  --zone=${ZONE}
Your active configuration is: [cloudshell-5399]
The following instances will be deleted. Any attached disks configured to be auto-deleted will be deleted unless they are attached to any other instances or the `--keep-disks` flag is given and specifies them for keeping. Deleting a disk 
is irreversible and any data on the disk will be lost.
 - [instance-1] in [us-central1-a]

Do you want to continue (Y/n)?  Y

Deleted

Si vous avez créé un projet pour cet atelier de programmation, vous pouvez le supprimer entièrement: https://console.cloud.google.com/cloud-resource-manager

9. Félicitations

Bravo ! Vous avez terminé cet atelier de programmation.

Points abordés

  • Déployer AlloyDB Omni sur un cluster Google Kubernetes
  • Se connecter à AlloyDB Omni
  • Charger des données dans AlloyDB Omni
  • Déployer un modèle d'encapsulation ouvert sur GKE
  • Enregistrer un modèle d'encapsulation dans AlloyDB Omni
  • Générer des représentations vectorielles continues pour la recherche sémantique
  • Utiliser des représentations vectorielles continues générées pour la recherche sémantique dans AlloyDB Omni
  • Créer et utiliser des index vectoriels dans AlloyDB

Pour en savoir plus sur l'utilisation de l'IA dans AlloyDB Omni, consultez la documentation.

10. Enquête

Résultat :

Comment allez-vous utiliser ce tutoriel ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices