Skip to content

Commit 0bf3c51

Browse files
authored
5/x: make old/new global cache configurable (#18801)
Differential Revision: D93044328 Pull Request resolved: #18801
1 parent 59f66cf commit 0bf3c51

File tree

3 files changed

+183
-3
lines changed

3 files changed

+183
-3
lines changed

backends/apple/coreml/runtime/delegate/backend_delegate.mm

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ - (BOOL)purgeModelsCacheAndReturnError:(NSError * _Nullable __autoreleasing *)er
127127
@property (assign, readonly, nonatomic) BackendDelegate::Config config;
128128
@property (strong, readonly, nonatomic) dispatch_queue_t syncQueue;
129129
@property (strong, nonatomic, nullable) ETCoreMLModelManager *impl;
130+
@property (strong, nonatomic, nullable) ETCoreMLModelCache *defaultCache;
130131
@property (assign, readonly, nonatomic) BOOL isAvailable;
131132

132133
@end
@@ -165,6 +166,16 @@ - (BOOL)_loadAndReturnError:(NSError * _Nullable __autoreleasing *)error {
165166

166167
self.impl = modelManager;
167168

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+
168179
if (self.config.should_prewarm_asset) {
169180
[modelManager prewarmRecentlyUsedAssetsWithMaxCount:1];
170181
}
@@ -199,6 +210,7 @@ - (ModelHandle*)loadModelFromAOTData:(NSData*)data
199210
configuration:configuration
200211
methodName:nil
201212
functionName:nil
213+
cachePath:nil
202214
error:error];
203215
}
204216

@@ -221,6 +233,23 @@ - (ModelHandle*)loadModelFromAOTData:(NSData*)data
221233
functionName:(nullable NSString*)functionName
222234
cachePath:(nullable NSString*)cachePath
223235
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
252+
error:(NSError* __autoreleasing*)error {
224253
if (![self loadAndReturnError:error]) {
225254
return nil;
226255
}
@@ -240,8 +269,21 @@ - (ModelHandle*)loadModelFromAOTData:(NSData*)data
240269
return nil;
241270
}
242271
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+
}
243284
}
244-
// cache == nil means loadModelFromAOTData will use self.cache (default cache)
285+
// If useNewCache is false or defaultCache is nil, cache remains nil
286+
// and loadModelFromAOTData will use the asset manager path
245287

246288
auto handle = [self.impl loadModelFromAOTData:data
247289
configuration:configuration
@@ -346,15 +388,19 @@ explicit BackendDelegateImpl(const Config& config) noexcept
346388
NSString *methodNameStr = method_name ? @(method_name) : nil;
347389
NSString *functionNameStr = function_name ? @(function_name) : nil;
348390

349-
// Parse cache_dir from runtime_specs
391+
// Parse cache_dir and _use_new_cache from runtime_specs
350392
NSString *cachePath = nil;
393+
BOOL useNewCache = NO; // Default to using the old cache (asset manager)
351394
for (size_t i = 0; i < runtime_specs.size(); ++i) {
352395
const auto& opt = runtime_specs[i];
353396
if (std::strcmp(opt.key, "cache_dir") == 0) {
354397
if (auto* arr = std::get_if<std::array<char, executorch::runtime::kMaxOptionValueLength>>(&opt.value)) {
355398
cachePath = @(arr->data());
356399
}
357-
break;
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+
}
358404
}
359405
}
360406

@@ -366,6 +412,7 @@ explicit BackendDelegateImpl(const Config& config) noexcept
366412
methodName:methodNameStr
367413
functionName:functionNameStr
368414
cachePath:cachePath
415+
useNewCache:useNewCache
369416
error:&localError];
370417
if (localError != nil) {
371418
ETCoreMLLogError(localError, "Model init failed");

backends/apple/coreml/runtime/include/coreml_backend/coreml_backend_options.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ class LoadOptionsBuilder {
9595
return *this;
9696
}
9797

98+
/**
99+
* Controls whether to use the new filesystem cache (ETCoreMLModelCache).
100+
*
101+
* This is a temporary runtime option for A/B testing the new cache
102+
* implementation. It will be removed once the new cache is fully rolled out.
103+
*
104+
* @param enabled If true, uses the new filesystem cache.
105+
* If false (default), uses the legacy asset manager.
106+
* @return Reference to this builder for chaining.
107+
*/
108+
LoadOptionsBuilder& setUseNewCache(bool enabled) {
109+
options_.set_option("_use_new_cache", enabled);
110+
return *this;
111+
}
112+
98113
/**
99114
* Returns the backend identifier for this options builder.
100115
*/

backends/apple/coreml/runtime/test/coreml_backend_options_test.cpp

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,121 @@ TEST_F(CoreMLBackendOptionsTest, IntegrationWithOptionsMapCacheDir) {
221221
}
222222
EXPECT_TRUE(found_cache_dir) << "cache_dir option not found";
223223
}
224+
225+
// Test setUseNewCache with true
226+
TEST_F(CoreMLBackendOptionsTest, SetUseNewCacheTrue) {
227+
LoadOptionsBuilder builder;
228+
builder.setUseNewCache(true);
229+
230+
auto options = builder.view();
231+
EXPECT_EQ(options.size(), 1);
232+
EXPECT_STREQ(options[0].key, "_use_new_cache");
233+
234+
if (auto* val = std::get_if<bool>(&options[0].value)) {
235+
EXPECT_TRUE(*val);
236+
} else {
237+
FAIL() << "Expected bool value for _use_new_cache";
238+
}
239+
}
240+
241+
// Test setUseNewCache with false
242+
TEST_F(CoreMLBackendOptionsTest, SetUseNewCacheFalse) {
243+
LoadOptionsBuilder builder;
244+
builder.setUseNewCache(false);
245+
246+
auto options = builder.view();
247+
EXPECT_EQ(options.size(), 1);
248+
EXPECT_STREQ(options[0].key, "_use_new_cache");
249+
250+
if (auto* val = std::get_if<bool>(&options[0].value)) {
251+
EXPECT_FALSE(*val);
252+
} else {
253+
FAIL() << "Expected bool value for _use_new_cache";
254+
}
255+
}
256+
257+
// Test setUseNewCache method chaining
258+
TEST_F(CoreMLBackendOptionsTest, SetUseNewCacheChaining) {
259+
LoadOptionsBuilder builder;
260+
auto& result = builder.setUseNewCache(true);
261+
262+
// Should return reference to the same builder
263+
EXPECT_EQ(&result, &builder);
264+
}
265+
266+
// Test combining setComputeUnit, setCacheDirectory, and setUseNewCache
267+
TEST_F(CoreMLBackendOptionsTest, AllOptionsCombined) {
268+
LoadOptionsBuilder builder;
269+
builder.setComputeUnit(LoadOptionsBuilder::ComputeUnit::CPU_AND_GPU)
270+
.setCacheDirectory("/path/to/cache")
271+
.setUseNewCache(true);
272+
273+
auto options = builder.view();
274+
EXPECT_EQ(options.size(), 3);
275+
276+
// Find and verify each option
277+
bool found_compute_unit = false;
278+
bool found_cache_dir = false;
279+
bool found_use_new_cache = false;
280+
281+
for (size_t i = 0; i < options.size(); ++i) {
282+
if (std::strcmp(options[i].key, "compute_unit") == 0) {
283+
found_compute_unit = true;
284+
if (auto* arr = std::get_if<std::array<char, kMaxOptionValueLength>>(&options[i].value)) {
285+
EXPECT_STREQ(arr->data(), "cpu_and_gpu");
286+
}
287+
} else if (std::strcmp(options[i].key, "cache_dir") == 0) {
288+
found_cache_dir = true;
289+
if (auto* arr = std::get_if<std::array<char, kMaxOptionValueLength>>(&options[i].value)) {
290+
EXPECT_STREQ(arr->data(), "/path/to/cache");
291+
}
292+
} else if (std::strcmp(options[i].key, "_use_new_cache") == 0) {
293+
found_use_new_cache = true;
294+
if (auto* val = std::get_if<bool>(&options[i].value)) {
295+
EXPECT_TRUE(*val);
296+
}
297+
}
298+
}
299+
300+
EXPECT_TRUE(found_compute_unit) << "compute_unit option not found";
301+
EXPECT_TRUE(found_cache_dir) << "cache_dir option not found";
302+
EXPECT_TRUE(found_use_new_cache) << "_use_new_cache option not found";
303+
}
304+
305+
// Test integration with LoadBackendOptionsMap including _use_new_cache
306+
TEST_F(CoreMLBackendOptionsTest, IntegrationWithOptionsMapUseNewCache) {
307+
LoadOptionsBuilder coreml_opts;
308+
coreml_opts.setUseNewCache(true);
309+
310+
LoadBackendOptionsMap map;
311+
EXPECT_EQ(map.set_options(coreml_opts), Error::Ok);
312+
313+
EXPECT_EQ(map.size(), 1);
314+
EXPECT_TRUE(map.has_options("CoreMLBackend"));
315+
316+
auto retrieved = map.get_options("CoreMLBackend");
317+
EXPECT_EQ(retrieved.size(), 1);
318+
EXPECT_STREQ(retrieved[0].key, "_use_new_cache");
319+
320+
if (auto* val = std::get_if<bool>(&retrieved[0].value)) {
321+
EXPECT_TRUE(*val);
322+
} else {
323+
FAIL() << "Expected bool value for _use_new_cache";
324+
}
325+
}
326+
327+
// Test setUseNewCache updates when called multiple times
328+
TEST_F(CoreMLBackendOptionsTest, SetUseNewCacheMultipleTimes) {
329+
LoadOptionsBuilder builder;
330+
builder.setUseNewCache(true);
331+
builder.setUseNewCache(false);
332+
333+
auto options = builder.view();
334+
EXPECT_EQ(options.size(), 1);
335+
336+
if (auto* val = std::get_if<bool>(&options[0].value)) {
337+
EXPECT_FALSE(*val); // Last value wins
338+
} else {
339+
FAIL() << "Expected bool value for _use_new_cache";
340+
}
341+
}

0 commit comments

Comments
 (0)