Hướng dẫn xây dựng mô hình ngôn ngữ lớn (LLM) từ đầu với GuppyLM và bài học API AI

07 tháng 4, 2026·9 phút đọc

Bài viết giới thiệu cách xây dựng một mô hình ngôn ngữ lớn nhỏ (Tiny LLM) từ đầu bằng PyTorch với dự án GuppyLM, giúp hiểu sâu quá trình tokenization, attention và inference. Qua đó cải thiện khả năng sử dụng API AI và triển khai các bài test tích hợp hiệu quả bằng công cụ Apidog.

Hướng dẫn xây dựng mô hình ngôn ngữ lớn (LLM) từ đầu với GuppyLM và bài học API AI

Hướng dẫn xây dựng mô hình ngôn ngữ lớn (LLM) từ đầu với GuppyLM và bài học API AI

Tóm tắt:
Xây dựng một mô hình ngôn ngữ lớn nhỏ chỉ với vài trăm dòng Python giúp làm rõ các khái niệm như mã hóa văn bản (tokenization), cơ chế attention và quá trình suy luận (inference). Qua đó, bạn sẽ trở thành người tiêu dùng API AI thông minh hơn khi tích hợp LLM. Dự án mã nguồn mở GuppyLM là một ví dụ tiêu biểu có thể đào tạo nhanh trên GPU thương mại và dễ dàng chỉnh sửa, thích hợp cho học tập và thử nghiệm.

Giới thiệu mô hình ngôn ngữ lớn nhỏ

Phần lớn nhà phát triển coi các mô hình ngôn ngữ lớn như một “hộp đen” — bạn nhập văn bản, nhận ra đầu ra dạng token, còn bên trong là “phép thuật”. Tuy nhiên, khi cần gỡ lỗi, điều chỉnh hoặc hiểu lý do model sinh ra dữ liệu sai lệch (hallucination), mô hình này không còn hiệu quả.

GuppyLM — một dự án transformer 8.7 triệu tham số viết bằng Python, có thể đào tạo trong chưa đầy 1 giờ trên GPU phổ thông — làm sáng tỏ các chi tiết bên trong mô hình. Mục tiêu không phải để cạnh tranh GPT-4, mà để bạn có cái nhìn trực quan về cách hoạt động nội tại của LLM.

Đặc điểm mô hình nhỏ và lợi ích

Mô hình nhỏ có từ khoảng 1 triệu đến 25 triệu tham số, so với hàng trăm tỷ tham số của mô hình sản xuất như GPT-4. Ví dụ:

  • GuppyLM: 8.7 triệu tham số
  • nanoGPT: 124 triệu tham số
  • MicroLM: 1-2 triệu tham số

Ưu điểm mô hình nhỏ:

  • Có thể đào tạo trên laptop hoặc Google Colab
  • Chạy hoàn toàn trên RAM với CPU
  • Có thể xem, sửa và debug trực tiếp trên trọng số

Nhược điểm:

  • Không xử lý được inference phức tạp
  • Khó tạo văn bản dài và mạch lạc
  • Chất lượng đầu ra không bằng mô hình sản xuất

Điều quan trọng khi xây dựng mô hình nhỏ là học được cách hoạt động chứ không phải giải pháp cuối cùng.

Các thành phần chính của mô hình LLM

Một LLM cơ bản gồm 4 phần:

1. Tokenizer

Chuyển đoạn văn bản thành các số nguyên đại diện cho token, ví dụ:
"Hello, world!" → [15496, 11, 995, 0]

Việc hiểu tokenizer giúp bạn biết được số lượng token, chi phí API và tránh bị cắt ngẫu nhiên trong prompt. GuppyLM sử dụng tokenizer đơn giản theo ký tự, trong khi mô hình thực tế dùng kỹ thuật BPE cùng tập từ vựng lớn.

2. Embedding layer

Mã token được chuyển thành vector dày đặc (dense vector). GuppyLM dùng embedding 384 chiều, kèm vector vị trí (positional embedding) để xác lập thứ tự token.

3. Transformer blocks

Gồm hai phần chính:

  • Self-attention: Mỗi token xét tất cả token khác để xác định quan trọng với dự đoán token kế tiếp (GuppyLM có 6 attention heads × 6 layers).
  • Feed-forward network: Mạng neural nối tiếp đa lớp với hàm kích hoạt ReLU.

4. Output head

Lớp linear chuyển vector cuối cùng thành kích thước tập từ rồi tính softmax lấy token có xác suất cao nhất hoặc sample theo phân phối.

Mẫu mã PyTorch xây dựng TinyLLM cơ bản

Dưới đây là ví dụ đơn giản với các tham số cấu hình và lớp PyTorch tự định nghĩa cho mô hình:

import torch
import torch.nn as nn
import torch.nn.functional as F

VOCAB_SIZE = 256    # ký tự ASCII
D_MODEL = 128       # embedding dimension
N_HEADS = 4         # số đầu attention
N_LAYERS = 3        # số block transformer
SEQ_LEN = 64        # độ dài context
DROPOUT = 0.1

class SelfAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.n_heads = n_heads
        self.head_dim = d_model // n_heads
        self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
        self.proj = nn.Linear(d_model, d_model, bias=False)
        self.dropout = nn.Dropout(DROPOUT)

    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim)
        q, k, v = qkv.unbind(dim=2)
        q = q.transpose(1, 2)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)
        scale = self.head_dim ** -0.5
        attn = (q @ k.transpose(-2, -1)) * scale
        mask = torch.triu(torch.ones(T, T, device=x.device), diagonal=1).bool()
        attn = attn.masked_fill(mask, float('-inf'))
        attn = F.softmax(attn, dim=-1)
        attn = self.dropout(attn)
        out = (attn @ v).transpose(1, 2).reshape(B, T, C)
        return self.proj(out)

class TransformerBlock(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.attn = SelfAttention(d_model, n_heads)
        self.ff = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.ReLU(),
            nn.Linear(4 * d_model, d_model),
            nn.Dropout(DROPOUT),
        )
        self.ln1 = nn.LayerNorm(d_model)
        self.ln2 = nn.LayerNorm(d_model)

    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.ff(self.ln2(x))
        return x

class TinyLLM(nn.Module):
    def __init__(self):
        super().__init__()
        self.embed = nn.Embedding(VOCAB_SIZE, D_MODEL)
        self.pos_embed = nn.Embedding(SEQ_LEN, D_MODEL)
        self.blocks = nn.ModuleList([
            TransformerBlock(D_MODEL, N_HEADS) for _ in range(N_LAYERS)
        ])
        self.ln_f = nn.LayerNorm(D_MODEL)
        self.head = nn.Linear(D_MODEL, VOCAB_SIZE, bias=False)

    def forward(self, idx):
        B, T = idx.shape
        tok_emb = self.embed(idx)
        pos = torch.arange(T, device=idx.device)
        pos_emb = self.pos_embed(pos)
        x = tok_emb + pos_emb
        for block in self.blocks:
            x = block(x)
        x = self.ln_f(x)
        logits = self.head(x)
        return logits

model = TinyLLM()
print(f"Model size: {sum(p.numel() for p in model.parameters()):,} parameters")  # ~1.2M

Vòng lặp huấn luyện đơn giản

import torch.optim as optim

def train(model, data, epochs=100, lr=3e-4):
    optimizer = optim.AdamW(model.parameters(), lr=lr)
    model.train()
    for epoch in range(epochs):
        x = data[:, :-1]   # input: tất cả token trừ token cuối
        y = data[:, 1:]    # target: dịch sang phải 1 token
        logits = model(x)
        loss = F.cross_entropy(logits.reshape(-1, VOCAB_SIZE), y.reshape(-1))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if epoch % 10 == 0:
            print(f"Epoch {epoch}, loss: {loss.item():.4f}")

Sinh văn bản (Inference)

@torch.no_grad()
def generate(model, prompt_ids, max_new_tokens=50, temperature=1.0, top_k=10):
    model.eval()
    ids = torch.tensor([prompt_ids])
    for _ in range(max_new_tokens):
        idx_cond = ids[:, -SEQ_LEN:]
        logits = model(idx_cond)
        logits = logits[:, -1, :] / temperature
        v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
        logits[logits < v[:, [-1]]] = float('-inf')
        probs = F.softmax(logits, dim=-1)
        next_id = torch.multinomial(probs, num_samples=1)
        ids = torch.cat([ids, next_id], dim=1)
    return ids[0].tolist()

Bài học về API AI từ việc xây dựng model này

  • Temperature và sampling là các thao tác cơ học trên logits trước softmax, không phải "thuật toán bí mật".
  • Context window là giới hạn cứng—model chỉ xét một số lượng token nhất định, token cũ bị loại bỏ im lặng.
  • Streaming token chỉ là lần lượt xuất token được sinh ra, không thay đổi kiến trúc model.
  • Logits cho biết tại sao khó tạo output có cấu trúc (như JSON) vì từng token đều cần xác suất phù hợp.

Test tích hợp API AI với Apidog

Sử dụng Apidog để:

  • Kiểm tra streaming token và cấu trúc token trả về
  • Simulate các trường hợp đặc biệt như output bị cắt, bị content filter, timeout mạng
  • Giảm thiểu chi phí API khi chạy test liên tục trong CI/CD

Ví dụ kịch bản test xác nhận token count và kết thúc hợp lệ:

{
  "assertions": [
    { "field": "response.usage.completion_tokens", "operator": "less_than", "value": 512 },
    { "field": "response.choices[0].finish_reason", "operator": "equals", "value": "stop" },
    { "field": "response.choices[0].message.content", "operator": "not_empty" }
  ]
}

Các kỹ thuật nâng cao quan trọng cho triển khai thực tế

  • Quantization: giảm kích thước model scale (từ FP32 xuống INT8 hoặc INT4) giúp tiết kiệm bộ nhớ, tăng tốc inference.
  • KV cache: lưu trữ kết quả attention của các token trước để tăng tốc sinh token kế tiếp.

Khi nào dùng TinyLLM hoặc API LLM sản xuất?

Tình huống sử dụngTiny LLMAPI sản xuất
Hiểu chi tiết sâu về modelTốtQuá mức cần thiết
Prototype ứng dụng mớiChất lượng thấpTốt nhất
Xử lý dữ liệu riêng tưTốtTùy nhà cung cấp
Triển khai offline hoặc edgeĐượcKhông khả thi
Hạn chế chi phí, dữ liệu lớnCó thể (hy sinh)Đắt đỏ
Nhiệm vụ cần inference lớnKhông khả thiBắt buộc

Lời khuyên: dùng API cho sản xuất nhưng tự xây dựng model nhỏ để nắm vững nguyên lý.

Kết luận

Dành một cuối tuần để xây dựng TinyLLM cho phép bạn hiểu một cách trực quan hoạt động bên trong của mô hình ngôn ngữ lớn. Hiểu biết này rất hữu ích khi bạn debug, điều chỉnh sampling, và viết các bài test tốt hơn cho API AI.

Bắt đầu với GuppyLM, tập đào tạo trên bộ dữ liệu văn bản bất kỳ, xem xét kỹ vòng inference. Sau đó, tích hợp và thử nghiệm API với Apidog giúp bạn làm chủ hơn trong triển khai AI thực tế.


Câu hỏi thường gặp

  • Mô hình ngôn ngữ nhỏ cần bao nhiêu tham số để sinh văn bản mạch lạc?
    Khoảng 10-50 triệu với dữ liệu huấn luyện chất lượng. Dưới 1 triệu thường khó hiểu. GuppyLM với 8.7 triệu đủ tốt cho hội thoại ngắn.

  • Chạy LLM nhỏ có cần GPU không?
    Không bắt buộc. Dưới 100 triệu tham số có thể chạy CPU, nhưng inference chậm hơn GPU.

  • Nên dùng dữ liệu nào để huấn luyện?
    Văn bản tự do như Project Gutenberg, Wikipedia hoặc dataset chat như GuppyLM 60k trên HuggingFace. Nếu sinh code, dùng The Stack hoặc CodeParrot.

  • Temperature và top-k khác nhau thế nào?
    Temperature điều chỉnh độ “mềm” của logits, top-k giới hạn số token đầu ra có thể chọn. Thường dùng cùng nhau.

  • Tại sao LLM đôi khi lặp lại từ?
    Model đánh giá cao token vừa sinh nên lặp lại. APIs thật sự áp dụng penalty để giảm hiện tượng này.

  • Mất bao lâu để huấn luyện TinyLLM?
    Khoảng dưới 2 tiếng trên một GPU phổ thông như RTX 3060.

  • Cách nhanh nhất để đưa model LLM nhỏ lên API thực tế?
    Chuyển model sang định dạng GGUF, phục vụ bằng llama-server tạo endpoint chuẩn OpenAI, rồi test bằng Apidog.

  • Làm sao mô hình LLM sản xuất xử lý câu dài hơn window training?
    Dùng kỹ thuật như RoPE, sliding attention, lưu trữ kèm truy xuất giúp mở rộng ngữ cảnh hiệu quả.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗