Ngay sau khi bạn nhấn Enter, một chatbot AI không trả lời ngay lập tức. Trong khoảng dừng từ 1 đến 2 giây đầu tiên, hệ thống chạy cả một chuỗi bước chuẩn bị mà bạn không thấy gì ngoài con trỏ nhấp nháy.
Khoảng lặng đó là lúc hệ thống lắp ghép tài liệu ngữ cảnh, chạy kiểm duyệt an toàn, rồi bước vào giai đoạn "prefill" để tính toán toàn bộ dữ liệu đầu vào. Chỉ khi giai đoạn này kết thúc, chữ đầu tiên mới xuất hiện thông qua quá trình "decode", tức là giải mã tuần tự từng token một.
Nhìn từ phía màn hình thì có vẻ chưa có gì xảy ra, nhưng khoảng lặng này lại đúng là lúc bộ xử lý đồ họa (GPU) hoạt động với cường độ cao nhất. Hệ thống phải dựng lại toàn bộ lịch sử hội thoại, vì các mô hình ngôn ngữ lớn (LLM) về bản chất là không có trạng thái (stateless). Những gì bạn vừa gõ chỉ là một mẩu nhỏ trong tài liệu tổng thể được gửi đến mô hình, và mô hình phải đọc hết tài liệu đó trước khi kịp bắt đầu hiển thị phản hồi.

Mô hình không bao giờ nhận được câu bạn vừa gõ
Câu bạn vừa gõ không đi thẳng đến mô hình. Trước khi gửi đi, lớp ứng dụng thực hiện một quá trình gọi là context engineering để lắp ghép một tài liệu hoàn chỉnh, và câu của bạn chỉ là một dòng trong đó. Tài liệu này gồm: chỉ dẫn hệ thống (system prompt), định nghĩa các công cụ (tool definitions), dữ liệu truy xuất từ bộ nhớ dài hạn hoặc tài liệu bên ngoài (RAG), và toàn bộ lịch sử hội thoại.

Vì các mô hình LLM hiện nay hoạt động theo cơ chế stateless, chúng không có khả năng tự nhớ những gì đã nói ở lượt trước. Cảm giác "nó nhớ mình" thực chất là một ảo giác do lớp phần mềm dựng lên và duy trì, bằng cách chép lại rồi gửi toàn bộ lịch sử từ đầu ở mỗi lượt chat mới. Không có bộ nhớ nào nằm ở phía mô hình cả, chỉ có một tài liệu mỗi lúc một dài.
Kiểu chép lại đó làm lượng token phình lên rất nhanh. Một lượt chat đầu tiên có thể tốn 1.100 token, nhưng đến lượt thứ 20, con số vọt lên khoảng 4.900 token. Giá token đầu vào thường rẻ hơn token đầu ra, nhưng vì khối lượng đầu vào tích tụ và lặp lại qua mỗi vòng đời hội thoại, chi phí đầu vào thường chiếm tỷ trọng lớn nhất trong tổng chi phí vận hành. Nói cách khác, phần tiền lớn nhất bạn trả là để mô hình đọc lại những gì nó đã đọc.
Lớp kiểm duyệt chạy trước khi mô hình được gọi
Trước khi tài liệu ngữ cảnh đến được mô hình chính, nó phải đi qua một tầng bảo vệ (safety layer). Đây là một mô hình phân loại riêng biệt và nhỏ hơn, ngồi đọc yêu cầu của bạn để quyết định nó có vi phạm chính sách hay không, rồi cho đi tiếp hoặc chặn lại ngay.

Giữ lớp kiểm duyệt này tốn kém hơn nhiều người tưởng. Các bộ phân loại thế hệ đầu làm tăng khoảng 23,7% tài nguyên tính toán, và kéo theo tỷ lệ từ chối sai (những lời nhắc vô hại bị chặn nhầm) tăng thêm khoảng 0,38 điểm phần trăm. Một mức phụ trội như vậy chỉ để canh cổng là cái giá không nhà cung cấp nào muốn trả mãi.
Nên các hệ thống hiện đại chuyển sang cơ chế cascade: một bộ lọc rẻ tiền quét nhanh toàn bộ lưu lượng, và chỉ phần bị gắn cờ nghi vấn mới được chuyển tiếp tới bộ phân loại đắt tiền. Cách này kéo chi phí phát sinh xuống còn khoảng 1% và tỷ lệ từ chối sai thêm chỉ còn khoảng 0,05 điểm phần trăm. Rẻ đến vậy nhưng nó vẫn là một chặng bạn phải chờ, và là một phần nguyên nhân tạo ra khoảng lặng trước khi có phản hồi đầu tiên.
Vì sao cùng một đoạn văn lại tốn số token khác nhau
Mô hình ngôn ngữ không làm việc trực tiếp với ký tự, mà thông qua các đơn vị gọi là token, được tạo ra bởi cơ chế chia nhỏ văn bản (tokenization), mà phổ biến nhất là Byte Pair Encoding (BPE). Hệ thống xử lý dựa trên các byte dữ liệu thô, để mọi hệ chữ viết đều có thể được biểu diễn. Với tiếng Anh, một token trung bình tương đương khoảng ba phần tư của một từ.

Theo nghiên cứu "Language Model Tokenizers Introduce Unfairness Between Languages", cùng một nội dung được dịch sang các ngôn ngữ khác nhau có thể chênh lệch số token tới 15 lần. Cần nói rõ: đây là kết quả đo trên những bộ tokenizer cụ thể tại thời điểm nghiên cứu, không phải con số phổ quát cho mọi mô hình hiện nay.
Riêng con số cho tiếng Việt thì cụ thể hơn nhiều. Vẫn trên bộ dữ liệu song ngữ của nghiên cứu đó, cùng một khối nội dung, tiếng Việt tốn khoảng 2,45 lần số token so với tiếng Anh khi đo bằng cl100k_base, tức bộ tokenizer của GPT-3.5 và GPT-4. Cần gắn kèm tên bộ tokenizer thì con số mới có nghĩa, vì với những bộ đời cũ hơn khoảng cách còn rộng hơn nhiều, chẳng hạn tokenizer của GPT-2 lên tới 4,54 lần.
Hệ quả kỹ thuật thì vẫn còn nguyên. Ngôn ngữ nào bị cắt vụn thành nhiều token hơn thì người dùng ngôn ngữ đó có cửa sổ ngữ cảnh hẹp hơn trên cùng một lượng thông tin, độ trễ cao hơn, và chi phí vận hành đắt hơn. Tôi thấy đây là bất công dễ bị bỏ qua nhất trong cả chuỗi, vì nó xuất hiện ngay ở khâu tokenization, trước cả khi mô hình được gọi.
Bạn đang dùng chung GPU với người lạ
Hiệu năng của GPU thường bị giới hạn bởi băng thông bộ nhớ (memory-IO bound) hơn là bởi năng lực tính toán. Để tối ưu chi phí, các nhà cung cấp dùng cơ chế batching: nạp tham số mô hình một lần rồi dùng chung cho yêu cầu của nhiều người dùng khác nhau trong cùng một thời điểm. Câu hỏi của bạn đi chung chuyến với câu hỏi của vài chục người bạn không quen.

Cơ chế batching tĩnh phải chờ câu trả lời dài nhất trong nhóm kết thúc mới giải phóng chỗ, nghĩa là một câu hỏi ngắn vẫn phải ngồi đợi người bên cạnh viết xong bài luận. Continuous batching, tức là lập lịch ở mức từng vòng lặp, cho phép một yêu cầu mới chiếm ngay chỗ của yêu cầu vừa xong, và trong đo đạc của Anyscale, cách này cho thông lượng cao hơn tới 23 lần so với batching tĩnh.
Chính việc dùng chung này sinh ra hiện tượng nondeterminism (tính không xác định). Ngay cả khi đặt nhiệt độ (temperature) bằng 0, mức lẽ ra phải cho ra kết quả y hệt sau mỗi lần chạy, mô hình vẫn có thể trả về kết quả khác nhau giữa các lần chạy trên server chung. Nguyên nhân nằm ở tính không kết hợp của số dấu phẩy động: (a + b) + c không phải lúc nào cũng bằng a + (b + c) khi máy tính làm tròn ở độ chính xác hữu hạn.
Khi kích thước lô thay đổi, tùy vào số "người lạ" đang dùng chung GPU với bạn, thứ tự thực hiện các phép cộng trong phép toán ma trận cũng thay đổi, tạo ra những sai số làm tròn siêu nhỏ. Các sai lệch này tích tụ qua hàng trăm lớp của mô hình, và đến một điểm nào đó là đủ để mô hình chọn một token khác hẳn. Cùng một câu hỏi, cùng một cấu hình, mà câu trả lời lệch đi chỉ vì lúc đó server đông hay vắng.
Prefill và decode: khoảng lặng và dòng chữ chạy
Quá trình sinh phản hồi chia thành hai giai đoạn có đặc tính kỹ thuật trái ngược nhau.

Prefill đọc toàn bộ prompt đầu vào một lượt theo kiểu song song, tận dụng tối đa sức mạnh tính toán của GPU. Đây chính là giai đoạn tạo ra khoảng lặng ban đầu, đo bằng chỉ số thời gian đến token đầu tiên (Time to First Token, TTFT).
Decode bắt đầu sau khi có token đầu tiên, sinh lần lượt từng token tiếp theo. Mỗi token mới phải dựa trên toàn bộ các token đã tạo trước đó, nên giai đoạn này bị giới hạn bởi tốc độ bộ nhớ chứ không phải năng lực tính toán. Nó tạo ra hiệu ứng chữ chạy trên màn hình, đo bằng thời gian trên mỗi token đầu ra (Time Per Output Token, TPOT).
Hội thoại càng dài thì prefill càng tốn thời gian, nên khoảng lặng ban đầu càng lâu, trong khi tốc độ chữ chạy ở giai đoạn decode hầu như không đổi.
KV cache quyết định một máy phục vụ được bao nhiêu người
Để khỏi phải tính lại từ đầu cho mỗi token mới ở giai đoạn decode, hệ thống lưu kết quả trung gian của các token trước đó vào KV cache. Nhưng KV cache ngốn rất nhiều bộ nhớ GPU. Theo nhóm vLLM, một mô hình 13B cần khoảng 800 KB bộ nhớ cho mỗi token. Với một GPU A100 40 GB, sau khi nạp khoảng 26 GB tham số mô hình, phần còn lại chỉ vừa đủ chứa khoảng 15.000 token cho tất cả mọi người đang dùng máy đó.

Các hệ thống cũ lãng phí 60-80% lượng bộ nhớ đó, vì chúng cấp phát một khối liên tục theo độ dài chuỗi tối đa ngay từ đầu, kể cả khi người dùng mới gõ vài từ. Giải pháp PagedAttention, lấy cảm hứng từ cơ chế phân trang bộ nhớ ảo của hệ điều hành, chia KV cache thành các trang nhỏ không cần nằm liên tiếp nhau và chỉ cấp phát khi cần. Kỹ thuật này kéo mức lãng phí xuống dưới 4%, nghĩa là cùng một phần cứng phục vụ được nhiều người hơn hẳn.
Streaming: một chữ đã hiện thì không rút lại được
Cơ chế streaming đẩy token ra màn hình ngay khi nó vừa được sinh, thay vì chờ câu trả lời hoàn chỉnh. Bạn cảm thấy nhanh hơn hẳn, dù tổng thời gian không đổi.
Đổi lại là một rủi ro kỹ thuật khó chịu: nếu kiểm duyệt đầu ra chỉ chạy sau khi phản hồi hoàn tất, thì từ ngữ vi phạm đã hiện lên rồi và không rút lại được. Còn nếu giữ lại toàn bộ phản hồi để kiểm tra xong mới hiển thị, thì lợi ích của streaming mất sạch.
Nên các hệ thống phải kiểm duyệt song song ngay trong lúc stream, hoặc dùng kỹ thuật đọc trạng thái nội tại (internal state) của mô hình để dự đoán vi phạm trước cả khi token được giải mã thành chữ.
Gọi công cụ: toàn bộ quy trình chạy lại từ đầu
Mô hình AI không tự truy cập internet hay cơ sở dữ liệu. Nó chỉ sinh ra văn bản yêu cầu gọi công cụ (tool call); lớp ứng dụng bên ngoài đọc yêu cầu đó, thực thi công cụ, rồi đưa kết quả trở lại cho mô hình như một đầu vào mới.

Điểm mấu chốt: mỗi lần một công cụ trả kết quả, kết quả đó được ghép vào ngữ cảnh và toàn bộ quy trình chạy lại từ đầu (lắp ghép tài liệu, kiểm duyệt, tokenization, prefill) cho một phiên suy luận (inference) mới. Đó là lý do một câu trả lời có dùng công cụ luôn lâu hơn thấy rõ.
Chi phí vì thế cộng dồn rất nhanh. Nếu một yêu cầu phức tạp cần gọi công cụ 20 lần với khối chỉ dẫn ban đầu là 2.000 token, riêng phần chỉ dẫn đó đã bị tính tiền 20 lần, thành 40.000 token đầu vào, chưa kể lịch sử hội thoại mỗi vòng lại dài thêm.
Hai giây đó thực sự đi đâu?
Khoảng dừng 1-2 giây sau khi bạn nhấn Enter là tổng hòa của: độ trễ mạng, thời gian lắp ghép hàng ngàn token ngữ cảnh, lớp kiểm duyệt cascade, thời gian xếp hàng chờ một chỗ trong lô, và cuối cùng là khối lượng tính toán khổng lồ của giai đoạn prefill. Phần lớn nhất, trong các hội thoại đã dài, gần như luôn là prefill.

Từ đó rút ra một mẹo tối ưu thực dụng: giữ các phần nội dung ổn định (system prompt, các ví dụ cố định) ở trên cùng của prompt, và đặt nội dung thay đổi ở cuối. Cách sắp xếp này giúp cơ chế prefix caching giữ lại các tính toán tĩnh trong bộ nhớ giữa các lượt, cắt bớt đáng kể thời gian prefill và tiền vận hành.
Tài liệu tham khảo
- What Happens Inside an AI Chatbot Between Enter and the First Word? — ByteByteGo
- Effective context engineering for AI agents — Anthropic
- Next-generation Constitutional Classifiers — Anthropic
- Language Model Tokenizers Introduce Unfairness Between Languages
- Achieve 23x LLM Inference Throughput & Reduce p50 Latency — Anyscale
- Defeating Nondeterminism in LLM Inference — Thinking Machines Lab
- Efficient Memory Management for Large Language Model Serving with PagedAttention
- Mastering LLM Techniques: Inference Optimization — NVIDIA
- Prompt caching — Claude Platform Docs