Hacker News Nổi bật (buzzing.cc bản dịch tiếng Trung)
88

Thủ thuật

Lập trình cho AI Agent: Ngôn ngữ nào tối ưu nhất?

(giờ Việt Nam)

Tóm tắt AI

Bài viết thực nghiệm bác bỏ quan điểm ngôn ngữ động tiết kiệm token hơn cho AI. Kết quả cho thấy hiệu suất phụ thuộc vào độ phức tạp của tác vụ: ngôn ngữ động thắng ở tác vụ nhỏ, nhưng ngôn ngữ tĩnh lại vượt trội khi giải quyết các vấn đề lớn hơn.

Bản dịch AI

Bài viết được trích dẫn khá rộng rãi này (dù sao thì tôi vẫn liên tục thấy nó được nhắc đến) gợi ý rằng các ngôn ngữ động và/hoặc các ngôn ngữ biểu đạt mọi thứ một cách súc tích hơn sẽ tiết kiệm token hơn. Có vẻ như nó được trích dẫn đủ nhiều để các kết quả tìm kiếm của LLM đều đồng tình. Ví dụ, khi tôi tìm kiếm "dynamic vs static language token cost" (không dùng dấu ngoặc kép), bản tóm tắt AI của Google đã mở đầu bằng

Các ngôn ngữ định kiểu động (dynamically typed) thường có chi phí token LLM thấp hơn so với các ngôn ngữ định kiểu tĩnh (statically typed) truyền thống vì việc lược bỏ các khai báo kiểu tường minh giúp mã nguồn trở nên gọn gàng hơn.

AI của Google đã trích dẫn cùng một bài viết đó, gợi ý rằng một số ngôn ngữ động súc tích có thể chỉ tốn từ 1/2 đến 1/3 chi phí token so với các ngôn ngữ tĩnh như Rust, Go, C++, v.v. Tác giả cho biết

Có một khoảng cách rất đáng kể là 2,6 lần giữa C (ngôn ngữ kém hiệu quả về token nhất mà tôi so sánh) và Clojure (ngôn ngữ hiệu quả nhất).

Và sau đó họ đã thử nghiệm với J, nói rằng

Nó chiếm ưu thế với trung bình chỉ 70 token, gần bằng một nửa so với Clojure (109 token). Các ngôn ngữ mảng (array languages) có thể cực kỳ tiết kiệm token khi chúng tránh được các bộ ký tự lạ. Nếu hiệu quả về token hóa ra là một yếu tố thúc đẩy chính, thì đây có lẽ là một hướng tiến hóa rất thú vị cho các ngôn ngữ lập trình.

Một so sánh khác về token giữa ngôn ngữ động và tĩnh mà tôi tìm thấy là bài này, nó cũng củng cố kết luận tương tự. Nếu bạn muốn coi đây là phần 8 của loạt bài tập về đánh giá (benchmarking, evals) và thiết kế thực nghiệm, bạn có thể nhấp vào các liên kết và suy nghĩ về các vấn đề đánh giá trước khi đọc tiếp.

Nếu không tự chạy đánh giá của riêng mình, một vấn đề của thí nghiệm đầu tiên là các bài toán quá tầm thường, điều mà chúng ta có thể thấy từ trích dẫn ở trên; một bài toán có thể giải quyết trong 70 token bằng J và 109 token bằng Clojure thì thực sự không phải là một bài toán khó (tác giả đã sử dụng Rosetta Code). Như chúng ta đã thấy khi xem xét các đánh giá khác về "caveman mode" so với các đánh giá của chính chúng ta, bạn có thể nhận được những kết quả rất khác biệt từ các bài toán tầm thường, nơi phần lớn công việc chỉ là in ra câu trả lời, so với các bài toán ít tầm thường hơn một chút đòi hỏi một lượng "công việc thực sự"; những lợi ích lớn mà "caveman mode" tuyên bố và thể hiện trong các bản sao chép sẽ biến mất khi bạn bắt đầu xem xét các bài toán cần nhiều hơn vài token. Nhìn chung, hiệu suất trên các tác vụ tầm thường không mang tính tổng quát.

Các vấn đề trong liên kết thứ hai tinh tế hơn một chút, vì vậy chúng ta sẽ để dành phần lớn cho phụ lục, nhưng chúng bao gồm các vấn đề như một trong các bài kiểm tra thực thi sai đường dẫn (không tồn tại), khiến bài kiểm tra thất bại. Một trong các tác nhân (agent) sau đó đã tạo liên kết tượng trưng (symlink) đường dẫn không tồn tại đó tới tệp thực thi của chính nó, điều này có hiệu quả cho trường hợp đó, nhưng cũng khiến mọi bài kiểm tra sau đó chạy tệp thực thi của tác nhân đó thay vì tệp thực thi đúng. Tác giả cố gắng rút ra kết luận về ý nghĩa của việc Rust có một số lỗi, nhưng tất cả chỉ có nghĩa là việc chấm điểm cho Rust đã chạy trước khi tác nhân Go tạo liên kết tượng trưng cho tất cả các điểm số trên bài kiểm tra bị lỗi đó sang tệp thực thi của Go.

Thay vì dựa vào các đánh giá này, chúng ta có thể thử chạy một vài đánh giá của riêng mình. Như chúng ta có thể thấy từ các đánh giá này cũng như các đánh giá được thảo luận trong các bài tập trước, rất dễ để tạo ra một bài đánh giá không phản ánh đúng những gì người tạo ra nó nghĩ. Không nghi ngờ gì nữa, các đánh giá này cũng sẽ không phải là ngoại lệ và sẽ có những khiếm khuyết (xem phụ lục bên dưới để biết thêm chi tiết).

Để xây dựng trực giác về mọi thứ, tôi thích đăng ký trước các dự đoán của mình trước khi xem kết quả. Một số điều tôi đã đăng ký trước với bạn bè là:

Zstd

Đối với đánh giá đầu tiên, tôi đã thử cung cấp cho các tác nhân RFC của zstd (cộng với errata) và yêu cầu chúng triển khai một bộ giải mã zstd hoàn chỉnh (các tác nhân bị kẹt trong một container không có quyền truy cập internet). Các bài kiểm tra không được cung cấp cho các tác nhân. Đối với một thứ có phạm vi rộng như zstd, việc mong đợi các bài kiểm tra bao phủ mọi trường hợp có thể xảy ra là không hợp lý. Ví dụ, mặc dù zstd là một phần mềm được kiểm thử khá kỹ lưỡng, tôi đã từng tìm thấy một lỗi hỏng dữ liệu trong zstd. Bộ kiểm thử không nhằm mục đích tìm ra các trường hợp cực đoan có thể ẩn nấp trong nhiều năm, mà thay vào đó nhằm kiểm tra các trường hợp khác nhau có thể "dễ dàng" suy ra từ RFC mà lẽ ra phải hoạt động.

Dưới đây, trục x là chi phí và trục y là điểm chính xác (càng lên cao và sang trái càng tốt / càng xuống thấp và sang phải càng tệ); kết quả trung bình trên các nỗ lực trung bình và siêu cấp với GPT-5.6 Sol. Nếu chúng ta chỉ nhìn vào mức trung bình (và bỏ qua thực tế là kết quả thường khác biệt rất lớn trên các tác vụ khác nhau), chúng ta có thể đi đến kết luận giống như đánh giá của Alderson, rằng các ngôn ngữ động hiệu quả và tốt hơn khi sử dụng LLM vì (bỏ qua các ngôn ngữ tương đối khó hiểu) cụm ngôn ngữ động nằm ở phía trên và bên trái của cụm ngôn ngữ tĩnh (chúng tôi đã sử dụng mã màu của Alderson cho tĩnh so với động để dễ so sánh trong nháy mắt). Nhưng nếu chúng ta nhìn vào nỗ lực siêu cấp, kết quả khá hỗn hợp, với một vài ngôn ngữ tĩnh đạt kết quả tốt nhất, và có nhiều ngôn ngữ tĩnh hơn ngôn ngữ động trong số các kết quả tốt hơn.

Các biểu đồ bên dưới cũng có nút chuyển đổi trục x sang thời gian thay vì chi phí. Dự án mame/ai-coding-lang-bench lưu ý rằng việc nhận kết quả nhanh hơn là rất có giá trị (cá nhân tôi không thấy vậy vì kết quả mất nhiều thời gian đến mức tôi làm việc đa nhiệm thay vì chờ đợi), vì vậy chúng ta cũng có thể xem xét điều đó. Tương tự, chúng ta quan sát thấy không loại ngôn ngữ nào chiếm ưu thế hơn loại kia mặc dù, ở mức nỗ lực trung bình cho tác vụ cụ thể này, các kết quả ngôn ngữ động tốt nhất một lần nữa lại tốt hơn các kết quả ngôn ngữ tĩnh tốt nhất (mặc dù, một lần nữa, chúng khá gần nhau).

Chúng ta có thể quan sát thấy rằng, giống như khi chúng ta so sánh các đánh giá "caveman mode" hoàn toàn tầm thường với một đánh giá "caveman mode" ít tầm thường hơn, các mối quan hệ rất mạnh mẽ tồn tại trong các đánh giá tầm thường không mang tính tổng quát cho trường hợp lớn hơn này. Như trường hợp đó, các tỷ lệ hiệu suất cực đoan biến mất trong các đánh giá lớn hơn này, ngoại trừ trong các trường hợp chúng ta có thể mong đợi hiệu suất kém, chẳng hạn như khi sử dụng hợp ngữ (assembly) (sẽ tốn thời gian và khó khăn hơn đáng kể đối với con người) và khi sử dụng các ngôn ngữ tương đối khó hiểu mà chúng ta có thể không mong đợi các phòng thí nghiệm AI đang nỗ lực tạo ra dữ liệu môi trường RL tổng hợp.

Lưu ý rằng điều này trái ngược với những gì đánh giá đầu tiên tìm thấy khi nó gợi ý rằng các ngôn ngữ rất dày đặc như J sẽ có ý nghĩa vì lý do hiệu quả. Có lẽ việc sử dụng một ngôn ngữ khó hiểu (và "kỳ lạ") có thể có ý nghĩa nếu bạn có ngân sách rất lớn và bạn có thể huấn luyện hoặc tinh chỉnh một mô hình để đạt hiệu quả cho ngôn ngữ ưa thích của mình, nhưng nếu bạn là người dùng LLM bình thường, có vẻ như gắn bó với một ngôn ngữ phổ biến là lựa chọn tốt hơn so với việc sử dụng một ngôn ngữ dày đặc khó hiểu.

Và hóa ra nếu chúng ta vẽ biểu đồ độ phổ biến của ngôn ngữ so với hiệu suất trên đánh giá này (không hiển thị), chúng ta quan sát thấy một mối tương quan thuận từ yếu đến trung bình, nơi các ngôn ngữ phổ biến hơn dẫn đến các giải pháp chính xác hơn cũng như rẻ hơn.

Như chúng tôi đã lưu ý trước đó, các đánh giá rất gần gũi có thể đưa ra các kết quả khác biệt đáng kể. Ví dụ, chúng tôi đã thấy các kết quả khác biệt đáng kể trong các đánh giá Tối ưu hóa 1 so với Tối ưu hóa 2 ở đây khi Tối ưu hóa 1 và Tối ưu hóa 2 đang tối ưu hóa việc nén và giải nén bzip2 trong wasm, đây là các tác vụ khá gần gũi. Để đưa ra một tuyên bố mạnh mẽ, phổ quát, như "ngôn ngữ động hiệu quả hơn ngôn ngữ tĩnh", chúng ta sẽ phải chạy các đánh giá trên nhiều tác vụ. Tuy nhiên, việc chỉ ra rằng một tuyên bố như

Các ngôn ngữ định kiểu động thường có chi phí token LLM thấp hơn so với các ngôn ngữ định kiểu tĩnh truyền thống vì việc lược bỏ các khai báo kiểu tường minh giúp mã nguồn trở nên gọn gàng hơn.

có lẽ tốt nhất chỉ đúng một cách mơ hồ theo hướng nào đó và không thực sự liên quan đến bất kỳ trường hợp cụ thể nào và có lẽ không đủ mạnh để có liên quan nói chung, chúng ta chỉ cần thử một vài trường hợp và thấy rằng tuyên bố đó không đúng về mặt tổng quát. Ở trên, chúng ta đã thấy rằng ở một mức nỗ lực, tuyên bố có vẻ như có phần đúng, nhưng có những ngoại lệ, và sau đó ở mức nỗ lực cao hơn, tuyên bố có vẻ không thực sự đúng, điều này đủ để nói rằng tuyên bố đó có lẽ không đúng trên toàn cầu, trừ khi đánh giá của chúng ta có một yếu tố gây nhiễu làm mất hiệu lực hoàn toàn.

Pandoc

Nhưng, chỉ để có cái nhìn về một tác vụ rất khác cũng được trình bày theo cách khác (giống TDD hơn là "đọc đặc tả"), đánh giá tiếp theo này lấy đánh giá Pandoc ProgramBench và sửa đổi nó cho trường hợp sử dụng của chúng tôi. Thay vì tác vụ kỹ thuật đảo ngược do ProgramBench trình bày, chúng tôi cung cấp cho các tác nhân tài liệu ProgramBench cũng như các bài kiểm tra ProgramBench và sau đó chấm điểm các tác nhân dựa trên một tập hợp các bài kiểm tra giữ lại (holdout set) để đo lường hiệu suất của từng điều kiện.

Trong các kết quả dưới đây, trục x lại là chi phí và trục y là điểm số trên các bài kiểm tra giữ lại.

Như trước đây, chúng ta không thấy mối quan hệ rất mạnh mẽ giữa sự thành công hoặc chi phí và việc một ngôn ngữ là tĩnh hay động hay rất dày đặc. Chúng ta lại thấy rằng các ngôn ngữ tương đối khó hiểu có xu hướng làm kém (mặc dù Clojure làm tốt hơn nhiều ở đây so với trên Zstd). Ngoài ra, Hợp ngữ (Assembly) làm tệ hơn nhiều, điều này có vẻ dễ hiểu vì chúng ta mong đợi một con người viết Hợp ngữ sẽ gặp bất lợi hơn nhiều khi triển khai Pandoc so với triển khai Zstd và dường như không có lý do mạnh mẽ nào để nghĩ rằng LLM sẽ khác biệt về mặt này.

Tất cả những điều này có ý nghĩa gì?

Ai mà biết được?

Tôi có rất nhiều câu hỏi về những gì hoạt động tốt khi sử dụng LLM (chẳng hạn như, kỹ thuật kiểm thử nào hoạt động tốt, ngôn ngữ nào hoạt động tốt, kiến trúc phần mềm nào hoạt động tốt, liệu chi phí sửa lỗi có thay đổi theo ngôn ngữ không, liệu chi phí bảo trì chương trình chung có thay đổi theo ngôn ngữ không, v.v.). Hầu hết các câu hỏi này đều không được trả lời trong dữ liệu công khai và, nếu chúng đã được trả lời trong các phòng thí nghiệm AI, thông tin đó hầu như không được công khai.

Hầu hết các tuyên bố được đưa ra về việc một ngôn ngữ cụ thể tốt cho việc sử dụng LLM dường như đều sai (ví dụ: tuyên bố rằng Ruby, Clojure và J đặc biệt phù hợp với LLM, vốn được đề cập trong các đánh giá được liên kết ở trên, cũng như tuyên bố khá phổ biến rằng Elixir đặc biệt phù hợp với LLM), nhưng không rõ điều gì mới là đúng.

Năm 2014, chúng tôi đã xem xét tài liệu về kiểu tĩnh so với kiểu động và thấy rằng việc khảo sát tài liệu không mang lại nhiều thông tin ngoài một vài nghiên cứu điển hình. Đối với một ví dụ điển hình cho một nghiên cứu học thuật tiêu chuẩn, chúng tôi đã thấy bài báo, "Hệ thống định kiểu tĩnh có cải thiện khả năng bảo trì của các hệ thống phần mềm không? Một nghiên cứu thực nghiệm", mà tôi đã bình luận:

Các đối tượng được cung cấp các lớp (classes) mà họ phải sửa lỗi trong mã hiện có hoặc điền vào các phương thức stub. Các lớp tĩnh cho Java, các lớp động cho Groovy. Trong các trường hợp lỗi kiểu (và các lỗi không tìm thấy phương thức tương ứng), các nhà phát triển đã giải quyết vấn đề nhanh hơn trong Java. Đối với các lỗi ngữ nghĩa, không có sự khác biệt. Nghiên cứu đã sử dụng thiết kế trong cùng đối tượng (within-subject design), với thứ tự tác vụ ngẫu nhiên trên 33 đối tượng. Một hạn chế đáng chú ý là nghiên cứu đã tránh sử dụng "các cấu trúc điều khiển phức tạp", chẳng hạn như vòng lặp và đệ quy, vì chúng làm tăng phương sai trong thời gian giải quyết. Kết quả là, tất cả các lỗi đều là lỗi tầm thường. Điều này có thể thấy ở thời gian trung bình để giải quyết các tác vụ, nằm trong khoảng hàng trăm giây. Các tác vụ có thể bao gồm nhiều lỗi, vì vậy thời gian cho mỗi lỗi là khá thấp.

Việc chọn các tác vụ tránh "các cấu trúc điều khiển phức tạp" như vòng lặp và đệ quy, nơi các tác vụ mất hàng trăm giây làm cho kết quả trở nên vô nghĩa đối với các tác vụ thực sự tiêu tốn thời gian của một lập trình viên chuyên nghiệp, giống như đánh giá đầu tiên mà chúng ta đã thấy nơi các tác vụ mất hàng chục đến hàng trăm token. Tuy nhiên, với LLM, chúng ta thực sự có thể cung cấp cho chúng các tác vụ không tầm thường và so sánh cách chúng thực hiện. Có vấn đề về việc kết quả mang tính tổng quát đến đâu đối với các tác vụ khác nhau, nhưng chúng ta sẽ gặp chính vấn đề đó với các nghiên cứu trên con người, thậm chí còn tệ hơn (phương sai của LLM là rất lớn, nhưng phương sai của con người còn lớn hơn vì bạn không thể bắt cùng một người thực hiện một loạt các tác vụ với các hạt giống khác nhau). Và trong khi 20 đô la để có một LLM triển khai bộ giải mã Zstd không hẳn là rẻ khi bạn nhân với số lượng ngôn ngữ và số lần lặp lại cho mỗi điều kiện, nếu bạn nghĩ về chi phí thuê một lập trình viên chuyên nghiệp có thể đọc RFC zstd và triển khai nó, thì không đời nào nghiên cứu tương đương lại được thực hiện vì chi phí sẽ làm cho nó hoàn toàn không khả thi. Điều đó càng đúng gấp đôi đối với tác vụ Pandoc.

Với LLM, rất nhiều câu hỏi đã chuyển từ chỗ không thể trả lời được thành có thể trả lời được với một chút nỗ lực và một số token. Do các động lực đang diễn ra, không rõ liệu chúng ta có sớm nhận được câu trả lời cho những câu hỏi như thế này hay không, nhưng ít nhất bây giờ cũng có thể thử sức với nó.

Có rất nhiều tuyên bố tôi thấy trôi nổi mà các đánh giá này không thể chứng minh hoặc bác bỏ (vì lý do đã lưu ý ở trên rằng, do phương sai giữa các bài toán khác nhau, sẽ cần phải thử nhiều tác vụ hơn nữa), nhưng chúng làm sáng tỏ một số điều, chẳng hạn như:

Đối với các dự đoán đã đăng ký trước của tôi, chúng tôi có

Nhân tiện, một lý do chính khiến Clojure cải thiện rất nhiều trong đánh giá Pandoc so với đánh giá Zstd là trong đánh giá Zstd, 36/40 chương trình Clojure ở mức trung bình và 5/40 ở mức siêu cấp đã thất bại trong bài kiểm tra vì chuyển đổi byte bị lỗi (throws) trên 128–255 (có lẽ nên sử dụng unchecked-byte?) và họ đã sử dụng chuyển đổi này không phù hợp.

Đó là một kết quả thực tế, ở chỗ, nếu bạn yêu cầu mô hình GPT công khai tốt nhất triển khai Zstd (và có lẽ nếu bạn thực hiện các tác vụ thao tác bit/byte khác mà điều này có thể xảy ra), nó sẽ phát ra mã bị lỗi theo cách cụ thể này. Nếu có các bài kiểm tra phát hiện ra điều này, lỗi sẽ được sửa, nhưng nó vẫn sẽ tốn thời gian và token. Cho dù một ngôn ngữ có làm tốt hay không, luôn có những chi phí như thế này ở khắp mọi nơi (ví dụ: cargo liên tục được gọi với các đối số sai, sau đó ngay lập tức được phát hiện và sửa chữa, nhưng tôi đã nhận thấy vòng lặp này thực sự có thể tiêu tốn một lượng thời gian đáng kể trên các dự án thực tế của tôi trừ khi bạn đưa ra hướng dẫn rõ ràng cho codex về cách gọi cargo, và rõ ràng là điều đó xứng đáng với không gian trong cửa sổ ngữ cảnh).

Dù sao đi nữa, tất cả những điều này là minh họa cho lý do tại sao, nếu ai đó muốn đưa ra một tuyên bố mạnh mẽ về ngôn ngữ hoặc lớp ngôn ngữ nào đặc biệt tốt với LLM, họ sẽ cần phải chạy khá nhiều đánh giá khác nhau. Nếu chúng ta đào sâu vào lý do tại sao bất kỳ điều kiện cụ thể nào đạt được điểm số nhất định, các lỗi gây ra điểm số đó thường là thứ gì đó đặc thù, nơi không phải lúc nào cũng rõ ràng mức độ vấn đề mang tính tổng quát trên các tác vụ hoặc các thiết lập khác nhau. Không có cách nào để nhìn vào điểm số trên một đánh giá hoặc thậm chí năm hoặc mười đánh giá và rút ra kết luận về lập trình nói chung.

Đúng là trong cả đánh giá Zstd và đánh giá Pandoc, chúng ta thấy mối tương quan giữa độ phổ biến của ngôn ngữ và kết quả tích cực (độ chính xác cao hơn, chi phí thấp hơn, thời gian thực hiện thấp hơn) và có vẻ hợp lý khi chúng ta thấy điều này trên các đánh giá khác, nhưng sẽ là sai lầm nếu rút ra kết luận mạnh mẽ về bất kỳ ngôn ngữ cụ thể nào. Tôi đã đưa ra một cảnh báo như thế này khi tôi xem xét tần suất các dự án khác nhau có bản dựng bị hỏng theo dữ liệu GitHub CI, lưu ý rằng có những lý do khác nhau khiến một bản dựng có thể bị hỏng nhiều hay ít hơn giữa các dự án và người ta không nên rút ra kết luận mạnh mẽ vì kết quả giữa các dự án không nhất thiết có thể so sánh được (ví dụ: nếu nhánh chính của một dự án là một loại ứng viên phát hành đã qua kiểm duyệt khác, dự án đó sẽ được mong đợi có tỷ lệ hỏng bản dựng thấp, nhưng điều đó không thể so sánh với một dự án nơi mọi người đang phát triển trực tiếp trên nhánh chính).

Ngay sau đó, một người liên quan đến một trong những ngôn ngữ có điểm số cao (nếu tôi nhớ không nhầm, đó là Martin Odersky và Scala) đã tweet bài viết và trích dẫn thứ hạng cao của ngôn ngữ này như một chiến thắng cho ngôn ngữ đó. Đó là một kết luận không có cơ sở và, do nhiều nguồn phương sai đang diễn ra ở đây, bất kỳ kết luận nào như vậy về một ngôn ngữ đơn lẻ thậm chí còn không có cơ sở hơn ở đây.

Đọc bài gốc

Bài viết được AI dịch và tổng hợp tự động từ Hacker News Nổi bật (buzzing.cc bản dịch tiếng Trung). 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.