---
title: "Cửa sổ ngữ cảnh không phải trí nhớ"
description: "Cửa sổ ngữ cảnh là ngân sách hữu hạn, không phải trí nhớ. Cùng một request, hai tỉ lệ ước lượng token lệch nhau 1,6 lần, và bạn không biết mình ở đâu."
pubDate: 2026-09-20
author: tusgino
tags: [ai-engineering, architecture, llm]
series:
  id: tu-llm-den-harness
  title: "Từ LLM đến Harness"
  order: 2
cover: /images/covers/cua-so-ngu-canh-khong-phai-tri-nho.svg
coverAlt: "Một thanh ngân sách bị lấp đầy dần, phần cuối bị cắt cụt khi vượt giới hạn"
faq:
  - q: "Cửa sổ ngữ cảnh lớn có nghĩa là model nhớ nhiều hơn không?"
    a: "Không. Cửa sổ lớn nghĩa là mỗi lần gọi bạn được gửi nhiều hơn. Model vẫn không giữ gì giữa hai lần gọi, và mọi thứ trong cửa sổ vẫn là dữ liệu bạn gửi kèm."
  - q: "Vượt cửa sổ ngữ cảnh thì chuyện gì xảy ra?"
    a: "Request bị từ chối, hoặc hệ thống của bạn tự cắt bớt trước khi gửi. Kiểu thứ hai nguy hiểm hơn vì nó âm thầm: model nhận một lịch sử đã bị cắt mà không biết là đã bị cắt."
  - q: "Vì sao ước lượng token lại lệch đến 1,6 lần?"
    a: "Vì hai bên dùng hai tỉ lệ khác nhau: 4 ký tự mỗi token và 2,5 ký tự mỗi token. Cả hai đều là tỉ lệ cố định, nên khoảng lệch luôn đúng 1,6 lần bất kể nội dung."
  - q: "Tiếng Việt có tốn nhiều token hơn tiếng Anh không?"
    a: "Tài liệu của cả bốn nhà cung cấp đều không nói gì về token hoá tiếng Việt, nên không có câu trả lời chính thức. Đo được là: cùng nội dung, tiếng Việt tốn 1,36 byte mỗi ký tự so với 1,00 của tiếng Anh."
---

<TLDR>Cửa sổ ngữ cảnh là một ngân sách hữu hạn tính bằng token, không phải trí nhớ. Cùng một request, hai tỉ lệ ước lượng đã công bố cho ra hai kết quả lệch nhau 1,6 lần.</TLDR>

## Cửa sổ to hơn không làm model nhớ hơn

Ba năm trước cửa sổ ngữ cảnh đo bằng nghìn token. Bây giờ mô hình phổ biến nhất của mỗi nhà cung cấp đều ở mức một triệu. Nghe như model đã có trí nhớ.

Không phải. Cửa sổ lớn hơn nghĩa là **mỗi lần gọi bạn được gửi nhiều hơn**. Model vẫn không giữ gì giữa hai lần gọi — đó là chuyện của bài trước. Cửa sổ chỉ quyết định bạn nhét được bao nhiêu vào một lần.

Và cửa sổ là ngân sách, không phải kho chứa. Cạn ngân sách thì mất dữ liệu, theo một trong hai kiểu — và một trong hai kiểu đó tệ hơn hẳn.

## Bốn con số, và một trong đó không ai đo được

Không cần API key để thấy vấn đề. Script nằm trong repo này tại `docs/research/measure-context-budget.mjs`.

Phép đo thứ nhất: cùng một nội dung, ba cách viết.

| Mẫu | Ký tự | Byte | ÷4 (thấp) | ÷2,5 (cao) |
|---|---|---|---|---|
| Tiếng Việt | 169 | 230 | 42,3 | 67,6 |
| Tiếng Việt bỏ dấu | 169 | 169 | 42,3 | 67,6 |
| Tiếng Anh | 190 | 190 | 47,5 | 76 |

Bỏ dấu: **169 ký tự → 169 ký tự** (không đổi), nhưng **230 byte → 169 byte** (giảm 27%).

```chart title="Cùng một câu: số ký tự không đổi, số byte thì đổi" type="bar"
{"labels": ["Tiếng Việt", "Tiếng Việt bỏ dấu", "Tiếng Anh"], "series": [{"name": "Ký tự", "values": [169, 169, 190]}, {"name": "Byte UTF-8", "values": [230, 169, 190]}]}
```

Phép đo thứ hai: một cửa sổ ngữ cảnh chứa được bao nhiêu lượt. Mỗi lượt là một request đầy đủ, gồm system prompt, khai báo tool, lịch sử và kết quả tool.

| Cửa sổ | Theo ÷4 | Theo ÷2,5 | Khoảng |
|---|---|---|---|
| 200.000 | 620 | 387 | 1,6× |
| 1.000.000 | 3.103 | 1.939 | 1,6× |

<StatRow stats={[
  { value: "1,6×", label: "Khoảng lệch giữa hai cách ước lượng", delta: "4 ký tự/token so với 2,5", trend: "flat" },
  { value: "27%", label: "Byte tiết kiệm khi bỏ dấu tiếng Việt", delta: "Không đổi một ký tự nào", trend: "down" },
  { value: "1.289", label: "Ký tự của một request có tool", delta: "System, tool, lịch sử, kết quả", trend: "up" }
]} />

Dòng `vi-no-diacritics` đáng dừng lại. Nó là **đúng câu tiếng Việt đó**, chỉ bỏ dấu. Số ký tự không đổi một đơn vị: 169 trước, 169 sau. Nhưng số byte giảm từ 230 xuống 169.

Nghĩa là phần byte tăng thêm **không đến từ việc câu dài hơn**. Nó đến từ mã hoá UTF-8. Nội dung y hệt, cùng số ký tự, mà tốn thêm 27% dung lượng.

Và tôi phải nói rõ giới hạn: đây là phép đo **byte**, không phải phép đo **token**. Tokenizer không chia theo byte.

## Vì sao con số 1,6 không phải là một phát hiện

Hai cột token ở trên lấy từ hai tỉ lệ cố định đã công bố: cột thấp dùng 4 ký tự mỗi token, cột cao dùng 2,5.

Vì cả hai đều là tỉ lệ cố định, thương số giữa chúng luôn bằng 1,6 — với mọi văn bản, mọi ngôn ngữ. Con số 1,6 không phản ánh bản chất dữ liệu của bạn, mà chỉ phơi bày khoảng cách 60% giữa hai mốc neo ước lượng phổ biến nhất.

Điều đáng lo không phải con số 1,6, mà là **cả hai mỏ neo đó đều có thật và đều đang được dùng trong production.**

Nguồn của hai mỏ neo:

- **4 ký tự mỗi token** là heuristic của `dsh-token-meter`. Chính tài liệu của nó tự cảnh báo: *"CJK text and JSON schemas underprice badly at four characters per token"*. Google Gemini cũng công bố đúng con số này: *"a token is equivalent to about 4 characters"*.
- **2,5 ký tự mỗi token** suy ra từ tài liệu Anthropic: *"1M tokens is roughly 555k words or 2.5M Unicode characters"*.

Và OpenAI nói thẳng về cách ước lượng: *"estimates like `characters / 4` are inaccurate"*.

Ba nhà cung cấp đưa ra ba con số khác nhau, và không ai sai cả. Đó chính là nghịch lý.

## Ba tầng của cùng một ngân sách

```diagram
type: layers
title: Ba tầng của ngân sách ngữ cảnh
desc: Từ con số công bố của nhà cung cấp, qua khoảng lệch ước lượng heuristic, tới ngưỡng quyết định thực tế
nodes:
  - id: contract
    label: Tầng hợp đồng
    sub: Cửa sổ nhà cung cấp công bố
    kind: external
  - id: estimate
    label: Tầng ước lượng
    sub: Lệch 1,6 lần giữa ÷4 và ÷2,5
    kind: optional
    focal: true
  - id: decision
    label: Tầng quyết định
    sub: Ngân sách an toàn bạn dám tin
    focal: true
edges:
  - [contract, estimate]
  - [estimate, decision]
```

### Tầng hợp đồng: cửa sổ là con số do nhà cung cấp ấn định

DeepSeek công bố cửa sổ 1M và đầu ra tối đa 384K cho `deepseek-flash` và `deepseek-v4-pro`. Anthropic để Haiku 4.5 ở 200K trong khi các mô hình mới hơn ở 1M. Đó là những con số khác nhau cho cùng một khái niệm.

Hệ quả vận hành: đổi model là đổi ngân sách. Một prompt vừa vặn với model này có thể tràn cửa sổ model kia, và bạn không nhận được bất kỳ cảnh báo nào ở tầng kiểu dữ liệu (type level).

### Tầng ước lượng: bạn không đo được cái bạn cần đo

Muốn biết con số token chính xác thì bắt buộc phải chạy đúng tokenizer của model bạn gọi. Không có đường tắt nào khác.

Và ngay cả khi có tokenizer, con số đó vẫn chỉ là ước lượng. Tài liệu Anthropic ghi rõ *"The token count is an estimate"*. Họ cảnh báo thêm rằng tokenizer thế hệ mới sinh ra *"approximately 30 percent more tokens than on earlier models"* cho cùng một đoạn văn bản. Tăng ba mươi phần trăm dung lượng chỉ vì đổi tokenizer.

### Tầng quyết định: bạn phải chọn tin ai

Đây là chỗ phát sinh chi phí lớn nhất. Nếu bạn ước lượng quá thấp, request vượt ngưỡng cửa sổ và bị từ chối ngay lập tức. Nếu bạn ước lượng quá cao, bạn cắt ngắn ngữ cảnh quá sớm và làm mất dữ liệu model đang cần.

Hai rủi ro đó không hề đối xứng: ước lượng thấp thì hệ thống sập to tiếng và dễ bắt lỗi; ước lượng cao thì hỏng âm thầm, và bạn chỉ nhận ra khi model trả lời sai vì thiếu dữ kiện.

## Việc làm được ngay

<Steps>
<Step title="Chốt một con số cho mỗi model">Ghi ra cửa sổ thật của model bạn dùng, lấy từ tài liệu nhà cung cấp, không lấy từ trí nhớ. Đổi model thì cập nhật con số đó.</Step>
<Step title="Đo, đừng đoán">Chạy `count_tokens` hoặc tokenizer của nhà cung cấp trên chính payload bạn gửi. OpenAI, Anthropic và Gemini đều có endpoint đếm token.</Step>
<Step title="Chừa biên cho phần bạn không đếm được">Khai báo tool và system prompt cũng chiếm chỗ. Đo cả gói, không đo mỗi câu hỏi của người dùng.</Step>
</Steps>

Bước thứ ba là bước hay bị bỏ. Một request thật không phải một câu hỏi — nó là system prompt cộng khai báo tool cộng lịch sử cộng kết quả tool. Trong phép đo của tôi, phần đó là 1.289 ký tự, và nó không phải phần người dùng gõ.

<Callout type="warn" title="Đừng để hệ thống tự cắt mà không ghi lại">
Khi vượt cửa sổ, có hai kiểu hỏng. Request bị từ chối thì ồn ào nhưng trung thực. Hệ thống tự cắt bớt rồi gửi thì im lặng — và model nhận một lịch sử đã bị tỉa mà không biết. Kiểu thứ hai tạo ra những câu trả lời sai trông rất tự tin.
</Callout>

## Nguyên tắc

Cửa sổ ngữ cảnh là ngân sách, và mọi thứ bạn nhét vào đều đang chiếm chỗ của thứ khác.

<Quote cite="tusgino">Cửa sổ ngữ cảnh không cho model thêm trí nhớ. Nó cho bạn thêm chỗ để tự quyết định cái gì đáng nhớ.</Quote>

<KeyTakeaways>
- Chốt cửa sổ thật của từng model từ tài liệu nhà cung cấp; đổi model là đổi ngân sách, không có cảnh báo nào ở tầng kiểu.
- Chạy endpoint đếm token trên **toàn bộ** payload, không chỉ câu hỏi của người dùng — system prompt và khai báo tool cũng chiếm chỗ.
- Đừng quy byte sang token. Nếu buộc phải ước lượng, ghi rõ bạn dùng tỉ lệ nào, vì khoảng cách giữa hai tỉ lệ đã công bố là 1,6 lần.
- Ưu tiên ước lượng cao hơn ước lượng thấp: cắt sớm thì mất ngữ cảnh, nhưng vượt cửa sổ thì mất cả request.
- Nếu hệ thống tự cắt ngữ cảnh, ghi lại sự kiện đó. Một lần cắt không được log là một lần model trả lời sai mà không ai truy được vì sao.
</KeyTakeaways>
