Skip to content

Lend part of a card from a running engine and get it back, without a park or a reload #189

Open

Description

@TacoTakumi

Project home: https://github.com/TacoTakumi/vram-lend

My setup

  • Ubuntu 24.04, NVIDIA driver 610.43.02, CUDA 13.
  • 2x RTX 3090 (24 GB), 1x RTX 5060 Ti (16 GB) and 2x RTX 4060 Ti (16 GB). 96 GB of VRAM, 128 GB RAM.
  • llama-swap serves everything. Most models run on llama.cpp. GLM-5.3-Flash and DeepSeek V4.1 Flash run on Project Maya, Qwen 3.8 Flash Next runs on Strata.
  • Right now Maya runs GLM-5.3-Flash and DSV41 as three llama-swap entries, on 2, 3 or 5 cards.
  • ComfyUI runs under llama-swap on the 5060 Ti.
  • VoiceManager runs on a 4060 Ti.

What I want to do

When I want ComfyUI or a voice model, I switch Maya from the 5 card entry to the 2 or 3 card entry. The model reloads and I lose the session cache. When I am done I switch back and it reloads again.

Maya keeps the experts in system RAM and a cache of them on the cards. It can already give part of that cache away inside its own process. The vision encoder starts only when a picture arrives, in VRAM that the model lends from its expert cache. I want the same thing between processes. Maya drops part of its cache on one card and keeps serving, only slower. ComfyUI or the voice model uses that memory. When the borrower is done, Maya takes the memory back and fills its cache again.

So the engine does not stop. It lends N MiB on one named card and gets it back later. Park is all or nothing and needs-mib is fixed at the grant, so neither covers this. It is close to the fit and scenes item on your roadmap, but here the holder keeps running.

Questions

  1. Would you take a lease that its holder can shrink and grow while it serves? The flow I have in mind:
  • A borrower asks for N MiB on card X.
  • Berth asks the holder to give back N MiB on card X.
  • The holder answers when the memory is free. Berth checks the driver read, as it does for a release now.
  • Berth grants the borrower a sized lease.
  • When the borrower ends, Berth tells the holder it can take the memory back.
  1. Should this go in the park protocol? Something like this would work for Maya and Strata:
POST /berth/lend    {"card": "0000:05:00.0", "mib": 8192}  -> 200 when the memory is free, 409 if the engine cannot give that much
POST /berth/return  {"card": "0000:05:00.0"}               -> 200 when the engine holds it again
GET  /berth/state   -> {"state": "hot", "lent": {"0000:05:00.0": 8192}}
  1. My model holds five cards and lends from one of them. Will the v0.4 lease table let one lease cover several cards, with a size for each card?

  2. I will build this in my own forks of Berth, Maya and Strata. If the shape fits where Berth is going, would you take the Berth side back as a PR?

Thanks!

Activity

  1. T-Crypt commented on Oct 23, 2026

    @T-Crypt
    Owner

    Yes. This fits Berth, and a box like yours is what multi-GPU needs. We'll build the Berth side.

    1. A lease that shrinks and grows. Yes, with your flow. Berth asks the holder for a lend before it would drain it, so Maya keeps serving where today it stops. Two rules on our side:

    • Berth grants the borrower after a driver read shows the memory free, the same check a release gets. Maya has to give the memory back to the driver (cudaFree or its allocator's release), not keep it in a pool.
    • Berth calls /berth/return after the borrower's lease ends and the driver shows its memory gone, so the two never overlap. If a lend doesn't free the memory within the release grace (30 s), Berth falls back to today's drain.

    2. The protocol. Yes, beside /berth/park and /berth/resume, in your shape, with three changes:

    • card is the PCI address, in the form berth daemon --gpu takes (0000:05:00.0).
    • A 409 says what the engine can lend right now, {"max_mib": 6144}, so Berth stops asking instead of guessing.
    • GET /berth/state adds lendable beside lent: per card, the most the engine will give. Berth reads it before it asks.

    An engine opts in on its entry with lend = "hook", as park = "hook" works today.

    3. One lease across five cards. Not in v0.3. Today each card has its own berthd, so a process on five cards holds a lease on one card's daemon and shows as a stowaway on the other four. v0.4 brings one berthd for every card, with one lease that covers several cards and a size for each. A lend then shrinks that lease on one card and leaves the other four alone.

    4. Your forks. Build the Maya side. We'll build the Berth side on a branch and link it here, so we don't write it twice. If you change the Berth side while you test, send it as a PR and we'll review it.

    What would help tonight

    All of this runs on v0.3.6, or with no Berth at all.

    1. Your cards:
      nvidia-smi --query-gpu=index,name,pci.bus_id,uuid,memory.total --format=csv
    2. While Maya's five-card entry serves:
      nvidia-smi --query-compute-apps=pid,process_name,gpu_bus_id,used_memory --format=csv
      We need to know if Maya runs as one process across the five cards or one per card.
    3. Maya's lend, timed against the driver. Give back 8 GiB of expert cache on one card, repeat the query from step 2 until that card's number drops, and note the time and the drop. Then the same for taking it back. This tells us if the driver check works with Maya.
    4. The llama-swap entries for Maya (2, 3 and 5 cards), ComfyUI and VoiceManager. Cut paths and keys.
    5. One berthd on each 3090 with v0.3.6, then both statuses while Maya runs:
      berth daemon --gpu 0000:XX:00.0 --socket "$XDG_RUNTIME_DIR/berth-a.sock" \
        --counters ~/.local/state/berth/a-counters.json --usage ~/.local/state/berth/a-usage.json \
        --footprint ~/.local/state/berth/a-footprint.json --devices ""
      berth daemon --gpu 0000:YY:00.0 --socket "$XDG_RUNTIME_DIR/berth-b.sock" \
        --counters ~/.local/state/berth/b-counters.json --usage ~/.local/state/berth/b-usage.json \
        --footprint ~/.local/state/berth/b-footprint.json --devices ""
      BERTH_SOCKET="$XDG_RUNTIME_DIR/berth-a.sock" berth status --format json
      BERTH_SOCKET="$XDG_RUNTIME_DIR/berth-b.sock" berth status --format json
      That shows what per-card daemons see on driver 610 with your 3090s, before we change anything.

    When the branch can lend, we'll post a small stub engine that serves /berth/lend and /berth/return, so you can test the Berth side before Maya's is done.

  2. self-assigned this
    on Oct 23, 2026
  3. TacoTakumi commented on Oct 23, 2026

    @TacoTakumi
    Author

    Maya's five card entry is serving right now, so 1, 2 and 4 are below. 3 and 5 need the cards free, so they come later.

    1. Cards

    index, name, pci.bus_id, memory.total
    0, RTX 3090,    00000000:01:00.0, 24576 MiB
    1, RTX 3090,    00000000:02:00.0, 24576 MiB
    2, RTX 5060 Ti, 00000000:05:00.0, 16311 MiB
    3, RTX 4060 Ti, 00000000:09:00.0, 16380 MiB
    4, RTX 4060 Ti, 00000000:0A:00.0, 16380 MiB
    

    Card 4 drives the display.

    2. Maya on five cards

    Maya is one process across all five cards.

    pid, process_name, gpu_bus_id, used_gpu_memory
    1357957, maya, 00000000:01:00.0, 23498 MiB
    1357957, maya, 00000000:02:00.0, 23498 MiB
    1357957, maya, 00000000:05:00.0, 15244 MiB
    1357957, maya, 00000000:09:00.0, 15326 MiB
    1357957, maya, 00000000:0A:00.0, 14560 MiB
    

    Each card has about 600 MiB free. From Maya's load log, each card holds one range of layers, and most of each card is the expert pool:

    Card Layers Expert pool Everything else
    01:00.0 0-11 19.21 GB 3.71 GB
    02:00.0 12-23 19.63 GB 3.30 GB
    05:00.0 24-31 12.49 GB 2.39 GB
    09:00.0 32-38 13.01 GB 1.95 GB
    0A:00.0 39-44 10.93 GB 4.03 GB

    "Everything else" is dense weights, KV at 256K, activations and buffers. So a lend comes out of the expert pool and no layers move.

    3. Maya's lend against the driver

    Maya already lends part of its expert pool to its own vision encoder. It empties the tail of the pool, frees it with cudaFree and keeps serving. When the encoder ends it allocates the tail again. Today that lend has three limits:

    • It works only on the first card.
    • Its size is fixed at load (2.69 GB on this entry).
    • On more than one card the engine must be idle.

    So I can't do 8 GiB on any card yet. When the rig is free I'll time the existing 2.69 GB lend on card 0 against nvidia-smi. Then I'll extend it in my fork to any card, any size and while requests run, and post the 8 GiB numbers.

    4. llama-swap entries

    Paths cut. The three Maya entries differ only in the config file, which sets the cards:

    glm53-gsq3-maya-2x3090:   # cards 0,1
      env: ["PYTHONUNBUFFERED=1"]
      checkEndpoint: "/health"
      unloadTimeout: 100
      cmd: python -m serve.server --engine strata --config glm53-gsq3-2x3090.json --port ${PORT} --host 127.0.0.1
    
    glm53-gsq3-maya-3gpu:     # cards 0,1,2
      # same, --config glm53-gsq3-3gpu.json
    
    glm53-gsq3-maya-5gpu:     # cards 0-4
      # same, --config glm53-gsq3-5gpu.json
    
    comfyui_auto:
      env:
        - "CUDA_DEVICE_ORDER=PCI_BUS_ID"
        - "CUDA_VISIBLE_DEVICES=2"
      proxy: "http://127.0.0.1:8188"
      checkEndpoint: "/system_stats"
      ttl: 0
      unloadTimeout: 30
      cmdStop: "kill -INT ${PID}"
      cmd: python main.py --use-sage-attention --listen 127.0.0.1 --port 8188

    In the matrix, ComfyUI can run with the 2 card entry. The 3 and 5 card entries evict it. DeepSeek V4.1 on Maya has the same three entries.

    VoiceManager is not in llama-swap. It runs on card 3 (09:00.0), and each voice session starts two audio.cpp sidecars there. The TTS sidecar (Breeze-TTS-2 q8_0) uses 5136 MiB, and the STT sidecar (Nemotron 3.5 f16) uses about 1480 MiB. That is about 6.5 GiB per session, and it is freed when the session ends. With Maya on five cards, card 3 has about 600 MiB free, so voice can't run. That is a case I want the lend for.

    5. Two berthd on the 3090s

    When the cards are free.

  4. T-Crypt commented on Oct 23, 2026

    @T-Crypt
    Owner

    Thank you, this is what we needed. It settles the order of work.

    What your numbers decide

    • Maya is one process on five cards. Today each card has its own berthd, so four of those daemons would see Maya as a stowaway, and none of them could ask it to lend. Lends on your box need one berthd for every card and one lease for Maya with a share on each card. That moves the multi-card lease ahead of the lend. It's the next thing we build, not a v0.4 item at the back of the list.
    • A lend comes out of the expert pool and no layers move. That fits: Maya's share on 09:00.0 shrinks by N while its other four shares stay as they are.
    • Your target case: a voice session asks for about 6.5 GiB on 09:00.0, Maya lends it from that card's pool and keeps serving, and Berth hands it back when the session ends and the driver shows the sidecars' memory gone.
    • Card 0A:00.0 drives your display, so each card gets its own reserve. A lend or a grant never takes a card below it.

    The model all of this follows is written up in docs/design.md (in review now, together with the first piece built on it: parking an engine in RAM with cuda-checkpoint, #191). Your box is stage C in its table.

    What we build, in order

    1. One berthd for every card, with a lease that holds one share per card. A process on several cards (Maya) is counted per card by bus ID, as nvidia-smi reports it.
    2. Per-card reserves, and berth run --gpu <bus id> for a run that uses one card (ComfyUI on 05:00.0, a voice sidecar on 09:00.0).
    3. The lend: /berth/lend and /berth/return on the protocol as agreed, asked of the share on the borrower's card.

    We'll post here when step 1 can run on your box, with the exact commands.

    What still helps, when the rig is free

    • Item 3: the existing 2.69 GB lend on card 0, timed against nvidia-smi, then your 8 GiB numbers from the fork.
    • Item 5: the two berthds on the 3090s. It's still the baseline for step 1 on driver 610.
    • One more: how VoiceManager starts a session's two sidecars (one command each, or one parent that starts both). That decides whether a session asks for one share of 6.5 GiB or two.
  5. TacoTakumi commented on Oct 24, 2026

    @TacoTakumi
    Author

    VoiceManager

    The VoiceManager server is one long-lived process. For each session it starts two child processes on 09:00.0: the TTS sidecar when the client connects, and the STT sidecar when the session first needs it. At the end of the session it stops both (SIGTERM, then SIGKILL after 5 s).

    The engines don't change after startup, so the size of a session is known. One share per voice session, about 6.5 GiB on 09:00.0. For us the harness that starts a voice conversation asks for it before it starts VoiceManager and gives it back when voice ends. VoiceManager could ask per session itself later, but I don't expect every borrower to know about Berth, so the caller asking is the general case.

    Item 3: Maya's existing lend, timed against nvidia-smi

    Maya on five cards, engine idle between requests, one picture per request so the vision encoder starts each time. A loop read nvidia-smi for 01:00.0 about every 140 ms. Times are from when the client sent the request.

    • 6 lends, each 2758 MiB.
    • Maya's process dropped by 2758 MiB 0.10 to 0.19 s after the request. That includes the HTTP and server time.
    • The encoder used 1332 to 1484 MiB of it.
    • Maya was back at full size 0.93 to 1.22 s after the request, one read after the encoder exited.
    • To free the 2758 MiB, Maya moved 116 to 257 experts to colder slots and dropped 3 to 137.

    So the driver check works with Maya's lend, and it frees in about a tenth of a second. This is the lend that exists today: card 0 only, a fixed size, engine idle. The 8 GiB numbers on any card, and a lend while requests run, come from my fork.

    Item 5: two berthd on the 3090s, v0.3.6, driver 610.43.02

    Run as you wrote it, with Maya on all five cards. Both daemons came up and both statuses answered.

    berthd 0.3.6 on /run/user/1000/berth-a.sock (api 1)
    gpu0    NVIDIA GeForce RTX 3090: 23616 MiB used of 24576 MiB, 562 MiB free (leased by berth, baseline 118 MiB, read 3s ago)
    gpu1    NVIDIA GeForce RTX 3090: 23616 MiB used of 24576 MiB, 562 MiB free (inventory only, read 3s ago)
    gpu2    NVIDIA GeForce RTX 5060 Ti: 15361 MiB used of 16311 MiB, 581 MiB free (inventory only, read 3s ago)
    gpu3    NVIDIA GeForce RTX 4060 Ti: 15443 MiB used of 16380 MiB, 589 MiB free (inventory only, read 3s ago)
    gpu4    NVIDIA GeForce RTX 4060 Ti: 15261 MiB used of 16380 MiB, 763 MiB free (inventory only, read 3s ago)
    
    SQUATTER                                            PID      HOLDS      SEEN  UNPROTECTED RUN
    /path/to/project-maya-v1125/build/maya  1438191  23498 MiB  8s
    
    ENGINE      TYPE        UP   MODELS                DECODE  PREFILL  OUT TOKENS  QUEUE  EST. VRAM
    llama-swap  llama-swap  yes  glm53-gsq3-maya-5gpu  0 t/s   0 t/s    0           0      -
    

    The b daemon (02:00.0) shows the same, with gpu1 leased and gpu0 inventory only. Each daemon sees Maya as a squatter with 23498 MiB on its own card, and each found llama-swap by itself. I have the JSON for both if you want it.

  6. T-Crypt commented on Oct 24, 2026

    @T-Crypt
    Owner

    Thank you, that answers everything we asked.

    VoiceManager. One share of about 6.5 GiB on 09:00.0. The harness asks for it before it starts voice and gives it back when voice ends. That's a plain sized lease. One detail is on us. The sidecars are children of VoiceManager, not of the harness, so today Berth would count their memory on 09:00.0 as a stowaway rather than under the harness's lease. The lease will name the processes it covers, so the sidecars count against the 6.5 GiB share.

    Item 3. Maya's process drops by 2758 MiB 0.10 to 0.19 s after the request. That drop is the driver read a lend waits for, and it comes well inside the 30 s grace. The return follows rule 2 in docs/design.md. Berth tells Maya to grow back only after the driver shows the borrower's memory gone, so Maya never regrows into memory a sidecar still holds.

    Item 5. Each daemon saw Maya on its own card at the right size (23498 MiB), so per-card counting by bus ID works on driver 610. The run also shows what goes wrong with one daemon per card. Both daemons found llama-swap and would each drain it on their own. Both would also admit parks into the same system RAM. One berthd per machine fixes both.

    One change to the order. Before the multi-card lease, we're fixing how Berth counts system RAM for parks (#193). Today Berth charges a park its VRAM footprint, not the RAM it holds. On our 4090 box, Berth charges a parked ComfyUI 17.9 GiB, and the process holds 3.2 GiB. With five cards parking into one 128 GB pool, Berth has to measure that per process first. It's a short piece of work, and it lands before step 1.

    One more reading, while Maya serves. Maya keeps its experts in system RAM, and how it holds them decides how Berth counts them:

    grep -E 'VmRSS|RssAnon|RssFile|RssShmem|VmLck|VmPin' /proc/$(pgrep -x maya)/status
    grep -E 'MemAvailable|Cached|Shmem:' /proc/meminfo

    If the experts show up in RssFile (an mmap of the model file), the kernel counts them as available, and a park beside Maya could push them out of the page cache. If they show up in RssAnon, RssShmem or VmPin, the kernel already counts them as used.

  7. TacoTakumi commented on Oct 24, 2026

    @TacoTakumi
    Author

    The engine side of the lend works in my Maya fork, for both engines (GLM-5.3-Flash and DeepSeek-V4.1), on any card, while requests run.

    https://github.com/TacoTakumi/project-maya/tree/vram-lend

    The routes are not the /berth/lend and /berth/return we agreed. Maya serves engine-neutral names so any client can use them, and Maya does not track who borrows. Berth keeps that ledger. A lend names the GPU in a gpu field and returns a lease, and a return names the lease, not the GPU.

    POST /vram/lend    {"gpu": "0000:09:00.0", "mib": 8192}
    200                {"id": "1", "gpu": "0000:09:00.0", "mib": 8208}
    409                {"max_mib": 10320}
    503                {"error": "the engine did not answer LEND in 30 s: it may still lend that memory - the server will give it back when it does"}
    
    POST /vram/return  {"id": "1"}
    200                {"id": "1", "gpu": "0000:09:00.0", "mib": 8208}
    409                {"error": "lease 1's memory is still in use; it stays lent - try again later", "retry": true}
    404                {"error": "no open lease 1"}
    
    GET /vram/state
    200  {"gpus": [{"gpu": "0000:09:00.0", "lendable": 10320, "lent": 0}, ...],
          "leases": [{"id": "1", "gpu": "0000:09:00.0", "mib": 8208}]}
    

    How it behaves:

    • The gpu field is the PCI address. Case does not matter, and the nvidia-smi form 00000000:09:00.0 also works. A body that still sends card in place of gpu gets a 400.
    • A lend rounds up to whole blocks. The blocks are 16 to 64 MiB, depending on the GPU and the expert size.
    • Several leases can be open on one GPU. Each one comes back on its own.
    • A return answers 409 with retry while another process still holds the memory. The lease stays open, and the same return works when the memory is free.
    • Leases end when the engine process ends. After a restart the state shows nothing lent.

    Measured on the rig with an 8 GiB lend on 09:00.0 (a 4060 Ti) while one request streams. The times are from when the client sent the request, and nvidia-smi was read every 50 ms.

    GLM on all five cards:

    • The lend answers in 0.41 s. nvidia-smi shows the 8208 MiB free 0.04 s after the answer.
    • The return answers in 0.05 s. Maya is back at full size 0.09 s after the request.
    • The largest gap between streamed tokens is 0.40 s across the lend and 0.16 s across the return.
    • Decode speed with the 8 GiB lent stays within noise (52.5 t/s before, 50.8 lent, 52.2 after).

    DeepSeek on all five cards:

    • The lend answers in 0.20 s, and the largest token gap is 0.28 s.
    • The return answers in 0.11 s.

    Lendable right after load, GLM on five cards, in MiB: 01:00.0 15372, 02:00.0 16688, 05:00.0 9648, 09:00.0 10320, 0A:00.0 8256. Maya keeps enough on each card that prompt reading stays fast. At the full lendable on the 5060 Ti a 4K prompt still reads at 91% of its speed.

    One limit matters for the 30 s grace. A lend that arrives while a long prompt is being read waits for the end of that piece of the read. I saw 9 to 14 s with a 12K token prompt. A very long prompt could push a lend past 30 s. Then the lend answers 503, and Maya gives that memory back by itself when the engine lends it. The state lists that lease until it is back.

    Can Berth call these routes? The flow would be: lend on the borrower's card, wait for the driver check as now, and return the lease by its id when the borrower's lease ends.

    Strata next, but not today.

  8. T-Crypt commented on Oct 25, 2026

    @T-Crypt
    Owner

    That's fine. Strata can come whenever it suits you. There's no rush, since we want to get this right.

    Yes, Berth can call these routes. Engine-neutral names, with Berth keeping the ledger, fit how we planned the lend. We'll build the Berth side against /vram/lend, /vram/return and /vram/state:

    • A lend goes to the borrower's card by PCI address. Berth starts the borrower once the driver shows the memory free, as it does now.
    • A return names the lease id. A 409 with retry keeps the borrower's share counted on that card, and Berth retries until Maya takes the memory back.
    • A 503 counts as a lend that has not landed. Berth won't start the borrower on it. Berth returns that lease by id once /vram/state lists it.
    • After a berthd restart, /vram/state is the read that tells Berth which shares are still lent.

    We'll keep working on our side in the meantime, starting with the system RAM count for parks (#193), and we'll post here when we need a specific test from the rig. The /proc reading from my last comment would still help whenever Maya is serving.

  9. TacoTakumi commented on Oct 25, 2026

    @TacoTakumi
    Author

    Here is the /proc reading from the vram-lend build on all five cards, taken 20 s into a streamed answer. The DeepSeek binary is maya-ds4, so pgrep -x maya finds only GLM.

    GLM-5.3-Flash

    • VmRSS 62.9 GiB: RssShmem 49.7, RssFile 8.5, RssAnon 4.7.
    • VmLck and VmPin are 0.
    • The RssShmem is 49.4 GiB of /dev/zero mappings. That is how cudaHostAlloc memory shows up, and it is the pinned expert tier.
    • The RssFile is 7.7 GiB of an mmap of the GGUF file.
    • MemAvailable fell from 117.7 GiB before the load to 60.4 GiB.

    So the GLM experts are in RssShmem, and the kernel already counts them as used.

    DeepSeek-V4.1

    • Idle after load: VmRSS 21.4 GiB: RssShmem 12.6, RssFile 7.6, RssAnon 1.3.
    • While serving: VmRSS 65.9 GiB: RssFile 52.0, RssShmem 12.6, RssAnon 1.3.
    • VmLck and VmPin are 0.
    • The RssFile is mmaps of four of the six GGUF shards. It grew by 44 GiB in the first 20 s of the stream.
    • The RssShmem is 12.3 GiB of /dev/zero, pinned the same as in GLM.
    • MemAvailable fell from 117.2 GiB before the load to 100.2 GiB idle, and it was 103.1 GiB while serving. The 52 GiB of file pages did not lower it.

    So most DeepSeek experts are in RssFile. The kernel counts them as available, and a park beside DeepSeek could push them out of the page cache.

    One thing for the count: cudaHostAlloc memory is pinned, but VmPin and VmLck read 0 for it. VmPin can't tell Berth what Maya holds. RssShmem can.

    The page cache was warm before both loads (108 GiB cached), because these files were read in earlier runs. So the DeepSeek pages came from the cache and not from the disk.

    Edit: the project now has a home: https://github.com/TacoTakumi/vram-lend

  10. T-Crypt commented on Oct 26, 2026

    @T-Crypt
    Owner

    The Berth side of the lend is in the first v0.4 pre-release, v0.4.0-rc.1, as an experimental feature. It is a GitHub pre-release, so a normal install stays on v0.3.6. Testing a pre-release covers installing it.

    It calls your /vram/lend, /vram/return and /vram/state routes as agreed. The contract is in contrib/LEND-PROTOCOL.md. This build lends on one card: each berthd governs one card, so the test is a berthd on 09:00.0, Maya's run declaring lend = "hook" with lend_url, and a sized borrower (VoiceManager with --needs around 6.5 GiB) asking for that card. Lends across Maya's five cards under one lease come in a later pre-release.

    What we'd like to see: the borrower granted only after the card shows the memory free, Maya still serving, and the take-back after the borrower stops. A 409 with retry should be asked again about once a second. berth events around one lend and return shows all of it (lease.lending, lease.lent, lease.returned).

  11. TacoTakumi commented on Oct 26, 2026

    @TacoTakumi
    Author

    rc.1 on the rig

    berth 0.4.0-rc.1, Ubuntu 24.04, driver 615.71.09. One berthd on 09:00.0 (RTX 4060 Ti, baseline 121 MiB). Maya's vram-lend build runs on all five cards under berth run --as maya, with lend = "hook" and lend_url. VoiceManager runs under berth run --needs-mib 6656 and asks for 09:00.0 while Maya streams an answer.

    Two bugs

    The lend itself works. Berth asked for 6656 MiB, Maya gave 6672 MiB in 0.34 s, and the next card read confirmed it. In the same tick Berth dropped the lend ("it is draining") and drained Maya. Voice got the card 34 s later, after Maya was gone. The result is the same with the borrower at foreground and at interactive.

    The ask leaves out the card's baseline. Maya holds the whole card, so avail is 0 and the 121 MiB baseline drops out of the shortfall. After the lend the waiter is still 105 MiB short. lendRoom has no case for a holder that already lent to this waiter, so the waiter falls through to the drain.

    With the ask fixed, the grant works, but the take-back never comes. retirable counts the holder's whole card plus the lent memory against the ceiling, and that never fits on a card with a baseline. Maya stayed lent until I restarted berthd, and the re-attach returned it.

    The fix

    Two small changes in internal/broker/lend.go. The ask adds back what the holders count over the ceiling. retirable caps the holder's own count at the ceiling, so only other leases can block a take-back. The new test fails on both before the change. It's one commit on next, with a changelog entry:

    TacoTakumi@095ef50

    With the fix, one real voice session

    • Berth asks for 6777 MiB, which is 6656 plus the baseline. Maya gives 6816 MiB, and the card drops 0.33 s after the ask.
    • The next card read confirms the lend and grants voice, 2.1 s after the request. Most of that is the 2 s poll.
    • The TTS and STT sidecars load on 09:00.0 in the lent memory, 5132 MiB and 1476 MiB. The card peaks at 15203 of 16380 MiB.
    • Voice stops. 2.0 s later the card read shows its memory gone, and 49 ms after that Berth asks Maya to take it back. The card is at Maya's full size again at once.
    • Maya streams one answer through all of it. The largest token gaps are 0.38 s at the lend and 0.19 s at the return.
    lease.requested  voice  foreground
    lease.lending    maya   maya lends 6777 MiB to voice
    lease.waiting    voice  next in line: waiting for maya to lend 6777 MiB
    lease.lent       maya   maya lent 6816 MiB to voice; the card shows it free
    lease.granted    voice  waited 2.121s
    lease.released   voice
    lease.freed      voice
    lease.returned   maya   maya took back the 6816 MiB it lent
    

    I did not see a 409 with retry. Berth asks for the return only after the borrower's memory is gone, so Maya never answered 409 in these runs.

  12. T-Crypt commented on Oct 26, 2026

    @T-Crypt
    Owner

    Thank you for running this on the rig, and for tracing both bugs down to the line. Your reading was right. The lend landed, the waiter came up short by the baseline, and nothing in lendRoom covered a holder that had already lent to that waiter.

    I moved the fix one step back, into the number both of your call sites read. budget() counted a whole-card holder at the card's total, and the baseline sits inside that total. The ceiling then took the baseline off the card a second time. In #211 a whole-card holder counts the ceiling instead: the total minus the reserve, the baseline and any stowaways. That one change fixes the ask, the grant and the take-back.

    One difference you will see on the rig: Berth now asks Maya for the waiter's need alone, 6656 MiB rather than 6777, because Maya's count no longer includes the 121 MiB baseline. Your test is the basis of the new one, and you're credited as co-author on the commit. It fails on next with the drain you saw.

    #211 is merged into next and will be in v0.4.0-rc.2. When that's out, could you run the same voice session again? The same berth events lines would show it, with lease.lending asking for 6656 MiB.

    You're right about the 409. Berth asks for the return only after the card shows the borrower's memory gone, so Maya has no reason to refuse it. The retry path is for an engine that is busy at the moment of the ask. Strata answers 409 while a request runs, and I'll cover that path in a live test with Strata on the 4090 here.

    Next on our side is lending across Maya's cards under one lease: one berthd governing all five cards, so voice can borrow on 09:00.0 from the same lease that holds the 3090s.

  13. TacoTakumi commented on Oct 26, 2026

    @TacoTakumi
    Author

    Thanks! I'll run the same voice session on rc.2 when it's out.

    If you want to try the lend on Strata, my fork serves the same /vram/lend, /vram/return and /vram/state routes as Maya, so a Strata entry can use lend = "hook" with lend_url. It's opt in with "vram_lend": true in the server config.

    https://github.com/TacoTakumi/Strata/tree/vram-lend
    Niko1221/Strata#4168

  14. T-Crypt commented on Oct 26, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.2 is out with the baseline fix (#211). Like rc.1, it's a GitHub pre-release, so a normal install stays on v0.3.6.

    We also ran a live lend here on an RTX 4090, with Strata as the holder and a 3 GiB borrower. It found one more case, fixed in #215, which will be in rc.3. Strata gave 2939 MiB when asked for 3072, and the card's own free memory covered the gap. berthd confirmed the lend from the card, then counted only the engine's figure and drained the holder. If Maya ever gives less than it's asked for, you'd see the same thing on rc.2. In your runs it gave a little more, so rc.2 should be enough for the voice session.

    With #215, the 4090 run went: lend asked, confirmed 3.7 s later, borrower granted, borrower gone, and the memory back with Strata 0.38 s after the card showed it free. Strata stayed on the card throughout.

  15. T-Crypt commented on Oct 26, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.3 is out, also as a pre-release. Use it rather than rc.2. It has the fix for an engine that lends a little less than asked (#215), and one more lend fix from the 4090 tests (#216). berthd used to give up on a lend 30 s after the ask while berth run gave the engine 60 s to answer. It now waits the same 60 s for the answer, and the 30 s for the card to show the memory free starts when the engine answers. On the 4090, a lend asked mid-answer landed 38.9 s after the ask and the borrower was granted with no drain.

    Your voice session test on rc.3 would cover all of it.

  16. TacoTakumi commented on Oct 27, 2026

    @TacoTakumi
    Author

    rc.3 on the rig

    Same setup as the rc.1 run: berthd on 09:00.0, Maya under berth run --as maya with lend = "hook", VoiceManager under berth run --needs-mib 6656 at foreground, asking for the card while Maya streams an answer. One real voice session, TTS then STT.

    The lend, the grant and the take-back all work on rc.3. No drain.

    • Berth asks for 6656 MiB. Maya gives 6672 MiB and the card drops 0.35 s after the ask.
    • The next card read confirms the lend and grants voice, 2.5 s after the request.
    • The TTS and STT sidecars load on 09:00.0 in the lent memory, 5132 MiB and 1476 MiB. The card peaks at 15349 of 16380 MiB.
    • Voice stops. 0.8 s later lease.freed shows the memory gone, and 2.05 s after that Berth asks Maya to take it back. The card is at Maya's full size 31 ms after the return.
    • Maya streams one answer through all of it, 2991 chunks in 84 s. The largest token gap at the lend is 0.38 s, at the return 0.21 s.
    lease.requested  voice  foreground
    lease.lending    maya   maya lends 6656 MiB to voice
    lease.waiting    voice  next in line: waiting for maya to lend 6656 MiB
    lease.lent       maya   maya lent 6672 MiB to voice; the card shows it free
    lease.granted    voice  waited 2.474s
    lease.released   voice
    lease.freed      voice
    lease.returned   maya   maya took back the 6672 MiB it lent
    

    One note on the baseline. This berthd started on an empty card, so its baseline is 15 MiB. The 121 MiB in the rc.1 run was Maya's own context on the card at the time berthd read it. The ask is 6656 MiB in both cases with #211, so it changes nothing here.

  17. T-Crypt commented on Oct 27, 2026

    @T-Crypt
    Owner

    Thanks for the run. It shows each part of the lend working on rc.3:

    • Maya gave 6672 MiB for a 6656 MiB ask, and berthd confirmed it from the card before it granted voice. That is the case fix(broker): a confirmed lend credits the room the card shows free #215 fixed.
    • Voice was granted 2.5 s after it asked, with no drain. The return started 2.05 s after the card showed voice's memory gone, and Maya was back at full size 31 ms later.
    • Maya kept streaming through both. The largest token gaps were 0.38 s at the lend and 0.21 s at the return.

    Thanks for the baseline note too. The 121 MiB from the rc.1 run is accounted for.

    Next is one berthd for every card on a machine, so your five cards run under one daemon and one berth status. The first two parts are merged: the node that holds the cards (#218), and one nvidia-smi read for every card (#219). The part that opens every card comes next. Then a lease can name its card, and one lease can span several cards, which covers Maya across all five. It goes out as an rc, and I'll ping you here with what to run.

  18. T-Crypt commented on Oct 27, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.4 is out as a pre-release. It runs every card under one berthd.

    • berthd governs every card it finds. --gpu all is the new default, and each card is named gpu plus its index. Each card keeps its own leases and queue, so a request on one card never drains a holder on another.
    • berth run --gpu gpu3, or gpu = "gpu3" on an engine entry, puts that engine's lease on gpu3. A PCI address works too (--gpu 0000:09:00.0). The run sets CUDA_VISIBLE_DEVICES to the card's UUID unless your environment already sets it, so a launcher that picks its own cards keeps its setting.
    • berth status lists every card and adds a CARD column to the lease tables.

    One lease across several cards isn't in this build. Maya on all five cards will hold its lease on the card you name, and its memory on the other four shows as stowaways there. That part comes next.

    What would help:

    1. Stop the per-card berthd, start one berth daemon with no --gpu, and paste berth status.
    2. Rerun the rc.3 voice session with both runs naming the card at 09:00.0: Maya with --gpu 0000:09:00.0 and VoiceManager with the same flag. Paste berth events around the lend and the return.

    If you install the user service again, berth service install now writes --gpu all. A unit you already have keeps the --gpu it was written with.

  19. TacoTakumi commented on Oct 27, 2026

    @TacoTakumi
    Author

    rc.4 on the rig

    One berthd with no --gpu finds all five cards and names them gpu0 to gpu4. Same voice session as the rc.3 run, with Maya and VoiceManager both on the 4060 Ti at 09:00.0.

    The PCI form of --gpu is refused

    berth run --gpu 0000:09:00.0 is refused when berthd runs with --gpu all:

    berth: refused: no card 0000:09:00.0 on this node (cards: gpu0, gpu1, gpu2, gpu3, gpu4)
    

    00000000:09:00.0 gives the same answer. --gpu gpu3 works, so both runs used gpu3. I think the cause is in internal/cli/daemon.go. With --gpu all, each card gets gpuSel{index: d.Index} and no address, so node.Card.Addr is empty and Node.Card never matches a PCI address. The device from gpu.List already has the BusID.

    CUDA_VISIBLE_DEVICES

    berth run sets gpu3's UUID on Maya's server. Maya's server then sets its engine's CUDA_VISIBLE_DEVICES from its own config, so Maya still runs on all five cards. VoiceManager names its card as cuda:3 in its config. With only the UUID visible, cuda:3 doesn't exist, so for this run I set it to cuda:0.

    The lend on gpu3

    • Berth asks for 6656 MiB. Maya gives 6672 MiB and the card drops 0.31 s after the ask.
    • The lend is confirmed and voice is granted 1.54 s after the request.
    • The TTS and STT sidecars load on 09:00.0 in the lent memory, 5132 MiB and 1476 MiB. The card peaks at 15347 of 16380 MiB.
    • Voice stops. lease.freed comes 2.18 s later, and the return 61 ms after that. The card is at Maya's full size 32 ms after the return.
    • Maya streams one answer through all of it, 2491 chunks in 70 s. The largest token gap at the lend is 0.43 s, at the return 0.23 s.
    lease.requested  voice  gpu3  foreground
    lease.lending    maya   gpu3  maya lends 6656 MiB to voice
    lease.waiting    voice  gpu3  next in line: waiting for maya to lend 6656 MiB
    lease.lent       maya   gpu3  maya lent 6672 MiB to voice; the card shows it free
    lease.granted    voice  gpu3  waited 1.539s
    lease.released   voice  gpu3
    lease.freed      voice  gpu3
    lease.returned   maya   gpu3  maya took back the 6672 MiB it lent
    

    berth status during the lend:

    gpu0    NVIDIA GeForce RTX 3090: 22932 MiB used of 24576 MiB, 1246 MiB free (leased by berth, baseline 122 MiB)
    gpu1    NVIDIA GeForce RTX 3090: 23564 MiB used of 24576 MiB, 614 MiB free (leased by berth, baseline 122 MiB)
    gpu2    NVIDIA GeForce RTX 5060 Ti: 15313 MiB used of 16311 MiB, 629 MiB free (leased by berth, baseline 121 MiB)
    gpu3    NVIDIA GeForce RTX 4060 Ti: 8729 MiB used of 16380 MiB, 7303 MiB free (leased by berth, baseline 121 MiB)
    gpu4    NVIDIA GeForce RTX 4060 Ti: 15339 MiB used of 16380 MiB, 684 MiB free (leased by berth, baseline 877 MiB)
    
    LEASE    CARD  ENGINE  PRIORITY     STATE  NEEDS     HOLDS     PIDS       TURN  AGE
    maya-3   gpu3  maya    interactive  HOT    card      8608 MiB  [1170811]  -     1m14s
    voice-4  gpu3  voice   foreground   HOT    6656 MiB  0 MiB     []         -     3s
    
    SQUATTER    CARD  PID      HOLDS      SEEN
    build/maya  gpu0  1170811  22810 MiB  1m13s
    build/maya  gpu1  1170811  23442 MiB  1m13s
    build/maya  gpu2  1170811  15192 MiB  1m13s
    build/maya  gpu4  1170811  14462 MiB  1m11s
    

    Maya's memory on the other four cards shows as squatters, as you said it would. Each one adds a squatter.appeared event and 1 to the squatters caught count on the board.

  20. T-Crypt commented on Oct 27, 2026

    @T-Crypt
    Owner

    Thanks for the rc.4 run. The lend on gpu3 matches rc.3. Voice was granted 1.54 s after it asked, Maya was back at full size 32 ms after the return, and the largest token gap was 0.43 s.

    The PCI form of --gpu. Your reading of the cause is right. With --gpu all, berthd builds each card from its index and never records the card's PCI address, so a run that names 0000:09:00.0 matches nothing. A berthd started with --gpu 3 has the same gap. I'll fix it so every card carries the address from the device list, and the fix goes out in rc.5. Until then, --gpu gpu3 is the way to name that card.

    CUDA_VISIBLE_DEVICES. This is how CUDA treats the variable. When it lists one UUID, CUDA numbers the listed cards from 0, so the engine sees gpu3 as cuda:0. Your change to cuda:0 is the right setting for a run that names one card. If VoiceManager has to keep cuda:3, set CUDA_VISIBLE_DEVICES in its environment and Berth leaves it alone. One caution. CUDA's default order is fastest card first, not PCI order, so without CUDA_DEVICE_ORDER=PCI_BUS_ID the card CUDA calls cuda:3 may not be the one at 09:00.0. The UUID avoids that.

    Maya on five cards. That is the part I'm building now. berth run --gpu gpu0,gpu1,gpu2,gpu3,gpu4, or the same list in gpu = on the entry, will hold one lease on all five cards, granted on all of them at once. The run gets all five UUIDs in CUDA_VISIBLE_DEVICES, in the order you list them, and Maya's memory stops showing up as squatters on the other cards. A drain on any of the five stops Maya.

    The first version of that lease can't lend. A voice request on gpu3 would drain Maya instead of borrowing from it. Lending from one card of a multi-card lease comes after that. So for your voice setup, keep Maya's lease on gpu3 as you have it now, and use the multi-card lease when you want the squatter lines gone and can live without the lend. I'll ping you here when it is in an rc.

  21. T-Crypt commented on Oct 27, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.5 is out as a pre-release. It has the two things from your rc.4 run.

    --gpu by PCI address. berth run --gpu 0000:09:00.0 and 00000000:09:00.0 now work when berthd runs with --gpu all (#229). Each card takes its address from the device list.

    One lease across several cards (#230). Give --gpu a comma list, or put the same list in gpu = on the entry:

    berth run --as maya --gpu gpu0,gpu1,gpu2,gpu3,gpu4 -- <Maya's command>

    berthd grants the lease on all five cards at once, and only when each one fits. Maya's server starts with CUDA_VISIBLE_DEVICES listing the five UUIDs in the order you named them. Its PID is attached on every card, so its memory there counts as the lease's and not as a squatter's. A drain on any of the five stops Maya, and its exit frees all five.

    As I said before, this lease can't lend yet. A voice request on gpu3 drains Maya instead of borrowing from it. Keep Maya on gpu3 alone for the voice setup.

    What would help:

    1. Start Maya with the five-card list and paste berth status. It should show one lease ID on gpu0 through gpu4 and no build/maya squatter lines.
    2. If you can spare a restart of Maya: with the five-card lease up, run any foreground berth run --gpu gpu3 and paste berth events --json from that point. It should show Maya drained once and every card freed. The text form of berth events doesn't print the card yet, so the five grant lines look the same there. --json names each card.
    3. berth run --gpu 0000:09:00.0 once, to confirm the address form.
  22. T-Crypt commented on Oct 28, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.6 adds two things for a machine with several cards:

    • berth events prints the card on each line when berthd governs more than one, so you no longer need --format json to see it. On one card the lines are unchanged.
    • The gateway checks the card an engine runs on. A background engine on gpu0 used to wait until every card was free, so Maya's memory on the other cards kept it out. The board API also names each card.

    The three checks from my last comment still apply, and rc.6 is the build to run them on.

  23. TacoTakumi commented on Oct 28, 2026

    @TacoTakumi
    Author

    rc.6 on the rig

    All three checks match what you expected. One berthd with no --gpu, berth 0.4.0-rc.6.

    C1: Maya under one lease on five cards

    Maya starts under berth run --as maya --gpu gpu0,gpu1,gpu2,gpu3,gpu4. The lease is granted on all five cards in the same millisecond, and Maya is ready 45 s later.

    LEASE   CARD  ENGINE  PRIORITY     STATE  NEEDS  HOLDS      PIDS       TURN  AGE
    maya-3  gpu0  maya    interactive  HOT    card   22810 MiB  [1848848]  -     10m46s
    maya-3  gpu1  maya    interactive  HOT    card   23442 MiB  [1848848]  -     10m46s
    maya-3  gpu2  maya    interactive  HOT    card   15192 MiB  [1848848]  -     10m46s
    maya-3  gpu3  maya    interactive  HOT    card   15280 MiB  [1848848]  -     10m46s
    maya-3  gpu4  maya    interactive  HOT    card   14518 MiB  [1848848]  -     10m46s
    

    There are no squatter rows. Maya's server gets the five UUIDs in CUDA_VISIBLE_DEVICES, in the order I listed them. Maya's server then sets its engine's CUDA_VISIBLE_DEVICES to 0,1,2,3,4 with CUDA_DEVICE_ORDER=PCI_BUS_ID from its own config. Those are the same five cards.

    C2: a foreground run on gpu3 drains Maya

    With Maya idle I ran berth run --needs-mib 1 --priority foreground --gpu gpu3 -- sleep 20.

    24  10:48:24.949  lease.requested  gpu3  c2-probe (c2-probe-4)  1 MiB  foreground
    25  10:48:24.949  lease.draining   gpu3  maya (maya-3)  c2-probe (foreground) needs the card
    26  10:48:24.949  lease.waiting    gpu3  c2-probe (c2-probe-4)  next in line: draining maya [interactive, active]
    27  10:48:25.757  lease.waiting    gpu3  c2-probe (c2-probe-4)  next in line: waiting for maya [interactive, draining] to free memory
    28  10:48:32.984  lease.released   gpu0  maya (maya-3)  22692 MiB
    29  10:48:32.984  lease.released   gpu1  maya (maya-3)  560 MiB
    30  10:48:32.984  lease.released   gpu2  maya (maya-3)  476 MiB
    31  10:48:32.984  lease.released   gpu3  maya (maya-3)  430 MiB
    32  10:48:32.984  lease.waiting    gpu3  c2-probe (c2-probe-4)  next in line: waiting for maya [interactive, releasing] to free memory
    33  10:48:32.984  lease.released   gpu4  maya (maya-3)  430 MiB
    34  10:48:33.745  lease.freed      gpu0  maya (maya-3)  22810 MiB
    35  10:48:33.746  lease.freed      gpu1  maya (maya-3)  23442 MiB
    36  10:48:33.746  lease.freed      gpu4  maya (maya-3)  14518 MiB
    37  10:48:33.747  lease.freed      gpu3  maya (maya-3)  15280 MiB
    38  10:48:33.747  lease.granted    gpu3  c2-probe (c2-probe-4)  waited 8.789s
    39  10:48:33.747  lease.freed      gpu2  maya (maya-3)  15192 MiB
    40  10:48:53.778  lease.released   gpu3  c2-probe (c2-probe-4)
    41  10:48:55.676  lease.freed      gpu3  c2-probe (c2-probe-4)
    
    The same events from berth events --format json
    {"seq":24,"at":"2026-10-28T10:48:24.949908707-03:00","card":"gpu3","kind":"lease.requested","lease":"c2-probe-4","engine":"c2-probe","bytes":1048576,"detail":"foreground"}
    {"seq":25,"at":"2026-10-28T10:48:24.949925813-03:00","card":"gpu3","kind":"lease.draining","lease":"maya-3","engine":"maya","detail":"c2-probe (foreground) needs the card"}
    {"seq":26,"at":"2026-10-28T10:48:24.949927527-03:00","card":"gpu3","kind":"lease.waiting","lease":"c2-probe-4","engine":"c2-probe","detail":"next in line: draining maya [interactive, active]"}
    {"seq":27,"at":"2026-10-28T10:48:25.757092397-03:00","card":"gpu3","kind":"lease.waiting","lease":"c2-probe-4","engine":"c2-probe","detail":"next in line: waiting for maya [interactive, draining] to free memory"}
    {"seq":28,"at":"2026-10-28T10:48:32.984261051-03:00","card":"gpu0","kind":"lease.released","lease":"maya-3","engine":"maya","bytes":23794286592}
    {"seq":29,"at":"2026-10-28T10:48:32.98427174-03:00","card":"gpu1","kind":"lease.released","lease":"maya-3","engine":"maya","bytes":587202560}
    {"seq":30,"at":"2026-10-28T10:48:32.984277345-03:00","card":"gpu2","kind":"lease.released","lease":"maya-3","engine":"maya","bytes":499122176}
    {"seq":31,"at":"2026-10-28T10:48:32.984282588-03:00","card":"gpu3","kind":"lease.released","lease":"maya-3","engine":"maya","bytes":450887680}
    {"seq":32,"at":"2026-10-28T10:48:32.984293647-03:00","card":"gpu3","kind":"lease.waiting","lease":"c2-probe-4","engine":"c2-probe","detail":"next in line: waiting for maya [interactive, releasing] to free memory"}
    {"seq":33,"at":"2026-10-28T10:48:32.984298875-03:00","card":"gpu4","kind":"lease.released","lease":"maya-3","engine":"maya","bytes":450887680}
    {"seq":34,"at":"2026-10-28T10:48:33.745381131-03:00","card":"gpu0","kind":"lease.freed","lease":"maya-3","engine":"maya","bytes":23918018560}
    {"seq":35,"at":"2026-10-28T10:48:33.746462671-03:00","card":"gpu1","kind":"lease.freed","lease":"maya-3","engine":"maya","bytes":24580718592}
    {"seq":36,"at":"2026-10-28T10:48:33.74658267-03:00","card":"gpu4","kind":"lease.freed","lease":"maya-3","engine":"maya","bytes":15223226368}
    {"seq":37,"at":"2026-10-28T10:48:33.747179133-03:00","card":"gpu3","kind":"lease.freed","lease":"maya-3","engine":"maya","bytes":16022241280}
    {"seq":38,"at":"2026-10-28T10:48:33.747190492-03:00","card":"gpu3","kind":"lease.granted","lease":"c2-probe-4","engine":"c2-probe","detail":"waited 8.789s"}
    {"seq":39,"at":"2026-10-28T10:48:33.747199828-03:00","card":"gpu2","kind":"lease.freed","lease":"maya-3","engine":"maya","bytes":15929966592}
    {"seq":40,"at":"2026-10-28T10:48:53.778234678-03:00","card":"gpu3","kind":"lease.released","lease":"c2-probe-4","engine":"c2-probe"}
    {"seq":41,"at":"2026-10-28T10:48:55.67691238-03:00","card":"gpu3","kind":"lease.freed","lease":"c2-probe-4","engine":"c2-probe"}
    • Maya is drained once, and all five cards are freed.
    • Berth sends SIGTERM at the drain. Maya's own shutdown takes 8.0 s of the 8.8 s wait.
    • The lease.released lines show 22692 MiB on gpu0 and 430 to 560 MiB on the other cards. The lease.freed lines show the full holds.
    • llama-swap logs the exit as an upstream process that exited unexpectedly and shows the model as not running. The next request loads it again.

    C3: --gpu by PCI address

    berth run --gpu 0000:09:00.0 and --gpu 00000000:09:00.0 are both granted on gpu3, with gpu3's UUID in CUDA_VISIBLE_DEVICES.

    berth: granted c3-probe-1
    CUDA_VISIBLE_DEVICES=GPU-12b260f6-f0b3-eb76-f6cf-dd06626c957d
    berth: granted c3-probe-2
    CUDA_VISIBLE_DEVICES=GPU-12b260f6-f0b3-eb76-f6cf-dd06626c957d
    

    I didn't run a lend from the five-card lease, since you said it can't lend yet. The voice setup stays on the gpu3 lease.

  24. T-Crypt commented on Oct 28, 2026

    @T-Crypt
    Owner

    Thanks for running all three. Each one matches what berthd should do, so the five-card lease is good for v0.4.0.

    Two notes on C2:

    • lease.released and lease.freed count different things. released is what the lease still held on each card when Maya's process exited. Maya had spent 8 s shutting down by then and had already given back most of gpu1 to gpu4. freed is the most the lease held on that card, and it fires once Maya is gone from the card.
    • llama-swap reports an unexpected upstream exit because the drain stops Maya's server from outside llama-swap. The next request loads it again, as you saw. That is how a drain works today.

    Lending from a lease on several cards won't be in v0.4.0. Keep the voice setup on its own gpu3 lease until then.

  25. rajputkunal4151-jpg commented on Oct 29, 2026

    @rajputkunal4151-jpg

    Voice command

  26. T-Crypt commented on Oct 29, 2026

    @T-Crypt
    Owner

    v0.4.0-rc.8 is the build I plan to release as v0.4.0. It has three changes that matter on a rig with five cards:

    • One card's probe failure no longer stops berthd at startup or keeps the gateway's background engines off the other cards. Each GPU in the status carries its own probe_error.
    • berth notify titles name the card (gpu1: GPU passed to a VM), and squatter and alert webhooks carry a card field.
    • In the harbor, each card's panel lists only its own leases and names the model each one serves. Maya's lease shows on each card as on gpu0..gpu4.

    If you can run it for a day the way you run rc.6, any change in what C1 and C2 showed would hold the release. Lending from a lease on several cards is still after v0.4.0.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions