1 điểm bởi GN⁺ 1 giờ trước | Chưa có bình luận nào. | Chia sẻ qua WhatsApp
  • Các nhà phát triển Anthropic gần đây đã di trú 10 gói mã có quy mô từ hàng chục nghìn đến hàng trăm nghìn dòng trong vòng một tháng bằng Claude Fable 5, Claude Opus 4.8 và workflow động, đồng thời cải thiện quy trình lặp tạo ra mã thay vì sửa từng đoạn mã riêng lẻ
  • Việc di trú Bun từ Zig sang Rust đã tạo ra 1 triệu dòng trong chưa đầy 2 tuần và vượt qua 100% bộ test hiện có trước khi merge; một dự án Python→TypeScript đã chuyển 165.000 dòng trong một cuối tuần bằng cách dùng hàng trăm agent, cổng kiểm soát 8 bước và 3 vòng rà soát đối kháng
  • Di trú quy mô lớn phù hợp để xây dựng vòng lặp kiểm chứng khách quan vì có thể song song hóa công việc, mã hiện có đóng vai trò đặc tả và đáp án, còn lỗi biên dịch và test tự động tạo ra hàng đợi công việc tiếp theo
  • Quy trình được thực hiện theo từng bước từ chuẩn bị tiêu chí đánh giá, lập rulebook, bản đồ phụ thuộc và danh sách khác biệt, stress test quy tắc, dịch toàn bộ, biên dịch, chạy và so sánh hành vi; với lỗi lặp lại thì không sửa từng file mà sửa quy tắc cấp cao rồi tạo lại
  • Chi phí vẫn ở mức hàng chục nghìn đến hàng trăm nghìn USD trở lên, nhưng có thể bỏ branch thất bại rồi thử lại; đợt di trú Bun tiêu tốn khoảng 165.000 USD theo giá API và đạt mức giảm dùng bộ nhớ, giảm 19% kích thước binary, cải thiện 2~5% hiệu năng trên workload thực tế

Cải thiện vòng lặp tạo sinh thay vì sửa mã

  • AI code migration là cách các agent chuyển một codebase production sang ngôn ngữ hoặc framework mới
    • Thay vì kỹ sư tự dịch từng file, họ viết quy tắc di trú và vòng lặp kiểm chứng
    • Agent lặp lại việc dịch, biên dịch và test cho đến khi hành vi của mã mới khớp với bản gốc
    • Có thể rút ngắn các dự án từng mất nhiều năm xuống còn vài tuần
  • Tại Anthropic, họ đã di trú 10 gói mã có quy mô từ hàng chục nghìn đến hàng trăm nghìn dòng trong một tháng bằng Claude Fable 5, Claude Opus 4.8 và workflow động
  • Nguyên tắc vận hành cốt lõi là không trực tiếp vá mã đã sinh ra, mà chỉnh sửa vòng lặp đã tạo ra đoạn mã đó

Các ví dụ di trú thực tế

  • Di trú Bun từ Zig sang Rust

    • Jarred Sumner đã di trú Bun từ Zig sang Rust bằng Claude Code
    • Tạo ra 1 triệu dòng mã trong chưa đầy 2 tuần
    • Vượt qua 100% bộ test hiện có của Bun trong CI trước khi merge
    • 19 lỗi hồi quy được phát hiện sau khi merge đều đã được sửa
    • Bản port Rust được đưa vào Claude Code trong tháng 6
    • Bun có hơn 10 triệu lượt tải mỗi tháng và cũng được dùng rộng rãi bên trong Claude Code
  • Di trú từ Python sang TypeScript

    • Mike Krieger đã di trú một codebase Python thành 165.000 dòng TypeScript trong một cuối tuần
    • Dùng hàng trăm agent, cổng kiểm soát 8 bước và 3 vòng rà soát đối kháng
    • Thực hiện bước kiểm tra tương đương cuối cùng bằng cách so sánh đầu ra của mọi lệnh với bản Python gốc
    • Lặp lại việc bỏ toàn bộ kết quả di trú rồi chỉnh sửa quy tắc và workflow, cuối cùng chọn kết quả ở lần chạy thứ ba

Khi nào nên xem xét lại việc chuyển ngôn ngữ

  • Có thể cân nhắc di trú nếu môi trường kỹ thuật đã thay đổi từ sau lần phát triển đầu tiên, khiến các đánh đổi cũ trở thành ràng buộc, xuất hiện cách tiếp cận tốt hơn hoặc hệ sinh thái ban đầu đã thu hẹp
  • Zig mang lại hiệu năng cấp C và sự đơn giản, phù hợp với giai đoạn đầu khi Bun được phát triển bởi một người, nhưng sự đơn giản đó có những đánh đổi đã được biết đến
  • Trước đây, việc chuyển ngôn ngữ đòi hỏi phải dừng roadmap và dồn nguồn lực từ nhiều quý
    • Có thể phải duy trì song song hai codebase trong nhiều quý hoặc nhiều năm
    • Nếu mức độ khớp hành vi cuối cùng chỉ đạt 90% thì có thể tạo ra vấn đề bảo trì còn lớn hơn trước khi bắt đầu
    • Giờ đây đã có lựa chọn xóa branch thất bại và chạy lại
  • Việc di trú 1 triệu dòng không còn mặc định đòi hỏi 3~4 triệu USD chi phí kỹ thuật trong 4 năm, nhưng vẫn có thể tốn hàng chục nghìn đến hàng trăm nghìn USD trở lên
    • Đợt di trú Bun tiêu thụ 5,9 tỷ input token không cache và 690 triệu output token
    • Chi phí theo giá API là khoảng 165.000 USD
    • Ở bản port của Mike, phần cốt lõi dùng 27 triệu token
  • Cơ sở kinh doanh cho việc di trú không còn cần phải ở mức sống còn; chỉ riêng một năm sửa lỗi bộ nhớ lặp đi lặp lại hoặc một nút thắt mãn tính cũng có thể đủ để biện minh
  • Gỡ nút thắt build của Python

    • Công cụ nội bộ của Mike được phân phối cho người dùng dưới dạng một binary duy nhất, nhưng việc tạo binary theo từng nền tảng bằng toolchain Python mất khoảng 8 phút
    • Với toàn bộ build matrix, mỗi bản phát hành phải chờ khoảng 30 phút
    • Sau khi chuyển sang TypeScript, thời gian biên dịch giảm còn khoảng 2 giây, khởi động binary nhanh hơn 6 lần và cả pipeline triển khai riêng cũng bị loại bỏ

Vì sao AI agent phù hợp với di trú

  • Có thể xử lý song song
    • Có thể chia thành hàng nghìn đơn vị độc lập như file hay crate để nhiều agent xử lý đồng thời
  • Mã hiện có đóng vai trò đặc tả rõ ràng và toàn diện
    • Cũng có thể dùng làm tài liệu tham chiếu cốt lõi để tạo chỉ dẫn cho agent dịch
  • Bộ test đóng vai trò trọng tài tích hợp sẵn
    • Nếu việc kiểm chứng mang tính khách quan thì mô hình có thể lặp công việc nhiều ngày dựa trên đáp án đúng mà không cần con người liên tục can thiệp chất lượng
  • Vì lỗi biên dịch hoặc test tự động trở thành hạng mục công việc tiếp theo nên giảm nhu cầu tự viết hàng đợi công việc riêng
  • Có thể đưa tính nhất quán và xử lý ngoại lệ vào vòng lặp
    • Reviewer liên kết từng vấn đề với quy tắc bị vi phạm
    • Cách giải quyết ngoại lệ được chuyển thành quy tắc để mọi agent sau đó đều tuân theo
    • Thay vì sai khác hành vi âm thầm, vi phạm quy tắc trở thành hạng mục công việc rõ ràng
  • Fable và Opus 4.8 được dùng để ủy quyền, chỉ huy, xác minh công việc song song của các sub-agent và tìm nhiều con đường để đạt mục tiêu
  • Tối ưu lượng token sử dụng bằng mô hình tư vấn kết hợp nhiều cấp model

Điều kiện tiên quyết: đánh giá tính tương đương giữa bản gốc và bản port

  • Trước khi bắt đầu di trú, cần một trọng tài mạnh để đánh giá mã nguồn gốc và mã đích theo cùng một tiêu chuẩn
    • Không có trọng tài thì cũng không có tiêu chí thành công hay điều kiện dừng
    • Các test phụ thuộc vào hàm nội bộ của ngôn ngữ gốc có thể không chạy nguyên vẹn trên mã đích
  • Phân loại test hiện có thành test có thể biểu diễn dưới dạng lời gọi từ bên ngoài và test phụ thuộc vào triển khai nội bộ sẽ không được port
  • Viết lại các test hành vi bên ngoài thành các assertion có thể chạy trên cả bản gốc lẫn bản port
    • Agent đối kháng xác minh xem các assertion có bị làm yếu đi trong quá trình viết lại hay không
  • Chạy trọng tài trên mã nguồn gốc để xác nhận là pass, sau đó kiểm tra xem nó có fail trên mã cố tình bị làm hỏng hay không
  • Jarred có một bộ test lớn được viết bằng TypeScript, tức ngôn ngữ thứ ba
  • Mike tạo một equivalence harness với 7 kịch bản sử dụng thực tế và coi mọi thay đổi hành vi là bug cần sửa

Bước 1: rulebook, bản đồ phụ thuộc và danh sách khác biệt

  • Các đầu ra nền tảng không phải chỉ là kết quả dịch đơn giản, mà là danh sách nơi cần refactor, rulebook cho cách dịch và bản đồ phụ thuộc để quyết định thứ tự công việc
  • Thứ tự soạn thảo là quan trọng
    • Trước hết phải xác định giá trị mặc định của rulebook thì mới có thể định nghĩa các mục không xử lý được bằng mặc định đó thành danh sách khác biệt
    • Rulebook và danh sách khác biệt được kiểm chứng cùng nhau qua một cuộc audit chung
  • Rulebook

    • Hình thức của rulebook thay đổi tùy theo việc mã mới giữ nguyên cấu trúc cũ hay thiết kế lại hoàn toàn
    • Nếu giữ nguyên cấu trúc như Jarred, trọng tâm sẽ là bảng đối chiếu giữa kiểu dữ liệu và thành ngữ của hai ngôn ngữ, còn thành phần khó dịch thì tham chiếu danh sách khác biệt
    • Nếu thiết kế lại như Mike, rulebook đóng vai trò tài liệu thiết kế
    • Jarred trò chuyện với Claude để tạo chính sách cho từng vùng mơ hồ và cấu hình 8 sub-agent để mỗi agent rà soát một trong 8 nhóm lỗi dự kiến
  • Bản đồ phụ thuộc

    • Trong di trú song song, cần nắm được phụ thuộc giữa các file để quyết định file nào chuyển trước và file nào đưa vào cùng một batch
    • Với mã legacy không có manifest tường minh và các codebase như C/C++, Python, cần tự phát hiện và lập bản đồ phụ thuộc
    • Agent Claude Code có thể tạo bản đồ bằng vòng lặp viết, chạy, rà soát và chỉnh sửa các script có tính quyết định
    • Có thể xem ví dụ tổng quát trong prompt lập bản đồ phụ thuộc
  • Danh sách khác biệt ngôn ngữ và reviewer hoài nghi

    • Danh sách khác biệt ghi lại những tri thức có trong mã cũ một cách ngầm định nhưng phải biểu đạt tường minh ở ngôn ngữ đích
    • Với Zig→Rust, khác biệt chính là cách quản lý bộ nhớ
    • Trong Zig, việc caller phải giải phóng buffer có thể chỉ nằm trong comment; dù quên giải phóng vẫn biên dịch được và chỉ phát hiện rò rỉ khi chạy
    • Trong Rust, quyền sở hữu được chuyển cho caller, bộ nhớ được giải phóng tự động, còn việc dùng sau khi chuyển hoặc giải phóng hai lần sẽ không biên dịch
    • Với Python→TypeScript, khác biệt chính là interface và contract
    • Python không cần khai báo hình dạng của object nhận vào hay giá trị trả về
    • Trong TypeScript, phải viết contract cho method, đối số và dạng trả về thì mới biên dịch được
    • Jarred liệt kê khác biệt trước khi dịch, còn Mike dịch trước rồi mới tạo danh sách trong quá trình audit, nên cả hai cách đều có thể dùng tùy dự án
    • Có thể xem ví dụ tổng quát trong prompt tạo danh sách khác biệt

Bước 2: stress test quy tắc

  • Trước khi di trú toàn bộ, họ dùng một đợt thử nghiệm nhỏ để lắc thử rulebook và tìm vấn đề
  • Jarred so sánh ba tác vụ agent
    • Agent thứ nhất dịch 3 file theo rulebook
    • Agent thứ hai dịch lượng tương tự như một kỹ sư Rust giàu kinh nghiệm
    • Agent thứ ba viết các quy tắc dịch mới dựa trên khác biệt giữa hai kết quả
  • Nhờ vậy, họ phát hiện 2 vấn đề nghiêm trọng trước khi nó lan ra toàn bộ 1.448 file
  • Cách này chỉ hoạt động với di trú giữ nguyên cấu trúc, nơi có thể so sánh từng dòng giữa hai bản dịch của cùng một file
  • Trong trường hợp thiết kế lại như Mike, reviewer đối kháng sẽ tấn công tài liệu thiết kế và việc xác minh phải diễn ra bằng các lần chạy end-to-end có thể bỏ đi
  • Mọi file dịch trong đợt thử đều phải bị loại bỏ; mục tiêu là cải thiện quy tắc chứ không phải tích lũy tiến độ mã nguồn
  • Có thể xem ví dụ tác vụ tổng quát trong prompt stress test

Bước 3: dịch toàn bộ mã

  • Từ đây, mọi giai đoạn đều dùng vòng lặp đa agent theo mô hình triển khai→rà soát→sửa
  • Việc triển khai hàng loạt có thể giao cho model nhỏ, còn rà soát do model lớn đảm nhiệm
    • Mike dùng 12 sub-agent Claude Sonnet khi song song hóa phần di trú cốt lõi
  • Hàng đợi công việc được quản lý một cách cơ học
    • Script batch xác định trạng thái hoàn tất dựa trên sự tồn tại của file đã dịch trên đĩa
    • Chia số file còn lại thành các batch cho agent triển khai
    • Mỗi lần chạy đều dựng lại hàng đợi từ trạng thái trên đĩa nên mặc định có thể dừng rồi tiếp tục
  • Nếu agent quá thận trọng và chỉ xử lý ít một, có thể ra chỉ thị trực diện hơn kèm ngữ cảnh rằng compiler sẽ bắt lỗi ở bước tiếp theo
  • Các mục chưa thể xử lý chắc chắn được đánh dấu bằng // TODO(port): <reason> rồi giải quyết ở bước 4
  • Danh sách việc sau đó được tự động tạo ra từ lỗi biên dịch, crash trong smoke test và lỗi test
  • Rà soát đối kháng và cập nhật quy tắc

    • Hai reviewer đối kháng ở các ngữ cảnh độc lập sẽ đánh giá kết quả triển khai, nếu bất đồng thì agent thứ ba sẽ phân xử
    • Nếu cùng một lỗi lặp lại trên nhiều file thì không sửa riêng từng file
    • Thay vào đó, thêm một câu vào rulebook rồi sinh lại các batch bị ảnh hưởng
    • Ngay cả ở giai đoạn dịch, rulebook vẫn tiếp tục mở rộng và mã lệch quy tắc không được vá tay
  • Vị trí đặt compiler trong batch

    • Nếu thời gian biên dịch ngắn, có thể đưa nó vào trong vòng lặp dịch
    • Mike chạy biên dịch TypeScript ở mọi vòng vì mỗi đơn vị chỉ mất vài giây
    • Nếu biên dịch lâu, sẽ dời sang bước kế tiếp
    • Jarred cấm dùng compiler trong vòng lặp dịch vì cargo mất vài phút để chạy
    • Từ giai đoạn này prompt ngắn hơn, ví dụ có thể xem ở prompt khởi động dịch

Bước 4~6: biên dịch, chạy và khớp hành vi

  • Ba bước này dùng chung một cấu trúc vòng lặp, và càng về sau càng cần ít phán đoán của con người hơn
  • Tùy ngôn ngữ và quy mô dự án, bước biên dịch có thể được hấp thụ vào toàn bộ giai đoạn dịch
  • Bước 4: biên dịch

    • Jarred cấu hình để một script điều phối chạy compiler một lần trên toàn bộ workspace
    • Các agent sửa lỗi xử lý danh sách lỗi song song, qua rà soát đối kháng rồi build lại, lặp quá trình này liên tục
    • Việc rà soát danh sách lỗi dùng để phát hiện vấn đề mang tính hệ thống chứ không phải từng lỗi riêng lẻ
    • Sau khi sửa import vòng mà cơ chế biên dịch trì hoãn của Zig từng cho phép, hàng nghìn lỗi module Rust đã xuất hiện
    • Họ thêm vào vòng lặp logic phân loại xem phụ thuộc nào cần xóa hoặc di chuyển, hay cần cấu trúc lại ranh giới ở đâu
  • Bước 5: chạy và smoke test

    • Các crash trong smoke test đóng vai trò đáp án cơ học giống như danh sách lỗi biên dịch
    • Vấn đề không được xử lý từng cái một mà được gom theo nhóm nguyên nhân gốc rồi để sub-agent đối kháng rà soát
  • Bước 6: so sánh hành vi với bản gốc

    • Sau khi hoàn tất dịch, biên dịch và smoke test, mã được chia nhỏ rồi chạy bộ test đã chuẩn bị từ trước trên cả bản gốc lẫn bản port
    • Agent sửa lỗi xem đồng thời các test thất bại và cả hai codebase, còn reviewer đối kháng xác minh kết quả sửa
    • Chỉ build daemon mới được phép build lại binary
    • Agent sửa lỗi viết patch, còn daemon gom các patch rồi chỉ rebuild một lần
    • Sau đó chạy lại các test bị ảnh hưởng và trả kết quả
    • Công việc được tuần tự hóa để nhiều agent không cùng lúc chạy các bản build đắt đỏ
    • Nếu cùng một lỗi xuất hiện lặp lại ở nhiều test, họ sửa quy tắc cấp cao đã tạo ra bug đó rồi chỉ sinh lại các file bị ảnh hưởng bởi quy tắc ấy
  • Khi không có bộ test

    • Mike dùng Claude để tạo một script nhỏ chạy 7 kịch bản thực tế trên mã Python gốc và bản port mới rồi so sánh kết quả
    • Mỗi kịch bản thất bại được giao cho một agent sửa lỗi riêng, lặp lại cho đến khi cả 7 đều pass
    • Claude còn tự thiết kế bộ test end-to-end của riêng nó và chạy tự động suốt đêm
    • Quá trình sửa lỗi rồi chạy lại được lặp trong bốn đêm
    • Danh sách kịch bản chuẩn bị trước còn giúp phát hiện cả những vấn đề usability nhỏ khó dự đoán
    • Ngay cả khi không có test sẵn, Claude vẫn có thể tạo trọng tài bằng cách dùng codebase gốc làm đáp án

Các nguyên tắc vận hành rút ra từ nhiều lần chạy lặp

  • Thay vì làm y hệt hướng dẫn, nên lập kế hoạch di trú cùng Claude phù hợp với đặc điểm dự án rồi mới bắt đầu
  • Hãy để các agent sửa lỗi xử lý thất bại đơn lẻ, còn con người tập trung vào mẫu lỗi lặp đi lặp lại
  • Rà soát phải mang tính đối kháng, còn xác minh phải mang tính cơ học
    • Rà soát đối kháng hữu ích cho công việc kéo dài và có thể đáng để chấp nhận tiêu tốn thêm token
    • Dùng compiler, diff, bộ test và các script làm trọng tài cuối cùng
  • Không dùng model lớn nhất cho mọi công việc
    • Model nhỏ phù hợp để song song hóa triển khai hàng loạt
    • Model lớn nhất nên tập trung vào rà soát và viết quy tắc để các agent khác làm theo
  • Thời gian của con người nên được đầu tư sớm vào rulebook và stress test; các bước sau chủ yếu là công việc tiêu hao hàng đợi
  • Trạng thái hoàn tất phải được xác định cơ học, như “file đầu ra tồn tại trên đĩa”, và hàng đợi phải có thể tiếp tục lại

Kết quả và giới hạn của đợt di trú Bun

  • Bản port Rust của Bun đang chạy trong production, nhưng khoảng 4% mã Rust nằm trong các khối unsafe
    • Phần lớn là thao tác con trỏ một dòng ở ranh giới C/C++
  • Mọi rò rỉ bộ nhớ có thể phát hiện bằng công cụ đều đã được sửa
    • Trong benchmark lặp build 2.000 lần, mức dùng bộ nhớ giảm từ 6.745MB xuống còn 609MB
  • Kích thước binary trên Linux và Windows giảm 19%
  • Tối ưu xuyên ngôn ngữ giúp cải thiện 2~5% hiệu năng ở các workload thực tế như dịch vụ HTTP, next build, tsc
  • Với di trú quy mô lớn, điều cần rà soát không phải từng dòng mã sinh ra mà là kết quả do vòng lặp tạo ra và các mẫu lặp

Tài liệu liên quan

Chưa có bình luận nào.

Chưa có bình luận nào.