• Stefy Lanza (nextime / spora )'s avatar
    longcat: cache prompt embeddings, so the encoder need not load (v0.2.81) · 175f2b2a
    Stefy Lanza (nextime / spora ) authored
    The UMT5-XXL text encoder is ~11 GB and turns a prompt into a few MB of
    embeddings that are a pure function of the prompt and the encoder. v0.2.76 made
    that run once per request instead of once per segment; this makes it once per
    prompt, ever.
    
    The saved GPU pass is not the point — it is seconds. The point is that a cached
    prompt needs no encoder AT ALL, so with the cache on the encoder is built
    lazily: ~11 GB of host RAM and about a minute of load that a repeated prompt
    never spends. That is the answer to "can we keep everything in VRAM": not by
    quantising the encoder (v0.2.79 measured int8 resident at 24.0 GB on a 24 GB
    card, and NF4 at 22.1 GB with less headroom than a 29-frame clip already uses),
    but by not having an encoder resident at all.
    
    Off by default — embeddings are derived data, but they are also several MB each
    under the user's cache directory, so growing that is opted into:
    
        "prompt_cache": "off",          # off | disk
        "prompt_cache_dir": "",         # blank = <coderai home>/prompt_cache/longcat
        "prompt_cache_max_gb": 0,       # 0 = 2 GB, least-recently-used evicted
    
    per model or server-wide, validated at startup rather than after a seven-minute
    load, settable from the models page, and in api_model_configure's whitelist so a
    save from that page does not silently drop it.
    
    An entry served to the wrong pipeline would be a silent quality change, which is
    worse than a miss, so the key covers the checkpoint, the dtype, the encoder's
    quantisation and the call's own arguments — an NF4 encoder cannot be served a
    bf16 entry. `device` is deliberately excluded: it moves the embeddings without
    changing them, and including it would split one entry into one per device.
    
    Every failure is a miss, never an error. Entries are written to a .tmp and
    renamed, read with weights_only=True (a cache directory is not a trusted source
    for a pickle), deleted when unreadable, and a cache that cannot be written warns
    once and generation carries on without it.
    
    _LazyEncoder is deliberately not a transparent proxy: upstream guards every use
    with `if self.text_encoder is not None`, so a proxy that looked like a module
    would be moved to the card by pipe.to() and offloaded by _place_pipeline,
    building the very weights it exists to avoid. pipe.text_encoder stays None and
    the holder sits beside it in _state, which is why both paths already do the
    right thing.
    
    Found while wiring it: a lazily realised encoder can be quantised, and the
    offload swap would have shuttled bitsandbytes weights to the host. The swap
    guard now checks the model, not just the offload mode.
    
    /health reports the cache and whether the encoder was ever built — laziness is
    invisible otherwise — and GET /prompt-cache returns its stats.
    
    No new dependencies. Unverified on the card: the round trip, eviction, corrupt
    entries and the key are covered by 54 tests against real tensors, but no
    generation has run through it yet.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_0185zEBiy3KWB37xNGSrxL7w
    175f2b2a
Name
Last commit
Last update
..
test_admin_auth.py Loading commit data...
test_admin_remote_ui.py Loading commit data...
test_audio_ml_backends.py Loading commit data...
test_audio_ml_endpoints.py Loading commit data...
test_broker_config.py Loading commit data...
test_broker_dispatch.py Loading commit data...
test_broker_protocol.py Loading commit data...
test_broker_streaming.py Loading commit data...
test_burst_placement.py Loading commit data...
test_character_studio_fetch.py Loading commit data...
test_compose.py Loading commit data...
test_front_ocr_swap_gate.py Loading commit data...
test_image_has_c_compiler.py Loading commit data...
test_longcat_avatar_layout.py Loading commit data...
test_longcat_bsa.py Loading commit data...
test_longcat_cancellable.py Loading commit data...
test_longcat_checkpoint_resolve.py Loading commit data...
test_longcat_component_quant.py Loading commit data...
test_longcat_config_ui.py Loading commit data...
test_longcat_external_training.py Loading commit data...
test_longcat_int8_load.py Loading commit data...
test_longcat_lora.py Loading commit data...
test_longcat_orchestration.py Loading commit data...
test_longcat_packaging.py Loading commit data...
test_longcat_placement.py Loading commit data...
test_longcat_prompt_cache.py Loading commit data...
test_longcat_quantize.py Loading commit data...
test_longcat_scale.py Loading commit data...
test_longcat_tasks.py Loading commit data...
test_longcat_variants.py Loading commit data...
test_longcat_vendored_source.py Loading commit data...
test_longcat_venv_env.py Loading commit data...
test_longcat_vram_estimate.py Loading commit data...
test_longcat_worker.py Loading commit data...
test_manual_multimodal_test_client.py Loading commit data...
test_model_scan_cache.py Loading commit data...
test_ocr_olmocr.py Loading commit data...
test_ocr_vram_share_and_fallback.py Loading commit data...
test_remote_execution.py Loading commit data...
test_runpod_ocr_remotization.py Loading commit data...
test_studio_composed_surfaces.py Loading commit data...
test_township_api_key.py Loading commit data...
test_township_longcat.py Loading commit data...
test_township_page_js.py Loading commit data...
test_township_templates.py Loading commit data...
test_township_video_dataset.py Loading commit data...
test_upgrade_launcher.py Loading commit data...
test_video_as_b64_scope.py Loading commit data...
test_vllm_share_self_tuning.py Loading commit data...
test_vram_releasers.py Loading commit data...
test_weight_stores.py Loading commit data...
test_whisper_server_local_models.py Loading commit data...