Ottimizzare Gemma per le attività di visione utilizzando Hugging Face Transformers e QLoRA

Visualizza su ai.google.dev Esegui in Google Colab Esegui in Kaggle Apri in Vertex AI Visualizza l'origine su GitHub

Questa guida ti illustra come ottimizzare Gemma su un set di dati di immagini e testo personalizzato per un'attività di visione (generazione di descrizioni dei prodotti) utilizzando Hugging Face Transformers e TRL. Scoprirai:

  • Che cos'è l'adattamento a basso ranking quantizzato (QLoRA)
  • Come configurare l'ambiente di sviluppo
  • Come creare e preparare il set di dati di ottimizzazione
  • Come ottimizzare Gemma utilizzando TRL e SFTTrainer
  • Come testare l'inferenza del modello e generare descrizioni dei prodotti da immagini e testo.

Che cos'è l'adattamento a basso ranking quantizzato (QLoRA)

Questa guida illustra l'utilizzo dell'adattamento a basso ranking quantizzato (QLoRA), che è diventato un metodo diffuso per ottimizzare in modo efficiente gli LLM, in quanto riduce i requisiti di risorse di calcolo mantenendo prestazioni elevate. In QloRA, il modello preaddestrato viene quantizzato a 4 bit e i pesi vengono bloccati. Vengono quindi collegati i livelli di adattatore addestrabile (LoRA) e vengono addestrati solo i livelli di adattatore. Successivamente, i pesi dell'adattatore possono essere uniti al modello di base o mantenuti come adattatore separato.

Configura l'ambiente di sviluppo

Il primo passaggio consiste nell'installare le librerie Hugging Face, inclusi TRL e set di dati, per ottimizzare il modello aperto.

# Install Pytorch & other libraries
%pip install torch tensorboard torchvision

# Install Transformers
%pip install transformers

# Install Hugging Face libraries
%pip install datasets accelerate evaluate bitsandbytes trl peft protobuf pillow sentencepiece

# COMMENT IN: if you are running on a GPU that supports BF16 data type and flash attn, such as NVIDIA L4 or NVIDIA A100
#%pip install flash-attn

Nota: se utilizzi una GPU con architettura Ampere (come NVIDIA L4) o versioni successive, puoi utilizzare Flash Attention. Flash Attention è un metodo che accelera notevolmente i calcoli e riduce la memoria utilizzata da quadratico a lineare nella lunghezza della sequenza, accelerando l'addestramento fino a 3 volte. Scopri di più su FlashAttention.

Per pubblicare il modello, devi disporre di un token Hugging Face valido. Se esegui l'operazione in Google Colab, puoi utilizzare in modo sicuro il token Hugging Face utilizzando i secret di Colab. In alternativa, puoi impostare il token direttamente nel metodo login. Assicurati che il token abbia anche accesso in scrittura, poiché il modello viene inviato all'Hub durante l'addestramento.

# Login into Hugging Face Hub
from huggingface_hub import login
login()

Crea e prepara il set di dati di ottimizzazione

Quando ottimizzi gli LLM, è importante conoscere il caso d'uso e l'attività che vuoi risolvere. In questo modo, puoi creare un set di dati per ottimizzare il modello. Se non hai ancora definito il tuo caso d'uso, ti consigliamo di ricominciare da capo.

Ad esempio, questa guida si concentra sul seguente caso d'uso:

  • Ottimizzazione di un modello Gemma per generare descrizioni dei prodotti concise e ottimizzate per la SEO per una piattaforma di e-commerce, appositamente progettate per la ricerca mobile.

Questa guida utilizza il set di dati philschmid/amazon-product-descriptions-vlm, un set di dati di descrizioni dei prodotti Amazon, incluse immagini e categorie di prodotti.

Hugging Face TRL supporta le conversazioni multimodali. La parte importante è il ruolo "image", che indica alla classe di elaborazione che deve caricare l'immagine. La struttura deve essere la seguente:

{"messages": [{"role": "system", "content": [{"type": "text", "text":"You are..."}]}, {"role": "user", "content": [{"type": "text", "text": "..."}, {"type": "image"}]}, {"role": "assistant", "content": [{"type": "text", "text": "..."}]}]}
{"messages": [{"role": "system", "content": [{"type": "text", "text":"You are..."}]}, {"role": "user", "content": [{"type": "text", "text": "..."}, {"type": "image"}]}, {"role": "assistant", "content": [{"type": "text", "text": "..."}]}]}
{"messages": [{"role": "system", "content": [{"type": "text", "text":"You are..."}]}, {"role": "user", "content": [{"type": "text", "text": "..."}, {"type": "image"}]}, {"role": "assistant", "content": [{"type": "text", "text": "..."}]}]}

Ora puoi utilizzare la libreria Hugging Face Datasets per caricare il set di dati e creare un modello di prompt per combinare l'immagine, il nome del prodotto e la categoria e aggiungere un messaggio di sistema. Il set di dati include le immagini come oggetti Pil.Image.

from datasets import load_dataset
from PIL import Image

# System message for the assistant
system_message = "You are an expert product description writer for Amazon."

# User prompt that combines the user query and the schema
user_prompt = """Create a Short Product description based on the provided <PRODUCT> and <CATEGORY> and image.
Only return description. The description should be SEO optimized and for a better mobile search experience.

<PRODUCT>
{product}
</PRODUCT>

<CATEGORY>
{category}
</CATEGORY>
"""

# Convert dataset to OAI messages
def format_data(sample):
    return {
        "messages": [
            {
                "role": "system",
                #"content": [{"type": "text", "text": system_message}],
                "content": system_message,
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": user_prompt.format(
                            product=sample["Product Name"],
                            category=sample["Category"],
                        ),
                    },
                    {
                        "type": "image",
                        "image": sample["image"],
                    },
                ],
            },
            {
                "role": "assistant",
                "content": [{"type": "text", "text": sample["description"]}],
            },
        ],
    }

def process_vision_info(messages: list[dict]) -> list[Image.Image]:
    image_inputs = []
    # Iterate through each conversation
    for msg in messages:
        # Get content (ensure it's a list)
        content = msg.get("content", [])
        if not isinstance(content, list):
            content = [content]

        # Check each content element for images
        for element in content:
            if isinstance(element, dict) and (
                "image" in element or element.get("type") == "image"
            ):
                # Get the image and convert to RGB
                if "image" in element:
                    image = element["image"]
                else:
                    image = element
                image_inputs.append(image.convert("RGB"))
    return image_inputs

# Load dataset from the hub
dataset = load_dataset("philschmid/amazon-product-descriptions-vlm", split="train")
dataset = dataset.train_test_split(test_size=0.1)

# Convert dataset to OAI messages
# need to use list comprehension to keep Pil.Image type, .mape convert image to bytes
dataset_train = [format_data(sample) for sample in dataset["train"]]
dataset_test = [format_data(sample) for sample in dataset["test"]]

print(dataset_train[345]["messages"])
README.md: 0.00B [00:00, ?B/s]
data/train-00000-of-00001.parquet:   0%|          | 0.00/47.6M [00:00<?, ?B/s]
Generating train split:   0%|          | 0/1345 [00:00<?, ? examples/s]
[{'role': 'system', 'content': 'You are an expert product description writer for Amazon.'}, {'role': 'user', 'content': [{'type': 'text', 'text': "Create a Short Product description based on the provided <PRODUCT> and <CATEGORY> and image.\nOnly return description. The description should be SEO optimized and for a better mobile search experience.\n\n<PRODUCT>\nRazor Agitator BMX/Freestyle Bike, 20-Inch\n</PRODUCT>\n\n<CATEGORY>\nSports & Outdoors | Outdoor Recreation | Cycling | Kids' Bikes & Accessories | Kids' Bikes\n</CATEGORY>\n"}, {'type': 'image', 'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=500x413 at 0x7B7250181790>}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': 'Conquer the streets with the Razor Agitator BMX Bike! This 20-inch freestyle bike is built for young riders ready to take on any challenge. Durable frame, responsive handling – perfect for tricks and cruising.  Get yours today!'}]}]

Ottimizza Gemma utilizzando TRL e SFTTrainer

Ora puoi ottimizzare il modello. Hugging Face TRL SFTTrainer semplifica l'ottimizzazione supervisionata degli LLM aperti. Il SFTTrainer è una sottoclasse del Trainer della libreria transformers e supporta tutte le stesse funzionalità, tra cui logging, valutazione e checkpoint, ma aggiunge ulteriori funzionalità di qualità della vita, tra cui:

  • Formattazione del set di dati, inclusi formati conversazionali e di istruzioni
  • Addestramento solo sui completamenti, ignorando i prompt
  • Compressione dei set di dati per un addestramento più efficiente
  • Supporto per l'ottimizzazione efficiente dei parametri (PEFT), incluso QloRA
  • Preparazione del modello e del tokenizer per l'ottimizzazione conversazionale (ad esempio l'aggiunta di token speciali)

Il seguente codice carica il modello Gemma e il tokenizer da Hugging Face e inizializza la configurazione della quantizzazione.

import torch
from transformers import AutoProcessor, AutoModelForImageTextToText, BitsAndBytesConfig

# Hugging Face model id
model_id = "google/gemma-4-E2B" # @param ["google/gemma-4-E2B","google/gemma-4-E4B"] {"allow-input":true}

# Check if GPU benefits from bfloat16
if torch.cuda.get_device_capability()[0] < 8:
    raise ValueError("GPU does not support bfloat16, please use a GPU that supports bfloat16.")

# Define model init arguments
model_kwargs = dict(
    dtype=torch.bfloat16, # What torch dtype to use, defaults to auto
    device_map="auto", # Let torch decide how to load the model
)

# BitsAndBytesConfig int-4 config
model_kwargs["quantization_config"] = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=model_kwargs["dtype"],
    bnb_4bit_quant_storage=model_kwargs["dtype"],
)

# Load model and tokenizer
model = AutoModelForImageTextToText.from_pretrained(model_id, **model_kwargs)
processor = AutoProcessor.from_pretrained("google/gemma-4-E2B-it") # Load the Instruction Tokenizer to use the official Gemma template
config.json: 0.00B [00:00, ?B/s]
model.safetensors:   0%|          | 0.00/10.2G [00:00<?, ?B/s]
Loading weights:   0%|          | 0/2011 [00:00<?, ?it/s]
generation_config.json:   0%|          | 0.00/149 [00:00<?, ?B/s]
processor_config.json: 0.00B [00:00, ?B/s]
chat_template.jinja: 0.00B [00:00, ?B/s]
config.json: 0.00B [00:00, ?B/s]
tokenizer_config.json: 0.00B [00:00, ?B/s]
tokenizer.json:   0%|          | 0.00/32.2M [00:00<?, ?B/s]

SFTTrainer supporta un'integrazione integrata con peft, che semplifica l'ottimizzazione efficiente degli LLM utilizzando QLoRA. Devi solo creare un LoraConfig e fornirlo al trainer.

from peft import LoraConfig

peft_config = LoraConfig(
    lora_alpha=16,
    lora_dropout=0.05,
    r=16,
    bias="none",
    target_modules="all-linear",
    task_type="CAUSAL_LM",
    modules_to_save=["lm_head", "embed_tokens"], # make sure to save the lm_head and embed_tokens as you train the special tokens
    ensure_weight_tying=True,
)

Prima di poter iniziare l'addestramento, devi definire l'iperparametro che vuoi utilizzare in un SFTConfig e un collate_fn personalizzato per gestire l'elaborazione della visione. collate_fn converte i messaggi con testo e immagini in un formato comprensibile per il modello.

from trl import SFTConfig

args = SFTConfig(
    output_dir="gemma-product-description",     # directory to save and repository id
    num_train_epochs=3,                         # number of training epochs
    per_device_train_batch_size=1,              # batch size per device during training
    optim="adamw_torch_fused",                  # use fused adamw optimizer
    logging_steps=5,                            # log every 5 steps
    save_strategy="epoch",                      # save checkpoint every epoch
    eval_strategy="epoch",                      # evaluate checkpoint every epoch
    learning_rate=2e-4,                         # learning rate, based on QLoRA paper
    bf16=True,                                  # use bfloat16 precision
    max_grad_norm=0.3,                          # max gradient norm based on QLoRA paper
    lr_scheduler_type="constant",               # use constant learning rate scheduler
    push_to_hub=True,                           # push model to hub
    report_to="tensorboard",                    # report metrics to tensorboard
    dataset_text_field="",                      # need a dummy field for collator
    dataset_kwargs={"skip_prepare_dataset": True}, # important for collator
    remove_unused_columns = False               # important for collator
)

# Create a data collator to encode text and image pairs
def collate_fn(examples):
    texts = []
    images = []
    for example in examples:
        image_inputs = process_vision_info(example["messages"])
        text = processor.apply_chat_template(
            example["messages"], add_generation_prompt=False, tokenize=False
        )
        texts.append(text.strip())
        images.append(image_inputs)

    # Tokenize the texts and process the images
    batch = processor(text=texts, images=images, return_tensors="pt", padding=True)

    # The labels are the input_ids, and we mask the padding tokens and image tokens in the loss computation
    labels = batch["input_ids"].clone()

    # Mask tokens for not being used in the loss computation
    labels[labels == processor.tokenizer.pad_token_id] = -100
    labels[labels == processor.tokenizer.boi_token_id] = -100
    labels[labels == processor.tokenizer.image_token_id] = -100
    labels[labels == processor.tokenizer.eoi_token_id] = -100

    batch["labels"] = labels
    return batch

Ora hai tutti i componenti di base necessari per creare il tuo SFTTrainer per avviare l'addestramento del modello.

from trl import SFTTrainer

# Create Trainer object
trainer = SFTTrainer(
    model=model,
    args=args,
    train_dataset=dataset_train,
    eval_dataset=dataset_test,
    peft_config=peft_config,
    processing_class=processor,
    data_collator=collate_fn,
)

Avvia l'addestramento chiamando il metodo train().

# Start training, the model will be automatically saved to the Hub and the output directory
trainer.train()

# Save the final model again to the Hugging Face Hub
trainer.save_model()
The tokenizer has new PAD/BOS/EOS tokens that differ from the model config and generation config. The model config and generation config were aligned accordingly, being updated with the tokenizer's values. Updated tokens: {'eos_token_id': 1, 'bos_token_id': 2, 'pad_token_id': 0}.

Prima di poter testare il modello, assicurati di liberare la memoria.

# free the memory again
del model
del trainer
torch.cuda.empty_cache()

Quando utilizzi QLoRA, addestri solo gli adattatori e non l'intero modello. Ciò significa che quando salvi il modello durante l'addestramento, salvi solo i pesi dell'adattatore e non l'intero modello. Se vuoi salvare l'intero modello, in modo da semplificarne l'utilizzo con gli stack di gestione come vLLM o TGI, puoi unire i pesi dell'adattatore ai pesi del modello utilizzando il metodo merge_and_unload e poi salvare il modello con il metodo save_pretrained. In questo modo viene salvato un modello predefinito, che può essere utilizzato per l'inferenza.

from peft import PeftModel

# Load Model base model
model = AutoModelForImageTextToText.from_pretrained(model_id, low_cpu_mem_usage=True)

# Merge LoRA and base model and save
peft_model = PeftModel.from_pretrained(model, args.output_dir)
merged_model = peft_model.merge_and_unload()
merged_model.save_pretrained("merged_model", safe_serialization=True, max_shard_size="2GB")

processor = AutoProcessor.from_pretrained(args.output_dir)
processor.save_pretrained("merged_model")
Loading weights:   0%|          | 0/2011 [00:00<?, ?it/s]
Writing model shards:   0%|          | 0/5 [00:00<?, ?it/s]
['merged_model/processor_config.json']

Testa l'inferenza del modello e genera descrizioni dei prodotti

Una volta completato l'addestramento, dovrai valutare e testare il modello. Puoi caricare diversi campioni dal set di dati di test e valutare il modello su questi campioni.

model_id = "merged_model"

# Load Model with PEFT adapter
model = AutoModelForImageTextToText.from_pretrained(
  model_id,
  device_map="auto",
  dtype="auto",
)
processor = AutoProcessor.from_pretrained(model_id)
Loading weights:   0%|          | 0/2012 [00:00<?, ?it/s]
The tied weights mapping and config for this model specifies to tie model.language_model.embed_tokens.weight to lm_head.weight, but both are present in the checkpoints with different values, so we will NOT tie them. You should update the config with `tie_word_embeddings=False` to silence this warning.

Puoi testare l'inferenza fornendo un nome, una categoria e un'immagine del prodotto. Il sample include una action figure Marvel.

import requests
from PIL import Image

# Test sample with Product Name, Category and Image
sample = {
  "product_name": "Hasbro Marvel Avengers-Serie Marvel Assemble Titan-Held, Iron Man, 30,5 cm Actionfigur",
  "category": "Toys & Games | Toy Figures & Playsets | Action Figures",
  "image": Image.open(requests.get("https://m.media-amazon.com/images/I/81+7Up7IWyL._AC_SY300_SX300_.jpg", stream=True).raw).convert("RGB")
}

def generate_description(sample, model, processor):
    # Convert sample into messages and then apply the chat template
    messages = [
        {"role": "system", "content": system_message},
        {"role": "user", "content": [
            {"type": "image","image": sample["image"]},
            {"type": "text", "text": user_prompt.format(product=sample["product_name"], category=sample["category"])},
        ]},
    ]
    text = processor.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    print(text)
    # Process the image and text
    image_inputs = process_vision_info(messages)
    # Tokenize the text and process the images
    inputs = processor(
        text=[text],
        images=image_inputs,
        padding=True,
        return_tensors="pt",
    )
    # Move the inputs to the device
    inputs = inputs.to(model.device)

    # Generate the output
    stop_token_ids = [processor.tokenizer.eos_token_id, processor.tokenizer.convert_tokens_to_ids("<turn|>")]
    generated_ids = model.generate(**inputs, max_new_tokens=256, top_p=1.0, do_sample=True, temperature=0.8, eos_token_id=stop_token_ids, disable_compile=True)
    # Trim the generation and decode the output to text
    generated_ids_trimmed = [out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]
    output_text = processor.batch_decode(
        generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
    )
    return output_text[0]

# generate the description
description = generate_description(sample, model, processor)
print("MODEL OUTPUT>> \n")
print(description)
<bos><|turn>system
You are an expert product description writer for Amazon.<turn|>
<|turn>user


<|image|>

Create a Short Product description based on the provided <PRODUCT> and <CATEGORY> and image.
Only return description. The description should be SEO optimized and for a better mobile search experience.

<PRODUCT>
Hasbro Marvel Avengers-Serie Marvel Assemble Titan-Held, Iron Man, 30,5 cm Actionfigur
</PRODUCT>

<CATEGORY>
Toys & Games | Toy Figures & Playsets | Action Figures
</CATEGORY><turn|>
<|turn>model

MODEL OUTPUT>> 

Enhance your collection with the Marvel Avengers - Avengers Assemble Ultron-Comforter Set! This soft and cuddly blanket and pillowcase feature everyone's favorite Avengers, Iron Man, and his loyal companion War Machine. Officially licensed by Marvel.  Bring home the heroic team!

Riepilogo e passaggi successivi

Questo tutorial ha illustrato come ottimizzare un modello Gemma per le attività di visione utilizzando TRL e QLoRA, in particolare per la generazione di descrizioni dei prodotti. Consulta i seguenti documenti: