1111#import " ETCoreMLAssetManager.h"
1212#import " ETCoreMLLogging.h"
1313#import " ETCoreMLModel.h"
14+ #import " ETCoreMLModelCache.h"
1415#import " ETCoreMLModelManager.h"
1516#import " ETCoreMLStrings.h"
1617#import " model_event_logger.h"
@@ -100,7 +101,14 @@ - (ModelHandle*)loadModelFromAOTData:(NSData*)data
100101 configuration : (MLModelConfiguration*)configuration
101102 methodName : (nullable NSString *)methodName
102103 functionName : (nullable NSString *)functionName
103- error : (NSError * __autoreleasing*)error ;
104+ error : (NSError * __autoreleasing*)error ;
105+
106+ - (ModelHandle*)loadModelFromAOTData : (NSData *)data
107+ configuration : (MLModelConfiguration*)configuration
108+ methodName : (nullable NSString *)methodName
109+ functionName : (nullable NSString *)functionName
110+ cachePath : (nullable NSString *)cachePath
111+ error : (NSError * __autoreleasing*)error ;
104112
105113- (ModelHandle*)loadModelFromAOTData : (NSData *)data
106114 configuration : (MLModelConfiguration*)configuration
@@ -119,6 +127,7 @@ - (BOOL)purgeModelsCacheAndReturnError:(NSError * _Nullable __autoreleasing *)er
119127@property (assign , readonly , nonatomic ) BackendDelegate::Config config;
120128@property (strong , readonly , nonatomic ) dispatch_queue_t syncQueue;
121129@property (strong , nonatomic , nullable ) ETCoreMLModelManager *impl;
130+ @property (strong , nonatomic , nullable ) ETCoreMLModelCache *defaultCache;
122131@property (assign , readonly , nonatomic ) BOOL isAvailable;
123132
124133@end
@@ -157,6 +166,16 @@ - (BOOL)_loadAndReturnError:(NSError * _Nullable __autoreleasing *)error {
157166
158167 self.impl = modelManager;
159168
169+ // Create default filesystem cache at the same location as assets
170+ NSURL *defaultCacheURL = [NSURL fileURLWithPath: ETCoreMLStrings.assetsDirectoryPath isDirectory: YES ];
171+ ETCoreMLModelCache *defaultCache = [[ETCoreMLModelCache alloc ] initWithCacheRootDirectory: defaultCacheURL];
172+ if (defaultCache.isReady ) {
173+ self.defaultCache = defaultCache;
174+ } else {
175+ ETCoreMLLogError (defaultCache.initializationError ,
176+ " Default cache initialization failed, will use asset manager as fallback" );
177+ }
178+
160179 if (self.config .should_prewarm_asset ) {
161180 [modelManager prewarmRecentlyUsedAssetsWithMaxCount: 1 ];
162181 }
@@ -191,22 +210,86 @@ - (ModelHandle*)loadModelFromAOTData:(NSData*)data
191210 configuration: configuration
192211 methodName: nil
193212 functionName: nil
213+ cachePath: nil
214+ error: error];
215+ }
216+
217+ - (ModelHandle*)loadModelFromAOTData : (NSData *)data
218+ configuration : (MLModelConfiguration*)configuration
219+ methodName : (nullable NSString *)methodName
220+ functionName : (nullable NSString *)functionName
221+ error : (NSError * __autoreleasing*)error {
222+ return [self loadModelFromAOTData: data
223+ configuration: configuration
224+ methodName: methodName
225+ functionName: functionName
226+ cachePath: nil
194227 error: error];
195228}
196229
197230- (ModelHandle*)loadModelFromAOTData : (NSData *)data
198231 configuration : (MLModelConfiguration*)configuration
199232 methodName : (nullable NSString *)methodName
200233 functionName : (nullable NSString *)functionName
234+ cachePath : (nullable NSString *)cachePath
235+ error : (NSError * __autoreleasing*)error {
236+ // Default to using the old cache (useNewCache = NO)
237+ return [self loadModelFromAOTData: data
238+ configuration: configuration
239+ methodName: methodName
240+ functionName: functionName
241+ cachePath: cachePath
242+ useNewCache: NO
243+ error: error];
244+ }
245+
246+ - (ModelHandle*)loadModelFromAOTData : (NSData *)data
247+ configuration : (MLModelConfiguration*)configuration
248+ methodName : (nullable NSString *)methodName
249+ functionName : (nullable NSString *)functionName
250+ cachePath : (nullable NSString *)cachePath
251+ useNewCache : (BOOL )useNewCache
201252 error : (NSError * __autoreleasing*)error {
202253 if (![self loadAndReturnError: error]) {
203254 return nil ;
204255 }
205256
257+ id <ETCoreMLCache> cache = nil ;
258+ if (cachePath != nil ) {
259+ // Use NEW filesystem cache at specified path
260+ NSURL *cacheURL = [NSURL fileURLWithPath: cachePath isDirectory: YES ];
261+ ETCoreMLModelCache *modelCache = [[ETCoreMLModelCache alloc ] initWithCacheRootDirectory: cacheURL];
262+ if (!modelCache.isReady ) {
263+ // Fallback error if initializationError is unexpectedly nil
264+ NSError *cacheError = modelCache.initializationError
265+ ?: [NSError errorWithDomain: ETCoreMLModelCacheErrorDomain
266+ code: ETCoreMLModelCacheErrorCodeInitializationFailed
267+ userInfo: @{NSLocalizedDescriptionKey : @" Cache initialization failed" }];
268+ if (error) *error = cacheError;
269+ return nil ;
270+ }
271+ cache = modelCache;
272+ } else if (useNewCache) {
273+ if (self.defaultCache != nil ) {
274+ // Use default filesystem cache
275+ cache = self.defaultCache ;
276+ } else {
277+ // Fallback: useNewCache requested but default cache unavailable
278+ NSError *fallbackError = [NSError errorWithDomain: ETCoreMLErrorDomain
279+ code: ETCoreMLErrorInternalError
280+ userInfo: @{NSLocalizedDescriptionKey : @" Default cache unavailable" }];
281+ ETCoreMLLogError (fallbackError,
282+ " useNewCache=YES but default cache is unavailable, falling back to asset manager" );
283+ }
284+ }
285+ // If useNewCache is false or defaultCache is nil, cache remains nil
286+ // and loadModelFromAOTData will use the asset manager path
287+
206288 auto handle = [self .impl loadModelFromAOTData: data
207289 configuration: configuration
208290 methodName: methodName
209291 functionName: functionName
292+ cache: cache
210293 error: error];
211294 if ((handle != NULL ) && self.config .should_prewarm_model ) {
212295 [self .impl prewarmModelWithHandle: handle error: nil ];
@@ -291,9 +374,10 @@ explicit BackendDelegateImpl(const Config& config) noexcept
291374 BackendDelegateImpl& operator =(BackendDelegateImpl const &) = delete ;
292375
293376Handle *init (Buffer processed,
294- const std::unordered_map<std::string, Buffer>& specs,
295- const char * method_name = nullptr ,
296- const char * function_name = nullptr ) const noexcept override {
377+ const std::unordered_map<std::string, Buffer>& specs,
378+ const char * method_name = nullptr ,
379+ const char * function_name = nullptr ,
380+ executorch::runtime::Span<const executorch::runtime::BackendOption> runtime_specs = {}) const noexcept override {
297381 NSError *localError = nil ;
298382 MLModelConfiguration *configuration = get_model_configuration (specs, &localError);
299383 if (configuration == nil ) {
@@ -304,13 +388,31 @@ explicit BackendDelegateImpl(const Config& config) noexcept
304388 NSString *methodNameStr = method_name ? @(method_name) : nil ;
305389 NSString *functionNameStr = function_name ? @(function_name) : nil ;
306390
391+ // Parse cache_dir and _use_new_cache from runtime_specs
392+ NSString *cachePath = nil ;
393+ BOOL useNewCache = NO ; // Default to using the old cache (asset manager)
394+ for (size_t i = 0 ; i < runtime_specs.size (); ++i) {
395+ const auto & opt = runtime_specs[i];
396+ if (std::strcmp (opt.key , " cache_dir" ) == 0 ) {
397+ if (auto * arr = std::get_if<std::array<char , executorch::runtime::kMaxOptionValueLength >>(&opt.value )) {
398+ cachePath = @(arr->data ());
399+ }
400+ } else if (std::strcmp (opt.key , " _use_new_cache" ) == 0 ) {
401+ if (auto * val = std::get_if<bool >(&opt.value )) {
402+ useNewCache = *val ? YES : NO ;
403+ }
404+ }
405+ }
406+
307407 NSData *data = [NSData dataWithBytesNoCopy: const_cast <void *>(processed.data ())
308408 length: processed.size ()
309409 freeWhenDone: NO ];
310410 ModelHandle *modelHandle = [model_manager_ loadModelFromAOTData: data
311411 configuration: configuration
312412 methodName: methodNameStr
313413 functionName: functionNameStr
414+ cachePath: cachePath
415+ useNewCache: useNewCache
314416 error: &localError];
315417 if (localError != nil ) {
316418 ETCoreMLLogError (localError, " Model init failed" );
0 commit comments