Step-3.7-Flash 是阶跃星辰(StepFun)推出的多模态混合专家(MoE)大模型,具备**图文理解(Vision-Language Understanding)**能力,可同时处理图像与文本输入并生成文本回复。
silu(gate).clamp(max=limit) * up.clamp(-limit, limit)),通过 clamp 截断抑制数值溢出,保障长序列与大词表下的数值稳定性说明:本指导对应**推理(Inference)**场景。
--trust-remote-code 加载模型自带的 configuration_step3p7.py 等配置文件即可。
step3p7.py、架构注册条目、tokenizer 中文编码修复(v0.20.2 仅有父模型 Step3VL,无 Step3p7)。swiglustep_and_mul(开源贡献 PR:vllm-project/vllm-ascend#10491)。完整改动见附录 6,照此手动适配即可。
适用于图文理解、视觉问答、文档/图表理解、多模态对话等推理场景,支持单机多卡部署。
表 1 配套软件信息
| 配套 | 版本 |
|---|---|
| 推理框架 vllm-ascend | v0.20.2rc1(叠加 Step3.7 适配补丁 5 文件,见 2.3 节) |
| CANN | 9.0.0 |
| Python | 3.11 |
| torch | 2.10.0 |
| torch_npu | 2.10.0 |
推理框架:vLLM-Ascend。
| 项目 | 信息 |
|---|---|
| 推理设备 | 910B3(8 卡) |
| 部署卡类型 | Ascend 910B3 |
| 部署方式 | 单机 8 卡(--tensor-parallel-size 8) |
| 操作系统 | ARM(openEuler / EulerOS) |
vllm-ascend v0.20.2rc1 尚未原生支持 Step-3.7-Flash,需拉取基础镜像后手动适配两层共 5 个文件:
| 侧 | 文件 | 作用 |
|---|---|---|
| vllm | model_executor/models/step3p7.py | 新增 Step3p7 模型定义(v0.20.2 仅有父模型 Step3VL) |
| vllm | model_executor/models/registry.py | 注册 Step3p7 架构条目 |
| vllm | tokenizers/registry.py | tokenizer 中文编码修复(加入 _MODEL_TYPES_WITH_INCORRECT_TOKENIZER_CLASS + get_cached_tokenizer 包装) |
| vllm-ascend | ops/activation.py | 新增 swiglustep_and_mul 算子(PR #10491) |
| vllm-ascend | ops/fused_moe/moe_mlp.py | MoE MLP 加 SWIGLUSTEP 分支(PR #10491) |
⚠️ 5 个文件缺一不可:vllm 侧少盖 → Step3.7 架构不注册 → fallback 报
_get_num_multimodal_tokens;vllm-ascend 侧少盖 → 第 43–44 层激活数值错误。
适配步骤:
docker pull quay.io/ascend/vllm-ascend:v0.20.2rc1进入容器后,按附录 6 手动修改上述 5 个文件(vllm 侧 3 + vllm-ascend 侧 2)。
重启 vllm 服务(见第 3 节)。
5 个缺一不可:只改 vllm-ascend 2 个会跑不通(报 _get_num_multimodal_tokens);只改 vllm 侧则第 43–44 层激活数值错误。
改动来源:vllm-ascend 侧 2 文件对应 PR vllm-project/vllm-ascend#10491;vllm 侧 3 文件为 Step3.7 模型适配,完整内容见附录 6。
docker run -itd \
--name vllm_step3p7 \
--net=host \
--shm-size=500g \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /etc/hccn.conf:/etc/hccn.conf \
-v /mnt:/mnt \
--privileged \
step3p7-vllm-ascend:v0.20.2rc1 bash# 以 HuggingFace 镜像源为例,按实际社区链接替换 repo_id
pip install huggingface_hub
python -c "
from huggingface_hub import HfApi
api = HfApi()
api.snapshot_download(
repo_id='stepfun-ai/Step-3.7-Flash',
repo_type='model',
resume_download=True,
revision='main',
local_dir='/mnt/models/step-3.7',
etag_timeout=10000
)
"权重下载完成后,确认目录下包含
config.json、configuration_step3p7.py、tokenizer_config.json及权重文件(.safetensors)。
单机 8 卡部署,使用 8 卡张量并行、float16 精度:
# 进入容器
docker exec -it vllm_step3p7 bash
# 单机环境变量(8 卡)
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
export TASK_QUEUE_ENABLE=1
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=100
export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD
export PYTORCH_NPU_ALLOC_CONF="expandable_segments:True"
export VLLM_ASCEND_ENABLE_PREFETCH_MLP=1
# 启动服务
vllm serve /mnt/models/step-3.7 \
--served-model-name step3p7 \
--host 0.0.0.0 \
--port 2077 \
--tensor-parallel-size 8 \
--dtype float16 \
--max-model-len 4096 \
--max-num-seqs 32 \
--trust-remote-code \
--seed 1024关键参数说明:
| 参数 | 说明 |
|---|---|
--trust-remote-code | 必填。加载模型自带的 configuration_step3p7.py 配置(含 swiglu_limits、layer_types 等完整字段) |
--tensor-parallel-size 8 | 单机 8 卡张量并行 |
--dtype float16 | 推理精度 float16(非量化路径) |
--max-model-len 4096 | 最大序列长度,按显存与业务需求调整 |
服务启动后(日志出现 Application startup complete),将 0.0.0.0:2077 替换为实际部署 IP 和端口进行验证。
纯文本对话:
curl http://0.0.0.0:2077/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "step3p7",
"max_tokens": 128,
"messages": [
{"role": "user", "content": "请用三句话介绍长江的地理特征。"}
]
}'多模态(图文)对话:
curl http://0.0.0.0:2077/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "step3p7",
"max_tokens": 256,
"messages": [
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}},
{"type": "text", "text": "请描述这张图片的内容。"}
]
}
]
}'多模态请求需将
image_url替换为可访问的图片地址或 base64 编码。
| 卡数 | 输入长度 (tokens) | 输出长度 (tokens) | TTFT (ms) | TPOT (ms) | 并发 | 单卡输出吞吐 (tokens/s) |
|---|---|---|---|---|---|---|
| 8 | 1024 | 1024 | 952.7 | 52.1 | 20 | 18.8849 |
| 8 | 2048 | 1024 | 1105.8 | 47.1 | 15 | 20.8506 |
| 8 | 4096 | 1024 | 1868.6 | 49.3 | 15 | 20.0091 |
| 8 | 8192 | 1024 | 1922.6 | 47.5 | 10 | 20.3001 |
| 8 | 16384 | 1024 | 4772.6 | 51.8 | 10 | 17.982 |
| 8 | 32768 | 1024 | 6891.6 | 47.4 | 8 | 19.211 |
| 8 | 65536 | 1024 | 29154.6 | 53 | 4 | 12.297 |
| 8 | 131072 | 1024 | 60072.77 | 34.44 | 2 | 2.35 |
Q1:启动报 'Step3VLProcessor' object has no attribute '_get_num_multimodal_tokens',或提示 Step3.7 架构不被支持?(最高频)
A:vllm 侧 3 个文件没盖全(常见于只盖了 vllm-ascend 2 个算子文件)。Step3.7 架构未注册 → vllm fallback 到通用 wrapper → 报此错。按 2.3 节把 5 个文件盖全(尤其 vllm 侧的 step3p7.py、models/registry.py、tokenizers/registry.py)。
Q2:启动报错找不到某配置字段(如 swiglu_limits / layer_types / max_position_embeddings)?
A:确认启动命令包含 --trust-remote-code。该参数使 vllm 加载模型自带的 configuration_step3p7.py(字段完整)。缺失会改用框架内置简化配置,导致字段缺失。
切勿在 vllm 的
transformers_utils/config.py里加step3p7 → Step3VLConfig映射——那会强制trust_remote_code=False、绕过模型自带配置(治标旧方案的坑,治本方案已避免,详见 case_study 问题 1)。
Q3:MoE 层报与激活函数相关的错误 / 第 43–44 层数值异常? A:确认镜像已叠加 swiglustep 补丁(vllm-ascend 侧 2 文件,见 2.3 节)。未打补丁的 v0.20.2rc1 不支持 SwigluStepAndMul。
Q4:中文输出乱码、内容无关(幻觉)、或 prompt_tokens 异常少(7~8)?
A:检查 tokenizers/registry.py 补丁是否生效(step3p7 须在 _MODEL_TYPES_WITH_INCORRECT_TOKENIZER_CLASS 集合)。该集合让 vllm 绕过错误的 LlamaTokenizerFast 包装,否则中文编码丢失。同时确认 tokenizer_config.json 含 chat_template(ChatML)。
Q5:显存不足(OOM)?
A:降低 --max-model-len 与 --max-num-seqs,或提高 --gpu-memory-utilization(注意留足 KV Cache 与权重空间)。MoE 模型权重较大,建议在 8 卡环境下部署。
Q6:是否支持量化部署? A:本指导基于 float16 非量化路径,已完整验证。量化(W8A8 等)路径为后续增强项,待验证通过后补充。
本节给出 vllm-ascend v0.20.2rc1 需手动修改的 5 个文件的完整内容。改完重启 vllm 服务生效。
(1)新增 vllm/model_executor/models/step3p7.py —— Step3p7 模型定义(继承 v0.20.2 自带的 Step3VL):
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Inference-only Jurassic model."""
import torch
from vllm.config import VllmConfig
from vllm.logger import init_logger
from vllm.model_executor.layers.activation import get_act_fn
from vllm.model_executor.layers.linear import ColumnParallelLinear
from .step3_vl import Step3VLForConditionalGeneration
from .step_vl import PerceptionEncoder
from .utils import WeightsMapper, init_vllm_registered_model, maybe_prefix
from .vision import run_dp_sharded_vision_model
logger = init_logger(__name__)
class Step3p7ForConditionalGeneration(Step3VLForConditionalGeneration):
hf_to_vllm_mapper = WeightsMapper(
orig_to_new_prefix={
"model.vision_model.": "vision_model.",
"model.vit_large_projector.": "vit_large_projector.",
"model.vit_large_projector": "vit_large_projector",
"model.language_model.": "language_model.model.",
"model.language_model": "language_model.model",
"model.": "language_model.model.",
"lm_head.": "language_model.lm_head.",
"lm_head": "language_model.lm_head",
},
orig_to_new_substr={
".attn.in_proj_weight": ".attn.qkv_proj.weight",
".attn.in_proj_bias": ".attn.qkv_proj.bias",
".mlp.c_fc": ".mlp.fc1",
".mlp.c_proj": ".mlp.fc2",
},
)
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
super(Step3VLForConditionalGeneration, self).__init__()
config = vllm_config.model_config.hf_config
multimodal_config = vllm_config.model_config.multimodal_config
quant_config = vllm_config.quant_config
self.config = config
self.multimodal_config = multimodal_config
self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
with self._mark_tower_model(vllm_config, "image"):
self.vision_model = PerceptionEncoder(
config.vision_config,
get_act_fn(config.vision_config.hidden_act),
quant_config=quant_config,
prefix=maybe_prefix(prefix, "vision_model"),
)
self.vit_large_projector = ColumnParallelLinear(
config.vision_config.width * 4,
config.text_config.hidden_size,
bias=config.projector_bias,
gather_output=True,
quant_config=quant_config,
prefix=maybe_prefix(prefix, "vit_large_projector"),
disable_tp=self.use_data_parallel,
)
with self._mark_language_model(vllm_config):
self.language_model = init_vllm_registered_model(
vllm_config=vllm_config,
hf_config=config.text_config,
prefix=maybe_prefix(prefix, "language_model"),
)
self.make_empty_intermediate_tensors = (
self.language_model.make_empty_intermediate_tensors
)
def _get_vision_model_output(
self, input_tensor: torch.Tensor | None
) -> torch.Tensor | None:
if input_tensor is None:
return None
if self.use_data_parallel:
return run_dp_sharded_vision_model(input_tensor, self.vision_model)
return self.vision_model(input_tensor)
def _process_image_features(self, image_features: torch.Tensor) -> torch.Tensor:
image_features, _ = self.vit_large_projector(image_features)
return image_features(2)vllm/model_executor/models/registry.py —— 在 _SPECULATIVE_DECODING_MODELS 字典(Step3p5MTP 条目附近)加一行注册:
"Step3p7ForConditionalGeneration": ("step3p7", "Step3p7ForConditionalGeneration"),(3)vllm/tokenizers/registry.py —— tokenizer 中文编码修复(2 处):
a) _MODEL_TYPES_WITH_INCORRECT_TOKENIZER_CLASS 集合加入 step3p7(否则中文编码丢失、prompt_tokens 只有 7~8):
_MODEL_TYPES_WITH_INCORRECT_TOKENIZER_CLASS: set[str] = {"step3_vl", "step3p7"}b) 在 get_tokenizer 的 from_pretrained 之后,对集合内的 model_type 加 get_cached_tokenizer 包装(v0.20.2 缺此步,main 已有;缺它会报 TokenizersBackend has no attribute max_chars_per_token):
tokenizer = tokenizer_cls_.from_pretrained(tokenizer_name, *args, **kwargs)
if model_type in _MODEL_TYPES_WITH_INCORRECT_TOKENIZER_CLASS:
from vllm.tokenizers.hf import get_cached_tokenizer
tokenizer = get_cached_tokenizer(tokenizer)(4)vllm_ascend/ops/activation.py —— 新增 swiglustep_and_mul 算子:
def swiglustep_and_mul(x: torch.Tensor, limit: float = 7.0) -> torch.Tensor:
"""Out-variant of swiglustep activation.
Computes:
silu(x[:d]).clamp(max=limit) * x[d:].clamp(-limit, limit)
"""
gate, up = x.chunk(2, dim=-1)
gate = torch.nn.functional.silu(gate)
gate = gate.clamp(max=limit)
up = up.clamp(min=-limit, max=limit)
out = gate * up
return out(5)vllm_ascend/ops/fused_moe/moe_mlp.py —— MoE MLP 加 SWIGLUSTEP 分支:
a) 顶部 import 加入 swiglustep_and_mul:
from vllm_ascend.ops.activation import AscendSwigluOAIAndMul, swiglustep_and_mulb) 非量化 MLP(unquant_apply_mlp)补充 swiglustep 分支:
elif act_name == "swiglustep":
limit = swiglu_limit if swiglu_limit > 0 else 7.0
gate_up_out = swiglustep_and_mul(gate_up_out, limit=limit)vllm-ascend 侧 2 文件对应 PR vllm-project/vllm-ascend#10491;本节给出全部 5 文件的完整改动,照此手动适配即可。