$ cat ./blog/gen-ai/llm-fundamentals/embedding-trong-llm.md
seriesCách hoạt động của LLMbài 6

Embedding trong LLM: từ token ID đến vector mà Transformer tính toán được

Bài thứ sáu trong series Cách hoạt động của LLM: vì sao một số nguyên phải thành vector trước khi model làm được gì với nó, bảng embedding thật ra là cái gì, khoảng cách giữa hai vector mã hoá điều gì, vị trí được đưa vào bằng cách nào, và vì sao vector ở lớp cuối không còn là embedding của token đang đứng đó.

~/diagrams/embedding-trong-llm/embedding-pipeline.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi của một token: ID được dùng làm số hàng để tra bảng E, cộng thêm tín hiệu vị trí, đi qua residual stream, rồi được chiếu ngược về vocabulary thành logits. Hàng dưới là những điều dễ bị bỏ qua - ID tự nó không mang ý nghĩa, bảng E là tham số học được chứ không phải từ điển tra cứu, thứ tự phải được cộng vào một cách tường minh, và vector ở lớp cuối đã mang ngữ cảnh nên không còn cùng loại với vector đầu vào.
đường đi của một tokenbảng tham số và vị tríchỗ trực giác hay đi sai

Bài thứ năm kết thúc ở một dòng rất gọn: thứ model nhận không phải văn bản, mà là input_ids = [8812, 13, 7021, ...]. Bài này trả lời câu hỏi ngay sau đó - một dãy số nguyên như vậy thì tính toán được gì.

Câu trả lời ngắn là: không gì cả, chừng nào chúng còn là số nguyên.

Token ID 8812 không lớn hơn ID 13 theo bất kỳ nghĩa nào có ích. Nó cũng không gần ID 8813 hơn ID 42. Những con số đó là chỗ ngồi được đánh số trong một danh sách mà tokenizer đã chốt từ trước, và thứ tự của danh sách ấy phần lớn là ngẫu nhiên. Cộng, trừ hay so sánh chúng đều vô nghĩa.

Nhưng phép tính bên trong Transformer - nhân ma trận, cộng vector, softmax - lại chỉ làm việc với số thực. Giữa hai thế giới đó phải có một bước chuyển. Bước đó gọi là embedding, và nó là cửa vào duy nhất của model.

Sơ đồ ở đầu bài (Hình 1) là toàn bộ chặng đường: ID thành vector, vector cộng thêm thông tin vị trí, đi qua các block, rồi được chiếu ngược trở lại vocabulary.


01Embedding là gì, nói cho đúng

Định nghĩa ngắn nhất: embedding là một phép tra bảng.

Có một ma trận E với V hàng và d cột. V là kích thước vocabulary - số token mà tokenizer biết. d là số chiều của model. Token ID chính là số hàng.

E có V hàng, d cột

id = 8812  ->  E[8812]  ->  [0.21, -1.04, 0.77, ..., 0.03]
                            (d số thực)

Thế thôi. Không có phép biến đổi phức tạp nào ở đây. id là chỉ mục, E[id] là hàng thứ id.

Điều làm nó thú vị không nằm ở phép tra, mà ở chỗ E là tham số. Nó được khởi tạo ngẫu nhiên và được cập nhật bởi gradient descent, hệt như mọi ma trận weight khác trong model. Không ai nạp sẵn nghĩa của từ vào đó.

1.1 Ba từ hay bị dùng lẫn

Trước khi đi tiếp, cần tách ba thứ mà tiếng Anh lẫn tiếng Việt đều gọi chung là “embedding”:

Cách dùng Nó thật sự là gì
Embedding layer phép tra bảng ở lớp 0, biến ID thành vector
Embedding matrix ma trận E kích thước V × d, là tham số của model
Embedding vector một hàng cụ thể, hoặc - gây nhầm nhất - vector ở lớp cuối

Ba thứ này liên quan nhau nhưng không đồng nghĩa. Phần lớn hiểu lầm về embedding đến từ việc trộn cái thứ nhất với cái thứ ba, và bài này sẽ quay lại điểm đó nhiều lần.

1.2 Vì sao phải là vector, không phải số

Giả sử ta bướng bỉnh và giữ nguyên số nguyên. Model sẽ phải học một hàm nhận đầu vào là 8812. Nhưng một số nguyên chỉ cho model đúng một chiều thông tin, và chiều đó lại mang thứ tự sai: 8812 nằm giữa 8811 và 8813, trong khi ba token ấy chẳng liên quan gì đến nhau.

Vector d chiều thì khác. Nó cho model d con số để mô tả một token, và quan trọng hơn, nó cho phép hai token gần nhau theo nhiều hướng khác nhau cùng lúc. Hai từ có thể giống nhau về loại từ, khác nhau về sắc thái, lại cùng thuộc một lĩnh vực - và không gian nhiều chiều đủ chỗ để mã hoá đồng thời những quan hệ đó.

Số nguyên là một cái tên. Vector là một vị trí. Model học được từ vị trí, không học được từ tên.


02Vì sao không ai nhân one-hot thật

Trong sách giáo khoa, embedding hay được viết như một phép nhân ma trận. Token được biểu diễn bằng vector one-hot: một vector dài V, toàn số 0, trừ đúng một số 1 ở vị trí id.

V = 8, id = 3

one-hot = [0, 0, 0, 1, 0, 0, 0, 0]

Nhân vector đó với E:

(1 × V) · (V × d)  ->  (1 × d)

Kết quả là gì? Mỗi hàng của E được nhân với phần tử tương ứng của one-hot. Bảy hàng nhân 0 và biến mất. Một hàng nhân 1 và sống sót. Kết quả đúng bằng E[3].

~/diagrams/embedding-trong-llm/onehot-vs-lookup.htmltương tác
mở riêng ↗
HìnhHàng trên là cách công thức viết: dựng một vector one-hot dài V rồi nhân với ma trận E. Hàng giữa là cách framework thực thi: đọc thẳng hàng thứ id, không nhân gì cả. Hai đường cho cùng một kết quả, nhưng hàng dưới cho thấy chi phí lệch nhau rất xa - one-hot chạm V số cho mỗi token, còn gather chỉ chạm d số.
cùng một kết quảcách thực thi thậtchi phí của mỗi cách

Vậy nên hai cách nói không mâu thuẫn. Nhưng không framework nào nhân one-hot thật, vì lý do rất thực dụng.

Với V = 128.000 và một sequence 8.192 token, riêng việc dựng ma trận one-hot đã cần hơn một tỷ số. Toàn bộ chúng, trừ 8.192 số, đều bằng 0. Phép nhân sau đó là hàng tỷ phép nhân với 0.

Phép gather làm đúng việc cần làm: đọc d số ở đúng hàng cần đọc.

2.1 Hệ quả thật sự nằm ở gradient

Điểm này quan trọng hơn chuyện tiết kiệm phép tính, và nó sẽ quay lại ở phần 09 và phần 14.

Vì embedding là phép chọn hàng, gradient cũng chỉ chảy ngược về những hàng đã được chọn. Một token không xuất hiện trong batch thì hàng của nó nhận gradient bằng 0, và giá trị không đổi một chút nào.

batch chạm 4.000 hàng khác nhau
E có 128.000 hàng
-> 124.000 hàng đứng yên trong bước cập nhật này

Đây gọi là sparse gradient, và nó là lý do các framework có cài đặt riêng cho embedding thay vì dùng chung đường với các layer dày đặc.


03Bảng embedding lớn đến mức nào

Hai con số quyết định toàn bộ bảng, và cả hai đều đã bị chốt trước khi training bắt đầu.

V đến từ tokenizer. Nếu tokenizer có 128.000 token, bảng phải có đúng 128.000 hàng. d là chiều của residual stream - con số dùng chung cho toàn bộ model, sẽ nói kỹ ở phần 08.

~/diagrams/embedding-trong-llm/embedding-matrix.htmltương tác
mở riêng ↗
HìnhVocabulary quyết định số hàng, chiều model quyết định số cột, và tích của hai số đó là số tham số riêng của bảng vào. Tra bảng chỉ lấy ra đúng một hàng. Điều dễ quên là toàn bộ ma trận này khởi tạo bằng số ngẫu nhiên - mọi cấu trúc trong không gian đều là thứ training tạo ra, không phải thứ được nạp sẵn.
hai chiều của bảngmột hàng được tratrạng thái trước khi train

Nhân hai con số lại:

V = 128.000
d = 4.096

V × d = 524.288.000 tham số

Hơn 500 triệu tham số, chỉ riêng cho bảng vào.

3.1 Tỷ lệ thay đổi theo kích thước model

Con số tuyệt đối trên là cố định, nhưng ý nghĩa của nó phụ thuộc phần còn lại của model.

Kích thước model Embedding chiếm khoảng
vài trăm triệu tham số phần rất lớn, đôi khi quá nửa
vài tỷ tham số một phần đáng kể
hàng chục tỷ trở lên tỷ lệ nhỏ, vì layer áp đảo

Ở model nhỏ, bảng embedding có thể là thành phần nặng nhất. Đây là một trong những lý do các model nhỏ hay dùng vocabulary gọn hơn, hoặc buộc chung bảng vào với bảng ra - kỹ thuật ở phần 11.

Ở model rất lớn, V × d gần như không đáng kể so với hàng chục layer, và cân nhắc lại đi theo hướng khác.

3.2 Vì sao đổi tokenizer là chuyện lớn

Vì V xác định số hàng, đổi tokenizer nghĩa là đổi toàn bộ ý nghĩa của từng hàng. Hàng thứ 8812 của tokenizer cũ và hàng thứ 8812 của tokenizer mới không liên quan gì đến nhau.

Không có cách nào chỉ thay tokenizer mà giữ nguyên bảng embedding cũ. Đó là lý do bài Tokenization gọi tokenizer là quyết định gần như không thể đảo ngược.


04Khoảng cách giữa hai vector mã hoá điều gì

Đây là phần dễ bị lãng mạn hoá nhất, nên hãy nói thẳng: không gian embedding học từ phân bố, không học từ định nghĩa.

Không ai nói cho model biết mèo là động vật. Model chỉ thấy token mèo xuất hiện cạnh những token nào, trong hàng nghìn tỷ vị trí. Gradient kéo những token dùng được ở cùng chỗ lại gần nhau. Cấu trúc trong không gian là sản phẩm của việc đó.

~/diagrams/embedding-trong-llm/vector-space.htmltương tác
mở riêng ↗
HìnhHàng trên là nguồn gốc: token hay đứng cạnh cùng những thứ giống nhau thì bị gradient kéo lại gần, nên vị trí gần nhau là hệ quả của phân bố chứ không phải của nghĩa. Hàng giữa là cách đọc - cosine bỏ độ dài và chỉ giữ hướng, vì độ dài vector tương quan mạnh với tần suất. Hàng dưới là hai chỗ trực giác thường sai: từ trái nghĩa rất gần nhau, và phép loại suy kém ổn định hơn nhiều so với tiếng đồn.
cấu trúc đến từ đâucách đochỗ trực giác đi sai

4.1 Vì sao dùng cosine

Cách đo phổ biến nhất là cosine similarity - cosine của góc giữa hai vector:

cos(a, b) = (a · b) / (|a| × |b|)

Nó chỉ xét hướng và bỏ hoàn toàn độ dài. Lý do rất thực tế: độ dài của một vector embedding tương quan mạnh với tần suất của token đó trong corpus, chứ không với ý nghĩa. Token phổ biến được cập nhật nhiều lần nên vector của chúng có xu hướng dài hơn.

Nếu dùng khoảng cách Euclid thẳng, ta sẽ vô tình so sánh tần suất thay vì so sánh nội dung. Bỏ độ dài đi giúp token hiếm và token phổ biến còn đặt cạnh nhau được.

4.2 Hai chỗ trực giác đi sai

Từ trái nghĩa thường rất gần nhau. nóng và lạnh xuất hiện trong gần như cùng một tập ngữ cảnh: cùng đi với nước, thời tiết, quá, hơi. Xét theo phân bố, chúng thay thế được cho nhau, nên vector của chúng gần nhau. Không gian mã hoá “dùng được ở cùng chỗ”, và điều đó không đồng nghĩa với “nghĩa giống nhau”.

Phép loại suy không chắc chắn như tiếng đồn. Ví dụ kinh điển vua - đàn ông + đàn bà ≈ nữ hoàng là thật, nhưng nó phụ thuộc nhiều vào cách chuẩn hoá, cách đo và việc có loại bỏ ba từ gốc khỏi kết quả hay không. Nó là một tính chất thú vị, không phải một phép toán đáng tin.

Và cũng đừng tìm trục nào có tên. Không có chiều thứ 317 mang nghĩa “mức độ trang trọng”. Thông tin nằm rải trên nhiều chiều cùng lúc.


05Embedding tĩnh và embedding theo ngữ cảnh

Đây là chỗ phân biệt quan trọng nhất của cả bài.

Trước Transformer, word2vec và GloVe cho mỗi từ đúng một vector, cố định sau khi train xong. Tra bảng là hết. Từ đá trong hòn đá, đá bóng và nước đá nhận cùng một vector.

~/diagrams/embedding-trong-llm/static-vs-contextual.htmltương tác
mở riêng ↗
HìnhHàng trên là embedding tĩnh: cùng token ID thì cùng một hàng, bất kể câu chứa nó là gì, nên mọi nghĩa của một từ bị ép vào một điểm. Hàng giữa là điều xảy ra trong LLM: sau khi đi qua các block attention, mỗi lần xuất hiện có một vector riêng. Hàng dưới nhắc lại điều dễ quên - bảng E không biến mất, nó vẫn là lớp 0 mà mọi vector ngữ cảnh khởi đầu từ đó.
một vector cho mỗi tokenmột vector cho mỗi lần dùngchỗ hai cách gặp nhau

Trong một LLM, câu chuyện khác. E[id] vẫn là một vector tĩnh - đó là điểm xuất phát. Nhưng ngay khi vector đó đi qua block attention đầu tiên, nó bắt đầu trộn thông tin từ các vị trí xung quanh. Sau vài chục layer, vector ở vị trí đó đã mang dấu vết của toàn bộ ngữ cảnh đứng trước.

Câu 1: "hòn đá nặng"      -> vector tại vị trí "đá" = v1
Câu 2: "đá bóng rất hay"   -> vector tại vị trí "đá" = v2
Câu 3: "cho thêm nước đá"  -> vector tại vị trí "đá" = v3

v1 ≠ v2 ≠ v3, dù cả ba đều bắt đầu từ cùng E[id]

5.1 Vì sao điều này quan trọng

Embedding tĩnh phải nhét mọi nghĩa của một từ vào một điểm duy nhất. Kết quả là một vector trung bình, không đúng hẳn với nghĩa nào. Với từ đa nghĩa, đó là giới hạn không vượt qua được bằng cách thêm dữ liệu.

Embedding theo ngữ cảnh không có vấn đề đó, vì nó không cố mô tả một từ - nó mô tả một vị trí trong một câu cụ thể.

5.2 Nhưng bảng E không biến mất

Một nhầm lẫn hay gặp là kết luận rằng LLM “không có embedding tĩnh”. Nó có. Bảng E vẫn nằm đó, vẫn là lớp 0, và mọi vector ngữ cảnh đều khởi đầu từ đúng một hàng của nó.

Cái thay đổi là: hàng đó không còn là câu trả lời cuối cùng, nó là điều kiện ban đầu.


06Vì sao model phải được cho biết vị trí

Có một tính chất của self-attention dễ bị bỏ qua: nó không biết thứ tự.

Attention tính điểm giữa từng cặp query và key. Phép tính đó không tham chiếu đến chỉ số vị trí ở bất kỳ đâu. Nếu ta hoán vị các token đầu vào, đầu ra cũng chỉ hoán vị theo đúng cách đó - giá trị không đổi. Thuật ngữ cho tính chất này là permutation equivariant.

~/diagrams/embedding-trong-llm/position-need.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi đúng: token được nhúng, cộng tín hiệu vị trí, rồi mới vào attention, nên kết quả phụ thuộc thứ tự. Hàng dưới là điều xảy ra nếu bỏ bước cộng vị trí - câu trở thành một tập hợp không thứ tự, và hai câu đảo từ cho ra cùng một kết quả. Ô cuối hàng dưới là nhận xét dẫn tới các cách mã hoá hiện đại: cái thật sự cần là khoảng cách giữa hai token, không phải chỉ số tuyệt đối của chúng.
đường đi có vị tríkhoảng cách tương đốiđiều xảy ra nếu thiếu

Cụ thể, nếu chỉ có embedding token và không có gì khác:

"mèo đuổi chuột"   -> tập {mèo, đuổi, chuột}
"chuột đuổi mèo"   -> tập {chuột, đuổi, mèo}

Hai tập giống hệt nhau.

Model sẽ không có cách nào phân biệt ai đuổi ai. Đây không phải lỗi cài đặt mà là tính chất toán học của phép tính.

RNN không gặp vấn đề này vì nó đọc tuần tự - thứ tự nằm sẵn trong cách nó chạy. Transformer đọc mọi vị trí cùng lúc, và đó chính là lý do nó nhanh. Cái giá phải trả là thứ tự phải được cộng vào một cách tường minh.

6.1 Cái thật sự cần là khoảng cách

Một nhận xét dẫn tới toàn bộ thiết kế hiện đại: quan hệ giữa hai token phụ thuộc vào việc chúng cách nhau bao xa, chứ không phải vào chỉ số tuyệt đối của chúng.

Hai token liền kề có quan hệ chặt, dù chúng đứng ở vị trí 5 và 6 hay 5.000 và 5.001. Mã hoá được ý đó thì model tổng quát hoá tốt hơn nhiều.


07Bốn cách đưa vị trí vào model

Các cách mã hoá vị trí chia thành hai họ, khác nhau ở chỗ chúng can thiệp.

~/diagrams/embedding-trong-llm/position-schemes.htmltương tác
mở riêng ↗
HìnhHàng trên là họ cộng thẳng vào vector đầu vào trước khi vào block, gồm sinusoidal của Transformer gốc và learned của GPT-2. Hàng giữa là họ can thiệp bên trong phép tính attention, gồm RoPE và ALiBi, và chỉ họ này nói trực tiếp về khoảng cách giữa hai token. Hàng dưới là cái đánh đổi lộ ra khi câu dài hơn lúc train: learned chặn cứng ở độ dài đã học, còn các cách tương đối ngoại suy dễ hơn nhưng vẫn cần hiệu chỉnh.
cộng vào đầu vàocan thiệp trong attentionđánh đổi về độ dài

Sinusoidal là cách của Transformer gốc năm 2017. Mỗi vị trí có một vector tính bằng các hàm sin và cos ở nhiều tần số khác nhau, cộng thẳng vào embedding token. Không có tham số nào phải học.

Learned là cách của GPT-2 và nhiều model sau đó. Đơn giản hơn nữa: có một bảng thứ hai, mỗi vị trí một hàng, học y như bảng token.

đầu vào lớp 0 = E[token_id] + P[position]

Cách này dễ hiểu và chạy tốt, nhưng có một hạn chế cứng. Nếu bảng P chỉ có 1.024 hàng, vị trí 1.025 đơn giản là không tồn tại. Đó là lý do context window của các model đời đó là một con số chốt chặt.

RoPE - Rotary Position Embedding - đổi cách tiếp cận. Thay vì cộng gì vào vector đầu vào, nó xoay query và key một góc tỷ lệ với vị trí, ngay bên trong phép tính attention. Điều hay là sau phép xoay, tích vô hướng giữa query ở vị trí m và key ở vị trí n chỉ còn phụ thuộc vào hiệu m - n.

Nói cách khác, RoPE mã hoá đúng thứ ta cần: khoảng cách, không phải chỉ số.

ALiBi đi theo cùng tinh thần nhưng thô hơn: cộng thẳng một khoản phạt tỷ lệ với khoảng cách vào điểm attention, khiến token càng xa càng bị giảm trọng số.

7.1 Vì sao RoPE thắng

Hầu hết model mở hiện nay dùng RoPE. Lý do là nó xử lý câu dài tốt hơn hẳn: vì công thức chỉ phụ thuộc khoảng cách, model không gặp “vị trí chưa từng thấy” theo cách mà learned gặp.

Cần nói rõ một điều hay bị phóng đại: RoPE không cho phép mở rộng context vô hạn miễn phí. Vượt xa độ dài đã train, chất lượng vẫn giảm, và các kỹ thuật nới như điều chỉnh tần số hay fine-tune thêm trên câu dài vẫn cần thiết. RoPE làm cho việc nới khả thi, không làm cho nó tự động.


08Residual stream: chỗ vector thật sự sống

Để hiểu vì sao d xuất hiện ở khắp nơi, cần một mô hình tư duy về cách vector di chuyển qua model.

Hãy hình dung mỗi vị trí token giữ đúng một vector d chiều, chạy suốt từ lớp 0 đến lớp cuối. Vector đó gọi là residual stream.

~/diagrams/embedding-trong-llm/residual-stream.htmltương tác
mở riêng ↗
HìnhEmbedding cộng vị trí là giá trị khởi đầu của residual stream. Các block attention và MLP không thay thế vector đó - chúng đọc nó rồi cộng thêm phần đóng góp của mình vào, nên dấu vết của embedding ban đầu vẫn còn ở những lớp sau. Vì mọi block đều nhận d chiều và trả về d chiều, con số d là ràng buộc chung của toàn bộ model, và đó là lý do bảng E buộc phải có đúng d cột.
băng thông dùng chungblock cộng thêm vàochiều cố định suốt model

Điểm mấu chốt: các block không thay thế vector đó. Chúng đọc nó, tính ra một phần đóng góp, rồi cộng phần đó vào:

x0 = E[id] + vị trí
x1 = x0 + attention(x0)
x2 = x1 + mlp(x1)
x3 = x2 + attention(x2)
...

Đây là ý nghĩa của chữ “residual” trong residual connection. Hệ quả trực tiếp là embedding ban đầu không biến mất - nó vẫn còn trong tổng, chỉ là bị phủ thêm nhiều lớp thông tin khác.

8.1 Vì sao d có mặt ở mọi nơi

Vì mọi block đều nhận d chiều và trả về d chiều, d trở thành ràng buộc chung:

  • bảng E phải có đúng d cột, nếu không thì không cộng vào được;
  • mọi attention head cộng lại phải cho ra d chiều;
  • MLP nở ra rồi phải co lại đúng d;
  • bảng ra phải nhận d chiều.

Đổi d nghĩa là đổi gần như toàn bộ hình dạng của model.

8.2 Vector ở lớp cuối không phải embedding của token đó

Đây là chỗ cần nói thật rõ, vì nó là nguồn gốc của rất nhiều nhầm lẫn.

Vector ở vị trí t tại lớp cuối không mô tả token đứng ở vị trí t. Nó đã hấp thụ toàn bộ ngữ cảnh đứng trước, và nó được huấn luyện để phục vụ đúng một việc: đoán token tiếp theo.

vector tại vị trí t, lớp cuối
   -> không phải "token t là gì"
   -> mà là "token t+1 nên là gì"

Lấy vector đó ra và đọc như embedding của token hiện tại là hiểu sai mục đích của nó.


09Bảng embedding được học như thế nào

Không có thuật toán riêng nào cho embedding. Nó nhận gradient từ đúng hàm loss next-token đã mô tả ở bài Dataset, qua đúng cơ chế backpropagation như mọi weight khác.

~/diagrams/embedding-trong-llm/embedding-gradient.htmltương tác
mở riêng ↗
HìnhForward chỉ tra những hàng ứng với token thật sự có trong batch. Backward đưa gradient ngược qua model, nhưng khi tới bảng E, nó chỉ chảy về đúng những hàng đã được tra - phần còn lại nhận gradient bằng 0. Optimizer vì vậy chỉ ghi giá trị mới vào một phần rất nhỏ của bảng ở mỗi bước, và đó là lý do chất lượng một vector tỷ lệ với số lần token đó xuất hiện.
forward và backwardgradient thưahàng không được chạm

Điều đáng chú ý là cấu trúc ngữ nghĩa là sản phẩm phụ. Không có bước nào trong training nói “hãy đặt các từ cùng chủ đề gần nhau”. Chỉ có một sức ép duy nhất: đoán đúng token kế tiếp. Hoá ra cách hiệu quả nhất để làm việc đó là sắp xếp các token dùng được ở cùng chỗ vào cùng vùng không gian - nên model tự làm vậy.

9.1 Tần suất quyết định chất lượng

Vì gradient thưa, số lần một hàng được cập nhật bằng đúng số lần token đó xuất hiện trong training.

token phổ biến ("của", "là", "the")
   -> chạm hàng triệu lần
   -> vector được nắn rất kỹ

token hiếm
   -> chạm vài lần
   -> vector còn gần giá trị khởi tạo

Đây là lý do trực tiếp dẫn tới hiện tượng ở phần 14.


10Unembedding: đường quay về vocabulary

Model kết thúc với một vector d chiều. Nhưng thứ ta cần là một token. Phải có bước đi ngược.

~/diagrams/embedding-trong-llm/unembedding-logits.htmltương tác
mở riêng ↗
HìnhVector ở lớp cuối được nhân với ma trận ra kích thước d nhân V, cho ra một điểm số cho mỗi token trong vocabulary. Bản chất mỗi điểm là tích vô hướng giữa vector cuối và một hàng của ma trận ra, nên token nào có hàng cùng hướng với vector cuối thì điểm càng cao. Softmax biến điểm thành xác suất, và mọi can thiệp kiểu temperature hay top-p đều nằm sau bước đó, ngoài model.
đường về vocabularytích vô hướngphần nằm ngoài model

Bước đó là unembedding: nhân vector d chiều với một ma trận d × V, cho ra V số - mỗi token một điểm. Những điểm này gọi là logits.

vector (1 × d) · ma trận ra (d × V)  ->  logits (1 × V)

Nhìn kỹ hơn: điểm của token thứ i chính là tích vô hướng giữa vector cuối và hàng thứ i của ma trận ra. Nghĩa là hình học ở đây có ý nghĩa rất trực tiếp - vector cuối càng cùng hướng với một hàng thì token đó điểm càng cao.

Sinh văn bản, xét cho cùng, là bài toán tìm hàng gần nhất theo hướng.

10.1 Softmax và phần nằm ngoài model

Softmax biến V điểm số thành một phân bố xác suất cộng lại bằng 1. Sau đó mới đến bước chọn token.

Điều đáng nhớ: model chỉ trả về logits. Nó không tự chọn. Temperature, top-k, top-p đều thao tác trên phân bố đã có, và đổi chúng không đổi một tham số nào của model. Đây là ranh giới giữa “model” và “cách ta dùng model”, và bài Transformer từ A đến Z đã đi qua phần sampling này kỹ hơn.


11Weight tying: dùng chung một ma trận cho cả hai đầu

Để ý hình dạng của hai bảng:

bảng vào  E : V × d
bảng ra   U : d × V

Chúng là chuyển vị của nhau về kích thước. Nên có một câu hỏi tự nhiên: dùng chung một ma trận được không?

~/diagrams/embedding-trong-llm/weight-tying.htmltương tác
mở riêng ↗
HìnhBảng vào và bảng ra nằm ở hai đầu model và có hình dạng khớp nhau, nên có thể dùng lại đúng một ma trận cho cả hai. Buộc chung tiết kiệm đúng V nhân d tham số, khoản này rất đáng kể ở model nhỏ. Cái giá là hai vai trò khác nhau bị ép chia sẻ một biểu diễn: hàng của bảng vào nói token trông thế nào khi đi vào, còn hàng của bảng ra nói khi nào nên sinh ra token đó.
hai đầu của modelkhi buộc chungcái đánh đổi

Được, và kỹ thuật đó gọi là weight tying. Nó tiết kiệm đúng V × d tham số - với con số ở phần 03 là hơn 500 triệu.

11.1 Vì sao nó hợp lý về mặt trực giác

Có một lập luận đẹp cho việc buộc chung. Nếu hàng của E mô tả token mèo, và logit của mèo là tích vô hướng giữa vector cuối với hàng tương ứng của U, thì việc dùng chung một hàng nghĩa là: model sinh ra token mèo khi vector cuối trỏ về đúng hướng mà mèo đại diện.

Điều đó nghe rất tự nhiên, và trong thực tế weight tying hoạt động tốt, đặc biệt ở model nhỏ.

11.2 Nhưng nó không miễn phí

Cái giá là hai vai trò khác nhau bị ép dùng chung một biểu diễn:

  • hàng của E trả lời: token này trông như thế nào khi đi vào model;
  • hàng của U trả lời: khi nào thì nên sinh ra token này.

Hai câu hỏi đó liên quan nhưng không đồng nhất. Ở model đủ lớn, khi V × d không còn là khoản đáng kể, nhiều recipe chọn tách riêng trở lại để mỗi ma trận tự do làm việc của nó.


12Chọn d bằng bao nhiêu

d không phải một con số tuỳ ý. Nó bị ràng buộc từ nhiều phía cùng lúc.

Ràng buộc Vì sao
Chia hết cho số attention head mỗi head lấy d / n_heads chiều
Đủ lớn để chứa thông tin residual stream là băng thông duy nhất
Không quá lớn chi phí mọi layer tăng theo d
Hợp với phần cứng bội của 64 hoặc 128 chạy nhanh hơn

Vì vậy các giá trị thực tế gần như luôn là những con số tròn: 768, 1.024, 2.048, 4.096, 8.192.

Có một cách nghĩ hữu ích: d là băng thông của model. Mọi thứ mà một vị trí cần mang theo - token gốc là gì, nó đứng ở đâu, ngữ cảnh nói gì, model đang định làm gì - đều phải chen vào cùng d con số đó. Tăng d cho model nhiều chỗ hơn để giữ thông tin, nhưng làm mọi phép tính đắt lên.

Và như Chinchilla đã chỉ ra ở khía cạnh khác, tăng kích thước mà không tăng dữ liệu tương ứng thì phần capacity thêm vào không được dùng hết.


13Embedding của LLM khác embedding cho retrieval

Khi làm RAG hay semantic search, ta gặp một thứ cũng tên là embedding: đưa một câu vào model, nhận về một vector, rồi cất vào vector database. Đây không phải cùng một thứ với embedding bên trong LLM.

~/diagrams/embedding-trong-llm/llm-vs-retrieval-embedding.htmltương tác
mở riêng ↗
HìnhBên trong LLM, embedding là trạng thái trung gian trên từng token, phục vụ việc đoán token kế tiếp. Embedding model cho retrieval trả về một vector cho cả đoạn văn bản như kết quả cuối cùng, và được train bằng mục tiêu contrastive để câu gần nghĩa thì vector gần nhau. Hai thứ khác nhau cả về đơn vị, mục tiêu huấn luyện lẫn nơi được dùng, nên lấy hidden state của LLM đi làm retrieval thường yếu hơn một model được train riêng.
bên trong LLMmodel cho retrievalchỗ khác nhau

Ba khác biệt đáng kể:

Đơn vị khác nhau. LLM cho một vector trên mỗi vị trí token. Embedding model cho một vector trên cả đoạn. Muốn đi từ token sang đoạn thì phải gộp lại, và cách gộp - lấy trung bình, lấy token cuối, dùng một token đặc biệt - là một lựa chọn thiết kế có ảnh hưởng thật.

Mục tiêu huấn luyện khác nhau. LLM tối ưu cho next-token prediction. Embedding model thường được train bằng mục tiêu contrastive: kéo cặp câu liên quan lại gần và đẩy cặp không liên quan ra xa. Hai sức ép đó nắn không gian theo hai cách khác nhau.

Vai trò khác nhau. Trong LLM, embedding là trạng thái trung gian của một phép tính lớn hơn. Với retrieval, vector chính là sản phẩm.

Đây là lý do lấy hidden state của một LLM đem đi làm semantic search thường cho kết quả kém hơn một embedding model được train riêng, dù LLM lớn hơn nhiều lần. Bài RAG không cứu được dữ liệu bẩn nói thêm về chỗ này ở tầng hệ thống.


14Những token mà vector của chúng chưa từng được học

Ghép hai điều đã nói - gradient thưa ở phần 09 và tokenizer được train riêng ở phần 03 - ta được một hệ quả kỳ lạ nhưng có thật.

~/diagrams/embedding-trong-llm/undertrained-tokens.htmltương tác
mở riêng ↗
HìnhHàng trên là vòng đời bình thường: hàng được khởi tạo ngẫu nhiên, xuất hiện trong batch, nhận gradient, dịch dần vào cụm có nghĩa. Hàng dưới là nhánh hỏng - token gần như không bao giờ vào batch thì gradient bằng 0 và hàng giữ nguyên giá trị khởi tạo suốt cả run. Muốn sửa thì phải quay lại nhánh trên bằng cách train thêm, còn nếu bỏ qua thì phần đuôi vocabulary sinh ra những token cho hành vi khó đoán lúc inference.
vòng đời bình thườngđường quay lạinhánh không được nắn

Tokenizer thường được train trên một corpus khác với corpus pretraining. Nó có thể sinh ra token cho những chuỗi ký tự về sau gần như không gặp lại - một tên người dùng trên diễn đàn, một chuỗi rác trong dữ liệu crawl, một mẩu mã bị lặp.

Những token đó tồn tại trong vocabulary, nên chúng có hàng riêng trong E. Nhưng vì gần như không bao giờ xuất hiện trong training, hàng của chúng giữ nguyên giá trị khởi tạo ngẫu nhiên suốt cả run.

Kết quả là những vector nhiễu nằm lệch hẳn khỏi mọi vùng có nghĩa. Khi model gặp chúng lúc inference, hành vi trở nên khó đoán - model có thể lảng tránh, lặp lại, hoặc trả lời hoàn toàn lạc đề. Cộng đồng gọi chúng là glitch token, và ví dụ nổi tiếng nhất là chuỗi SolidGoldMagikarp tìm thấy trong vocabulary của GPT-2 và GPT-3.

Cách xử lý không nằm ở model mà ở dữ liệu và tokenizer: cắt bớt phần đuôi vocabulary, train tokenizer trên đúng corpus sẽ dùng để pretrain, hoặc kiểm tra tần suất từng token trước khi chốt.

Một token có mặt trong vocabulary không có nghĩa là model đã học được gì về nó.


15Đa ngôn ngữ và đa phương thức

Hai mở rộng đáng nói, vì cả hai đều là hệ quả trực tiếp của những gì ở trên.

15.1 Nhiều ngôn ngữ, một không gian

Model đa ngôn ngữ dùng chung một bảng E cho mọi ngôn ngữ. Không có bảng riêng cho tiếng Việt và bảng riêng cho tiếng Anh.

Điều thú vị là các khái niệm tương đương giữa các ngôn ngữ có xu hướng nằm gần nhau trong không gian, dù không ai ép chúng phải vậy. Lý do vẫn là phân bố: dog và chó xuất hiện trong những ngữ cảnh có cấu trúc tương tự, nên gradient nắn chúng về những vùng tương tự.

Nhưng sự chia sẻ đó không đều. Ngôn ngữ chiếm tỷ lệ nhỏ trong corpus sẽ có ít token riêng hơn, tần suất mỗi token thấp hơn, và vì phần 09, vector kém được nắn hơn. Đây là chỗ vấn đề data mixture quay lại dưới dạng chất lượng biểu diễn.

15.2 Ảnh không có ID để tra

Model đa phương thức không thể tra bảng cho ảnh - ảnh không có token ID. Thay vào đó, một encoder biến mảnh ảnh thành vector d chiều trực tiếp.

Nhưng điểm mấu chốt là những vector đó được đưa vào cùng residual stream, cùng số chiều d, xen kẽ với vector của token văn bản. Từ góc nhìn của các block phía sau, không có gì phân biệt “vector đến từ chữ” và “vector đến từ ảnh”.

[vector ảnh] [vector ảnh] [E[id]] [E[id]] [E[id]] ...
        cùng d chiều, cùng một residual stream

Cách một model xử lý được nhiều phương thức, vì vậy, không phải là có nhiều model bên trong. Nó chỉ là nhiều cách khác nhau để tạo ra vector d chiều cho một băng thông duy nhất.


16Những hiểu lầm phổ biến

Hiểu lầm Thực tế
“Embedding lưu nghĩa của từ” nó lưu vị trí học được từ phân bố đồng hiện; nghĩa là cách ta diễn giải vị trí đó
“Vector gần nhau thì nghĩa giống nhau” gần nhau nghĩa là dùng được ở cùng chỗ, nên từ trái nghĩa thường rất gần nhau
“Embedding của LLM dùng được cho semantic search” khác đơn vị và khác mục tiêu train; model chuyên cho retrieval thường tốt hơn
“Vector ở lớp cuối là embedding của token đó” nó mang cả ngữ cảnh và được train để đoán token kế tiếp, không để mô tả token hiện tại
“One-hot nhân ma trận là cách embedding chạy” về toán thì đúng, nhưng cài đặt là gather; khác nhau hàng tỷ phép tính
“Model biết thứ tự từ vì nó đọc từ trái sang phải” self-attention không có thứ tự; vị trí phải được cộng vào tường minh
“RoPE cho context dài vô hạn” nó ngoại suy tốt hơn learned, nhưng vượt xa độ dài train vẫn cần hiệu chỉnh
“Mỗi chiều embedding có một ý nghĩa” thông tin nằm rải trên nhiều chiều; không có trục nào mang tên một khái niệm
“Token nào trong vocabulary cũng được học như nhau” gradient thưa nên token hiếm có thể giữ nguyên vector khởi tạo
“Buộc chung bảng vào và bảng ra luôn tốt hơn” tiết kiệm tham số nhưng ép hai vai trò khác nhau dùng chung biểu diễn

Embedding là chỗ ngôn ngữ trở thành hình học

Có một khoảnh khắc chuyển pha trong toàn bộ pipeline, và nó nằm đúng ở đây.

Trước embedding, mọi thứ còn là ký hiệu: ký tự, token, số hiệu. Ta có thể so sánh chúng bằng nhau hay khác nhau, nhưng không thể hỏi “cái này gần cái kia bao nhiêu”. Sau embedding, mọi thứ là điểm trong một không gian có khoảng cách, có hướng, có phép cộng. Từ chỗ này trở đi, câu hỏi về ngôn ngữ trở thành câu hỏi về hình học - và hình học thì tính được bằng gradient descent.

Những gì nên mang theo:

  • embedding là phép tra bảng, và bảng đó là tham số học được, không phải từ điển;
  • cấu trúc trong không gian đến từ phân bố đồng hiện, không từ định nghĩa;
  • gần nhau nghĩa là thay thế được cho nhau, nên trái nghĩa thường rất gần;
  • vị trí không tự có, nó phải được cộng vào, và cái đáng mã hoá là khoảng cách;
  • residual stream là băng thông d chiều dùng chung, nên d ràng buộc cả model;
  • vector ở lớp cuối phục vụ việc đoán token kế tiếp, không mô tả token hiện tại;
  • gradient của embedding rất thưa, nên tần suất quyết định chất lượng vector;
  • embedding trong LLM và embedding cho retrieval là hai thứ khác nhau.

Và thứ mà một token đã đi qua trong bài này:

"đá"  ->  id 7021  ->  E[7021]  ->  cộng vị trí
      ->  residual stream qua N block
      ->  vector d chiều mang cả ngữ cảnh
      ->  logits trên V token

Ở bài tiếp theo, ta dừng lại đúng ở đoạn giữa - phần mà bài này cố tình gọi gọn là “qua các block”. Đó là chỗ attention quyết định mỗi vị trí được đọc những vị trí nào, và vì sao cơ chế đó lại là thứ làm Transformer khác hẳn mọi kiến trúc trước nó.


Tài liệu tham khảo

Trương Đức Dũng

AI Engineer - Hà Nội

Tập trung vào LLM, RAG và các hệ thống AI cần chạy ổn định trong môi trường vận hành thật.