• Stefy Lanza (nextime / spora )'s avatar
    runpod+ocr: fill a warm floor in waves, and put the OCR pipeline on the pod image (v0.3.69) · 4fe78dbc
    Stefy Lanza (nextime / spora ) authored
    THE WARM FLOOR. A declared floor of 8 took ~40 minutes to exist. Measured on the
    Digesta orchestrator, one pod per scaler pass at ~5 minutes each:
    
        15:15:27  warm floor 8, 2 pod(s) healthy — renting one
        15:21:12  warm floor 8, 3 pod(s) healthy — renting one
        15:32:41  warm floor 8, 4 pod(s) healthy — renting one
    
    Two causes. First, `maintain()` rented exactly one pod per pass, gated on
    `not self._booting`. A floor is a DECLARATION, not a prediction -- min_pods,
    keep_warm or a client lease already said how many should be warm -- so the
    deficit is filled at once, bounded by the model's existing
    `scale_up_max_parallel_boots` (no new knob; it already means "how many pods may
    be booting at once"). Second, `_provision_one` is "Blocking (minutes)" and this
    called it INLINE from the scaler loop, so one warm boot stalled that loop --
    including the registry heartbeat that lives in it and runs every 60s. A stalled
    heartbeat is exactly what makes a sibling treat our booting pod as abandoned,
    which 0.3.68 made load-bearing. Warm boots now run on their own threads.
    
    Adoption loops too (a restart orphans the whole fleet; eleven pods at one per
    tick took six minutes), but BOUNDED two ways: `_adopt_shared_pod()` returning
    True is a report, not a guarantee -- it can succeed without the pool gaining a
    healthy pod -- and the first `while ... and adopt()` form span forever the first
    time that happened, taking the scaler thread and the heartbeat with it. At most
    `deficit` attempts, and stop when an adoption does not raise the count. A pass
    that adopted rents nothing: renting beside a fresh adoption is how one job pays
    for two cards.
    
    THE IMAGE. coderai-vllm-ocr existed to pin vLLM 0.20.1, the version surya
    spawns, because a page of crisp Italian came back from 0.27.1 with one U+FFFD
    per accented letter. That was the wrong conclusion from a real measurement:
    surya-ocr-2's tokenizer is BYTE-level, and the fleet has been reading accented
    Italian on 0.27.1 ever since. Two images serving the same weights on two vLLMs
    is an invisible difference, and it cost three rounds of blaming the pod image.
    So the pins are now IDENTICAL and the check READS the fleet's pin from disk
    rather than restating a version -- bumping profiles/vllm cannot silently leave
    this image behind.
    
    What distinguishes the image instead is the OCR PIPELINE on the pod: the surya
    client venv beside the model, plus pypdfium2 in the main venv, so a page is
    rasterised and read on the card rather than crossing the network as a ~1 MB
    base64 PNG (and four more times over for a page resumed four times).
    
    CPU TORCH, and it is the whole image-size story. Measured in a resolved surya
    venv: nvidia 3196 MB + torch 1175 MB + triton 896 MB = ~5.3 GB of CUDA stack,
    for a venv whose only local torch workload is surya's small line detector. On
    the pod the GPU is already committed to serving the model, so giving the
    detector the card would take VRAM and SMs from the thing that matters; on a
    GPU-less orchestrator it can never execute at all (cuda available: False today
    while its surya venv carries cu130). ~280 MB instead of ~5.3 GB, and the venv
    check asserts torch.version.cuda is None so a resolve cannot quietly add 5 GB
    back.
    
    THE DETECTOR rides that same CPU torch, which is why it costs ~30 MB rather
    than a third torch. detect.py tries ultralytics in-process first (every existing
    install is unchanged) and falls back to codai/ocr/workers/detect_worker.py in
    whichever venv has it -- surya's first. A detector that cannot be reached looks
    like a MISSING detector (the ordinary 503), never a broken install. No weights
    are baked: detect_model_path defaults to "" and a released image must contain no
    models, configs, keys or secrets.
    
    Tests: 17 warm-floor (+4 for the spin), 14 detector. Four older tests encoded
    the decisions this reverses -- two asserted the 0.20.1 pin (replaced by one
    requiring the two pin files to be identical), one asserted the warm floor "must
    still be single-flight", one counted a source string globally and could not tell
    intent-held-spelling-changed from a regression (now per path). Suite at the 25
    pre-existing failures.
    
    NOT BUILT and NOT USABLE YET: nothing has resolved surya-ocr + pillow<11 +
    torch+cpu + ultralytics together, and the orchestrator cannot drive a pod-side
    pipeline until the dual mode lands.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01HXRMe9ZPUQCCCtBai9uqt3
    4fe78dbc
test_runpod_client_scale.py 22.6 KB