Giải quyết sự không đồng bộ trong việc trộn liên tục
Quay lại bài viết
Giải quyết sự không đồng bộ trong việc trộn liên tục
Đã xuất bản
Ngày 14 tháng 5 năm 2026
Cập nhật trên GitHub
Ủng hộ 34
+28
Rémi Ouazan Reboul ror Theo dõi
Pedro Cuenca pcuenq Theo dõi
Aritra Roy Gosthipaty ariG23498 Theo dõi
Trộn đồng bộ
Tạo đồng thời Luồng CUDA là gì?
Luồng mặc định và không mặc định
Quay lại việc trộn liên tục
Thực thi đồng bộ hóa Sự kiện CUDA là gì?
Sử dụng các sự kiện trong Batching liên tục
Đổ đầy chân không Điều kiện cuộc đua
Chuyển tiếp
Vòng lặp không đồng bộ đầy đủ
Nó có thực sự hoạt động không?
Kết luận
TL;DR: chúng tôi giải thích cách phân tách khối lượng công việc CPU và GPU để tăng hiệu suất suy luận đáng kể.
Đây là bài thứ hai trong loạt bài về suy luận LLM hiệu quả. Bài đăng đầu tiên đề cập đến việc phân đợt liên tục từ các nguyên tắc đầu tiên. Nó giới thiệu một số khái niệm mà chúng tôi xây dựng dựa trên: bộ đệm KV, FlashAttention, mặt nạ chú ý, v.v.
Một H200 có giá khoảng 5 USD một giờ đối với Điểm cuối suy luận. Giá đó rẻ trong một giờ, nhưng sử dụng nó trong một ngày và bạn đã phải trả 120 USD. Nếu đúng như vậy, bạn muốn GPU của mình được sử dụng tối đa.
Chúng tôi đã thấy rằng Liên tục hàng loạt cải thiện việc sử dụng GPU bằng cách lên lịch các lô được đóng gói chặt chẽ, do đó không có sự lãng phí tính toán nào khi đệm. Nhưng có một nguồn lãng phí thứ hai mà việc phân mẻ liên tục không giải quyết được: theo mặc định, nó là đồng bộ. Điều này có nghĩa là CPU và GPU thay phiên nhau: trong khi GPU tính toán, CPU sẽ chờ. Và trong khi CPU chuẩn bị đợt tiếp theo thì GPU sẽ chờ. Trong một vòng lặp chạy hàng trăm bước mỗi giây, những khoảng trống nhàn rỗi đó cộng lại và như chúng tôi sẽ trình bày, chúng có thể chiếm gần một phần tư tổng thời gian chạy. Để đảm bảo GPU bận tính toán 100% thời gian, chúng ta cần loại bỏ những khoảng trống đó.
Để đạt được điều này, chúng ta có thể sử dụng tính năng tạo khối không đồng bộ: chúng ta sẽ tách việc chuẩn bị hàng loạt CPU khỏi tính toán hàng loạt GPU, để cả hai có thể chạy song song và chúng ta luôn có một GPU hiệu quả 🔥
Trộn đồng bộ
Đây là cách hoạt động của khối đồng bộ đơn giản:
Khi CPU chuẩn bị một đợt mới, nó sẽ chọn những yêu cầu cần đưa vào, cập nhật bảng bộ đệm KV, loại bỏ các yêu cầu đã hoàn thành trong các lần chạy trước và thừa nhận các yêu cầu mới để lấp đầy không gian trống. Sau khi hoàn tất, nó sẽ chuyển các đầu vào đã chuẩn bị sang GPU. GPU chạy chuyển tiếp và lấy mẫu (tức là chọn) mã thông báo mới cho mỗi yêu cầu. Kết quả sẽ quay trở lại CPU, vì vậy nó biết mỗi yêu cầu vừa tạo ra mã thông báo nào, sau đó toàn bộ chu trình sẽ lặp lại.
Hãy chú ý chú thích màu đỏ ở bên phải: sau khi GPU kết thúc quá trình tính toán, nó sẽ không hoạt động. Lô tiếp theo không thể bắt đầu cho đến khi CPU hoàn thành bước cập nhật: lấy mẫu mã thông báo đầu ra, cập nhật trạng thái yêu cầu, lên lịch lại lô.
Đây là điểm kém hiệu quả cốt lõi của việc phân khối đồng bộ: CPU và GPU thay phiên nhau. Trong khi GPU đang tính toán thì CPU không hoạt động. Trong khi CPU đang cập nhật thì GPU không hoạt động. Trong mọi trường hợp, cả hai đều không làm công việc hữu ích cùng một lúc. Đối với một lượt chuyển tiếp duy nhất, đây có vẻ như là một cái giá nhỏ phải trả, nhưng trong một vòng lặp phân khối liên tục chạy hàng trăm bước mỗi giây, những khoảng trống nhàn rỗi này sẽ tích tụ thành tổn thất thông lượng thực sự.
Để thể hiện điều này, chúng tôi lập hồ sơ thời gian dành cho CPU và GPU khi tạo mã thông báo 8K với kích thước lô là 32 bằng mô hình 8B:
Nếu muốn tạo cùng một loại biểu đồ, bạn có thể sử dụng mã bó liên tục để kết xuất các khoảng hoạt động của CPU và GPU và sử dụng tập lệnh này.
Dòng thời gian xen kẽ giữa màu xanh lá cây (GPU hoạt động, CPU không hoạt động) và màu đỏ (CPU hoạt động, GPU không hoạt động): cả hai không bao giờ trùng nhau. Tổng thời gian tạo là 300,6 giây, trong đó 24,0% thời gian dành cho GPU nhàn rỗi chờ CPU hoàn thành. Theo quan điểm của GPU, gần một phần tư thời gian của thế hệ bị lãng phí. Đây là cách nhìn bi quan về mọi việc.
Cách lạc quan nhất là thời gian tạo sẽ giảm từ 300 xuống 228 giây (tăng tốc 24% miễn phí!), nếu chúng ta có thể loại bỏ hoàn toàn chi phí hoạt động của CPU. Điều này không yêu cầu thay đổi nhân hoặc mô hình mới, chỉ cần phối hợp cẩn thận phần cứng.
Về cơ bản, ý tưởng rất đơn giản: chúng ta cần tìm ra cách chạy chuẩn bị hàng loạt cho lô N+1 trong khi lô N đang tính toán. Nhưng ý tưởng đơn giản này ẩn chứa một số khó khăn về mặt kỹ thuật:
Làm cách nào chúng ta có thể khởi chạy thứ gì đó trên GPU và lấy lại quyền kiểm soát cho CPU?
Làm cách nào chúng tôi có thể đảm bảo dữ liệu đã sẵn sàng cho các tác vụ CPU hoặc GPU vào thời điểm mỗi tác vụ được khởi chạy?
Làm thế nào chúng ta có thể chuẩn bị lô N+1 nếu nó dựa trên dự đoán của lô N?
Bằng cách trả lời những câu hỏi đó, chúng ta sẽ xây dựng tính năng tạo lô không đồng bộ ngay từ đầu. Chúng tôi đã làm theo các bước tương tự để triển khai nó như một phần của việc tạo khối liên tục trong thư viện máy biến áp. Hãy kiểm tra mã và so sánh!
Tạo đồng thời
Mục tiêu cuối cùng của chúng tôi là thực hiện đồng thời các hoạt động của CPU và GPU. Chúng ta cần một cách để phân loại các hoạt động của mình để có thể cho máy biết những hoạt động nào có thể chạy đồng thời. Chúng tôi có thể đạt được điều này bằng cách sử dụng luồng CUDA.
Luồng CUDA là gì?
Để hiểu cách CUDA sắp xếp các hoạt động của nó, chúng ta cần nói về các luồng CUDA. Luồng là một hàng đợi có thứ tự các hoạt động GPU (khởi chạy hạt nhân, bản sao bộ nhớ, rào cản đồng bộ hóa) thực thi theo thứ tự chúng được gửi. Mọi hoạt động của GPU luôn được lên lịch trong một luồng. Các hoạt động trong cùng một luồng là tuần tự: GPU sẽ không bắt đầu luồng tiếp theo cho đến khi luồng trước đó hoàn thành. Các hoạt động trong các luồng khác nhau độc lập với nhau và có thể chạy đồng thời. Để minh họa, nếu bạn khởi chạy 3 thao tác trên 3 luồng khác nhau, quá trình thực thi sẽ như sau:
Cả ba hoạt động đều bắt đầu cùng một lúc. Đây là một sự đơn giản hóa nhỏ: mọi hoạt động của GPU cuối cùng đều do CPU khởi tạo và quá trình khởi tạo đó mất một khoảng thời gian nhỏ: tìm đúng kernel, thực hiện cuộc gọi, chuyển lệnh từ CPU sang GPU, v.v. Đây được gọi là chi phí khởi động CPU và một sơ đồ thực tế hơn trông như thế này:
Các hoạt động vẫn còn
Nguồn tin: Hugging Face Blog. Bản dịch tiếng Việt do AI thực hiện, có thể có sai sót.