Pull requests / #3371

#3371 [Engine] allow CPU vision to load on demand without elastic VRAM

open · @keylimesoda · 0 comentarios · En GitHub

Server & APIAMD / HIPNVIDIA / CUDAModels & quantsDocumentationLinux

Descripción

## Problem and result

With a CPU picture reader (`vision.gpu: false`), `vision.elastic: true` is currently disabled unless the text engine also enables `vram_elastic`. As a result, starting the text model starts the CPU encoder too, even when every request is text. Enabling elastic VRAM merely to defer a CPU process is unnecessary and changes the text engine's memory policy.

This allows the existing opt-in on-demand lifecycle for a CPU encoder without elastic VRAM: text leaves it unloaded, the first picture starts it, and idle unload stops its process. Set `"elastic": true` in an existing CPU `"vision"` section; `"elastic_idle_s"` keeps its existing default of 120 seconds.

## Related discussion

Searched open and closed issues and PRs for CPU vision, elastic encoders and on-demand loading; found no duplicate CPU-specific fix. Related GPU lifecycle work: #2196. The tool-returned-image path in #3174 is separate and is not changed here. No changes to defaults or engine architecture; this is a focused extension of the existing opt-in lifecycle.

## Approach

Allow CPU elastic startup independently of `vram_elastic`, and skip the VRAM reserve and free-VRAM probes when starting that CPU reader. Reuse the existing FIFO, idle timer and unload/restart paths. GPU readers still require `vram_elastic` and retain their existing reserve behavior. Without `vision.elastic: true`, eager and lazy startup remain unchanged.

Regression checks cover text-first loading, first/repeated pictures, idle unload and reload, no CPU VRAM calls, eager startup, and GPU eligibility/reserve behavior. The docs explain the CPU settings and load-time tradeoff. No personal configuration, model files, engine code or dependencies are included.

## Validation

Tested revision: `0fd7a70725de94e9fb847039666ad0973e7df781`, based on current `main` at `e88ee39d` (0.2.0-beta).
Environment: x86-64 Linux (Omarchy/Arch), Python 3.14.8, Ryzen 9 9950X, 60 GiB RAM, RTX 4090 24 GB. The hardware smoke used the released 0.1.45.3 picture reader in **CPU mode**, existing IQ3_S model shards and BF16 mmproj, 8 encoder threads, and synthetic 128×128 PNGs.

**Passed:**

- `python -m unittest serve.test_vision_elastic serve.test_lifecycle serve.test_vram`: 26 tests, OK.
- `python -m unittest serve.test_server serve.test_runconfig`: 350 tests, OK.
- `git diff --check`: clean.
- `python /tmp/strata-cpu-vision-http-smoke.py`, with `STRATA_TEST_CONFIG` pointing to the existing CPU-vision config: a local HTTP harness using the real `Vision` reader and the existing `VramEngine` text-engine fixture. A text request left `Vision.proc` unset; a red-square image started the real CPU reader and encoded; advancing `last_image_at` beyond the configured 120-second threshold and invoking the idle check stopped that process; another text request left it unloaded; a distinct blue-square image started a new process and encoded. Both `engine.vram()` and `Service.free_vram_mib()` were patched to raise on any call. Neither was called. All test processes were shut down.

Observed smoke output (request/timing lines omitted):

```text
PASS: first HTTP text request left CPU encoder unloaded
PASS: first HTTP image started and encoded on CPU
[strata] images: the CPU image encoder stopped after 120 s without a picture
"first_text_keeps_vision_unloaded": true
"idle_stops_process": true
"vram_resize_and_telemetry_calls": 0
"encoder_stopped_at_end": true
```

The unittest runs emitted ResourceWarnings for telemetry fixture files, HTTP errors and sockets; both runs completed with OK.

**Failed:** none observed.

**Not run:** full text-model image-answer generation or quality checks; GPU vision hardware execution; CUDA/HIP/SYCL/Metal builds (no engine or compiled-backend sources changed). The real smoke verifies CPU encoding and lifecycle with a fake text engine, not backend hardware compatibility or answer quality. No performance claim is made.

## Limits

The first picture after unload pays the encoder's load time. With the existing idle polling, a 120-second timeout normally unloads after about 120–150 seconds, later if a request owns the FIFO. Unloading ends the encoder process; existing embedding caches and reclaimable OS file cache may remain. This does not change tool-returned-image handling or fix the separate issue in #3174.

En el sitio

Enlaces a install, modelos, releases.