Skip to content

[Other] serve: an all-cached image turn never wakes the elastic encoder - #2455

Closed
jerry78424 wants to merge 3 commits into
Niko1221:mainfrom
jerry78424:vision-cache-wake
Closed

jerry78424 wants to merge 3 commits into
Niko1221:mainfrom
jerry78424:vision-cache-wake

Conversation

@jerry78424

@jerry78424 jerry78424 commented Oct 14, 2026 •

Copy link
Copy Markdown

Problem and result

An elastic image encoder is woken (seconds of start, VRAM the expert cache gives up) on every turn that re-sends pictures it has already encoded - which is what most clients do on every turn. A turn whose images are all in the hash cache needs no encoder process at all.

Measured on RTX 5090 32 GB, Windows 11 (26200), engine 0.1.44.1, nvfp4, elastic_idle_s: 10 (table and verbatim log lines in #2454): before - a text-only turn with a re-sent picture cost a 2.3 s start + 2085 MiB taken from the expert cache; after - 722 ms with no start line. A re-sent-picture turn: 1277 ms, no start. New pictures unchanged (3979-4213 ms, still start).

Related discussion

Resolves #2454. Independent of #2641 (the tool-picture wake lands in this same method - textual overlap only; the two PRs conflict in neither order, verified by cherry-pick).

Approach

  • serve/server.py: Vision.key_of() / Vision.cached() - a hash-cache lookup that answers with the process down (the cache and its .sve files survive an unload, as before) and marks a hit most recently used, as encode() does on its hit path. prepare() maps the turn's images through the cache first, for an elastic encoder only: when every one is a hit the turn skips the encode FIFO, _vision_up() and the encode. A miss - alone or alongside hits - keeps today's path byte-for-byte: FIFO, make-room, start, encode.
  • serve/test_vision_cache_wake.py (new, CPU-only, no engine): a re-sent picture does not wake a stopped encoder; a new picture alongside cached ones still wakes it; the lookup answers with the process down and keeps the re-sent picture from aging out of the 64-entry cache; a non-elastic turn keeps today's path even when every picture is cached.
  • docs/VRAM_ELASTIC.md, docs/DETAILS.md: both halves of the elastic rule - a picture's turn starts the encoder, a turn of pictures the cache knows takes neither the turn nor a start.

Validation

Passed:

  • Every new behavior fails on a tree without the code that provides it.
  • python -m unittest serve.test_vision_elastic serve.test_vision_busy serve.test_vision_cache_wake serve.test_images: the vision four 36 OK - at e88ee39d (v0.2.0-beta-12), rebased 2026-10-20, Windows 11 26200, Python 3.12 venv, CPU-only tests.
  • Full serve suite: 791 OK (15 skipped).
  • With [Other] serve: a tool's picture starts the elastic encoder, and its turn counts #2641 cherry-picked on top: the files stay 19 + 19 OK.

Failed: none observed.

Not run: AMD/HIP and layer-split machines (a layer split refuses elastic anyway; its start failure is #2435). No native files are touched, so no native builds are required.

Limits

  • What it leaves alone: non-elastic vision, the start/stop/idle lifecycle itself, the encode path, and every native file (no CUDA/HIP/SYCL build needed).
  • The measured numbers are one machine (RTX 5090, nvfp4); the mechanism - no process start for an all-cached turn - is hardware-independent, but the saved seconds are not claimed for other machines.

@qiuzan2001

Copy link
Copy Markdown

Two small points from reading the branch (tests serve.test_vision_cache_wake, test_vision_elastic, test_vision_busy, test_images pass here):

  1. Vision.cached() (serve/server.py:2409-2417) reads self.cache.get(key) without the move-to-end that encode() does on a hit (self.cache[key] = self.cache.pop(key), line 2437). Pictures that a client re-sends every turn now take the all-cached path and are never marked as recently used, so once newer images fill the 64-entry cache they are the first evicted, and the next turn that re-sends them has to encode them again and pays the elastic start. Refreshing the order inside cached() keeps the old LRU behaviour.
  2. The description says the branch "is only entered when vision.elastic", but the new if all(c is not None for c in cached) in prepare() has no such check: a non-elastic encoder also skips _turn() and the last_image_at update on an all-hit turn. Probably harmless, but it is a change outside the elastic case.

jerry78424 pushed a commit to jerry78424/Strata that referenced this pull request Oct 15, 2026
Both points from Niko1221#2454/Niko1221#2455 review (qiuzan2001):

cached() returned the hit without the move-to-end encode() does on its
hit path, so the re-sent pictures this path exists to answer were the
oldest entries in the dict - encode()'s oldest-first eviction (past 64
entries) took exactly them, and the turn after paid the encode and the
elastic start this PR removes. The lookup now refreshes the order inside
the same lock.

The description said the branch is only entered when vision.elastic; the
code did not check. The lookup is now computed only for an elastic
encoder: a non-elastic turn takes today's path byte-for-byte (FIFO,
encode, cache hits inside encode()) and does not pay the key_of()
hashing either.

Two tests: a cache lookup marks the picture most recently used (the
eviction order of three stored pictures flips to its benefit); an
elastic=False encoder sees encode_all on every turn, hits included, with
no make-room request. Suite: test_vision_cache_wake 8, plus
test_vision_elastic / test_vision_busy / test_images - 36 OK.
@jerry78424

jerry78424 commented Oct 15, 2026 •

Copy link
Copy Markdown
Author

Both points are right - fixed in bf73c58.

  1. Agreed, and it regressed the PR's own hot path: the re-sent pictures this lookup answers for were the oldest entries in the dict, so encode()'s oldest-first eviction took exactly them once the cache filled past 64, and the turn after paid the encode and the elastic start. cached() now moves the hit to the end inside the same lock, as encode()'s hit path does. New test: with three stored pictures the lookup flips the eviction order to the re-sent one's benefit.

  2. Agreed, the sentence did not match the code. The lookup is now computed only for an elastic encoder - a non-elastic turn takes today's path byte-for-byte (FIFO, encode, cache hits inside encode()) and does not pay the key_of() hashing either. New test: an elastic=False encoder sees encode_all on every turn, hits included, with no make-room request.

Docs (VRAM_ELASTIC/DETAILS) carried the wake rule only one way; they now say both halves (bdbb0e6).

Suite on the branch: serve 791 OK (15 skipped), the vision four 36 OK.

jerry78424 pushed a commit to jerry78424/Strata that referenced this pull request Oct 15, 2026
Both points from Niko1221#2454/Niko1221#2455 review (qiuzan2001):

cached() returned the hit without the move-to-end encode() does on its
hit path, so the re-sent pictures this path exists to answer were the
oldest entries in the dict - encode()'s oldest-first eviction (past 64
entries) took exactly them, and the turn after paid the encode and the
elastic start this PR removes. The lookup now refreshes the order inside
the same lock.

The description said the branch is only entered when vision.elastic; the
code did not check. The lookup is now computed only for an elastic
encoder: a non-elastic turn takes today's path byte-for-byte (FIFO,
encode, cache hits inside encode()) and does not pay the key_of()
hashing either.

Two tests: a cache lookup marks the picture most recently used (the
eviction order of three stored pictures flips to its benefit); an
elastic=False encoder sees encode_all on every turn, hits included, with
no make-room request. Suite: test_vision_cache_wake 6, plus
test_vision_elastic / test_vision_busy / test_images - 36 OK.
pinokio added 3 commits October 19, 2026 23:57
The hash cache and its .sve files survive an elastic unload, but every
image turn still took the encode FIFO and _vision_up() - so a client
that re-sends its pictures (which is what most do) paid the elastic
start again on every turn after an idle stop, text-only turns included.
prepare() now maps the turn's images through the cache first: when every
one is a hit the turn skips the FIFO, the start and the encode; a miss
keeps today's path unchanged. serve/test_vision_cache_wake.py (CPU-only)
covers it; the serve suite passes (348 tests, 3 skipped). Resolves Niko1221#2454.
Both points from Niko1221#2454/Niko1221#2455 review (qiuzan2001):

cached() returned the hit without the move-to-end encode() does on its
hit path, so the re-sent pictures this path exists to answer were the
oldest entries in the dict - encode()'s oldest-first eviction (past 64
entries) took exactly them, and the turn after paid the encode and the
elastic start this PR removes. The lookup now refreshes the order inside
the same lock.

The description said the branch is only entered when vision.elastic; the
code did not check. The lookup is now computed only for an elastic
encoder: a non-elastic turn takes today's path byte-for-byte (FIFO,
encode, cache hits inside encode()) and does not pay the key_of()
hashing either.

Two tests: a cache lookup marks the picture most recently used (the
eviction order of three stored pictures flips to its benefit); an
elastic=False encoder sees encode_all on every turn, hits included, with
no make-room request. Suite: test_vision_cache_wake 6, plus
test_vision_elastic / test_vision_busy / test_images - 36 OK.
The elastic paragraphs in VRAM_ELASTIC.md and DETAILS.md told only when
the encoder starts (a picture's turn, if it is not running). Since this
branch a turn whose pictures are all in the hash cache takes neither the
FIFO turn nor the start - the cache and its .sve files survive the stop,
and most clients re-send every picture of the conversation on every turn
- so both paragraphs now carry the other half of the rule, and the
bullet points at the new unit tests.

Docs only; the code and its suite are unchanged (791 OK, 15 skipped,
serve discover on this branch).
@Niko1221

Copy link
Copy Markdown
Owner

Thanks for the pull request.

We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected.

To reopen it takes about two minutes:

  1. Rebase on main if it no longer applies, and re-run your checks on 0.2.0-beta.
  2. Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other].
  3. Fill in the pull request template: the problem and result, and the validation (what you ran, on which revision, what you saw).
  4. Comment /reopen. A check runs and either reopens the pull request or lists what is still missing.

The rules are in CONTRIBUTING.md.

@Niko1221 Niko1221 closed this Oct 19, 2026
jerry78424 pushed a commit to jerry78424/Strata that referenced this pull request Oct 20, 2026
Both points from Niko1221#2454/Niko1221#2455 review (qiuzan2001):

cached() returned the hit without the move-to-end encode() does on its
hit path, so the re-sent pictures this path exists to answer were the
oldest entries in the dict - encode()'s oldest-first eviction (past 64
entries) took exactly them, and the turn after paid the encode and the
elastic start this PR removes. The lookup now refreshes the order inside
the same lock.

The description said the branch is only entered when vision.elastic; the
code did not check. The lookup is now computed only for an elastic
encoder: a non-elastic turn takes today's path byte-for-byte (FIFO,
encode, cache hits inside encode()) and does not pay the key_of()
hashing either.

Two tests: a cache lookup marks the picture most recently used (the
eviction order of three stored pictures flips to its benefit); an
elastic=False encoder sees encode_all on every turn, hits included, with
no make-room request. Suite: test_vision_cache_wake 6, plus
test_vision_elastic / test_vision_busy / test_images - 36 OK.
@jerry78424 jerry78424 changed the title serve: an all-cached image turn never wakes the elastic encoder [Other] serve: an all-cached image turn never wakes the elastic encoder Oct 20, 2026
@jerry78424

Copy link
Copy Markdown
Author

/reopen

@github-actions

Copy link
Copy Markdown

The check passed, but GitHub would not reopen it (Validation Failed: ). If the branch was deleted, restore it or open a new pull request and link this one.

@jerry78424

Copy link
Copy Markdown
Author

Reopened as #3380 (GitHub could not reopen this one after the rebase force-push; same branch, checks re-run on e88ee39).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: elastic image encoder wakes on every turn when the client re-sends pictures it already encoded

3 participants