540fb5af
tangwang
添加了可关闭的开关:保留默认行为(...
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
结论先说:**YAML 里能对齐的项(`model_name`、`max_model_len`、`infer_batch_size`、`prefix_caching` 等)你们已经基本对齐了**;`qwen3_vllm_score` 更慢,主要来自**两条后端走的不是同一条 vLLM 推理路径**,以及 **score 后端在 T4 上强制了 attention 后端**,和 **generate 路径更容易吃到「同 query、多 doc」的优化**。
---
## 1. 配置层面:哪些「对等」、哪些根本不存在于另一侧
两边共用的逻辑在代码里是一致的:`infer_batch_size`、`sort_by_doc_length`、去重、`instruction` / `instruction_format` 的语义(在各自实现里)是对齐设计的。
差异在于 **`qwen3_vllm_score` 必须多出来的 LLM 构造参数**:`runner` / `convert` / `hf_overrides`(把 Hub 模型改成 `Qwen3ForSequenceClassification` 那条链路)。`qwen3_vllm` 没有这些,因为它是**普通 causal LM + `generate`**。这不是 `config.yaml` 漏配,而是两种 API 的必要差别。
```132:140:reranker/backends/qwen3_vllm.py
self._llm = LLM(
model=model_name,
tensor_parallel_size=tensor_parallel_size,
max_model_len=max_model_len,
gpu_memory_utilization=gpu_memory_utilization,
enable_prefix_caching=enable_prefix_caching,
enforce_eager=enforce_eager,
dtype=dtype,
)
```
```167:195:reranker/backends/qwen3_vllm_score.py
llm_kwargs: Dict[str, Any] = {
"model": model_name,
"runner": runner,
"convert": convert,
"tensor_parallel_size": tensor_parallel_size,
"max_model_len": max_model_len,
"gpu_memory_utilization": gpu_memory_utilization,
"enable_prefix_caching": enable_prefix_caching,
"enforce_eager": enforce_eager,
"dtype": dtype,
}
hf_overrides: Dict[str, Any] = dict(self._config.get("hf_overrides") or {})
if use_hf_overrides:
hf_overrides = {
**hf_overrides,
"architectures": ["Qwen3ForSequenceClassification"],
"classifier_from_token": ["no", "yes"],
"is_original_qwen3_reranker": True,
}
if hf_overrides:
llm_kwargs["hf_overrides"] = hf_overrides
attn_cfg = _resolve_vllm_attention_config(self._config)
if attn_cfg is not None:
llm_kwargs["attention_config"] = attn_cfg
self._llm = LLM(**llm_kwargs)
```
**小坑(仅当有人删掉 YAML 字段时):**
`instruction_format` 的**代码默认值不一致**——`qwen3_vllm` 默认 `compact`,`qwen3_vllm_score` 默认 `standard`。你贴的片段里两边都写了 `standard`,所以当前是对齐的。
```93:98:reranker/backends/qwen3_vllm.py
_fmt = str(self._config.get("instruction_format") or "compact").strip().lower()
```
```104:109:reranker/backends/qwen3_vllm_score.py
_fmt = str(self._config.get("instruction_format") or "standard").strip().lower()
```
---
## 2. 为什么「按理 score 更快」在你们机器上反过来
你们自己的报告里写的是 **Tesla T4**(算力 **sm_75 < 8.0**)。这一点和代码里的行为直接相关。
### (1)只有 score 后端在 sm<8 时**强制** `TRITON_ATTN`
```65:75:reranker/backends/qwen3_vllm_score.py
major, minor = torch.cuda.get_device_capability()
if major < 8:
logger.info(
"[Qwen3_VLLM_SCORE] GPU compute capability %d.%d < 8.0; using attention backend "
"TRITON_ATTN (Flash-Attention 2 requires sm >= 80). "
...
)
return {"backend": "TRITON_ATTN"}
```
`qwen3_vllm` **没有**这段逻辑,**不写** `attention_config`,完全交给 vLLM 在 **generate** 路径上自己选实现。
因此在 T4 上很容易出现:**两条路径实际用的 attention / kernel 组合并不相同**;若默认路径比强制的 `TRITON_ATTN` 更适合你们的 batch 与序列长度,就会出现 **score 更慢**。
若要验证,可在 score 的 YAML 里试 `vllm_attention_backend`(或与 `RERANK_VLLM_ATTENTION_BACKEND` 对齐到和 generate 实际一致的后端),或在 Ampere+ 上复测矩阵。
### (2)工作量与 vLLM 优化重心不同(这是主因之一)
- **generate 后端**:`max_tokens=1`、`allowed_token_ids` 只有 yes/no,本质是 **prefill + 极短 decode**,且 logprobs 只关心最后一步的分布。
- **score 后端**:`LLM.score()` 走 **pooling / cross-encoder 式**的打分图,是另一条 runner,**不等于**「比 1-token generate 一定更少算」;在 vLLM 里通常 **causal generate 路径打磨得更狠**。
所以「score API 更高级所以一定更快」在这个模型用法下**不一定成立**。
### (3)`enable_prefix_caching: true` 对两边的「可缓存前缀」不对称
同一 query、多个 doc 时,**generate** 路径用 chat template 拼出来的 prompt,**从 system 到 query 的长前缀在 batch 内完全相同**,很容易成为 prefix caching 的理想场景。
**score** 路径把内容拆成 `queries` / `documents` 两列交给 `score()`,内部如何切块、是否能把「同一 query 对应多 doc」映射成与 generate 同等强度的前缀复用,依赖 vLLM 实现;很多版本下 **generate + 共享前缀** 更占便宜。你们 `max_model_len: 160` 很短,prefill 成本敏感,**谁更吃到缓存**会明显拉开差距。
### (4)Tokenizer 侧:后者多了一步「批量模板」优化
`qwen3_vllm` 对整批 `apply_chat_template` 一次做完再 `generate`:
```171:180:reranker/backends/qwen3_vllm.py
messages_batch = [
self._format_messages(self._instruction, q, d) for q, d in pairs
]
tokenized = self._tokenizer.apply_chat_template(
messages_batch,
tokenize=True,
add_generation_prompt=False,
enable_thinking=False,
)
```
`qwen3_vllm_score` 在 Python 里逐对拼字符串,再进 `score()`(tokenization 在 vLLM 内)。这一项通常不是第一瓶颈,但在 **batch 大、序列短** 时也会有一点差别。
### (5)两个 venv 的 vLLM 版本不同
- `.venv-reranker`:`vllm>=0.8.5`(实际装的几版本会变)
- `.venv-reranker-score`:固定 `vllm==0.18.0`
对比「谁更快」时,**版本 + 代码路径**是绑在一起的;不能假设「新 vLLM + score」在 T4 上一定赢过「旧 vLLM + 1-token generate」。
---
## 3. 和你们 `RESULTS.md` 的对应关系
`perf_reports/.../RESULTS.md` 里:**同一 `instruction_format` 下 `qwen3_vllm` 全程低于 `qwen3_vllm_score`**,与上面 **T4 + attention 强制 + 不同 runner + prefix cache 利用率** 的解释一致;报告里也写了在别的 GPU / vLLM 版本下排序可能变,这是合理的。
---
## 4. 若要「对齐实验」可以怎么做(方向性)
1. **在 Ampere(A10/A100 等 sm≥80)上跑同一脚本**,看 score 是否反超(FlashAttention 路径更完整时,score 路径有时会更合理)。
2. **在 score 侧显式设置 `vllm_attention_backend`**(或与 env 对齐),避免在 T4 上只有 score 被锁死 `TRITON_ATTN` 而 generate 走另一条。
3. **固定两边 `pip show vllm` 版本**再比,否则「版本差」会污染结论。
4. 用 vLLM 的 profiler / 日志确认 **prefix cache hit** 在两种后端上的差异(若你们要量化「缓存」这一条)。
**总结:** 不是 `config.yaml` 里少抄了几个键;而是 **推理图不同、T4 上 attention 策略不对称、以及 generate 对「同 query 多 doc」更友好**,导致在你们当前环境下 **`qwen3_vllm` 比 `qwen3_vllm_score` 更快是合理现象**,与「score API 理论上更干净」并不矛盾。
|