@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,984 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 019c9619-nv01-p1-l02
|
|
3
|
+
title: 'Bài 2: Transformer Architecture & Attention Mechanism'
|
|
4
|
+
slug: bai-2-transformer-architecture-attention
|
|
5
|
+
description: >-
|
|
6
|
+
Self-attention, multi-head attention, positional encoding.
|
|
7
|
+
Encoder-decoder architecture. BERT, GPT, T5 model families.
|
|
8
|
+
Tokenization: BPE, WordPiece, SentencePiece.
|
|
9
|
+
NLP tasks: classification, NER, QA, summarization.
|
|
10
|
+
duration_minutes: 90
|
|
11
|
+
is_free: true
|
|
12
|
+
video_url: null
|
|
13
|
+
sort_order: 2
|
|
14
|
+
section_title: "Part 1: Deep Learning Foundations"
|
|
15
|
+
course:
|
|
16
|
+
id: 019c9619-nv01-7001-c001-nv0100000001
|
|
17
|
+
title: 'Luyện thi NVIDIA DLI — Generative AI with Diffusion Models & LLMs'
|
|
18
|
+
slug: luyen-thi-nvidia-dli-generative-ai
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
<h2 id="gioi-thieu">1. Giới thiệu</h2>
|
|
22
|
+
|
|
23
|
+
<p><strong>Transformer</strong> là kiến trúc nền tảng đằng sau mọi mô hình Generative AI hiện đại — từ <strong>GPT</strong>, <strong>BERT</strong>, <strong>Stable Diffusion</strong> đến <strong>LLaMA</strong>. Trong bài assessment của NVIDIA DLI, bạn phải hiểu rõ cách <strong>Attention Mechanism</strong> hoạt động và có khả năng implement nó bằng PyTorch.</p>
|
|
24
|
+
|
|
25
|
+
<p>Bài học này sẽ đi từ <strong>Scaled Dot-Product Attention</strong> đến toàn bộ kiến trúc <strong>Transformer</strong>, sau đó mapping sang các model families và NLP tasks cụ thể.</p>
|
|
26
|
+
|
|
27
|
+
<blockquote><p><strong>Exam tip:</strong> NVIDIA DLI assessment thường yêu cầu bạn hoàn thành code cho attention mechanism hoặc debug lỗi dimension mismatch trong Transformer. Hãy nắm vững <strong>tensor shapes</strong> qua từng bước của attention — đây là chìa khóa để pass assessment.</p></blockquote>
|
|
28
|
+
|
|
29
|
+
<figure><img src="/storage/uploads/2026/04/nvidia-dli-bai2-transformer-architecture.png" alt="Kiến trúc Transformer — Encoder-Decoder, Self-Attention, Cross-Attention" loading="lazy" /><figcaption>Kiến trúc Transformer — Encoder-Decoder, Self-Attention, Cross-Attention</figcaption></figure>
|
|
30
|
+
|
|
31
|
+
<h2 id="attention-mechanism">2. Attention Mechanism</h2>
|
|
32
|
+
|
|
33
|
+
<h3 id="attention-intuition">2.1 Trực giác — "Phần nào quan trọng nhất?"</h3>
|
|
34
|
+
|
|
35
|
+
<p><strong>Attention</strong> trả lời câu hỏi: "Khi xử lý token hiện tại, những token nào trong input sequence là quan trọng nhất?" Thay vì nén toàn bộ sequence vào một fixed-size vector như RNN, Attention cho phép mô hình "nhìn" trực tiếp vào mọi vị trí của input.</p>
|
|
36
|
+
|
|
37
|
+
<p>Cơ chế hoạt động qua 3 thành phần:</p>
|
|
38
|
+
|
|
39
|
+
<ul>
|
|
40
|
+
<li><strong>Query (Q)</strong> — "Tôi đang tìm gì?" — token hiện tại đặt câu hỏi</li>
|
|
41
|
+
<li><strong>Key (K)</strong> — "Tôi chứa thông tin gì?" — mỗi token quảng cáo nội dung của mình</li>
|
|
42
|
+
<li><strong>Value (V)</strong> — "Đây là thông tin thực sự" — nội dung được trả về khi match</li>
|
|
43
|
+
</ul>
|
|
44
|
+
|
|
45
|
+
<pre><code class="language-text">Ví dụ: "The cat sat on the mat because it was tired"
|
|
46
|
+
↑
|
|
47
|
+
Token "it" (Query)
|
|
48
|
+
│
|
|
49
|
+
┌─────────────────────────────┤
|
|
50
|
+
│ Attention scores: │
|
|
51
|
+
│ "cat" = 0.72 ← cao! │
|
|
52
|
+
│ "mat" = 0.11 │
|
|
53
|
+
│ "sat" = 0.08 │
|
|
54
|
+
│ "The" = 0.03 │
|
|
55
|
+
│ ... │
|
|
56
|
+
└─────────────────────────────┘
|
|
57
|
+
→ "it" attend chủ yếu vào "cat"
|
|
58
|
+
</code></pre>
|
|
59
|
+
|
|
60
|
+
<h3 id="scaled-dot-product">2.2 Scaled Dot-Product Attention</h3>
|
|
61
|
+
|
|
62
|
+
<p>Công thức chính của attention:</p>
|
|
63
|
+
|
|
64
|
+
<pre><code class="language-text">Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V
|
|
65
|
+
|
|
66
|
+
Trong đó:
|
|
67
|
+
Q: Query matrix — shape (seq_len, d_k)
|
|
68
|
+
K: Key matrix — shape (seq_len, d_k)
|
|
69
|
+
V: Value matrix — shape (seq_len, d_v)
|
|
70
|
+
d_k: dimension của Key vectors
|
|
71
|
+
√d_k: scaling factor để tránh gradient vanishing trong softmax
|
|
72
|
+
</code></pre>
|
|
73
|
+
|
|
74
|
+
<p>Tại sao cần <strong>scaling</strong> bằng √d_k? Khi d_k lớn, dot product Q·K^T có thể rất lớn, khiến softmax bão hòa → gradient gần bằng 0. Chia cho √d_k giữ variance ổn định.</p>
|
|
75
|
+
|
|
76
|
+
<pre><code class="language-text">Flow của Scaled Dot-Product Attention:
|
|
77
|
+
|
|
78
|
+
Q ──┐
|
|
79
|
+
│──→ MatMul ──→ Scale (÷√d_k) ──→ Mask (opt.) ──→ Softmax ──→ MatMul ──→ Output
|
|
80
|
+
K ──┘ ↑
|
|
81
|
+
│
|
|
82
|
+
V ────────────────────────────────────────────────────────────────────┘
|
|
83
|
+
|
|
84
|
+
Shapes (batch_size=B, seq_len=S, d_k=D):
|
|
85
|
+
Q: (B, S, D)
|
|
86
|
+
K^T: (B, D, S)
|
|
87
|
+
Q·K^T: (B, S, S) ← attention score matrix
|
|
88
|
+
softmax: (B, S, S) ← attention weights (mỗi hàng sum = 1)
|
|
89
|
+
× V: (B, S, D) ← weighted output
|
|
90
|
+
</code></pre>
|
|
91
|
+
|
|
92
|
+
<h3 id="attention-code">2.3 Code: Implement Attention từ Scratch</h3>
|
|
93
|
+
|
|
94
|
+
<pre><code class="language-python">import torch
|
|
95
|
+
import torch.nn.functional as F
|
|
96
|
+
import math
|
|
97
|
+
|
|
98
|
+
def scaled_dot_product_attention(Q, K, V, mask=None):
|
|
99
|
+
"""
|
|
100
|
+
Q: (batch, seq_len, d_k)
|
|
101
|
+
K: (batch, seq_len, d_k)
|
|
102
|
+
V: (batch, seq_len, d_v)
|
|
103
|
+
mask: (batch, 1, seq_len) or (batch, seq_len, seq_len)
|
|
104
|
+
"""
|
|
105
|
+
d_k = Q.size(-1)
|
|
106
|
+
|
|
107
|
+
# Step 1: Tính attention scores
|
|
108
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
|
|
109
|
+
# scores shape: (batch, seq_len, seq_len)
|
|
110
|
+
|
|
111
|
+
# Step 2: Apply mask (cho causal attention trong decoder)
|
|
112
|
+
if mask is not None:
|
|
113
|
+
scores = scores.masked_fill(mask == 0, float('-inf'))
|
|
114
|
+
|
|
115
|
+
# Step 3: Softmax để có attention weights
|
|
116
|
+
attn_weights = F.softmax(scores, dim=-1)
|
|
117
|
+
# attn_weights shape: (batch, seq_len, seq_len)
|
|
118
|
+
|
|
119
|
+
# Step 4: Weighted sum of values
|
|
120
|
+
output = torch.matmul(attn_weights, V)
|
|
121
|
+
# output shape: (batch, seq_len, d_v)
|
|
122
|
+
|
|
123
|
+
return output, attn_weights
|
|
124
|
+
|
|
125
|
+
# Demo
|
|
126
|
+
batch_size, seq_len, d_k = 2, 5, 64
|
|
127
|
+
Q = torch.randn(batch_size, seq_len, d_k)
|
|
128
|
+
K = torch.randn(batch_size, seq_len, d_k)
|
|
129
|
+
V = torch.randn(batch_size, seq_len, d_k)
|
|
130
|
+
|
|
131
|
+
output, weights = scaled_dot_product_attention(Q, K, V)
|
|
132
|
+
print(f"Output shape: {output.shape}") # (2, 5, 64)
|
|
133
|
+
print(f"Weights shape: {weights.shape}") # (2, 5, 5)
|
|
134
|
+
print(f"Weights sum per row: {weights.sum(dim=-1)}") # mỗi hàng = 1.0
|
|
135
|
+
</code></pre>
|
|
136
|
+
|
|
137
|
+
<blockquote><p><strong>Exam tip:</strong> Lỗi phổ biến nhất khi implement attention: quên <code>K.transpose(-2, -1)</code> hoặc chia sai dimension. Hãy luôn kiểm tra shape sau mỗi bước — <code>scores</code> phải có shape <code>(batch, seq_len, seq_len)</code>.</p></blockquote>
|
|
138
|
+
|
|
139
|
+
<h2 id="multi-head-attention">3. Multi-Head Attention</h2>
|
|
140
|
+
|
|
141
|
+
<h3 id="why-multi-head">3.1 Tại sao cần nhiều heads?</h3>
|
|
142
|
+
|
|
143
|
+
<p>Một head duy nhất chỉ học được <strong>một loại relationship</strong>. Multi-Head Attention cho phép mô hình attend đồng thời vào nhiều <strong>representation subspaces</strong> khác nhau:</p>
|
|
144
|
+
|
|
145
|
+
<ul>
|
|
146
|
+
<li>Head 1: học syntactic relationships (subject-verb)</li>
|
|
147
|
+
<li>Head 2: học coreference (pronoun → noun)</li>
|
|
148
|
+
<li>Head 3: học positional proximity</li>
|
|
149
|
+
<li>Head 4: học semantic similarity</li>
|
|
150
|
+
</ul>
|
|
151
|
+
|
|
152
|
+
<pre><code class="language-text">Multi-Head Attention Flow:
|
|
153
|
+
|
|
154
|
+
Input (batch, seq_len, d_model)
|
|
155
|
+
│
|
|
156
|
+
├── Linear → Q ──┐
|
|
157
|
+
├── Linear → K ──┼── Split thành h heads
|
|
158
|
+
└── Linear → V ──┘
|
|
159
|
+
│
|
|
160
|
+
┌─────────────┼─────────────┐
|
|
161
|
+
▼ ▼ ▼
|
|
162
|
+
Head 1 Head 2 ... Head h
|
|
163
|
+
Attention() Attention() Attention()
|
|
164
|
+
│ │ │
|
|
165
|
+
└─────────────┼─────────────┘
|
|
166
|
+
│
|
|
167
|
+
Concatenate
|
|
168
|
+
│
|
|
169
|
+
Linear
|
|
170
|
+
│
|
|
171
|
+
Output (batch, seq_len, d_model)
|
|
172
|
+
|
|
173
|
+
Shapes (d_model=512, h=8, d_k = d_model/h = 64):
|
|
174
|
+
Input: (B, S, 512)
|
|
175
|
+
Per-head Q/K/V: (B, h, S, 64) ← reshape sau linear
|
|
176
|
+
Per-head out: (B, h, S, 64)
|
|
177
|
+
Concat: (B, S, 512) ← h × d_k = d_model
|
|
178
|
+
Final output: (B, S, 512)
|
|
179
|
+
</code></pre>
|
|
180
|
+
|
|
181
|
+
<h3 id="multi-head-code">3.2 Code: MultiHeadAttention Module</h3>
|
|
182
|
+
|
|
183
|
+
<pre><code class="language-python">import torch
|
|
184
|
+
import torch.nn as nn
|
|
185
|
+
import math
|
|
186
|
+
|
|
187
|
+
class MultiHeadAttention(nn.Module):
|
|
188
|
+
def __init__(self, d_model, num_heads):
|
|
189
|
+
super().__init__()
|
|
190
|
+
assert d_model % num_heads == 0, "d_model phải chia hết cho num_heads"
|
|
191
|
+
|
|
192
|
+
self.d_model = d_model
|
|
193
|
+
self.num_heads = num_heads
|
|
194
|
+
self.d_k = d_model // num_heads
|
|
195
|
+
|
|
196
|
+
# Linear projections cho Q, K, V và output
|
|
197
|
+
self.W_q = nn.Linear(d_model, d_model)
|
|
198
|
+
self.W_k = nn.Linear(d_model, d_model)
|
|
199
|
+
self.W_v = nn.Linear(d_model, d_model)
|
|
200
|
+
self.W_o = nn.Linear(d_model, d_model)
|
|
201
|
+
|
|
202
|
+
def forward(self, query, key, value, mask=None):
|
|
203
|
+
batch_size = query.size(0)
|
|
204
|
+
|
|
205
|
+
# Step 1: Linear projections
|
|
206
|
+
Q = self.W_q(query) # (B, S, d_model)
|
|
207
|
+
K = self.W_k(key)
|
|
208
|
+
V = self.W_v(value)
|
|
209
|
+
|
|
210
|
+
# Step 2: Reshape thành multi-head format
|
|
211
|
+
# (B, S, d_model) → (B, S, h, d_k) → (B, h, S, d_k)
|
|
212
|
+
Q = Q.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
213
|
+
K = K.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
214
|
+
V = V.view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
215
|
+
|
|
216
|
+
# Step 3: Scaled dot-product attention cho mỗi head
|
|
217
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
|
|
218
|
+
|
|
219
|
+
if mask is not None:
|
|
220
|
+
scores = scores.masked_fill(mask == 0, float('-inf'))
|
|
221
|
+
|
|
222
|
+
attn_weights = torch.softmax(scores, dim=-1)
|
|
223
|
+
context = torch.matmul(attn_weights, V)
|
|
224
|
+
# context: (B, h, S, d_k)
|
|
225
|
+
|
|
226
|
+
# Step 4: Concatenate heads
|
|
227
|
+
# (B, h, S, d_k) → (B, S, h, d_k) → (B, S, d_model)
|
|
228
|
+
context = context.transpose(1, 2).contiguous().view(
|
|
229
|
+
batch_size, -1, self.d_model
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Step 5: Final linear projection
|
|
233
|
+
output = self.W_o(context)
|
|
234
|
+
return output
|
|
235
|
+
|
|
236
|
+
# Demo
|
|
237
|
+
d_model, num_heads = 512, 8
|
|
238
|
+
mha = MultiHeadAttention(d_model, num_heads)
|
|
239
|
+
|
|
240
|
+
x = torch.randn(2, 10, d_model) # batch=2, seq_len=10
|
|
241
|
+
output = mha(x, x, x) # self-attention: Q=K=V=x
|
|
242
|
+
print(f"Output shape: {output.shape}") # (2, 10, 512)
|
|
243
|
+
</code></pre>
|
|
244
|
+
|
|
245
|
+
<table>
|
|
246
|
+
<thead>
|
|
247
|
+
<tr><th>Parameter</th><th>Giá trị thường gặp</th><th>Ghi chú</th></tr>
|
|
248
|
+
</thead>
|
|
249
|
+
<tbody>
|
|
250
|
+
<tr><td>d_model</td><td>512, 768, 1024</td><td>Kích thước embedding</td></tr>
|
|
251
|
+
<tr><td>num_heads</td><td>8, 12, 16</td><td>d_model phải chia hết cho num_heads</td></tr>
|
|
252
|
+
<tr><td>d_k = d_model / h</td><td>64, 64, 64</td><td>Mỗi head thường có d_k = 64</td></tr>
|
|
253
|
+
<tr><td>Total params (MHA)</td><td>4 × d_model²</td><td>4 linear layers: W_q, W_k, W_v, W_o</td></tr>
|
|
254
|
+
</tbody>
|
|
255
|
+
</table>
|
|
256
|
+
|
|
257
|
+
<blockquote><p><strong>Exam tip:</strong> Trong DLI assessment, nếu bạn gặp lỗi <code>RuntimeError: shape mismatch</code> ở attention, hãy kiểm tra: (1) <code>d_model % num_heads == 0</code>, (2) <code>view()</code> và <code>transpose()</code> đúng thứ tự, (3) nhớ gọi <code>.contiguous()</code> trước <code>.view()</code> sau transpose.</p></blockquote>
|
|
258
|
+
|
|
259
|
+
<h2 id="transformer-architecture">4. Transformer Architecture</h2>
|
|
260
|
+
|
|
261
|
+
<h3 id="encoder-block">4.1 Encoder Block</h3>
|
|
262
|
+
|
|
263
|
+
<p>Mỗi layer trong <strong>Transformer Encoder</strong> gồm 2 sub-layers với <strong>residual connections</strong> và <strong>layer normalization</strong>:</p>
|
|
264
|
+
|
|
265
|
+
<pre><code class="language-text">Transformer Encoder Block:
|
|
266
|
+
|
|
267
|
+
Input
|
|
268
|
+
│
|
|
269
|
+
▼
|
|
270
|
+
┌─────────────────────────────┐
|
|
271
|
+
│ Multi-Head Self-Attention │
|
|
272
|
+
└──────────────┬──────────────┘
|
|
273
|
+
│
|
|
274
|
+
┌──────────┴──────────┐
|
|
275
|
+
│ Add & Norm │ ← x + Sublayer(x), rồi LayerNorm
|
|
276
|
+
└──────────┬──────────┘
|
|
277
|
+
│
|
|
278
|
+
┌──────────┴──────────┐
|
|
279
|
+
│ Feed-Forward Net │ ← 2 linear layers + ReLU/GELU
|
|
280
|
+
│ FFN(x) = W₂·σ(W₁x + b₁) + b₂
|
|
281
|
+
└──────────┬──────────┘
|
|
282
|
+
│
|
|
283
|
+
┌──────────┴──────────┐
|
|
284
|
+
│ Add & Norm │
|
|
285
|
+
└──────────┬──────────┘
|
|
286
|
+
│
|
|
287
|
+
Output
|
|
288
|
+
</code></pre>
|
|
289
|
+
|
|
290
|
+
<h3 id="decoder-block">4.2 Decoder Block</h3>
|
|
291
|
+
|
|
292
|
+
<p><strong>Decoder</strong> có thêm một sub-layer <strong>Cross-Attention</strong> — attend vào output của encoder:</p>
|
|
293
|
+
|
|
294
|
+
<pre><code class="language-text">Transformer Decoder Block:
|
|
295
|
+
|
|
296
|
+
Input (shifted right)
|
|
297
|
+
│
|
|
298
|
+
▼
|
|
299
|
+
┌────────────────────────────────┐
|
|
300
|
+
│ Masked Multi-Head Attention │ ← causal mask: chỉ nhìn tokens trước
|
|
301
|
+
└──────────────┬─────────────────┘
|
|
302
|
+
│
|
|
303
|
+
┌──────────┴──────────┐
|
|
304
|
+
│ Add & Norm │
|
|
305
|
+
└──────────┬──────────┘
|
|
306
|
+
│
|
|
307
|
+
┌──────────┴──────────────────────────┐
|
|
308
|
+
│ Cross Multi-Head Attention │ ← Q từ decoder, K/V từ encoder
|
|
309
|
+
│ Q = decoder hidden states │
|
|
310
|
+
│ K, V = encoder output │
|
|
311
|
+
└──────────────────┬──────────────────┘
|
|
312
|
+
│
|
|
313
|
+
┌──────────┴──────────┐
|
|
314
|
+
│ Add & Norm │
|
|
315
|
+
└──────────┬──────────┘
|
|
316
|
+
│
|
|
317
|
+
┌──────────┴──────────┐
|
|
318
|
+
│ Feed-Forward Net │
|
|
319
|
+
└──────────┬──────────┘
|
|
320
|
+
│
|
|
321
|
+
┌──────────┴──────────┐
|
|
322
|
+
│ Add & Norm │
|
|
323
|
+
└──────────┬──────────┘
|
|
324
|
+
│
|
|
325
|
+
Output
|
|
326
|
+
</code></pre>
|
|
327
|
+
|
|
328
|
+
<h3 id="full-transformer">4.3 Full Transformer Architecture</h3>
|
|
329
|
+
|
|
330
|
+
<pre><code class="language-text">┌─────────────────────────────────────────────────────────────────┐
|
|
331
|
+
│ TRANSFORMER ARCHITECTURE │
|
|
332
|
+
├───────────────────────┬─────────────────────────────────────────┤
|
|
333
|
+
│ ENCODER │ DECODER │
|
|
334
|
+
│ │ │
|
|
335
|
+
│ Input Embedding │ Output Embedding │
|
|
336
|
+
│ + │ + │
|
|
337
|
+
│ Positional Enc. │ Positional Enc. │
|
|
338
|
+
│ │ │ │ │
|
|
339
|
+
│ ┌────┴────┐ │ ┌────┴──────────┐ │
|
|
340
|
+
│ │ MH │ │ │ Masked MH │ │
|
|
341
|
+
│ │ Self- │ │ │ Self- │ │
|
|
342
|
+
│ │ Attn │ │ │ Attention │ │
|
|
343
|
+
│ └────┬────┘ │ └────┬──────────┘ │
|
|
344
|
+
│ Add & Norm │ Add & Norm │
|
|
345
|
+
│ │ │ │ │
|
|
346
|
+
│ ┌────┴────┐ │ ┌────────┴────────────┐ │
|
|
347
|
+
│ │ Feed │ │ │ Cross MH Attention │ │
|
|
348
|
+
│ │ Forward │ ─────┼───►│ Q=dec, K/V=enc │ │
|
|
349
|
+
│ │ Net │ │ └─────────┬───────────┘ │
|
|
350
|
+
│ └────┬────┘ │ Add & Norm │
|
|
351
|
+
│ Add & Norm │ │ │
|
|
352
|
+
│ │ │ ┌────┴────┐ │
|
|
353
|
+
│ × N layers │ │ Feed │ │
|
|
354
|
+
│ │ │ │ Forward │ │
|
|
355
|
+
│ Encoder │ └────┬────┘ │
|
|
356
|
+
│ Output │ Add & Norm │
|
|
357
|
+
│ │ │ │
|
|
358
|
+
│ │ × N layers │
|
|
359
|
+
│ │ │ │
|
|
360
|
+
│ │ Linear + Softmax │
|
|
361
|
+
│ │ │ │
|
|
362
|
+
│ │ Output Probabilities │
|
|
363
|
+
└───────────────────────┴─────────────────────────────────────────┘
|
|
364
|
+
</code></pre>
|
|
365
|
+
|
|
366
|
+
<h3 id="positional-encoding">4.4 Positional Encoding</h3>
|
|
367
|
+
|
|
368
|
+
<p>Transformer không có khái niệm "thứ tự" như RNN. <strong>Positional Encoding</strong> thêm thông tin vị trí vào embedding bằng hàm sin/cos:</p>
|
|
369
|
+
|
|
370
|
+
<pre><code class="language-text">PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
|
|
371
|
+
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
|
|
372
|
+
|
|
373
|
+
pos: vị trí token trong sequence (0, 1, 2, ...)
|
|
374
|
+
i: chỉ số dimension (0, 1, 2, ..., d_model/2)
|
|
375
|
+
</code></pre>
|
|
376
|
+
|
|
377
|
+
<p>Đây chính xác là concept được <strong>reuse trong Diffusion Models</strong> — sinusoidal embeddings để encode timestep t. Nắm vững positional encoding ở đây sẽ giúp bạn rất nhiều ở phần Diffusion.</p>
|
|
378
|
+
|
|
379
|
+
<pre><code class="language-python">import torch
|
|
380
|
+
import math
|
|
381
|
+
|
|
382
|
+
class PositionalEncoding(nn.Module):
|
|
383
|
+
def __init__(self, d_model, max_len=5000):
|
|
384
|
+
super().__init__()
|
|
385
|
+
pe = torch.zeros(max_len, d_model)
|
|
386
|
+
position = torch.arange(0, max_len).unsqueeze(1).float()
|
|
387
|
+
div_term = torch.exp(
|
|
388
|
+
torch.arange(0, d_model, 2).float()
|
|
389
|
+
* (-math.log(10000.0) / d_model)
|
|
390
|
+
)
|
|
391
|
+
pe[:, 0::2] = torch.sin(position * div_term) # even indices
|
|
392
|
+
pe[:, 1::2] = torch.cos(position * div_term) # odd indices
|
|
393
|
+
pe = pe.unsqueeze(0) # (1, max_len, d_model)
|
|
394
|
+
self.register_buffer('pe', pe)
|
|
395
|
+
|
|
396
|
+
def forward(self, x):
|
|
397
|
+
# x: (batch, seq_len, d_model)
|
|
398
|
+
return x + self.pe[:, :x.size(1), :]
|
|
399
|
+
|
|
400
|
+
# Demo
|
|
401
|
+
pe = PositionalEncoding(d_model=512)
|
|
402
|
+
x = torch.randn(2, 100, 512) # batch=2, seq_len=100
|
|
403
|
+
output = pe(x)
|
|
404
|
+
print(f"Output shape: {output.shape}") # (2, 100, 512)
|
|
405
|
+
</code></pre>
|
|
406
|
+
|
|
407
|
+
<h3 id="layer-norm">4.5 Layer Normalization vs Batch Normalization</h3>
|
|
408
|
+
|
|
409
|
+
<table>
|
|
410
|
+
<thead>
|
|
411
|
+
<tr><th>Feature</th><th>Batch Normalization</th><th>Layer Normalization</th></tr>
|
|
412
|
+
</thead>
|
|
413
|
+
<tbody>
|
|
414
|
+
<tr><td>Normalize across</td><td>Batch dimension</td><td>Feature dimension</td></tr>
|
|
415
|
+
<tr><td>Phụ thuộc batch size</td><td>Có — cần batch đủ lớn</td><td>Không — hoạt động trên từng sample</td></tr>
|
|
416
|
+
<tr><td>Dùng trong</td><td>CNN (computer vision)</td><td>Transformer, RNN (NLP)</td></tr>
|
|
417
|
+
<tr><td>Inference behavior</td><td>Dùng running statistics</td><td>Tính trực tiếp — giống train</td></tr>
|
|
418
|
+
<tr><td>Transformer sử dụng</td><td>Không</td><td><strong>Có — mọi sub-layer</strong></td></tr>
|
|
419
|
+
</tbody>
|
|
420
|
+
</table>
|
|
421
|
+
|
|
422
|
+
<blockquote><p><strong>Exam tip:</strong> Nếu câu hỏi hỏi "vì sao Transformer dùng LayerNorm thay vì BatchNorm?" → trả lời: (1) NLP có variable sequence length → batch stats không ổn định, (2) LayerNorm không phụ thuộc batch size, hoạt động tốt với batch size nhỏ và inference.</p></blockquote>
|
|
423
|
+
|
|
424
|
+
<h2 id="model-families">5. Model Families</h2>
|
|
425
|
+
|
|
426
|
+
<h3 id="encoder-only">5.1 Encoder-only: BERT</h3>
|
|
427
|
+
|
|
428
|
+
<p><strong>BERT</strong> (Bidirectional Encoder Representations from Transformers) chỉ sử dụng phần <strong>Encoder</strong>. Pre-training bằng:</p>
|
|
429
|
+
|
|
430
|
+
<ul>
|
|
431
|
+
<li><strong>MLM (Masked Language Modeling)</strong> — che 15% tokens, dự đoán token bị che</li>
|
|
432
|
+
<li><strong>NSP (Next Sentence Prediction)</strong> — hai câu có liên tiếp không?</li>
|
|
433
|
+
</ul>
|
|
434
|
+
|
|
435
|
+
<p>Vì BERT nhìn được cả hai hướng (bidirectional), nó rất mạnh cho các task <strong>understanding</strong>: classification, NER, QA.</p>
|
|
436
|
+
|
|
437
|
+
<h3 id="decoder-only">5.2 Decoder-only: GPT</h3>
|
|
438
|
+
|
|
439
|
+
<p><strong>GPT</strong> (Generative Pre-trained Transformer) chỉ sử dụng <strong>Decoder</strong> với <strong>causal masking</strong> — mỗi token chỉ attend vào các token trước nó. Pre-training bằng <strong>next-token prediction</strong>.</p>
|
|
440
|
+
|
|
441
|
+
<pre><code class="language-text">Causal Attention Mask (GPT):
|
|
442
|
+
|
|
443
|
+
Token: [The] [cat] [sat] [on]
|
|
444
|
+
The ✓ ✗ ✗ ✗
|
|
445
|
+
cat ✓ ✓ ✗ ✗
|
|
446
|
+
sat ✓ ✓ ✓ ✗
|
|
447
|
+
on ✓ ✓ ✓ ✓
|
|
448
|
+
|
|
449
|
+
✓ = có thể attend ✗ = bị mask (= -inf trước softmax)
|
|
450
|
+
|
|
451
|
+
→ Mỗi token chỉ "thấy" tokens trước nó
|
|
452
|
+
→ Phù hợp cho generation: sinh token tiếp theo
|
|
453
|
+
</code></pre>
|
|
454
|
+
|
|
455
|
+
<h3 id="encoder-decoder">5.3 Encoder-Decoder: T5</h3>
|
|
456
|
+
|
|
457
|
+
<p><strong>T5</strong> (Text-to-Text Transfer Transformer) sử dụng đầy đủ kiến trúc <strong>Encoder-Decoder</strong>. Mọi task đều được frame thành "text-in → text-out":</p>
|
|
458
|
+
|
|
459
|
+
<ul>
|
|
460
|
+
<li>Translation: <code>"translate English to French: The cat sat"</code> → <code>"Le chat s'est assis"</code></li>
|
|
461
|
+
<li>Summarization: <code>"summarize: {long text}"</code> → <code>"{summary}"</code></li>
|
|
462
|
+
<li>Classification: <code>"classify: {text}"</code> → <code>"positive"</code></li>
|
|
463
|
+
</ul>
|
|
464
|
+
|
|
465
|
+
<h3 id="model-comparison">5.4 Comparison Table</h3>
|
|
466
|
+
|
|
467
|
+
<table>
|
|
468
|
+
<thead>
|
|
469
|
+
<tr><th>Feature</th><th>BERT (Encoder)</th><th>GPT (Decoder)</th><th>T5 (Enc-Dec)</th></tr>
|
|
470
|
+
</thead>
|
|
471
|
+
<tbody>
|
|
472
|
+
<tr><td>Architecture</td><td>Encoder-only</td><td>Decoder-only</td><td>Encoder-Decoder</td></tr>
|
|
473
|
+
<tr><td>Directionality</td><td>Bidirectional</td><td>Left-to-right (causal)</td><td>Bidirectional enc + causal dec</td></tr>
|
|
474
|
+
<tr><td>Pre-training</td><td>MLM + NSP</td><td>Next-token prediction</td><td>Span corruption</td></tr>
|
|
475
|
+
<tr><td>Attention mask</td><td>Full (nhìn mọi token)</td><td>Causal (chỉ nhìn trước)</td><td>Full enc + causal dec</td></tr>
|
|
476
|
+
<tr><td>Mạnh cho</td><td>Understanding: NER, QA, classification</td><td>Generation: text, code</td><td>Seq2seq: translation, summary</td></tr>
|
|
477
|
+
<tr><td>Output</td><td>Contextual embeddings</td><td>Next token probability</td><td>Target sequence</td></tr>
|
|
478
|
+
<tr><td>Ví dụ models</td><td>BERT, RoBERTa, DeBERTa</td><td>GPT-2/3/4, LLaMA</td><td>T5, BART, mBART</td></tr>
|
|
479
|
+
</tbody>
|
|
480
|
+
</table>
|
|
481
|
+
|
|
482
|
+
<pre><code class="language-text">Decision Tree — Chọn Model Family:
|
|
483
|
+
|
|
484
|
+
┌─ Cần sinh text dài?
|
|
485
|
+
│ YES → Decoder-only (GPT, LLaMA)
|
|
486
|
+
│
|
|
487
|
+
Task ───────────┤
|
|
488
|
+
│ ┌─ Input→Output seq?
|
|
489
|
+
│ │ YES → Encoder-Decoder (T5, BART)
|
|
490
|
+
NO ─┤
|
|
491
|
+
│ NO → Encoder-only (BERT)
|
|
492
|
+
│ (classification, NER, embedding)
|
|
493
|
+
└──────────────────────────────────
|
|
494
|
+
</code></pre>
|
|
495
|
+
|
|
496
|
+
<blockquote><p><strong>Exam tip:</strong> Câu hỏi thường gặp: "Cho task X, nên dùng model family nào?" Rule nhanh: (1) Understanding / classification → BERT, (2) Generation → GPT, (3) Seq2seq (translation, summarization) → T5. Nhưng lưu ý: GPT đủ lớn cũng làm được mọi task qua <strong>prompting</strong>.</p></blockquote>
|
|
497
|
+
|
|
498
|
+
<h2 id="tokenization">6. Tokenization</h2>
|
|
499
|
+
|
|
500
|
+
<h3 id="token-vs-word">6.1 Token vs Word</h3>
|
|
501
|
+
|
|
502
|
+
<p>Mô hình ngôn ngữ không làm việc với "từ" mà với <strong>tokens</strong> — đơn vị nhỏ hơn hoặc bằng một từ. Tokenization quyết định cách chia text thành tokens.</p>
|
|
503
|
+
|
|
504
|
+
<pre><code class="language-text">Ví dụ tokenization cho "unbelievable":
|
|
505
|
+
|
|
506
|
+
Word-level: ["unbelievable"] → vocab quá lớn, OOV nhiều
|
|
507
|
+
Character: ["u","n","b","e",...] → sequence quá dài
|
|
508
|
+
Subword (BPE): ["un", "believ", "able"] → cân bằng vocab size và seq length
|
|
509
|
+
</code></pre>
|
|
510
|
+
|
|
511
|
+
<h3 id="bpe-wordpiece-sentencepiece">6.2 BPE, WordPiece, SentencePiece</h3>
|
|
512
|
+
|
|
513
|
+
<table>
|
|
514
|
+
<thead>
|
|
515
|
+
<tr><th>Algorithm</th><th>Sử dụng bởi</th><th>Cách hoạt động</th><th>Đặc điểm</th></tr>
|
|
516
|
+
</thead>
|
|
517
|
+
<tbody>
|
|
518
|
+
<tr><td><strong>BPE</strong> (Byte-Pair Encoding)</td><td>GPT-2/3/4, RoBERTa</td><td>Merge cặp byte phổ biến nhất lặp lại</td><td>Bottom-up, greedy merging</td></tr>
|
|
519
|
+
<tr><td><strong>WordPiece</strong></td><td>BERT, DistilBERT</td><td>Merge pair maximize likelihood</td><td>Dùng <code>##</code> prefix cho subword</td></tr>
|
|
520
|
+
<tr><td><strong>SentencePiece</strong></td><td>T5, LLaMA, mT5</td><td>Unigram LM hoặc BPE trên raw text</td><td>Language-agnostic, không cần pre-tokenize</td></tr>
|
|
521
|
+
</tbody>
|
|
522
|
+
</table>
|
|
523
|
+
|
|
524
|
+
<pre><code class="language-text">Ví dụ so sánh:
|
|
525
|
+
|
|
526
|
+
Input: "I love tokenization"
|
|
527
|
+
|
|
528
|
+
BPE (GPT-2): ["I", " love", " token", "ization"]
|
|
529
|
+
WordPiece (BERT): ["I", "love", "token", "##ization"]
|
|
530
|
+
SentencePiece (T5): ["▁I", "▁love", "▁token", "ization"]
|
|
531
|
+
|
|
532
|
+
WordPiece dùng ## cho continuation
|
|
533
|
+
SentencePiece dùng ▁ cho word start
|
|
534
|
+
</code></pre>
|
|
535
|
+
|
|
536
|
+
<h3 id="tokenizer-code">6.3 Code: Tokenization với HuggingFace</h3>
|
|
537
|
+
|
|
538
|
+
<pre><code class="language-python">from transformers import AutoTokenizer
|
|
539
|
+
|
|
540
|
+
# Load tokenizers của mỗi model family
|
|
541
|
+
bert_tok = AutoTokenizer.from_pretrained("bert-base-uncased")
|
|
542
|
+
gpt2_tok = AutoTokenizer.from_pretrained("gpt2")
|
|
543
|
+
t5_tok = AutoTokenizer.from_pretrained("t5-small")
|
|
544
|
+
|
|
545
|
+
text = "Transformers are amazing for NLP tasks!"
|
|
546
|
+
|
|
547
|
+
# BERT (WordPiece)
|
|
548
|
+
bert_tokens = bert_tok.tokenize(text)
|
|
549
|
+
print(f"BERT: {bert_tokens}")
|
|
550
|
+
# ['transformers', 'are', 'amazing', 'for', 'nl', '##p', 'tasks', '!']
|
|
551
|
+
|
|
552
|
+
# GPT-2 (BPE)
|
|
553
|
+
gpt2_tokens = gpt2_tok.tokenize(text)
|
|
554
|
+
print(f"GPT-2: {gpt2_tokens}")
|
|
555
|
+
# ['Trans', 'formers', 'Ġare', 'Ġamazing', 'Ġfor', 'ĠNLP', 'Ġtasks', '!']
|
|
556
|
+
|
|
557
|
+
# T5 (SentencePiece)
|
|
558
|
+
t5_tokens = t5_tok.tokenize(text)
|
|
559
|
+
print(f"T5: {t5_tokens}")
|
|
560
|
+
# ['▁Transform', 'ers', '▁are', '▁amazing', '▁for', '▁NLP', '▁tasks', '!']
|
|
561
|
+
|
|
562
|
+
# Encode → token IDs
|
|
563
|
+
ids = bert_tok.encode(text, return_tensors="pt")
|
|
564
|
+
print(f"Token IDs shape: {ids.shape}")
|
|
565
|
+
|
|
566
|
+
# Decode ngược lại
|
|
567
|
+
decoded = bert_tok.decode(ids[0])
|
|
568
|
+
print(f"Decoded: {decoded}")
|
|
569
|
+
</code></pre>
|
|
570
|
+
|
|
571
|
+
<p>Vocab size ảnh hưởng đến embedding size và model capacity:</p>
|
|
572
|
+
|
|
573
|
+
<table>
|
|
574
|
+
<thead>
|
|
575
|
+
<tr><th>Model</th><th>Tokenizer</th><th>Vocab Size</th><th>Ghi chú</th></tr>
|
|
576
|
+
</thead>
|
|
577
|
+
<tbody>
|
|
578
|
+
<tr><td>BERT-base</td><td>WordPiece</td><td>30,522</td><td>Lowercase English</td></tr>
|
|
579
|
+
<tr><td>GPT-2</td><td>BPE</td><td>50,257</td><td>Case-sensitive</td></tr>
|
|
580
|
+
<tr><td>T5</td><td>SentencePiece</td><td>32,100</td><td>Multilingual capable</td></tr>
|
|
581
|
+
<tr><td>LLaMA-2</td><td>SentencePiece</td><td>32,000</td><td>BPE variant</td></tr>
|
|
582
|
+
<tr><td>GPT-4</td><td>BPE (cl100k)</td><td>100,256</td><td>Optimized for code + multilingual</td></tr>
|
|
583
|
+
</tbody>
|
|
584
|
+
</table>
|
|
585
|
+
|
|
586
|
+
<h2 id="nlp-tasks">7. NLP Tasks Mapping</h2>
|
|
587
|
+
|
|
588
|
+
<h3 id="task-overview">7.1 Task → Model → Output</h3>
|
|
589
|
+
|
|
590
|
+
<table>
|
|
591
|
+
<thead>
|
|
592
|
+
<tr><th>NLP Task</th><th>Task Type</th><th>Best Model Family</th><th>Output</th></tr>
|
|
593
|
+
</thead>
|
|
594
|
+
<tbody>
|
|
595
|
+
<tr><td>Text Classification</td><td>Sequence classification</td><td>Encoder (BERT)</td><td>Single label</td></tr>
|
|
596
|
+
<tr><td>Sentiment Analysis</td><td>Sequence classification</td><td>Encoder (BERT)</td><td>Positive/Negative</td></tr>
|
|
597
|
+
<tr><td>Named Entity Recognition</td><td>Token classification</td><td>Encoder (BERT)</td><td>Label per token</td></tr>
|
|
598
|
+
<tr><td>Question Answering</td><td>Extractive / Generative</td><td>Encoder or Enc-Dec</td><td>Span or text</td></tr>
|
|
599
|
+
<tr><td>Summarization</td><td>Seq2seq generation</td><td>Enc-Dec (T5, BART)</td><td>Summary text</td></tr>
|
|
600
|
+
<tr><td>Translation</td><td>Seq2seq generation</td><td>Enc-Dec (T5, mBART)</td><td>Translated text</td></tr>
|
|
601
|
+
<tr><td>Text Generation</td><td>Autoregressive</td><td>Decoder (GPT)</td><td>Continuation</td></tr>
|
|
602
|
+
<tr><td>Code Generation</td><td>Autoregressive</td><td>Decoder (CodeGen)</td><td>Code</td></tr>
|
|
603
|
+
</tbody>
|
|
604
|
+
</table>
|
|
605
|
+
|
|
606
|
+
<h3 id="classification-code">7.2 Code: Fine-tune BERT cho Text Classification</h3>
|
|
607
|
+
|
|
608
|
+
<pre><code class="language-python">import torch
|
|
609
|
+
import torch.nn as nn
|
|
610
|
+
from transformers import BertModel, BertTokenizer
|
|
611
|
+
|
|
612
|
+
class BertClassifier(nn.Module):
|
|
613
|
+
def __init__(self, num_classes, model_name='bert-base-uncased'):
|
|
614
|
+
super().__init__()
|
|
615
|
+
self.bert = BertModel.from_pretrained(model_name)
|
|
616
|
+
self.dropout = nn.Dropout(0.1)
|
|
617
|
+
self.classifier = nn.Linear(self.bert.config.hidden_size, num_classes)
|
|
618
|
+
|
|
619
|
+
def forward(self, input_ids, attention_mask):
|
|
620
|
+
# BERT output: last_hidden_state, pooler_output
|
|
621
|
+
outputs = self.bert(input_ids=input_ids,
|
|
622
|
+
attention_mask=attention_mask)
|
|
623
|
+
|
|
624
|
+
# Lấy [CLS] token representation cho classification
|
|
625
|
+
cls_output = outputs.pooler_output # (batch, hidden_size)
|
|
626
|
+
cls_output = self.dropout(cls_output)
|
|
627
|
+
logits = self.classifier(cls_output) # (batch, num_classes)
|
|
628
|
+
return logits
|
|
629
|
+
|
|
630
|
+
# Sử dụng
|
|
631
|
+
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
|
|
632
|
+
model = BertClassifier(num_classes=3)
|
|
633
|
+
|
|
634
|
+
# Tokenize input
|
|
635
|
+
text = "This movie was absolutely wonderful!"
|
|
636
|
+
encoded = tokenizer(text, return_tensors='pt', padding=True,
|
|
637
|
+
truncation=True, max_length=128)
|
|
638
|
+
|
|
639
|
+
# Forward pass
|
|
640
|
+
logits = model(encoded['input_ids'], encoded['attention_mask'])
|
|
641
|
+
prediction = torch.argmax(logits, dim=-1)
|
|
642
|
+
print(f"Predicted class: {prediction.item()}")
|
|
643
|
+
</code></pre>
|
|
644
|
+
|
|
645
|
+
<pre><code class="language-python"># Training loop cho BERT classifier
|
|
646
|
+
from torch.utils.data import DataLoader
|
|
647
|
+
|
|
648
|
+
optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5)
|
|
649
|
+
loss_fn = nn.CrossEntropyLoss()
|
|
650
|
+
|
|
651
|
+
model.train()
|
|
652
|
+
for epoch in range(3):
|
|
653
|
+
total_loss = 0
|
|
654
|
+
for batch in train_loader:
|
|
655
|
+
optimizer.zero_grad()
|
|
656
|
+
|
|
657
|
+
logits = model(batch['input_ids'], batch['attention_mask'])
|
|
658
|
+
loss = loss_fn(logits, batch['labels'])
|
|
659
|
+
|
|
660
|
+
loss.backward()
|
|
661
|
+
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
|
662
|
+
optimizer.step()
|
|
663
|
+
|
|
664
|
+
total_loss += loss.item()
|
|
665
|
+
|
|
666
|
+
print(f"Epoch {epoch+1}, Loss: {total_loss / len(train_loader):.4f}")
|
|
667
|
+
</code></pre>
|
|
668
|
+
|
|
669
|
+
<blockquote><p><strong>Exam tip:</strong> Khi fine-tune BERT, 3 điểm quan trọng: (1) <strong>Learning rate nhỏ</strong> (2e-5 đến 5e-5) vì model đã pre-trained, (2) <strong>Gradient clipping</strong> (<code>clip_grad_norm_</code>) để ổn định training, (3) Dùng <strong>pooler_output</strong> (CLS token) cho classification, <strong>last_hidden_state</strong> cho token-level tasks (NER).</p></blockquote>
|
|
670
|
+
|
|
671
|
+
<h2 id="cheat-sheet">8. Cheat Sheet</h2>
|
|
672
|
+
|
|
673
|
+
<table>
|
|
674
|
+
<thead>
|
|
675
|
+
<tr><th>Concept</th><th>Key Formula / Pattern</th><th>Ghi nhớ</th></tr>
|
|
676
|
+
</thead>
|
|
677
|
+
<tbody>
|
|
678
|
+
<tr><td>Scaled Dot-Product</td><td><code>softmax(QK^T / √d_k) V</code></td><td>Chia √d_k để tránh softmax bão hòa</td></tr>
|
|
679
|
+
<tr><td>Multi-Head</td><td>Split → h × Attention → Concat → Linear</td><td>d_k = d_model / num_heads</td></tr>
|
|
680
|
+
<tr><td>Positional Encoding</td><td>sin/cos functions</td><td>Reused trong Diffusion timestep embedding</td></tr>
|
|
681
|
+
<tr><td>Encoder (BERT)</td><td>Bidirectional, MLM</td><td>Understanding tasks: NER, QA, classification</td></tr>
|
|
682
|
+
<tr><td>Decoder (GPT)</td><td>Causal mask, next-token</td><td>Generation tasks: text, code</td></tr>
|
|
683
|
+
<tr><td>Enc-Dec (T5)</td><td>Cross-attention, seq2seq</td><td>Translation, summarization</td></tr>
|
|
684
|
+
<tr><td>BPE</td><td>Merge frequent byte pairs</td><td>GPT family</td></tr>
|
|
685
|
+
<tr><td>WordPiece</td><td>Maximize likelihood merge</td><td>BERT family, dùng ## prefix</td></tr>
|
|
686
|
+
<tr><td>SentencePiece</td><td>Language-agnostic on raw text</td><td>T5, LLaMA, dùng ▁ prefix</td></tr>
|
|
687
|
+
<tr><td>Causal Mask</td><td>Lower-triangular matrix</td><td>GPT: mỗi token chỉ thấy trước nó</td></tr>
|
|
688
|
+
<tr><td>Layer Norm</td><td>Normalize across features</td><td>Dùng trong Transformer (không phải BatchNorm)</td></tr>
|
|
689
|
+
<tr><td>Fine-tune LR</td><td>2e-5 → 5e-5</td><td>LR nhỏ vì pre-trained weights</td></tr>
|
|
690
|
+
</tbody>
|
|
691
|
+
</table>
|
|
692
|
+
|
|
693
|
+
<h2 id="practice-questions">9. Practice Questions</h2>
|
|
694
|
+
|
|
695
|
+
<p>Các câu hỏi dạng coding assessment tương tự NVIDIA DLI:</p>
|
|
696
|
+
|
|
697
|
+
<p><strong>Q1:</strong> Implement <code>scaled_dot_product_attention</code> function. Hàm nhận Q, K, V tensors và optional mask, trả về output và attention weights.</p>
|
|
698
|
+
|
|
699
|
+
<details>
|
|
700
|
+
<summary>Xem đáp án Q1</summary>
|
|
701
|
+
|
|
702
|
+
<pre><code class="language-python">import torch
|
|
703
|
+
import torch.nn.functional as F
|
|
704
|
+
import math
|
|
705
|
+
|
|
706
|
+
def scaled_dot_product_attention(Q, K, V, mask=None):
|
|
707
|
+
"""
|
|
708
|
+
Args:
|
|
709
|
+
Q: (batch, seq_len, d_k)
|
|
710
|
+
K: (batch, seq_len, d_k)
|
|
711
|
+
V: (batch, seq_len, d_v)
|
|
712
|
+
mask: optional (batch, 1, seq_len) or (batch, seq_len, seq_len)
|
|
713
|
+
Returns:
|
|
714
|
+
output: (batch, seq_len, d_v)
|
|
715
|
+
attn_weights: (batch, seq_len, seq_len)
|
|
716
|
+
"""
|
|
717
|
+
d_k = Q.size(-1)
|
|
718
|
+
|
|
719
|
+
# Compute attention scores
|
|
720
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
|
|
721
|
+
|
|
722
|
+
# Apply mask if provided
|
|
723
|
+
if mask is not None:
|
|
724
|
+
scores = scores.masked_fill(mask == 0, float('-inf'))
|
|
725
|
+
|
|
726
|
+
# Softmax over last dimension (key dimension)
|
|
727
|
+
attn_weights = F.softmax(scores, dim=-1)
|
|
728
|
+
|
|
729
|
+
# Weighted sum of values
|
|
730
|
+
output = torch.matmul(attn_weights, V)
|
|
731
|
+
|
|
732
|
+
return output, attn_weights
|
|
733
|
+
|
|
734
|
+
# Kiểm tra:
|
|
735
|
+
B, S, D = 2, 4, 64
|
|
736
|
+
Q = torch.randn(B, S, D)
|
|
737
|
+
K = torch.randn(B, S, D)
|
|
738
|
+
V = torch.randn(B, S, D)
|
|
739
|
+
out, w = scaled_dot_product_attention(Q, K, V)
|
|
740
|
+
assert out.shape == (B, S, D)
|
|
741
|
+
assert w.shape == (B, S, S)
|
|
742
|
+
assert torch.allclose(w.sum(dim=-1), torch.ones(B, S), atol=1e-6)
|
|
743
|
+
print("All assertions passed!")
|
|
744
|
+
</code></pre>
|
|
745
|
+
|
|
746
|
+
<p><em>Giải thích: Điểm mấu chốt là (1) <code>K.transpose(-2, -1)</code> để matrix multiply đúng, (2) chia <code>math.sqrt(d_k)</code> để scale, (3) <code>masked_fill</code> với <code>-inf</code> trước softmax, (4) softmax trên <code>dim=-1</code>. Lỗi phổ biến: quên transpose K hoặc softmax sai dim.</em></p>
|
|
747
|
+
</details>
|
|
748
|
+
|
|
749
|
+
<p><strong>Q2:</strong> Điều gì xảy ra nếu loại bỏ Positional Encoding khỏi Transformer? Viết code chứng minh.</p>
|
|
750
|
+
|
|
751
|
+
<details>
|
|
752
|
+
<summary>Xem đáp án Q2</summary>
|
|
753
|
+
|
|
754
|
+
<pre><code class="language-python">import torch
|
|
755
|
+
|
|
756
|
+
# Self-attention KHÔNG có positional encoding
|
|
757
|
+
# → output là permutation invariant (thứ tự token không ảnh hưởng)
|
|
758
|
+
|
|
759
|
+
def self_attention_no_pos(x):
|
|
760
|
+
"""x: (batch, seq_len, d_model)"""
|
|
761
|
+
d_k = x.size(-1)
|
|
762
|
+
scores = torch.matmul(x, x.transpose(-2, -1)) / (d_k ** 0.5)
|
|
763
|
+
weights = torch.softmax(scores, dim=-1)
|
|
764
|
+
return torch.matmul(weights, x)
|
|
765
|
+
|
|
766
|
+
# Tạo input
|
|
767
|
+
x = torch.randn(1, 4, 8) # 4 tokens, d_model=8
|
|
768
|
+
|
|
769
|
+
# Output gốc
|
|
770
|
+
out1 = self_attention_no_pos(x)
|
|
771
|
+
|
|
772
|
+
# Shuffle thứ tự tokens: [0,1,2,3] → [2,0,3,1]
|
|
773
|
+
perm = [2, 0, 3, 1]
|
|
774
|
+
x_shuffled = x[:, perm, :]
|
|
775
|
+
out2 = self_attention_no_pos(x_shuffled)
|
|
776
|
+
|
|
777
|
+
# Kiểm tra: output cũng bị shuffle theo cùng thứ tự
|
|
778
|
+
inv_perm = [1, 3, 0, 2] # inverse permutation
|
|
779
|
+
out2_reordered = out2[:, inv_perm, :]
|
|
780
|
+
|
|
781
|
+
print(f"Difference: {(out1 - out2_reordered).abs().max().item():.10f}")
|
|
782
|
+
# → gần 0! Attention không phân biệt thứ tự
|
|
783
|
+
# → "The cat sat on mat" = "mat on sat cat The"
|
|
784
|
+
# Đó là lý do PHẢI có Positional Encoding!
|
|
785
|
+
</code></pre>
|
|
786
|
+
|
|
787
|
+
<p><em>Giải thích: Không có Positional Encoding, self-attention là <strong>permutation equivariant</strong> — nó xử lý "The cat sat" giống hệt "sat The cat". Positional Encoding phá vỡ tính đối xứng này, cho phép model phân biệt thứ tự tokens. Trong Diffusion Models, cùng concept này được dùng cho timestep embedding.</em></p>
|
|
788
|
+
</details>
|
|
789
|
+
|
|
790
|
+
<p><strong>Q3:</strong> GPT sử dụng causal masking. Hãy viết code tạo causal mask và giải thích mỗi phần tử trong ma trận mask.</p>
|
|
791
|
+
|
|
792
|
+
<details>
|
|
793
|
+
<summary>Xem đáp án Q3</summary>
|
|
794
|
+
|
|
795
|
+
<pre><code class="language-python">import torch
|
|
796
|
+
|
|
797
|
+
def create_causal_mask(seq_len):
|
|
798
|
+
"""
|
|
799
|
+
Tạo causal (look-ahead) mask cho decoder.
|
|
800
|
+
mask[i][j] = 1 nếu token i được attend vào token j (j <= i)
|
|
801
|
+
mask[i][j] = 0 nếu token i KHÔNG được nhìn token j (j > i)
|
|
802
|
+
"""
|
|
803
|
+
mask = torch.tril(torch.ones(seq_len, seq_len))
|
|
804
|
+
return mask
|
|
805
|
+
|
|
806
|
+
seq_len = 5
|
|
807
|
+
mask = create_causal_mask(seq_len)
|
|
808
|
+
print("Causal Mask:")
|
|
809
|
+
print(mask)
|
|
810
|
+
# tensor([[1., 0., 0., 0., 0.],
|
|
811
|
+
# [1., 1., 0., 0., 0.],
|
|
812
|
+
# [1., 1., 1., 0., 0.],
|
|
813
|
+
# [1., 1., 1., 1., 0.],
|
|
814
|
+
# [1., 1., 1., 1., 1.]])
|
|
815
|
+
|
|
816
|
+
# Áp dụng vào attention
|
|
817
|
+
def causal_attention(Q, K, V):
|
|
818
|
+
d_k = Q.size(-1)
|
|
819
|
+
seq_len = Q.size(1)
|
|
820
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
|
|
821
|
+
|
|
822
|
+
# Apply causal mask
|
|
823
|
+
mask = create_causal_mask(seq_len).unsqueeze(0) # (1, S, S)
|
|
824
|
+
scores = scores.masked_fill(mask == 0, float('-inf'))
|
|
825
|
+
# Kết quả: vị trí tương lai → -inf → softmax → 0
|
|
826
|
+
|
|
827
|
+
weights = torch.softmax(scores, dim=-1)
|
|
828
|
+
print("Attention weights (causal):")
|
|
829
|
+
print(weights[0].detach())
|
|
830
|
+
# Row 0: [1.0, 0.0, 0.0, 0.0, 0.0] ← token 0 chỉ thấy chính nó
|
|
831
|
+
# Row 1: [0.4, 0.6, 0.0, 0.0, 0.0] ← token 1 thấy token 0,1
|
|
832
|
+
# Row 4: [0.1, 0.2, 0.3, 0.2, 0.2] ← token 4 thấy tất cả
|
|
833
|
+
|
|
834
|
+
return torch.matmul(weights, V)
|
|
835
|
+
|
|
836
|
+
B, S, D = 1, 5, 32
|
|
837
|
+
Q = torch.randn(B, S, D)
|
|
838
|
+
K = torch.randn(B, S, D)
|
|
839
|
+
V = torch.randn(B, S, D)
|
|
840
|
+
out = causal_attention(Q, K, V)
|
|
841
|
+
print(f"Output shape: {out.shape}") # (1, 5, 32)
|
|
842
|
+
</code></pre>
|
|
843
|
+
|
|
844
|
+
<p><em>Giải thích: Causal mask là ma trận tam giác dưới (<code>torch.tril</code>). Vị trí <code>mask[i][j]=0</code> (j > i) được fill bằng <code>-inf</code> trước softmax, biến thành 0 sau softmax. Điều này đảm bảo mỗi token chỉ attend vào các token trước nó — essential cho autoregressive generation trong GPT.</em></p>
|
|
845
|
+
</details>
|
|
846
|
+
|
|
847
|
+
<p><strong>Q4:</strong> Cho các use cases sau, hãy chọn model family phù hợp nhất và giải thích lý do:</p>
|
|
848
|
+
<ul>
|
|
849
|
+
<li>(a) Phân loại email spam/not-spam</li>
|
|
850
|
+
<li>(b) Dịch tiếng Việt sang tiếng Anh</li>
|
|
851
|
+
<li>(c) Chatbot tạo text tự do</li>
|
|
852
|
+
<li>(d) Trích xuất tên người từ văn bản (NER)</li>
|
|
853
|
+
</ul>
|
|
854
|
+
|
|
855
|
+
<details>
|
|
856
|
+
<summary>Xem đáp án Q4</summary>
|
|
857
|
+
|
|
858
|
+
<pre><code class="language-python"># Mapping use cases → model families
|
|
859
|
+
|
|
860
|
+
tasks = {
|
|
861
|
+
"(a) Email spam classification": {
|
|
862
|
+
"model_family": "Encoder-only (BERT)",
|
|
863
|
+
"reason": "Classification task — cần hiểu toàn bộ email "
|
|
864
|
+
"(bidirectional). Output = 1 label (spam/not-spam). "
|
|
865
|
+
"BERT + Linear classifier head.",
|
|
866
|
+
"code_hint": "BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)"
|
|
867
|
+
},
|
|
868
|
+
"(b) Vietnamese → English translation": {
|
|
869
|
+
"model_family": "Encoder-Decoder (T5, mBART)",
|
|
870
|
+
"reason": "Seq2seq task — input sequence (Vietnamese) → output "
|
|
871
|
+
"sequence (English). Encoder hiểu input, decoder sinh "
|
|
872
|
+
"output. T5 hoặc mBART cho multilingual.",
|
|
873
|
+
"code_hint": "T5ForConditionalGeneration.from_pretrained('t5-base')"
|
|
874
|
+
},
|
|
875
|
+
"(c) Free-form chatbot": {
|
|
876
|
+
"model_family": "Decoder-only (GPT, LLaMA)",
|
|
877
|
+
"reason": "Autoregressive generation — sinh text token-by-token, "
|
|
878
|
+
"không cần encoder riêng. GPT/LLaMA với instruction "
|
|
879
|
+
"tuning cho chatbot use case.",
|
|
880
|
+
"code_hint": "AutoModelForCausalLM.from_pretrained('meta-llama/Llama-2-7b-chat-hf')"
|
|
881
|
+
},
|
|
882
|
+
"(d) Named Entity Recognition": {
|
|
883
|
+
"model_family": "Encoder-only (BERT)",
|
|
884
|
+
"reason": "Token classification — cần gán label cho TỪNG token "
|
|
885
|
+
"(B-PER, I-PER, O, B-LOC,...). BERT bidirectional "
|
|
886
|
+
"giúp mỗi token nhìn context cả hai phía.",
|
|
887
|
+
"code_hint": "BertForTokenClassification.from_pretrained('bert-base-uncased', num_labels=9)"
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
for task, info in tasks.items():
|
|
892
|
+
print(f"\n{task}")
|
|
893
|
+
print(f" → {info['model_family']}")
|
|
894
|
+
print(f" Lý do: {info['reason']}")
|
|
895
|
+
print(f" Code: {info['code_hint']}")
|
|
896
|
+
</code></pre>
|
|
897
|
+
|
|
898
|
+
<p><em>Giải thích: Rule tổng quát: (1) Nếu output là 1 label cho toàn bộ input → Encoder (BERT), (2) Nếu output là label cho mỗi token → Encoder (BERT) + token classification head, (3) Nếu output là sequence khác input language/format → Encoder-Decoder (T5), (4) Nếu cần sinh text liên tục → Decoder (GPT). Tuy nhiên, trong thực tế, LLM decoder-only đủ lớn (GPT-4, LLaMA-70B) có thể làm tốt mọi task qua prompting.</em></p>
|
|
899
|
+
</details>
|
|
900
|
+
|
|
901
|
+
<p><strong>Q5:</strong> Debug lỗi sau trong Transformer. Code có bug ở dimension — tìm và sửa:</p>
|
|
902
|
+
|
|
903
|
+
<pre><code class="language-python"># BUG CODE — Tìm và sửa lỗi
|
|
904
|
+
class BrokenMultiHeadAttention(nn.Module):
|
|
905
|
+
def __init__(self, d_model=512, num_heads=8):
|
|
906
|
+
super().__init__()
|
|
907
|
+
self.d_model = d_model
|
|
908
|
+
self.num_heads = num_heads
|
|
909
|
+
self.d_k = d_model // num_heads # 64
|
|
910
|
+
|
|
911
|
+
self.W_q = nn.Linear(d_model, d_model)
|
|
912
|
+
self.W_k = nn.Linear(d_model, d_model)
|
|
913
|
+
self.W_v = nn.Linear(d_model, d_model)
|
|
914
|
+
self.W_o = nn.Linear(d_model, d_model)
|
|
915
|
+
|
|
916
|
+
def forward(self, x, mask=None):
|
|
917
|
+
B = x.size(0)
|
|
918
|
+
Q = self.W_q(x)
|
|
919
|
+
K = self.W_k(x)
|
|
920
|
+
V = self.W_v(x)
|
|
921
|
+
|
|
922
|
+
# BUG: reshape sai thứ tự dimensions
|
|
923
|
+
Q = Q.view(B, self.num_heads, -1, self.d_k) # ← Sai!
|
|
924
|
+
K = K.view(B, self.num_heads, -1, self.d_k)
|
|
925
|
+
V = V.view(B, self.num_heads, -1, self.d_k)
|
|
926
|
+
|
|
927
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
|
|
928
|
+
weights = torch.softmax(scores, dim=-1)
|
|
929
|
+
context = torch.matmul(weights, V)
|
|
930
|
+
|
|
931
|
+
# BUG: quên contiguous() trước view
|
|
932
|
+
context = context.transpose(1, 2).view(B, -1, self.d_model) # ← Sai!
|
|
933
|
+
return self.W_o(context)
|
|
934
|
+
</code></pre>
|
|
935
|
+
|
|
936
|
+
<details>
|
|
937
|
+
<summary>Xem đáp án Q5</summary>
|
|
938
|
+
|
|
939
|
+
<pre><code class="language-python">import torch
|
|
940
|
+
import torch.nn as nn
|
|
941
|
+
|
|
942
|
+
class FixedMultiHeadAttention(nn.Module):
|
|
943
|
+
def __init__(self, d_model=512, num_heads=8):
|
|
944
|
+
super().__init__()
|
|
945
|
+
self.d_model = d_model
|
|
946
|
+
self.num_heads = num_heads
|
|
947
|
+
self.d_k = d_model // num_heads
|
|
948
|
+
|
|
949
|
+
self.W_q = nn.Linear(d_model, d_model)
|
|
950
|
+
self.W_k = nn.Linear(d_model, d_model)
|
|
951
|
+
self.W_v = nn.Linear(d_model, d_model)
|
|
952
|
+
self.W_o = nn.Linear(d_model, d_model)
|
|
953
|
+
|
|
954
|
+
def forward(self, x, mask=None):
|
|
955
|
+
B = x.size(0)
|
|
956
|
+
Q = self.W_q(x)
|
|
957
|
+
K = self.W_k(x)
|
|
958
|
+
V = self.W_v(x)
|
|
959
|
+
|
|
960
|
+
# FIX 1: Đúng thứ tự: (B, S, d_model) → (B, S, h, d_k) → (B, h, S, d_k)
|
|
961
|
+
# Phải view thành (B, S, h, d_k) TRƯỚC, rồi transpose(1,2)
|
|
962
|
+
Q = Q.view(B, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
963
|
+
K = K.view(B, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
964
|
+
V = V.view(B, -1, self.num_heads, self.d_k).transpose(1, 2)
|
|
965
|
+
|
|
966
|
+
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
|
|
967
|
+
weights = torch.softmax(scores, dim=-1)
|
|
968
|
+
context = torch.matmul(weights, V) # (B, h, S, d_k)
|
|
969
|
+
|
|
970
|
+
# FIX 2: Thêm .contiguous() sau transpose trước .view()
|
|
971
|
+
context = context.transpose(1, 2).contiguous().view(B, -1, self.d_model)
|
|
972
|
+
return self.W_o(context)
|
|
973
|
+
|
|
974
|
+
# Verify
|
|
975
|
+
model = FixedMultiHeadAttention(d_model=512, num_heads=8)
|
|
976
|
+
x = torch.randn(2, 10, 512)
|
|
977
|
+
out = model(x)
|
|
978
|
+
print(f"Output shape: {out.shape}") # (2, 10, 512) ✓
|
|
979
|
+
assert out.shape == (2, 10, 512)
|
|
980
|
+
print("Fixed! All correct.")
|
|
981
|
+
</code></pre>
|
|
982
|
+
|
|
983
|
+
<p><em>Giải thích: Có 2 lỗi: <strong>Bug 1:</strong> <code>view(B, num_heads, -1, d_k)</code> sai vì tensor layout trong memory là <code>(B, S, d_model)</code>. Phải view thành <code>(B, S, num_heads, d_k)</code> trước rồi <code>transpose(1, 2)</code> để có <code>(B, num_heads, S, d_k)</code>. View trực tiếp thành <code>(B, num_heads, S, d_k)</code> sẽ trộn lẫn data giữa các heads. <strong>Bug 2:</strong> Sau <code>transpose(1, 2)</code>, tensor không còn contiguous trong memory. Gọi <code>.view()</code> trên non-contiguous tensor gây RuntimeError. Phải thêm <code>.contiguous()</code> trước <code>.view()</code>.</em></p>
|
|
984
|
+
</details>
|