วิธีรัน NVIDIA Dory 4B ด้วย llama.cpp: คู่มือเจาะลึกแบบ Loop by Loop

ในบทช่วยสอนนี้ เราจะทำงานกับ Dory 1.0 4B ซึ่งเป็นโมเดลการให้เหตุผลขนาดเล็กจาก Nemotron Labs ของ NVIDIA จุดเด่นคือเลเยอร์ส่วนกลางของโมเดลจะรวมกันเป็นบล็อกที่ใช้ร่วมกัน ซึ่งสามารถนำไปใช้ซ้ำได้ 1, 2, 3 หรือ 4 ครั้งสำหรับทุกๆ token
การผ่านเลเยอร์ในแต่ละรอบที่เพิ่มขึ้น หรือลูป (loop) จะใช้พลังการคำนวณต่อ token มากขึ้นโดยไม่ต้องเพิ่มจำนวนพารามิเตอร์แม้แต่ตัวเดียว ทำให้ checkpoint เดียวสามารถรันที่งบประมาณการคำนวณได้ 4 ระดับโดยไม่ต้องเทรนใหม่ เราจะเริ่มต้นด้วยการสร้าง branch ของ llama.cpp ตามที่ model card ของ NVIDIA ระบุไว้ พร้อมดาวน์โหลดไฟล์ GGUF ขนาด 3.7 GB เพื่อวิเคราะห์สถาปัตยกรรมและคาดการณ์ต้นทุนพารามิเตอร์รวมถึงหน่วยความจำแคชในแต่ละลูป
จากนั้นเราจะนำข้อมูลที่คาดการณ์มาทดสอบกับเซิร์ฟเวอร์จริง เพื่อวัดความเร็วในการ decode ในทุกระดับของลูป ตรวจสอบว่าการใช้แคชลูปสุดท้ายที่มีราคาถูกกว่าส่งผลต่อโมเดลอย่างไร พร้อมรันการประเมิน GSM8K แบบจับคู่เพื่อดูความเปลี่ยนแปลงของความแม่นยำ ความยาวของคำตอบ และเวลาที่ใช้ต่อหนึ่งคำถามเมื่อจำนวนลูปเพิ่มขึ้น
import os
import re
import sys
import json
import time
import math
import random
import signal
import shutil
import hashlib
import platform
import statistics
import traceback
import subprocess
import collections
import urllib.request
from pathlib import Path
RESULTS = {}
CLAIMS = []
CURRENT = ["1"]
def banner(title):
print("\n" + "=" * 78)
print(title)
print("=" * 78)
def section(name):
def wrap(fn):
def run(*a, **kw):
CURRENT[0] = name.split(".")[0]
banner(name)
t0 = time.time()
try:
out = fn(*a, **kw)
RESULTS[name] = out if isinstance(out, str) else "ok"
return out
except Exception as e:
RESULTS[name] = f"SKIPPED / FAILED -> {type(e).__name__}: {e}"
print(f"\n[!] {name} did not complete: {type(e).__name__}: {e}")
traceback.print_exc(limit=3)
return None
finally:
print(f"\n[{time.time() - t0:.1f} s]")
return run
return wrap
def claim(text, held):
CLAIMS.append((CURRENT[0], text, bool(held)))
return held
banner("1. Build the pinned Dory llama.cpp fork and record the environment")
FORK = "https://github.com/brianchmielNV/llama.cpp.git"
FORK_COMMIT = "98a74c97ea09ac72f6ceda1559808fc0a2f447ce"
GGUF_REPO = "nvidia/NVIDIA-Nemotron-Labs-Dory1.0-4B-GGUF"
GGUF_REV = "df53755cb8741843af8e019e088819fbef17e29b"
GGUF_FILE = "NVIDIA-Nemotron-Labs-Dory1.0-4B-Q5_K_M.gguf"
GGUF_SIZE = 3_673_288_864
GGUF_SHA256 = "96080be49ffd67b916018b7bdef8c484762576fb52780e10ab98ac4978a660e0"
GSM8K_REV = "740312add88f781978c0658806c59bc2815b9866"
WORK = Path(os.environ.get("DORY_WORK") or ("/content/dory" if os.path.isdir("/content") else "dory_work")).resolve()
WORK.mkdir(parents=True, exist_ok=True)
(WORK / "logs").mkdir(exist_ok=True)
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "gguf==0.19.0", "huggingface_hub", "pandas", "pyarrow"], check=True)
SYSTEM = platform.system()
GPU = ""
if shutil.which("nvidia-smi"):
q = subprocess.run(["nvidia-smi", "--query-gpu=name,memory.total,compute_cap", "--format=csv,noheader"],
capture_output=True, text=True)
GPU = q.stdout.strip().splitlines()[0] if q.returncode == 0 and q.stdout.strip() else ""
BACKEND = "metal" if SYSTEM == "Darwin" else ("cuda" if GPU else "cpu")
SRC = WORK / "llama.cpp-dory"
SERVER_BIN = SRC / "build" / "bin" / "llama-server"
free_gb = shutil.disk_usage(WORK).free / 1e9
print(f"{SYSTEM} {platform.machine()}, Python {platform.python_version()}, {os.cpu_count()} CPU cores, "
f"backend {BACKEND}{' (' + GPU + ')' if GPU else ''}, {free_gb:.1f} GB free disk")
if not SERVER_BIN.exists():
t0 = time.time()
if not (SRC / ".git").exists():
subprocess.run(["git", "init", "-q", str(SRC)], check=True)
subprocess.run(["git", "-C", str(SRC), "remote", "add", "origin", FORK], check=True)
subprocess.run(["git", "-C", str(SRC), "fetch", "-q", "--depth", "1", "origin", FORK_COMMIT], check=True)
subprocess.run(["git", "-C", str(SRC), "checkout", "-q", "--detach", "FETCH_HEAD"], check=True)
flags = ["-DCMAKE_BUILD_TYPE=Release", "-DLLAMA_BUILD_SERVER=ON", "-DLLAMA_BUILD_TESTS=OFF",
"-DLLAMA_BUILD_EXAMPLES=OFF", "-DLLAMA_BUILD_APP=OFF", "-DLLAMA_BUILD_UI=OFF",
"-DLLAMA_USE_PREBUILT_UI=ON", "-DLLAMA_OPENSSL=OFF"]
if BACKEND == "metal":
flags += ["-DCMAKE_OSX_ARCHITECTURES=arm64", "-DGGML_METAL=ON", "-DGGML_METAL_EMBED_LIBRARY=ON",
"-DGGML_BLAS=ON", "-DGGML_BLAS_VENDOR=Apple"]
elif BACKEND == "cuda":
cap = GPU.split(",")[-1].strip().replace(".", "")
flags += ["-DGGML_CUDA=ON", f"-DCMAKE_CUDA_ARCHITECTURES={cap}", "-DGGML_CUDA_FA=OFF", "-DGGML_NATIVE=OFF"]
env = dict(os.environ, HF_UI_VERSION="b11551")
with open(WORK / "build.log", "w") as log:
subprocess.run(["cmake", "-S", str(SRC), "-B", str(SRC / "build")] + flags, check=True, env=env, stdout=log, stderr=log)
subprocess.run(["cmake", "--build", str(SRC / "build"), "--parallel", str(os.cpu_count()), "--target", "llama-server"],
check=True, env=env, stdout=log, stderr=log)
print(f"built llama-server in {time.time() - t0:.0f} s")
head = subprocess.run(["git", "-C", str(SRC), "rev-parse", "HEAD"], capture_output=True, text=True).stdout.strip()
assert head == FORK_COMMIT, f"checked out {head}, expected {FORK_COMMIT}"
version = subprocess.run([str(SERVER_BIN), "--version"], capture_output=True, text=True)
print(f"fork commit {head[:12]}; {(version.stdout + version.stderr).strip().splitlines()[-1]}")
RESULTS["1. Build the pinned Dory llama.cpp fork and record the environment"] = f"fork {head[:12]}, backend {BACKEND}"เราทำการสร้าง llama-server จาก commit ที่ระบุไว้ใน branch Dory เนื่องจากปัจจุบันการรองรับ Dory ใน llama.cpp ยังคงเป็นเพียง draft pull request และยังไม่มีเวอร์ชันสำเร็จรูปใดที่สามารถโหลดโมเดลนี้ได้
ระบบจะตรวจหาแพลตฟอร์มโดยอัตโนมัติเพื่อส่งค่า Metal flags สำหรับ Mac หรือคอมไพล์ CUDA kernels สำหรับ GPU บน Colab โดยเลือกข้าม flash-attention kernels ที่ไม่ได้ใช้งาน เพื่อให้การประมวลผลบน T4 รวดเร็วขึ้น การเช็คเอาต์ commit ที่เจาะจงช่วยให้มั่นใจว่าผลลัพธ์ทุกอย่างจะสามารถตรวจสอบย้อนกลับไปยังเวอร์ชันของโค้ดที่ถูกต้องแม่นยำได้
ความคิดเห็น (0)
เข้าสู่ระบบเพื่อร่วมแสดงความเห็น
สมัครสมาชิกมาเป็นคนแรกที่แสดงความเห็นกันเลยโบร
