- annacomnena2026-09-0516823
根据仓库 zyYuc/qwen3-8-27b-dual-2080ti-vllm 编译专属vllm版本,实际上0.24.0之前的几个版本可以直接使用(我记得我在0.19.0,0.20.0版本是可以的),但是之后开始出现sm75不允许使用fp8 kvcache了。 这里记录一下我的抄作业方案(专用于 sm75设备 启动 qwen3.8 27b),如果大家需要参考请使用自己的具体路径:
编译```
cd llms
git clone https://github.com/zyYuc/qwen3-8-27b-dual-2080ti-vllm.git
cd qwen3-8-27b-dual-2080ti-vllm
source vllm271-sm75/bin/activate
uv pip install -U setuptools wheel packaging ninja
uv pip install torch==2.13.0 \
--index-url https://download.pytorch.org/whl/cu130
``````
git clone https://github.com/vllm-project/vllm.git vllm-0.27.1-sm75
cd vllm-0.27.1-sm75
git checkout v0.27.1
git apply --check \
~/llms/qwen3-8-27b-dual-2080ti-vllm/patches/vllm-v0.27.1-sm75-qwen3.8.patch
``注意此时的位置``
(vllm271-sm75) annacomnena@annacomnena-Default-string:~/llms/qwen3-8-27b-dual-2080ti-vllm/vllm-0.27.1-sm75
``如果,没有报错旧apply patch``
git apply \
~/llms/qwen3-8-27b-dual-2080ti-vllm/patches/vllm-v0.27.1-sm75-qwen3.8.patch
```注意:CUDA-toolkit版本:首先安装```
sudo apt update
sudo apt install cuda-toolkit-13-0
``然后``
export CUDA_HOME=/usr/local/cuda-13.0
export PATH="$CUDA_HOME/bin:$PATH"
export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}"
export TORCH_CUDA_ARCH_LIST="7.5"
``此时验证cudatoolkit 与cuda版本:``
which nvcc
nvcc --version
python -c "import torch; print(torch.__version__, torch.version.cuda)"patch完成之后可以安装依赖:``` uv pip install -e . ```他会自动构建,如果失败了需要清理缓存,基本必定失败因为uv会自己创造临时环境``` rm -rf build .deps CMakeCache.txt CMakeFiles uv cache clean vllm ```因此uv需要锁定:``` uv pip install \ "numpy==2.3.5" \ "setuptools>=77.0.3,<81.0.0" \ "setuptools-scm>=8.0" \ "setuptools-rust>=1.9.0" \ "packaging>=24.2" \ cmake ninja wheel jinja2 ```再构建:``` uv pip install --no-build-isolation -e . ```构建完成后拿FlashQLA的特定版本:(3ab27d77d8ca01d7a4718903b726add1a8886c0e)``` cd ~/llms/qwen3-8-27b-dual-2080ti-vllm git clone https://github.com/weicj/FlashQLA-SM70-SM75.git cd FlashQLA-SM70-SM75 git checkout 3ab27d77d8ca01d7a4718903b726add1a8886c0e ```验证patch:``` git apply --check ../patches/flashqla-sm70-sm75-local.patch ```如果没有输出,那么正常,继续``` git apply ../patches/flashqla-sm70-sm75-local.patch ```保持环境变量然后编译:``` export CUDA_HOME=/usr/local/cuda-13.0 export PATH="$CUDA_HOME/bin:$PATH" export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}" export TORCH_CUDA_ARCH_LIST="7.5" uv pip install --no-build-isolation -e . ```下一步锁定flashinfer版本:``` cd ~/llms/qwen3-8-27b-dual-2080ti-vllm uv pip install "flashinfer-python==0.6.16.post3" ```应当: flashinfer-python 0.6.16.post3 torch 2.13.0+cu130 vllm 0.27.1 transformers 5.15.1 triton 3.7.1,检查:``` bash scripts/check_flashinfer_sm75.sh ```True 为通过; 总检察脚本:``` python - <<'PY' import torch import vllm import transformers import triton import importlib.metadata as md print("torch :", torch.__version__) print("torch CUDA :", torch.version.cuda) print("vllm :", vllm.__version__) print("transformers:", transformers.__version__) print("triton :", triton.__version__) print("flashinfer :", md.version("flashinfer-python")) from flash_qla.ops.gated_delta_rule.legacy import chunk_gated_delta_rule_fwd_legacy print("FlashQLA : legacy import OK") PY ```启动 3.8 27b``` cd ~/llms conda deactivate source qwen3-8-27b-dual-2080ti-vllm/vllm271-sm75/bin/activate export CUDA_HOME=/usr/local/cuda-13.0 export PATH="$CUDA_HOME/bin:$PATH" export LD_LIBRARY_PATH="$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}" export PYTHONPATH=~/llms/qwen3-8-27b-dual-2080ti-vllm/FlashQLA-SM70-SM75 export VLLM_USE_DEEP_GEMM=0 export VLLM_USE_FLASHINFER_SAMPLER=0 export VLLM_QWOPUS_MTP_BF16_DRAFT=1 export VLLM_SM75_SPEC_SYNC_MODE=safe export VLLM_USE_V2_MODEL_RUNNER=1 vllm serve ./Qwen3.8-27B-W4A16-AWQ \ --port 3001 \ --served-model-name medium \ --dtype half \ --tensor-parallel-size 2 \ --device-ids 0,1 \ --kv-cache-dtype fp8_e4m3 \ --max-model-len 200000 \ --max-num-seqs 3 \ --max-num-batched-tokens 4096 \ --enable-prefix-caching \ --enable-chunked-prefill \ --no-async-scheduling \ --language-model-only \ --skip-mm-profiling \ --reasoning-parser qwen3 \ --enable-auto-tool-choice \ --tool-call-parser qwen3_coder \ --gpu-memory-utilization 0.92 \ --additional-config '{"gdn_prefill_backend":"flashqla_legacy"}' \ --speculative-config '{"method":"mtp","num_speculative_tokens":3,"draft_sample_method":"probabilistic"}' \ --compilation-config '{"cudagraph_capture_sizes":[4,8,12],"max_cudagraph_capture_size":12}' ```其中mtp默认 greedy : –speculative-config ‘{“method”:“mtp”,“num_speculative_tokens”:2}’ 可改成K=3+probabilistic: –speculative-config ‘{“method”:“mtp”,“num_speculative_tokens”:3,“draft_sample_method”:“probabilistic”}’ 另外的 : –kv-offloading-size 6 –kv-offloading-backend native 6是6GB,但是会造成额外的KV搬运性能损耗;有时候能开开,有时候开不开; 也可以替换为lmcache; 可以不开;常常开不开; 另外的, --compilation-config ‘{“cudagraph_capture_sizes”:[4,8,12],“max_cudagraph_capture_size”:12}’ 需要开启,以降低3并发的时候可能造成的调度器错误导致推理尾部错误;(不确定是调度器还是哪里的问题,在推理快结束时候常常会只有2-3tps的解码速度) `VLLM_QWOPUS_MTP_BF16_DRAFT` 的作用是mtp使用bf16,而不是一样的把他当成awq量化;0.28.0中官方已修复 但是最终测下来速度和lmdeploy直接启动,是一模一样的,单流下预填充913tps/解码36tps; 只是lmdepoly目前很尴尬,turbomind后端支持awq量化,不支持mtp;pytorch后端支持mtp,但是只支持bf16权重; 实际上做的比较好的1猫vllm,拿了turbomind嫁接到vllm上,效果是不错的,不过他那个仓库是给sm70设备专用的(看来v100要涨价了) Ornith 1.5``` vllm serve ./Ornith-1.5-35B-A3B-AWQ-INT4 \ --port 3001 \ --served-model-name medium \ --dtype half \ --tensor-parallel-size 2 \ --device-ids 0,1 \ --kv-cache-dtype fp8_e4m3 \ --max-model-len 200000 \ --max-num-seqs 7 \ --max-num-batched-tokens 4096 \ --enable-prefix-caching \ --enable-chunked-prefill \ --no-async-scheduling \ --language-model-only \ --skip-mm-profiling \ --reasoning-parser qwen3 \ --enable-auto-tool-choice \ --tool-call-parser qwen3_coder \ --gpu-memory-utilization 0.92 \ --additional-config '{"gdn_prefill_backend":"flashqla_legacy"}' \ ```ulkaa 这个量化 MTP head是原模型**逐字节搬过来的完整 BF16 head** 但是开启了`VLLM_QWOPUS_MTP_BF16_DRAFT` 也不能正确使用MTP;因为它只能处理dense模型的dense mtp; 对于qwen3_5 moe,不过ornith做的本来也不好用; shisa-ai/Ornith-1.5-35B-A3B-MTP-ONLY 做了一个ornith专用的 MTP模型; 替换为更好的mtp模型 下载 shisa-ai/Ornith-1.5-35B-A3B-MTP-ONLY; 替换权重(注意备份原mtp权重model-mtp.safetensors和model.safetensors.index.json)之后, 修改model.safetensors.index.json: (使用vscode)查找和替换,启用正则:``` ^\s*"mtp\.[^"]+"\s*:\s*"[^"]+",?\s*\r?\n ```全部替换,删除原有的785行,加入:``` "mtp.layers.0.self_attn.q_proj.weight": "model-mtp.safetensors", "mtp.layers.0.self_attn.k_proj.weight": "model-mtp.safetensors", "mtp.layers.0.self_attn.v_proj.weight": "model-mtp.safetensors", "mtp.layers.0.self_attn.o_proj.weight": "model-mtp.safetensors", "mtp.layers.0.self_attn.q_norm.weight": "model-mtp.safetensors", "mtp.layers.0.self_attn.k_norm.weight": "model-mtp.safetensors", "mtp.layers.0.input_layernorm.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.gate.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.shared_expert.gate_proj.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.shared_expert.up_proj.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.shared_expert.down_proj.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.shared_expert_gate.weight": "model-mtp.safetensors", "mtp.layers.0.mlp.experts.gate_up_proj": "model-mtp.safetensors", "mtp.layers.0.mlp.experts.down_proj": "model-mtp.safetensors", "mtp.layers.0.post_attention_layernorm.weight": "model-mtp.safetensors", "mtp.pre_fc_norm_embedding.weight": "model-mtp.safetensors", "mtp.pre_fc_norm_hidden.weight": "model-mtp.safetensors", "mtp.fc.weight": "model-mtp.safetensors", "mtp.norm.weight": "model-mtp.safetensors", ```修复moe模型的moe mtp 上游新版 `qwen3_5_mtp.py` 的思路是:如果 MTP 被排除在 target 的量化之外,就在创建整个 MTP decoder layer 时临时把 `vllm_config.quant_config` 设为 `None`,创建完再恢复。 把现有的 `VLLM_QWOPUS_MTP_BF16_DRAFT=1` 扩展成:不仅 `mtp.fc` 用 BF16,整个 `mtp.layers.0` 都用 BF16。 打开:``` /home/annacomnena/llms/qwen3-8-27b-dual-2080ti-vllm/vllm-0.27.1-sm75/ vllm/model_executor/models/qwen3_5_mtp.py ```补丁:``` --- a/qwen3_5_mtp.py +++ b/qwen3_5_mtp.py @@ -119,25 +119,45 @@ prefix=f"{prefix}.fc", ) - # GPTQ: quantized checkpoints may exclude MTP from quantization via - # quantization_config.dynamic with "-:pattern" entries. When detected, - # disable quantization for MTP layers so they use unquantized params. + # Quantized checkpoints may exclude MTP from quantization. In addition + # to the upstream dynamic-exclude handling below, QWOPUS checkpoints can + # keep the *entire* MTP draft layer (including routed MoE experts) in + # BF16 while the target model uses compressed-tensors/AWQ. In that case + # the MTP decoder layer must be constructed with quantization disabled; + # otherwise RoutedExperts is built with packed/Marlin parameters and the + # BF16 fused MTP tensors (w13_weight / w2_weight) cannot be loaded. original_quant = vllm_config.quant_config - if quant_config and quant_config.get_name() not in ("modelopt_fp4",): + force_unquantized_mtp_layer = qwopus_bf16_mtp and quant_name != "fp8" + + if force_unquantized_mtp_layer: + logger.info( + "VLLM_QWOPUS_MTP_BF16_DRAFT=1: building the entire " + "Qwen3.5 MTP decoder layer unquantized; target quantization=%s", + quant_name or "disabled", + ) + vllm_config.quant_config = None + elif quant_config and quant_config.get_name() not in ("modelopt_fp4",): + # GPTQ / other quantized checkpoints may exclude MTP via + # quantization_config.dynamic with "-:pattern" entries. hf_qc = getattr(model_config.hf_config, "quantization_config", None) if isinstance(hf_qc, dict): dynamic = hf_qc.get("dynamic", {}) if any(k.startswith("-:") and "mtp" in k for k in dynamic): vllm_config.quant_config = None - self.layers = torch.nn.ModuleList( - Qwen3_5DecoderLayer( - vllm_config, - layer_type="full_attention", - prefix=f"{prefix}.layers.{idx}", + + try: + self.layers = torch.nn.ModuleList( + Qwen3_5DecoderLayer( + vllm_config, + layer_type="full_attention", + prefix=f"{prefix}.layers.{idx}", + ) + for idx in range(self.num_mtp_layers) ) - for idx in range(self.num_mtp_layers) - ) - vllm_config.quant_config = original_quant + finally: + # Do not leak the draft-layer override into the target model or + # later modules such as lm_head. + vllm_config.quant_config = original_quant self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory( ["hidden_states", "residual"], config.hidden_size ) ```合适的话会看到:``` (Worker_TP1 pid=2168845) INFO 09-04 15:46:45 [qwen3_5_mtp.py:106] VLLM_QWOPUS_MTP_BF16_DRAFT=1: loading Qwen3.5 MTP draft fc without the target quantization config when needed; draft layer quantization=compressed-tensors (Worker_TP1 pid=2168845) INFO 09-04 15:46:45 [qwen3_5_mtp.py:133] VLLM_QWOPUS_MTP_BF16_DRAFT=1: building the entire Qwen3.5 MTP decoder layer unquantized; target quantization=compressed-tensors ```如果MTP开2,并发开6,最终CUDA图捕捉size要重新计算,1生成+2草稿=3,6并发应当是:``` --compilation-config '{"cudagraph_capture_sizes":[3,6,9,12,15,18],"max_cudagraph_capture_size":18}' ```并且max-num-seqs开7给调度器留一个余量; 如果MTP=2,那么:``` --max-num-seqs 7 \ --compilation-config '{"cudagraph_capture_sizes":[4,8,12,16,20,24,28],"max_cudagraph_capture_size":28}' ```,“draft_tensor_parallel_size”:1 可以不TP2,减小卡间通信成本,但是可能起不来 完整启动命令:``` vllm serve ./Ornith-1.5-35B-A3B-AWQ-INT4 \ --port 3001 \ --served-model-name medium \ --dtype half \ --tensor-parallel-size 2 \ --device-ids 0,1 \ --kv-cache-dtype fp8_e4m3 \ --max-model-len 200000 \ --max-num-seqs 7 \ --max-num-batched-tokens 4096 \ --enable-prefix-caching \ --enable-chunked-prefill \ --no-async-scheduling \ --language-model-only \ --skip-mm-profiling \ --reasoning-parser qwen3 \ --enable-auto-tool-choice \ --tool-call-parser qwen3_coder \ --gpu-memory-utilization 0.92 \ --additional-config '{"gdn_prefill_backend":"flashqla_legacy"}' \ --speculative-config '{"method":"mtp","num_speculative_tokens":1,"draft_sample_method":"probabilistic"}' \ --compilation-config '{"cudagraph_capture_sizes":[2,4,6,8,10,12],"max_cudagraph_capture_size":12}' ```其实我最终测下来可能mtp开1就够了,多了也没啥用,第二节接受度很低,所以此时把cuda图编译的设置做相应的更改; 由于没有nvlink,最终decode 单流也只有50-60tps,不知道上了nvlink能不能好一些主楼 - Star2026-09-1611566
如果想用GGUF模型的话双卡能用起来吗,比如用llamacpp启动的话
- annacomnena2026-09-1616823
可,不过我没用llamacpp,但是llamacpp确实是可以双卡使用的,而且现在llamacpp也可以tensor并行和并发推理了,只是效果没有vllm、lmdeploy、fastllm、sglang这类对于dense模型可以线性获得双倍性能这么好,另外可能prefill也不够快。
如果你找不到合适的awq marlin 或者safetensor这类权重可以考虑llamacpp,实际上vllm等等框架对gguf的支持还不够完善