mirror of
https://github.com/LostRuins/koboldcpp.git
synced 2026-09-18 16:55:14 +02:00
Merge commit '32eddaf2ea8dd5d499dee9655592a89b91bfde9d' into concedo_experimental
# Conflicts: # docs/multimodal.md # docs/preset.md # ggml/src/ggml-hexagon/ggml-hexagon.cpp # ggml/src/ggml-hexagon/htp/CMakeLists.txt # ggml/src/ggml-hexagon/htp/flash-attn-ops.c # ggml/src/ggml-hexagon/htp/hex-dma.h # ggml/src/ggml-hexagon/htp/hex-utils.h # ggml/src/ggml-hexagon/htp/hmx-flash-attn-ops.c # ggml/src/ggml-hexagon/htp/hmx-matmul-ops.c # ggml/src/ggml-hexagon/htp/hmx-queue.c # ggml/src/ggml-hexagon/htp/hmx-queue.h # ggml/src/ggml-hexagon/htp/htp-ctx.h # ggml/src/ggml-hexagon/htp/htp-ops.h # ggml/src/ggml-hexagon/htp/main.c # ggml/src/ggml-hexagon/htp/matmul-ops.c # scripts/snapdragon/ggml-hexagon-profile.py # scripts/ui-assets.cmake # tools/export-lora/README.md # tools/server/CMakeLists.txt
This commit is contained in:
+105
-86
@@ -114,7 +114,7 @@ struct mtmd_image_tokens {
|
||||
// true if one of entries in batch_f32 is a placeholder
|
||||
bool is_placeholder() const {
|
||||
for (const auto & entry : batch_f32.entries) {
|
||||
if (entry->is_placeholder()) {
|
||||
if (entry.is_placeholder()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ struct mtmd_audio_tokens {
|
||||
// true if one of entries in batch_f32 is a placeholder
|
||||
bool is_placeholder() const {
|
||||
for (const auto & entry : batch_f32.entries) {
|
||||
if (entry->is_placeholder()) {
|
||||
if (entry.is_placeholder()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -516,6 +516,7 @@ struct mtmd_context {
|
||||
LOG_WRN("%s: llama 4 vision is known to have degraded quality:\n"
|
||||
" https://github.com/ggml-org/llama.cpp/pull/13282\n", __func__);
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_llava_uhd>(ctx_v);
|
||||
ov_img_first = false;
|
||||
} break;
|
||||
case PROJECTOR_TYPE_STEP3VL:
|
||||
{
|
||||
@@ -539,6 +540,7 @@ struct mtmd_context {
|
||||
img_beg = "<img>";
|
||||
img_end = "</img>";
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_internvl>(ctx_v);
|
||||
ov_img_first = false;
|
||||
} break;
|
||||
case PROJECTOR_TYPE_KIMIVL:
|
||||
{
|
||||
@@ -615,11 +617,13 @@ struct mtmd_context {
|
||||
{
|
||||
img_end = "\n"; // prevent empty batch on llama-server
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_deepseekocr>(ctx_v);
|
||||
ov_img_first = false;
|
||||
} break;
|
||||
case PROJECTOR_TYPE_DEEPSEEKOCR2:
|
||||
{
|
||||
img_end = "\n"; // prevent empty batch on llama-server
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_deepseekocr2>(ctx_v);
|
||||
ov_img_first = false;
|
||||
} break;
|
||||
case PROJECTOR_TYPE_HUNYUANVL:
|
||||
{
|
||||
@@ -640,6 +644,7 @@ struct mtmd_context {
|
||||
img_beg = "<image>";
|
||||
img_end = "";
|
||||
image_preproc = std::make_unique<mtmd_image_preprocessor_granite>(ctx_v);
|
||||
ov_img_first = true;
|
||||
} break;
|
||||
default:
|
||||
throw std::runtime_error(string_format("%s: unexpected vision projector type %d\n", __func__, proj));
|
||||
@@ -1050,7 +1055,7 @@ struct mtmd_tokenizer {
|
||||
|
||||
// TODO @ngxson : this is quite hacky because preprocessor only support batch with one single element, that need to be fixed in the future (e.g. by changing the preprocessor interface always take single input)
|
||||
|
||||
clip_image_f32_batch batch_f32;
|
||||
mtmd_image_preproc_out preproc_out;
|
||||
|
||||
for (const auto * bmp : bitmaps) {
|
||||
// sanity check
|
||||
@@ -1063,44 +1068,54 @@ struct mtmd_tokenizer {
|
||||
}
|
||||
|
||||
// convert mtmd_bitmap to clip_image_u8
|
||||
clip_image_u8_ptr img_u8(clip_image_u8_init());
|
||||
img_u8->set_size(
|
||||
clip_image_u8 img_u8;
|
||||
img_u8.set_size(
|
||||
{(int)bmp->nx, (int)bmp->ny},
|
||||
bmp->is_placeholder());
|
||||
img_u8->cpy_buf(bmp->get_ro_buf());
|
||||
img_u8.cpy_buf(bmp->get_ro_buf());
|
||||
|
||||
// preprocess image
|
||||
clip_image_f32_batch tmp_batch;
|
||||
bool ok = ctx->image_preproc->preprocess(*img_u8, tmp_batch);
|
||||
if (!ok) {
|
||||
LOG_ERR("Unable to preprocess image\n");
|
||||
return 2;
|
||||
}
|
||||
mtmd_image_preproc_out tmp_preproc_out = ctx->image_preproc->preprocess(img_u8);
|
||||
|
||||
// move entries and grid dimensions to the "global" batch_f32
|
||||
for (auto & entry : tmp_batch.entries) {
|
||||
batch_f32.entries.emplace_back(std::move(entry));
|
||||
// move entries and grid dimensions to the "global" preproc_out
|
||||
for (auto & entry : tmp_preproc_out.entries) {
|
||||
preproc_out.entries.emplace_back(std::move(entry));
|
||||
}
|
||||
|
||||
// for llava-uhd style, we need to handle grid too
|
||||
// we don't care about overwriting these values for now because llama-uhd doesn't support batching anyway
|
||||
batch_f32.grid_x = tmp_batch.grid_x;
|
||||
batch_f32.grid_y = tmp_batch.grid_y;
|
||||
// we don't care about overwriting these values for now because the case where bitmaps.size() > 1 is only for frame merging (qwen-vl), not supported by llava-uhd
|
||||
if ((tmp_preproc_out.grid_x > 0 && tmp_preproc_out.grid_y > 0)
|
||||
|| tmp_preproc_out.has_overview()) {
|
||||
GGML_ASSERT(bitmaps.size() == 1);
|
||||
preproc_out.grid_x = tmp_preproc_out.grid_x;
|
||||
preproc_out.grid_y = tmp_preproc_out.grid_y;
|
||||
preproc_out.overview = std::move(tmp_preproc_out.overview);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("%s: preproc_out has %zu entries, grid_x = %d, grid_y = %d, has_overview = %d\n",
|
||||
__func__, preproc_out.entries.size(), preproc_out.grid_x, preproc_out.grid_y,
|
||||
preproc_out.has_overview() ? 1 : 0);
|
||||
|
||||
// handle llava-uhd style preprocessing
|
||||
const bool has_tiling_grid = batch_f32.grid_x > 0 && batch_f32.grid_y > 0;
|
||||
// (output either a grid, or overview-only)
|
||||
const bool has_tiling_grid = (preproc_out.grid_x > 0 && preproc_out.grid_y > 0)
|
||||
|| preproc_out.has_overview();
|
||||
|
||||
if (has_tiling_grid) {
|
||||
// [QWEN_VIDEO] we do not support "frame merging" for llama-uhd style, so no batching for now
|
||||
GGML_ASSERT(bitmaps.size() == 1);
|
||||
|
||||
const int n_col = batch_f32.grid_x;
|
||||
const int n_row = batch_f32.grid_y;
|
||||
const int n_col = preproc_out.grid_x;
|
||||
const int n_row = preproc_out.grid_y;
|
||||
|
||||
// split batch into chunks of single images
|
||||
// NOTE: batch_f32 will be invalidated after this call
|
||||
auto chunks = split_batch_to_chunk(std::move(batch_f32), bitmaps[0]->id);
|
||||
auto chunks = split_batch_to_chunk(std::move(preproc_out), bitmaps[0]->id);
|
||||
GGML_ASSERT(chunks.size() > 0);
|
||||
|
||||
// NOTE: preproc_out is invalidated after this point, do not use it anymore
|
||||
|
||||
// split_batch_to_chunk must always put the overview image first
|
||||
auto ov_chunk = std::move(chunks.front());
|
||||
chunks.erase(chunks.begin());
|
||||
|
||||
@@ -1127,7 +1142,16 @@ struct mtmd_tokenizer {
|
||||
std::snprintf(buf.get(), sz, ctx->sli_img_start_tmpl.c_str(), y+1, x+1);
|
||||
add_text(std::string(buf.get(), buf.get() + sz - 1), true);
|
||||
}
|
||||
cur.entries.emplace_back(std::move(chunks[y * n_col + x]));
|
||||
|
||||
auto & curr_chunk = chunks[y * n_col + x];
|
||||
auto & curr_batch = curr_chunk.tokens_image->batch_f32;
|
||||
if (curr_batch.entries.size() != 1) {
|
||||
throw std::runtime_error(string_format("%s: expect 1 image in batch_f32", __func__));
|
||||
}
|
||||
|
||||
LOG_DBG("%s: adding slice image at row %d col %d\n", __func__, y, x);
|
||||
cur.entries.emplace_back(std::move(curr_chunk));
|
||||
|
||||
add_text(ctx->tok_sli_img_end);
|
||||
if (!is_last_in_row) {
|
||||
add_text(ctx->tok_sli_img_mid);
|
||||
@@ -1149,9 +1173,14 @@ struct mtmd_tokenizer {
|
||||
|
||||
} else {
|
||||
|
||||
if (preproc_out.entries.size() == 0) {
|
||||
LOG_ERR("%s: no image tokens produced by preprocessor (ref: https://github.com/ggml-org/llama.cpp/pull/24769)\n", __func__);
|
||||
return 2;
|
||||
}
|
||||
|
||||
size_t n_tokens = 0;
|
||||
for (const auto & e : batch_f32.entries) {
|
||||
n_tokens += clip_n_output_tokens(ctx->ctx_v, e.get());
|
||||
for (auto & e : preproc_out.entries) {
|
||||
n_tokens += clip_n_output_tokens(ctx->ctx_v, &e);
|
||||
if (clip_model_n_temporal_merge(ctx->ctx_v) == 2) {
|
||||
// [QWEN_VIDEO] pair input is merged to the same embd, so only count as one image
|
||||
break;
|
||||
@@ -1165,8 +1194,8 @@ struct mtmd_tokenizer {
|
||||
|
||||
if (mtmd_decode_use_mrope(ctx)) {
|
||||
// for Qwen2VL, we need this information for M-RoPE decoding positions
|
||||
image_tokens->nx = clip_n_output_tokens_x(ctx->ctx_v, batch_f32.entries[0].get());
|
||||
image_tokens->ny = clip_n_output_tokens_y(ctx->ctx_v, batch_f32.entries[0].get());
|
||||
image_tokens->nx = clip_n_output_tokens_x(ctx->ctx_v, &preproc_out.entries[0]);
|
||||
image_tokens->ny = clip_n_output_tokens_y(ctx->ctx_v, &preproc_out.entries[0]);
|
||||
} else {
|
||||
// other models, we only need the total number of tokens
|
||||
image_tokens->nx = n_tokens;
|
||||
@@ -1181,6 +1210,12 @@ struct mtmd_tokenizer {
|
||||
image_tokens->image_idx = n_images_added;
|
||||
GGML_ASSERT(n_tokens == (size_t)image_tokens->n_tokens());
|
||||
}
|
||||
|
||||
clip_image_f32_batch batch_f32;
|
||||
batch_f32.is_audio = false;
|
||||
batch_f32.entries = std::move(preproc_out.entries);
|
||||
// do NOT use preproc_out from this point on, it's moved
|
||||
|
||||
image_tokens->batch_f32 = std::move(batch_f32);
|
||||
image_tokens->id = bitmaps[0]->id; // optional
|
||||
|
||||
@@ -1260,13 +1295,13 @@ struct mtmd_tokenizer {
|
||||
for (auto & mel_spec : mel_spec_chunks) {
|
||||
const bool is_placeholder = mel_spec.data.empty();
|
||||
|
||||
clip_image_f32_ptr mel_f32(clip_image_f32_init());
|
||||
mel_f32->set_size(
|
||||
clip_image_f32 mel_f32;
|
||||
mel_f32.set_size(
|
||||
{mel_spec.n_len, mel_spec.n_mel},
|
||||
is_placeholder, /* is_audio */ true);
|
||||
mel_f32->cpy_buf(mel_spec.data);
|
||||
mel_f32.cpy_buf(mel_spec.data);
|
||||
|
||||
size_t n_tokens = clip_n_output_tokens(ctx->ctx_a, mel_f32.get());
|
||||
size_t n_tokens = clip_n_output_tokens(ctx->ctx_a, &mel_f32);
|
||||
|
||||
clip_image_f32_batch batch_f32;
|
||||
batch_f32.is_audio = true;
|
||||
@@ -1296,16 +1331,18 @@ struct mtmd_tokenizer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<mtmd_input_chunk> split_batch_to_chunk(clip_image_f32_batch && batch_f32, const std::string & id) {
|
||||
std::vector<mtmd_input_chunk> split_batch_to_chunk(mtmd_image_preproc_out && preproc_out, const std::string & id) {
|
||||
std::vector<mtmd_input_chunk> chunks;
|
||||
|
||||
for (auto & entry : batch_f32.entries) {
|
||||
auto process_chunk = [&](clip_image_f32 && img) {
|
||||
mtmd_image_tokens_ptr image_tokens(new mtmd_image_tokens);
|
||||
image_tokens->nx = clip_n_output_tokens(ctx->ctx_v, entry.get());
|
||||
image_tokens->nx = clip_n_output_tokens(ctx->ctx_v, &img);
|
||||
image_tokens->ny = 1;
|
||||
image_tokens->batch_f32.entries.push_back(std::move(entry));
|
||||
image_tokens->batch_f32.entries.push_back(std::move(img));
|
||||
image_tokens->id = id;
|
||||
|
||||
GGML_ASSERT(image_tokens->nx > 0);
|
||||
|
||||
mtmd_input_chunk chunk{
|
||||
MTMD_INPUT_CHUNK_TYPE_IMAGE,
|
||||
{}, // text tokens
|
||||
@@ -1313,6 +1350,21 @@ struct mtmd_tokenizer {
|
||||
nullptr, // audio tokens
|
||||
};
|
||||
chunks.emplace_back(std::move(chunk));
|
||||
};
|
||||
|
||||
// overview image first
|
||||
auto & overview = preproc_out.overview;
|
||||
if (overview.nx() == 0 || overview.ny() == 0) {
|
||||
throw std::runtime_error(string_format("%s: invalid overview image for llava-uhd style preprocessing\n", __func__));
|
||||
}
|
||||
process_chunk(std::move(preproc_out.overview));
|
||||
|
||||
// then, process slices
|
||||
for (auto & entry : preproc_out.entries) {
|
||||
if (entry.nx() == 0 || entry.ny() == 0) {
|
||||
throw std::runtime_error(string_format("%s: invalid image slice for llava-uhd style preprocessing\n", __func__));
|
||||
}
|
||||
process_chunk(std::move(entry));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
@@ -1386,57 +1438,22 @@ static int32_t mtmd_encode_impl(mtmd_context * ctx, const mtmd_image_tokens * im
|
||||
LOG_ERR("%s: this API does not support non-vision input, please use mtmd_encode_chunk instead\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
auto proj_type = clip_get_projector_type(ctx_clip);
|
||||
|
||||
int n_embd_out = ctx->n_embd_out();
|
||||
auto n_tokens_out = image_tokens->n_tokens();
|
||||
out_embd.resize((size_t)n_embd_out * n_tokens_out);
|
||||
|
||||
bool ok = false;
|
||||
|
||||
if (clip_is_llava(ctx_clip)
|
||||
|| proj_type == PROJECTOR_TYPE_MINICPMV
|
||||
|| proj_type == PROJECTOR_TYPE_GLM_EDGE
|
||||
|| proj_type == PROJECTOR_TYPE_INTERNVL
|
||||
|| proj_type == PROJECTOR_TYPE_DEEPSEEKOCR2
|
||||
|| proj_type == PROJECTOR_TYPE_GRANITE4_VISION) {
|
||||
// TODO @ngxson : llava does not support batched encoding ; this should be fixed inside clip_image_batch_encode()
|
||||
const auto & entries = image_tokens->batch_f32.entries;
|
||||
// entries may have different token counts
|
||||
// e.g., DeepSeek-OCR-2: 144 per tile views, 257 for the global view
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < entries.size(); i++) {
|
||||
if (entries[i]->is_placeholder()) {
|
||||
LOG_ERR("%s: image tokens batch entry %zu is placeholder\n", __func__, i);
|
||||
return 1;
|
||||
}
|
||||
int n_tokens_per_image = clip_n_output_tokens(ctx_clip, entries[i].get());
|
||||
std::vector<float> tmp_embd((size_t)n_tokens_per_image * n_embd_out);
|
||||
bool ok_i = clip_image_encode(
|
||||
ctx_clip,
|
||||
ctx->n_threads,
|
||||
entries[i].get(),
|
||||
tmp_embd);
|
||||
if (!ok_i) {
|
||||
LOG_ERR("%s: failed to encode image %zu\n", __func__, i);
|
||||
return 1;
|
||||
}
|
||||
ok = true;
|
||||
std::copy(tmp_embd.begin(), tmp_embd.end(), out_embd.begin() + offset);
|
||||
offset += static_cast<size_t>(n_embd_out) * n_tokens_per_image;
|
||||
}
|
||||
} else {
|
||||
if (image_tokens->is_placeholder()) {
|
||||
LOG_ERR("%s: image tokens batch is placeholder\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
ok = clip_image_batch_encode(
|
||||
ctx_clip,
|
||||
ctx->n_threads,
|
||||
&image_tokens->batch_f32,
|
||||
out_embd);
|
||||
if (image_tokens->is_placeholder()) {
|
||||
LOG_ERR("%s: image tokens batch is placeholder\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = clip_image_batch_encode(
|
||||
ctx_clip,
|
||||
ctx->n_threads,
|
||||
&image_tokens->batch_f32,
|
||||
out_embd);
|
||||
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
@@ -2075,16 +2092,18 @@ void mtmd_debug_preprocess_image(mtmd_context * ctx, const std::vector<uint8_t>
|
||||
clip_image_u8 img_u8;
|
||||
img_u8.set_size({nx, ny}, false);
|
||||
img_u8.cpy_buf(rgb_values);
|
||||
clip_image_f32_batch batch_f32;
|
||||
GGML_ASSERT(ctx->image_preproc != nullptr);
|
||||
bool ok = ctx->image_preproc->preprocess(img_u8, batch_f32);
|
||||
if (!ok) {
|
||||
LOG_ERR("%s: failed to preprocess image\n", __func__);
|
||||
return;
|
||||
mtmd_image_preproc_out preproc_out = ctx->image_preproc->preprocess(img_u8);
|
||||
|
||||
clip_image_f32_batch batch_f32;
|
||||
batch_f32.is_audio = false;
|
||||
for (auto & entry : preproc_out.entries) {
|
||||
batch_f32.entries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
LOG_INF("%s: preprocessed image to batch_f32 with %d entries\n", __func__, (int)batch_f32.entries.size());
|
||||
for (size_t i = 0; i < batch_f32.entries.size(); i++) {
|
||||
LOG_INF("%s: entry %zu has nx=%d, ny=%d\n", __func__, i, batch_f32.entries[i]->nx(), batch_f32.entries[i]->ny());
|
||||
LOG_INF("%s: entry %zu has nx=%d, ny=%d\n", __func__, i, batch_f32.entries[i].nx(), batch_f32.entries[i].ny());
|
||||
// TODO: better way to dump entry content?
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user