Skip to content

serialize: does Tell.prefill retain the same autoreleased Metal wrappers as the lane path did? (measure before fixing) #150

Description

@penta2himajin

Follow-up from #148 / PR #149, raised by an independent read-only review of that fix. Measurement first — no fix proposed.

The observation

#148's root cause was: the steel-hybrid prefill wraps every per-layer MLX temporary in a noCopy MTLBuffer (arrToBufasMTLBuffer); those wrappers are autoreleased, and the pool drains only at thread exit. On the lane path that thread is ContinuousScheduler's decode thread, so a whole prefill's wrappers stayed live at once:

retained Metal ≈ concurrency × promptLen × ~2.03MB/token

Fixed for lanes by draining per prefill chunk (PR #149).

That mechanism is path-agnostic. Tell.prefill (swift/Sources/QwispCore/TellRuntime.swift, the chunk loop) runs the same steel-hybrid forwardRowsHybrid at chunk 1024, on a per-request detached thread (LLMBackend.swift), with no per-chunk pool. Serialize's pool does drain at request end, so there is no cross-request ratchet — but the in-request peak should scale with prompt length by the same law.

Why this is not obviously already known

If the coefficient were the same, a 48K-token serialize prefill would imply ~100GB of simultaneous wrapper retention, which plainly does not happen — long serialize prefills work today. So either:

  • the coefficient differs materially on that path (different call shape, fewer/smaller wrapped temporaries), or
  • something already bounds it there, or
  • nobody has ever watched footprint during a long serialize prefill.

This contradiction is unresolved by reading alone and is exactly why this is a measurement issue, not a fix issue.

The measurement

One long single-stream prefill (e.g. 32–48K tokens) on the default serialize path, sampling footprint <pid> throughout (not ps rss — it undercounts wired GPU pages), plus MLX.Memory active/cache and MTLDevice.currentAllocatedSize if the QWISP_LANE_MEMDBG-style hook from PR #149 is generalised to that path.

Expected discriminator:

  • footprint peak scales at ~MB per prompt token during prefill → same bug, quantify before fixing
  • footprint stays flat / scales at KB per token → the lane path was special, close this

If it does reproduce

Note both natural fix sites (forwardRowsHybrid, Tell.prefill) are on the frozen forward path (CLAUDE.md prohibition 3), which is why PR #149 placed its drain lane-locally. A shared-point fix needs owner sign-off; a Tell.prefill-local drain mirroring PR #149 would be the lower-risk shape.

Refs

#148 (root cause + full measurement trail) · PR #149 (the lane fix) · TellRuntime.swift Tell.prefill · LLMBackend.swift (per-request thread) · SeedlessFusedVerify.swift arrToBuf / forwardRowsHybrid

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions