Đi sâu vào định dạng GGUF: Những gì có và những gì còn thiếu

Phần mềm14 tháng 5, 2026·8 phút đọc

GGUF là định dạng tệp đơn giản nhưng mạnh mẽ được llama.cpp sử dụng, gói gọn mọi thứ cần thiết để chạy mô hình ngôn ngữ trong một tệp duy nhất. Bài viết phân tích các thành phần metadata như chat template và cấu hình sampler, đồng thời chỉ ra những thiếu sót hiện tại như hỗ trợ tool calling, think tokens và khả năng đa phương thức.

Đi sâu vào định dạng GGUF: Những gì có và những gì còn thiếu

GGUF là định dạng tệp mà llama.cpp sử dụng cho các mô hình ngôn ngữ. Điểm thực sự thú vị của GGUF là nó chỉ gói gọn trong một tệp duy nhất.

Hãy so sánh điều này với một kho lưu trữ safetensors điển hình trên Hugging Face, nơi có vô số tệp JSON cần thiết nằm rải rác, hoặc so với một mô hình Ollama điển hình, là một OCI chứa các lớp json, go template, v.v. bên trong. Nội dung về cơ bản là giống nhau, nhưng GGUF làm cho việc sử dụng thuận tiện hơn (ergonomic) bằng cách giữ tất cả những thứ này trong một tệp duy nhất.

Nhưng những thứ này là gì, và liệu chúng có bao phủ mọi thứ cần thiết hay không?

Chat Templates (Mẫu hội thoại)

Các mô hình ngôn ngữ hội thoại được huấn luyện trên các chuỗi tuân theo một định dạng cụ thể, trông giống như một cuộc hội thoại. Ví dụ, định dạng của Gemma4 sẽ khác với LFM2.

Khi bắt đầu thêm các tính năng phức tạp như cách định dạng các khối lý luận (reasoning blocks), cách hiển thị mô tả công cụ (tool descriptions), các cuộc gọi công cụ và phản hồi của chúng, cũng như cách mã hóa tin nhắn đa phương tiện (hình ảnh, âm thanh, video, v.v.), mọi thứ trở nên phức tạp hơn rất nhiều.

Tất cả điều này được xử lý bởi một chat template, một tập lệnh bằng ngôn ngữ mẫu Jinja2. Jinja2 là một ngôn ngữ lập trình — nó có các vòng lặp, điều kiện, phép gán, danh sách, từ điển, v.v. — do đó, bất kỳ ứng dụng LLM hội thoại nào cũng phải cung cấp một trình thông dịch ngôn ngữ lập trình có khả năng chạy các chương trình như vậy mỗi khi có tin nhắn mới.

Có sự khác biệt về hiệu suất đáng kể giữa các triển khai Jinja2. Tuy nhiên, chat templating không phải là nút thắt hiệu suất chính trong ứng dụng LLM cục bộ, nên không đáng để tranh cãi về điều này.

Special Tokens (Token đặc biệt)

Các mô hình ngôn ngữ sẽ liên tục xuất ra token tiếp theo cho bất kỳ chuỗi token nào bạn đưa vào, mãi mãi — vì vậy chúng ta cần một cách nào đó để dừng chúng lại. Giải pháp điển hình là một loại token kết thúc chuỗi (end-of-sequence token). Ý tưởng là để engine suy luận dừng tạo ra văn bản bất cứ khi nào mô hình phát ra một token như vậy.

Đây là một ví dụ về token đặc biệt. Các token đặc biệt nói chung là những token có ý nghĩa ngữ nghĩa rộng hơn các chữ cái mà chúng đại diện. Chúng thường là những token không nên hiển thị cho người dùng, mặc dù chúng (thường) vẫn có biểu diễn văn bản, nên chúng có thể hiển thị được.

Ví dụ, một vài token cho Gemma4:

  • Token ID 1: Kết thúc chuỗi, mô hình phát ra cái này để dừng tạo.
  • Token ID 2: Bắt đầu chuỗi, được thêm vào đầu vào.
  • Token ID 46: Đánh dấu bắt đầu một cuộc gọi công cụ.
  • Token ID 47: Đánh dấu kết thúc một cuộc gọi công cụ.

Sampler Configuration (Cấu hình Sampler)

Các mô hình ngôn ngữ xuất ra phân phối xác suất của token tiếp theo. Việc chọn một token từ phân phối này được gọi là lấy mẫu (sampling). Cách tiếp cận đơn giản nhất là chọn ngẫu nhiên từ phân phối có trọng số.

Nhưng chúng ta có thể làm được nhiều hơn thế. Người ta đã chứng minh rằng bạn có thể nhận được kết quả tốt hơn bằng cách áp dụng một số biến đổi cho phân phối xác suất trước khi chọn một token cụ thể. Khi các phòng thí nghiệm nghiên cứu phát hành một mô hình mới, họ thường bao gồm cấu hình sampler được đề xuất cụ thể.

May mắn thay, một bổ sung gần đây cho định dạng GGUF cho phép chuỗi sampler được chỉ định trực tiếp trong tệp mô hình. Điều này làm cho định dạng tùy chỉnh của chúng tôi trở nên lỗi thời — và chính xác là kết quả chúng tôi mong muốn.

Sampler Chain Sequence (Trình tự chuỗi Sampler)

Thứ tự của các bước lấy mẫu có thể tạo ra sự khác biệt lớn đối với phân phối cuối cùng. Thật frustrate là hầu hết các định dạng cấu hình sampler (bao gồm cả các tệp json của hình ảnh Ollama và generation_config.json của HF) không có bất kỳ cách nào để chỉ định thứ tự của các bước lấy mẫu.

Tôi rất vui vì tiêu chuẩn GGUF cho điều này bao gồm trường general.sampling.sequence, cho phép bạn chỉ định thứ tự. Tuy nhiên, nhiều mô hình GGUF vẫn sẽ bỏ qua trường này và mong đợi thứ tự ngầm định là "bất cứ điều gì llama.cpp làm theo mặc định". Dù sao thì nó vẫn hoạt động.

Những gì vẫn còn thiếu?

Các engine suy luận tốt nhằm mục đích cung cấp một giao diện thống nhất cho các mô hình ngôn ngữ khác nhau. Những thứ bổ sung trong metadata GGUF bao phủ rất nhiều điều này, vì vậy việc phân tích và sử dụng những thứ đó cho phép chúng ta tránh được nhiều đường dẫn mã cụ thể cho từng mô hình.

Thiếu: Định dạng Tool Calling

Một điều mà dường như mọi engine suy luận đều có các đường dẫn mã hóa cứng (hardcoded) để phân tích các định dạng gọi công cụ khác nhau. Ví dụ, một cuộc gọi công cụ của Qwen3 trông khác với Qwen3.5 và Gemma4.

Hiện tại, một loạt các engine suy luận khác nhau đang vội vàng triển khai các trình phân tích cú pháp (parser) bất cứ khi nào một mô hình mới được phát hành. Sẽ là một bổ sung tuyệt vời cho tiêu chuẩn GGUF nếu tệp mô hình bao gồm một ngữ pháp (grammar), từ đó chúng ta có thể suy ra một trình phân tích cú pháp.

Thiếu: Think Tokens

Đây chắc chắn là điều dễ nhất để thêm vào. Các kho lưu trữ Hugging Face thượng nguồn đã bắt đầu bao gồm trường think_token. Điều này cực kỳ hữu ích để tách biệt phần suy nghĩ của đầu ra được tạo ra, vì nó thường nên được loại bỏ hoặc hiển thị khác với đầu ra chính.

Vì một lý do nào đó, các chuyển đổi GGUF hạ nguồn thường không bao gồm cái này. Điều này làm cho các engine suy luận dựa trên GGUF không thể tách luồng suy nghĩ khỏi đầu ra chính mà không cần viết các đường dẫn mã cụ thể cho các dòng mô hình cụ thể.

Thiếu: Projection Models (Mô hình chiếu)

Tương tác LLM đa phương thức (tức là để LLM nhìn thấy hình ảnh và âm thanh một cách tự nhiên thay vì chỉ văn bản) yêu cầu một mô hình bổ sung để xử lý đầu vào không phải văn bản, được gọi là "projection model". Quy ước hiện tại là truyền vào hai tệp GGUF: một GGUF cho mô hình ngôn ngữ chính và một mô hình nhỏ hơn để xử lý hình ảnh và âm thanh.

Điều này phá vỡ tính tiện lợi của "chỉ một tệp". Sẽ là một cải tiến tuyệt vời nếu tệp GGUF duy nhất có thể gói gọn trọng số và cấu hình của mô hình projection bên trong tệp chính. Mô hình projection thường có kích thước khoảng 1GB — đủ lớn để chúng ta chắc chắn muốn bỏ qua nó khi không được sử dụng. Nhưng tôi nghĩ việc cung cấp hai biến thể của GGUF là hợp lý: một có trọng số projection và một không.

Thiếu: Danh sách các tính năng được hỗ trợ

Các mô hình không hỗ trợ cùng một thứ và không dễ dàng phát hiện từ tệp GGUF những gì thực sự được hỗ trợ. Một số mô hình hỗ trợ tiêu thụ hình ảnh, một số thì không. Một số hỗ trợ gọi công cụ theo bản năng, một số thì không. Hiện tại, cách xử lý tốt nhất là thực hiện so khớp chuỗi con (substring matching) trên chat template để xem nó có cố gắng hiển thị danh sách lược đồ json công cụ hay không. Điều này rõ ràng là một giải pháp tạm thời (hacky).

Tôi rất muốn cộng đồng GGUF bắt đầu thêm các cờ tính năng (feature flags) vào tệp mô hình, để các thư viện suy luận không phụ thuộc mô hình như của chúng ta có thể cung cấp thông báo lỗi và cảnh báo nhất quán hơn khi một chương trình tiêu thụ cố gắng thực hiện gọi công cụ trên một mô hình không hỗ trợ gọi công cụ theo bản năng.

Kết luận

Tôi yêu GGUF. Tôi yêu nó vì nó chỉ là một tệp duy nhất bao gồm mọi thứ cần thiết để chạy mô hình chính xác mà không cần thêm nhiều đường dẫn mã cụ thể cho mô hình. Tôi cũng yêu GGUF vì nó là một định dạng mở, có thể mở rộng với một cộng đồng mạnh mẽ xung quanh.

Điều này có nghĩa là chúng ta có thể cùng nhau làm việc để củng cố tiêu chuẩn này, giữ trải nghiệm nhà phát triển tuyệt vời trong khi có thể dễ dàng thay đổi mô hình trong một ứng dụng mà không cần viết lại bất kỳ mã nào.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗