MetaX-Tech Developer Forum 论坛首页
  • 沐曦开发者
search
Sign in

justinniu

  • Members
  • Joined 2026年8月5日
  • message 帖子
  • forum 主题
  • favorite 关注者
  • favorite_border Follows
  • person_outline 详细信息

justinniu has posted 6 messages.

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月11日 17:17

    MacaEP 推理服务 CPU 与延迟优化实践

    1. 背景

    operation-light-infer 使用 ONNX Runtime MacaEP 执行在线模型推理。初始部署为同一张卡创建 8 个后端实例,每个实例拥有独立的 Ort::Session、推理线程和 Batch 队列。

    压测中主要观察到三个问题:

    • CPU 使用率长期约为 40%,并伴随周期性毛刺;
    • 单次 Session::Run() 耗时约为 6~8ms;
    • 高 QPS 下请求队列快速积压,最老请求排队可达到数十毫秒,严重时队列达到上限并拒绝请求。

    本次优化通过降低同卡 Session 并发、启用共享队列和 Maca Graph,将 CPU 使用率和推理耗时显著降低。

    2. 优化结果

    2.1 降低 Session 数量

    在逐步将实例数从 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 没有获得对应的吞吐收益。

    2.2 开启 Maca Graph

    最终启用:

    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
    

    3. 根因分析

    3.1 CPU 使用率为什么随实例数上升

    每个后端实例都拥有独立的 Session 和推理线程。实例数增加后,同一设备上同时活跃的执行流增多:

    更多 Session
    → 更多 host polling 和驱动调用
    → 更多 kernel 调度与同步竞争
    → 单次 Run 变慢
    → CPU 使用率和毛刺增加
    

    因此,降低实例数既减少了 CPU 活跃线程,也降低了 MacaEP 内部的设备竞争。

    3.2 共享队列解决了什么问题

    默认的多队列模式会先通过 round-robin 将请求分配给不同后端。启用:

    MX_SHARED_QUEUE=1
    

    后,多个 Session 共同消费一个模型级队列,主要收益包括:

    • 空闲 Session 可以领取下一个 Batch;
    • 避免某个实例排队而另一个实例空闲;
    • 避免请求过早被拆散到多个独立队列;
    • 提升调度的 work-conserving 特性。

    单独启用共享队列时性能变化不大,因此它不是本次 7~9 倍加速的主要来源,主要作用是改善调度和负载均衡。

    3.3 Maca Graph 为什么能显著降低耗时

    普通执行路径需要在每次推理时由 CPU 逐个调度算子:

    CPU 逐个调度算子
    → 多次调用驱动启动 kernel
    → 处理 kernel 依赖和同步
    → 获取输出
    

    Maca Graph 会预先捕获完整执行流程,后续请求直接重放执行图:

    填充固定输入 Buffer
    → Graph replay
    → 获取输出
    

    模型计算量并未消失,主要减少的是 CPU 到驱动之间重复发生的算子调度、kernel launch 和同步成本。本模型原先显然受到这些 host 侧开销的显著影响,因此 Graph replay 带来了较大的收益。

    4. Maca Graph 生效的前提

    当前实现满足 Graph replay 的关键条件:

    1. 每次执行固定为 Batch 128;
    2. 输入 Tensor shape 固定;
    3. 每个后端实例复用固定输入 Buffer;
    4. 输入内存地址在 Session 生命周期内保持不变;
    5. Warmup 与线上请求使用相同的 Session、shape 和输入 Buffer。

    当实际 Batch 小于 128 时,请求数据写入固定 Buffer 的前半部分,其余位置补零。MacaEP 始终看到相同的 Batch、shape 和地址,因此可以稳定重放已经捕获的执行图。

    5. 关键代码

    5.1 读取 Graph 和固定 Buffer 配置

    文件: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 默认开启,因此本次无需额外设置。

    5.2 生成固定 Graph shape

    文件: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
    

    5.3 将 Graph 配置交给 MacaEP

    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);
    

    5.4 创建固定地址输入 Buffer

    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 只在初始化时分配一次,线上推理始终复用相同地址。

    5.5 写入实际请求并补零

    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。

    5.6 Warmup 和线上请求使用同一 Buffer

    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;
    }
    

    6. 推荐配置

    当前验证有效的核心配置为:

    MX_SHARED_QUEUE=1
    MX_MACA_GRAPH=1
    

    固定输入 Buffer 无需额外配置,其代码默认值为开启:

    MX_FIXED_INPUT_BUFFERS=1
    

    建议根据目标 QPS 继续测试合适的 num_instances。Graph 开启后单次执行耗时已显著下降,原有实例数可能再次过量。

    7. 上线验证与回滚

    Graph replay 强依赖固定 shape 和固定地址,上线前建议完成以下验证:

    • 使用相同输入对比 MX_MACA_GRAPH=0/1 的逐元素输出;
    • 覆盖实际 Batch 1、32、64、127、128;
    • 连续输入不同数据,确认输出没有复用上一批结果;
    • 多实例并发验证请求之间不存在串数据;
    • 持续压测至少 30~60 分钟,观察错误率、显存、内存和 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 执行回退开关。

    8. 结论

    本次优化的核心经验是:推理实例数并非越多越好。多个 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 主要改善调度和负载均衡。

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月10日 18:54
    @shuai_chen has written:

    尊敬的开发者您好,请保持同样的参数对比。

    我看沐曦c++推理只支持onnx啊,没法同环境比🤔

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月10日 18:54

    我看沐曦c++推理只支持onnx啊,没法同环境比🤔

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月10日 17:30
    @shuai_chen has written:

    尊敬的开发者您好,您是容器部署的吗

    现在又有个问题了,模型推理的时候,CPU使用率100%了,GPU使用率比较低,相同的模型在4090上用pytorch推理并不是这样的,有什么优化思路吗

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月6日 10:25
    @shuai_chen has written:

    尊敬的开发者您好,您是容器部署的吗

    是的,我现在只保留了mx_onnxruntime, 现在能成功加载模型了,之前还引了pytorch和xgb的相关依赖

  • See post chevron_right
    justinniu
    Members
    沐曦C550,用 MacaRT C++ API 加载Onnx模型失败,卡在创建Session 已解决 2026年8月5日 16:31

    一、软硬件信息
    1.服务器厂家:
    2.沐曦GPU型号:C550
    3.操作系统内核版本:5.14.0-3.1.8.kwai.x86_64
    4.是否开启CPU虚拟化:是
    5.mx-smi回显:

    Quoted message:

    mx-smi version: 2.2.9

    =================== MetaX System Management Interface Log ===================
    Timestamp : Wed Aug 5 16:33:27 2026

    Attached GPUs : 1
    +---------------------------------------------------------------------------------+
    | MX-SMI 2.2.9 Kernel Mode Driver Version: 3.4.3 |
    | MACA Version: 3.3.2.27 BIOS Version: 1.30.0.0 |
    |------------------+-----------------+---------------------+----------------------|
    | Board Name | GPU Persist-M | Bus-id | GPU-Util sGPU-M |
    | Pwr:Usage/Cap | Temp Perf | Memory-Usage | GPU-State |
    |==================+=================+=====================+======================|
    | 0 MetaX C550 | 0 Off | 0000:c2:00.0 | 0% Disabled |
    | 91W / 450W | 33C P0 | 858/65536 MiB | Available |
    +------------------+-----------------+---------------------+----------------------+

    +---------------------------------------------------------------------------------+
    | Process: |
    | GPU PID Process Name GPU Memory |
    | Usage(MiB) |
    |=================================================================================|
    | no process found |
    +---------------------------------------------------------------------------------+

    End of Log

    二、问题现象
    请描述详细的问题现象日志。若日志过长,请上传附件(txt格式)。

    代码 has written:

    bool MxOnnxRuntimeBackend::Init(const ModelConfig& config) {
    CleanupRuntimeResources();
    config_ = config;
    initialized_ = false;

    options_ = MxRuntimeOptions();
    options_.device_id = ReadEnvInt("MX_DEVICE_ID", 0, 0, INT_MAX);
    
    std::string model_path = config_.LocalPath;
    if (!model_path.empty() && model_path.front() != '/') {
        model_path = BuildRelativePath(model_path);
    }
    if (model_path.empty()) {
        LOG(ERROR) << LogPrefix("init:1/4") << " empty model path";
        return false;
    }
    LOG(INFO) << LogPrefix("init:1/4") << " LOAD-ONLY build (no warmup, no inference)"
              << " path=" << model_path
              << " MX_DEVICE_ID=" << options_.device_id;
    
    try {
        LOG(INFO) << LogPrefix("init:2/4") << " acquiring shared Ort::Env";
        Ort::Env& env = SharedOrtEnv();
    
        // The documented minimum: name the device through the provider options
        // and let MacaEP do the rest. DisableMemPattern and ORT_DISABLE_ALL are
        // kept from the vendor sample because that configuration is known to
        // load successfully.
        Ort::SessionOptions session_options;
        OrtMACAProviderOptions maca_options;
        maca_options.device_id = options_.device_id;
        maca_options.maca_graph_switch = 0;
        session_options.AppendExecutionProvider_MACA(maca_options);
        session_options.DisableMemPattern();
        session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_DISABLE_ALL);
        LOG(INFO) << LogPrefix("init:2/4") << " SessionOptions ready"
                  << " device_id=" << maca_options.device_id;
    
        LOG(INFO) << LogPrefix("init:3/4") << " creating Ort::Session ..."
                  << " (if this is the last line, session creation hung or crashed)";
        const auto begin = std::chrono::steady_clock::now();
        session_ = std::make_unique<Ort::Session>(env, model_path.c_str(), session_options);
        const auto end = std::chrono::steady_clock::now();
        LOG(INFO) << LogPrefix("init:3/4") << " Ort::Session created in "
                  << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count()
                  << " ms";
    
        LogModelIo();
    
        initialized_ = true;
        LOG(INFO) << LogPrefix("init:done") << " model loaded"
                  << " inputs=" << input_names_.size()
                  << " outputs=" << output_names_.size()
                  << " device_id=" << options_.device_id;
        return true;
    } catch (const Ort::Exception& error) {
        LOG(ERROR) << LogPrefix("init:failed") << " ORT exception loading " << model_path
                   << " code=" << error.GetOrtErrorCode() << ": " << error.what();
    } catch (const std::exception& error) {
        LOG(ERROR) << LogPrefix("init:failed") << " exception loading " << model_path
                   << ": " << error.what();
    }
    CleanupRuntimeResources();
    return false;
    

    }

  • 沐曦开发者论坛
powered by misago