Hướng dẫn · 9 phút đọc

Đóng gói một trình thông dịch Python nặng 234MB vào trong ứng dụng Swift: Quy trình phát hành runtime ML tùy chọn của MinuteAI

Tính năng phân tách người nói là một câu chuyện. Việc build, kiểm thử và phát hành runtime Python/PyTorch nặng 743MB đứng sau nó lại là một câu chuyện khác. Đây là quy trình MinuteAI thực hiện trước khi bản tải đó đến được máy Mac của người dùng.

Đóng gói một trình thông dịch Python nặng 234MB vào trong ứng dụng Swift: Quy trình phát hành runtime ML tùy chọn của MinuteAI

Phần dễ kể chỉ dừng lại ở “tính năng”

Chúng tôi từng viết về lý do MinuteAI cung cấp tính năng phân tách người nói (speaker diarization) dưới dạng một bản tải tùy chọn nặng 234MB, thay vì đóng gói pyannote.audio và PyTorch thẳng vào ứng dụng. Bài viết đó nói về kiến trúc: một runtime Python chạy dưới dạng subprocess, được tải về khi cần, và được giữ tách biệt một cách có chủ đích khỏi file nhị phân SwiftUI. Điều bài viết đó chưa nói tới là file 234MB ấy được tạo ra như thế nào ngay từ đầu — phần việc không bao giờ xuất hiện trong một bài giới thiệu tính năng, nhưng phải diễn ra đúng đắn mỗi lần, trước khi dù chỉ một byte của nó đến được máy Mac của người dùng.

MinuteAI giữ artifact phát hành trong một repository riêng, tách khỏi mã nguồn ứng dụng — đây là một sự tách biệt có chủ đích, không phải tình cờ theo lịch sử dự án. Repo ứng dụng sở hữu phần mã Swift chịu trách nhiệm tải, giải nén và vận hành runtime; repo phát hành chỉ tồn tại để lưu trữ file tarball đã build như một asset của GitHub Release, và để ghi lại cách tái tạo nó. Không có gì trong repo phát hành là mã nguồn để đọc hiểu xem tính năng phân tách người nói thực sự làm gì — đó là sổ tay vận hành cho việc build ra thứ mà ứng dụng sẽ tải về.

Build, rồi phải chứng minh nó hoạt động

Sổ tay vận hành là một chuỗi cố định gồm ba giai đoạn, và được thiết kế để chạy tuần tự, bởi một con người, không phải bởi một CI runner: build runtime, kiểm thử nó, rồi mới đóng gói. Giai đoạn build lắp ráp một môi trường Python 3.13.11 độc lập, tự chứa, với PyTorch 2.10.0 và pyannote.audio 4.0.4 được cài đặt sẵn, và được ghi chú là có tính idempotent — an toàn để chạy lại — bởi vì một lần build sạch sẽ tải và biên dịch khoảng 5 đến 10 phút phụ thuộc, đạt khoảng 712MB trước khi đóng gói.

Điều xảy ra giữa lúc build và lúc đóng gói mới là phần đáng để dừng lại xem xét kỹ, vì nó không chỉ là một thủ tục hình thức. Giai đoạn kiểm thử là một cổng chặn gồm năm bước kiểm tra: xác nhận Python và các thư viện ML import được và báo đúng phiên bản mong đợi, tải mô hình phân tách người nói về một thư mục tạm bằng một access token Hugging Face thật, tổng hợp một đoạn âm thanh mô phỏng hai người nói, chạy mô hình trên đoạn âm thanh đó, và xác nhận nó thực sự phát hiện được hai người nói trên ít nhất một đoạn (segment). Bước tổng hợp âm thanh quan trọng hơn vẻ ngoài của nó — sổ tay vận hành nêu rõ đoạn âm thanh kiểm thử phải được tạo ra từ chính công cụ chuyển văn bản thành giọng nói (text-to-speech) của macOS, chứ không phải một tông sóng sin tổng hợp, bởi mô hình này không nhận diện tông tổng hợp là giọng nói một cách đáng tin cậy. Một lần kiểm thử dùng âm thanh giả vẫn có thể trả về kết quả có vẻ sạch trong khi thực chất không chứng minh được điều gì. Không có gì được chuyển sang bước đóng gói cho tới khi cả năm bước kiểm tra đều báo thành công.

Hai lỗi đáng được ghi lại hai lần

Hai lỗi cụ thể được ghi thẳng vào phần xử lý sự cố của sổ tay vận hành, như những điều đã từng xảy ra một lần và có chi phí thấp để ngăn chúng xảy ra lần nữa. Lỗi thứ nhất là một lỗi trong bước cắt tỉa dependency: một phiên bản trước đó của script build đã cắt bớt những package được cho là không dùng đến, và sklearn cùng networkx bị cuốn vào đợt cắt tỉa đó — nhưng thực tế pyannote.audio 4.x lại phụ thuộc vào cả hai, và việc cắt bỏ chúng tạo ra một runtime lỗi ngay khi import với thông báo ModuleNotFoundError: No module named 'networkx'. Đây là kiểu lỗi chỉ lộ ra sau khi đã đóng gói xong, lúc artifact đã bị cắt tỉa đã được build — có lẽ vì vậy mà nó được ghi thành một dòng cố định trong tài liệu xử lý sự cố, thay vì chỉ là một lần sửa rồi thôi.

Lỗi thứ hai đến từ thượng nguồn, không phải từ nội bộ. pyannote.audio 4.x đã thay đổi chính giá trị mà pipeline của nó trả về, bọc kết quả phân tách người nói trong một đối tượng DiarizeOutput thay vì trả trực tiếp đối tượng Annotation mà mã nguồn cũ vẫn mong đợi — khiến mọi đoạn mã viết theo API 3.x bị hỏng, bao gồm cả lệnh gọi itertracks() mà chính script suy luận của MinuteAI phụ thuộc vào. Rắc rối chuyển đổi này không phải là vấn đề riêng của dự án này. Cùng một thay đổi DiarizeOutput đó cũng làm hỏng các công cụ phân tách người nói khác được xây trên pyannote, trong đó có WhisperX, nghiêm trọng tới mức trình theo dõi issue trên GitHub của dự án đó vẫn còn một luồng thảo luận đang mở về nó. Cách MinuteAI sửa lỗi — lấy speaker_diarization ra khỏi đối tượng kết quả trước khi gọi các phương thức cũ — chính là cách mà cộng đồng pyannote nói chung cũng đi tới. Đây là một cách hữu ích để nhìn nhận toàn bộ pipeline này: phần lớn những gì nó phòng ngừa không phải là sự mong manh riêng của MinuteAI, mà là cái giá bình thường của việc phụ thuộc vào một thư viện ML thay đổi nhanh — chỉ là cái giá đó được làm cho hiện rõ ra, thay vì bị âm thầm hấp thụ đi.

Vì sao không dùng thẳng cơ chế của Apple

Cũng đáng để hỏi vì sao một payload tùy chọn nặng 743MB lại được tải về qua một bộ tải tự viết, giao tiếp với GitHub Releases, thay vì dùng hệ thống On-Demand Resources của chính Apple — vốn được tạo ra chính là để tải nội dung sau khi cài đặt. Có hai lý do loại trừ phương án đó. On-Demand Resources giới hạn một resource tag ở mức 512MB sau khi app đã qua app slicing, với 64MB được nêu ra như mức lý tưởng — một môi trường gồm Python, PyTorch và pyannote không thể vừa trong giới hạn đó, còn chưa kể tới sự khác biệt giữa “một tài nguyên media” và “một runtime thực thi có cả cây site-packages riêng của nó”. Bên cạnh đó, Apple cũng đang dần loại bỏ On-Demand Resources qua các phiên bản hệ điều hành gần đây, chuyển sang một framework mới hơn là Background Assets — khiến đây trở thành một mục tiêu đang thu hẹp dần để xây dựng phụ thuộc vào, bất kể vấn đề kích thước. Việc phát hành một tarball tự chứa qua GitHub Releases và tự điều khiển việc tải về từ phía mã ứng dụng giúp tránh được cả hai vấn đề đó — đổi lại là phải tự lo những việc mà hạ tầng của chính Apple lẽ ra đã xử lý miễn phí, chẳng hạn như việc quản lý resource tag.

Sự đánh đổi đó cũng xuất hiện ở một chỗ khác trong pipeline. GitHub Releases là một nơi thuận tiện để lưu trữ một file nhị phân lớn — bản thân file không bị giới hạn dung lượng, và băng thông cũng không bị tính phí ngược lại cho người phát hành — nhưng nó chưa bao giờ được thiết kế như một nền tảng lưu trữ bản phát hành theo đúng nghĩa của một CDN chuyên dụng: không có số liệu phân tích lượt tải, không có cơ chế tích hợp sẵn để phân giải “bản mới nhất” theo chương trình, chỉ có những URL dài và dễ gãy. Mã Swift của MinuteAI xử lý vấn đề thứ hai theo cách trực tiếp nhất — URL tải về được ghim cố định vào một tag phát hành cụ thể ngay trong mã nguồn, thay vì được phân giải lúc chạy — nghĩa là việc nâng cấp runtime lên một phiên bản pyannote hay PyTorch mới đòi hỏi một thay đổi mã nguồn ứng dụng và một lần build lại, chứ không phải một thao tác đổi cấu hình phía máy chủ. Dù runtime và ứng dụng đã được tách rời khỏi chu kỳ phát hành của nhau, đây vẫn là một ràng buộc thực tế đối với tần suất runtime phân tách người nói có thể được cập nhật độc lập.

Phần không xuất hiện trên sơ đồ kiến trúc

Không điều gì ở trên làm thay đổi những gì đã thực sự được đưa vào tính năng phân tách người nói. Đây là một luận điểm khác: rằng “tải tùy chọn thay vì đóng gói sẵn” nghe như một quyết định kiến trúc gói gọn trong một dòng, nhưng phía sau nó là một chuỗi công việc vận hành dài hơn nhiều — một script build phải luôn giữ tính idempotent, một cổng kiểm thử không được tin vào một bộ tạo tông mà phải tạo ra giọng nói thật, hai lỗi cụ thể đã từng xảy ra một lần, và một cơ chế phân phối được chọn một phần vì nó không vừa với công cụ chuẩn của Apple, một phần vì nó vừa với công cụ chuẩn của GitHub. Ai cũng có thể quyết định biến một dependency ML thành tùy chọn. Việc khiến quyết định đó có thể lặp lại một cách an toàn mỗi lần mô hình hoặc runtime cần cập nhật mới là phần công việc ít được nhìn thấy hơn.


MinuteAI có sẵn trên App Store cho macOS và iOS, hoặc bạn có thể tìm hiểu thêm tại getminute.app. Có câu hỏi về cách nó phù hợp với quy trình làm việc của bạn? Hãy liên hệ qua [email protected].

Xem sản phẩm của chúng tôi

Từ MinuteAI đến AgentKits — khám phá các sản phẩm và dự án chúng tôi đã triển khai.

Xem danh mục

Bài viết liên quan

Hướng dẫn

Apple và Google Vừa Bắt Đầu Ghi Chép Cuộc Gọi Miễn Phí. Không Bên Nào Chạm Đến Tab Zoom

iOS 26 và Pixel Recorder của Google giờ đây thực hiện phiên âm và tóm tắt cuộc gọi ngay trên thiết bị, miễn phí. Đây là ranh giới cụ thể mà cả hai nền tảng đều chưa vượt qua — và đó chính xác là nơi Tiện ích Chrome của MinuteAI hoạt động.

Hướng dẫn

Cảnh Báo 12 Nghìn Tỷ Yên Của Nhật Bản Không Phải Câu Chuyện Về Thiếu Kỹ Sư COBOL. Đó Là Câu Chuyện Về Bảng Mã Ký Tự.

Cảnh báo 'vách đá 2025' của METI thường được hiểu là câu chuyện về nhân sự nghỉ hưu và bài toán chi phí thay-thế-toàn-bộ. Nhưng lỗi thực sự làm hỏng các dự án di trú lại nhỏ hơn và dễ bị bỏ qua hơn: EBCDIC và Shift-JIS thậm chí không thống nhất chữ cái hay chữ số được sắp xếp trước. Vì sao Legacy Dragon xử lý bảng mã ký tự ngay ở tầng phân tích cú pháp, chứ không phải như một bước tiền xử lý gắn thêm sau này.

Hướng dẫn

Không Trang Giá, Không Đồng Hồ Tính Phí API: Kinh Tế Học Đằng Sau Các Công Cụ Miễn Phí Của PrivateAI

AI trên đám mây được tính giá theo token vì mỗi truy vấn đều tốn chi phí điện toán thực sự cho nhà cung cấp. Các công cụ chạy trên thiết bị không có hóa đơn đó. Đây là những gì sự khác biệt về cấu trúc này thực sự mang lại — và không mang lại — cho một sản phẩm như PrivateAI.