已合并
【feat】NPUAffinityController support multiple rangs of affinity cpu core #34959
【feat】NPUAffinityController support multiple rangs of affinity cpu core #34959
已合并
zhaoyu65创建于 5月7日
zhaoyu65
zhaoyu65成员
5月7日

【合入来源】

如有社区issue,请关联issue链接
请勿携带内部流程信息(需求链接、问题单、内部issue等)

【修改方案】

1、修改内容总体

本次 commit 的核心目的是让 NPUAffinityController 支持绑定多个不连续的 CPU 核范围。同时修改了lintrunner工具扫描出来的代码规范问题。

主要技术变更:

  • 将数据结构从 CoreIdRange {start, end} 改为 CoreIdList = std::set<CoreId>,支持离散/多段 CPU 核集合,set容器符合语义,不存在重复值
  • 支持解析查出来的多段CPU亲和段
  • 对GetAffinityCPUInfo.cpp和NPUAffinityController.cpp重构,优化函数圏复杂度,将cpp内部调用函数使用namespace或static关键词封装,

2、各文件修改的内容和目的

文件 修改内容 目的
NPUAffinityController.h CoreIdRange 结构体替换为 CoreIdList = std::set<CoreId>,新增 SetThreadAffinity(const CoreIdList) 接口 数据结构重构,支持多核集合
NPUAffinityController.cpp 重构核心绑定逻辑:解析配置支持 npu0:0-1,npu0:4-9 多段格式;新增 formatCoreRange() 输出函数;重构 parseDeviceCoreRange() 解析多段范围 核心实现支持多段 CPU 核绑定
GetAffinityCPUInfo.cpp parseAffinityCPU() 改为 parseAffinityCores(),支持逗号分隔的多段范围;重构 GetExclusiveAffinityCPU() 的核分配逻辑 DCMI 接口层适配新数据结构,避免重复初始化
GetAffinityCPUInfo.h 简化接口,对其他cpp文件只保留 GetAffinityCores(int card_id)接口 接口统一
Module.cpp Python 绑定增加对 PyList 参数的解析,支持python内部接口侧传入 [0, 1, 4, 5] 格式的核列表 Python API 支持新参数类型
affinity.py _set_thread_affinity() 支持 List[int]List[List[int]] 参数,如 [[0,5], [8,10]] Python 层 API 扩展
torch_npu_schema.json 更新签名 Union[List[int], List[List[int]], NoneType] 类型提示更新
test_affinity.py 新增加多段CPU核相关测试用例 新增用例

3、修改后的影响

功能增强

  • 用户可配置多段 CPU 核范围:如 CPU_AFFINITY_CONF='1,npu0:0-3,npu0:8-15',同一设备可绑定不连续的核,取并集
  • API 更灵活set_thread_affinity() 新增接受 [[0,5], [8,10]] 格式参数的功能

行为变化

  • 环境变量 CPU_AFFINITY_CONF 同一设备多次指定核范围会合并(如 npu0:0-1,npu0:4-9 绑定核 0,1,4,5,6,7,8,9)

代码改进

  • 新增 formatCoreRange() 函数统一输出格式(如 0-3,8-15
  • 代码结构更清晰:解析函数拆分为 parseNpuAffineMode()parseLazyBindMode()parseForceMode()parseDeviceCoreRange()

【资料变更】

在后续2.7.1分支的PR中修改

【接口变更】

CPU_AFFINITY_CONF环境变量和set_thread_affinity()接口变更方案已通过SEG例会评审,兼容旧版本;

【功能验证】

自测OK
image.png

验证环境变量CPU_AFFINITY_CONF

#!/bin/bash

# 设置环境变量
export ASCEND_PROCESS_LOG_PATH=$(pwd)
export ASCEND_GLOBAL_LOG_LEVEL=0

echo -e 'import torch\na=torch.tensor([1],device="npu")\nb=a+1\n' > add.py

# 定义配置和期望值数组
configs=(
    "1,npu_affine:1,npu0:0-5"
    "1,npu_affine:1,npu0:0-5,npu1:6-7,npu2:8-8,npu2:10-11,npu0:12-19"
    "1,npu_affine:1,npu0:0-5,npu1:10-13,npu0:4-7"
)

values=(
    "0-5"
    "0-5,12-19"
    "0-7"
)

run_test() {
    local index=$1
    local conf=$2
    local expected_val=$3
    
    export CPU_AFFINITY_CONF="$conf"
    
    # 清理旧日志,确保获取的是本次运行的日志
    rm -rf debug/
    
    # 执行测试脚本
    python add.py
    
    # 检查 python 执行是否成功,可选
    if [ $? -ne 0 ]; then
        echo "[ERROR] Index $index: python add.py failed." >> result.log
        return 1
    fi

    # 提取 affinity 值
    # 使用 find 避免 glob 匹配不到文件时的报错,或者保留原来的 glob 但确保目录存在
    local log_file
    log_file=$(find debug/plog -name "*.log" 2>/dev/null | head -1)
    
    if [ -z "$log_file" ]; then
        echo "[ERROR] Index $index: No log file found in debug/plog/" >> result.log
        return 1
    fi

    local affinity_line
    affinity_line=$(grep "Device 0 set acl_thread affinity to" "$log_file" 2>/dev/null | head -1)
    
    if [ -z "$affinity_line" ]; then
        echo "[ERROR] Index $index: Affinity line not found in log." >> result.log
        return 1
    fi

    # 提取 "affinity to" 后面的具体值
    local affinity_value
    # sed 提取最后一个非空字段,或者根据具体日志格式调整
    affinity_value=$(echo "$affinity_line" | sed -n 's/.*affinity to \([^ ]*\).*/\1/p')
    
    # 去除可能的首尾空白字符 (trim)
    affinity_value=$(echo "$affinity_value" | xargs)
    
    # 比较实际值与期望值
    if [ "$affinity_value" == "$expected_val" ]; then
        echo "测试环境变量 CPU_AFFINITY_CONF $conf: 的结果为:SUCCESS (Actual: ${affinity_value} == Expected: ${expected_val})"
    else
        echo "测试环境变量 CPU_AFFINITY_CONF $conf: 的结果为:FAIL (Actual: ${affinity_value} != Expected: ${expected_val})"
    fi
}

# 循环遍历配置
# 使用 C 风格的 for 循环以便同时获取索引
for ((i=0; i<${#configs[@]}; i++)); do
    run_test "$i" "${configs[$i]}" "${values[$i]}"
done

echo "All tests completed."

测试结果为:所有用例都通过
image.png

【CheckList】

PR提交人对以下CheckList自检项进行全量自检,自检通过或不涉及,均修改 [ ] 为 [x]

likedislike
Pull Request已成功合入, 合并人@ascend-robot
(感谢 zhaoyu65 的贡献)
zhaoyu65zhaoyu65成员
5月7日 创建了 pull request,commit 95aec0a0
zhaoyu65zhaoyu65成员
5月7日 关联了issue:[Feature]: 【运行时】NPU亲和性绑核支持绑定多个CPU范围
zhaoyu65
zhaoyu65成员
5月7日 评论:

compile

likedislike
ascend-robot
ascend-robot成员
5月7日 评论:

Thanks for your pull-request.
The full list of commands accepted by me can be found at here
You can get sig-info at here


PR Approval Progress

Congratulations! All modules have met the lgtm and approve requirements.

Module Approval Details

module lgtm status approve status
repo-Ascend/pytorch liujunzhu, hbhu_bin (2/2) liujunzhu (1/1)
test liujunzhu, hbhu_bin (2/2) liujunzhu (1/1)
torch_npu/utils liujunzhu, hbhu_bin (2/2) liujunzhu (1/1)

💡 Tip:

  • Committer can comment /approve or /lgtm
  • Commenting /approve implies both code review (lgtm) and intent to merge (approve)

CLA Signature Pass

zhaoyu65, thanks for your pull request. All authors of the commits have signed the CLA. 👍

likedislike
此处折叠了152条消息 查看更多
ascend-robotascend-robot成员
5月11日 添加了label:approved
hbhu_bin成员
5月11日 评论:

/lgtm

likedislike
ascend-robotascend-robot成员
5月11日 添加了label:lgtm
ascend-robot
ascend-robot成员
5月11日 评论:

Review Guide

This pull-request passes review.
Committers who wrote a comment of /approve are: liujunzhu.
Reviewers who wrote a comment of /lgtm are: liujunzhu, hbhu_bin.

likedislike
ascend-robotascend-robot成员
5月11日 合入了pull request