Repository: ggml-org/llama.cpp
Pull request: Model fix qwen3vl reranker support

`Qwen3-VL` variant of [Qwen3-VL-Reranker-2B](https://huggingface.co/Qwen/Qwen3-VL-Reranker-2B) (i dont test [Qwen3-VL-Reranker-8B](https://huggingface.co/Qwen/Qwen3-VL-Reranker-8B) but i think will work too) was not fully handled by the existing Qwen reranker path

Changes that i need to make:
	- detects `Qwen3-VL-Reranker` during GGUF conversion (like was made for `Qwen3-Reranker`, but for **VL** on [convert_hf_to_gguf.py](https://github.com/ggml-org/llama.cpp/compare/master...ViniciosLugli:llama.cpp:fix/qwen3-vl-reranker#diff-ec77d8003b92ff283179456d36b8b56abf635e7b1232e70daf16676e8920ccf1))
	- adds `cls.output` to the `qwen3vl` tensor set
	- also applies the same rank pooling behavior used by `qwen3` rerankers to `qwen3vl`

Tested from a clean clone:
	- rebuilt `llama.cpp`
	- reconverted [Qwen3-VL-Reranker-2B](https://huggingface.co/Qwen/Qwen3-VL-Reranker-2B)
	- quantized to `Q8_0`
	- verified `/v1/rerank` returns the expected top result on simple sanity checks manually.
  
Example:

```json
{
    "query": "capital of France",
    "documents": [
        "Paris is the capital of France.",
        "A toaster is a kitchen appliance.",
        "Mount Everest is the highest mountain above sea level."
    ]
}
```

Before :
```json
{
    "model": "main",
    "object": "list",
    "usage": {
        "prompt_tokens": 33,
        "total_tokens": 33
    },
    "results": [
        {
            "index": 2,
            "relevance_score": 0.49271294474601746
        },
        {
            "index": 1,
            "relevance_score": -0.10783563554286957
        },
        {
            "index": 0,
            "relevance_score": -0.20799249410629272
        }
    ]
}
```

After
```json
{
    "model": "main",
    "object": "list",
    "usage": {
        "prompt_tokens": 58,
        "total_tokens": 58
    },
    "results": [
        {
            "index": 0,
            "relevance_score": 0.7014886140823364
        },
        {
            "index": 1,
            "relevance_score": 0.30603134632110596
        },
        {
            "index": 2,
            "relevance_score": 0.29874086380004883
        }
    ]
}
```

Changed files:
filename: convert_hf_to_gguf.py
patch: @@ -4390,15 +4390,31 @@ def __init__(self, *args, **kwargs):
         hparams = ModelBase.load_hparams(self.dir_model, is_mistral_format=False)
         self.origin_hf_arch = hparams.get('architectures', [None])[0]
 
-        # a bit hacky, but currently the only way to detect if this is a rerank model
-        # ref: https://huggingface.co/Qwen/Qwen3-Reranker-0.6B
+        if self._is_qwen3_reranker():
+            self._find_rerank_config()
+
+    def _is_qwen3_reranker(self) -> bool:
         readme_path = self.dir_model / "README.md"
         readme_text = ""
         if readme_path.exists():
             with readme_path.open("r", encoding="utf-8") as f:
                 readme_text = f.read()
-        if "# Qwen3-Reranker" in readme_text:
-            self._find_rerank_config()
+
+        name_hints = [
+            str(self.dir_model.name),
+            str(self.hparams.get("_name_or_path", "")),
+            str(self.hparams.get("model_type", "")),
+            str(self.origin_hf_arch or ""),
+        ]
+        name_hints = [hint.lower() for hint in name_hints if hint]
+
+        if "# qwen3-reranker" in readme_text.lower() or "# qwen3-vl-reranker" in readme_text.lower():
+            return True
+
+        if any("qwen3-reranker" in hint or "qwen3-vl-reranker" in hint for hint in name_hints):
+            return True
+
+        return "sequenceclassification" in (self.origin_hf_arch or "").lower()
 
     def set_vocab(self):
         # deal with intern-s1-mini

filename: src/llama-arch.cpp
patch: @@ -1087,6 +1087,7 @@ static std::set<llm_tensor> llm_get_tensor_names(llm_arch arch) {
                 LLM_TENSOR_TOKEN_EMBD,
                 LLM_TENSOR_OUTPUT_NORM,
                 LLM_TENSOR_OUTPUT,
+                LLM_TENSOR_CLS_OUT,
                 LLM_TENSOR_ATTN_NORM,
                 LLM_TENSOR_ATTN_Q,
                 LLM_TENSOR_ATTN_Q_NORM,

filename: src/llama-graph.cpp
patch: @@ -250,7 +250,7 @@ void llm_graph_input_cls::set_input(const llama_ubatch * ubatch) {
 
         const bool last = (
              cparams.pooling_type == LLAMA_POOLING_TYPE_LAST ||
-            (cparams.pooling_type == LLAMA_POOLING_TYPE_RANK && arch == LLM_ARCH_QWEN3) // qwen3 reranking & embedding models use last token
+            (cparams.pooling_type == LLAMA_POOLING_TYPE_RANK && (arch == LLM_ARCH_QWEN3 || arch == LLM_ARCH_QWEN3VL)) // qwen3 reranking & embedding models use last token
         );
 
         for (int i = 0; i < n_tokens; ++i) {
@@ -2552,7 +2552,7 @@ void llm_graph_context::build_pooling(
                 }
 
                 // softmax for qwen3 reranker
-                if (arch == LLM_ARCH_QWEN3) {
+                if (arch == LLM_ARCH_QWEN3 || arch == LLM_ARCH_QWEN3VL) {
                     cur = ggml_soft_max(ctx0, cur);
                 }
             } break;