Report whether XNNPACK packed weights fell back to heap - #22413
Conversation
Summary: When the mmap'd packed-weight cache file cannot be used, XNNPACK packs weights into heap and compilation succeeds anyway. Every fallback branch in `XNNWeightsCache` returns `Error::Ok` and at most writes an `ET_LOG`, which does not reach a warehouse. So in production a fallback is indistinguishable from a model that is simply larger — the only symptom is several hundred MiB of extra dirty anonymous memory and no error anywhere. This diff records the outcome inside the cache and exposes it; D118207xxx (the child) consumes it and emits QPL. Split because the two halves have different owners. **Byte accounting, not a status flag.** The first cut of this reported a tri-state "did the file open". That hides the case that matters most: a cache that loads successfully but is only *partial*, so `loaded_from_disk_` routes every subsequent named pack to heap, never persists it, and re-packs it on every launch — permanently, per device, while still reporting `FileBacked`. `reserve_space` has five heap exits and only two were failures. So the cache now counts bytes per reason and reports `heap_bytes` against `mapped_bytes`; the ratio is the signal, and `packed_cache_state` is kept only to separate "feature off" from "perfect cache hit, no new packs". **Counters are atomic and read without the instance mutex.** That mutex is held across the whole of `xnn_create_runtime`, so a telemetry read that waited on it could stall an inference thread for the length of a model compile. The counters are independent accumulators with no invariant spanning them, so relaxed atomics are sufficient and `aggregate_stats()` takes no per-instance lock. Exposed read-only through the existing backend-options channel (`packed_cache_state` / `_errno` / `_heap_mib` / `_mapped_mib` / `_heap_reason` / `_file_mib`) rather than new API surface. **`errno` is deliberately how this answers "was it disk space", instead of sampling free space.** The disk-space APIs are on Apple's Required Reason API list and would force a PrivacyInfo declaration on every iOS consumer of this header — the same reason `load_packed_cache` already uses `lseek` over `fstat`. `ENOSPC` needs no such API. One behaviour change beyond telemetry: the `open(O_RDWR|O_CREAT)` failure branch previously returned `Error::Ok` with no log at all — the branch most likely to fire under disk pressure was the one silent branch. It now logs at ERR like its siblings. Differential Revision: D118206001
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22413
Note: Links to docs will display an error until the docs builds have been completed. ❌ 8 New Failures, 2 Unrelated FailuresAs of commit 4099fe4 with merge base 0c6ecea ( NEW FAILURES - The following jobs have failed:
FLAKY - The following jobs failed but were likely due to flakiness present on trunk:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@doggeral has exported this pull request. If you are a Meta employee, you can view the originating Diff in D118206001. |
This PR needs a
|
Summary:
When the mmap'd packed-weight cache file cannot be used, XNNPACK packs weights
into heap and compilation succeeds anyway. Every fallback branch in
XNNWeightsCachereturnsError::Okand at most writes anET_LOG, whichdoes not reach a warehouse. So in production a fallback is indistinguishable
from a model that is simply larger — the only symptom is several hundred MiB of
extra dirty anonymous memory and no error anywhere.
This diff records the outcome inside the cache and exposes it; D118207xxx (the
child) consumes it and emits QPL. Split because the two halves have different
owners.
Byte accounting, not a status flag. The first cut of this reported a
tri-state "did the file open". That hides the case that matters most: a cache
that loads successfully but is only partial, so
loaded_from_disk_routesevery subsequent named pack to heap, never persists it, and re-packs it on
every launch — permanently, per device, while still reporting
FileBacked.reserve_spacehas five heap exits and only two were failures. So the cachenow counts bytes per reason and reports
heap_bytesagainstmapped_bytes;the ratio is the signal, and
packed_cache_stateis kept only to separate"feature off" from "perfect cache hit, no new packs".
Counters are atomic and read without the instance mutex. That mutex is held
across the whole of
xnn_create_runtime, so a telemetry read that waited on itcould stall an inference thread for the length of a model compile. The counters
are independent accumulators with no invariant spanning them, so relaxed
atomics are sufficient and
aggregate_stats()takes no per-instance lock.Exposed read-only through the existing backend-options channel
(
packed_cache_state/_errno/_heap_mib/_mapped_mib/_heap_reason/_file_mib) rather than new API surface.errnois deliberately how this answers "was it disk space", instead ofsampling free space. The disk-space APIs are on Apple's Required Reason API
list and would force a PrivacyInfo declaration on every iOS consumer of this
header — the same reason
load_packed_cachealready useslseekoverfstat.ENOSPCneeds no such API.One behaviour change beyond telemetry: the
open(O_RDWR|O_CREAT)failurebranch previously returned
Error::Okwith no log at all — the branch mostlikely to fire under disk pressure was the one silent branch. It now logs at
ERR like its siblings.
Differential Revision: D118206001