ข้ามไปยังเนื้อหาหลัก

วิธีปรับแต่ง DiffusionGemma บน PubMedQA ด้วย Unsloth

เรียนรู้วิธีปรับแต่ง DiffusionGemma 26B-A4B สำหรับการตอบคำถามด้านชีวการแพทย์บน PubMedQA ด้วย Unsloth, LoRA, Hugging Face และ GPU RunPod H100
อัปเดตแล้ว 3 ส.ค. 2569  · 9 นาที อ่าน

สำรวจด้วย AI

เปิดใน ChatGPTเปิดใน Claudeเปิดใน Perplexity

DiffusionGemma คือโมเดลภาษาทดลองจาก Google DeepMind ที่สร้างข้อความต่างจากโมเดลภาษาแบบดั้งเดิม แทนที่จะทำนายทีละโทเค็นจากซ้ายไปขวา โมเดลจะเริ่มจาก ผืนผ้าใบของโทเค็นที่มีสัญญาณรบกวนคงที่ และค่อย ๆ ปรับให้ดีขึ้นผ่านขั้นตอนการลบสัญญาณรบกวนหลายครั้ง วิธีนี้ช่วยให้โมเดลอัปเดตหลายตำแหน่งของโทเค็นแบบขนาน และแก้ไขบางส่วนของคำตอบระหว่างการสร้างได้

ในคู่มือนี้ เราจะทำการปรับแต่ง diffusiongemma-26B-A4B-it บนชุดข้อมูล PubMedQA โดยใช้ GPU NVIDIA H100 โมเดลจะได้รับคำถามด้านชีวการแพทย์พร้อมบริบทที่เกี่ยวข้อง แล้วทำนายคำตอบว่า yes, no หรือ maybe เราจะเตรียมข้อมูล ฝึกตัวปรับ LoRA ประเมินโมเดลก่อนและหลังการปรับแต่ง และอัปโหลดตัวปรับขั้นสุดท้ายไปยัง Hugging Face

ฉันได้เผยแพร่โน้ตบุ๊กฉบับเต็มแล้ว เพื่อให้ตรวจสอบโค้ดต้นฉบับ ติดตามไปพร้อมกัน และรันการทดลองได้ด้วยตนเอง

หมายเหตุ: โครงการนี้มีไว้เพื่อการเรียนรู้และทดลองเท่านั้น ไม่ควรใช้เพื่อการตัดสินใจทางการแพทย์จริง

1. เปิด RunPod Jupyter Notebook

สร้างพ็อด RunPod ใหม่ด้วย GPU NVIDIA H100 และเลือกเทมเพลต PyTorch/Jupyter กำหนดค่า พื้นที่จัดเก็บถาวรอย่างน้อย 100 GB เพื่อป้องกันไม่ให้ไฟล์โมเดลและผลลัพธ์การฝึกหายไปเมื่อพ็อดหยุดทำงาน

เพิ่มโทเค็นเข้าถึง Hugging Face เป็นตัวแปรสภาพแวดล้อม:

HF_TOKEN=your_hugging_face_token

Editing the Runpod Pytorch template

วิธีนี้ช่วยให้ดาวน์โหลดโมเดลและชุดข้อมูลได้เร็วขึ้น และให้อัปโหลดตัวปรับ LoRA ที่บันทึกไว้ไปยัง Hugging Face ได้โดยไม่ต้องเข้าสู่ระบบจากโน้ตบุ๊กด้วยตนเอง

พ็อดที่กำหนดค่าควรมีค่าใช้จ่ายประมาณ 3 ดอลลาร์ต่อชั่วโมง ทั้งนี้ราคาสุดท้ายอาจแตกต่างกันตามความพร้อมใช้งานของ GPU และประเภทพ็อดที่เลือก

Runpod H100 GPU summary

เมื่อพ็อดทำงานแล้ว ให้เปิด JupyterLab หรือ Jupyter Notebook จากอินเทอร์เฟซ RunPod และสร้างโน้ตบุ๊กใหม่ชื่อ diffusiongemma_pubmedqa.ipynb

2. ติดตั้งแพ็กเกจที่จำเป็น

รันคำสั่งต่อไปนี้ในเซลล์แรกของโน้ตบุ๊กเพื่อติดตั้ง Unsloth และไลบรารีที่ต้องใช้สำหรับโหลด ปรับแต่ง และบันทึก DiffusionGemma

%%capture
%pip install --upgrade pip wheel setuptools packaging ninja
%pip install unsloth
%pip install --no-deps --upgrade --force-reinstall git+https://github.com/unslothai/unsloth-zoo.git git+https://github.com/unslothai/unsloth.git
%pip install sentencepiece protobuf "datasets==4.3.0" "huggingface_hub>=0.34.0" hf_transfer
%pip install --no-deps bitsandbytes accelerate peft trl triton
%pip install --no-deps --upgrade "torchao>=0.16.0"
%pip install --no-deps transformers==5.11.0 "tokenizers>=0.22.0,<=0.23.0"

คำสั่ง %%capture จะซ่อนเอาต์พุตการติดตั้งที่ยาว แพ็กเกจถูกตรึงเวอร์ชันเพื่อหลีกเลี่ยงปัญหาความเข้ากันได้ระหว่าง DiffusionGemma, Transformers, Unsloth และไลบรารีการฝึก

เมื่อการติดตั้งเสร็จสิ้น ให้รีสตาร์ตเคอร์เนลของโน้ตบุ๊กก่อนดำเนินการต่อ

3. นำเข้าไลบรารี

นำเข้าไลบรารีที่ต้องใช้สำหรับการเตรียมชุดข้อมูล การโหลดโมเดล การฝึก และการประเมินผล

import copy
import os
import random
import time

import torch
from datasets import load_dataset
from unsloth import FastModel

os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
torch._dynamo.config.recompile_limit = 64

print("Torch:", torch.__version__)
print("CUDA available:", torch.cuda.is_available())
print(
    "GPU:",
    torch.cuda.get_device_name(0)
    if torch.cuda.is_available()
    else "None",
)

HF_HUB_ENABLE_HF_TRANSFER ช่วยให้ดาวน์โหลดจาก Hugging Face Hub ได้เร็วขึ้น ขณะที่การเพิ่มขีดจำกัดการคอมไพล์ซ้ำของ Dynamo ช่วยป้องกันการหยุดชะงักระหว่างทำงานกับโมเดล

เมื่อเซลล์รันสำเร็จ Unsloth จะปรับสภาพแวดล้อมการฝึก และเอาต์พุตควรยืนยันว่า CUDA พร้อมใช้งานและตรวจพบ GPU H100 แล้ว

🦥 Unsloth: Will patch your computer to enable 2x faster free fine-tuning.
🦥 Unsloth Zoo will now patch everything to make training faster!
Torch: 2.10.0+cu128
CUDA available: True
GPU: NVIDIA H100 80GB HBM3

4. ตั้งค่าคอนฟิก

กำหนดค่าโมเดล ชุดข้อมูล พารามิเตอร์การฝึก การตั้งค่าการประเมินผล และไดเรกทอรีเอาต์พุตไว้ในที่เดียว

MODEL_NAME = "unsloth/diffusiongemma-26B-A4B-it"
DATASET_NAME = "qiaojin/PubMedQA"

TRAIN_SUBSET = "pqa_artificial"
EVAL_SUBSET = "pqa_labeled"

N_TRAIN = 3000
N_EVAL = 200

MAX_CONTEXT_CHARS = 2500

STEPS = 60
GRAD_ACCUM = 4
LR = 1e-4
T_LO = 0.1

EVAL_TOTAL = 50
EVAL_DENOISING_STEPS = 16

OUTPUT_DIR = "diffusiongemma_pubmedqa_lora"

เราจะใช้ตัวอย่างแบบประดิษฐ์ 3,000 ตัวอย่างสำหรับการฝึก และตัวอย่างที่ติดฉลากด้วยมือ 200 ตัวอย่างสำหรับการประเมินผล เพื่อให้การทดลองเร็วขึ้น โมเดลจะฝึก 60 สเต็ป และประเมิน 50 ตัวอย่างโดยใช้ 16 ขั้นตอนการลบสัญญาณรบกวน

การทำงานของ DiffusionGemma

ก่อนโหลดโมเดล ควรจินตนาการว่าจริง ๆ แล้ว DiffusionGemma สร้างคำตอบอย่างไร แทนที่จะเขียนโทเค็นต่อเนื่องกันทีละตัว มันจะเริ่มจากผืนผ้าใบโทเค็นความยาวคงที่และค่อย ๆ ปรับให้ดีขึ้นผ่านหลายขั้นตอนการลบสัญญาณรบกวน โดยอัปเดตหลายตำแหน่งพร้อมกันจนข้อความกลายเป็นคำตอบที่สอดคล้องกัน

โค้ดด้านล่างรายงานความยาวผืนผ้าใบเป็น 256 ซึ่งเป็นขนาดของหนึ่งบล็อก สำหรับคำตอบสั้น ๆ แบบ yes/no/maybe ผืนผ้าใบเดียวก็เพียงพอ ส่วนผลลัพธ์ที่ยาวกว่าจะถูกสร้างโดยเชื่อมผืนผ้าใบเข้าด้วยกันทีละบล็อก ไดอะแกรมด้านล่างแสดงกระบวนการปรับละเอียดนี้ในภาพรวม:

Text Diffusion Explanation

5. โหลด DiffusionGemma

โหลดโมเดล DiffusionGemma ที่ปรับจูนด้วยคำสั่ง (instruction-tuned) ในความแม่นยำ bfloat16 เราจะไม่ใช้การควอนไทซ์ 4 บิต เพราะ GPU H100 มีหน่วยความจำมากพอที่จะโหลดโมเดลด้วยความแม่นยำที่สูงกว่า

model, tokenizer = FastModel.from_pretrained(
    model_name=MODEL_NAME,
    dtype=torch.bfloat16,
    load_in_4bit=False,
)

processor = tokenizer
tok = processor.tokenizer if hasattr(processor, "tokenizer") else processor

vocab = model.config.text_config.vocab_size
canvas_len = model.config.canvas_length

dev = next(
    (p.device for p in model.parameters() if p.device.type != "meta"),
    torch.device("cuda"),
)

print("Vocab size:", vocab)
print("Canvas length:", canvas_len)
print("Model device:", dev)

ขนาดคลังคำจะใช้ตอนเพิ่มสัญญาณรบกวนแบบสุ่มระหว่างการฝึกแบบ diffusion ส่วนความยาวผืนผ้าใบกำหนดจำนวนโทเค็นสูงสุดที่โมเดลสามารถปรับให้ดีขึ้นได้ในหนึ่งบล็อกของการสร้างผลลัพธ์

ควรเห็นเอาต์พุตประมาณนี้:

Vocab size: 262144
Canvas length: 256
Model device: cuda:0

6. เพิ่มตัวปรับ LoRA

เพิ่มตัวปรับ LoRA เพื่อให้ฝึกเฉพาะพารามิเตอร์ส่วนเล็ก ๆ ที่เพิ่มเข้ามา แทนการอัปเดตโมเดลฐาน 26 พันล้านพารามิเตอร์ทั้งหมด

model = FastModel.get_peft_model(
    model,
    r=64,
    lora_alpha=128,
    use_gradient_checkpointing=False,
)

วิธีนี้ช่วยลดหน่วยความจำและการคำนวณที่ต้องใช้ในการปรับแต่งได้มาก ปิดการใช้งาน gradient checkpointing เพราะ H100 มีหน่วยความจำ GPU เพียงพอสำหรับการทดลองนี้

7. โหลด PubMedQA

โหลดชุดย่อย PubMedQA แบบประดิษฐ์สำหรับการฝึก และชุดย่อยที่ติดฉลากด้วยมือสำหรับการประเมินผล

train_data = load_dataset(
    DATASET_NAME,
    TRAIN_SUBSET,
    split="train",
)

eval_data = load_dataset(
    DATASET_NAME,
    EVAL_SUBSET,
    split="train",
)

print("Train size:", len(train_data))
print("Eval size:", len(eval_data))
print(train_data[0])

ชุดย่อยสำหรับฝึกมีตัวอย่างที่สร้างขึ้นโดยอัตโนมัติ ขณะที่ชุดย่อยสำหรับประเมินมีคำถามด้านชีวการแพทย์ที่ผู้เชี่ยวชาญติดฉลากไว้

การพิมพ์แถวแรกช่วยให้ตรวจสอบคำถาม บริบทจากบทคัดย่อ และคำตัดสินสุดท้ายก่อนจัดรูปแบบข้อมูล

ควรเห็นว่า:

Train size: 211269
Eval size: 1000

แต่ละตัวอย่างประกอบด้วยคำถามด้านชีวการแพทย์ หนึ่งหรือหลายย่อหน้าบริบทจากบทคัดย่อ และคำตอบสุดท้ายเป็น yes, no หรือ maybe

Loading the PubMedQA

8. แปลงชุดข้อมูล

แปลงแต่ละตัวอย่าง PubMedQA ให้อยู่ในรูปแบบสไตล์แชตที่มีพรอมต์จากผู้ใช้และคำตอบของผู้ช่วย

def make_prompt(row):
    context = " ".join(row["context"]["contexts"])
    context = context[:MAX_CONTEXT_CHARS]
    question = row["question"]

    return f"""Answer the biomedical research question using only the context.

Context:
{context}

Question:
{question}

Answer with only one word: yes, no, or maybe."""


def make_answer(row):
    return row["final_decision"].strip().lower()


def convert_row(row):
    answer = make_answer(row)

    if answer not in ["yes", "no", "maybe"]:
        return None

    return {
        "messages": [
            {"role": "user", "content": make_prompt(row)},
            {"role": "assistant", "content": answer},
        ]
    }


train_rows = []

for row in train_data.select(range(N_TRAIN)):
    item = convert_row(row)

    if item is not None:
        train_rows.append(item)


eval_rows = []

for row in eval_data.select(range(N_EVAL)):
    item = convert_row(row)

    if item is not None:
        eval_rows.append(item)


print("Prepared train examples:", len(train_rows))
print("Prepared eval examples:", len(eval_rows))
print(train_rows[0]["messages"][0]["content"])
print("Answer:", train_rows[0]["messages"][1]["content"])

ย่อหน้าบริบทจะถูกผสานเป็นสตริงเดียวและจำกัดไว้ที่ 2,500 อักขระเพื่อให้ควบคุมอินพุตได้ง่ายขึ้น แต่ละคำตอบถูกแปลงเป็นตัวพิมพ์เล็ก และตัวอย่างที่มีฉลากนอกเหนือจาก yes, no หรือ maybe จะถูกลบออก

การพิมพ์ตัวอย่างแรกที่แปลงแล้วช่วยยืนยันว่าบริบท คำถาม และคำตอบถูกจัดรูปแบบถูกต้องก่อนการฝึก

Formatting the PubMedQA dataset in chat format

9. สร้างตัวอย่างการฝึกแบบ Diffusion

DiffusionGemma ต้องการให้วางคำตอบเป้าหมายไว้ภายในผืนผ้าใบความยาวคงที่ ฟังก์ชันนี้จะโทเคไนซ์พรอมต์ แปลงคำตอบเป็น ID ของโทเค็น เติมให้ครบตามความยาวผืนผ้าใบของโมเดล และสร้างมาสก์ที่แสดงว่าโทเค็นใดควรร่วมคำนวณค่า loss

eos = model.generation_config.eos_token_id or [1]
eos = eos[0] if isinstance(eos, (list, tuple)) else eos

pad = tok.pad_token_id if tok.pad_token_id is not None else eos


def build_examples(rows):
    examples = []

    for row in rows:
        user_message = row["messages"][0]
        assistant_message = row["messages"][1]

        prompt_ids = processor.apply_chat_template(
            [user_message],
            tokenize=True,
            add_generation_prompt=True,
            return_tensors="pt",
        )[0]

        answer_ids = tok.encode(
            assistant_message["content"],
            add_special_tokens=False,
        )

        content = answer_ids + [eos]
        n = len(content)

        if n > canvas_len:
            continue

        x0 = torch.tensor(
            content + [pad] * (canvas_len - n),
            dtype=torch.long,
        )

        loss_mask = torch.zeros(canvas_len, dtype=torch.bool)
        loss_mask[:n] = True

        examples.append((prompt_ids, x0, loss_mask))

    return examples


examples = build_examples(train_rows)

โทเค็นจบลำดับจะถูกเพิ่มหลังแต่ละคำตอบ ส่วนตำแหน่งที่เหลือของผืนผ้าใบจะเติมด้วยโทเค็น padding มาสก์ loss รับประกันว่าการฝึกจะโฟกัสเฉพาะโทเค็นคำตอบและโทเค็นจบลำดับ ไม่ใช่ตำแหน่งที่ถูกเติม

10. สร้างฟังก์ชันทำอนุมานและประเมินผล

ถัดไป กำหนดฟังก์ชันที่ใช้ในการสร้างคำตอบ ทำความสะอาดเอาต์พุตของโมเดล และคำนวณความแม่นยำในการประเมิน

สร้างคำตอบ

ฟังก์ชัน answer_question() จะจัดรูปแบบพรอมต์ สร้างคำตอบผ่านหลายขั้นตอนการลบสัญญาณรบกวน และถอดรหัสโทเค็นที่สร้างเป็นข้อความ

def answer_question(prompt, steps=64):
    input_ids = processor.apply_chat_template(
        [{"role": "user", "content": prompt}],
        tokenize=True,
        add_generation_prompt=True,
        return_tensors="pt",
    ).to(dev)

    gen_config = copy.deepcopy(model.generation_config)
    gen_config.max_denoising_steps = steps
    gen_config.max_new_tokens = canvas_len

    model.eval()

    with torch.no_grad():
        output = model.generate(
            input_ids=input_ids,
            generation_config=gen_config,
        )

    generated = output.sequences[0, input_ids.shape[1]:]
    text = tok.decode(
        generated.tolist(),
        skip_special_tokens=True,
    )

    return text.strip().lower()

สกัดการทำนาย

แม้พรอมต์จะขอคำตอบแบบคำเดียว แต่บางครั้งโมเดลอาจสร้างข้อความเพิ่มเติม ฟังก์ชันนี้จะดึงเอาการทำนาย yes, no หรือ maybe ที่ถูกต้องคำแรกออกมา

def clean_prediction(text):
    text = text.lower().strip()

    if text.startswith("yes"):
        return "yes"

    if text.startswith("no"):
        return "no"

    if text.startswith("maybe"):
        return "maybe"

    words = text.replace(".", " ").replace(",", " ").split()

    for word in words:
        if word in ["yes", "no", "maybe"]:
            return word

    return "unknown"

ประเมินความแม่นยำ

ฟังก์ชันประเมินผลจะเปรียบเทียบการทำนายที่ทำความสะอาดแล้วกับคำตอบที่ถูกต้อง พิมพ์ผลลัพธ์ของแต่ละตัวอย่าง และคืนค่าความแม่นยำโดยรวมพร้อมการทำนายรายตัวอย่าง

def evaluate_model(
    rows,
    total=50,
    steps=64,
    title="Evaluation",
):
    correct = 0
    results = []
    total = min(total, len(rows))

    print(title)
    print("-" * len(title))

    for i, row in enumerate(rows[:total], start=1):
        prompt = row["messages"][0]["content"]
        gold = row["messages"][1]["content"]

        raw_pred = answer_question(prompt, steps=steps)
        pred = clean_prediction(raw_pred)

        is_correct = pred == gold
        correct += int(is_correct)

        results.append({
            "index": i,
            "gold": gold,
            "prediction": pred,
            "raw_prediction": raw_pred,
            "correct": is_correct,
        })

        print(
            f"{i:02d}. Gold: {gold} | "
            f"Pred: {pred} | Correct: {is_correct}"
        )

    accuracy = correct / total if total else 0

    print()
    print("Accuracy:", accuracy)
    print()

    return {
        "accuracy": accuracy,
        "correct": correct,
        "total": total,
        "results": results,
    }

11. ประเมินโมเดลก่อนการปรับแต่ง

รันการประเมินก่อนการฝึกเพื่อกำหนดค่าพื้นฐาน

before_eval = evaluate_model(
    eval_rows,
    total=EVAL_TOTAL,
    steps=EVAL_DENOISING_STEPS,
    title="Before Fine-Tuning Evaluation",
)

การประเมินนี้ใช้ 50 ตัวอย่าง โดยคำตอบแต่ละข้อใช้ 16 ขั้นตอนการลบสัญญาณรบกวน ในการทดลองนี้ โมเดลฐานตอบถูก 30 จาก 50 คำถาม

Baseline result of the DiffusionGemma

ต่อมาจะนำค่าพื้นฐานนี้ไปเปรียบเทียบกับความแม่นยำหลังการปรับแต่ง

12. ตั้งค่าการฝึก

สลับโมเดลเป็นโหมดฝึก สร้างออปติไมเซอร์และตัวกำหนดตารางอัตราการเรียนรู้ และกำหนดว่าจะแทรกสัญญาณรบกวนเข้าในโทเค็นคำตอบที่สะอาดอย่างไรระหว่างการฝึกแบบ diffusion

model.config.use_cache = True
model.train()

opt = torch.optim.AdamW(
    [p for p in model.parameters() if p.requires_grad],
    lr=LR,
    betas=(0.9, 0.95),
    weight_decay=0.0,
)

sched = torch.optim.lr_scheduler.OneCycleLR(
    opt,
    max_lr=LR,
    total_steps=STEPS,
    pct_start=0.03,
    anneal_strategy="cos",
)

ส่งเฉพาะพารามิเตอร์ที่มี requires_grad=True ไปยังออปติไมเซอร์ ซึ่งหมายความว่ากระบวนการฝึกจะอัปเดตตัวปรับ LoRA ไม่ใช่โมเดลเต็ม

ถัดไป สร้างฟังก์ชันการทำให้เสียหายที่สุ่มแทนที่สัดส่วนหนึ่งของผืนผ้าใบคำตอบด้วยโทเค็นแบบสุ่ม

def corrupt(x0):
    noise_level = random.uniform(T_LO, 1.0)

    xt = x0.to(dev).clone()

    noise_mask = (
        torch.rand(canvas_len, device=dev) < noise_level
    )

    xt[noise_mask] = torch.randint(
        0,
        vocab,
        (canvas_len,),
        device=dev,
    )[noise_mask]

    return xt.unsqueeze(0)

ระดับสัญญาณรบกวนจะเปลี่ยนไปในแต่ละตัวอย่าง ระหว่างการฝึก โมเดลจะเรียนรู้การกู้คืนคำตอบต้นฉบับจากโทเค็นบนผืนผ้าใบที่ถูกทำให้เสียหายเหล่านี้

13. ฝึกโมเดล

ลูปต่อไปนี้จะฝึกตัวปรับ LoRA เป็นเวลา 60 สเต็ป โดยใช้การสะสมกราเดียนต์

order = list(range(len(examples)))
ptr = 0
start_time = time.time()

opt.zero_grad(set_to_none=True)

for step in range(1, STEPS + 1):
    step_loss = 0.0

    for _ in range(GRAD_ACCUM):
        if ptr >= len(order):
            random.shuffle(order)
            ptr = 0

        prompt_ids, x0, loss_mask = examples[order[ptr]]
        ptr += 1

        output = model(
            input_ids=prompt_ids.unsqueeze(0).to(dev),
            canvas_ids=corrupt(x0),
            self_conditioning_logits=None,
        )

        logits = output.logits[0].float()
        mask = loss_mask.to(dev)

        loss = torch.nn.functional.cross_entropy(
            logits[mask],
            x0.to(dev)[mask],
        )

        (loss / GRAD_ACCUM).backward()
        step_loss += loss.item() / GRAD_ACCUM

    torch.nn.utils.clip_grad_norm_(
        [
            p
            for p in model.parameters()
            if p.requires_grad
        ],
        1.0,
    )

    opt.step()
    sched.step()
    opt.zero_grad(set_to_none=True)

    if step % 20 == 0:
        elapsed = time.time() - start_time

        print(
            f"step {step}/{STEPS} | "
            f"loss {step_loss:.4f} | "
            f"{elapsed:.0f}s"
        )

ในแต่ละตัวอย่างฝึก โมเดลจะได้รับพรอมต์ด้านชีวการแพทย์และผืนผ้าใบคำตอบที่ถูกทำให้เสียหาย จะคำนวณ ค่า loss แบบ cross-entropy เฉพาะโทเค็นคำตอบจริงที่เลือกด้วยมาสก์ loss

การสะสมกราเดียนต์จะรวมสี่ตัวอย่างก่อนอัปเดตโมเดล และมีการใช้การตัดกราเดียนต์เพื่อให้การฝึกมีเสถียรภาพ

ในการทดลองนี้ การฝึกเสร็จในเวลาประมาณสองนาที:

step 20/60 | loss 0.0019 | 43s
step 40/60 | loss 0.0003 | 85s
step 60/60 | loss 0.0001 | 126s

ค่า loss ที่ลดลงอย่างต่อเนื่องบ่งชี้ว่าตัวปรับกำลังเรียนรู้ที่จะกู้คืนคำตอบที่คาดหวังจากผืนผ้าใบที่มีสัญญาณรบกวน ระหว่างการฝึก ยังสามารถรัน nvidia-smi ในเทอร์มินัลของ RunPod เพื่อติดตามการใช้หน่วยความจำและการใช้งาน GPU ได้

Nvidia model statistic while model training

14. ประเมินโมเดลที่ปรับแต่งแล้ว

รันการประเมินแบบเดียวกันอีกครั้งหลังการฝึกเพื่อวัดว่าการปรับแต่งทำให้ประสิทธิภาพของโมเดลดีขึ้นหรือไม่

after_eval = evaluate_model(
    eval_rows,
    total=EVAL_TOTAL,
    steps=EVAL_DENOISING_STEPS,
    title="After Fine-Tuning Evaluation",
)

โมเดลที่ปรับแต่งแล้วจะถูกประเมินบนตัวอย่าง 50 ตัวอย่างเดิม โดยใช้ 16 ขั้นตอนการลบสัญญาณรบกวนเช่นเดียวกับการประเมินค่าพื้นฐาน

Fine-tune result of the DiffusionGemma

จากนั้น เปรียบเทียบความแม่นยำก่อนและหลังการปรับแต่ง

before_accuracy = before_eval["accuracy"]
after_accuracy = after_eval["accuracy"]
improvement = after_accuracy - before_accuracy

print("Before fine-tuning accuracy:", before_accuracy)
print("After fine-tuning accuracy:", after_accuracy)
print("Improvement:", improvement)
Before fine-tuning accuracy: 0.6
After fine-tuning accuracy: 0.8
Improvement: 0.2

ในการทดลองนี้ ความแม่นยำของโมเดลเพิ่มจาก 0.60 เป็น 0.80

คิดเป็นการเพิ่มขึ้น 20 จุดเปอร์เซ็นต์ โดยโมเดลตอบถูก 40 จาก 50 คำถามหลังการปรับแต่ง เทียบกับ 30 จาก 50 ก่อนการฝึก

15. บันทึกและอัปโหลดตัวปรับที่ปรับแต่งแล้ว

บันทึกตัวปรับ LoRA ที่ฝึกแล้วและไฟล์โปรเซสเซอร์ไปยังไดเรกทอรีเอาต์พุตที่กำหนดไว้ก่อนหน้า

model.save_pretrained(OUTPUT_DIR)
processor.save_pretrained(OUTPUT_DIR)

print(f"Saved LoRA adapter to: {OUTPUT_DIR}")

ควรเห็นว่า:

Saved LoRA adapter to: diffusiongemma_pubmedqa_lora

การบันทึกนี้จะเก็บเฉพาะตัวปรับ LoRA ที่มีน้ำหนักเบา ไม่ใช่สำเนาโมเดลฐาน 26 พันล้านพารามิเตอร์ทั้งก้อนอีกรอบ

ถัดไป อัปโหลดตัวปรับและไฟล์โปรเซสเซอร์ไปยัง Hugging Face Hub:

REPO_ID = "kingabzpro/diffusiongemma_pubmedqa"

model.push_to_hub(REPO_ID)
processor.push_to_hub(REPO_ID)

เนื่องจากตัวแปรสภาพแวดล้อม HF_TOKEN ถูกเพิ่มไว้ตอนกำหนดค่าพ็อด RunPod แล้ว Hugging Face จึงควรยืนยันตัวตนให้โดยอัตโนมัติ ต้องรัน notebook_login() เฉพาะเมื่อยังไม่ได้กำหนดค่าโทเค็นไว้เท่านั้น

from huggingface_hub import notebook_login

notebook_login()

หลังการอัปโหลดเสร็จสิ้น ที่เก็บจะมีตัวปรับ LoRA และคอนฟิกของโปรเซสเซอร์ที่จำเป็นต่อการโหลดโมเดลที่ปรับแต่งภายหลัง

Finetuned lora on Hugging face: diffusiongemma_pubmedqa

แหล่งที่มา: kingabzpro/diffusiongemma_pubmedqa · Hugging Face 

บทส่งท้าย

การปรับแต่ง DiffusionGemma ด้วย Unsloth นั้นง่ายเกินคาด ส่วนที่ใช้เวลามากที่สุดคือการติดตั้งไลบรารีที่ถูกต้องและทำความเข้าใจกระบวนการฝึกที่เฉพาะทางของ diffusion เมื่อจัดสภาพแวดล้อมพร้อมแล้ว การโหลดโมเดล ฝึกตัวปรับ LoRA ประเมินผล และอัปโหลดผลลัพธ์ไปยัง Hugging Face ล้วนเป็นไปอย่างราบรื่น

ฉันพบว่า DiffusionGemma น่าสนใจเป็นพิเศษเพราะมันไม่ได้สร้างข้อความทีละโทเค็นแบบโมเดลภาษาดั้งเดิม แต่ทำงานกับผืนผ้าใบคงที่และค่อย ๆ ปรับโทเค็นที่มีสัญญาณรบกวนผ่านขั้นตอนการลบสัญญาณรบกวน การทำความเข้าใจขั้นตอนการสร้างที่ต่างออกไปนี้ และการปรับแต่งมันกับงานตอบคำถามด้านชีวการแพทย์ ทำให้การทดลองครั้งนี้มีคุณค่าเป็นพิเศษสำหรับฉัน

แม้ตั้งค่าขนาดเล็ก ความแม่นยำบนตัวอย่าง 50 ข้อเพิ่มจาก 0.60 เป็น 0.80 — แต่ด้วยขนาดตัวอย่างเท่านี้ ค่าคลาดเคลื่อนยังสูง และฐาน "ตอบว่า yes ตลอด" ก็ทำได้ราว 55% บนชุดประเมินนี้อยู่แล้ว

นอกจากนี้ควรสังเกตว่าชุดฝึกแบบประดิษฐ์แทบไม่มีฉลาก "maybe" เลย ทำให้โมเดลมีโอกาสเรียนรู้คลาสนี้น้อย แม้ว่าจะปรากฏในข้อมูลประเมินก็ตาม จงมองว่านี่เป็นการทดลองอย่างรวดเร็วเพื่อดูว่ากระบวนการปรับแต่งทำงานอย่างไร ไม่ใช่หลักฐานว่าโมเดลพร้อมใช้ทางการแพทย์จริง

พร้อมจะก้าวข้ามการปรับแต่งครั้งเดียวหรือยัง? แทร็กDeveloping Large Language Models ของเราจะพาจากพื้นฐาน PyTorch และ transformers ไปสู่การสร้างและดีพลอย LLM ของตนเอง

หัวข้อ

คอร์ส LLM แนะนำ

Tracks

การพัฒนาโมเดลภาษาขนาดใหญ่

16 ชม.
เรียนรู้การพัฒนาโมเดลภาษาขนาดใหญ่ (LLMs) ด้วย PyTorch และ Hugging Face โดยใช้เทคนิคการเรียนรู้เชิงลึกและ NLP ล่าสุด
ดูรายละเอียดRight Arrow
เริ่มหลักสูตร
ดูเพิ่มเติมRight Arrow