pradit/pages/BlogPost.tsx
2025-11-20 17:04:57 +07:00

445 lines
23 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from "react";
import { MatrixViz } from "../components/MatrixViz";
import { useLanguage } from "../App";
import {
Play,
Pause,
FileText,
Headphones,
Zap,
Volume2,
X,
} from "lucide-react";
export const BlogPost: React.FC = () => {
const { language } = useLanguage();
const [activeSection, setActiveSection] = useState("intro");
const [activeTab, setActiveTab] = useState<"read" | "listen">("read");
const [isPlaying, setIsPlaying] = useState(false);
const [showMiniPlayer, setShowMiniPlayer] = useState(false);
// Simple scroll spy logic
useEffect(() => {
const handleScroll = () => {
const sections = ["intro", "methods", "results", "discussion"];
for (const section of sections) {
const element = document.getElementById(section);
if (element) {
const rect = element.getBoundingClientRect();
if (rect.top >= 0 && rect.top <= 300) {
setActiveSection(section);
break;
}
}
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const handleMainPlayPause = () => {
const newState = !isPlaying;
setIsPlaying(newState);
if (newState) {
setShowMiniPlayer(true);
}
};
const content = {
en: {
title: "LoRA Without Regret",
subtitle: "John Schulman in collaboration with Pradit",
date: "Sep 29, 2025",
tldr_title: "Executive Summary (TL;DR)",
tldr_text:
"Low-Rank Adaptation (LoRA) is a technique to fine-tune large models efficiently. Our research confirms that while LoRA saves 90% of memory, it can match full fine-tuning performance only if the rank (r) is sufficiently high and alpha scaling is tuned correctly. Ideal for post-training but not pre-training.",
tabs: { read: "Read Article", listen: "Listen (TTS)" },
intro_1:
"Todays leading language models contain upwards of a trillion parameters, pretrained on tens of trillions of tokens. Base model performance keeps improving with scale, as these trillions are necessary for learning and representing all the patterns in written-down human knowledge.",
intro_2:
"In contrast, post-training involves smaller datasets and generally focuses on narrower domains of knowledge and ranges of behavior. It seems wasteful to use a terabit of weights to represent updates from a gigabit or megabit of training data. This intuition has motivated parameter efficient fine-tuning (PEFT), which adjusts a large network by updating a much smaller set of parameters.",
method_title: "The Method",
method_1:
"The leading PEFT method is low-rank adaptation, or LoRA. LoRA replaces each weight matrix W from the original model with a modified version W' = W + γBA, where B and A are matrices that together have far fewer parameters than W, and γ is a constant scaling factor.",
method_2:
"In effect, LoRA creates a low-dimensional representation of the updates imparted by fine-tuning. LoRA may offer advantages in the cost and speed of post-training, and there are also a few operational reasons to prefer it to full fine-tuning.",
results_title: "Operational Advantages",
results_list: [
{
title: "Multi-tenant serving",
text: "Since LoRA trains an adapter (i.e., the A and B matrices) while keeping the original weights unchanged, a single inference server can keep many adapters in memory.",
},
{
title: "Layout size for training",
text: "FullFT usually requires an order of magnitude more accelerators than sampling from the same model does.",
},
{
title: "Ease of loading",
text: "With fewer weights to store, LoRA adapters are fast and easy to set up or transfer between machines.",
},
],
conclusion_title: "Conclusion",
conclusion_text:
"The question remains: can LoRA match the performance of full fine-tuning? Our internal benchmarks suggest yes, provided rank scaling is carefully managed.",
nav: {
intro: "Introduction",
methods: "Methods",
results: "Results",
discussion: "Conclusion",
},
now_playing: "Now Playing",
paused: "Paused",
transcript_title: "Transcript",
},
th: {
title: "LoRA โดยไม่เสียใจ",
subtitle: "John Schulman ร่วมกับ Pradit (ประดิษฐ์)",
date: "29 กันยายน 2025",
tldr_title: "บทสรุปผู้บริหาร (TL;DR)",
tldr_text:
"Low-Rank Adaptation (LoRA) เป็นเทคนิคการปรับแต่งโมเดลขนาดใหญ่ที่มีประสิทธิภาพ งานวิจัยของเรายืนยันว่าแม้ LoRA จะประหยัดหน่วยความจำได้ถึง 90% แต่จะให้ผลลัพธ์เทียบเท่าการ Fine-tune แบบเต็มรูปแบบได้ก็ต่อเมื่อค่า Rank (r) สูงเพียงพอและมีการปรับค่า Alpha อย่างถูกต้อง เหมาะสำหรับขั้นตอน Post-training แต่ไม่เหมาะกับ Pre-training",
tabs: { read: "อ่านบทความ", listen: "ฟังเสียงบรรยาย" },
intro_1:
"โมเดลภาษาชั้นนำในปัจจุบันมีพารามิเตอร์มากกว่าล้านล้านตัว ผ่านการฝึกฝนบนข้อมูลมหาศาล ประสิทธิภาพของโมเดลพื้นฐานดีขึ้นเรื่อยๆ ตามขนาด ซึ่งจำเป็นสำหรับการเรียนรู้รูปแบบความรู้ทั้งหมดของมนุษย์",
intro_2:
"ในทางตรงกันข้าม การฝึกฝนหลังเสร็จสิ้น (Post-training) มักใช้ชุดข้อมูลขนาดเล็กและเน้นขอบเขตความรู้ที่แคบกว่า ดูเหมือนจะสิ้นเปลืองที่จะใช้น้ำหนักระดับเทราบิตเพื่อแทนการอัปเดตจากข้อมูลเพียงกิกะบิต แนวคิดนี้จึงนำไปสู่การปรับแต่งแบบประหยัดพารามิเตอร์ (PEFT)",
method_title: "วิธีการ",
method_1:
"วิธี PEFT ชั้นนำคือ Low-rank adaptation หรือ LoRA ซึ่งแทนที่เมทริกซ์น้ำหนัก W แต่ละตัวด้วยเวอร์ชันแก้ไข W' = W + γBA โดยที่ B และ A เป็นเมทริกซ์ที่มีพารามิเตอร์น้อยกว่า W มาก",
method_2:
"โดยสรุป LoRA สร้างตัวแทนมิติที่ต่ำกว่าของการอัปเดตที่เกิดจากการ Fine-tuning LoRA อาจมีข้อได้เปรียบในด้านต้นทุนและความเร็ว และยังมีเหตุผลด้านการปฏิบัติงานที่ทำให้เป็นที่นิยมมากกว่า Full Fine-tuning",
results_title: "ข้อได้เปรียบด้านการปฏิบัติงาน",
results_list: [
{
title: "การให้บริการแบบ Multi-tenant",
text: "เนื่องจาก LoRA ฝึกฝน Adapter (เมทริกซ์ A และ B) โดยไม่เปลี่ยนน้ำหนักเดิม เซิร์ฟเวอร์เดียวจึงสามารถเก็บ Adapter หลายตัวในหน่วยความจำได้",
},
{
title: "ขนาด Layout สำหรับการฝึก",
text: "FullFT มักต้องการตัวเร่งความเร็ว (GPU/TPU) มากกว่าการสุ่มตัวอย่างจากโมเดลเดียวกันถึงหนึ่งระดับ (Order of magnitude)",
},
{
title: "ความง่ายในการโหลด",
text: "ด้วยน้ำหนักที่ต้องเก็บน้อยกว่า LoRA adapters จึงตั้งค่าและโอนย้ายระหว่างเครื่องได้รวดเร็ว",
},
],
conclusion_title: "บทสรุป",
conclusion_text:
"คำถามยังคงอยู่: LoRA สามารถทำงานได้เทียบเท่ากับ Full Fine-tuning หรือไม่? การทดสอบภายในของเราชี้ว่า 'ได้' หากมีการจัดการ Rank scaling อย่างระมัดระวัง",
nav: {
intro: "บทนำ",
methods: "วิธีการ",
results: "ผลลัพธ์",
discussion: "บทสรุป",
},
now_playing: "กำลังเล่น",
paused: "หยุดชั่วคราว",
transcript_title: "คำบรรยาย",
},
};
const t = content[language];
return (
<div className="bg-paper min-h-screen pb-24 relative">
{/* Article Header */}
<header className="pt-24 pb-8 max-w-4xl mx-auto px-6 text-center">
<h1 className="font-serif text-4xl md:text-5xl text-ink leading-tight mb-6">
{t.title}
</h1>
<div className="font-sans text-sm text-subtle uppercase tracking-widest mb-2">
{t.subtitle}
</div>
<time className="font-serif italic text-subtle">{t.date}</time>
{/* View Tabs */}
<div className="flex justify-center mt-12 mb-8">
<div className="inline-flex bg-gray-100 p-1 rounded-lg border border-gray-200">
<button
onClick={() => setActiveTab("read")}
className={`flex items-center px-6 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "read" ? "bg-white text-ink shadow-sm" : "text-subtle hover:text-ink"}`}
>
<FileText size={16} className="mr-2" />
{t.tabs.read}
</button>
<button
onClick={() => setActiveTab("listen")}
className={`flex items-center px-6 py-2 rounded-md text-sm font-bold transition-all ${activeTab === "listen" ? "bg-white text-ink shadow-sm" : "text-subtle hover:text-ink"}`}
>
<Headphones size={16} className="mr-2" />
{t.tabs.listen}
</button>
</div>
</div>
</header>
{/* TL;DR Section */}
<div className="max-w-4xl mx-auto px-6 mb-12">
<div className="bg-blue-50 border border-blue-100 p-6 md:p-8 rounded-sm relative overflow-hidden">
<div className="absolute top-0 left-0 w-1 h-full bg-accent"></div>
<div className="flex items-start gap-4">
<div className="bg-white p-2 rounded-full shadow-sm text-accent shrink-0 mt-1">
<Zap size={20} />
</div>
<div>
<h3 className="font-sans font-bold text-xs uppercase tracking-widest text-accent mb-2">
{t.tldr_title}
</h3>
<p className="font-serif text-ink leading-relaxed text-base md:text-lg">
{t.tldr_text}
</p>
</div>
</div>
</div>
</div>
{activeTab === "listen" ? (
/* Voice Interface */
<div className="max-w-5xl mx-auto px-4 sm:px-6 animate-in fade-in zoom-in duration-300">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Left: Sticky Player */}
<div className="md:sticky md:top-32 h-fit">
<div className="bg-[#1e1e1e] rounded-xl p-8 text-center shadow-2xl border border-gray-800">
<div className="w-20 h-20 bg-gray-800 rounded-full mx-auto mb-6 flex items-center justify-center shadow-inner relative">
{isPlaying && (
<span className="absolute w-full h-full rounded-full border-2 border-green-500 animate-ping opacity-20"></span>
)}
<Headphones size={28} className="text-gray-400" />
</div>
<h3 className="text-white font-serif text-xl mb-2">
{t.title}
</h3>
<p className="text-gray-400 font-mono text-[10px] mb-8">
Voice: Gemini 2.5 Flash TTS
</p>
{/* Fake Waveform */}
<div className="h-12 flex items-center justify-center gap-1 mb-8 px-4 overflow-hidden">
{[...Array(30)].map((_, i) => (
<div
key={i}
className={`w-1 bg-green-500 transition-all duration-150 rounded-full ${isPlaying ? "animate-pulse" : ""}`}
style={{
height: isPlaying ? `${Math.random() * 100}%` : "10%",
opacity: isPlaying ? 1 : 0.3,
}}
/>
))}
</div>
<div className="flex items-center justify-center gap-6">
<button className="text-gray-400 hover:text-white transition-colors font-mono text-xs">
1.0x
</button>
<button
onClick={handleMainPlayPause}
className="w-14 h-14 bg-white rounded-full flex items-center justify-center hover:scale-105 transition-transform text-black shadow-lg"
>
{isPlaying ? (
<Pause fill="black" size={20} />
) : (
<Play fill="black" size={20} className="ml-1" />
)}
</button>
<button className="text-gray-400 hover:text-white transition-colors font-mono text-xs">
-15s
</button>
</div>
</div>
</div>
{/* Right: Scrollable Transcript */}
<div className="flex flex-col h-[600px]">
<div className="font-sans text-xs font-bold uppercase tracking-widest text-subtle mb-4 flex items-center gap-2">
<FileText size={14} /> {t.transcript_title}
</div>
<div className="flex-grow overflow-y-auto pr-4 space-y-6 text-subtle font-serif leading-relaxed border-l-2 border-gray-100 pl-6 custom-scrollbar">
<p className="text-ink font-medium">{t.intro_1}</p>
<p>{t.intro_2}</p>
<div className="h-px bg-gray-100 w-full my-4"></div>
<p className="text-xs font-sans font-bold text-gray-400 uppercase">
{t.method_title}
</p>
<p>{t.method_1}</p>
<p>{t.method_2}</p>
<div className="h-px bg-gray-100 w-full my-4"></div>
<p className="text-xs font-sans font-bold text-gray-400 uppercase">
{t.results_title}
</p>
{t.results_list.map((item, idx) => (
<p key={idx}>
<strong className="text-gray-700">{item.title}:</strong>{" "}
{item.text}
</p>
))}
<div className="h-px bg-gray-100 w-full my-4"></div>
<p>{t.conclusion_text}</p>
</div>
</div>
</div>
</div>
) : (
/* Read Interface */
<main className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8 grid grid-cols-1 lg:grid-cols-12 gap-12 pb-20">
{/* Left Sidebar - TOC */}
<aside className="hidden lg:block lg:col-span-3 relative">
<nav className="sticky top-32 space-y-4">
<ul className="font-serif text-sm text-subtle space-y-3 border-l border-gray-200 pl-4">
<li>
<a
href="#intro"
className={`block transition-colors ${activeSection === "intro" ? "text-ink font-bold" : "hover:text-ink"}`}
>
{t.nav.intro}
</a>
</li>
<li>
<a
href="#methods"
className={`block transition-colors ${activeSection === "methods" ? "text-ink font-bold" : "hover:text-ink"}`}
>
{t.nav.methods}
</a>
</li>
<li>
<a
href="#results"
className={`block transition-colors ${activeSection === "results" ? "text-ink font-bold" : "hover:text-ink"}`}
>
{t.nav.results}
</a>
</li>
<li>
<a
href="#discussion"
className={`block transition-colors ${activeSection === "discussion" ? "text-ink font-bold" : "hover:text-ink"}`}
>
{t.nav.discussion}
</a>
</li>
</ul>
</nav>
</aside>
{/* Main Content */}
<article className="col-span-1 lg:col-span-6 font-serif text-lg leading-relaxed text-gray-800">
<MatrixViz />
<section id="intro" className="mb-12">
<p className="mb-6 first-letter:float-left first-letter:text-7xl first-letter:pr-4 first-letter:font-bold first-letter:text-ink">
{t.intro_1}
</p>
<p className="mb-6">{t.intro_2}</p>
</section>
<section id="methods" className="mb-12">
<h2 className="font-sans font-bold text-xl text-ink mt-12 mb-6">
{t.method_title}
</h2>
<p className="mb-6">{t.method_1}</p>
<p className="mb-6">{t.method_2}</p>
</section>
<section id="results" className="mb-12">
<h3 className="font-sans font-bold text-lg text-ink mt-8 mb-4">
{t.results_title}
</h3>
<ul className="list-disc list-outside ml-6 space-y-4 mb-6 text-base">
{t.results_list.map((item, idx) => (
<li key={idx}>
<strong className="text-ink">{item.title}.</strong>{" "}
{item.text}
</li>
))}
</ul>
</section>
<section id="discussion" className="mb-12">
<h2 className="font-sans font-bold text-xl text-ink mt-12 mb-6">
{t.conclusion_title}
</h2>
<p className="mb-6">{t.conclusion_text}</p>
</section>
</article>
{/* Right Sidebar - Footnotes */}
<aside className="hidden lg:block lg:col-span-3 relative">
<div className="sticky top-32 space-y-12">
<div className="text-xs text-subtle font-serif leading-relaxed border-l-2 border-gray-100 pl-3">
<sup className="font-bold mr-1">1</sup>
Punica: Multi-Tenant LoRA Serving (Chen, Ye, et al, 2023)
</div>
<div className="text-xs text-subtle font-serif leading-relaxed border-l-2 border-gray-100 pl-3">
<sup className="font-bold mr-1">2</sup>
LoRA: Low-Rank Adaptation of Large Language Models (Hu et al,
2021)
</div>
</div>
</aside>
</main>
)}
{/* Mini Player Overlay - Persistent if showMiniPlayer is true */}
{showMiniPlayer && activeTab === "read" && (
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-40 animate-in slide-in-from-bottom-4 fade-in">
<div className="bg-ink text-white pl-4 pr-4 py-3 rounded-full shadow-2xl flex items-center gap-4 border border-gray-700">
<div className="flex items-center gap-3 border-r border-gray-700 pr-4">
<div className="relative flex h-3 w-3">
{isPlaying && (
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
)}
<span
className={`relative inline-flex rounded-full h-3 w-3 ${isPlaying ? "bg-green-500" : "bg-yellow-500"}`}
></span>
</div>
<div className="flex flex-col">
<span className="text-[10px] font-sans font-bold uppercase tracking-wider text-gray-400">
{isPlaying ? t.now_playing : t.paused}
</span>
<span className="text-xs font-bold font-serif truncate max-w-[150px]">
{t.title}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsPlaying(!isPlaying)}
className="hover:text-green-400 transition-colors p-1"
title={isPlaying ? "Pause" : "Play"}
>
{isPlaying ? (
<Pause size={20} fill="currentColor" />
) : (
<Play size={20} fill="currentColor" />
)}
</button>
<button
onClick={() => setActiveTab("listen")}
className="text-xs font-mono underline hover:text-gray-300 px-2"
>
{language === "en" ? "Transcript" : "คำบรรยาย"}
</button>
<div className="w-px h-4 bg-gray-700 mx-1"></div>
<button
onClick={() => {
setIsPlaying(false);
setShowMiniPlayer(false);
}}
className="text-gray-400 hover:text-red-400 transition-colors p-1"
title="Close Player"
>
<X size={18} />
</button>
</div>
</div>
</div>
)}
</div>
);
};