llm

5 thứ dev hay nhầm về LLM context window

Đang build LLM features và thấy cùng một misconceptions lặp đi lặp lại. Context window không phải bộ nhớ. 128k token không phải 128k từ. Đây là 5 cái sai phổ biến nhất.

Mình vừa debug một bug khá buồn cười: con chatbot của team cứ quên tên user sau mỗi page refresh. Dev viết nó tin rằng model “nhớ” session trước. Không, nó không nhớ gì hết.

Đây là những misconceptions mình thấy lặp đi lặp lại nhất khi review code LLM của anh em.

1. “Context window = bộ nhớ của AI”

Model không lưu trữ gì giữa các API call. Mỗi request là một tờ giấy trắng hoàn toàn.

Context window là cửa sổ model dùng để đọc input trong request hiện tại — không phải RAM, không phải storage. Call xong là mất hết. Request tiếp theo, model không biết request trước tồn tại.

import anthropic

client = anthropic.Anthropic()

# Call đầu tiên
response1 = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=100,
    messages=[{"role": "user", "content": "Tên mình là Huy."}]
)
print(response1.content[0].text)
# Output: "Xin chào Huy! Có thể giúp gì cho bạn?"

# Call thứ hai — model không biết "Huy" là ai
response2 = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=100,
    messages=[{"role": "user", "content": "Tên mình là gì vậy?"}]
)
print(response2.content[0].text)
# Output: "Tôi không biết tên của bạn. Bạn có thể cho tôi biết không?"

Muốn model “nhớ” conversation history, anh em phải tự pass lại messages vào mỗi request:

# Cách đúng: tự manage history
history = []

def chat(user_message: str) -> str:
    history.append({"role": "user", "content": user_message})

    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=500,
        messages=history  # pass toàn bộ history
    )

    assistant_message = response.content[0].text
    history.append({"role": "assistant", "content": assistant_message})
    return assistant_message

chat("Tên mình là Huy.")
chat("Tên mình là gì vậy?")  # Lần này model trả lời đúng

Cái “memory” trong ChatGPT là feature do OpenAI build ở application layer, không phải thứ gì built-in trong model.

2. “128k token = 128k từ”

Token không phải word. Tokenizer tách text thành các chunk nhỏ — có khi là cả từ, có khi chỉ là một vài ký tự, tùy model và tùy ngôn ngữ.

Rough benchmarks cho GPT/Claude tokenizers:

  • Tiếng Anh thông thường: ~0.75 words/token (1 token ≈ 4 chars)
  • Code: thường nhiều token hơn vì symbols
  • Tiếng Việt: tệ hơn đáng kể vì nhiều diacritics
# pip install tiktoken
import tiktoken

enc = tiktoken.get_encoding("cl100k_base")  # encoding của GPT-4, Claude tương tự

text_en = "The quick brown fox jumps over the lazy dog"
text_vi = "Con cáo nâu nhanh nhẹn nhảy qua con chó lười biếng"
code = "def calculate_fibonacci(n: int) -> list[int]:\n    if n <= 0:\n        return []\n    result = [0, 1]\n    for i in range(2, n):\n        result.append(result[-1] + result[-2])\n    return result"

def analyze(label, text):
    tokens = enc.encode(text)
    words = len(text.split())
    print(f"{label}")
    print(f"  Words: {words}, Tokens: {len(tokens)}, Ratio: {len(tokens)/words:.2f} token/word")

analyze("English", text_en)
analyze("Vietnamese", text_vi)
analyze("Python code", code)

Kết quả điển hình:

English
  Words: 9, Tokens: 9, Ratio: 1.00 token/word
Vietnamese
  Words: 9, Tokens: 16, Ratio: 1.78 token/word
Python code
  Words: 18, Tokens: 57, Ratio: 3.17 token/word

Tiếng Việt ngốn gần gấp đôi token so với tiếng Anh cho cùng một nội dung. Code Python còn tệ hơn nhiều.

Hệ quả thực tế: đừng dùng word count để estimate context usage hay cost. Chạy tokenizer thực tế mà đo.

3. “Nhét càng nhiều context vào càng tốt”

Có một vấn đề tên là “lost in the middle” — được document trong nghiên cứu năm 2023 của Stanford. Kết quả: model attend tốt nhất vào đầu và cuối context, performance giảm đáng kể với thông tin ở giữa long context.

# Cấu trúc prompt kém — critical info bị chôn ở giữa
bad_prompt = """
Đây là toàn bộ documentation của hệ thống:
{200 trang docs không liên quan}

QUAN TRỌNG: User chỉ có quyền đọc, không được ghi dữ liệu.

{100 trang docs khác không liên quan}

Hãy generate code để thực hiện yêu cầu sau: {user_request}
"""

# Cấu trúc prompt tốt hơn — critical info ở đầu và cuối
good_prompt = """
CONSTRAINTS (bắt buộc tuân theo):
- User chỉ có quyền đọc, không được ghi dữ liệu.

Context liên quan:
{chỉ lấy docs liên quan, không dump tất}

Task: {user_request}

Nhắc lại: user chỉ có quyền đọc.
"""

Một số nguyên tắc mình đang dùng:

  • Đặt instructions và constraints ở đầu prompt
  • Đặt câu hỏi hoặc task cụ thể ở cuối
  • Context dài thì lặp lại thông tin quan trọng ở cả đầu lẫn cuối
  • Đừng kỳ vọng model tự tìm ra một câu quan trọng nằm giữa trang 50 của 100 trang docs

4. “System prompt không tốn token”

Tốn. System prompt count vào context window và tốn tiền y như bất kỳ token nào khác.

import anthropic

client = anthropic.Anthropic()

system_prompt = """
Bạn là một trợ lý kỹ thuật chuyên về Python và machine learning.
Khi trả lời, hãy:
1. Luôn đưa ra code example cụ thể
2. Giải thích từng bước rõ ràng
3. Mention potential pitfalls nếu có
4. Dùng tiếng Việt nhưng technical terms giữ tiếng Anh
"""

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=500,
    system=system_prompt,
    messages=[{"role": "user", "content": "Gradient descent là gì?"}]
)

usage = response.usage
print(f"Input tokens: {usage.input_tokens}")
print(f"Output tokens: {usage.output_tokens}")
# System prompt tokens được tính vào input_tokens

500 tokens system prompt × 10,000 calls/ngày = 5 triệu tokens mỗi ngày chỉ để trả cho cái system prompt lặp đi lặp lại.

Fix thực tế là prompt caching. Anthropic và OpenAI đều hỗ trợ cache system prompt để chỉ tính phí lần đầu:

# Anthropic prompt caching
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=500,
    system=[
        {
            "type": "text",
            "text": system_prompt,
            "cache_control": {"type": "ephemeral"}  # cache lại
        }
    ],
    messages=[{"role": "user", "content": "Gradient descent là gì?"}]
)

# Lần đầu: cache miss, tính full price
# Lần sau: cache hit, chỉ tính ~10% của input token price
print(f"Cache creation tokens: {response.usage.cache_creation_input_tokens}")
print(f"Cache read tokens: {response.usage.cache_read_input_tokens}")

Không bật caching thì mỗi call anh em đang trả full price cho cùng một system prompt đó.

5. “Context window đủ lớn thì không cần RAG”

Kỹ thuật mà nói thì nhét hết codebase vào context là được. Production thì đừng.

Ba lý do:

Latency: Context càng dài thì time-to-first-token càng lâu. Nhét 200k tokens vào mỗi request khi user hỏi một câu đơn giản là waste.

Cost: Token usage tỷ lệ thuận với chi phí. Large context + high traffic = hóa đơn to bất ngờ.

Precision: RAG retrieve đúng đoạn liên quan, giúp model focus. Dump toàn bộ docs vào thì rơi đúng vào cái “lost in the middle” ở mục 3.

# Ví dụ so sánh cost estimate (không phải số chính xác)
import anthropic

PRICE_PER_1M_INPUT = 3.0  # USD, Claude Sonnet

# Approach 1: Dump toàn bộ docs
full_docs_tokens = 150_000  # 150k tokens docs
user_question_tokens = 50   # câu hỏi ngắn
total_without_rag = full_docs_tokens + user_question_tokens

# Approach 2: RAG retrieve top-k relevant chunks
retrieved_chunks_tokens = 3_000  # chỉ lấy đúng phần liên quan
total_with_rag = retrieved_chunks_tokens + user_question_tokens

cost_without_rag = (total_without_rag / 1_000_000) * PRICE_PER_1M_INPUT
cost_with_rag = (total_with_rag / 1_000_000) * PRICE_PER_1M_INPUT

print(f"Without RAG: {total_without_rag:,} tokens = ${cost_without_rag:.4f}/request")
print(f"With RAG:    {total_with_rag:,} tokens = ${cost_with_rag:.4f}/request")
print(f"At 10k requests/day: ${(cost_without_rag - cost_with_rag) * 10000:.0f} saved/day")

# Without RAG: 150,050 tokens = $0.0005/request
# With RAG:      3,050 tokens = $0.0000/request
# At 10k requests/day: $4 saved/day → $1,400/year

Có exception: codebase nhỏ, one-off analysis, hoặc khi retrieval precision quá kém. Nhưng default nên là RAG trước, rồi mới scale context khi có evidence thực sự cần.

Kết

  • Luôn pass history vào messages array — model không tự nhớ gì giữa các call.
  • Dùng tokenizer thực tế để estimate cost — đặc biệt với Vietnamese text và code, word count không reliable.
  • Critical info vào đầu hoặc cuối prompt, không chôn ở giữa context dài. Và bật prompt caching cho system prompt nếu call volume cao.