char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Xuan-Son Nguyenserver: refactor subproc handling (#28555)82d6bb284

char/aliceai-t5-moe
16.4 KiB412 linesraw
1#pragma once
2
3#include "common.h"
4#include "download.h"
5#include "preset.h"
6#include "server-common.h"
7#include "server-http.h"
8#include "server-queue.h"
9
10#include <mutex>
11#include <condition_variable>
12#include <thread>
13#include <functional>
14#include <memory>
15#include <optional>
16#include <set>
17#include <string>
18#include <unordered_map>
19
20/**
21 * state diagram:
22 *
23 * DOWNLOADING ──► DOWNLOADED ──► (replaced by new instance)
24 *
25 * UNLOADED ──► LOADING ──► LOADED ◄──── SLEEPING
26 *  ▲            │            │               ▲
27 *  └───failed───┘            │               │
28 *  ▲                         └──sleeping─────┘
29 *  └────────unloaded─────────┘
30 */
31enum server_model_status {
32    // TODO: also add downloading state when the logic is added
33    SERVER_MODEL_STATUS_DOWNLOADING,
34    SERVER_MODEL_STATUS_DOWNLOADED,
35    SERVER_MODEL_STATUS_UNLOADED,
36    SERVER_MODEL_STATUS_LOADING,
37    SERVER_MODEL_STATUS_LOADED,
38    SERVER_MODEL_STATUS_SLEEPING
39};
40
41enum server_model_source {
42    SERVER_MODEL_SOURCE_PRESET,
43    SERVER_MODEL_SOURCE_MODELS_DIR,
44    SERVER_MODEL_SOURCE_CACHE,
45};
46
47enum server_child_mode {
48    SERVER_CHILD_MODE_NORMAL,   // load the model and run normally
49    SERVER_CHILD_MODE_DOWNLOAD, // download the model and exit
50};
51
52static std::string server_model_status_to_string(server_model_status status) {
53    switch (status) {
54        case SERVER_MODEL_STATUS_DOWNLOADING: return "downloading";
55        case SERVER_MODEL_STATUS_DOWNLOADED:  return "downloaded";
56        case SERVER_MODEL_STATUS_UNLOADED:    return "unloaded";
57        case SERVER_MODEL_STATUS_LOADING:     return "loading";
58        case SERVER_MODEL_STATUS_LOADED:      return "loaded";
59        case SERVER_MODEL_STATUS_SLEEPING:    return "sleeping";
60        default:                              return "unknown";
61    }
62}
63
64static std::string server_model_source_to_string(server_model_source source) {
65    switch (source) {
66        case SERVER_MODEL_SOURCE_PRESET:     return "preset";
67        case SERVER_MODEL_SOURCE_MODELS_DIR: return "models_dir";
68        case SERVER_MODEL_SOURCE_CACHE:      return "cache";
69        default:                             return "unknown";
70    }
71}
72
73struct server_model_meta {
74    server_model_source source = SERVER_MODEL_SOURCE_CACHE;
75    common_preset preset;
76    std::string name;
77    std::set<std::string> aliases; // additional names that resolve to this model
78    std::set<std::string> tags;    // informational tags, not used for routing
79    int port = 0;
80    server_model_status status = SERVER_MODEL_STATUS_UNLOADED;
81    int64_t last_used = 0; // for LRU unloading
82    std::vector<std::string> args; // args passed to the model instance, will be populated by render_args()
83    json loaded_info; // info to be reflected via /v1/models endpoint ; if in DOWNLOADING state, it should contain download progress info
84    json progress; // reflect load or download progress info, if any
85    int exit_code = 0; // exit code of the model instance process (only valid if status == FAILED)
86    int stop_timeout = 0; // seconds to wait before force-killing the model instance during shutdown
87    mtmd_caps multimodal; // multimodal capabilities
88    bool hidden = false; // hidden from GET /models, but still accept if requested
89
90    bool is_ready() const {
91        return status == SERVER_MODEL_STATUS_LOADED;
92    }
93
94    bool is_running() const {
95        return status == SERVER_MODEL_STATUS_LOADED || status == SERVER_MODEL_STATUS_LOADING || status == SERVER_MODEL_STATUS_SLEEPING;
96    }
97
98    bool is_ready_or_sleep() const {
99        return status == SERVER_MODEL_STATUS_LOADED || status == SERVER_MODEL_STATUS_SLEEPING;
100    }
101
102    bool is_failed() const {
103        return status == SERVER_MODEL_STATUS_UNLOADED && exit_code != 0;
104    }
105
106    void update_args(common_preset_context & ctx_presets, std::string bin_path);
107    void update_caps();
108};
109
110struct server_models_routes;
111struct server_lru_sched; // defined in server-models.cpp
112struct server_monitor;   // defined in server-models.cpp
113
114struct server_models {
115    friend struct server_models_routes;
116    friend struct server_lru_sched;
117    friend struct server_monitor;
118
119private:
120    struct instance_t {
121        std::shared_ptr<server_subproc> subproc; // shared with the monitor thread
122        server_model_meta meta;
123        int req_count = 0; // number of active proxy requests
124
125        // ask the child to exit (it handles the command on its stdin, see server_child::setup)
126        void request_exit() const;
127    };
128
129    std::mutex mutex;
130    std::condition_variable cv;
131    std::map<std::string, instance_t> mapping;
132
133    // models asked to stop, still counted as running until the monitor records their exit
134    std::set<std::string> stopping_models;
135
136    // set to true while load_models() is executing a reload; load() will wait until clear
137    bool is_reloading = false;
138
139    // if true, the next get_meta() will trigger a reload of model list
140    bool need_reload = false;
141
142    // models marked with load-on-startup, unset once load_startup_models() drains it
143    // no value means the startup phase is over, so a reload must not queue anything
144    std::optional<std::vector<std::string>> startup_models{std::in_place};
145
146    // conv_id -> model name that currently serves its stream session, lets the resumable stream
147    // routes go straight to the owning child instead of polling every one. populated when
148    // proxy_request forwards a POST carrying an X-Conversation-Id. best effort: a stale entry just
149    // makes the child answer not found and the client recovers. owns its lock, one mutex per struct
150    struct conv_model_tracker {
151        // returns the ticket of this registration, 0 when nothing was registered. erasing or
152        // replacing the entry invalidates the ticket, which is how a stop cancels a request
153        // parked in the model load wait
154        uint64_t remember(const std::string & conv_id, const std::string & model) {
155            if (conv_id.empty() || model.empty()) {
156                return 0;
157            }
158            std::lock_guard<std::mutex> lock(mu);
159            uint64_t ticket = next_ticket++;
160            map[conv_id] = { model, ticket };
161            return ticket;
162        }
163
164        // false means a stop erased the entry or a newer request replaced it
165        bool alive(const std::string & conv_id, uint64_t ticket) {
166            std::lock_guard<std::mutex> lock(mu);
167            auto it = map.find(conv_id);
168            return it != map.end() && it->second.ticket == ticket;
169        }
170
171        std::optional<std::string> lookup(const std::string & conv_id) {
172            if (conv_id.empty()) {
173                return std::nullopt;
174            }
175            std::lock_guard<std::mutex> lock(mu);
176            auto it = map.find(conv_id);
177            if (it == map.end()) {
178                return std::nullopt;
179            }
180            return it->second.model;
181        }
182
183        void forget(const std::string & conv_id) {
184            if (conv_id.empty()) {
185                return;
186            }
187            std::lock_guard<std::mutex> lock(mu);
188            map.erase(conv_id);
189        }
190
191      private:
192        struct entry_t {
193            std::string model;
194            uint64_t    ticket;
195        };
196        std::mutex                               mu;
197        uint64_t                                 next_ticket = 1;
198        std::unordered_map<std::string, entry_t> map;
199    };
200
201    common_preset_context ctx_preset;
202
203    common_params base_params;
204    std::string bin_path;
205    std::vector<std::string> base_env;
206    common_preset base_preset; // base preset from llama-server CLI args
207
208    // queue of requests waiting for a models_max slot
209    std::unique_ptr<server_lru_sched> sched;
210
211    // if true, add some delay to simulate works (useful for testing)
212    bool debug_fake_timing = false;
213
214    void update_meta(const std::string & name, const server_model_meta & meta);
215
216    // unload least recently used models if the limit is reached
217    void unload_lru();
218
219    // not thread-safe, caller must hold mutex
220    void add_model(server_model_meta && meta);
221
222    // ask the monitor to stop a running instance; send_exit is false for a child that was already force-killed
223    // not thread-safe, caller must hold mutex
224    void request_stop(const std::string & name, bool send_exit = true);
225
226    // called by the monitor once a child exited and was reaped
227    void on_child_exit(const std::string & name, const std::shared_ptr<server_subproc> & proc, server_child_mode mode, int exit_code);
228
229    // notify SSE clients
230    void notify_sse(const std::string & event, const std::string & model_id, const json & data = nullptr);
231
232public:
233    // conv_id -> model tracker for the resumable stream routes, owns its lock
234    conv_model_tracker conv_models;
235
236    server_models(const common_params & params, int argc, char ** argv);
237    ~server_models();
238
239    server_response sse; // for real-time updates via SSE endpoint
240
241    // (re-)load the list of models from various sources and prepare the metadata mapping
242    // - if this is called the first time, simply populate the metadata
243    // - if this is called subsequently (e.g. when refreshing from disk):
244    //   - if a model is running but updated or removed from the source, it will be unloaded
245    //   - if a model is not running, it will be added or updated according to the source
246    void load_models();
247
248    // lazy-load startup_models, to be called after main() setup phase
249    void load_startup_models();
250
251    // check if a model instance exists (thread-safe)
252    bool has_model(const std::string & name);
253
254    // return a copy of model metadata (thread-safe)
255    std::optional<server_model_meta> get_meta(const std::string & name);
256
257    // return a copy of all model metadata (thread-safe)
258    std::vector<server_model_meta> get_all_meta();
259
260    struct load_options {
261        server_child_mode mode = SERVER_CHILD_MODE_NORMAL;
262        // used for spawning a downloading child process
263        std::optional<server_model_meta> custom_meta = std::nullopt;
264    };
265
266    // load and unload model instances
267    // these functions are thread-safe
268    void load(const std::string & name);
269    void load(const std::string & name, const load_options & opts);
270    void unload(const std::string & name);
271    void unload_all();
272
273    struct update_status_args {
274        server_model_status status;
275        int exit_code = 0; // only valid if status == UNLOADED
276        json loaded_info = nullptr;
277        json progress = nullptr;
278    };
279    // update the status of a model instance (thread-safe)
280    // also send SSE notification to /models/sse endpoint
281    void update_status(const std::string & name, const update_status_args & args);
282    void update_download_progress(const std::string & name, const common_download_progress & progress, bool done, bool ok = true);
283
284    // remove a cache model from disk and update the list (thread-safe)
285    // note: only cache models can be removed; returns false if the model doesn't exist or is not a cache model
286    bool remove(const std::string & name);
287
288    // wait until the model instance is fully loaded (thread-safe)
289    // note: predicate is called while holding the lock
290    // return when the model no longer in "loading" state
291    void wait(const std::string & name, std::function<bool(const server_model_meta &)> predicate);
292    void wait(std::unique_lock<std::mutex> & lk, const std::string & name, std::function<bool(const server_model_meta &)> predicate);
293
294    // ensure the model is in ready state (thread-safe)
295    // return false if model is ready
296    // otherwise, load the model and blocking wait until it's ready, then return true (meta may need to be refreshed)
297    // if models_max is reached, the request waits in a queue until a slot frees up
298    // throws if the load fails, or if should_stop fires while waiting
299    bool ensure_model_ready(const std::string & name, const std::function<bool()> & should_stop = nullptr);
300
301    // proxy an HTTP request to the model instance
302    server_http_res_ptr proxy_request(const server_http_req & req, const std::string & method, const std::string & name, bool update_last_used, bool detached = false);
303
304    // handle message sent from server_child::notify_to_router()
305    // raw input must starts with CMD_CHILD_TO_ROUTER_STATE, followed by a JSON string
306    // called from the monitor thread
307    // payload per state:
308    //     state = loading     -> payload = {} (TODO: add progress info)
309    //     state = ready       -> payload = model_info (json), or {} if wakeup from sleeping
310    //     state = sleeping    -> payload = {}
311    void handle_child_state(const std::string & name, const std::string & raw_input);
312
313private:
314    // one thread watching every child; keep last, the destructor joins the thread
315    std::unique_ptr<server_monitor> monitor;
316};
317
318struct server_child {
319    // serializes the notify_to_router writes
320    std::mutex mtx_stdout;
321    std::atomic<bool> is_finished_downloading = false; // set by run_download
322
323    // return true if the current process is a child server instance
324    bool is_child();
325    server_child_mode get_mode();
326    int run_download(common_params & params);
327
328    // register the shutdown_handler to be called by the router
329    // return the monitoring thread (to be joined by the caller)
330    std::thread setup(const std::function<void(int)> & shutdown_handler);
331
332    // notify router server for status changes (e.g. loading, downloading, sleeping, etc.)
333    // message will be handled by server_models::handle_child_state() on the router side
334    void notify_to_router(const std::string & state_name, const json & payload);
335};
336
337struct server_models_routes {
338    common_params params;
339    json ui_settings = json::object();     // Primary: new name
340    std::atomic<bool> stopping = false;    // for graceful disconnecting SSE clients during shutdown
341    server_models models;
342    server_models_routes(const common_params & params, int argc, char ** argv)
343            : params(params), models(params, argc, argv) {
344        const std::string & cfg = this->params.ui_config_json;
345        if (!cfg.empty()) {
346            try {
347                json json_settings = json::parse(cfg);
348                ui_settings = json_settings;
349            } catch (const std::exception & e) {
350                LOG_ERR("%s: failed to parse UI config: %s\n", __func__, e.what());
351                throw;
352            }
353        }
354        init_routes();
355    }
356
357    void init_routes();
358    // handlers using lambda function, so that they can capture `this` without `std::bind`
359    server_http_context::handler_t get_router_props;
360    server_http_context::handler_t proxy_get;
361    server_http_context::handler_t proxy_post;
362    server_http_context::handler_t get_router_models;
363    server_http_context::handler_t post_router_models_load;
364    server_http_context::handler_t post_router_models_unload;
365    // management API
366    server_http_context::handler_t get_router_models_sse;
367    server_http_context::handler_t post_router_models;
368    server_http_context::handler_t del_router_models;
369
370    // router side handlers for the resumable streaming routes. each resolves the child that owns
371    // a conversation through the conv_id -> model map, no probing or fan out
372    server_http_context::handler_t router_stream_get;
373    server_http_context::handler_t router_streams_lookup;
374    server_http_context::handler_t router_stream_delete;
375};
376
377/**
378 * A simple HTTP proxy that forwards requests to another server
379 * and relays the responses back.
380 */
381struct server_http_proxy : server_http_res {
382    std::function<void()> cleanup = nullptr;
383    server_http_proxy(const std::string & method,
384                      const std::string & scheme,
385                      const std::string & host,
386                      int port,
387                      const std::string & path,
388                      const std::map<std::string, std::string> & headers,
389                      const std::string & body,
390                      const std::map<std::string, uploaded_file> & files,
391                      const std::function<bool()> should_stop,
392                      int32_t timeout_read,
393                      int32_t timeout_write
394                      );
395    ~server_http_proxy() {
396        if (cleanup_pipes) {
397            cleanup_pipes();
398        }
399        if (cleanup) {
400            cleanup();
401        }
402    }
403private:
404    std::function<void()> cleanup_pipes = nullptr;
405    std::thread thread;
406    struct msg_t {
407        std::map<std::string, std::string> headers;
408        int status = 0;
409        std::string data;
410        std::string content_type;
411    };
412};