Chạy LLM on-device trong app Capacitor: iOS 26 và Gemini Nano
iOS 26 mở Apple Intelligence qua Foundation Models, Android chạy Gemini Nano qua ML Kit. Một API TypeScript cho cả hai, và các giới hạn cần biết trước.
Thêm tính năng AI vào app mobile lâu nay đồng nghĩa với việc gửi text của người dùng lên server: chọn nhà cung cấp, tìm chỗ giấu API key không nằm trong bundle, và chấp nhận một hoá đơn tăng theo từng token. Điện thoại hiện nay mở ra đường khác — và đường đó vừa có một lớp API chung cho cả hai nền tảng.
iOS 26 đưa Apple Intelligence ra sau framework Foundation Models, còn các máy Android đời mới chạy Gemini Nano qua ML Kit và AICore. Cả hai model đều thuộc về hệ điều hành, nên prompt, lịch sử hội thoại và câu trả lời đều nằm lại trên máy. Không có key nào phải bảo vệ, không có chi phí theo request.
Được gì, mất gì
Ngoài quyền riêng tư, giữ model trên máy kéo theo ba thứ: không mất tiền theo token vì inference chạy trên phần cứng người dùng đã trả tiền; sinh text được cả khi ở chế độ máy bay hoặc mất sóng; và app không phình thêm dung lượng, vì model là một phần của OS.
Đổi lại là giới hạn thật. Context dùng được chỉ khoảng 4.000 token trên cả hai nền tảng, và yêu cầu phần cứng loại phần lớn thiết bị đang lưu hành hiện nay. Đủ cho tóm tắt một ghi chú hay viết lại một đoạn văn — không đủ để suy luận trên một tài liệu dài.
Thiết bị nào chạy được
| Nền tảng | Model | Yêu cầu |
|---|---|---|
| Android | Gemini Nano (ML Kit GenAI Prompt qua AICore) | API level 26+, máy hỗ trợ Gemini Nano |
| iOS | Apple Intelligence Foundation Models | iOS 26+, iPhone 15 Pro trở lên, build bằng Xcode 26 |
| Web | không có | getAvailability() trả về unavailable |
Hai chi tiết ảnh hưởng trực tiếp tới kế hoạch phát hành. ML Kit GenAI Prompt SDK của Google vẫn đang ở giai đoạn beta, nên plugin ghim com.google.mlkit:genai-prompt ở bản 1.0.0-beta2 và cho phép ghi đè bằng biến Gradle $mlkitGenaiPromptVersion. Và Gemini Nano hiện chỉ chạy trên rất ít máy — theo bài viết là khoảng dòng Pixel 9 và Galaxy S25 — nên bước kiểm tra khả dụng là bắt buộc, không phải tuỳ chọn.
Lệnh đầu tiên luôn là getAvailability()
getAvailability() không bao giờ reject. Trên nền tảng hoặc phiên bản OS không có model hệ thống, nó resolve với unavailable, nên bạn gọi được ngay lúc khởi động và rẽ nhánh UI từ đó. Có bảy trạng thái, mỗi trạng thái ứng với một hành động khác nhau: available (bắt đầu sinh text), device-not-eligible trên iOS (rơi về model cloud), downloadable trên Android (gọi downloadModel()), downloading (hiện tiến trình), not-enabled trên iOS (chỉ người dùng vào Settings), not-ready (thử lại sau) và unavailable (ẩn tính năng).
Trạng thái thay đổi ngay trong lúc app đang chạy — người dùng có thể bật Apple Intelligence trong Settings, hoặc bản tải Gemini Nano vừa xong — nên bắt sự kiện availabilityChange và switch đủ cả bảy trạng thái, đừng chỉ xử lý bốn cái Android báo hay năm cái iOS báo.
Giới hạn siết ở đâu
Trên Android, maxOutputTokens tối đa 4096 và temperature nằm trong khoảng 0.0–1.0; iOS không có giới hạn output nào được ghi rõ và cho phép temperature trên 1.0. Muốn một code path chung cho cả hai thì lấy khoảng của Android làm chuẩn.
Thứ cần thiết kế xung quanh là context: hội thoại đủ dài sẽ tràn, generation reject với GENERATION_FAILED, và cách chữa là mở chat mới seed bằng một bản tóm tắt sinh ra khi còn chỗ. Vài hành vi khác nên biết trước: mỗi chat chỉ chạy một generation tại một thời điểm, prompt thứ hai bị reject với GENERATION_IN_PROGRESS chứ không xếp hàng; lịch sử chat không sống qua lần khởi động lại app; và cancelGeneration() dừng ngay trên iOS nhưng chỉ best-effort trên Android, nên vẫn có thể có thêm vài textChunk về sau khi huỷ.
Nên làm gì tiếp
Lưu ý về nguồn: bài hướng dẫn này do chính đội Capawesome — tác giả plugin Capacitor LLM — công bố, và plugin phát hành kèm gói thuê bao trả phí Capawesome Insiders. Nếu bạn chỉ ship Android, bài viết cho biết có plugin Capacitor ML Kit GenAI Prompt miễn phí bọc thẳng API của Google.
Thực tế nhất cho một đội ở Việt Nam: coi on-device là nhánh tăng cường, không phải nhánh mặc định. Gắn getAvailability() vào màn hình khởi động, đo xem bao nhiêu phần trăm thiết bị người dùng thật sự trả về available, rồi mới quyết định có viết đường fallback lên cloud hay không.
Không spam, hủy đăng ký bất kỳ lúc nào.