ERNIE-Image 是百度开发的文生图扩散模型,基于 Transformer 架构,支持高质量文本到图像生成。
Transformer Config:
- hidden_size: 4096
- num_layers: 36
- num_attention_heads: 32
- ffn_hidden_size: 12288
- in_channels: 128 (latent)
- out_channels: 128
- patch_size: 1
- text_in_dim: 3072
- eps: 1e-06
- qk_layernorm: True
- rope_axes_dim: [32, 48, 48]
- rope_theta: 256混合序列: 模型同时处理 image tokens + text tokens
Sequence Parallel挑战:
NPU硬件:
| 依赖包 | 版本 | 说明 |
|---|---|---|
| cann | 8.5.1 | 昇腾基础库 |
| torch | 2.9.0+cpu | PyTorch基础库 |
| torch_npu | 2.9.0 | 华为NPU适配 |
| diffusers | 0.38.0 | HuggingFace扩散模型库(含修改) |
| transformers | 5.5.3 | 文本编码器依赖 |
| vllm | 0.19.1+empty | vLLM推理框架 |
| vllm_ascend | 0.19.1rc1 | NPU backend |
| vllm-omni | 0.19.0rc1+npu | 多模态扩展框架(含修改) |
| modelscope | 1.36.3 | 模型下载工具 |
| accelerate | 1.12.0 | 分布式推理支持 |
SP=4配置:
parallel_config:
sequence_parallel_size: 4 # 4个NPU并行
ulysses_degree: 4 # Ulysses SP算法
ring_degree: 1
tensor_parallel_size: 1
pipeline_parallel_size: 1
data_parallel_size: 1使用 ModelScope SDK 下载:
# 安装 modelscope
pip install modelscope
# 下载 ERNIE-Image 模型
python3 << 'EOF'
from modelscope import snapshot_download
model_dir = snapshot_download(
'PaddlePaddle/ERNIE-Image',
cache_dir='/opt/data/modelscope/hub'
)
print(f"Model downloaded to: {model_dir}")
EOF权重目录结构:
/opt/data/modelscope/hub/models/PaddlePaddle/ERNIE-Image/
├── transformer/
│ ├── config.json
│ ├── diffusion_pytorch_model.safetensors
│ └── model.safetensors.index.json
├── vae/
│ ├── config.json
│ └── diffusion_pytorch_model.safetensors
├── text_encoder/
│ ├── config.json
│ ├── model.safetensors
│ └── tokenizer_config.json
├── scheduler/
│ └── scheduler_config.jsondocker pull m.daocloud.io/quay.io/ascend/vllm-ascend:v0.19.1rc1-openeulerdocker run -it -u root -d --net=host \
--privileged --ipc=host \
--device=/dev/davinci_manager \
--device=/dev/devmm_svm \
--device=/dev/hisi_hdc \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/sbin:/usr/local/sbin \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /opt/data/modelscope:/opt/data/modelscope --name ernie-image \
m.daocloud.io/quay.io/ascend/vllm-ascend:v0.19.1rc1-openeuler /bin/bashERNIE-Image 的正确部署需要对两个核心库进行修改:
位置: /usr/local/python3.11.14/lib/python3.11/site-packages/diffusers/models/transformers/transformer_ernie_image.py
修改内容: 在 ErnieImageSingleStreamAttnProcessor.__call__ 中添加NPU优化的rotary embedding
Patch: 见 patches/diffusers_transformer_ernie_image.patch
关键代码:
## add by wei (Line 123)
def apply_rotary_emb_npu(x_in: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
rot_dim = freqs_cis.shape[-1]
x, x_pass = x_in[..., :rot_dim], x_in[..., rot_dim:]
cos_ = torch.cos(freqs_cis).to(x.dtype)
sin_ = torch.sin(freqs_cis).to(x.dtype)
# Use NPU optimized rotary embedding
out = rotary_position_embedding(x, cos_, sin_, rotated_mode='rotated_half')
return torch.cat((out, x_pass), dim=-1)
# Auto-select NPU or CPU implementation
if freqs_cis is not None:
if is_torch_npu_available():
query = apply_rotary_emb_npu(query, freqs_cis)
key = apply_rotary_emb_npu(key, freqs_cis)
else:
query = apply_rotary_emb(query, freqs_cis)
key = apply_rotary_emb(key, freqs_cis)位置: /usr/local/python3.11.14/lib/python3.11/site-packages/diffusers/models/attention_dispatch.py
修改内容: 添加laser attention判断和NPU mask广播函数
Patch: 见 patches/diffusers_attention_dispatch.patch
关键代码:
## add by wei (Line 3311)
def is_supported_laser_attention(head_dim, q_seqlen, kv_seqlen):
MAX_DIM = 128
MIN_SEQLEN_SELF = 4000
MIN_SEQLEN_CROSS = 118404
MAX_SEQLEN_CROSS = 119056
if head_dim > MAX_DIM:
return False
if q_seqlen == kv_seqlen:
return q_seqlen >= MIN_SEQLEN_SELF
else:
return (MIN_SEQLEN_CROSS <= q_seqlen <= MAX_SEQLEN_CROSS) and \
(MIN_SEQLEN_CROSS <= kv_seqlen <= MAX_SEQLEN_CROSS)
def _broadcast_attn_mask_npu(query, key, attn_mask):
if attn_mask is not None:
if attn_mask.ndim == 2 and attn_mask.shape[0] == query.shape[0] and attn_mask.shape[1] == key.shape[1]:
batch_size, seq_len_q, seq_len_kv = attn_mask.shape[0], query.shape[1], key.shape[1]
attn_mask = attn_mask.unsqueeze(1).expand(batch_size, seq_len_q, seq_len_kv).unsqueeze(1).contiguous()
elif attn_mask.ndim == 4 and attn_mask.shape[1:3] == (1, 1):
attn_mask = attn_mask.expand(-1, -1, query.shape[1], -1).contiguous()
return attn_mask位置: /vllm-workspace/vllm-omni/vllm_omni/diffusion/models/ernie_image/pipeline_ernie_image.py
主要修改:
Patch: 见 patches/vllm_omni_pipeline_ernie_image.patch
位置: /vllm-workspace/vllm-omni/vllm_omni/diffusion/models/ernie_image/ernie_image_transformer.py
主要修改:
_sp_plan["x_embedder"]配置,改为手动shardPatch: 见 patches/vllm_omni_ernie_image_transformer.patch
位置: /vllm-workspace/vllm-omni/vllm_omni/diffusion/models/ernie_image/ulysses_attention.py
主要修改(关键bug修复):
[B, S, C](而非启发式判断)核心修复逻辑:
# Step 1: Gather hidden_states
B, S_local, C = hidden_states.shape
# Convert to sequence-first for gather
hidden_states = hidden_states.transpose(0, 1) # [B, S, C] -> [S, B, C]
hidden_states_full = sp_gather(hidden_states, dim=0) # Gather along sequence
hidden_states_full = hidden_states_full.transpose(0, 1) # Back to [B, S_full, C]
# Step 2: Gather rotary embedding (same process)
cos = cos.transpose(0, 1)
cos_full = sp_gather(cos, dim=0)
cos_full = cos_full.transpose(0, 1)
# Step 3: Create full attention mask
mask_full = torch.ones((B, 1, S_full, S_full), ...)
# Step 4: Compute attention on full sequence
output_full = processor(attn, hidden_states_full, mask_full, rotary_full)
# Step 5: Scatter back
output_full = output_full.transpose(0, 1)
output_local = sp_shard(output_full, dim=0)
output_local = output_local.transpose(0, 1)Patch: 见 patches/vllm_omni_ulysses_attention.patch
# 1. 修改 diffusers (需要root权限)
sudo cp patches/diffusers_transformer_ernie_image.patch \
/usr/local/python3.11.14/lib/python3.11/site-packages/diffusers/models/transformers/
sudo patch -p0 < patches/diffusers_transformer_ernie_image.patch
sudo cp patches/diffusers_attention_dispatch.patch \
/usr/local/python3.11.14/lib/python3.11/site-packages/diffusers/models/
sudo patch -p0 < patches/diffusers_attention_dispatch.patch
# 2. 修改 vllm-omni
cd /vllm-workspace/vllm-omni/vllm_omni/diffusion/models/ernie_image/
patch -p0 < patches/vllm_omni_pipeline_ernie_image.patch
patch -p0 < patches/vllm_omni_ernie_image_transformer.patch
patch -p0 < patches/vllm_omni_ulysses_attention.patch单卡启动:
cd /vllm-workspace/ernie-image/bin
# 单卡配置文件(需自行创建)
vllm serve /opt/data/modelscope/hub/models/PaddlePaddle/ERNIE-Image \
--config config/ernie_stage_single.yaml \
--port 8000 \
--dtype bfloat16SP=4启动:
cd /vllm-workspace/ernie-image/bin
# 使用SP=4配置启动
bash start_ernie.shstart_ernie.sh 内容:
#!/bin/bash
echo "Starting ERNIE-Image (v0.19.0rc1 + diffusers adapter)..."
echo "Model: /opt/data/modelscope/hub/models/PaddlePaddle/ERNIE-Image"
echo "Port: 8000"
echo "Config: /vllm-workspace/ernie-image/config/ernie_stage_sp4_custom.yaml"
vllm serve /opt/data/modelscope/hub/models/PaddlePaddle/ERNIE-Image \
--config /vllm-workspace/ernie-image/config/ernie_stage_sp4_custom.yaml \
--port 8000 \
--dtype bfloat16服务验证:
# 检查服务状态
curl http://localhost:8000/v1/models
# 测试生成
curl -X POST http://localhost:8000/v1/images/generations \
-H "Content-Type: application/json" \
-d '{
"prompt": "A beautiful sunset over the ocean",
"size": "1024x1024",
"num_inference_steps": 50,
"guidance_scale": 4.0
}'优化效果:1024x1024 离线推理:79s(单卡) -> 28s(4卡)
Cache-DiT 是 vLLM-Omni 针对 DiT(Diffusion Transformer)模型的多步推理加速技术。DiT 模型在图像生成时需要迭代执行多步(如 50 步),每一步都跑一遍完整的 transformer forward。Cache-DiT 的核心思路是:相邻步之间的中间层输出变化很小,可以缓存上一步的结果,跳过部分层的计算,直接复用缓存的残差。
1、修改ErnieImageTransformer.py
# 旧
class ErnieImageTransformer2DModel(nn.Module):
# 新
class ErnieImageTransformer2DModel(CachedTransformer):通过修改ErnieImageTransformer,使用cache-dit特性来进行模型的性能验证。
Cache-DiT 兼容性标记 — CachedTransformer 是 vLLM-Omni 对 DiT 模型的"标记基类"。cache_backend.enable(pipeline) 为 ERNIE-Image 调用专属 enable_cache_for_ernie_image,使用 Pattern_3 对 transformer.layers 做缓存适配。
CFG(Classifier-Free Guidance) 分离支持 — do_true_cfg 属性和 enable_separate_cfg 类属性用于 CFG 并行推理时区分正/负条件分支的缓存状态,避免残差互相污染。
修改后的预估计算量差异如下:
无 Cache-DiT(50 步全部计算):
Step 0: 全部 N 层计算 → hidden_0
Step 1: 全部 N 层计算 → hidden_1
...
Step 49: 全部 N 层计算 → hidden_49
总计算量: 50 × N 层
有 Cache-DiT(缓存部分步的部分层):
Step 0-3: 全部 N 层计算 ← warmup,始终全算
Step 4-6: 残差差值 < threshold → 缓存 ← 只算前几层,其余复用
Step 7: 连续缓存达上限,强制全算 ← max_continuous_cached_steps
...
总计算量: 大幅减少(取决于缓存比例)影响cache-dit的核心参数 参数解释如下
| 参数 | 默认值 | 作用 |
|---|---|---|
max_warmup_steps | 4 | 前 4 步始终全算,不缓存(建立缓存基线) |
residual_diff_threshold | 0.24 | 残差差值低于此阈值 → 缓存;高于 → 全算 |
max_continuous_cached_steps | 3 | 最大连续缓存步数,防止精度累积退化 |
max_cached_steps | 30 | 最大总缓存步数(-1 = 不限) |
Fn_compute_blocks | 1 | 每个 cache cycle 的前向计算块数 |
测试配置:
| 参数 | 值 |
|---|---|
| 模型 | ERNIE-Image |
| 图像尺寸 | 1328 × 928 |
| 推理步数 | 50 |
| SP size | 1(单卡) |
| 平台 | 华为昇腾 NPU(单卡) |
| guidance_scale | 4.0 |
时延对比:
| 配置 | 总推理时延 (s) | 加速比 | Cache-DiT 参数 | 备注 |
|---|---|---|---|---|
Cache-DiT OFF (--cache-backend none) | 69.98 | — | — | 全量计算基线 |
| Cache-DiT ON (原始配置, threshold=0.05) | 69.17 | 1.1% | F1B1_W5_M20_MC10_R0.05 | threshold 过严,几乎无缓存 |
| Cache-DiT ON (配置 A, threshold=0.24) | 47.0 | 32.8% | F1B0_W4_M30_MC10_R0.24 | threshold 放宽 + Bn=0,效果显著 |
配置 的关键调整:
| 参数 | 原始值 | 配置值 | 影响 |
|---|---|---|---|
residual_diff_threshold | 0.05 | 0.24 | 最关键:0.05 太严(仅 5% 差值以下才缓存),0.24 放宽到代码默认值,大幅提高缓存命中率 |
Bn_compute_blocks | 1 | 0 | 移除 backward compute block,减少缓存周期开销 |
max_warmup_steps | 5 | 4 | 多一步可缓存 |
max_cached_steps | 20 | 30 | 放宽总缓存上限 |
vLLM‑Omni 配置了sp_plan,这个是一套规划器 + 配置接口,用来自动 / 手动配置 “序列并行怎么切、怎么调度、怎么通信” 。但是由于NPU和GPU的通信方式等差异,不能直接复用 GPU 的 _sp_plan hook 方案。以下按数据流阶段解析每处修改。
| 层级 | 原因 | 详细说明 |
|---|---|---|
| 工程层 | _sp_plan hook 不可用 | SequenceParallelSplitHook + auto_pad 依赖 NCCL 通信行为。HCCL 上 auto_pad 与 all_to_all_single 有兼容性问题,hook 系统不可用 |
| Padding 层 | Padding 时机矛盾 | GPU 在 freqs 输出 上补零(RoPE 计算完之后);NPU 必须在 grid_yx 输入坐标 上补零(RoPE 计算之前),因为 gather 后 freqs shape 必须与全局序列对齐。 |
| 健壮性层 | HCCL 排序不确定 | NCCL 保证 gather/scatter 排序确定性;HCCL 排序不确定,local RoPE 路径依赖排序对称性,global RoPE 消除排序依赖 |
代码变化:
self.sp_size = _get_sequence_parallel_world_size_or_one()
if current_omni_platform.is_npu():
self._sp_plan = {} # 清空自动分片计划
if self.sp_size > 1:
for layer in self.layers:
attn = layer.self_attention
attn.attn.skip_sequence_parallel = True # 跳过 vLLM 内置 SP
ulysses_processor = ErnieImageUlyssesAttnProcessorV2()
attn.set_processor(ulysses_processor) # 安装自定义处理器三个关键操作:
_sp_plan = {} — 禁用 _sp_plan hook 系统(三层原因见概述),转为手动控制分片时机
skip_sequence_parallel = True — 阻止 vLLM Attention 内部做 AllToAll。Ulysses processor 已在外部做 AllToAll,如果 Attention 内部再做一次,就重复通信了
安装 Ulysses Processor — 每个 attention 层安装 ErnieImageUlyssesAttnProcessorV2,统一处理 AllToAll → RoPE → Attention → AllToAll 的完整流程
Pipeline 初始化也需要调整:
if current_omni_platform.is_npu():
# 1. 直接读 config.json(需要传入 parallel_config + od_config)
transformer_config_path = Path(model) / "transformer" / "config.json"
tf_config_dict = json.load(transformer_config_path)
self.transformer = ErnieImageTransformer2DModel(
parallel_config=self.parallel_config,
od_config=od_config,
**tf_config_dict,
).to(self._execution_device)
# 2. __init__ 中直接加载权重(DO NOT remove unless verified with benchmark)
# 实测:__init__ 加载 ~27s,延迟到 load_weights() ~34s(慢 21%)
transformer_path = Path(model) / "transformer"
for weight_file in transformer_path.glob("*.safetensors"):
...
self.transformer.load_weights(weights_dict.items())NPU 需要传入 parallel_config(初始化 sp_size)和 od_config(完整并行配置),GPU 的 get_transformer_config_kwargs 不支持这两个参数。权重提前加载是因为 NPU 上 load_weights() 有 21% 的额外开销。
_prepare_npu vs unified_prepare原版 forward() 是一个统一的流程(unified_prepare),现在拆为两条路径:
if current_omni_platform.is_npu():
x, S_local, ... = self._prepare_npu(hidden_states, text_bth, text_lens)
else:
x, S_local, ... = self._prepare_gpu(hidden_states, text_bth, text_lens, N_img)核心差异:padding 的处理时机
| 场景 | GPU (_sp_plan) | NPU (_prepare_npu) |
|---|---|---|
| 运行范围 | 在 全序列 上计算(不分片) | 在 本地 shard 上计算(手动分片后) |
| Padding 时机 | auto_pad 在 RoPE 输出上补零 | 手动在 grid_yx 输入坐标上补零 |
| Padding 后 RoPE | 先算 RoPE 再 shard(split_output=True) | 先 padding grid_yx → 再算 RoPE → 再 shard |
| Mask | 2D 本地 [B, seq_local] | 4D 全局 [B, 1, S_global, S_global] |
为什么 NPU 必须先 padding 再算 RoPE:
当 N_img % sp_size != 0 时,需要在 image grid 上补零行。这些零行对应的 RoPE 位置坐标(grid_yx)也必须补零,否则 RoPE 给 padding token 分配真实位置编号,导致注意力出错。
# GPU: pad freqs output (unified_prepare)
freqs_cos = torch.cat([freqs_cos, torch.zeros(...)], dim=...) # ← RoPE 之后补零
# NPU: pad grid_yx input (_prepare_npu)
pad_grid = torch.zeros((pad_size, 2)) # ← RoPE 之前补零坐标 (0,0)
grid_yx_padded = torch.cat([grid_yx, pad_grid], dim=0)GPU 的 _sp_plan 可以在 RoPE 输出上补零(因为 hook 在 shard 前就完成了 RoPE,padding 零值不影响位置编码)。NPU 没有 hook,必须在输入坐标上 padding,因为 gather 后 freqs shape 必须与全局序列对齐——在 freqs 输出上补零会导致 gather 后全局 freqs 的零行与 padding token 实际位置不匹配。
NPU 的注意力计算通过自定义 ErnieImageUlyssesAttnProcessorV2 完成,是 SP 适配的核心。
# ErnieImageAttention 新增
self.processor = None
def set_processor(self, processor):
self.processor = processor
def forward(self, ...):
if self.processor is not None:
return self.processor(self, hidden_states, attention_mask, image_rotary_emb, **kwargs)
# Default GPU attention logic (原有代码不变)
...借鉴 diffusers 的 AttentionProcessor 设计模式——通过外置处理器替换注意力逻辑,模型本体保持不变。运行时只需一行判断,零开销。
输入: hidden_states [B, S_local, C](本地序列片段)
Step 1: to_q/k/v 投影
→ query/key/value: [B, S_local, H, D]
Step 2: AllToAll gather (scatter heads, gather sequence)
SeqAllToAll4D(scatter_idx=2, gather_idx=1)
→ [B, S_global, H/P, D](全局序列,部分 heads)
Step 3: 全局 RoPE
freqs 如是本地片段 → sp_gather 拼成全局
→ 正确的全局位置编码
Step 4: 全局 Attention
mask: 从 4D mask 中提取全局掩码
→ attn(query, key, value, metadata)
Step 5: AllToAll scatter (gather heads, scatter sequence)
SeqAllToAll4D(scatter_idx=1, gather_idx=2)
→ [B, S_local, H, D](回到本地序列,全部 heads)
Step 6: to_out 输出投影
→ [B, S_local, C]_apply_rotary_emb 格式适配# 旧:只支持 [B, S, H, D]
cos_ = freqs_cos.unsqueeze(2).to(x.dtype)
# 新:同时支持 [S, B, H, D](NPU AllToAll 后可能出现的格式)
if x.dim() == 4 and x.shape[0] != freqs_cos.shape[0]:
cos_ = freqs_cos.transpose(0, 1).unsqueeze(2).to(x.dtype) # freqs [B,S,D] → [S,B,1,D]
else:
cos_ = freqs_cos.unsqueeze(2).to(x.dtype) # freqs [B,S,D] → [B,S,1,D]NPU 的 AllToAll 操作后,Q/K/V tensor 可能变为 [S, B, H, D](序列维度在最前面),而 freqs 始终是 [B, S, D](batch-first)。不做维度对齐会导致 freqs 的 batch 维与 x 的 sequence 维对乘——位置编码错乱。
三种场景使得 [S, B, H, D] 格式出现:
| 场景 | 说明 |
|---|---|
| HCCL 数据排序差异 | HCCL 的 all_to_all_single 内部数据打包策略可能使 [S_global, B, H/P, D] 成为更自然的输出格式,强行 transpose 回 batch-first 可能因 NPU 内存布局产生性能开销 |
| NPU 注意力后端格式要求 | MindIE-SD 的 attention_forward 可能期望 sequence-first 格式,避免多余 transpose |
| freqs 维度对齐 | x 是 [S, B, H, D] 时,freqs 必须 transpose(0,1) 变为 [S, B, D] 再 unsqueeze(2) 才能与 x 的 dim 排列一致 |
GPU 的"本地 RoPE"实际是"全局计算后 _sp_plan hook 自动分片"——freqs 在分片前已包含绝对位置,分片后自然正确。NPU 不能复用,原因有三层:
第一层:_sp_plan hook 不可用 — auto_pad 与 HCCL 不兼容,没有"全局计算后自动分片"机制。
第二层:Padding 时机矛盾 — GPU pad freqs 输出,NPU 必须 pad grid_yx 输入。没有 hook 就不能"先算 RoPE 再 shard",必须先 shard 再 gather 再算 RoPE。
第三层:HCCL 排序不确定 — NCCL 保证 gather/scatter 排序对称性。HCCL 不保证,local RoPE 路径依赖排序对称性(scatter 期望按 rank 顺序拆分回各 rank)。global RoPE 通过 sp_gather freqs 与实际 token 一一对应,消除排序依赖。
_gather_image_npu + VAE rank 0 独占解码def _vae_decode(self, latents, device, output_type="pil"):
if current_omni_platform.is_npu():
sp_size = getattr(self.parallel_config, "sequence_parallel_size", None) or 1
if sp_size > 1:
sp_rank = get_sequence_parallel_rank()
dist.broadcast(latents, src=0, group=get_sp_group().device_group)
if sp_rank != 0:
return DiffusionOutput(output=[], peak_memory_mb=0.0)
# 原有的 BN 校正 + unpatchify + VAE decode
...为什么 NPU 需要 rank 0 独占解码:
_sp_plan 自动 gather 完整输出,所有 rank 的 latents 一致,冗余解码不影响正确性_gather_image_npu() gather 后,各 rank latents 可能因 padding 截断时机不同有微小差异策略:dist.broadcast 确保 rank 0 的 latents 广播给所有 rank → 只有 rank 0 做 VAE 解码。
| 维度 | GPU (NCCL) | NPU (HCCL) |
|---|---|---|
| 通信库 | NCCL | HCCL |
| 硬件互联 | NVLink / PCIe | HCCS / PCIe |
| 调用时机 | vLLM Attention 内部自动 | Ulysses processor 手动 |
| RoPE 时机 | AllToAll 之前(全局计算后 shard) | AllToAll 之后(先 gather 再算) |
| 数据流 | 全局 RoPE → shard → AllToAll → Attention → AllToAll → gather | pad+shard → AllToAll → 全局 RoPE → Attention → AllToAll → 截断 |
| Padding | freqs 输出上补零 | grid_yx 输入坐标上补零 |
| Mask | 2D [B, seq_local] | 4D [B, 1, S_global, S_global] |
| Token 排序 | NCCL 确定性 rank 0→1→2→... | HCCL 不确定,global RoPE 消除依赖 |
| 内存布局 | CUDA 行优先 | Ascend 5ND + 对齐约束 |
| VAE | 所有 rank 冗余解码 | rank 0 独占 |
| 权重加载 | 延迟到 load_weights() | __init__ 中提前加载(+21%) |
测试配置:
| 参数 | 值 |
|---|---|
| 模型 | ERNIE-Image |
| 图像尺寸 | 1328 × 928 |
| 推理步数 | 50 |
| guidance_scale | 4.0 |
| 平台 | 华为昇腾 NPU |
时延对比:
| SP size | 总推理时延 (s) | 加速比 | 备注 |
|---|---|---|---|
| SP=1(单卡) | 69.98 | 1.00x | 基线 |
| SP=2 | 36.32 | 1.93x | 近线性加速 |
| SP=4 | 27.32 | 2.56x | 超线性衰减,通信开销占比增大 |
通过使能cache-dit,增加到4卡进行并发推理,实现性能2.56倍提升,满足客户需求。
详细修改见:vllm-omni:pr3415