react-native-litert-lm 0.3.7 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -135
- package/android/build.gradle +12 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +276 -62
- package/android/src/main/java/dev/litert/litertlm/LiteRTLMPackage.kt +19 -2
- package/android/src/test/java/com/margelo/nitro/core/Promise.kt +46 -0
- package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +105 -0
- package/ios/HybridLiteRTLM.swift +1344 -0
- package/ios/Tests/HybridLiteRTLMTests.swift +113 -0
- package/lib/__mocks__/react-native-nitro-modules.d.ts +65 -0
- package/lib/__mocks__/react-native-nitro-modules.js +60 -0
- package/lib/__tests__/hooks.test.d.ts +1 -0
- package/lib/__tests__/hooks.test.js +124 -0
- package/lib/__tests__/memoryTracker.test.d.ts +1 -0
- package/lib/__tests__/memoryTracker.test.js +74 -0
- package/lib/__tests__/modelFactory.test.d.ts +1 -0
- package/lib/__tests__/modelFactory.test.js +68 -0
- package/lib/hooks.js +27 -3
- package/lib/index.d.ts +6 -2
- package/lib/index.js +8 -8
- package/lib/modelFactory.js +82 -63
- package/lib/specs/LiteRTLM.nitro.d.ts +87 -2
- package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +94 -9
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +5 -1
- package/nitrogen/generated/android/c++/JLLMConfig.hpp +40 -3
- package/nitrogen/generated/android/c++/JMultimodalPart.hpp +74 -0
- package/nitrogen/generated/android/c++/JPartType.hpp +61 -0
- package/nitrogen/generated/android/c++/JToolDefinition.hpp +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/GenerationStats.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +28 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +46 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Message.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MultimodalPart.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/PartType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolDefinition.kt +61 -0
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +57 -1
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +414 -3
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Umbrella.hpp +41 -3
- package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +4 -6
- package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +10 -0
- package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +240 -0
- package/nitrogen/generated/ios/swift/Backend.swift +44 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/GenerationStats.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +71 -0
- package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +431 -0
- package/nitrogen/generated/ios/swift/LLMConfig.swift +203 -0
- package/nitrogen/generated/ios/swift/MemoryUsage.swift +44 -0
- package/nitrogen/generated/ios/swift/Message.swift +34 -0
- package/nitrogen/generated/ios/swift/MultimodalPart.swift +83 -0
- package/nitrogen/generated/ios/swift/PartType.swift +44 -0
- package/nitrogen/generated/ios/swift/Role.swift +44 -0
- package/nitrogen/generated/ios/swift/ToolDefinition.swift +39 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +9 -2
- package/nitrogen/generated/shared/c++/LLMConfig.hpp +22 -2
- package/nitrogen/generated/shared/c++/MultimodalPart.hpp +99 -0
- package/nitrogen/generated/shared/c++/PartType.hpp +80 -0
- package/nitrogen/generated/shared/c++/ToolDefinition.hpp +91 -0
- package/package.json +22 -11
- package/react-native-litert-lm.podspec +17 -19
- package/scripts/download-ios-frameworks.sh +17 -50
- package/scripts/framework-source.js +46 -0
- package/scripts/postinstall.js +40 -18
- package/src/__mocks__/react-native-nitro-modules.ts +58 -0
- package/src/__tests__/hooks.test.ts +153 -0
- package/src/__tests__/memoryTracker.test.ts +87 -0
- package/src/__tests__/modelFactory.test.ts +96 -0
- package/src/hooks.ts +29 -7
- package/src/index.ts +7 -10
- package/src/modelFactory.ts +104 -80
- package/src/specs/LiteRTLM.nitro.ts +106 -2
- package/cpp/HybridLiteRTLM.cpp +0 -939
- package/cpp/HybridLiteRTLM.hpp +0 -169
- package/cpp/IOSDownloadHelper.h +0 -24
- package/ios/IOSDownloadHelper.mm +0 -129
- package/scripts/build-ios-engine.sh +0 -302
- package/scripts/stubs/cxx_bridge_stubs.cc +0 -224
- package/scripts/stubs/gemma_model_constraint_provider.cc +0 -46
- package/scripts/stubs/llguidance_stubs.c +0 -101
- package/src/templates.ts +0 -105
package/cpp/HybridLiteRTLM.hpp
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// HybridLiteRTLM.hpp
|
|
3
|
-
// react-native-litert-lm
|
|
4
|
-
//
|
|
5
|
-
// High-performance LLM inference using LiteRT-LM.
|
|
6
|
-
// Supports Gemma 4, Gemma 3n, and other .litertlm models.
|
|
7
|
-
//
|
|
8
|
-
// NOTE: This C++ implementation is used for iOS ONLY.
|
|
9
|
-
// Android uses the Kotlin implementation in `android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt`.
|
|
10
|
-
// Do not assume changes here will affect Android.
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
#pragma once
|
|
14
|
-
|
|
15
|
-
#include "../nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp"
|
|
16
|
-
|
|
17
|
-
// LiteRT-LM C API (iOS uses prebuilt framework with C ABI)
|
|
18
|
-
#ifdef __APPLE__
|
|
19
|
-
#include "include/litert_lm_engine.h"
|
|
20
|
-
#endif
|
|
21
|
-
|
|
22
|
-
// Memory usage headers
|
|
23
|
-
#ifdef __APPLE__
|
|
24
|
-
#include <mach/mach.h>
|
|
25
|
-
#include <mach/mach_host.h>
|
|
26
|
-
#endif
|
|
27
|
-
#ifdef __ANDROID__
|
|
28
|
-
#include <malloc.h>
|
|
29
|
-
#include <fstream>
|
|
30
|
-
#endif
|
|
31
|
-
|
|
32
|
-
#include <string>
|
|
33
|
-
#include <optional>
|
|
34
|
-
#include <vector>
|
|
35
|
-
#include <memory>
|
|
36
|
-
#include <mutex>
|
|
37
|
-
#include <functional>
|
|
38
|
-
#include <atomic>
|
|
39
|
-
|
|
40
|
-
namespace margelo::nitro::litertlm {
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* HybridLiteRTLM: React Native bindings for LiteRT-LM.
|
|
44
|
-
*
|
|
45
|
-
* On iOS, wraps the LiteRT-LM C API (engine.h) with prebuilt framework.
|
|
46
|
-
* On Android, this class is unused — the Kotlin implementation is used instead.
|
|
47
|
-
*/
|
|
48
|
-
class HybridLiteRTLM : public HybridLiteRTLMSpec {
|
|
49
|
-
public:
|
|
50
|
-
HybridLiteRTLM() : HybridObject(TAG) {}
|
|
51
|
-
|
|
52
|
-
~HybridLiteRTLM() override {
|
|
53
|
-
close();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Prevent copying
|
|
57
|
-
HybridLiteRTLM(const HybridLiteRTLM&) = delete;
|
|
58
|
-
HybridLiteRTLM& operator=(const HybridLiteRTLM&) = delete;
|
|
59
|
-
|
|
60
|
-
public:
|
|
61
|
-
// HybridLiteRTLMSpec interface implementation
|
|
62
|
-
|
|
63
|
-
std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath,
|
|
64
|
-
const std::optional<LLMConfig>& config) override;
|
|
65
|
-
|
|
66
|
-
std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) override;
|
|
67
|
-
|
|
68
|
-
std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message,
|
|
69
|
-
const std::string& imagePath) override;
|
|
70
|
-
|
|
71
|
-
std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url,
|
|
72
|
-
const std::string& fileName,
|
|
73
|
-
const std::optional<std::function<void(double)>>& onProgress) override;
|
|
74
|
-
|
|
75
|
-
std::shared_ptr<Promise<void>> deleteModel(const std::string& fileName) override;
|
|
76
|
-
|
|
77
|
-
std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message,
|
|
78
|
-
const std::string& audioPath) override;
|
|
79
|
-
|
|
80
|
-
void sendMessageAsync(
|
|
81
|
-
const std::string& message,
|
|
82
|
-
const std::function<void(const std::string&, bool)>& onToken
|
|
83
|
-
) override;
|
|
84
|
-
|
|
85
|
-
std::vector<Message> getHistory() override;
|
|
86
|
-
|
|
87
|
-
void resetConversation() override;
|
|
88
|
-
|
|
89
|
-
bool isReady() override;
|
|
90
|
-
|
|
91
|
-
GenerationStats getStats() override;
|
|
92
|
-
|
|
93
|
-
MemoryUsage getMemoryUsage() override;
|
|
94
|
-
|
|
95
|
-
void close() override;
|
|
96
|
-
|
|
97
|
-
private:
|
|
98
|
-
// LiteRT-LM C API resources (iOS only)
|
|
99
|
-
#ifdef __APPLE__
|
|
100
|
-
LiteRtLmEngine* engine_ = nullptr;
|
|
101
|
-
LiteRtLmConversation* conversation_ = nullptr;
|
|
102
|
-
LiteRtLmConversationConfig* conv_config_ = nullptr;
|
|
103
|
-
LiteRtLmSessionConfig* session_config_ = nullptr;
|
|
104
|
-
#endif
|
|
105
|
-
|
|
106
|
-
// State
|
|
107
|
-
bool isLoaded_ = false;
|
|
108
|
-
std::vector<Message> history_;
|
|
109
|
-
GenerationStats lastStats_{0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
110
|
-
|
|
111
|
-
// Thread safety
|
|
112
|
-
mutable std::mutex mutex_;
|
|
113
|
-
|
|
114
|
-
// Configuration - backend
|
|
115
|
-
Backend backend_ = Backend::CPU;
|
|
116
|
-
|
|
117
|
-
// System prompt / instruction
|
|
118
|
-
std::string systemPrompt_;
|
|
119
|
-
|
|
120
|
-
// Configuration - sampling parameters
|
|
121
|
-
double temperature_ = 0.7;
|
|
122
|
-
double topK_ = 40.0;
|
|
123
|
-
double topP_ = 0.95;
|
|
124
|
-
double maxTokens_ = 1024.0;
|
|
125
|
-
|
|
126
|
-
// Helper to ensure model is loaded
|
|
127
|
-
void ensureLoaded() const {
|
|
128
|
-
if (!isLoaded_) {
|
|
129
|
-
throw std::runtime_error("LiteRTLM: No model loaded. Call loadModel() first.");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Helper to create a new conversation from existing engine
|
|
134
|
-
void createNewConversation();
|
|
135
|
-
|
|
136
|
-
// JSON helpers for building C API message payloads
|
|
137
|
-
static std::string escapeJson(const std::string& input);
|
|
138
|
-
static std::string buildTextMessageJson(const std::string& text);
|
|
139
|
-
static std::string buildImageMessageJson(const std::string& text, const std::string& imagePath);
|
|
140
|
-
static std::string buildAudioMessageJson(const std::string& text, const std::string& audioPath);
|
|
141
|
-
static std::string extractTextFromResponse(const std::string& jsonResponse);
|
|
142
|
-
|
|
143
|
-
// Internal implementations (called from Promise lambdas)
|
|
144
|
-
void loadModelInternal(const std::string& modelPath, const std::optional<LLMConfig>& config);
|
|
145
|
-
std::string sendMessageInternal(const std::string& message);
|
|
146
|
-
std::string sendMessageWithImageInternal(const std::string& message, const std::string& imagePath);
|
|
147
|
-
std::string sendMessageWithAudioInternal(const std::string& message, const std::string& audioPath);
|
|
148
|
-
|
|
149
|
-
// Streaming callback context (must be a plain struct for C function pointer)
|
|
150
|
-
struct StreamContext {
|
|
151
|
-
std::function<void(const std::string&, bool)> onToken;
|
|
152
|
-
std::string rawResponse; // Raw accumulated chunks (before stripping)
|
|
153
|
-
std::string fullResponse; // Clean accumulated text (after stripping)
|
|
154
|
-
size_t lastEmittedLength; // Length of fullResponse already emitted to JS
|
|
155
|
-
std::vector<Message>* history;
|
|
156
|
-
std::mutex* historyMutex;
|
|
157
|
-
std::string userMessage;
|
|
158
|
-
GenerationStats* lastStats;
|
|
159
|
-
std::chrono::steady_clock::time_point startTime;
|
|
160
|
-
int tokenCount;
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Static C callback for streaming (no captures needed)
|
|
164
|
-
static void streamCallbackFn(void* callback_data, const char* chunk,
|
|
165
|
-
bool is_final, const char* error_msg);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
} // namespace margelo::nitro::litertlm
|
|
169
|
-
|
package/cpp/IOSDownloadHelper.h
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <optional>
|
|
6
|
-
|
|
7
|
-
namespace litert_lm {
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Download a file from a URL to the app's Caches/litert_models directory.
|
|
11
|
-
* Uses NSURLSession for efficient, resumable downloads.
|
|
12
|
-
*
|
|
13
|
-
* @param url HTTPS URL to download from
|
|
14
|
-
* @param fileName Destination filename
|
|
15
|
-
* @param onProgress Optional progress callback (0.0 to 1.0)
|
|
16
|
-
* @return Absolute path to the downloaded file
|
|
17
|
-
* @throws std::runtime_error on download failure
|
|
18
|
-
*/
|
|
19
|
-
std::string downloadModelFile(
|
|
20
|
-
const std::string& url,
|
|
21
|
-
const std::string& fileName,
|
|
22
|
-
const std::optional<std::function<void(double)>>& onProgress);
|
|
23
|
-
|
|
24
|
-
} // namespace litert_lm
|
package/ios/IOSDownloadHelper.mm
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
#import <Foundation/Foundation.h>
|
|
2
|
-
#include "../cpp/IOSDownloadHelper.h"
|
|
3
|
-
#include <stdexcept>
|
|
4
|
-
|
|
5
|
-
namespace litert_lm {
|
|
6
|
-
|
|
7
|
-
std::string downloadModelFile(
|
|
8
|
-
const std::string& url,
|
|
9
|
-
const std::string& fileName,
|
|
10
|
-
const std::optional<std::function<void(double)>>& onProgress) {
|
|
11
|
-
@autoreleasepool {
|
|
12
|
-
NSString* nsUrl = [NSString stringWithUTF8String:url.c_str()];
|
|
13
|
-
NSString* nsFileName = [NSString stringWithUTF8String:fileName.c_str()];
|
|
14
|
-
|
|
15
|
-
// Use Caches directory — survives app relaunch but can be
|
|
16
|
-
// reclaimed by the system under storage pressure.
|
|
17
|
-
NSString* cachesDir = NSSearchPathForDirectoriesInDomains(
|
|
18
|
-
NSCachesDirectory, NSUserDomainMask, YES).firstObject;
|
|
19
|
-
NSString* modelsDir = [cachesDir stringByAppendingPathComponent:@"litert_models"];
|
|
20
|
-
|
|
21
|
-
// Create models subdirectory
|
|
22
|
-
NSFileManager* fm = [NSFileManager defaultManager];
|
|
23
|
-
if (![fm fileExistsAtPath:modelsDir]) {
|
|
24
|
-
[fm createDirectoryAtPath:modelsDir
|
|
25
|
-
withIntermediateDirectories:YES
|
|
26
|
-
attributes:nil
|
|
27
|
-
error:nil];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
NSString* destPath = [modelsDir stringByAppendingPathComponent:nsFileName];
|
|
31
|
-
|
|
32
|
-
// If the file already exists and has content, skip download
|
|
33
|
-
if ([fm fileExistsAtPath:destPath]) {
|
|
34
|
-
NSDictionary* attrs = [fm attributesOfItemAtPath:destPath error:nil];
|
|
35
|
-
unsigned long long fileSize = [attrs fileSize];
|
|
36
|
-
if (fileSize > 0) {
|
|
37
|
-
NSLog(@"[LiteRTLM] Model already cached at %@ (%llu bytes), skipping download",
|
|
38
|
-
destPath, fileSize);
|
|
39
|
-
if (onProgress.has_value()) {
|
|
40
|
-
onProgress.value()(1.0);
|
|
41
|
-
}
|
|
42
|
-
return std::string([destPath UTF8String]);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
NSLog(@"[LiteRTLM] Downloading model from %@ to %@", nsUrl, destPath);
|
|
47
|
-
|
|
48
|
-
NSURL* requestUrl = [NSURL URLWithString:nsUrl];
|
|
49
|
-
if (!requestUrl) {
|
|
50
|
-
throw std::runtime_error("Invalid download URL: " + url);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Synchronous download using NSURLSession on this background thread.
|
|
54
|
-
__block NSError* downloadError = nil;
|
|
55
|
-
|
|
56
|
-
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
|
57
|
-
|
|
58
|
-
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
59
|
-
config.timeoutIntervalForRequest = 30;
|
|
60
|
-
config.timeoutIntervalForResource = 3600; // 1 hour for large models
|
|
61
|
-
|
|
62
|
-
NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
|
|
63
|
-
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl];
|
|
64
|
-
[request setHTTPMethod:@"GET"];
|
|
65
|
-
|
|
66
|
-
// Use downloadTask for proper progress tracking and disk-efficient downloads
|
|
67
|
-
NSURLSessionDownloadTask* task = [session downloadTaskWithRequest:request
|
|
68
|
-
completionHandler:^(NSURL* location, NSURLResponse* response, NSError* error) {
|
|
69
|
-
if (error) {
|
|
70
|
-
downloadError = error;
|
|
71
|
-
dispatch_semaphore_signal(semaphore);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check HTTP status
|
|
76
|
-
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
77
|
-
NSInteger statusCode = [(NSHTTPURLResponse*)response statusCode];
|
|
78
|
-
if (statusCode >= 400) {
|
|
79
|
-
downloadError = [NSError errorWithDomain:@"LiteRTLM"
|
|
80
|
-
code:statusCode
|
|
81
|
-
userInfo:@{NSLocalizedDescriptionKey:
|
|
82
|
-
[NSString stringWithFormat:@"HTTP %ld", (long)statusCode]}];
|
|
83
|
-
dispatch_semaphore_signal(semaphore);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Move downloaded file to destination
|
|
89
|
-
NSError* moveError = nil;
|
|
90
|
-
[fm removeItemAtPath:destPath error:nil]; // Remove any partial file
|
|
91
|
-
if (![fm moveItemAtURL:location
|
|
92
|
-
toURL:[NSURL fileURLWithPath:destPath]
|
|
93
|
-
error:&moveError]) {
|
|
94
|
-
downloadError = moveError;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
dispatch_semaphore_signal(semaphore);
|
|
98
|
-
}];
|
|
99
|
-
|
|
100
|
-
[task resume];
|
|
101
|
-
|
|
102
|
-
// Poll for progress while waiting for completion
|
|
103
|
-
while (dispatch_semaphore_wait(semaphore,
|
|
104
|
-
dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC)) != 0) {
|
|
105
|
-
if (onProgress.has_value() && task.countOfBytesExpectedToReceive > 0) {
|
|
106
|
-
double progress = (double)task.countOfBytesReceived /
|
|
107
|
-
(double)task.countOfBytesExpectedToReceive;
|
|
108
|
-
onProgress.value()(progress);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
[session finishTasksAndInvalidate];
|
|
113
|
-
|
|
114
|
-
if (downloadError) {
|
|
115
|
-
throw std::runtime_error("Download failed: " +
|
|
116
|
-
std::string([[downloadError localizedDescription] UTF8String]));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Final progress callback
|
|
120
|
-
if (onProgress.has_value()) {
|
|
121
|
-
onProgress.value()(1.0);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
NSLog(@"[LiteRTLM] Model downloaded successfully to %@", destPath);
|
|
125
|
-
return std::string([destPath UTF8String]);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
} // namespace litert_lm
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# build-ios-engine.sh
|
|
3
|
-
#
|
|
4
|
-
# Builds the LiteRT-LM C engine as a static library for iOS (device + simulator)
|
|
5
|
-
# using Bazel, then packages it into an XCFramework for CocoaPods.
|
|
6
|
-
#
|
|
7
|
-
# Prerequisites:
|
|
8
|
-
# - Bazel 7.6.1+ (via Bazelisk recommended)
|
|
9
|
-
# - Xcode command line tools
|
|
10
|
-
#
|
|
11
|
-
# Usage:
|
|
12
|
-
# ./scripts/build-ios-engine.sh
|
|
13
|
-
#
|
|
14
|
-
# Output:
|
|
15
|
-
# ios/Frameworks/LiteRTLM.xcframework/ (static library + headers)
|
|
16
|
-
|
|
17
|
-
set -euo pipefail
|
|
18
|
-
|
|
19
|
-
LITERT_LM_REPO="https://github.com/google-ai-edge/LiteRT-LM.git"
|
|
20
|
-
FRAMEWORK_NAME="LiteRTLM"
|
|
21
|
-
|
|
22
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
23
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
24
|
-
LITERT_LM_VERSION="$(node -e "console.log(require('$PROJECT_ROOT/package.json').litertLm.iosGitTag)")"
|
|
25
|
-
OUTPUT_DIR="$PROJECT_ROOT/ios/Frameworks"
|
|
26
|
-
C_API_HEADER_DIR="$PROJECT_ROOT/cpp/include"
|
|
27
|
-
BUILD_DIR="$PROJECT_ROOT/.litert-lm-build"
|
|
28
|
-
|
|
29
|
-
echo "==> Building LiteRT-LM ${LITERT_LM_VERSION} C engine for iOS..."
|
|
30
|
-
echo ""
|
|
31
|
-
|
|
32
|
-
# ---- 1. Clone / update the LiteRT-LM repo --------------------------------
|
|
33
|
-
echo "==> Step 1: Preparing LiteRT-LM source..."
|
|
34
|
-
if [ -f "$BUILD_DIR/LiteRT-LM/.bazelrc" ] && [ -f "$BUILD_DIR/LiteRT-LM/requirements.txt" ]; then
|
|
35
|
-
echo " Source already exists, checking out ${LITERT_LM_VERSION}..."
|
|
36
|
-
cd "$BUILD_DIR/LiteRT-LM"
|
|
37
|
-
git fetch --tags 2>/dev/null || true
|
|
38
|
-
git checkout "$LITERT_LM_VERSION" 2>/dev/null || git checkout "tags/$LITERT_LM_VERSION"
|
|
39
|
-
else
|
|
40
|
-
# Clean up any failed previous clone
|
|
41
|
-
rm -rf "$BUILD_DIR/LiteRT-LM"
|
|
42
|
-
mkdir -p "$BUILD_DIR"
|
|
43
|
-
echo " Cloning LiteRT-LM (shallow, skipping LFS)..."
|
|
44
|
-
|
|
45
|
-
# Clone without LFS filter to avoid requiring git-lfs installation.
|
|
46
|
-
# The prebuilt/ directory will contain LFS pointer files but we don't
|
|
47
|
-
# need those — we're building the engine from source.
|
|
48
|
-
GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 --branch "$LITERT_LM_VERSION" \
|
|
49
|
-
-c filter.lfs.smudge=cat \
|
|
50
|
-
-c filter.lfs.process= \
|
|
51
|
-
-c filter.lfs.clean=cat \
|
|
52
|
-
-c filter.lfs.required=false \
|
|
53
|
-
"$LITERT_LM_REPO" "$BUILD_DIR/LiteRT-LM"
|
|
54
|
-
|
|
55
|
-
cd "$BUILD_DIR/LiteRT-LM"
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
LITERT_SRC="$BUILD_DIR/LiteRT-LM"
|
|
59
|
-
|
|
60
|
-
# ---- 1b. Apply iOS-specific patches ---------------------------------------
|
|
61
|
-
# These patches fix:
|
|
62
|
-
# - mmap PROT_WRITE removal (iOS rejects CoW for large files)
|
|
63
|
-
# - Error capture API (litert_lm_get_last_error)
|
|
64
|
-
# - Engine registerer moved outside anonymous namespace (iOS linker stripping)
|
|
65
|
-
# - Minijinja/Rust stub replacement (custom C++ prompt template)
|
|
66
|
-
PATCHES_DIR="$PROJECT_ROOT/scripts/patches"
|
|
67
|
-
if [ -d "$PATCHES_DIR" ]; then
|
|
68
|
-
for PATCH_FILE in "$PATCHES_DIR"/*.patch; do
|
|
69
|
-
if [ -f "$PATCH_FILE" ]; then
|
|
70
|
-
echo " Applying patch: $(basename "$PATCH_FILE")..."
|
|
71
|
-
cd "$LITERT_SRC"
|
|
72
|
-
git apply --check "$PATCH_FILE" 2>/dev/null && \
|
|
73
|
-
git apply "$PATCH_FILE" || \
|
|
74
|
-
echo " (patch already applied or conflicts, skipping)"
|
|
75
|
-
fi
|
|
76
|
-
done
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
# ---- 2. Verify Bazel is available -----------------------------------------
|
|
80
|
-
echo ""
|
|
81
|
-
echo "==> Step 2: Checking Bazel..."
|
|
82
|
-
if command -v bazelisk &>/dev/null; then
|
|
83
|
-
BAZEL="bazelisk"
|
|
84
|
-
elif command -v bazel &>/dev/null; then
|
|
85
|
-
BAZEL="bazel"
|
|
86
|
-
else
|
|
87
|
-
echo "Error: Bazel is not installed."
|
|
88
|
-
echo "Install via: brew install bazelisk"
|
|
89
|
-
echo "Or download from: https://github.com/bazelbuild/bazelisk"
|
|
90
|
-
exit 1
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
BAZEL_VERSION=$($BAZEL --version 2>&1 | head -1)
|
|
94
|
-
echo " Using: $BAZEL ($BAZEL_VERSION)"
|
|
95
|
-
|
|
96
|
-
# ---- 3. Build the C engine static library for iOS -------------------------
|
|
97
|
-
echo ""
|
|
98
|
-
echo "==> Step 3: Building //c:engine for iOS..."
|
|
99
|
-
|
|
100
|
-
cd "$LITERT_SRC"
|
|
101
|
-
|
|
102
|
-
# Get Bazel output base (where all .o files live in the actual filesystem)
|
|
103
|
-
BAZEL_OUTPUT_BASE=$($BAZEL info output_base 2>/dev/null)
|
|
104
|
-
BAZEL_EXECROOT="$BAZEL_OUTPUT_BASE/execroot"
|
|
105
|
-
|
|
106
|
-
STAGE_DIR="$BUILD_DIR/staged-libs"
|
|
107
|
-
mkdir -p "$STAGE_DIR"
|
|
108
|
-
|
|
109
|
-
# Helper: build for a config, then merge ALL transitive .o files into one .a
|
|
110
|
-
# Bazel's cc_library produces thin archives — the engine's transitive deps
|
|
111
|
-
# (absl, protobuf, runtime, KleidiAI, etc.) are separate .o files that must
|
|
112
|
-
# be merged into a single self-contained static library for Xcode.
|
|
113
|
-
build_fat_static_lib() {
|
|
114
|
-
local CONFIG="$1"
|
|
115
|
-
local CONFIG_DIR="$2" # e.g. "ios_arm64-opt" or "ios_sim_arm64-opt"
|
|
116
|
-
local OUTPUT_PATH="$3"
|
|
117
|
-
|
|
118
|
-
echo " Building for $CONFIG..."
|
|
119
|
-
# Build both the engine AND all cc_proto_library targets in a single Bazel
|
|
120
|
-
# invocation. Bazel's cc_proto_library compiles proto-generated code, but
|
|
121
|
-
# the .pb.o files only appear in the output tree if these targets are
|
|
122
|
-
# explicitly requested alongside the engine.
|
|
123
|
-
$BAZEL build \
|
|
124
|
-
//c:engine \
|
|
125
|
-
@sentencepiece//:sentencepiece_cc_proto \
|
|
126
|
-
@sentencepiece//:sentencepiece_model_cc_proto \
|
|
127
|
-
//runtime/proto:engine_cc_proto \
|
|
128
|
-
//runtime/proto:sampler_params_cc_proto \
|
|
129
|
-
//runtime/proto:llm_metadata_cc_proto \
|
|
130
|
-
//runtime/proto:token_cc_proto \
|
|
131
|
-
//runtime/proto:llm_model_type_cc_proto \
|
|
132
|
-
//runtime/util:external_file_cc_proto \
|
|
133
|
-
@com_google_protobuf//:protobuf \
|
|
134
|
-
@com_googlesource_code_re2//:re2 \
|
|
135
|
-
--config=$CONFIG 2>&1 | tail -5
|
|
136
|
-
|
|
137
|
-
echo " Collecting transitive object files from $CONFIG_DIR..."
|
|
138
|
-
local OBJ_LIST="$STAGE_DIR/${CONFIG}-objects.txt"
|
|
139
|
-
find "$BAZEL_EXECROOT" -path "*/${CONFIG_DIR}/bin/*" -name "*.o" \
|
|
140
|
-
! -name "*.h.processed" 2>/dev/null | sort > "$OBJ_LIST"
|
|
141
|
-
|
|
142
|
-
# ---- Compile stubs for Rust/llguidance deps (unavailable on iOS) ----------
|
|
143
|
-
local EXTRA_OBJS="$STAGE_DIR/${CONFIG}-extra-objs"
|
|
144
|
-
rm -rf "$EXTRA_OBJS"
|
|
145
|
-
mkdir -p "$EXTRA_OBJS"
|
|
146
|
-
|
|
147
|
-
local STUBS_DIR="$PROJECT_ROOT/scripts/stubs"
|
|
148
|
-
local STUB_FILES=$(find "$STUBS_DIR" \( -name "*.cc" -o -name "*.c" \) 2>/dev/null)
|
|
149
|
-
if [ -n "$STUB_FILES" ]; then
|
|
150
|
-
echo " Compiling stubs for unavailable dependencies..."
|
|
151
|
-
local SDK_NAME="iphoneos"
|
|
152
|
-
local TARGET_TRIPLE="arm64-apple-ios15.0"
|
|
153
|
-
if [[ "$CONFIG_DIR" == *"sim"* ]]; then
|
|
154
|
-
SDK_NAME="iphonesimulator"
|
|
155
|
-
TARGET_TRIPLE="arm64-apple-ios15.0-simulator"
|
|
156
|
-
fi
|
|
157
|
-
local SDK_PATH=$(xcrun --sdk "$SDK_NAME" --show-sdk-path)
|
|
158
|
-
|
|
159
|
-
for STUB_SRC in $STUB_FILES; do
|
|
160
|
-
local STUB_BASE=$(basename "$STUB_SRC")
|
|
161
|
-
local STUB_NAME="${STUB_BASE%.*}"
|
|
162
|
-
local STUB_EXT="${STUB_BASE##*.}"
|
|
163
|
-
echo " → $STUB_NAME ($STUB_EXT)"
|
|
164
|
-
|
|
165
|
-
if [ "$STUB_EXT" = "cc" ]; then
|
|
166
|
-
xcrun clang++ -c -std=c++20 \
|
|
167
|
-
-target "$TARGET_TRIPLE" \
|
|
168
|
-
-isysroot "$SDK_PATH" \
|
|
169
|
-
-DNDEBUG \
|
|
170
|
-
-o "$EXTRA_OBJS/${STUB_NAME}.o" \
|
|
171
|
-
"$STUB_SRC" 2>&1 || true
|
|
172
|
-
else
|
|
173
|
-
xcrun clang -c \
|
|
174
|
-
-target "$TARGET_TRIPLE" \
|
|
175
|
-
-isysroot "$SDK_PATH" \
|
|
176
|
-
-DNDEBUG \
|
|
177
|
-
-o "$EXTRA_OBJS/${STUB_NAME}.o" \
|
|
178
|
-
"$STUB_SRC" 2>&1 || true
|
|
179
|
-
fi
|
|
180
|
-
done
|
|
181
|
-
|
|
182
|
-
# Add successfully compiled stubs to the object list
|
|
183
|
-
find "$EXTRA_OBJS" -name "*.o" -size +0c >> "$OBJ_LIST"
|
|
184
|
-
fi
|
|
185
|
-
|
|
186
|
-
local OBJ_COUNT=$(wc -l < "$OBJ_LIST" | tr -d ' ')
|
|
187
|
-
echo " Found $OBJ_COUNT total object files (including proto + stubs)"
|
|
188
|
-
|
|
189
|
-
if [ "$OBJ_COUNT" -eq 0 ]; then
|
|
190
|
-
echo "Error: No object files found for $CONFIG in $CONFIG_DIR"
|
|
191
|
-
exit 1
|
|
192
|
-
fi
|
|
193
|
-
|
|
194
|
-
# Merge all .o files into a single fat static library using libtool
|
|
195
|
-
echo " Merging into fat static library..."
|
|
196
|
-
xcrun libtool -static -o "$OUTPUT_PATH" -filelist "$OBJ_LIST" 2>&1 | grep -v "has no symbols" || true
|
|
197
|
-
|
|
198
|
-
local LIB_SIZE=$(du -h "$OUTPUT_PATH" | cut -f1)
|
|
199
|
-
echo " ✅ $CONFIG: $LIB_SIZE ($OBJ_COUNT objects)"
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
# Build for device (arm64)
|
|
203
|
-
DEVICE_LIB="$STAGE_DIR/libengine-device.a"
|
|
204
|
-
build_fat_static_lib "ios_arm64" "ios_arm64-opt" "$DEVICE_LIB"
|
|
205
|
-
|
|
206
|
-
echo ""
|
|
207
|
-
|
|
208
|
-
# Build for simulator (sim_arm64)
|
|
209
|
-
SIM_LIB="$STAGE_DIR/libengine-sim.a"
|
|
210
|
-
build_fat_static_lib "ios_sim_arm64" "ios_sim_arm64-opt" "$SIM_LIB"
|
|
211
|
-
|
|
212
|
-
echo ""
|
|
213
|
-
echo " Device lib: $DEVICE_LIB ($(du -h "$DEVICE_LIB" | cut -f1))"
|
|
214
|
-
echo " Simulator lib: $SIM_LIB ($(du -h "$SIM_LIB" | cut -f1))"
|
|
215
|
-
|
|
216
|
-
# ---- 4. Copy the C API header ---------------------------------------------
|
|
217
|
-
echo ""
|
|
218
|
-
echo "==> Step 4: Vendoring C API header..."
|
|
219
|
-
mkdir -p "$C_API_HEADER_DIR"
|
|
220
|
-
cp "$LITERT_SRC/c/engine.h" "$C_API_HEADER_DIR/litert_lm_engine.h"
|
|
221
|
-
echo " ✅ Copied engine.h → cpp/include/litert_lm_engine.h"
|
|
222
|
-
|
|
223
|
-
# ---- 5. Create XCFramework from static libraries --------------------------
|
|
224
|
-
echo ""
|
|
225
|
-
echo "==> Step 5: Creating XCFramework..."
|
|
226
|
-
|
|
227
|
-
rm -rf "$OUTPUT_DIR"
|
|
228
|
-
mkdir -p "$OUTPUT_DIR"
|
|
229
|
-
|
|
230
|
-
TMP_DIR="$(mktemp -d)"
|
|
231
|
-
cleanup() { rm -rf "$TMP_DIR"; }
|
|
232
|
-
trap cleanup EXIT
|
|
233
|
-
|
|
234
|
-
# Create framework bundles from static libraries
|
|
235
|
-
for ARCH_NAME in "device" "simulator"; do
|
|
236
|
-
if [ "$ARCH_NAME" = "device" ]; then
|
|
237
|
-
LIB_PATH="$DEVICE_LIB"
|
|
238
|
-
else
|
|
239
|
-
LIB_PATH="$SIM_LIB"
|
|
240
|
-
fi
|
|
241
|
-
|
|
242
|
-
FW_DIR="$TMP_DIR/$ARCH_NAME/$FRAMEWORK_NAME.framework"
|
|
243
|
-
mkdir -p "$FW_DIR/Headers"
|
|
244
|
-
|
|
245
|
-
# Copy static lib as the framework binary
|
|
246
|
-
cp "$LIB_PATH" "$FW_DIR/$FRAMEWORK_NAME"
|
|
247
|
-
|
|
248
|
-
# Copy headers
|
|
249
|
-
cp "$C_API_HEADER_DIR/litert_lm_engine.h" "$FW_DIR/Headers/"
|
|
250
|
-
|
|
251
|
-
# Create Info.plist
|
|
252
|
-
cat > "$FW_DIR/Info.plist" << PLIST
|
|
253
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
254
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
255
|
-
<plist version="1.0">
|
|
256
|
-
<dict>
|
|
257
|
-
<key>CFBundleDevelopmentRegion</key>
|
|
258
|
-
<string>en</string>
|
|
259
|
-
<key>CFBundleExecutable</key>
|
|
260
|
-
<string>${FRAMEWORK_NAME}</string>
|
|
261
|
-
<key>CFBundleIdentifier</key>
|
|
262
|
-
<string>com.google.ai.edge.litert-lm</string>
|
|
263
|
-
<key>CFBundleInfoDictionaryVersion</key>
|
|
264
|
-
<string>6.0</string>
|
|
265
|
-
<key>CFBundleName</key>
|
|
266
|
-
<string>${FRAMEWORK_NAME}</string>
|
|
267
|
-
<key>CFBundlePackageType</key>
|
|
268
|
-
<string>FMWK</string>
|
|
269
|
-
<key>CFBundleShortVersionString</key>
|
|
270
|
-
<string>0.10.2</string>
|
|
271
|
-
<key>CFBundleVersion</key>
|
|
272
|
-
<string>1</string>
|
|
273
|
-
<key>MinimumOSVersion</key>
|
|
274
|
-
<string>15.0</string>
|
|
275
|
-
</dict>
|
|
276
|
-
</plist>
|
|
277
|
-
PLIST
|
|
278
|
-
done
|
|
279
|
-
|
|
280
|
-
# Create XCFramework
|
|
281
|
-
xcodebuild -create-xcframework \
|
|
282
|
-
-framework "$TMP_DIR/device/$FRAMEWORK_NAME.framework" \
|
|
283
|
-
-framework "$TMP_DIR/simulator/$FRAMEWORK_NAME.framework" \
|
|
284
|
-
-output "$OUTPUT_DIR/$FRAMEWORK_NAME.xcframework" 2>&1
|
|
285
|
-
|
|
286
|
-
echo " ✅ XCFramework created at: ios/Frameworks/${FRAMEWORK_NAME}.xcframework"
|
|
287
|
-
|
|
288
|
-
# ---- 6. Create zip for release asset --------------------------------------
|
|
289
|
-
echo ""
|
|
290
|
-
echo "==> Step 6: Creating release asset zip..."
|
|
291
|
-
cd "$OUTPUT_DIR"
|
|
292
|
-
zip -r "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" . -x ".*" 2>&1
|
|
293
|
-
ZIP_SIZE=$(du -h "$PROJECT_ROOT/LiteRTLM-ios-frameworks.zip" | cut -f1)
|
|
294
|
-
echo " ✅ Created LiteRTLM-ios-frameworks.zip (${ZIP_SIZE})"
|
|
295
|
-
|
|
296
|
-
echo ""
|
|
297
|
-
echo "==> Done! iOS engine built and packaged."
|
|
298
|
-
echo ""
|
|
299
|
-
echo "Contents:"
|
|
300
|
-
find "$OUTPUT_DIR" -type f | head -20 | while read f; do
|
|
301
|
-
echo " $(echo "$f" | sed "s|$PROJECT_ROOT/||") ($(du -h "$f" | cut -f1))"
|
|
302
|
-
done
|