#!/bin/bash
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
echo "Error: This file is a library. Please source it."
exit 1
fi
readonly __UTILS_SCRIPT_PATH="$(readlink -f "${BASH_SOURCE[0]}")"
readonly __UTILS_DIR="$(dirname "${__UTILS_SCRIPT_PATH}")"
readonly __PROJECT_ROOT="$(dirname "${__UTILS_DIR}")"
readonly CONFIG_DIR="${__PROJECT_ROOT}/config"
readonly ONNX_PATH="${__PROJECT_ROOT}/build/scripts/onnx_plugin"
readonly JSON_FILE="${ONNX_PATH}/json.hpp"
parse_arguments() {
case "${AI_CORE_PROFILE:-v220}" in
c310)
: "${ai_core:=ai_core-Ascend950}"
;;
v220)
: "${ai_core:=ai_core-Ascend910B1}"
;;
esac
while [[ "$#" -gt 0 ]]; do
case $1 in
--vendor-name)
if [ -z "$2" ]; then
echo "ERROR: --vendor-name requires a non-empty argument." >&2
return 1
fi
vendor_name="$2"
shift 2
;;
--ai-core)
ai_core="$2"
shift 2
;;
*)
echo "Unknown parameter passed: $1" >&2
return 1
;;
esac
done
if [ -z "$vendor_name" ]; then
echo "ERROR: --vendor-name is required." >&2
return 1
fi
export vendor_name
export ai_core
return 0
}
VALID_AI_CORES=(
"ai_core-Ascend910B1"
"ai_core-Ascend910B2"
"ai_core-Ascend910B3"
"ai_core-Ascend910B4"
"ai_core-Ascend910_93"
"ai_core-Ascend310P3"
)
VALID_AI_CORES_C310=(
"ai_core-Ascend950"
)
validate_ai_core() {
local target_core="$1"
local profile="${AI_CORE_PROFILE:-v220}"
case "$profile" in
c310)
for valid_core in "${VALID_AI_CORES_C310[@]}"; do
if [ "$target_core" = "$valid_core" ]; then
echo "ai_core $target_core"
return 0
fi
done
echo "Error: ai core must be one of: [${VALID_AI_CORES_C310[*]}] (AI_CORE_PROFILE=${profile})" >&2
return 1
;;
v220)
for valid_core in "${VALID_AI_CORES[@]}"; do
if [ "$target_core" = "$valid_core" ]; then
echo "ai_core $target_core"
return 0
fi
done
echo "Error: ai core must be one of: [${VALID_AI_CORES[*]}] (AI_CORE_PROFILE=${profile})" >&2
return 1
;;
*)
echo "ERROR: AI_CORE_PROFILE must be 'v220' or 'c310' (or unset for v220), got: '${AI_CORE_PROFILE}'" >&2
return 1
;;
esac
}
get_gpp_path() {
local gpp_path
gpp_path=$(which g++)
if [ -z "$gpp_path" ]; then
echo "ERROR: g++ not found in PATH." >&2
return 1
fi
export GPP_PATH="$gpp_path"
return 0
}
check_system_and_cann() {
local target_core="$1"
ARCH=$(uname -m)
if [ -z "${ARCH}" ]; then
echo "ERROR: get arch failed" >&2
return 1
fi
OS_ID=$(cat /etc/os-release | sed -n 's/^ID=//p' | sed 's/^"//;s/"$//')
if [ -z "${OS_ID}" ]; then
echo "ERROR: get os_id failed" >&2
exit 1
fi
SAFE_REGEX='^[A-Za-z0-9._-]+$'
if ! [[ "$OS_ID" =~ $SAFE_REGEX ]]; then
echo "ERROR: invalid os_id: $OS_ID" >&2
exit 1
fi
if ! [[ "$ARCH" =~ $SAFE_REGEX ]]; then
echo "ERROR: invalid arch: $ARCH" >&2
exit 1
fi
get_gpp_path || return 1
export ARCH
export OS_ID="$OS_ID"
return 0
}
check_and_fetch_third_party_libs() {
if [ -d "$catlass_include_dir" ]; then
echo "Found CATLASS library at ${catlass_include_dir}"
else
echo "Not found CATLASS library at ${catlass_include_dir}, attempting to fetch via git submodule..."
if ! git submodule update --init --recursive; then
echo "Error: Failed to fetch CATLASS library at ${catlass_include_dir}" >&2
return 1
elif [ -d "$catlass_include_dir" ]; then
echo "Successfully fetched CATLASS library at ${catlass_include_dir}"
else
echo "Error: git submodule succeeded but CATLASS library not found at ${catlass_include_dir}" >&2
return 1
fi
fi
return 0
}
overwrite_source_with_target() {
local src_dir="$1"
local tgt_dir="$2"
if [ ! -d "$src_dir" ]; then
echo "ERROR: Source directory not found: $src_dir" >&2
return 1
fi
if [ ! -d "$tgt_dir" ]; then
echo "ERROR: Target directory not found: $tgt_dir" >&2
return 1
fi
find "$src_dir" -type f | while read -r src_file; do
local relative_path="${src_file#$src_dir/}"
local target_file="$tgt_dir/$relative_path"
if [ -f "$target_file" ]; then
cp -f "$target_file" "$src_file"
echo "Overwrite $src_file with $target_file"
else
echo "WARNING: Target file not found for $src_file, skipping overwrite." >&2
fi
done
}
generate_op_name() {
local vendor_name="$1"
local op_name=$(echo "$vendor_name" | sed -r 's/(^|_)([a-z])/\U\2/g')
echo "$op_name"
}
set_build_version() {
local build_dir="$1"
if [ -d "${build_dir}/cmake" ]; then
export BUILD_VERSION="legacy"
else
export BUILD_VERSION="modern"
fi
}
gen_build_dir() {
local work_dir="$1"
local vendor_name="$2"
local op_name="$3"
if [ -z "$op_name" ]; then
op_name=$(generate_op_name "$vendor_name")
fi
rm -rf "${work_dir}/${vendor_name}"
local json_file="${OPERATOR_JSON_FILE:-${work_dir}/${vendor_name}.json}"
msopgen gen -i "${json_file}" -f tf -c ${ai_core} -lan cpp -out "${work_dir}/${vendor_name}" -m 0 -op ${op_name}
set_build_version "${work_dir}/${vendor_name}"
if [ "${BUILD_VERSION}" = "modern" ]; then
overwrite_source_with_target "${work_dir}/${vendor_name}" \
"${__PROJECT_ROOT}/ai_core_op/custom_op_template" || return 1
fi
}
replace_operator_sources() {
local src_dir="$1"
local tgt_dir="$2"
if [ ! -d "$src_dir" ]; then
echo "ERROR: Source directory not found: $src_dir" >&2
return 1
fi
if [ ! -d "$tgt_dir" ]; then
echo "ERROR: Target directory not found: $tgt_dir" >&2
return 1
fi
rm -rf "${tgt_dir}/op_kernel"/*.h "${tgt_dir}/op_kernel"/*.cpp 2>/dev/null || true
rm -rf "${tgt_dir}/op_host"/*.h "${tgt_dir}/op_host"/*.cpp 2>/dev/null || true
cp -rf ${src_dir}/op_kernel/* "${tgt_dir}/op_kernel/"
cp -rf ${src_dir}/op_host/* "${tgt_dir}/op_host/"
if [ "${COPY_KERNEL_COMMON_UTILS}" = "1" ]; then
cp -f "${KERNEL_COMMON_UTILS_DIR:-${__PROJECT_ROOT}/ai_core_op/common}/kernel_common_utils.h" \
"${tgt_dir}/op_kernel/"
fi
return 0
}
build_onnx_adapter() {
local a_core="$1"
local o_path="$2"
local j_file="$3"
local v_name="$4"
local work_dir="$5"
if [ "$a_core" = "ai_core-Ascend310P3" ]; then
echo "Building ONNX adapter for ${a_core}..."
local build_script="${o_path}/build_onnx.sh"
if [ ! -f "$build_script" ]; then
echo "ERROR: build_onnx.sh not found in ${o_path}" >&2
return 1
fi
bash "$build_script"
local dest_dir="${work_dir}/${v_name}/framework/onnx_plugin"
mkdir -p "$dest_dir"
cp -rf "${j_file}" "$dest_dir"
local src_onnx_dir="$(dirname "$work_dir")/onnx_plugin"
if [ -d "$src_onnx_dir" ]; then
cp -rf "${src_onnx_dir}"/* "$dest_dir"
else
echo "ERROR: onnx_plugin directory not found at $src_onnx_dir" >&2
return 1
fi
fi
return 0
}
configure_cmake_presets() {
local v_name="$1"
local a_core="$2"
local build_ver="$3"
local target_dir="$4"
if [ -z "$5" ]; then
local enable_catlass=${enable_catlass:-"False"}
else
local enable_catlass="$5"
fi
local cmake_file="${target_dir}/CMakePresets.json"
if [ ! -f "$cmake_file" ]; then
echo "ERROR: CMakePresets.json file not exist in ${target_dir}." >&2
return 1
fi
if [ "$build_ver" = "legacy" ]; then
sed -i "s:\"/usr/local/Ascend/latest\":\"${ASCEND_TOOLKIT_HOME}\":g" "$cmake_file"
else
sed -i "s:\"/usr/local/Ascend/cann\":\"${ASCEND_TOOLKIT_HOME}\":g" "$cmake_file"
fi
sed -i "s:\"customize\":\"${v_name}\":g" "$cmake_file"
local chip_name_raw
chip_name_raw=$(echo "$a_core" | sed 's/^ai_core-//i')
local chip_name
chip_name=$(echo "$chip_name_raw" | tr '[:upper:]' '[:lower:]')
local config_file="${CONFIG_DIR}/transform.json"
if [ ! -f "$config_file" ]; then
echo "ERROR: Config file transform.json not found at $config_file" >&2
return 1
fi
local mapped_value
mapped_value=$(python3 -c "import json; cfg=json.load(open('${config_file}')); \
print(cfg['SOC_VERSION_TYPE'].get('${chip_name}', ''))" 2>/dev/null)
if [ -z "$mapped_value" ]; then
echo "WARNING: No mapping found for chip '$chip_name' in $config_file." >&2
return 1
else
echo "Mapping found: '$chip_name' -> '$mapped_value'"
sed -i "s:\"__ASCEND_COMPUTE_UNIT__\":\"${mapped_value}\":g" "$cmake_file"
fi
sed -i "s:\"/usr/bin/aarch64-linux-gnu-g++\":\"${GPP_PATH}\":g" "$cmake_file"
if [ -n "$CATLASS_HOME" ] && [ "$enable_catlass" = "True" ]; then
sed -i "s:\"/usr/local/Ascend/catlass\":\"${CATLASS_HOME}\":g" "$cmake_file"
line=`awk '/ENABLE_CATLASS/{print NR}' "$cmake_file"`
line=`expr ${line} + 2`
sed -i "${line}s/False/True/g" "$cmake_file"
fi
if [ "$with_onnx" = "true" ]; then
line=`awk '/WITH_ONNX/{print NR}' "$cmake_file"`
line=`expr ${line} + 2`
sed -i "${line}s/False/True/g" "$cmake_file"
fi
if [ "$ENABLE_SOURCE_PACKAGE" = "true" ] && [ "$BUILD_VERSION" = "modern" ]; then
line=`awk '/ENABLE_SOURCE_PACKAGE/{print NR}' "$cmake_file"`
line=`expr ${line} + 2`
sed -i "${line}s/False/True/g" "$cmake_file"
fi
if [ "${AI_CORE_PROFILE:-v220}" = "c310" ] && [ -n "${OPERATOR_JSON_FILE:-}" ] \
&& [ -f "${OPERATOR_JSON_FILE}" ]; then
local c310_install_root
c310_install_root="$(dirname "${target_dir}")"
cp -f "${OPERATOR_JSON_FILE}" "${c310_install_root}/${v_name}.json"
fi
return 0
}
apply_op_kernel_compile_options_dual() {
local target_dir="$1"
local opts_body="$2"
local f="${target_dir}/op_kernel/CMakeLists.txt"
local tmp
if [ ! -f "$f" ]; then
echo "ERROR: op_kernel/CMakeLists.txt not found under ${target_dir}" >&2
return 1
fi
if [ -z "$opts_body" ]; then
echo "ERROR: apply_op_kernel_compile_options_dual: opts_body is empty" >&2
return 1
fi
if grep -q 'npu_op_kernel_options' "$f"; then
if ! grep -q 'OPTIONS "--cce-long-call=true"' "$f"; then
echo "ERROR: ${f} uses npu_op_kernel_options but lacks OPTIONS \"--cce-long-call=true\" (sync with custom_op_template)" >&2
return 1
fi
tmp=$(mktemp "${TMPDIR:-/tmp}/op_builder_utils.XXXXXX")
awk -v extra=" ${opts_body}" '
/OPTIONS "--cce-long-call=true"/ && done == 0 {
sub(/OPTIONS "--cce-long-call=true"/, "&" extra)
done = 1
}
{ print }
' "$f" > "$tmp" && mv "$tmp" "$f"
else
tmp=$(mktemp "${TMPDIR:-/tmp}/op_builder_utils.XXXXXX")
{
printf '%s\n' "add_ops_compile_options(ALL OPTIONS ${opts_body})"
cat "$f"
} > "$tmp" && mv "$tmp" "$f"
fi
return 0
}
prepare_and_build() {
local build_ver="$1"
local v_name="$2"
local target_dir="$3"
if [ -z "$4" ]; then
local enable_catlass=${enable_catlass:-"False"}
else
local enable_catlass="$4"
fi
if [ "$build_ver" = "legacy" ]; then
echo "Preparing legacy build environment (legacy version)..."
local makeself_cmake="${target_dir}/cmake/makeself.cmake"
if [ -f "$makeself_cmake" ]; then
sed -i 's/--nomd5/--nomd5 --nocrc/g' "$makeself_cmake"
fi
local kernel_cmakelists="${target_dir}/op_kernel/CMakeLists.txt"
if [ -f "$kernel_cmakelists" ]; then
if [ "$with_onnx" = "true" ]; then
local add_line="install(FILES \${CMAKE_CURRENT_SOURCE_DIR}/../../${v_name}.json \
DESTINATION packages/vendors/\${vendor_name}/op_impl/ai_core/tbe/\${v_name}_impl/dynamic)"
sed -i "\$a\\$add_line" "$kernel_cmakelists"
fi
sed -i '1i\add_ops_compile_options(ALL OPTIONS --cce-long-call=true)' "$kernel_cmakelists"
fi
if [ -n "$CATLASS_HOME" ] && [ "$enable_catlass" = "True" ] && [ -f "$kernel_cmakelists" ]; then
local catlass_include_dir="${CATLASS_HOME}/include"
if [ "${AI_CORE_PROFILE:-v220}" = "c310" ]; then
sed -i "2i\add_ops_compile_options(ALL OPTIONS -DCATLASS_ARCH=3510 -DARCH_CODE=Ascend950 -DUSE_TLA=1 \
-DCATLASS_BISHENG_ARCH=a5 -DCATLASS_ARCH_A5_ENABLED=true -DENABLE_CV_COMM_VIA_SSBUF=true \
-DCATLASS_HOME=${CATLASS_HOME} -I${catlass_include_dir})" "${kernel_cmakelists}"
else
sed -i "2i\add_ops_compile_options(ALL OPTIONS -DCATLASS_ARCH=2201 -DARCH_CODE=AtlasA2 -DUSE_TLA=0 \
-DCATLASS_BISHENG_ARCH=a2 -DCATLASS_ARCH_A2_ENABLED=true -DENABLE_CV_COMM_VIA_SSBUF=true \
-DCATLASS_HOME=${CATLASS_HOME} -I${catlass_include_dir})" "${kernel_cmakelists}"
fi
fi
local host_cmakelists="${target_dir}/op_host/CMakeLists.txt"
if [ -f "$host_cmakelists" ]; then
sed -i "1 i include(../../../../cmake/func.cmake)" "$host_cmakelists"
local line1
line1=$(awk '/target_compile_definitions(cust_optiling PRIVATE OP_TILING_LIB)/{print NR}' "$host_cmakelists")
if [ -n "$line1" ]; then
sed -i "${line1}s/OP_TILING_LIB/OP_TILING_LIB LOG_CPP/g" "$host_cmakelists"
fi
local line2
line2=$(awk '/target_compile_definitions(cust_op_proto PRIVATE OP_PROTO_LIB)/{print NR}' "$host_cmakelists")
if [ -n "$line2" ]; then
sed -i "${line2}s/OP_PROTO_LIB/OP_PROTO_LIB LOG_CPP/g" "$host_cmakelists"
fi
fi
if compgen -G "${target_dir}/cmake/*.cmake" > /dev/null; then
for f in "${target_dir}"/cmake/*.cmake; do
sed -i '/\${ASCEND_CANN_PACKAGE_PATH}\/include/a\
\${ASCEND_CANN_PACKAGE_PATH}\/pkg_inc
' "$f"
done
fi
fi
local build_script="${target_dir}/build.sh"
if [ -f "$build_script" ]; then
(
cd "$target_dir" || exit 1
bash build.sh
) || return 1
else
echo "ERROR: build.sh not found in ${target_dir}" >&2
return 1
fi
return 0
}
install_operator_package() {
local o_id="$1"
local a_arch="$2"
local target_dir="$3"
local installer="${target_dir}/build_out/custom_opp_${o_id}_${a_arch}.run"
if [ ! -f "$installer" ]; then
echo "ERROR: Installer package not found: $installer" >&2
return 1
fi
echo "Installing operator package: $installer"
bash -- "$installer"
return $?
}
build_and_install_operator() {
local work_dir="$1"
local vendor_name="$2"
local with_onnx="$3"
local cmake_preset="${CMAKE_PRESET_VENDOR_NAME:-$vendor_name}"
echo "=========================================="
echo "Start Building Operator: ${vendor_name}"
echo "Target AI Core: ${ai_core}"
echo "Work Directory : ${work_dir}"
echo "=========================================="
validate_ai_core "$ai_core" || return 1
check_system_and_cann "$ai_core" || return 1
gen_build_dir "$work_dir" "$vendor_name" "${MSOPGEN_OP_NAME:-}" || return 1
local op_src_root="${OPERATOR_SOURCE_ROOT:-${work_dir}}"
replace_operator_sources "$op_src_root" "${work_dir}/${vendor_name}" || return 1
if [ -n "${INSERT_SUPPORT_950_PATHS:-}" ]; then
local _r
for _r in ${INSERT_SUPPORT_950_PATHS}; do
sed -i "1i #define SUPPORT_950" "${work_dir}/${vendor_name}/${_r}"
done
fi
if [ "$with_onnx" = "true" ]; then
build_onnx_adapter "$ai_core" "$ONNX_PATH" "$JSON_FILE" "$vendor_name" "$work_dir" || return 1
fi
configure_cmake_presets "$cmake_preset" "$ai_core" "$BUILD_VERSION" "${work_dir}/${vendor_name}" || return 1
prepare_and_build "$BUILD_VERSION" "$cmake_preset" "${work_dir}/${vendor_name}" || return 1
install_operator_package "$OS_ID" "$ARCH" "${work_dir}/${vendor_name}" || return 1
echo "=========================================="
echo "Operator ${vendor_name} built and installed successfully."
echo "=========================================="
return 0
}