Show HN: Outlines - Công cụ đảm bảo LLM tạo JSON hợp lệ và các đầu ra có cấu trúc
(github.com/normal-computing)- Outlines là công cụ đầu ra có cấu trúc dành cho LLM, cho phép chỉ định kiểu đầu ra mong muốn trong quá trình sinh để đảm bảo dữ liệu khớp chính xác với cấu trúc đó
- Thay vì sửa các đầu ra sai sau khi sinh bằng parsing, regex hoặc mã dễ vỡ, công cụ trực tiếp cưỡng chế cấu trúc hợp lệ ngay ở giai đoạn sinh
- Cách sử dụng có dạng
model(prompt, output_type), và có thể chỉ định đầu ra khớp với hệ thống kiểu của Python nhưLiteral["Yes", "No"],int, Pydantic model - Các kiểu đầu ra được hỗ trợ gồm lựa chọn được định nghĩa sẵn, Function Calls dựa trên chữ ký hàm, schema JSON/Pydantic, mẫu biểu thức chính quy, và cưỡng chế cấu trúc dựa trên ngữ pháp (grammars)
- Tích hợp mô hình được chia thành vLLM·Ollama có hỗ trợ máy chủ, transformers·llama.cpp có hỗ trợ mô hình cục bộ, và OpenAI·Gemini·Dottxt có hỗ trợ API
- Các workflow ví dụ gồm chuyển email khách hàng thành ticket dịch vụ, chuyển mô tả sản phẩm thành dữ liệu danh mục, trả về thông tin có cấu trúc hoặc
"I don't know"từ mô tả sự kiện không đầy đủ, phân loại tài liệu theo các danh mục định nghĩa sẵn, và chuyển yêu cầu họp bằng ngôn ngữ tự nhiên thành tham số hàm - Mẫu prompt hỗ trợ
outlines.Template.from_stringdựa trên Jinja và tải từ file, dùng để tách các prompt phức tạp khỏi mã và tái sử dụng - Cài đặt bằng
pip install outlines, và.txt APIhiện được hiển thị là đang ở trạng thái early access
1 bình luận
Ý kiến trên Hacker News
Thư viện này có vẻ dùng một ý tưởng đơn giản về mặt cơ chế là mask một phần không gian từ vựng và tiến hành hiệu quả theo từng bước thời gian; rất hay
Tuy nhiên, từ góc nhìn của người đã dùng các thư viện áp cấu trúc đầu ra lên LLM nền, tôi tò mò liệu các mô hình nền như Llama2 có thật sự hoạt động tốt không
Theo kinh nghiệm của tôi thì gần như là “hoàn toàn không”, và để nó thực sự chạy được thì cần khá nhiều instruction tuning cho mục đích cụ thể
Hơn nữa, việc mask không gian trạng thái theo kiểu hậu xử lý trong lúc sinh trên một mô hình đã được instruction-tune rốt cuộc cũng là thay đổi phân phối sinh, nên trực giác cho thấy điều đó có thể gây hại cho instruction tuning
Về ý thứ hai, nếu mục tiêu là chẳng hạn khiến mô hình chỉ sinh JSON, thì hoàn toàn có thể làm được 100% bằng cách giới hạn token đầu ra nào được phép dùng và token nào không
Tôi có thể dùng GPU trên M2 Mac nên đang dùng bản MLC qua plugin https://github.com/simonw/llm-mlc
Bài báo “Guiding Language Models of Code with Global Context using Monitors”(https://arxiv.org/abs/2306.10763) đề xuất Monitor Guided Decoding, kết nối LLM với phân tích tĩnh để dẫn dắt nó sinh mã có tính nhất quán về kiểu
Không cần bất kỳ fine-tuning nào, chỉ cần dùng phân tích tĩnh để dẫn dắt sinh ở cấp token tại các điểm cụ thể, chất lượng mã sinh ra đã cải thiện rõ rệt cả về khả năng biên dịch lẫn độ khớp với đáp án đúng. Ngay cả mô hình rất nhỏ (1.1B) cũng tạo ra nhiều mã biên dịch được hơn các mô hình lớn hơn nhiều (175B), đồng thời độ khớp đáp án cũng được cải thiện
Khác biệt chính chỉ là hàm sinh khi đó là con người chứ không phải LLM. Tôi không thấy có lý do gì để không bỏ người trung gian ra
Trong mã truyền thống, điều kiện biên đúng nghĩa thường gần với các trường hợp đặc biệt nhỏ, nhưng với LLM thì ta không biết điều gì sẽ khiến nó lao sang hướng kỳ quặc, và mã phân tích cú pháp phải gánh sự hỗn loạn đó
Nói cách khác, cảm giác như tỷ lệ các trường hợp cần được xem là điều kiện biên đã tăng vọt
Với GPT-4, chỉ cần đưa ví dụ vào system message là có thể khiến nó trả về JSON hợp lệ, và 9 trên 10 lần sẽ hoạt động
Nhưng nó vẫn mang tính xác suất, và 9 trên 10 lần là chưa đủ
Đôi khi nó cũng tạo ra câu trả lời ảo giác kiểu
{"key1": "value1", "key2": "value2" for i in range(n)}Nếu prompt lại kèm thông báo lỗi parse thì thường lần thử thứ hai sẽ giải quyết được
Nhưng việc escape dấu ngoặc kép và ký tự xuống dòng thì kém ổn định hơn. Dù đưa nhiều ví dụ, chỉ khoảng một nửa escape đúng, và ngay cả khi prompt lại với lỗi escape thì tỷ lệ thành công vẫn chỉ khoảng 50%
Prompt đó nằm ở đây: https://github.com/microsoft/TypeChat/blob/c45460f4030938da3...
Tôi cho rằng cách tiếp cận dựa trên ngữ pháp như ở đây, hoặc những cách như https://github.com/ggerganov/llama.cpp/pull/1773, là giải pháp thanh lịch hơn nhiều
Tôi hiểu mọi người muốn JSON, nhưng với tôi nó giống như cố bắt mèo đi bơi. Cuối cùng có thể thành công, nhưng đó không phải thiên hướng tự nhiên
Lỗi chủ yếu là không cung cấp lối thoát. LLM cố tìm câu trả lời đúng, nên nếu bạn đưa các văn bản vào và yêu cầu trả về dữ liệu có cấu trúc, nhưng một văn bản nào đó lại trống, nó sẽ khó xác định đáp án đúng và sinh ảo giác
Cách giải quyết là tạo một lối thoát, chẳng hạn thêm một boolean như
textIsMissingvào một trong các tham số. Nếu tính trước kiểu lỗi này thì nó hoạt động hoàn hảoTôi thử lại nhiều lần và còn yêu cầu sửa tiếp, nhưng vẫn không vượt qua kiểm tra; nó không tạo được JSON hợp lệ 100% dù chỉ một lần, nên cuối cùng tôi bỏ cuộc
Thứ nhất, không cần đưa quá nhiều ví dụ vào prompt nên dùng ít token hơn
Thứ hai, ít bị ảnh hưởng bởi vấn đề quên
Một lợi thế nhỏ là có thể kiểm soát chính xác đầu ra mong muốn nên bắt đầu từ đâu. Tuy nhiên nhìn tổng thể, tôi xem đây là một tính năng bổ sung tốt chứ không hẳn là thứ quá cốt lõi
Phần lớn sức mạnh của LLM nằm ở phân phối xác suất đã được hiệu chỉnh trong phản hồi, nhưng kỹ thuật này có lẽ đang vứt bỏ khả năng đó. Tôi thắc mắc vì sao mức này lại được xem là đủ
Lấy ví dụ đơn giản: giả sử các đầu ra khả dĩ của LLM chỉ gồm “hello world”, “food”, “hello”, “good day”, và khi không có prompt thì tất cả có xác suất như nhau. Giả sử ngữ pháp chỉ đặt ràng buộc rằng ở đâu đó trong đầu ra phải có dấu cách
Nếu lấy mẫu đầu ra của LLM cho đến khi vượt qua ngữ pháp, ta sẽ nhận được “hello world” và “good day” với xác suất bằng nhau. Nhưng nếu áp dụng kỹ thuật trên website, “hello world” sẽ xuất hiện thường xuyên gấp đôi “good day”
Vấn đề cốt lõi là một tiền tố câu trả lời nào đó có thể vốn có xác suất cực thấp để dẫn tới một phản hồi hợp lệ, nhưng kỹ thuật này, nếu giả định là sẽ thành công, lại tạo ra một phản hồi hợp lệ từ tiền tố đó. Nếu đủ độc lập ở những chỗ thích hợp thì có thể ổn, nhưng trong mô hình tự hồi quy, các lỗi tương quan sẽ tích lũy rất nhanh
Nếu chỉ nói riêng về JSON: khi LLM tạo ra phản hồi không tuân thủ schema, liệu nó tạo ra nhiều hơn hay ít hơn các lỗi sự kiện, hallucination, chuỗi bị cắt, bỏ sót nhân vật chính, v.v.? Nếu tỷ lệ lỗi sự kiện có liên hệ không tầm thường với tỷ lệ lỗi schema, hướng này nguy hiểm hơn vẻ ngoài. Nhìn vào việc một từ cụ thể hoặc một cụm từ bị dính liền có thể ảnh hưởng lớn đến đầu ra của LLM, có vẻ rất có khả năng các chi tiết như tuân thủ schema cũng lan sang các đặc tính khác của đầu ra
Tôi đã cố nghĩ ra một ví dụ “tiền tố câu trả lời có xác suất rất thấp để dẫn tới phản hồi hợp lệ, nhưng kỹ thuật bằng cách nào đó vẫn dựng được phản hồi hợp lệ và gây ra vấn đề”, nhưng chưa nghĩ ra được. Nếu có ví dụ hay thì đây có thể là một câu hỏi nghiên cứu thú vị
Liên quan đến việc này, LLama.cpp đã triển khai lấy mẫu dựa trên ngữ pháp vào tháng trước
https://news.ycombinator.com/item?id=36819906
https://github.com/ggerganov/llama.cpp/pull/1773
Cách của chúng tôi hiệu quả hơn nhiều. llama.cpp duyệt toàn bộ vocabulary khoảng 50 nghìn token ở mỗi bước để tạo mask
Chúng tôi tạo index khi khởi tạo, và ở mỗi bước khi xây dựng mask thì chỉ cần tra cứu dictionary. Đây là cách đánh đổi bộ nhớ để lấy tốc độ, và việc lấy mẫu nhanh như lấy mẫu tiêu chuẩn
Nhìn vào việc có nhiều bài báo liên quan, chắc hẳn cũng có nhiều nơi khác làm. Điểm chính ở đây và trong công việc hiện tại là cung cấp khả năng dẫn hướng với chi phí rất thấp; với trường hợp regex thì đã được triển khai một thời gian, rồi sau đó mở rộng sang JSON
Cảm ơn vì đã làm ra thứ này; nguyên lý hoạt động là một ý tưởng quá hiển nhiên đến mức thật ngạc nhiên là các nền tảng hạng nhất vẫn chưa làm
Ngoài JSON, tôi tò mò nó có thể được dùng thế nào cho các tác vụ khác cần đầu vào có cấu trúc
Có thể đây là hệ quả của việc tài nguyên phát triển bị giới hạn. Nếu một công nghệ 10 năm tuổi thiếu tính năng cơ bản như vậy thì đáng ngạc nhiên, nhưng với công nghệ AI đang trong cuộc chạy đua vũ trang, việc vẫn thiếu các tính năng tiện ích thì có vẻ chấp nhận được
Tôi không rõ cái này khác các dự án sau như thế nào
https://github.com/1rgs/jsonformer
https://github.com/newhouseb/clownfish
https://github.com/mkuchnik/relm
https://github.com/ggerganov/llama.cpp/pull/1773
https://github.com/Shopify/torch-grammar
Nhìn chung, các hệ thống dẫn hướng dựa trên logit như vậy rất nhiều, và lý do chúng chưa có traction lớn là vì các mô hình hiệu năng cao nhất nằm sau REST API không cho phép cách tiếp cận tinh vi như thế
Các mô hình đó tốt hơn nhiều, nên mọi người nói chung hài lòng với cách yêu cầu lại cho đến khi nhận được định dạng đúng. Với GPT-4, theo trải nghiệm của tôi, những thất bại như vậy cũng khá hiếm
Chúng tôi duyệt vocabulary một lần khi khởi tạo để tạo index, rồi sau đó quá trình sinh nhanh như sinh tiêu chuẩn
Nói cách khác, có phải mỗi khi LLM tạo ra một token, nó sẽ cập nhật “mặt nạ” thiên lệch logit sao cho token tiếp theo chỉ có thể là token JSON hợp lệ không? Rất hay
Tôi không chắc cách này thật sự có thể đảm bảo 100% ra sao
Sửa: bài báo giải thích phần này thận trọng hơn
Brandon Willard này có phải là Brandon Willard, vũ công breakdance ở Detroit không?
Sửa: Đúng rồi! https://brandonwillard.github.io/
remilouf, vì anh nói nền tảng của mình là “lập trình xác suất, quan hệ và ký hiệu”, tôi nghĩ anh hiểu rằng việc sinh văn bản từ văn pháp chính quy, văn pháp phi ngữ cảnh, hay thực tế là văn pháp ở bất kỳ cấp độ nào, không phải là vấn đề
Ví dụ, trong một ngôn ngữ quan hệ như Prolog, chỉ cần cung cấp văn pháp bằng ký pháp Definite Clause Grammars là có thể làm rất dễ dàng
Theo tôi thấy, cách tiếp cận này yêu cầu người dùng cung cấp văn pháp. Vậy tôi thắc mắc lợi thế của việc dùng LLM để sinh văn bản là gì
Tại sao không chỉ chạy văn pháp như một bộ sinh để tạo ra văn bản mong muốn? Như vậy có thể giảm được công sức và chi phí rất lớn của việc huấn luyện LLM ngay từ đầu. Nếu chỉ muốn sinh văn bản có cấu trúc thì tôi cũng không hiểu vì sao lại cần LLM, vốn là mô hình ngôn ngữ tự nhiên
Lý do vẫn cần LLM ngay cả khi chỉ muốn văn bản có cấu trúc là để phân tích văn bản phi cấu trúc do con người viết và trả về dữ liệu có cấu trúc mà máy có thể dùng
Nếu yêu cầu là “10 chữ số đầu tiên của pi là gì?” và giới hạn phản hồi bằng regex
"[0-9]+\.[0-9]+", thì mục tiêu là nhận được đáp án đúng thực sự là 3.1415926535, chứ không phải một chuỗi ngẫu nhiên khớp mẫu như “1.2346789”Thú vị đấy, gần đây chúng tôi cũng đã làm một công cụ tương tự để ràng buộc đầu ra của llama khớp với interface TypeScript[1]
Tôi tin chắc rằng việc đảm bảo định dạng đầu ra sẽ trở nên quan trọng trong vài thập kỷ tới, khi LLM được dùng cho các mục đích thực tế chứ không phải đồ chơi
[1] https://github.com/ggerganov/llama.cpp/discussions/2494