-
Couldn't load subscription status.
- Fork 13.4k
Qwen vl bounding box and overall vision fix #16745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This is just a quick and dirty demonstration.
| /*.output =*/ udata->output.data(), | ||
| /*.kv_position_of_token=*/ udata->kv_position_of_token.data(), | ||
| /*.data =*/ std::move(udata), | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: reorder so that kv_position_of_token is last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about what you mean by that. It can't be after .data because it should be treated like other members of the update struct
| { | ||
| if (ubatch->kv_position_of_token[i] != -1) | ||
| map_kv_to_batch[ubatch->kv_position_of_token[i]] = i; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're filling a vector of int32_t's but iterating over uint32_t's, this is bound to trigger type conversion checks. Just iterate over int32_t.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check to minimize casts, thanks for the reminder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick question: do we still need to provide the absolute (KV) position via llama_batch? I'm not sure what happen if the image is separated into multiple llama_batch before llama_decode
| std::vector<llama_seq_id> seq_id_unq; | ||
| std::vector<int32_t> seq_idx; | ||
| std::vector<int8_t> output; | ||
| std::vector<int32_t> kv_position_of_token;//when pushed to the kv cache, where is the token pushed (used for causal masking) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest a different naming for it, either kv_pos or abs_pos (absolute position). But it's up to @ggerganov to decide the naming here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was extra pedantic here :-)
| llama_pos mtmd_image_tokens_get_n_pos(const mtmd_image_tokens * image_tokens) { | ||
| if (image_tokens->use_mrope_pos) { | ||
| return 1; // for M-RoPE, the whole image is 1 in temporal dimension | ||
| return (::std::max)(image_tokens->nx, image_tokens->ny);//assuming image, not video // for M-RoPE, the whole image is 1 in temporal dimension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return (::std::max)(image_tokens->nx, image_tokens->ny);//assuming image, not video // for M-RoPE, the whole image is 1 in temporal dimension | |
| return std::max(image_tokens->nx, image_tokens->ny); // for M-RoPE, the image takes max(t, w, h) positions; t is omitted because we don't support video input |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding (::std::max), I am being extra careful because sometimes on windows max is a preprocessor definition and it breaks std::max. Parenthesis stop that behavior. If this is prevented for sure in llamacpp, we can remove them. And I will remove the useless :: scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the comment, it is not exactly "t", much like k.t with k a frame rate dependent constant and I will update the comment with the actual expression. I have some needs for video encoding in my side and may try and implement it in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am being extra careful because sometimes on windows max is a preprocessor definition and it breaks std::max
std::max is used in multiple places across llama.cpp code base, there is nothing wrong with it. you should look to see how other files have certain #define to resolve the problem
| //if (p0 >= 0) { | ||
| // bool ok = true; | ||
| // | ||
| // if (batch.token) { | ||
| // if (seq_pos_min(s) != p0 + 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this code block is safe to remove, better remove it altogether
Applied changes from llama.cpp PR #16745 to fix cache causal masking issues for Qwen2.5 VL models. Key changes: - Disabled consecutive position validation in llama-batch.cpp (allows position jumps for vision embeddings) - Added kv_position_of_token field to track KV cache positions for proper causal masking - Modified causal masking logic to use batch positions instead of temporal positions - Updated M-RoPE position calculation to use max(nx, ny) for images This fix allows Qwen VL models to handle non-consecutive positions in embeddings, which is required for proper vision processing. Ref: ggml-org/llama.cpp#16745
From my understanding, the only use of the kv positions is causal making, which only needs to know if the considered token is after the current token. The batches should be inserted sequentially to the kV cache, so only the last batch matters and "after the current token in the current batch" means "after the current token". If apply_ubatch is not called on the last inserted ubatch, we do have a problem, yes, because my code only checks causality within the current batch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR in #15474 that I reviewed sometime ago seemed like a better solution. This PR makes an invalid assumption about the KV cell indices - namely that they can be used for causality checks.
| } | ||
|
|
||
| cells.pos_set(idx, ubatch.pos[i]); | ||
| ubatch.kv_position_of_token[i] = (int32_t)idx;//set the position in the kv cache as a property for this token (needed for proper causal masking) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not work - we should never use the idx in the KV cache buffer for causality checks. Even if this works sometimes, in general nothing guarantees that the cache is ordered causally in memory. The simplest scenario in which this breaks is multiple sequences with unified KV cache.
So it looks like we really need, explicitly, another sequence 0,1,2,3,... (as illustrated in your review) that's indepedent of KV cell indices and dedicated to causual masking. |
I see what you mean @ggerganov . The main issue is not actually to store the new sequence of positions, but rather the fact that most code assumes that the base value for positional encoding is equal to the token age in the cache. This in turn means @rujialiu 's #15474 solution PLUS modifying mtmd_image_tokens_get_n_pos() to return both the kv cache sequence position and the positional encoding position, and updating everything that depends on these. |
This commit introduces comprehensive support for Qwen3-VL vision-language models, including both the dense variant and the Mixture-of-Experts (MoE) architecture with DeepStack fusion capabilities. ## Overview Qwen3-VL represents Alibaba's advanced multimodal models capable of understanding and reasoning about images alongside text. This implementation enables running these models for various vision-language tasks including image understanding, optical character recognition (OCR), visual question answering, and document analysis. ## Architecture Implementation ### Core Architecture (llama-arch.cpp/h) - **LLM_ARCH_QWEN3_VL**: Dense vision-language model architecture - **LLM_ARCH_QWEN3_VL_MOE**: Mixture-of-Experts variant with expert routing - Complete tensor mapping registration for both architectures - Architecture-specific parameter handling and validation ### Model Loading (llama-model.cpp) **Hyperparameter Loading** - QWEN3_VL: Standard dense model configuration * Uses full n_embd dimension throughout * 36 layers for 4B parameter variant - QWEN3_VL_MOE: Expert-based configuration * 4x n_embd expansion (n_embd/4 per channel × 4 channels) * 48 layers (30B-A3B) or 94 layers (235B-A22B) * Expert feed-forward network dimensions **Multi-axis Rotary Position Embedding (M-RoPE)** - Configured rope_sections = [24, 20, 20, 0] * Temporal dimension: 24 dims * Height dimension: 20 dims * Width dimension: 20 dims * Unused dimension: 0 - Enables spatial awareness for image patch processing - Added debug logging for MRoPE configuration verification **Tensor Initialization** - QWEN3_VL follows QWEN3 dense structure * Token embeddings, output projection * Per-layer: attention (Q/K/V/O), normalization, FFN - QWEN3_VL_MOE includes expert-specific tensors * Expert gate networks for routing * Per-expert FFN weights (gate, down, up) * Shared and expert-specific parameters ### Graph Building (llama-graph.cpp/h) **DeepStack Architecture for MoE** The Qwen3-VL-MoE variant implements a novel DeepStack fusion mechanism: 1. **Channel Splitting**: Vision embeddings split into 3 processing channels - ds0, ds1, ds2 (DeepStack channels 0, 1, 2) - Each channel: n_embd/4 dimensions 2. **Per-layer Processing**: Independent expert selection per channel - Token-level expert routing - Gated mixture-of-experts computation - Q/K normalization before attention 3. **Fusion Layers**: Learned merging at early transformer layers - Fusion occurs at layers 0, 1, and 2 - DeepStack merger combines information across channels - Only active when vision embeddings present (text-only safe) **Batch Processing** - Enhanced position array handling for M-RoPE multi-dimensional positions - Proper ubatch preparation distinguishing vision vs text tokens - Conditional graph construction based on modality ### Vision Processing (clip.cpp/clip-impl.h) **PROJECTOR_TYPE_QWEN3VLMOE** - New projector type for Qwen3-VL-MoE vision encoder - Handles projection from vision encoder to language model space **DeepStack Merger Implementation** The merger is a learnable 2-layer MLP with normalization: ``` Input (3 channels) → LayerNorm(norm_w, norm_b) → Linear(fc1_w, fc1_b) → GELU activation → Linear(fc2_w, fc2_b) → Output (fused representation) ``` Components: - `norm_w`, `norm_b`: Layer normalization parameters - `fc1_w`, `fc1_b`: First linear projection - `fc2_w`, `fc2_b`: Second linear projection **Spatial Operations** - Fixed spatial merge for vision patch sequences - Proper handling of patch grid dimensions - Vision-text boundary management **Safety Improvements** - Removed illegal zero-tensor initialization for text-only inputs - Conditional fusion: only processes when vision embeddings exist - Prevents memory access violations in text-only inference ### Platform Support (llama-model-loader.cpp) **Windows File Handle Limit** - Increased stdio limit to 2048 handles (from default ~512) - Critical for MoE models with many expert weight files - Uses `_setmaxstdio()` on Windows platform - Prevents "too many open files" errors during loading ### Reference Patches (llama/patches/) Included for transparency and reproducibility: - `0033-qwen3vl-base-architecture.patch` - `0034-qwen3vl-deepstack-implementation.patch` - `0035-qwen3vl-memory-fix.patch` - `0036-qwen3vl-layer-norm-bias.patch` ## Technical Specifications ### Qwen3-VL (Dense) - **Type**: Standard transformer with integrated vision encoder - **Layers**: 36 (4B parameter model) - **Embedding**: Full n_embd dimension - **Position Encoding**: M-RoPE with 4 dimensional sections - **Use Cases**: General vision-language understanding ### Qwen3-VL-MoE (Mixture of Experts) - **Type**: Sparse MoE with DeepStack fusion - **Layers**: 48 (30B activated/3B) or 94 (235B activated/22B) - **Embedding**: 4-channel architecture (n_embd/4 per channel) - **Experts**: Multiple expert networks per layer with learned routing - **Fusion**: 3-layer early fusion (layers 0, 1, 2) - **Use Cases**: High-quality vision understanding at improved efficiency ### DeepStack Fusion Mechanism The multi-channel fusion enables: 1. **Parallel Processing**: Different aspects of vision processed independently 2. **Early Integration**: Information merged in early transformer layers 3. **Adaptive Routing**: Expert selection per channel and token 4. **Efficiency**: Sparse activation patterns reduce computation ## Capabilities Enabled This implementation supports: - **Multimodal Chat**: Conversational AI with image understanding - **Image Captioning**: Detailed image descriptions - **Visual Question Answering**: Answer questions about image content - **Optical Character Recognition**: Extract text from images - **Document Understanding**: Analyze documents, tables, charts - **Image Analysis**: Detailed visual scene understanding ## References and Acknowledgments This implementation is based on the outstanding work by the community: **Primary Source Repository** - Branch: https://github.com/LETS-BEE/llama.cpp/commits/qwen3vl/ - Author: LETS-BEE **Source Commits** (applied in llama/patches/): 1. Base Architecture LETS-BEE/llama.cpp@9971912 2. DeepStack Implementation LETS-BEE/llama.cpp@b913e89 3. Memory Access Fix LETS-BEE/llama.cpp@de0e3d3 4. Layer Normalization Update LETS-BEE/llama.cpp@e45aecb **Related Discussions and Pull Requests** - Upstream llama.cpp Discussion: ggml-org/llama.cpp#16207 (comment) - Upstream llama.cpp PR: ggml-org/llama.cpp#16745 - Related Ollama PR: ollama#12665 **Additional Context** - OCR-related discussion: ggml-org/llama.cpp#16764 ## Testing Tested with: - Qwen3-VL 4B parameter models (dense) - Qwen3-VL-MoE 30B-A3B models (MoE) - Various image understanding tasks - Text-only and multimodal inference modes ## Future Work Potential enhancements: - Additional model size variants - Performance optimizations for DeepStack fusion - Extended M-RoPE configuration options - Enhanced vision preprocessing pipelines --- Special thanks to the llama.cpp community and all contributors who made this multimodal vision-language support possible.
Yes, that's why I didn't go further. I hoped to come up with a correct solution that has the following property:
However, 2 is difficult because, we need to go over lots of codes and think about "which And trying to keep 1 is even more difficult. |
|
@rujialiu I know a way to see the implications of such a change. We change the output of mtmd_image_tokens_get_n_pos() to an opaque struct and see what that breaks, and we start figuring things out by propagating the opaque struct and fixing things as we go. |
That's a brilliant idea! |
This PR adresses the root cause of issue #13694 which has been stale for quite a long time.
It is an alternate solution to #15474 and is necessary because pull request #15474 causes incorrect positions to be used for positional encoding after an image.
The root issue is discussed in #13694 and #15474 and comes down to this: for models with positional encoding, llamacpp expects token positions in the KV cache to be strictly increasing for causal masking, but Qwen VL and possibly others have a token pos that do not respect this (they can decrease for instance).
Although this PR does incur one root change inside ubatch (introducing a new field to store the position at which the token was inserted in the KV cache), positional encodings match transformer's implementation. The causal check uses this new position instead of the token pos.
This PR may be necessary to reach perfect accuracy in Qwen3VL, as mentionned in the discussion of #16207
This is my first PR.
Thanks @ggerganov @ngxson and especially @rujialiu