99#include < executorch/backends/xnnpack/runtime/XNNWeightsCache.h>
1010#include < executorch/runtime/core/error.h>
1111#include < executorch/runtime/core/memory_allocator.h>
12+ #ifndef _WIN32
13+ #include < fcntl.h>
14+ #include < sys/file.h>
15+ #include < sys/mman.h>
1216#include < sys/stat.h>
17+ #include < unistd.h>
18+ #include < cerrno>
19+ #endif
1320#include < xnnpack.h>
1421#include < exception>
1522#include < memory>
@@ -41,13 +48,63 @@ XNNWeightsCache::XNNWeightsCache() {
4148 (enum xnn_status (*)(void *))XNNWeightsCache::delete_cache;
4249}
4350
51+ XNNWeightsCache::~XNNWeightsCache () {
52+ #ifndef _WIN32
53+ for (auto & region : mmap_regions_) {
54+ if (region.addr != nullptr && region.addr != MAP_FAILED ) {
55+ munmap (region.addr , region.size );
56+ }
57+ }
58+ mmap_regions_.clear ();
59+ if (packed_file_fd_ >= 0 ) {
60+ close (packed_file_fd_);
61+ packed_file_fd_ = -1 ;
62+ }
63+ #endif
64+ }
65+
4466Error XNNWeightsCache::initialize_for_runtime (
4567 MemoryAllocator* runtime_allocator,
4668 const NamedDataMap* named_data_map) {
4769 runtime_allocator_ = runtime_allocator;
4870 named_data_map_ = named_data_map;
4971 is_finalized_ = false ;
5072
73+ #ifndef _WIN32
74+ // Open the file for packed weights. Each reserve_space() call
75+ // independently mmaps a region of the file. Once packed_file_disabled_
76+ // is set we never re-open — re-opening with O_TRUNC would corrupt any
77+ // still-live mappings into the same path and cause SIGBUS on access.
78+ if (!packed_cache_path_.empty () && packed_file_fd_ < 0 &&
79+ !packed_file_disabled_) {
80+ packed_file_fd_ =
81+ open (packed_cache_path_.c_str (), O_RDWR | O_CREAT | O_TRUNC , 0600 );
82+ if (packed_file_fd_ < 0 ) {
83+ ET_LOG (
84+ Error,
85+ " Failed to open packed weight file: %s (errno=%d)" ,
86+ packed_cache_path_.c_str (),
87+ errno);
88+ } else if (flock (packed_file_fd_, LOCK_EX | LOCK_NB ) != 0 ) {
89+ // Another XNNWeightsCache instance (this process or another) is
90+ // already using this path. O_TRUNC above would corrupt its mappings.
91+ // Disable mmap for this instance to prevent collision; fall back to
92+ // heap allocation for the remainder of this cache's lifetime.
93+ ET_LOG (
94+ Error,
95+ " Another instance is using packed weight cache file %s (errno=%d); "
96+ " disabling mmap path" ,
97+ packed_cache_path_.c_str (),
98+ errno);
99+ close (packed_file_fd_);
100+ packed_file_fd_ = -1 ;
101+ packed_file_disabled_ = true ;
102+ } else {
103+ ET_LOG (Info, " Opened packed weight file: %s" , packed_cache_path_.c_str ());
104+ }
105+ }
106+ #endif
107+
51108 return Error::Ok;
52109}
53110
@@ -73,6 +130,26 @@ Result<std::vector<std::string>> XNNWeightsCache::finalize_for_runtime() {
73130 }
74131 }
75132
133+ #ifndef _WIN32
134+ // Schedule async flush for newly added regions only.
135+ // MS_ASYNC returns immediately; OS flushes in the background.
136+ if (mmap_regions_.size () > mmap_regions_synced_) {
137+ size_t new_count = mmap_regions_.size () - mmap_regions_synced_;
138+ for (size_t i = mmap_regions_synced_; i < mmap_regions_.size (); ++i) {
139+ if (mmap_regions_[i].addr != nullptr ) {
140+ msync (mmap_regions_[i].addr , mmap_regions_[i].size , MS_ASYNC );
141+ }
142+ }
143+ mmap_regions_synced_ = mmap_regions_.size ();
144+ ET_LOG (
145+ Info,
146+ " Scheduled async flush: %zu new regions (%zu total), %zu MB packed weights" ,
147+ new_count,
148+ mmap_regions_.size (),
149+ packed_file_used_ / (1024 * 1024 ));
150+ }
151+ #endif
152+
76153 return packed_data_names;
77154}
78155
@@ -111,12 +188,30 @@ Error XNNWeightsCache::delete_packed_data(
111188 entry->second .ref_count --;
112189 if (entry->second .ref_count == 0 ) {
113190 void * packed_data_ptr = packed_data_ptrs_[entry->second .offset ];
114- // Erase the key/value from the map frees the pointer holding the packed
115- // data
191+ // Erase the key/value from the map frees the pointer holding the
192+ // packed data. No-op on the file-backed mmap path, where the
193+ // container is not populated.
116194 packed_pointer_to_container_.erase (packed_data_ptr);
117- // remove the pointer from the packed_data_ptrs_
195+ #ifndef _WIN32
196+ // File-backed mmap path: munmap the region so VM and page-cache
197+ // usage is released, not just retained until cache destruction.
198+ // The vector slot is set to nullptr below so existing offsets remain
199+ // valid for any concurrent lookups.
200+ auto region_it = file_ptr_to_region_index_.find (packed_data_ptr);
201+ if (region_it != file_ptr_to_region_index_.end ()) {
202+ size_t idx = region_it->second ;
203+ MmapRegion& region = mmap_regions_[idx];
204+ if (region.addr != nullptr && region.addr != MAP_FAILED ) {
205+ munmap (region.addr , region.size );
206+ region.addr = nullptr ;
207+ region.size = 0 ;
208+ }
209+ file_ptr_to_region_index_.erase (region_it);
210+ }
211+ #endif
212+ // Remove the pointer from packed_data_ptrs_.
118213 packed_data_ptrs_[entry->second .offset ] = nullptr ;
119- // Erase the name to packed metadata entry
214+ // Erase the name to packed metadata entry.
120215 name_to_packed_data_metadata_.erase (entry->first );
121216 }
122217 }
@@ -158,38 +253,80 @@ size_t XNNWeightsCache::look_up(
158253 return packed_weight_entry->second .offset ;
159254}
160255
161- /* *
162- * Reserve space in the weight cache for n bytes of weight data, aligned to
163- * context->kPackedAllocationAlignment. This function will return nullptr if
164- * the allocation fails.
165- */
166256void * XNNWeightsCache::reserve_space (XNNWeightsCache* context, size_t n) {
167- // MemoryAllocator* allocator = context->runtime_allocator_;
168- // void* reserved_pointer = allocator->allocate(n,
169- // context->kPackedAllocationAlignment);
257+ #ifndef _WIN32
258+ if (context->packed_file_fd_ >= 0 ) {
259+ size_t page_size = sysconf (_SC_PAGESIZE);
260+ size_t file_offset =
261+ (context->packed_file_used_ + page_size - 1 ) & ~(page_size - 1 );
262+ size_t map_size = (n + page_size - 1 ) & ~(page_size - 1 );
263+
264+ if (ftruncate (context->packed_file_fd_ , file_offset + map_size) != 0 ) {
265+ ET_LOG (
266+ Error,
267+ " ftruncate to %zu failed (errno=%d)" ,
268+ file_offset + map_size,
269+ errno);
270+ close (context->packed_file_fd_ );
271+ context->packed_file_fd_ = -1 ;
272+ // Existing mmap_regions_ still reference this inode. Disable the
273+ // file-backed path permanently so a future initialize_for_runtime
274+ // doesn't re-open + O_TRUNC the same path and trigger SIGBUS on the
275+ // stale mappings.
276+ context->packed_file_disabled_ = true ;
277+ return context->reserve_space_heap (n);
278+ }
170279
171- // return reserved_pointer;
280+ void * ptr = mmap (
281+ nullptr ,
282+ map_size,
283+ PROT_READ | PROT_WRITE ,
284+ MAP_SHARED ,
285+ context->packed_file_fd_ ,
286+ file_offset);
287+ if (ptr == MAP_FAILED ) {
288+ ET_LOG (Error, " mmap %zu bytes failed (errno=%d)" , map_size, errno);
289+ close (context->packed_file_fd_ );
290+ context->packed_file_fd_ = -1 ;
291+ context->packed_file_disabled_ = true ;
292+ return context->reserve_space_heap (n);
293+ }
294+
295+ // mmap returns page-aligned (>= 4 KiB), which trivially satisfies the
296+ // 64-byte kPackedAllocationAlignment XNNPACK expects. Assert defensively.
297+ ET_DCHECK_MSG (
298+ (reinterpret_cast <uintptr_t >(ptr) % kPackedAllocationAlignment ) == 0 ,
299+ " mmap returned ptr not aligned to %zu bytes" ,
300+ kPackedAllocationAlignment );
301+
302+ context->packed_file_used_ = file_offset + map_size;
303+ context->file_ptr_to_region_index_ [ptr] = context->mmap_regions_ .size ();
304+ context->mmap_regions_ .push_back ({ptr, map_size});
305+ return ptr;
306+ }
307+ #endif
308+
309+ return context->reserve_space_heap (n);
310+ }
311+
312+ void * XNNWeightsCache::reserve_space_heap (size_t n) {
172313 try {
173314 std::string data_container;
174- size_t raw_allocation_size = n + context-> kPackedAllocationAlignment - 1 ;
315+ size_t raw_allocation_size = n + kPackedAllocationAlignment - 1 ;
175316 data_container.resize (raw_allocation_size);
176317
177318 void * maybe_aligned_space = data_container.data ();
178319 void * aligned_space = std::align (
179- context-> kPackedAllocationAlignment ,
320+ kPackedAllocationAlignment ,
180321 n,
181322 maybe_aligned_space,
182323 raw_allocation_size // Note that std::align mutates this value.
183324 );
184325 ET_CHECK_MSG (aligned_space != nullptr , " Memory alignment failed." );
185326
186- context->packed_pointer_to_container_ [aligned_space] =
187- std::move (data_container);
327+ packed_pointer_to_container_[aligned_space] = std::move (data_container);
188328 return aligned_space;
189329 } catch (std::bad_alloc& e) {
190- // XNNPACK can gracefully handle allocation failures, so return nullptr.
191- // We want to be able to recover from a failed attempt to load a large
192- // model without a crash.
193330 ET_LOG (
194331 Error,
195332 " XNN weight cache failed to allocate %zu bytes: %s." ,
@@ -267,6 +404,10 @@ enum xnn_status XNNWeightsCache::delete_cache(XNNWeightsCache* context) {
267404 return xnn_status_success;
268405}
269406
407+ void XNNWeightsCache::set_packed_cache_path (const std::string& path) {
408+ packed_cache_path_ = path;
409+ }
410+
270411} // namespace delegate
271412} // namespace xnnpack
272413} // namespace backends
0 commit comments