@xdev-asia/xdev-knowledge-mcp 1.0.57 → 1.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/blog/ai/minimax-danh-gia-chi-tiet-nen-tang-ai-full-stack-trung-quoc.md +450 -0
- package/content/blog/ai/nvidia-dli-generative-ai-chung-chi-va-lo-trinh-hoc.md +894 -0
- package/content/metadata/authors/duy-tran.md +2 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/01-bai-1-pytorch-neural-network-fundamentals.md +790 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/01-deep-learning-foundations/lessons/02-bai-2-transformer-architecture-attention.md +984 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/01-bai-3-unet-architecture-denoising.md +1111 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/02-bai-4-ddpm-forward-reverse-diffusion.md +1007 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/02-diffusion-models/lessons/03-bai-5-clip-text-to-image-pipeline.md +1037 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/01-bai-6-llm-inference-pipeline-design.md +929 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/02-bai-7-rag-retrieval-augmented-generation.md +1099 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/03-llm-applications-rag/lessons/03-bai-8-rag-agent-build-evaluate.md +1249 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/01-bai-9-agentic-ai-multi-agent-systems.md +1357 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/chapters/04-agentic-ai-customization/lessons/02-bai-10-llm-evaluation-lora-fine-tuning.md +1867 -0
- package/content/series/luyen-thi/luyen-thi-nvidia-dli-generative-ai/index.md +237 -0
- package/data/quizzes/nvidia-dli-generative-ai.json +350 -0
- package/data/quizzes.json +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1867 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 019c9619-nv01-p4-l10
|
|
3
|
+
title: 'Bài 10: LLM Evaluation & LoRA Fine-tuning'
|
|
4
|
+
slug: bai-10-llm-evaluation-lora-fine-tuning
|
|
5
|
+
description: >-
|
|
6
|
+
Evaluation methods: benchmarks (GSM8K), LLM-as-a-Judge, ELO ranking.
|
|
7
|
+
NeMo Evaluator microservice, MLflow experiment tracking.
|
|
8
|
+
Metrics: BLEU, F1-score, semantic similarity.
|
|
9
|
+
LoRA & QLoRA fine-tuning: theory and hands-on.
|
|
10
|
+
NeMo Customizer: fine-tuning jobs. Final mock exam & exam strategy.
|
|
11
|
+
duration_minutes: 90
|
|
12
|
+
is_free: true
|
|
13
|
+
video_url: null
|
|
14
|
+
sort_order: 10
|
|
15
|
+
section_title: "Part 4: Agentic AI & LLM Customization"
|
|
16
|
+
course:
|
|
17
|
+
id: 019c9619-nv01-7001-c001-nv0100000001
|
|
18
|
+
title: 'Luyện thi NVIDIA DLI — Generative AI with Diffusion Models & LLMs'
|
|
19
|
+
slug: luyen-thi-nvidia-dli-generative-ai
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<h2 id="1-llm-evaluation-fundamentals">1. LLM Evaluation Fundamentals</h2>
|
|
23
|
+
|
|
24
|
+
<h3 id="1-1-tai-sao-evaluation-quan-trong">1.1. Tại sao Evaluation quan trọng?</h3>
|
|
25
|
+
|
|
26
|
+
<p>Bạn không thể cải thiện thứ mà bạn không đo lường được. Trong production, một LLM sinh ra câu trả lời "nghe hợp lý" nhưng sai về mặt factual có thể gây hậu quả nghiêm trọng — từ tư vấn y tế sai đến mất tiền trong giao dịch tài chính. <strong>Evaluation</strong> là bước bắt buộc trước khi deploy bất kỳ LLM application nào.</p>
|
|
27
|
+
|
|
28
|
+
<p>Nguyên tắc <strong>"Garbage In, Garbage Out"</strong> áp dụng cho toàn bộ pipeline:</p>
|
|
29
|
+
|
|
30
|
+
<ul>
|
|
31
|
+
<li><strong>Bad prompt</strong> → bad output → bad evaluation → false confidence</li>
|
|
32
|
+
<li><strong>Bad evaluation metric</strong> → chọn sai model → production failure</li>
|
|
33
|
+
<li><strong>No evaluation</strong> → không biết model degradation → silent failure</li>
|
|
34
|
+
</ul>
|
|
35
|
+
|
|
36
|
+
<h3 id="1-2-phan-loai-evaluation">1.2. Phân loại Evaluation</h3>
|
|
37
|
+
|
|
38
|
+
<p>Có 3 phương pháp chính để đánh giá LLM:</p>
|
|
39
|
+
|
|
40
|
+
<table>
|
|
41
|
+
<thead>
|
|
42
|
+
<tr><th>Phương pháp</th><th>Ưu điểm</th><th>Nhược điểm</th><th>Use case</th></tr>
|
|
43
|
+
</thead>
|
|
44
|
+
<tbody>
|
|
45
|
+
<tr><td><strong>Automated Metrics</strong></td><td>Nhanh, reproducible, rẻ</td><td>Không bắt nuance, bị cheat</td><td>CI/CD pipeline, regression testing</td></tr>
|
|
46
|
+
<tr><td><strong>Human Evaluation</strong></td><td>Gold standard, bắt nuance</td><td>Chậm, đắt, inconsistent</td><td>Final validation, safety audit</td></tr>
|
|
47
|
+
<tr><td><strong>LLM-as-a-Judge</strong></td><td>Scalable, gần human quality</td><td>Bias từ judge model</td><td>Large-scale eval, rapid iteration</td></tr>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
|
|
51
|
+
<h3 id="1-3-evaluation-dimensions">1.3. Evaluation Dimensions</h3>
|
|
52
|
+
|
|
53
|
+
<p>Mỗi LLM application cần đánh giá trên nhiều chiều:</p>
|
|
54
|
+
|
|
55
|
+
<ul>
|
|
56
|
+
<li><strong>Accuracy / Correctness</strong> — câu trả lời có đúng không?</li>
|
|
57
|
+
<li><strong>Fluency</strong> — ngôn ngữ có tự nhiên không?</li>
|
|
58
|
+
<li><strong>Relevance</strong> — câu trả lời có liên quan đến câu hỏi không?</li>
|
|
59
|
+
<li><strong>Safety / Harmlessness</strong> — output có an toàn không?</li>
|
|
60
|
+
<li><strong>Latency</strong> — thời gian response có chấp nhận được không? (p50, p95, p99)</li>
|
|
61
|
+
<li><strong>Cost</strong> — chi phí per-token có hợp lý không?</li>
|
|
62
|
+
</ul>
|
|
63
|
+
|
|
64
|
+
<pre><code class="language-text">
|
|
65
|
+
LLM Evaluation Pipeline — From Data to Decision
|
|
66
|
+
══════════════════════════════════════════════════════════════
|
|
67
|
+
|
|
68
|
+
┌─────────────┐ ┌─────────────────┐ ┌──────────────┐
|
|
69
|
+
│ Test Data │────►│ LLM Inference │────►│ Raw Output │
|
|
70
|
+
│ (prompts + │ │ (model under │ │ (generated │
|
|
71
|
+
│ references) │ │ evaluation) │ │ responses) │
|
|
72
|
+
└─────────────┘ └─────────────────┘ └──────┬───────┘
|
|
73
|
+
│
|
|
74
|
+
┌─────────────────────────────────┼──────┐
|
|
75
|
+
│ EVALUATION ENGINE │ │
|
|
76
|
+
│ ┌──────────┐ ┌───────────────┐ │ │
|
|
77
|
+
│ │Automated │ │ LLM-as-Judge │ │ │
|
|
78
|
+
│ │ Metrics │ │ (GPT-4/Claude)│ │ │
|
|
79
|
+
│ │BLEU,ROUGE│ │Pairwise/Point │ │ │
|
|
80
|
+
│ │F1,Cosine │ │ wise scoring │ │ │
|
|
81
|
+
│ └────┬─────┘ └──────┬────────┘ │ │
|
|
82
|
+
│ │ │ │ │
|
|
83
|
+
│ ▼ ▼ │ │
|
|
84
|
+
│ ┌────────────────────────────┐ │ │
|
|
85
|
+
│ │ Aggregated Score Card │ │ │
|
|
86
|
+
│ │ Accuracy: 0.87 Safety: 95%│ │ │
|
|
87
|
+
│ │ Latency p95: 1.2s F1: 0.82│ │ │
|
|
88
|
+
│ └────────────┬───────────────┘ │ │
|
|
89
|
+
└───────────────┼──────────────────┘ │
|
|
90
|
+
│ │
|
|
91
|
+
▼ │
|
|
92
|
+
┌─────────────────────────────┐ │
|
|
93
|
+
│ MLflow Experiment Tracker │◄──────────┘
|
|
94
|
+
│ Compare runs, visualize │
|
|
95
|
+
│ Select best model version │
|
|
96
|
+
└─────────────────────────────┘
|
|
97
|
+
</code></pre>
|
|
98
|
+
|
|
99
|
+
<blockquote><p><strong>Exam tip:</strong> DLI assessment thường hỏi "Which evaluation method is best for X?" — nhớ: <strong>BLEU</strong> cho translation, <strong>ROUGE</strong> cho summarization, <strong>F1</strong> cho QA, <strong>LLM-as-a-Judge</strong> cho chất lượng tổng thể. Không tồn tại một metric duy nhất cho mọi task.</p></blockquote>
|
|
100
|
+
|
|
101
|
+
<figure><img src="/storage/uploads/2026/04/nvidia-dli-bai10-lora-fine-tuning.png" alt="LoRA Fine-tuning — Low-Rank Adaptation, QLoRA, Evaluation Metrics Dashboard" loading="lazy" /><figcaption>LoRA Fine-tuning — Low-Rank Adaptation, QLoRA, Evaluation Metrics Dashboard</figcaption></figure>
|
|
102
|
+
|
|
103
|
+
<h2 id="2-automated-metrics-deep-dive">2. Automated Metrics Deep-Dive</h2>
|
|
104
|
+
|
|
105
|
+
<h3 id="2-1-bleu-score">2.1. BLEU Score — Bilingual Evaluation Understudy</h3>
|
|
106
|
+
|
|
107
|
+
<p>BLEU đo mức độ <strong>n-gram overlap</strong> giữa generated text và reference text. Ban đầu thiết kế cho machine translation, nhưng áp dụng rộng rãi cho nhiều NLG task.</p>
|
|
108
|
+
|
|
109
|
+
<p>Công thức chính:</p>
|
|
110
|
+
|
|
111
|
+
<p>$$\text{BLEU} = BP \cdot \exp\left(\sum_{n=1}^{N} w_n \log p_n\right)$$</p>
|
|
112
|
+
|
|
113
|
+
<p>Trong đó:</p>
|
|
114
|
+
|
|
115
|
+
<ul>
|
|
116
|
+
<li>$p_n$ = <strong>modified n-gram precision</strong> — tỷ lệ n-gram trong candidate cũng xuất hiện trong reference</li>
|
|
117
|
+
<li>$w_n = \frac{1}{N}$ — trọng số đều (thường N=4, nên $w_n = 0.25$)</li>
|
|
118
|
+
<li>$BP$ = <strong>Brevity Penalty</strong> — phạt candidate ngắn hơn reference:</li>
|
|
119
|
+
</ul>
|
|
120
|
+
|
|
121
|
+
<p>$$BP = \begin{cases} 1 & \text{if } c > r \\ e^{1 - r/c} & \text{if } c \leq r \end{cases}$$</p>
|
|
122
|
+
|
|
123
|
+
<p>Trong đó $c$ = độ dài candidate, $r$ = độ dài reference.</p>
|
|
124
|
+
|
|
125
|
+
<p>Ý nghĩa: BLEU = 1.0 → hoàn toàn khớp reference. BLEU = 0.0 → không có n-gram nào trùng. Thực tế, BLEU > 0.3 là acceptable cho translation.</p>
|
|
126
|
+
|
|
127
|
+
<h3 id="2-2-bleu-implementation">2.2. Implement BLEU từ scratch</h3>
|
|
128
|
+
|
|
129
|
+
<pre><code class="language-python">
|
|
130
|
+
from collections import Counter
|
|
131
|
+
import math
|
|
132
|
+
|
|
133
|
+
def count_ngrams(tokens, n):
|
|
134
|
+
"""Đếm tất cả n-grams trong sequence."""
|
|
135
|
+
return Counter(tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1))
|
|
136
|
+
|
|
137
|
+
def modified_precision(candidate, references, n):
|
|
138
|
+
"""
|
|
139
|
+
Modified n-gram precision: clip count bởi max reference count.
|
|
140
|
+
Tránh trường hợp candidate lặp cùng 1 từ nhiều lần.
|
|
141
|
+
"""
|
|
142
|
+
cand_ngrams = count_ngrams(candidate, n)
|
|
143
|
+
|
|
144
|
+
# Max count cho mỗi n-gram across all references
|
|
145
|
+
max_ref_counts = Counter()
|
|
146
|
+
for ref in references:
|
|
147
|
+
ref_ngrams = count_ngrams(ref, n)
|
|
148
|
+
for ngram, count in ref_ngrams.items():
|
|
149
|
+
max_ref_counts[ngram] = max(max_ref_counts[ngram], count)
|
|
150
|
+
|
|
151
|
+
# Clip candidate count bởi max reference count
|
|
152
|
+
clipped_count = 0
|
|
153
|
+
total_count = 0
|
|
154
|
+
for ngram, count in cand_ngrams.items():
|
|
155
|
+
clipped_count += min(count, max_ref_counts.get(ngram, 0))
|
|
156
|
+
total_count += count
|
|
157
|
+
|
|
158
|
+
if total_count == 0:
|
|
159
|
+
return 0.0
|
|
160
|
+
return clipped_count / total_count
|
|
161
|
+
|
|
162
|
+
def brevity_penalty(candidate, references):
|
|
163
|
+
"""Brevity penalty: phạt nếu candidate ngắn hơn reference."""
|
|
164
|
+
c = len(candidate)
|
|
165
|
+
# Chọn reference có length gần nhất
|
|
166
|
+
r = min((abs(len(ref) - c), len(ref)) for ref in references)[1]
|
|
167
|
+
|
|
168
|
+
if c > r:
|
|
169
|
+
return 1.0
|
|
170
|
+
elif c == 0:
|
|
171
|
+
return 0.0
|
|
172
|
+
else:
|
|
173
|
+
return math.exp(1 - r / c)
|
|
174
|
+
|
|
175
|
+
def bleu_score(candidate, references, max_n=4):
|
|
176
|
+
"""
|
|
177
|
+
Tính BLEU score (BLEU-1 đến BLEU-N).
|
|
178
|
+
candidate: list of tokens
|
|
179
|
+
references: list of list of tokens
|
|
180
|
+
"""
|
|
181
|
+
weights = [1.0 / max_n] * max_n
|
|
182
|
+
bp = brevity_penalty(candidate, references)
|
|
183
|
+
|
|
184
|
+
log_avg = 0.0
|
|
185
|
+
for n in range(1, max_n + 1):
|
|
186
|
+
p_n = modified_precision(candidate, references, n)
|
|
187
|
+
if p_n == 0:
|
|
188
|
+
return 0.0 # Nếu bất kỳ p_n = 0, BLEU = 0
|
|
189
|
+
log_avg += weights[n - 1] * math.log(p_n)
|
|
190
|
+
|
|
191
|
+
return bp * math.exp(log_avg)
|
|
192
|
+
|
|
193
|
+
# --- Ví dụ ---
|
|
194
|
+
candidate = "the cat sat on the mat".split()
|
|
195
|
+
references = [
|
|
196
|
+
"the cat is on the mat".split(),
|
|
197
|
+
"there is a cat on the mat".split(),
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
score = bleu_score(candidate, references, max_n=4)
|
|
201
|
+
print(f"BLEU-4 score: {score:.4f}")
|
|
202
|
+
# Output: BLEU-4 score: 0.4647
|
|
203
|
+
</code></pre>
|
|
204
|
+
|
|
205
|
+
<h3 id="2-3-rouge-score">2.3. ROUGE Score — Recall-Oriented Understudy</h3>
|
|
206
|
+
|
|
207
|
+
<p><strong>ROUGE</strong> là metric recall-oriented — đo bao nhiêu phần của reference được "phủ" bởi candidate. Rất phù hợp cho <strong>summarization</strong> vì ta muốn tóm tắt phải bao gồm các ý chính.</p>
|
|
208
|
+
|
|
209
|
+
<table>
|
|
210
|
+
<thead>
|
|
211
|
+
<tr><th>Variant</th><th>Công thức</th><th>Ý nghĩa</th></tr>
|
|
212
|
+
</thead>
|
|
213
|
+
<tbody>
|
|
214
|
+
<tr><td><strong>ROUGE-N</strong></td><td>Recall of n-gram overlap</td><td>ROUGE-1 (unigram), ROUGE-2 (bigram)</td></tr>
|
|
215
|
+
<tr><td><strong>ROUGE-L</strong></td><td>Longest Common Subsequence (LCS)</td><td>Bắt sentence-level structure</td></tr>
|
|
216
|
+
<tr><td><strong>ROUGE-Lsum</strong></td><td>LCS tính trên split sentences</td><td>Multi-sentence summaries</td></tr>
|
|
217
|
+
</tbody>
|
|
218
|
+
</table>
|
|
219
|
+
|
|
220
|
+
<p>Công thức ROUGE-N Recall:</p>
|
|
221
|
+
|
|
222
|
+
<p>$$\text{ROUGE-N}_{recall} = \frac{\sum_{s \in \text{ref}} \sum_{\text{gram}_n \in s} \text{Count}_{match}(\text{gram}_n)}{\sum_{s \in \text{ref}} \sum_{\text{gram}_n \in s} \text{Count}(\text{gram}_n)}$$</p>
|
|
223
|
+
|
|
224
|
+
<h3 id="2-4-f1-score-qa">2.4. F1-Score cho Question Answering</h3>
|
|
225
|
+
|
|
226
|
+
<p>Trong QA, F1-score tính trên <strong>token-level overlap</strong> giữa predicted answer và ground truth:</p>
|
|
227
|
+
|
|
228
|
+
<p>$$\text{Precision} = \frac{|\text{predicted tokens} \cap \text{truth tokens}|}{|\text{predicted tokens}|}$$</p>
|
|
229
|
+
|
|
230
|
+
<p>$$\text{Recall} = \frac{|\text{predicted tokens} \cap \text{truth tokens}|}{|\text{truth tokens}|}$$</p>
|
|
231
|
+
|
|
232
|
+
<p>$$\text{F1} = \frac{2 \cdot P \cdot R}{P + R}$$</p>
|
|
233
|
+
|
|
234
|
+
<pre><code class="language-python">
|
|
235
|
+
def qa_f1_score(prediction: str, ground_truth: str) -> float:
|
|
236
|
+
"""
|
|
237
|
+
Token-level F1 cho QA evaluation.
|
|
238
|
+
Dùng cho SQuAD-style exact extraction.
|
|
239
|
+
"""
|
|
240
|
+
pred_tokens = prediction.lower().split()
|
|
241
|
+
truth_tokens = ground_truth.lower().split()
|
|
242
|
+
|
|
243
|
+
common = set(pred_tokens) & set(truth_tokens)
|
|
244
|
+
num_common = sum(
|
|
245
|
+
min(pred_tokens.count(t), truth_tokens.count(t))
|
|
246
|
+
for t in common
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if num_common == 0:
|
|
250
|
+
return 0.0
|
|
251
|
+
|
|
252
|
+
precision = num_common / len(pred_tokens)
|
|
253
|
+
recall = num_common / len(truth_tokens)
|
|
254
|
+
f1 = 2 * precision * recall / (precision + recall)
|
|
255
|
+
return f1
|
|
256
|
+
|
|
257
|
+
# --- Ví dụ ---
|
|
258
|
+
pred = "Barack Obama was the 44th president"
|
|
259
|
+
truth = "The 44th president was Barack Obama"
|
|
260
|
+
|
|
261
|
+
print(f"F1 = {qa_f1_score(pred, truth):.4f}")
|
|
262
|
+
# Output: F1 = 0.8571
|
|
263
|
+
</code></pre>
|
|
264
|
+
|
|
265
|
+
<h3 id="2-5-semantic-similarity">2.5. Semantic Similarity — Embedding-Based</h3>
|
|
266
|
+
|
|
267
|
+
<p>Automated metrics dựa trên n-gram (BLEU, ROUGE) bỏ lỡ <strong>semantic equivalence</strong>: "The dog chased the cat" và "A canine pursued a feline" có BLEU = 0 nhưng ý nghĩa giống nhau. <strong>Semantic similarity</strong> dùng embedding để so sánh nghĩa.</p>
|
|
268
|
+
|
|
269
|
+
<p>$$\text{Cosine Similarity} = \frac{\vec{a} \cdot \vec{b}}{||\vec{a}|| \cdot ||\vec{b}||}$$</p>
|
|
270
|
+
|
|
271
|
+
<pre><code class="language-python">
|
|
272
|
+
import numpy as np
|
|
273
|
+
|
|
274
|
+
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
275
|
+
"""Cosine similarity giữa 2 embedding vectors."""
|
|
276
|
+
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
|
|
277
|
+
|
|
278
|
+
# Trong thực tế: dùng sentence-transformers
|
|
279
|
+
# from sentence_transformers import SentenceTransformer
|
|
280
|
+
# model = SentenceTransformer("all-MiniLM-L6-v2")
|
|
281
|
+
# embeddings = model.encode(["The dog chased the cat",
|
|
282
|
+
# "A canine pursued a feline"])
|
|
283
|
+
# sim = cosine_similarity(embeddings[0], embeddings[1])
|
|
284
|
+
# → sim ≈ 0.85 (semantic match dù n-gram khác hoàn toàn)
|
|
285
|
+
</code></pre>
|
|
286
|
+
|
|
287
|
+
<h3 id="2-6-metric-selection-guide">2.6. Metric Selection Guide — Chọn metric nào cho task nào?</h3>
|
|
288
|
+
|
|
289
|
+
<table>
|
|
290
|
+
<thead>
|
|
291
|
+
<tr><th>Task</th><th>Primary Metric</th><th>Secondary Metric</th><th>Lý do</th></tr>
|
|
292
|
+
</thead>
|
|
293
|
+
<tbody>
|
|
294
|
+
<tr><td>Machine Translation</td><td>BLEU</td><td>COMET, chrF</td><td>Precision-oriented: dịch chính xác từng từ</td></tr>
|
|
295
|
+
<tr><td>Summarization</td><td>ROUGE-L</td><td>BERTScore</td><td>Recall-oriented: phải cover key points</td></tr>
|
|
296
|
+
<tr><td>Question Answering</td><td>F1 / Exact Match</td><td>Semantic Sim</td><td>Token overlap + meaning</td></tr>
|
|
297
|
+
<tr><td>Dialogue / Chat</td><td>LLM-as-a-Judge</td><td>Human eval</td><td>Subjective quality, khó dùng auto metric</td></tr>
|
|
298
|
+
<tr><td>Code Generation</td><td>pass@k</td><td>Execution accuracy</td><td>Code phải chạy đúng, không chỉ "giống"</td></tr>
|
|
299
|
+
<tr><td>RAG Pipeline</td><td>Context Relevance + Faithfulness</td><td>Answer F1</td><td>Multi-dimensional eval (RAGAS framework)</td></tr>
|
|
300
|
+
</tbody>
|
|
301
|
+
</table>
|
|
302
|
+
|
|
303
|
+
<blockquote><p><strong>Exam tip:</strong> Nếu đề hỏi "metric phù hợp cho summarization" → chọn <strong>ROUGE</strong> (recall). Nếu hỏi "metric cho translation" → chọn <strong>BLEU</strong> (precision). Đây là câu hỏi rất hay gặp.</p></blockquote>
|
|
304
|
+
|
|
305
|
+
<p><strong>Q1:</strong> A team is evaluating an LLM-powered chatbot for customer support. They need a metric that can handle paraphrasing — where the meaning is correct but wording differs from reference answers. Which metric is MOST appropriate?</p>
|
|
306
|
+
|
|
307
|
+
<ul>
|
|
308
|
+
<li>A) BLEU-4</li>
|
|
309
|
+
<li>B) ROUGE-1</li>
|
|
310
|
+
<li>C) Exact Match</li>
|
|
311
|
+
<li>D) Semantic similarity (embedding-based)</li>
|
|
312
|
+
</ul>
|
|
313
|
+
|
|
314
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
315
|
+
<p><strong>D) Semantic similarity (embedding-based)</strong> ✓</p>
|
|
316
|
+
<p><em>BLEU và ROUGE dựa trên n-gram overlap → fail khi paraphrasing (cùng ý nhưng khác từ). Exact Match chỉ match 100% string. Semantic similarity dùng embedding vectors nên bắt được meaning equivalence dù wording khác. Trong customer support, user hỏi nhiều cách khác nhau cho cùng một vấn đề, nên embedding-based metric là phù hợp nhất.</em></p>
|
|
317
|
+
</details>
|
|
318
|
+
|
|
319
|
+
<h2 id="3-llm-as-a-judge">3. LLM-as-a-Judge & Human Evaluation</h2>
|
|
320
|
+
|
|
321
|
+
<h3 id="3-1-llm-as-a-judge-pattern">3.1. LLM-as-a-Judge Pattern</h3>
|
|
322
|
+
|
|
323
|
+
<p>Ý tưởng: dùng một <strong>strong model</strong> (GPT-4, Claude 3.5) để đánh giá output của <strong>weaker model</strong> hoặc model đang được test. Phương pháp này scalable hơn human evaluation và gần với human judgment hơn automated metrics.</p>
|
|
324
|
+
|
|
325
|
+
<p>Hai kiểu chính:</p>
|
|
326
|
+
|
|
327
|
+
<table>
|
|
328
|
+
<thead>
|
|
329
|
+
<tr><th>Kiểu</th><th>Cách hoạt động</th><th>Ưu điểm</th><th>Nhược điểm</th></tr>
|
|
330
|
+
</thead>
|
|
331
|
+
<tbody>
|
|
332
|
+
<tr><td><strong>Pointwise</strong></td><td>Judge chấm điểm 1 response (1-5)</td><td>Đơn giản, absolute score</td><td>Calibration bias</td></tr>
|
|
333
|
+
<tr><td><strong>Pairwise</strong></td><td>Judge so sánh 2 responses: A vs B</td><td>Relative ranking, ít bias</td><td>$O(n^2)$ comparisons</td></tr>
|
|
334
|
+
</tbody>
|
|
335
|
+
</table>
|
|
336
|
+
|
|
337
|
+
<h3 id="3-2-pointwise-implementation">3.2. Pointwise Scoring Implementation</h3>
|
|
338
|
+
|
|
339
|
+
<pre><code class="language-python">
|
|
340
|
+
import json
|
|
341
|
+
|
|
342
|
+
JUDGE_PROMPT = """You are an impartial judge evaluating AI assistant responses.
|
|
343
|
+
Rate the following response on a scale of 1-5 for each criterion.
|
|
344
|
+
|
|
345
|
+
## Criteria
|
|
346
|
+
- **Correctness** (1-5): Is the information factually accurate?
|
|
347
|
+
- **Helpfulness** (1-5): Does it actually answer the question?
|
|
348
|
+
- **Conciseness** (1-5): Is it appropriately concise without losing important info?
|
|
349
|
+
- **Safety** (1-5): Does it avoid harmful, biased, or inappropriate content?
|
|
350
|
+
|
|
351
|
+
## Question
|
|
352
|
+
{question}
|
|
353
|
+
|
|
354
|
+
## Response to evaluate
|
|
355
|
+
{response}
|
|
356
|
+
|
|
357
|
+
## Output Format (JSON only)
|
|
358
|
+
{{
|
|
359
|
+
"correctness": {{"score": int, "reason": "..."}},
|
|
360
|
+
"helpfulness": {{"score": int, "reason": "..."}},
|
|
361
|
+
"conciseness": {{"score": int, "reason": "..."}},
|
|
362
|
+
"safety": {{"score": int, "reason": "..."}},
|
|
363
|
+
"overall": {{"score": float, "summary": "..."}}
|
|
364
|
+
}}
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
def judge_response(client, question: str, response: str) -> dict:
|
|
368
|
+
"""
|
|
369
|
+
Dùng strong LLM (GPT-4) làm judge.
|
|
370
|
+
Returns structured evaluation scores.
|
|
371
|
+
"""
|
|
372
|
+
prompt = JUDGE_PROMPT.format(question=question, response=response)
|
|
373
|
+
|
|
374
|
+
result = client.chat.completions.create(
|
|
375
|
+
model="gpt-4",
|
|
376
|
+
messages=[{"role": "user", "content": prompt}],
|
|
377
|
+
temperature=0.0, # Deterministic judging
|
|
378
|
+
response_format={"type": "json_object"},
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return json.loads(result.choices[0].message.content)
|
|
382
|
+
|
|
383
|
+
# --- Ví dụ sử dụng ---
|
|
384
|
+
# scores = judge_response(client,
|
|
385
|
+
# question="What is LoRA fine-tuning?",
|
|
386
|
+
# response="LoRA adds low-rank matrices to freeze model weights..."
|
|
387
|
+
# )
|
|
388
|
+
# print(scores["overall"]["score"]) # → 4.2
|
|
389
|
+
</code></pre>
|
|
390
|
+
|
|
391
|
+
<h3 id="3-3-elo-ranking-system">3.3. ELO Ranking System cho LLMs</h3>
|
|
392
|
+
|
|
393
|
+
<p><strong>ELO rating</strong> (lấy từ chess) gán mỗi model một rating number. Khi 2 model "đấu" (judge chọn winner), rating cập nhật:</p>
|
|
394
|
+
|
|
395
|
+
<p>$$E_A = \frac{1}{1 + 10^{(R_B - R_A)/400}}$$</p>
|
|
396
|
+
|
|
397
|
+
<p>$$R'_A = R_A + K \cdot (S_A - E_A)$$</p>
|
|
398
|
+
|
|
399
|
+
<p>Trong đó:</p>
|
|
400
|
+
<ul>
|
|
401
|
+
<li>$R_A, R_B$ = current ratings</li>
|
|
402
|
+
<li>$E_A$ = expected score (xác suất A thắng)</li>
|
|
403
|
+
<li>$S_A$ = actual score (1 = win, 0.5 = draw, 0 = loss)</li>
|
|
404
|
+
<li>$K$ = sensitivity factor (thường K=32)</li>
|
|
405
|
+
</ul>
|
|
406
|
+
|
|
407
|
+
<pre><code class="language-python">
|
|
408
|
+
import random
|
|
409
|
+
|
|
410
|
+
class ELORanker:
|
|
411
|
+
"""
|
|
412
|
+
ELO ranking system cho LLM comparison.
|
|
413
|
+
Chatbot Arena (lmsys.org) dùng hệ thống tương tự.
|
|
414
|
+
"""
|
|
415
|
+
def __init__(self, k_factor: int = 32, initial_rating: int = 1500):
|
|
416
|
+
self.k = k_factor
|
|
417
|
+
self.initial = initial_rating
|
|
418
|
+
self.ratings: dict[str, float] = {}
|
|
419
|
+
self.match_history: list[dict] = []
|
|
420
|
+
|
|
421
|
+
def get_rating(self, model: str) -> float:
|
|
422
|
+
return self.ratings.setdefault(model, float(self.initial))
|
|
423
|
+
|
|
424
|
+
def expected_score(self, ra: float, rb: float) -> float:
|
|
425
|
+
"""Xác suất A thắng B."""
|
|
426
|
+
return 1.0 / (1.0 + 10 ** ((rb - ra) / 400))
|
|
427
|
+
|
|
428
|
+
def update(self, winner: str, loser: str, draw: bool = False):
|
|
429
|
+
"""
|
|
430
|
+
Cập nhật ratings sau 1 trận.
|
|
431
|
+
winner = model A, loser = model B (hoặc draw).
|
|
432
|
+
"""
|
|
433
|
+
ra = self.get_rating(winner)
|
|
434
|
+
rb = self.get_rating(loser)
|
|
435
|
+
|
|
436
|
+
ea = self.expected_score(ra, rb)
|
|
437
|
+
eb = self.expected_score(rb, ra)
|
|
438
|
+
|
|
439
|
+
if draw:
|
|
440
|
+
sa, sb = 0.5, 0.5
|
|
441
|
+
else:
|
|
442
|
+
sa, sb = 1.0, 0.0
|
|
443
|
+
|
|
444
|
+
self.ratings[winner] = ra + self.k * (sa - ea)
|
|
445
|
+
self.ratings[loser] = rb + self.k * (sb - eb)
|
|
446
|
+
|
|
447
|
+
self.match_history.append({
|
|
448
|
+
"winner": winner, "loser": loser, "draw": draw,
|
|
449
|
+
"new_ratings": dict(self.ratings)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
def leaderboard(self) -> list[tuple[str, float]]:
|
|
453
|
+
"""Trả về sorted leaderboard."""
|
|
454
|
+
return sorted(self.ratings.items(), key=lambda x: -x[1])
|
|
455
|
+
|
|
456
|
+
# --- Simulate matchups ---
|
|
457
|
+
ranker = ELORanker()
|
|
458
|
+
models = ["GPT-4o", "Claude-3.5", "Llama-3-70B", "Gemini-1.5"]
|
|
459
|
+
|
|
460
|
+
# 50 random pairwise comparisons (simplified simulation)
|
|
461
|
+
for _ in range(50):
|
|
462
|
+
a, b = random.sample(models, 2)
|
|
463
|
+
# Giả lập: GPT-4o và Claude thắng nhiều hơn
|
|
464
|
+
win_probs = {"GPT-4o": 0.7, "Claude-3.5": 0.65,
|
|
465
|
+
"Llama-3-70B": 0.45, "Gemini-1.5": 0.55}
|
|
466
|
+
if random.random() < win_probs[a] / (win_probs[a] + win_probs[b]):
|
|
467
|
+
ranker.update(winner=a, loser=b)
|
|
468
|
+
else:
|
|
469
|
+
ranker.update(winner=b, loser=a)
|
|
470
|
+
|
|
471
|
+
print("=== LLM Leaderboard ===")
|
|
472
|
+
for model, rating in ranker.leaderboard():
|
|
473
|
+
print(f" {model:20s} ELO: {rating:.0f}")
|
|
474
|
+
</code></pre>
|
|
475
|
+
|
|
476
|
+
<h3 id="3-4-nemo-evaluator">3.4. NeMo Evaluator Microservice</h3>
|
|
477
|
+
|
|
478
|
+
<p><strong>NeMo Evaluator</strong> là thành phần trong NVIDIA NeMo framework, cho phép chạy evaluation có hệ thống trên LLM endpoints. Config bằng YAML, gọi qua REST API.</p>
|
|
479
|
+
|
|
480
|
+
<pre><code class="language-python">
|
|
481
|
+
# NeMo Evaluator — Config ví dụ
|
|
482
|
+
nemo_eval_config = {
|
|
483
|
+
"type": "llm-as-judge",
|
|
484
|
+
"model": {
|
|
485
|
+
"api_endpoint": "http://nim-llm:8000/v1/chat/completions",
|
|
486
|
+
"model_id": "meta/llama-3.1-70b-instruct"
|
|
487
|
+
},
|
|
488
|
+
"judge": {
|
|
489
|
+
"api_endpoint": "http://nim-judge:8000/v1/chat/completions",
|
|
490
|
+
"model_id": "nvidia/llama-3.1-nemotron-70b-instruct"
|
|
491
|
+
},
|
|
492
|
+
"dataset": {
|
|
493
|
+
"path": "/data/eval/legal_qa_golden.jsonl",
|
|
494
|
+
"format": "jsonl",
|
|
495
|
+
"fields": {
|
|
496
|
+
"question": "input",
|
|
497
|
+
"reference": "expected_output"
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
"metrics": ["correctness", "relevance", "conciseness"],
|
|
501
|
+
"output": {
|
|
502
|
+
"path": "/results/eval_run_001.json",
|
|
503
|
+
"mlflow_tracking_uri": "http://mlflow:5000",
|
|
504
|
+
"experiment_name": "legal-qa-eval"
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
</code></pre>
|
|
508
|
+
|
|
509
|
+
<blockquote><p><strong>Exam tip:</strong> NeMo Evaluator hỗ trợ cả <strong>automated metrics</strong> (BLEU, ROUGE) lẫn <strong>LLM-as-a-Judge</strong>. Khi đề hỏi "How to evaluate model before deployment using NeMo" → NeMo Evaluator microservice. Khi hỏi "How to track evaluation experiments" → <strong>MLflow</strong> integration.</p></blockquote>
|
|
510
|
+
|
|
511
|
+
<p><strong>Q2:</strong> In an ELO ranking system for LLMs, Model A has a rating of 1600 and Model B has a rating of 1400. What is the expected probability that Model A wins a pairwise comparison?</p>
|
|
512
|
+
|
|
513
|
+
<ul>
|
|
514
|
+
<li>A) 50%</li>
|
|
515
|
+
<li>B) 64%</li>
|
|
516
|
+
<li>C) 76%</li>
|
|
517
|
+
<li>D) 88%</li>
|
|
518
|
+
</ul>
|
|
519
|
+
|
|
520
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
521
|
+
<p><strong>C) 76%</strong> ✓</p>
|
|
522
|
+
<p><em>Áp dụng công thức: $E_A = \frac{1}{1 + 10^{(1400-1600)/400}} = \frac{1}{1 + 10^{-0.5}} = \frac{1}{1 + 0.316} = \frac{1}{1.316} \approx 0.76$ hay 76%. Rating difference 200 tương đương ~76% win probability. Nhớ: mỗi 400 điểm chênh lệch = 10x expected win ratio.</em></p>
|
|
523
|
+
</details>
|
|
524
|
+
|
|
525
|
+
<h2 id="4-systematic-evaluation-nemo-mlflow">4. Systematic Evaluation với NeMo & MLflow</h2>
|
|
526
|
+
|
|
527
|
+
<h3 id="4-1-nemo-evaluator-workflow">4.1. NeMo Evaluator Workflow</h3>
|
|
528
|
+
|
|
529
|
+
<p>Pipeline evaluation có hệ thống trong NVIDIA NeMo ecosystem:</p>
|
|
530
|
+
|
|
531
|
+
<pre><code class="language-text">
|
|
532
|
+
NeMo Evaluation Workflow — End-to-End
|
|
533
|
+
══════════════════════════════════════════════════════════════
|
|
534
|
+
|
|
535
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
536
|
+
│ Prepare │ │ Deploy NIM │ │ Deploy │
|
|
537
|
+
│ Eval Dataset│ │ Model Under │ │ Judge Model │
|
|
538
|
+
│ (JSONL) │ │ Test (NIM) │ │ (Nemotron) │
|
|
539
|
+
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
540
|
+
│ │ │
|
|
541
|
+
▼ ▼ ▼
|
|
542
|
+
┌────────────────────────────────────────────────────────┐
|
|
543
|
+
│ NeMo Evaluator Microservice │
|
|
544
|
+
│ │
|
|
545
|
+
│ 1. Load eval dataset (questions + references) │
|
|
546
|
+
│ 2. Send each question to Model Under Test │
|
|
547
|
+
│ 3. Collect responses │
|
|
548
|
+
│ 4. Score via automated metrics AND/OR LLM judge │
|
|
549
|
+
│ 5. Aggregate results → score card │
|
|
550
|
+
└────────────────────────┬───────────────────────────────┘
|
|
551
|
+
│
|
|
552
|
+
▼
|
|
553
|
+
┌────────────────────────────────────────────────────────┐
|
|
554
|
+
│ MLflow Server │
|
|
555
|
+
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
|
|
556
|
+
│ │ Experiment 1│ │ Experiment 2│ │ Experiment 3 │ │
|
|
557
|
+
│ │ base model │ │ LoRA v1 │ │ LoRA v2 │ │
|
|
558
|
+
│ │ F1: 0.62 │ │ F1: 0.78 │ │ F1: 0.81 │ │
|
|
559
|
+
│ │ BLEU: 0.31 │ │ BLEU: 0.42 │ │ BLEU: 0.45 │ │
|
|
560
|
+
│ └─────────────┘ └─────────────┘ └──────────────┘ │
|
|
561
|
+
│ │
|
|
562
|
+
│ → Select best: Experiment 3 (LoRA v2) → deploy to NIM│
|
|
563
|
+
└────────────────────────────────────────────────────────┘
|
|
564
|
+
</code></pre>
|
|
565
|
+
|
|
566
|
+
<h3 id="4-2-gsm8k-benchmark">4.2. GSM8K Benchmark</h3>
|
|
567
|
+
|
|
568
|
+
<p><strong>GSM8K</strong> (Grade School Math 8K) là benchmark chứa ~8.5K bài toán tiểu học, dùng để test <strong>reasoning ability</strong> của LLM. Mỗi bài có chain-of-thought solution.</p>
|
|
569
|
+
|
|
570
|
+
<pre><code class="language-python">
|
|
571
|
+
# Ví dụ GSM8K question format
|
|
572
|
+
gsm8k_example = {
|
|
573
|
+
"question": "Janet buys 3 pounds of steak at $8/pound and 2 pounds "
|
|
574
|
+
"of chicken at $5/pound. How much does she spend total?",
|
|
575
|
+
"answer": "3 pounds of steak cost 3 * 8 = <<3*8=24>>24 dollars. "
|
|
576
|
+
"2 pounds of chicken cost 2 * 5 = <<2*5=10>>10 dollars. "
|
|
577
|
+
"Total cost is 24 + 10 = <<24+10=34>>34 dollars. #### 34"
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
# Evaluation: extract final answer after ####, compare with model output
|
|
581
|
+
def extract_gsm8k_answer(solution: str) -> str:
|
|
582
|
+
"""Trích xuất final answer từ GSM8K format."""
|
|
583
|
+
if "####" in solution:
|
|
584
|
+
return solution.split("####")[-1].strip()
|
|
585
|
+
# Fallback: lấy số cuối cùng
|
|
586
|
+
import re
|
|
587
|
+
numbers = re.findall(r'-?\d+\.?\d*', solution)
|
|
588
|
+
return numbers[-1] if numbers else ""
|
|
589
|
+
|
|
590
|
+
def evaluate_gsm8k(model_answers: list[str],
|
|
591
|
+
ground_truths: list[str]) -> dict:
|
|
592
|
+
"""Tính accuracy trên GSM8K."""
|
|
593
|
+
correct = 0
|
|
594
|
+
for pred, truth in zip(model_answers, ground_truths):
|
|
595
|
+
pred_ans = extract_gsm8k_answer(pred)
|
|
596
|
+
truth_ans = extract_gsm8k_answer(truth)
|
|
597
|
+
if pred_ans == truth_ans:
|
|
598
|
+
correct += 1
|
|
599
|
+
accuracy = correct / len(ground_truths)
|
|
600
|
+
return {"accuracy": accuracy, "correct": correct,
|
|
601
|
+
"total": len(ground_truths)}
|
|
602
|
+
</code></pre>
|
|
603
|
+
|
|
604
|
+
<h3 id="4-3-zero-shot-vs-few-shot">4.3. Zero-Shot vs Few-Shot Evaluation</h3>
|
|
605
|
+
|
|
606
|
+
<p>So sánh performance khi prompting khác nhau:</p>
|
|
607
|
+
|
|
608
|
+
<table>
|
|
609
|
+
<thead>
|
|
610
|
+
<tr><th>Setting</th><th>GSM8K Accuracy (Llama-3-8B)</th><th>GSM8K Accuracy (Llama-3-70B)</th></tr>
|
|
611
|
+
</thead>
|
|
612
|
+
<tbody>
|
|
613
|
+
<tr><td><strong>Zero-shot</strong></td><td>~48%</td><td>~78%</td></tr>
|
|
614
|
+
<tr><td><strong>Zero-shot CoT</strong> ("think step by step")</td><td>~56%</td><td>~83%</td></tr>
|
|
615
|
+
<tr><td><strong>5-shot</strong></td><td>~55%</td><td>~85%</td></tr>
|
|
616
|
+
<tr><td><strong>5-shot CoT</strong></td><td>~62%</td><td>~90%</td></tr>
|
|
617
|
+
</tbody>
|
|
618
|
+
</table>
|
|
619
|
+
|
|
620
|
+
<p>Nhận xét: <strong>Few-shot + Chain-of-Thought</strong> luôn tốt nhất. Model lớn hơn (70B) benefit nhiều hơn từ CoT so với model nhỏ (8B).</p>
|
|
621
|
+
|
|
622
|
+
<h3 id="4-4-mlflow-tracking">4.4. MLflow Experiment Tracking</h3>
|
|
623
|
+
|
|
624
|
+
<pre><code class="language-python">
|
|
625
|
+
import mlflow
|
|
626
|
+
|
|
627
|
+
# Thiết lập MLflow experiment
|
|
628
|
+
mlflow.set_tracking_uri("http://mlflow:5000")
|
|
629
|
+
mlflow.set_experiment("legal-qa-model-comparison")
|
|
630
|
+
|
|
631
|
+
def run_evaluation_experiment(model_name: str,
|
|
632
|
+
model_endpoint: str,
|
|
633
|
+
eval_dataset: list[dict]):
|
|
634
|
+
"""
|
|
635
|
+
Chạy evaluation và log kết quả vào MLflow.
|
|
636
|
+
"""
|
|
637
|
+
with mlflow.start_run(run_name=f"eval-{model_name}"):
|
|
638
|
+
# Log parameters
|
|
639
|
+
mlflow.log_param("model_name", model_name)
|
|
640
|
+
mlflow.log_param("eval_dataset_size", len(eval_dataset))
|
|
641
|
+
mlflow.log_param("eval_type", "automated + llm-judge")
|
|
642
|
+
|
|
643
|
+
# Run inference + evaluation
|
|
644
|
+
results = run_nemo_evaluation(model_endpoint, eval_dataset)
|
|
645
|
+
|
|
646
|
+
# Log metrics
|
|
647
|
+
mlflow.log_metric("bleu_score", results["bleu"])
|
|
648
|
+
mlflow.log_metric("rouge_l", results["rouge_l"])
|
|
649
|
+
mlflow.log_metric("f1_score", results["f1"])
|
|
650
|
+
mlflow.log_metric("judge_correctness", results["judge_correctness"])
|
|
651
|
+
mlflow.log_metric("judge_relevance", results["judge_relevance"])
|
|
652
|
+
mlflow.log_metric("latency_p95_ms", results["latency_p95"])
|
|
653
|
+
|
|
654
|
+
# Log artifacts (full results, sample outputs)
|
|
655
|
+
mlflow.log_dict(results, "full_results.json")
|
|
656
|
+
|
|
657
|
+
print(f"[{model_name}] F1={results['f1']:.3f}, "
|
|
658
|
+
f"BLEU={results['bleu']:.3f}, "
|
|
659
|
+
f"Judge={results['judge_correctness']:.2f}")
|
|
660
|
+
</code></pre>
|
|
661
|
+
|
|
662
|
+
<p><strong>Q3:</strong> A data scientist runs the same evaluation on three model configurations using NeMo Evaluator: base model, LoRA fine-tuned (rank 8), and LoRA fine-tuned (rank 32). All results are logged to MLflow. Which MLflow feature should they use to select the best configuration?</p>
|
|
663
|
+
|
|
664
|
+
<ul>
|
|
665
|
+
<li>A) MLflow Model Registry</li>
|
|
666
|
+
<li>B) MLflow Projects</li>
|
|
667
|
+
<li>C) MLflow Experiment comparison / search runs</li>
|
|
668
|
+
<li>D) MLflow Deployments</li>
|
|
669
|
+
</ul>
|
|
670
|
+
|
|
671
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
672
|
+
<p><strong>C) MLflow Experiment comparison / search runs</strong> ✓</p>
|
|
673
|
+
<p><em>MLflow Experiments cho phép so sánh metrics across runs — filter, sort, visualize. Model Registry là để versioning models đã chọn. Projects là để package code. Deployments là để serve models. Bước "chọn best config" thuộc về experiment comparison.</em></p>
|
|
674
|
+
</details>
|
|
675
|
+
|
|
676
|
+
<h2 id="5-lora-theory">5. LoRA — Low-Rank Adaptation Theory</h2>
|
|
677
|
+
|
|
678
|
+
<h3 id="5-1-van-de-full-fine-tuning">5.1. Vấn đề của Full Fine-tuning</h3>
|
|
679
|
+
|
|
680
|
+
<p>Full fine-tuning cập nhật <strong>toàn bộ</strong> parameters của model. Với LLM hiện đại, điều này cực kỳ tốn kém:</p>
|
|
681
|
+
|
|
682
|
+
<table>
|
|
683
|
+
<thead>
|
|
684
|
+
<tr><th>Model</th><th>Parameters</th><th>Full FT Memory (FP16)</th><th>Full FT Memory (FP32)</th><th>GPU Needed</th></tr>
|
|
685
|
+
</thead>
|
|
686
|
+
<tbody>
|
|
687
|
+
<tr><td>Llama-3-8B</td><td>8B</td><td>~32 GB</td><td>~64 GB</td><td>1× A100 80GB</td></tr>
|
|
688
|
+
<tr><td>Llama-3-70B</td><td>70B</td><td>~280 GB</td><td>~560 GB</td><td>4-8× A100 80GB</td></tr>
|
|
689
|
+
<tr><td>Llama-3-405B</td><td>405B</td><td>~1.6 TB</td><td>~3.2 TB</td><td>32× A100 80GB</td></tr>
|
|
690
|
+
</tbody>
|
|
691
|
+
</table>
|
|
692
|
+
|
|
693
|
+
<p>Ngoài chi phí, full fine-tuning còn gây:</p>
|
|
694
|
+
|
|
695
|
+
<ul>
|
|
696
|
+
<li><strong>Catastrophic forgetting</strong> — model quên kiến thức cũ khi học task mới</li>
|
|
697
|
+
<li><strong>Storage overhead</strong> — mỗi fine-tuned version là 1 bản copy đầy đủ</li>
|
|
698
|
+
<li><strong>Overfitting risk</strong> — dễ overfit trên dataset nhỏ</li>
|
|
699
|
+
</ul>
|
|
700
|
+
|
|
701
|
+
<h3 id="5-2-lora-intuition">5.2. LoRA Intuition — Weight Updates are Low-Rank</h3>
|
|
702
|
+
|
|
703
|
+
<p>Observation quan trọng từ paper <em>"LoRA: Low-Rank Adaptation of Large Language Models"</em> (Hu et al., 2021): khi fine-tuning LLM, <strong>weight changes $\Delta W$ có rank thấp</strong> — nghĩa là phần lớn thông tin nằm trong vài dimensions chính.</p>
|
|
704
|
+
|
|
705
|
+
<p>Thay vì học $\Delta W \in \mathbb{R}^{d \times k}$ (quá nhiều parameters), ta phân tích nó thành tích 2 matrix nhỏ hơn:</p>
|
|
706
|
+
|
|
707
|
+
<p>$$W' = W + \Delta W = W + BA$$</p>
|
|
708
|
+
|
|
709
|
+
<p>Trong đó:</p>
|
|
710
|
+
<ul>
|
|
711
|
+
<li>$W \in \mathbb{R}^{d \times k}$ — pretrained weight (frozen, không update)</li>
|
|
712
|
+
<li>$B \in \mathbb{R}^{d \times r}$ — LoRA down-projection</li>
|
|
713
|
+
<li>$A \in \mathbb{R}^{r \times k}$ — LoRA up-projection</li>
|
|
714
|
+
<li>$r \ll \min(d, k)$ — <strong>rank</strong>, thường r = 4, 8, 16, 32</li>
|
|
715
|
+
</ul>
|
|
716
|
+
|
|
717
|
+
<h3 id="5-3-parameter-count">5.3. Parameter Count Calculation</h3>
|
|
718
|
+
|
|
719
|
+
<p>Savings cực kỳ ấn tượng. Ví dụ với một attention layer:</p>
|
|
720
|
+
|
|
721
|
+
<p>$$\text{Full params} = d \times k$$</p>
|
|
722
|
+
|
|
723
|
+
<p>$$\text{LoRA params} = d \times r + r \times k = r(d + k)$$</p>
|
|
724
|
+
|
|
725
|
+
<p>$$\text{Ratio} = \frac{r(d+k)}{dk}$$</p>
|
|
726
|
+
|
|
727
|
+
<pre><code class="language-python">
|
|
728
|
+
def lora_param_analysis(d: int, k: int, r: int,
|
|
729
|
+
num_layers: int,
|
|
730
|
+
target_modules: int = 4):
|
|
731
|
+
"""
|
|
732
|
+
Tính parameter count cho LoRA configuration.
|
|
733
|
+
target_modules: Q, K, V, O projections (thường 4)
|
|
734
|
+
"""
|
|
735
|
+
full_params_per_layer = d * k * target_modules
|
|
736
|
+
lora_params_per_layer = r * (d + k) * target_modules
|
|
737
|
+
|
|
738
|
+
total_full = full_params_per_layer * num_layers
|
|
739
|
+
total_lora = lora_params_per_layer * num_layers
|
|
740
|
+
|
|
741
|
+
ratio = total_lora / total_full * 100
|
|
742
|
+
|
|
743
|
+
return {
|
|
744
|
+
"full_params": f"{total_full:,}",
|
|
745
|
+
"lora_params": f"{total_lora:,}",
|
|
746
|
+
"ratio": f"{ratio:.2f}%",
|
|
747
|
+
"savings": f"{100 - ratio:.2f}%"
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
# Llama-3-8B: d=4096, k=4096, 32 layers
|
|
751
|
+
result = lora_param_analysis(d=4096, k=4096, r=16, num_layers=32)
|
|
752
|
+
print(f"Full fine-tuning: {result['full_params']} params")
|
|
753
|
+
print(f"LoRA (r=16): {result['lora_params']} params")
|
|
754
|
+
print(f"LoRA / Full: {result['ratio']}")
|
|
755
|
+
print(f"Savings: {result['savings']}")
|
|
756
|
+
|
|
757
|
+
# Output:
|
|
758
|
+
# Full fine-tuning: 2,147,483,648 params (~2.1B for attention only)
|
|
759
|
+
# LoRA (r=16): 16,777,216 params (~16.8M)
|
|
760
|
+
# LoRA / Full: 0.78%
|
|
761
|
+
# Savings: 99.22%
|
|
762
|
+
</code></pre>
|
|
763
|
+
|
|
764
|
+
<h3 id="5-4-rank-selection">5.4. Rank Selection — Trade-offs</h3>
|
|
765
|
+
|
|
766
|
+
<table>
|
|
767
|
+
<thead>
|
|
768
|
+
<tr><th>Rank ($r$)</th><th>Trainable Params</th><th>Quality</th><th>Training Speed</th><th>Use case</th></tr>
|
|
769
|
+
</thead>
|
|
770
|
+
<tbody>
|
|
771
|
+
<tr><td>$r = 4$</td><td>Rất ít (~4M)</td><td>Good cho simple tasks</td><td>Nhanh nhất</td><td>Style transfer, format tuning</td></tr>
|
|
772
|
+
<tr><td>$r = 8$</td><td>Ít (~8M)</td><td>Good-Very Good</td><td>Nhanh</td><td>Domain adaptation, QA</td></tr>
|
|
773
|
+
<tr><td>$r = 16$</td><td>Vừa (~17M)</td><td>Very Good</td><td>Medium</td><td>Most tasks (default choice)</td></tr>
|
|
774
|
+
<tr><td>$r = 32$</td><td>Nhiều hơn (~34M)</td><td>Excellent</td><td>Chậm hơn</td><td>Complex domain, code gen</td></tr>
|
|
775
|
+
<tr><td>$r = 64$</td><td>Khá nhiều (~67M)</td><td>Near full FT</td><td>Chậm</td><td>Diminishing returns</td></tr>
|
|
776
|
+
</tbody>
|
|
777
|
+
</table>
|
|
778
|
+
|
|
779
|
+
<h3 id="5-5-alpha-scaling">5.5. Alpha Scaling Factor</h3>
|
|
780
|
+
|
|
781
|
+
<p>LoRA dùng scaling factor $\alpha$ để kiểm soát "mức ảnh hưởng" của adaptation:</p>
|
|
782
|
+
|
|
783
|
+
<p>$$h = Wx + \frac{\alpha}{r} \cdot BAx$$</p>
|
|
784
|
+
|
|
785
|
+
<p>Thường $\alpha = r$ hoặc $\alpha = 2r$. Khi $\alpha = r$, scaling factor = 1 (không thay đổi). Tăng $\alpha$ → LoRA adaptation ảnh hưởng lớn hơn.</p>
|
|
786
|
+
|
|
787
|
+
<h3 id="5-6-which-layers">5.6. Apply LoRA vào layers nào?</h3>
|
|
788
|
+
|
|
789
|
+
<p>Trong transformer, LoRA thường áp dụng vào <strong>attention projections</strong>:</p>
|
|
790
|
+
|
|
791
|
+
<ul>
|
|
792
|
+
<li><strong>Q (Query)</strong> — ✓ luôn nên apply</li>
|
|
793
|
+
<li><strong>K (Key)</strong> — ✓ nên apply</li>
|
|
794
|
+
<li><strong>V (Value)</strong> — ✓ luôn nên apply (quan trọng nhất)</li>
|
|
795
|
+
<li><strong>O (Output)</strong> — ✓ tùy chọn, có thể bỏ nếu cần tiết kiệm</li>
|
|
796
|
+
<li><strong>MLP layers</strong> — optional, giúp cho complex adaptations</li>
|
|
797
|
+
</ul>
|
|
798
|
+
|
|
799
|
+
<p>Paper gốc cho thấy: apply LoRA vào <strong>Q + V</strong> đã đủ tốt cho hầu hết tasks.</p>
|
|
800
|
+
|
|
801
|
+
<pre><code class="language-text">
|
|
802
|
+
LoRA within Transformer Attention Layer
|
|
803
|
+
══════════════════════════════════════════════════════════════
|
|
804
|
+
|
|
805
|
+
Input: x ∈ ℝ^(seq_len × d_model)
|
|
806
|
+
─────────────────────────────────────────────────────────
|
|
807
|
+
|
|
808
|
+
┌─────────────────────────────────┐
|
|
809
|
+
│ Original Path (Frozen) │
|
|
810
|
+
│ │
|
|
811
|
+
│ Q = W_q · x (frozen W_q) │ ┌──────────────────┐
|
|
812
|
+
│ K = W_k · x (frozen W_k) │ │ LoRA Adapters │
|
|
813
|
+
│ V = W_v · x (frozen W_v) │ │ │
|
|
814
|
+
│ │ │ ΔQ = B_q·A_q · x │
|
|
815
|
+
│ Attn = softmax(QK^T/√d) · V │ │ ΔK = B_k·A_k · x │
|
|
816
|
+
│ │ │ ΔV = B_v·A_v · x │
|
|
817
|
+
│ Out = W_o · Attn (frozen W_o) │ │ ΔO = B_o·A_o·Attn│
|
|
818
|
+
└───────────────┬─────────────────┘ └────────┬─────────┘
|
|
819
|
+
│ │
|
|
820
|
+
▼ ▼
|
|
821
|
+
┌───────────────────────────────────────────┐
|
|
822
|
+
│ h = W·x + (α/r) · BA·x │
|
|
823
|
+
│ Final Output │
|
|
824
|
+
│ (frozen pretrained + trainable LoRA) │
|
|
825
|
+
└───────────────────────────────────────────┘
|
|
826
|
+
|
|
827
|
+
Memory: Only B (d×r) and A (r×k) are trained
|
|
828
|
+
Example: d=4096, r=16 → 4096×16 + 16×4096 = 131,072 params
|
|
829
|
+
vs full: 4096×4096 = 16,777,216 params → 128× smaller!
|
|
830
|
+
</code></pre>
|
|
831
|
+
|
|
832
|
+
<h3 id="5-7-comparison-table">5.7. LoRA vs Alternatives</h3>
|
|
833
|
+
|
|
834
|
+
<table>
|
|
835
|
+
<thead>
|
|
836
|
+
<tr><th>Method</th><th>Trainable Params</th><th>Memory</th><th>Quality</th><th>Khi nào dùng</th></tr>
|
|
837
|
+
</thead>
|
|
838
|
+
<tbody>
|
|
839
|
+
<tr><td><strong>Full Fine-tuning</strong></td><td>100%</td><td>Rất cao</td><td>Best (nhưng overfit risk)</td><td>Có nhiều data + GPU resources</td></tr>
|
|
840
|
+
<tr><td><strong>LoRA</strong></td><td>0.1-1%</td><td>Thấp</td><td>Very Good</td><td>Default choice cho hầu hết tasks</td></tr>
|
|
841
|
+
<tr><td><strong>QLoRA</strong></td><td>0.1-1%</td><td>Rất thấp</td><td>Good-Very Good</td><td>Limited GPU memory</td></tr>
|
|
842
|
+
<tr><td><strong>Prefix Tuning</strong></td><td><0.1%</td><td>Thấp nhất</td><td>Good cho specific tasks</td><td>Short, structured outputs</td></tr>
|
|
843
|
+
<tr><td><strong>Adapter Tuning</strong></td><td>1-5%</td><td>Medium</td><td>Good</td><td>Multi-task learning</td></tr>
|
|
844
|
+
<tr><td><strong>Prompt Tuning</strong></td><td><0.01%</td><td>Minimal</td><td>Moderate</td><td>Simple classification</td></tr>
|
|
845
|
+
</tbody>
|
|
846
|
+
</table>
|
|
847
|
+
|
|
848
|
+
<h3 id="5-8-lora-implementation">5.8. Implement LoRA Wrapper từ Scratch</h3>
|
|
849
|
+
|
|
850
|
+
<pre><code class="language-python">
|
|
851
|
+
import torch
|
|
852
|
+
import torch.nn as nn
|
|
853
|
+
import math
|
|
854
|
+
|
|
855
|
+
class LoRALinear(nn.Module):
|
|
856
|
+
"""
|
|
857
|
+
LoRA wrapper cho nn.Linear layer.
|
|
858
|
+
Freezes original weight, adds low-rank BA decomposition.
|
|
859
|
+
"""
|
|
860
|
+
def __init__(self, original_layer: nn.Linear,
|
|
861
|
+
rank: int = 16, alpha: float = 16.0):
|
|
862
|
+
super().__init__()
|
|
863
|
+
self.original = original_layer
|
|
864
|
+
self.rank = rank
|
|
865
|
+
self.alpha = alpha
|
|
866
|
+
self.scaling = alpha / rank
|
|
867
|
+
|
|
868
|
+
in_features = original_layer.in_features
|
|
869
|
+
out_features = original_layer.out_features
|
|
870
|
+
|
|
871
|
+
# Freeze original weights
|
|
872
|
+
self.original.weight.requires_grad_(False)
|
|
873
|
+
if self.original.bias is not None:
|
|
874
|
+
self.original.bias.requires_grad_(False)
|
|
875
|
+
|
|
876
|
+
# LoRA matrices
|
|
877
|
+
# A: initialized with Kaiming uniform (like the paper)
|
|
878
|
+
# B: initialized with zeros (so ΔW = BA = 0 at start)
|
|
879
|
+
self.lora_A = nn.Parameter(
|
|
880
|
+
torch.empty(rank, in_features)
|
|
881
|
+
)
|
|
882
|
+
self.lora_B = nn.Parameter(
|
|
883
|
+
torch.zeros(out_features, rank)
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
# Initialize A with Kaiming
|
|
887
|
+
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
|
888
|
+
|
|
889
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
890
|
+
# Original frozen path
|
|
891
|
+
h = self.original(x)
|
|
892
|
+
# LoRA adaptation path: scaling * (x @ A^T @ B^T)
|
|
893
|
+
lora_out = x @ self.lora_A.T @ self.lora_B.T
|
|
894
|
+
h = h + self.scaling * lora_out
|
|
895
|
+
return h
|
|
896
|
+
|
|
897
|
+
def merge_weights(self) -> nn.Linear:
|
|
898
|
+
"""
|
|
899
|
+
Merge LoRA vào original weight cho inference.
|
|
900
|
+
Không cần tính LoRA riêng nữa → zero overhead.
|
|
901
|
+
"""
|
|
902
|
+
merged = nn.Linear(
|
|
903
|
+
self.original.in_features,
|
|
904
|
+
self.original.out_features,
|
|
905
|
+
bias=self.original.bias is not None
|
|
906
|
+
)
|
|
907
|
+
# W' = W + (α/r) × B × A
|
|
908
|
+
merged.weight.data = (
|
|
909
|
+
self.original.weight.data +
|
|
910
|
+
self.scaling * self.lora_B @ self.lora_A
|
|
911
|
+
)
|
|
912
|
+
if self.original.bias is not None:
|
|
913
|
+
merged.bias.data = self.original.bias.data
|
|
914
|
+
return merged
|
|
915
|
+
|
|
916
|
+
def apply_lora_to_model(model: nn.Module,
|
|
917
|
+
rank: int = 16,
|
|
918
|
+
alpha: float = 16.0,
|
|
919
|
+
target_modules: list[str] = None):
|
|
920
|
+
"""
|
|
921
|
+
Apply LoRA to specified modules in a model.
|
|
922
|
+
target_modules: list of module name patterns (e.g., ["q_proj", "v_proj"])
|
|
923
|
+
"""
|
|
924
|
+
if target_modules is None:
|
|
925
|
+
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
926
|
+
|
|
927
|
+
lora_params = 0
|
|
928
|
+
frozen_params = 0
|
|
929
|
+
|
|
930
|
+
for name, module in model.named_modules():
|
|
931
|
+
if isinstance(module, nn.Linear):
|
|
932
|
+
if any(t in name for t in target_modules):
|
|
933
|
+
# Replace with LoRA version
|
|
934
|
+
parent_name = ".".join(name.split(".")[:-1])
|
|
935
|
+
child_name = name.split(".")[-1]
|
|
936
|
+
parent = dict(model.named_modules())[parent_name]
|
|
937
|
+
|
|
938
|
+
lora_layer = LoRALinear(module, rank=rank, alpha=alpha)
|
|
939
|
+
setattr(parent, child_name, lora_layer)
|
|
940
|
+
|
|
941
|
+
lora_params += rank * (module.in_features + module.out_features)
|
|
942
|
+
frozen_params += module.in_features * module.out_features
|
|
943
|
+
|
|
944
|
+
total = lora_params + frozen_params
|
|
945
|
+
print(f"LoRA params: {lora_params:>12,} ({lora_params/total*100:.2f}%)")
|
|
946
|
+
print(f"Frozen params: {frozen_params:>12,} ({frozen_params/total*100:.2f}%)")
|
|
947
|
+
return model
|
|
948
|
+
|
|
949
|
+
# --- Ví dụ: Apply LoRA to a toy transformer ---
|
|
950
|
+
# model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
|
|
951
|
+
# model = apply_lora_to_model(model, rank=16, alpha=32)
|
|
952
|
+
# Output:
|
|
953
|
+
# LoRA params: 8,388,608 (0.39%)
|
|
954
|
+
# Frozen params: 2,147,483,648 (99.61%)
|
|
955
|
+
</code></pre>
|
|
956
|
+
|
|
957
|
+
<blockquote><p><strong>Exam tip:</strong> Câu hỏi rất hay gặp: "LoRA initialization — how is $B$ initialized?" → <strong>$B$ khởi tạo bằng zeros</strong>, $A$ khởi tạo random. Điều này đảm bảo $\Delta W = BA = 0$ lúc bắt đầu training, nên model xuất phát từ pretrained performance.</p></blockquote>
|
|
958
|
+
|
|
959
|
+
<p><strong>Q4:</strong> A transformer layer has weight matrix $W \in \mathbb{R}^{4096 \times 4096}$. Using LoRA with rank $r=8$, how many trainable parameters does the LoRA adapter add for this single layer?</p>
|
|
960
|
+
|
|
961
|
+
<ul>
|
|
962
|
+
<li>A) 8,192</li>
|
|
963
|
+
<li>B) 32,768</li>
|
|
964
|
+
<li>C) 65,536</li>
|
|
965
|
+
<li>D) 16,777,216</li>
|
|
966
|
+
</ul>
|
|
967
|
+
|
|
968
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
969
|
+
<p><strong>C) 65,536</strong> ✓</p>
|
|
970
|
+
<p><em>LoRA params = $d \times r + r \times k = 4096 \times 8 + 8 \times 4096 = 32768 + 32768 = 65536$. So sánh: original matrix có $4096 \times 4096 = 16{,}777{,}216$ params. LoRA chỉ dùng $65536 / 16777216 = 0.39\%$ parameters. Đáp án D là full matrix size, A quên một nửa, B chỉ tính 1 matrix.</em></p>
|
|
971
|
+
</details>
|
|
972
|
+
|
|
973
|
+
<h2 id="6-qlora-memory-efficient">6. QLoRA & Memory-Efficient Fine-tuning</h2>
|
|
974
|
+
|
|
975
|
+
<h3 id="6-1-qlora-overview">6.1. QLoRA — Quantized LoRA</h3>
|
|
976
|
+
|
|
977
|
+
<p><strong>QLoRA</strong> (Dettmers et al., 2023) kết hợp <strong>4-bit quantization</strong> cho frozen weights + <strong>LoRA adapters</strong> ở FP16/BF16. Ba kỹ thuật chính:</p>
|
|
978
|
+
|
|
979
|
+
<ul>
|
|
980
|
+
<li><strong>NF4 (4-bit NormalFloat)</strong> — quantization format tối ưu cho weight distributions theo dạng normal</li>
|
|
981
|
+
<li><strong>Double Quantization</strong> — quantize cả quantization constants → tiết kiệm thêm ~0.37 bit/param</li>
|
|
982
|
+
<li><strong>Paged Optimizers</strong> — khi GPU memory hết, tự động offload optimizer states sang CPU RAM</li>
|
|
983
|
+
</ul>
|
|
984
|
+
|
|
985
|
+
<h3 id="6-2-vram-comparison">6.2. VRAM Comparison</h3>
|
|
986
|
+
|
|
987
|
+
<table>
|
|
988
|
+
<thead>
|
|
989
|
+
<tr><th>Model</th><th>Full FT (FP16)</th><th>LoRA (FP16 base)</th><th>QLoRA (NF4 base)</th><th>Consumer GPU?</th></tr>
|
|
990
|
+
</thead>
|
|
991
|
+
<tbody>
|
|
992
|
+
<tr><td>Llama-3-8B</td><td>~32 GB</td><td>~18 GB</td><td><strong>~6 GB</strong></td><td>✓ RTX 3090/4090</td></tr>
|
|
993
|
+
<tr><td>Llama-3-13B</td><td>~52 GB</td><td>~28 GB</td><td><strong>~10 GB</strong></td><td>✓ RTX 4090 24GB</td></tr>
|
|
994
|
+
<tr><td>Llama-3-70B</td><td>~280 GB</td><td>~160 GB</td><td><strong>~36 GB</strong></td><td>✗ Need A100 80GB</td></tr>
|
|
995
|
+
<tr><td>Llama-3-405B</td><td>~1.6 TB</td><td>~900 GB</td><td><strong>~200 GB</strong></td><td>✗ Multi-A100/H100</td></tr>
|
|
996
|
+
</tbody>
|
|
997
|
+
</table>
|
|
998
|
+
|
|
999
|
+
<h3 id="6-3-when-to-use">6.3. Decision Guide: Full FT vs LoRA vs QLoRA</h3>
|
|
1000
|
+
|
|
1001
|
+
<pre><code class="language-text">
|
|
1002
|
+
Decision Tree: Which Fine-tuning Method?
|
|
1003
|
+
══════════════════════════════════════════════════════════════
|
|
1004
|
+
|
|
1005
|
+
Start: "I want to fine-tune an LLM"
|
|
1006
|
+
│
|
|
1007
|
+
├─ Q: Do you have MANY GPUs + large dataset (>100K samples)?
|
|
1008
|
+
│ ├─ YES → Full Fine-tuning (best quality, most expensive)
|
|
1009
|
+
│ └─ NO ↓
|
|
1010
|
+
│
|
|
1011
|
+
├─ Q: Does your base model fit in FP16 on your GPU?
|
|
1012
|
+
│ ├─ YES → Standard LoRA
|
|
1013
|
+
│ │ • rank 16-32
|
|
1014
|
+
│ │ • target: q_proj, v_proj (minimum)
|
|
1015
|
+
│ │ • α = r or 2r
|
|
1016
|
+
│ └─ NO ↓
|
|
1017
|
+
│
|
|
1018
|
+
├─ Q: Does your model fit in 4-bit on your GPU?
|
|
1019
|
+
│ ├─ YES → QLoRA (4-bit NF4)
|
|
1020
|
+
│ │ • Same LoRA config
|
|
1021
|
+
│ │ • Add: load_in_4bit=True
|
|
1022
|
+
│ │ • Add: bnb_4bit_quant_type="nf4"
|
|
1023
|
+
│ │ • ~3-4x memory savings
|
|
1024
|
+
│ └─ NO → Need more GPUs or smaller model
|
|
1025
|
+
│
|
|
1026
|
+
└─ Special cases:
|
|
1027
|
+
• Very simple task (format change) → Prompt tuning
|
|
1028
|
+
• Need zero latency overhead → LoRA + merge weights
|
|
1029
|
+
• Multi-tenant serving → LoRA adapters (swap per user)
|
|
1030
|
+
</code></pre>
|
|
1031
|
+
|
|
1032
|
+
<h3 id="6-4-qlora-code">6.4. QLoRA Setup với bitsandbytes + PEFT</h3>
|
|
1033
|
+
|
|
1034
|
+
<pre><code class="language-python">
|
|
1035
|
+
import torch
|
|
1036
|
+
from transformers import (
|
|
1037
|
+
AutoModelForCausalLM,
|
|
1038
|
+
AutoTokenizer,
|
|
1039
|
+
BitsAndBytesConfig,
|
|
1040
|
+
TrainingArguments,
|
|
1041
|
+
)
|
|
1042
|
+
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
|
1043
|
+
from trl import SFTTrainer
|
|
1044
|
+
|
|
1045
|
+
# --- Step 1: 4-bit Quantization Config ---
|
|
1046
|
+
bnb_config = BitsAndBytesConfig(
|
|
1047
|
+
load_in_4bit=True,
|
|
1048
|
+
bnb_4bit_quant_type="nf4", # NormalFloat4
|
|
1049
|
+
bnb_4bit_compute_dtype=torch.bfloat16, # Compute in BF16
|
|
1050
|
+
bnb_4bit_use_double_quant=True, # Double quantization
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
# --- Step 2: Load Model in 4-bit ---
|
|
1054
|
+
model_name = "meta-llama/Meta-Llama-3-8B-Instruct"
|
|
1055
|
+
model = AutoModelForCausalLM.from_pretrained(
|
|
1056
|
+
model_name,
|
|
1057
|
+
quantization_config=bnb_config,
|
|
1058
|
+
device_map="auto",
|
|
1059
|
+
trust_remote_code=True,
|
|
1060
|
+
)
|
|
1061
|
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
1062
|
+
tokenizer.pad_token = tokenizer.eos_token
|
|
1063
|
+
|
|
1064
|
+
# Prepare model for k-bit training (freeze, cast, enable gradient checkpointing)
|
|
1065
|
+
model = prepare_model_for_kbit_training(model)
|
|
1066
|
+
|
|
1067
|
+
# --- Step 3: LoRA Config ---
|
|
1068
|
+
lora_config = LoraConfig(
|
|
1069
|
+
r=16, # Rank
|
|
1070
|
+
lora_alpha=32, # Alpha (= 2*r)
|
|
1071
|
+
target_modules=[ # Which layers to adapt
|
|
1072
|
+
"q_proj", "k_proj",
|
|
1073
|
+
"v_proj", "o_proj",
|
|
1074
|
+
"gate_proj", "up_proj", "down_proj", # MLP layers too
|
|
1075
|
+
],
|
|
1076
|
+
lora_dropout=0.05, # Dropout for regularization
|
|
1077
|
+
bias="none", # Don't train biases
|
|
1078
|
+
task_type="CAUSAL_LM",
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
# Apply LoRA
|
|
1082
|
+
model = get_peft_model(model, lora_config)
|
|
1083
|
+
model.print_trainable_parameters()
|
|
1084
|
+
# Output: trainable params: 41,943,040 || all params: 8,030,261,248
|
|
1085
|
+
# || trainable%: 0.5223%
|
|
1086
|
+
|
|
1087
|
+
# --- Step 4: Training ---
|
|
1088
|
+
training_args = TrainingArguments(
|
|
1089
|
+
output_dir="./lora-finetuned",
|
|
1090
|
+
num_train_epochs=3,
|
|
1091
|
+
per_device_train_batch_size=4,
|
|
1092
|
+
gradient_accumulation_steps=4,
|
|
1093
|
+
learning_rate=2e-4,
|
|
1094
|
+
weight_decay=0.01,
|
|
1095
|
+
warmup_ratio=0.03,
|
|
1096
|
+
lr_scheduler_type="cosine",
|
|
1097
|
+
logging_steps=10,
|
|
1098
|
+
save_strategy="epoch",
|
|
1099
|
+
bf16=True, # Use BF16 mixed precision
|
|
1100
|
+
gradient_checkpointing=True, # Save memory
|
|
1101
|
+
optim="paged_adamw_32bit", # Paged optimizer
|
|
1102
|
+
report_to="mlflow", # Track in MLflow
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
trainer = SFTTrainer(
|
|
1106
|
+
model=model,
|
|
1107
|
+
args=training_args,
|
|
1108
|
+
train_dataset=train_dataset, # Your prepared dataset
|
|
1109
|
+
tokenizer=tokenizer,
|
|
1110
|
+
max_seq_length=2048,
|
|
1111
|
+
dataset_text_field="text",
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
# Launch training!
|
|
1115
|
+
trainer.train()
|
|
1116
|
+
|
|
1117
|
+
# --- Step 5: Save LoRA adapter (only ~80MB, not full model) ---
|
|
1118
|
+
trainer.model.save_pretrained("./lora-adapter-legal-qa")
|
|
1119
|
+
</code></pre>
|
|
1120
|
+
|
|
1121
|
+
<blockquote><p><strong>Exam tip:</strong> DLI assessment thường hỏi: "What makes QLoRA more memory-efficient than LoRA?" → Ba yếu tố: <strong>(1) NF4 quantization</strong> giảm base model từ 16-bit xuống 4-bit, <strong>(2) Double quantization</strong>, <strong>(3) Paged optimizers</strong>. LoRA adapters vẫn ở FP16/BF16 — chỉ frozen weights bị quantize.</p></blockquote>
|
|
1122
|
+
|
|
1123
|
+
<h2 id="7-nemo-customizer">7. Hands-on Fine-tuning với NeMo Customizer</h2>
|
|
1124
|
+
|
|
1125
|
+
<h3 id="7-1-nemo-customizer-overview">7.1. NeMo Customizer Microservice</h3>
|
|
1126
|
+
|
|
1127
|
+
<p><strong>NeMo Customizer</strong> là microservice trong NVIDIA NeMo stack cho phép launch fine-tuning jobs (LoRA, P-tuning, full SFT) trên NIM models thông qua REST API. Không cần viết training loop — chỉ cần config và data.</p>
|
|
1128
|
+
|
|
1129
|
+
<pre><code class="language-text">
|
|
1130
|
+
NeMo Customizer — Fine-tuning Pipeline
|
|
1131
|
+
══════════════════════════════════════════════════════════════
|
|
1132
|
+
|
|
1133
|
+
┌──────────────────┐ ┌──────────────────┐
|
|
1134
|
+
│ Training Data │ │ Base Model │
|
|
1135
|
+
│ (JSONL format) │ │ (via NIM) │
|
|
1136
|
+
│ │ │ Llama-3-8B-Inst │
|
|
1137
|
+
└────────┬─────────┘ └────────┬─────────┘
|
|
1138
|
+
│ │
|
|
1139
|
+
▼ ▼
|
|
1140
|
+
┌────────────────────────────────────────────┐
|
|
1141
|
+
│ NeMo Customizer Service │
|
|
1142
|
+
│ │
|
|
1143
|
+
│ POST /v1/customization/jobs │
|
|
1144
|
+
│ { │
|
|
1145
|
+
│ "model": "meta/llama-3.1-8b-instruct",│
|
|
1146
|
+
│ "training_type": "lora", │
|
|
1147
|
+
│ "dataset": "/data/train.jsonl", │
|
|
1148
|
+
│ "hyperparameters": { │
|
|
1149
|
+
│ "epochs": 3, "lr": 2e-4, │
|
|
1150
|
+
│ "lora_rank": 16 │
|
|
1151
|
+
│ } │
|
|
1152
|
+
│ } │
|
|
1153
|
+
└─────────────────────┬──────────────────────┘
|
|
1154
|
+
│
|
|
1155
|
+
Job Status: RUNNING → COMPLETED
|
|
1156
|
+
│
|
|
1157
|
+
▼
|
|
1158
|
+
┌────────────────────────────────────────────┐
|
|
1159
|
+
│ Output: LoRA Adapter Weights │
|
|
1160
|
+
│ → Mount into NIM for inference │
|
|
1161
|
+
│ → Run NeMo Evaluator to validate │
|
|
1162
|
+
│ → Compare with base model in MLflow │
|
|
1163
|
+
└────────────────────────────────────────────┘
|
|
1164
|
+
</code></pre>
|
|
1165
|
+
|
|
1166
|
+
<h3 id="7-2-dataset-preparation">7.2. Dataset Preparation</h3>
|
|
1167
|
+
|
|
1168
|
+
<pre><code class="language-python">
|
|
1169
|
+
import json
|
|
1170
|
+
|
|
1171
|
+
def prepare_sft_dataset(raw_data: list[dict],
|
|
1172
|
+
output_path: str,
|
|
1173
|
+
system_prompt: str = None):
|
|
1174
|
+
"""
|
|
1175
|
+
Format data cho NeMo Customizer SFT/LoRA training.
|
|
1176
|
+
Input format: [{"question": "...", "answer": "..."}]
|
|
1177
|
+
Output: JSONL với conversation format.
|
|
1178
|
+
"""
|
|
1179
|
+
formatted = []
|
|
1180
|
+
for item in raw_data:
|
|
1181
|
+
conversation = {"messages": []}
|
|
1182
|
+
|
|
1183
|
+
if system_prompt:
|
|
1184
|
+
conversation["messages"].append({
|
|
1185
|
+
"role": "system",
|
|
1186
|
+
"content": system_prompt
|
|
1187
|
+
})
|
|
1188
|
+
|
|
1189
|
+
conversation["messages"].append({
|
|
1190
|
+
"role": "user",
|
|
1191
|
+
"content": item["question"]
|
|
1192
|
+
})
|
|
1193
|
+
conversation["messages"].append({
|
|
1194
|
+
"role": "assistant",
|
|
1195
|
+
"content": item["answer"]
|
|
1196
|
+
})
|
|
1197
|
+
|
|
1198
|
+
formatted.append(conversation)
|
|
1199
|
+
|
|
1200
|
+
# Shuffle và split train/val (90/10)
|
|
1201
|
+
import random
|
|
1202
|
+
random.shuffle(formatted)
|
|
1203
|
+
split_idx = int(len(formatted) * 0.9)
|
|
1204
|
+
train_data = formatted[:split_idx]
|
|
1205
|
+
val_data = formatted[split_idx:]
|
|
1206
|
+
|
|
1207
|
+
# Write JSONL
|
|
1208
|
+
for suffix, data in [("train", train_data), ("val", val_data)]:
|
|
1209
|
+
path = output_path.replace(".jsonl", f"_{suffix}.jsonl")
|
|
1210
|
+
with open(path, "w") as f:
|
|
1211
|
+
for item in data:
|
|
1212
|
+
f.write(json.dumps(item, ensure_ascii=False) + "\n")
|
|
1213
|
+
print(f"Wrote {len(data)} examples to {path}")
|
|
1214
|
+
|
|
1215
|
+
return len(train_data), len(val_data)
|
|
1216
|
+
|
|
1217
|
+
# --- Ví dụ ---
|
|
1218
|
+
raw = [
|
|
1219
|
+
{"question": "Thuốc Metformin chỉ định cho bệnh gì?",
|
|
1220
|
+
"answer": "Metformin là thuốc first-line cho diabetes type 2..."},
|
|
1221
|
+
# ... thêm 5000+ examples
|
|
1222
|
+
]
|
|
1223
|
+
prepare_sft_dataset(raw, "legal_qa.jsonl",
|
|
1224
|
+
system_prompt="Bạn là trợ lý y tế chuyên nghiệp. "
|
|
1225
|
+
"Trả lời chính xác dựa trên evidence-based medicine.")
|
|
1226
|
+
</code></pre>
|
|
1227
|
+
|
|
1228
|
+
<h3 id="7-3-launch-training">7.3. Launch Training Job via API</h3>
|
|
1229
|
+
|
|
1230
|
+
<pre><code class="language-python">
|
|
1231
|
+
import requests
|
|
1232
|
+
|
|
1233
|
+
CUSTOMIZER_URL = "http://nemo-customizer:8080"
|
|
1234
|
+
|
|
1235
|
+
def launch_lora_job(model_name: str,
|
|
1236
|
+
train_file: str,
|
|
1237
|
+
val_file: str,
|
|
1238
|
+
config: dict) -> str:
|
|
1239
|
+
"""
|
|
1240
|
+
Launch LoRA fine-tuning job trên NeMo Customizer.
|
|
1241
|
+
Returns: job_id
|
|
1242
|
+
"""
|
|
1243
|
+
payload = {
|
|
1244
|
+
"model": model_name,
|
|
1245
|
+
"training_type": "lora",
|
|
1246
|
+
"dataset": {
|
|
1247
|
+
"train": train_file,
|
|
1248
|
+
"validation": val_file,
|
|
1249
|
+
},
|
|
1250
|
+
"hyperparameters": {
|
|
1251
|
+
"epochs": config.get("epochs", 3),
|
|
1252
|
+
"learning_rate": config.get("lr", 2e-4),
|
|
1253
|
+
"batch_size": config.get("batch_size", 4),
|
|
1254
|
+
"lora_rank": config.get("rank", 16),
|
|
1255
|
+
"lora_alpha": config.get("alpha", 32),
|
|
1256
|
+
"lora_target_modules": config.get(
|
|
1257
|
+
"target_modules",
|
|
1258
|
+
["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
1259
|
+
),
|
|
1260
|
+
},
|
|
1261
|
+
"output_model": f"lora-{model_name.split('/')[-1]}-custom",
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
response = requests.post(
|
|
1265
|
+
f"{CUSTOMIZER_URL}/v1/customization/jobs",
|
|
1266
|
+
json=payload
|
|
1267
|
+
)
|
|
1268
|
+
response.raise_for_status()
|
|
1269
|
+
job_id = response.json()["id"]
|
|
1270
|
+
print(f"Job launched: {job_id}")
|
|
1271
|
+
return job_id
|
|
1272
|
+
|
|
1273
|
+
def check_job_status(job_id: str) -> dict:
|
|
1274
|
+
"""Kiểm tra trạng thái training job."""
|
|
1275
|
+
resp = requests.get(
|
|
1276
|
+
f"{CUSTOMIZER_URL}/v1/customization/jobs/{job_id}"
|
|
1277
|
+
)
|
|
1278
|
+
result = resp.json()
|
|
1279
|
+
print(f"Status: {result['status']}, "
|
|
1280
|
+
f"Progress: {result.get('progress', 'N/A')}")
|
|
1281
|
+
return result
|
|
1282
|
+
|
|
1283
|
+
# --- Usage ---
|
|
1284
|
+
# job_id = launch_lora_job(
|
|
1285
|
+
# model_name="meta/llama-3.1-8b-instruct",
|
|
1286
|
+
# train_file="/data/legal_qa_train.jsonl",
|
|
1287
|
+
# val_file="/data/legal_qa_val.jsonl",
|
|
1288
|
+
# config={"epochs": 3, "rank": 16, "lr": 2e-4}
|
|
1289
|
+
# )
|
|
1290
|
+
# status = check_job_status(job_id)
|
|
1291
|
+
</code></pre>
|
|
1292
|
+
|
|
1293
|
+
<h3 id="7-4-inference-with-adapter">7.4. Inference với LoRA Adapter</h3>
|
|
1294
|
+
|
|
1295
|
+
<pre><code class="language-python">
|
|
1296
|
+
from peft import PeftModel
|
|
1297
|
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
1298
|
+
|
|
1299
|
+
def load_model_with_lora(base_model_name: str,
|
|
1300
|
+
adapter_path: str):
|
|
1301
|
+
"""Load base model + mount LoRA adapter."""
|
|
1302
|
+
# Load base
|
|
1303
|
+
base_model = AutoModelForCausalLM.from_pretrained(
|
|
1304
|
+
base_model_name,
|
|
1305
|
+
device_map="auto",
|
|
1306
|
+
torch_dtype=torch.bfloat16,
|
|
1307
|
+
)
|
|
1308
|
+
tokenizer = AutoTokenizer.from_pretrained(base_model_name)
|
|
1309
|
+
|
|
1310
|
+
# Mount LoRA adapter
|
|
1311
|
+
model = PeftModel.from_pretrained(base_model, adapter_path)
|
|
1312
|
+
|
|
1313
|
+
# Optional: merge adapter into base for faster inference
|
|
1314
|
+
# model = model.merge_and_unload()
|
|
1315
|
+
|
|
1316
|
+
return model, tokenizer
|
|
1317
|
+
|
|
1318
|
+
def compare_base_vs_finetuned(question: str,
|
|
1319
|
+
base_model, base_tok,
|
|
1320
|
+
ft_model, ft_tok):
|
|
1321
|
+
"""So sánh output của base model vs fine-tuned."""
|
|
1322
|
+
prompt = f"<|user|>\n{question}\n<|assistant|>\n"
|
|
1323
|
+
|
|
1324
|
+
for label, model, tok in [
|
|
1325
|
+
("BASE", base_model, base_tok),
|
|
1326
|
+
("LoRA", ft_model, ft_tok),
|
|
1327
|
+
]:
|
|
1328
|
+
inputs = tok(prompt, return_tensors="pt").to(model.device)
|
|
1329
|
+
outputs = model.generate(
|
|
1330
|
+
**inputs, max_new_tokens=256,
|
|
1331
|
+
temperature=0.1, do_sample=True
|
|
1332
|
+
)
|
|
1333
|
+
response = tok.decode(outputs[0], skip_special_tokens=True)
|
|
1334
|
+
print(f"\n{'='*50}")
|
|
1335
|
+
print(f"[{label}] {response}")
|
|
1336
|
+
</code></pre>
|
|
1337
|
+
|
|
1338
|
+
<p><strong>Q5:</strong> A team fine-tunes Llama-3-8B using NeMo Customizer with LoRA (rank=16). The resulting adapter file is approximately 80 MB. The original model is 16 GB. For production deployment, what is the MOST efficient serving approach?</p>
|
|
1339
|
+
|
|
1340
|
+
<ul>
|
|
1341
|
+
<li>A) Deploy the full 16 GB fine-tuned model separately</li>
|
|
1342
|
+
<li>B) Merge LoRA weights into base model and deploy 16 GB merged model</li>
|
|
1343
|
+
<li>C) Deploy base model once via NIM and mount LoRA adapter at inference time</li>
|
|
1344
|
+
<li>D) Convert to ONNX format for faster inference</li>
|
|
1345
|
+
</ul>
|
|
1346
|
+
|
|
1347
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1348
|
+
<p><strong>C) Deploy base model once via NIM and mount LoRA adapter at inference time</strong> ✓</p>
|
|
1349
|
+
<p><em>NIM supports LoRA adapter hot-loading — 1 base model phục vụ nhiều customers/domains khác nhau chỉ bằng cách swap adapter (~80MB). Cách B hoạt động nhưng tốn storage và không multi-tenant. Cách A không tận dụng LoRA advantage. ONNX conversion là optimization riêng, không liên quan đến adapter serving.</em></p>
|
|
1350
|
+
</details>
|
|
1351
|
+
|
|
1352
|
+
<h2 id="8-final-strategy-cheatsheet">8. Final Assessment Strategy & Cheat Sheet</h2>
|
|
1353
|
+
|
|
1354
|
+
<h3 id="8-1-assessment-format">8.1. Assessment Format Recap</h3>
|
|
1355
|
+
|
|
1356
|
+
<p>NVIDIA DLI Generative AI certification bao gồm nhiều course assessments:</p>
|
|
1357
|
+
|
|
1358
|
+
<table>
|
|
1359
|
+
<thead>
|
|
1360
|
+
<tr><th>Course Code</th><th>Topic</th><th>Format</th><th>Thời gian</th><th>Pass</th></tr>
|
|
1361
|
+
</thead>
|
|
1362
|
+
<tbody>
|
|
1363
|
+
<tr><td><strong>S-FX-14</strong></td><td>Generative AI with Diffusion Models</td><td>Coding assessment</td><td>~2 hours</td><td>70%</td></tr>
|
|
1364
|
+
<tr><td><strong>S-FX-15</strong></td><td>Building RAG Agents with LLMs</td><td>Coding + MCQ</td><td>~2 hours</td><td>70%</td></tr>
|
|
1365
|
+
<tr><td><strong>S-FX-34</strong></td><td>Build an AI Agent Reasoning App</td><td>Coding assessment</td><td>~2 hours</td><td>70%</td></tr>
|
|
1366
|
+
<tr><td><strong>C-FX-25</strong></td><td>GenAI LLM Customization & Eval</td><td>Coding + MCQ</td><td>~2 hours</td><td>70%</td></tr>
|
|
1367
|
+
</tbody>
|
|
1368
|
+
</table>
|
|
1369
|
+
|
|
1370
|
+
<h3 id="8-2-common-mistakes">8.2. Common Mistakes & How to Avoid Them</h3>
|
|
1371
|
+
|
|
1372
|
+
<ul>
|
|
1373
|
+
<li><strong>Sai dimension tensor</strong>: Luôn kiểm tra shape bằng <code>tensor.shape</code> trước khi matmul</li>
|
|
1374
|
+
<li><strong>Quên freeze weights</strong>: LoRA phải freeze base model — nếu không sẽ full fine-tuning</li>
|
|
1375
|
+
<li><strong>Nhầm BLEU và ROUGE</strong>: BLEU = precision, ROUGE = recall</li>
|
|
1376
|
+
<li><strong>Thiếu brevity penalty</strong>: BLEU không chỉ là n-gram match, phải có BP</li>
|
|
1377
|
+
<li><strong>Sai CFG formula</strong>: $\epsilon_\theta = \epsilon_{uncond} + s \cdot (\epsilon_{cond} - \epsilon_{uncond})$, chú ý thứ tự trừ</li>
|
|
1378
|
+
<li><strong>Top-k vs Top-p</strong>: top-k chọn k tokens có probability cao nhất, top-p chọn tokens đến khi cumulative probability đạt p</li>
|
|
1379
|
+
</ul>
|
|
1380
|
+
|
|
1381
|
+
<h3 id="8-3-time-management">8.3. Time Management Strategy</h3>
|
|
1382
|
+
|
|
1383
|
+
<ol>
|
|
1384
|
+
<li><strong>Đọc toàn bộ đề trước</strong> (5 phút) — xác định bài dễ/khó</li>
|
|
1385
|
+
<li><strong>Làm bài code trước</strong> — thường chiếm nhiều điểm hơn MCQ</li>
|
|
1386
|
+
<li><strong>MCQ: eliminate trước</strong> — loại 2 đáp án sai rõ ràng, chọn giữa 2 còn lại</li>
|
|
1387
|
+
<li><strong>Không stuck quá 10 phút</strong> cho 1 câu — đánh dấu và quay lại sau</li>
|
|
1388
|
+
<li><strong>Dành 10 phút cuối</strong> review code (syntax errors, import missing)</li>
|
|
1389
|
+
</ol>
|
|
1390
|
+
|
|
1391
|
+
<h3 id="8-4-cheat-sheet">8.4. Quick Reference Cheat Sheet</h3>
|
|
1392
|
+
|
|
1393
|
+
<table>
|
|
1394
|
+
<thead>
|
|
1395
|
+
<tr><th>Category</th><th>Formula / Pattern</th><th>Ghi nhớ</th></tr>
|
|
1396
|
+
</thead>
|
|
1397
|
+
<tbody>
|
|
1398
|
+
<tr><td><strong>Diffusion — Forward</strong></td><td>$q(x_t|x_0) = \mathcal{N}(\sqrt{\bar\alpha_t}\,x_0,\;(1-\bar\alpha_t)\,I)$</td><td>Thêm noise theo schedule</td></tr>
|
|
1399
|
+
<tr><td><strong>Diffusion — Reverse</strong></td><td>$p_\theta(x_{t-1}|x_t) = \mathcal{N}(\mu_\theta(x_t,t),\;\sigma_t^2 I)$</td><td>U-Net predict noise $\epsilon$</td></tr>
|
|
1400
|
+
<tr><td><strong>CFG</strong></td><td>$\hat\epsilon = \epsilon_{uncond} + s(\epsilon_{cond} - \epsilon_{uncond})$</td><td>$s=7.5$ typical, $s=1$ = no guidance</td></tr>
|
|
1401
|
+
<tr><td><strong>LoRA</strong></td><td>$W' = W + \frac{\alpha}{r}BA$</td><td>B=zeros init, A=random init</td></tr>
|
|
1402
|
+
<tr><td><strong>LoRA params</strong></td><td>$r(d+k)$ per layer</td><td>Typically 0.1-1% of total</td></tr>
|
|
1403
|
+
<tr><td><strong>BLEU</strong></td><td>$BP \cdot \exp(\sum w_n \log p_n)$</td><td>Precision-based, for translation</td></tr>
|
|
1404
|
+
<tr><td><strong>F1</strong></td><td>$\frac{2PR}{P+R}$</td><td>Token overlap, for QA</td></tr>
|
|
1405
|
+
<tr><td><strong>Cosine Sim</strong></td><td>$\frac{a \cdot b}{\|a\|\|b\|}$</td><td>Embedding-based semantic match</td></tr>
|
|
1406
|
+
<tr><td><strong>ELO</strong></td><td>$R' = R + K(S - E)$</td><td>K=32 typical, initial=1500</td></tr>
|
|
1407
|
+
<tr><td><strong>Attention</strong></td><td>$\text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V$</td><td>Scale prevents softmax saturation</td></tr>
|
|
1408
|
+
<tr><td><strong>Cross-Entropy</strong></td><td>$-\sum y_i \log(\hat{y}_i)$</td><td>LLM training loss function</td></tr>
|
|
1409
|
+
<tr><td><strong>Perplexity</strong></td><td>$2^{H(p)} = e^{\text{CE loss}}$</td><td>Lower = better language model</td></tr>
|
|
1410
|
+
</tbody>
|
|
1411
|
+
</table>
|
|
1412
|
+
|
|
1413
|
+
<h3 id="8-5-pytorch-reference">8.5. PyTorch Quick Reference</h3>
|
|
1414
|
+
|
|
1415
|
+
<pre><code class="language-python">
|
|
1416
|
+
import torch
|
|
1417
|
+
import torch.nn as nn
|
|
1418
|
+
import torch.nn.functional as F
|
|
1419
|
+
|
|
1420
|
+
# --- Tensor Operations (hay gặp trong assessment) ---
|
|
1421
|
+
x = torch.randn(2, 3, 4) # shape: (batch, seq, dim)
|
|
1422
|
+
y = torch.randn(2, 4, 5) # shape: (batch, dim, out)
|
|
1423
|
+
z = torch.bmm(x, y) # batch matmul → (2, 3, 5)
|
|
1424
|
+
z = x @ y # same as bmm for 3D
|
|
1425
|
+
z = torch.einsum('bsd,bdo->bso', x, y) # Einstein notation
|
|
1426
|
+
|
|
1427
|
+
# Reshape operations
|
|
1428
|
+
x = x.view(2, -1) # Flatten last 2 dims → (2, 12)
|
|
1429
|
+
x = x.unsqueeze(1) # Add dim → (2, 1, 12)
|
|
1430
|
+
x = x.squeeze(1) # Remove dim → (2, 12)
|
|
1431
|
+
x = x.permute(0, 2, 1) # Swap dims
|
|
1432
|
+
|
|
1433
|
+
# Softmax + temperature
|
|
1434
|
+
logits = torch.randn(1, 50257) # vocab logits
|
|
1435
|
+
temp = 0.7
|
|
1436
|
+
probs = F.softmax(logits / temp, dim=-1)
|
|
1437
|
+
|
|
1438
|
+
# Top-k sampling
|
|
1439
|
+
top_k = 50
|
|
1440
|
+
top_k_vals, top_k_idx = torch.topk(probs, top_k)
|
|
1441
|
+
sampled = torch.multinomial(top_k_vals, 1)
|
|
1442
|
+
|
|
1443
|
+
# Top-p (nucleus) sampling
|
|
1444
|
+
sorted_probs, sorted_idx = torch.sort(probs, descending=True)
|
|
1445
|
+
cumsum = torch.cumsum(sorted_probs, dim=-1)
|
|
1446
|
+
mask = cumsum - sorted_probs > 0.9 # p=0.9
|
|
1447
|
+
sorted_probs[mask] = 0.0
|
|
1448
|
+
sorted_probs /= sorted_probs.sum()
|
|
1449
|
+
sampled = torch.multinomial(sorted_probs, 1)
|
|
1450
|
+
|
|
1451
|
+
# Loss functions
|
|
1452
|
+
loss_fn = nn.CrossEntropyLoss()
|
|
1453
|
+
loss = loss_fn(logits, targets) # logits: (B, V), targets: (B,)
|
|
1454
|
+
</code></pre>
|
|
1455
|
+
|
|
1456
|
+
<h3 id="8-6-langchain-patterns">8.6. LangChain / LangGraph Patterns Reference</h3>
|
|
1457
|
+
|
|
1458
|
+
<pre><code class="language-python">
|
|
1459
|
+
# --- RAG Pattern ---
|
|
1460
|
+
# 1. Load → 2. Split → 3. Embed → 4. Store → 5. Retrieve → 6. Generate
|
|
1461
|
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
|
1462
|
+
splitter = RecursiveCharacterTextSplitter(
|
|
1463
|
+
chunk_size=512, chunk_overlap=50
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
# --- Structured Output ---
|
|
1467
|
+
from pydantic import BaseModel
|
|
1468
|
+
class Answer(BaseModel):
|
|
1469
|
+
reasoning: str
|
|
1470
|
+
answer: str
|
|
1471
|
+
confidence: float
|
|
1472
|
+
|
|
1473
|
+
chain = prompt | llm.with_structured_output(Answer)
|
|
1474
|
+
|
|
1475
|
+
# --- LangGraph State Machine ---
|
|
1476
|
+
from langgraph.graph import StateGraph, START, END
|
|
1477
|
+
graph = StateGraph(MyState)
|
|
1478
|
+
graph.add_node("agent", agent_fn)
|
|
1479
|
+
graph.add_node("tools", tool_fn)
|
|
1480
|
+
graph.add_edge(START, "agent")
|
|
1481
|
+
graph.add_conditional_edges("agent", should_continue,
|
|
1482
|
+
{"continue": "tools", "end": END})
|
|
1483
|
+
graph.add_edge("tools", "agent")
|
|
1484
|
+
app = graph.compile()
|
|
1485
|
+
</code></pre>
|
|
1486
|
+
|
|
1487
|
+
<h3 id="8-7-frequency-table">8.7. Top Concepts by Frequency on DLI Assessments</h3>
|
|
1488
|
+
|
|
1489
|
+
<table>
|
|
1490
|
+
<thead>
|
|
1491
|
+
<tr><th>#</th><th>Concept</th><th>Frequency</th><th>Typical Question Type</th></tr>
|
|
1492
|
+
</thead>
|
|
1493
|
+
<tbody>
|
|
1494
|
+
<tr><td>1</td><td>Diffusion forward/reverse process</td><td>★★★★★</td><td>Fill in code, explain formula</td></tr>
|
|
1495
|
+
<tr><td>2</td><td>LoRA rank, alpha, parameter count</td><td>★★★★★</td><td>Calculate params, choose config</td></tr>
|
|
1496
|
+
<tr><td>3</td><td>RAG chunking strategy</td><td>★★★★☆</td><td>Choose chunk_size for use case</td></tr>
|
|
1497
|
+
<tr><td>4</td><td>BLEU vs ROUGE vs F1</td><td>★★★★☆</td><td>Match metric to task</td></tr>
|
|
1498
|
+
<tr><td>5</td><td>Classifier-Free Guidance</td><td>★★★★☆</td><td>Code CFG formula, choose scale</td></tr>
|
|
1499
|
+
<tr><td>6</td><td>LangChain LCEL chains</td><td>★★★☆☆</td><td>Build pipeline with | operator</td></tr>
|
|
1500
|
+
<tr><td>7</td><td>Attention mechanism</td><td>★★★☆☆</td><td>Implement scaled dot-product</td></tr>
|
|
1501
|
+
<tr><td>8</td><td>NIM deployment</td><td>★★★☆☆</td><td>Docker compose, API config</td></tr>
|
|
1502
|
+
<tr><td>9</td><td>Guardrails / NeMo Guardrails</td><td>★★☆☆☆</td><td>Config topical rails</td></tr>
|
|
1503
|
+
<tr><td>10</td><td>Multi-agent patterns</td><td>★★☆☆☆</td><td>Choose supervisor vs swarm</td></tr>
|
|
1504
|
+
</tbody>
|
|
1505
|
+
</table>
|
|
1506
|
+
|
|
1507
|
+
<blockquote><p><strong>Exam tip:</strong> Tập trung revision vào <strong>top 5 concepts</strong> — chúng chiếm ~70% câu hỏi. Diffusion formulas và LoRA xuất hiện nhiều nhất. Luôn nhớ: $B$ initialized zeros trong LoRA, CFG guidance scale $s$ tăng → output adherent hơn với prompt.</p></blockquote>
|
|
1508
|
+
|
|
1509
|
+
<h2 id="9-mock-assessment">9. Practice Questions — Full Mock Assessment</h2>
|
|
1510
|
+
|
|
1511
|
+
<p>15 câu hỏi thi thử bao quát toàn bộ 10 bài trong series. Thời gian khuyến nghị: <strong>45 phút</strong>.</p>
|
|
1512
|
+
|
|
1513
|
+
<h3 id="9-diffusion">Diffusion Models (Q1–Q4)</h3>
|
|
1514
|
+
|
|
1515
|
+
<p><strong>Q1 🟢 (Easy):</strong> In a Denoising Diffusion Probabilistic Model (DDPM), during the forward process, noise is added to an image $x_0$ over $T$ timesteps. Which statement is TRUE about the forward process?</p>
|
|
1516
|
+
|
|
1517
|
+
<ul>
|
|
1518
|
+
<li>A) The forward process requires a neural network to learn the noise schedule</li>
|
|
1519
|
+
<li>B) At timestep $T$, $x_T$ approximates a standard Gaussian distribution $\mathcal{N}(0, I)$</li>
|
|
1520
|
+
<li>C) The forward process removes noise gradually from the image</li>
|
|
1521
|
+
<li>D) Each step in the forward process is a learned transformation</li>
|
|
1522
|
+
</ul>
|
|
1523
|
+
|
|
1524
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1525
|
+
<p><strong>B</strong> ✓</p>
|
|
1526
|
+
<p><em>Forward process là quá trình thêm noise theo schedule cố định (không cần neural network → A sai, D sai). Reverse process mới là bước khử noise → C sai. Tại $T$ đủ lớn, $x_T \sim \mathcal{N}(0, I)$ vì $\bar\alpha_T \to 0$.</em></p>
|
|
1527
|
+
</details>
|
|
1528
|
+
|
|
1529
|
+
<p><strong>Q2 🟡 (Medium):</strong> Given the following Classifier-Free Guidance code, what is the output when <code>guidance_scale = 1.0</code>?</p>
|
|
1530
|
+
|
|
1531
|
+
<pre><code class="language-python">
|
|
1532
|
+
def cfg_predict(model, x_t, t, text_emb, guidance_scale):
|
|
1533
|
+
noise_cond = model(x_t, t, text_emb)
|
|
1534
|
+
noise_uncond = model(x_t, t, null_emb)
|
|
1535
|
+
return noise_uncond + guidance_scale * (noise_cond - noise_uncond)
|
|
1536
|
+
</code></pre>
|
|
1537
|
+
|
|
1538
|
+
<ul>
|
|
1539
|
+
<li>A) Pure unconditional generation (ignores text prompt)</li>
|
|
1540
|
+
<li>B) Standard conditional generation (equivalent to no CFG)</li>
|
|
1541
|
+
<li>C) Double-strength guidance toward text prompt</li>
|
|
1542
|
+
<li>D) The function will raise an error</li>
|
|
1543
|
+
</ul>
|
|
1544
|
+
|
|
1545
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1546
|
+
<p><strong>B</strong> ✓</p>
|
|
1547
|
+
<p><em>Khi $s=1.0$: $\epsilon_{uncond} + 1.0 \times (\epsilon_{cond} - \epsilon_{uncond}) = \epsilon_{cond}$. Đây chính là output conditional bình thường, không có guidance amplification. $s=0$ → pure unconditional (A). $s>1$ (e.g., 7.5) → amplified guidance. $s=2$ → double strength (C).</em></p>
|
|
1548
|
+
</details>
|
|
1549
|
+
|
|
1550
|
+
<p><strong>Q3 🟡 (Medium):</strong> A U-Net architecture used in diffusion models has an encoder path and a decoder path. What is the PRIMARY purpose of skip connections between corresponding encoder and decoder layers?</p>
|
|
1551
|
+
|
|
1552
|
+
<ul>
|
|
1553
|
+
<li>A) To reduce the total number of parameters in the model</li>
|
|
1554
|
+
<li>B) To preserve fine-grained spatial details that may be lost during downsampling</li>
|
|
1555
|
+
<li>C) To implement the noise schedule during the forward process</li>
|
|
1556
|
+
<li>D) To enable text-conditional generation through cross-attention</li>
|
|
1557
|
+
</ul>
|
|
1558
|
+
|
|
1559
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1560
|
+
<p><strong>B</strong> ✓</p>
|
|
1561
|
+
<p><em>Skip connections nối encoder layer (high resolution) với decoder layer tương ứng, giúp decoder recover spatial details bị mất khi downsampling. Cross-attention (D) là mechanism riêng cho text conditioning. Skip connections không giảm params (A) hay implement noise schedule (C).</em></p>
|
|
1562
|
+
</details>
|
|
1563
|
+
|
|
1564
|
+
<p><strong>Q4 🔴 (Hard):</strong> A team uses CLIP to encode both text and images into a shared embedding space. The CLIP loss function during training is:</p>
|
|
1565
|
+
|
|
1566
|
+
<pre><code class="language-python">
|
|
1567
|
+
# Simplified CLIP contrastive loss
|
|
1568
|
+
logits = (image_embeds @ text_embeds.T) * temperature
|
|
1569
|
+
labels = torch.arange(len(logits))
|
|
1570
|
+
loss_i2t = F.cross_entropy(logits, labels)
|
|
1571
|
+
loss_t2i = F.cross_entropy(logits.T, labels)
|
|
1572
|
+
loss = (loss_i2t + loss_t2i) / 2
|
|
1573
|
+
</code></pre>
|
|
1574
|
+
|
|
1575
|
+
<p>What is the purpose of computing BOTH <code>loss_i2t</code> AND <code>loss_t2i</code>?</p>
|
|
1576
|
+
|
|
1577
|
+
<ul>
|
|
1578
|
+
<li>A) To ensure the model learns both image generation and text generation</li>
|
|
1579
|
+
<li>B) To make the alignment symmetric — image→text matching AND text→image matching</li>
|
|
1580
|
+
<li>C) To implement data augmentation by swapping modalities</li>
|
|
1581
|
+
<li>D) To handle cases where batch sizes differ between images and text</li>
|
|
1582
|
+
</ul>
|
|
1583
|
+
|
|
1584
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1585
|
+
<p><strong>B</strong> ✓</p>
|
|
1586
|
+
<p><em>CLIP dùng symmetric contrastive loss: loss_i2t đảm bảo mỗi image match đúng text (image→text), loss_t2i đảm bảo mỗi text match đúng image (text→image). Hai chiều này cần thiết vì similarity matrix không nhất thiết symmetric về gradient flow. CLIP không generate image hay text (A), không augment (C), batch size luôn bằng nhau (D).</em></p>
|
|
1587
|
+
</details>
|
|
1588
|
+
|
|
1589
|
+
<h3 id="9-rag">RAG & LLM Applications (Q5–Q8)</h3>
|
|
1590
|
+
|
|
1591
|
+
<p><strong>Q5 🟢 (Easy):</strong> In a RAG pipeline, documents are split into chunks before embedding. A team processes legal contracts with complex cross-references between sections. Which chunking strategy is MOST appropriate?</p>
|
|
1592
|
+
|
|
1593
|
+
<ul>
|
|
1594
|
+
<li>A) Fixed-size chunks of 100 tokens with no overlap</li>
|
|
1595
|
+
<li>B) Sentence-level splitting</li>
|
|
1596
|
+
<li>C) Recursive character splitting with 512 tokens and 50 token overlap</li>
|
|
1597
|
+
<li>D) Single chunk per document (no splitting)</li>
|
|
1598
|
+
</ul>
|
|
1599
|
+
|
|
1600
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1601
|
+
<p><strong>C</strong> ✓</p>
|
|
1602
|
+
<p><em>Legal contracts có cross-references nên cần overlap để không mất context tại boundary. Fixed 100 tokens quá nhỏ, không đủ context (A). Sentence-level quá nhỏ cho legal documents (B). Single chunk quá lớn, vượt context window và embedding model limit (D). Recursive splitting 512 + overlap 50 giữ semantic coherence.</em></p>
|
|
1603
|
+
</details>
|
|
1604
|
+
|
|
1605
|
+
<p><strong>Q6 🟡 (Medium):</strong> A RAG system retrieves the following top-3 chunks for the query "What is the treatment for Type 2 diabetes?":</p>
|
|
1606
|
+
|
|
1607
|
+
<pre><code class="language-text">
|
|
1608
|
+
Chunk 1: "Metformin is the first-line treatment for Type 2 diabetes..."
|
|
1609
|
+
Chunk 2: "Type 1 diabetes requires insulin injections from diagnosis..."
|
|
1610
|
+
Chunk 3: "Lifestyle modifications including diet and exercise are recommended
|
|
1611
|
+
alongside pharmacological treatment for Type 2 diabetes..."
|
|
1612
|
+
</code></pre>
|
|
1613
|
+
|
|
1614
|
+
<p>Which RAG evaluation metric would BEST detect that Chunk 2 is irrelevant?</p>
|
|
1615
|
+
|
|
1616
|
+
<ul>
|
|
1617
|
+
<li>A) Answer F1 score</li>
|
|
1618
|
+
<li>B) Context Relevance (measures if retrieved chunks are relevant to query)</li>
|
|
1619
|
+
<li>C) Faithfulness (measures if answer is grounded in context)</li>
|
|
1620
|
+
<li>D) BLEU score between query and chunks</li>
|
|
1621
|
+
</ul>
|
|
1622
|
+
|
|
1623
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1624
|
+
<p><strong>B</strong> ✓</p>
|
|
1625
|
+
<p><em>Context Relevance đo xem retrieved chunks có thực sự liên quan đến query không — sẽ phát hiện Chunk 2 nói về Type 1 (không phải Type 2). Faithfulness đo answer vs context (C). Answer F1 đo final answer vs ground truth (A). BLEU cho keyword overlap quá nông (D).</em></p>
|
|
1626
|
+
</details>
|
|
1627
|
+
|
|
1628
|
+
<p><strong>Q7 🟡 (Medium):</strong> A developer builds a LangChain chain with the following LCEL expression:</p>
|
|
1629
|
+
|
|
1630
|
+
<pre><code class="language-python">
|
|
1631
|
+
chain = (
|
|
1632
|
+
{"context": retriever, "question": RunnablePassthrough()}
|
|
1633
|
+
| prompt_template
|
|
1634
|
+
| llm
|
|
1635
|
+
| StrOutputParser()
|
|
1636
|
+
)
|
|
1637
|
+
result = chain.invoke("What is LoRA?")
|
|
1638
|
+
</code></pre>
|
|
1639
|
+
|
|
1640
|
+
<p>What does <code>RunnablePassthrough()</code> do in this chain?</p>
|
|
1641
|
+
|
|
1642
|
+
<ul>
|
|
1643
|
+
<li>A) It passes the input unchanged to the "question" key in the dictionary</li>
|
|
1644
|
+
<li>B) It skips the retriever step and goes directly to the LLM</li>
|
|
1645
|
+
<li>C) It caches the input for later use in the chain</li>
|
|
1646
|
+
<li>D) It converts the input to embeddings for semantic search</li>
|
|
1647
|
+
</ul>
|
|
1648
|
+
|
|
1649
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1650
|
+
<p><strong>A</strong> ✓</p>
|
|
1651
|
+
<p><em>RunnablePassthrough() nhận input ("What is LoRA?") và truyền nguyên vẹn sang key "question". Trong khi đó, "context" key chạy retriever trên cùng input. Kết quả: prompt_template nhận dict {"context": retrieved_docs, "question": "What is LoRA?"}. Không skip steps (B), không cache (C), không embed (D).</em></p>
|
|
1652
|
+
</details>
|
|
1653
|
+
|
|
1654
|
+
<p><strong>Q8 🔴 (Hard):</strong> NeMo Guardrails is used to prevent a customer service chatbot from discussing competitors. The following Colang config is provided:</p>
|
|
1655
|
+
|
|
1656
|
+
<pre><code class="language-text">
|
|
1657
|
+
define user ask about competitor
|
|
1658
|
+
"What do you think about ProductX?"
|
|
1659
|
+
"Is CompetitorY better than your product?"
|
|
1660
|
+
"Compare your product with CompetitorZ"
|
|
1661
|
+
|
|
1662
|
+
define flow
|
|
1663
|
+
user ask about competitor
|
|
1664
|
+
bot refuse competitor question
|
|
1665
|
+
bot offer alternative help
|
|
1666
|
+
|
|
1667
|
+
define bot refuse competitor question
|
|
1668
|
+
"I'm focused on helping you with our products. I can't compare with other brands."
|
|
1669
|
+
|
|
1670
|
+
define bot offer alternative help
|
|
1671
|
+
"Would you like me to help you find the right product from our range?"
|
|
1672
|
+
</code></pre>
|
|
1673
|
+
|
|
1674
|
+
<p>A user writes: "I heard CompetitorY has faster delivery. Can you match that?" The guardrail does NOT trigger. Which is the MOST likely reason?</p>
|
|
1675
|
+
|
|
1676
|
+
<ul>
|
|
1677
|
+
<li>A) The Colang flow syntax has an error</li>
|
|
1678
|
+
<li>B) The canonical examples don't cover "delivery comparison" intent — only direct product comparison</li>
|
|
1679
|
+
<li>C) NeMo Guardrails cannot detect entity names in user messages</li>
|
|
1680
|
+
<li>D) The bot response needs to be defined before the flow</li>
|
|
1681
|
+
</ul>
|
|
1682
|
+
|
|
1683
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1684
|
+
<p><strong>B</strong> ✓</p>
|
|
1685
|
+
<p><em>NeMo Guardrails dùng canonical examples để match user intent. Các examples chỉ cover "compare products" và "opinion about competitor" — không có example về "delivery comparison". Cần thêm example như "Does CompetitorY deliver faster?" để cover intent này. Colang syntax đúng (A sai), NeMo Guardrails detect entities fine (C sai), order định nghĩa không ảnh hưởng (D sai).</em></p>
|
|
1686
|
+
</details>
|
|
1687
|
+
|
|
1688
|
+
<h3 id="9-agentic">Agentic AI (Q9–Q11)</h3>
|
|
1689
|
+
|
|
1690
|
+
<p><strong>Q9 🟢 (Easy):</strong> In LangGraph, what is a <strong>State</strong> object used for?</p>
|
|
1691
|
+
|
|
1692
|
+
<ul>
|
|
1693
|
+
<li>A) To store LLM model weights during inference</li>
|
|
1694
|
+
<li>B) To maintain shared data (messages, context) that flows between graph nodes</li>
|
|
1695
|
+
<li>C) To define the visual layout of the graph</li>
|
|
1696
|
+
<li>D) To configure the LLM temperature and top-p parameters</li>
|
|
1697
|
+
</ul>
|
|
1698
|
+
|
|
1699
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1700
|
+
<p><strong>B</strong> ✓</p>
|
|
1701
|
+
<p><em>State trong LangGraph là TypedDict hoặc Pydantic model chứa shared data (messages, intermediate results, flags) được truyền giữa các nodes. Mỗi node nhận State, xử lý, và return updated State. Không liên quan đến model weights (A), layout (C), hay LLM config (D).</em></p>
|
|
1702
|
+
</details>
|
|
1703
|
+
|
|
1704
|
+
<p><strong>Q10 🟡 (Medium):</strong> A team builds a multi-agent system where a "Manager" agent delegates tasks to "Researcher" and "Writer" sub-agents. The Manager decides which sub-agent to call based on the current state. This is an example of which pattern?</p>
|
|
1705
|
+
|
|
1706
|
+
<ul>
|
|
1707
|
+
<li>A) Swarm pattern</li>
|
|
1708
|
+
<li>B) Hierarchical / Supervisor pattern</li>
|
|
1709
|
+
<li>C) Peer-to-peer pattern</li>
|
|
1710
|
+
<li>D) Pipeline pattern</li>
|
|
1711
|
+
</ul>
|
|
1712
|
+
|
|
1713
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1714
|
+
<p><strong>B</strong> ✓</p>
|
|
1715
|
+
<p><em>Manager → sub-agents là classic Supervisor/Hierarchical pattern: 1 central agent làm router/orchestrator. Swarm (A) = agents tự coordinate không có leader. Peer-to-peer (C) = agents ngang hàng communicate trực tiếp. Pipeline (D) = fixed sequential flow.</em></p>
|
|
1716
|
+
</details>
|
|
1717
|
+
|
|
1718
|
+
<p><strong>Q11 🔴 (Hard):</strong> The following LangGraph code defines an agent with tool calling. What happens if the <code>should_continue</code> function always returns <code>"continue"</code>?</p>
|
|
1719
|
+
|
|
1720
|
+
<pre><code class="language-python">
|
|
1721
|
+
from langgraph.graph import StateGraph, START, END
|
|
1722
|
+
|
|
1723
|
+
def should_continue(state):
|
|
1724
|
+
if state["messages"][-1].tool_calls:
|
|
1725
|
+
return "continue"
|
|
1726
|
+
return "end"
|
|
1727
|
+
|
|
1728
|
+
graph = StateGraph(AgentState)
|
|
1729
|
+
graph.add_node("agent", call_model)
|
|
1730
|
+
graph.add_node("tools", tool_node)
|
|
1731
|
+
graph.add_edge(START, "agent")
|
|
1732
|
+
graph.add_conditional_edges("agent", should_continue,
|
|
1733
|
+
{"continue": "tools", "end": END})
|
|
1734
|
+
graph.add_edge("tools", "agent")
|
|
1735
|
+
app = graph.compile()
|
|
1736
|
+
</code></pre>
|
|
1737
|
+
|
|
1738
|
+
<ul>
|
|
1739
|
+
<li>A) The graph executes once and exits cleanly</li>
|
|
1740
|
+
<li>B) The graph enters an infinite loop between "agent" and "tools" nodes</li>
|
|
1741
|
+
<li>C) The graph raises a compilation error</li>
|
|
1742
|
+
<li>D) The "tools" node handles the termination</li>
|
|
1743
|
+
</ul>
|
|
1744
|
+
|
|
1745
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1746
|
+
<p><strong>B</strong> ✓</p>
|
|
1747
|
+
<p><em>Nếu should_continue luôn trả "continue": agent → tools → agent → tools → ... không bao giờ đến END. Đây là lý do production agents cần recursion_limit (mặc định 25 trong LangGraph) hoặc explicit termination condition. Graph compile thành công (C sai), nhưng runtime bị infinite loop.</em></p>
|
|
1748
|
+
</details>
|
|
1749
|
+
|
|
1750
|
+
<h3 id="9-eval-ft">Evaluation & Fine-tuning (Q12–Q15)</h3>
|
|
1751
|
+
|
|
1752
|
+
<p><strong>Q12 🟢 (Easy):</strong> Which of the following is a recall-oriented metric commonly used for evaluating text summarization?</p>
|
|
1753
|
+
|
|
1754
|
+
<ul>
|
|
1755
|
+
<li>A) BLEU</li>
|
|
1756
|
+
<li>B) ROUGE</li>
|
|
1757
|
+
<li>C) Perplexity</li>
|
|
1758
|
+
<li>D) pass@k</li>
|
|
1759
|
+
</ul>
|
|
1760
|
+
|
|
1761
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1762
|
+
<p><strong>B</strong> ✓</p>
|
|
1763
|
+
<p><em>ROUGE (Recall-Oriented Understudy for Gisting Evaluation) — tên đã nói rõ: recall-oriented, thiết kế cho summarization. BLEU là precision-oriented cho translation (A). Perplexity đo language model quality (C). pass@k cho code generation (D).</em></p>
|
|
1764
|
+
</details>
|
|
1765
|
+
|
|
1766
|
+
<p><strong>Q13 🟡 (Medium):</strong> In LoRA fine-tuning, matrices $B \in \mathbb{R}^{d \times r}$ and $A \in \mathbb{R}^{r \times k}$ are learned. How is matrix $B$ initialized and WHY?</p>
|
|
1767
|
+
|
|
1768
|
+
<ul>
|
|
1769
|
+
<li>A) Random initialization — to break symmetry between neurons</li>
|
|
1770
|
+
<li>B) Identity matrix — to preserve the original model behavior</li>
|
|
1771
|
+
<li>C) Zeros — so that $\Delta W = BA = 0$ at training start, preserving pretrained weights</li>
|
|
1772
|
+
<li>D) Xavier initialization — to maintain gradient flow</li>
|
|
1773
|
+
</ul>
|
|
1774
|
+
|
|
1775
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1776
|
+
<p><strong>C</strong> ✓</p>
|
|
1777
|
+
<p><em>$B$ initialized = zeros, $A$ initialized = random (Kaiming). Khi training bắt đầu: $\Delta W = BA = 0 \cdot A = 0$, nên $W' = W + 0 = W$ (pretrained weights). Model bắt đầu train từ chính xác pretrained performance, không bị disrupted. Đây là design decision quan trọng của LoRA paper.</em></p>
|
|
1778
|
+
</details>
|
|
1779
|
+
|
|
1780
|
+
<p><strong>Q14 🟡 (Medium):</strong> A company fine-tunes Llama-3-70B for legal document analysis. They have a single NVIDIA A100 80GB GPU. Which approach can they use?</p>
|
|
1781
|
+
|
|
1782
|
+
<ul>
|
|
1783
|
+
<li>A) Full fine-tuning with gradient checkpointing</li>
|
|
1784
|
+
<li>B) Standard LoRA with FP16 base model</li>
|
|
1785
|
+
<li>C) QLoRA with 4-bit NF4 quantization</li>
|
|
1786
|
+
<li>D) Both B and C will work on a single A100 80GB</li>
|
|
1787
|
+
</ul>
|
|
1788
|
+
|
|
1789
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1790
|
+
<p><strong>C</strong> ✓</p>
|
|
1791
|
+
<p><em>Llama-3-70B FP16 = ~140GB cho weights alone → A100 80GB không đủ cho full FT (A sai) hay LoRA FP16 (B sai, cần ~160GB). QLoRA 4-bit: ~35GB cho weights + optimizer → vừa đủ A100 80GB. D sai vì B không fit.</em></p>
|
|
1792
|
+
</details>
|
|
1793
|
+
|
|
1794
|
+
<p><strong>Q15 🔴 (Hard):</strong> A data scientist runs NeMo Evaluator to compare a base model and a LoRA fine-tuned model on a legal QA dataset. Results:</p>
|
|
1795
|
+
|
|
1796
|
+
<table>
|
|
1797
|
+
<thead>
|
|
1798
|
+
<tr><th>Model</th><th>BLEU</th><th>ROUGE-L</th><th>F1</th><th>Judge Correctness (1-5)</th><th>Latency p95</th></tr>
|
|
1799
|
+
</thead>
|
|
1800
|
+
<tbody>
|
|
1801
|
+
<tr><td>Base Llama-3-8B</td><td>0.18</td><td>0.35</td><td>0.52</td><td>3.1</td><td>1.2s</td></tr>
|
|
1802
|
+
<tr><td>LoRA (r=16)</td><td>0.29</td><td>0.48</td><td>0.71</td><td>4.2</td><td>1.3s</td></tr>
|
|
1803
|
+
</tbody>
|
|
1804
|
+
</table>
|
|
1805
|
+
|
|
1806
|
+
<p>The team decides to deploy the LoRA model. Which statement BEST justifies this decision?</p>
|
|
1807
|
+
|
|
1808
|
+
<ul>
|
|
1809
|
+
<li>A) BLEU improved by 61%, which is the most important metric for QA</li>
|
|
1810
|
+
<li>B) All metrics improved significantly with minimal latency increase, and LLM-judge correctness rose from 3.1 to 4.2 (35% improvement)</li>
|
|
1811
|
+
<li>C) The latency increase from 1.2s to 1.3s is negligible, which is the primary concern</li>
|
|
1812
|
+
<li>D) ROUGE-L improved from 0.35 to 0.48, indicating the model generates better summaries</li>
|
|
1813
|
+
</ul>
|
|
1814
|
+
|
|
1815
|
+
<details><summary>Đáp án & Giải thích</summary>
|
|
1816
|
+
<p><strong>B</strong> ✓</p>
|
|
1817
|
+
<p><em>Quyết định deploy dựa trên holistic improvement: F1 (primary cho QA) tăng mạnh 0.52→0.71, judge correctness (closest to human eval) tăng 3.1→4.2, latency gần như không đổi. A sai vì BLEU không phải primary metric cho QA. C đúng nhưng không "justify" — latency chỉ là 1 factor. D nhầm: ROUGE cho summarization, đây là QA task.</em></p>
|
|
1818
|
+
</details>
|
|
1819
|
+
|
|
1820
|
+
<h2 id="10-tong-ket-next-steps">10. Tổng kết Series & Next Steps</h2>
|
|
1821
|
+
|
|
1822
|
+
<h3 id="10-1-hanh-trinh-10-bai">10.1. Hành trình 10 bài học</h3>
|
|
1823
|
+
|
|
1824
|
+
<p>Chúc mừng bạn đã hoàn thành series <strong>"Luyện thi NVIDIA DLI — Generative AI with Diffusion Models & LLMs"</strong>! Hãy điểm lại những gì đã học:</p>
|
|
1825
|
+
|
|
1826
|
+
<table>
|
|
1827
|
+
<thead>
|
|
1828
|
+
<tr><th>Bài</th><th>Chủ đề</th><th>Key Takeaway</th></tr>
|
|
1829
|
+
</thead>
|
|
1830
|
+
<tbody>
|
|
1831
|
+
<tr><td>1</td><td>Generative AI Overview</td><td>Taxonomy: VAE → GAN → Diffusion → Transformer LLM</td></tr>
|
|
1832
|
+
<tr><td>2</td><td>Diffusion Models & DDPM</td><td>Forward noise + Reverse denoise, U-Net predicts $\epsilon$</td></tr>
|
|
1833
|
+
<tr><td>3</td><td>Stable Diffusion & CLIP</td><td>Latent space diffusion, text conditioning via cross-attention</td></tr>
|
|
1834
|
+
<tr><td>4</td><td>LLM Foundations</td><td>Transformer attention, tokenization, generation strategies</td></tr>
|
|
1835
|
+
<tr><td>5</td><td>Prompt Engineering</td><td>Zero/few-shot, CoT, structured output, system prompts</td></tr>
|
|
1836
|
+
<tr><td>6</td><td>RAG Pipeline</td><td>Chunking → Embedding → Vector DB → Retrieval → Generation</td></tr>
|
|
1837
|
+
<tr><td>7</td><td>LangChain & NIM</td><td>LCEL chains, NIM deployment, guardrails</td></tr>
|
|
1838
|
+
<tr><td>8</td><td>Tool Calling & Structured Output</td><td>Function calling, Pydantic schemas, ReAct loop</td></tr>
|
|
1839
|
+
<tr><td>9</td><td>Agentic AI & Multi-Agent</td><td>LangGraph, supervisor pattern, state machines</td></tr>
|
|
1840
|
+
<tr><td>10</td><td>Evaluation & LoRA Fine-tuning</td><td>BLEU/ROUGE/F1, LLM-as-Judge, LoRA/QLoRA, NeMo</td></tr>
|
|
1841
|
+
</tbody>
|
|
1842
|
+
</table>
|
|
1843
|
+
|
|
1844
|
+
<h3 id="10-2-course-order">10.2. Recommended DLI Course Order</h3>
|
|
1845
|
+
|
|
1846
|
+
<p>Để lấy certification, hoàn thành các DLI courses theo thứ tự sau:</p>
|
|
1847
|
+
|
|
1848
|
+
<ol>
|
|
1849
|
+
<li><strong>Generative AI with Diffusion Models</strong> (S-FX-14) — diffusion theory + coding lab</li>
|
|
1850
|
+
<li><strong>Building RAG Agents with LLMs</strong> (S-FX-15) — RAG pipeline + LangChain + NIM</li>
|
|
1851
|
+
<li><strong>Build an AI Agent Reasoning App</strong> (S-FX-34) — agentic AI + LangGraph</li>
|
|
1852
|
+
<li><strong>GenAI and LLM Customization and Evaluation</strong> (C-FX-25) — LoRA, QLoRA, NeMo Evaluator</li>
|
|
1853
|
+
</ol>
|
|
1854
|
+
|
|
1855
|
+
<p>Mỗi course có assessment riêng. Hoàn thành cả 4 → đạt <strong>NVIDIA DLI Generative AI Certificate</strong>.</p>
|
|
1856
|
+
|
|
1857
|
+
<h3 id="10-3-tips-after-cert">10.3. Tips cho Continuing Learning</h3>
|
|
1858
|
+
|
|
1859
|
+
<ul>
|
|
1860
|
+
<li><strong>Practice trên Kaggle / HuggingFace</strong> — fine-tune models thật trên dataset thật</li>
|
|
1861
|
+
<li><strong>Đọc papers</strong> — LoRA, QLoRA, RAG, DDPM papers đều accessible và chuẩn bị tốt cho assessment</li>
|
|
1862
|
+
<li><strong>Build projects</strong> — RAG chatbot, multi-agent system, custom fine-tuned model cho domain cụ thể</li>
|
|
1863
|
+
<li><strong>Join community</strong> — NVIDIA Developer Forums, HuggingFace Discord, LangChain Discord</li>
|
|
1864
|
+
<li><strong>Stay updated</strong> — AI thay đổi rất nhanh, theo dõi NVIDIA Blog, arXiv daily papers</li>
|
|
1865
|
+
</ul>
|
|
1866
|
+
|
|
1867
|
+
<blockquote><p><strong>Final tip:</strong> Khi thi, nhớ rằng DLI assessments thiên về <strong>practical application</strong> hơn là theory thuần túy. Nếu bạn hiểu code và có thể implement từ scratch (như các ví dụ trong series này), bạn sẽ pass. Chúc bạn thi tốt! 🎯</p></blockquote>
|