Duże modele językowe (LLM) są wszędzie, od codziennych aplikacji po zaawansowane narzędzia. Używanie ich jest łatwe. Ale co, jeśli musisz uruchomić własny model? Niezależnie od tego, czy masz jeden dobrze dostosowany, czy zajmujesz się danymi wrażliwymi na prywatność, złożoność rośnie. W tym poście podzielimy się tym, czego nauczyliśmy się podczas budowania własnego systemu wnioskowania LLM. Zajmiemy się przechowywaniem i wdrażaniem modeli, projektowaniem architektury usług i rozwiązywaniem problemów w świecie rzeczywistym, takich jak routing, strumieniowanie i zarządzanie mikrousługami. Proces wiązał się z wyzwaniami, ale ostatecznie zbudowaliśmy niezawodny system i zebraliśmy lekcje warte dzielenia się. Wprowadzenie LLM zasilają szeroką gamę zastosowań - od chatbotów i agentów przepływu pracy do inteligentnych narzędzi do automatyzacji. Podczas gdy generowanie zwiększone o odzyskanie, połączenie narzędzi i protokoły wielodzietne są ważne, działają na poziomie powyżej podstawowego silnika: podstawowego LLM. Wiele projektów opiera się na zewnętrznych dostawcach, takich jak , , lub , co jest wystarczające dla większości przypadków użytkowania. Ale dla niektórych aplikacji szybko staje się problemem. Co jeśli dostawca upadnie? Co jeśli potrzebujesz pełnej kontroli nad opóźnieniem, ceną lub czasem pracy? Co najważniejsze – co jeśli zależy Ci na prywatności i nie możesz sobie pozwolić na przesyłanie danych użytkownika osobom trzecim? Otwarcie Bliźnięta Antropologiczne Otwarcie Bliźnięta Antropologiczne Właśnie w tym miejscu staje się niezbędne samoobsługiwanie. Obsługa wstępnie wyszkolonego lub dobrze dostosowanego modelu zapewnia kontrolę, bezpieczeństwo i możliwość dostosowania modelu do konkretnych potrzeb biznesowych. Budowa takiego systemu nie wymaga dużego zespołu ani rozległych zasobów. Zbudowaliśmy go z niewielkim budżetem, małym zespołem i zaledwie kilkoma węzłami. To ograniczenie wpłynęło na naszą decyzję architektoniczną, wymagając od nas skupienia się na praktyczności i wydajności. Ogólny przegląd Są to podstawowe elementy, które tworzą rdzeń systemu. Oznacza to spójne formaty żądania/odpowiedzi, schematy parametrów generowania, struktury historii dialogów i serializację, która działa wszędzie – od frontend do backend do modelowych biegaczy. Obsługa wielu modeli, typów żądań i priorytetów hosta wymaga świadomych decyzji dotyczących routingu. Opisujemy, w jaki sposób przychodzące żądania użytkowników są przekierowywane przez system – od punktu wejścia początkowego do odpowiedniego węzła pracownika – i w jaki sposób odpowiedzi są przesyłane z powrotem. Gdzie żyją modele i jak są przygotowywane do użytku produkcyjnego? Omówimy kluczowe testy, które należy wykonać, w tym zapewnienie niezawodności modelu. Możliwość obserwacji. skąd wiesz, że wszystko działa? pokażemy, jakie metryki śledzimy, jak monitorujemy awarie i jakie sondy używamy, aby zapewnić zdrowie i niezawodność systemu. Schemat i kodowanie danych Wybór odpowiedniego schematu transferu danych jest kluczowy. Wspólny format usług ułatwia integrację, zmniejsza błędy i poprawia adaptację. Dlaczego schemat projektowania ma znaczenie Nie ma uniwersalnego standardu wymiany danych LLM. Wielu dostawców stosuje schematy podobne do Inni – tak jak lub Wielu z tych dostawców oferuje kompatybilne z OpenAI SDK, które zachowują ten sam schemat, chociaż często z ograniczeniami lub zmniejszonymi zestawami funkcji (np. , Inne projekty takie jak Celem jest ujednolicenie tych wariantów, włączając je do interfejsu zgodnego z OpenAI. Otwarcie Claude Bliźnięta Zgodny z OpenAI SDK firmy Anthropic Warstwa kompatybilności OpenAI Gemini OpenRouter Otwarcie Claude Bliźnięta Zgodny z OpenAI SDK firmy Anthropic Warstwa kompatybilności OpenAI Gemini OpenRouter Trzymanie się jednego wstępnie zdefiniowanego schematu dostawcy ma swoje zalety: Otrzymasz dobrze przetestowane, stabilne API. Możesz polegać na istniejących SDK i narzędziach. But there are real downsides too: Tworzy zablokowanie dostawców, co utrudnia obsługę wielu dostawców. Ogranicza elastyczność w rozszerzaniu schematu o niestandardowe funkcje wymagane dla potrzeb biznesowych lub wymagań zespołu Data Science. Jesteś narażony na przerywanie zmian lub odpisów poza twoją kontrolą. Schematy te często niosą ze sobą ograniczenia dziedziczenia, które ograniczają kontrolę drobnych ziaren. W tym celu postanowiliśmy zdefiniować naszą - schemat zaprojektowany wokół naszych potrzeb, który możemy następnie mapować do różnych formatów zewnętrznych w razie potrzeby. own internal data model Internal Schema Design Zanim zajmiemy się wyzwaniami, zdefiniujmy problem i określmy nasze oczekiwania dotyczące rozwiązania: Łatwa konwersja do formatów wymaganych przez dostawców zewnętrznych i odwrotnie. Full support for features specific to our business and data science teams. Upewnij się, że schemat jest łatwo rozszerzalny, aby zaspokoić przyszłe wymagania. Zaczęliśmy od przeglądu głównych schematów LLM, aby zrozumieć, w jaki sposób dostawcy strukturują wiadomości, parametry i wyjścia. Wspólne w większości systemów, w tym: core domain entities Wiadomości (np. szybkie, historia) Parametry generowania (np. temperatura, top_p, beam_search) Zidentyfikowaliśmy pewne parametry, takie jak , , lub , jako specyficzne dla wewnętrznej konfiguracji dostawcy i logiki biznesowej. Elementy te znajdują się poza domeną podstawową LLM i nie są częścią wspólnego schematu. Zamiast tego są traktowane jako opcjonalne rozszerzenia. Za każdym razem, gdy funkcja staje się powszechnie przyjęta lub niezbędna do szerszej interoperacyjności, oceniamy jej integrację z schematem podstawowym. service_tier usage_metadata reasoning_mode Na wysokim poziomie nasz schemat wejść jest skonstruowany z tymi kluczowymi komponentami: Model – służy jako klucz routingu, działa jako identyfikator routingu, umożliwiając systemowi kierowanie żądania do odpowiedniego węzła robotnika. Parametry generowania – ustawienia modelu podstawowego (np. temperatura, top_p, max_tokens). Wiadomości – historia konwersacji i szybkie obciążenia. Narzędzia — Definicje narzędzi, które model może używać. To prowadzi nas do następującego schematu, reprezentowanego w Ilustruje strukturę i zamiar projektu, chociaż niektóre szczegóły wdrożenia są pomijane ze względu na prostotę. Pydantic-like Pytanie podobnie class ChatCompletionRequest(BaseModel): model: str # Routing key to select the appropriate model or service messages: list[Message] # Prompt and dialogue history generation_parameters: GenerationParameters # Core generation settings tools: list[Tool] # Optional tool defenitions class GenerationParameters(BaseModel): temperature: float top_p: float max_tokens: int beam_search: BeamSearchParams # Optional, non-core fields specific to certain providers provider_extensions: dict[str, Any] = {} ... # Other parameters Celowo przeniesiliśmy parametry generacji do oddzielnego pola gniazdowego zamiast umieszczać je na poziomie korzeniowym. parametry (np. temperatura, top-p, ustawienia modelu) oraz Wiele zespołów w naszym ekosystemie przechowuje te stałe parametry w zewnętrznych systemach konfiguracyjnych, co czyni to oddzielenie praktycznym i koniecznym. constant zmienna Dodatkowe pole o nazwie W ramach Te parametry różnią się znacznie w różnych dostawców LLM, walidacja i interpretacja tych pól jest — komponent, który wie, jak komunikować się z konkretnym dostawcą modelu. W ten sposób unikamy niepotrzebnego przełączania przechodzącego spowodowanego nadmierną walidacją danych w wielu usługach. provider_extensions GenerationParameters delegated to the final module that handles model inference Aby zapewnić kompatybilność wsteczną, nowe funkcje schematu wyjściowego są wprowadzane jako w schemacie żądania. Pole te działają jako flagi funkcji — użytkownicy muszą je ustawić, aby zdecydować się na określone zachowania. To podejście utrzymuje schemat rdzenia stabilny, umożliwiając jednocześnie ewolucję stopniową. Na przykład ślady rozumowania zostaną uwzględnione w wyjściu tylko wtedy, gdy odpowiednie pole zostanie ustawione w żądaniu. explicit, optional fields Schematy te są utrzymywane w udostępnionej bibliotece Python i wykorzystywane w różnych usługach w celu zapewnienia spójnego przetwarzania żądań i odpowiedzi. Working with Third-Party providers Zaczęliśmy od wyjaśnienia, w jaki sposób zbudowaliśmy własną platformę – po co więc martwić się kompatybilnością między zewnętrznymi dostawcami? Syntetyczne generowanie danych do prototypowania i eksperymentowania przez nasze zespoły naukowe. where some proprietary models perform better out of the box. General-purpose tasks Niewrażliwe przypadki użytkowania, w których prywatność, opóźnienie lub kontrola infrastruktury są mniej krytyczne. Ogólny przepływ komunikacji z dostawcami zewnętrznymi można podsumować w następujący sposób: Proces ten obejmuje następujące kroki: Specjalna usługa LLM-Gateway odpowiedzialna za komunikację z dostawcą otrzymuje żądanie użytkownika w naszym formacie schematu. Wniosek jest konwertowany do formatu specyficznego dla dostawcy, w tym wszelkich rozszerzeń provide_extensions. Dostawca zewnętrzny przetwarza żądanie i zwraca odpowiedź. LLM-Gateway Service otrzymuje odpowiedź i mapuje ją z powrotem do naszego standaryzowanego schematu odpowiedzi. Jest to schemat na wysokim poziomie, który abstrahuje od poszczególnych mikrousług. Szczegóły dotyczące określonych komponentów i formatu odpowiedzi strumieniowej zostaną omówione w poniższych sekcjach. Streaming w formacie Odpowiedzi LLM są generowane stopniowo - token za tokenem - a następnie agregowane do z punktu widzenia użytkownika, czy to za pośrednictwem przeglądarki, aplikacji mobilnej, czy terminala, doświadczenie musi pozostać . This requires a transport mechanism that supports . chunks fluid and responsive low-latency, real-time streaming Istnieją dwa podstawowe sposoby na osiągnięcie tego celu: WebSockets – kanał komunikacji w pełnym dupleksie, który umożliwia ciągłą dwukierunkową interakcję między klientem a serwerem. Wydarzenia wysyłane przez serwer (SSE): Jednostronny protokół strumieniowy oparty na HTTP, który jest szeroko stosowany do aktualizacji w czasie rzeczywistym. WebSockets WEBSockety Wydarzenia wysyłane przez serwer (SSE) Wydarzenia wysyłane przez serwer (SSE) Dlaczego SSE nad WebSockets? Chociaż obie opcje są możliwe, — w szczególności dla interfejsów API zgodnych z OpenAI i podobnych systemów. Wynika to z kilku praktycznych zalet: SSE is the more commonly used solution for standard LLM inference Prostota: SSE działa nad standardowym HTTP, nie wymaga specjalnych uaktualnień ani negocjacji. Kompatybilność: działa natywnie we wszystkich głównych przeglądarkach bez dodatkowych bibliotek. Jednostronny przepływ: Większość odpowiedzi LLM przepływa tylko z serwera na klienta, co jest zgodne z projektem SSE. Proxy-Friendliness: SSE świetnie radzi sobie ze standardową infrastrukturą HTTP, w tym z odwrotnymi proxy. Upgrade lub negocjacje Ze względu na te korzyści, . SSE is typically chosen for text-only, prompt-response streaming use cases However, some emerging use cases require richer, low-latency, bidirectional communication — such as real-time transcription or speech-to-speech interactions. Rozwiązywanie tych potrzeb poprzez wykorzystanie Protokoły te lepiej nadają się do ciągłego multimodalnego wejścia i wyjścia. Rozwiązanie API w czasie rzeczywistym WebSockets Rozwiązanie API w czasie rzeczywistym Ponieważ nasz system skupia się wyłącznie na Trzymamy się z za jego prostotę, kompatybilność i dostosowanie do naszego modelu strumieniowego. text-based interactions SSE Reply Stream treści z wybrana jako warstwa transportu, następnym krokiem było zdefiniowanie Efektywne strumieniowanie wymaga więcej niż tylko surowego tekstu – musi dostarczać wystarczającej ilości informacji. aby wspierać konsumentów w dalszym ciągu, takich jak interfejsy użytkownika i narzędzia do automatyzacji. SSE what structure, metadata, and context Metadane na poziomie nagłówka. Podstawowe informacje identyfikacyjne, takie jak identyfikator żądania. Rzeczywista zawartość Chunks. Podstawowa wydajność – tokeny lub struny generowane przez model – jest dostarczana stopniowo, gdy sekwencje (n) są przesyłane z powrotem, cząstką za cząstką. Każde pokolenie może składać się z wielu sekwencji (np. n = 2, n = 4) .Te sekwencje są generowane niezależnie i przesyłane równolegle, każda podzielona na swój własny zestaw stopniowych fragmentów. Metadane na poziomie użytkowania i tokenów. Obejmuje to liczbę wygenerowanych tokenów, dane czasowe i opcjonalne metody diagnostyczne, takie jak logproby lub ślady rozważania. After defining the structure of the streamed response, we also considered several non-functional requirements essential for reliability and future evolution. Nasz design strumienia ma na celu: Strukturowane – wyraźnie rozróżnia typy zawartości i granice zdarzeń. — capable of carrying optional metadata without breaking existing clients. Extensible Solidny – odporny na zniekształcone, opóźnione lub częściowe dane. W wielu aplikacjach, takich jak lub — multiple sequences (completions) are generated in parallel as part of a single generation request. side-by-side comparison diverse sampling Najbardziej wszechstronny format odpowiedzi strumieniowych jest zdefiniowany w Zgodnie ze specyfikacją, pojedynczy kawałek generacji może zawierać wiele sekwencji w i array : OpenAI API Referencje choices OpenAI API Referencje Wybór array Lista opcji zakończenia czatu. Może zawierać więcej niż jeden element, jeśli n jest większy niż 1. choices array A list of chat completion choices. Can contain more than one element if n is greater than 1. Can also be empty for the last chunk. Although, in practice, individual chunks usually contain only a single delta, the format allows for multiple sequence updates per chunk. It’s important to account for this, as future updates might make broader use of this capability. Notably, even the is designed to support this structure. official Python SDK official Python SDK We chose to follow the same structure to ensure compatibility with a wide range of potential features. The diagram below illustrates an example from our implementation, where a single generation consists of three sequences, streamed in six chunks over time: This chunk marks the beginning of the entire generation. It doesn’t contain any actual content but includes shared metadata, such as the generation ID, timestamp, and role (e.g., assistant, etc.). Chunk 1 — Generation Start. Chunk 2 — Sequence Start (Green & Purple). Dwie sekwencje zaczynają strumieniowanie równolegle. Każdy jest oznaczony unikalnym identyfikatorem, aby odróżnić go od innych. The third sequence starts (blue), while the first two sequences (green and purple) stream incremental content via delta events. Chunk 3 — Sequence Start (Blue) & Sequence Delta. The green and blue sequences continue streaming deltas. The purple sequence finishes — this includes a structured finish_reason (like stop, length, etc.). Chunk 4 — Midstream Updates & Finish (Purple). Both the green and blue sequences complete. Each sequence’s lifecycle is now fully enclosed between its respective start and finish markers. Chunk 5 — Remaining Sequence Finishes. This chunk closes the generation and may include global usage statistics, final token counts, latency info, or other diagnostics. Chunk 6 — Generation Finish. As you see, to make the stream robust and easier to parse, we opted to , rather than relying on implicit mechanisms such as null checks, EOFs, or magic tokens. This structured approach simplifies downstream parsing, especially in environments where multiple completions are streamed in parallel, and also improves debuggability and fault isolation during development and runtime inspection. explicitly signal Start and Finish events for both the overall generation and each individual sequence Moreover, we introduce an additional chunk that carries structured information about failures. Some errors — such as malformed requests or authorization issues — can be surfaced directly via standard HTTP response codes. However, if an error occurs , we have two options: either abruptly terminate the HTTP stream or emit a well-formed SSE error event. We chose the latter. Abruptly closing the connection makes it hard for clients to distinguish between network issues and actual model/service failures. By using a dedicated error chunk, we enable more reliable detection and propagation of issues during streaming. Error during the generation process Usługi backendowe i przepływ żądań W centrum systemu znajduje się jeden punkt wejścia: . It handles basic concerns like authentication, usage tracking and quota enforcement, request formatting, and routing based on the specified model. While it may look like the Gateway carries a lot of responsibility, each task is intentionally simple and modular. For external providers, it adapts requests to their APIs and maps responses back into a unified format. For self-hosted models, requests are routed directly to internal systems using our own unified schema. This design allows seamless support for both external and internal models through a consistent interface. LLM-Gateway Modele samoobsługowe Jak wspomniano wcześniej, is well-suited for streaming responses to end users, but it’s not a practical choice for . When a request arrives, it must be routed to a suitable worker node for model inference, and the result streamed back. While some systems handle this using chained HTTP proxies and header-based routing, in our experience, this approach becomes difficult to manage and evolve as the logic grows in complexity. Server-Sent Events (SSE) internal backend communication Nasza wewnętrzna infrastruktura musi wspierać: — Requests may have different urgency levels (e.g., interactive vs. batch), and high-priority tasks must be handled first. Priority-aware scheduling — Certain nodes run on higher-performance GPUs and should be preferred; others serve as overflow capacity. Hardware-aware routing — Each worker is configured to support only a subset of models, based on hardware compatibility and resource constraints. Model-specific dispatching Aby sprostać tym wymaganiom, stosujemy Aby odłączyć routing zadań od dostarczania wyników. Ta konstrukcja zapewnia lepszą elastyczność i odporność w różnych warunkach obciążenia i routingu. for this purpose, though other brokers could also be viable depending on your latency, throughput, and operational preferences. RabbitMQ was a natural fit given its maturity and alignment with our existing tooling. message broker króliki króliki RabbitMQ Now let’s take a closer look at how this system is implemented in practice: We use , allowing us to route requests based on model compatibility and node capabilities. The process is as follows: dedicated queues per model The LLM-Gateway service (represented as the user) initiates an HTTP request to trigger a text generation task. starts a new to manage this request. Client Sends Request. Scheduler service Request Handler The request is handled by the , which selects the appropriate queue (marked in green on the image) based on the requested model and appends the message to it. Task Routing via Scheduler service. Scheduler An appropriate (only one worker is shown for simplicity, but there are many) subscribed to the queue picks up the task and begins processing. This worker runs the selected model locally. Worker Picks Up Task. Inference Worker The worker streams the response chunk-by-chunk into the , to which the . Streaming the Response. Response Queue Scheduler replica handling the request is subscribed The Scheduler listens to the reply queue and receives the response chunks as they arrive. Receiving Response Chunks. The chunks are converted to SSE format and streamed to the client. SSE Streaming. Do obsługi , unikamy przytłaczania brokera wiadomości: large payloads Zamiast osadzać duże dane wejściowe lub wyjściowe bezpośrednio w zadaniu, przesyłamy je do zewnętrznego magazynu zgodnego z S3. A reference (such as a URL or resource ID) is included in the task metadata, and the worker retrieves the actual content when needed. Zastosowanie projektu z RabbitMQ Kiedy chodzi o , each is a regular RabbitMQ , dedicated to handling a single model type. We require Co można osiągnąć za pomocą . In this setup, messages with higher priority values are delivered and processed before lower priority ones. For , where messages should be directed to the most performant available nodes first, can help. Consumers with higher priority receive messages as long as they are active; lower-priority consumers only receive messages when the higher-priority ones are blocked or unavailable. routing and publishing messages Request Queue Koła priority-aware scheduling Priorytety wiadomości hardware-aware routing consumer priorities Koła Priorytety wiadomości consumer priorities Jeśli utrata wiadomości jest niedopuszczalna, należy wykonać następujące czynności: Wydawca potwierdza, aby upewnić się, że broker otrzymał i zapisał wiadomość. Trwałe kolejki i trwałe wiadomości, aby dane przetrwały. for stronger durability through replication. These also support . Quorum queues uproszczone przesłanie i priorytety konsumentów od RabbitMQ 4.0 Publisher confirms Publisher confirms Quorum queues Quorum queues uproszczone przesłanie i priorytety konsumentów od RabbitMQ 4.0 So far, we’ve covered how tasks are published — but how is the handled? The first step is to understand how work in RabbitMQ. The broker supports a concept called , which are bound to a single connection and automatically deleted when that connection closes. This makes them a natural fit for our setup. streamed response temporary queues exclusive queues Ekskluzywne kolejki Ekskluzywne kolejki Tworzymy , ensuring it’s automatically cleaned up when the replica shuts down. However, this introduces a challenge: while each service replica has a single RabbitMQ queue, it must handle . one exclusive queue per Scheduler service replica many requests in parallel To address this, we treat the RabbitMQ queue as a , skierowanie odpowiedzi do poprawnej repliki programu Scheduler. Każde żądanie użytkownika jest przypisywane , which is included in every response chunk. Inside the Utrzymujemy dodatkową with short-lived in-memory queues — one per active request. Incoming chunks are matched to these queues based on the identifier and forwarded accordingly. These in-memory queues are discarded once the request completes, while the RabbitMQ queue persists for the lifetime of the service replica. transport layer unique identifier Scheduler in-memory routing layer Schematycznie wygląda to następująco: Centralny dystrybutor w programie Scheduler wysyła fragmenty do odpowiedniej kolejki w pamięci, z których każdy jest zarządzany przez dedykowanego manipulatora. Inference There are several mature frameworks available for efficient LLM inference, such as and . These systems are designed to and generate response tokens in real time, often with features like continuous batching and GPU memory optimization. In our setup, we use jako główny silnik inferencyjny, z kilkoma niestandardowymi modyfikacjami: WLLM SGLANG process multiple sequences in parallel vLLM WLLM WLLM SGLANG SGLANG Wdrożenie niestandardowego wyszukiwania wiązki – aby lepiej dopasować się do naszej logiki generacji i obsługiwać strukturalne ograniczenia. — allowing models to return outputs conforming to business-specific formats. Support for structured output schemas Through experience, we’ve learned that even minor library updates can — whether in output quality, determinism, or concurrency behavior. Because of this, we’ve established a robust testing pipeline: significantly alter model behavior Testy stresowe mają na celu wykrycie problemów z równoległością, wycieków pamięci lub regresji stabilności. Testowanie determinizmu w celu zapewnienia spójnych wyników dla stałych nasion i zestawów parametrów. to cover a wide range of generation settings, without going overboard. Parameter grid testing Storage and deployment Most modern systems run in — either in the cloud or within Kubernetes (K8s). While this setup works well for typical backend services, it introduces challenges around Modele LLM mogą być , a model pieczenia waży bezpośrednio w obrazach Docker — szybko staje się problematyczny: containerized environments model weight storage tens or even hundreds of gigabytes in size Powolne budowanie – nawet przy budowaniu wielostopniowym i pamięci podręcznej, przesyłanie dużych plików modelu podczas fazy budowy może znacznie zwiększyć czas CI. — Each rollout requires pulling massive images, which can take several minutes and cause downtime or delays. Slow deployments — Neither Docker registries nor Kubernetes nodes are optimized for handling extremely large images, resulting in bloated storage usage and bandwidth strain. Resource inefficiency Aby to rozwiązać, rozdzielamy from the Docker image lifecycle. Our models are stored in an , i pobrano tuż przed uruchomieniem usługi wnioskowania. Aby poprawić czas uruchomienia i uniknąć nadmiernych pobrań, używamy również to cache model weights on each node. model storage external S3-compatible object storage local persistent volumes (PVCs) Lokalne trwałe objętości (PVC) local persistent volumes (PVCs) Observability Taki system – zbudowany na — requires to ensure reliability and performance at scale. streaming, message queues, and real-time token generation robust observability In addition to standard service-level metrics (CPU, memory, error rates, etc.), we found it essential to monitor the following: Głębokość kolejki, blokada wiadomości i liczba konsumentów – monitorowanie liczby wiadomości w oczekiwaniu, bieżącego rozmiaru kolejki i liczby aktywnych konsumentów pomaga wykryć bariery w dystrybucji zadań i zaburzenia równowagi w wykorzystaniu pracowników. — tracking the number of tokens or response chunks generated per second helps identify latency or throughput regressions. Token/chunk throughput — to pinpoint where requests fail or stall across components (gateway, broker, workers, etc.). Rozproszone śledzenie Sprawdzanie stanu zdrowia silnika inferencyjnego – ponieważ procesy inferencyjne mogą ulec awarii w rzadkich warunkach (np. złe wejścia lub skrajne wartości parametrów), kluczowe znaczenie ma proaktywne monitorowanie żywotności i gotowości. Distributed tracing Rozproszone śledzenie Further Improvements While our system is production-ready, there are still important challenges and opportunities for optimization: Using a to boost inference performance. distributed KV-cache Obsługa anulowania żądania w celu zachowania komputera, gdy wyjścia nie są już potrzebne. Creating a for data science teams. simple model delivery pipeline Conclusion While building a reliable and provider-independent LLM serving system can seem complex at first, it doesn’t require reinventing the wheel. Each component — streaming via SSE, task distribution through message brokers, and inference handled by runtimes like vLLM — serves a clear purpose and is grounded in existing, well-supported tools. With the right structure in place, it’s possible to create a maintainable and adaptable setup that meets production requirements without unnecessary complexity. W następnym poście przeanalizujemy bardziej zaawansowane tematy, takie jak rozproszone pamięci podręcznej KV, obsługa wielu modeli w replikach i przepływy pracy wdrażania odpowiednie dla zespołów zorientowanych na ML. Autorzy toczka , Stanislav Shimovolos Stanislav Shimovolos Stanislav Shimovolos Tochka , Maxim Afanasyev Maxim Afanasyev Maksym Afanasiew Uznania Prace wykonane w Toskanii Dmitry Kryukov Dmitry Kryukov Dmitrij Kryukow