Thủ thuật
Tại sao bộ nhớ LLM lại đắt đỏ và cách tối ưu hóa hiệu quả
(giờ Việt Nam)
Tóm tắt AI
Chi phí vận hành LLM tăng vọt khi xử lý ngữ cảnh dài do bộ nhớ đệm KV tăng tuyến tính. Bài viết phân tích nguyên nhân và các kỹ thuật tối ưu như GQA để giảm tải dung lượng GPU.
Bản dịch AI


Trực ca (on-call) không nên giống như việc phải liên tục đi chữa cháy. Hướng dẫn này từ Datadog sẽ phân tích cách các đội ngũ SRE hiệu suất cao giảm thiểu tình trạng mệt mỏi vì cảnh báo (alert fatigue), tối ưu hóa quy trình phản hồi sự cố và thiết kế các ca trực không gây kiệt sức cho kỹ sư. Bạn sẽ học được cách:
Cắt giảm nhiễu cảnh báo bằng cách gắn các tín hiệu với tác động thực tế đến người dùng
Cải thiện phản hồi với vai trò rõ ràng và các lộ trình leo thang (escalation paths) thông minh hơn
Biến các sự cố thành vòng lặp phản hồi để cải thiện độ tin cậy của hệ thống
Nhận hướng dẫn
Tại sao việc gửi một prompt dài 100.000 từ cho một mô hình lại tốn kém hơn nhiều so với một prompt ngắn, ngay cả khi mô hình và phần cứng vẫn giữ nguyên?
Một phần quan trọng của câu trả lời nằm ở một khối bộ nhớ làm việc được gọi là KV cache.
Bộ nhớ này được xây dựng trong khi mô hình tạo phản hồi. Nó tách biệt với kiến thức được lưu trữ trong các trọng số (weights) của mô hình, và nó chứa các vector key và value được tính toán cho mỗi token của đầu vào. Bộ nhớ đệm này tăng dần theo từng token, và trong một ngữ cảnh dài, nó có thể chiếm dung lượng đáng kể trên GPU.
Ví dụ, đối với một mô hình 70 tỷ tham số với ngữ cảnh 128.000 token, dung lượng này lên tới khoảng 40 gigabyte, một lượng bộ nhớ GPU đáng kể và sẽ tăng lên theo mỗi người dùng bạn thêm vào.

Biểu đồ trên đặt ra một câu hỏi hiển nhiên: tại sao bộ nhớ đệm lại tồn tại và tại sao nó lại tăng trưởng theo cách đó? Trong bài viết này, chúng ta sẽ tìm hiểu cách các LLM sử dụng bộ nhớ, tại sao nó trở nên đắt đỏ và cách khắc phục.
Tuyên bố miễn trừ trách nhiệm: Bài viết này dựa trên các chi tiết được chia sẻ công khai từ nhiều nguồn khác nhau. Tài liệu tham khảo ở cuối bài. Vui lòng bình luận nếu bạn phát hiện bất kỳ điểm nào không chính xác.
Hãy bắt đầu với công việc mà một mô hình thực hiện để tạo ra một token.
Để chọn từ tiếp theo, nó thực hiện một bước chú ý (attention step), nơi token mới nhất tự so sánh với mọi token đã xuất hiện trước đó. Sự so sánh này sử dụng hai vector cho mỗi token trước đó, là key và value, đơn giản là các bản tóm tắt số học mà mô hình tính toán cho token đó bên trong mỗi lớp (layer). Một mô hình nếu phải tính toán lại key và value cho mọi token trước đó ở mỗi bước sẽ thấy khối lượng công việc trên mỗi token tăng lên khi đầu vào dài ra. Việc lặp lại như vậy là hoàn toàn lãng phí, vì các key và value đó không thay đổi sau khi một token đã được xử lý.
KV cache loại bỏ sự lãng phí này bằng cách lưu trữ các vector key và value đó ngay lần đầu tiên chúng được tính toán. Ở bước tiếp theo, mô hình chỉ tính toán key và value cho token mới và đọc phần còn lại trực tiếp từ bộ nhớ đệm.

Nhìn chung, đây là một giải pháp tuyệt vời. Tuy nhiên, việc lưu bộ nhớ đệm giải quyết được vấn đề tốc độ nhưng lại tạo ra một vấn đề mới. Bộ nhớ đệm giờ đây phải được đọc ở mỗi bước, và đây chính là nguồn gốc thực sự của chi phí ngày càng tăng.
Một chi tiết đáng lưu ý ở đây là bộ nhớ đệm lưu trữ các vector thay vì văn bản gốc. Điều này cũng giải thích cho các lỗi hết bộ nhớ (out-of-memory) trông có vẻ khó hiểu khi bản thân mô hình vẫn còn dư dung lượng.
Việc tạo token diễn ra trong hai giai đoạn, và chúng gây áp lực lên phần cứng theo những cách khác nhau:
Giai đoạn đầu là prefill, nơi mô hình đọc toàn bộ đầu vào cùng một lúc. Nó xử lý tất cả các token đầu vào song song và xây dựng các vector key và value của chúng vào bộ nhớ đệm trong một lần truyền duy nhất. Prefill giữ cho các đơn vị tính toán của GPU luôn bận rộn, vì vậy chúng ta gọi đó là compute-bound (giới hạn bởi tính toán), nghĩa là giới hạn nằm ở tốc độ chip thực hiện các phép tính số học.
Giai đoạn thứ hai là decoding, nơi mô hình tạo ra đầu ra từng token một. Mỗi token mới thực hiện một bước chú ý so với toàn bộ bộ nhớ đệm, nghĩa là mô hình đọc mọi key và value đã lưu trữ ra khỏi bộ nhớ GPU trước khi có thể xuất ra token tiếp theo. Nó lặp lại việc đọc đó cho mỗi token được tạo ra. Giới hạn ở đây là tốc độ bộ nhớ đệm có thể di chuyển từ bộ nhớ vào các đơn vị tính toán, vì vậy chúng ta gọi decoding là memory-bound (giới hạn bởi bộ nhớ).

Chi phí của việc tạo văn bản với ngữ cảnh dài không đến từ việc giữ bộ nhớ đệm mà đến từ việc quét qua toàn bộ nó trên mỗi token.
Bộ nhớ đệm lớn hơn đồng nghĩa với việc nhiều dữ liệu đi qua bus bộ nhớ trên mỗi token, điều này thể hiện trực tiếp qua việc tạo văn bản chậm hơn và tốn kém hơn. Nó cũng giải thích tại sao một yêu cầu có thể chạy chậm ngay cả khi nó vẫn nằm gọn trong bộ nhớ.
Nếu chi phí tỉ lệ thuận với lượng bộ nhớ đệm chúng ta đọc mỗi bước, thì kích thước của bộ nhớ đệm là điều tiếp theo cần hiểu rõ.
Kích thước bộ nhớ đệm là tích của một vài con số:
Hệ số hai bao gồm key và value.
Số lượng lớp (layers) vì mỗi lớp giữ bộ nhớ đệm riêng của nó.
Key-value heads thiết lập số lượng bộ dữ liệu mà mỗi lớp lưu trữ.
Head dimension là kích thước của mỗi vector đó.
Bytes per number là không gian mà một giá trị được lưu trữ chiếm dụng.
Tokens là độ dài ngữ cảnh, với mỗi token là một mục nhập.
Batch size là số lượng yêu cầu được phục vụ cùng một lúc.
Tóm lại, kích thước bộ nhớ đệm bằng 2 nhân với số lớp, nhân với số key-value heads, nhân với head dimension, nhân với bytes per number, nhân với số token và nhân với batch size.

Hai điều cần lưu ý ở đây như sau:
Bộ nhớ đệm tăng theo đường thẳng với số lượng token, vì vậy tăng gấp đôi ngữ cảnh sẽ làm tăng gấp đôi bộ nhớ đệm.
Nó tăng theo cùng một cách với batch size, vì vậy việc phục vụ nhiều người dùng cùng lúc cũng làm tăng quy mô bộ nhớ đệm nhanh tương ứng.
Bài viết được AI dịch và tổng hợp tự động từ ByteByteGo. Liên kết bài gốc ở phía trên. Dữ liệu đồng bộ qua API công khai được ghi nguồn tại AI HOT (canonical) ↗. AIHOT.vn luôn dẫn nguồn đầy đủ — nếu bạn thấy điểm cần chỉnh sửa, hãy gửi ý kiến tại trang phản hồi.