一、软硬件信息
1.服务器厂家:
2.沐曦GPU型号:C550
3.操作系统内核版本:5.14.0-3.1.8.kwai.x86_64
4.是否开启CPU虚拟化:是
5.mx-smi回显:
二、问题现象
请描述详细的问题现象日志。若日志过长,请上传附件(txt格式)。
一、软硬件信息
1.服务器厂家:
2.沐曦GPU型号:C550
3.操作系统内核版本:5.14.0-3.1.8.kwai.x86_64
4.是否开启CPU虚拟化:是
5.mx-smi回显:
二、问题现象
请描述详细的问题现象日志。若日志过长,请上传附件(txt格式)。
尊敬的开发者您好,您是容器部署的吗
是的,我现在只保留了mx_onnxruntime, 现在能成功加载模型了,之前还引了pytorch和xgb的相关依赖
现在又有个问题了,模型推理的时候,CPU使用率100%了,GPU使用率比较低,相同的模型在4090上用pytorch推理并不是这样的,有什么优化思路吗
尊敬的开发者您好,请保持同样的参数对比。
我看沐曦c++推理只支持onnx啊,没法同环境比🤔
我看沐曦c++推理只支持onnx啊,没法同环境比🤔
operation-light-infer 使用 ONNX Runtime MacaEP 执行在线模型推理。初始部署为同一张卡创建 8 个后端实例,每个实例拥有独立的 Ort::Session、推理线程和 Batch 队列。
压测中主要观察到三个问题:
Session::Run() 耗时约为 6~8ms;本次优化通过降低同卡 Session 并发、启用共享队列和 Maca Graph,将 CPU 使用率和推理耗时显著降低。
在逐步将实例数从 8 调整为 4、2 的过程中,观测结果如下:
| 实例数 | CPU 使用率 | 典型 run_us | 说明 |
| -----: | ----------: | ------------: | ----------------------------------------------------- |
| 8 | 约 40% | 约 7.2ms | 多 Session 竞争明显 |
| 4 | 约 25% | 约 4.9~6.5ms | CPU 和单次执行耗时下降 |
| 2 | 约 18%~19% | 约 3.2ms | 该阶段压测 QPS 已翻倍,不能与前两组直接按相同负载比较 |
实例数减少后,CPU 和单次执行耗时同时下降,说明原有 8 Session 属于过度并发。多个 Session 同时向同一设备提交任务,会增加 host polling、kernel launch、同步和设备争用开销。
在未开启 Maca Graph 时,2 个实例和 4 个实例的满 Batch 理论吞吐接近:
2 instances: 2 × 128 / 3.2ms ≈ 80k samples/s
4 instances: 4 × 128 / 6.5ms ≈ 79k samples/s
实例翻倍后单次 Run() 也接近翻倍,因此增加 Session 没有获得对应的吞吐收益。
最终启用:
MX_SHARED_QUEUE=1
MX_MACA_GRAPH=1
MX_FIXED_INPUT_BUFFERS 没有额外配置;代码默认值已经是开启状态。
开启 Maca Graph 后,典型指标变为:
| 指标 | 开启前 | 开启后 |
| ----------------- | --------------: | ------------: |
| run_us | 约 5~8ms | 约 0.7~0.9ms |
| oldest_queue_us | 最高约 20~58ms | 约 0.5ms |
| queue_depth | 最高达到 256 | 通常为 0 |
| 单次执行加速 | — | 约 7~9 倍 |
代表性日志:
slot_count=11 batch_samples=54 padded_batch=128
oldest_queue_us=539 avg_queue_us=277
merge_us=50 run_us=779 split_us=14 queue_depth=0
每个后端实例都拥有独立的 Session 和推理线程。实例数增加后,同一设备上同时活跃的执行流增多:
更多 Session
→ 更多 host polling 和驱动调用
→ 更多 kernel 调度与同步竞争
→ 单次 Run 变慢
→ CPU 使用率和毛刺增加
因此,降低实例数既减少了 CPU 活跃线程,也降低了 MacaEP 内部的设备竞争。
默认的多队列模式会先通过 round-robin 将请求分配给不同后端。启用:
MX_SHARED_QUEUE=1
后,多个 Session 共同消费一个模型级队列,主要收益包括:
单独启用共享队列时性能变化不大,因此它不是本次 7~9 倍加速的主要来源,主要作用是改善调度和负载均衡。
普通执行路径需要在每次推理时由 CPU 逐个调度算子:
CPU 逐个调度算子
→ 多次调用驱动启动 kernel
→ 处理 kernel 依赖和同步
→ 获取输出
Maca Graph 会预先捕获完整执行流程,后续请求直接重放执行图:
填充固定输入 Buffer
→ Graph replay
→ 获取输出
模型计算量并未消失,主要减少的是 CPU 到驱动之间重复发生的算子调度、kernel launch 和同步成本。本模型原先显然受到这些 host 侧开销的显著影响,因此 Graph replay 带来了较大的收益。
当前实现满足 Graph replay 的关键条件:
当实际 Batch 小于 128 时,请求数据写入固定 Buffer 的前半部分,其余位置补零。MacaEP 始终看到相同的 Batch、shape 和地址,因此可以稳定重放已经捕获的执行图。
文件:src/backend/mx_onnxruntime_backend.cc
options_.enable_maca_graph =
ReadEnvBool("MX_MACA_GRAPH", false);
options_.max_batch_samples = kFixedBatchSamples;
options_.use_fixed_input_buffers =
ReadEnvBool("MX_FIXED_INPUT_BUFFERS", true);
MX_MACA_GRAPH 默认关闭,需要显式开启;MX_FIXED_INPUT_BUFFERS 默认开启,因此本次无需额外设置。
文件:src/backend/mx_onnxruntime_backend.cc
const int64_t fixed_batch = options_.max_batch_samples;
for (size_t i = 0; i < input_model_shapes_.size(); ++i) {
std::vector<int64_t> shape;
if (!ResolveFixedShape(input_model_shapes_[i], fixed_batch, &shape)) {
return false;
}
if (i > 0) opt_shape << ",";
opt_shape << input_names_[i] << ":";
for (size_t d = 0; d < shape.size(); ++d) {
if (d > 0) opt_shape << "x";
opt_shape << shape[d];
}
}
maca_graph_opt_shape_ = opt_shape.str();
当前模型会得到类似配置:
numerical:128x48,categorical:128x39,keyword_vec:128x768
const bool graph_capture =
options_.enable_maca_graph &&
probed &&
!maca_graph_opt_shape_.empty();
maca_options.maca_graph_switch = graph_capture ? 1 : 0;
maca_options.maca_graph_opt_shape =
graph_capture ? maca_graph_opt_shape_.c_str() : nullptr;
maca_options.maca_graph_max_opt_batch =
graph_capture ? options_.max_batch_samples : 0;
session_options.AppendExecutionProvider_MACA(maca_options);
fixed_inputs_.float_bufs.emplace_back(total_elements, 0.0f);
void* data_ptr = fixed_inputs_.float_bufs.back().data();
fixed_inputs_.tensors.push_back(
Ort::Value::CreateTensor(
input_mem_info_,
data_ptr,
total_elements * OrtTypeByteWidth(input_types_[i]),
final_shape.data(),
final_shape.size(),
input_types_[i]));
这些 Buffer 只在初始化时分配一次,线上推理始终复用相同地址。
for (const auto& slot : slots) {
const size_t bytes = static_cast<size_t>(
slot->batch_size * elements_per_sample) * width;
std::memcpy(out + offset_bytes, src, bytes);
offset_bytes += bytes;
}
if (offset_bytes < total_bytes) {
std::memset(out + offset_bytes, 0, total_bytes - offset_bytes);
}
无论实际 Batch 是多少,最终送入 Session 的 shape 都保持 Batch 128。
Warmup:
if (fixed_inputs_ready_) {
ZeroFixedInputs();
run_holder = &fixed_inputs_;
}
RunSession(*run_holder, &outputs, true);
线上推理:
if (fixed_inputs_ready_) {
if (!FillFixedInputs(slots, total_batch_samples)) {
return -1;
}
run_holder = &fixed_inputs_;
}
if (!RunSession(*run_holder, &outputs, true)) {
return -1;
}
当前验证有效的核心配置为:
MX_SHARED_QUEUE=1
MX_MACA_GRAPH=1
固定输入 Buffer 无需额外配置,其代码默认值为开启:
MX_FIXED_INPUT_BUFFERS=1
建议根据目标 QPS 继续测试合适的 num_instances。Graph 开启后单次执行耗时已显著下降,原有实例数可能再次过量。
Graph replay 强依赖固定 shape 和固定地址,上线前建议完成以下验证:
MX_MACA_GRAPH=0/1 的逐元素输出;run_us P99;fixed_inputs=1、maca_graph=1 和 queue_shared=1。如出现兼容性、数值或稳定性问题,可分别回滚:
MX_MACA_GRAPH=0
MX_SHARED_QUEUE=0
其中 MX_MACA_GRAPH=0 是最重要的 Graph 执行回退开关。
本次优化的核心经验是:推理实例数并非越多越好。多个 Session 在同一设备上可能增加 CPU polling 和设备竞争,导致 CPU 与单次执行耗时同时上升。
最终优化链路为:
减少 Session 数量
→ 降低 CPU 使用率和设备竞争
启用共享队列
→ 改善多个 Session 之间的任务分配
启用 Maca Graph
→ 固定 Batch、shape 和输入地址
→ 从逐算子调度切换为整图重放
→ Run 从约 5~8ms 降至约 0.7~0.9ms
→ 请求排队基本消失
CPU 降低的主要来源是减少 Session 数量;推理耗时大幅降低的主要来源是 MX_MACA_GRAPH=1;MX_SHARED_QUEUE=1 主要改善调度和负载均衡。