Hướng Dẫn Xây Dựng Mô Hình Ngôn Ngữ LLM Nhỏ Gọn Từ Đầu và Những Bài Học Quý Giá

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

Bài viết trình bày cách tạo một mô hình ngôn ngữ LLM nhỏ gọn với chưa đến 300 dòng mã Python, giúp hiểu rõ các cơ chế tokenization, attention và inference, từ đó nâng cao kỹ năng tích hợp API AI trong ứng dụng.

Hướng Dẫn Xây Dựng Mô Hình Ngôn Ngữ LLM Nhỏ Gọn Từ Đầu và Những Bài Học Quý Giá

Hướng Dẫn Xây Dựng Mô Hình Ngôn Ngữ LLM Nhỏ Gọn Từ Đầu và Những Bài Học Quý Giá

Bài viết này chia sẻ cách xây dựng một mô hình ngôn ngữ lớn (LLM) nhỏ gọn chỉ với chưa đến 300 dòng mã Python, dựa trên dự án GuppyLM. Quá trình này không những giúp bạn nắm bắt sâu sắc các thành phần như tokenization, cơ chế attention, và vòng lặp inference, mà còn hỗ trợ cải thiện khả năng tích hợp và gỡ lỗi API AI trong ứng dụng thực tế.

Giới thiệu

Phần lớn nhà phát triển hiện nay xem LLM như một hộp đen — nhập văn bản, nhận token, và điều kỳ diệu xảy ra bên trong. Tuy nhiên, khi cần tinh chỉnh sampling, khắc phục lỗi dữ liệu đầu ra, hay gỡ lỗi tích hợp API streaming, việc hiểu rõ cơ chế bên trong là rất cần thiết.

GuppyLM là một mô hình transformer với 8,7 triệu tham số, hoàn toàn viết bằng Python, có thể huấn luyện trên GPU phổ thông chỉ trong khoảng chưa đầy một giờ. Mục tiêu của GuppyLM không phải để cạnh tranh với các mô hình hàng đầu như GPT-4 mà là để cung cấp một nền tảng hiểu biết thực tế về cách LLM vận hành.

LLM nhỏ gọn là gì và tại sao nên xây dựng?

Các mô hình lớn sản xuất như GPT-4 thường sở hữu hàng trăm tỷ tham số. Trong khi đó, mô hình LLM nhỏ gọn thường nằm trong khoảng từ 1 triệu đến 25 triệu tham số, ví dụ như GuppyLM (8,7 triệu tham số), nanoGPT (124 triệu), hoặc MicroLM (1-2 triệu).

LLM nhỏ gọn có thể làm được:

  • Huấn luyện nhanh trên laptop hoặc Google Colab
  • Chạy hiệu quả trên CPU mà không cần GPU cao cấp
  • Dễ dàng kiểm tra, sửa đổi và gỡ lỗi các trọng số chi tiết

LLM nhỏ gọn có giới hạn:

  • Không thể thực hiện suy luận phức tạp như các mô hình lớn
  • Khó sinh ra văn bản dài, mạch lạc với độ ổn định cao
  • Không thể lưu giữ hay truy vấn kiến thức sâu rộng như các mô hình sản xuất

Tuy nhiên, giá trị lớn nhất từ việc tự xây dựng một LLM nhỏ chính là hiểu sâu các cơ chế bên trong và áp dụng được trong giai đoạn tích hợp và phát triển ứng dụng AI.

Các thành phần cốt lõi trong một LLM

Trước khi bắt tay vào xây dựng, bạn cần hiểu rõ 4 cấu phần chính giúp một LLM hoạt động:

1. Bộ mã hóa token (Tokenizer)

Tokenizer có nhiệm vụ chuyển đổi văn bản thô thành dãy ID số nguyên đại diện cho các token. Ví dụ, "Hello, world!" có thể được mã hóa thành [15496, 11, 995, 0]. Việc hiểu cách tokenizer xử lý sẽ giúp bạn tối ưu prompt và quản lý số lượng token, từ đó kiểm soát chi phí và độ trễ khi gọi API.

GuppyLM sử dụng tokenizer cấp ký tự đơn giản, còn các mô hình lớn như GPT-4 hay Claude sử dụng phương pháp BPE với từ vựng lên đến 50-100k token.

2. Lớp nhúng (Embedding layer)

Lớp nhúng ánh xạ các ID token thành vector liên tục trong không gian chiều cao (ví dụ 384 chiều), sao cho các token có ngữ nghĩa gần nhau sẽ có vector gần nhau. Ngoài ra, mô hình cũng thêm embedding vị trí (position embedding) để nhận biết thứ tự token trong chuỗi.

3. Các khối Transformer

Cấu trúc chính của LLM bao gồm:

  • Self-attention: Tự chú ý cho phép mỗi token tham chiếu toàn bộ chuỗi để xác định các token quan trọng hỗ trợ dự đoán.
  • Feed-forward network (MLP): Một mạng đa tầng giúp biến đổi biểu diễn token sau lớp attention.

Ví dụ GuppyLM sử dụng 6 lớp transformer với 6 head attention mỗi lớp.

4. Lớp đầu ra (Output head)

Sau cùng, biểu diễn token được đưa qua lớp tuyến tính nhằm dự đoán xác suất token tiếp theo trong tập từ vựng. Softmax được áp dụng để chuyển các giá trị thành xác suất, từ đó tiến hành sampling hay argmax để sinh token.

Ví dụ xây dựng LLM tối giản bằng Python

Đoạn mã dưới đây cho thấy cách xây dựng một LLM đơn giản sử dụng PyTorch, dựa trên cấu trúc GuppyLM với:

  • Tokenizer cấp ký tự (256 token)
  • Kích thước embedding 128 chiều
  • 4 attention heads và 3 lớp transformer
  • Cửa sổ ngữ cảnh dài 64 token
import torch
import torch.nn as nn
import torch.nn.functional as F

VOCAB_SIZE = 256
D_MODEL = 128
N_HEADS = 4
N_LAYERS = 3
SEQ_LEN = 64
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()
total_params = sum(p.numel() for p in model.parameters())
print(f"Model size: {total_params:,} parameters")  # khoảng 1.2 triệu tham số

Vòng lặp huấn luyện mẫu

Sử dụng AdamW, huấn luyện trên tensor token dữ liệu:

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]
        y = data[:, 1:]
        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) với sampling top-k & temperature

@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()

Kiến thức bổ ích khi làm việc với API AI

  • Temperature và sampling không phải phép màu: Temperature điều chỉnh độ ngẫu nhiên đầu ra, còn top-k giới hạn token ứng viên; nhiều API đặt temperature > 0 để tránh kết quả lặp lại.
  • Cửa sổ ngữ cảnh là giới hạn cứng: Khi vượt quá số token tối đa, các token cũ bị cắt, API không nhớ được toàn bộ lịch sử hội thoại.
  • Streaming token thực ra là nhiều bước inference lặp lại: Nếu luồng bị ngắt, bạn cần khởi động lại inference.
  • Kỹ thuật logit giúp tạo đầu ra cấu trúc: Để sinh dữ liệu JSON hay định dạng phức tạp, mô hình cần giới hạn token hợp lệ tại từng bước, tương tự các chế độ “đầu ra có cấu trúc” của các API.

Kiểm thử tích hợp AI API với Apidog

Tính năng Kịch bản Kiểm thử (Test Scenarios) của Apidog hỗ trợ tạo luồng kiểm thử API mở rộng:

  • Chuỗi các lệnh gọi tới /v1/chat/completions
  • Xác nhận các trường trong response như finish_reason == "stop", token sinh ra hợp lệ
  • Mô phỏng kịch bản chat nhiều lượt
  • Giả lập lỗi như timeout, content_filter, hay finish_reason khác

Cách này giúp tiết kiệm tài nguyên API và nâng cao độ tin cậy khi triển khai.

Ví dụ xác nhận số lượng token và trạng thái phản hồi:

{
  "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"}
  ]
}

Kỹ thuật nâng cao: Lượng tử hóa và cache KV

  • Lượng tử hóa (Quantization): Giảm precision của trọng số từ 32-bit xuống 8-bit hoặc 4-bit, giúp giảm bộ nhớ và tăng tốc inference mà chỉ mất ít độ chính xác. PyTorch hỗ trợ lượng tử hóa động INT8 rất tiện dụng.

  • Bộ nhớ đệm cặp key-value (KV cache): Giúp tránh tính toán lại attention trên toàn bộ chuỗi cho mỗi token mới, tạo điều kiện cho streaming hiệu quả.

Khi nào nên dùng LLM nhỏ gọn và khi nào dùng API sản xuất?

Trường hợp sử dụngLLM nhỏ gọnAPI sản xuất
Tìm hiểu cơ chế bên trongTốt nhấtQuá mức cần thiết
Tạo mẫu ứng dụng mớiKhông đủ chất lượngTốt nhất
Dữ liệu riêng tư/nhạy cảmLựa chọn tốtTùy nhà cung cấp
Triển khai ngoại tuyến hoặc biênKhả thiKhông thể
Chi phí nhạy cảm, tải lớnKhả thi nếu đánh đổiĐắt đỏ
Tác vụ suy luận phức tạpKhông khả thiBắt buộc

Thông thường, nhà phát triển dùng API sản xuất cho ứng dụng thực tế, trong khi sử dụng LLM nhỏ để học và nghiên cứu cách mô hình vận hành.

Kết luận

Chỉ trong một cuối tuần, bạn có thể xây dựng một LLM nhỏ gọn để hiểu sâu cơ chế bên trong của các mô hình AI hiện đại, từ đó cải thiện kỹ năng tích hợp và gỡ lỗi API AI. Đừng quên thử sử dụng tính năng Test Scenarios của Apidog để nâng cao chất lượng kiểm thử và triển khai ứng dụng AI của bạn.

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

  • LLM nhỏ gọn cần bao nhiêu tham số để sinh văn bản mạch lạc?
    Khoảng 10-50 triệu tham số với dữ liệu huấn luyện phù hợp. Dưới 1 triệu thường cho kết quả kém. GuppyLM (8,7M) đủ chat ngắn trong phạm vi huấn luyện ~60 chủ đề.

  • Có thể chạy LLM nhỏ gọn không cần GPU?
    Có, các mô hình dưới 100 triệu tham số chạy được trên CPU nhưng chậm hơn. Ví dụ GuppyLM có thể sinh token trong vài ms trên laptop.

  • Nên dùng dữ liệu gì để huấn luyện?
    Văn bản thuần như Gutenberg, Wikipedia; hoặc bộ hội thoại 60K trên HuggingFace (arman-bd/guppylm-60k-generic). Sinh mã có thể dùng The Stack hoặc CodeParrot.

  • Khác biệt giữa temperature và top-k sampling?
    Temperature điều chỉnh độ ngẫu nhiên toàn bộ phân phối logit, còn top-k cắt lọc chỉ k token xác suất cao nhất để sampling.

  • Tại sao LLM hay bị lặp lại?
    Do model gán xác suất cao cho token vừa sinh do trùng context. API thường dùng penalty để giảm lặp.

  • Mất bao lâu để huấn luyện LLM nhỏ gọn?
    GuppyLM huấn luyện trong chưa đến 2 giờ trên GPU đơn như RTX 3060.

  • Cách nhanh nhất triển khai LLM thành API?
    Xuất file GGUF rồi phục vụ qua llama-server để tạo endpoint OpenAI-compatible, tiện test với Apidog.

  • Làm sao xử lý ngữ cảnh dài hơn cửa sổ huấn luyện?
    Dùng kỹ thuật RoPE, attention dạng sliding window hay retrieval-augmented generation để mở rộng khả năng.


Việc tự xây dựng và tìm hiểu cơ chế LLM là bước khởi đầu lý tưởng để làm chủ công nghệ AI, giúp bạn phát triển ứng dụng tối ưu và hiệu quả hơn trong bối cảnh công nghệ AI phát triển bùng nổ hiện nay.

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 ↗