Open
fix: CannBotDSL 不可用时跳过构建并隔离运行依赖 #3
zyh_gitcodeAdd on 15 days ago
fix: CannBotDSL 不可用时跳过构建并隔离运行依赖 #3
Open
Merged fromfix/cannbotdsl-compatibilityintozyh-mqsmla-l2-l0
Total: 12 files changed+569-18
| @@ -68,6 +68,8 @@ aclnnStatus aclnnMixedQuantSparseFlashMlaGetWorkspaceSize( | |||
| 68 | if (!workspaceSize || !executor) { | 68 | if (!workspaceSize || !executor) { |
| 69 | return ACLNN_ERR_PARAM_NULLPTR; | 69 | return ACLNN_ERR_PARAM_NULLPTR; |
| 70 | } | 70 | } |
| 71 | + *workspaceSize = 0; | ||
| 72 | + *executor = nullptr; | ||
| 71 | auto uniqueExecutor = CREATE_EXECUTOR(); | 73 | auto uniqueExecutor = CREATE_EXECUTOR(); |
| 72 | if (!uniqueExecutor.get()) { | 74 | if (!uniqueExecutor.get()) { |
| 73 | return ACLNN_ERR_INNER_CREATE_EXECUTOR; | 75 | return ACLNN_ERR_INNER_CREATE_EXECUTOR; |
| @@ -34,7 +34,8 @@ add_modules_sources( | |||
| 34 | ) | 34 | ) |
| 35 | 35 | ||
| 36 | if(BUILD_OPEN_PROJECT) | 36 | if(BUILD_OPEN_PROJECT) |
| 37 | - enable_cannbotdsl_kernel(mixed_quant_sparse_flash_mla "${CMAKE_CURRENT_SOURCE_DIR}/../op_dsl") | 37 | + enable_cannbotdsl_kernel(mixed_quant_sparse_flash_mla "${CMAKE_CURRENT_SOURCE_DIR}/../op_dsl" |
| 38 | + L0_NAME MixedQuantSparseFlashMlaDsl) | ||
| 38 | endif() | 39 | endif() |
| 39 | 40 | ||
| 40 | include(${CMAKE_CURRENT_SOURCE_DIR}/../../sparse_flash_mla/op_host/checkers/checker_sources.cmake) | 41 | include(${CMAKE_CURRENT_SOURCE_DIR}/../../sparse_flash_mla/op_host/checkers/checker_sources.cmake) |
| @@ -812,7 +812,7 @@ function collect_rpm_deb_package() { | |||
| 812 | 812 | ||
| 813 | function build_package(){ | 813 | function build_package(){ |
| 814 | if [ -f "${BUILD_PATH}/no_ops.flag" ]; then | 814 | if [ -f "${BUILD_PATH}/no_ops.flag" ]; then |
| 815 | - echo "[INFO] No operator can be compiled (all requested ops skipped by CANN version check), skip package generation." | 815 | + echo "[INFO] No operator can be compiled (all requested ops skipped by compatibility checks), skip package generation." |
| 816 | return 0 | 816 | return 0 |
| 817 | fi | 817 | fi |
| 818 | if [[ "${PACKAGE_TYPE}" == "all" ]]; then | 818 | if [[ "${PACKAGE_TYPE}" == "all" ]]; then |
| @@ -845,7 +845,7 @@ function build_host(){ | |||
| 845 | 845 | ||
| 846 | function build_kernel(){ | 846 | function build_kernel(){ |
| 847 | if [ -f "${BUILD_PATH}/no_ops.flag" ]; then | 847 | if [ -f "${BUILD_PATH}/no_ops.flag" ]; then |
| 848 | - echo "[INFO] No operator can be compiled (all requested ops skipped by CANN version check), skip kernel build." | 848 | + echo "[INFO] No operator can be compiled (all requested ops skipped by compatibility checks), skip kernel build." |
| 849 | return 0 | 849 | return 0 |
| 850 | fi | 850 | fi |
| 851 | build ops_transformer_kernel | 851 | build ops_transformer_kernel |
| @@ -568,6 +568,15 @@ else() | |||
| 568 | file(REMOVE "${CMAKE_BINARY_DIR}/no_ops.flag") | 568 | file(REMOVE "${CMAKE_BINARY_DIR}/no_ops.flag") |
| 569 | endif() | 569 | endif() |
| 570 | 570 | ||
| 571 | +get_property(_dsl_skipped GLOBAL PROPERTY CANNBOTDSL_SKIPPED_ONLY_OPS) | ||
| 572 | +if(_dsl_skipped AND NOT CANNBOTDSL_PACKAGES) | ||
| 573 | + set(_remaining_ops ${ASCEND_OP_NAME}) | ||
| 574 | + list(REMOVE_ITEM _remaining_ops ${_dsl_skipped}) | ||
| 575 | + if(NOT _remaining_ops) | ||
| 576 | + file(WRITE "${CMAKE_BINARY_DIR}/no_ops.flag" "no operator passed compatibility checks\n") | ||
| 577 | + endif() | ||
| 578 | +endif() | ||
| 579 | + | ||
| 571 | # 已生成的 DSL 包仍需构建和打包,不属于零算子。 | 580 | # 已生成的 DSL 包仍需构建和打包,不属于零算子。 |
| 572 | if (CANNBOTDSL_PACKAGES) | 581 | if (CANNBOTDSL_PACKAGES) |
| 573 | file(REMOVE "${CMAKE_BINARY_DIR}/no_ops.flag") | 582 | file(REMOVE "${CMAKE_BINARY_DIR}/no_ops.flag") |
| @@ -759,6 +759,27 @@ endfunction() | |||
| 759 | 759 | ||
| 760 | # Called by the operator CMakeLists after native operator selection. | 760 | # Called by the operator CMakeLists after native operator selection. |
| 761 | function(enable_cannbotdsl_kernel op_name source_dir) | 761 | function(enable_cannbotdsl_kernel op_name source_dir) |
| 762 | + set(${op_name}_DSL_AVAILABLE FALSE PARENT_SCOPE) | ||
| 763 | + cmake_parse_arguments(DSL "" "L0_NAME" "" ${ARGN}) | ||
| 764 | + if(DSL_L0_NAME AND TARGET ${OPHOST_NAME}_opapi_obj) | ||
| 765 | + string(REGEX REPLACE "([a-z0-9])([A-Z])" "\\1_\\2" _l0_stem "${DSL_L0_NAME}") | ||
| 766 | + string(TOLOWER "${_l0_stem}" _l0_stem) | ||
| 767 | + set(_unavailable "${CMAKE_BINARY_DIR}/cannbot_dsl/unavailable/${op_name}") | ||
| 768 | + file(MAKE_DIRECTORY "${_unavailable}") | ||
| 769 | + file(CONFIGURE OUTPUT "${_unavailable}/${_l0_stem}.h" CONTENT [=[ | ||
| 770 | +#pragma once | ||
| 771 | +#include <aclnn/aclnn_base.h> | ||
| 772 | +#include <opdev/op_log.h> | ||
| 773 | +namespace l0op { | ||
| 774 | +template <typename... Args> inline aclnnStatus @DSL_L0_NAME@(Args&&...) | ||
| 775 | +{ | ||
| 776 | + OP_LOGE(ACLNN_ERR_INNER, "CannBotDSL @op_name@ was not compiled in this Host package; check the build compatibility warning and install matching components."); | ||
| 777 | + return ACLNN_ERR_INNER; | ||
| 778 | +} | ||
| 779 | +} | ||
| 780 | +]=] @ONLY) | ||
| 781 | + target_include_directories(${OPHOST_NAME}_opapi_obj PRIVATE "${_unavailable}") | ||
| 782 | + endif() | ||
| 762 | set(_dsl_compute_unit ascend950) | 783 | set(_dsl_compute_unit ascend950) |
| 763 | if(NOT _dsl_compute_unit IN_LIST ASCEND_COMPUTE_UNIT | 784 | if(NOT _dsl_compute_unit IN_LIST ASCEND_COMPUTE_UNIT |
| 764 | OR (NOT ENABLE_OPS_HOST AND NOT ENABLE_OPS_KERNEL)) | 785 | OR (NOT ENABLE_OPS_HOST AND NOT ENABLE_OPS_KERNEL)) |
| @@ -787,14 +808,48 @@ function(enable_cannbotdsl_kernel op_name source_dir) | |||
| 787 | set(_mode kernel) | 808 | set(_mode kernel) |
| 788 | endif() | 809 | endif() |
| 789 | set(_stage "${_dsl_root}/${_mode}/${op_name}") | 810 | set(_stage "${_dsl_root}/${_mode}/${op_name}") |
| 811 | + if(TARGET nnopbase) | ||
| 812 | + include(CheckCXXSourceCompiles) | ||
| 813 | + set(CMAKE_REQUIRED_INCLUDES ${NNOPBASE_OPDEV_INC_DIR} ${NNOPBASE_ACLNN_INC_DIR} | ||
| 814 | + ${ACL_INC_DIR} "${ASCEND_CANN_PACKAGE_PATH}/include") | ||
| 815 | + set(CMAKE_REQUIRED_LIBRARIES nnopbase "${ASCEND_CANN_PACKAGE_PATH}/lib64/libascendcl.so") | ||
| 816 | + set(CMAKE_REQUIRED_FLAGS "-std=c++17") | ||
| 817 | + set(CMAKE_REQUIRED_LINK_OPTIONS "-Wl,--allow-shlib-undefined") | ||
| 818 | + unset(_dsl_abi_ok CACHE) | ||
| 819 | + check_cxx_source_compiles([=[ | ||
| 820 | +#include <acl/acl.h> | ||
| 821 | +#include <opdev/direct_invoke_task.h> | ||
| 822 | +int main() { | ||
| 823 | + using Submit = aclnnStatus (*)(const char*, uint32_t, op::DirectInvokePrepareFn, | ||
| 824 | + op::DirectInvokeLaunchFn, op::DirectInvokeDestroyFn, aclOpExecutor*, op::OpArgContext*); | ||
| 825 | + Submit volatile submit = &op::AddDirectInvokeTask; | ||
| 826 | + auto volatile load = &aclrtBinaryLoadFromData; | ||
| 827 | + auto volatile get = &aclrtBinaryGetFunctionByEntry; | ||
| 828 | + auto volatile launch = &aclrtLaunchKernelWithArgsArray; | ||
| 829 | + auto volatile simt = &aclrtLaunchSIMTKernelWithArgsArray; | ||
| 830 | + return submit && load && get && launch && simt ? 0 : 1; | ||
| 831 | +} | ||
| 832 | +]=] _dsl_abi_ok) | ||
| 833 | + endif() | ||
| 790 | file(GLOB_RECURSE _dependencies CONFIGURE_DEPENDS "${_source_dir}/*.py") | 834 | file(GLOB_RECURSE _dependencies CONFIGURE_DEPENDS "${_source_dir}/*.py") |
| 791 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_dependencies} "${_builder}") | 835 | set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_dependencies} "${_builder}") |
| 792 | - execute_process(COMMAND "${CMAKE_COMMAND}" -E env "ASCEND_HOME_PATH=${ASCEND_CANN_PACKAGE_PATH}" | 836 | + if(TARGET nnopbase AND NOT _dsl_abi_ok) |
| 793 | - TORCH_DEVICE_BACKEND_AUTOLOAD=0 PYTHONDONTWRITEBYTECODE=1 | 837 | + set(_result 77) |
| 794 | - "${HI_PYTHON}" "${_builder}" | 838 | + set(_stderr "CANN Runtime or OpBase DirectInvoke ABI does not match; see the CMake configure log") |
| 795 | - --source "${_source_dir}/${op_name}.py" --output "${_stage}" | 839 | + else() |
| 796 | - --soc "${_dsl_compute_unit}" --mode "${_mode}" --aclnn-type "${_aclnn_type}" | 840 | + execute_process(COMMAND "${CMAKE_COMMAND}" -E env "ASCEND_HOME_PATH=${ASCEND_CANN_PACKAGE_PATH}" |
| 797 | - RESULT_VARIABLE _result OUTPUT_VARIABLE _stdout ERROR_VARIABLE _stderr) | 841 | + TORCH_DEVICE_BACKEND_AUTOLOAD=0 PYTHONDONTWRITEBYTECODE=1 |
| 842 | + "${HI_PYTHON}" "${_builder}" | ||
| 843 | + --source "${_source_dir}/${op_name}.py" --output "${_stage}" | ||
| 844 | + --soc "${_dsl_compute_unit}" --mode "${_mode}" --aclnn-type "${_aclnn_type}" | ||
| 845 | + RESULT_VARIABLE _result OUTPUT_VARIABLE _stdout ERROR_VARIABLE _stderr) | ||
| 846 | + endif() | ||
| 847 | + if(_result EQUAL 77) | ||
| 848 | + file(REMOVE_RECURSE "${_stage}") | ||
| 849 | + file(REMOVE "${_stage}.input.json" "${_stage}.report.json") | ||
| 850 | + message(WARNING "[CannBotDSL] ${op_name}: DSL not compiled; other implementations remain enabled.\n${_stderr}") | ||
| 851 | + return() | ||
| 852 | + endif() | ||
| 798 | if(NOT _result EQUAL 0) | 853 | if(NOT _result EQUAL 0) |
| 799 | file(MAKE_DIRECTORY "${_dsl_root}/${_mode}") | 854 | file(MAKE_DIRECTORY "${_dsl_root}/${_mode}") |
| 800 | set(_error_log "${_stage}.error.log") | 855 | set(_error_log "${_stage}.error.log") |
| @@ -812,6 +867,7 @@ function(enable_cannbotdsl_kernel op_name source_dir) | |||
| 812 | endif() | 867 | endif() |
| 813 | message(FATAL_ERROR "DSL source publication failed for ${op_name}: ${_cause}\nExit result: ${_result}\nPython: ${HI_PYTHON}\nFull log: ${_error_log}") | 868 | message(FATAL_ERROR "DSL source publication failed for ${op_name}: ${_cause}\nExit result: ${_result}\nPython: ${HI_PYTHON}\nFull log: ${_error_log}") |
| 814 | endif() | 869 | endif() |
| 870 | + set(${op_name}_DSL_AVAILABLE TRUE PARENT_SCOPE) | ||
| 815 | file(READ "${_stage}.report.json" _report) | 871 | file(READ "${_stage}.report.json" _report) |
| 816 | string(JSON _count LENGTH "${_report}" dependencies) | 872 | string(JSON _count LENGTH "${_report}" dependencies) |
| 817 | math(EXPR _last "${_count} - 1") | 873 | math(EXPR _last "${_count} - 1") |
| @@ -134,6 +134,13 @@ function(_cannbotdsl_add_runtime source) | |||
| 134 | endif() | 134 | endif() |
| 135 | endforeach() | 135 | endforeach() |
| 136 | add_subdirectory("${source}" "${CMAKE_BINARY_DIR}/cannbot_dsl/runtime") | 136 | add_subdirectory("${source}" "${CMAKE_BINARY_DIR}/cannbot_dsl/runtime") |
| 137 | + get_target_property(_runtime_type opkit_runtime TYPE) | ||
| 138 | + if(NOT _runtime_type STREQUAL "INTERFACE_LIBRARY") | ||
| 139 | + # Guard the runtime submission once for generated and handwritten L0 users. | ||
| 140 | + # Scope weak declarations here so newer optional APIs do not prevent ASC library loading. | ||
| 141 | + set(_compat_header "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../common/include/common/cannbotdsl_compat.h") | ||
| 142 | + target_compile_options(opkit_runtime PRIVATE "SHELL:-include '${_compat_header}'") | ||
| 143 | + endif() | ||
| 137 | foreach(_variable CANN_ROOT OPBASE_INCLUDE_DIR OPBASE_LIBRARY) | 144 | foreach(_variable CANN_ROOT OPBASE_INCLUDE_DIR OPBASE_LIBRARY) |
| 138 | if(_variable IN_LIST _temporary_cache) | 145 | if(_variable IN_LIST _temporary_cache) |
| 139 | unset(${_variable} CACHE) | 146 | unset(${_variable} CACHE) |
| @@ -162,8 +169,6 @@ function(cannbotdsl_add_modules out_headers) | |||
| 162 | _cannbotdsl_add_runtime("${_stage}/runtime") | 169 | _cannbotdsl_add_runtime("${_stage}/runtime") |
| 163 | endif() | 170 | endif() |
| 164 | if(TARGET ${OPHOST_NAME}_opapi_obj) | 171 | if(TARGET ${OPHOST_NAME}_opapi_obj) |
| 165 | - string(TOUPPER "${op_name}" _op_macro) | ||
| 166 | - target_compile_definitions(${OPHOST_NAME}_opapi_obj PRIVATE "${_op_macro}_WITH_DSL=1") | ||
| 167 | target_link_libraries(${OPHOST_NAME}_opapi_obj PRIVATE opkit::runtime) | 172 | target_link_libraries(${OPHOST_NAME}_opapi_obj PRIVATE opkit::runtime) |
| 168 | endif() | 173 | endif() |
| 169 | string(JSON _last LENGTH "${_package}" operators) | 174 | string(JSON _last LENGTH "${_package}" operators) |
| @@ -189,7 +194,27 @@ function(cannbotdsl_add_modules out_headers) | |||
| 189 | add_subdirectory("${_module_dir}" "${_dsl_root}/${_operator}/module") | 194 | add_subdirectory("${_module_dir}" "${_dsl_root}/${_operator}/module") |
| 190 | endif() | 195 | endif() |
| 191 | if(TARGET ${OPHOST_NAME}_opapi_obj) | 196 | if(TARGET ${OPHOST_NAME}_opapi_obj) |
| 192 | - target_include_directories(${OPHOST_NAME}_opapi_obj PRIVATE "${_module_dir}/op_api") | 197 | + target_include_directories(${OPHOST_NAME}_opapi_obj BEFORE PRIVATE "${_module_dir}/op_api") |
| 198 | + endif() | ||
| 199 | + get_target_property(_runtime_type opkit_runtime TYPE) | ||
| 200 | + if(NOT _runtime_type STREQUAL "INTERFACE_LIBRARY" AND NOT _aclnn_type STREQUAL "aclnn_exclude") | ||
| 201 | + # Adapt only the generated L0 build copy. Keep the publication and its | ||
| 202 | + # integrity manifest intact; no OpKit function names are redefined. | ||
| 203 | + set(_l0 "${_module_dir}/op_api/${_stem}.cpp") | ||
| 204 | + file(READ "${_l0}" _l0_code) | ||
| 205 | + set(_submit "return ADD_TO_LAUNCHER_LIST_CANNBOTDSL(") | ||
| 206 | + string(FIND "${_l0_code}" "${_submit}" _submit_at) | ||
| 207 | + if(_submit_at LESS 0) | ||
| 208 | + message(FATAL_ERROR "Unsupported generated DSL L0 entry: ${_l0}") | ||
| 209 | + endif() | ||
| 210 | + set(_compat_header "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../common/include/common/cannbotdsl_compat.h") | ||
| 211 | + string(REPLACE "${_submit}" | ||
| 212 | + "auto compatibility_status = ops_transformer::CheckCannbotdslRuntime(\"${_operator}\");\n if (compatibility_status != ACLNN_SUCCESS) { return compatibility_status; }\n ${_submit}" | ||
| 213 | + _l0_code "${_l0_code}") | ||
| 214 | + set(_checked_l0 "${_dsl_root}/${_operator}/checked_l0.cpp") | ||
| 215 | + file(WRITE "${_checked_l0}" "#include \"${_compat_header}\"\n${_l0_code}") | ||
| 216 | + set_source_files_properties("${_l0}" TARGET_DIRECTORY "operator_${_operator}" PROPERTIES HEADER_FILE_ONLY TRUE) | ||
| 217 | + target_sources("operator_${_operator}" PRIVATE "${_checked_l0}") | ||
| 193 | endif() | 218 | endif() |
| 194 | # Hide implementation symbols while retaining the public ACLNN declarations. | 219 | # Hide implementation symbols while retaining the public ACLNN declarations. |
| 195 | set_target_properties("operator_${_operator}" PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES) | 220 | set_target_properties("operator_${_operator}" PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES) |
| @@ -12,12 +12,46 @@ | |||
| 12 | import argparse | 12 | import argparse |
| 13 | import fcntl | 13 | import fcntl |
| 14 | import hashlib | 14 | import hashlib |
| 15 | +import importlib.metadata | ||
| 16 | +import inspect | ||
| 15 | import json | 17 | import json |
| 16 | import os | 18 | import os |
| 17 | from pathlib import Path | 19 | from pathlib import Path |
| 18 | import shutil | 20 | import shutil |
| 21 | +import sys | ||
| 19 | import tempfile | 22 | import tempfile |
| 20 | 23 | ||
| 24 | +class DslUnavailable(ValueError): | ||
| 25 | + """The optional DSL build environment does not meet its requirements.""" | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +def check_distribution(name): | ||
| 29 | + from packaging.requirements import Requirement | ||
| 30 | + from packaging.specifiers import SpecifierSet | ||
| 31 | + | ||
| 32 | + dist = importlib.metadata.distribution(name) | ||
| 33 | + python = dist.metadata.get("Requires-Python") | ||
| 34 | + if python and not SpecifierSet(python).contains(".".join(map(str, sys.version_info[:3]))): | ||
| 35 | + raise DslUnavailable(f"{name} requires Python {python}; detected {sys.version.split()[0]}") | ||
| 36 | + for text in dist.requires or (): | ||
| 37 | + requirement = Requirement(text) | ||
| 38 | + if requirement.marker and not requirement.marker.evaluate({"extra": ""}): | ||
| 39 | + continue | ||
| 40 | + found = importlib.metadata.version(requirement.name) | ||
| 41 | + if requirement.specifier and not requirement.specifier.contains(found, prereleases=True): | ||
| 42 | + raise DslUnavailable(f"{name} requires {requirement}; detected {requirement.name}=={found}") | ||
| 43 | + return {"version": dist.version} | ||
| 44 | + | ||
| 45 | +def check_dependencies(): | ||
| 46 | + try: | ||
| 47 | + for name in ("cannbot-dsl", "opkit"): | ||
| 48 | + check_distribution(name) | ||
| 49 | + import opkit | ||
| 50 | + if not {"mode", "l0", "l2"}.issubset(inspect.signature(opkit.build).parameters): | ||
| 51 | + raise DslUnavailable("OpKit must support Host/Kernel/All publication and l0/l2") | ||
| 52 | + except (ImportError, OSError, RuntimeError, ValueError) as exc: | ||
| 53 | + raise DslUnavailable(str(exc)) from exc | ||
| 54 | + | ||
| 21 | 55 | ||
| 22 | def resolve_target(soc): | 56 | def resolve_target(soc): |
| 23 | # Match the SoCs supported by OpKit device publication. Resolve the selected | 57 | # Match the SoCs supported by OpKit device publication. Resolve the selected |
| @@ -107,7 +141,7 @@ def framework_files(): | |||
| 107 | distribution("opkit").locate_file("opkit/resources/runtime/CMakeLists.txt") | 141 | distribution("opkit").locate_file("opkit/resources/runtime/CMakeLists.txt") |
| 108 | ) | 142 | ) |
| 109 | if not runtime.is_file(): | 143 | if not runtime.is_file(): |
| 110 | - raise ValueError( | 144 | + raise DslUnavailable( |
| 111 | f"OpKit at {opkit.__file__} does not provide the current device/C++ runtime at {runtime}. " | 145 | f"OpKit at {opkit.__file__} does not provide the current device/C++ runtime at {runtime}. " |
| 112 | "The build toolchain requires matching device/C++ compiler components." | 146 | "The build toolchain requires matching device/C++ compiler components." |
| 113 | ) | 147 | ) |
| @@ -138,8 +172,11 @@ def framework_files(): | |||
| 138 | ) | 172 | ) |
| 139 | from opkit.compiler.toolchain import optimizer_path | 173 | from opkit.compiler.toolchain import optimizer_path |
| 140 | 174 | ||
| 141 | - paths.add(Path(optimizer_path()).resolve(strict=True)) | 175 | + try: |
| 142 | - paths.add(Path(resolve_bisheng().path).resolve(strict=True)) | 176 | + paths.add(Path(optimizer_path()).resolve(strict=True)) |
| 177 | + paths.add(Path(resolve_bisheng().path).resolve(strict=True)) | ||
| 178 | + except (OSError, RuntimeError) as exc: | ||
| 179 | + raise DslUnavailable(str(exc)) from exc | ||
| 143 | version = Path(os.environ["ASCEND_HOME_PATH"]) / "version.info" | 180 | version = Path(os.environ["ASCEND_HOME_PATH"]) / "version.info" |
| 144 | if version.is_file(): | 181 | if version.is_file(): |
| 145 | paths.add(version.resolve()) | 182 | paths.add(version.resolve()) |
| @@ -276,8 +313,13 @@ def main(): | |||
| 276 | args.output.parent.mkdir(parents=True, exist_ok=True) | 313 | args.output.parent.mkdir(parents=True, exist_ok=True) |
| 277 | with Path(str(args.output) + ".lock").open("w") as lock: | 314 | with Path(str(args.output) + ".lock").open("w") as lock: |
| 278 | fcntl.flock(lock, fcntl.LOCK_EX) | 315 | fcntl.flock(lock, fcntl.LOCK_EX) |
| 279 | - build(args) | 316 | + try: |
| 317 | + check_dependencies() | ||
| 318 | + build(args) | ||
| 319 | + except DslUnavailable as exc: | ||
| 320 | + print(f"CannBotDSL unavailable: {exc}", file=sys.stderr) | ||
| 321 | + return 77 | ||
| 280 | 322 | ||
| 281 | 323 | ||
| 282 | if __name__ == "__main__": | 324 | if __name__ == "__main__": |
| 283 | - main() | 325 | + raise SystemExit(main()) |
| @@ -0,0 +1,45 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * Licensed under the CANN Open Software License Agreement Version 2.0. | ||
| 4 | + */ | ||
| 5 | +#ifndef OPS_TRANSFORMER_CANNBOTDSL_COMPAT_H_ | ||
| 6 | +#define OPS_TRANSFORMER_CANNBOTDSL_COMPAT_H_ | ||
| 7 | + | ||
| 8 | +// Optional DSL runtime and L0 entries share these capability checks. | ||
| 9 | +// ASC does not require these headers or symbols. | ||
| 10 | +#include "runtime.h" | ||
| 11 | +#include "opdev/op_log.h" | ||
| 12 | +#include <cstring> | ||
| 13 | + | ||
| 14 | +#pragma weak aclrtBinaryLoadFromData | ||
| 15 | +#pragma weak aclrtBinaryGetFunctionByEntry | ||
| 16 | +#pragma weak aclrtLaunchKernelWithArgsArray | ||
| 17 | +#pragma weak aclrtLaunchSIMTKernelWithArgsArray | ||
| 18 | + | ||
| 19 | +namespace op { | ||
| 20 | +extern decltype(AddDirectInvokeTask) AddDirectInvokeTask __attribute__((weak)); | ||
| 21 | + | ||
| 22 | +} // namespace op | ||
| 23 | + | ||
| 24 | +namespace ops_transformer { | ||
| 25 | +inline aclnnStatus CheckCannbotdslRuntime(const char* name) | ||
| 26 | +{ | ||
| 27 | + const bool directInvoke = op::AddDirectInvokeTask != nullptr; | ||
| 28 | + const bool runtime = aclrtBinaryLoadFromData && aclrtBinaryGetFunctionByEntry && | ||
| 29 | + aclrtLaunchKernelWithArgsArray && aclrtLaunchSIMTKernelWithArgsArray; | ||
| 30 | + if (!directInvoke || !runtime) { | ||
| 31 | + OP_LOGE(ACLNN_ERR_INNER, "CannBotDSL operator %s is unavailable: incompatible %s.", name, | ||
| 32 | + directInvoke ? "Runtime binary/launch APIs" : "OpBase DirectInvoke ABI"); | ||
| 33 | + return ACLNN_ERR_INNER; | ||
| 34 | + } | ||
| 35 | + const char* soc = aclrtGetSocName(); | ||
| 36 | + if (soc == nullptr || std::strncmp(soc, "Ascend950", 9) != 0) { | ||
| 37 | + OP_LOGE(ACLNN_ERR_INNER, "CannBotDSL operator %s requires Ascend950; detected %s.", | ||
| 38 | + name, soc == nullptr ? "unknown" : soc); | ||
| 39 | + return ACLNN_ERR_INNER; | ||
| 40 | + } | ||
| 41 | + return ACLNN_SUCCESS; | ||
| 42 | +} | ||
| 43 | +} // namespace ops_transformer | ||
| 44 | + | ||
| 45 | +#endif | ||
| @@ -144,3 +144,15 @@ DSL Host 模块。最终由原有安装规则汇总,不需要手动拼接中 | |||
| 144 | 144 | ||
| 145 | 扩展编译规格、接入 ASC/DSL 混合算子及了解各接口分类,请继续阅读 | 145 | 扩展编译规格、接入 ASC/DSL 混合算子及了解各接口分类,请继续阅读 |
| 146 | [DSL 开发指南](cannbotdsl_develop_guide.md)。 | 146 | [DSL 开发指南](cannbotdsl_develop_guide.md)。 |
| 147 | + | ||
| 148 | +## 可选 DSL 环境与兼容性提示 | ||
| 149 | + | ||
| 150 | +DSL 是可选后端。公共构建会检查本次 Python 中的 `cannbot-dsl`、`opkit` 及其发布元数据声明的依赖范围、Host/Kernel 分模式接口和构建工具;同时检查所选 CANN 的 Runtime API 与 OpBase DirectInvoke 接口。Host 任务也需要 DSL 前端和 Host 降低工具,但不要求构建机器存在 NPU。 | ||
| 151 | + | ||
| 152 | +环境不满足时输出 `[CannBotDSL] ... DSL not compiled`,不登记该 DSL 发布包。混合算子的 ASC 部分继续构建,其他算子仍按原有条件编译和安装。跳过原因直接写入构建日志,对应的历史发布包会被清理,避免误收进新安装包。仅选择 AddDsl 且环境不满足时,日志会说明未生成可执行算子并跳过打包。 | ||
| 153 | + | ||
| 154 | +Transformer 在生成 L0 的编译副本中显式调用公共检查,保留发布包与校验信息不变,不使用函数名称替换宏。运行时检查实际 OpBase DirectInvoke ABI、Runtime 二进制加载/启动接口和 SoC;接口按弱引用处理,避免可选 DSL 的新符号阻止 ASC 共享库加载。检查失败只返回当前 DSL 调用的错误,日志指出不满足的组件能力。MQSMLA 的 DSL 分支未编入时也会明确提示,不自动切换到数据格式不同的 ASC 分支。AddDsl 的 Torch 调用若未找到安装接口,会提示检查构建兼容日志和 OPP 安装。 | ||
| 155 | + | ||
| 156 | +兼容性检查使用组件声明的版本范围和实际接口能力,不要求 Toolkit 与构建时版本字符串完全相等。源码错误、DSL 编译错误和产物校验错误仍会报告失败,不会被作为环境不支持而隐藏。运行 C++ ACLNN 产物不要求部署机器安装 Python 的 CannBotDSL/OpKit。 | ||
| 157 | + | ||
| 158 | +混合算子的公共发布登记可指定 L0 名称,例如 `enable_cannbotdsl_kernel(mixed_quant_sparse_flash_mla "${CMAKE_CURRENT_SOURCE_DIR}/../op_dsl" L0_NAME MixedQuantSparseFlashMlaDsl)`。Host 未发布时框架生成轻量不可用入口,Host 发布成功时优先使用真实生成接口;算子 C++ 不需要通过 `WITH_DSL` 条件编译分发。 | ||
| @@ -6,6 +6,11 @@ if(NOT "add_dsl" IN_LIST ASCEND_OP_NAME) | |||
| 6 | return() | 6 | return() |
| 7 | endif() | 7 | endif() |
| 8 | 8 | ||
| 9 | -add_op_to_compiled_list("${CMAKE_CURRENT_SOURCE_DIR}") | ||
| 10 | add_modules_sources(OPTYPE add_dsl ACLNNTYPE aclnn) | 9 | add_modules_sources(OPTYPE add_dsl ACLNNTYPE aclnn) |
| 11 | enable_cannbotdsl_kernel(add_dsl "${CMAKE_CURRENT_SOURCE_DIR}/op_dsl") | 10 | enable_cannbotdsl_kernel(add_dsl "${CMAKE_CURRENT_SOURCE_DIR}/op_dsl") |
| 11 | + | ||
| 12 | +if(add_dsl_DSL_AVAILABLE) | ||
| 13 | + add_op_to_compiled_list("${CMAKE_CURRENT_SOURCE_DIR}") | ||
| 14 | +else() | ||
| 15 | + set_property(GLOBAL APPEND PROPERTY CANNBOTDSL_SKIPPED_ONLY_OPS add_dsl) | ||
| 16 | +endif() | ||
| @@ -33,6 +33,9 @@ at::Tensor add_dsl(const at::Tensor& x, const at::Tensor& y) | |||
| 33 | 33 | ||
| 34 | const c10::OptionalDeviceGuard device_guard(x.device()); | 34 | const c10::OptionalDeviceGuard device_guard(x.device()); |
| 35 | auto out = at::empty(x.sizes(), x.options()); | 35 | auto out = at::empty(x.sizes(), x.options()); |
| 36 | + TORCH_CHECK(GetOpApiFuncAddr("aclnnAddDslGetWorkspaceSize") != nullptr, | ||
| 37 | + "CannBotDSL AddDsl was not compiled or installed in the selected OPP package. " | ||
| 38 | + "Check the build compatibility warning and install matching CANN/DSL components."); | ||
| 36 | ACLNN_CMD(aclnnAddDsl, x, y, out); | 39 | ACLNN_CMD(aclnnAddDsl, x, y, out); |
| 37 | return out; | 40 | return out; |
| 38 | } | 41 | } |
| @@ -0,0 +1,351 @@ | |||
| 1 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | +# Licensed under the CANN Open Software License Agreement Version 2.0. | ||
| 3 | +"""Optional DSL compatibility must not prevent unrelated ASC builds or calls.""" | ||
| 4 | + | ||
| 5 | +import ctypes | ||
| 6 | +import importlib.util | ||
| 7 | +import os | ||
| 8 | +from pathlib import Path | ||
| 9 | +import shutil | ||
| 10 | +import subprocess | ||
| 11 | +import sys | ||
| 12 | +import types | ||
| 13 | + | ||
| 14 | +import pytest | ||
| 15 | + | ||
| 16 | +ROOT = Path(__file__).resolve().parents[3] | ||
| 17 | + | ||
| 18 | +RUNTIME_INTERFACE = '''#pragma once | ||
| 19 | +#include <cstdint> | ||
| 20 | +using aclnnStatus = int; | ||
| 21 | +struct aclOpExecutor {}; | ||
| 22 | +constexpr int ACLNN_SUCCESS=0; | ||
| 23 | +constexpr int ACLNN_ERR_INNER=7; | ||
| 24 | +extern "C" { | ||
| 25 | +int aclrtBinaryLoadFromData(); | ||
| 26 | +int aclrtBinaryGetFunctionByEntry(); | ||
| 27 | +int aclrtLaunchKernelWithArgsArray(); | ||
| 28 | +int aclrtLaunchSIMTKernelWithArgsArray(); | ||
| 29 | +const char* aclrtGetSocName(); | ||
| 30 | +} | ||
| 31 | +namespace op { | ||
| 32 | +struct OpArgContext {}; | ||
| 33 | +using DirectInvokePrepareFn = void(*)(); | ||
| 34 | +using DirectInvokeLaunchFn = void(*)(); | ||
| 35 | +using DirectInvokeDestroyFn = void(*)(); | ||
| 36 | +inline void DestroyOpArgContext(OpArgContext*) {} | ||
| 37 | +aclnnStatus AddDirectInvokeTask(const char*, uint32_t, DirectInvokePrepareFn, | ||
| 38 | + DirectInvokeLaunchFn, DirectInvokeDestroyFn, aclOpExecutor*, OpArgContext*); | ||
| 39 | +} | ||
| 40 | +''' | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +def checker(): | ||
| 44 | + spec = importlib.util.spec_from_file_location("dsl_compat", ROOT / "cmake/scripts/build_cannbotdsl.py") | ||
| 45 | + module = importlib.util.module_from_spec(spec) | ||
| 46 | + spec.loader.exec_module(module) | ||
| 47 | + return module | ||
| 48 | + | ||
| 49 | + | ||
| 50 | +@pytest.mark.parametrize("version,expected", [("0.7.0", False), ("0.8.1", True), ("0.9.0", False)]) | ||
| 51 | +def test_declared_dependency_range(monkeypatch, version, expected): | ||
| 52 | + module = checker() | ||
| 53 | + monkeypatch.setattr(module.importlib.metadata, "distribution", lambda name: types.SimpleNamespace( | ||
| 54 | + version="0.8.0", requires=["cannbot-dsl>=0.8.0,<0.9.0", 'torch; extra == "test"'], metadata={})) | ||
| 55 | + monkeypatch.setattr(module.importlib.metadata, "version", lambda name: version) | ||
| 56 | + if expected: | ||
| 57 | + assert module.check_distribution("opkit") == {"version": "0.8.0"} | ||
| 58 | + else: | ||
| 59 | + with pytest.raises(module.DslUnavailable, match="cannbot-dsl"): | ||
| 60 | + module.check_distribution("opkit") | ||
| 61 | + | ||
| 62 | + | ||
| 63 | +def test_requires_python_is_checked(monkeypatch): | ||
| 64 | + module = checker() | ||
| 65 | + monkeypatch.setattr(module.importlib.metadata, "distribution", lambda name: types.SimpleNamespace( | ||
| 66 | + version="0.8.0", requires=[], metadata={"Requires-Python": ">=99"})) | ||
| 67 | + with pytest.raises(module.DslUnavailable, match="Python"): | ||
| 68 | + module.check_distribution("opkit") | ||
| 69 | + | ||
| 70 | + | ||
| 71 | +def test_missing_component_skips_publication(tmp_path, monkeypatch, capsys): | ||
| 72 | + module = checker() | ||
| 73 | + def missing(): | ||
| 74 | + raise module.DslUnavailable("No module named 'opkit'") | ||
| 75 | + monkeypatch.setattr(module, "check_dependencies", missing) | ||
| 76 | + monkeypatch.setattr(sys, "argv", ["build", "--source", str(tmp_path / "entry.py"), | ||
| 77 | + "--output", str(tmp_path / "package"), "--soc", "ascend950"]) | ||
| 78 | + assert module.main() == 77 | ||
| 79 | + assert "opkit" in capsys.readouterr().err | ||
| 80 | + assert not (tmp_path / "package").exists() | ||
| 81 | + | ||
| 82 | + | ||
| 83 | +def test_source_error_is_not_an_environment_skip(tmp_path, monkeypatch): | ||
| 84 | + module = checker() | ||
| 85 | + monkeypatch.setattr(module, "check_dependencies", lambda: None) | ||
| 86 | + def fail(args): | ||
| 87 | + raise ValueError("operator compile error") | ||
| 88 | + monkeypatch.setattr(module, "build", fail) | ||
| 89 | + monkeypatch.setattr(sys, "argv", ["build", "--source", str(tmp_path / "entry.py"), | ||
| 90 | + "--output", str(tmp_path / "package"), "--soc", "ascend950"]) | ||
| 91 | + with pytest.raises(ValueError, match="operator compile error"): | ||
| 92 | + module.main() | ||
| 93 | + | ||
| 94 | + | ||
| 95 | +@pytest.mark.parametrize("mode", ["all", "host", "kernel"]) | ||
| 96 | +def test_compatibility_skip_preserves_asc_and_removes_old_publication(tmp_path, mode): | ||
| 97 | + scripts = tmp_path / "cmake/scripts" | ||
| 98 | + scripts.mkdir(parents=True) | ||
| 99 | + (scripts / "build_cannbotdsl.py").write_text( | ||
| 100 | + "import sys\nprint('CannBotDSL unavailable: OpBase ABI unavailable', file=sys.stderr)\nsys.exit(77)\n") | ||
| 101 | + (tmp_path / "entry.py").write_text("# DSL collector") | ||
| 102 | + stage = tmp_path / f"build/cannbot_dsl/{mode}/entry" | ||
| 103 | + stage.mkdir(parents=True) | ||
| 104 | + (stage / "package.json").write_text("old publication") | ||
| 105 | + for suffix in (".input.json", ".report.json"): | ||
| 106 | + Path(str(stage) + suffix).write_text("old report") | ||
| 107 | + (tmp_path / "asc.cpp").write_text('extern "C" int AscOnly() { return 42; }\n') | ||
| 108 | + (tmp_path / "CMakeLists.txt").write_text(f''' | ||
| 109 | +cmake_minimum_required(VERSION 3.19) | ||
| 110 | +project(optional_dsl CXX) | ||
| 111 | +include("{ROOT}/cmake/func.cmake") | ||
| 112 | +set(OPS_ADV_DIR "{tmp_path}") | ||
| 113 | +set(HI_PYTHON "{sys.executable}") | ||
| 114 | +set(ASCEND_BINARY_OUT_DIR "${{CMAKE_BINARY_DIR}}/binary") | ||
| 115 | +set(ASCEND_CANN_PACKAGE_PATH "{tmp_path}/sdk") | ||
| 116 | +set(ASCEND_COMPUTE_UNIT ascend950) | ||
| 117 | +set(ENABLE_OPS_HOST {"OFF" if mode == "kernel" else "ON"}) | ||
| 118 | +set(ENABLE_OPS_KERNEL {"OFF" if mode == "host" else "ON"}) | ||
| 119 | +set(MODULE_OPTYPE entry) | ||
| 120 | +set(MODULE_ACLNNTYPE aclnn_inner) | ||
| 121 | +enable_cannbotdsl_kernel(entry "{tmp_path}") | ||
| 122 | +get_property(packages GLOBAL PROPERTY CANNBOTDSL_PACKAGES) | ||
| 123 | +if(packages OR entry_DSL_AVAILABLE) | ||
| 124 | + message(FATAL_ERROR "Incompatible DSL publication was registered") | ||
| 125 | +endif() | ||
| 126 | +add_library(asc SHARED asc.cpp) | ||
| 127 | +install(TARGETS asc DESTINATION lib) | ||
| 128 | +''') | ||
| 129 | + build = tmp_path / "build" | ||
| 130 | + configured = subprocess.run(["cmake", "-S", str(tmp_path), "-B", str(build)], capture_output=True, text=True) | ||
| 131 | + assert configured.returncode == 0, configured.stdout + configured.stderr | ||
| 132 | + assert "DSL not compiled" in " ".join(configured.stderr.split()) | ||
| 133 | + assert "OpBase ABI unavailable" in " ".join(configured.stderr.split()) | ||
| 134 | + for args in (["--build", str(build)], ["--install", str(build), "--prefix", str(tmp_path / "install")]): | ||
| 135 | + result = subprocess.run(["cmake", *args], capture_output=True, text=True) | ||
| 136 | + assert result.returncode == 0, result.stdout + result.stderr | ||
| 137 | + library = next((tmp_path / "install/lib").glob("*asc.*")) | ||
| 138 | + assert ctypes.CDLL(str(library)).AscOnly() == 42 | ||
| 139 | + assert not stage.exists() | ||
| 140 | + assert not Path(str(stage) + ".report.json").exists() | ||
| 141 | + assert not list((tmp_path / "install").rglob("package.json")) | ||
| 142 | + | ||
| 143 | + | ||
| 144 | +@pytest.mark.parametrize("missing", ["opbase", "runtime", "soc", "none"]) | ||
| 145 | +def test_missing_optional_runtime_symbols_allow_asc_library_to_load(tmp_path, missing): | ||
| 146 | + if sys.platform != "linux": | ||
| 147 | + pytest.skip("ELF weak symbol and RTLD_NOW contract") | ||
| 148 | + compiler = shutil.which("c++") | ||
| 149 | + if not compiler: | ||
| 150 | + pytest.skip("C++ compiler is required") | ||
| 151 | + (tmp_path / "opdev").mkdir() | ||
| 152 | + (tmp_path / "opdev/op_log.h").write_text( | ||
| 153 | + '#include <cstdio>\n#define OP_LOGE(code, ...) do { std::fprintf(stderr, __VA_ARGS__); } while (0)\n') | ||
| 154 | + # Public interface stand-ins; compile the real Transformer guard and enforce | ||
| 155 | + # eager DSO resolution so an unresolved optional symbol cannot be hidden. | ||
| 156 | + (tmp_path / "runtime.h").write_text(RUNTIME_INTERFACE) | ||
| 157 | + component = '#include "runtime.h"\n' | ||
| 158 | + if missing != "opbase": | ||
| 159 | + component += 'namespace op { aclnnStatus AddDirectInvokeTask(const char*, uint32_t, DirectInvokePrepareFn, DirectInvokeLaunchFn, DirectInvokeDestroyFn, aclOpExecutor*, OpArgContext*) { return 0; } }\n' | ||
| 160 | + if missing != "runtime": | ||
| 161 | + component += 'extern "C" { int aclrtBinaryLoadFromData(){return 0;} int aclrtBinaryGetFunctionByEntry(){return 0;} int aclrtLaunchKernelWithArgsArray(){return 0;} int aclrtLaunchSIMTKernelWithArgsArray(){return 0;} }\n' | ||
| 162 | + component += 'extern "C" const char* aclrtGetSocName() { return "' + ("Ascend910B" if missing == "soc" else "Ascend950") + '"; }\n' | ||
| 163 | + (tmp_path / "components.cpp").write_text(component) | ||
| 164 | + (tmp_path / "entry.cpp").write_text('''namespace op { | ||
| 165 | +int InvokeDsl() { | ||
| 166 | + auto status = ops_transformer::CheckCannbotdslRuntime("SampleDsl"); | ||
| 167 | + if (status != ACLNN_SUCCESS) { return status; } | ||
| 168 | + return AddDirectInvokeTask("SampleDsl", 0, nullptr, nullptr, nullptr, nullptr, nullptr); | ||
| 169 | +} | ||
| 170 | +} | ||
| 171 | +extern "C" int AscOnly() { return 42; } | ||
| 172 | +extern "C" int DslOnly() { return op::InvokeDsl(); } | ||
| 173 | +''') | ||
| 174 | + library = tmp_path / "libentry.so" | ||
| 175 | + object_file = tmp_path / "entry.o" | ||
| 176 | + subprocess.run([compiler, "-std=c++17", "-fPIC", "-c", str(tmp_path / "entry.cpp"), "-I", str(tmp_path), | ||
| 177 | + "-include", str(ROOT / "common/include/common/cannbotdsl_compat.h"), "-o", str(object_file)], check=True, capture_output=True) | ||
| 178 | + subprocess.run([compiler, "-std=c++17", "-shared", "-fPIC", str(object_file), str(tmp_path / "components.cpp"), | ||
| 179 | + "-I", str(tmp_path), "-Wl,-z,defs", "-o", str(library)], check=True, capture_output=True) | ||
| 180 | + handle = ctypes.CDLL(str(library), mode=os.RTLD_NOW | os.RTLD_LOCAL) | ||
| 181 | + assert handle.AscOnly() == 42 | ||
| 182 | + assert handle.DslOnly() == (0 if missing == "none" else 7) | ||
| 183 | + | ||
| 184 | + | ||
| 185 | +@pytest.mark.parametrize("has_runtime_api", [None, False, True]) | ||
| 186 | +def test_abi_link_probe_uses_the_selected_sdk(tmp_path, has_runtime_api): | ||
| 187 | + if sys.platform != "linux": | ||
| 188 | + pytest.skip("Linux SDK link probe") | ||
| 189 | + from test_cannbotdsl_build import compiler_fixture | ||
| 190 | + | ||
| 191 | + fixture = compiler_fixture(tmp_path) | ||
| 192 | + sdk = tmp_path / "sdk" | ||
| 193 | + include = sdk / "include" | ||
| 194 | + (include / "acl").mkdir(parents=True) | ||
| 195 | + (include / "opdev").mkdir() | ||
| 196 | + (sdk / "lib64").mkdir() | ||
| 197 | + (include / "acl/acl.h").write_text('''#pragma once | ||
| 198 | +#include <cstdint> | ||
| 199 | +using aclnnStatus = int; | ||
| 200 | +struct aclOpExecutor; | ||
| 201 | +extern "C" { | ||
| 202 | +int aclrtBinaryLoadFromData(); | ||
| 203 | +int aclrtBinaryGetFunctionByEntry(); | ||
| 204 | +int aclrtLaunchKernelWithArgsArray(); | ||
| 205 | +int aclrtLaunchSIMTKernelWithArgsArray(); | ||
| 206 | +} | ||
| 207 | +''') | ||
| 208 | + (include / "opdev/direct_invoke_task.h").write_text('''#pragma once | ||
| 209 | +#include <acl/acl.h> | ||
| 210 | +namespace op { | ||
| 211 | +struct OpArgContext; | ||
| 212 | +using DirectInvokePrepareFn = void(*)(); | ||
| 213 | +using DirectInvokeLaunchFn = void(*)(); | ||
| 214 | +using DirectInvokeDestroyFn = void(*)(); | ||
| 215 | +aclnnStatus AddDirectInvokeTask(const char*, uint32_t, DirectInvokePrepareFn, | ||
| 216 | + DirectInvokeLaunchFn, DirectInvokeDestroyFn, aclOpExecutor*, OpArgContext*); | ||
| 217 | +} | ||
| 218 | +''') | ||
| 219 | + (tmp_path / "opbase.cpp").write_text('''#include <opdev/direct_invoke_task.h> | ||
| 220 | +namespace op { aclnnStatus AddDirectInvokeTask(const char*, uint32_t, DirectInvokePrepareFn, | ||
| 221 | + DirectInvokeLaunchFn, DirectInvokeDestroyFn, aclOpExecutor*, OpArgContext*) { return 0; } } | ||
| 222 | +''') | ||
| 223 | + (tmp_path / "acl.cpp").write_text( | ||
| 224 | + 'extern "C" { int aclrtBinaryLoadFromData(){return 0;} int aclrtBinaryGetFunctionByEntry(){return 0;} ' | ||
| 225 | + 'int aclrtLaunchKernelWithArgsArray(){return 0;} ' | ||
| 226 | + + ('int aclrtLaunchSIMTKernelWithArgsArray(){return 0;}' if has_runtime_api else '') + '}') | ||
| 227 | + for source, output in (("opbase.cpp", "libnnopbase.so"), ("acl.cpp", "libascendcl.so")): | ||
| 228 | + subprocess.run(["c++", "-shared", "-fPIC", "-I", str(include), str(tmp_path / source), | ||
| 229 | + "-o", str(sdk / "lib64" / output)], check=True, capture_output=True) | ||
| 230 | + if has_runtime_api is None: | ||
| 231 | + (sdk / "lib64/libascendcl.so").unlink() | ||
| 232 | + (tmp_path / "entry.py").write_text("# collector") | ||
| 233 | + (tmp_path / "CMakeLists.txt").write_text(f''' | ||
| 234 | +cmake_minimum_required(VERSION 3.19) | ||
| 235 | +project(sdk_abi_probe CXX) | ||
| 236 | +include("{ROOT}/cmake/func.cmake") | ||
| 237 | +{fixture} | ||
| 238 | +set(ASCEND_CANN_PACKAGE_PATH "{sdk}") | ||
| 239 | +set(NNOPBASE_OPDEV_INC_DIR "{include}") | ||
| 240 | +set(ENABLE_OPS_HOST ON) | ||
| 241 | +set(ENABLE_OPS_KERNEL ON) | ||
| 242 | +set(ASCEND_COMPUTE_UNIT ascend950) | ||
| 243 | +add_library(nnopbase SHARED IMPORTED) | ||
| 244 | +set_target_properties(nnopbase PROPERTIES IMPORTED_LOCATION "{sdk}/lib64/libnnopbase.so") | ||
| 245 | +enable_cannbotdsl_kernel(entry "{tmp_path}") | ||
| 246 | +get_property(packages GLOBAL PROPERTY CANNBOTDSL_PACKAGES) | ||
| 247 | +file(WRITE "${{CMAKE_BINARY_DIR}}/packages.txt" "${{packages}}") | ||
| 248 | +''') | ||
| 249 | + result = subprocess.run(["cmake", "-S", str(tmp_path), "-B", str(tmp_path / "build")], capture_output=True, text=True) | ||
| 250 | + assert result.returncode == 0, result.stdout + result.stderr | ||
| 251 | + assert bool((tmp_path / "build/packages.txt").read_text()) is bool(has_runtime_api) | ||
| 252 | + if not has_runtime_api: | ||
| 253 | + assert "ABI does not match" in " ".join(result.stderr.split()) | ||
| 254 | + | ||
| 255 | + | ||
| 256 | +def test_unavailable_l0_is_generated_without_operator_ifdefs(tmp_path): | ||
| 257 | + scripts = tmp_path / "cmake/scripts" | ||
| 258 | + scripts.mkdir(parents=True) | ||
| 259 | + (scripts / "build_cannbotdsl.py").write_text("import sys\nprint('DSL unavailable', file=sys.stderr)\nsys.exit(77)\n") | ||
| 260 | + (tmp_path / "entry.py").write_text("# collector") | ||
| 261 | + (tmp_path / "aclnn").mkdir() | ||
| 262 | + (tmp_path / "opdev").mkdir() | ||
| 263 | + (tmp_path / "aclnn/aclnn_base.h").write_text("using aclnnStatus=int; constexpr int ACLNN_ERR_INNER=7;\n") | ||
| 264 | + (tmp_path / "opdev/op_log.h").write_text("#define OP_LOGE(...) ((void)0)\n") | ||
| 265 | + (tmp_path / "api.cpp").write_text('#include "sample_dsl.h"\nextern "C" int CallDsl(){return l0op::SampleDsl(1, nullptr);}\n') | ||
| 266 | + (tmp_path / "CMakeLists.txt").write_text(f''' | ||
| 267 | +cmake_minimum_required(VERSION 3.19) | ||
| 268 | +project(unavailable_l0 CXX) | ||
| 269 | +include("{ROOT}/cmake/func.cmake") | ||
| 270 | +set(OPS_ADV_DIR "{tmp_path}") | ||
| 271 | +set(HI_PYTHON "{sys.executable}") | ||
| 272 | +set(ASCEND_BINARY_OUT_DIR "${{CMAKE_BINARY_DIR}}/binary") | ||
| 273 | +set(ASCEND_COMPUTE_UNIT ascend950) | ||
| 274 | +set(ENABLE_OPS_HOST ON) | ||
| 275 | +set(MODULE_OPTYPE entry) | ||
| 276 | +set(MODULE_ACLNNTYPE aclnn_inner) | ||
| 277 | +set(OPHOST_NAME host) | ||
| 278 | +add_library(host_opapi_obj SHARED api.cpp) | ||
| 279 | +target_include_directories(host_opapi_obj PRIVATE "{tmp_path}") | ||
| 280 | +enable_cannbotdsl_kernel(entry "{tmp_path}" L0_NAME SampleDsl) | ||
| 281 | +''') | ||
| 282 | + build = tmp_path / "build" | ||
| 283 | + for args in (["-S", str(tmp_path), "-B", str(build)], ["--build", str(build)]): | ||
| 284 | + result = subprocess.run(["cmake", *args], capture_output=True, text=True) | ||
| 285 | + assert result.returncode == 0, result.stdout + result.stderr | ||
| 286 | + library = next(build.glob("*host_opapi_obj.*")) | ||
| 287 | + assert ctypes.CDLL(str(library)).CallDsl() == 7 | ||
| 288 | + | ||
| 289 | + | ||
| 290 | +def test_generated_l0_checks_runtime_explicitly(tmp_path): | ||
| 291 | + if sys.platform != "linux": | ||
| 292 | + pytest.skip("Linux optional runtime linkage") | ||
| 293 | + from test_cannbotdsl_build import compiler_fixture | ||
| 294 | + | ||
| 295 | + fixture = compiler_fixture(tmp_path) | ||
| 296 | + (tmp_path / "opdev").mkdir() | ||
| 297 | + (tmp_path / "opdev/op_log.h").write_text("#define OP_LOGE(...) ((void)0)\n") | ||
| 298 | + builder = tmp_path / "cmake/scripts/build_cannbotdsl.py" | ||
| 299 | + # Stand-ins publish a real static runtime target and generated L0 source. | ||
| 300 | + # The production CMake then creates and compiles its guarded build copy. | ||
| 301 | + with builder.open("a") as stream: | ||
| 302 | + stream.write("\n(root / 'runtime/runtime.h').write_text(" + repr(RUNTIME_INTERFACE) + ")\n") | ||
| 303 | + stream.write("(root / 'runtime/stub.cpp').write_text('extern \\\"C\\\" const char* aclrtGetSocName(){return \\\"Ascend950\\\";}\\n')\n".replace('\\\\"', '\\"')) | ||
| 304 | + stream.write("(root / 'runtime/CMakeLists.txt').write_text('add_library(opkit_runtime STATIC stub.cpp)\\nadd_library(opkit::runtime ALIAS opkit_runtime)\\ntarget_include_directories(opkit_runtime PUBLIC \"${CMAKE_CURRENT_SOURCE_DIR}\" \"" + str(tmp_path) + "\")\\n')\n") | ||
| 305 | + l0 = ('#define ADD_TO_LAUNCHER_LIST_CANNBOTDSL(...) 0\n' | ||
| 306 | + 'namespace l0op { int entry() { return ADD_TO_LAUNCHER_LIST_CANNBOTDSL(entry); } }\n') | ||
| 307 | + stream.write("(root / op / 'op_api' / (op + '.cpp')).write_text(" + repr(l0) + ")\n") | ||
| 308 | + stream.write("module = root / op / 'CMakeLists.txt'\nmodule.write_text(module.read_text().replace(str(root / op) + '/', './'))\n") | ||
| 309 | + (tmp_path / "entry.py").write_text("# collector") | ||
| 310 | + (tmp_path / "api.cpp").write_text('namespace l0op { int entry(); }\nextern "C" int CallDsl(){return l0op::entry();}\n') | ||
| 311 | + (tmp_path / "CMakeLists.txt").write_text(f''' | ||
| 312 | +cmake_minimum_required(VERSION 3.19) | ||
| 313 | +project(explicit_guard CXX) | ||
| 314 | +include("{ROOT}/cmake/func.cmake") | ||
| 315 | +{fixture} | ||
| 316 | +set(ASCEND_COMPUTE_UNIT ascend950) | ||
| 317 | +set(ENABLE_OPS_HOST ON) | ||
| 318 | +set(ENABLE_OPS_KERNEL ON) | ||
| 319 | +enable_cannbotdsl_kernel(entry "{tmp_path}") | ||
| 320 | +get_property(CANNBOTDSL_PACKAGES GLOBAL PROPERTY CANNBOTDSL_PACKAGES) | ||
| 321 | +include("{ROOT}/cmake/opbuild.cmake") | ||
| 322 | +cannbotdsl_add_modules(headers) | ||
| 323 | +add_library(api SHARED api.cpp) | ||
| 324 | +cannbotdsl_attach_to_opapi(api) | ||
| 325 | +''') | ||
| 326 | + build = tmp_path / "build" | ||
| 327 | + for args in (["-S", str(tmp_path), "-B", str(build)], ["--build", str(build)]): | ||
| 328 | + result = subprocess.run(["cmake", *args], capture_output=True, text=True) | ||
| 329 | + assert result.returncode == 0, result.stdout + result.stderr | ||
| 330 | + assert ctypes.CDLL(str(build / "libapi.so"), mode=os.RTLD_NOW).CallDsl() == 7 | ||
| 331 | + checked = build / "cannbot_dsl/entry/checked_l0.cpp" | ||
| 332 | + assert "CheckCannbotdslRuntime" in checked.read_text() | ||
| 333 | + original = build / "cannbot_dsl/all/entry/entry/op_api/entry.cpp" | ||
| 334 | + assert "CheckCannbotdslRuntime" not in original.read_text() | ||
| 335 | + | ||
| 336 | + | ||
| 337 | +@pytest.mark.parametrize("selected,skipped,expected", [ | ||
| 338 | + ("regular", "", False), ("add_dsl", "add_dsl", True), | ||
| 339 | + ("regular;add_dsl", "add_dsl", False), | ||
| 340 | +]) | ||
| 341 | +def test_only_unavailable_dsl_selection_skips_package(tmp_path, selected, skipped, expected): | ||
| 342 | + cmake = (ROOT / "cmake/custom_build.cmake").read_text() | ||
| 343 | + start = cmake.index("get_property(_dsl_skipped GLOBAL PROPERTY CANNBOTDSL_SKIPPED_ONLY_OPS)") | ||
| 344 | + end = cmake.index("# 已生成的 DSL 包", start) | ||
| 345 | + script = tmp_path / "skip.cmake" | ||
| 346 | + script.write_text(f'set(CMAKE_BINARY_DIR "{tmp_path}")\nset(ASCEND_OP_NAME "{selected}")\n' | ||
| 347 | + f'set_property(GLOBAL PROPERTY CANNBOTDSL_SKIPPED_ONLY_OPS "{skipped}")\n' | ||
| 348 | + + cmake[start:end]) | ||
| 349 | + result = subprocess.run(["cmake", "-P", str(script)], capture_output=True, text=True) | ||
| 350 | + assert result.returncode == 0, result.stdout + result.stderr | ||
| 351 | + assert (tmp_path / "no_ops.flag").exists() is expected | ||