LM Warden

Następna tura trafia tam, gdzie poprzednia zostawiła swój cache.

Agent w każdej turze wysyła ponownie całą historię, więc długa rozmowa to głównie prefiks, który silnik już przeczytał. vLLM przechowuje tę pracę w cache prefiksów (stan uwagi promptów, które już widział) i pomija ją, gdy ten sam prefiks przyjdzie następnym razem, ale tylko na replice, która tę pracę wykonała: każda pełna kopia modelu ma własny cache. Własny mechanizm równoważenia obciążenia vLLM wysyła każdą turę do repliki z najkrótszą kolejką, która zwykle nigdy nie widziała tej rozmowy, więc czyta całą historię od nowa. LM Warden umieszcza każdą nową rozmowę na najmniej obciążonej replice (żądania w toku, zapełnienie cache i liczba aktywnych rozmów) i trzyma ją tam, używając identyfikatora sesji, który Claude Code już wysyła. Przenosi żądanie tylko wtedy, gdy replika przekroczy próg przepełnienia: domyślnie gdy żądań w toku jest więcej niż ćwierć limitu równoczesnych sekwencji modelu, 8 na naszym serwerze z czterema kartami.

Repliki, a więc i ten routing, są częścią instalacji dla systemu Linux z kartami NVIDIA; aplikacja na Maca (beta) nie ma równoległości tensorowej ani równoległości danych.

Why affinity: one conversation over four turnsTwo panels of four replicas. First: each of four turns of one conversation lands on a different replica and is almost entirely prefilled from scratch. Second: all four turns stay on one replica and only the new turn is prefilled; the rest is served from the prefix cache.served from the prefix cacheprefilled from scratchvLLM’s balancer: a turn lands anywherereplica 0replica 1replica 2replica 3t1t2t3t4prefilled again: about the whole history, every turnLM Warden: a conversation stays on its replicareplica 0replica 1replica 2replica 3t1t2t3t4prefilled again: only the new turn
Why affinity: one conversation over four turnsTwo panels of four replicas. First: each of four turns of one conversation lands on a different replica and is almost entirely prefilled from scratch. Second: all four turns stay on one replica and only the new turn is prefilled; the rest is served from the prefix cache.served from the prefix cacheprefilled from scratchvLLM’s balancer: a turn lands anywherereplica 0replica 1replica 2replica 3t1t2t3t4prefilled again: about the whole history,every turnLM Warden: a conversation stays on its replicareplica 0replica 1replica 2replica 3t1t2t3t4prefilled again: only the new turn
Rys. 6Cztery tury jednej rozmowy na modelu z czterema replikami. Z mechanizmem równoważenia obciążenia vLLM każda tura przechodzi prefill (wczytanie promptu do cache) niemal od zera na tej replice, która ją dostała. Z LM Warden rozmowa zostaje na replice 2 i prefill przechodzi tylko nowa tura. Rysunek mechanizmu, a nie pomiar.

Zmierzone na serwerze z czterema kartami: przy przypiętych rozmowach udział tokenów promptu obsłużonych z cache wzrósł z 56 do 86% przy 4 równoczesnych sesjach, a mediana czasu do pierwszego bajtu spadła z 2,05 do 0,38 s. Liczba tokenów wyjściowych na minutę była wyższa na każdym przeprowadzonym przez nas etapie, a p95 czasu do pierwszego bajtu (najwolniejsze 5%) niższe na każdym etapie. Liczba pracujących replik prawie się nie zmieniła (2,71 wobec 2,83 z czterech przy 4 sesjach): zysk to ponowne użycie cache, a nie więcej pracujących kart.

Etapy z 24 i 32 sesjami to celowe przeciążenie czterech małych kart, przy którym czekanie sięga dziesiątek sekund z koligacją lub bez. Tam widać kompromis: ogon jest nadal krótszy z koligacją, ale mediana jest dłuższa. Liczby poniżej.

Karty GPU
4 × NVIDIA RTX A4000, każda 16 GiB
Model
qwen3-4b-dp4 (Qwen/Qwen3-4B-Instruct-2507), bf16
Układ
data_parallel_size 4, tensor_parallel_size 1
vLLM
0.26.0
LM Warden
To wydanie
Zarejestrowano
4 października 2026 r., oba warianty jeden po drugim
Obciążenie
Syntetyczne sesje w stylu Claude Code z naszego własnego narzędzia testowego, a nie sam Claude Code: wspólny prompt systemowy o długości około 3,5 tys. tokenów, pierwsza wiadomość unikalna dla sesji (nominalnie 6 tys., zmierzone około 7,2–7,5 tys.), potem jeszcze pięć tur po około 400 nowych tokenów każda, maksymalnie 256 tokenów wyjściowych na turę. Pętla zamknięta: sesja zaczyna następną turę, gdy poprzednia się kończy. 3 minuty na etap.
Time to first byte by concurrent sessionsMedian and 95th-percentile time to first byte on the four-card box at 4 to 32 concurrent sessions, affinity on against vLLM’s balancer, on a log scale from 0.3 to 100 seconds. 4 sessions: median 2.05 s off, 0.38 s on, p95 3.5 s off, 2.84 s on; 8 sessions: median 2.33 s off, 0.48 s on, p95 6.32 s off, 5.03 s on; 16 sessions: median 3.25 s off, 0.85 s on, p95 26.24 s off, 24.88 s on; 24 sessions: median 5.06 s off, 13.14 s on, p95 58.82 s off, 38.34 s on; 32 sessions: median 12.32 s off, 27.66 s on, p95 82.84 s off, 38.92 s on.affinity onvLLM balancerTime to first byte, median0.3131030100seconds, log scale48162432concurrent sessionsvLLM balancer, 4 sessions: 2.05 svLLM balancer, 8 sessions: 2.33 svLLM balancer, 16 sessions: 3.25 svLLM balancer, 24 sessions: 5.06 svLLM balancer, 32 sessions: 12.32 svLLM balanceraffinity on, 4 sessions: 0.38 saffinity on, 8 sessions: 0.48 saffinity on, 16 sessions: 0.85 saffinity on, 24 sessions: 13.14 saffinity on, 32 sessions: 27.66 saffinity onTime to first byte, p950.3131030100seconds, log scale48162432concurrent sessionsvLLM balancer, 4 sessions: 3.5 svLLM balancer, 8 sessions: 6.32 svLLM balancer, 16 sessions: 26.24 svLLM balancer, 24 sessions: 58.82 svLLM balancer, 32 sessions: 82.84 svLLM balanceraffinity on, 4 sessions: 2.84 saffinity on, 8 sessions: 5.03 saffinity on, 16 sessions: 24.88 saffinity on, 24 sessions: 38.34 saffinity on, 32 sessions: 38.92 saffinity on
Time to first byte by concurrent sessionsMedian and 95th-percentile time to first byte on the four-card box at 4 to 32 concurrent sessions, affinity on against vLLM’s balancer, on a log scale from 0.3 to 100 seconds. 4 sessions: median 2.05 s off, 0.38 s on, p95 3.5 s off, 2.84 s on; 8 sessions: median 2.33 s off, 0.48 s on, p95 6.32 s off, 5.03 s on; 16 sessions: median 3.25 s off, 0.85 s on, p95 26.24 s off, 24.88 s on; 24 sessions: median 5.06 s off, 13.14 s on, p95 58.82 s off, 38.34 s on; 32 sessions: median 12.32 s off, 27.66 s on, p95 82.84 s off, 38.92 s on.affinity onvLLM balancerTime to first byte, median0.3131030100seconds, log scale48162432concurrent sessionsvLLM balancer, 4 sessions: 2.05 svLLM balancer, 8 sessions: 2.33 svLLM balancer, 16 sessions: 3.25 svLLM balancer, 24 sessions: 5.06 svLLM balancer, 32 sessions: 12.32 svLLM balanceraffinity on, 4 sessions: 0.38 saffinity on, 8 sessions: 0.48 saffinity on, 16 sessions: 0.85 saffinity on, 24 sessions: 13.14 saffinity on, 32 sessions: 27.66 saffinity onTime to first byte, p950.3131030100seconds, log scale48162432concurrent sessionsvLLM balancer, 4 sessions: 3.5 svLLM balancer, 8 sessions: 6.32 svLLM balancer, 16 sessions: 26.24 svLLM balancer, 24 sessions: 58.82 svLLM balancer, 32 sessions: 82.84 svLLM balanceraffinity on, 4 sessions: 2.84 saffinity on, 8 sessions: 5.03 saffinity on, 16 sessions: 24.88 saffinity on, 24 sessions: 38.34 saffinity on, 32 sessions: 38.92 saffinity on
Rys. 7Czas do pierwszego bajtu przy 4–32 równoczesnych sesjach, mediana i p95, w skali logarytmicznej, aby etapy poniżej sekundy dało się odczytać obok tych trwających minutę. Z koligacją mediana jest znacznie niższa do 16 sesji (0,38 wobec 2,05 s przy 4) i wyższa przy 24 i 32; p95 jest niższy na każdym etapie, choć przy 16 różnica (24,9 wobec 26,2 s) mieści się w szumie jednego przebiegu na wariant. Zmierzone po stronie klienta, przez internet, do pierwszego strumieniowanego tokenu.
SessionsSessions/h off → onOutput tokens/min off → onTTFB p50 off → on (s)TTFB p95 off → on (s)Replicas busy off → onPrefix hits off → on (%)Stayed on replica (%)
4180 → 2405,886 → 6,7442.05 → 0.383.5 → 2.842.71 → 2.8356.2 → 86.4100
8300 → 3808,364 → 10,7522.33 → 0.486.32 → 5.033.74 → 3.7444.3 → 86.1100
16300 → 3609,558 → 12,2043.25 → 0.8526.24 → 24.883.91 → 3.6933.2 → 48.6100
24300 → 4209,048 → 11,1785.06 → 13.1458.82 → 38.343.91 → 432.8 → 37.989.1
32520 → 62014,760 → 16,63812.32 → 27.6682.84 → 38.924 → 431.6 → 31.667.3

Zmierzone na naszym serwerze z czterema kartami na małym modelu, najpierw z wyłączoną koligacją, potem z włączoną, bez ponownego uruchamiania vLLM pomiędzy: drugi wariant zaczynał z rozgrzanym cache prefiksów, a w obu wariantach prompty losowano z tym samym ziarnem generatora. Każdy etap trwa 3 minuty, więc kończy się w nim tylko od 9 do 31 sesji, a jedna sesja więcej lub mniej zmienia liczbę sesji na godzinę o 20; stabilniejszą kolumną są tokeny wyjściowe na minutę. Twój model, karty i obciążenie będą inne; karta replik na stronie modelu pokazuje te same liczniki dla twojego systemu.

Do 16 sesji koligacja prowadzi na każdym etapie pod względem przepustowości, trafień w cache prefiksów i obu kolumn czasu do pierwszego bajtu: trafienia rosną z 56 do 86% przy 4 sesjach, mediana czasu do pierwszego bajtu spada z 2,05 do 0,38 s, a liczba tokenów wyjściowych rośnie z 5 886 do 6 744 na minutę (sesji na godzinę ze 180 do 240). Przy 24 i 32 sesjach, czyli na etapach przeciążenia, każda replika jest nasycona. Liczba tokenów wyjściowych jest nadal wyższa (11 178 wobec 9 048 i 16 638 wobec 14 760 na minutę), a wolny ogon znacznie krótszy (p95 38 wobec 59 s oraz 39 wobec 83 s), ale mediana czasu do pierwszego bajtu jest z koligacją gorsza: 13,1 wobec 5,1 s przy 24 sesjach, 27,7 wobec 12,3 s przy 32. Nie ustaliliśmy przyczyny, a obniżenie progu przepełnienia z 8 do 4 tego nie zmieniło. Przewaga jest największa, dopóki jest zapas; serwer pracujący na granicy możliwości powinien zmierzyć oba ustawienia, a koligację można wyłączyć dla każdego modelu osobno.

Replicas busy by concurrent sessionsAverage replicas with running requests, of four, affinity on against vLLM’s balancer: 4 sessions 2.71 off, 2.83 on; 8 sessions 3.74 off, 3.74 on; 16 sessions 3.91 off, 3.69 on; 24 sessions 3.91 off, 4 on; 32 sessions 4 off, 4 on.affinity onvLLM balancerReplicas busy01234replicas busy, of 448162432concurrent sessionsvLLM balancer, 4 sessions: 2.71vLLM balancer, 8 sessions: 3.74vLLM balancer, 16 sessions: 3.91vLLM balancer, 24 sessions: 3.91vLLM balancer, 32 sessions: 4vLLM balanceraffinity on, 4 sessions: 2.83affinity on, 8 sessions: 3.74affinity on, 16 sessions: 3.69affinity on, 24 sessions: 4affinity on, 32 sessions: 4affinity on
Replicas busy by concurrent sessionsAverage replicas with running requests, of four, affinity on against vLLM’s balancer: 4 sessions 2.71 off, 2.83 on; 8 sessions 3.74 off, 3.74 on; 16 sessions 3.91 off, 3.69 on; 24 sessions 3.91 off, 4 on; 32 sessions 4 off, 4 on.affinity onvLLM balancerReplicas busy01234replicas busy, of 448162432concurrent sessionsvLLM balancer, 4 sessions: 2.71vLLM balancer, 8 sessions: 3.74vLLM balancer, 16 sessions: 3.91vLLM balancer, 24 sessions: 3.91vLLM balancer, 32 sessions: 4vLLM balanceraffinity on, 4 sessions: 2.83affinity on, 8 sessions: 3.74affinity on, 16 sessions: 3.69affinity on, 24 sessions: 4affinity on, 32 sessions: 4affinity on
Rys. 8Zajęte repliki z czterech: repliki z wykonywanymi żądaniami przy każdym odpytaniu, uśrednione w ramach etapu. To nie jest wykorzystanie GPU. Koligacja prawie tego nie zmienia; zysk widać na rys. 9.
Prefix-cache hit rate by concurrent sessionsShare of prompt tokens served from the prefix cache in each step, all four replicas pooled, affinity on against vLLM’s balancer: 4 sessions 56.2% off, 86.4% on; 8 sessions 44.3% off, 86.1% on; 16 sessions 33.2% off, 48.6% on; 24 sessions 32.8% off, 37.9% on; 32 sessions 31.6% off, 31.6% on.affinity onvLLM balancerPrefix-cache hit rate0255075100% of prompt tokens48162432concurrent sessionsvLLM balancer, 4 sessions: 56.2%vLLM balancer, 8 sessions: 44.3%vLLM balancer, 16 sessions: 33.2%vLLM balancer, 24 sessions: 32.8%vLLM balancer, 32 sessions: 31.6%vLLM balanceraffinity on, 4 sessions: 86.4%affinity on, 8 sessions: 86.1%affinity on, 16 sessions: 48.6%affinity on, 24 sessions: 37.9%affinity on, 32 sessions: 31.6%affinity on
Prefix-cache hit rate by concurrent sessionsShare of prompt tokens served from the prefix cache in each step, all four replicas pooled, affinity on against vLLM’s balancer: 4 sessions 56.2% off, 86.4% on; 8 sessions 44.3% off, 86.1% on; 16 sessions 33.2% off, 48.6% on; 24 sessions 32.8% off, 37.9% on; 32 sessions 31.6% off, 31.6% on.affinity onvLLM balancerPrefix-cache hit rate0255075100% of prompt tokens48162432concurrent sessionsvLLM balancer, 4 sessions: 56.2%vLLM balancer, 8 sessions: 44.3%vLLM balancer, 16 sessions: 33.2%vLLM balancer, 24 sessions: 32.8%vLLM balancer, 32 sessions: 31.6%vLLM balanceraffinity on, 4 sessions: 86.4%affinity on, 8 sessions: 86.1%affinity on, 16 sessions: 48.6%affinity on, 24 sessions: 37.9%affinity on, 32 sessions: 31.6%affinity on
Rys. 9Współczynnik trafień w cache prefiksów na każdym etapie: stosunek tokenów promptu obsłużonych z cache do wszystkich żądanych tokenów promptu, łącznie dla czterech replik, na podstawie zmiany własnych liczników vLLM w ramach etapu. Zysk utrzymuje się, dopóki jest zapas, i znika przy 32 sesjach, gdy rozmowy przechodzą na inne repliki.
Karta Data-parallel routing na stronie modelu z plakietkami Affinity on, Spill threshold 8 (auto), Sticky 100,0% i In flight 0. Tabela wymienia repliki od 0 do 3 z kolumnami: w toku, sticky (38, 21, 19 i 29), przyjęte po przepełnieniu innej repliki, przypięte przez klienta, wykonywane i oczekujące, użycie KV oraz współczynnik trafień w cache prefiksów (82,2; 86,0; 69,0 i 80,0 procent), a także wiersz informujący, że liczniki działają od uruchomienia LM Warden. Koligacja włączona, próg przepełnienia 8 (auto) Współczynnik trafień dla każdej repliki, z własnych liczników vLLM
Rys. 10Te same liczniki na stronie modelu: jeden wiersz na replikę z żądaniami, które obsługuje, liczbą rozmów, które zostały (sticky) lub trafiły tu po przepełnieniu innej repliki, tym, co jest wykonywane i oczekuje, użyciem cache KV i współczynnikiem trafień w cache prefiksów. Zarejestrowano 4 października 2026 r. z włączoną koligacją i progiem przepełnienia 8 (auto): sticky 100%, 38, 21, 19 i 29 żądań sticky na replikach od 0 do 3 oraz współczynnik trafień w cache prefiksów 82,2; 86,0; 69,0 i 80,0%. Liczniki działają od restartu LM Warden, a cały ruch w tym oknie odbywał się z włączoną koligacją: syntetyczne sesje agentów plus prawdziwe tury Claude Code. To zrzut panelu, a nie wynik testu. Przewiń obraz w bok, aby zobaczyć wszystkie kolumny.

Serwer, przez który w ogóle zajęliśmy się routingiem replik, działa z nim od tamtej pory. Ma siedem kart RTX 5090 obsługujących model 27B jako siedem jednokartowych replik dla dużej grupy agentów Claude Code działających w trybie headless. Nasza pierwsza reguła rozmieszczania haszowała identyfikator sesji, co dawało stabilność, ale nie uwzględniało obciążenia. Przy 26 sesjach agentów po około 58 tys. tokenów każda trzy repliki stały bezczynnie, podczas gdy inne zapełniały cache do 93%, a udział tokenów promptu obsłużonych z cache spadł z 81% po restarcie do 28% w ciągu około 35 minut. Wtedy zmniejszyliśmy liczbę sesji do 16, około dwóch na replikę, tak aby zbiory robocze mieściły się w cache, i zaczęliśmy umieszczać nowe rozmowy na najmniej obciążonej replice. Przez następną godzinę udział wynosił 78–79%. To obserwacja, a nie test A/B: jedno obciążenie i dwie zmiany wprowadzone razem, więc zysk nie jest wyłącznie zasługą rozmieszczania. Praktyczna reguła, jaka z tego wynika: liczba równoczesnych sesji pomnożona przez ich typowy kontekst powinna być mniejsza niż łączny cache serwera.

Wykres Tokens per second ze strony Stats z jednej godziny. Słupki promptu są podzielone na cached, zmierzone, na niebiesko i computed na fioletowo, od około 15 tys. do 45 tys. tokenów na sekundę, przy czym większość każdego słupka jest niebieska, z podsumowaniem cache hit 78% z ostatniej godziny. Tokeny odpowiedzi na sekundę, od około 70 do 420, są narysowane na zielono poniżej. Zmierzone przez silnik dla każdego żądania
Rys. 11Tokeny promptu na sekundę na serwerze z siedmioma kartami GPU, podzielone na to, co każda replika odczytała z cache prefiksów, i to, co musiała obliczyć, z udziałem z ostatniej godziny: 78%. Część z cache to własny licznik silnika dla każdego zakończonego żądania, a nie szacunek; żądanie, którego silnik nie zmierzył, liczy się jako obliczone. Zarejestrowano 5 października 2026 r. Przewiń obraz w bok, aby zobaczyć całą godzinę.