CoolFace
Datasetpublic

echodict/llama.cpp

version https://git-lfs.github.com/spec/v1 oid sha256:cfc44b7ba25614df70e6b65e3341cae0310163bd32fd31a6b928a542df433faf size 30786

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes773downloads
mtmd.cpp1438 linesDownload Raw Back to mtmd
1#include "clip.h"2#include "clip-impl.h"3#include "mtmd.h"4#include "mtmd-audio.h"5#include "mtmd-image.h"6#include "debug/mtmd-debug.h"7 8#include "llama.h"9 10// fix problem with std::min and std::max11#if defined(_WIN32)12#define WIN32_LEAN_AND_MEAN13#ifndef NOMINMAX14#   define NOMINMAX15#endif16#include <windows.h>17#endif18 19#include <algorithm>20#include <cerrno>21#include <cstdio>22#include <cstdlib>23#include <cstring>24#include <vector>25 26// represents raw image data, layout is RGBRGBRGB...27// length of data must be nx * ny * 328struct mtmd_bitmap {29    uint32_t nx;30    uint32_t ny;31    std::vector<unsigned char> data;32    std::string id; // optional user-defined id, for ex: can be set to image hash, useful for KV cache tracking33    bool is_audio = false; // true if the bitmap is audio34};35 36// position indexing for decoder model37enum mtmd_pos_type {38    MTMD_POS_TYPE_NORMAL, // number of positions equals to number of tokens39    MTMD_POS_TYPE_MROPE, // qwen-vl mrope style, each image takes max(t,h,w) position indexes40};41 42struct mtmd_image_tokens {43    uint32_t nx; // number of tokens in x direction44    uint32_t ny; // number of tokens in y direction45    mtmd_pos_type pos = MTMD_POS_TYPE_NORMAL;46    uint32_t n_tokens() const { return nx * ny; }47    clip_image_f32_batch batch_f32; // preprocessed image patches48    std::string id; // optional user-defined ID, useful for KV cache tracking49 50    mtmd_image_tokens clone() {51        return mtmd_image_tokens{52            nx,53            ny,54            pos,55            batch_f32.clone(),56            id57        };58    }59};60using mtmd_image_tokens_ptr = std::unique_ptr<mtmd_image_tokens>;61 62struct mtmd_audio_tokens {63    uint32_t n_tokens; // number of tokens64    clip_image_f32_batch batch_f32; // preprocessed image patches65    std::string id; // optional user-defined ID, useful for KV cache tracking66 67    mtmd_audio_tokens clone() {68        return mtmd_audio_tokens{69            n_tokens,70            batch_f32.clone(),71            id72        };73    }74};75using mtmd_audio_tokens_ptr = std::unique_ptr<mtmd_audio_tokens>;76 77struct mtmd_input_chunk {78    mtmd_input_chunk_type type;79    std::vector<llama_token> tokens_text;80    mtmd_image_tokens_ptr tokens_image;81    mtmd_audio_tokens_ptr tokens_audio;82};83 84struct mtmd_input_chunks {85    std::vector<mtmd_input_chunk> entries;86};87 88// slice template, used by some llava-uhd models to correctly place the special tokens around image embeddings89// models not having it (llava-1.6) will process embeddings without any special tokens in-between90enum mtmd_slice_tmpl {91    MTMD_SLICE_TMPL_NONE,92    MTMD_SLICE_TMPL_MINICPMV_2_5,93    MTMD_SLICE_TMPL_MINICPMV_2_6,94    MTMD_SLICE_TMPL_LLAMA4,95    MTMD_SLICE_TMPL_IDEFICS3,96    MTMD_SLICE_TMPL_LFM2,97    MTMD_SLICE_TMPL_STEP3VL,98};99 100const char * mtmd_default_marker() {101    return "<__media__>";102}103 104static clip_flash_attn_type mtmd_get_clip_flash_attn_type(enum llama_flash_attn_type flash_attn_type) {105    switch (flash_attn_type) {106        case LLAMA_FLASH_ATTN_TYPE_AUTO:     return CLIP_FLASH_ATTN_TYPE_AUTO;107        case LLAMA_FLASH_ATTN_TYPE_DISABLED: return CLIP_FLASH_ATTN_TYPE_DISABLED;108        case LLAMA_FLASH_ATTN_TYPE_ENABLED:  return CLIP_FLASH_ATTN_TYPE_ENABLED;109    }110    return CLIP_FLASH_ATTN_TYPE_AUTO;111}112 113mtmd_context_params mtmd_context_params_default() {114    mtmd_context_params params {115        /* use_gpu           */ true,116        /* print_timings     */ true,117        /* n_threads         */ 4,118        /* image_marker      */ nullptr,119        /* media_marker      */ mtmd_default_marker(),120        /* flash_attn_type   */ LLAMA_FLASH_ATTN_TYPE_AUTO,121        /* warmup            */ true,122        /* image_min_tokens  */ -1,123        /* image_max_tokens  */ -1,124        /* cb_eval           */ nullptr,125        /* cb_eval_user_data */ nullptr,126    };127    return params;128}129 130struct mtmd_context {131    struct clip_ctx * ctx_v; // vision132    struct clip_ctx * ctx_a; // audio133    const struct llama_model * text_model;134    std::vector<float> image_embd_v; // image embedding vector135 136    bool print_timings;137    int n_threads;138    std::string media_marker;139    const int n_embd_text;140    mtmd_pos_type pos_type;141 142    // these are not token, but strings used to mark the beginning and end of image/audio embeddings143    std::string img_beg;144    std::string img_end;145    std::string aud_beg;146    std::string aud_end;147 148    // for llava-uhd style models, we need special tokens in-between slices149    // minicpmv calls them "slices", llama 4 calls them "tiles"150    mtmd_slice_tmpl slice_tmpl = MTMD_SLICE_TMPL_NONE;151    std::vector<llama_token> tok_ov_img_start;  // overview image152    std::vector<llama_token> tok_ov_img_end;    // overview image153    std::vector<llama_token> tok_slices_start;  // start of all slices154    std::vector<llama_token> tok_slices_end;    // end of all slices155    std::vector<llama_token> tok_sli_img_start; // single slice start156    std::vector<llama_token> tok_sli_img_end;   // single slice end157    std::vector<llama_token> tok_sli_img_mid;   // between 2 slices158    std::vector<llama_token> tok_row_end;       // end of row159    bool tok_row_end_trail = false;160    bool ov_img_first      = false;161 162    // string template for slice image delimiters with row/col (idefics3)163    std::string sli_img_start_tmpl;164 165    std::unique_ptr<mtmd_audio_preprocessor> audio_preproc;166    std::unique_ptr<mtmd_image_preprocessor> image_preproc;167 168    // TODO @ngxson : add timings169 170    mtmd_context(const char * mmproj_fname,171                   const llama_model * text_model,172                   const mtmd_context_params & ctx_params) :173        text_model   (text_model),174        print_timings(ctx_params.print_timings),175        n_threads    (ctx_params.n_threads),176        media_marker (ctx_params.media_marker),177        n_embd_text  (llama_model_n_embd_inp(text_model))178    {179        if (ctx_params.image_marker != nullptr) {180            throw std::runtime_error("custom image_marker is not supported anymore, use media_marker instead");181        }182 183        if (media_marker.empty()) {184            throw std::runtime_error("media_marker must not be empty");185        }186 187        auto decoder_rope_type = llama_model_rope_type(text_model);188        switch (decoder_rope_type) {189            case LLAMA_ROPE_TYPE_NORM:190            case LLAMA_ROPE_TYPE_NEOX:191                {192                    pos_type = MTMD_POS_TYPE_NORMAL;193                } break;194            case LLAMA_ROPE_TYPE_MROPE:195            case LLAMA_ROPE_TYPE_IMROPE:196                {197                    pos_type = MTMD_POS_TYPE_MROPE;198                } break;199            default:200                throw std::runtime_error(string_format("unsupported decoder rope type: %d\n", decoder_rope_type));201        }202 203        clip_context_params ctx_clip_params {204            /* use_gpu           */ ctx_params.use_gpu,205            /* flash_attn_type   */ mtmd_get_clip_flash_attn_type(ctx_params.flash_attn_type),206            /* image_min_tokens  */ ctx_params.image_min_tokens,207            /* image_max_tokens  */ ctx_params.image_max_tokens,208            /* warmup            */ ctx_params.warmup,209            /* cb_eval           */ ctx_params.cb_eval,210            /* cb_eval_user_data */ ctx_params.cb_eval_user_data,211        };212 213        auto res = clip_init(mmproj_fname, ctx_clip_params);214        ctx_v = res.ctx_v;215        ctx_a = res.ctx_a;216        if (!ctx_v && !ctx_a) {217            throw std::runtime_error(string_format("Failed to load CLIP model from %s\n", mmproj_fname));218        }219 220        // if both vision and audio mmproj are present, we need to validate their n_embd221        if (ctx_v && ctx_a) {222            int n_embd_v = clip_n_mmproj_embd(ctx_v);223            int n_embd_a = clip_n_mmproj_embd(ctx_a);224            if (n_embd_v != n_embd_a) {225                throw std::runtime_error(string_format(226                    "mismatch between vision and audio mmproj (n_embd_v = %d, n_embd_a = %d)\n",227                    n_embd_v, n_embd_a));228            }229        }230 231        // since we already validate n_embd of vision and audio mmproj,232        // we can safely assume that they are the same233        int n_embd_clip = clip_n_mmproj_embd(ctx_v ? ctx_v : ctx_a);234        if (n_embd_text != n_embd_clip) {235            throw std::runtime_error(string_format(236                "mismatch between text model (n_embd = %d) and mmproj (n_embd = %d)\n"237                "hint: you may be using wrong mmproj\n",238                n_embd_text, n_embd_clip));239        }240        if (ctx_v) {241            init_vision();242        }243        if (ctx_a) {244            init_audio();245        }246    }247 248    void init_vision() {249        GGML_ASSERT(ctx_v != nullptr);250        image_preproc.reset();251 252        projector_type proj = clip_get_projector_type(ctx_v);253 254        switch (proj) {255            case PROJECTOR_TYPE_MLP:256            case PROJECTOR_TYPE_MLP_NORM:257            case PROJECTOR_TYPE_LDP:258            case PROJECTOR_TYPE_LDPV2:259            case PROJECTOR_TYPE_COGVLM:260            case PROJECTOR_TYPE_JANUS_PRO:261            case PROJECTOR_TYPE_GLM_EDGE:262                {263                    bool has_pinpoints = !clip_get_hparams(ctx_v)->image_res_candidates.empty();264                    if (has_pinpoints) {265                        image_preproc = std::make_unique<mtmd_image_preprocessor_llava_uhd>(ctx_v);266                    } else {267                        image_preproc = std::make_unique<mtmd_image_preprocessor_fixed_size>(ctx_v);268                    }269                } break;270            case PROJECTOR_TYPE_MINICPMV:271                {272                    int minicpmv_version = clip_is_minicpmv(ctx_v);273                    if (minicpmv_version == 2) {274                        // minicpmv 2.5 format:275                        // <image> (overview) </image><slice><image> (slice) </image><image> (slice) </image>\n ... </slice>276                        slice_tmpl        = MTMD_SLICE_TMPL_MINICPMV_2_5;277                        tok_ov_img_start  = {lookup_token("<image>")};278                        tok_ov_img_end    = {lookup_token("</image>")};279                        tok_slices_start  = {lookup_token("<slice>")};280                        tok_slices_end    = {lookup_token("</slice>")};281                        tok_sli_img_start = tok_ov_img_start;282                        tok_sli_img_end   = tok_ov_img_end;283                        tok_row_end       = {lookup_token("\n")};284                        tok_row_end_trail = false; // no trailing end-of-row token285                        ov_img_first      = true;286                    } else if (minicpmv_version == 3 || minicpmv_version == 4 || minicpmv_version == 5 || minicpmv_version == 6 || minicpmv_version == 100045) {287                        // minicpmv 2.6 format:288                        // <image> (overview) </image><slice> (slice) </slice><slice> (slice) </slice>\n ...289                        slice_tmpl        = MTMD_SLICE_TMPL_MINICPMV_2_6;290                        tok_ov_img_start  = {lookup_token("<image>")};291                        tok_ov_img_end    = {lookup_token("</image>")};292                        tok_sli_img_start = {lookup_token("<slice>")};293                        tok_sli_img_end   = {lookup_token("</slice>")};294                        tok_row_end       = {lookup_token("\n")};295                        tok_row_end_trail = false; // no trailing end-of-row token296                        ov_img_first      = true;297 298                    } else if (minicpmv_version != 0) {299                        throw std::runtime_error(string_format("unsupported minicpmv version: %d\n", minicpmv_version));300                    }301                    image_preproc = std::make_unique<mtmd_image_preprocessor_llava_uhd>(ctx_v);302                } break;303            case PROJECTOR_TYPE_QWEN2VL:304            case PROJECTOR_TYPE_QWEN25VL:305            case PROJECTOR_TYPE_QWEN3VL:306                {307                    // <|vision_start|> ... (image embeddings) ... <|vision_end|>308                    img_beg = "<|vision_start|>";309                    img_end = "<|vision_end|>";310                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);311                } break;312            case PROJECTOR_TYPE_YOUTUVL:313                {314                    // <|vision_start|> ... (image embeddings) ... <|vision_end|>315                    img_beg = "<|vision_start|>";316                    img_end = "<|vision_end|>";317                    image_preproc = std::make_unique<mtmd_image_preprocessor_youtuvl>(ctx_v);318                } break;319            case PROJECTOR_TYPE_GEMMA3:320            case PROJECTOR_TYPE_GEMMA3NV:321                {322                    // <start_of_image> ... (image embeddings) ... <end_of_image>323                    img_beg = "<start_of_image>";324                    img_end = "<end_of_image>";325                    image_preproc = std::make_unique<mtmd_image_preprocessor_fixed_size>(ctx_v);326                } break;327            case PROJECTOR_TYPE_IDEFICS3:328                {329                    // https://github.com/huggingface/transformers/blob/a42ba80fa520c784c8f11a973ca9034e5f859b79/src/transformers/models/idefics3/processing_idefics3.py#L192-L215330                    slice_tmpl         = MTMD_SLICE_TMPL_IDEFICS3;331                    tok_ov_img_start   = {lookup_token("\n\n"), lookup_token("<fake_token_around_image>"), lookup_token("<global-img>")};332                    tok_ov_img_end     = {lookup_token("<fake_token_around_image>")};333                    tok_row_end        = {lookup_token("\n")};334                    sli_img_start_tmpl = "<fake_token_around_image><row_%d_col_%d>";335                    image_preproc = std::make_unique<mtmd_image_preprocessor_idefics3>(ctx_v);336                } break;337            case PROJECTOR_TYPE_PIXTRAL:338                {339                    // https://github.com/huggingface/transformers/blob/1cd110c6cb6a6237614130c470e9a902dbc1a4bd/docs/source/en/model_doc/pixtral.md340                    img_end = "[IMG_END]";341                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);342                } break;343            case PROJECTOR_TYPE_PHI4:344                {345                    // Phi-4 uses media marker insertion only. Keep image boundary text empty.346                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);347                } break;348            case PROJECTOR_TYPE_LLAMA4:349                {350                    // (more details in mtmd_context constructor)351                    img_beg = "<|image_start|>";352                    img_end = "<|image_end|>";353                    LOG_WRN("%s: llama 4 vision is known to have degraded quality:\n"354                            "    https://github.com/ggml-org/llama.cpp/pull/13282\n", __func__);355                    image_preproc = std::make_unique<mtmd_image_preprocessor_llava_uhd>(ctx_v);356                } break;357            case PROJECTOR_TYPE_STEP3VL:358                {359                    // Step3 format:360                    //   <patch_start> (patch) <patch_end> [<patch_newline>]361                    //   ... (all patch rows)362                    //   <im_start> (overview) <im_end>363                    slice_tmpl        = MTMD_SLICE_TMPL_STEP3VL;364                    tok_ov_img_start  = {lookup_token("<im_start>")};365                    tok_ov_img_end    = {lookup_token("<im_end>")};366                    tok_sli_img_start = {lookup_token("<patch_start>")};367                    tok_sli_img_end   = {lookup_token("<patch_end>")};368                    tok_row_end       = {lookup_token("<patch_newline>")};369                    tok_row_end_trail = false;370                    ov_img_first      = false; // patches first, overview last371                    image_preproc = std::make_unique<mtmd_image_preprocessor_step3vl>(ctx_v);372                } break;373            case PROJECTOR_TYPE_INTERNVL:374                {375                    // <img> ... (image embeddings) ... </img>376                    img_beg = "<img>";377                    img_end = "</img>";378                    image_preproc = std::make_unique<mtmd_image_preprocessor_internvl>(ctx_v);379                } break;380            case PROJECTOR_TYPE_KIMIVL:381                {382                    // <|media_start|> ... (image embeddings) ... <|media_end|>383                    img_beg = "<|media_start|>";384                    img_end = "<|media_end|>";385                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);386                } break;387            case PROJECTOR_TYPE_KIMIK25:388                {389                    // <|media_begin|> ... (image embeddings) ... <|media_end|>390                    img_beg = "<|media_begin|>";391                    img_end = "<|media_end|>";392                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);393                } break;394            case PROJECTOR_TYPE_LIGHTONOCR:395                {396                    // <|im_start|> ... (image embeddings) ... <|im_end|>397                    img_beg = "<|im_start|>";398                    img_end = "<|im_end|>";399                    image_preproc = std::make_unique<mtmd_image_preprocessor_longest_edge>(ctx_v);400                } break;401            case PROJECTOR_TYPE_DOTS_OCR:402                {403                    // <|img|> ... (image embeddings) ... <|endofimg|>404                    img_beg = "<|img|>";405                    img_end = "<|endofimg|>";406                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);407                } break;408            case PROJECTOR_TYPE_NEMOTRON_V2_VL:409                {410                    image_preproc = std::make_unique<mtmd_image_preprocessor_fixed_size>(ctx_v);411                } break;412            case PROJECTOR_TYPE_LFM2:413                {414                    // multi-tile:415                    //   <|image_start|>416                    //     <|img_row_1_col_1|> (tile) <|img_row_1_col_2|> (tile) ...417                    //     <|img_thumbnail|> (thumbnail)418                    //   <|image_end|>419                    // single-tile:420                    //   <|image_start|> (image) <|image_end|>421                    img_beg            = "<|image_start|>";422                    img_end            = "<|image_end|>";423                    slice_tmpl         = MTMD_SLICE_TMPL_LFM2;424                    sli_img_start_tmpl = "<|img_row_%d_col_%d|>";425                    tok_ov_img_start   = {lookup_token("<|img_thumbnail|>")};426                    ov_img_first       = false;427                    image_preproc = std::make_unique<mtmd_image_preprocessor_lfm2>(ctx_v);428                } break;429            case PROJECTOR_TYPE_GLM4V:430                {431                    // <|begin_of_image|> ... (image embeddings) ... <|end_of_image|>432                    img_beg = "<|begin_of_image|>";433                    img_end = "<|end_of_image|>";434                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);435                } break;436            case PROJECTOR_TYPE_PADDLEOCR:437                {438                    // <|IMAGE_START|> ... (image embeddings) ... <|IMAGE_END|>439                    img_beg = "<|IMAGE_START|>";440                    img_end = "<|IMAGE_END|>";441                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);442                } break;443            case PROJECTOR_TYPE_GEMMA4V:444                {445                    // <|image> ... (image embeddings) ... <image|>446                    img_beg = "<|image>";447                    img_end = "<image|>";448                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);449                } break;450            case PROJECTOR_TYPE_DEEPSEEKOCR:451                {452                    img_end = "\n"; // prevent empty batch on llama-server453                    image_preproc = std::make_unique<mtmd_image_preprocessor_deepseekocr>(ctx_v);454                } break;455            case PROJECTOR_TYPE_HUNYUANOCR:456                {457                    // note: these use fullwidth | (U+FF5C) and ▁ (U+2581) to match the tokenizer vocabulary458                    img_beg = "<|hy_place▁holder▁no▁100|>";459                    img_end = "<|hy_place▁holder▁no▁101|>";460                    image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);461                } break;462            default:463                throw std::runtime_error(string_format("%s: unexpected vision projector type %d\n", __func__, proj));464        }465 466        GGML_ASSERT(image_preproc != nullptr);467    }468 469    void init_audio() {470        GGML_ASSERT(ctx_a != nullptr);471        audio_preproc.reset();472 473        projector_type proj = clip_get_projector_type(ctx_a);474 475        LOG_WRN("%s: audio input is in experimental stage and may have reduced quality:\n"476                "    https://github.com/ggml-org/llama.cpp/discussions/13759\n", __func__);477 478        // set preprocessor479        switch (proj) {480            case PROJECTOR_TYPE_QWEN2A:481            case PROJECTOR_TYPE_QWEN3A:482            case PROJECTOR_TYPE_QWEN25O:483                {484                    // <|audio_bos|> ... (embeddings) ... <|audio_eos|>485                    aud_beg = "<|audio_bos|>";486                    aud_end = "<|audio_eos|>";487                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_whisper>(ctx_a);488                } break;489            case PROJECTOR_TYPE_VOXTRAL:490                {491                    // [BEGIN_AUDIO] ... (embeddings) ...492                    aud_beg = "[BEGIN_AUDIO]";493                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_whisper>(ctx_a);494                } break;495            case PROJECTOR_TYPE_MUSIC_FLAMINGO:496                {497                    // <sound> ... (embeddings) ...498                    aud_beg = "<sound>";499                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_whisper>(ctx_a);500                } break;501            case PROJECTOR_TYPE_ULTRAVOX:502            case PROJECTOR_TYPE_GLMA:503            case PROJECTOR_TYPE_MERALION:504                {505                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_whisper>(ctx_a);506                } break;507            case PROJECTOR_TYPE_LFM2A:508                {509                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_conformer>(ctx_a);510                } break;511            case PROJECTOR_TYPE_GEMMA4A:512                {513                    aud_beg = "<|audio>";514                    aud_end = "<audio|>";515                    audio_preproc = std::make_unique<mtmd_audio_preprocessor_gemma4a>(ctx_a);516                } break;517            default:518                throw std::runtime_error(string_format("%s: unexpected audio projector type %d\n", __func__, proj));519        }520 521        // initialize audio preprocessor522        GGML_ASSERT(audio_preproc != nullptr);523        audio_preproc->initialize();524    }525 526    // get clip ctx based on chunk type527    clip_ctx * get_clip_ctx(const mtmd_input_chunk * chunk) const {528        if (chunk->type == MTMD_INPUT_CHUNK_TYPE_IMAGE) {529            return ctx_v;530        } else if (chunk->type == MTMD_INPUT_CHUNK_TYPE_AUDIO) {531            return ctx_a;532        }533        GGML_ABORT("unknown chunk type");534    }535 536    projector_type proj_type_v() const {537        return ctx_v ? clip_get_projector_type(ctx_v) : PROJECTOR_TYPE_UNKNOWN;538    }539 540    projector_type proj_type_a() const {541        return ctx_a ? clip_get_projector_type(ctx_a) : PROJECTOR_TYPE_UNKNOWN;542    }543 544    ~mtmd_context() {545        clip_free(ctx_a);546        clip_free(ctx_v);547    }548 549private:550    llama_token lookup_token(const std::string & token_text) {551        const llama_vocab * vocab = llama_model_get_vocab(text_model);552        const int n_vocab = llama_vocab_n_tokens(vocab);553        for (int i = 0; i < n_vocab; i++) {554            if (token_to_piece(vocab, i, true) == token_text) {555                return i;556            }557        }558        return LLAMA_TOKEN_NULL;559    }560 561    std::string token_to_piece(const llama_vocab * vocab, llama_token token, bool special) {562        std::string piece;563        piece.resize(piece.capacity());  // using string internal cache, 15 bytes + '\n'564        const int n_chars = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special);565        if (n_chars < 0) {566            piece.resize(-n_chars);567            int check = llama_token_to_piece(vocab, token, &piece[0], piece.size(), 0, special);568            GGML_ASSERT(check == -n_chars);569        } else {570            piece.resize(n_chars);571        }572        return piece;573    }574};575 576mtmd_context * mtmd_init_from_file(const char * mmproj_fname,577        const struct llama_model * text_model,578        const struct mtmd_context_params ctx_params) {579    try {580        return new mtmd_context(mmproj_fname, text_model, ctx_params);581    } catch (const std::exception & e) {582        LOG_ERR("%s: error: %s\n", __func__, e.what());583        return nullptr;584    }585}586 587void mtmd_free(mtmd_context * ctx) {588    delete ctx;589}590 591struct mtmd_tokenizer {592    mtmd_context * ctx;593    std::vector<const mtmd_bitmap *> bitmaps;594 595    std::string input_text;596    bool add_special;597    bool parse_special;598    const llama_vocab * vocab;599 600    mtmd_input_chunks cur;601 602    mtmd_tokenizer(mtmd_context * ctx,603            const mtmd_input_text * text,604            const mtmd_bitmap ** bitmaps,605            size_t n_bitmaps) : ctx(ctx), bitmaps(bitmaps, bitmaps + n_bitmaps) {606        add_special   = text->add_special;607        parse_special = text->parse_special;608        input_text    = text->text;609        vocab         = llama_model_get_vocab(ctx->text_model);610    }611 612    int32_t tokenize(mtmd_input_chunks * output) {613        cur.entries.clear();614        std::vector<std::string> parts = split_text(input_text, ctx->media_marker);615        size_t i_bm = 0; // index of the current bitmap616        for (auto & part : parts) {617            if (part == ctx->media_marker) {618                // this is a marker, we should add the next bitmap619                if (i_bm >= bitmaps.size()) {620                    LOG_ERR("%s: error: number of bitmaps (%zu) does not match number of markers (%zu)\n",621                            __func__, bitmaps.size(), parts.size() - 1);622                    return 1;623                }624                const mtmd_bitmap * bitmap = bitmaps[i_bm++];625                int32_t res = add_media(bitmap);626                if (res != 0) {627                    return res;628                }629            } else {630                // this is a text part, we should add it as text631                add_text(part, parse_special);632            }633        }634 635        if (add_special && llama_vocab_get_add_bos(vocab)) {636            // if first chunk is text, we add BOS token to first text chunk637            // otherwise, create a new text chunk with BOS token638            if (!cur.entries.empty() && cur.entries[0].type == MTMD_INPUT_CHUNK_TYPE_TEXT) {639                // add BOS token to the beginning of first text chunk640                cur.entries[0].tokens_text.insert(cur.entries[0].tokens_text.begin(), llama_vocab_bos(vocab));641            } else {642                // create a new text chunk with BOS token at the beginning643                mtmd_input_chunk bos_chunk{644                    MTMD_INPUT_CHUNK_TYPE_TEXT,645                    {llama_vocab_bos(vocab)},646                    nullptr, // image tokens647                    nullptr, // audio tokens648                };649                cur.entries.insert(cur.entries.begin(), std::move(bos_chunk));650            }651        }652 653        if (add_special && llama_vocab_get_add_eos(vocab)) {654            // if last chunk is text, we add EOS token to it655            add_text({llama_vocab_eos(vocab)});656        }657 658        if (i_bm != bitmaps.size()) {659            LOG_ERR("%s: error: number of bitmaps (%zu) does not match number of markers (%zu)\n",660                    __func__, bitmaps.size(), parts.size() - 1);661            return 1;662        }663 664        *output = std::move(cur);665 666        return 0;667    }668 669    void add_text(const std::string & txt, bool parse_special) {670        LOG_DBG("%s: %s\n", __func__, txt.c_str());671        auto tokens = mtmd_tokenize_text_internal(vocab, txt, /* add_special */ false, parse_special);672        add_text(tokens);673    }674 675    void add_text(const std::vector<llama_token> & tokens) {676        if (tokens.empty()) {677            return;678        }679        // if last entry is also a text chunk, add tokens to it instead of creating new chunk680        if (!cur.entries.empty() && cur.entries.back().type == MTMD_INPUT_CHUNK_TYPE_TEXT) {681            cur.entries.back().tokens_text.insert(682                                            cur.entries.back().tokens_text.end(),683                                            tokens.begin(),684                                            tokens.end());685        } else {686            mtmd_input_chunk chunk{687                MTMD_INPUT_CHUNK_TYPE_TEXT,688                tokens,689                nullptr, // image tokens690                nullptr, // audio tokens691            };692            cur.entries.emplace_back(std::move(chunk));693        }694    }695 696    int32_t add_media(const mtmd_bitmap * bitmap) {697        if (!bitmap->is_audio) {698            // handle image699 700            if (!ctx->ctx_v) {701                LOG_ERR("%s: error: model does not support vision input\n", __func__);702                return 2;703            }704 705            if (!ctx->img_beg.empty()) {706                add_text(ctx->img_beg, true); // add image begin token707            }708 709            // sanity check710            GGML_ASSERT(bitmap->nx > 0 && bitmap->ny > 0);711            GGML_ASSERT(bitmap->data.size() == (size_t)bitmap->nx * bitmap->ny * 3);712            GGML_ASSERT(ctx->image_preproc != nullptr);713 714            // convert mtmd_bitmap to clip_image_u8715            clip_image_u8_ptr img_u8(clip_image_u8_init());716            img_u8->nx = bitmap->nx;717            img_u8->ny = bitmap->ny;718            img_u8->buf.resize(bitmap->data.size());719            std::memcpy(img_u8->buf.data(), bitmap->data.data(), img_u8->nx * img_u8->ny * 3);720 721            // preprocess image722            clip_image_f32_batch batch_f32;723            bool ok = ctx->image_preproc->preprocess(*img_u8, batch_f32);724            if (!ok) {725                LOG_ERR("Unable to preprocess image\n");726                return 2;727            }728 729            // handle llava-uhd style preprocessing730            const bool has_tiling_grid = batch_f32.grid_x > 0 && batch_f32.grid_y > 0;731            if (732                ctx->slice_tmpl == MTMD_SLICE_TMPL_MINICPMV_2_5733                || ctx->slice_tmpl == MTMD_SLICE_TMPL_MINICPMV_2_6734                || ctx->slice_tmpl == MTMD_SLICE_TMPL_LLAMA4735                || ctx->slice_tmpl == MTMD_SLICE_TMPL_IDEFICS3736                || ctx->slice_tmpl == MTMD_SLICE_TMPL_STEP3VL737                || (ctx->slice_tmpl == MTMD_SLICE_TMPL_LFM2 && has_tiling_grid)738            ) {739                const int n_col = batch_f32.grid_x;740                const int n_row = batch_f32.grid_y;741                // split batch into chunks of single images742                // NOTE: batch_f32 will be invalidated after this call743                auto chunks = split_batch_to_chunk(std::move(batch_f32), bitmap->id);744                GGML_ASSERT(chunks.size() > 0);745 746                auto ov_chunk = std::move(chunks.front());747                chunks.erase(chunks.begin());748 749                // add overview image (first)750                if (ctx->ov_img_first) {751                    add_text(ctx->tok_ov_img_start);752                    cur.entries.emplace_back(std::move(ov_chunk));753                    add_text(ctx->tok_ov_img_end);754                }755 756                // add slices (or tiles)757                if (!chunks.empty()) {758                    GGML_ASSERT((int)chunks.size() == n_row * n_col);759                    add_text(ctx->tok_slices_start);760                    for (int y = 0; y < n_row; y++) {761                        for (int x = 0; x < n_col; x++) {762                            const bool is_last_in_row = (x == n_col - 1);763                            if (!ctx->tok_sli_img_start.empty()) {764                                add_text(ctx->tok_sli_img_start);765                            } else if (!ctx->sli_img_start_tmpl.empty()) {766                                // If using a template to preceed a slice image767                                const size_t sz = std::snprintf(nullptr, 0, ctx->sli_img_start_tmpl.c_str(), y+1, x+1) + 1;768                                std::unique_ptr<char[]> buf(new char[sz]);769                                std::snprintf(buf.get(), sz, ctx->sli_img_start_tmpl.c_str(), y+1, x+1);770                                add_text(std::string(buf.get(), buf.get() + sz - 1), true);771                            }772                            cur.entries.emplace_back(std::move(chunks[y * n_col + x]));773                            add_text(ctx->tok_sli_img_end);774                            if (!is_last_in_row) {775                                add_text(ctx->tok_sli_img_mid);776                            }777                        }778                        if ((y != n_row - 1 || ctx->tok_row_end_trail)) {779                            add_text(ctx->tok_row_end);780                        }781                    }782                    add_text(ctx->tok_slices_end);783                }784 785                // add overview image (last)786                if (!ctx->ov_img_first) {787                    add_text(ctx->tok_ov_img_start);788                    cur.entries.emplace_back(std::move(ov_chunk));789                    add_text(ctx->tok_ov_img_end);790                }791 792            } else {793                size_t n_tokens = 0;794                for (const auto & entry : batch_f32.entries) {795                    n_tokens += clip_n_output_tokens(ctx->ctx_v, entry.get());796                }797 798                mtmd_image_tokens_ptr image_tokens(new mtmd_image_tokens);799                if (mtmd_decode_use_mrope(ctx)) {800                    // for Qwen2VL, we need this information for M-RoPE decoding positions801                    image_tokens->nx = clip_n_output_tokens_x(ctx->ctx_v, batch_f32.entries[0].get());802                    image_tokens->ny = clip_n_output_tokens_y(ctx->ctx_v, batch_f32.entries[0].get());803                } else {804                    // other models, we only need the total number of tokens805                    image_tokens->nx = n_tokens;806                    image_tokens->ny = 1;807                }808                image_tokens->pos = ctx->pos_type;809                image_tokens->batch_f32 = std::move(batch_f32);810                image_tokens->id = bitmap->id; // optional811 812                LOG_DBG("image_tokens->nx = %d\n", image_tokens->nx);813                LOG_DBG("image_tokens->ny = %d\n", image_tokens->ny);814                LOG_DBG("batch_f32 size = %d\n", (int)image_tokens->batch_f32.entries.size());815 816                mtmd_input_chunk chunk{817                    MTMD_INPUT_CHUNK_TYPE_IMAGE,818                    {}, // text tokens819                    std::move(image_tokens),820                    nullptr, // audio tokens821                };822                cur.entries.emplace_back(std::move(chunk));823            }824 825            if (!ctx->img_end.empty()) {826                add_text(ctx->img_end, true); // add image end token827            }828 829        } else {830            // handle audio831 832            if (!ctx->ctx_a) {833                LOG_ERR("%s: error: model does not support audio input\n", __func__);834                return 2;835            }836 837            if (bitmap->data.size() == 0) {838                LOG_ERR("%s: error: empty audio data\n", __func__);839                return 2;840            }841 842            if (!ctx->aud_beg.empty()) {843                add_text(ctx->aud_beg, true); // add audio begin token844            }845 846            // sanity check847            GGML_ASSERT(ctx->audio_preproc != nullptr);848            GGML_ASSERT(bitmap->data.size() > sizeof(float));849            GGML_ASSERT(bitmap->data.size() % sizeof(float) == 0);850 851            // preprocess audio852            std::vector<mtmd_audio_mel> mel_spec_chunks;853            const float * samples = (const float *)bitmap->data.data();854            size_t n_samples = bitmap->data.size() / sizeof(float);855            bool ok = ctx->audio_preproc->preprocess(samples, n_samples, mel_spec_chunks);856            if (!ok) {857                LOG_ERR("Unable to preprocess audio\n");858                return 2;859            }860 861            // consider each mel_spec as a separate audio chunk862            // TODO: maybe support batching, but this may come with memory cost863            for (auto & mel_spec : mel_spec_chunks) {864                clip_image_f32_ptr mel_f32(clip_image_f32_init());865                mel_f32->nx  = mel_spec.n_len;866                mel_f32->ny  = mel_spec.n_mel;867                mel_f32->buf = std::move(mel_spec.data);868                size_t n_tokens = clip_n_output_tokens(ctx->ctx_a, mel_f32.get());869 870                clip_image_f32_batch batch_f32;871                batch_f32.is_audio = true;872                batch_f32.entries.push_back(std::move(mel_f32));873 874                mtmd_audio_tokens_ptr audio_tokens(new mtmd_audio_tokens);875                audio_tokens->n_tokens = n_tokens;876                audio_tokens->batch_f32 = std::move(batch_f32);877                audio_tokens->id = bitmap->id; // optional878 879                LOG_DBG("audio_tokens->n_tokens = %d\n", audio_tokens->n_tokens);880 881                mtmd_input_chunk chunk{882                    MTMD_INPUT_CHUNK_TYPE_AUDIO,883                    {}, // text tokens884                    nullptr, // image tokens885                    std::move(audio_tokens),886                };887                cur.entries.emplace_back(std::move(chunk));888            }889 890            if (!ctx->aud_end.empty()) {891                add_text(ctx->aud_end, true); // add audio end token892            }893        }894 895        return 0;896    }897 898    std::vector<mtmd_input_chunk> split_batch_to_chunk(clip_image_f32_batch && batch_f32, const std::string & id) {899        std::vector<mtmd_input_chunk> chunks;900 901        for (auto & entry : batch_f32.entries) {902            mtmd_image_tokens_ptr image_tokens(new mtmd_image_tokens);903            image_tokens->nx = clip_n_output_tokens(ctx->ctx_v, entry.get());904            image_tokens->ny = 1;905            image_tokens->batch_f32.entries.push_back(std::move(entry));906            image_tokens->id = id;907 908            mtmd_input_chunk chunk{909                MTMD_INPUT_CHUNK_TYPE_IMAGE,910                {}, // text tokens911                std::move(image_tokens),912                nullptr, // audio tokens913            };914            chunks.emplace_back(std::move(chunk));915        }916 917        return chunks;918    }919 920    // for example: "a <__media__> b <__media__> c" --> "a", "<__media__>", "b", "<__media__>", "c"921    static std::vector<std::string> split_text(const std::string & input, const std::string & delimiter) {922        std::vector<std::string> result;923        if (input.empty()) {924            return result;925        }926        size_t start = 0;927        size_t pos = 0;928        while ((pos = input.find(delimiter, start)) != std::string::npos) {929            if (pos > start) {930                result.push_back(input.substr(start, pos - start));931            }932            result.push_back(delimiter);933            start = pos + delimiter.length();934        }935        if (start < input.length()) {936            result.push_back(input.substr(start));937        }938        return result;939    }940 941    // copied from common_tokenize942    static std::vector<llama_token> mtmd_tokenize_text_internal(943        const struct llama_vocab * vocab,944               const std::string & text,945                            bool   add_special,946                            bool   parse_special) {947        // upper limit for the number of tokens948        int n_tokens = text.length() + 2 * add_special;949        std::vector<llama_token> result(n_tokens);950        n_tokens = llama_tokenize(vocab, text.data(), text.length(), result.data(), result.size(), add_special, parse_special);951        if (n_tokens < 0) {952            result.resize(-n_tokens);953            int check = llama_tokenize(vocab, text.data(), text.length(), result.data(), result.size(), add_special, parse_special);954            GGML_ASSERT(check == -n_tokens);955        } else {956            result.resize(n_tokens);957        }958        return result;959    }960};961 962int32_t mtmd_tokenize(mtmd_context * ctx,963            mtmd_input_chunks * output,964            const mtmd_input_text * text,965            const mtmd_bitmap ** bitmaps,966            size_t n_bitmaps) {967    mtmd_tokenizer tokenizer(ctx, text, bitmaps, n_bitmaps);968    return tokenizer.tokenize(output);969}970 971int32_t mtmd_encode_chunk(mtmd_context * ctx, const mtmd_input_chunk * chunk) {972    if (chunk->type == MTMD_INPUT_CHUNK_TYPE_TEXT) {973        LOG_WRN("mtmd_encode_chunk has no effect for text chunks\n");974        return 0;975    } else if (chunk->type == MTMD_INPUT_CHUNK_TYPE_IMAGE) {976        if (!ctx->ctx_v) {977            LOG_ERR("%s: model does not support vision input\n", __func__);978            return 1;979        }980        return mtmd_encode(ctx, chunk->tokens_image.get());981    } else if (chunk->type == MTMD_INPUT_CHUNK_TYPE_AUDIO) {982        if (!ctx->ctx_a) {983            LOG_ERR("%s: model does not support audio input\n", __func__);984            return 1;985        }986        int n_mmproj_embd = ctx->n_embd_text;987        ctx->image_embd_v.resize(chunk->tokens_audio->n_tokens * n_mmproj_embd);988        bool ok = clip_image_batch_encode(989            ctx->ctx_a,990            ctx->n_threads,991            &chunk->tokens_audio->batch_f32,992            ctx->image_embd_v.data());993        return ok ? 0 : 1;994    }995 996    LOG_ERR("%s: unknown chunk type %d\n", __func__, (int)chunk->type);997    return 1;998}999 1000int32_t mtmd_encode(mtmd_context * ctx, const mtmd_image_tokens * image_tokens) {1001    clip_ctx * ctx_clip = ctx->ctx_v;1002    if (!ctx_clip) {1003        LOG_ERR("%s: this API does not support non-vision input, please use mtmd_encode_chunk instead\n", __func__);1004        return 1;1005    }1006    auto proj_type = clip_get_projector_type(ctx_clip);1007    int n_mmproj_embd = clip_n_mmproj_embd(ctx_clip);1008    ctx->image_embd_v.resize(image_tokens->n_tokens() * n_mmproj_embd);1009    bool ok = false;1010 1011    if (clip_is_llava(ctx_clip)1012        || clip_is_minicpmv(ctx_clip)1013        || clip_is_glm(ctx_clip)1014        || proj_type == PROJECTOR_TYPE_INTERNVL) {1015        // TODO @ngxson : llava does not support batched encoding ; this should be fixed inside clip_image_batch_encode()1016        const auto & entries = image_tokens->batch_f32.entries;1017        for (size_t i = 0; i < entries.size(); i++) {1018            int n_tokens_per_image = clip_n_output_tokens(ctx_clip, entries[i].get());1019            ok = clip_image_encode(1020                ctx_clip,1021                ctx->n_threads,1022                entries[i].get(),1023                ctx->image_embd_v.data() + i*n_mmproj_embd*n_tokens_per_image);1024        }1025    } else {1026        ok = clip_image_batch_encode(1027            ctx_clip,1028            ctx->n_threads,1029            &image_tokens->batch_f32,1030            ctx->image_embd_v.data());1031    }1032 1033    return ok ? 0 : 1;1034}1035 1036float * mtmd_get_output_embd(mtmd_context * ctx) {1037    return ctx->image_embd_v.data();1038}1039 1040bool mtmd_decode_use_non_causal(const mtmd_context * ctx, const mtmd_input_chunk * chunk) {1041    auto proj_type = ctx->proj_type_v();1042    if (chunk && chunk->type == MTMD_INPUT_CHUNK_TYPE_AUDIO) {1043        proj_type = ctx->proj_type_a();1044    }1045    switch (proj_type) {1046        case PROJECTOR_TYPE_GEMMA3:1047        case PROJECTOR_TYPE_GEMMA4V:1048            return true;1049        default:1050            return false;1051    }1052}1053 1054bool mtmd_decode_use_mrope(const mtmd_context * ctx) {1055    return ctx->pos_type == MTMD_POS_TYPE_MROPE;1056}1057 1058bool mtmd_support_vision(const mtmd_context * ctx) {1059    return ctx->ctx_v != nullptr;1060}1061 1062bool mtmd_support_audio(const mtmd_context * ctx) {1063    return ctx->ctx_a != nullptr;1064}1065 1066int mtmd_get_audio_sample_rate(const mtmd_context * ctx) {1067    if (!ctx->ctx_a) {1068        return -1;1069    }1070    return clip_get_hparams(ctx->ctx_a)->audio_sample_rate;1071}1072 1073//1074// public API functions1075//1076 1077// mtmd_bitmap1078 1079mtmd_bitmap * mtmd_bitmap_init(uint32_t nx,1080                               uint32_t ny,1081                               const unsigned char * data) {1082    mtmd_bitmap * bitmap = new mtmd_bitmap;1083    bitmap->nx = nx;1084    bitmap->ny = ny;1085    size_t data_size = (size_t)nx * ny * 3;1086    bitmap->data.resize(data_size);1087    std::memcpy(bitmap->data.data(), data, data_size);1088    return bitmap;1089}1090 1091mtmd_bitmap * mtmd_bitmap_init_from_audio(size_t n_samples,1092                                          const float * data) {1093    mtmd_bitmap * bitmap = new mtmd_bitmap;1094    bitmap->nx = n_samples;1095    bitmap->ny = 1;1096    bitmap->is_audio = true;1097    size_t data_size = n_samples * sizeof(float);1098    bitmap->data.resize(data_size);1099    std::memcpy(bitmap->data.data(), data, data_size);1100    return bitmap;1101}1102 1103uint32_t mtmd_bitmap_get_nx(const mtmd_bitmap * bitmap) {1104    return bitmap->nx;1105}1106 1107uint32_t mtmd_bitmap_get_ny(const mtmd_bitmap * bitmap) {1108    return bitmap->ny;1109}1110 1111const unsigned char * mtmd_bitmap_get_data(const mtmd_bitmap * bitmap) {1112    return bitmap->data.data();1113}1114 1115size_t mtmd_bitmap_get_n_bytes(const mtmd_bitmap * bitmap) {1116    return bitmap->data.size();1117}1118 1119bool mtmd_bitmap_is_audio(const mtmd_bitmap * bitmap) {1120    return bitmap->is_audio;1121}1122 1123const char * mtmd_bitmap_get_id(const mtmd_bitmap * bitmap) {1124    return bitmap->id.c_str();1125}1126 1127void mtmd_bitmap_set_id(mtmd_bitmap * bitmap, const char * id) {1128    if (id) {1129        bitmap->id = std::string(id);1130    } else {1131        bitmap->id.clear();1132    }1133}1134 1135void mtmd_bitmap_free(mtmd_bitmap * bitmap) {1136    if (bitmap) {1137        delete bitmap;1138    }1139}1140 1141// mtmd_input_chunks1142 1143mtmd_input_chunks * mtmd_input_chunks_init() {1144    return new mtmd_input_chunks;1145}1146 1147size_t mtmd_input_chunks_size(const mtmd_input_chunks * chunks) {1148    return chunks->entries.size();1149}1150 1151const mtmd_input_chunk * mtmd_input_chunks_get(const mtmd_input_chunks * chunks, size_t idx) {1152    if (idx >= chunks->entries.size()) {1153        return nullptr;1154    }1155    return &chunks->entries[idx];1156}1157 1158void mtmd_input_chunks_free(mtmd_input_chunks * chunks) {1159    if (chunks) {1160        delete chunks;1161    }1162}1163 1164// mtmd_input_chunk1165 1166enum mtmd_input_chunk_type mtmd_input_chunk_get_type(const mtmd_input_chunk * chunk) {1167    return chunk->type;1168}1169 1170const llama_token * mtmd_input_chunk_get_tokens_text(const mtmd_input_chunk * chunk, size_t * n_tokens_output) {1171    if (chunk->type == MTMD_INPUT_CHUNK_TYPE_TEXT) {1172        *n_tokens_output = chunk->tokens_text.size();1173        return chunk->tokens_text.data();1174    }1175    *n_tokens_output = 0;1176    return nullptr;1177}1178 1179const mtmd_image_tokens * mtmd_input_chunk_get_tokens_image(const mtmd_input_chunk * chunk) {1180    if (chunk->type == MTMD_INPUT_CHUNK_TYPE_IMAGE) {1181        return chunk->tokens_image.get();1182    }1183    return nullptr;1184}1185 1186size_t mtmd_input_chunk_get_n_tokens(const mtmd_input_chunk * chunk) {1187    if (chunk->type == MTMD_INPUT_CHUNK_TYPE_TEXT) {1188        return chunk->tokens_text.size();1189    } else if (chunk->type == MTMD_INPUT_CHUNK_TYPE_IMAGE) {1190        return mtmd_image_tokens_get_n_tokens(chunk->tokens_image.get());1191    } else if (chunk->type == MTMD_INPUT_CHUNK_TYPE_AUDIO) {1192        return chunk->tokens_audio->n_tokens;1193    } else {1194        GGML_ABORT("invalid chunk type");1195    }1196}1197 1198llama_pos mtmd_input_chunk_get_n_pos(const mtmd_input_chunk * chunk) {1199    if (chunk->type == MTMD_INPUT_CHUNK_TYPE_TEXT) {1200        return chunk->tokens_text.size();

Showing the first 1,200 of 1438 lines. Download the file for the rest.