本案例提供了在A+X环境下基于vLLM部署Kimi-K2-Thinking模型的实践方法。
安装关键软件及依赖时,各组件的版本号必须匹配。
部署卡数:本文以4节点部署为例,每个节点配备16张NPU,共计64张910B卡进行部署。
https://www.modelscope.cn/models/moonshotai/Kimi-K2-Thinking
原模型文件对Linear层权重进行了int4分组量化,激活值未量化,需要使用msit工具将模型文件全部反量化为BF16格式。

模型权重文件反向量化INT4->BF16
反量化工具及脚本: https://gitcode.com/libarry/msit_9400/blob/k2/msmodelslim/example/DeepSeek/convert_int4_to_bf16.py
克隆工具及脚本到本地,并执行量化脚本:
git clone https://gitcode.com/libarry/msit_9400.git
cd msit_9400
git checkout k2
cd msmodelslim/example/DeepSeek
python convert_int4_to_bf16.py \
--input-int4-hf-path /path/before-quant-model \
--output-bf16-hf-path /path/after-quant-model注意:生成的量化后文件夹只包含变更后的文件,需要将原模型文件中未更改的文件合并到新生成的量化文件夹,形成最终的量化后的模型文件。
vllm-ascend的权重配置文件未适配新模型的权重文件,导致vllm_ascend/quantization/quant_config.py 从 self.quant_description 字典中查找权重文件键值失败。 找到以下文件:/usr/local/python3.11.13/lib/python3.11/site-packages/vllm_ascend/quantization/quant_config.py 找到is_layer_skipped_ascend函数替换为如下代码,跳过量化匹配,默认视为BF16。
def is_layer_skipped_ascend(
self,
prefix: str,
fused_mapping: Mapping[str, List[str]] = MappingProxyType({})):
# adapted from vllm.model_executor.layers.quantization.utils.quant_utils.is_layer_skipped
proj_name = prefix.split(".")[-1]
if proj_name in fused_mapping:
shard_prefixes = [
prefix.replace(proj_name, shard_proj_name)
for shard_proj_name in fused_mapping[proj_name]
]
is_skipped = None
for shard_prefix in shard_prefixes:
key = shard_prefix + '.weight'
# 使用 .get() 避免 KeyError,默认视为 "FLOAT"(即跳过量化)
weight_type = self.quant_description.get(key, "FLOAT")
is_shard_skipped = (weight_type == "FLOAT")
if is_skipped is None:
is_skipped = is_shard_skipped
elif is_shard_skipped != is_skipped:
raise ValueError(
f"Detected some but not all shards of {prefix} "
"are quantized. All shards of fused layers "
"must have the same precision."
)
else:
key = prefix + '.weight'
# 同样使用 .get() 提供默认值
weight_type = self.quant_description.get(key, "FLOAT")
is_skipped = (weight_type == "FLOAT")
assert is_skipped is not None
return is_skipped启动命令:
Kimi-K2-Thinking BF16模型参数大约2T左右,本案例部署在4个节点,共64张910B卡。
/infer_vllm_m_kimi_k2_thinking.sh \
--model-path ${MODEL_PATH} \
--served-model-name kimi \
--port ${PORT} \
--leaderip ${LWS_LEADER_ADDRESS} \
--index ${LWS_WORKER_INDEX} \
--tensor-parallel-size 8 \
--max-model-len 16384 \
--max-num-seqs 16 \
--data-parallel-size 8 \
--data-parallel-size-local 2 \
--gpu-memory-utilization 0.9 \启动脚本:
#!/bin/bash
# 初始化参数(未传递时使用默认值)
model_path="" # 模型路径(必填)
served_model_name="" # 服务模型名(必填)
port="8000" # 服务端口(默认8000)
leaderip="" # 主节点IP(从节点必填)
max_model_len="32768" # 最大模型长度(默认32768)
max_num_seqs="32" # 最大序列数(默认32)
api_server_count="1" # API服务器数量(默认1)
index="0" # 节点索引(0为主节点,默认0)
tensor_parallel_size="" # 张量并行大小(必填)
data_parallel_size_local="1" # 本地数据并行大小(默认1)
data_parallel_size="1" # 总数据并行大小(默认1)
gpu_memory_utilization="0.95" # GPU内存利用率(默认0.8)
pipeline_parallel_size="1" # 管道并行数(默认1)
# 解析命令行参数(长选项形式)
while [[ $# -gt 0 ]]; do
case "$1" in
--model-path)
model_path="$2"
shift 2
;;
--served-model-name)
served_model_name="$2"
shift 2
;;
--port)
port="$2"
shift 2
;;
--leaderip)
leaderip="$2"
shift 2
;;
--max-model-len)
max_model_len="$2"
shift 2
;;
--max-num-seqs)
max_num_seqs="$2"
shift 2
;;
--api-server-count)
api_server_count="$2"
shift 2
;;
--index)
# 校验节点索引为非负整数
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
echo "Error: 节点索引(--index)必须是非负整数" >&2
exit 1
fi
index="$2"
shift 2
;;
--tensor-parallel-size)
tensor_parallel_size="$2"
shift 2
;;
--data-parallel-size-local)
data_parallel_size_local="$2"
shift 2
;;
--data-parallel-size)
data_parallel_size="$2"
shift 2
;;
--gpu-memory-utilization)
gpu_memory_utilization="$2"
shift 2
;;
--pipeline-parallel-size)
pipeline_parallel_size="$2"
shift 2
;;
*)
echo "Error: 未知参数 $1" >&2
echo "正确用法: $0 --model-path <模型路径> --served-model-name <服务名> --tensor-parallel-size <张量并行数> [选项]" >&2
echo "可选选项:" >&2
echo " --port <端口> 服务端口(默认8000)" >&2
echo " --leaderip <主节点IP> 从节点必填,指定主节点IP" >&2
echo " --max-model-len <最大模型长度> 默认32768" >&2
echo " --max-num-seqs <最大序列数> 默认32" >&2
echo " --api-server-count <API服务器数量> 默认1" >&2
echo " --index <节点索引> 0为主节点(默认),非负整数" >&2
echo " --data-parallel-size-local <本地数据并行大小> 默认1" >&2
echo " --data-parallel-size <总数据并行大小> 默认1" >&2
echo " --gpu-memory-utilization <GPU内存利用率> 默认0.95" >&2
echo " --pipeline-parallel-size <管道并行数> 默认1" >&2
exit 1
;;
esac
done
# 检查必填参数(仅无默认值的参数为必填)
if [[ -z "$model_path" || -z "$served_model_name" || -z "$tensor_parallel_size" ]]; then
echo "Error: 缺少必填参数,必须提供:--model-path、--served-model-name、--tensor-parallel-size" >&2
exit 1
fi
# 从节点必须提供主节点IP
if [[ "$index" -ne 0 && -z "$leaderip" ]]; then
echo "Error: 从节点(index=$index)必须通过--leaderip指定主节点IP" >&2
exit 1
fi
# 校验ifconfig命令是否存在
if ! command -v ifconfig &> /dev/null; then
echo "Error: 未找到ifconfig命令,请安装net-tools或检查环境变量" >&2
exit 1
fi
# 选择网络接口(优先eth开头,其次enp开头)
nic_name=$(ifconfig | grep -Eo '^(eth|enp)[^:]*' | sort -r | head -n1)
if [[ -z "$nic_name" ]]; then
echo "Error: 未找到eth/enp开头的网络接口,请检查网络配置" >&2
exit 1
fi
# 获取本地IP
local_ip=$(ifconfig "$nic_name" | grep 'inet ' | awk '{print $2}' | grep -v '^127\.' | head -n1)
if [[ -z "$local_ip" ]]; then
echo "Error: 无法从接口$nic_name获取有效IP地址" >&2
exit 1
fi
echo "已选择网络接口: $nic_name,本地IP: $local_ip"
# 打印参数信息
echo -e "\n[DEBUG] 部署参数:"
echo "========================================"
echo "必选参数:"
echo " 模型路径: $model_path"
echo " 服务模型名: $served_model_name"
echo " 张量并行大小: $tensor_parallel_size"
echo "节点信息:"
echo " 节点索引: $index(0为主节点)"
echo " 主节点IP: ${leaderip:-本地节点(主节点)}"
echo "并行配置:"
echo " 总数据并行数: $data_parallel_size"
echo " 本地数据并行数: $data_parallel_size_local"
echo " 管道并行数: $pipeline_parallel_size"
echo " 启用expert-parallel: 是(默认启用)"
echo "其他配置:"
echo " 服务端口: $port"
echo " 最大模型长度: $max_model_len"
echo " 最大序列数: $max_num_seqs"
echo " API服务器数量: $api_server_count"
echo " GPU内存利用率: $gpu_memory_utilization"
echo "========================================\n"
source /usr/local/Ascend/ascend-toolkit/set_env.sh
source /usr/local/Ascend/ascend-toolkit/latest/opp/vendors/customize/bin/set_env.bash
export HCCL_IF_IP="$local_ip"
export GLOO_SOCKET_IFNAME="$nic_name"
export TP_SOCKET_IFNAME="$nic_name"
export HCCL_SOCKET_IFNAME="$nic_name"
export OMP_PROC_BIND=false
export OMP_NUM_THREADS=1
export HCCL_BUFFSIZE=1024
export VLLM_ENGINE_INIT_TIMEOUT=3600
export ENGINE_INIT_TIMEOUT=3600
export HCCL_EXEC_TIMEOUT=7200
export HCCL_CONNECT_TIMEOUT=3600
max_num_batched_tokens=$((max_model_len * 1))
if [[ "$index" -eq 0 ]]; then
# 主节点命令
vllm_cmd="vllm serve \"$model_path\" \
--host 0.0.0.0 \
--port \"$port\" \
--served-model-name \"$served_model_name\" \
--tensor-parallel-size \"$tensor_parallel_size\" \
--data-parallel-size \"$data_parallel_size\" \
--data-parallel-size-local \"$data_parallel_size_local\" \
--data-parallel-address \"$local_ip\" \
--data-parallel-rpc-port 13389 \
--enable-expert-parallel \
--max-num-seqs \"$max_num_seqs\" \
--max-model-len \"$max_model_len\" \
--max-num-batched-tokens \"$max_num_batched_tokens\" \
--trust-remote-code \
--gpu-memory-utilization \"$gpu_memory_utilization\" \
--seed 1024 \
--quantization ascend \
--additional-config '{\"ascend_scheduler_config\":{\"enabled\":true},\"torchair_graph_config\":{\"enabled\":true,\"graph_batch_sizes\":[16]}}' \
"
else
# 从节点命令(计算起始rank)
data_parallel_start_rank=$((index * data_parallel_size_local))
vllm_cmd="vllm serve \"$model_path\" \
--host 0.0.0.0 \
--port \"$port\" \
--headless \
--served-model-name \"$served_model_name\" \
--tensor-parallel-size \"$tensor_parallel_size\" \
--data-parallel-size \"$data_parallel_size\" \
--data-parallel-size-local \"$data_parallel_size_local\" \
--data-parallel-start-rank \"$data_parallel_start_rank\" \
--data-parallel-address \"$leaderip\" \
--data-parallel-rpc-port 13389 \
--enable-expert-parallel \
--max-num-seqs \"$max_num_seqs\" \
--max-model-len \"$max_model_len\" \
--max-num-batched-tokens \"$max_num_batched_tokens\" \
--trust-remote-code \
--gpu-memory-utilization \"$gpu_memory_utilization\" \
--seed 1024 \
--quantization ascend \
--additional-config '{\"ascend_scheduler_config\":{\"enabled\":true},\"torchair_graph_config\":{\"enabled\":true,\"graph_batch_sizes\":[16]}}' \
"
fi
# 打印并执行命令
echo "[INFO] 启动命令:"
echo "========================================"
echo "$vllm_cmd" | sed 's/\\//g'
echo "========================================\n"
eval "$vllm_cmd"