Fine-tune Open Source LLMs on Google Cloud

1. מבוא

במעבדה הזו תלמדו איך ליצור צינור עיבוד נתונים מלא ומתאים לסביבת ייצור לצורך כוונון עדין של Llama 2, מודל שפה פופולרי בקוד פתוח, באמצעות Google Kubernetes Engine ‏ (GKE). תלמדו על החלטות ארכיטקטוניות, על פשרות נפוצות ועל רכיבים שמשקפים תהליכי עבודה של Machine Learning Operations (MLOps) בעולם האמיתי.

תקצו אשכול GKE, תבנו צינור עיבוד נתונים לאימון בקונטיינרים באמצעות LoRA (התאמה בדרגה נמוכה) ותריצו את משימת האימון ב-GKE.

סקירה כללית של הארכיטקטורה

אלה הפעולות שנעשה היום:

ארכיטקטורה שמציגה אשכול GKE עם צמתי GPU, דלי GCS ופרסום המודל

הארכיטקטורה כוללת:

  • אשכול GKE: ניהול משאבי המחשוב
  • מאגר צמתים של GPU: יחידת GPU אחת מסוג L4 ‏ (Spot) לאימון
  • GCS Bucket: אחסון של מודלים ומערכי נתונים
  • Workload Identity: גישה מאובטחת בין K8s ל-GCS

מה תלמדו

  • הקצאה והגדרה של אשכול GKE עם תכונות שעברו אופטימיזציה לעומסי עבודה של למידת מכונה.
  • הטמעה של גישה מאובטחת ללא מפתח מ-GKE לשירותים אחרים של Google Cloud באמצעות Workload Identity.
  • פיתוח צינור עיבוד נתונים לאימון בקונטיינר באמצעות Docker.
  • אפשר לבצע כוונון יעיל בפרמטרים של מודל קוד פתוח ביעילות באמצעות Parameter-Efficient Fine-Tuning (PEFT)‎ עם LoRA.

2. הגדרת הפרויקט

חשבון Google

אם אין לכם חשבון Google אישי, אתם צריכים ליצור חשבון Google.

משתמשים בחשבון לשימוש אישי במקום בחשבון לצורכי עבודה או בחשבון בית ספרי.

כניסה למסוף Google Cloud

נכנסים למסוף Google Cloud באמצעות חשבון Google אישי.

יצירת פרויקט (אופציונלי)

אם אין לכם פרויקט שאתם רוצים להשתמש בו בסדנה הזו, אתם יכולים ליצור פרויקט חדש כאן.

3. פתיחת Cloud Shell Editor

  1. כדי לעבור ישירות אל Cloud Shell Editor, לוחצים על הקישור הזה.
  2. אם תתבקשו לאשר בשלב כלשהו היום, תצטרכו ללחוץ על אישור כדי להמשיך.לוחצים כדי לתת הרשאה ל-Cloud Shell
  3. אם הטרמינל לא מופיע בתחתית המסך, פותחים אותו:
    • לוחצים על הצגה.
    • לוחצים על Terminal (מסוף)פתיחת טרמינל חדש ב-Cloud Shell Editor.
  4. בטרמינל, מגדירים את הפרויקט באמצעות הפקודה הבאה:
    gcloud config set project [PROJECT_ID]
    
    • דוגמה:
      gcloud config set project lab-project-id-example
      
    • אם אתם לא זוכרים את מזהה הפרויקט, אתם יכולים להציג רשימה של כל מזהי הפרויקטים באמצעות הפקודה:
      gcloud projects list
      
      הגדרת מזהה הפרויקט בטרמינל של Cloud Shell Editor
  5. תוצג ההודעה הבאה:
    Updated property [core/project].
    

4. הפעלת ממשקי API

כדי להשתמש ב-GKE ובשירותים אחרים, צריך להפעיל את ממשקי ה-API הנדרשים בפרויקט בענן שלכם ב-Google Cloud.

  1. בטרמינל, מפעילים את ממשקי ה-API:
    gcloud services enable container.googleapis.com \
        artifactregistry.googleapis.com \
        cloudbuild.googleapis.com \
        iam.googleapis.com \
        compute.googleapis.com \
        iamcredentials.googleapis.com \
        storage.googleapis.com
    

מבוא לממשקי ה-API

  • Google Kubernetes Engine API‏ (container.googleapis.com) מאפשר ליצור ולנהל את אשכול GKE שמריץ את האפליקציה.
  • Artifact Registry API‏ (artifactregistry.googleapis.com) מספק מאגר פרטי ומאובטח לאחסון תמונות של קונטיינרים.
  • Cloud Build API‏ (cloudbuild.googleapis.com) משמש את הפקודה gcloud builds submit כדי ליצור את תמונת הקונטיינר בענן.
  • IAM API ‏ (iam.googleapis.com) מאפשר לכם לנהל את בקרת הגישה והזהות של המשאבים שלכם ב-Google Cloud.
  • Compute Engine API‏ (compute.googleapis.com) מספק מכונות וירטואליות מאובטחות שניתנות להתאמה אישית ופועלות בתשתית של Google.
  • IAM Service Account Credentials API‏ (iamcredentials.googleapis.com) מאפשר ליצור פרטי כניסה לטווח קצר לחשבונות שירות.
  • Cloud Storage API ‏ (storage.googleapis.com) מאפשר לכם לאחסן ולאחזר נתונים בענן. כאן הוא משמש לאחסון של מודלים ושל מערכי נתונים.

5. הגדרת סביבת הפרויקט

יצירת ספריית עבודה

  1. בטרמינל, יוצרים ספרייה לפרויקט ועוברים אליה.
    mkdir llama-finetuning
    cd llama-finetuning
    

הגדרה של משתני סביבה

  1. בטרמינל, יוצרים קובץ בשם env.sh כדי לאחסן את משתני הסביבה. כך תוכלו לטעון אותם מחדש בקלות אם הסשן יתנתק.
    cat <<EOF > env.sh
    export PROJECT_ID=$(gcloud config get-value project)
    export CLUSTER_NAME="ml-gke"
    export GPU_NODE_POOL_NAME="gpu-pool"
    export MACHINE_TYPE="e2-standard-4"
    export GPU_MACHINE_TYPE="g2-standard-16"
    export GPU_TYPE="nvidia-l4"
    export GPU_COUNT=1
    export REGION="asia-southeast1"
    export NODE_LOCATIONS="asia-southeast1-a,asia-southeast1-b"
    EOF
    
  2. מריצים את הקובץ כדי לטעון את המשתנים לסשן הנוכחי:
    source env.sh
    

6. הקצאת אשכול GKE

  1. בטרמינל, יוצרים את אשכול ה-GKE עם מאגר צמתים שמוגדר כברירת מחדל. התהליך יימשך כ-5 דקות.
    gcloud container clusters create $CLUSTER_NAME \
        --project=$PROJECT_ID \
        --region=$REGION \
        --release-channel=rapid \
        --machine-type=$MACHINE_TYPE \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --addons=GcsFuseCsiDriver,HttpLoadBalancing \
        --enable-image-streaming \
        --enable-ip-alias \
        --num-nodes=1 \
        --enable-autoscaling \
        --min-nodes=1 \
        --max-nodes=3
    
  2. בשלב הבא, מוסיפים מאגר צמתים של GPU לאשכול. מאגר הצמתים הזה ישמש לאימון המודל.
    gcloud container node-pools create $GPU_NODE_POOL_NAME \
        --project=$PROJECT_ID \
        --cluster=$CLUSTER_NAME \
        --region=$REGION \
        --machine-type=$GPU_MACHINE_TYPE \
        --accelerator type=$GPU_TYPE,count=$GPU_COUNT,gpu-driver-version=latest \
        --ephemeral-storage-local-ssd=count=1 \
        --enable-autoscaling \
        --enable-image-streaming \
        --num-nodes=0 \
        --min-nodes=0 \
        --max-nodes=1 \
        --location-policy=ANY \
        --node-taints=nvidia.com/gpu=present:NoSchedule \
        --node-locations=$NODE_LOCATIONS \
        --spot
    
  3. לבסוף, מקבלים את פרטי הכניסה לאשכול החדש ומוודאים שאפשר להתחבר אליו.
    gcloud container clusters get-credentials $CLUSTER_NAME --region=$REGION
    
    kubectl get nodes
    

7. הגדרת גישה ל-Hugging Face

אחרי שהתשתית מוכנה, צריך לספק לפרויקט את פרטי הכניסה הנדרשים כדי לגשת למודל ולנתונים. במשימה הזו, קודם תקבלו טוקן של Hugging Face.

קבלת טוקן של Hugging Face

  1. אם אין לכם חשבון Hugging Face, עוברים אל huggingface.co/join בכרטיסייה בדפדפן חדשה ומשלימים את תהליך ההרשמה.
  2. אחרי שנרשמים ונכנסים לחשבון, עוברים אל huggingface.co/meta-llama/Llama-2-7b-hf.
  3. קוראים את תנאי הרישיון ולוחצים על הכפתור כדי לאשר אותם.
  4. עוברים לדף של טוקנים לגישה ב-Hugging Face בכתובת huggingface.co/settings/tokens.
  5. לוחצים על טוקן חדש.
  6. בשדה תפקיד, בוחרים באפשרות קריאה.
  7. בשדה שם, מזינים שם תיאורי (למשל, finetuning-lab).
  8. לוחצים על יצירת טוקן.
  9. מעתיקים את הטוקן שנוצר ללוח. תצטרכו אותו בשלב הבא.

עדכון משתני סביבה

עכשיו מוסיפים את הטוקן של Hugging Face ואת השם של קטגוריית ה-GCS לקובץ env.sh. מחליפים את [your-hf-token] באסימון שהעתקתם.

  1. בטרמינל, מוסיפים את המשתנים החדשים ל-env.sh וטוענים אותם מחדש:
    cat <<EOF >> env.sh
    export HF_TOKEN="[your-hf-token]"
    export BUCKET_NAME="\${PROJECT_ID}-llama-fine-tuning"
    EOF
    
    source env.sh
    

8. הגדרת Workload Identity

בשלב הבא תגדירו את Workload Identity, שהיא הדרך המומלצת לאפשר לאפליקציות שפועלות ב-GKE לגשת לשירותי Google Cloud בלי לנהל מפתחות סטטיים של חשבונות שירות. מידע נוסף זמין במאמרי העזרה בנושא Workload Identity.

  1. קודם יוצרים חשבון שירות של Google‏ (GSA). בטרמינל, מריצים את הפקודה:
    cat <<EOF >> env.sh
    export GSA_NAME="llama-fine-tuning"
    EOF
    source env.sh
    
    gcloud iam service-accounts create $GSA_NAME \
      --display-name="Llama Fine-tuning Service Account"
    
  2. לאחר מכן, יוצרים את קטגוריית GCS ומעניקים ל-GSA הרשאות גישה אליה:
    gcloud storage buckets create gs://$BUCKET_NAME --project=$PROJECT_ID --location=$REGION
    gcloud storage buckets add-iam-policy-binding gs://$BUCKET_NAME \
        --member=serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \
        --role=roles/storage.admin
    
  3. עכשיו יוצרים חשבון שירות ב-Kubernetes‏ (KSA):
    cat <<EOF >> env.sh
    export KSA_NAME="llama-workload-sa"
    export NAMESPACE="ml-workloads"
    EOF
    source env.sh
    
    kubectl create namespace $NAMESPACE
    kubectl create serviceaccount $KSA_NAME --namespace $NAMESPACE
    
  4. לבסוף, יוצרים את הקישור של מדיניות IAM בין GSA ל-KSA:
    gcloud iam service-accounts add-iam-policy-binding ${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com \
        --role roles/iam.workloadIdentityUser \
        --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${NAMESPACE}/${KSA_NAME}]"
    
    kubectl annotate serviceaccount $KSA_NAME --namespace $NAMESPACE \
        iam.gke.io/gcp-service-account=${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com
    

9. הכנת מודל הבסיס

בצינורות של למידת מכונה (ML) בייצור, מודלים גדולים כמו Llama 2 (בגודל של כ-13GB) בדרך כלל מוכנים מראש ב-Cloud Storage במקום להוריד אותם במהלך האימון. הגישה הזו מספקת מהימנות טובה יותר, גישה מהירה יותר ומונעת בעיות ברשת. ‫Google Cloud מספקת גרסאות של מודלים פופולריים שהורדו מראש בדלי GCS ציבוריים, ותשתמשו בהם בשיעור ה-Lab הזה.

  1. קודם כול, צריך לוודא שיש לכם גישה למודל Llama 2 שסופק על ידי Google:
    gcloud storage ls gs://vertex-model-garden-public-us-central1/llama2/llama2-7b-hf/
    
  2. מעתיקים את מודל Llama 2 מהקטגוריה הציבורית הזו לקטגוריה של הפרויקט שלכם באמצעות הפקודה gcloud storage. ההעברה הזו מתבצעת באמצעות הרשת הפנימית המהירה של Google, והיא אמורה להימשך רק דקה או שתיים.
    gcloud storage cp -r gs://vertex-model-garden-public-us-central1/llama2/llama2-7b-hf \
      gs://${BUCKET_NAME}/llama2-7b/
    
  3. כדי לוודא שקבצי המודל הועתקו בצורה נכונה, מציגים את התוכן של דלי האחסון.
    gcloud storage ls --recursive --long gs://${BUCKET_NAME}/llama2-7b/llama2-7b-hf/
    

10. הכנת קוד האימון

עכשיו תיצרו את האפליקציה שמופעלת בקונטיינר ומבצעת כוונון עדין של המודל. משימה זו משתמשת ב-LoRA (התאמה בדרגה נמוכה), טכניקת כוונון יעיל בפרמטרים (PEFT) שמפחיתה באופן דרמטי את דרישות הזיכרון על ידי אימון שכבות 'מתאם' קטנות בלבד במקום המודל כולו.

עכשיו יוצרים את סקריפטים של Python לצינור העיבוד של האימון.

  1. בטרמינל, מריצים את הפקודה הבאה כדי לפתוח את הקובץ train.py:
    cloudshell edit train.py
    
  2. מדביקים את הקוד הבא לקובץ train.py:
#!/usr/bin/env python3
"""Fine-tune Llama 2 with LoRA on American Stories dataset """

import os
import torch
import logging

from pathlib import Path
from datasets import load_dataset, concatenate_datasets
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    Trainer,
    TrainingArguments,
    DataCollatorForLanguageModeling
)
from peft import get_peft_model, LoraConfig

os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["NCCL_DEBUG"] = "INFO"

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class SimpleTextDataset(torch.utils.data.Dataset):
    def __init__(self, input_ids, attention_mask):
        self.input_ids = input_ids
        self.attention_mask = attention_mask
    
    def __len__(self):
        return len(self.input_ids)
    
    def __getitem__(self, idx):
        return {
            'input_ids': self.input_ids[idx],
            'attention_mask': self.attention_mask[idx],
            'labels': self.input_ids[idx].clone()
        }

def get_lora_config():
    config = {
       "r": 16,
       "lora_alpha": 32,
       "target_modules": [
          "q_proj", "k_proj", "v_proj", "o_proj",
          "gate_proj", "up_proj", "down_proj"
       ],
       "lora_dropout": 0.05,
       "task_type": "CAUSAL_LM",
    }
    return LoraConfig(**config)

def load_model_and_tokenizer(model_path):
    logger.info("Loading tokenizer...")
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
    tokenizer.padding_side = "left"
    
    logger.info("Loading model...")

    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True,
        use_cache=False
    )
    
    return model, tokenizer

def prepare_dataset(tokenizer, max_length=512):
    logger.info("Loading American Stories dataset...")
     We recommend using o
    dataset = load_dataset(
        "dell-research-harvard/AmericanStories",
        "subset_years",
        year_list=["1809", "1810", "1811", "1812", "1813", "1814", "1815"],
        trust_remote_code=True
    )
    
    all_articles = []
    for year_data in dataset.values():
        all_articles.extend(year_data["article"])
    
    logger.info(f"Total articles collected: {len(all_articles)}")
    
    batch_size = 1000
    all_input_ids = []
    all_attention_masks = []
    
    for i in range(0, len(all_articles), batch_size):
        batch_articles = all_articles[i:i+batch_size]
        logger.info(f"Processing batch {i//batch_size + 1}/{(len(all_articles) + batch_size - 1)//batch_size}")
        
        encodings = tokenizer(
            batch_articles,
            padding="max_length",
            truncation=True,
            max_length=max_length,
            return_tensors="pt"
        )
        
        all_input_ids.append(encodings['input_ids'])
        all_attention_masks.append(encodings['attention_mask'])
    
    # Concatenate all batches
    input_ids = torch.cat(all_input_ids, dim=0)
    attention_mask = torch.cat(all_attention_masks, dim=0)
    
    logger.info(f"Total tokenized examples: {len(input_ids)}")
    
    # Create simple dataset
    dataset = SimpleTextDataset(input_ids, attention_mask)
    
    return dataset

def train_model(model, tokenizer, train_dataset, output_dir):
    logger.info(f"Train dataset size: {len(train_dataset)}")
    
    n_gpus = torch.cuda.device_count()
    logger.info(f"Available GPUs: {n_gpus}")
    
    # For multi-GPU, we can increase batch size
    per_device_batch_size = 2 if n_gpus > 1 else 1
    gradient_accumulation_steps = 2 if n_gpus > 1 else 4
    
    # Training for 250 steps
    max_steps = 250
    
    training_args = TrainingArguments(
        output_dir=output_dir,
        max_steps=max_steps,
        per_device_train_batch_size=per_device_batch_size,
        gradient_accumulation_steps=gradient_accumulation_steps,
        learning_rate=2e-4,
        warmup_steps=20,
        fp16=True,
        gradient_checkpointing=True,
        logging_steps=10,
        evaluation_strategy="no",
        save_strategy="no",
        optim="adamw_torch",
        ddp_find_unused_parameters=False,
        dataloader_num_workers=0,
        remove_unused_columns=False,
        report_to=[],
        disable_tqdm=False,
        logging_first_step=True,
    )
    
    # Create trainer
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        tokenizer=tokenizer,
        data_collator=DataCollatorForLanguageModeling(
            tokenizer=tokenizer,
            mlm=False,
            pad_to_multiple_of=8
        )
    )
    
    # Train
    logger.info(f"Starting training on {n_gpus} GPU(s)...")
    logger.info(f"Training for {max_steps} steps - approximately {(max_steps * 2.5 / 60):.1f} minutes")
    
    try:
        trainer.train()
        logger.info("Training completed successfully!")
    except Exception as e:
        logger.error(f"Training error: {e}")
        logger.info("Attempting to save model despite error...")
    
    logger.info("Saving model...")
    try:
        trainer.save_model(output_dir)
        tokenizer.save_pretrained(output_dir)
        logger.info(f"Model saved successfully to {output_dir}!")

        if not output_dir.startswith("/gcs-mount"):
            logger.info("Copying artifacts to GCS bucket...")
            gcs_target = "/gcs-mount/llama2-7b-american-stories"
            os.makedirs(gcs_target, exist_ok=True)
            return_code = os.system(f"cp -r {output_dir}/* {gcs_target}/")
            if return_code != 0:
                raise RuntimeError(f"Failed to copy model to GCS: cp command returned {return_code}")
            logger.info(f"Copied to {gcs_target}")

    except Exception as e:
        logger.error(f"Error saving model or copying to GCS: {e}")
        raise

def run_inference(model, tokenizer):
    logger.info("Running inference test...")
    
    prompt = "The year was 1812, and the"
    
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
    
    model.eval()
    
    with torch.no_grad():
        outputs = model.generate(
            **inputs, 
            max_new_tokens=50, 
            do_sample=True, 
            temperature=0.7,
            pad_token_id=tokenizer.pad_token_id
        )
    
    generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
    logger.info("-" * 50)
    logger.info(f"Input Prompt: {prompt}")
    logger.info(f"Generated Text: {generated_text}")
    logger.info("-" * 50)

def main():
    if torch.cuda.is_available():
        for i in range(torch.cuda.device_count()):
            logger.info(f"GPU {i}: {torch.cuda.get_device_name(i)}")
    
    model_path = os.getenv('MODEL_PATH', '/gcs-mount/llama2-7b/llama2-7b-hf')
    output_path = os.getenv('OUTPUT_PATH', '/gcs-mount/llama2-7b-american-stories')
    
    # Load model and tokenizer
    model, tokenizer = load_model_and_tokenizer(model_path)
    model.enable_input_require_grads()

    # Apply LoRA
    logger.info("Applying LoRA configuration...")
    lora_config = get_lora_config()
    model = get_peft_model(model, lora_config)
    model.train()
    
    # Prepare dataset
    train_dataset = prepare_dataset(tokenizer)
    
    # Train
    train_model(model, tokenizer, train_dataset, output_path)
    
    # Run Inference
    run_inference(model, tokenizer)
    
    logger.info("Training and inference complete!")

if __name__ == "__main__":
    main()

11. הסבר על קוד האימון

הסקריפט train.py מתזמן את תהליך הכוונון העדין. נפרט את הרכיבים העיקריים שלו.

הגדרות אישיות

הסקריפט משתמש ב-LoraConfig כדי להגדיר את ההגדרות של Low-Rank Adaptation. ‫LoRA מפחית באופן משמעותי את מספר הפרמטרים שאפשר לאמן, וכך מאפשר לכם לבצע כוונון עדין של מודלים גדולים במעבדים גרפיים קטנים יותר.

def get_lora_config():
    config = {
       "r": 16,
       "lora_alpha": 32,
       "target_modules": ["q_proj", "k_proj", "v_proj", "o_proj", ...],
       "lora_dropout": 0.05,
       "task_type": "CAUSAL_LM",
    }
    return LoraConfig(**config)

הכנת מערך נתונים

הפונקציה prepare_dataset טוענת את מערך הנתונים American Stories ומעבדת אותו למקטעים עם טוקנים. הוא משתמש ב-SimpleTextDataset בהתאמה אישית כדי לטפל בטנסורים של הקלט בצורה יעילה.

def prepare_dataset(tokenizer, max_length=512):
    dataset = load_dataset("dell-research-harvard/AmericanStories", ...)
    # ... tokenization logic ...
    return SimpleTextDataset(input_ids, attention_mask)

רכבת

הפונקציה train_model מגדירה את Trainer עם ארגומנטים ספציפיים שעברו אופטימיזציה לעומס העבודה הזה. הפרמטרים העיקריים כוללים:

  • gradient_accumulation_steps: עוזר לדמות גודל אצווה גדול יותר בלי להגדיל את השימוש בזיכרון.
  • fp16=True: נעשה שימוש באימון עם דיוק מעורב כדי לצמצם את השימוש בזיכרון ולהגביר את המהירות.
  • gradient_checkpointing=True: חוסך זיכרון על ידי חישוב מחדש של ההפעלות במהלך המעבר לאחור במקום לשמור אותן.
  • optim="adamw_torch": נעשה שימוש בהטמעה הרגילה של אופטימיזציית AdamW מ-PyTorch.
training_args = TrainingArguments(
    per_device_train_batch_size=per_device_batch_size,
    gradient_accumulation_steps=gradient_accumulation_steps,
    fp16=True,
    gradient_checkpointing=True,
    optim="adamw_torch",
)

הסקת מסקנות

הפונקציה run_inference מבצעת בדיקה מהירה של המודל שעבר התאמה עדינה באמצעות הנחיה לדוגמה. היא מוודאת שהמודל נמצא במצב הערכה ומפיקה טקסט כדי לוודא שהמתאמים פועלים בצורה תקינה.

def run_inference(model, tokenizer):
    prompt = "The year was 1812, and the"
    # ... generation logic ...
    logger.info(f"Generated Text: {generated_text}")

12. העברת האפליקציה לקונטיינר

עכשיו יוצרים את קובץ האימג' של קונטיינר האימון באמצעות Docker ומעבירים אותו בדחיפה אל Google Artifact Registry.

  1. בטרמינל, מריצים את הפקודה הבאה כדי לפתוח את הקובץ Dockerfile:
    cloudshell edit Dockerfile
    
  2. מדביקים את הקוד הבא לקובץ Dockerfile:
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime

WORKDIR /app

# Install required packages
RUN pip install --no-cache-dir \
    transformers==4.46.0 \
    datasets==3.1.0 \
    pyarrow==15.0.0 \
    peft==0.13.2 \
    accelerate==1.1.0 \
    tensorboard==2.18.0 \
    nvidia-ml-py==12.535.161 \
    scipy==1.13.1

# Copy training scripts
COPY train.py /app/

# Run training
CMD ["python", "train.py"]

יצירה של קונטיינר ושליחתו

  1. יוצרים את מאגר Artifact Registry:
    gcloud artifacts repositories create gke-finetune \
        --repository-format=docker \
        --location=$REGION \
        --description="Docker repository for Llama fine-tuning"
    
  2. יוצרים את קובץ האימג' ומעבירים אותו בדחיפה באמצעות Cloud Build:
    gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/gke-finetune/llama-trainer:latest .
    

13. פריסת משימת הכוונון העדין

  1. יוצרים את מניפסט המשימה של Kubernetes כדי להתחיל את משימת הכוונון העדין. בטרמינל, מריצים את הפקודה:
    cloudshell edit training_job.yaml
    
  2. מדביקים את הקוד הבא לקובץ training_job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
  name: llama-fine-tuning
  namespace: ml-workloads
spec:
  template:
    metadata:
      annotations:
        gke-gcsfuse/volumes: "true"
        gke-gcsfuse/memory-limit: "4Gi"
    spec:
      serviceAccountName: llama-workload-sa
      restartPolicy: OnFailure
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
        - key: cloud.google.com/gke-spot
          operator: Exists
          effect: NoSchedule
      nodeSelector:
        cloud.google.com/gke-accelerator: nvidia-l4

      containers:
        - name: training
          image: ${REGION}-docker.pkg.dev/${PROJECT_ID}/gke-finetune/llama-trainer:latest
          env:
            - name: MODEL_PATH
              value: "/gcs-mount/llama2-7b/llama2-7b-hf"
            - name: OUTPUT_PATH
              value: "/tmp/llama2-7b-american-stories"
            - name: NCCL_DEBUG
              value: "INFO"
          resources:
            requests:
              nvidia.com/gpu: 1
              cpu: "8"
              memory: "32Gi"
            limits:
              nvidia.com/gpu: 1

          volumeMounts:
            - name: gcs-fuse
              mountPath: /gcs-mount
            - name: shm
              mountPath: /dev/shm
      volumes:
        - name: gcs-fuse
          csi:
            driver: gcsfuse.csi.storage.gke.io
            volumeAttributes:
              bucketName: ${BUCKET_NAME}
              mountOptions: "implicit-dirs"
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 32Gi
  1. לבסוף, מפעילים את מניפסט המשימה של Kubernetes כדי להתחיל את משימת הכוונון העדין באשכול GKE.
    envsubst < training_job.yaml | kubectl apply -f -
    

14. מעקב אחרי משימת האימון

אפשר לעקוב אחרי ההתקדמות של משימת האימון במסוף Google Cloud.

  1. עוברים לדף Kubernetes Engine > עומסי עבודה.
    צפייה בעומסי עבודה ב-GKE
  2. לוחצים על המשימה llama-fine-tuning כדי לראות את הפרטים שלה.
  3. הכרטיסייה פרטים מוצגת כברירת מחדל. אפשר לראות את מדדי ניצול ה-GPU בקטע Resources (משאבים).מדדי ניצול של GPU
  4. לוחצים על הכרטיסייה יומנים כדי לראות את יומני האימון. אפשר לראות את התקדמות האימון, כולל ההפסד וקצב הלמידה.יומני אימון שמציגים את הפסד ואת קצב הלמידה

15. הסרת המשאבים

כדי להימנע מחיובים בחשבון Google Cloud בגלל השימוש במשאבים שנעשה במסגרת המדריך הזה, אפשר למחוק את הפרויקט שמכיל את המשאבים, או להשאיר את הפרויקט ולמחוק את המשאבים בנפרד.

מחיקת אשכול GKE

gcloud container clusters delete $CLUSTER_NAME --region $REGION --quiet

מחיקת מאגר Artifact Registry

gcloud artifacts repositories delete gke-finetune --location $REGION --quiet

מחיקת קטגוריית GCS

gcloud storage rm -r gs://${BUCKET_NAME}

16. מעולה!

השלמת בהצלחה כוונון עדין של מודל שפה גדול (LLM) בקוד פתוח ב-GKE.

Recap

בשיעור ה-Lab הזה:

  • הקצאת אשכול GKE עם האצת GPU.
  • הגדרת Workload Identity לגישה מאובטחת לשירותי Google Cloud.
  • העברת משימת אימון של PyTorch לקונטיינר באמצעות Docker ו-Artifact Registry.
  • הפעלתם משימת כוונון עדין באמצעות LoRA כדי להתאים את Llama 2 למערך נתונים חדש.

המאמרים הבאים

תוכנית לימודים של Google Cloud

ה-Lab הזה הוא חלק מתוכנית הלימודים Production-Ready AI with Google Cloud. כאן אפשר לעיין בתוכנית הלימודים המלאה כדי לגשר על הפער בין אב טיפוס לבין ייצור.

שתפו את ההתקדמות שלכם באמצעות ההאשטאג #ProductionReadyAI.