Hai người thợ trong cỗ máy AI

Mỗi lần AI trả lời, có hai người thợ tính cách trái ngược thay nhau làm việc — và gần như mọi thủ thuật tăng tốc inference đều sinh ra từ mâu thuẫn giữa họ. Một chuyến đi vào bên trong cỗ máy suy luận: prefill, decode, KV cache, batching, quantization, và vì sao đến lúc phải tự nuôi server.

$ git log --oneline --stat
✍️ author: duthaho 📅 date: 18/06/2026 ⏱️ read: ...
ai-inference-engineering.md readonly

Bạn gõ một câu hỏi, nhấn Enter. Khoảng một giây trôi qua. Rồi con chữ bắt đầu hiện ra — không phải cả đoạn cùng lúc, mà nhỏ giọt, từng chữ một, như có ai đó đang gõ phía sau tấm kính. Cái khoảng lặng một giây đó, và cái nhịp gõ nhỏ giọt sau đó, không phải là một quá trình. Chúng là hai — hai người thợ hoàn toàn khác nhau, đụng phải hai bức tường khác nhau. Hiểu được vì sao họ khác nhau là hiểu được gần như toàn bộ ngành kỹ thuật suy luận (inference).

Inference engineering — tạm dịch là kỹ thuật suy luận — là nghề lo cho các mô hình đã huấn luyện xong chạy thật hiệu quả trong production. Nó trải dài từ code GPU tầng thấp, qua các framework phục vụ mô hình, tới hạ tầng cloud buộc tất cả lại với nhau; và người làm nghề lúc nào cũng phải cân cùng lúc bốn thứ kéo nhau: độ trễ, sản lượng, chi phí, chất lượng. Ba năm trước, đây gần như là đặc sản riêng của vài phòng lab tiên phong. Giờ thì bất kỳ công ty nào chạy AI nghiêm túc đều phải đầu tư vào nó.

Hãy bước vào trong xưởng.

Cái nhà kho không ai dọn đi được

Giữa xưởng là một nhà kho khổng lồ: đó là trọng số của mô hình (model weights) — hàng chục, có khi hàng trăm gigabyte, nằm lì trong bộ nhớ GPU. Không ai bê nó đi chỗ khác giữa chừng được. Mọi việc trong xưởng, suy cho cùng, là chuyện đi lại với cái nhà kho này: ai phải chạm vào nó, chạm bao nhiêu lần, và mỗi lần lôi ra bao nhiêu. Giữ trong đầu hình ảnh đó, vì hai người thợ sắp xuất hiện đối xử với nhà kho theo hai cách trái ngược.

Người thợ thứ nhất: kẻ đọc ngấu nghiến

Khi câu hỏi của bạn vừa tới, người thợ thứ nhất chộp lấy. Anh ta đọc toàn bộ đề bài cùng một lúc — mọi token trong prompt, đẩy qua mọi tầng của mô hình trong một loạt phép tính dồn dập. Đây là pha prefill.

Token là đơn vị nhỏ nhất mà mô hình làm việc — đại khái một từ hoặc một mẩu từ. Chữ "inference" có thể gói gọn trong một token, còn "engineering" có khi tách làm hai. Mọi con số kiểu "token mỗi giây" đều đếm theo đơn vị này.

Anh ta làm xong hai việc. Một: nhả ra token đầu tiên của câu trả lời. Hai — và đây mới là phần ít người để ý — anh ta để lại một chồng ghi chú dày cộp gọi là KV cache: những giá trị attention trung gian đã tính cho từng token đầu vào, ghi sẵn ra để người đến sau khỏi phải tính lại từ đầu.

Người thợ này nghẽn ở đâu? Ở chính đôi tay của mình. Anh phải làm một núi phép tính song song, nên thứ giới hạn anh là sức tính của GPU — các nhân toán học chạy hết công suất. Dân trong nghề gọi đây là compute-bound: nghẽn vì tính. Và thước đo dành cho anh là TTFT (Time To First Token) — cái khoảng lặng một giây bạn chờ trước khi chữ đầu tiên hiện ra chính là anh đang làm việc.

Người thợ thứ hai: kẻ viết và những chuyến chạy kho

Token đầu tiên xong, người thợ thứ hai vào ca. Việc của anh là viết nốt câu trả lời — nhưng mỗi lần chỉ một chữ. Viết một token, rồi token đó thành đầu vào để đoán token kế tiếp, cứ thế lặp lại. Đây là pha decode, và nó tuần tự: chữ sau phải đợi chữ trước.

Nghe thì nhàn. Nhưng đây là chỗ trớ trêu nhất của cả cỗ máy: để viết được một chữ duy nhất, anh ta phải lôi gần như toàn bộ nhà kho trọng số chạy ngang qua bàn mình một lượt. Phép tính để sinh ra một token thì cỏn con; cái tốn kém là chuyến vận chuyển. Viết xong một chữ, anh chạy lại nhà kho lần nữa cho chữ tiếp theo. Lại một chuyến. Lại một chữ.

Cho nên người thợ thứ hai gần như chẳng bao giờ dùng hết sức tính — đôi tay anh ngồi không phần lớn thời gian, trong khi anh mòn chân chạy kho. Anh nghẽn ở băng thông bộ nhớ: tốc độ đọc trọng số từ memory ra. Đây là memory-bandwidth-bound. Thước đo của anh là TPS (Tokens Per Second) — nhịp gõ chữ nhỏ giọt mà bạn thấy trên màn hình. May là chồng ghi chú KV cache người thứ nhất để lại giúp anh khỏi phải đọc lại cả đoạn hội thoại mỗi lượt — không có nó, mỗi chữ sẽ là một cơn ác mộng.

Hai người thợ — hai bức tường ngược nhau PREFILL · "kẻ đọc" đọc cả prompt một lần nghẽn: SỨC TÍNH thước đo: TTFT DECODE · "kẻ viết" viết từng chữ, lặp lại nghẽn: BĂNG THÔNG KHO thước đo: TPS

Hai người thợ đụng hai bức tường khác nhau. Nên một mẹo giúp người này chạy nhanh hơn thường chẳng nhúc nhích gì người kia.

Đây là câu nói cần khắc lên tường xưởng. Gần như mọi kỹ thuật tối ưu inference đều phải khai báo trước: nó cứu kẻ đọc, hay cứu kẻ viết? Nhầm địa chỉ là tiền và công đổ sông đổ biển.

Nói cho gọn, mọi kỹ thuật rơi vào đúng ba cái rổ: thứ tăng tốc kẻ đọc (prefill), thứ tăng tốc kẻ viết (decode), và thứ sắp xếp lại cán cân giữa hai người. Đó cũng là lý do mọi benchmark báo TTFT và TPS thành hai con số tách biệt — đo riêng từng người thợ, vì cải thiện người này không tự động cải thiện người kia.

Sáu mẹo của ông quản đốc

Người trông coi cái xưởng này — tạm gọi là ông quản đốc — có một xâu chìa khóa gồm sáu mẹo. Mỗi mẹo đánh vào một trong hai bức tường, và mỗi mẹo đều có cái giá của nó.

1 · Gộp khách — đông vui nhưng phải xếp hàng

Thay vì phục vụ từng người một, ông quản đốc gộp nhiều yêu cầu (batching) lại để mỗi chuyến chạy kho của kẻ viết phục vụ được cả chục khách cùng lúc. Trọng số đã lôi ra rồi thì cho nhiều người dùng ké luôn. Tổng sản lượng (throughput) tăng vọt vì sức tính của GPU được dùng hết thay vì ngồi không giữa các request. Cái giá: từng khách lẻ phải chờ lâu hơn một chút vì bị xếp chung. Đây là sự đánh đổi gốc rễ, lặp lại ở mọi mẹo phía sau — nhanh cho cả đám thường nghĩa là chậm hơn cho một người.

Mỗi loại sản phẩm chọn một điểm khác nhau trên cái thang đó: chatbot cho người dùng cuối nghiêng về độ trễ thấp, còn pipeline xử lý hàng loạt thì nghiêng hẳn về sản lượng tối đa.

2 · Nhớ phần mở đầu — đừng đọc lại cái đã đọc

Nếu hai lá thư mở đầu giống hệt nhau, hà cớ gì đọc lại từ đầu? Prefix caching giữ lại KV cache của đoạn đầu dùng chung, tái dùng cho tới đúng token đầu tiên khác đi. Hệ quả rất thực dụng cho người viết prompt: đặt phần cố định lên trước, phần thay đổi xuống cuối. Một system prompt dài giống nhau ở mọi request là bạn của cache; nhét cái biến thiên (tên user, timestamp) lên đầu là tự tay phá cache, trả tiền đọc lại từ con số không mỗi lần. Đây cũng chính là lý do các nhà cung cấp API tính phí rẻ hơn hẳn cho phần input đã nằm trong cache.

3 · Bóp nhỏ trọng số — nhưng đừng đụng vào xương

Quantization nén trọng số từ 16-bit xuống 8-bit, thậm chí 4-bit. Một mũi tên trúng cả hai người thợ: kẻ đọc tính nhanh hơn vì phép tính độ chính xác thấp rẻ hơn, kẻ viết chạy kho nhẹ hơn vì có ít byte phải vác. Mức lợi điển hình tầm 30–50%.

Nhưng không phải chỗ nào cũng bóp được như nhau. Thứ tự nhạy cảm, từ dễ hỏng nhất tới chịu đòn tốt nhất: tầng attention → KV cache → activations → trọng số tuyến tính. Phần lớn hệ thống production giữ nguyên attention ở độ chính xác đầy đủ, vì sai số ở đó tích lũy qua từng token — bóp nhầm chỗ này thì cả câu trả lời trôi dần khỏi quỹ đạo. Bóp mỡ thì được, đừng đụng vào xương.

4 · Thằng học việc đoán trước — thầy chỉ chấm

Đây là mẹo tôi thấy thanh lịch nhất. Cho một mô hình nháp nhỏ và nhanh chạy trước, phỏng đoán vài token kế tiếp. Rồi mô hình chính — thay vì è cổ sinh từng token từ đầu — chỉ cần kiểm cả loạt phỏng đoán đó trong một lượt chạy duy nhất. Mẹo này khai thác một bất đối xứng: sinh một token từ con số không thì đắt, nhưng xác nhận xem một token có khớp với cái mô hình chính sẽ chọn không thì rẻ hơn nhiều. Như thầy giáo: bắt thầy tự viết bài thì lâu, đưa bài học trò làm sẵn cho thầy gạch đúng-sai thì nhanh. (Hoặc như chơi Sudoku: giải thì toát mồ hôi, nhưng kiểm một bảng đã điền thì liếc cái là xong.) Mô hình chính nhận đúng những token khớp với dự đoán của mình, gạt phần còn lại — kết quả là nhiều token bật ra sau một lượt chạy, chỗ lẽ ra chỉ có đúng một.

Tên gọi: speculative decoding. Nó kéo TPS lên mà không đụng tới TTFT — tức cứu kẻ viết, kệ kẻ đọc. Lưu ý: nó chỉ lời khi GPU còn rảnh tay. Lúc máy đã chật khách (batch lớn, GPU bão hòa), người ta tắt nó đi — vì khi ấy đôi tay tính toán đã bận, chẳng còn chỗ trống để kiểm bài hộ miễn phí nữa.

5 · Một xưởng không chứa nổi — chia ra nhiều phòng

Có những mô hình to tới mức một GPU không ôm xuể — hoặc nhét vừa nhưng chạy trên một GPU thì độ trễ cao quá. Khi đó phải chia mô hình ra nhiều GPU (parallelism), và có hai kiểu chia thống trị thế giới mô hình mở:

  • Tensor parallelism — xẻ từng tầng của mô hình ra nhiều GPU, mỗi con giữ một mảnh và cùng gánh phần việc của tầng đó. Sau mỗi tầng phải ghép kết quả lại, nên đòi đường truyền cực nhanh giữa các GPU — kiểu NVLink của NVIDIA. Chia kiểu này "đau" về giao tiếp, nhưng là lựa chọn mặc định để phục vụ các mô hình dense rất lớn, và hợp khi các GPU nằm sát nhau trong cùng một node.
  • Expert parallelism — dành cho mô hình kiểu mixture-of-experts, nơi mỗi token chỉ đánh thức một nhúm "chuyên gia" chứ không phải toàn bộ. Rải các chuyên gia khác nhau ra các GPU khác nhau, và vì mỗi token chỉ gọi vài chuyên gia nên lượng nói chuyện giữa các GPU ít hơn — hợp cho setup nhiều node, đường truyền giữa các máy hạn chế.

Thực tế người ta phối cả hai: tensor parallelism trong một node (nơi có NVLink), expert parallelism giữa các node.

6 · Tách hẳn hai người thợ ra hai phòng

Mẹo cuối là mẹo triệt để nhất, và nó đi thẳng từ cái insight ban đầu. Nếu kẻ đọc nghẽn vì tính còn kẻ viết nghẽn vì băng thông — sao lại bắt cả hai dùng chung một loại bàn ghế? Disaggregation tách prefill và decode ra hai cụm GPU riêng, mỗi cụm chọn phần cứng tối ưu đúng cho bức tường của mình, và chuyển KV cache qua lại bằng đường truyền tốc độ cao.

Disaggregation — mỗi người thợ một phòng riêng [PHÒNG ĐỌC] prefill ──(gửi tập ghi chú KV)──▶ [PHÒNG VIẾT] decode GPU mạnh về sức tính GPU mạnh về băng thông ▲ └── request ngắn / đã cache ──(bỏ qua, chạy thẳng phòng viết)──┘

Quy trình ba bước: cụm prefill sinh token đầu + KV cache → đẩy cache sang cụm decode để viết nốt → và một nước cờ khôn: những request ngắn hoặc đã có sẵn cache thì bỏ qua chặng bàn giao, chạy thẳng trên phần cứng decode. Hai cụm cũng được co giãn độc lập: hôm nay nhiều prompt dài thì thêm máy cho phòng đọc, không phải đụng tới phòng viết.

Đây là kỹ thuật "kiến trúc" nhất trong cả sáu — nó coi đọc và viết là hai dịch vụ riêng, vận hành riêng, có cần gạt riêng để co giãn. Với những hệ thống chạy inference quy mô lớn, một khi đã hiểu rõ hình dạng lưu lượng của mình, bước này gần như là bắt buộc.

Vậy có nên tự dựng xưởng riêng?

Tới đây câu hỏi thực tế hiện ra: bạn có cần biết hết những thứ này không, hay cứ thuê xưởng của người khác (gọi API) là xong? Câu trả lời thẳng thắn từ bài gốc: giai đoạn đầu, cứ thuê. Tối ưu có ý nghĩa chỉ khi có ràng buộc thật để mà tối ưu, mà sản phẩm non trẻ thì còn mơ hồ về lưu lượng, về yêu cầu độ trễ, về bài toán đơn vị kinh tế. Tự dựng xưởng lúc đó là tối ưu cho một tương lai bạn còn chưa biết mặt.

Ba tín hiệu cho biết đã đến lúc tự nuôi server:

Ba lằn ranh build-vs-buy

Chi phí API leo lên thành một dòng đáng kể trong bảng kế toán. Độ trễ bạn cần đã vượt khỏi khả năng của API đóng. Độ tin cậy bạn đòi hỏi cao hơn mức SLA mà nhà cung cấp cam kết. Chạm một trong ba, bài toán bắt đầu nghiêng về phía tự dựng.

Ví dụ kinh điển: Cursor, với Composer 2.0, cần độ trễ autocomplete dưới một giây — điều một API đa dụng (vốn phải tối ưu trung bình cho cả nghìn khách hàng) gần như không thể chiều. Chỉ khi tự host và tự làm inference engineering, cái mốc đó mới chạm tới được.

Và sân chơi tự host giờ dễ thở hơn xưa nhiều. Hugging Face đang chứa hơn hai triệu mô hình mở — gấp khoảng 25 lần so với năm năm trước. Những mô hình như DeepSeek V3 đã thu hẹp khoảng cách năng lực với mô hình đóng, cho doanh nghiệp một lựa chọn thật sự. Đổi lại công sức kỹ thuật: chi phí thường giảm chừng 80% khi đã đủ quy mô, và độ sẵn sàng có thể đạt bốn số chín với cụm chuyên dụng, so với hai số chín thường thấy ở API công cộng. Cũng vì thế mà giờ đủ loại công ty dựng inference stack nghiêm túc — từ startup AI-native, sản phẩm có sẵn tích hợp thêm AI, tới cả những ngành vốn rất thận trọng như y tế.

Tóm lại trong một cái khung

Suy luận LLM là hai thao tác với ràng buộc vật lý ngược nhau: prefill nghẽn tính, chạy một lần cho mỗi request; decode nghẽn băng thông, chạy một lần cho mỗi token. Nắm được cái chia đôi đó, sáu mẹo còn lại tự xếp vào chỗ:

  • Gộp khách — đổi độ trễ từng người lấy tổng sản lượng.
  • Nhớ phần đầu — cắt việc của kẻ đọc khi các prompt chung đoạn mở đầu.
  • Bóp trọng số — nén weights, lợi cho cả hai người thợ.
  • Đoán trước — vắt thêm token từ kẻ viết bằng cách tận dụng sức tính đang nhàn rỗi.
  • Chia GPU — trải mô hình quá khổ ra nhiều máy.
  • Tách phòng — cho kẻ đọc và kẻ viết chạy trên phần cứng riêng, mỗi người một bức tường.

Trên tất cả còn là câu hỏi thuê-hay-dựng: cứ thuê API khi còn non trẻ, và chỉ tự dựng khi chi phí, độ trễ, hoặc độ tin cậy chạm lằn ranh.


Lần tới khi bạn gõ một câu và thấy con chữ hiện ra nhỏ giọt phía sau tấm kính, hãy nhớ: đó không phải một cỗ máy đang "nghĩ". Đó là kẻ đọc vừa buông bút sau một giây tính toán cật lực, và kẻ viết đang cắm cúi chạy đi chạy lại cái nhà kho, mỗi vòng đổi lấy đúng một chữ. Mọi con số trên hóa đơn, mọi mili-giây độ trễ, mọi quyết định thuê-hay-dựng, đều quy về một câu hỏi đẹp đẽ và giản dị: lúc này, ta đang đụng phải bức tường nào?

Đọc thêm

comments.md