char-fork/llama.cpp

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

Xuan-Son Nguyenserver: refactor sleep handling, allow access /metrics during sleep (#27376)947fd9bb2

char/aliceai-t5-moe
9.1 KiB244 linesraw
1#pragma once
2
3#include "server-task.h"
4
5#include <condition_variable>
6#include <deque>
7#include <exception>
8#include <mutex>
9#include <thread>
10#include <vector>
11#include <unordered_set>
12
13// struct for managing server tasks
14// in most cases, use server_response_reader to post new tasks and retrieve results
15struct server_queue {
16private:
17    int id = 0;
18    bool running  = false;
19    bool sleeping = false;
20    bool req_stop_sleeping = false;
21    int64_t time_last_task = 0;
22
23    // queues
24    std::deque<server_task> queue_tasks;
25    std::deque<server_task> queue_tasks_deferred;
26    // tasks declined while yielding, put back in queue_tasks once the yield is done
27    // note: kept as a member so that cleanup_pending_task() can also reach them
28    std::deque<server_task> queue_tasks_unhandled;
29
30    std::mutex mutex_tasks;
31    std::condition_variable condition_tasks;
32
33    // used by yield_to_queue, all fields are guarded by mutex_tasks
34    struct worker_t {
35        std::thread             thread;
36        std::condition_variable cv;        // the worker sleeps on this until a yield starts
37        std::exception_ptr      exception; // exception thrown while processing tasks, if any
38        bool stop     = false;
39        bool busy     = false; // set by yield_to_queue(), cleared by the worker once it is done processing tasks
40        bool yielding = false; // work() is still running on the start_loop() thread
41    };
42    worker_t worker;
43
44    // callback functions
45    std::function<bool(server_task &&, bool)> callback_new_task;
46    std::function<void(void)>                 callback_update_slots;
47    std::vector<std::function<void(bool)>>    callback_sleeping_state;
48
49public:
50    ~server_queue() { worker_stop(); }
51
52    // Add a new task to the end of the queue
53    int post(server_task && task, bool front = false);
54
55    // multi-task version of post()
56    int post(std::vector<server_task> && tasks, bool front = false);
57
58    // Add a new task, but defer until one slot is available
59    void defer(server_task && task);
60
61    // Get the next id for creating a new task
62    int get_new_id();
63
64    // Call when the state of one slot is changed, it will move one task from deferred to main queue
65    // prioritize tasks that use the specified slot (otherwise, pop the first deferred task)
66    void pop_deferred_task(int id_slot);
67
68    // if sleeping, request exiting sleep state and wait until it is done
69    // returns immediately if not sleeping
70    void wait_until_no_sleep();
71
72    bool is_sleeping() {
73        std::unique_lock<std::mutex> lock(mutex_tasks);
74        return sleeping;
75    }
76
77    // end the start_loop routine
78    void terminate();
79
80    /**
81     * Main loop consists of these steps:
82     * - Wait until a new task arrives
83     * - Process the task (i.e. maybe copy data into slot)
84     * - Check if multitask is finished
85     * - Update all slots
86     *
87     * Sleeping procedure (disabled if idle_sleep_ms < 0):
88     * - If there is no task after idle_sleep_ms, enter sleeping state
89     *   note: metrics tasks are processed as usual, but do not reset the idle timer
90     * - Call callback_sleeping_state(true)
91     * - Wait until req_stop_sleeping is set to true
92     * - Call callback_sleeping_state(false)
93     * - Exit sleeping state
94     */
95    void start_loop(int64_t idle_sleep_ms = -1);
96
97    // while waiting for work() to finish, run process_new_tasks on the worker thread
98    // returns once work() is done (may throw exceptions)
99    // must be called from start_loop() thread (ideally inside callback_update_slots)
100    // use case: return metrics while encode/decode is running
101    // ref: https://github.com/ggml-org/llama.cpp/pull/27041
102    //
103    // tasks declined by callback_new_task are put back in the queue once this returns
104    void yield_to_queue(std::function<void()> && work);
105
106    // for metrics
107    size_t queue_tasks_deferred_size() {
108        std::unique_lock<std::mutex> lock(mutex_tasks);
109        return queue_tasks_deferred.size();
110    }
111
112    //
113    // Functions below are not thread-safe, must only be used before start_loop() is called
114    //
115
116    // Register function to process a new task
117    // the second argument tells whether the queue is currently yielding (see yield_to_queue)
118    // only then may the callback return false to decline the task, and it must leave it
119    // untouched, so that it can be put back in the queue later
120    // note: while yielding, the callback runs on worker thread, not main thread
121    void on_new_task(std::function<bool(server_task &&, bool)> callback) {
122        callback_new_task = std::move(callback);
123    }
124
125    // Register the function to be called when all slots data is ready to be processed
126    void on_update_slots(std::function<void(void)> callback) {
127        callback_update_slots = std::move(callback);
128    }
129
130    // Register callback for sleeping state change; multiple callbacks are allowed
131    // for example: register order cb0, cb1, cb2
132    // entering sleep: queue.sleeping = true --> cb0(true) --> cb1(true) --> cb2(true)
133    // leaving sleep: cb2(false) --> cb1(false) --> cb0(false) --> queue.sleeping = false
134    // note: caller will hold mutex_tasks while calling the callbacks
135    void on_sleeping_state(std::function<void(bool)> callback) {
136        callback_sleeping_state.push_back(std::move(callback));
137    }
138
139private:
140    void cleanup_pending_task(int id_target);
141
142    // process all pending tasks in the queue
143    // returns true if the queue is terminated, false if there is no more task to process
144    // while yielding, declined tasks are moved to queue_tasks_unhandled
145    bool process_new_tasks(bool is_yielding);
146
147    // for worker_t
148    void worker_loop();
149    void worker_stop();
150};
151
152// struct for managing server responses
153// in most cases, use server_response_reader to retrieve results
154struct server_response {
155private:
156    bool running = true;
157
158    // for keeping track of all tasks waiting for the result
159    std::unordered_set<int> waiting_task_ids;
160
161    // the main result queue (using ptr for polymorphism)
162    std::vector<server_task_result_ptr> queue_results;
163
164    std::mutex mutex_results;
165    std::condition_variable condition_results;
166
167public:
168    // add the id_task to the list of tasks waiting for response
169    void add_waiting_task_id(int id_task);
170
171    void add_waiting_task_ids(const std::unordered_set<int> & id_tasks);
172
173    // when the request is finished, we can remove task associated with it
174    void remove_waiting_task_id(int id_task);
175
176    // remove multiple tasks from waiting list
177    void remove_waiting_task_ids(const std::unordered_set<int> & id_tasks);
178
179    // This function blocks the thread until there is a response for one of the id_tasks
180    server_task_result_ptr recv(const std::unordered_set<int> & id_tasks);
181
182    // same as recv(), but have timeout in seconds
183    // if timeout is reached, nullptr is returned
184    server_task_result_ptr recv_with_timeout(const std::unordered_set<int> & id_tasks, int timeout);
185
186    // single-task version of recv()
187    server_task_result_ptr recv(int id_task);
188
189    // Send a new result to a waiting id_task
190    void send(server_task_result_ptr && result);
191
192    // broadcast a new result to all waiting tasks
193    // (used by router mode)
194    void broadcast(server_task_result_ptr && result);
195
196    // terminate the waiting loop
197    void terminate();
198};
199
200// RAII wrapper to make working with server_queue and server_response easier
201// it provides a generator-like API for server responses
202// support pooling connection state and aggregating multiple results
203struct server_response_reader {
204    std::unordered_set<int> id_tasks;
205    server_queue & queue_tasks;
206    server_response & queue_results;
207    size_t received_count = 0;
208    bool cancelled = false;
209    int polling_interval_seconds;
210
211    // tracking generation state and partial tool calls
212    // only used by streaming completions
213    std::vector<task_result_state> states;
214
215    // should_stop function will be called each polling_interval_seconds
216    server_response_reader(server_queue & queue_tasks, server_response & queue_results, int polling_interval_seconds)
217        : queue_tasks(queue_tasks), queue_results(queue_results), polling_interval_seconds(polling_interval_seconds) {}
218    ~server_response_reader() {
219        stop();
220    }
221
222    int get_new_id() {
223        return queue_tasks.get_new_id();
224    }
225
226    // if front = true, the task will be posted to the front of the queue (high priority)
227    void post_task(server_task && task, bool front = false);
228    void post_tasks(std::vector<server_task> && tasks, bool front = false);
229    bool has_next() const;
230
231    // return nullptr if should_stop() is true before receiving a result
232    // note: if one error is received, it will stop further processing and return error result
233    server_task_result_ptr next(const std::function<bool()> & should_stop);
234
235    struct batch_response {
236        bool is_terminated = false; // if true, indicates that processing was stopped before all results were received
237        std::vector<server_task_result_ptr> results;
238        server_task_result_ptr error; // nullptr if no error
239    };
240    // aggregate multiple results
241    batch_response wait_for_all(const std::function<bool()> & should_stop);
242
243    void stop();
244};