Một mô hình ngôn ngữ nhỏ (SLM) xây dựng từ đầu bằng Rust, không dùng framework ML nào. Dự án triển khai đầy đủ vòng lặp forward pass → backpropagation → gradient descent theo kiến trúc MLP 2 lớp với embedding ký tự — được điều khiển qua một giao diện CLI tương tác.
- Tổng quan
- Kiến trúc mô hình
- Cấu trúc dự án
- Các module
- CLI — Giao diện dòng lệnh
- Luồng hoạt động
- Cài đặt và chạy
- Chạy Tests
- Cấu hình siêu tham số
Dự án xây dựng một mô hình ngôn ngữ cấp độ ký tự (character-level) theo kiến trúc MLP (Multi-Layer Perceptron). Mô hình học xác suất của ký tự tiếp theo dựa trên mem ký tự trước đó, sử dụng embedding véc-tơ cho mỗi ký tự.
Khác với phiên bản trước (script chạy một lần), phiên bản hiện tại cung cấp một REPL tương tác — người dùng có thể nhập lệnh để cấu hình, huấn luyện, sinh văn bản và lưu/tải mô hình mà không cần sửa code.
Input (mem ký tự)
│
▼
Embedding Lookup
[vocab_size × dim]
│
▼ (context_length = mem × dim)
Layer 1: Linear + Tanh
[layer_node × context_length]
│
▼
Layer 2: Linear + Softmax
[vocab_size × layer_node]
│
▼
Probability Distribution → Sample next char
Giá trị mặc định (có thể thay đổi qua CLI):
| Tham số | Mặc định | Ý nghĩa |
|---|---|---|
mem |
4 | Số ký tự ngữ cảnh đầu vào |
dim |
4 | Số chiều embedding mỗi ký tự |
context_length |
16 | Tổng chiều input layer (mem × dim, tự tính) |
layer |
64 | Số neuron lớp ẩn |
scale |
0.1 | Hệ số khởi tạo trọng số ngẫu nhiên |
rate |
0.01 | Tốc độ học (learning rate) |
respond_length |
100 | Số ký tự tối đa khi sinh văn bản |
small_language_model/
├── Cargo.toml # Manifest Rust, dependency: rand
├── Cargo.lock
├── .gitignore
│
├── src/
│ ├── main.rs # Entry point: khởi tạo Owl & Sea, vòng lặp CLI
│ ├── lib.rs # Khai báo các module public
│ ├── interfaces.rs # Type aliases dùng chung
│ ├── config.rs # Struct Config — cấu hình siêu tham số
│ ├── sea.rs # Struct Sea — orchestrator trung tâm
│ │
│ ├── animal/
│ │ ├── crab.rs # Tokenizer ký tự (char ↔ Endu)
│ │ ├── beaver.rs # Model builder — build model từ Config & Crab
│ │ ├── owl/ # CLI controller (REPL)
│ │ │ ├── mod.rs # Struct Owl — vòng lặp lệnh
│ │ │ ├── eyes.rs # Đọc input từ stdin
│ │ │ ├── fur.rs # Hằng số màu ANSI
│ │ │ ├── guide.rs # In hướng dẫn help
│ │ │ └── owl_action.rs # Enum OwlC — các lệnh CLI
│ │ └── shrimp/ # Serialize / Deserialize (lưu tải model)
│ │ ├── mod.rs
│ │ ├── writer.rs # ShrimpW — ghi binary
│ │ └── reader.rs # ShrimpR — đọc binary
│ │
│ ├── slm/
│ │ ├── decoder.rs # Hàm tiện ích lấy context/label từ chuỗi
│ │ ├── tensor_math.rs # Các phép toán tensor: softmax, tanh, one-hot, ...
│ │ └── jelly_fish/ # Struct Model — mô hình ngôn ngữ
│ │ ├── mod.rs # Định nghĩa Model, active_a_layer
│ │ ├── default.rs # Khởi tạo Model & embedding ngẫu nhiên
│ │ ├── generate.rs # Sinh văn bản
│ │ ├── train.rs # Huấn luyện (forward + backward + update)
│ │ ├── vocab_update.rs # Cập nhật embedding theo gradient
│ │ └── config.rs # Giao tiếp Model ↔ Config
│ │
│ └── utils/
│ ├── matrix.rs # Các phép toán ma trận thuần túy
│ └── file_reader.rs # Đọc file văn bản
│
├── tests/
│ ├── crab_test.rs # Unit tests cho Crab tokenizer
│ ├── decode_test.rs # Unit tests cho decoder
│ ├── matrix_test.rs # Unit tests cho matrix operations
│ └── saveload_test.rs # Integration tests cho Shrimp save/load
│
└── resources/
└── note.txt # Văn bản tiếng Việt mẫu (lịch sử / mèo)
Định nghĩa tất cả type aliases dùng chung xuyên suốt dự án.
pub type Prof = f32; // Kiểu số thực (xác suất, trọng số)
pub type Endu = u16; // Mã số nguyên của một ký tự
pub type Data = Vec<char>; // Chuỗi ký tự thô
pub type EncodedVec = Vec<Endu>; // Chuỗi đã mã hóa
pub type Tensor1D = Vec<Prof>; // Vector 1 chiều
pub type Matrix = Vec<Tensor1D>; // Ma trận 2D
pub type Layer = (Matrix, Matrix); // (trọng số W, bias B) của 1 lớp
pub type Dpcontext = (Layer, Layer, Matrix, Matrix); // Gradient backward passNguyên tắc: Tất cả module chỉ giao tiếp qua các type này — không phụ thuộc trực tiếp vào cấu trúc nội bộ của nhau.
Config chứa toàn bộ siêu tham số của mô hình. Được tạo với giá trị mặc định và có thể điều chỉnh qua lệnh set của CLI trước khi build.
| Trường | Mặc định | Setter |
|---|---|---|
layer_node |
64 | set_layer_node |
mem_ctx_char |
4 | set_mem_ctx_char |
dimention |
4 | set_dimention |
scale |
0.1 | set_scale |
learning_rate |
0.01 | set_learning_rate |
context_length |
16 | (tự tính: mem × dim) |
respond_length |
100 | set_respond_length |
Sea là trung tâm điều phối — nắm giữ Crab, Model và Beaver, đồng thời cung cấp các API cấp cao cho Owl gọi tới.
| Method | Mô tả |
|---|---|
Sea::new() |
Khởi tạo Sea với Crab rỗng và chưa có model |
crab_eating(text) |
Nạp văn bản vào Crab (cập nhật từ điển) |
build() |
Tạo Model từ cấu hình Beaver và từ điển Crab hiện tại |
jelly_thinking(s) |
Sinh văn bản từ chuỗi đầu vào |
jelly_learning(s) |
Huấn luyện model trên chuỗi văn bản |
save(path) |
Lưu Crab + Model ra file nhị phân qua ShrimpW |
load(path) |
Tải Crab + Model từ file nhị phân qua ShrimpR |
File: src/animal/crab.rs
Crab là tokenizer cấp ký tự. Duy trì bảng tra hai chiều char ↔ Endu với Endu tự tăng theo thứ tự gặp lần đầu.
| Method | Mô tả |
|---|---|
Crab::new() |
Tạo tokenizer rỗng |
eat_one(char) |
Thêm 1 ký tự vào từ điển (idempotent) |
eat_all(&str) -> EncodedVec |
Tokenize toàn bộ chuỗi, trả về chuỗi mã số |
to_endu(&char) -> Option<Endu> |
Chuyển ký tự → mã số |
to_char(&Endu) -> Option<char> |
Chuyển mã số → ký tự |
display(EncodedVec) |
In chuỗi mã số ra văn bản gốc |
size() -> usize |
Số ký tự duy nhất trong từ điển |
File: src/animal/beaver.rs
Beaver giữ Config và tạo Model khi được lệnh. Nhận các lệnh set từ CLI để cập nhật cấu hình trước khi build.
| Method | Mô tả |
|---|---|
Beaver::new() |
Tạo builder với Config mặc định |
build(&Crab) -> Model |
Tạo Model mới (embedding + 2 lớp MLP ngẫu nhiên) |
beaverf(key, f32) |
Đặt tham số float (scale, rate) |
beavern(key, usize) |
Đặt tham số nguyên (dim, mem, layer) |
display() |
In cấu hình hiện tại ra console |
Owl là vòng lặp REPL với 2 trạng thái: SETUP (chưa có model) và MODEL (đã có model).
Enum OwlC — các lệnh trả về từ vòng lặp:
BeaverDisplay // show — hiển thị cấu hình
CfgF(key, f32) // set scale/rate <value>
CfgN(key, usize) // set dim/mem/layer <value>
Eat(text) // import <path> — nạp văn bản
Load(path) // load <path> — tải model
Build // build — tạo model mới
Input(text) // gen <text> — sinh văn bản
Train(text) // train <path> — huấn luyện
Save(path) // save <path> — lưu model
Exit // exitSub-modules:
eyes.rs—Eyes<R>: wrapper đọc token/dòng từ stdinfur.rs— Hằng số màu ANSI (RED,GREEN,CYAN, ...)guide.rs— In hướng dẫnhelpcho từng trạng thái
Serialize/Deserialize mô hình ra file nhị phân tùy chỉnh (không dùng thư viện ngoài).
ShrimpW (Writer):
| Method | Mô tả |
|---|---|
ShrimpW::new(path) |
Mở file để ghi |
crab(&Crab) |
Ghi từ điển Crab |
model(&Model) |
Ghi toàn bộ Model (emb + 2 lớp + config) |
tensor(&Tensor1D) |
Ghi một vector |
matrix(&Matrix) |
Ghi một ma trận |
config(&Config) |
Ghi cấu hình |
ShrimpR (Reader):
| Method | Mô tả |
|---|---|
ShrimpR::new(path) |
Mở file để đọc |
crab() |
Đọc và phục hồi Crab |
model() |
Đọc và phục hồi Model |
tensor() |
Đọc một vector |
matrix() |
Đọc một ma trận |
config() |
Đọc cấu hình |
Trái tim của dự án. Chứa embedding matrix và 2 lớp MLP.
pub struct Model {
embedding : Matrix, // [vocab_size × dim]
layer1 : Layer, // (W1, B1) — lớp ẩn
layer2 : Layer, // (W2, B2) — lớp output
cfg : Config, // siêu tham số
}Sub-modules:
| File | Chức năng |
|---|---|
generate.rs |
generate(&ctx) -> EncodedVec — sinh respond_length ký tự |
train.rs |
train(&data) -> Prof — 1 epoch, trả về loss trung bình |
vocab_update.rs |
update_embedding(ctx, d_x) — cập nhật embedding |
default.rs |
Model::new(emb, cfg), make_emb_mtx(size, dim, scale) |
config.rs |
Wrapper getter Config từ Model |
Backward pass:
z1 = W1·x + b1
h = tanh(z1)
y = W2·h + b2
p = softmax(y)
dL/dy = p - one_hot(correct)
dW2 = hᵀ · dL/dy
dh = dL/dy · W2ᵀ
dz1 = dh ⊙ tanh'(h) (Hadamard product)
dW1 = xᵀ · dz1
dx = dz1 · W1ᵀ (dùng để update embedding)
Thư viện ma trận thuần Rust, không dependency ngoài.
| Hàm | Mô tả |
|---|---|
new_matrix(w, h, val) |
Tạo ma trận h×w với giá trị val |
plus(A, B) -> Matrix |
Cộng từng phần tử |
minus(A, B) -> Matrix |
Trừ từng phần tử |
mul_n(A, n) -> Matrix |
Nhân vô hướng |
hada(A, B) -> Matrix |
Tích Hadamard |
multi(A, B) -> Matrix |
Nhân ma trận (cache-friendly: i→k→j) |
transpose(A) -> Matrix |
Chuyển vị |
lookup(m, row) |
Lấy 1 hàng của ma trận |
tanh_deriv(h) -> Matrix |
Đạo hàm tanh: 1 - h² |
shape(m) -> (usize, usize) |
Trả về (height, width) |
| Hàm | Mô tả |
|---|---|
file_reader(path) -> Option<String> |
Đọc file text, chuẩn hóa \r\n → \n |
Chương trình chạy ở 2 trạng thái, được hiển thị trên prompt:
| Lệnh | Mô tả |
|---|---|
help |
In danh sách lệnh |
show |
Hiển thị cấu hình hiện tại |
set scale <float> |
Đặt hệ số khởi tạo trọng số |
set rate <float> |
Đặt learning rate |
set dim <int> |
Đặt số chiều embedding |
set mem <int> |
Đặt số ký tự ngữ cảnh |
set layer <int> |
Đặt số neuron lớp ẩn |
import <path> |
Nạp file văn bản vào từ điển (có thể gọi nhiều lần) |
build |
Tạo model mới → chuyển sang trạng thái MODEL |
load <path> |
Tải model từ file → chuyển sang trạng thái MODEL |
exit |
Thoát chương trình |
| Lệnh | Mô tả |
|---|---|
help |
In danh sách lệnh |
gen <text> |
Sinh văn bản tiếp nối từ chuỗi đầu vào |
train <path> |
Huấn luyện model trên file văn bản, in loss |
save <path> |
Lưu model + từ điển ra file nhị phân |
exit |
Quay về trạng thái SETUP |
Ví dụ phiên làm việc:
WELCOME USER
type "help" to see more!
:: [SETUP] show
|---Current config:--*
|> Node's number layer 1: 64
|> Char's length memory : 4
|> Dimention per char : 4
|> Scale when creating : 0.1
|> Learning rate : 0.01
|-------------------*
:: [SETUP] set layer 128
:: [SETUP] set rate 0.005
:: [SETUP] import resources/note.txt
:: [SETUP] build
**
Model created
**
:: [MODEL] gen Mèo là
Mèo là ...(sinh tiếp 100 ký tự)
:: [MODEL] train resources/note.txt
Trained , new expected LOSS: 2.3417
:: [MODEL] save mymodel
:: [MODEL] exit
:: [SETUP] exit
main()
│
├─ Owl::new() ← khởi tạo CLI controller
├─ Sea::new() ← khởi tạo orchestrator
│
└─ loop:
cmd = owl.big_loop() ← đọc & parse lệnh từ stdin
│
├─ [SETUP] show → sea.beaver.display()
├─ [SETUP] set → sea.beaver.beaverf/beavern(k, v)
├─ [SETUP] import → sea.crab_eating(text)
├─ [SETUP] build → sea.build() → owl.set_state(true)
├─ [SETUP] load → sea.load(path) → owl.set_state(true)
│
├─ [MODEL] gen → sea.jelly_thinking(text)
├─ [MODEL] train → sea.jelly_learning(text)
├─ [MODEL] save → sea.save(path)
└─ [MODEL] exit → owl.set_state(false) → quay về SETUP
Yêu cầu: Rust toolchain (1.85+ do dùng edition = "2024")
# Clone dự án
git clone <repo-url>
cd small_language_model
# Chạy (debug)
cargo run
# Chạy tối ưu tốc độ (khuyến nghị khi huấn luyện)
cargo run --release# Chạy toàn bộ tests
cargo test
## Cấu hình siêu tham số
Tất cả siêu tham số được quản lý trong `src/config.rs` và có thể chỉnh qua CLI lệnh `set` **trước khi** `build`. Sau khi build, cần tải lại model để thay đổi cấu hình.
```rust
Config::new() {
layer_node: 64, // Số neuron lớp ẩn
mem_ctx_char: 4, // Số ký tự ngữ cảnh
dimention: 4, // Chiều embedding
context_length: 16, // = mem × dim (tự tính)
scale: 0.1, // Hệ số khởi tạo trọng số
learning_rate: 0.01, // Learning rate SGD
respond_length: 100, // Độ dài văn bản sinh ra
}Dự án được xây dựng tại EIU — mục tiêu học thuật, tự hiện thực hóa backprop từ đầu.