---
title: "GPU và hành trình của một token"
description: "Đi từ prompt tới token kế tiếp qua CPU, VRAM, các tầng Transformer và những cụm nhân tính toán bên trong GPU; đồng thời chỉ ra điều gì thực sự quyết định tốc độ chạy model local."
date: 2026-07-15T14:22:48.000Z
author: "GPT-5.6"
tags: ["AI", "GPU", "Hardware", "Local LLM"]
lang: vi
canonical: https://namnn.com/blog/gpu-va-hanh-trinh-cua-mot-token/
---

Bạn vừa nhấn Enter sau một prompt dài. Quạt GPU tăng tốc, công suất nhảy vọt, rồi token đầu tiên xuất hiện. Sau đó các token tiếp theo chảy ra đều hơn. Nhìn từ giao diện chat, đây chỉ là một dòng chữ đang được nối dài. Nhìn từ phần cứng, mỗi token có thể là kết quả của hàng tỷ phép nhân cộng, nhiều lượt đọc bộ nhớ và một chuỗi phối hợp giữa CPU với GPU.

Tôi nghĩ đây là cách dễ hiểu nhất để trả lời câu hỏi vì sao GPU nằm ở trung tâm làn sóng AI hiện tại: không phải vì GPU “thông minh” hơn CPU, mà vì hình dạng của phép tính trong mô hình ngôn ngữ khớp gần như hoàn hảo với thứ GPU được thiết kế để làm nhanh. Mạng Transformer biến phần lớn công việc thành phép nhân ma trận dày đặc; GPU có rất nhiều lane thực thi, phần cứng nhân ma trận chuyên dụng và băng thông bộ nhớ lớn để nuôi chúng. CPU vẫn cần thiết, nhưng được tối ưu cho một kiểu bài toán khác.

## CPU nhanh một luồng, GPU thắng bằng số lượng

Một CPU desktop hiện đại có số core tương đối ít. Mỗi core dành nhiều transistor cho cache lớn, dự đoán nhánh, thực thi ngoài thứ tự và các cơ chế giảm latency của một luồng lệnh phức tạp. Đây là lựa chọn hợp lý cho hệ điều hành, trình duyệt, database và mọi workload có nhiều nhánh khó đoán.

GPU đi theo hướng khác. Nó dành nhiều diện tích chip hơn cho các đơn vị số học và dùng hàng nghìn thread để che thời gian chờ bộ nhớ. Trong mô hình CUDA, GPU NVIDIA gồm nhiều Streaming Multiprocessor (SM); thread được gom thành các warp 32 thread và được warp scheduler đưa vào thực thi. AMD gọi khối tương ứng là Compute Unit (CU), còn nhóm thread cùng thực thi là wavefront. Tên khác nhau, tư tưởng chung vẫn là cho nhiều phần tử dữ liệu đi qua cùng một luồng tính toán.<sup class="cite"><a href="#ref-2">2</a></sup><sup class="cite"><a href="#ref-3">3</a></sup>

![So sánh hệ thống thực thi GPU của NVIDIA CUDA và AMD](/images/gpu-va-hanh-trinh-cua-mot-token/gpu-execution-nvidia-amd.webp)

| CPU | GPU |
|---|---|
| Ít core phức tạp, tối ưu latency mỗi thread | Nhiều cụm tính toán, tối ưu throughput |
| Cache lớn, dự đoán nhánh và out-of-order execution | Nhiều thread đang chờ sẵn để che latency bộ nhớ |
| Hợp với control flow rẽ nhánh, công việc tuần tự | Hợp với cùng một phép toán áp lên rất nhiều phần tử |
| RAM dung lượng lớn, băng thông thường thấp hơn VRAM | VRAM nhỏ hơn RAM nhưng có băng thông rất cao |

GPU không biến một phép cộng thành phép cộng “xịn” hơn. Nó thắng khi có đủ hàng, cột, token hoặc request để hàng loạt đơn vị tính toán cùng bận. Nếu workload nhỏ, nhiều nhánh hoặc liên tục phải đợi CPU, phần cứng đồ sộ ấy vẫn có thể ngồi chơi.

## Bên trong GPU có gì?

Ta có thể nhìn GPU như một hệ thống nhiều tầng, từ ngoài vào trong:

![Infographic các tầng bên trong GPU từ CPU và RAM tới VRAM, L2 cache, SM hoặc Compute Unit và các execution cores](/images/gpu-va-hanh-trinh-cua-mot-token/inside-a-gpu.webp)

**SM hoặc CU** là cụm thực thi tương đối độc lập. Một kernel lớn được chia thành nhiều block; phần cứng phân phối các block lên những cụm còn tài nguyên. Bên trong mỗi cụm có scheduler, register file, shared memory và nhiều execution unit.

**Các execution unit không chuyên nhân ma trận** gồm FP/INT ALU, load/store unit và các đơn vị chức năng khác. Chúng chia nhau xử lý số học phổ thông, tính địa chỉ, logic, truy cập bộ nhớ và những phần việc không phù hợp với Tensor Core hoặc matrix core. “CUDA core” là tên tiếp thị hữu ích để so trong cùng một thế hệ, nhưng không phải đơn vị đo hiệu năng AI tuyệt đối giữa các kiến trúc.

**Tensor Core hoặc matrix core** thực hiện matrix multiply-accumulate trên những tile nhỏ, chẳng hạn dạng `D = A × B + C`. Nhiều tile ghép lại thành phép nhân ma trận lớn của model. Phần cứng này đặc biệt hiệu quả với FP16, BF16, FP8 hoặc các định dạng số nguyên được hỗ trợ; song loại dữ liệu, kích thước tile và kernel phải phù hợp thì năng lực lý thuyết mới biến thành tốc độ thật.<sup class="cite"><a href="#ref-4">4</a></sup>

**Register và shared memory** là vùng nhớ on-chip rất nhanh. Register giữ dữ liệu riêng của thread; shared memory cho các thread trong block trao đổi và tái sử dụng tile dữ liệu. Dung lượng của chúng nhỏ, nên kernel phải chia ma trận thành từng lát vừa vặn. Dùng quá nhiều register hoặc shared memory cho mỗi block còn làm giảm số warp có thể đồng thời hoạt động trên SM, tức giảm occupancy.

**L2 và VRAM** chứa tập dữ liệu lớn hơn. L2 là cache dùng chung, còn VRAM là nơi đặt phần lớn weights, KV cache và tensor trung gian. Một H100 SXM là ví dụ cực đoan của triết lý này: NVIDIA công bố biến thể 80 GB HBM3 có băng thông trên 3 TB/s, trong khi L2 chỉ ở mức vài chục MB.<sup class="cite"><a href="#ref-5">5</a></sup> Kho nhỏ, nhanh giữ phần đang dùng; kho lớn, chậm hơn giữ cả model.

Đường đi của một toán hạng thường là VRAM → L2 → shared memory/register → execution unit, rồi kết quả đi ngược ra. Tối ưu kernel phần lớn là bài toán giảm số lần đi xa: đọc liên tục để các lane gom giao dịch bộ nhớ, giữ tile nóng ở on-chip memory và tái sử dụng dữ liệu trước khi đẩy ra VRAM.

## Weight là thứ model đã học và mang theo

Trong bài này, **weight** là một con số mà model học được trong quá trình training. Hàng tỷ weight được xếp thành các vector và ma trận; cả tập hợp ấy tạo nên phần lớn parameter của model. Khi ta tải một checkpoint để chat, thứ chiếm nhiều dung lượng nhất chính là những ma trận số này.

Một linear layer có thể được viết gọn như sau:

$$
y = xW + b
$$

`x` là dữ liệu đi vào layer, `W` là ma trận weight, `b` là bias và `y` là kết quả đi ra. Với một ví dụ chỉ có hai chiều:

$$
\begin{bmatrix}2 & 3\end{bmatrix}
\begin{bmatrix}0.5 & 1.0 \\ 1.5 & -0.5\end{bmatrix}
=
\begin{bmatrix}5.5 & 0.5\end{bmatrix}
$$

![Ví dụ số: input vector nhân với ma trận weight để tạo output vector](/images/gpu-va-hanh-trinh-cua-mot-token/weight-matrix-numeric-example.webp)

Giữ nguyên input `[2, 3]` nhưng đổi các số trong ma trận, output sẽ đổi theo. Training là quá trình điều chỉnh những số ấy để dự đoán của model ngày càng khớp dữ liệu huấn luyện. Đến lúc inference, weights thường được giữ cố định; mỗi prompt mới chỉ đưa dữ liệu khác đi qua cùng các ma trận đã học.

Weight không phải một mẩu kiến thức có thể đọc riêng lẻ. Ta không thể mở một ô trong ma trận rồi thấy câu “Hà Nội là thủ đô Việt Nam”. Khả năng nhận biết ngôn ngữ, kiến thức và cách phản hồi được phân tán qua rất nhiều weight cùng tương tác. Trong Transformer, các ma trận này được dùng ở nhiều chỗ:

- embedding biến token ID thành vector;
- các projection tạo `Q`, `K`, `V` cho attention;
- output projection và MLP biến đổi hidden state ở từng layer;
- LM head đổi hidden state cuối thành logits trên toàn vocabulary.

![Infographic giải thích weight là các con số được điều chỉnh khi training rồi được dùng lại lúc inference](/images/gpu-va-hanh-trinh-cua-mot-token/what-are-model-weights.webp)

Hình trên minh họa một weight matrix bằng những con số cụ thể. Không có ô nào tự chứa nguyên một câu hay một fact; ý nghĩa xuất hiện từ cách hàng tỷ con số tương tác khi input đi qua model.

![Các ma trận weight được dùng tại embedding, Q K V attention, output projection, MLP và LM head](/images/gpu-va-hanh-trinh-cua-mot-token/weights-transformer-flow.webp)

Hình trên chỉ giữ lại đường đi cần thiết để thấy các ma trận weight tham gia ở đâu; normalization, residual connection và việc lặp qua nhiều Transformer layer đã được lược bỏ.

### Từ phép nhân ma trận tới token kế tiếp

Phép nhân ma trận không trực tiếp sinh ra chữ và cũng không tìm một từ đã lưu sẵn. Sau Transformer layer cuối, context hiện tại được nén vào hidden state $h$, một vector gồm $d$ số. LM head là ma trận $W_{\mathrm{LM}}$ có kích thước $d \times V$, trong đó $V$ là số token trong vocabulary. Có thể hình dung mỗi cột của ma trận này gắn với một token:

$$
\mathbf{z} = \mathbf{h}W_{\mathrm{LM}}
$$

Kết quả $\mathbf{z}$ có $V$ phần tử, gọi là **logits**: mỗi token nhận đúng một điểm số. Điểm của một token là dot product giữa hidden state và cột weight tương ứng. Nếu hướng của $h$ khớp mạnh với những gì model đã học cho token `FAST`, logit của `FAST` sẽ cao hơn các token còn lại.

![Hidden state nhân LM head để chấm điểm vocabulary, đổi logits thành xác suất và chọn token kế tiếp](/images/gpu-va-hanh-trinh-cua-mot-token/lm-head-next-token.webp)

Softmax biến logits thành một phân phối xác suất. Trong ví dụ trên, bốn logit `3.7`, `2.3`, `1.8`, `1.1` tương ứng xấp xỉ `68%`, `17%`, `10%`, `5%`. Sampling chọn một token từ phân phối ấy; token được nối vào chuỗi, rồi toàn bộ vòng decode chạy lại để dự đoán token tiếp theo. Token có logit cao nhất thường dễ được chọn hơn, nhưng temperature cùng các chiến lược top-k hoặc top-p có thể làm kết quả khác với phép lấy giá trị lớn nhất.

Ba loại dữ liệu cùng nằm trong VRAM nhưng giữ vai trò khác nhau:

| Thành phần | Chứa gì? | Thay đổi khi nào? |
|---|---|---|
| Weights | Các tham số model đã học | Thay đổi khi training hoặc fine-tuning; thường cố định lúc inference |
| Activations | Kết quả trung gian khi input đi qua từng layer | Sinh mới trong mỗi lượt tính toán rồi được giải phóng khi không còn cần |
| KV cache | `K` và `V` của các token đã xử lý trong từng layer | Tăng theo số token của request đang chạy |

Vì weights được dùng lại ở mọi request, runtime cố giữ chúng gần execution unit nhất có thể. Model 7B có khoảng 7 tỷ parameter; nếu trung bình mỗi parameter chiếm 2 byte ở FP16, riêng phần này đã xấp xỉ 14 GB. Đó là lý do dung lượng VRAM quyết định model có vừa hay không, còn băng thông VRAM quyết định GPU đưa các weight ấy tới cụm tính toán nhanh đến đâu.

## Vì sao Transformer rất hợp GPU?

Transformer gốc thay <span class="inline-note" data-note-id="recurrence" data-note-text="Recurrence là cách xử lý tuần tự: trạng thái của bước sau phụ thuộc vào kết quả bước trước, nên khó xử lý đồng thời nhiều token. Attention cho phép một token tính quan hệ với các token khác trong cùng lượt, đặc biệt thuận lợi cho giai đoạn prefill trên GPU.">recurrence</span> bằng <span class="inline-note" data-note-id="attention" data-note-text="Attention là cơ chế tính trọng số liên quan giữa các token. Từ các vector Q, K và V, model xác định token hiện tại nên tập trung vào phần nào của context rồi trộn thông tin đó thành hidden state mới.">attention</span>, nhờ vậy các token trong giai đoạn xử lý prompt có thể được tính song song hơn.<sup class="cite"><a href="#ref-1">1</a></sup> Trong một decoder-only LLM hiện đại, phần nặng nhất của mỗi layer thường xoay quanh hai nhóm phép tính:

- Các phép nhân ma trận: linear projection tạo `Q`, `K`, `V`, output của attention, các projection trong MLP, cùng hai tích `QK^T` và `Attention × V`.
- Các phép không phải GEMM như normalization, softmax, activation, residual add và reshape.

Với scaled dot-product attention:

$$
\operatorname{Attention}(Q,K,V)=\operatorname{softmax}\left(\frac{QK^T}{\sqrt{d}}+\text{mask}\right)V
$$

Các linear layer lẫn hai tích ma trận trong công thức trên đều có thể được chia thành tile. Hàng nghìn thread tải tile, Tensor Core nhân cộng, rồi kết quả được cộng dồn. Những phép như softmax hay normalization không phải GEMM thuần; chúng thường chạy bằng kernel vector, reduction hoặc kernel đã fusion để bớt ghi rồi đọc lại VRAM.

Đây cũng là lý do software stack quan trọng ngang silicon. CUDA, ROCm, Metal, cuBLAS, hipBLAS, shader compiler và các runtime inference quyết định tensor được chia thế nào, kernel nào được fusion, dữ liệu có nằm đúng chỗ không. Một GPU có thông số TOPS cao nhưng thiếu kernel tối ưu cho quantization format của model vẫn có thể thua một GPU yếu hơn trên giấy.

## Từ prompt tới token đầu tiên

Một lượt sinh văn bản có hai pha rất khác nhau: **prefill** và **decode**. Tài liệu tối ưu inference của NVIDIA cũng tách hai pha này vì prefill có mức song song cao, còn decode sinh tự hồi quy từng token và thường bị giới hạn bởi băng thông đọc weights.<sup class="cite"><a href="#ref-6">6</a></sup>

```mermaid
sequenceDiagram
    participant CPU
    participant GPU
    participant VRAM

    CPU->>CPU: Tokenize prompt
    CPU->>VRAM: Nạp weights + input IDs
    CPU->>GPU: Launch prefill
    loop Mỗi Transformer layer
        GPU->>VRAM: Đọc weights
        GPU->>GPU: QKV → attention → MLP
        GPU->>VRAM: Ghi K/V cache
    end
    GPU->>GPU: LM head → logits → sampling
    GPU-->>CPU: Token ID mới
    CPU->>CPU: Decode token thành text
```

![Infographic từ token ID qua embedding, matrix operations, logits và sampling để tạo token kế tiếp](/images/gpu-va-hanh-trinh-cua-mot-token/from-token-id-to-next-token.webp)

Trình tự thực tế phụ thuộc runtime, nhưng một đường đi điển hình gồm:

1. **CPU tokenize prompt.** Chuỗi Unicode được ánh xạ thành danh sách token ID. CPU cũng chuẩn bị buffer, tham số sampling và command cho GPU.
2. **Runtime bảo đảm weights ở nơi cần thiết.** Nếu model vừa mở, weights được đọc từ SSD vào RAM rồi chuyển sang VRAM, hoặc được memory-map và nạp theo nhu cầu. Đây là lúc tốc độ SSD và PCIe ảnh hưởng rõ tới thời gian load.
3. **GPU chạy embedding và prefill.** Toàn bộ token trong prompt đi qua các layer. Vì có nhiều token cùng lúc, các phép GEMM có ma trận đủ lớn để lấp đầy GPU. Kết quả `K` và `V` của từng layer được ghi vào KV cache.
4. **Layer cuối tạo logits.** Hidden state được chiếu lên kích thước vocabulary. Logits sau đó đi qua temperature, top-k, top-p hoặc thuật toán sampling khác. Runtime có thể sampling ngay trên GPU hoặc chuyển phần dữ liệu cần thiết về CPU.
5. **Token ID được detokenize.** CPU ghép token thành byte rồi thành text để giao diện hiển thị.

Khoảng chờ trước token đầu tiên, thường gọi là time to first token, chịu ảnh hưởng mạnh từ độ dài prompt, tốc độ prefill, thời gian nạp model và overhead khởi tạo. Prompt dài cho GPU nhiều việc song song hơn, nhưng tổng lượng tính toán attention cũng tăng.

## Từ một token sang token kế tiếp

Sau prefill, model bước vào decode. Đây là chỗ dễ gây hiểu nhầm nhất: GPU có thể tính song song rất nhiều phần tử **bên trong** một token, nhưng không thể tính tùy ý nhiều token tương lai song song vì token sau phụ thuộc token vừa chọn.

Với mỗi token mới, runtime lặp lại chu trình sau:

```mermaid
flowchart LR
    ID[Token ID] --> Embed[Embedding]
    Embed --> Layer[Transformer layers]
    Layer --> Logits[Logits]
    Logits --> Sample[Sampling]
    Sample --> Next[Token kế tiếp]
    Next --> ID
    Layer <--> KV[(KV cache)]
```

Trong từng layer, hidden state của token hiện tại được nhân với weights để tạo `Q`, `K`, `V`. `K` và `V` mới được nối vào KV cache; `Q` hiện tại đọc toàn bộ phần cache cần thiết để tính attention. KV cache tránh việc tính lại `K` và `V` của mọi token cũ, khiến lượng tính toán mỗi bước giảm đáng kể, nhưng dung lượng cache tăng theo context.<sup class="cite"><a href="#ref-7">7</a></sup>

Decode batch 1 thường tạo các phép nhân dạng matrix-vector hoặc ma trận rất “mỏng”. GPU chưa chắc dùng hết năng lực tính toán, nhưng vẫn phải đọc phần lớn weights qua các layer cho mỗi token. Vì vậy tốc độ decode thường gần với bài toán băng thông:

$$
\text{tokens/s tối đa gần đúng} \lesssim \frac{\text{băng thông bộ nhớ hữu dụng}}{\text{số byte weights phải đọc mỗi token}}
$$

Đây chỉ là trần ước lượng, chưa tính KV cache, activation, cache hit, dequantization và overhead kernel. Dẫu vậy, nó giải thích một hiện tượng quen thuộc: quantization 4-bit đôi khi không chỉ giúp model vừa VRAM mà còn tăng tốc, bởi mỗi token phải kéo ít byte weights hơn. Đổi lại, kernel cần giải mã format quantized và chất lượng đầu ra có thể giảm tùy model, phương pháp và mức bit.

Nếu tăng batch hoặc phục vụ nhiều request đồng thời, cùng một lần đọc weights có thể phục vụ nhiều token đang decode. Arithmetic intensity tăng, GPU dùng compute tốt hơn và throughput tổng tăng; latency của từng người dùng chưa chắc giảm.

## Chọn GPU chạy model local: đừng nhìn mỗi TOPS

Với local inference, bốn nhóm yếu tố hữu ích hơn một dòng “AI TOPS” trên hộp sản phẩm.

### 1. Dung lượng VRAM quyết định model có vừa hay không

#### Đọc tên model và file trước khi tải

Một tên như `Qwen2.5-7B-Instruct-Q4_K_M.gguf` đang ghép nhiều loại thông tin vào cùng một dòng. Tách chúng ra sẽ dễ chọn đúng file hơn:

| Phần tên | Nó cho biết gì? | Có liên quan tới VRAM thế nào? |
|---|---|---|
| `7B`, `8B`, `14B`, `70B` | Xấp xỉ số lượng parameter của model dense | Số càng lớn, weights càng chiếm nhiều bộ nhớ |
| `Base`, `Chat`, `Instruct` | Model gốc hay đã được fine-tune để hội thoại/làm theo instruction | Thường không làm thay đổi đáng kể cách tính dung lượng nếu số parameter giữ nguyên |
| `FP16`, `BF16` | Mỗi weight thường dùng 16 bit | Chất lượng gần checkpoint gốc nhưng tốn khoảng 2 byte mỗi parameter |
| `Q8_0`, `Q6_K`, `Q5_K_M`, `Q4_K_M` | Mức quantization; con số thấp hơn thường dùng ít bit hơn | File nhỏ hơn và thường cần ít VRAM hơn, đổi lại có thể mất chất lượng hoặc thêm overhead giải mã |
| `GGUF` | Container thường dùng với `llama.cpp` và các runtime tương thích | Bên trong GGUF vẫn có thể là FP16, Q8, Q5, Q4 hoặc loại khác; chỉ nhìn `.gguf` chưa suy ra được kích thước |
| `AWQ`, `GPTQ`, `EXL2` | Phương pháp hoặc format quantization gắn với những backend nhất định | Phải kiểm tra runtime và GPU có hỗ trợ đúng format, không chỉ kiểm tra dung lượng |
| `8K`, `32K`, `128K` | Context length tối đa mà model hoặc bản cấu hình công bố | Không phải kích thước weights, nhưng context thực dùng càng dài thì KV cache càng lớn |

Tên MoE cần đọc khác model dense. Ví dụ `8x7B` thường mô tả tám expert cỡ 7B, nhưng mỗi token chỉ kích hoạt một phần số expert. **Active parameters** giúp hình dung lượng compute mỗi token; VRAM lại phải xét **tổng weights thực sự được nạp**, nên không được tính một model `8x7B` như model dense 7B.

#### Tính nhẩm phần weights

Nếu số parameter được ghi theo tỷ, phép tính nhẩm là:

$$
M_{\mathrm{weights}}\;(\mathrm{GB}) \approx
\frac{P_{\mathrm{billion}} \times b_{\mathrm{weight}}}{8}
$$

Trong đó $P_{\mathrm{billion}}$ là số lượng parameter tính theo tỷ, còn $b_{\mathrm{weight}}$ là số bit trung bình dùng cho mỗi weight.

Mỗi một tỷ parameter vì thế cần xấp xỉ 2 GB ở FP16/BF16, 1 GB ở 8-bit, 0,625 GB ở 5-bit và 0,5 GB ở 4-bit. Bảng dưới chỉ tính phần weights lý tưởng theo hệ thập phân:

| Model dense | FP16/BF16 | 8-bit | 5-bit | 4-bit |
|---|---:|---:|---:|---:|
| 7B | 14 GB | 7 GB | 4,4 GB | 3,5 GB |
| 8B | 16 GB | 8 GB | 5 GB | 4 GB |
| 14B | 28 GB | 14 GB | 8,8 GB | 7 GB |
| 32B | 64 GB | 32 GB | 20 GB | 16 GB |
| 70B | 140 GB | 70 GB | 43,8 GB | 35 GB |

Số `B` trong tên thường đã được làm tròn, còn những format như `Q4_K_M` hoặc `Q5_K_M` không nhất thiết đạt đúng 4 hoặc 5 bit cho mọi tensor. Bảng phù hợp để loại nhanh những cấu hình chắc chắn không vừa; khi chọn file cụ thể, hãy dùng kích thước file thật.

Đừng đem con số trong bảng so thẳng với dung lượng in trên hộp GPU. Một ngân sách thực dụng hơn là:

$$
M_{\mathrm{VRAM}} \approx
M_{\mathrm{weights}} + M_{\mathrm{KV}} + M_{\mathrm{runtime}} + M_{\mathrm{reserve}}
$$

File quantized còn có scale, metadata, alignment và một số tensor giữ precision cao hơn. KV cache thay đổi theo kiến trúc model, context thực dùng, batch size và KV precision. Nếu GPU đồng thời xuất hình cho desktop, hệ điều hành và ứng dụng khác cũng dùng VRAM. Chừa khoảng 10–20% thay vì cố nhét tới byte cuối thường đỡ gặp lỗi hơn; đây là khoảng dự phòng vận hành, không phải hằng số của mọi backend.

#### Từ GPU phổ thông tới accelerator

Áp phép tính trên vào ba mức VRAM thường gặp:

- **GPU 8 GB:** model 7B/8B Q4 thường còn đủ chỗ cho runtime và context vừa phải; Q5 có thể vừa nhưng cần xem kích thước file cùng KV cache. Bản 7B Q8 đã sát trần, 8B Q8 gần như không còn khoảng dự phòng, còn FP16 không phù hợp nếu muốn giữ toàn bộ model trong VRAM.
- **GPU 12 GB:** 7B/8B Q8 thường khả thi với context vừa phải; 14B Q4 cũng có cơ hội vừa. Model 7B FP16 có 14 GB weights lý tưởng nên không vừa hoàn toàn.
- **GPU 16 GB:** 7B/8B Q8 khá thoải mái và 14B Q4/Q5 thực tế hơn. 7B FP16 có thể đã chiếm gần hết VRAM sau khi cộng overhead, nên “14 GB weights trên card 16 GB” vẫn chưa phải cấu hình thoải mái.

Ở phân khúc data center và hệ thống AI cá nhân, phép tính không đổi nhưng loại bộ nhớ cùng băng thông khác hẳn:

| Phần cứng | Bộ nhớ công bố | Tính nhẩm model có thể chứa |
|---|---:|---|
| NVIDIA A100 | 40 hoặc 80 GB HBM; bản 80 GB đạt khoảng 1,9–2,0 TB/s tùy PCIe hay SXM | Bản 80 GB chứa thoải mái 70B Q4; 70B Q8 có khoảng 70 GB weights nên phải kiểm tra kỹ KV cache và overhead |
| NVIDIA H100 | H100 SXM có 80 GB; H100 NVL có 94 GB mỗi GPU | Một H100 80 GB vẫn không chứa trọn 70B FP16 vì riêng weights đã gần 140 GB; cặp H100 NVL có tổng 188 GB và có thể chia model qua hai GPU |
| NVIDIA DGX Spark | 128 GB LPDDR5x unified system memory, băng thông 273 GB/s | 70B Q8 hoặc Q4 có thể nằm trong cùng pool bộ nhớ; 70B FP16 gần 140 GB thì không. Bộ nhớ này còn được CPU, GPU và hệ thống dùng chung |

A100 80 GB có băng thông HBM khoảng 1,9–2,0 TB/s, còn H100 đạt khoảng 3,35 TB/s trên bản SXM và 3,9 TB/s trên H100 NVL.<sup class="cite"><a href="#ref-13">13</a></sup><sup class="cite"><a href="#ref-14">14</a></sup> DGX Spark đổi hướng: 128 GB unified memory giúp một máy nhỏ chứa được model lớn, nhưng băng thông 273 GB/s thấp hơn nhiều so với hai accelerator trên.<sup class="cite"><a href="#ref-15">15</a></sup> Vì vậy DGX Spark có lợi thế về dung lượng và sự tiện dụng trên desktop; không nên nhìn con số 128 GB rồi suy ra tốc độ decode ngang GPU dùng HBM.

Dung lượng chỉ trả lời **có nạp được hay không**. Hai card cùng 12 GB vẫn có thể cho token/s khác nhau vì băng thông bộ nhớ, compute, datatype và chất lượng kernel. Cách chắc nhất trước khi tải là xem kích thước chính file model, chọn context dự kiến, ước lượng KV cache rồi mới so với VRAM còn trống trên máy.

Với batch gồm `N` sequence có cùng context dài `T` token, KV cache gần đúng cho attention thông thường là:

$$
\text{KV bytes} \approx 2 \times N \times L \times T \times H_{kv} \times D_h \times S
$$

Trong đó `N` là batch size, `L` là số layer, `Hkv` là số KV head, `Dh` là kích thước mỗi head và `S` là số byte mỗi phần tử. Hệ số 2 dành cho cả key lẫn value. Nếu các sequence dài không đều, thay `N × T` bằng tổng số token đang được cache trong cả batch. Grouped-query attention giảm `Hkv`, còn quantized KV cache giảm `S`; cả hai đều giúp tiết kiệm dung lượng và có thể thay đổi tốc độ.<sup class="cite"><a href="#ref-6">6</a></sup>

### 2. Băng thông VRAM thường chi phối decode

Hai card có cùng dung lượng VRAM vẫn có thể cho token/s khác xa nếu loại nhớ, bus width và tốc độ bộ nhớ khác nhau. GDDR trên card consumer và HBM trên accelerator đều nhằm đưa nhiều byte mỗi giây tới chip. Băng thông công bố là mức lý thuyết; locality, cache, kernel và mức công suất quyết định phần sử dụng được.

### 3. Compute và datatype quyết định prefill, batch lớn

Prefill, xử lý nhiều request và model đa phương thức có thể đẩy workload gần giới hạn compute hơn. Khi ấy số matrix core, xung nhịp, công suất, khả năng FP16/BF16/FP8/INT8/INT4 và chất lượng kernel đều đáng kể. Đừng lấy TOPS của INT4 để suy ra tốc độ một model đang chạy kernel FP16.

### 4. Software support quyết định phần cứng có được dùng đúng cách

CUDA thường có đường tối ưu rộng nhất trên NVIDIA; AMD dùng HIP/ROCm; Apple Silicon dùng Metal. `llama.cpp` hỗ trợ CPU, CUDA, HIP, Metal, Vulkan và cả hybrid CPU+GPU, đồng thời cung cấp nhiều mức quantization.<sup class="cite"><a href="#ref-8">8</a></sup> Nhưng “có backend” mới chỉ là điểm bắt đầu. Cần xem đúng model architecture, quant format và kernel chủ chốt đã được backend đó tối ưu hay chưa.

## Unified memory không phải một khái niệm duy nhất

**Unified memory** nghĩa rộng là CPU và GPU có thể làm việc với dữ liệu qua một mô hình bộ nhớ thống nhất, thay vì ứng dụng phải tự quản lý mọi lần copy giữa hai bên. Nhưng chữ “thống nhất” có thể chỉ hai cơ chế khác nhau: cùng dùng **một pool bộ nhớ vật lý**, hoặc chỉ dùng **chung không gian địa chỉ** trong khi RAM và VRAM vẫn là hai pool tách biệt.

![So sánh unified physical memory với CUDA Unified Memory trên hệ thống có RAM và VRAM tách biệt](/images/gpu-va-hanh-trinh-cua-mot-token/unified-memory-two-mechanisms.webp)

Ở sơ đồ bên trái, CPU và GPU cùng truy cập một kho vật lý; Apple Silicon và DGX Spark là hai ví dụ. Ở bên phải, CUDA Unified Memory giúp chương trình nhìn thấy một không gian địa chỉ thống nhất, còn runtime phải quyết định page đang nằm trong RAM hay VRAM và di chuyển nó khi cần.

Trên Apple Silicon, CPU và GPU nằm trong cùng SoC và truy cập cùng một pool bộ nhớ vật lý. Dữ liệu không cần phải copy qua PCIe giữa system RAM và VRAM như trên máy có GPU rời; CPU cùng GPU vẫn cần đồng bộ quyền truy cập và vẫn tranh chấp băng thông của hệ thống.<sup class="cite"><a href="#ref-9">9</a></sup> Lợi thế lớn cho local LLM là một pool unified memory dung lượng lớn có thể chứa model vượt quá VRAM của nhiều card consumer. Điều đó không có nghĩa hệ thống Apple Silicon sẽ tự động sinh token nhanh bằng các accelerator dùng HBM.

CUDA Unified Memory trên hệ thống có CPU và GPU rời lại thiên về một không gian địa chỉ chung được runtime cùng phần cứng quản lý. Trang nhớ có thể migrate giữa RAM và VRAM, hoặc GPU truy cập từ xa qua PCIe/NVLink tùy hệ thống. Oversubscription giúp chương trình không văng lỗi ngay khi vượt VRAM, nhưng page fault, migration và băng thông liên kết có thể làm tốc độ tụt mạnh.<sup class="cite"><a href="#ref-10">10</a></sup>

Vì vậy câu “GPU dùng được RAM hệ thống” thiếu mất nửa cơ chế. Câu hỏi đúng phải là:

- CPU và GPU có dùng chung **bộ nhớ vật lý** hay chỉ chung **không gian địa chỉ**?
- Dữ liệu nằm ở đâu lúc kernel đọc nó?
- Nó đi qua bus nào, với băng thông và latency bao nhiêu?
- Runtime có giữ được weights resident hay liên tục gây migration?

## Mainboard, CPU, RAM và SSD ảnh hưởng đến đâu?

Nếu toàn bộ model cùng KV cache đã nằm trong VRAM, một lượt decode batch nhỏ chủ yếu quay vòng bên trong GPU. Khi đó nâng SSD gần như không tăng token/s; mainboard đắt hơn cũng không biến GDDR6 thành HBM.

Các linh kiện còn lại quan trọng ở những ranh giới khác:

**PCIe và mainboard.** PCIe chở weights lúc load, dữ liệu khi offload và giao tiếp CPU–GPU. PCIe 4.0 chạy 16 GT/s mỗi lane, gấp đôi tốc độ truyền của PCIe 3.0 theo PCI-SIG.<sup class="cite"><a href="#ref-11">11</a></sup> Một card cắm vào slot vật lý x16 vẫn có thể chỉ nhận liên kết x4 nếu slot đó được đi dây x4 hoặc chia sẻ lane với SSD; liên kết cũng có thể chạy ở thế hệ PCIe thấp hơn. Các giới hạn này có thể bóp throughput. Với model nằm trọn VRAM, bus thường không phải điểm nghẽn ở mỗi token; với hybrid offload, nó có thể trở thành điểm nghẽn chính.

**CPU.** CPU tokenize, điều phối runtime, launch kernel, thực hiện sampling trong một số backend và chạy những layer không offload. CPU yếu có thể giữ chân GPU khi model nhỏ, batch nhỏ hoặc pipeline gọi nhiều tác vụ ngoài model. Khi phần lớn layer nằm trên CPU, SIMD support, số core, băng thông RAM và NUMA đều đáng kể.

**RAM.** Cần đủ để memory-map hoặc giữ model, cache của hệ điều hành và phần offload. Dual-channel hoặc nhiều kênh nhớ tăng băng thông CPU inference. Nhưng RAM nhiều không thay thế được VRAM nhanh: dữ liệu GPU phải đọc qua PCIe vẫn đi qua một liên kết hẹp hơn nhiều so với bus VRAM on-board.

**SSD.** Ảnh hưởng thời gian tải model và đổi checkpoint, không trực tiếp quyết định tốc độ steady-state khi model đã resident. SSD nhanh đặc biệt dễ nhận ra với file hàng chục hoặc hàng trăm GB; sau đó sân khấu thuộc về bộ nhớ và compute.

**Nguồn và tản nhiệt.** GPU chạm giới hạn nhiệt hoặc power limit sẽ hạ xung. Benchmark vài giây có thể đẹp, còn một phiên context dài hoặc batch liên tục mới cho thấy tốc độ bền vững. Card đủ VRAM nhưng bị giới hạn điện vẫn có thể thua cấu hình cân bằng hơn.

## Khi model không vừa VRAM

Có bốn cách xử lý, mỗi cách đánh đổi một thứ khác nhau:

- **Quantize weights:** giảm dung lượng và traffic, đổi lấy sai số cùng overhead dequantization.
- **Giảm context hoặc KV precision:** tiết kiệm KV cache, nhưng mất context hữu dụng hoặc thêm chi phí xử lý.
- **Offload một số layer sang CPU:** vẫn chạy được model lớn hơn, nhưng mỗi token phải đi qua cả hai miền tính toán; băng thông RAM và PCIe bắt đầu lộ rõ.
- **Chia model qua nhiều GPU:** tăng tổng VRAM, song phải trả chi phí giao tiếp giữa GPU. NVLink, PCIe topology và cách chia layer/tensor quyết định kết quả.

`llama.cpp` thậm chí có tùy chọn CUDA Unified Memory để fallback sang RAM thay vì crash khi hết VRAM. Tài liệu của dự án mô tả đúng lợi ích là tránh lỗi hết bộ nhớ, không hứa rằng phần RAM ấy có tốc độ như VRAM.<sup class="cite"><a href="#ref-12">12</a></sup>

Đây là chỗ tôi thường ưu tiên theo thứ tự: làm model vừa hoàn toàn trong VRAM hoặc unified physical memory; nếu chưa vừa thì chọn quantization hợp lý; chỉ sau đó mới offload. Chạy được và chạy nhanh là hai tiêu chí khác nhau.

## GPU là trung tâm, nhưng không hoạt động một mình

AI hiện tại đặt GPU ở giữa vì cả huấn luyện lẫn inference đều cần số lượng lớn phép nhân ma trận và lượng dữ liệu khổng lồ được đưa tới execution unit. Huấn luyện cùng prefill tận dụng độ song song; decode tận dụng băng thông; Tensor Core tận dụng precision thấp; software stack biến graph của model thành kernel phù hợp với silicon.

Nhưng token không sinh ra “trong GPU” theo nghĩa cô lập. CPU chuẩn bị và điều phối, RAM cùng SSD giữ dữ liệu trước khi nạp, PCIe hoặc fabric nối các miền bộ nhớ, runtime chọn kernel, còn VRAM quyết định phần working set nào ở đủ gần compute. Chỉ cần một mắt xích buộc weights di chuyển qua liên kết chậm ở mỗi bước, bảng thông số TOPS đẹp đến mấy cũng không cứu được token/s.

Lần tới khi thấy một model local chạy chậm, tôi sẽ không hỏi mỗi “GPU bao nhiêu core?”. Tôi sẽ nhìn đường đi của từng byte: weights đang ở đâu, KV cache lớn đến mức nào, kernel dùng datatype gì, và mỗi token phải vượt qua ranh giới bộ nhớ nào. Phần cứng AI, xét đến cùng, là nghệ thuật giữ dữ liệu đủ gần nơi thực hiện phép nhân.

## Tài liệu tham khảo

<ol class="references">
<li id="ref-1">Ashish Vaswani và cộng sự (2017). <em>Attention Is All You Need.</em> <a href="https://arxiv.org/abs/1706.03762" target="_blank" rel="noopener">arXiv:1706.03762</a></li>
<li id="ref-2">NVIDIA. <em>CUDA C++ Programming Guide: Programming Model và SIMT.</em> <a href="https://docs.nvidia.com/cuda/cuda-programming-guide/01-introduction/programming-model.html" target="_blank" rel="noopener">CUDA Documentation</a></li>
<li id="ref-3">AMD. <em>HIP Documentation: Hardware implementation.</em> <a href="https://rocm.docs.amd.com/projects/HIP/en/develop/understand/hardware_implementation.html" target="_blank" rel="noopener">ROCm Documentation</a></li>
<li id="ref-4">NVIDIA. <em>CUDA Programming Guide: Writing Tile Kernels.</em> <a href="https://docs.nvidia.com/cuda/cuda-programming-guide/02-basics/writing-tile-kernels.html" target="_blank" rel="noopener">CUDA Documentation</a></li>
<li id="ref-5">NVIDIA (2022). <em>NVIDIA Hopper Architecture In-Depth.</em> <a href="https://developer.nvidia.com/blog/nvidia-hopper-architecture-in-depth/" target="_blank" rel="noopener">NVIDIA Technical Blog</a></li>
<li id="ref-6">NVIDIA (2023). <em>Mastering LLM Techniques: Inference Optimization.</em> <a href="https://developer.nvidia.com/blog/mastering-llm-techniques-inference-optimization/" target="_blank" rel="noopener">NVIDIA Technical Blog</a></li>
<li id="ref-7">Hugging Face. <em>Transformers: Caching.</em> <a href="https://huggingface.co/docs/transformers/cache_explanation" target="_blank" rel="noopener">Transformers Documentation</a></li>
<li id="ref-8">ggml-org. <em>llama.cpp.</em> <a href="https://github.com/ggml-org/llama.cpp" target="_blank" rel="noopener">GitHub repository</a></li>
<li id="ref-9">Apple (2020). <em>Explore the new system architecture of Apple silicon Macs.</em> <a href="https://developer.apple.com/videos/play/wwdc2020/10686/" target="_blank" rel="noopener">Apple Developer</a></li>
<li id="ref-10">NVIDIA. <em>CUDA Programming Guide: Unified Memory.</em> <a href="https://docs.nvidia.com/cuda/cuda-programming-guide/04-special-topics/unified-memory.html" target="_blank" rel="noopener">CUDA Documentation</a></li>
<li id="ref-11">PCI-SIG. <em>PCI Express 4.0 FAQ.</em> <a href="https://pcisig.com/faq?field_category_value%5B%5D=pci_express_4.0&amp;keys=" target="_blank" rel="noopener">PCI-SIG</a></li>
<li id="ref-12">ggml-org. <em>llama.cpp build documentation: Unified Memory.</em> <a href="https://github.com/ggml-org/llama.cpp/blob/master/docs/build.md#unified-memory" target="_blank" rel="noopener">GitHub repository</a></li>
<li id="ref-13">NVIDIA. <em>NVIDIA A100 Tensor Core GPU: Specifications.</em> <a href="https://www.nvidia.com/en-us/data-center/a100/" target="_blank" rel="noopener">NVIDIA Data Center</a></li>
<li id="ref-14">NVIDIA. <em>NVIDIA H100 Tensor Core GPU: Specifications.</em> <a href="https://www.nvidia.com/en-us/data-center/h100/" target="_blank" rel="noopener">NVIDIA Data Center</a></li>
<li id="ref-15">NVIDIA. <em>DGX Spark User Guide: Hardware Overview.</em> <a href="https://docs.nvidia.com/dgx/dgx-spark/hardware.html" target="_blank" rel="noopener">NVIDIA Documentation</a></li>
</ol>