model: Muse Glimmer Support (#26841)
* Get started with Onyx * Add architecture * Skip keys handled in super() * Loading tensors * Shorten * Graph * Apply suggestion from @pcuenca * Remove norm now embedding in transformers weights * Add eot * Explicit output_multiplier * Handle post_norm_eps * No super call; unhardcode eot. The pattern `self._set_vocab_gpt2()` seems preferred throughout the codebase, and it allows `set_vocab()` to be called from a different part of the Python class hierarchy: the drafter model converter that we may need eventually. * Register for drafting * DFlash: inherit rope type from the linked target. Another option would be to store it in the gguf file itself. * mmproj conversion Note: some fields to be renamed after the implementation works. We are keeping compatibility with the reference Meta gguf for testing purposes. * "clip" header declarations * Load mmproj * Pre-processing * Graph * Go back to using delimiters. Otherwise our generations are worse. Transformers does not use them. We need to trace inputs to verify whether they are equivalent. * downsample_factor -> merge_size * Add vision graph lol, forgot from a previous commit * Additional renames, align with llama.cpp / transformers * Prefer _size instead of independent _h and _w * Fix token layout Co-authored-by: Young Han <younghan@fb.com> * onyx: bring the chat parser onto the onyx branch common/chat.cpp on this branch has no Onyx handling, so a converted model serves malformed chat: the assistant preamble leaks into content ("to=self<|message|>...") and tool calls fail with HTTP 500 "The model produced output that does not match the expected peg-native format" common_chat_params_init_onyx exists on onyx-fair-patch, added there by 8bb73dd3d. It was never on this branch, so this is not a regression -- the two lines developed independently. The code here is taken verbatim from that commit. It is the clean side of `git merge origin/onyx-fair-patch`: chat.cpp is one of the files that merges without conflict. The full merge is not viable -- it produces 13 conflicts, including add/add on conversion/onyx.py and src/models/onyx.cpp where the q_norm-folding and metadata-scale approaches contradict each other, and #4/#7 are stacked on this branch's side of that. Verified on this branch: builds with 0 errors, converts an Onyx checkpoint, and serving it gives "4" for "What is 2+2?" plus a correct get_weather {"city":"Paris"} tool call, where the unported branch gives the two failures above. No converter or runtime changes are included, so this should not interact with the q_norm work. Co-authored-by: Beto de Paola <betodepaola@meta.com> * Less params, bilinear pos-emb interpolation as a graph op instead of CPU * Map to symbolic V_MMPROJ instead of strings * Make a couple params explicit * Patchify via build_inp() * No param for rope_theta * Small cleanup * Restore blank line * Unpermute, to adapt to the latest transformers checkpoint * Apply norm after token embeddings This follows the latest transformers approach. * Remove duplicated function * build_vit * onyx: use the model rope theta on sliding-window layers * DFlash: conversion from transformers drafter * Revert rope_type derivation from target NOTE: this breaks compatibility with Meta's distributed DFlash GGUFs, as the Q/K are stored in "NEOX" (rotated half) format, like in transformers. * Apply suggestion from @pcuenca * Set model type * Remove comment that will become obsolete * Hardcode post_norm_rms_eps instead of new param * Derive SWA+RoPE pattern from gguf array or scalar * Fix model type <-> number of layers * Reorder * Rename * Fix typo * DFlash: seed the draft KV cache from multimodal embedding batches `common_speculative_impl_draft_dflash::process()` returned early on any batch carrying embeddings, so an image prefill never had its target-layer features fused through the DFlash encoder and injected into the draft's KV cache. That left a hole spanning the image's positions, and the next injection at a post-image position failed to initialize its batch: ``` decoding image batch 1/1, n_tokens_batch = 256 decode: failed to initialize batch llama_decode: failed to decode, ret = -1 process: llama_decode(ctx_dft) failed rc=-1 (n_tokens=17, offset=0) srv decode: failed to process speculative batch ``` Every image request with `--spec-type draft-dflash` failed with HTTP 500. Text-only was unaffected, since those batches carry token ids and were let through. Restore the earlier condition, which admits a batch that is either tokens or embeddings and skips only the degenerate neither/both cases. The rest of `process()` is already layout-agnostic -- it gathers features via `llama_get_embeddings_layer_inp()` and indexes `batch_in.pos[]` / `batch_in.seq_id[]`, none of which assume token ids -- so this is the whole fix. Validated against `muse-glimmer-30B-bf16.gguf` + `mmproj-muse-glimmer-30B-bf16.gguf` + a DFlash draft head, on an image describe-the-shapes request: - before: HTTP 500, `failed to process speculative batch` - after: HTTP 200, draft acceptance 0.34012 (167 accepted / 491 generated), mean len 3.04 Output equivalence holds, which is the property that matters: at temperature 0 the drafted response is byte-identical to the same request served with no draft attached (1213/1213 chars), so the draft is drafting correctly through the image context rather than merely not crashing. * Conversion: prefer rewrite to mapping * Revert "Conversion: prefer rewrite to mapping" This reverts commit a92d0ac584d315e876741e85b6dad3dbc8b23bf7. * fix lint * sliding_window metadata is not optional * disable state save/load * Apply suggestion from @pcuenca --------- Co-authored-by: Young Han <younghan@fb.com> Co-authored-by: Beto de Paola <betodepaola@meta.com> Co-authored-by: Daniel Han <michaelhan2050@gmail.com> Co-authored-by: ruanrms <ruanslv@gmail.com> Co-authored-by: Xuan Son Nguyen <son@huggingface.co> Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
This commit is contained in:
co-authored by
Young Han
Beto de Paola
Daniel Han
ruanrms
Xuan Son Nguyen
Sigbjørn Skjæret
parent
a52077c4ca
commit
62bf73d25c
@@ -43,6 +43,7 @@ add_library(mtmd
|
||||
models/kimivl.cpp
|
||||
models/kimik25.cpp
|
||||
models/nemotron-v2-vl.cpp
|
||||
models/muse-glimmer.cpp
|
||||
models/llama4.cpp
|
||||
models/llava.cpp
|
||||
models/minicpmv.cpp
|
||||
|
||||
@@ -455,6 +455,7 @@ enum projector_type {
|
||||
PROJECTOR_TYPE_MIMO_AUDIO,
|
||||
PROJECTOR_TYPE_QWEN3TTS_SPKENC,
|
||||
PROJECTOR_TYPE_QWEN3TTS_GEN,
|
||||
PROJECTOR_TYPE_MUSE_GLIMMER,
|
||||
PROJECTOR_TYPE_UNKNOWN,
|
||||
};
|
||||
|
||||
@@ -514,6 +515,7 @@ static std::map<projector_type, std::string> PROJECTOR_TYPE_NAMES = {
|
||||
{ PROJECTOR_TYPE_PARAKEET, "parakeet"},
|
||||
{ PROJECTOR_TYPE_QWEN3TTS_SPKENC, "qwen3tts_spkenc"},
|
||||
{ PROJECTOR_TYPE_QWEN3TTS_GEN, "qwen3tts_gen"},
|
||||
{ PROJECTOR_TYPE_MUSE_GLIMMER, "muse-glimmer"},
|
||||
};
|
||||
|
||||
static projector_type clip_projector_type_from_string(const std::string & str) {
|
||||
|
||||
@@ -109,6 +109,11 @@ struct clip_hparams {
|
||||
int32_t downsample_query_side;
|
||||
int32_t downsample_window_side;
|
||||
|
||||
// Muse Glimmer vision (per-block sparse-window pattern, learned pos-emb, patch-temporal)
|
||||
// NOTE: these perhaps shouldn't have the architecture prefix
|
||||
int32_t muse_glimmer_patch_temporal = 0;
|
||||
int32_t muse_glimmer_sparse_factor = 0;
|
||||
|
||||
// audio
|
||||
int32_t n_mel_bins = 0; // whisper preprocessor
|
||||
int32_t proj_stack_factor = 0; // ultravox
|
||||
|
||||
@@ -954,6 +954,10 @@ static std::unique_ptr<clip_graph> clip_get_graph_builder(clip_ctx * ctx, const
|
||||
{
|
||||
builder = std::make_unique<clip_graph_minimax_m3>(ctx, img);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
builder = std::make_unique<clip_graph_muse_glimmer>(ctx, img);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
{
|
||||
builder = std::make_unique<clip_graph_step3vl>(ctx, img);
|
||||
@@ -1572,6 +1576,17 @@ struct clip_model_loader {
|
||||
hparams.set_limit_image_tokens(8, 576);
|
||||
hparams.set_warmup_n_tokens(16*16);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
hparams.n_merge = 2; // pixel-shuffle downsample after the ViT
|
||||
hparams.image_resize_algo = RESIZE_ALGO_LANCZOS;
|
||||
hparams.rope_theta = 10000.0f;
|
||||
hparams.muse_glimmer_patch_temporal = 2;
|
||||
hparams.muse_glimmer_sparse_factor = 4; // 3 sparse layers + 1 global, repeating
|
||||
get_u32(KEY_SPATIAL_MERGE_SIZE, hparams.n_merge, false);
|
||||
hparams.set_limit_image_tokens(1, 4096);
|
||||
hparams.set_warmup_n_tokens(32*32);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MIMOVL:
|
||||
{
|
||||
hparams.n_merge = 2; // spatial_merge_size
|
||||
@@ -2317,6 +2332,13 @@ struct clip_model_loader {
|
||||
model.mm_merger_fc2_w = get_tensor(string_format(TN_MM_MERGER_FC2, "weight"));
|
||||
model.mm_merger_fc2_b = get_tensor(string_format(TN_MM_MERGER_FC2, "bias"));
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
// 3-linear MLP: fc -> erf-GELU -> proj -> erf-GELU -> vision_proj (into LLM residual dim)
|
||||
model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"));
|
||||
model.mm_1_w = get_tensor(string_format(TN_LLAVA_PROJ, 1, "weight"));
|
||||
model.mm_2_w = get_tensor(string_format(TN_LLAVA_PROJ, 2, "weight"));
|
||||
} break;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
{
|
||||
model.mm_0_w = get_tensor(string_format(TN_LLAVA_PROJ, 0, "weight"));
|
||||
@@ -3745,6 +3767,7 @@ int clip_n_output_tokens_x(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
case PROJECTOR_TYPE_PADDLEOCR:
|
||||
case PROJECTOR_TYPE_HUNYUANVL:
|
||||
case PROJECTOR_TYPE_YOUTUVL:
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
return (img->nx() / params.patch_size) / 2;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
return img->nx() / (params.patch_size * params.n_merge);
|
||||
@@ -3770,6 +3793,7 @@ int clip_n_output_tokens_y(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
case PROJECTOR_TYPE_PADDLEOCR:
|
||||
case PROJECTOR_TYPE_HUNYUANVL:
|
||||
case PROJECTOR_TYPE_YOUTUVL:
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
return (img->ny() / params.patch_size) / 2;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
return img->ny() / (params.patch_size * params.n_merge);
|
||||
@@ -3848,6 +3872,7 @@ int clip_n_output_tokens(const clip_ctx * ctx, const clip_image_f32 * img) {
|
||||
case PROJECTOR_TYPE_MINIMAX_M3:
|
||||
case PROJECTOR_TYPE_GLM4V:
|
||||
case PROJECTOR_TYPE_YOUTUVL:
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
// dynamic size (2 conv, so double patch size)
|
||||
int x_patch = img->nx() / (params.patch_size * 2);
|
||||
@@ -4193,6 +4218,70 @@ bool clip_encode(struct clip_ctx * ctx, struct clip_encode_params * params) {
|
||||
|
||||
// set input per projector
|
||||
switch (ctx->model.proj_type) {
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
const int grid_w = pos_w; // image_size_width / patch_size
|
||||
const int grid_h = pos_h; // image_size_height / patch_size
|
||||
const int n_tok = grid_w * grid_h;
|
||||
const int pgrid = (int) std::sqrt((double) ctx->model.position_embeddings->ne[1]); // 32
|
||||
const int f = hparams.n_merge; // downsample 2
|
||||
|
||||
// pixel patchify runs inside the graph via build_inp() (ggml_conv_2d);
|
||||
// pos-emb bilinear interp via resize_position_embeddings().
|
||||
|
||||
// --- sparse window grouping (pgrid x pgrid windows) ---
|
||||
const int win = pgrid;
|
||||
const int nwin_h = (grid_h + win - 1) / win;
|
||||
const int nwin_w = (grid_w + win - 1) / win;
|
||||
std::vector<int32_t> sp_perm; sp_perm.reserve(n_tok);
|
||||
std::vector<int> sp_slens;
|
||||
for (int wy = 0; wy < nwin_h; wy++) {
|
||||
for (int wx = 0; wx < nwin_w; wx++) {
|
||||
int cnt = 0;
|
||||
for (int hh = 0; hh < win; hh++) {
|
||||
for (int ww = 0; ww < win; ww++) {
|
||||
const int gy = wy * win + hh;
|
||||
const int gx = wx * win + ww;
|
||||
if (gy < grid_h && gx < grid_w) { sp_perm.push_back(gy * grid_w + gx); cnt++; }
|
||||
}
|
||||
}
|
||||
if (cnt > 0) sp_slens.push_back(cnt);
|
||||
}
|
||||
}
|
||||
std::vector<int32_t> rpos_w(n_tok), rpos_h(n_tok), inv_perm(n_tok);
|
||||
for (int i = 0; i < n_tok; i++) {
|
||||
const int orig = sp_perm[i];
|
||||
rpos_w[i] = (orig % grid_w) + 1; // 1-indexed
|
||||
rpos_h[i] = (orig / grid_w) + 1;
|
||||
inv_perm[orig] = i;
|
||||
}
|
||||
set_input_i32("muse_glimmer_sp_perm", sp_perm);
|
||||
set_input_i32("muse_glimmer_inv_perm", inv_perm);
|
||||
set_input_i32("muse_glimmer_pos_w", rpos_w);
|
||||
set_input_i32("muse_glimmer_pos_h", rpos_h);
|
||||
|
||||
// block-diagonal window mask (permuted order)
|
||||
std::vector<float> sp_mask((size_t) n_tok * n_tok, -INFINITY);
|
||||
{
|
||||
int off = 0;
|
||||
for (int s : sp_slens) {
|
||||
for (int a = 0; a < s; a++)
|
||||
for (int b = 0; b < s; b++)
|
||||
sp_mask[(size_t) (off + a) * n_tok + (off + b)] = 0.0f;
|
||||
off += s;
|
||||
}
|
||||
}
|
||||
set_input_f32("muse_glimmer_sp_mask", sp_mask);
|
||||
|
||||
// pixel-shuffle gather (original order): f*f spatial neighbours grouped
|
||||
std::vector<int32_t> dsp; dsp.reserve(n_tok);
|
||||
for (int oy = 0; oy < grid_h / f; oy++)
|
||||
for (int ox = 0; ox < grid_w / f; ox++)
|
||||
for (int ry = 0; ry < f; ry++)
|
||||
for (int rx = 0; rx < f; rx++)
|
||||
dsp.push_back((oy * f + ry) * grid_w + (ox * f + rx));
|
||||
set_input_i32("muse_glimmer_ds_perm", dsp);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MINICPMV:
|
||||
{
|
||||
// inspired from siglip:
|
||||
@@ -5369,6 +5458,8 @@ int clip_n_mmproj_embd(const struct clip_ctx * ctx) {
|
||||
return ctx->model.mm_model_mlp_3_w->ne[1];
|
||||
case PROJECTOR_TYPE_MINIMAX_M3:
|
||||
return ctx->model.mm_merger_fc2_b->ne[0];
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
return ctx->model.mm_2_w->ne[1];
|
||||
case PROJECTOR_TYPE_QWEN2VL:
|
||||
case PROJECTOR_TYPE_QWEN25VL:
|
||||
case PROJECTOR_TYPE_EXAONE4_5:
|
||||
|
||||
@@ -365,3 +365,8 @@ private:
|
||||
ggml_tensor * build_newline_row(ggml_context * ctx0);
|
||||
ggml_tensor * append_rowwise_newlines(ggml_context * ctx0, ggml_tensor * tile_output);
|
||||
};
|
||||
|
||||
struct clip_graph_muse_glimmer : clip_graph {
|
||||
clip_graph_muse_glimmer(clip_ctx * ctx, const clip_image_f32 & img) : clip_graph(ctx, img) {}
|
||||
ggml_cgraph * build() override;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "models.h"
|
||||
|
||||
// MuseGlimmer vision encoder: 50-layer ViT with 2D RoPE, sparse block-diagonal
|
||||
// window attention (every 4th + last layer global), pixel-shuffle downsample, then
|
||||
// adapter MLP + LLM's vision_projection.
|
||||
//
|
||||
// Several quantities are precomputed on host and fed as named graph inputs (filled in
|
||||
// clip.cpp set_input, PROJECTOR_TYPE_MUSE_GLIMMER branch):
|
||||
// muse_glimmer_pos_w/_h [n_tok] i32 : 1-indexed RoPE positions (sparse-permuted order)
|
||||
// muse_glimmer_sp_perm [n_tok] i32 : window grouping permutation (applied after ln_pre)
|
||||
// muse_glimmer_inv_perm [n_tok] i32 : inverse of sp_perm (applied after blocks)
|
||||
// muse_glimmer_ds_perm [n_tok] i32 : pixel-shuffle gather (original order)
|
||||
// muse_glimmer_sp_mask [n_tok, n_tok] f32 : block-diagonal window mask (sparse layers)
|
||||
ggml_cgraph * clip_graph_muse_glimmer::build() {
|
||||
const int ds = hparams.n_merge; // downsample factor (2)
|
||||
const int sf = hparams.muse_glimmer_sparse_factor; // 4
|
||||
const int n_tok = n_patches;
|
||||
const int n_out = (n_patches_x / ds) * (n_patches_y / ds);
|
||||
const float rope_base = hparams.rope_theta; // 10000
|
||||
|
||||
auto inp_i32 = [&](const char * name, int64_t n) {
|
||||
ggml_tensor * t = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n);
|
||||
ggml_set_name(t, name);
|
||||
ggml_set_input(t);
|
||||
return t;
|
||||
};
|
||||
|
||||
ggml_tensor * pos_w = inp_i32("muse_glimmer_pos_w", n_tok);
|
||||
ggml_tensor * pos_h = inp_i32("muse_glimmer_pos_h", n_tok);
|
||||
ggml_tensor * sp_perm = inp_i32("muse_glimmer_sp_perm", n_tok);
|
||||
ggml_tensor * inv_perm = inp_i32("muse_glimmer_inv_perm", n_tok);
|
||||
ggml_tensor * ds_perm = inp_i32("muse_glimmer_ds_perm", n_tok);
|
||||
|
||||
ggml_tensor * sp_mask = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tok, n_tok);
|
||||
ggml_set_name(sp_mask, "muse_glimmer_sp_mask");
|
||||
ggml_set_input(sp_mask);
|
||||
|
||||
// patchify via build_inp (conv2d over raw pixels) + bilinear-resized learned pos-emb
|
||||
ggml_tensor * x = build_inp(); // [n_embd, n_tok, 1]
|
||||
x = ggml_add(ctx0, x, resize_position_embeddings(GGML_SCALE_MODE_BILINEAR));
|
||||
cb(x, "after_posemb", -1);
|
||||
|
||||
// group patches into pgrid x pgrid windows (sparse attention order)
|
||||
x = ggml_get_rows(ctx0, x, sp_perm);
|
||||
cb(x, "after_sp_perm", -1);
|
||||
|
||||
// per-layer mask: sparse layers get sp_mask, global layers (every sf-th and last) get none
|
||||
std::vector<ggml_tensor *> attn_mask_layers(n_layer);
|
||||
for (int il = 0; il < n_layer; ++il) {
|
||||
const bool is_global = (il == n_layer - 1) || ((il + 1) % sf == 0);
|
||||
attn_mask_layers[il] = is_global ? nullptr : sp_mask;
|
||||
}
|
||||
|
||||
// 2D RoPE: first half of head_dim uses width pos, second half uses height pos
|
||||
auto add_pos = [&](ggml_tensor * cur, const clip_layer &) {
|
||||
return build_rope_2d(ctx0, cur, pos_w, pos_h, rope_base, false);
|
||||
};
|
||||
|
||||
build_vit_opts opts;
|
||||
opts.attn_mask_layers = std::move(attn_mask_layers);
|
||||
|
||||
// pre_ln, per-layer transformer, post_ln (all inside build_vit); reference uses exact (erf) GELU
|
||||
x = build_vit(x, n_tok, NORM_TYPE_NORMAL, FFN_GELU_ERF, nullptr, add_pos, opts);
|
||||
|
||||
// un-permute back to original grid order
|
||||
x = ggml_get_rows(ctx0, x, inv_perm);
|
||||
cb(x, "after_inv_perm", -1);
|
||||
|
||||
// pixel-shuffle downsample: gather f*f spatial neighbors then concat channel-outer.
|
||||
// out[c*(ds*ds)+s, o] = x[ds_perm gathered][o*(ds*ds)+s, c]
|
||||
x = ggml_get_rows(ctx0, x, ds_perm); // [n_embd, n_tok], grouped
|
||||
x = ggml_reshape_3d(ctx0, x, n_embd, ds * ds, n_out);// [c, s, o]
|
||||
x = ggml_permute(ctx0, x, 1, 0, 2, 3); // [s, c, o]
|
||||
x = ggml_cont(ctx0, x);
|
||||
x = ggml_reshape_2d(ctx0, x, n_embd * ds * ds, n_out); // [6144, n_out]
|
||||
cb(x, "encoder_out", -1);
|
||||
|
||||
// adapter (6144->4096->4096, exact GELU each) + LLM vision_projection (4096->6656)
|
||||
x = build_mm(model.mm_0_w, x);
|
||||
x = ggml_gelu_erf(ctx0, x);
|
||||
x = build_mm(model.mm_1_w, x);
|
||||
x = ggml_gelu_erf(ctx0, x);
|
||||
x = build_mm(model.mm_2_w, x); // [6656, n_out]
|
||||
cb(x, "projected", -1);
|
||||
|
||||
ggml_build_forward_expand(gf, x);
|
||||
return gf;
|
||||
}
|
||||
@@ -1615,3 +1615,65 @@ mtmd_image_preproc_out mtmd_image_preprocessor_granite::preprocess(const clip_im
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
//
|
||||
// mtmd_image_preprocessor_muse_glimmer
|
||||
//
|
||||
|
||||
// Replicates transformers' get_aspect_ratio_preserving_size
|
||||
static clip_image_size muse_glimmer_grid_size(int img_w, int img_h, int patch_hw, int max_tokens) {
|
||||
double i_nph = (double) img_h / patch_hw;
|
||||
double i_npw = (double) img_w / patch_hw;
|
||||
const double ratio = i_nph > 0.0 ? i_npw / i_nph : 1.0;
|
||||
if (i_nph * i_npw > (double) max_tokens) {
|
||||
i_nph = std::sqrt((double) max_tokens / ratio);
|
||||
i_npw = i_nph * ratio;
|
||||
}
|
||||
const int hs[2] = { (int) std::floor(i_nph), (int) std::ceil(i_nph) };
|
||||
const int ws[2] = { (int) std::floor(i_npw), (int) std::ceil(i_npw) };
|
||||
const double target_ar = (double) img_h / (double) img_w;
|
||||
int best_nph = -1;
|
||||
int best_npw = -1;
|
||||
double best_d = 0.0;
|
||||
for (int a = 0; a < 2; ++a) {
|
||||
for (int b = 0; b < 2; ++b) {
|
||||
const int nph = hs[a];
|
||||
const int npw = ws[b];
|
||||
if (nph < 1 || npw < 1 || nph * npw > max_tokens) {
|
||||
continue;
|
||||
}
|
||||
const double d = std::fabs((double) nph / (double) npw - target_ar);
|
||||
const int n_tokens = nph * npw;
|
||||
const int best_n_tokens = best_nph * best_npw;
|
||||
if (best_nph < 0 || d < best_d || (d == best_d && n_tokens > best_n_tokens)) {
|
||||
best_nph = nph;
|
||||
best_npw = npw;
|
||||
best_d = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (best_nph < 0) { // no candidate fit under the cap: round and clamp
|
||||
best_nph = std::max(1, (int) std::lround(i_nph));
|
||||
best_npw = std::max(1, (int) std::lround(i_npw));
|
||||
}
|
||||
return clip_image_size{ best_npw * patch_hw, best_nph * patch_hw };
|
||||
}
|
||||
|
||||
mtmd_image_preproc_out mtmd_image_preprocessor_muse_glimmer::preprocess(const clip_image_u8 & img) {
|
||||
const int patch_hw = hparams.patch_size * hparams.n_merge;
|
||||
const int patch_area = hparams.patch_size * hparams.patch_size * hparams.n_merge * hparams.n_merge;
|
||||
GGML_ASSERT(patch_area > 0 && hparams.image_max_pixels > 0);
|
||||
const int max_tokens = hparams.image_max_pixels / patch_area;
|
||||
|
||||
const clip_image_size original_size = img.get_size();
|
||||
const clip_image_size target_size = muse_glimmer_grid_size(
|
||||
original_size.width, original_size.height, patch_hw, max_tokens);
|
||||
|
||||
// PIL resizes directly to (target_w, target_h) -- a stretch, no padding.
|
||||
clip_image_u8 resized_image;
|
||||
img_tool::resize(img, resized_image, target_size, hparams.image_resize_algo, PAD_NONE);
|
||||
|
||||
mtmd_image_preproc_out output;
|
||||
output.append(hparams, resized_image, true);
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -230,3 +230,9 @@ struct mtmd_image_preprocessor_granite : mtmd_image_preprocessor_llava_uhd {
|
||||
mtmd_image_preprocessor_granite(const clip_ctx * ctx) : mtmd_image_preprocessor_llava_uhd(ctx) {}
|
||||
mtmd_image_preproc_out preprocess(const clip_image_u8 & img) override;
|
||||
};
|
||||
|
||||
// pick the patch grid closest to the input aspect ratio under the per-image token cap, stretch-resize.
|
||||
struct mtmd_image_preprocessor_muse_glimmer : mtmd_image_preprocessor {
|
||||
mtmd_image_preprocessor_muse_glimmer(const clip_ctx * ctx) : mtmd_image_preprocessor(ctx) {}
|
||||
mtmd_image_preproc_out preprocess(const clip_image_u8 & img) override;
|
||||
};
|
||||
|
||||
@@ -699,6 +699,12 @@ struct mtmd_context {
|
||||
img_end = "]<]end of image[>[";
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_dyn_size>(ctx_v);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_MUSE_GLIMMER:
|
||||
{
|
||||
img_beg = "<|image_start|>";
|
||||
img_end = "<|image_end|>";
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_muse_glimmer>(ctx_v);
|
||||
} break;
|
||||
case PROJECTOR_TYPE_YOUTUVL:
|
||||
{
|
||||
// <|vision_start|> ... (image embeddings) ... <|vision_end|>
|
||||
|
||||
Reference in New Issue
Block a user