llama: default qwen2.5vl window attention metadata (#16868)

Existing qwen2.5vl GGUFs can contain an empty qwen25vl.vision.fullatt_block_indexes array. The compat layer translated the projector metadata but left clip.vision.n_wa_pattern unset, causing llama-server to fail loading the CLIP model.

Default the runtime compat value to the standard Qwen2.5-VL pattern when the key cannot be derived, and make the converter emit the same default for nil or empty fullatt block metadata.

Fixes #16540
This commit is contained in:
Daniel Hiltgen
2026-06-24 10:35:29 -07:00
committed by GitHub
parent c191a145bb
commit 33878e671a
3 changed files with 53 additions and 5 deletions
+8 -5
View File
@@ -39,10 +39,6 @@ func (q *qwen25VLModel) KV(t *Tokenizer) KV {
} }
} }
if q.VisionModel.FullAttentionBlocks == nil {
kv["qwen25vl.vision.fullatt_block_indexes"] = []int32{7, 15, 23, 31}
}
kv["qwen25vl.vision.block_count"] = cmp.Or(q.VisionModel.Depth, 32) kv["qwen25vl.vision.block_count"] = cmp.Or(q.VisionModel.Depth, 32)
kv["qwen25vl.vision.embedding_length"] = q.VisionModel.HiddenSize kv["qwen25vl.vision.embedding_length"] = q.VisionModel.HiddenSize
kv["qwen25vl.vision.attention.head_count"] = cmp.Or(q.VisionModel.NumHeads, 16) kv["qwen25vl.vision.attention.head_count"] = cmp.Or(q.VisionModel.NumHeads, 16)
@@ -53,12 +49,19 @@ func (q *qwen25VLModel) KV(t *Tokenizer) KV {
kv["qwen25vl.vision.window_size"] = cmp.Or(q.VisionModel.WindowSize, 112) kv["qwen25vl.vision.window_size"] = cmp.Or(q.VisionModel.WindowSize, 112)
kv["qwen25vl.vision.attention.layer_norm_epsilon"] = cmp.Or(q.VisionModel.RMSNormEps, 1e-6) kv["qwen25vl.vision.attention.layer_norm_epsilon"] = cmp.Or(q.VisionModel.RMSNormEps, 1e-6)
kv["qwen25vl.vision.rope.freq_base"] = cmp.Or(q.VisionModel.RopeTheta, 1e4) kv["qwen25vl.vision.rope.freq_base"] = cmp.Or(q.VisionModel.RopeTheta, 1e4)
kv["qwen25vl.vision.fullatt_block_indexes"] = q.VisionModel.FullAttentionBlocks kv["qwen25vl.vision.fullatt_block_indexes"] = q.fullAttentionBlocks()
kv["qwen25vl.vision.temporal_patch_size"] = cmp.Or(q.VisionModel.TemporalPatchSize, 2) kv["qwen25vl.vision.temporal_patch_size"] = cmp.Or(q.VisionModel.TemporalPatchSize, 2)
return kv return kv
} }
func (q *qwen25VLModel) fullAttentionBlocks() []int32 {
if len(q.VisionModel.FullAttentionBlocks) > 0 {
return q.VisionModel.FullAttentionBlocks
}
return []int32{7, 15, 23, 31}
}
func (q *qwen25VLModel) Tensors(ts []Tensor) []*ggml.Tensor { func (q *qwen25VLModel) Tensors(ts []Tensor) []*ggml.Tensor {
var out []*ggml.Tensor var out []*ggml.Tensor
+44
View File
@@ -0,0 +1,44 @@
package convert
import (
"slices"
"testing"
)
func TestQwen25VLFullAttentionBlockDefaults(t *testing.T) {
tokenizer := &Tokenizer{Vocabulary: &Vocabulary{}}
for _, tt := range []struct {
name string
blocks []int32
want []int32
}{
{
name: "nil",
want: []int32{7, 15, 23, 31},
},
{
name: "empty",
blocks: []int32{},
want: []int32{7, 15, 23, 31},
},
{
name: "custom",
blocks: []int32{5, 17},
want: []int32{5, 17},
},
} {
t.Run(tt.name, func(t *testing.T) {
model := &qwen25VLModel{}
model.VisionModel.FullAttentionBlocks = tt.blocks
got, ok := model.KV(tokenizer)["qwen25vl.vision.fullatt_block_indexes"].([]int32)
if !ok {
t.Fatalf("fullatt_block_indexes has unexpected type %T", got)
}
if !slices.Equal(got, tt.want) {
t.Fatalf("fullatt_block_indexes = %v, want %v", got, tt.want)
}
})
}
}
+1
View File
@@ -2958,6 +2958,7 @@ void handle_qwen25vl_clip(gguf_context * meta, ggml_context * ctx) {
gguf_set_val_u32(meta, "clip.vision.n_wa_pattern", (uint32_t)(arr[0] + 1)); gguf_set_val_u32(meta, "clip.vision.n_wa_pattern", (uint32_t)(arr[0] + 1));
} }
} }
inject_u32_if_missing(meta, "clip.vision.n_wa_pattern", 8);
// Default image_size = 560 (Qwen2VLVisionModel default, no image_size in HF config). // Default image_size = 560 (Qwen2VLVisionModel default, no image_size in HF config).
inject_u32_if_missing(meta, "clip.vision.image_size", 560); inject_u32_if_missing(meta, "clip.vision.image_size", 560);