Skip to content

Semantic load path memory is bounded by estimate only; actual RSS growth during model open/indexing is unobserved #754

Description

@ScriptedAlchemy

Follow-up finding from #753 part 2 (observed incident: projection load reported loading for 16 minutes past load_deadline_ms: 30000, RSS climbing 11.4 GB -> 18.5 GB until a kernel OOM).

The state-truth defects around that incident are fixed on the PR #707 lane:

  • the load deadline now fires during a cold open_session (typed LoadDeadlineExceeded, loader thread keeps the slot/reservation until the runtime returns, then releases), and
  • lifecycle Indexing{completed_units,total_units} now surfaces as runtime Indexing progress instead of being flattened to Loading, so long "loading" phases that are actually indexing are no longer mislabeled.

What remains and is worth its own slice: the semantic load path's memory ceiling is enforced only against estimates, never against actual usage while work is in flight.

Attribution (code pointers, crates/tracedecay-semantic):

  1. SessionPool reserves runtime.resident_bytes_reservation(authority) (the manifest resident_bytes_estimate) before open and re-checks session.resident_bytes_estimate() after open (session_pool.rs, cold-open path). Nothing observes actual RSS between those two points.
  2. FastEmbedEmbeddingRuntime::open_session (fastembed_adapter.rs:1179) buffers the entire model + tokenizer members in memory (fastembed_model, fastembed_adapter.rs:1295) and then builds the ORT session via TextEmbedding::try_new_from_user_defined. ORT's graph parse/optimization/arena allocation during that constructor is unbounded by any TraceDecay ceiling; peak transient memory is a multiple of model bytes and invisible to the pool's accounting.
  3. After the (new) deadline abandonment, the loader thread cannot cancel ORT mid-build; the memory stays genuinely held (and truthfully accounted as reserved) until ORT returns. A hung or thrashing ORT build therefore still holds its memory unboundedly in time, just no longer blocks the acquisition.

The 7.1 GB growth in the incident is far larger than one ~600 MB model graph build, which is consistent with the mislabeled-indexing mechanism (real projection/indexing work accumulating while the surface said loading) rather than a single ORT session build; with the progress mapping fixed, the next occurrence will be attributable directly from status output.

Suggested direction (not started): enforce an actual resident bound during load/indexing — either sample the loader's RSS against resident_byte_ceiling and fail typed, or isolate the ORT session build so it can be cancelled/killed at the deadline instead of merely abandoned. Per repo rules the ceilings themselves are symptoms guards and stay unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions