CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 3d agoView on Hugging Face
0likes1.1kdownloads
server-common.h652 linesDownload Raw Back to server
1#pragma once2 3#include "common.h"4#include "log.h"5#include "llama.h"6#include "chat.h"7#include "mtmd.h"8#include "mtmd-helper.h"9#include "subproc.h"10 11#include "json.h"12 13#include <atomic>14#include <chrono>15#include <condition_variable>16#include <cinttypes>17#include <cstdio>18#include <functional>19#include <mutex>20#include <queue>21#include <string>22#include <vector>23 24using json = common_json;25 26#define SLT_DBG(slot, fmt, ...) LOG_DBG("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__)27#define SLT_TRC(slot, fmt, ...) LOG_TRC("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__)28#define SLT_INF(slot, fmt, ...) LOG_INF("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__)29#define SLT_WRN(slot, fmt, ...) LOG_WRN("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__)30#define SLT_ERR(slot, fmt, ...) LOG_ERR("slot %12.*s: id %2d | task %d | " fmt, 12, __func__, (slot).id, ((slot).task ? (slot).task->id : -1), __VA_ARGS__)31#define SLT_CNT(slot, fmt, ...) LOG_CNT(""                                 fmt,                                                                __VA_ARGS__)32 33#define SRV_DBG(fmt, ...) LOG_DBG("srv  %12.*s: " fmt, 12, __func__, __VA_ARGS__)34#define SRV_TRC(fmt, ...) LOG_TRC("srv  %12.*s: " fmt, 12, __func__, __VA_ARGS__)35#define SRV_INF(fmt, ...) LOG_INF("srv  %12.*s: " fmt, 12, __func__, __VA_ARGS__)36#define SRV_WRN(fmt, ...) LOG_WRN("srv  %12.*s: " fmt, 12, __func__, __VA_ARGS__)37#define SRV_ERR(fmt, ...) LOG_ERR("srv  %12.*s: " fmt, 12, __func__, __VA_ARGS__)38#define SRV_CNT(fmt, ...) LOG_CNT(""              fmt,               __VA_ARGS__)39 40using raw_buffer = std::vector<uint8_t>;41 42template <typename T>43static T json_value(const json & body, const std::string & key, const T & default_value) {44    // Fallback null to default value45    if (body.contains(key) && !body.at(key).is_null()) {46        try {47            return body.at(key).get<T>();48        } catch (const common_json_error & err) {49            LOG_WRN("Wrong type supplied for parameter '%s', using default value: %s\n", key.c_str(), err.what());50            return default_value;51        }52    } else {53        return default_value;54    }55}56 57// https://community.openai.com/t/openai-chat-list-of-error-codes-and-types/357791/1158enum error_type {59    ERROR_TYPE_INVALID_REQUEST,60    ERROR_TYPE_AUTHENTICATION,61    ERROR_TYPE_SERVER,62    ERROR_TYPE_NOT_FOUND,63    ERROR_TYPE_PERMISSION,64    ERROR_TYPE_UNAVAILABLE, // custom error65    ERROR_TYPE_NOT_SUPPORTED, // custom error66    ERROR_TYPE_EXCEED_CONTEXT_SIZE, // custom error67};68 69// thin wrapper around common_grammar_trigger with (de)serialization functions70struct server_grammar_trigger {71    common_grammar_trigger value;72 73    server_grammar_trigger() = default;74    server_grammar_trigger(const common_grammar_trigger & value) : value(value) {}75    server_grammar_trigger(const json & in) {76        value.type = (common_grammar_trigger_type) in.at("type").get<int>();77        value.value = in.at("value").get<std::string>();78        if (value.type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) {79            value.token = (llama_token) in.at("token").get<int>();80        }81    }82 83    json to_json() const {84        json out {85            {"type", (int) value.type},86            {"value", value.value},87        };88        if (value.type == COMMON_GRAMMAR_TRIGGER_TYPE_TOKEN) {89            out["token"] = (int) value.token;90        }91        return out;92    }93};94 95json format_error_response(const std::string & message, const enum error_type type);96 97//98// random string / id99//100 101std::string random_string();102std::string gen_chatcmplid();103std::string gen_tool_call_id();104 105// get a random marker; note: each time the server restarts, the marker will be different106const char * get_media_marker();107 108//109// lora utils110//111 112// check whether the given lora set has only aloras activated (empty => false)113bool lora_all_alora(const std::vector<common_adapter_lora_info> & loras);114 115// if the two sets of loras are different, they require a cache clear unless the116// change is only from aloras to aloras.117bool lora_should_clear_cache(118        const std::vector<common_adapter_lora_info> & current,119        const std::vector<common_adapter_lora_info> & next);120 121std::map<int, float> parse_lora_request(const json & data);122 123bool are_lora_equal(124        const std::vector<common_adapter_lora_info> & l1,125        const std::vector<common_adapter_lora_info> & l2);126 127// get the ids of all enabled loras128std::vector<size_t> lora_get_enabled_ids(const std::vector<common_adapter_lora_info> & loras);129 130//131// server_tokens132//133 134/**135 * server_tokens is a helper to manage the input tokens and image for the server.136 * it is made this way to simplify the logic of KV cache management.137 */138struct server_tokens {139    bool has_mtmd = false;140 141private: // disallow accessing these members directly, risking out-of-sync142 143    // map a **start** index in tokens to the image chunk144    // note: the order need to be in-sync with tokens145    std::map<size_t, mtmd::input_chunk_ptr> map_idx_to_media;146 147    // list of tokens148    //   if the token is LLAMA_TOKEN_NULL, it indicates that this position is occupied by media chunk149    //   otherwise, it is a normal text token150    // note: a non-text chunk can occupy multiple tokens (aka memory cells) in the token list151    // note(2): for M-RoPE, an image can occupy different number of pos; do not assume 1-to-1 mapping tokens <-> pos152    llama_tokens tokens;153 154    // for ex. with input of 5 text tokens and 2 images (each image occupies 3 tokens and 2 pos):155    //      [0] [1] [2] [3] [4] [img0] [img0] [img0] [img1] [img1] [img1]156    // idx  0   1   2   3   4   5      6      7      8      9      10157    // pos  0   1   2   3   4   5      5      5      7      7      7158    // map_idx_to_media will contain: {5, img0}, {8, img1}159 160public:161    server_tokens() = default;162    ~server_tokens() = default;163 164    // Prevent copying165    // TODO: server_tokens should be copyable - remove this:166    server_tokens(const server_tokens&) = delete;167    server_tokens& operator=(const server_tokens&) = delete;168 169    // Allow moving (usually implicitly generated if members are movable)170    server_tokens(server_tokens&&) = default;171    server_tokens& operator=(server_tokens&&) = default;172 173    // Allow accessing elements using [] operator174    llama_token operator[](size_t index) { return tokens[index]; }175    const llama_token& operator[](size_t index) const { return tokens[index]; }176 177    server_tokens(mtmd::input_chunks & mtmd_chunks, bool has_mtmd);178    server_tokens(const llama_tokens & tokens, bool has_mtmd);179 180    // for debugging181    std::string str() const;182 183    // the next position after n_tokens. if n_tokens < 0, return the next position after all tokens.184    llama_pos pos_next(int64_t n_tokens = -1) const;185 186    // number of tokens with position < max_pos187    size_t size_up_to_pos(llama_pos max_pos) const;188 189    const mtmd::input_chunk_ptr & find_chunk(size_t idx) const;190 191    // find next media chunk after idx192    // returns a pair of pointer to the chunk (nullptr if not found) and its start index in tokens193    std::pair<const mtmd::input_chunk_ptr *, size_t> find_next_media_chunk(size_t idx) const;194 195    void push_back(llama_token tok);196 197    // will create a copy of the chunk if it contains non-text data198    void push_back(const mtmd_input_chunk * chunk);199 200    // same as push_back, but media chunks are stored as placeholders (no image/audio data)201    // only use this if the chunk will never be encoded again (e.g. it is already in the KV cache)202    void push_back_placeholder(const mtmd_input_chunk * chunk);203 204    // appends server tokens, updates the media map. copies media chunks.205    void push_back(server_tokens & tokens);206 207    // for compatibility with context shift and prompt truncation208    void insert(const llama_tokens & inp_tokens);209 210    // for compatibility with speculative decoding, ctx shift211    const llama_tokens & get_tokens() const;212 213    llama_tokens get_text_tokens() const;214 215    std::vector<char> serialize() const;216    static server_tokens deserialize(const llama_tokens & packed, bool has_mtmd);217 218    // for compatibility with speculative decoding219    void set_token(llama_pos pos, llama_token id);220 221    size_t size() const { return tokens.size(); }222 223    bool empty() const { return tokens.empty(); }224 225    void clear() {226        map_idx_to_media.clear();227        tokens.clear();228    }229 230    void keep_first(size_t n);231 232    std::string detokenize(const llama_context * ctx, bool special) const;233 234    size_t get_common_prefix(const server_tokens & b) const;235 236    // split the tokens into message spans, skipping over media chunks237    common_chat_msg_spans find_message_spans(const common_chat_msg_delimiters & delims) const;238 239    // check text token IDs and the mapping between media chunks and token ranges240    bool validate(const struct llama_context * ctx) const;241 242    server_tokens clone() const;243};244 245 246//247// tokenizer and input processing utils248//249 250bool json_is_array_of_numbers(const json & data);251 252// is array having BOTH numbers & strings?253bool json_is_array_of_mixed_numbers_strings(const json & data);254 255// does array have any individual integers/tokens?256bool json_is_array_and_contains_numbers(const json & data);257 258// get value by path(key1 / key2)259json json_get_nested_values(const std::vector<std::string> & paths, const json & js);260 261/**262 * this handles 2 cases:263 * - only string, example: "string"264 * - mixed string and tokens, example: [12, 34, "string", 56, 78]265 */266llama_tokens tokenize_mixed(const llama_vocab * vocab, const json & json_prompt, bool add_special, bool parse_special);267 268// return the last index of character that can form a valid string269// if the last character is potentially cut in half, return the index before the cut270// if validate_utf8(text) == text.size(), then the whole text is valid utf8271size_t validate_utf8(const std::string& text);272 273// process mtmd prompt, return the server_tokens containing both text tokens and media chunks274// if is_placeholder is true, the media chunk will be treated as placeholder for counting tokens; the output tokens are not usable for actual inference (e.g. for submitting a task to server_queue)275server_tokens process_mtmd_prompt(276                                        mtmd_context * mctx,277                                        const std::string & prompt,278                                        const std::vector<raw_buffer> & files,279                                        const mtmd_helper_init_opt & init_opt,280                                        bool is_placeholder = false);281 282/**283 * break the input "prompt" object into multiple prompt if needed, then tokenize them284 * this supports these cases:285 * - "prompt": "string"286 * - "prompt": [12, 34, 56]287 * - "prompt": [12, 34, "string", 56, 78]288 * - "prompt": { "prompt_string": "string", "multimodal_data": [ "base64" ] }289 * and multiple prompts (multi-tasks):290 * - "prompt": ["string1", "string2"]291 * - "prompt": ["string1", [12, 34, 56]]292 * - "prompt": [[12, 34, 56], [78, 90, 12]]293 * - "prompt": [[12, 34, "string", 56, 78], [12, 34, 56], { "prompt_string": "string", "multimodal_data": [ "base64" ]}]294 */295std::vector<server_tokens> tokenize_input_prompts(296                                        const llama_vocab * vocab,297                                        mtmd_context * mctx,298                                        const json & json_prompt,299                                        bool add_special,300                                        bool parse_special,301                                        const mtmd_helper_init_opt & init_opt);302 303//304// OAI utils305//306 307// global server parameters for chat formatting / parsing308struct server_chat_params {309    bool use_jinja;310    bool prefill_assistant;311    common_reasoning_format reasoning_format;312    std::map<std::string, std::string> chat_template_kwargs; // mapping key --> json value313    common_chat_templates_ptr tmpls;314    bool allow_image;315    bool allow_audio;316    bool allow_video;317    bool enable_thinking = true;318    int  reasoning_budget = -1;319    std::string reasoning_budget_message;320    std::string media_path;321    bool force_pure_content = false;322};323 324// used by /completions endpoint325json oaicompat_completion_params_parse(const json & body);326 327// used by /chat/completions endpoint328json oaicompat_chat_params_parse(329    json & body, /* openai api json semantics */330    const server_chat_params & opt,331    std::vector<raw_buffer> & out_files);332 333// TODO: move it to server-task.cpp334json format_embeddings_response_oaicompat(335    const json & request,336    const std::string & model_name,337    const json & embeddings,338    bool use_base64 = false);339 340// TODO: move it to server-task.cpp341json format_response_rerank(342        const json & request,343        const std::string & model_name,344        const json & ranks,345        bool is_tei_format,346        std::vector<std::string> & texts,347        int top_n);348 349//350// stats and metrics351//352 353// shared between server_slot and server_task_result_*354struct server_slot_stats {355    uint64_t n_prompt_cached    = 0;356    uint64_t n_prompt_processed = 0;357    uint64_t n_gen              = 0;358 359    // speculative decoding stats360    // note: the per-position breakdown lives in server_slot, it is not needed in a task result361    uint64_t n_draft_tokens      = 0;362    uint64_t n_draft_accepted    = 0;363    uint64_t n_draft_verif_steps = 0;364 365    // these are absolute timestamps (in us)366    // note: must be signed - they are subtracted before the later ones are set367    int64_t t_start       = 0;368    int64_t t_prompt_last = 0;369    int64_t t_gen_last    = 0;370 371    // can only move one direction: start -> prompt -> gen372    void update_prompt_start() {373        GGML_ASSERT(t_start == 0);374        t_start = ggml_time_us();375    }376    void set_prompt_last(int64_t t_us) {377        GGML_ASSERT(t_start > 0);378        t_prompt_last = t_us;379    }380    void update_prompt_last() {381        set_prompt_last(ggml_time_us());382    }383    void update_gen_last() {384        GGML_ASSERT(t_prompt_last > 0);385        t_gen_last = ggml_time_us();386    }387 388    // these are time durations389    int64_t t_elapsed_us() const {390        return ggml_time_us() - t_start;391    }392    double t_prompt_ms() const {393        if (t_prompt_last == 0) {394            return 0.0; // the prompt is not processed yet395        }396        return (t_prompt_last - t_start) / 1000.0;397    }398    int64_t t_gen_us() const {399        if (t_gen_last == 0) {400            return 0; // the generation is not started yet401        }402        // clamp to 1 us, the first token can land in the same us as t_prompt_last403        return std::max<int64_t>(1, t_gen_last - t_prompt_last);404    }405    double t_gen_ms() const {406        return t_gen_us() / 1000.0;407    }408 409    // number of decode steps spent on generation410    // the first token is free, it comes from the logits of the last prompt batch411    uint64_t n_gen_steps() const {412        return n_gen > 0 ? n_gen - 1 : 0;413    }414 415    // other derived metrics416    // note: all of them return 0.0 if the divisor is not known yet417    double t_prompt_per_token_ms() const {418        return n_prompt_processed > 0 ? t_prompt_ms() / n_prompt_processed : 0.0;419    }420    double t_gen_per_token_ms() const {421        return n_gen_steps() > 0 ? t_gen_ms() / n_gen_steps() : 0.0;422    }423    double n_prompt_tps() const {424        const double t_ms = t_prompt_ms();425        return t_ms > 0.0 ? 1e3 / t_ms * n_prompt_processed : 0.0;426    }427    double n_gen_tps() const {428        const double t_ms = t_gen_ms();429        return t_ms > 0.0 ? 1e3 / t_ms * n_gen_steps() : 0.0;430    }431 432    // false if the slot never started, i.e. the task result carries no stats433    bool is_set() const {434        return t_start > 0;435    }436 437    json to_json() const;438};439 440// shared between server_context_impl and server_task_result_*441// unlike server_slot_stats, server_metrics is server-global and cumulative, not tied to a slot442struct server_metrics {443    int64_t t_start = 0;444 445    struct bucket {446        uint64_t count = 0; // number of tokens447        uint64_t steps = 0; // number of decode steps,448                            // this excludes first generated token (logits from prompt batch)449        uint64_t time  = 0; // in microseconds450 451        // the rate uses the decode steps, so that "free" tokens do not inflate it452        double n_per_second() const {453            return time > 0 ? (double) steps / (double) time * 1e6 : 0.0;454        }455 456        void add(uint64_t n, uint64_t n_steps, uint64_t t_us) {457            count += n;458            steps += n_steps;459            time  += t_us;460        }461    };462 463    // these are reset by reset_bucket(), only the rate is read from them464    bucket prompt_bucket;465    bucket predict_bucket;466 467    // metrics below are cumulative since the server started468    bucket prompt; // only processed tokens, cached ones are counted separately below469    bucket predict;470 471    // tokens reused from the cache need no decode, so they only have a count472    uint64_t n_prompt_cached = 0;473 474    uint64_t n_tokens_max = 0;475 476    uint64_t n_decode     = 0;477    uint64_t n_busy_slots = 0;478 479    uint64_t n_draft_tokens      = 0; // Total draft tokens generated480    uint64_t n_draft_accepted    = 0; // Draft tokens actually accepted481    uint64_t n_draft_verif_steps = 0; // Total draft token verification steps by the target model482    std::vector<uint64_t> n_accepted_per_pos; // Accepted tokens per draft position483 484    void init() {485        t_start = ggml_time_us();486    }487 488    void reset_bucket() {489        prompt_bucket  = {};490        predict_bucket = {};491    }492 493    void add_prompt(uint64_t n_tokens, uint64_t t_us) {494        prompt       .add(n_tokens, n_tokens, t_us);495        prompt_bucket.add(n_tokens, n_tokens, t_us);496    }497 498    void add_prompt_cached(uint64_t n_tokens) {499        n_prompt_cached += n_tokens;500    }501};502 503//504// other utils505//506 507std::vector<llama_token_data> get_token_probabilities(llama_context * ctx, int idx, size_t n_top);508 509std::string safe_json_to_str(const json & data);510 511std::string tokens_to_str(llama_context * ctx, const llama_tokens & tokens);512std::string tokens_to_str(const llama_vocab * vocab, const llama_tokens & tokens);513 514// format incomplete utf-8 multibyte character for output515std::string tokens_to_output_formatted_string(const llama_context * ctx, const llama_token token);516 517// format server-sent event (SSE), return the formatted string to send518// note: if data is a json array, it will be sent as multiple events, one per item519std::string format_oai_sse(const json & data);520 521std::string format_oai_resp_sse(const json & data);522 523// format Anthropic-style SSE with event types524std::string format_anthropic_sse(const json & data);525 526bool is_valid_utf8(const std::string & str);527 528//529// formatting output responses530// TODO: move these to server-task.cpp531//532 533llama_tokens format_prompt_infill(534        const llama_vocab * vocab,535        const json & input_prefix,536        const json & input_suffix,537        const json & input_extra,538        const int n_batch,539        const int n_predict,540        const int n_ctx,541        const bool spm_infill,542        const llama_tokens & tokens_prompt);543 544// format rerank task: [BOS]query[EOS][SEP]doc[EOS].545server_tokens format_prompt_rerank(546        const struct llama_model * model,547        const struct llama_vocab * vocab,548        mtmd_context * mctx,549        const std::string & query,550        const std::string & doc,551        const mtmd_helper_init_opt & init_opt);552 553// simple implementation of a pipe554// used for streaming data between threads555template<typename T>556struct server_pipe {557    std::mutex mutex;558    std::condition_variable cv;559    std::queue<T> queue;560    std::atomic<bool> writer_closed{false};561    std::atomic<bool> reader_closed{false};562 563    // 0 = unbounded (default)564    // > 0, write() drops the oldest item once the queue is full565    size_t max_size = 0;566 567    void close_write() {568        writer_closed.store(true, std::memory_order_relaxed);569        cv.notify_all();570    }571 572    void close_read() {573        reader_closed.store(true, std::memory_order_relaxed);574        cv.notify_all();575    }576 577    // close_on_stop = true: should_stop means the reader is gone for good, so the writer is told the pipe is broken.578    // close_on_stop = false: should_stop is a per-read deadline and further reads still come, so the pipe stays usable.579    bool read(T & output, const std::function<bool()> & should_stop, bool close_on_stop = true) {580        std::unique_lock<std::mutex> lk(mutex);581        constexpr auto poll_interval = std::chrono::milliseconds(500);582        while (true) {583            if (!queue.empty()) {584                output = std::move(queue.front());585                queue.pop();586                return true;587            }588            if (writer_closed.load()) {589                return false; // clean EOF590            }591            if (should_stop && should_stop()) { // a null should_stop means "never stop"592                if (close_on_stop) {593                    close_read(); // signal broken pipe to writer594                }595                return false; // cancelled / deadline reached596            }597            cv.wait_for(lk, poll_interval);598        }599    }600 601    bool write(T && data) {602        std::lock_guard<std::mutex> lk(mutex);603        if (reader_closed.load()) {604            return false; // broken pipe605        }606        if (max_size > 0) {607            while (queue.size() >= max_size) {608                queue.pop(); // drop oldest to stay bounded609            }610        }611        queue.push(std::move(data));612        cv.notify_one();613        return true;614    }615};616 617// wrapper around common_subproc to manage a child server process618// mainly used by router mode619struct server_subproc {620    common_subproc sproc;621    std::atomic<bool> stopped{false}; // set by the monitor once the process exited and was reaped622 623    bool is_alive() { return sproc.alive(); }624    void terminate() { sproc.terminate(); }625    int  join() { return sproc.join(); }626 627    // true if the child's combined stdout/stderr pipe is available (call after create())628    bool has_output();629 630    // non-blocking read631    // returns the number of bytes read, 0 when nothing is available, -1 when the pipe is closed or broken632    int read_output(char * buf, size_t len);633 634    // wait until one of a set of children has output, wake() is called, or a timeout passes635    struct waiter {636        waiter();637        ~waiter();638 639        // thread-safe; on Windows this is a no-op, wait() returns within 50 ms anyway640        void wake();641 642        // timeout_ms < 0 waits until data or wake(); ready[i] is set for each child with data (or a broken pipe)643        void wait(const std::vector<server_subproc *> & procs, std::vector<bool> & ready, int64_t timeout_ms);644 645    private:646        intptr_t wake_fd[2] = { -1, -1 }; // POSIX self-pipe647    };648 649private:650    intptr_t out_handle = -1; // fd on POSIX, HANDLE on Windows; taken lazily from sproc651};652