- Workshop này tập trung vào việc huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu bằng PyTorch, không sử dụng trọng số tiền huấn luyện hay các thư viện transformer cấp cao, nhằm giúp người tham gia hiểu sâu về kiến trúc và quá trình xây dựng LLM.
- Dự án sử dụng một mô hình nhỏ dựa trên GPT2 và mã hóa cấp ký tự đơn giản, cho phép thực hành với tài nguyên hạn chế như máy tính xách tay cá nhân (16GB RAM) hoặc Google Colab.
- Nội dung chia thành bốn khối xây dựng chính của LLM – Bộ mã hóa, Kiến trúc mô hình, Vòng lặp huấn luyện, và Suy luận – đồng thời đi sâu vào các thành phần cốt lõi của Transformer như multi-head self-attention, MLP, residual connections và layer normalization.
Training an LLM from Scratch, Locally — Angelos Perivolaropoulos, ElevenLabs
- Xây dựng LLM từ đầu: Học cách huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu (
from scratch) bằng PyTorch, không dùngtrọng số tiền huấn luyện(pre-trained weights), để hiểu rõ cơ chế vận hành. - Chuẩn bị môi trường: Có thể huấn luyện
mô hìnhcục bộ trên máy tính xách tay (yêu cầu tối thiểu 16GB RAM) hoặc sử dụngGPUmiễn phí trên Google Colab; cài đặt các thư viện cần thiết nhưtorch,numpy,tqdm,tiktoken. - Tokenization cho người mới bắt đầu: Sử dụng
mã hóa cấp ký tự(character-level tokenization) cho cácmô hìnhnhỏ để giảmkích thước từ vựngvà tăng tốc độ huấn luyện, đặc biệt khi dữ liệu hạn chế. - Bốn khối xây dựng cốt lõi: Nắm vững bốn phần thiết yếu của một dự án LLM:
Bộ mã hóa(Tokenizer),Kiến trúc mô hình(Model Architecture),Vòng lặp huấn luyện(Training Loop), vàPhần suy luận(Inference). - Thành phần Transformer: Hiểu các khối kiến tạo chính của kiến trúc
transformer:Multi-Head Self-Attention(để hiểu mối quan hệ token),MLP(Mạng Nơ-ron Truyền Thẳng),Residual Connections(để ổn định quá trình huấn luyện) vàLayer Normalization(để ngăn chặn các activation bùng nổ). - Vòng lặp huấn luyện quan trọng: Nhận thức rằng
vòng lặp huấn luyệnvà các kỹ thuậttinh chỉnh(fine-tuning),hậu huấn luyện(post-training) là yếu tố then chốt quyết định hiệu suất củamô hình, ngay cả khi kiến trúc cơ bản tương tự. - Mở rộng mô hình: Để mở rộng
mô hìnhlên quy mô lớn hơn hoặc cho cáctrường hợp sử dụngphức tạp hơn, cân nhắc sử dụng cácbộ mã hóatiên tiến hơn nhưbyte pair encodingvà chuẩn bị tài nguyên huấn luyện lớn hơn.
- Mô hình Ngôn ngữ Lớn (LLM) — Large Language Model (LLM)
- từ đầu — from scratch
- trọng số tiền huấn luyện — pre-trained weights
- suy luận — inference
- bộ mã hóa — tokenizer
- mã thông báo — token
- embedding — embedding
- vòng lặp huấn luyện — training loop
- chuẩn hóa lớp — layer normalization
- kết nối dư — residual connections
Giới thiệu và Mục tiêu Workshop
Cảm ơn các bạn đã tham gia buổi workshop này. Đây sẽ là một buổi workshop thực hành, hy vọng các bạn có thể bắt tay vào thực hiện một dự án rất thú vị.
Đầu tiên, một chút về bản thân tôi. Tên tôi là Angelos, tôi là trưởng nhóm Speech to Text tại 11 Labs. Tôi là một kỹ sư nghiên cứu, vì vậy tôi dành phần lớn thời gian để huấn luyện các mô hình mới, làm việc về suy luận (inference) và cũng tham gia vào mảng sản phẩm. Tôi chịu trách nhiệm, thật không may, đôi khi phải trò chuyện với khách hàng, điều mà tôi không thực sự yêu thích. Tôi là kiểu người thích đi thẳng vào nghiên cứu, huấn luyện các mô hình và tạo ra những thứ tiên tiến, mạnh mẽ. Hiện tại, tôi đang phát triển các mô hình thời gian thực để phiên âm, đặc biệt dành cho các tác nhân. Nếu bạn chưa biết, chúng tôi có mô hình scribe v2 mà nhóm tôi đã huấn luyện, hiện là mô hình phiên âm tốt nhất trên thị trường xét về các điểm chuẩn công khai phổ biến. Vì vậy, nếu bạn cần bất kỳ trường hợp sử dụng phiên âm nào, hãy dùng thử. Tôi nghĩ nó khá tốt.
Giờ đây, trong khuôn khổ workshop, hôm nay chúng ta sẽ huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu (from scratch). Tức là, không có trọng số tiền huấn luyện (pre-trained weights), không có bất cứ thứ gì bạn có thể lấy trực tuyến từ một thư viện transformers. Chúng ta sẽ làm việc hoàn toàn trên PyTorch và một số thư viện cơ bản. Chúng ta có thể đi sâu hơn một cấp độ và không sử dụng PyTorch, nhưng tôi không muốn gây áp lực quá nhiều cho bạn. Vì vậy, tôi nghĩ PyTorch là một cấp độ tốt, và đây sẽ là một dấu hiệu tốt cho thấy các kỹ sư nghiên cứu thực thụ trong các phòng thí nghiệm lớn thiết kế các mô hình của họ như thế nào. Mọi thứ nâng cao hơn sau đó chủ yếu là các tối ưu hóa và làm cho quy mô tốt hơn, lớn hơn và cải thiện cho các trường hợp sử dụng cụ thể. Vì vậy, những gì bạn sẽ làm hôm nay là gần như 80% chặng đường để tạo ra một mô hình từ đầu.
Chuẩn bị môi trường
Nếu bạn có thể truy cập mã QR này, bạn sẽ tìm thấy kho lưu trữ GitHub. Tôi sẽ chuyển sang đó ngay bây giờ. Chúng ta sẽ để mã này thêm một chút. Bạn có hai lựa chọn. Một là huấn luyện mô hình cục bộ trên máy tính xách tay của bạn. Nếu bạn có 16GB bộ nhớ, bạn sẽ có thể làm được. Đó là một mô hình nhỏ, rất nhỏ mà bạn có thể huấn luyện nhanh chóng. Nếu không, và tôi đoán là không nhiều người có nguồn tài nguyên mạnh, Google Colab có thể là một lựa chọn khác. Google Colab cung cấp GPU miễn phí mà bạn có thể sử dụng để huấn luyện một mô hình nhỏ như vậy. Vậy đó sẽ là lựa chọn của bạn.
Trong khi tôi để mã QR này thêm một chút, sau đó chúng ta có thể đi vào kho lưu trữ thực tế.
Nguồn cảm hứng
Để bạn có một số ý tưởng về nguồn cảm hứng của dự án này. Lần đầu tiên tôi tiếp xúc với transformers nói chung là qua video này của Andrej Karpathy, một trong những đồng sáng lập của OpenAI, có tên là nano GPT. Đối với tôi, đó là một dự án rất truyền cảm hứng. Về cơ bản, đó là điều đã truyền cảm hứng cho workshop này. Nó ở cấp độ thấp hơn một chút so với những gì chúng ta sẽ làm bây giờ, đi sâu hơn một chút vào cách bạn sử dụng NumPy cho các phép tính. Nhưng tôi nghĩ đó là một bản thiết kế tốt mà chúng ta có thể làm theo để tạo ra một mô hình từ đầu.
Để tôi di chuyển cái này lên màn hình. Nhưng vâng, nếu bạn chưa biết dự án này, tôi nghĩ đó là một phần giới thiệu tuyệt vời. Nếu bạn đã biết dự án này và đã huấn luyện các LLM như vậy trước đây, điều này có thể có vẻ hơi đơn giản đối với bạn. Nhưng tôi nghĩ có những cách chúng ta có thể mở rộng workshop này để hướng tới việc tạo ra một cuộc thi nhỏ, mà tôi rất muốn xem các bạn có thể đưa ra những gì.
Kiến trúc Mô hình và Các Khối Xây dựng
Đối với workshop cụ thể này, chúng ta sẽ làm việc với một mô hình rất nhỏ dựa trên kiến trúc GPT2. Đây là một kiến trúc hơi cũ, nhưng các phần cơ bản và nền tảng của nó về cơ bản không thay đổi quá nhiều, và chúng ta sẽ tìm hiểu về chúng một chút nữa.
Bốn khối xây dựng bạn cần để huấn luyện một mô hình là:
-
Bộ mã hóa (
Tokenizer): Đây thường là điều đầu tiên bạn nghĩ đến khi tạo bất kỳmô hình transformermới nào. Tùy thuộc vàotrường hợp sử dụngcủa bạn, bạn sẽ muốn sử dụng mộtbộ mã hóakhác nhau. Nếu bạn muốn huấn luyện mộtmô hìnhrất lớn có khả năng tạo văn bản cho nhiều ngôn ngữ, bạn sẽ cần mộtbộ mã hóakhổng lồ, điều đó có nghĩa là bạn cũng sẽ cần một lượng dữ liệu khổng lồ để huấn luyện nó. Nhưng đối với mộtmô hìnhnhỏ hơn, mộtbộ mã hóanhỏ hơn vớiembeddingnhỏ hơn sẽ hoạt động tốt nhất và huấn luyện nhanh nhất khi bạn bị giới hạn về dữ liệu, đây là trường hợp của chúng ta hiện tại. CácMô hình Ngôn ngữ Lớn (LLM)không "thấy" văn bản; chúng làm việc với cácembeddinghoặc vector. Vì vậy, chúng ta cần một loại biểu diễn nào đó của các vector đó đểmô hìnhcó thể xử lý. Ở đây, chúng ta sẽ sử dụngmã hóa cấp ký tự(character-level tokenization) chỉ vì nó có số lượngmã thông báokhả thi thấp nhất. Trong trường hợp của chúng ta, trên tập dữ liệu, sẽ chỉ có 65embeddingvì có 65 ký tự khác nhau sẽ xuất hiện trong dữ liệu huấn luyện của chúng ta. Cách nó hoạt động là chúng ta sẽ sử dụng thư việnstorynày để chuyển các chuỗi thành số nguyên, và các chỉ mục này sau đó sẽ được chuyển thànhembeddingthông qualớp embeddingkhi chúng ta huấn luyệnLLM. Đó là mộtbộ mã hóarất đơn giản, sử dụng hàmenumeratetừ Python và sau đó chọn mục cụ thể đã được chọn với từ điển này. Như tôi đã nói trước đây, chúng ta sử dụngcấp ký tựvì nó dễ huấn luyện hơn. Vì chúng ta chỉ có 65mã thông báo, điều đó có nghĩa là các kết hợpbiogramsẽ là 65*65, tức là 4.225biogramkhả thi.Biogramvề cơ bản là khi bạn có mộtmã thông báovà sau đó bạn dự đoánmã thông báotiếp theo sau nó. Khái niệm vềbiogramnày là một khái niệm rất quan trọng khi bạn huấn luyệntransformers vì bạn muốnmô hìnhcủa mình nhìn thấy càng nhiềubiogramkhả thi càng tốt. Vì vậy, nếu bạn có mộtmô hìnhvới, giả sử, 200.000mã thông báo, bạn cần ít nhất 200.000mã thông báobình phương dữ liệu để có thể huấn luyện từ đầu một cách rất tốt. Hoặc ít nhất đây là độ lớn mà bạn đang tìm kiếm. Trong trường hợp của chúng ta, 4.000biogramlà rất khả thi. Tập dữ liệu này rất có thể sẽ bao gồm tất cả cácbiogramnhiều lần. Nếu chúng ta cố gắng huấn luyện bằng cách sử dụng mộtbộ mã hóađầy đủ, điều này sẽ không bao giờ hội tụ. Chúng ta có thể huấn luyện nó hàng giờ đồng hồ và sau đómô hìnhcủa chúng ta sẽ không bao giờ có thể đạt được kết quả tốt. Vấn đề vớibộ mã hóacấp ký tựlà chúng không mở rộng tốt vì cách cácmô hìnhhoạt động là chúng cần hiểu mối tương quan giữa cácmã thông báokhác nhau. Vì vậy, bạn có thể dễ dàng có một mối tương quan nói "the sky is blue" (bầu trời xanh) – nhữngmã thông báonày kết hợp lại có rất nhiều ý nghĩa. Nhưng "sky" (bầu trời) và sau đó "is" (là) và sau đó "bl" thì hơi khó hơn chomô hìnhđể có thể tạo ra sự chú ý tốt đến cácmã thông báonày. Vì vậy, điều này sẽ hoạt động khá tốt cho ví dụ của chúng ta. Nhưng nếu bạn muốn mộtmô hìnhrất, rất tốt, một là nó sẽ tốn kém để huấn luyện vì tất nhiên, bạn phải tạo ra rất nhiềumã thông báotrong quá trìnhsuy luậnvà trong quá trình huấn luyện. Nhưng nó cũng sẽ không bao giờ hội tụ thành một cái gì đó tốt vì cácmã thông báokết hợp không có quá nhiều ý nghĩa. -
Kiến trúc
mô hình: Thành thật mà nói, hầu hết cácmô hìnhít nhất trong giai đoạn chúng ta sẽ làm việc đều rất giống nhau, chúng chỉ làmô hình giải mã hướng nhân quả(decoder-only causal model) có cách sử dụngcơ chế tự chú ý nhân quả(causal self-attention) rất tương tự và cáclớp MLP(MLP layers) tương tự,chuẩn hóa lớp(layer norms) tương tự và tất cả các thứ tương tự mà chúng ta sẽ tìm hiểu. Vì vậy, nếu bạn biết cách tạo ra nhữngmô hìnhnhỏ này, rất dễ dàng để thực hiện quy trình tương tự cho cácmô hìnhlớn hơn, mới hơn. Nhưng tất nhiên, cácmô hìnhmới hơn sẽ chuyên biệt hơn nhiều chongữ cảnhdài hơn và về cơ bản được kiến trúc hóa theo cách có thể mở rộng quá trình huấn luyện đến càng nhiềumã thông báocàng tốt, điều mà chúng ta sẽ không cần trong trường hợp này. -
Vòng lặp huấn luyện (
Training Loop): Đây thường là phần quan trọng nhất khi bạn huấn luyện mộtmô hìnhmới. Nếu bạn kiểm tra sự khác biệt giữaGPT4,GP40vàGPT5hoặc thậm chí nếu bạn quay trở lại trước đó, điều bạn sẽ thấy chủ yếu là quá trìnhtiền huấn luyện(pre-training) thường rất giống nhau. Chính quá trìnhtinh chỉnh(fine-tuning) vàhậu huấn luyện(post-training), và về cơ bản những gì bạn sử dụng với cùng mộtmô hìnhcơ sở hoặcmô hìnhcơ sở rất giống nhau và cách bạn huấn luyện nó mới thực sự tạo ra sự khác biệt lớn về hiệu suất. Và bây giờ chúng ta thấy, ví dụ,Gemini 3ra mắt và sau đó có rất nhiềuđiểm chuẩntốt, và sau đó3.1ra mắt có hiệu suất gấp đôi trong một sốđiểm chuẩn, điều này thật điên rồ. Rõ ràng, cácmô hìnhrất giống nhau, nhưng thực tế trong quá trình huấn luyện, họ đã huấn luyệnmô hìnhmới theo những cách thông minh hơn để cải thiện hiệu suất một cách đáng kể. -
Phần
suy luận(Inference): Cuối cùng, tất nhiên là phầnsuy luận, phần này sẽ rất dễ dàng đối với chúng ta vì đây sẽ là mộtmô hìnhnhỏ có thể chạy ở mọi nơi. Vì vậy, đó sẽ là một phần rất đơn giản của các khối xây dựng.
Điều kiện tiên quyết và Bắt đầu
Bất kỳ máy tính xách tay nào cũng có thể được, miễn là có ít nhất 16GB RAM. Bạn cũng có thể làm việc với các máy tính xách tay nhỏ hơn, nó sẽ chỉ chậm hơn một chút. Các máy tính xách tay lớn hơn, bạn có thể tăng kích thước lô (batch sizes) cao hơn để huấn luyện nhanh hơn. Python 3.12 và tôi hy vọng rằng hầu hết các bạn đều có một số ý tưởng về cách viết Python. Nếu bạn không biết, tôi nghĩ bạn vẫn có thể sao chép-dán mọi thứ cho đến khi có cái gì đó hoạt động hoặc yêu cầu trợ giúp. Việc huấn luyện này sử dụng chip Apple Silicon (kiến trúc MPS), CUDA hoặc CPU, vì vậy bạn về cơ bản có thể hỗ trợ mọi thứ.
Về việc bắt đầu, để mọi thứ dễ dàng hơn, tôi đang sử dụng uv cho dự án này. Vì vậy, nếu bạn có máy tính xách tay, bạn có thể cài đặt uv trên máy của mình nếu chưa. Nó khá đơn giản và lý do uv khá đơn giản là bạn chỉ cần chạy uv sync và nó tạo ra một môi trường ảo (virtual env) cho bạn và làm cho cuộc sống của bạn dễ dàng hơn. Chúng ta sẽ viết mã trong Scratchpad hoặc nếu bạn đang sử dụng Google Colab, điều mà thành thật mà nói, nếu internet của bạn không tốt lắm, có lẽ đó là một ý hay hơn. Nếu bạn chỉ cần truy cập và tạo một dự án Colab mới, bạn có thể chạy lệnh này để cài đặt những gì chúng ta cần, đó là torch, numpy, tqdm và tiktoken. tiktoken chủ yếu để kiểm tra mọi thứ.
Tokenization và Bảng Nhúng
Đó là sự đánh đổi của chúng tôi, nhưng đó là sự đánh đổi mà chúng tôi sẵn sàng chấp nhận vì chúng tôi đang chạy một model nhỏ. Trong tương lai, nếu bạn muốn mở rộng model này lên một cái gì đó tốt hơn, nếu bạn muốn huấn luyện một LLM thực sự và bạn sẵn lòng huấn luyện trong khoảng một tuần hoặc sử dụng các GPU lớn hơn, sử dụng một tokenizer phù hợp – chẳng hạn như byte pair encoding là cách phổ biến nhất để thực hiện tokenization hiện nay. Về cơ bản, bạn lấy tất cả dữ liệu huấn luyện của mình, tìm tất cả các mẫu chung và kết hợp các mẫu ký tự chung đó thành các mã thông báo cụ thể mà sau đó bạn có thể tái sử dụng và mô hình có thể hiểu được mối quan hệ của chúng.
Cách điều này kết nối với bản thân mô hình là chúng ta có một bảng nhúng (embedding table) với kích thước bằng kích thước từ vựng (vocab size). Sau đó, như tôi đã nói trước đây, nó lấy một vectơ các số nguyên và sau đó trả về – xin lỗi, một danh sách các số nguyên và sau đó trả về một danh sách các vectơ, đó sẽ là các embedding của chúng ta. Và như tôi đã nói trước đây, nếu chúng ta sử dụng một tokenizer lớn như vậy, nó cũng sẽ lớn hơn gấp đôi kích thước mô hình của chúng ta, bởi vì nếu bạn chỉ nhân kích thước embedding của chúng ta là 384 cho mô hình chúng ta sẽ huấn luyện, thì đó đã là 25.000 tham số. Và trong trường hợp đó, nếu chúng ta sử dụng vocab của GPT-2 là 50.000, thì đó sẽ là 19 triệu tham số, tức là lớn hơn gấp ba lần mô hình, điều này sẽ không hợp lý trong trường hợp của chúng ta.
Kiến trúc Transformer
Bây giờ, chuyển sang phần tiếp theo của workshop này, đó là bản thân transformer. Như tôi đã đề cập trước đây, các transformer đã trở nên phổ biến (commoditized). Cách transformer hoạt động là có nhiều lab khác nhau tìm ra các tối ưu hóa khác nhau, nhưng về nguyên tắc, các tối ưu hóa đó chủ yếu là về việc "chúng ta có ý tưởng cơ bản này hoạt động rất tốt, làm thế nào để chúng ta có thể làm cho nó huấn luyện nhanh hơn và có ngữ cảnh lớn hơn?". Mọi người tìm ra những cách tối ưu hóa hơn là nhất thiết phải phát minh lại bánh xe.
Đối với điều này, ít nhất có một số phương pháp lai phức tạp hơn. Vì vậy, tôi sẽ không đi quá sâu vào cách transformer hoạt động. Và cũng để chứng minh rằng bạn không nhất thiết phải hiểu sâu sắc cách transformer hoạt động để có thể huấn luyện một cái gì đó như thế này. Khi tôi lần đầu thực hiện dự án này, tôi không hề biết transformer hoạt động như thế nào và tôi vẫn chưa hiểu nhiều lắm vào cuối dự án trước tôi đã làm. Nhưng khi bạn càng làm việc với nó và bạn càng có động lực để tiếp tục, bạn có thể hiểu tất cả các khái niệm khác nhau cùng nhau và cách chúng kết hợp với nhau, cũng như lý do tại sao chúng lại như vậy.
Các Khối Kiến Tạo của Transformer
Để quay lại bức tranh lớn, transformer đang sử dụng bốn khối kiến tạo khác nhau này.
Multi-Head Self-Attention
Thứ nhất là multi-head self-attention. Attention là điểm khác biệt khiến transformer khác với các mạng nơ-ron khác, đó là chúng thực sự có thể "chú ý" (attend) đến các mã thông báo trước đó và hiểu mối quan hệ giữa các mã thông báo mà tôi đã đề cập. Và đó là nơi attention xuất hiện. Tất nhiên, attention của bạn càng lớn, mô hình càng hiểu rõ những mối quan hệ đó. Quay trở lại điều tôi đã nói, đó là những gì các lab lớn như Gemini đang cố gắng làm: tạo ra một triệu ngữ cảnh và họ đang tìm cách vì nếu bạn chỉ cố gắng sử dụng một triệu ngữ cảnh cho một mô hình như thế này, nó sẽ dễ dàng bị lỗi, phép toán sẽ không hoạt động. Vì vậy, đó là lúc các kỹ sư từ các nhà nghiên cứu từ Gemini tìm ra cách để làm cho nó hoạt động và đó là điều tạo nên sự khác biệt, nhưng về cơ bản, đó vẫn là cùng một kiến trúc.
MLP (Mạng Nơ-ron Truyền Thẳng)
Tiếp theo là MLP hoặc feed forward network, về cơ bản nó lấy các mối quan hệ khác nhau giữa các mã thông báo đó. Và trong khi lấy chúng, bản thân các mối quan hệ được sắp xếp, nó kết hợp chúng lại với nhau để có thể tạo ra các logit mà sau đó sẽ là – về cơ bản, nó lấy ngữ cảnh và sau đó sắp xếp nó theo cách mà mô hình có thể tạo ra các logit và sau đó tạo ra các mã thông báo. Tôi hy vọng đó là một lời giải thích hợp lý.
Residual Connections (Kết nối Dư)
Sau đó, bạn có các residual connections, về cơ bản là để mô hình không phải "tự phát minh lại" sau mỗi layer. Residual về cơ bản có nghĩa là mỗi layer mà đi qua các kích hoạt (activations) – bởi vì như chúng ta sẽ nói sau, transformer được xây dựng trên nhiều layer khác nhau mà chúng ta truyền các activations qua từng layer một – và residuals ở đó để mỗi activation không hoàn toàn khởi động lại mọi thứ từ đầu. Nó chỉ thay đổi chúng một chút. Vì vậy, nó chỉ lấy đầu vào trước đó và tạo ra một sự khác biệt nhỏ và thêm vào. Layer tiếp theo làm điều tương tự và layer tiếp theo cũng làm điều tương tự và điều này sẽ tiếp tục. Bằng cách này, mô hình không, mỗi layer không tạo ra một thay đổi lớn đối với bản thân đầu vào, và mô hình có thể ổn định hơn trong quá trình huấn luyện.
Layer Normalization (Chuẩn hóa Lớp)
Cuối cùng, chuẩn hóa lớp (layer normalization) có một vai trò rất tương tự trong việc có thể – layer normalization cho phép bạn thu nhỏ các activation đó theo cách cho phép các activation đó không bùng nổ thành các giá trị rất lớn. Vì vậy, nếu một layer nhân activation của bạn lên 10 lần, giả sử, layer norm sẽ đẩy nó trở lại các giá trị bình thường. Vì vậy, nó không đi theo kiểu 10x 10x 10x và sau đó bạn có thể có hàng triệu giá trị bắt đầu từ 0.5 và sau đó kết thúc ở 10 triệu. Đó là mục đích của layer norm. Nhưng một lần nữa, đây chỉ là các khối kiến tạo. Bạn không nhất thiết phải biết tại sao chúng ở đó và mục đích của chúng là gì. Bạn sẽ học điều này khi bạn bắt đầu làm việc nhiều hơn với các mô hình này và hiểu tại sao những quyết định này được đưa ra, bởi vì tất cả chúng, như tôi đang nói với bạn, tất nhiên được thực hiện bởi – chúng ta có một ý tưởng nhất định, tất cả điều này không hiệu quả, vậy hãy thêm cái này để nó hoạt động. Xin lỗi, mời bạn tiếp tục.
[transcript bị gián đoạn]
Xin lỗi vì đã. Vâng. Vì vậy, tôi đã đi qua phần tokenization. Đó là điểm trước đó của tôi. Và đó là nếu bạn có thể quay lại, và bạn sẽ phải đi đi lại lại qua các slide khi bạn tự làm việc với mô hình của mình, bởi vì bạn sẽ phải sao chép-dán các phần hoặc tự mình tìm ra chúng. Nhưng nó đã giải thích tại sao chúng tôi chọn tokenization cấp ký tự và những lựa chọn khác mà chúng tôi có. Sau đó, đối với transformer, tôi đã giải thích các khối kiến trúc khác nhau trong bức tranh lớn.
Tokenization trong Ngôn ngữ Lập trình
Và bây giờ chuyển sang cách điều này trông như thế nào trong mã nguồn, bởi vì tất cả những gì tôi mô tả cho bạn thực sự rất ít code bạn cần để triển khai chúng. Đầu tiên, chúng ta bắt đầu với cơ sở của – xin lỗi, mời bạn tiếp tục.
[transcript bị gián đoạn]
Khi nói đến Python, bởi vì ít nhất trong tiếng Anh, bạn biết bạn có các từ, từ vựng khá cố định, nhưng trong Python, bạn có các biến riêng và vân vân. Vậy tokenization hoạt động như thế nào trong môi trường đó? Một mã thông báo sẽ là gì? Xin lỗi.
[transcript bị gián đoạn]
Vì vậy, trong một ngôn ngữ lập trình, tất nhiên bạn có cú pháp lập trình, các từ khóa (keywords), nhưng sau đó bạn cũng có các biến thông thường, tên hàm và vân vân. Vậy khi nói đến tokenization, điều đó hoạt động như thế nào?
Vâng, như tôi đã đề cập trước đây, chúng ta sẽ sử dụng một tokenizer cấp ký tự cho dự án này. Nhưng hầu hết các lab lớn không sử dụng tokenization cấp ký tự. Họ sử dụng thứ tôi đã đề cập trước đó, tokenizer BPE hoặc byte – về cơ bản, những gì họ làm là bạn xem xét dữ liệu huấn luyện của mình. Giả sử bạn có hàng nghìn tỷ mã thông báo và dữ liệu huấn luyện của bạn sẽ bao gồm code như bạn đã đề cập. Và cách nó hoạt động là nó sẽ chỉ tìm kiếm các mẫu chung. Vì vậy, nếu bạn có nhiều dữ liệu huấn luyện là code thì bạn sẽ thấy và sau đó bạn sẽ nhận ra "các for loop có vẻ là một ứng cử viên tốt để trở thành một mã thông báo". Vì vậy, for chắc chắn sẽ là một mã thông báo và sau đó bạn có thể có một số từ như enumerate, bạn thấy khá phổ biến trong dữ liệu huấn luyện. Vì vậy, đó sẽ là một mã thông báo khác. Vì vậy, bạn sẽ xem xét tất cả các mã thông báo khác nhau này và sau đó tạo tokenizer này dựa trên mối quan hệ chung giữa chúng. Tất nhiên, có lẽ một số ngôn ngữ như Pascal có thể không rất phổ biến, vì vậy các từ khóa của Pascal có thể không rất phổ biến. Mặc dù tôi nghĩ rằng có lẽ có một đại diện tốt trong các tokenizer đó, nhưng một số ngôn ngữ khác như những ngôn ngữ chỉ có khoảng trắng (whitespace only languages) này có lẽ sẽ không hoạt động rất tốt.
[transcript bị gián đoạn]
Câu hỏi của tôi thiên về. Một điều là các từ khóa của ngôn ngữ lập trình, nhưng sau đó bạn có tên biến của riêng bạn, không phổ biến giữa các chương trình khác nhau.
[transcript bị gián đoạn]
Tôi sẽ xem xét điều đó trong một khoảnh khắc.
[transcript bị gián đoạn]
Việc biến chúng thành mã thông báo, tôi không chắc điều đó giúp ích như thế nào.
Vì vậy, một lần nữa, không có cách cụ thể nào, không có con người nào tham gia vào quá trình này. Nó phụ thuộc vào dữ liệu huấn luyện bạn sử dụng để huấn luyện tokenizer này. Nếu code của bạn có foo và bar là các biến phổ biến, thì những thứ này có thể sẽ trở thành mã thông báo. Nếu tên biến của bạn rất lạ, như các ký tự ngẫu nhiên, thì có lẽ điều đó sẽ không có trong tokenizer. Và khi điều này xảy ra, tokenizer sẽ quay trở lại tokenization cấp ký tự hoặc cấp byte. Vì vậy, nếu mã thông báo của bạn chỉ là những ký tự ngẫu nhiên, nó sẽ là mỗi ký tự hoặc có thể một số tổ hợp có thể đi cùng nhau dưới dạng các mã thông báo khác nhau, nhưng hầu hết chúng sẽ là các mã thông báo riêng biệt và điều đó sẽ khá khó khăn khi thực hiện inference và – vâng, có lẽ không phải là tốt nhất nếu bạn muốn inference hiệu quả.
Các Tham số của Transformer trong Mã nguồn
Nhưng vâng, quay trở lại phía transformer của vấn đề, như tôi đã nói, các model nhìn chung khá giống nhau. Và code để thực sự triển khai các transformer này cũng khá đơn giản và dễ viết. Nó chỉ khoảng 100 dòng, thực tế thường ít hơn thế.
Điều đầu tiên chúng ta phải chấp nhận là các tham số của transformer này nên trông như thế nào. Như tôi đã đề cập trước đây, vocab size là kích thước của tokenizer của bạn. Trong trường hợp của chúng ta sẽ là 65. Block size về cơ bản là sequence length hoặc context window. Trong trường hợp của chúng ta, đó sẽ là 256, rất rất nhỏ đối với các model này, nhưng bởi vì chúng ta đang huấn luyện một model cục bộ, đó là điều chúng ta phải làm. Các lab lớn hơn sẽ sử dụng context size một triệu cho những thứ như vậy. Nhưng nói chung, 16.000 là một điểm trung gian phổ biến.
Sau đó, chúng ta có các layers. Như tôi đã đề cập trước đây, transformer có nhiều layer và sau đó bạn chạy các activation qua từng layer đó. Chúng ta sẽ chọn một con số khiêm tốn là sáu. Và sau đó là attention heads – cách attention hoạt động là bạn sẽ có các head khác nhau cho attention sẽ "chú ý" đến những thứ khác nhau. Ví dụ, một trong các attention heads có thể đang xem xét dấu câu, có lẽ một attention head khác có thể đang xem xét ngữ pháp. Vì vậy, tất cả các attention heads khác nhau này đều "chú ý" đến một tính năng cụ thể của văn bản hoặc nếu bạn đang sử dụng âm thanh, một tính năng cụ thể của âm thanh, v.v.
Cuối cùng, đó là embedding dimension. Đó là kích thước thực tế của các vectơ của các mã thông báo mà bạn tạo ra. Trong trường hợp của chúng ta, chúng ta sẽ bắt đầu với 384. Đó là tiêu chuẩn cho GPT-2.
Giá trị Mã thông báo và Cấu trúc Mã Nguồn Cơ Bản
Tuy nhiên, một giá trị lớn hơn tất nhiên sẽ chứa nhiều thông tin hơn trên mỗi mã thông báo. Một giá trị nhỏ hơn sẽ chứa ít hơn. Nhưng đó là một giá trị khá tiêu chuẩn mà bạn có thể bắt đầu.
Về phần codebase chính, bạn có thể thoải mái sao chép một phần của nó nếu bạn muốn dành nhiều thời gian hơn để tìm hiểu. Tôi không muốn đi quá sâu vào tất cả các chi tiết nhỏ về cách mọi thứ hoạt động. Nhưng về cơ bản, bạn thường có một module tổng thể – trong trường hợp này, chúng ta sẽ gọi đó là GPT – và module cấp cao nhất đó sẽ bao gồm tất cả các module khác mà tôi đã mô tả trước đó. Trong trường hợp này, nó sẽ nhận config mà chúng ta đã có ở trên, và sau đó chúng ta sẽ tạo ra nó bằng cách sử dụng torch module dict ở đây, đơn giản vì đó là cách dễ nhất để triển khai. Nhưng tất cả đây đều là toán học; mọi thứ bạn thấy ở đây đều là các phép tính, như các phép nhân ma trận mà torch cho phép chúng ta trừu tượng hóa và làm mọi thứ dễ dàng hơn. Hầu hết tất cả những gì bạn thấy ở đây đều là các mạng nơ-ron (neural networks), dù nhỏ hơn hay lớn hơn, được kết hợp lại với nhau.
Kích thước Đầu vào và Tham số Mô hình
Một câu hỏi. Vâng, đúng không? Vâng. Đúng vậy. Vậy là có nhiều thứ liên quan đến số lượng tham số. Ừm, không hẳn. Xin lỗi, có lẽ bạn có thể nhắc lại câu hỏi bằng micro. Vậy thì, kích thước của chuỗi đầu vào có liên quan đến số lượng tham số trong
mô hìnhcủa bạn không? Không nhất thiết. Bạn có thể có các chuỗi lớn với cácmô hìnhnhỏ, hoặc các chuỗi nhỏ với cácmô hìnhlớn hơn. Điều chính mà điều này cho thấy về cơ bản là có hai cách bạn có thể huấn luyện mộtmô hình. Bạn có thể huấn luyện mộtmô hìnhcófull attention(toàn bộ sự chú ý). Giả sử nó nhìn vào toàn bộ sự chú ý. Tức là nó nhìn vào toàn bộ quá khứ, về cơ bản, đó là những gì chúng ta đang xây dựng ở đây hôm nay. Hoặc bạn có thể có mộtmô hìnhcửa sổ (windowed model). Đó là nơi mà hầu hết cácmô hìnhlớn hơn hiện tại chỉ nhìn vào một lượng nhất định trong quá khứ. Và tham số này là kích thước củacửa sổ ngữ cảnhđó, phải không? Đúng vậy. Đó là thứ chúng ta sẽ sử dụng để huấn luyện. Vì vậy,mô hìnhsẽ không thấy bất kỳ điều gì lớn hơn 256mã thông báotrong một chuỗi. Nếu bạn cố gắng vượt quá con số đó, nó sẽ... nó sẽ bị lỗi. Sẽ có vấn đề.
Tối ưu hóa Hiệu suất Mô hình
Nhưng giả sử chúng ta có đủ năng lực tính toán để tăng số lượng tham số nhằm giúp
mô hìnhsuy luận tốt hơn, phải không? Bạn sẽ tăng con số nào ở đây? Chẳng hạn, bạn sẽ tăng số lượnglớp,đầu chú ý(attention heads) hay...? Tôi sẽ tăng tất cả những gì bạn thấy ở đây, ngoại trừ có lẽ là kích thướcembedding(nhiều chiều). Tôi không nhớ chính xác cácmô hìnhkhác làm gì. Những con số chính xác đó, tôi nghĩ có vẻ hợp lý với tôi. Nhưng mọi thứ khác hoạt động tốt cho mộtmã thông báonhỏ, mộtmô hìnhnhỏ. Kích thướckhối(block size) 256 là rất nhỏ. Nếu bạn cố gắng chạy điều này trênJGPT, nó sẽ quên những gì bạn đã viết 10 câu trước đó. Nhưng bạn càng làm điều này lớn hơn, việc huấn luyện càng khó khăn hơn. Và đó là điều tôi đã nói về các luật mở rộng (scaling laws): bạn không thể chỉ đến đây và nói, "Được rồi, thực ra, tôi không muốn 256, tôi muốn độ dàingữ cảnh2 triệu." Bạn không thể huấn luyện như vậy, ít nhất là vớikiến trúchiện tại này. Đó là lý do tại sao các nhà nghiên cứu sau khiGPT-3.5ra đời, họ nói, "Được rồi, mọi người phàn nàn rằng chúng ta chỉ cókích thước ngữ cảnh16k. Chúng ta muốn 1 triệu. Làm thế nào để làm được điều đó? Bạn không thể chỉ thay đổi con số này. Bạn phải thay đổikiến trúcđể cho phép huấn luyện... [nếu không] nó sẽ hết bộ nhớ ngay lập tức." Vì vậy, đó là một trong những điều mà các nhà nghiên cứu hiện đang cố gắng tìm ra: làm thế nào chúng ta có thể tăng các con số này trong khi vẫn giữ cho quá trình huấn luyện ổn định, và vẫn có đủkhả năng tính toánđể thực hiện điều này? Tôi hy vọng điều này trả lời câu hỏi của bạn. Tuyệt vời. Cảm ơn bạn.
Kiến trúc Mô hình GPT
Vậy thì, điều đầu tiên chúng ta phải làm là, như tôi đã đề cập trước đây, tạo ra biểu diễn cấp cao nhất của mô hình của chúng ta mà bạn có thể thấy ở đây dưới dạng lớp GPT. Tôi sẽ không đi sâu quá nhiều vào chi tiết nữa, nhưng bạn sẽ muốn biết cách mô hình của mình hiểu các embedding và cách nó hiểu embedding vị trí. Tôi không muốn đi vào chi tiết, nhưng về cơ bản, các mã thông báo cần phải hiểu cả hai. Sau đó, bạn sẽ có tất cả các lớp mà tôi đã đề cập trước đó, và mỗi lớp được cấu hình như một khối (block), mà chúng ta sẽ xem định nghĩa của khối sau. Nhưng về cơ bản, một khối giống như một lớp của transformer bao gồm attention riêng của nó và chuẩn hóa lớp (layer norms) riêng của nó, và chúng liên kết với nhau trong quá trình forward pass. Cuối cùng, bạn có LM head, và về cơ bản, LM head lấy tất cả các đầu ra ở trên và kết nối chúng lại với nhau thành cái mà chúng ta gọi là logits, đó là phân phối của mã thông báo tiếp theo cần được tạo ra. Bởi vì, như tôi đã đề cập trước đó (có lẽ tôi đã nói, có lẽ không), cách transformer hoạt động là chúng dự đoán mã thông báo tiếp theo. Vì vậy, bạn lấy ngữ cảnh trước đó, bạn dự đoán mã thông báo tiếp theo và bạn phải lấy mẫu mã thông báo này từ phân phối này. Và LM head tạo ra phân phối này mà chúng ta lấy mẫu từ.
Cơ chế Forward Pass
Tiếp theo, phần quan trọng khác, mà lại khá đơn giản và tương tự ở hầu hết các mô hình này, là một forward pass. Về cơ bản, nó cho phép mô hình nhận đầu vào, đó là các mã thông báo chúng ta đã đề cập, và đẩy chúng qua tất cả các thành phần khác nhau mà tôi đã nhắc đến trước đó. Nó thực hiện một số bước tiền xử lý, biến mã thông báo thành token embedding và embedding vị trí. Nó cộng chúng lại với nhau, sau đó đi qua tất cả các khối của một transformer, tất cả các lớp khác nhau của transformer.
Và chỉ cần chạy
forward passcủatransformerđó, sau đó thực hiện cácchuẩn hóa lớp, rồi đi quaLM headđể nhận được mộtphân phốicủa cáclogitsmà chúng ta đã đề cập trước đó. Và điều này là khi bạn đang huấn luyện. Trong trường hợp này, nó cũng sẽ tính toánloss(sai số). Và sau đó, cuối cùng, bạn sẽ nhận đượclogitsvàlosslàm đầu ra củamô hình transformercủa bạn. Tôi có một số biểu đồ ở đây mà bạn có thể xem; chúng khá cơ bản nhưng về cơ bản, chúng thể hiện luồng: lấy cácID mã thông báo(token IDs), truyền chúng, biến chúng thànhtoken embeddingvàembedding vị trí. Bạn cộng chúng lại với nhau, bạn truyền chúng đếntransformer, sau đó quachuẩn hóa lớpgiúp đưa đầu ra vào một không gian màLM headcó thể hiểu dễ dàng hơn. Và sau đó bạn trả vềphân phốivềmã thông báotiếp theo nên là gì, với kích thước này. Như tôi đã đề cập trước đó, 65 là kích thước củatừ vựng(vocabulary) hoặc kích thướcmã thông báocủa chúng ta.
Tự chú ý (Self-Attention)
Giờ đây, tự chú ý (self-attention) phức tạp hơn một chút. Tôi không muốn đi quá sâu vào cách đó hoạt động. Nhưng về cơ bản, attention có nhiệm vụ hiểu các mối quan hệ giữa các mã thông báo. Về cơ bản, điều gì là quan trọng? Chẳng hạn, nếu tôi nói 'bầu trời màu xanh,' thì 'bầu trời' và 'xanh' có mối tương quan rất lớn. Vì vậy, đó là những gì attention làm: dựa trên cách bạn đã huấn luyện trọng số của mình, bạn sẽ hiểu mã thông báo nào nên chú ý đến nhau và đặt trọng tâm cao hơn vào những mối quan hệ cụ thể đó. Quay trở lại điều tôi đã nói trước đây về lý do tại sao bộ mã hóa token (tokenizer) lại quan trọng đến vậy: bởi vì 'bầu trời' và 'xanh' rất dễ để mô hình tạo ra mối quan hệ này, trong khi trong trường hợp của chúng ta, bạn phải kết hợp các nhóm mã thông báo khác nhau lại với nhau, các ký tự khác nhau lại với nhau, điều đó khó hơn một chút. Nhưng đó là những gì attention làm: nó là cái mà mã thông báo này nên chú ý trong quá khứ và cái nào có tầm quan trọng nhất. Và nó cũng có một forward pass như tất cả các khối khác này. Trong quá trình triển khai của bạn, hãy thoải mái sao chép các khối này. Có lẽ bạn có thể dành thêm thời gian để hiểu các sơ đồ và cách mọi thứ hoạt động. Nhưng như bạn có thể thấy, ngay cả một thứ phức tạp như attention cũng chỉ là một vài dòng mã để triển khai.
Khối MLP và Kiến trúc Transformer Block
Và, cuối cùng, khối MLP (MLP block) cũng như tôi đã đề cập trước đó... lại một lần nữa, tôi đã nói về attention đa đầu (multi-head attention) – tại sao attention lại có nhiều đầu (heads) – bởi vì mỗi đầu chú ý đến các phần khác nhau tạo nên ngôn ngữ. Và sau đó khối MLP nhận đầu ra của attention và tổng hợp tất cả các mối quan hệ khác nhau đó thành một thứ mà mô hình có thể hiểu tốt hơn, về cơ bản lại là một mạng nơ-ron (neural network) kết hợp mọi thứ thành một thứ dễ hiểu hơn cho LM head để sau đó có thể tạo ra phân phối cho các logits.
Vâng. Vâng. Không, bạn có thể ngắt lời. Chúng ta có các
khối transformertương tự. À... Nó có cùngkiến trúcgiữa các [khối] khác nhau không? Không, mỗikhốicótrọng sốriêng của nó. Mộtkhốilà cách mỗilớp... Về cơ bản, bạn sẽ có mỗilớpsẽ gọi cáckhối. Chúng thường có một phần khác cho một tiền tố khác chotrọng số, chẳng hạn nhưMLPsẽ khác... à, có lẽ chúng thường được gọi làkhối FFNthay vìMLP, nhưng vẫn sẽ là như vậy. Vì vậy, mỗikhốicótrọng sốriêng và mỗilớp... Vâng, mỗikhốicóMLPriêng. Vì vậy, mỗikhốicó... như bạn có thể thấy ở đây,attentionriêng của nó,chuẩn hóa lớp(layer norms) riêng vàMLPriêng. Đó là những gì về cơ bản tạo nên mộtlớpcủatransformer, và đó là điểm tôi sẽ nói sau. Mọi thứ kết hợp lại trongkhốimà chúng ta có thể thấy ở đây, về cơ bản là mộtlớpcủatransformercó một sốchuẩn hóa(normalization), chạyattentionđể có được mối quan hệ giữa cácmã thông báo, và sau đó cóMLPlấy những mối quan hệ đó và biến chúng thành một biểu diễn dễ dàng chomô hìnhđể tạo ra cáclogits. Và điều này... ôi. Đây là những gì tôi đang trình bày. Xin lỗi, tôi có hai màn hình khác nhau. Tôi không nên làm vậy. Nhưng vâng, đây làkhối transformer. Đây làkhốixây dựng cơ bản của mộttransformer. Nó có mộtchuẩn hóa lớp,attention, và mộtchuẩn hóa lớpkhác. Không phải tất cả cácmô hìnhđều có cấu hình như thế này, nhưng cái cụ thể này có. Và sau đóMLPnhận mọi thứ và tạo ra một biểu diễn có ý nghĩa để chúng ta có thể tạo ra các thế hệ. Và bạn có thể thấy ở đây một biểu đồ đơn giản hơn về cách hoạt động này.
Kết nối dư (Residual Connections)
Vâng.
nano GPTgốc cũng cókết nối dư(residual connection). Nhớ không? À, vậy thìresidualnên ở đó. Ừm... Không, không, ý tôi là trêncarpet NP, bạn có... Tôi không... tôi không nhớ; đã khá lâu rồi kể từ khi tôi... Được rồi. Giống như, tôi có ý tưởng từ đó, nhưng tôi đã xây dựng mọi thứ từ đầu, vì vậy tôi không thực sự nhớ liệu nó có hay không. Nhưng chúng làresidualnhư bạn có thể... Ý tưởng củaresiduallà, như bạn có thể thấy ở đây, thay vì thực hiệnx = attention, bạn thực hiệnx = x + attention. Vì vậy, bạn nhận được sự khác biệt... các giá trị củakích hoạt(activations) không thay đổi quá nhiều. Đó là ý tưởng của việc thực hiệnresidual.
Kiến trúc và Tham số Mô hình
Vâng, chúng tôi có sẵn một codebase cho phần này. Nếu bạn muốn tự triển khai, bạn có thể sao chép tất cả các lớp khác nhau này vào một tệp duy nhất tên là model.py. Đây về cơ bản là toàn bộ công thức toán học về cách các transformer hoạt động.
Như tôi đã đề cập trước đó, chúng ta phải quyết định kích thước của transformer. Số lượng tham số mà bạn thấy ở đây là khoảng 10 triệu tham số, dựa trên những gì tôi đã trình bày ở trên. Bạn có thể tự tính bằng cách cộng tất cả các tham số của các phần khác nhau trong các khối mà chúng ta đã có.
Cụ thể:
Token embeddingslà 65 * 384, chúng tôi đã nói đây làvectorchoembedding, nên nó là 25k.Positional embeddingslà 256 * 384, nhớ rằng 256 là độ dài chuỗi, độ dài chuỗi tối đa củamô hình. Vậy là thêm 98k tham số nữa.- Phần lớn nhất của logic nằm trong chính các
transformer block, nơi chứa hầu hết các tham số. Chúng ta có 4x — số 4 ở đây đến từ cáchattentionhoạt động, nơi bạn có cặpkey-query-key-value. Vì vậy, nó là một phần củaattentioncó bốn tham số khác nhau cho 384 (là số lượngmã thông báo) nhân với mối quan hệ giữa cácmã thông báođó. Vậy là 384per vectornhân với 384. Do đó, chúng ta có 590k choattention per layer. - Đối với
MLP(Multi-Layer Perceptron), chúng ta cũng có logic tương tự. Tôi không nhớ con số 1.536 này là gì, nhưng cuối cùng nó cho ra 1.2 triệu.
Tổng số lượng tham số mà chúng ta sẽ huấn luyện là 1.8 triệu tham số, con số này khá tốt và dễ dàng để huấn luyện trên hầu hết các thiết bị. Bạn có thể xem lại phần này; nó có nhiều chi tiết hữu ích để bạn nghiên cứu sâu hơn và tự tìm hiểu. Nhưng đây là ý tưởng tổng quát ở mức rất cao về cách các transformer này hoạt động.
Mục tiêu Huấn luyện và Cross-Entropy
Bây giờ chúng ta sẽ chuyển sang training loop (vòng lặp huấn luyện) và đó là phần quan trọng nhất của dự án này. Chúng ta sẽ xem cách huấn luyện transformer này để thực hiện những gì chúng ta muốn.
Mục tiêu của quá trình huấn luyện mà chúng ta sẽ thực hiện hôm nay là tạo ra một thứ gì đó dễ nhận biết: bạn sẽ biết khi nào mô hình bắt đầu hoạt động và "hiểu" (groks), tức là nó đã nắm được mục tiêu của mình. Sẽ rất dễ để hiểu rằng mô hình thực sự hoạt động.
Thứ hai, đó phải là thứ gì đó khá dễ để mô hình học. Nếu bạn dạy mô hình cách viết mã Python như một lập trình viên thi đấu, đó là một nhiệm vụ rất khó và chúng ta sẽ không thể làm được ở đây.
Trong trường hợp của chúng tôi, mục tiêu sẽ là tạo ra một Mô hình Ngôn ngữ Lớn (LLM) kiểu Shakespeare, có thể tạo ra các câu thơ từ các tác phẩm của Shakespeare.
Như chúng ta đã đề cập trước đó, các mô hình này học bằng cách dự đoán mã thông báo tiếp theo. Cách cross-entropy hoạt động là bạn lấy các mã thông báo hiện tại mà bạn muốn huấn luyện (ví dụ: từ t0, t1 đến tn) và sau đó bạn muốn dự đoán t1, t2 đến tn+1. Vì vậy, cách cross-entropy này hoạt động là bạn lấy chuỗi của mình và chỉ dịch chuyển nó đi một đơn vị, đó là những gì mô hình cần học để tính toán. Và tất nhiên, bạn không có mã thông báo cuối cùng, vì vậy bạn phải cắt nó. Sau đó, mô hình học cách dự đoán mã thông báo tiếp theo dựa trên logic này.
Tải Dữ liệu Huấn luyện
Đối với mã huấn luyện thực tế, trước tiên chúng ta cần tải dữ liệu. Chúng tôi có một hàm để tải dữ liệu. Dữ liệu đã có sẵn trong chính repo, nằm trong thư mục data. Đó là một tập hợp các dòng và câu thơ khác nhau từ Shakespeare, khoảng 1 triệu mã thông báo hoặc 1 triệu ký tự.
Việc tải dữ liệu rất đơn giản, thậm chí là quá đơn giản. Đó là một trong những điều bạn có thể tối ưu hóa. Về cơ bản, nó chỉ lấy tất cả các mã thông báo, chia thành tập hợp kiểm định (validation set) và tập hợp huấn luyện (training set), sau đó xáo trộn và lấy 256 batch các chuỗi mã thông báo từ văn bản và sử dụng nó để huấn luyện. Kích thước batch sẽ là 64. Vì vậy, nó lấy 256 chuỗi mã thông báo, 64 trong số đó, xếp chồng chúng lại và đưa vào mô hình để dạy nó cách huấn luyện. Đây là một data loader (trình tải dữ liệu) rất đơn giản. Thông thường, các data loader này có thể khá phức tạp, đặc biệt là khi bạn có context length lớn hơn, cách bạn tải dữ liệu là một phần rất lớn trong cách mô hình cần học. Nhưng trong trường hợp của chúng tôi, chúng tôi có một triển khai rất đơn giản.
Lựa chọn Thiết bị
Tiếp theo là cách mã hoạt động để sử dụng một device (thiết bị). Mã này hoạt động cho MPS, CUDA và CPU. Nó phụ thuộc vào những gì laptop của bạn hỗ trợ hoặc nếu bạn chạy trên Google Colab. Nếu bạn chạy Google Colab, nó sẽ phát hiện CUDA và sẽ khá nhanh. MPS cũng khá nhanh. CPU sẽ chậm nhất nhưng vẫn hoạt động khá tốt.
Điều chỉnh Tốc độ Học
Tiếp theo là learning rate (tốc độ học), đây là một phần quan trọng trong cách các mô hình có thể học. Cách các mô hình hoạt động là bạn thường bắt đầu với learning rate cao nhất có thể mà không làm mô hình trở nên không ổn định (hay nói cách khác là không làm mô hình "mất kiểm soát"). Về cách nó hoạt động, bạn bắt đầu ở learning rate rất cao, đó về cơ bản là lượng mà mô hình có thể học mỗi bước, trọng số của mô hình cần di chuyển bao nhiêu theo hướng bạn muốn. Nếu bạn có learning rate rất cao, bạn sẽ làm lệch mục tiêu của mình, vì vậy mô hình của bạn có thể đi chệch hướng rất nhanh. Do đó, bạn muốn sử dụng learning rate rất phù hợp để mô hình của bạn có thể huấn luyện tốt.
Và thường bạn có khái niệm warm-up (khởi động), nơi bạn bắt đầu với learning rate rất nhỏ để tất cả các tối ưu hóa, trọng số của mô hình có thể cố định vào những vị trí phù hợp để quá trình huấn luyện bắt đầu. Vì vậy, bạn bắt đầu ở learning rate rất thấp, tăng nhẹ cho đến khi đạt đến đỉnh, và sau đó ở đỉnh bạn bắt đầu giảm learning rate. Đó là điều chúng tôi gọi là weight decay (giảm trọng số), cho đến khi bạn đạt đến điểm bạn hài lòng. Đối với một số người, con số này là 0. Tôi thích không để nó về 0 vì sau đó khó để khởi động lại quá trình huấn luyện, nhưng đó là ý tưởng. Bạn muốn learning rate thấp hơn khi mô hình gần đạt đến sự hoàn hảo, vì vậy bạn muốn bắt đầu với những thay đổi rất lớn để tìm kiếm các cực tiểu cục bộ tốt hoặc cực tiểu toàn cục, và sau đó để nó hiệu chỉnh khi quá trình huấn luyện tiếp tục.
Vì vậy, chúng ta sẽ bắt đầu với warm-up nhỏ gồm 100 bước và sau đó chúng ta sẽ sử dụng cosine decay (phân rã cosine) cho đến số bước tối đa mà chúng ta sẽ thực hiện, trong trường hợp này là 5.000. Vì vậy, chúng ta sẽ bắt đầu từ mức rất thấp, đạt đỉnh ở 100 bước và sau đó bắt đầu giảm dần cho đến 5.000 bước. Và đó là những gì AdamW normalizer thực hiện. Về cơ bản, nó cho phép khái niệm kiểm soát learning rate này bằng cách sử dụng cosine decay, và đó là normalizer phổ biến nhất mà mọi người sử dụng, ít nhất là đã từng sử dụng. Bây giờ có những normalizer tốt hơn, nhưng đây là cái đơn giản nhất để bắt đầu.
Vòng lặp Huấn luyện Toàn diện
Đây là cách training loop đầy đủ sẽ trông như thế nào. Một lần nữa, không quá nhiều mã. Bạn chỉ cần khởi tạo config của mình, trong trường hợp này sẽ là sáu lớp, sáu attention heads (đầu attention), kích thước embedding là 384 và 256 là độ dài chuỗi. Bạn khởi tạo mô hình của mình, sau đó tạo optimizer và bắt đầu các bước huấn luyện. TQDM giúp theo dõi loss (mất mát) và tất cả những thứ tương tự. Bạn muốn loss của mình bắt đầu ở mức cao và sau đó tiếp tục giảm cho đến khi đạt đến mức chấp nhận được.
Và một phần quan trọng khác là các đánh giá để đảm bảo rằng mô hình của bạn thực sự hoạt động tốt. Đó là lý do tại sao val_loss (loss kiểm định) có mặt ở đây. Rất dễ để các mô hình nhỏ như thế này bị overfit (quá khớp) vì bạn không có nhiều dữ liệu. Vì vậy, loss có thể tiếp tục giảm, điều đó có nghĩa là những gì mô hình dự đoán và dữ liệu huấn luyện rất giống nhau. Loss của bạn có thể tiếp tục giảm rất nhiều. Nhưng thực tế, tại một thời điểm nào đó, mô hình của bạn có thể bị overfit trong trường hợp này, và khi nó bị overfit, mặc dù loss giảm nhưng hiệu suất của mô hình lại kém hơn. Đó là lý do tại sao chúng ta có val_loss, là một phần của dữ liệu mà mô hình chưa bao giờ nhìn thấy và chúng ta chạy một forward pass để lấy loss của phần dữ liệu đó. Nếu val_loss này rất thấp, điều đó có nghĩa là mô hình đang hoạt động tốt vì mô hình chưa bao giờ nhìn thấy dữ liệu này, vì vậy nó không thể ghi nhớ những thứ mà nó chưa từng thấy. Đó là những gì chúng ta có ở đây với val_loss.
Tôi sẽ không giải thích khái niệm về backwards losses (mất mát ngược), nhưng về cơ bản đó là cách trọng số của mô hình di chuyển theo hướng được tối ưu hóa. Vì vậy, với mỗi bước, chúng ta có kích thước batch là 256 mã thông báo, nhưng kích thước batch là 64. Chúng ta đẩy ma trận này qua mô hình của chúng ta, và sau đó nó học, và sau đó optimizer thực hiện một bước bổ sung và learning rate được điều chỉnh tùy thuộc vào các bước bạn đang ở hiện tại.
Và cuối cùng, để có một cách bổ sung, cứ sau 10.000 bước, bạn lưu checkpoint của mình để có thể khởi động lại quá trình huấn luyện nếu cần từ điểm đó. Chúng tôi cũng đang chạy inference (suy luận) trên checkpoint hiện tại để xem mô hình thực sự dự đoán gì tại thời điểm này.
Phân tích Loss và Hiện tượng Overfitting
Chúng ta sẽ bắt đầu thấy rằng khi bắt đầu từ đầu, vì đây là một mô hình chúng ta huấn luyện từ đầu, loss sẽ về cơ bản là ngẫu nhiên, và trong trường hợp đó, nó sẽ là logarit tự nhiên của 65. Vì vậy, nó sẽ bắt đầu vào khoảng 4.17. Điều đó về cơ bản có nghĩa là mô hình không biết gì cả. Nó không có khái niệm gì về dữ liệu này.
Và dần dần, chúng ta sẽ bắt đầu thấy loss giảm xuống 3.3. Đó là khi mô hình sẽ bắt đầu hiểu tần suất ký tự. Nó sẽ vẫn chưa thể tạo ra từ, nhưng có thể hiểu những thứ như "th" là một phần của "the", là một từ phổ biến; "th" sẽ là một phần của những thứ mà nó bắt đầu tạo ra.
Sau đó, ở khoảng 2.5, nó sẽ tốt hơn một chút về "th" này và sau đó nó sẽ hiểu từ "in" và những thứ tương tự. Sau đó, ở khoảng 1.5 đến 2 loss, nó sẽ bắt đầu thực sự tạo ra các từ, và sau đó ở khoảng 1.0 đến 1.2, đó là lúc mô hình sẽ bắt đầu hoạt động khá tốt ở nhiệm vụ này. Nó sẽ thực sự có thể hiểu các tên từ văn bản. Nó sẽ bắt đầu tạo ra những thứ có ý nghĩa.
Nhưng sau đó, khi loss bắt đầu giảm xuống dưới 1.0 cho bộ dữ liệu cụ thể này, đó là lúc chúng ta sẽ bắt đầu thấy hiện tượng overfitting. Mô hình vẫn sẽ tạo ra những thứ hợp lý, nhưng nó sẽ không còn tốt hơn nữa.
Đây là một ví dụ ở 200 bước khi tôi thử nghiệm điều này: nó chỉ tạo ra những thứ hoàn toàn vô nghĩa. Sau đó, val_loss ở khoảng 3.5. Sau đó, ở khoảng 800 bước, nó bắt đầu tạo ra những thứ khá ổn. Vẫn chưa tuyệt vời, nhưng nó bắt đầu đạt được mục tiêu. Và ở 1.000 bước, nó đã tốt hơn. Có một thời điểm mà val_loss thực sự bắt đầu tăng thay vì giảm, và đó là lúc chúng ta biết mô hình bị overfit. Vì vậy, ở khoảng 2.400 bước là nơi hiệu suất tối ưu của mô hình này. Và nếu chúng ta tiếp tục, hiệu suất có thể không giảm nhưng mô hình bắt đầu trở nên kém sáng tạo hơn. Đó là một trong những điều cần lưu ý.
Đánh giá Mô hình và Các Bước Huấn luyện Tiếp theo
Hiện tại, eVals không phải là metric tốt nhất nếu bạn thực sự nghiêm túc về việc tinh chỉnh các Mô hình Ngôn ngữ Lớn (LLM). Thông thường, bạn có thể có một số điểm chuẩn (benchmark) chạy như một phần của quá trình huấn luyện và bạn có thể theo dõi xem các điểm chuẩn đó có tệ đi hay không. Tuy nhiên, đối với chúng tôi, đó là một cách rất dễ dàng và rẻ tiền để hiểu mô hình đang hoạt động như thế nào.
Vậy thì, các bước tiếp theo bây giờ sẽ là tự mình huấn luyện mô hình này. Với tình hình đường truyền internet không tốt lắm, tôi khuyên bạn nên sử dụng Google Colab cho việc này. Bạn có thể sao chép (copy paste) các phần từ đây, bạn có thể ghép nối mọi thứ lại với nhau. Việc sao chép có thể giúp bạn đạt được 90% mục tiêu. Có rất nhiều chỗ để cải thiện đối với những gì tôi đã viết ở đây, tôi cố tình làm cho nó cực kỳ đơn giản và có những điều bạn có thể tự mình cải thiện. Nhưng ý tưởng là để có được một cái gì đó hoạt động. Tôi hy vọng mọi người sẽ có thể làm cho một cái gì đó hoạt động nếu bạn quan tâm đến việc thực hiện điều này và có một mô hình bắt đầu từ con số không và có thể tạo ra một kết quả hợp lý.
Sinh văn bản: Phương pháp Greedy Decoding
Sau khi đã huấn luyện mô hình, phần tiếp theo là sinh văn bản (text generation), đây là khía cạnh suy luận (inference) của mọi thứ. Chúng ta sẽ chỉ sử dụng... có nhiều cách để thực hiện suy luận. Một cách là greedy decoding mà tôi đã đề cập trước đó. Bạn có tất cả các logits (là phân phối của các mã thông báo (token)) và bạn chỉ lấy mã thông báo có khả năng cao nhất, đó là ý nghĩa của greedy decoding.
Ví dụ, giả sử mã thông báo T và mã thông báo H đều nằm trong phân phối. Một cái có 80% xác suất, cái còn lại có 15% xác suất. Bạn sẽ luôn chọn cái đứng đầu. Đó là greedy decoding. Greedy decoding không hoạt động tốt lắm cho các LLM. Nó có thể hoạt động tốt cho các mô hình khác, nhưng đối với các LLM, nó làm cho chúng trở nên rất nhàm chán và không sáng tạo trong những gì chúng tạo ra. Vì vậy, bạn gần như không bao giờ muốn sử dụng greedy decoding cho các LLM. Bạn sẽ muốn sử dụng nó cho các mô hình khác, ví dụ như chuyển đổi giọng nói thành văn bản (transcription), greedy decoding là tốt nhất vì thường chỉ có một cách để bạn có thể chuyển đổi một cái gì đó. Bạn không muốn nó sáng tạo trong chuyển đổi giọng nói thành văn bản. Đó không phải là một ý hay.
Nâng cao Khả năng Sinh văn bản: Nhiệt độ và Top-K Sampling
Đó là greedy decoding. Trong trường hợp của chúng tôi, chúng ta sẽ không sử dụng nó, chúng ta sẽ sử dụng temperature (nhiệt độ). Về cơ bản, temperature là bạn không phải lúc nào cũng chọn mã thông báo có xác suất cao nhất. Đôi khi bạn có thể chọn mã thông báo có xác suất cao thứ hai hoặc thứ ba. Và mặc dù điều đó có vẻ không hợp lý, tại sao bạn lại chọn một mã thông báo kém hơn trong tình huống này? Nhưng đã có bằng chứng cho thấy điều này thực sự làm cho mô hình hoạt động tốt hơn. Đôi khi nó có thể rơi vào một Agent Loop kỳ lạ. Vì vậy, có những kỹ thuật bạn có thể sử dụng để đảm bảo nó không trở nên "điên rồ" bằng cách tạo ra một số nội dung vô nghĩa.
Điều tồi tệ nhất là nếu nó dự đoán một mã thông báo kết thúc bản ghi hoặc mã thông báo kết thúc văn bản và sau đó dừng việc tạo ra, điều mà có thể bạn đã thấy đôi khi khi sử dụng ChatGPT khi nó đột ngột dừng lại mà không có lý do. Đôi khi là vì lý do đó. Nhưng có những cách bạn có thể ngăn chặn điều này. Nói chung, temperature 0.7 là điểm giữa tốt nhất để suy luận hoạt động tốt.
Và sau đó bạn có top-K sampling, về cơ bản là nó ngăn mô hình nếu bạn có, ví dụ, năm mã thông báo rất có khả năng và sau đó mã thông báo thứ sáu hoàn toàn không có khả năng. Top-K sampling ngăn mô hình dự đoán mã thông báo thứ sáu không có khả năng này, mặc dù temperature có thể khiến bạn không may và temperature có thể chọn nó.
Đó là những gì top-K sampling làm và đây là hàm suy luận của chúng ta, nó rất đơn giản. Nó chỉ chạy, lấy tất cả các mã thông báo làm đầu vào, truyền nó qua mô hình, lấy các logits ra khỏi mô hình và chạy softmax (đó là những gì tôi đã mô tả trước đó). Nó sử dụng temperature để tính toán xác suất và sau đó quyết định logit tiếp theo nên là gì dựa trên những xác suất đó.
Và một điều bạn có thể làm là sử dụng seeds. Về cơ bản, seeds có nghĩa là hiện tại mọi thứ sẽ ngẫu nhiên nếu bạn cứ tiếp tục thử lại. Nhưng nếu bạn sử dụng một seed đã đặt, thì suy luận của bạn sẽ luôn trả về cùng một giá trị, điều này sẽ liên quan sau này.
Tổng hợp Mã nguồn và Quy trình Huấn luyện
Sau đó, ghép nối tất cả lại với nhau. Nếu chúng ta ghép nối tất cả lại với nhau, chúng ta sẽ có ba tệp khác nhau. Một là model.py bao gồm kiến trúc mô hình của chúng ta. Một là train.py bao gồm việc tải tập dữ liệu (dataset loading) và Agent Loop huấn luyện. Và cuối cùng, generate.py bao gồm suy luận của chúng ta. Và tổng cộng, điều này sẽ chỉ khoảng vài trăm dòng mã. Rất đơn giản. Và ngay cả với lượng mã này, nếu chúng ta có phần cứng mạnh, chúng ta có thể huấn luyện một LLM tốt bằng cách sử dụng kiến trúc này, nếu chúng ta có đủ dữ liệu và đủ tài nguyên, đó là tất cả những gì bạn cần.
Và đó về cơ bản là những gì GPT-3 và GPT-2 đã làm khi OpenAI phát hành chúng. Tôi nhớ khi OpenAI sắp phát hành GPT-2 và họ nói rằng họ sẽ không phát hành nó vì nó quá nguy hiểm cho nhân loại. Và đó là thời điểm đó, đó là cơ sở mã mà họ đang làm việc, cùng với rất nhiều dữ liệu và tất nhiên là một mô hình lớn hơn. Bây giờ điều này có vẻ hơi buồn cười, nhưng đối với họ, đó là một khoảnh khắc rất đáng báo động rằng chúng tôi đã làm điều này và sau đó mô hình thực sự hoạt động rất tốt trong các tác vụ. Nhưng cuối cùng, đó chỉ là những gì bạn thấy ở đây.
Vậy thì, ghép nối tất cả lại với nhau, nếu bạn sử dụng Google Colab, bạn có thể sử dụng đoạn mã này để tải xuống tập dữ liệu và bạn có thể sử dụng lệnh pip install này để cài đặt các thư viện (dependencies) khác nhau. Và sau đó nó sẽ trông giống như thế này, nơi bạn cài đặt thư viện của mình, bạn sao chép mã của mình. Và bạn có thể cần thực hiện một số kết nối với nhau, nhưng về cơ bản, bạn có thể chạy một lệnh train để sử dụng tập dữ liệu mà bạn muốn sử dụng và điều này sẽ bắt đầu quá trình huấn luyện và sau đó bạn có thể thấy hiệu suất của mô hình cải thiện từng bước. Đối với tôi, việc này mất khoảng 15 phút để huấn luyện trên Google Colab và bắt đầu nhận được kết quả tốt. Với một số cải tiến, bạn có thể làm cho nó nhanh hơn và có thể bạn có thể làm cho nó chậm hơn nhưng thực sự nhận được kết quả tốt hơn. Nhưng về cơ bản, nó rất đơn giản để làm cho nó hoạt động.
Một điều cần nhớ là bạn phải thay đổi runtime type của mình thành T4 GPU. Bởi vì điều này miễn phí và nó sẽ chạy khá nhanh.
Thử nghiệm, Giám sát và Gỡ lỗi Quá trình Huấn luyện
Vâng, hãy thoải mái huấn luyện. Bạn có thể thử bắt đầu với một mô hình rất nhỏ với 0.5 triệu tham số, chỉ có hai lớp và chỉ hai đầu chú ý (attention heads) và kích thước nhúng (embedding size) nhỏ hơn cho mỗi mã thông báo, sau đó bạn có thể thử các mô hình lớn hơn và lớn hơn cho đến khi bạn nhận được một số kết quả có thể sử dụng được.
Và như tôi đã đề cập trước đó, bạn có thể thử các chiều dài ngữ cảnh (context lengths) khác nhau. Tôi bắt đầu với 256, bạn có thể thử lớn hơn 512.
Và sau đó, nếu bạn muốn theo dõi và xem loss của bạn giảm như thế nào một cách đẹp mắt, bạn có thể sử dụng pipel này để xem các đồ thị. Những điều bạn muốn tìm kiếm là nếu train loss của bạn không giảm, điều đó có nghĩa là mô hình của bạn không học. Điều đó có thể có nghĩa là bạn có lỗi trong mã của mình, nếu train loss của bạn đang giảm nhưng validation loss của bạn không giảm mà thực sự tăng lên, điều đó có nghĩa là bạn đã overfit. Và nếu bạn có những đỉnh loss rất kỳ lạ (thường loss phải rất mượt mà), điều đó có nghĩa là lại có một số loại lỗi trong dữ liệu của bạn hoặc trong quá trình huấn luyện của bạn.
Và khi mô hình bắt đầu đi vào trạng thái plateau và không cải thiện hơn nữa, điều đó có nghĩa là bạn đã gần như cạn kiệt khả năng hữu ích của tập dữ liệu hiện tại của mình. Vì vậy, hoặc bạn sẽ sử dụng một mô hình lớn hơn hoặc bạn sẽ cần nhiều dữ liệu hơn.
Thử thách Huấn luyện Mô hình và Cách Nộp Bài
Bây giờ, một điều thú vị mà tôi muốn chúng ta làm là tôi nghĩ sẽ rất tuyệt nếu chúng ta có một cuộc thi để xem ai có thể huấn luyện mô hình tốt nhất ở đây. Nếu có ai đó có thể làm cho mọi thứ hoạt động. Hy vọng việc cung cấp đủ tốt. Và để đọc thêm, bạn có thể xem nhiều tài liệu cho hội thảo này.
Nhưng về cơ bản, thử thách là chúng ta có thể cùng nhau bỏ phiếu để tìm ra mô hình nào thực sự tạo ra câu thơ hay nhất của Shakespeare hoặc, nếu bạn sử dụng một tập dữ liệu khác, một bài thơ hay nhất hoặc trong thể loại đó.
Các quy tắc là bạn phải tự huấn luyện mô hình, như ở đây và hôm nay. Không thể là bạn yêu cầu ChatGPT đưa cho bạn một câu thơ hay, bạn phải sử dụng mô hình của riêng mình và để chứng minh điều này, bạn sử dụng một seed với một lời nhắc cụ thể và xem kết quả nó đưa ra là gì. Bạn có thể tự do tạo lại (regenerate) mọi thứ bao nhiêu lần tùy thích cho đến khi bạn nhận được kết quả tốt nhất.
Và tôi sẽ có một mã QR để bạn có thể gửi kết quả của mình và tôi có thể đi vòng quanh và giúp đỡ mọi người nếu bạn cần bất kỳ sự trợ giúp nào trong việc chạy huấn luyện. Và tất nhiên, quá trình huấn luyện này rất cơ bản. Có nhiều cách bạn có thể tối ưu hóa và làm cho nó tốt hơn. Vì vậy, tôi đoán đối với những người có kinh nghiệm hơn một chút, họ có thể triển khai những cải tiến này và có thể nhận được kết quả tốt hơn từ mô hình của họ.
Người chiến thắng sẽ nhận được một số swag miễn phí từ Level Labs, có thể là một chiếc áo hoodie hoặc một số tín dụng miễn phí. Tôi sẽ xem xét những gì tôi thực sự có thể cung cấp.
Vậy thì, đây là phần nộp bài. Nó cần phải sáng tạo và sau đó nó cần phải là một câu thơ hay. Và chúng ta có thể sử dụng một kiểu đấu loại. Oh, chuyện gì đã xảy ra? Oh, tuyệt vời. Chúng ta có thể sử dụng một kiểu đấu loại và chúng ta có thể bỏ phiếu xem câu thơ nào nghe hay nhất. Chúng có thể hài hước, chúng có thể được làm tốt, tùy thuộc vào bạn.
Khả năng Tái tạo và Tối ưu hóa Nâng cao
Và khả năng tái tạo (reproducibility) sẽ trông như thế này: Bạn chạy python generate.py trên điểm kiểm tra tốt nhất của bạn, lời nhắc mà bạn quyết định (tùy thuộc vào bạn), temperature và sau đó là một seed chứng minh rằng bạn thực sự có thể tạo ra kết quả của mình. Bạn có thể thử các kích thước mô hình khác nhau, ví dụ như 85 triệu tham số sẽ hơi lớn cho việc này, nhưng nếu bạn có tài nguyên, bạn có thể thử. Bạn có thể thử các tokenizer tốt hơn, như cái này tất nhiên là dựa trên ký tự, nhưng có lẽ bạn có thể huấn luyện tokenizer BPE của riêng mình dựa trên tập dữ liệu đó. Và cũng có những chỉnh sửa khác mà bạn có thể làm như context lớn hơn, như có một số tối ưu hóa về tinh chỉnh như sử dụng giá trị dropout. Bạn có thể dừng lại bất cứ khi nào bạn cảm thấy mô hình đủ tốt, thay đổi tốc độ học (learning rates) và về cơ bản làm cho mô hình tốt nhất có thể.
Vâng, đó là ý tưởng. Tôi sẽ đi vòng quanh và giúp đỡ bạn. Xin lỗi. Cứ tiếp tục. Hoặc chỉ cần...
Mô hình Suy luận (Q&A)
Vậy các mô hình suy luận (reasoning models) có khác biệt khá nhiều trong việc huấn luyện không? Bạn đang hỏi liệu các mô hình suy luận có khác biệt khá nhiều không? Các khối xây dựng (building blocks) cơ bản rất giống nhau. Bạn có thể huấn luyện cùng một mô hình chính xác, bạn có thể huấn luyện hậu kỳ (post-train) nó, đó là cách mà suy luận thường được dạy cho mô hình này. Bạn có một mô hình cơ sở hướng dẫn (instruct model) tốt và sau đó bạn huấn luyện hậu kỳ nó để trở thành một mô hình suy luận. Điều này rất dựa vào dữ liệu (data-driven). Vì vậy, bạn cần dữ liệu chất lượng rất cao và bạn sẽ sử dụng một loss đủ tốt để có thể học dữ liệu này một cách rất hiệu quả. Sự phức tạp của các mô hình suy luận là tìm kiếm dữ liệu chuỗi suy nghĩ (chain of thought) tốt này. Đó là lý do tại sao OpenAI có tất cả những người gắn nhãn này, họ là những sinh viên tiến sĩ, họ ghi lại các lý do về cách họ giải quyết vấn đề.
Chất Lượng Dữ Liệu và Suy Luận trong Mô Hình AI
Dữ liệu cần có chất lượng rất cao vì nó dạy mô hình cách suy nghĩ. Bạn không thể chỉ lên Reddit và lấy các bài đăng ngẫu nhiên; chắc chắn mô hình sẽ không học được cách suy luận theo cách đó. Bạn cần dữ liệu có giá trị cao, chất lượng tốt, dạy cho mô hình quy trình suy luận này. Suy luận về cơ bản là việc thêm ngữ cảnh vào mô hình, tức là thêm logic vào cơ chế attention để khi mô hình tạo ra phản hồi, nó có thể quay lại và chú ý đến những mã thông báo suy luận đó và đưa ra một phản hồi tốt hơn. Điều này giống như mô tả mô hình tốt hơn một chút. Sau đó, mô hình quay lại, xem các mã thông báo bạn đã mô tả và nói: "Ồ, thực ra tôi đã tìm ra điều này rồi. Bây giờ tôi sẽ viết nó ra." (Microphone không hoạt động phải không?) Vâng, chính xác.
Các mô hình có khả năng suy luận và không suy luận thường chia sẻ cùng một nền tảng cơ bản, sau đó một trong số chúng được post-training theo một cách khác và thêm khả năng này theo một cách post-training riêng biệt. Rất nhiều phòng thí nghiệm, ví dụ như mô hình Llama được phát hành, như Llama 3, thường phát hành phiên bản cơ bản và phiên bản instruct. Phiên bản cơ bản thường không có chain of thought reasoning. Nó thường là cùng một mô hình mà họ đã pre-train đầu tiên để có hiệu suất khá tốt, có thể fine-tuning thêm. Sau đó, bước tiếp theo là thực hiện post-training để dạy cho mô hình loại kiến thức này. Đó là lý do tại sao bạn thấy rất nhiều cải tiến lớn diễn ra rất nhanh trong ngành, như Gemini 3 lên 3.1. Về cơ bản, điều này là do cung cấp cho mô hình dữ liệu suy luận tốt hơn và dữ liệu post-training để fine-tuning tốt hơn cho các vấn đề cụ thể. Nó hơi giống benchmarking. Dữ liệu này thường rất giống với những gì các điểm chuẩn hiện tại đang sử dụng. Nhưng về cơ bản, đó là việc lấy một mô hình cơ bản, sử dụng dữ liệu mới này để cải thiện nó lên cấp độ tiếp theo.
Đổi Mới Cốt Lõi trong Huấn Luyện Mô Hình
So với những gì chúng ta đang thấy ở đây, một mô hình rất cơ bản, liệu có những đổi mới cơ bản trong quá trình huấn luyện chính của mô hình đang cung cấp sức mạnh cho các mô hình hiện nay không? Hay chủ yếu vẫn là tương tự, nhưng chỉ với những thủ thuật thông minh hơn, những điều chỉnh nhỏ, dữ liệu tốt hơn, v.v.? Liệu mọi người vẫn đang sử dụng cùng một attention layer và những thứ tương tự, hay có những thay đổi cơ bản nào đã và đang xảy ra?
Bạn nói đúng rằng phần lớn không gian này vẫn giữ nguyên. Họ có thể thực hiện một số thay đổi về cách attention chú ý đến các mã thông báo khác nhau, bởi vì đôi khi suy luận có thể khá lớn. Vì vậy, bạn cần độ dài chuỗi lớn để có thể đạt được kết quả tốt. Do đó, có rất nhiều thủ thuật mà các phòng thí nghiệm mới thực hiện để làm cho cơ chế attention hiệu quả hơn. Nhưng nhìn chung, bạn có thể lấy một mô hình như GPT-2 và biến nó thành một mô hình suy luận. Nếu bạn có dữ liệu và một mô hình đủ lớn để thực sự học hỏi từ quá trình suy luận đó, bởi vì những mô hình nhỏ sẽ không được hỗ trợ nhiều bởi suy luận. Nhưng nếu bạn có một mô hình đủ lớn có thể rút ra những điều hữu ích từ suy luận. Có những người đã lấy các mô hình cũ hơn, ví dụ như Llama 1B, vốn nhỏ và không được huấn luyện cho suy luận, sau đó biến chúng thành mô hình suy luận bằng cách sử dụng cùng một kiến trúc.
Quy Trình Tạo Tập Dữ Liệu Vàng
Bạn đã nỗ lực bao nhiêu để có được tập dữ liệu vàng của mình, và quy trình bạn tuân theo là gì? Ý bạn là cho post-training? Vâng.
Vâng, như tôi đã đề cập trước đây, thông thường các phòng thí nghiệm sẽ tìm đến các công ty gán nhãn dữ liệu như Scale AI. Đó có lẽ là công ty lớn nhất. Và Scale AI có một đội ngũ người mà bạn có thể yêu cầu: "Tôi muốn các nhà vật lý. Hãy cung cấp cho tôi dữ liệu từ các nhà vật lý." Sau đó, Scale AI sẽ tìm các nhà vật lý hợp đồng, trả cho họ số tiền cần thiết, và các nhà vật lý này có thể viết ra mọi thứ. Họ có thể được liên hệ để làm những việc khác nhau. Nhưng về cơ bản, nhiều công ty như Scale AI cung cấp dữ liệu cho Anthropic. Một công ty khác đã được Meta mua lại, nên có lẽ không còn nhiều nữa. Nhưng về cơ bản, những tập dữ liệu này được cung cấp bởi các công ty gán nhãn dữ liệu như Scale AI. Tuy nhiên, nhiều phòng thí nghiệm lớn cũng có các nhóm gán nhãn dữ liệu riêng của họ, thuê các nhà thầu để tạo ra những tập dữ liệu này. Nhưng như bạn đã nói trước đó, nếu tập dữ liệu này có dù chỉ một vài vấn đề nhỏ, nó có thể tạo ra hoặc phá vỡ mô hình của bạn. Vì vậy, những tập dữ liệu này là loại đắt nhất; chúng sẽ tiêu tốn hàng tấn tiền, nhưng chúng thực sự là thứ giúp mô hình trở nên tốt như hiện tại.
Đánh Giá Chất Lượng Dữ Liệu
Chỉ một câu hỏi phụ nhanh: Ngay cả trên đó, bạn vẫn phải đánh giá, đúng không? Câu trả lời sẽ không hoàn toàn giống nhau. Vậy mọi người có thực sự xem xét điều này không, hay bạn vẫn dựa vào LLM để đánh giá?
Bạn phải dựa vào con người cho những loại việc này. Thông thường, cách nó hoạt động, một phần lớn công việc của tôi thực sự là làm công tác tổ chức này. Thông thường, cách nó hoạt động là bạn có thể có một người tạo ra dữ liệu được gán nhãn chất lượng rất cao này. Và sau đó bạn có thể có một người gán nhãn đã được thăng cấp lên vị trí kiểm định chất lượng (QA), nơi công việc của họ là đảm bảo rằng tất cả các đầu ra của những người gán nhãn khác, những người ở cấp độ thấp hơn, đều chính xác. Và đó là một công việc khá khó khăn, bởi vì nếu QA của bạn không tốt, bạn sẽ bị sa thải. Vì vậy, đó là cách họ duy trì mức độ chất lượng khá cao.
Tính Không Xác Định của LLM và Tham Số Seed
Vâng. Vậy, các LLM là không xác định, nhưng tham số seed hoạt động như thế nào?
Cách các LLM là không xác định là do chúng về cơ bản sử dụng bộ tạo số ngẫu nhiên của máy tính của bạn hoặc của GPU mà bạn đang sử dụng, của hệ thống. Tham số seed về cơ bản làm cho tất cả các phép tính khác nhau đó luôn trả về cùng một giá trị. Vì vậy, mọi thứ không còn ngẫu nhiên nữa. Điều này không chỉ dành cho LLM. Các seed này có thể được sử dụng cho bất kỳ việc tạo ngẫu nhiên nào. Ví dụ, cho password hashes. Vâng, nó hoạt động chính xác theo cùng một cách. Vâng. Vâng. Vâng, nếu bạn sử dụng greedy decoding, vẫn có thể có một số điều. Tốt hơn là nên sử dụng seed ngay cả khi bạn sử dụng greedy decoding vì có thể có một số thứ khác mà có thể bạn không kiểm soát được. Ví dụ, đôi khi greedy decoding có thể không phải là greedy decoding thực sự; nó có thể là nhiệt độ 0.01, ví dụ như với VRM, họ thực hiện một số thủ thuật như vậy để có được đầu ra tốt. Vì vậy, nói chung, tốt nhất là nên sử dụng seed ngay cả khi bạn đang sử dụng greedy decoding.
Mô Hình Âm Thanh so với Văn Bản (11 Labs)
Vâng. Đây rõ ràng đều là văn bản. 11 Labs chủ yếu là âm thanh, đúng không? Vâng. Vậy điều này khác với việc làm việc với âm thanh như thế nào?
Thật đáng ngạc nhiên là rất giống nhau. Chắc chắn là phức tạp hơn, nhưng các phần của stack của hầu hết mô hình âm thanh cũng có thể xử lý văn bản vì các mô hình cần hiểu ngôn ngữ, và cách tốt nhất để dạy ngôn ngữ là văn bản. Tất nhiên, bạn có thể chứa một mô hình chỉ xử lý âm thanh. Và một lần nữa, đó là những gì tôi đã nói về bộ mã hóa token. Bạn mã hóa token âm thanh như thế nào? Khái niệm về một âm thanh là gì? Bộ mã hóa token của bạn nên lớn đến mức nào cho âm thanh? Nó chỉ nên là giọng nói của con người hay cũng nên là âm nhạc? Bạn có lấy các nốt của các nhạc cụ khác nhau làm các mã thông báo khác nhau không? Đây là những loại vấn đề bạn phải giải quyết nếu bạn đang tạo một mô hình âm thanh mà bạn không nhất thiết phải có cho văn bản. Nó dễ dàng hơn một chút. Nhưng các nguyên tắc cơ bản vẫn giống nhau. Nếu bạn muốn tạo âm thanh, bạn sẽ tạo một mã thông báo âm thanh, và sau đó mã thông báo âm thanh đó sẽ được sử dụng bởi bộ mã hóa token. Và sau đó bạn, bạn, nó không hoàn toàn giống nhau vì mã thông báo âm thanh sau đó bạn phải xử lý nó theo một số cách khác nhau để biến nó thành âm thanh thực tế. Nhưng rất nhiều ý tưởng chung vẫn áp dụng.
Hàm Loss trong Mô Hình Âm Thanh
Nhưng bạn có nhiều lỗi hơn hay gì đó không? Với văn bản, bạn biết đấy, đây là văn bản. Văn bản có thể rất rõ ràng, giống như một là một. Nhưng trong âm thanh, như một cao độ hoặc tần số hoặc âm thanh cụ thể, nhưng sự mơ hồ ở đó, nó đã bị mất ngay lập tức, đúng không?
Vâng. Vâng. Vì vậy, cách nó hoạt động là bạn không sử dụng loss cross-entropy. Bạn sử dụng các loại loss khác. Bạn có thể sử dụng cross-entropy, nhưng thường thì đó là các loss chuyên biệt hơn cho những gì bạn đang cố gắng làm. Ví dụ, có một loss được gọi là L2 loss, về cơ bản lấy hai phổ đồ (melspectrograms) và cố gắng xem sự khác biệt giữa hai phổ đồ đó, về cơ bản là sóng âm được mã hóa theo một cách nhất định. Và đó là một cách rất phổ biến để huấn luyện các mô hình TTS. Bạn huấn luyện dựa trên các loại loss cụ thể này. Và điều tương tự tôi đã đề cập trước đó, cross-entropy không thực sự hoạt động tốt cho những thứ như post-training. Bạn có thể sử dụng các loại loss khác ở đó. Hoặc nếu bạn đang chưng cất mô hình từ một mô hình lớn sang một mô hình nhỏ hơn, bạn không sử dụng loss cross-entropy, bạn có thể sử dụng loss KL divergence nơi bạn tìm phân phối mã thông báo của mô hình lớn hơn và bạn cố gắng khớp logits của mô hình nhỏ hơn. Vì vậy, có nhiều loại loss khác nhau cho các trường hợp sử dụng khác nhau. Không phải tất cả chúng đều hoạt động theo cùng một cách.
Hoạt Động của Mô Hình Đa Phương Thức
Vâng. Vậy, theo một nghĩa nào đó, bạn có từ vựng âm thanh, đúng không? Từ vựng văn bản, từ vựng âm thanh, từ vựng cùng nhau. Vậy, cách các mô hình đa phương thức hoạt động, thường thì bạn không sử dụng mã thông báo theo cùng một nghĩa. Chà, đây là lúc nó trở nên phức tạp hơn bởi vì những mô hình này không thực sự được xây dựng cho mục đích này, giống như mô hình GPT-2. Các mô hình mới hơn cũng có đầu vào embedding, về cơ bản, như tôi đã đề cập trước đó, mỗi mã thông báo sau đó tương ứng với một vector. Nhưng các vector này không nhất thiết phải tương ứng với các mã thông báo cụ thể. Bạn cũng có thể lấy các vector này từ những nơi khác.
Và điều mà nhiều phòng thí nghiệm làm là thay vì có một bộ mã hóa token cho video chẳng hạn, họ có một transformer khác mà họ gọi là bộ mã hóa video. Và họ đưa video qua bộ mã hóa video đó trước. Và bộ mã hóa video này sẽ lấy, giả sử bạn có một video dài 30 giây. Nó sẽ lấy một khung hình mỗi giây của video này và nó sẽ lấy những khung hình đó và sau đó đưa chúng qua transformer mới này, transformer encoder này hoạt động khá khác một chút. Và điều bạn làm là bạn lấy lớp cuối cùng của transformer này, các giá trị ẩn, cũng là các vector. Bạn lấy những vector đó từ bộ mã hóa này và bạn sẽ nhập chúng vào lớp embedding của mô hình transformer. Vì vậy, những gì mô hình sẽ thấy thường được tiền tố (prefixed). Bạn lấy video, đẩy nó qua bộ mã hóa, nhận được một số vector, và sau đó đặt những vector đó vào đầu vào embedding của transformer xử lý văn bản của bạn. Và cách nó sẽ trông như thế nào nếu bạn nhìn vào chuỗi là nó có thể sẽ là một lời nhắc, và sau đó có thể là một biểu diễn mã thông báo video. Nhưng embedding của mã thông báo video sẽ bị ghi đè bởi đầu ra của bộ mã hóa. Vì vậy, đó là cách các mô hình đa phương thức này hoạt động tương tự. Vâng, nó hoàn toàn giống nhau. Vector cũng giống nhau cho âm thanh. Bạn có một bộ mã hóa âm thanh và làm tương tự nhưng cho âm thanh. Nhưng về mặt những gì mô hình quan tâm, nó chỉ quan tâm đến những embedding này, đúng không? Nó không quan tâm đó là văn bản hay âm thanh hay video.
Biểu diễn Vector và Kiến trúc Mô hình
Nó quan tâm đến các vector này và đó là cách bạn biểu diễn chúng vào cùng một chiều mà mô hình transformer mong đợi. Vậy thì, có sự trùng khớp giữa các vector này không? Có lẽ có, tôi không chắc; nó phụ thuộc vào cách bạn huấn luyện. Những điều này hơi giống hộp đen. Có thể có, có thể không. Thực ra đó là một ý tưởng hay, một bài nghiên cứu tốt để xem liệu các bộ mã hóa video có thực sự khớp với cùng chiều với các bộ mã hóa văn bản hay không. Tôi không biết, tôi hình dung có thể có mối liên hệ.
Tạo Nhạc so với Lời Nói: Các Cách Tiếp Cận Khác Nhau
Người hỏi: Vâng, tôi tự hỏi rằng bạn thực hiện cả lời nói thông thường và cả tạo nhạc. Đó có phải là một vấn đề rất khác biệt, hay kiến trúc sẽ tương tự? Và khi bạn có các yếu tố hòa âm (harmonics) và những thứ tương tự, bạn có thể sử dụng một mô hình transformer tự hồi quy (autoregressive transformer) cơ bản không, hay các yếu tố đó phụ thuộc vào nhau nhiều hơn? Bạn có thể tạo ra mọi thứ cùng một lúc không?
Bạn có thể làm cả hai. Có những mô hình âm nhạc là tự hồi quy (autoregressive models). Cũng có những mô hình âm nhạc là mô hình khuếch tán (diffuser models). Nó phụ thuộc vào cách bạn huấn luyện chúng. Tôi nghĩ một số mô hình của Google dựa trên transformer. Một số mô hình mã nguồn mở thì dựa trên mô hình khuếch tán. Cả hai đều có thể hoạt động rất tốt.
Thách thức trong Mã hóa Âm nhạc
Chỉ là, như tôi đã nói, hơi khó để hình dung khái niệm về âm nhạc nếu bạn mã hóa và dự đoán mã thông báo tiếp theo. Nó khá khó vì rất trừu tượng. Vì vậy, mô hình khuếch tán thường hoạt động tốt hơn một chút trong các phương thức đa phương tiện (multimodal modalities) như tạo ảnh, âm nhạc, hoặc thậm chí âm thanh đối với một số mô hình – chúng có một phần khuếch tán trong quá trình của mình. Cả hai đều có thể hoạt động. Mô hình khuếch tán thường dễ triển khai hơn một chút. Tôi hy vọng câu trả lời này có ý nghĩa.
Huấn luyện Bộ Mã hóa Âm thanh
Người hỏi: Vâng.
Cách để làm cho nó dễ hơn... nó rất khó. Và nó không phải là thứ bạn chỉ cần ngồi xuống và nghĩ, "À, tôi sẽ mã hóa từ này thành từ kia." Nó không phải là thứ bạn ngồi xuống và quyết định. Bạn sử dụng một loại quy trình nào đó và huấn luyện một bộ mã hóa âm thanh (audio tokenizer). Nó rất giống với cách bạn huấn luyện một bộ mã hóa văn bản (text tokenizer).
Bạn sẽ sử dụng dữ liệu huấn luyện của mình và tìm các mẫu chung (common patterns) trong âm thanh, sau đó mã hóa các mẫu đó. Tất nhiên, khi chúng ta nói âm thanh, chúng ta không luôn chỉ nói đến tốc độ lấy mẫu (sample rate) thực tế và sóng âm. Thông thường, chúng ta chuyển đổi chúng thành một thứ gì đó dễ mã hóa hơn để thực hiện quá trình xử lý này. Phổ biến nhất là phổ kế Mel (Mel spectrograms).
Đầu tiên, bạn chuyển đổi âm thanh của mình thành phổ kế Mel, sau đó sử dụng các mảng số này để huấn luyện các bộ mã hóa của mình. Điều này sẽ rất phụ thuộc vào tập dữ liệu huấn luyện của bạn. Ví dụ, nếu bạn muốn mã hóa âm nhạc và sử dụng một tập dữ liệu âm nhạc, các mã thông báo âm thanh của bạn cho âm nhạc sẽ rất khác so với khi bạn có một tập dữ liệu giọng nói tập trung vào giọng người. Bây giờ, phần khó khăn là làm thế nào nếu bạn muốn làm cả giọng nói và âm nhạc? Đó là điều rất khó.
Thông báo Hội thảo và Cuộc thi
Có câu hỏi nào khác không? Tuyệt vời. Vâng, nếu bạn muốn, bạn có thể bắt đầu làm việc về việc huấn luyện mô hình nếu bạn có máy tính xách tay hoặc nếu bạn đã bắt đầu. Bạn có thể theo dõi hội thảo này để làm cho một cái gì đó hoạt động. Và nếu chúng ta có đủ bài nộp, chúng ta có thể tổ chức cuộc thi và xem ai thắng sẽ nhận được một số...
Người hỏi: [âm thanh không rõ].
Người nói: Xin lỗi.
Người hỏi: Hạn chót là khi nào?
Người nói: Bắt đầu ngay sau đó.
Vì chúng ta không còn nhiều thời gian, hãy đặt hạn chót là 5:45. Vì vậy, nếu bạn có bất kỳ câu hỏi hoặc cần giúp đỡ, xin hãy gọi tôi và tôi sẽ đến.
TL;DR
- Workshop này tập trung vào việc huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu bằng PyTorch, không sử dụng trọng số tiền huấn luyện hay các thư viện transformer cấp cao, nhằm giúp người tham gia hiểu sâu về kiến trúc và quá trình xây dựng LLM.
- Dự án sử dụng một mô hình nhỏ dựa trên GPT2 và mã hóa cấp ký tự đơn giản, cho phép thực hành với tài nguyên hạn chế như máy tính xách tay cá nhân (16GB RAM) hoặc Google Colab.
- Nội dung chia thành bốn khối xây dựng chính của LLM – Bộ mã hóa, Kiến trúc mô hình, Vòng lặp huấn luyện, và Suy luận – đồng thời đi sâu vào các thành phần cốt lõi của Transformer như multi-head self-attention, MLP, residual connections và layer normalization.
Điểm chính
- Xây dựng LLM từ đầu: Học cách huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu (
from scratch) bằng PyTorch, không dùngtrọng số tiền huấn luyện(pre-trained weights), để hiểu rõ cơ chế vận hành. - Chuẩn bị môi trường: Có thể huấn luyện
mô hìnhcục bộ trên máy tính xách tay (yêu cầu tối thiểu 16GB RAM) hoặc sử dụngGPUmiễn phí trên Google Colab; cài đặt các thư viện cần thiết nhưtorch,numpy,tqdm,tiktoken. - Tokenization cho người mới bắt đầu: Sử dụng
mã hóa cấp ký tự(character-level tokenization) cho cácmô hìnhnhỏ để giảmkích thước từ vựngvà tăng tốc độ huấn luyện, đặc biệt khi dữ liệu hạn chế. - Bốn khối xây dựng cốt lõi: Nắm vững bốn phần thiết yếu của một dự án LLM:
Bộ mã hóa(Tokenizer),Kiến trúc mô hình(Model Architecture),Vòng lặp huấn luyện(Training Loop), vàPhần suy luận(Inference). - Thành phần Transformer: Hiểu các khối kiến tạo chính của kiến trúc
transformer:Multi-Head Self-Attention(để hiểu mối quan hệ token),MLP(Mạng Nơ-ron Truyền Thẳng),Residual Connections(để ổn định quá trình huấn luyện) vàLayer Normalization(để ngăn chặn các activation bùng nổ). - Vòng lặp huấn luyện quan trọng: Nhận thức rằng
vòng lặp huấn luyệnvà các kỹ thuậttinh chỉnh(fine-tuning),hậu huấn luyện(post-training) là yếu tố then chốt quyết định hiệu suất củamô hình, ngay cả khi kiến trúc cơ bản tương tự. - Mở rộng mô hình: Để mở rộng
mô hìnhlên quy mô lớn hơn hoặc cho cáctrường hợp sử dụngphức tạp hơn, cân nhắc sử dụng cácbộ mã hóatiên tiến hơn nhưbyte pair encodingvà chuẩn bị tài nguyên huấn luyện lớn hơn.
Từ vựng
- Mô hình Ngôn ngữ Lớn (LLM) — Large Language Model (LLM)
- từ đầu — from scratch
- trọng số tiền huấn luyện — pre-trained weights
- suy luận — inference
- bộ mã hóa — tokenizer
- mã thông báo — token
- embedding — embedding
- vòng lặp huấn luyện — training loop
- chuẩn hóa lớp — layer normalization
- kết nối dư — residual connections
Nội dung chi tiết
Giới thiệu và Mục tiêu Workshop
Cảm ơn các bạn đã tham gia buổi workshop này. Đây sẽ là một buổi workshop thực hành, hy vọng các bạn có thể bắt tay vào thực hiện một dự án rất thú vị.
Đầu tiên, một chút về bản thân tôi. Tên tôi là Angelos, tôi là trưởng nhóm Speech to Text tại 11 Labs. Tôi là một kỹ sư nghiên cứu, vì vậy tôi dành phần lớn thời gian để huấn luyện các mô hình mới, làm việc về suy luận (inference) và cũng tham gia vào mảng sản phẩm. Tôi chịu trách nhiệm, thật không may, đôi khi phải trò chuyện với khách hàng, điều mà tôi không thực sự yêu thích. Tôi là kiểu người thích đi thẳng vào nghiên cứu, huấn luyện các mô hình và tạo ra những thứ tiên tiến, mạnh mẽ. Hiện tại, tôi đang phát triển các mô hình thời gian thực để phiên âm, đặc biệt dành cho các tác nhân. Nếu bạn chưa biết, chúng tôi có mô hình scribe v2 mà nhóm tôi đã huấn luyện, hiện là mô hình phiên âm tốt nhất trên thị trường xét về các điểm chuẩn công khai phổ biến. Vì vậy, nếu bạn cần bất kỳ trường hợp sử dụng phiên âm nào, hãy dùng thử. Tôi nghĩ nó khá tốt.
Giờ đây, trong khuôn khổ workshop, hôm nay chúng ta sẽ huấn luyện một Mô hình Ngôn ngữ Lớn (LLM) từ đầu (from scratch). Tức là, không có trọng số tiền huấn luyện (pre-trained weights), không có bất cứ thứ gì bạn có thể lấy trực tuyến từ một thư viện transformers. Chúng ta sẽ làm việc hoàn toàn trên PyTorch và một số thư viện cơ bản. Chúng ta có thể đi sâu hơn một cấp độ và không sử dụng PyTorch, nhưng tôi không muốn gây áp lực quá nhiều cho bạn. Vì vậy, tôi nghĩ PyTorch là một cấp độ tốt, và đây sẽ là một dấu hiệu tốt cho thấy các kỹ sư nghiên cứu thực thụ trong các phòng thí nghiệm lớn thiết kế các mô hình của họ như thế nào. Mọi thứ nâng cao hơn sau đó chủ yếu là các tối ưu hóa và làm cho quy mô tốt hơn, lớn hơn và cải thiện cho các trường hợp sử dụng cụ thể. Vì vậy, những gì bạn sẽ làm hôm nay là gần như 80% chặng đường để tạo ra một mô hình từ đầu.
Chuẩn bị môi trường
Nếu bạn có thể truy cập mã QR này, bạn sẽ tìm thấy kho lưu trữ GitHub. Tôi sẽ chuyển sang đó ngay bây giờ. Chúng ta sẽ để mã này thêm một chút. Bạn có hai lựa chọn. Một là huấn luyện mô hình cục bộ trên máy tính xách tay của bạn. Nếu bạn có 16GB bộ nhớ, bạn sẽ có thể làm được. Đó là một mô hình nhỏ, rất nhỏ mà bạn có thể huấn luyện nhanh chóng. Nếu không, và tôi đoán là không nhiều người có nguồn tài nguyên mạnh, Google Colab có thể là một lựa chọn khác. Google Colab cung cấp GPU miễn phí mà bạn có thể sử dụng để huấn luyện một mô hình nhỏ như vậy. Vậy đó sẽ là lựa chọn của bạn.
Trong khi tôi để mã QR này thêm một chút, sau đó chúng ta có thể đi vào kho lưu trữ thực tế.
Nguồn cảm hứng
Để bạn có một số ý tưởng về nguồn cảm hứng của dự án này. Lần đầu tiên tôi tiếp xúc với transformers nói chung là qua video này của Andrej Karpathy, một trong những đồng sáng lập của OpenAI, có tên là nano GPT. Đối với tôi, đó là một dự án rất truyền cảm hứng. Về cơ bản, đó là điều đã truyền cảm hứng cho workshop này. Nó ở cấp độ thấp hơn một chút so với những gì chúng ta sẽ làm bây giờ, đi sâu hơn một chút vào cách bạn sử dụng NumPy cho các phép tính. Nhưng tôi nghĩ đó là một bản thiết kế tốt mà chúng ta có thể làm theo để tạo ra một mô hình từ đầu.
Để tôi di chuyển cái này lên màn hình. Nhưng vâng, nếu bạn chưa biết dự án này, tôi nghĩ đó là một phần giới thiệu tuyệt vời. Nếu bạn đã biết dự án này và đã huấn luyện các LLM như vậy trước đây, điều này có thể có vẻ hơi đơn giản đối với bạn. Nhưng tôi nghĩ có những cách chúng ta có thể mở rộng workshop này để hướng tới việc tạo ra một cuộc thi nhỏ, mà tôi rất muốn xem các bạn có thể đưa ra những gì.
Kiến trúc Mô hình và Các Khối Xây dựng
Đối với workshop cụ thể này, chúng ta sẽ làm việc với một mô hình rất nhỏ dựa trên kiến trúc GPT2. Đây là một kiến trúc hơi cũ, nhưng các phần cơ bản và nền tảng của nó về cơ bản không thay đổi quá nhiều, và chúng ta sẽ tìm hiểu về chúng một chút nữa.
Bốn khối xây dựng bạn cần để huấn luyện một mô hình là:
-
Bộ mã hóa (
Tokenizer): Đây thường là điều đầu tiên bạn nghĩ đến khi tạo bất kỳmô hình transformermới nào. Tùy thuộc vàotrường hợp sử dụngcủa bạn, bạn sẽ muốn sử dụng mộtbộ mã hóakhác nhau. Nếu bạn muốn huấn luyện mộtmô hìnhrất lớn có khả năng tạo văn bản cho nhiều ngôn ngữ, bạn sẽ cần mộtbộ mã hóakhổng lồ, điều đó có nghĩa là bạn cũng sẽ cần một lượng dữ liệu khổng lồ để huấn luyện nó. Nhưng đối với mộtmô hìnhnhỏ hơn, mộtbộ mã hóanhỏ hơn vớiembeddingnhỏ hơn sẽ hoạt động tốt nhất và huấn luyện nhanh nhất khi bạn bị giới hạn về dữ liệu, đây là trường hợp của chúng ta hiện tại. CácMô hình Ngôn ngữ Lớn (LLM)không "thấy" văn bản; chúng làm việc với cácembeddinghoặc vector. Vì vậy, chúng ta cần một loại biểu diễn nào đó của các vector đó đểmô hìnhcó thể xử lý. Ở đây, chúng ta sẽ sử dụngmã hóa cấp ký tự(character-level tokenization) chỉ vì nó có số lượngmã thông báokhả thi thấp nhất. Trong trường hợp của chúng ta, trên tập dữ liệu, sẽ chỉ có 65embeddingvì có 65 ký tự khác nhau sẽ xuất hiện trong dữ liệu huấn luyện của chúng ta. Cách nó hoạt động là chúng ta sẽ sử dụng thư việnstorynày để chuyển các chuỗi thành số nguyên, và các chỉ mục này sau đó sẽ được chuyển thànhembeddingthông qualớp embeddingkhi chúng ta huấn luyệnLLM. Đó là mộtbộ mã hóarất đơn giản, sử dụng hàmenumeratetừ Python và sau đó chọn mục cụ thể đã được chọn với từ điển này. Như tôi đã nói trước đây, chúng ta sử dụngcấp ký tựvì nó dễ huấn luyện hơn. Vì chúng ta chỉ có 65mã thông báo, điều đó có nghĩa là các kết hợpbiogramsẽ là 65*65, tức là 4.225biogramkhả thi.Biogramvề cơ bản là khi bạn có mộtmã thông báovà sau đó bạn dự đoánmã thông báotiếp theo sau nó. Khái niệm vềbiogramnày là một khái niệm rất quan trọng khi bạn huấn luyệntransformers vì bạn muốnmô hìnhcủa mình nhìn thấy càng nhiềubiogramkhả thi càng tốt. Vì vậy, nếu bạn có mộtmô hìnhvới, giả sử, 200.000mã thông báo, bạn cần ít nhất 200.000mã thông báobình phương dữ liệu để có thể huấn luyện từ đầu một cách rất tốt. Hoặc ít nhất đây là độ lớn mà bạn đang tìm kiếm. Trong trường hợp của chúng ta, 4.000biogramlà rất khả thi. Tập dữ liệu này rất có thể sẽ bao gồm tất cả cácbiogramnhiều lần. Nếu chúng ta cố gắng huấn luyện bằng cách sử dụng mộtbộ mã hóađầy đủ, điều này sẽ không bao giờ hội tụ. Chúng ta có thể huấn luyện nó hàng giờ đồng hồ và sau đómô hìnhcủa chúng ta sẽ không bao giờ có thể đạt được kết quả tốt. Vấn đề vớibộ mã hóacấp ký tựlà chúng không mở rộng tốt vì cách cácmô hìnhhoạt động là chúng cần hiểu mối tương quan giữa cácmã thông báokhác nhau. Vì vậy, bạn có thể dễ dàng có một mối tương quan nói "the sky is blue" (bầu trời xanh) – nhữngmã thông báonày kết hợp lại có rất nhiều ý nghĩa. Nhưng "sky" (bầu trời) và sau đó "is" (là) và sau đó "bl" thì hơi khó hơn chomô hìnhđể có thể tạo ra sự chú ý tốt đến cácmã thông báonày. Vì vậy, điều này sẽ hoạt động khá tốt cho ví dụ của chúng ta. Nhưng nếu bạn muốn mộtmô hìnhrất, rất tốt, một là nó sẽ tốn kém để huấn luyện vì tất nhiên, bạn phải tạo ra rất nhiềumã thông báotrong quá trìnhsuy luậnvà trong quá trình huấn luyện. Nhưng nó cũng sẽ không bao giờ hội tụ thành một cái gì đó tốt vì cácmã thông báokết hợp không có quá nhiều ý nghĩa. -
Kiến trúc
mô hình: Thành thật mà nói, hầu hết cácmô hìnhít nhất trong giai đoạn chúng ta sẽ làm việc đều rất giống nhau, chúng chỉ làmô hình giải mã hướng nhân quả(decoder-only causal model) có cách sử dụngcơ chế tự chú ý nhân quả(causal self-attention) rất tương tự và cáclớp MLP(MLP layers) tương tự,chuẩn hóa lớp(layer norms) tương tự và tất cả các thứ tương tự mà chúng ta sẽ tìm hiểu. Vì vậy, nếu bạn biết cách tạo ra nhữngmô hìnhnhỏ này, rất dễ dàng để thực hiện quy trình tương tự cho cácmô hìnhlớn hơn, mới hơn. Nhưng tất nhiên, cácmô hìnhmới hơn sẽ chuyên biệt hơn nhiều chongữ cảnhdài hơn và về cơ bản được kiến trúc hóa theo cách có thể mở rộng quá trình huấn luyện đến càng nhiềumã thông báocàng tốt, điều mà chúng ta sẽ không cần trong trường hợp này. -
Vòng lặp huấn luyện (
Training Loop): Đây thường là phần quan trọng nhất khi bạn huấn luyện mộtmô hìnhmới. Nếu bạn kiểm tra sự khác biệt giữaGPT4,GP40vàGPT5hoặc thậm chí nếu bạn quay trở lại trước đó, điều bạn sẽ thấy chủ yếu là quá trìnhtiền huấn luyện(pre-training) thường rất giống nhau. Chính quá trìnhtinh chỉnh(fine-tuning) vàhậu huấn luyện(post-training), và về cơ bản những gì bạn sử dụng với cùng mộtmô hìnhcơ sở hoặcmô hìnhcơ sở rất giống nhau và cách bạn huấn luyện nó mới thực sự tạo ra sự khác biệt lớn về hiệu suất. Và bây giờ chúng ta thấy, ví dụ,Gemini 3ra mắt và sau đó có rất nhiềuđiểm chuẩntốt, và sau đó3.1ra mắt có hiệu suất gấp đôi trong một sốđiểm chuẩn, điều này thật điên rồ. Rõ ràng, cácmô hìnhrất giống nhau, nhưng thực tế trong quá trình huấn luyện, họ đã huấn luyệnmô hìnhmới theo những cách thông minh hơn để cải thiện hiệu suất một cách đáng kể. -
Phần
suy luận(Inference): Cuối cùng, tất nhiên là phầnsuy luận, phần này sẽ rất dễ dàng đối với chúng ta vì đây sẽ là mộtmô hìnhnhỏ có thể chạy ở mọi nơi. Vì vậy, đó sẽ là một phần rất đơn giản của các khối xây dựng.
Điều kiện tiên quyết và Bắt đầu
Bất kỳ máy tính xách tay nào cũng có thể được, miễn là có ít nhất 16GB RAM. Bạn cũng có thể làm việc với các máy tính xách tay nhỏ hơn, nó sẽ chỉ chậm hơn một chút. Các máy tính xách tay lớn hơn, bạn có thể tăng kích thước lô (batch sizes) cao hơn để huấn luyện nhanh hơn. Python 3.12 và tôi hy vọng rằng hầu hết các bạn đều có một số ý tưởng về cách viết Python. Nếu bạn không biết, tôi nghĩ bạn vẫn có thể sao chép-dán mọi thứ cho đến khi có cái gì đó hoạt động hoặc yêu cầu trợ giúp. Việc huấn luyện này sử dụng chip Apple Silicon (kiến trúc MPS), CUDA hoặc CPU, vì vậy bạn về cơ bản có thể hỗ trợ mọi thứ.
Về việc bắt đầu, để mọi thứ dễ dàng hơn, tôi đang sử dụng uv cho dự án này. Vì vậy, nếu bạn có máy tính xách tay, bạn có thể cài đặt uv trên máy của mình nếu chưa. Nó khá đơn giản và lý do uv khá đơn giản là bạn chỉ cần chạy uv sync và nó tạo ra một môi trường ảo (virtual env) cho bạn và làm cho cuộc sống của bạn dễ dàng hơn. Chúng ta sẽ viết mã trong Scratchpad hoặc nếu bạn đang sử dụng Google Colab, điều mà thành thật mà nói, nếu internet của bạn không tốt lắm, có lẽ đó là một ý hay hơn. Nếu bạn chỉ cần truy cập và tạo một dự án Colab mới, bạn có thể chạy lệnh này để cài đặt những gì chúng ta cần, đó là torch, numpy, tqdm và tiktoken. tiktoken chủ yếu để kiểm tra mọi thứ.
Tokenization và Bảng Nhúng
Đó là sự đánh đổi của chúng tôi, nhưng đó là sự đánh đổi mà chúng tôi sẵn sàng chấp nhận vì chúng tôi đang chạy một model nhỏ. Trong tương lai, nếu bạn muốn mở rộng model này lên một cái gì đó tốt hơn, nếu bạn muốn huấn luyện một LLM thực sự và bạn sẵn lòng huấn luyện trong khoảng một tuần hoặc sử dụng các GPU lớn hơn, sử dụng một tokenizer phù hợp – chẳng hạn như byte pair encoding là cách phổ biến nhất để thực hiện tokenization hiện nay. Về cơ bản, bạn lấy tất cả dữ liệu huấn luyện của mình, tìm tất cả các mẫu chung và kết hợp các mẫu ký tự chung đó thành các mã thông báo cụ thể mà sau đó bạn có thể tái sử dụng và mô hình có thể hiểu được mối quan hệ của chúng.
Cách điều này kết nối với bản thân mô hình là chúng ta có một bảng nhúng (embedding table) với kích thước bằng kích thước từ vựng (vocab size). Sau đó, như tôi đã nói trước đây, nó lấy một vectơ các số nguyên và sau đó trả về – xin lỗi, một danh sách các số nguyên và sau đó trả về một danh sách các vectơ, đó sẽ là các embedding của chúng ta. Và như tôi đã nói trước đây, nếu chúng ta sử dụng một tokenizer lớn như vậy, nó cũng sẽ lớn hơn gấp đôi kích thước mô hình của chúng ta, bởi vì nếu bạn chỉ nhân kích thước embedding của chúng ta là 384 cho mô hình chúng ta sẽ huấn luyện, thì đó đã là 25.000 tham số. Và trong trường hợp đó, nếu chúng ta sử dụng vocab của GPT-2 là 50.000, thì đó sẽ là 19 triệu tham số, tức là lớn hơn gấp ba lần mô hình, điều này sẽ không hợp lý trong trường hợp của chúng ta.
Kiến trúc Transformer
Bây giờ, chuyển sang phần tiếp theo của workshop này, đó là bản thân transformer. Như tôi đã đề cập trước đây, các transformer đã trở nên phổ biến (commoditized). Cách transformer hoạt động là có nhiều lab khác nhau tìm ra các tối ưu hóa khác nhau, nhưng về nguyên tắc, các tối ưu hóa đó chủ yếu là về việc "chúng ta có ý tưởng cơ bản này hoạt động rất tốt, làm thế nào để chúng ta có thể làm cho nó huấn luyện nhanh hơn và có ngữ cảnh lớn hơn?". Mọi người tìm ra những cách tối ưu hóa hơn là nhất thiết phải phát minh lại bánh xe.
Đối với điều này, ít nhất có một số phương pháp lai phức tạp hơn. Vì vậy, tôi sẽ không đi quá sâu vào cách transformer hoạt động. Và cũng để chứng minh rằng bạn không nhất thiết phải hiểu sâu sắc cách transformer hoạt động để có thể huấn luyện một cái gì đó như thế này. Khi tôi lần đầu thực hiện dự án này, tôi không hề biết transformer hoạt động như thế nào và tôi vẫn chưa hiểu nhiều lắm vào cuối dự án trước tôi đã làm. Nhưng khi bạn càng làm việc với nó và bạn càng có động lực để tiếp tục, bạn có thể hiểu tất cả các khái niệm khác nhau cùng nhau và cách chúng kết hợp với nhau, cũng như lý do tại sao chúng lại như vậy.
Các Khối Kiến Tạo của Transformer
Để quay lại bức tranh lớn, transformer đang sử dụng bốn khối kiến tạo khác nhau này.
Multi-Head Self-Attention
Thứ nhất là multi-head self-attention. Attention là điểm khác biệt khiến transformer khác với các mạng nơ-ron khác, đó là chúng thực sự có thể "chú ý" (attend) đến các mã thông báo trước đó và hiểu mối quan hệ giữa các mã thông báo mà tôi đã đề cập. Và đó là nơi attention xuất hiện. Tất nhiên, attention của bạn càng lớn, mô hình càng hiểu rõ những mối quan hệ đó. Quay trở lại điều tôi đã nói, đó là những gì các lab lớn như Gemini đang cố gắng làm: tạo ra một triệu ngữ cảnh và họ đang tìm cách vì nếu bạn chỉ cố gắng sử dụng một triệu ngữ cảnh cho một mô hình như thế này, nó sẽ dễ dàng bị lỗi, phép toán sẽ không hoạt động. Vì vậy, đó là lúc các kỹ sư từ các nhà nghiên cứu từ Gemini tìm ra cách để làm cho nó hoạt động và đó là điều tạo nên sự khác biệt, nhưng về cơ bản, đó vẫn là cùng một kiến trúc.
MLP (Mạng Nơ-ron Truyền Thẳng)
Tiếp theo là MLP hoặc feed forward network, về cơ bản nó lấy các mối quan hệ khác nhau giữa các mã thông báo đó. Và trong khi lấy chúng, bản thân các mối quan hệ được sắp xếp, nó kết hợp chúng lại với nhau để có thể tạo ra các logit mà sau đó sẽ là – về cơ bản, nó lấy ngữ cảnh và sau đó sắp xếp nó theo cách mà mô hình có thể tạo ra các logit và sau đó tạo ra các mã thông báo. Tôi hy vọng đó là một lời giải thích hợp lý.
Residual Connections (Kết nối Dư)
Sau đó, bạn có các residual connections, về cơ bản là để mô hình không phải "tự phát minh lại" sau mỗi layer. Residual về cơ bản có nghĩa là mỗi layer mà đi qua các kích hoạt (activations) – bởi vì như chúng ta sẽ nói sau, transformer được xây dựng trên nhiều layer khác nhau mà chúng ta truyền các activations qua từng layer một – và residuals ở đó để mỗi activation không hoàn toàn khởi động lại mọi thứ từ đầu. Nó chỉ thay đổi chúng một chút. Vì vậy, nó chỉ lấy đầu vào trước đó và tạo ra một sự khác biệt nhỏ và thêm vào. Layer tiếp theo làm điều tương tự và layer tiếp theo cũng làm điều tương tự và điều này sẽ tiếp tục. Bằng cách này, mô hình không, mỗi layer không tạo ra một thay đổi lớn đối với bản thân đầu vào, và mô hình có thể ổn định hơn trong quá trình huấn luyện.
Layer Normalization (Chuẩn hóa Lớp)
Cuối cùng, chuẩn hóa lớp (layer normalization) có một vai trò rất tương tự trong việc có thể – layer normalization cho phép bạn thu nhỏ các activation đó theo cách cho phép các activation đó không bùng nổ thành các giá trị rất lớn. Vì vậy, nếu một layer nhân activation của bạn lên 10 lần, giả sử, layer norm sẽ đẩy nó trở lại các giá trị bình thường. Vì vậy, nó không đi theo kiểu 10x 10x 10x và sau đó bạn có thể có hàng triệu giá trị bắt đầu từ 0.5 và sau đó kết thúc ở 10 triệu. Đó là mục đích của layer norm. Nhưng một lần nữa, đây chỉ là các khối kiến tạo. Bạn không nhất thiết phải biết tại sao chúng ở đó và mục đích của chúng là gì. Bạn sẽ học điều này khi bạn bắt đầu làm việc nhiều hơn với các mô hình này và hiểu tại sao những quyết định này được đưa ra, bởi vì tất cả chúng, như tôi đang nói với bạn, tất nhiên được thực hiện bởi – chúng ta có một ý tưởng nhất định, tất cả điều này không hiệu quả, vậy hãy thêm cái này để nó hoạt động. Xin lỗi, mời bạn tiếp tục.
[transcript bị gián đoạn]
Xin lỗi vì đã. Vâng. Vì vậy, tôi đã đi qua phần tokenization. Đó là điểm trước đó của tôi. Và đó là nếu bạn có thể quay lại, và bạn sẽ phải đi đi lại lại qua các slide khi bạn tự làm việc với mô hình của mình, bởi vì bạn sẽ phải sao chép-dán các phần hoặc tự mình tìm ra chúng. Nhưng nó đã giải thích tại sao chúng tôi chọn tokenization cấp ký tự và những lựa chọn khác mà chúng tôi có. Sau đó, đối với transformer, tôi đã giải thích các khối kiến trúc khác nhau trong bức tranh lớn.
Tokenization trong Ngôn ngữ Lập trình
Và bây giờ chuyển sang cách điều này trông như thế nào trong mã nguồn, bởi vì tất cả những gì tôi mô tả cho bạn thực sự rất ít code bạn cần để triển khai chúng. Đầu tiên, chúng ta bắt đầu với cơ sở của – xin lỗi, mời bạn tiếp tục.
[transcript bị gián đoạn]
Khi nói đến Python, bởi vì ít nhất trong tiếng Anh, bạn biết bạn có các từ, từ vựng khá cố định, nhưng trong Python, bạn có các biến riêng và vân vân. Vậy tokenization hoạt động như thế nào trong môi trường đó? Một mã thông báo sẽ là gì? Xin lỗi.
[transcript bị gián đoạn]
Vì vậy, trong một ngôn ngữ lập trình, tất nhiên bạn có cú pháp lập trình, các từ khóa (keywords), nhưng sau đó bạn cũng có các biến thông thường, tên hàm và vân vân. Vậy khi nói đến tokenization, điều đó hoạt động như thế nào?
Vâng, như tôi đã đề cập trước đây, chúng ta sẽ sử dụng một tokenizer cấp ký tự cho dự án này. Nhưng hầu hết các lab lớn không sử dụng tokenization cấp ký tự. Họ sử dụng thứ tôi đã đề cập trước đó, tokenizer BPE hoặc byte – về cơ bản, những gì họ làm là bạn xem xét dữ liệu huấn luyện của mình. Giả sử bạn có hàng nghìn tỷ mã thông báo và dữ liệu huấn luyện của bạn sẽ bao gồm code như bạn đã đề cập. Và cách nó hoạt động là nó sẽ chỉ tìm kiếm các mẫu chung. Vì vậy, nếu bạn có nhiều dữ liệu huấn luyện là code thì bạn sẽ thấy và sau đó bạn sẽ nhận ra "các for loop có vẻ là một ứng cử viên tốt để trở thành một mã thông báo". Vì vậy, for chắc chắn sẽ là một mã thông báo và sau đó bạn có thể có một số từ như enumerate, bạn thấy khá phổ biến trong dữ liệu huấn luyện. Vì vậy, đó sẽ là một mã thông báo khác. Vì vậy, bạn sẽ xem xét tất cả các mã thông báo khác nhau này và sau đó tạo tokenizer này dựa trên mối quan hệ chung giữa chúng. Tất nhiên, có lẽ một số ngôn ngữ như Pascal có thể không rất phổ biến, vì vậy các từ khóa của Pascal có thể không rất phổ biến. Mặc dù tôi nghĩ rằng có lẽ có một đại diện tốt trong các tokenizer đó, nhưng một số ngôn ngữ khác như những ngôn ngữ chỉ có khoảng trắng (whitespace only languages) này có lẽ sẽ không hoạt động rất tốt.
[transcript bị gián đoạn]
Câu hỏi của tôi thiên về. Một điều là các từ khóa của ngôn ngữ lập trình, nhưng sau đó bạn có tên biến của riêng bạn, không phổ biến giữa các chương trình khác nhau.
[transcript bị gián đoạn]
Tôi sẽ xem xét điều đó trong một khoảnh khắc.
[transcript bị gián đoạn]
Việc biến chúng thành mã thông báo, tôi không chắc điều đó giúp ích như thế nào.
Vì vậy, một lần nữa, không có cách cụ thể nào, không có con người nào tham gia vào quá trình này. Nó phụ thuộc vào dữ liệu huấn luyện bạn sử dụng để huấn luyện tokenizer này. Nếu code của bạn có foo và bar là các biến phổ biến, thì những thứ này có thể sẽ trở thành mã thông báo. Nếu tên biến của bạn rất lạ, như các ký tự ngẫu nhiên, thì có lẽ điều đó sẽ không có trong tokenizer. Và khi điều này xảy ra, tokenizer sẽ quay trở lại tokenization cấp ký tự hoặc cấp byte. Vì vậy, nếu mã thông báo của bạn chỉ là những ký tự ngẫu nhiên, nó sẽ là mỗi ký tự hoặc có thể một số tổ hợp có thể đi cùng nhau dưới dạng các mã thông báo khác nhau, nhưng hầu hết chúng sẽ là các mã thông báo riêng biệt và điều đó sẽ khá khó khăn khi thực hiện inference và – vâng, có lẽ không phải là tốt nhất nếu bạn muốn inference hiệu quả.
Các Tham số của Transformer trong Mã nguồn
Nhưng vâng, quay trở lại phía transformer của vấn đề, như tôi đã nói, các model nhìn chung khá giống nhau. Và code để thực sự triển khai các transformer này cũng khá đơn giản và dễ viết. Nó chỉ khoảng 100 dòng, thực tế thường ít hơn thế.
Điều đầu tiên chúng ta phải chấp nhận là các tham số của transformer này nên trông như thế nào. Như tôi đã đề cập trước đây, vocab size là kích thước của tokenizer của bạn. Trong trường hợp của chúng ta sẽ là 65. Block size về cơ bản là sequence length hoặc context window. Trong trường hợp của chúng ta, đó sẽ là 256, rất rất nhỏ đối với các model này, nhưng bởi vì chúng ta đang huấn luyện một model cục bộ, đó là điều chúng ta phải làm. Các lab lớn hơn sẽ sử dụng context size một triệu cho những thứ như vậy. Nhưng nói chung, 16.000 là một điểm trung gian phổ biến.
Sau đó, chúng ta có các layers. Như tôi đã đề cập trước đây, transformer có nhiều layer và sau đó bạn chạy các activation qua từng layer đó. Chúng ta sẽ chọn một con số khiêm tốn là sáu. Và sau đó là attention heads – cách attention hoạt động là bạn sẽ có các head khác nhau cho attention sẽ "chú ý" đến những thứ khác nhau. Ví dụ, một trong các attention heads có thể đang xem xét dấu câu, có lẽ một attention head khác có thể đang xem xét ngữ pháp. Vì vậy, tất cả các attention heads khác nhau này đều "chú ý" đến một tính năng cụ thể của văn bản hoặc nếu bạn đang sử dụng âm thanh, một tính năng cụ thể của âm thanh, v.v.
Cuối cùng, đó là embedding dimension. Đó là kích thước thực tế của các vectơ của các mã thông báo mà bạn tạo ra. Trong trường hợp của chúng ta, chúng ta sẽ bắt đầu với 384. Đó là tiêu chuẩn cho GPT-2.
Giá trị Mã thông báo và Cấu trúc Mã Nguồn Cơ Bản
Tuy nhiên, một giá trị lớn hơn tất nhiên sẽ chứa nhiều thông tin hơn trên mỗi mã thông báo. Một giá trị nhỏ hơn sẽ chứa ít hơn. Nhưng đó là một giá trị khá tiêu chuẩn mà bạn có thể bắt đầu.
Về phần codebase chính, bạn có thể thoải mái sao chép một phần của nó nếu bạn muốn dành nhiều thời gian hơn để tìm hiểu. Tôi không muốn đi quá sâu vào tất cả các chi tiết nhỏ về cách mọi thứ hoạt động. Nhưng về cơ bản, bạn thường có một module tổng thể – trong trường hợp này, chúng ta sẽ gọi đó là GPT – và module cấp cao nhất đó sẽ bao gồm tất cả các module khác mà tôi đã mô tả trước đó. Trong trường hợp này, nó sẽ nhận config mà chúng ta đã có ở trên, và sau đó chúng ta sẽ tạo ra nó bằng cách sử dụng torch module dict ở đây, đơn giản vì đó là cách dễ nhất để triển khai. Nhưng tất cả đây đều là toán học; mọi thứ bạn thấy ở đây đều là các phép tính, như các phép nhân ma trận mà torch cho phép chúng ta trừu tượng hóa và làm mọi thứ dễ dàng hơn. Hầu hết tất cả những gì bạn thấy ở đây đều là các mạng nơ-ron (neural networks), dù nhỏ hơn hay lớn hơn, được kết hợp lại với nhau.
Kích thước Đầu vào và Tham số Mô hình
Một câu hỏi. Vâng, đúng không? Vâng. Đúng vậy. Vậy là có nhiều thứ liên quan đến số lượng tham số. Ừm, không hẳn. Xin lỗi, có lẽ bạn có thể nhắc lại câu hỏi bằng micro. Vậy thì, kích thước của chuỗi đầu vào có liên quan đến số lượng tham số trong
mô hìnhcủa bạn không? Không nhất thiết. Bạn có thể có các chuỗi lớn với cácmô hìnhnhỏ, hoặc các chuỗi nhỏ với cácmô hìnhlớn hơn. Điều chính mà điều này cho thấy về cơ bản là có hai cách bạn có thể huấn luyện mộtmô hình. Bạn có thể huấn luyện mộtmô hìnhcófull attention(toàn bộ sự chú ý). Giả sử nó nhìn vào toàn bộ sự chú ý. Tức là nó nhìn vào toàn bộ quá khứ, về cơ bản, đó là những gì chúng ta đang xây dựng ở đây hôm nay. Hoặc bạn có thể có mộtmô hìnhcửa sổ (windowed model). Đó là nơi mà hầu hết cácmô hìnhlớn hơn hiện tại chỉ nhìn vào một lượng nhất định trong quá khứ. Và tham số này là kích thước củacửa sổ ngữ cảnhđó, phải không? Đúng vậy. Đó là thứ chúng ta sẽ sử dụng để huấn luyện. Vì vậy,mô hìnhsẽ không thấy bất kỳ điều gì lớn hơn 256mã thông báotrong một chuỗi. Nếu bạn cố gắng vượt quá con số đó, nó sẽ... nó sẽ bị lỗi. Sẽ có vấn đề.
Tối ưu hóa Hiệu suất Mô hình
Nhưng giả sử chúng ta có đủ năng lực tính toán để tăng số lượng tham số nhằm giúp
mô hìnhsuy luận tốt hơn, phải không? Bạn sẽ tăng con số nào ở đây? Chẳng hạn, bạn sẽ tăng số lượnglớp,đầu chú ý(attention heads) hay...? Tôi sẽ tăng tất cả những gì bạn thấy ở đây, ngoại trừ có lẽ là kích thướcembedding(nhiều chiều). Tôi không nhớ chính xác cácmô hìnhkhác làm gì. Những con số chính xác đó, tôi nghĩ có vẻ hợp lý với tôi. Nhưng mọi thứ khác hoạt động tốt cho mộtmã thông báonhỏ, mộtmô hìnhnhỏ. Kích thướckhối(block size) 256 là rất nhỏ. Nếu bạn cố gắng chạy điều này trênJGPT, nó sẽ quên những gì bạn đã viết 10 câu trước đó. Nhưng bạn càng làm điều này lớn hơn, việc huấn luyện càng khó khăn hơn. Và đó là điều tôi đã nói về các luật mở rộng (scaling laws): bạn không thể chỉ đến đây và nói, "Được rồi, thực ra, tôi không muốn 256, tôi muốn độ dàingữ cảnh2 triệu." Bạn không thể huấn luyện như vậy, ít nhất là vớikiến trúchiện tại này. Đó là lý do tại sao các nhà nghiên cứu sau khiGPT-3.5ra đời, họ nói, "Được rồi, mọi người phàn nàn rằng chúng ta chỉ cókích thước ngữ cảnh16k. Chúng ta muốn 1 triệu. Làm thế nào để làm được điều đó? Bạn không thể chỉ thay đổi con số này. Bạn phải thay đổikiến trúcđể cho phép huấn luyện... [nếu không] nó sẽ hết bộ nhớ ngay lập tức." Vì vậy, đó là một trong những điều mà các nhà nghiên cứu hiện đang cố gắng tìm ra: làm thế nào chúng ta có thể tăng các con số này trong khi vẫn giữ cho quá trình huấn luyện ổn định, và vẫn có đủkhả năng tính toánđể thực hiện điều này? Tôi hy vọng điều này trả lời câu hỏi của bạn. Tuyệt vời. Cảm ơn bạn.
Kiến trúc Mô hình GPT
Vậy thì, điều đầu tiên chúng ta phải làm là, như tôi đã đề cập trước đây, tạo ra biểu diễn cấp cao nhất của mô hình của chúng ta mà bạn có thể thấy ở đây dưới dạng lớp GPT. Tôi sẽ không đi sâu quá nhiều vào chi tiết nữa, nhưng bạn sẽ muốn biết cách mô hình của mình hiểu các embedding và cách nó hiểu embedding vị trí. Tôi không muốn đi vào chi tiết, nhưng về cơ bản, các mã thông báo cần phải hiểu cả hai. Sau đó, bạn sẽ có tất cả các lớp mà tôi đã đề cập trước đó, và mỗi lớp được cấu hình như một khối (block), mà chúng ta sẽ xem định nghĩa của khối sau. Nhưng về cơ bản, một khối giống như một lớp của transformer bao gồm attention riêng của nó và chuẩn hóa lớp (layer norms) riêng của nó, và chúng liên kết với nhau trong quá trình forward pass. Cuối cùng, bạn có LM head, và về cơ bản, LM head lấy tất cả các đầu ra ở trên và kết nối chúng lại với nhau thành cái mà chúng ta gọi là logits, đó là phân phối của mã thông báo tiếp theo cần được tạo ra. Bởi vì, như tôi đã đề cập trước đó (có lẽ tôi đã nói, có lẽ không), cách transformer hoạt động là chúng dự đoán mã thông báo tiếp theo. Vì vậy, bạn lấy ngữ cảnh trước đó, bạn dự đoán mã thông báo tiếp theo và bạn phải lấy mẫu mã thông báo này từ phân phối này. Và LM head tạo ra phân phối này mà chúng ta lấy mẫu từ.
Cơ chế Forward Pass
Tiếp theo, phần quan trọng khác, mà lại khá đơn giản và tương tự ở hầu hết các mô hình này, là một forward pass. Về cơ bản, nó cho phép mô hình nhận đầu vào, đó là các mã thông báo chúng ta đã đề cập, và đẩy chúng qua tất cả các thành phần khác nhau mà tôi đã nhắc đến trước đó. Nó thực hiện một số bước tiền xử lý, biến mã thông báo thành token embedding và embedding vị trí. Nó cộng chúng lại với nhau, sau đó đi qua tất cả các khối của một transformer, tất cả các lớp khác nhau của transformer.
Và chỉ cần chạy
forward passcủatransformerđó, sau đó thực hiện cácchuẩn hóa lớp, rồi đi quaLM headđể nhận được mộtphân phốicủa cáclogitsmà chúng ta đã đề cập trước đó. Và điều này là khi bạn đang huấn luyện. Trong trường hợp này, nó cũng sẽ tính toánloss(sai số). Và sau đó, cuối cùng, bạn sẽ nhận đượclogitsvàlosslàm đầu ra củamô hình transformercủa bạn. Tôi có một số biểu đồ ở đây mà bạn có thể xem; chúng khá cơ bản nhưng về cơ bản, chúng thể hiện luồng: lấy cácID mã thông báo(token IDs), truyền chúng, biến chúng thànhtoken embeddingvàembedding vị trí. Bạn cộng chúng lại với nhau, bạn truyền chúng đếntransformer, sau đó quachuẩn hóa lớpgiúp đưa đầu ra vào một không gian màLM headcó thể hiểu dễ dàng hơn. Và sau đó bạn trả vềphân phốivềmã thông báotiếp theo nên là gì, với kích thước này. Như tôi đã đề cập trước đó, 65 là kích thước củatừ vựng(vocabulary) hoặc kích thướcmã thông báocủa chúng ta.
Tự chú ý (Self-Attention)
Giờ đây, tự chú ý (self-attention) phức tạp hơn một chút. Tôi không muốn đi quá sâu vào cách đó hoạt động. Nhưng về cơ bản, attention có nhiệm vụ hiểu các mối quan hệ giữa các mã thông báo. Về cơ bản, điều gì là quan trọng? Chẳng hạn, nếu tôi nói 'bầu trời màu xanh,' thì 'bầu trời' và 'xanh' có mối tương quan rất lớn. Vì vậy, đó là những gì attention làm: dựa trên cách bạn đã huấn luyện trọng số của mình, bạn sẽ hiểu mã thông báo nào nên chú ý đến nhau và đặt trọng tâm cao hơn vào những mối quan hệ cụ thể đó. Quay trở lại điều tôi đã nói trước đây về lý do tại sao bộ mã hóa token (tokenizer) lại quan trọng đến vậy: bởi vì 'bầu trời' và 'xanh' rất dễ để mô hình tạo ra mối quan hệ này, trong khi trong trường hợp của chúng ta, bạn phải kết hợp các nhóm mã thông báo khác nhau lại với nhau, các ký tự khác nhau lại với nhau, điều đó khó hơn một chút. Nhưng đó là những gì attention làm: nó là cái mà mã thông báo này nên chú ý trong quá khứ và cái nào có tầm quan trọng nhất. Và nó cũng có một forward pass như tất cả các khối khác này. Trong quá trình triển khai của bạn, hãy thoải mái sao chép các khối này. Có lẽ bạn có thể dành thêm thời gian để hiểu các sơ đồ và cách mọi thứ hoạt động. Nhưng như bạn có thể thấy, ngay cả một thứ phức tạp như attention cũng chỉ là một vài dòng mã để triển khai.
Khối MLP và Kiến trúc Transformer Block
Và, cuối cùng, khối MLP (MLP block) cũng như tôi đã đề cập trước đó... lại một lần nữa, tôi đã nói về attention đa đầu (multi-head attention) – tại sao attention lại có nhiều đầu (heads) – bởi vì mỗi đầu chú ý đến các phần khác nhau tạo nên ngôn ngữ. Và sau đó khối MLP nhận đầu ra của attention và tổng hợp tất cả các mối quan hệ khác nhau đó thành một thứ mà mô hình có thể hiểu tốt hơn, về cơ bản lại là một mạng nơ-ron (neural network) kết hợp mọi thứ thành một thứ dễ hiểu hơn cho LM head để sau đó có thể tạo ra phân phối cho các logits.
Vâng. Vâng. Không, bạn có thể ngắt lời. Chúng ta có các
khối transformertương tự. À... Nó có cùngkiến trúcgiữa các [khối] khác nhau không? Không, mỗikhốicótrọng sốriêng của nó. Mộtkhốilà cách mỗilớp... Về cơ bản, bạn sẽ có mỗilớpsẽ gọi cáckhối. Chúng thường có một phần khác cho một tiền tố khác chotrọng số, chẳng hạn nhưMLPsẽ khác... à, có lẽ chúng thường được gọi làkhối FFNthay vìMLP, nhưng vẫn sẽ là như vậy. Vì vậy, mỗikhốicótrọng sốriêng và mỗilớp... Vâng, mỗikhốicóMLPriêng. Vì vậy, mỗikhốicó... như bạn có thể thấy ở đây,attentionriêng của nó,chuẩn hóa lớp(layer norms) riêng vàMLPriêng. Đó là những gì về cơ bản tạo nên mộtlớpcủatransformer, và đó là điểm tôi sẽ nói sau. Mọi thứ kết hợp lại trongkhốimà chúng ta có thể thấy ở đây, về cơ bản là mộtlớpcủatransformercó một sốchuẩn hóa(normalization), chạyattentionđể có được mối quan hệ giữa cácmã thông báo, và sau đó cóMLPlấy những mối quan hệ đó và biến chúng thành một biểu diễn dễ dàng chomô hìnhđể tạo ra cáclogits. Và điều này... ôi. Đây là những gì tôi đang trình bày. Xin lỗi, tôi có hai màn hình khác nhau. Tôi không nên làm vậy. Nhưng vâng, đây làkhối transformer. Đây làkhốixây dựng cơ bản của mộttransformer. Nó có mộtchuẩn hóa lớp,attention, và mộtchuẩn hóa lớpkhác. Không phải tất cả cácmô hìnhđều có cấu hình như thế này, nhưng cái cụ thể này có. Và sau đóMLPnhận mọi thứ và tạo ra một biểu diễn có ý nghĩa để chúng ta có thể tạo ra các thế hệ. Và bạn có thể thấy ở đây một biểu đồ đơn giản hơn về cách hoạt động này.
Kết nối dư (Residual Connections)
Vâng.
nano GPTgốc cũng cókết nối dư(residual connection). Nhớ không? À, vậy thìresidualnên ở đó. Ừm... Không, không, ý tôi là trêncarpet NP, bạn có... Tôi không... tôi không nhớ; đã khá lâu rồi kể từ khi tôi... Được rồi. Giống như, tôi có ý tưởng từ đó, nhưng tôi đã xây dựng mọi thứ từ đầu, vì vậy tôi không thực sự nhớ liệu nó có hay không. Nhưng chúng làresidualnhư bạn có thể... Ý tưởng củaresiduallà, như bạn có thể thấy ở đây, thay vì thực hiệnx = attention, bạn thực hiệnx = x + attention. Vì vậy, bạn nhận được sự khác biệt... các giá trị củakích hoạt(activations) không thay đổi quá nhiều. Đó là ý tưởng của việc thực hiệnresidual.
Kiến trúc và Tham số Mô hình
Vâng, chúng tôi có sẵn một codebase cho phần này. Nếu bạn muốn tự triển khai, bạn có thể sao chép tất cả các lớp khác nhau này vào một tệp duy nhất tên là model.py. Đây về cơ bản là toàn bộ công thức toán học về cách các transformer hoạt động.
Như tôi đã đề cập trước đó, chúng ta phải quyết định kích thước của transformer. Số lượng tham số mà bạn thấy ở đây là khoảng 10 triệu tham số, dựa trên những gì tôi đã trình bày ở trên. Bạn có thể tự tính bằng cách cộng tất cả các tham số của các phần khác nhau trong các khối mà chúng ta đã có.
Cụ thể:
Token embeddingslà 65 * 384, chúng tôi đã nói đây làvectorchoembedding, nên nó là 25k.Positional embeddingslà 256 * 384, nhớ rằng 256 là độ dài chuỗi, độ dài chuỗi tối đa củamô hình. Vậy là thêm 98k tham số nữa.- Phần lớn nhất của logic nằm trong chính các
transformer block, nơi chứa hầu hết các tham số. Chúng ta có 4x — số 4 ở đây đến từ cáchattentionhoạt động, nơi bạn có cặpkey-query-key-value. Vì vậy, nó là một phần củaattentioncó bốn tham số khác nhau cho 384 (là số lượngmã thông báo) nhân với mối quan hệ giữa cácmã thông báođó. Vậy là 384per vectornhân với 384. Do đó, chúng ta có 590k choattention per layer. - Đối với
MLP(Multi-Layer Perceptron), chúng ta cũng có logic tương tự. Tôi không nhớ con số 1.536 này là gì, nhưng cuối cùng nó cho ra 1.2 triệu.
Tổng số lượng tham số mà chúng ta sẽ huấn luyện là 1.8 triệu tham số, con số này khá tốt và dễ dàng để huấn luyện trên hầu hết các thiết bị. Bạn có thể xem lại phần này; nó có nhiều chi tiết hữu ích để bạn nghiên cứu sâu hơn và tự tìm hiểu. Nhưng đây là ý tưởng tổng quát ở mức rất cao về cách các transformer này hoạt động.
Mục tiêu Huấn luyện và Cross-Entropy
Bây giờ chúng ta sẽ chuyển sang training loop (vòng lặp huấn luyện) và đó là phần quan trọng nhất của dự án này. Chúng ta sẽ xem cách huấn luyện transformer này để thực hiện những gì chúng ta muốn.
Mục tiêu của quá trình huấn luyện mà chúng ta sẽ thực hiện hôm nay là tạo ra một thứ gì đó dễ nhận biết: bạn sẽ biết khi nào mô hình bắt đầu hoạt động và "hiểu" (groks), tức là nó đã nắm được mục tiêu của mình. Sẽ rất dễ để hiểu rằng mô hình thực sự hoạt động.
Thứ hai, đó phải là thứ gì đó khá dễ để mô hình học. Nếu bạn dạy mô hình cách viết mã Python như một lập trình viên thi đấu, đó là một nhiệm vụ rất khó và chúng ta sẽ không thể làm được ở đây.
Trong trường hợp của chúng tôi, mục tiêu sẽ là tạo ra một Mô hình Ngôn ngữ Lớn (LLM) kiểu Shakespeare, có thể tạo ra các câu thơ từ các tác phẩm của Shakespeare.
Như chúng ta đã đề cập trước đó, các mô hình này học bằng cách dự đoán mã thông báo tiếp theo. Cách cross-entropy hoạt động là bạn lấy các mã thông báo hiện tại mà bạn muốn huấn luyện (ví dụ: từ t0, t1 đến tn) và sau đó bạn muốn dự đoán t1, t2 đến tn+1. Vì vậy, cách cross-entropy này hoạt động là bạn lấy chuỗi của mình và chỉ dịch chuyển nó đi một đơn vị, đó là những gì mô hình cần học để tính toán. Và tất nhiên, bạn không có mã thông báo cuối cùng, vì vậy bạn phải cắt nó. Sau đó, mô hình học cách dự đoán mã thông báo tiếp theo dựa trên logic này.
Tải Dữ liệu Huấn luyện
Đối với mã huấn luyện thực tế, trước tiên chúng ta cần tải dữ liệu. Chúng tôi có một hàm để tải dữ liệu. Dữ liệu đã có sẵn trong chính repo, nằm trong thư mục data. Đó là một tập hợp các dòng và câu thơ khác nhau từ Shakespeare, khoảng 1 triệu mã thông báo hoặc 1 triệu ký tự.
Việc tải dữ liệu rất đơn giản, thậm chí là quá đơn giản. Đó là một trong những điều bạn có thể tối ưu hóa. Về cơ bản, nó chỉ lấy tất cả các mã thông báo, chia thành tập hợp kiểm định (validation set) và tập hợp huấn luyện (training set), sau đó xáo trộn và lấy 256 batch các chuỗi mã thông báo từ văn bản và sử dụng nó để huấn luyện. Kích thước batch sẽ là 64. Vì vậy, nó lấy 256 chuỗi mã thông báo, 64 trong số đó, xếp chồng chúng lại và đưa vào mô hình để dạy nó cách huấn luyện. Đây là một data loader (trình tải dữ liệu) rất đơn giản. Thông thường, các data loader này có thể khá phức tạp, đặc biệt là khi bạn có context length lớn hơn, cách bạn tải dữ liệu là một phần rất lớn trong cách mô hình cần học. Nhưng trong trường hợp của chúng tôi, chúng tôi có một triển khai rất đơn giản.
Lựa chọn Thiết bị
Tiếp theo là cách mã hoạt động để sử dụng một device (thiết bị). Mã này hoạt động cho MPS, CUDA và CPU. Nó phụ thuộc vào những gì laptop của bạn hỗ trợ hoặc nếu bạn chạy trên Google Colab. Nếu bạn chạy Google Colab, nó sẽ phát hiện CUDA và sẽ khá nhanh. MPS cũng khá nhanh. CPU sẽ chậm nhất nhưng vẫn hoạt động khá tốt.
Điều chỉnh Tốc độ Học
Tiếp theo là learning rate (tốc độ học), đây là một phần quan trọng trong cách các mô hình có thể học. Cách các mô hình hoạt động là bạn thường bắt đầu với learning rate cao nhất có thể mà không làm mô hình trở nên không ổn định (hay nói cách khác là không làm mô hình "mất kiểm soát"). Về cách nó hoạt động, bạn bắt đầu ở learning rate rất cao, đó về cơ bản là lượng mà mô hình có thể học mỗi bước, trọng số của mô hình cần di chuyển bao nhiêu theo hướng bạn muốn. Nếu bạn có learning rate rất cao, bạn sẽ làm lệch mục tiêu của mình, vì vậy mô hình của bạn có thể đi chệch hướng rất nhanh. Do đó, bạn muốn sử dụng learning rate rất phù hợp để mô hình của bạn có thể huấn luyện tốt.
Và thường bạn có khái niệm warm-up (khởi động), nơi bạn bắt đầu với learning rate rất nhỏ để tất cả các tối ưu hóa, trọng số của mô hình có thể cố định vào những vị trí phù hợp để quá trình huấn luyện bắt đầu. Vì vậy, bạn bắt đầu ở learning rate rất thấp, tăng nhẹ cho đến khi đạt đến đỉnh, và sau đó ở đỉnh bạn bắt đầu giảm learning rate. Đó là điều chúng tôi gọi là weight decay (giảm trọng số), cho đến khi bạn đạt đến điểm bạn hài lòng. Đối với một số người, con số này là 0. Tôi thích không để nó về 0 vì sau đó khó để khởi động lại quá trình huấn luyện, nhưng đó là ý tưởng. Bạn muốn learning rate thấp hơn khi mô hình gần đạt đến sự hoàn hảo, vì vậy bạn muốn bắt đầu với những thay đổi rất lớn để tìm kiếm các cực tiểu cục bộ tốt hoặc cực tiểu toàn cục, và sau đó để nó hiệu chỉnh khi quá trình huấn luyện tiếp tục.
Vì vậy, chúng ta sẽ bắt đầu với warm-up nhỏ gồm 100 bước và sau đó chúng ta sẽ sử dụng cosine decay (phân rã cosine) cho đến số bước tối đa mà chúng ta sẽ thực hiện, trong trường hợp này là 5.000. Vì vậy, chúng ta sẽ bắt đầu từ mức rất thấp, đạt đỉnh ở 100 bước và sau đó bắt đầu giảm dần cho đến 5.000 bước. Và đó là những gì AdamW normalizer thực hiện. Về cơ bản, nó cho phép khái niệm kiểm soát learning rate này bằng cách sử dụng cosine decay, và đó là normalizer phổ biến nhất mà mọi người sử dụng, ít nhất là đã từng sử dụng. Bây giờ có những normalizer tốt hơn, nhưng đây là cái đơn giản nhất để bắt đầu.
Vòng lặp Huấn luyện Toàn diện
Đây là cách training loop đầy đủ sẽ trông như thế nào. Một lần nữa, không quá nhiều mã. Bạn chỉ cần khởi tạo config của mình, trong trường hợp này sẽ là sáu lớp, sáu attention heads (đầu attention), kích thước embedding là 384 và 256 là độ dài chuỗi. Bạn khởi tạo mô hình của mình, sau đó tạo optimizer và bắt đầu các bước huấn luyện. TQDM giúp theo dõi loss (mất mát) và tất cả những thứ tương tự. Bạn muốn loss của mình bắt đầu ở mức cao và sau đó tiếp tục giảm cho đến khi đạt đến mức chấp nhận được.
Và một phần quan trọng khác là các đánh giá để đảm bảo rằng mô hình của bạn thực sự hoạt động tốt. Đó là lý do tại sao val_loss (loss kiểm định) có mặt ở đây. Rất dễ để các mô hình nhỏ như thế này bị overfit (quá khớp) vì bạn không có nhiều dữ liệu. Vì vậy, loss có thể tiếp tục giảm, điều đó có nghĩa là những gì mô hình dự đoán và dữ liệu huấn luyện rất giống nhau. Loss của bạn có thể tiếp tục giảm rất nhiều. Nhưng thực tế, tại một thời điểm nào đó, mô hình của bạn có thể bị overfit trong trường hợp này, và khi nó bị overfit, mặc dù loss giảm nhưng hiệu suất của mô hình lại kém hơn. Đó là lý do tại sao chúng ta có val_loss, là một phần của dữ liệu mà mô hình chưa bao giờ nhìn thấy và chúng ta chạy một forward pass để lấy loss của phần dữ liệu đó. Nếu val_loss này rất thấp, điều đó có nghĩa là mô hình đang hoạt động tốt vì mô hình chưa bao giờ nhìn thấy dữ liệu này, vì vậy nó không thể ghi nhớ những thứ mà nó chưa từng thấy. Đó là những gì chúng ta có ở đây với val_loss.
Tôi sẽ không giải thích khái niệm về backwards losses (mất mát ngược), nhưng về cơ bản đó là cách trọng số của mô hình di chuyển theo hướng được tối ưu hóa. Vì vậy, với mỗi bước, chúng ta có kích thước batch là 256 mã thông báo, nhưng kích thước batch là 64. Chúng ta đẩy ma trận này qua mô hình của chúng ta, và sau đó nó học, và sau đó optimizer thực hiện một bước bổ sung và learning rate được điều chỉnh tùy thuộc vào các bước bạn đang ở hiện tại.
Và cuối cùng, để có một cách bổ sung, cứ sau 10.000 bước, bạn lưu checkpoint của mình để có thể khởi động lại quá trình huấn luyện nếu cần từ điểm đó. Chúng tôi cũng đang chạy inference (suy luận) trên checkpoint hiện tại để xem mô hình thực sự dự đoán gì tại thời điểm này.
Phân tích Loss và Hiện tượng Overfitting
Chúng ta sẽ bắt đầu thấy rằng khi bắt đầu từ đầu, vì đây là một mô hình chúng ta huấn luyện từ đầu, loss sẽ về cơ bản là ngẫu nhiên, và trong trường hợp đó, nó sẽ là logarit tự nhiên của 65. Vì vậy, nó sẽ bắt đầu vào khoảng 4.17. Điều đó về cơ bản có nghĩa là mô hình không biết gì cả. Nó không có khái niệm gì về dữ liệu này.
Và dần dần, chúng ta sẽ bắt đầu thấy loss giảm xuống 3.3. Đó là khi mô hình sẽ bắt đầu hiểu tần suất ký tự. Nó sẽ vẫn chưa thể tạo ra từ, nhưng có thể hiểu những thứ như "th" là một phần của "the", là một từ phổ biến; "th" sẽ là một phần của những thứ mà nó bắt đầu tạo ra.
Sau đó, ở khoảng 2.5, nó sẽ tốt hơn một chút về "th" này và sau đó nó sẽ hiểu từ "in" và những thứ tương tự. Sau đó, ở khoảng 1.5 đến 2 loss, nó sẽ bắt đầu thực sự tạo ra các từ, và sau đó ở khoảng 1.0 đến 1.2, đó là lúc mô hình sẽ bắt đầu hoạt động khá tốt ở nhiệm vụ này. Nó sẽ thực sự có thể hiểu các tên từ văn bản. Nó sẽ bắt đầu tạo ra những thứ có ý nghĩa.
Nhưng sau đó, khi loss bắt đầu giảm xuống dưới 1.0 cho bộ dữ liệu cụ thể này, đó là lúc chúng ta sẽ bắt đầu thấy hiện tượng overfitting. Mô hình vẫn sẽ tạo ra những thứ hợp lý, nhưng nó sẽ không còn tốt hơn nữa.
Đây là một ví dụ ở 200 bước khi tôi thử nghiệm điều này: nó chỉ tạo ra những thứ hoàn toàn vô nghĩa. Sau đó, val_loss ở khoảng 3.5. Sau đó, ở khoảng 800 bước, nó bắt đầu tạo ra những thứ khá ổn. Vẫn chưa tuyệt vời, nhưng nó bắt đầu đạt được mục tiêu. Và ở 1.000 bước, nó đã tốt hơn. Có một thời điểm mà val_loss thực sự bắt đầu tăng thay vì giảm, và đó là lúc chúng ta biết mô hình bị overfit. Vì vậy, ở khoảng 2.400 bước là nơi hiệu suất tối ưu của mô hình này. Và nếu chúng ta tiếp tục, hiệu suất có thể không giảm nhưng mô hình bắt đầu trở nên kém sáng tạo hơn. Đó là một trong những điều cần lưu ý.
Đánh giá Mô hình và Các Bước Huấn luyện Tiếp theo
Hiện tại, eVals không phải là metric tốt nhất nếu bạn thực sự nghiêm túc về việc tinh chỉnh các Mô hình Ngôn ngữ Lớn (LLM). Thông thường, bạn có thể có một số điểm chuẩn (benchmark) chạy như một phần của quá trình huấn luyện và bạn có thể theo dõi xem các điểm chuẩn đó có tệ đi hay không. Tuy nhiên, đối với chúng tôi, đó là một cách rất dễ dàng và rẻ tiền để hiểu mô hình đang hoạt động như thế nào.
Vậy thì, các bước tiếp theo bây giờ sẽ là tự mình huấn luyện mô hình này. Với tình hình đường truyền internet không tốt lắm, tôi khuyên bạn nên sử dụng Google Colab cho việc này. Bạn có thể sao chép (copy paste) các phần từ đây, bạn có thể ghép nối mọi thứ lại với nhau. Việc sao chép có thể giúp bạn đạt được 90% mục tiêu. Có rất nhiều chỗ để cải thiện đối với những gì tôi đã viết ở đây, tôi cố tình làm cho nó cực kỳ đơn giản và có những điều bạn có thể tự mình cải thiện. Nhưng ý tưởng là để có được một cái gì đó hoạt động. Tôi hy vọng mọi người sẽ có thể làm cho một cái gì đó hoạt động nếu bạn quan tâm đến việc thực hiện điều này và có một mô hình bắt đầu từ con số không và có thể tạo ra một kết quả hợp lý.
Sinh văn bản: Phương pháp Greedy Decoding
Sau khi đã huấn luyện mô hình, phần tiếp theo là sinh văn bản (text generation), đây là khía cạnh suy luận (inference) của mọi thứ. Chúng ta sẽ chỉ sử dụng... có nhiều cách để thực hiện suy luận. Một cách là greedy decoding mà tôi đã đề cập trước đó. Bạn có tất cả các logits (là phân phối của các mã thông báo (token)) và bạn chỉ lấy mã thông báo có khả năng cao nhất, đó là ý nghĩa của greedy decoding.
Ví dụ, giả sử mã thông báo T và mã thông báo H đều nằm trong phân phối. Một cái có 80% xác suất, cái còn lại có 15% xác suất. Bạn sẽ luôn chọn cái đứng đầu. Đó là greedy decoding. Greedy decoding không hoạt động tốt lắm cho các LLM. Nó có thể hoạt động tốt cho các mô hình khác, nhưng đối với các LLM, nó làm cho chúng trở nên rất nhàm chán và không sáng tạo trong những gì chúng tạo ra. Vì vậy, bạn gần như không bao giờ muốn sử dụng greedy decoding cho các LLM. Bạn sẽ muốn sử dụng nó cho các mô hình khác, ví dụ như chuyển đổi giọng nói thành văn bản (transcription), greedy decoding là tốt nhất vì thường chỉ có một cách để bạn có thể chuyển đổi một cái gì đó. Bạn không muốn nó sáng tạo trong chuyển đổi giọng nói thành văn bản. Đó không phải là một ý hay.
Nâng cao Khả năng Sinh văn bản: Nhiệt độ và Top-K Sampling
Đó là greedy decoding. Trong trường hợp của chúng tôi, chúng ta sẽ không sử dụng nó, chúng ta sẽ sử dụng temperature (nhiệt độ). Về cơ bản, temperature là bạn không phải lúc nào cũng chọn mã thông báo có xác suất cao nhất. Đôi khi bạn có thể chọn mã thông báo có xác suất cao thứ hai hoặc thứ ba. Và mặc dù điều đó có vẻ không hợp lý, tại sao bạn lại chọn một mã thông báo kém hơn trong tình huống này? Nhưng đã có bằng chứng cho thấy điều này thực sự làm cho mô hình hoạt động tốt hơn. Đôi khi nó có thể rơi vào một Agent Loop kỳ lạ. Vì vậy, có những kỹ thuật bạn có thể sử dụng để đảm bảo nó không trở nên "điên rồ" bằng cách tạo ra một số nội dung vô nghĩa.
Điều tồi tệ nhất là nếu nó dự đoán một mã thông báo kết thúc bản ghi hoặc mã thông báo kết thúc văn bản và sau đó dừng việc tạo ra, điều mà có thể bạn đã thấy đôi khi khi sử dụng ChatGPT khi nó đột ngột dừng lại mà không có lý do. Đôi khi là vì lý do đó. Nhưng có những cách bạn có thể ngăn chặn điều này. Nói chung, temperature 0.7 là điểm giữa tốt nhất để suy luận hoạt động tốt.
Và sau đó bạn có top-K sampling, về cơ bản là nó ngăn mô hình nếu bạn có, ví dụ, năm mã thông báo rất có khả năng và sau đó mã thông báo thứ sáu hoàn toàn không có khả năng. Top-K sampling ngăn mô hình dự đoán mã thông báo thứ sáu không có khả năng này, mặc dù temperature có thể khiến bạn không may và temperature có thể chọn nó.
Đó là những gì top-K sampling làm và đây là hàm suy luận của chúng ta, nó rất đơn giản. Nó chỉ chạy, lấy tất cả các mã thông báo làm đầu vào, truyền nó qua mô hình, lấy các logits ra khỏi mô hình và chạy softmax (đó là những gì tôi đã mô tả trước đó). Nó sử dụng temperature để tính toán xác suất và sau đó quyết định logit tiếp theo nên là gì dựa trên những xác suất đó.
Và một điều bạn có thể làm là sử dụng seeds. Về cơ bản, seeds có nghĩa là hiện tại mọi thứ sẽ ngẫu nhiên nếu bạn cứ tiếp tục thử lại. Nhưng nếu bạn sử dụng một seed đã đặt, thì suy luận của bạn sẽ luôn trả về cùng một giá trị, điều này sẽ liên quan sau này.
Tổng hợp Mã nguồn và Quy trình Huấn luyện
Sau đó, ghép nối tất cả lại với nhau. Nếu chúng ta ghép nối tất cả lại với nhau, chúng ta sẽ có ba tệp khác nhau. Một là model.py bao gồm kiến trúc mô hình của chúng ta. Một là train.py bao gồm việc tải tập dữ liệu (dataset loading) và Agent Loop huấn luyện. Và cuối cùng, generate.py bao gồm suy luận của chúng ta. Và tổng cộng, điều này sẽ chỉ khoảng vài trăm dòng mã. Rất đơn giản. Và ngay cả với lượng mã này, nếu chúng ta có phần cứng mạnh, chúng ta có thể huấn luyện một LLM tốt bằng cách sử dụng kiến trúc này, nếu chúng ta có đủ dữ liệu và đủ tài nguyên, đó là tất cả những gì bạn cần.
Và đó về cơ bản là những gì GPT-3 và GPT-2 đã làm khi OpenAI phát hành chúng. Tôi nhớ khi OpenAI sắp phát hành GPT-2 và họ nói rằng họ sẽ không phát hành nó vì nó quá nguy hiểm cho nhân loại. Và đó là thời điểm đó, đó là cơ sở mã mà họ đang làm việc, cùng với rất nhiều dữ liệu và tất nhiên là một mô hình lớn hơn. Bây giờ điều này có vẻ hơi buồn cười, nhưng đối với họ, đó là một khoảnh khắc rất đáng báo động rằng chúng tôi đã làm điều này và sau đó mô hình thực sự hoạt động rất tốt trong các tác vụ. Nhưng cuối cùng, đó chỉ là những gì bạn thấy ở đây.
Vậy thì, ghép nối tất cả lại với nhau, nếu bạn sử dụng Google Colab, bạn có thể sử dụng đoạn mã này để tải xuống tập dữ liệu và bạn có thể sử dụng lệnh pip install này để cài đặt các thư viện (dependencies) khác nhau. Và sau đó nó sẽ trông giống như thế này, nơi bạn cài đặt thư viện của mình, bạn sao chép mã của mình. Và bạn có thể cần thực hiện một số kết nối với nhau, nhưng về cơ bản, bạn có thể chạy một lệnh train để sử dụng tập dữ liệu mà bạn muốn sử dụng và điều này sẽ bắt đầu quá trình huấn luyện và sau đó bạn có thể thấy hiệu suất của mô hình cải thiện từng bước. Đối với tôi, việc này mất khoảng 15 phút để huấn luyện trên Google Colab và bắt đầu nhận được kết quả tốt. Với một số cải tiến, bạn có thể làm cho nó nhanh hơn và có thể bạn có thể làm cho nó chậm hơn nhưng thực sự nhận được kết quả tốt hơn. Nhưng về cơ bản, nó rất đơn giản để làm cho nó hoạt động.
Một điều cần nhớ là bạn phải thay đổi runtime type của mình thành T4 GPU. Bởi vì điều này miễn phí và nó sẽ chạy khá nhanh.
Thử nghiệm, Giám sát và Gỡ lỗi Quá trình Huấn luyện
Vâng, hãy thoải mái huấn luyện. Bạn có thể thử bắt đầu với một mô hình rất nhỏ với 0.5 triệu tham số, chỉ có hai lớp và chỉ hai đầu chú ý (attention heads) và kích thước nhúng (embedding size) nhỏ hơn cho mỗi mã thông báo, sau đó bạn có thể thử các mô hình lớn hơn và lớn hơn cho đến khi bạn nhận được một số kết quả có thể sử dụng được.
Và như tôi đã đề cập trước đó, bạn có thể thử các chiều dài ngữ cảnh (context lengths) khác nhau. Tôi bắt đầu với 256, bạn có thể thử lớn hơn 512.
Và sau đó, nếu bạn muốn theo dõi và xem loss của bạn giảm như thế nào một cách đẹp mắt, bạn có thể sử dụng pipel này để xem các đồ thị. Những điều bạn muốn tìm kiếm là nếu train loss của bạn không giảm, điều đó có nghĩa là mô hình của bạn không học. Điều đó có thể có nghĩa là bạn có lỗi trong mã của mình, nếu train loss của bạn đang giảm nhưng validation loss của bạn không giảm mà thực sự tăng lên, điều đó có nghĩa là bạn đã overfit. Và nếu bạn có những đỉnh loss rất kỳ lạ (thường loss phải rất mượt mà), điều đó có nghĩa là lại có một số loại lỗi trong dữ liệu của bạn hoặc trong quá trình huấn luyện của bạn.
Và khi mô hình bắt đầu đi vào trạng thái plateau và không cải thiện hơn nữa, điều đó có nghĩa là bạn đã gần như cạn kiệt khả năng hữu ích của tập dữ liệu hiện tại của mình. Vì vậy, hoặc bạn sẽ sử dụng một mô hình lớn hơn hoặc bạn sẽ cần nhiều dữ liệu hơn.
Thử thách Huấn luyện Mô hình và Cách Nộp Bài
Bây giờ, một điều thú vị mà tôi muốn chúng ta làm là tôi nghĩ sẽ rất tuyệt nếu chúng ta có một cuộc thi để xem ai có thể huấn luyện mô hình tốt nhất ở đây. Nếu có ai đó có thể làm cho mọi thứ hoạt động. Hy vọng việc cung cấp đủ tốt. Và để đọc thêm, bạn có thể xem nhiều tài liệu cho hội thảo này.
Nhưng về cơ bản, thử thách là chúng ta có thể cùng nhau bỏ phiếu để tìm ra mô hình nào thực sự tạo ra câu thơ hay nhất của Shakespeare hoặc, nếu bạn sử dụng một tập dữ liệu khác, một bài thơ hay nhất hoặc trong thể loại đó.
Các quy tắc là bạn phải tự huấn luyện mô hình, như ở đây và hôm nay. Không thể là bạn yêu cầu ChatGPT đưa cho bạn một câu thơ hay, bạn phải sử dụng mô hình của riêng mình và để chứng minh điều này, bạn sử dụng một seed với một lời nhắc cụ thể và xem kết quả nó đưa ra là gì. Bạn có thể tự do tạo lại (regenerate) mọi thứ bao nhiêu lần tùy thích cho đến khi bạn nhận được kết quả tốt nhất.
Và tôi sẽ có một mã QR để bạn có thể gửi kết quả của mình và tôi có thể đi vòng quanh và giúp đỡ mọi người nếu bạn cần bất kỳ sự trợ giúp nào trong việc chạy huấn luyện. Và tất nhiên, quá trình huấn luyện này rất cơ bản. Có nhiều cách bạn có thể tối ưu hóa và làm cho nó tốt hơn. Vì vậy, tôi đoán đối với những người có kinh nghiệm hơn một chút, họ có thể triển khai những cải tiến này và có thể nhận được kết quả tốt hơn từ mô hình của họ.
Người chiến thắng sẽ nhận được một số swag miễn phí từ Level Labs, có thể là một chiếc áo hoodie hoặc một số tín dụng miễn phí. Tôi sẽ xem xét những gì tôi thực sự có thể cung cấp.
Vậy thì, đây là phần nộp bài. Nó cần phải sáng tạo và sau đó nó cần phải là một câu thơ hay. Và chúng ta có thể sử dụng một kiểu đấu loại. Oh, chuyện gì đã xảy ra? Oh, tuyệt vời. Chúng ta có thể sử dụng một kiểu đấu loại và chúng ta có thể bỏ phiếu xem câu thơ nào nghe hay nhất. Chúng có thể hài hước, chúng có thể được làm tốt, tùy thuộc vào bạn.
Khả năng Tái tạo và Tối ưu hóa Nâng cao
Và khả năng tái tạo (reproducibility) sẽ trông như thế này: Bạn chạy python generate.py trên điểm kiểm tra tốt nhất của bạn, lời nhắc mà bạn quyết định (tùy thuộc vào bạn), temperature và sau đó là một seed chứng minh rằng bạn thực sự có thể tạo ra kết quả của mình. Bạn có thể thử các kích thước mô hình khác nhau, ví dụ như 85 triệu tham số sẽ hơi lớn cho việc này, nhưng nếu bạn có tài nguyên, bạn có thể thử. Bạn có thể thử các tokenizer tốt hơn, như cái này tất nhiên là dựa trên ký tự, nhưng có lẽ bạn có thể huấn luyện tokenizer BPE của riêng mình dựa trên tập dữ liệu đó. Và cũng có những chỉnh sửa khác mà bạn có thể làm như context lớn hơn, như có một số tối ưu hóa về tinh chỉnh như sử dụng giá trị dropout. Bạn có thể dừng lại bất cứ khi nào bạn cảm thấy mô hình đủ tốt, thay đổi tốc độ học (learning rates) và về cơ bản làm cho mô hình tốt nhất có thể.
Vâng, đó là ý tưởng. Tôi sẽ đi vòng quanh và giúp đỡ bạn. Xin lỗi. Cứ tiếp tục. Hoặc chỉ cần...
Mô hình Suy luận (Q&A)
Vậy các mô hình suy luận (reasoning models) có khác biệt khá nhiều trong việc huấn luyện không? Bạn đang hỏi liệu các mô hình suy luận có khác biệt khá nhiều không? Các khối xây dựng (building blocks) cơ bản rất giống nhau. Bạn có thể huấn luyện cùng một mô hình chính xác, bạn có thể huấn luyện hậu kỳ (post-train) nó, đó là cách mà suy luận thường được dạy cho mô hình này. Bạn có một mô hình cơ sở hướng dẫn (instruct model) tốt và sau đó bạn huấn luyện hậu kỳ nó để trở thành một mô hình suy luận. Điều này rất dựa vào dữ liệu (data-driven). Vì vậy, bạn cần dữ liệu chất lượng rất cao và bạn sẽ sử dụng một loss đủ tốt để có thể học dữ liệu này một cách rất hiệu quả. Sự phức tạp của các mô hình suy luận là tìm kiếm dữ liệu chuỗi suy nghĩ (chain of thought) tốt này. Đó là lý do tại sao OpenAI có tất cả những người gắn nhãn này, họ là những sinh viên tiến sĩ, họ ghi lại các lý do về cách họ giải quyết vấn đề.
Chất Lượng Dữ Liệu và Suy Luận trong Mô Hình AI
Dữ liệu cần có chất lượng rất cao vì nó dạy mô hình cách suy nghĩ. Bạn không thể chỉ lên Reddit và lấy các bài đăng ngẫu nhiên; chắc chắn mô hình sẽ không học được cách suy luận theo cách đó. Bạn cần dữ liệu có giá trị cao, chất lượng tốt, dạy cho mô hình quy trình suy luận này. Suy luận về cơ bản là việc thêm ngữ cảnh vào mô hình, tức là thêm logic vào cơ chế attention để khi mô hình tạo ra phản hồi, nó có thể quay lại và chú ý đến những mã thông báo suy luận đó và đưa ra một phản hồi tốt hơn. Điều này giống như mô tả mô hình tốt hơn một chút. Sau đó, mô hình quay lại, xem các mã thông báo bạn đã mô tả và nói: "Ồ, thực ra tôi đã tìm ra điều này rồi. Bây giờ tôi sẽ viết nó ra." (Microphone không hoạt động phải không?) Vâng, chính xác.
Các mô hình có khả năng suy luận và không suy luận thường chia sẻ cùng một nền tảng cơ bản, sau đó một trong số chúng được post-training theo một cách khác và thêm khả năng này theo một cách post-training riêng biệt. Rất nhiều phòng thí nghiệm, ví dụ như mô hình Llama được phát hành, như Llama 3, thường phát hành phiên bản cơ bản và phiên bản instruct. Phiên bản cơ bản thường không có chain of thought reasoning. Nó thường là cùng một mô hình mà họ đã pre-train đầu tiên để có hiệu suất khá tốt, có thể fine-tuning thêm. Sau đó, bước tiếp theo là thực hiện post-training để dạy cho mô hình loại kiến thức này. Đó là lý do tại sao bạn thấy rất nhiều cải tiến lớn diễn ra rất nhanh trong ngành, như Gemini 3 lên 3.1. Về cơ bản, điều này là do cung cấp cho mô hình dữ liệu suy luận tốt hơn và dữ liệu post-training để fine-tuning tốt hơn cho các vấn đề cụ thể. Nó hơi giống benchmarking. Dữ liệu này thường rất giống với những gì các điểm chuẩn hiện tại đang sử dụng. Nhưng về cơ bản, đó là việc lấy một mô hình cơ bản, sử dụng dữ liệu mới này để cải thiện nó lên cấp độ tiếp theo.
Đổi Mới Cốt Lõi trong Huấn Luyện Mô Hình
So với những gì chúng ta đang thấy ở đây, một mô hình rất cơ bản, liệu có những đổi mới cơ bản trong quá trình huấn luyện chính của mô hình đang cung cấp sức mạnh cho các mô hình hiện nay không? Hay chủ yếu vẫn là tương tự, nhưng chỉ với những thủ thuật thông minh hơn, những điều chỉnh nhỏ, dữ liệu tốt hơn, v.v.? Liệu mọi người vẫn đang sử dụng cùng một attention layer và những thứ tương tự, hay có những thay đổi cơ bản nào đã và đang xảy ra?
Bạn nói đúng rằng phần lớn không gian này vẫn giữ nguyên. Họ có thể thực hiện một số thay đổi về cách attention chú ý đến các mã thông báo khác nhau, bởi vì đôi khi suy luận có thể khá lớn. Vì vậy, bạn cần độ dài chuỗi lớn để có thể đạt được kết quả tốt. Do đó, có rất nhiều thủ thuật mà các phòng thí nghiệm mới thực hiện để làm cho cơ chế attention hiệu quả hơn. Nhưng nhìn chung, bạn có thể lấy một mô hình như GPT-2 và biến nó thành một mô hình suy luận. Nếu bạn có dữ liệu và một mô hình đủ lớn để thực sự học hỏi từ quá trình suy luận đó, bởi vì những mô hình nhỏ sẽ không được hỗ trợ nhiều bởi suy luận. Nhưng nếu bạn có một mô hình đủ lớn có thể rút ra những điều hữu ích từ suy luận. Có những người đã lấy các mô hình cũ hơn, ví dụ như Llama 1B, vốn nhỏ và không được huấn luyện cho suy luận, sau đó biến chúng thành mô hình suy luận bằng cách sử dụng cùng một kiến trúc.
Quy Trình Tạo Tập Dữ Liệu Vàng
Bạn đã nỗ lực bao nhiêu để có được tập dữ liệu vàng của mình, và quy trình bạn tuân theo là gì? Ý bạn là cho post-training? Vâng.
Vâng, như tôi đã đề cập trước đây, thông thường các phòng thí nghiệm sẽ tìm đến các công ty gán nhãn dữ liệu như Scale AI. Đó có lẽ là công ty lớn nhất. Và Scale AI có một đội ngũ người mà bạn có thể yêu cầu: "Tôi muốn các nhà vật lý. Hãy cung cấp cho tôi dữ liệu từ các nhà vật lý." Sau đó, Scale AI sẽ tìm các nhà vật lý hợp đồng, trả cho họ số tiền cần thiết, và các nhà vật lý này có thể viết ra mọi thứ. Họ có thể được liên hệ để làm những việc khác nhau. Nhưng về cơ bản, nhiều công ty như Scale AI cung cấp dữ liệu cho Anthropic. Một công ty khác đã được Meta mua lại, nên có lẽ không còn nhiều nữa. Nhưng về cơ bản, những tập dữ liệu này được cung cấp bởi các công ty gán nhãn dữ liệu như Scale AI. Tuy nhiên, nhiều phòng thí nghiệm lớn cũng có các nhóm gán nhãn dữ liệu riêng của họ, thuê các nhà thầu để tạo ra những tập dữ liệu này. Nhưng như bạn đã nói trước đó, nếu tập dữ liệu này có dù chỉ một vài vấn đề nhỏ, nó có thể tạo ra hoặc phá vỡ mô hình của bạn. Vì vậy, những tập dữ liệu này là loại đắt nhất; chúng sẽ tiêu tốn hàng tấn tiền, nhưng chúng thực sự là thứ giúp mô hình trở nên tốt như hiện tại.
Đánh Giá Chất Lượng Dữ Liệu
Chỉ một câu hỏi phụ nhanh: Ngay cả trên đó, bạn vẫn phải đánh giá, đúng không? Câu trả lời sẽ không hoàn toàn giống nhau. Vậy mọi người có thực sự xem xét điều này không, hay bạn vẫn dựa vào LLM để đánh giá?
Bạn phải dựa vào con người cho những loại việc này. Thông thường, cách nó hoạt động, một phần lớn công việc của tôi thực sự là làm công tác tổ chức này. Thông thường, cách nó hoạt động là bạn có thể có một người tạo ra dữ liệu được gán nhãn chất lượng rất cao này. Và sau đó bạn có thể có một người gán nhãn đã được thăng cấp lên vị trí kiểm định chất lượng (QA), nơi công việc của họ là đảm bảo rằng tất cả các đầu ra của những người gán nhãn khác, những người ở cấp độ thấp hơn, đều chính xác. Và đó là một công việc khá khó khăn, bởi vì nếu QA của bạn không tốt, bạn sẽ bị sa thải. Vì vậy, đó là cách họ duy trì mức độ chất lượng khá cao.
Tính Không Xác Định của LLM và Tham Số Seed
Vâng. Vậy, các LLM là không xác định, nhưng tham số seed hoạt động như thế nào?
Cách các LLM là không xác định là do chúng về cơ bản sử dụng bộ tạo số ngẫu nhiên của máy tính của bạn hoặc của GPU mà bạn đang sử dụng, của hệ thống. Tham số seed về cơ bản làm cho tất cả các phép tính khác nhau đó luôn trả về cùng một giá trị. Vì vậy, mọi thứ không còn ngẫu nhiên nữa. Điều này không chỉ dành cho LLM. Các seed này có thể được sử dụng cho bất kỳ việc tạo ngẫu nhiên nào. Ví dụ, cho password hashes. Vâng, nó hoạt động chính xác theo cùng một cách. Vâng. Vâng. Vâng, nếu bạn sử dụng greedy decoding, vẫn có thể có một số điều. Tốt hơn là nên sử dụng seed ngay cả khi bạn sử dụng greedy decoding vì có thể có một số thứ khác mà có thể bạn không kiểm soát được. Ví dụ, đôi khi greedy decoding có thể không phải là greedy decoding thực sự; nó có thể là nhiệt độ 0.01, ví dụ như với VRM, họ thực hiện một số thủ thuật như vậy để có được đầu ra tốt. Vì vậy, nói chung, tốt nhất là nên sử dụng seed ngay cả khi bạn đang sử dụng greedy decoding.
Mô Hình Âm Thanh so với Văn Bản (11 Labs)
Vâng. Đây rõ ràng đều là văn bản. 11 Labs chủ yếu là âm thanh, đúng không? Vâng. Vậy điều này khác với việc làm việc với âm thanh như thế nào?
Thật đáng ngạc nhiên là rất giống nhau. Chắc chắn là phức tạp hơn, nhưng các phần của stack của hầu hết mô hình âm thanh cũng có thể xử lý văn bản vì các mô hình cần hiểu ngôn ngữ, và cách tốt nhất để dạy ngôn ngữ là văn bản. Tất nhiên, bạn có thể chứa một mô hình chỉ xử lý âm thanh. Và một lần nữa, đó là những gì tôi đã nói về bộ mã hóa token. Bạn mã hóa token âm thanh như thế nào? Khái niệm về một âm thanh là gì? Bộ mã hóa token của bạn nên lớn đến mức nào cho âm thanh? Nó chỉ nên là giọng nói của con người hay cũng nên là âm nhạc? Bạn có lấy các nốt của các nhạc cụ khác nhau làm các mã thông báo khác nhau không? Đây là những loại vấn đề bạn phải giải quyết nếu bạn đang tạo một mô hình âm thanh mà bạn không nhất thiết phải có cho văn bản. Nó dễ dàng hơn một chút. Nhưng các nguyên tắc cơ bản vẫn giống nhau. Nếu bạn muốn tạo âm thanh, bạn sẽ tạo một mã thông báo âm thanh, và sau đó mã thông báo âm thanh đó sẽ được sử dụng bởi bộ mã hóa token. Và sau đó bạn, bạn, nó không hoàn toàn giống nhau vì mã thông báo âm thanh sau đó bạn phải xử lý nó theo một số cách khác nhau để biến nó thành âm thanh thực tế. Nhưng rất nhiều ý tưởng chung vẫn áp dụng.
Hàm Loss trong Mô Hình Âm Thanh
Nhưng bạn có nhiều lỗi hơn hay gì đó không? Với văn bản, bạn biết đấy, đây là văn bản. Văn bản có thể rất rõ ràng, giống như một là một. Nhưng trong âm thanh, như một cao độ hoặc tần số hoặc âm thanh cụ thể, nhưng sự mơ hồ ở đó, nó đã bị mất ngay lập tức, đúng không?
Vâng. Vâng. Vì vậy, cách nó hoạt động là bạn không sử dụng loss cross-entropy. Bạn sử dụng các loại loss khác. Bạn có thể sử dụng cross-entropy, nhưng thường thì đó là các loss chuyên biệt hơn cho những gì bạn đang cố gắng làm. Ví dụ, có một loss được gọi là L2 loss, về cơ bản lấy hai phổ đồ (melspectrograms) và cố gắng xem sự khác biệt giữa hai phổ đồ đó, về cơ bản là sóng âm được mã hóa theo một cách nhất định. Và đó là một cách rất phổ biến để huấn luyện các mô hình TTS. Bạn huấn luyện dựa trên các loại loss cụ thể này. Và điều tương tự tôi đã đề cập trước đó, cross-entropy không thực sự hoạt động tốt cho những thứ như post-training. Bạn có thể sử dụng các loại loss khác ở đó. Hoặc nếu bạn đang chưng cất mô hình từ một mô hình lớn sang một mô hình nhỏ hơn, bạn không sử dụng loss cross-entropy, bạn có thể sử dụng loss KL divergence nơi bạn tìm phân phối mã thông báo của mô hình lớn hơn và bạn cố gắng khớp logits của mô hình nhỏ hơn. Vì vậy, có nhiều loại loss khác nhau cho các trường hợp sử dụng khác nhau. Không phải tất cả chúng đều hoạt động theo cùng một cách.
Hoạt Động của Mô Hình Đa Phương Thức
Vâng. Vậy, theo một nghĩa nào đó, bạn có từ vựng âm thanh, đúng không? Từ vựng văn bản, từ vựng âm thanh, từ vựng cùng nhau. Vậy, cách các mô hình đa phương thức hoạt động, thường thì bạn không sử dụng mã thông báo theo cùng một nghĩa. Chà, đây là lúc nó trở nên phức tạp hơn bởi vì những mô hình này không thực sự được xây dựng cho mục đích này, giống như mô hình GPT-2. Các mô hình mới hơn cũng có đầu vào embedding, về cơ bản, như tôi đã đề cập trước đó, mỗi mã thông báo sau đó tương ứng với một vector. Nhưng các vector này không nhất thiết phải tương ứng với các mã thông báo cụ thể. Bạn cũng có thể lấy các vector này từ những nơi khác.
Và điều mà nhiều phòng thí nghiệm làm là thay vì có một bộ mã hóa token cho video chẳng hạn, họ có một transformer khác mà họ gọi là bộ mã hóa video. Và họ đưa video qua bộ mã hóa video đó trước. Và bộ mã hóa video này sẽ lấy, giả sử bạn có một video dài 30 giây. Nó sẽ lấy một khung hình mỗi giây của video này và nó sẽ lấy những khung hình đó và sau đó đưa chúng qua transformer mới này, transformer encoder này hoạt động khá khác một chút. Và điều bạn làm là bạn lấy lớp cuối cùng của transformer này, các giá trị ẩn, cũng là các vector. Bạn lấy những vector đó từ bộ mã hóa này và bạn sẽ nhập chúng vào lớp embedding của mô hình transformer. Vì vậy, những gì mô hình sẽ thấy thường được tiền tố (prefixed). Bạn lấy video, đẩy nó qua bộ mã hóa, nhận được một số vector, và sau đó đặt những vector đó vào đầu vào embedding của transformer xử lý văn bản của bạn. Và cách nó sẽ trông như thế nào nếu bạn nhìn vào chuỗi là nó có thể sẽ là một lời nhắc, và sau đó có thể là một biểu diễn mã thông báo video. Nhưng embedding của mã thông báo video sẽ bị ghi đè bởi đầu ra của bộ mã hóa. Vì vậy, đó là cách các mô hình đa phương thức này hoạt động tương tự. Vâng, nó hoàn toàn giống nhau. Vector cũng giống nhau cho âm thanh. Bạn có một bộ mã hóa âm thanh và làm tương tự nhưng cho âm thanh. Nhưng về mặt những gì mô hình quan tâm, nó chỉ quan tâm đến những embedding này, đúng không? Nó không quan tâm đó là văn bản hay âm thanh hay video.
Biểu diễn Vector và Kiến trúc Mô hình
Nó quan tâm đến các vector này và đó là cách bạn biểu diễn chúng vào cùng một chiều mà mô hình transformer mong đợi. Vậy thì, có sự trùng khớp giữa các vector này không? Có lẽ có, tôi không chắc; nó phụ thuộc vào cách bạn huấn luyện. Những điều này hơi giống hộp đen. Có thể có, có thể không. Thực ra đó là một ý tưởng hay, một bài nghiên cứu tốt để xem liệu các bộ mã hóa video có thực sự khớp với cùng chiều với các bộ mã hóa văn bản hay không. Tôi không biết, tôi hình dung có thể có mối liên hệ.
Tạo Nhạc so với Lời Nói: Các Cách Tiếp Cận Khác Nhau
Người hỏi: Vâng, tôi tự hỏi rằng bạn thực hiện cả lời nói thông thường và cả tạo nhạc. Đó có phải là một vấn đề rất khác biệt, hay kiến trúc sẽ tương tự? Và khi bạn có các yếu tố hòa âm (harmonics) và những thứ tương tự, bạn có thể sử dụng một mô hình transformer tự hồi quy (autoregressive transformer) cơ bản không, hay các yếu tố đó phụ thuộc vào nhau nhiều hơn? Bạn có thể tạo ra mọi thứ cùng một lúc không?
Bạn có thể làm cả hai. Có những mô hình âm nhạc là tự hồi quy (autoregressive models). Cũng có những mô hình âm nhạc là mô hình khuếch tán (diffuser models). Nó phụ thuộc vào cách bạn huấn luyện chúng. Tôi nghĩ một số mô hình của Google dựa trên transformer. Một số mô hình mã nguồn mở thì dựa trên mô hình khuếch tán. Cả hai đều có thể hoạt động rất tốt.
Thách thức trong Mã hóa Âm nhạc
Chỉ là, như tôi đã nói, hơi khó để hình dung khái niệm về âm nhạc nếu bạn mã hóa và dự đoán mã thông báo tiếp theo. Nó khá khó vì rất trừu tượng. Vì vậy, mô hình khuếch tán thường hoạt động tốt hơn một chút trong các phương thức đa phương tiện (multimodal modalities) như tạo ảnh, âm nhạc, hoặc thậm chí âm thanh đối với một số mô hình – chúng có một phần khuếch tán trong quá trình của mình. Cả hai đều có thể hoạt động. Mô hình khuếch tán thường dễ triển khai hơn một chút. Tôi hy vọng câu trả lời này có ý nghĩa.
Huấn luyện Bộ Mã hóa Âm thanh
Người hỏi: Vâng.
Cách để làm cho nó dễ hơn... nó rất khó. Và nó không phải là thứ bạn chỉ cần ngồi xuống và nghĩ, "À, tôi sẽ mã hóa từ này thành từ kia." Nó không phải là thứ bạn ngồi xuống và quyết định. Bạn sử dụng một loại quy trình nào đó và huấn luyện một bộ mã hóa âm thanh (audio tokenizer). Nó rất giống với cách bạn huấn luyện một bộ mã hóa văn bản (text tokenizer).
Bạn sẽ sử dụng dữ liệu huấn luyện của mình và tìm các mẫu chung (common patterns) trong âm thanh, sau đó mã hóa các mẫu đó. Tất nhiên, khi chúng ta nói âm thanh, chúng ta không luôn chỉ nói đến tốc độ lấy mẫu (sample rate) thực tế và sóng âm. Thông thường, chúng ta chuyển đổi chúng thành một thứ gì đó dễ mã hóa hơn để thực hiện quá trình xử lý này. Phổ biến nhất là phổ kế Mel (Mel spectrograms).
Đầu tiên, bạn chuyển đổi âm thanh của mình thành phổ kế Mel, sau đó sử dụng các mảng số này để huấn luyện các bộ mã hóa của mình. Điều này sẽ rất phụ thuộc vào tập dữ liệu huấn luyện của bạn. Ví dụ, nếu bạn muốn mã hóa âm nhạc và sử dụng một tập dữ liệu âm nhạc, các mã thông báo âm thanh của bạn cho âm nhạc sẽ rất khác so với khi bạn có một tập dữ liệu giọng nói tập trung vào giọng người. Bây giờ, phần khó khăn là làm thế nào nếu bạn muốn làm cả giọng nói và âm nhạc? Đó là điều rất khó.
Thông báo Hội thảo và Cuộc thi
Có câu hỏi nào khác không? Tuyệt vời. Vâng, nếu bạn muốn, bạn có thể bắt đầu làm việc về việc huấn luyện mô hình nếu bạn có máy tính xách tay hoặc nếu bạn đã bắt đầu. Bạn có thể theo dõi hội thảo này để làm cho một cái gì đó hoạt động. Và nếu chúng ta có đủ bài nộp, chúng ta có thể tổ chức cuộc thi và xem ai thắng sẽ nhận được một số...
Người hỏi: [âm thanh không rõ].
Người nói: Xin lỗi.
Người hỏi: Hạn chót là khi nào?
Người nói: Bắt đầu ngay sau đó.
Vì chúng ta không còn nhiều thời gian, hãy đặt hạn chót là 5:45. Vì vậy, nếu bạn có bất kỳ câu hỏi hoặc cần giúp đỡ, xin hãy gọi tôi và tôi sẽ đến.