תארו לעצמכם: עוזר AI באפליקציה ניידת עובד במצב לא מקוון, כל הנתונים נשארים במכשיר. אין העברות לשרת, אין השהיית רשת. מודלי LLM בענן דורשים קישוריות קבועה, מעבירים נתונים רגישים וגורמים לעיכובים. עבור רפואה או פיננסים, זה בלתי מתקבל על הדעת. פתרון במכשיר פותר את הבעיות הללו אך דורש אינטגרציה קפדנית ספציפית לפלטפורמה. אנו משלבים את Llama.cpp — ספריית הסקת LLM על CPU/GPU — באפליקציות iOS ו-Android. בואו נצלול לפרטים הטכניים: מבחירת מודל ועד ניהול תרמי.
איך לבחור מודל לעוזר לא מקוון?
Llama.cpp עובד עם מודלים בפורמט GGUF. אפשרויות פופולריות לנייד:
| מודל | קוונטיזציה | גודל | RAM | מהירות (iPhone 14) |
|---|---|---|---|---|
| Llama-3.2-1B | Q4_K_M | 0.8 GB | ~1.2 GB | 25–35 t/s |
| Llama-3.2-3B | Q4_K_M | 2.0 GB | ~2.5 GB | 10–15 t/s |
| Phi-3-mini-4k | Q4_K_M | 2.2 GB | ~2.8 GB | 8–12 t/s |
| Gemma-2-2B | Q4_K_M | 1.6 GB | ~2.0 GB | 12–18 t/s |
| Qwen2.5-1.5B | Q4_K_M | 1.0 GB | ~1.4 GB | 20–28 t/s |
ב-iPhone SE דור 2 (3 GB RAM), Llama-3.2-3B Q4 פועל בגבול היכולת — OOM אפשרי עם הקשרים ארוכים. בחירה בטוחה למגוון רחב של מכשירים היא מודלים עד 1.5–2 GB. בפרויקט אחד לאפליקציה פיננסית, בחרנו ב-Llama-3.2-1B Q4_K_M, ששמר על שימוש בזיכרון מתחת ל-1 GB ב-iPhone SE. מהירות היצירה הייתה 25–30 t/s, מספיק לתשובות לשאלות. הגבלת תרמית מוזערה על ידי הגבלת ההקשר ל-1024 טוקנים.
בעיות ופתרונות ל-LLM במכשיר
| בעיה | פתרון |
|---|---|
| OOM בהקשר גדול | הגבל n_ctx ל-1024–2048 טוקנים |
| הגבלה תרמית | נטר את thermalState, הפסק בין יצירות |
| קובץ GGUF פגום | אמת SHA256 לאחר הורדה |
| מהירות נמוכה במכשירים ישנים | השתמש במודלים של 1B עם קוונטיזציה Q4 |
איך לבנות את llama.cpp ל-iOS?
# Клонируем репозиторий
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
# Сборка через CMake для iOS
cmake -B build-ios \
-DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \
-DPLATFORM=OS64 \ # arm64 only
-DLLAMA_METAL=ON \ # Metal GPU ускорение
-DLLAMA_STATIC=ON
cmake --build build-ios --config Release
תוצאה — ספרייה סטטית # Клонируем репозиторий git clone https://github.com/ggerganov/llama.cpp cd llama.cpp # Сборка через CMake для iOS cmake -B build-ios \ -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \ -DPLATFORM=OS64 \ # arm64 only -DLLAMA_METAL=ON \ # Metal GPU ускорение -DLLAMA_STATIC=ON cmake --build build-ios --config Release . צור Swift Package עם כותרת C-bridging:
// llama_bridge.h
#include "llama.h"
// Обёртки для Swift-дружественного API
void* llama_create_context(const char* model_path, int n_ctx, int n_gpu_layers);
const char* llama_generate_token(void* ctx, const char* prompt);
void llama_free_context(void* ctx);
libllama.a — מספר השכבות המועברות ל-GPU של Metal. ערך של // llama_bridge.h #include "llama.h" // Обёртки для Swift-дружественного API void* llama_create_context(const char* model_path, int n_ctx, int n_gpu_layers); const char* llama_generate_token(void* ctx, const char* prompt); void llama_free_context(void* ctx); פירושו כל השכבות על GPU. ב-iPhone 14 עם 6 GB זיכרון מאוחד — השתמש ב-n_gpu_layers. במכשירים עם 3 GB — נסה: יותר מדי שכבות על GPU גורמות ל-OOM.
עטיפת Swift לסטרימינג טוקנים
import Foundation
actor LlamaSession {
private var context: OpaquePointer?
private var model: OpaquePointer?
func load(modelPath: String, contextSize: Int32 = 2048, gpuLayers: Int32 = -1) throws {
var params = llama_model_default_params()
params.n_gpu_layers = gpuLayers
model = llama_load_model_from_file(modelPath, params)
guard model != nil else {
throw LlamaError.modelLoadFailed
}
var ctxParams = llama_context_default_params()
ctxParams.n_ctx = UInt32(contextSize)
ctxParams.n_batch = 512
context = llama_new_context_with_model(model, ctxParams)
}
func generate(prompt: String) -> AsyncThrowingStream<String, Error> {
AsyncThrowingStream { continuation in
Task.detached(priority: .userInitiated) {
// Токенизация
var tokens = [llama_token](repeating: 0, count: 4096)
let nTokens = llama_tokenize(self.model, prompt, Int32(prompt.utf8.count), &tokens, 4096, true, false)
// Инференс — по одному токену
for i in 0..<nTokens {
llama_batch_add(&batch, tokens[Int(i)], llama_pos(i), [0], false)
}
while true {
llama_decode(self.context, batch)
let nextToken = llama_sample_token_greedy(self.context, &candidates)
if nextToken == llama_token_eos(self.model) {
break
}
// Конвертация токена в строку
var buf = [Int8](repeating: 0, count: 64)
llama_token_to_piece(self.model, nextToken, &buf, 64, 0, true)
let piece = String(cString: buf)
continuation.yield(piece)
}
continuation.finish()
}
}
}
}סטרימינג טוקנים דרך -1 — משתמשים רואים טקסט בזמן יצירתו, ללא המתנה לתשובה המלאה. זה קריטי ל-UX: 10 טוקנים בשנייה מרגישים מקובלים כשהטקסט מופיע בהדרגה.
למה אילוצים תרמיים קריטיים?
Llama.cpp ב-iPhone מחמם את המכשיר במהלך יצירה ממושכת. הגבלת iOS: כשהמערכת מתחממת, היא מפחיתה את מהירות השעון, ומהירות היצירה יורדת מ-25 t/s ל-8–10 t/s. זו לא תקלה — זו התנהגות מערכת.
פתרון מעשי: הגבל את ההקשר המקסימלי (-1) ל-1024–2048 למפגשים קצרים. הפסק בין בקשות. נטר את import Foundation actor LlamaSession { private var context: OpaquePointer? private var model: OpaquePointer? func load(modelPath: String, contextSize: Int32 = 2048, gpuLayers: Int32 = -1) throws { var params = llama_model_default_params() params.n_gpu_layers = gpuLayers model = llama_load_model_from_file(modelPath, params) guard model != nil else { throw LlamaError.modelLoadFailed } var ctxParams = llama_context_default_params() ctxParams.n_ctx = UInt32(contextSize) ctxParams.n_batch = 512 context = llama_new_context_with_model(model, ctxParams) } func generate(prompt: String) -> AsyncThrowingStream<String, Error> { AsyncThrowingStream { continuation in Task.detached(priority: .userInitiated) { // Токенизация var tokens = [llama_token](repeating: 0, count: 4096) let nTokens = llama_tokenize(self.model, prompt, Int32(prompt.utf8.count), &tokens, 4096, true, false) // Инференс — по одному токену for i in 0..<nTokens { llama_batch_add(&batch, tokens[Int(i)], llama_pos(i), [0], false) } while true { llama_decode(self.context, batch) let nextToken = llama_sample_token_greedy(self.context, &candidates) if nextToken == llama_token_eos(self.model) { break } // Конвертация токена в строку var buf = [Int8](repeating: 0, count: 64) llama_token_to_piece(self.model, nextToken, &buf, 64, 0, true) let piece = String(cString: buf) continuation.yield(piece) } continuation.finish() } } } } ב-iOS:
NotificationCenter.default.addObserver(forName: ProcessInfo.thermalStateDidChangeNotification, ...) { _ in
let state = ProcessInfo.processInfo.thermalState
if state == .critical || state == .serious {
// Приостановить генерацию, уведомить пользователя
}
} טעויות אינטגרציה נפוצות
- הקשר גדול מדי — בחר n_ctx ≤ 2048 למכשירים ניידים.
- התעלמות מהגבלה תרמית — נטר את thermalState והפסק.
- גרסת מודל שגויה — אמת תאימות קובץ GGUF עם ה-build שלך של llama.cpp.
- חוסר אימות hash — קבצים פגומים גורמים לקריסות.
Android: llama.cpp דרך NDK
// CMakeLists.txt в jni/
add_library(llama_jni SHARED llama_jni.cpp)
target_link_libraries(llama_jni llama ggml)
// Kotlin side
class LlamaEngine {
init {
System.loadLibrary("llama_jni")
}
external fun loadModel(modelPath: String, nGpuLayers: Int): Long // возвращает handle
external fun generateNext(handle: Long, tokens: IntArray): String
external fun freeModel(handle: Long)
}ב-Android — backend של Vulkan במקום Metal: כלול את AsyncThrowingStream ב-CMakeLists. נתמך במכשירים עם Vulkan 1.1+, כמעט כל אלה עם Android 10+.
בעיה ב-Android: לתהליך אין מגבלת זיכרון כמשאב משותף — המערכת עשויה להרוג את האפליקציה (n_ctx) כשאין מספיק RAM ללא אזהרה. ProcessInfo.processInfo.thermalState — ההזדמנות האחרונה לשחרר הקשר לפני סיום התהליך.
הורדת מודל: התקדמות ואימות
קבצי GGUF שוקלים 1–4 GB. הורד דרך NotificationCenter.default.addObserver(forName: ProcessInfo.thermalStateDidChangeNotification, ...) { _ in let state = ProcessInfo.processInfo.thermalState if state == .critical || state == .serious { // Приостановить генерацию, уведомить пользователя } } (iOS) או // CMakeLists.txt в jni/ add_library(llama_jni SHARED llama_jni.cpp) target_link_libraries(llama_jni llama ggml) // Kotlin side class LlamaEngine { init { System.loadLibrary("llama_jni") } external fun loadModel(modelPath: String, nGpuLayers: Int): Long // возвращает handle external fun generateNext(handle: Long, tokens: IntArray): String external fun freeModel(handle: Long) } עם LLAMA_VULKAN=ON (Android). אימות SHA256 הוא חובה: לאחר ההורדה, חשב את ה-hash והשווה עם הצפוי מהמאגר ב-HuggingFace. GGUF פגום גורם לקריסה במהלך ניתוח הכותרת או מאוחר יותר במהלך הסקה — עדיף לתפוס אותו באימות.
הרשת הניידת פועלת מהר יותר ללא השהיית רשת, מה שחשוב במיוחד ליישומים רגישים לזמן. חיסכון בעלויות: פתרון לא מקוון לחלוטין מבטל הוצאות תשתית שרת.
מה כלול באינטגרציה
- ניתוח צי המכשירים ובחירת מודל קוונטיזציה אופטימלי
- בניית llama.cpp ל-iOS (Metal) ו/או Android (Vulkan)
- פיתוח עטיפת Swift/Kotlin עם סטרימינג טוקנים אסינכרוני
- יישום הורדת מודל עם התקדמות ואימות SHA256
- ממשק צ'אט עם אינדיקציית מצב תרמי
- בדיקות עומס במכשירים אמיתיים וכיוונון פרמטרי הקשר
- תיעוד אינטגרציה ותמיכה במהלך ההשקה
לוחות זמנים משוערים
פלטפורמה אחת, ממשק צ'אט בסיסי עם מודל נבחר — מ-3 שבועות. שתי הפלטפורמות, מספר אפשרויות מודל, הורדה ברקע, ניהול הקשר — מ-7 שבועות. העלות מחושבת באופן אישי.
הניסיון שלנו: 5 שנים בפיתוח נייד ומעל 20 פרויקטים עם ML במכשיר. אנו מבטיחים שהפתרון עובד במכשירי היעד לאחר בדיקות. קבלו ייעוץ על בחירת מודל והערכת פרויקט. הזמינו אינטגרציה וראו את היתרונות של עוזר AI לא מקוון.







