1. 06 Sep, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      runpod(phase2): serverless request path + per-model config + no-local-download · 17604ab4
      Stefy Lanza (nextime / spora ) authored
      End-to-end serverless: a model tagged backend:runpod (mode:serverless) proxies
      OpenAI chat/completions to a RunPod serverless endpoint. Runs on a remote GPU,
      holds NO local weights.
      
      - codai/api/runpod_worker.py: RunpodModelConfig + parse_model_runpod (mode/pools/
        criteria/gpu/price/scaling/cost/serverless), serverless_base_url + auth_headers.
      - codai/backends/runpod.py: RunpodBackend — thin OpenAI proxy (chat/stream, Bearer
        auth for serverless), uses_local_vram=False; pods/auto raise a clear "next phase"
        error for now.
      - manager.py: runpod in _ENGINE_BACKENDS + _runpod_name_claims (pin-only) +
        runpod_should_handle + load_model dispatch branch. _model_is_runpod() gate:
        set_default_model skips local download/cache for runpod-served models.
      - router.py + registry.py: runpod is an explicit-pin capability; added to every
        engine's default caps (no local VRAM → any node hosts the proxy; prefers primary).
      - admin routes: whitelist per-model `runpod` + `runpod_spillover` blocks; skip
        local-VRAM estimate for runpod models.
      - models.html: RunPod backend option + full per-model sub-form (mode w/ explainer,
        serverless endpoint, served model, pools/criteria/gpu/price/spot, min/max pods,
        idle timeout, image/disk/port/env, spend limit+period) + prefill/collect.
      
      Serverless verified wired in-image (ef1fd7a3802c). Pods pool/scaler, cost ledger,
      spillover routing, and stats UI are later phases.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      17604ab4
  2. 05 Sep, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      runpod(phase1): account config + GraphQL client + admin key/GPU-catalog plumbing · 7c057af2
      Stefy Lanza (nextime / spora ) authored
      First slice of the RunPod remote-GPU backend (rent cloud GPUs, proxy inference).
      Dormant unless enabled — no request path yet.
      
      - config.py: RunpodConfig (enabled/api_key/cloud_type/api_base/rest_base/
        serverless_base/default_gpu_type/data_center/global_max_hourly_usd); registered,
        loaded, serialized. get_active_runpod_config() in manager.py.
      - codai/api/runpod_client.py: GraphQL client — ping, list_gpu_types (+prices),
        pick_gpu (cheapest under a $/hr ceiling, VRAM filter, spot-aware), create_pod
        (on-demand + interruptible/spot), get_pod, stop/terminate, list_pods, wait_ready.
        API key via ?api_key=, masked in all error strings.
      - admin: settings GET/save runpod block (key masked, overwritten only on a real
        new value); GET /admin/api/runpod/gpu-types (key check + catalog) and
        /admin/api/runpod/status (pods + live $/hr).
      - settings.html: RunPod card (key/cloud/default-gpu/global $/hr cap/DC/advanced
        bases) + "Test key & list GPUs" button.
      
      Decisions locked: host in primary engine (Layer-2, zero local VRAM); default
      vLLM-OpenAI pod image; per-model $/hr + global budget; per-model allow_spot;
      mode pods|serverless per model. TODO (request-path phase): RunPod requests must
      bypass the GLOBAL concurrency gate only (QueueManager.max_parallel_requests).
      
      Client GraphQL field names written from docs — pending live verification with a key.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      7c057af2
  3. 02 Sep, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      audio(whisper-server): convert mp4/any input with our ffmpeg (fix stuck-queue hang) · 717ab1ad
      Stefy Lanza (nextime / spora ) authored
      whisper-server models (whisper0/1/2/3) relied on whisper.cpp's --convert,
      whose ffmpeg call deadlocks on mp4 (undrained pipe) and hangs forever,
      head-of-line blocking the single-threaded server so every later request
      queues (app reported jobs "stuck in queue"; found a --convert ffmpeg stuck 4h+).
      
      - WhisperServerManager.start(): drop the --convert flag.
      - add _to_wav_16k_mono(): ffmpeg via subprocess.run (drains stdout/stderr,
        no pipe deadlock) + 120s timeout; transcribe() converts before POST so
        whisper.cpp only ever receives clean 16k mono wav. Bad file fails fast.
      - keep_resident honored for whisper-server models (vram group membership).
      
      Verified on f595eb2a6ece after relaunch: whisper3 + mp4 -> 200 (was infinite
      hang); wav control + repeat mp4 return sub-second (no head-of-line stall).
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      717ab1ad
  4. 01 Sep, 2026 6 commits
    • Stefy Lanza (nextime / spora )'s avatar
      audio: CrisperWhisper long-form overlap windowing (no seam drops) · b6f6ca9e
      Stefy Lanza (nextime / spora ) authored
      Contiguous windows dropped words at each ~28s seam (Whisper's weak edge context +
      words straddling the boundary). Now use overlapping windows (29s wide, 24s hop =
      5s overlap), advancing by the hop and transcribing each full window, then
      de-duplicate the overlap by keeping only words that start at/after the last kept
      word's end, and rebuild the transcript from the de-duped words. Verified on an
      ~11min clip: 0 gaps >2s across all seams, continuous word timestamps to 673s,
      recovered the previously-dropped seam words.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      b6f6ca9e
    • Stefy Lanza (nextime / spora )'s avatar
      audio: CrisperWhisper long-form windowing + worker self-heal · b0483e8c
      Stefy Lanza (nextime / spora ) authored
      - Long-form fix: the HF pipeline's internal word-timestamp stitching crashes at
        the ~30s chunk boundary for CrisperWhisper (IndexError, dim-2 index==size). The
        service now windows manually — pipeline built without chunk_length_s; audio read
        via soundfile, split into <=28s windows (each a single Whisper pass), per-window
        word/segment timestamps offset by window start and concatenated. Verified on an
        ~11min clip: 1877 words, continuous timestamps to 673s, HTTP 200.
      - Self-heal: _RemoteCrisperWhisperBackend + _RemoteNemoBackend re-ensure the
        worker and retry once on a ConnectionError, so a restarted/killed worker no
        longer leaves a stale cached service_url in acquire_stt_backend.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      b0483e8c
    • Stefy Lanza (nextime / spora )'s avatar
      audio: CrisperWhisper support (isolated venv, verbatim + word timestamps) · b069137b
      Stefy Lanza (nextime / spora ) authored
      CrisperWhisper does verbatim ASR with precise word timestamps, but its custom
      generation config crashes on the server's transformers 5.x. Run it in its own
      venv worker (pinned torch 2.5.1+cu124 + transformers 4.46.3 — last pre-5.x with
      working Whisper word timestamps and a py3.13 tokenizers wheel):
      - codai/api/crisperwhisper_worker.py + tools/crisperwhisper_service.py
        (return_timestamps="word" + CrisperWhisper pause redistribution).
      - requirements-crisperwhisper.txt.
      - stt_backends.py: `crisperwhisper` is its own family -> _RemoteCrisperWhisperBackend
        (HTTP to the worker); generic `whisper-hf` still runs in the shared venv
        (text-only default + graceful timestamp fallback); HF-ASR path now converts any
        container (mp4/m4a/webm) to 16k mono wav first and uses model-correct
        return_timestamps (Whisper True/"word", CTC "chunk"/"word").
      - transcriptions.py: crisperwhisper dispatched as a GPU worker.
      - manager skip-download + admin backend option for whisper-hf/crisperwhisper.
      
      Verified: mp4 -> verbatim text + per-word timestamps, HTTP 200, on the 3090.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      b069137b
    • Stefy Lanza (nextime / spora )'s avatar
      audio: FP16 wav2vec2 on GPU (halves VRAM) · d51f46d2
      Stefy Lanza (nextime / spora ) authored
      Load the wav2vec2/HF-ASR pipeline in FP16 on CUDA (~3GB -> ~1.5GB), FP32 on CPU,
      with a fallback to FP32 if a checkpoint rejects half precision; overridable via a
      `dtype` config field. Lower the wav2vec2 VRAM reservation default 3->2GB to match.
      Enables co-residency of the full transcription/diarization/speaker stack with a
      14B LLM on one 24GB 3090 (measured ~14.5GB used with a gemma-4-14b @8k sibling).
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      d51f46d2
    • Stefy Lanza (nextime / spora )'s avatar
      audio: sliding-window speaker embeddings (window/step) · ed8c014d
      Stefy Lanza (nextime / spora ) authored
      /v1/audio/speaker-embeddings gains window (seconds) + step (default = window):
      returns one embedding per sliding window, each tagged start/end, instead of a
      single whole-file embedding. Implemented in the pyannote worker's /embed via
      _crop_wav over the cached embedder; client + endpoint pass the params through;
      whole-file mode and enroll/identify/verify are unchanged.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      ed8c014d
    • Stefy Lanza (nextime / spora )'s avatar
      audio: STT backends, diarization, speaker embeddings/ID, word timestamps · 7e5a925c
      Stefy Lanza (nextime / spora ) authored
      Transcription (/v1/audio/transcriptions):
      - Route multipart uploads by peeked model (front _peek_model) so they land on
        the model's assigned engine instead of scattering by capability.
      - New pluggable STT backends (codai/api/stt_backends.py): wav2vec2 (HF), vosk
        (CPU dirs), NVIDIA NeMo-Canary (isolated venv worker) + faster-whisper/whisper.
      - Per-model language selection + validation; Canary source->target translation.
      - OpenAI timestamp_granularities[]=word: top-level words[] in verbose_json
        (vosk/wav2vec2/faster-whisper/canary).
      - VRAM-eviction tracking (acquire_stt_backend) + keep_resident co-residency for
        the small set; /v1/models surfaces languages + backends.
      
      Diarization (/v1/audio/diarization + diarize=true):
      - pyannote.audio isolated venv worker (codai/api/pyannote_worker.py,
        tools/pyannote_service.py) on the 3090; speaker-labeled segments; graceful
        degradation. Pinned torch/pyannote/hf_hub/matplotlib in requirements-pyannote.txt.
      
      Speaker embeddings + recognition:
      - /v1/audio/speaker-embeddings (ecapa via speechbrain, pyannote/wespeaker).
      - Enroll/list/delete + identify/verify (codai/api/speaker_registry.py); persistent
        voiceprint store; diarization identify=true relabels turns with enrolled names.
      
      New isolated-venv requirements: requirements-nemo.txt, requirements-pyannote.txt
      (+ vosk in requirements.txt).
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      7e5a925c
  5. 27 Aug, 2026 7 commits
    • Stefy Lanza (nextime / spora )'s avatar
      vram: don't ratchet down a model's learned GPU-layer split under contention · f54428cf
      Stefy Lanza (nextime / spora ) authored
      record_vram_delta persisted measured_n_gpu_layers from every load, including
      VRAM-CONTENDED ones (another model resident → llama.cpp fit fewer layers). Since
      _learned_n_gpu_layers reuses that as a hard cap for auto (-1) configs, one unlucky
      contended load pinned a model half on CPU permanently — it never recovered even
      when the card was later free.
      
      Observed on the gemma-4-26B-A4B 32k config (alias lisa-32k): a contended load
      ratcheted 31→20 GPU layers and stuck, spilling ~5.8 GB of weights + KV to CPU on
      a 3090 with 11 GB free.
      
      Fix: in record_vram_delta, never overwrite a HIGHER known-good measured_n_gpu_layers
      with a LOWER value from an offloaded (CPU-spilled) load. A genuinely-too-big model
      still settles at its free-card maximum on an uncontended load, so keeping the higher
      value is safe; worst case is one offload-retry on a later truly-contended load.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      f54428cf
    • Stefy Lanza (nextime / spora )'s avatar
      logging: durable request-debug sink + non-silent engine log pump · ced55385
      Stefy Lanza (nextime / spora ) authored
      Fixes "embedding request debug on radeon disappeared" — a real regression
      where the radeon engine stopped emitting ALL log output the instant it
      finished loading its GGUF embedders (~13s after launch), while still serving,
      so its FULL REQUEST DEBUG + thermal debug vanished with no trace.
      
      Two changes:
      
      1) codai/api/log.py — the log_requests middleware now ALSO appends each
         FULL REQUEST DEBUG block, from the engine's own process, straight to
         <dir(CODERAI_LOG_FILE)>/requests.log via a single atomic O_APPEND write
         (tagged [ts][engine] from CODERAI_ENGINE_NAME). This survives the engine's
         stdout stream dying, so request bodies are captured regardless. Concurrent
         engines never interleave mid-block.
      
      2) codai/frontproxy/engine_supervisor.py — _pump_logs no longer dies silently:
         a bad line can't kill the pump, and on stream end it prints
         "[pump] log stream ended (<stdout EOF|pump error>); engine still ALIVE/exited"
         so a vanishing engine log stream is diagnosable instead of looking like
         "debug turned off".
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01R6iugoNeEgt9PkajKrFyq4
      ced55385
    • Stefy Lanza (nextime / spora )'s avatar
      config(multi-config): fix GGUF dispatch + per-config-id measured persistence · f9f2a373
      Stefy Lanza (nextime / spora ) authored
      Follow-up to the same-path multi-config runtime fix. Two more path-keyed spots
      broke a same-file sibling addressed by its alias (e.g. lisa-32k):
      
      1) GGUF dispatch: _resolve_local_gguf matched entries by path/basename only, so
         a sibling addressed by alias wasn't recognized as GGUF and fell to the HF
         loader ("lisa-32k is not a valid model identifier"). Now also match the
         entry's alias.
      
      2) Measured footprint cross-contamination: persist_model_field matched entries
         by path, so one config's measured_vram_gb/n_gpu_layers overwrote the sibling's
         — the small-ctx variant inherited the large-ctx footprint (34 GB) and still
         offloaded to CPU. persist_model_field now targets by config_id when given;
         record_vram_delta passes cfg['config_id']; _model_cfg carries config_id/
         config_name so the config knows its own entry identity.
      
      Net: lisa (178K) and lisa-32k (32K) are now fully independent — distinct config,
      GGUF load from the shared file, and separate measured footprints.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      f9f2a373
    • Stefy Lanza (nextime / spora )'s avatar
      config: honor same-path multi-config at runtime (key siblings by alias) · 6ec4af1c
      Stefy Lanza (nextime / spora ) authored
      The web UI stores sibling configs for one model file (distinct config_id, e.g. a
      smaller-context variant), and the front already routes by alias (_route_key:
      "two configs of the same model with distinct aliases can be assigned to
      different engines"). But engine startup keyed every text-model config by path
      (_model_id -> path), so a second same-path entry OVERWROTE the first —
      BOTH aliases then resolved to the last-registered config (verified: lisa and
      lisa-32k both got n_ctx=32768, silently changing the primary too).
      
      Fix: in main.py text-model registration, key the PRIMARY config by path (stays
      path-addressable) and each SIBLING (same path, already seen) by its ALIAS, so
      the two configs are distinct and each alias resolves to its own n_ctx. A sibling
      without an alias is skipped (can't be addressed). The GGUF loader already reads
      the file from cfg['path'], so an alias-keyed sibling still loads the right file.
      Verified: lisa->178000, lisa-32k->32768, same underlying .gguf.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      6ec4af1c
    • Stefy Lanza (nextime / spora )'s avatar
      rerank: resolve model via embedding category to pass manager type-validation · b72896e5
      Stefy Lanza (nextime / spora ) authored
      The reranker is registered in embedding_models, so request_model(model,"rerank")
      was rejected ("is a embedding model and cannot be used for rerank generation").
      Resolve with model_type="embedding" (its actual category) so validation passes;
      rerank.py still loads it as a cross-encoder (AutoModelForSequenceClassification),
      re-loading if a cached embedder object is found under the same key.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      b72896e5
    • Stefy Lanza (nextime / spora )'s avatar
      deps: bump bitsandbytes to >=0.50.2 (fix 4-bit load on transformers 5.12) · 28d7711d
      Stefy Lanza (nextime / spora ) authored
      bitsandbytes 0.49.2's Params4bit.__new__ has no **kwargs and rejects the
      _is_hf_initialized kwarg that transformers 5.12 passes during 4-bit weight
      init, so every load_in_4bit model crashed with:
        Params4bit.__new__() got an unexpected keyword argument '_is_hf_initialized'
      In production this turned a single default-model chat request (Qwen3.5-9B,
      load_in_4bit) into a ~2h internal 60-retry storm. bnb 0.50.2 adds **kwargs to
      Params4bit.__new__ (absorbs _is_hf_initialized); verified end-to-end: a
      transformers load_in_4bit load now succeeds on cuda:0 (torch 2.11/cu130), and a
      GPU 4-bit quantize/dequantize round-trips. Image rebuilt with bnb 0.50.2.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      28d7711d
    • Stefy Lanza (nextime / spora )'s avatar
      rerank: add /v1/rerank cross-encoder endpoint + surface OCR in /v1/models · e44d41ac
      Stefy Lanza (nextime / spora ) authored
      Reranker (e.g. BAAI/bge-reranker-v2-m3):
      - new codai/api/rerank.py: /v1/rerank scores query↔document pairs with a native
        AutoModelForSequenceClassification cross-encoder (no new dep), cached +
        VRAM/thermal-managed via request_model like embeddings. Returns
        {index, relevance_score(sigmoid), document?} sorted desc, top_n honored.
      - router: /v1/rerank added to _INFERENCE_PATHS so the front routes it by model
        capability (transformers → nvidia engine), like /v1/embeddings.
      - app: include rerank_router.
      - capabilities: add `reranking` + `ocr` fields; detect 'rerank'/'cross-encoder'
        names as reranking.
      
      /v1/models discoverability:
      - list_models() now appends enabled OCR engines (paddle/doctr/surya) as
        type='ocr' entries (a node subsystem invoked at /v1/ocr?engine=<id>), so
        clients can discover them.
      - a reranker registered in models.json (embedding_models, capabilities
        ['reranking']) surfaces automatically via list_models, like bge-m3.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      e44d41ac
  6. 26 Aug, 2026 4 commits
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings(bge-m3): vectorize sparse/colbert aggregation on GPU · 59f6ebe0
      Stefy Lanza (nextime / spora ) authored
      The per-token Python loops that built the sparse {token_id: weight} dict and
      the colbert vector lists held the GIL between forward passes, starving the GPU
      (steady ~10-36% util with brief bursts). Replace them with GPU tensor ops:
      
      - sparse: relu(sparse_linear·h) with invalid positions (specials/padding)
        zeroed, then max-pooled per token id via a single scatter_reduce(amax) into a
        (B, vocab) matrix; per-row nonzero extraction is O(nnz), not O(L).
      - colbert: one C-level tolist per row over its valid tokens (vs per-token).
      - valid mask computed on-device via torch.isin.
      
      Output byte-matches the previous per-token logic (dense/sparse/colbert within
      1e-4). Benefit grows with batch size, so larger client batches translate to GPU
      work instead of Python-loop time.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      59f6ebe0
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings(bge-m3): reliable sparse_head resolution via cache glob · 8fb4f85a
      Stefy Lanza (nextime / spora ) authored
      hf_hub_download(local_files_only=True) intermittently failed to locate the
      cached sparse_linear.pt inside the engine process, so _is_bge_m3 returned
      False and bge-m3 silently degraded to the dense-only sentence-transformers
      path. _resolve_repo_file now falls back through try_to_load_from_cache and a
      direct filesystem glob of the HF cache (models--org--name/snapshots/*/file),
      which resolves reliably regardless of process context. Detection keys off the
      sparse head file rather than the model_type string. (Debug scaffolding removed.)
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      8fb4f85a
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings(bge-m3): fix loader crash + head dtype/grad · cc06016d
      Stefy Lanza (nextime / spora ) authored
      - import build_from_pretrained_kwargs inside _load_bge_m3 (it's a local import
        of _load_embedding_model, not module-level) — the NameError made every bge-m3
        load fall over so the model silently degraded to the dense-only path.
      - cast the sparse/colbert heads to the base model's dtype (bf16) so the head
        matmul over the last hidden state doesn't dtype-mismatch.
      - requires_grad_(False) on the heads (inference only) — no autograd graph per
        request.
      - log the bge-m3 backend selection at load.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      cc06016d
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings: native bge-m3 dense+sparse(+colbert) multi-vector support · 66f5540b
      Stefy Lanza (nextime / spora ) authored
      Add BAAI/bge-m3 hybrid embedding (dense + sparse lexical, optional colbert)
      without the FlagEmbedding dependency (which pins older transformers/peft and
      would conflict with the transformers 5.x stack): the sparse/colbert heads are
      plain Linear layers over XLM-RoBERTa's last hidden state, reproduced natively
      with transformers + torch already in the venv.
      
      - new 'bge-m3' embedding backend: detected by xlm-roberta model_type + a
        sparse_linear.pt head (so plain bge-large etc. stay on the dense ST path);
        loads the base model + sparse_linear.pt (+ colbert_linear.pt if present).
      - _bge_m3_encode: dense = L2-normalized CLS; sparse = max-pooled
        relu(sparse_linear·h) per token id (specials dropped) as {token_id: weight};
        colbert = L2-normalized colbert_linear·h per content token.
      - /v1/embeddings extended: request.embedding_types selects any of
        dense|sparse|colbert (overrides the model-config default); response items
        gain sparse_embedding {indices, values} and colbert_embedding. Dense stays
        in the standard `embedding` field (base64 honored). Configurable default via
        the model's embedding_types config.
      - cleanup()/eviction branch moves the base model + heads off GPU.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      66f5540b
  7. 22 Aug, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      ocr: make managed Surya-2 vLLM reclaimable by VRAM eviction · 2c419247
      Stefy Lanza (nextime / spora ) authored
      The Surya-2 engine serves its VLM via a managed vLLM subprocess
      (vllm_worker.ensure_service). That subprocess is not a manager-tracked
      model, and the OCR VRAM releaser only tore down the lightweight worker
      pools (killing the HTTP workers) — it never stopped the vLLM holding the
      bulk VRAM (gpu_memory_utilization × card). So on-request eviction saw the
      VRAM as used, found nothing it could free, and (with no CPU fallback) a
      docTR/Paddle load on the same card could OOM.
      
      - vllm_worker: add stop_service_for(cfg, model_path, served_name) (stop ONE
        service, return estimated GB freed) + is_running() helper.
      - OcrManager._release_vram now also stops the managed Surya-2 vLLM
        (_stop_surya_vllm) when surya_serve == "vllm", so the registered external
        releaser actually reclaims its VRAM. Next OCR request re-boots it via
        ensure_service. Targeted by (model_path, served_name) so an LLM vLLM
        instance is left alone.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      2c419247
  8. 21 Aug, 2026 10 commits
  9. 20 Aug, 2026 2 commits
  10. 16 Aug, 2026 2 commits
    • Stefy Lanza (nextime / spora )'s avatar
      ocr: non-blocking first-use isolated-venv build (background + 503 retry) · f8b86701
      Stefy Lanza (nextime / spora ) authored
      First OCR request for an unbuilt paddle/surya engine no longer blocks for minutes;
      with auto_build it kicks off the background builder (same one the web UI uses) and
      returns 503 'building — retry shortly'. Without auto_build it points at Settings.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      f8b86701
    • Stefy Lanza (nextime / spora )'s avatar
      Add dedicated OCR subsystem + finalize multi-engine backends · 2485403f
      Stefy Lanza (nextime / spora ) authored
      OCR (v0.1.85): new codai/ocr/ subsystem — dedicated OCR engines (NOT a VLM):
      - docTR (in-process, GPU) + PaddleOCR & Surya (isolated-venv subprocess workers
        via codai/ocr/workers/ocr_worker.py to avoid opencv-contrib/pillow<11 clashes;
        paddle-gpu bundles its own CUDA so a cu12x wheel runs on newer-CUDA hosts).
      - /v1/ocr, /v1/ocr/batch, /v1/ocr/schemas CRUD; `ocr` pipeline step.
      - Data-driven, user-extensible extraction schemas (named files + inline + auto),
        built-in seeds (italian_sentenza/generic_document/invoice); optional JSON Schema
        validation. Structured extraction via an existing coderai text model.
      - Configurable stamp/signature detection (off|layout|detector|both); bundled
        signature YOLO default; layout markers EN+IT (timbro/sigillo/firma).
      - Admin OCR card: engines/instances/detect/extraction + schema picker/raw-JSON/
        field-builder + isolated-venv build trigger (background build + status).
      - Isolated venvs resolve baked(/opt) > /cache mount (persistent) > ~/.coderai.
      - Deps: pypdfium2 in base requirements; requirements-ocr(.txt/-paddle.txt);
        requirements-surya.txt; build.sh --ocr.
      
      Also lands the multi-engine backend work: colibri multi-family (GLM/DeepSeek/Kimi),
      kimi-k3-in-c (k3), ktransformers (kt) — per-model selectable via resolve_engine_backend;
      plus a deferred vLLM backend design note (docs/vllm.md).
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_01Mw2KQiswmD69T45fTfjKwW
      2485403f
  11. 31 Jul, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings 300ms default + admin GUI throttle fields; radeon crash-loop breaker · 07dd05a1
      Stefy Lanza (nextime / spora ) authored
      Embedding admission gate:
      - default min-interval 200ms -> 300ms (paces GPU starts harder)
      - expose embed_max_concurrency / embed_max_backlog / embed_min_interval_ms
        per-model in the admin model editor (embedding-gated section), round-tripped
        through /admin/api/model-configure into models.json
      
      Engine supervisor circuit breaker:
      - quarantine an engine that exits crashloop_max (5) times within
        crashloop_window (120s) instead of respawning it ~1/s forever. A GPU that has
        fallen off the bus (Polaris secondary-bus-reset bug) makes its engine die on
        every launch; the breaker takes radeon cleanly out of routing (embeddings fail
        fast) instead of hammering a dead card. Manual restart_engine() clears it.
      
      Pairs with host-side RX580 mitigations (dpm=high lock, lockup_timeout=25s,
      conc=1/300ms on the 3 vulkan embedding models, reset watchdog + thermal sampler).
      07dd05a1
  12. 30 Jul, 2026 1 commit
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings: per-model throttle + default 200ms pacing · dc6026ef
      Stefy Lanza (nextime / spora ) authored
      The RX 580 (Polaris/Vulkan) keeps timing out its GPU ring under relentless
      back-to-back embedding load — a global throttle can't distinguish a fragile card
      from a robust one. Make the admission gate PER-MODEL: each model id gets its own
      semaphore + backlog counter + pacing, resolved from the models.json entry
      (embed_max_concurrency / embed_max_backlog / embed_min_interval_ms), falling back
      to the CODERAI_EMBED_* env vars, then defaults. Default min interval is now
      200ms (was 0) so GPU starts are paced out of the box, giving the ring breathing
      room; a robust CUDA model can set it to 0 per-model. Bump 0.1.82.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_0196Gm4PNkcuybDj9yfcz3CR
      dc6026ef
  13. 29 Jul, 2026 3 commits
    • Stefy Lanza (nextime / spora )'s avatar
      colibri: single-client run_serve mode (kv_slots=1) to keep MTP enabled · 98963164
      Stefy Lanza (nextime / spora ) authored
      The mux serve path (SERVE_BATCH=1, run_serve_mux) force-disables the int8 MTP
      speculative-decode head ("speculation is not ragged-safe across KV slots") and
      its batched decode loop has no MTP path at all — MTP lives only in run_serve's
      single-sequence spec_decode. Since coderai drives colibri with kv_slots=1, drive
      run_serve instead (SERVE_BATCH=0): accepted MTP drafts mean fewer forward passes
      = fewer expert-streaming rounds, a real decode win on this IO-bound engine.
      
      MuxEngine now selects single-client mode when kv_slots==1: launches run_serve,
      drains its one-shot startup telemetry, and runs turns synchronously over the
      run_serve byte protocol (\x02PROMPT frame in; raw text + \x01\x01END\x01\x01 +
      STAT out) with SIGINT-based cancellation. Multi-slot keeps the batched mux path.
      pause/resume are no-ops in single-client mode (run_serve would misread the frame;
      thermal falls back to the front's SIGSTOP). Bump 0.1.81.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_0196Gm4PNkcuybDj9yfcz3CR
      98963164
    • Stefy Lanza (nextime / spora )'s avatar
      engines: scope cross-backend pooling to the split's lead engine only · cedaabb3
      Stefy Lanza (nextime / spora ) authored
      allow_cross ("use both cards for one model") was applied to EVERY engine the
      moment any model enabled gpu_split — which un-isolated the radeon engine's
      Vulkan ICD too. Combined with the embedding loader not pinning non-split models
      to their native card, the radeon engine's embeddings (Qwen3-Embedding,
      gme-Qwen2-VL) drifted onto the NVIDIA 3090 after a Vulkan device-order change.
      
      Scope it: each engine is isolated to its own backend by default; a per-model
      split names its lead engine (the model's `engine` field), and only that lead's
      card gets cross-backend visibility. A global offload.gpu_split still crosses all
      engines (explicit opt-in). Preserves nvidia-led splits onto the RX 580 while
      keeping radeon/intel Vulkan engines pinned to their own card. Bump 0.1.80.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_0196Gm4PNkcuybDj9yfcz3CR
      cedaabb3
    • Stefy Lanza (nextime / spora )'s avatar
      embeddings: admission gate (bounded concurrency + backlog shedding + pacing) · bbac56b1
      Stefy Lanza (nextime / spora ) authored
      Embeddings bypassed the front request queue (relayed unqueued) and the
      per-model lease scheduler, so the only guard was ratelimit.py's per-IP fixed
      window. A single authenticated client could pipeline unlimited embedding
      requests straight at the GPU — which on the Polaris/RX580 Vulkan backend wedged
      the SDMA ring (ring timeout -> GPU reset -> device lost from bus).
      
      Add _embed_admission() around create_embeddings: bounded concurrency, backlog
      shedding (429 when in-flight >= concurrency + backlog), and optional pacing
      between GPU starts. Tunable via CODERAI_EMBED_MAX_CONCURRENCY (2),
      CODERAI_EMBED_MAX_BACKLOG (32), CODERAI_EMBED_MIN_INTERVAL_MS (0). Bump 0.1.79.
      Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_0196Gm4PNkcuybDj9yfcz3CR
      bbac56b1