diff --git a/pto_isa.pin b/pto_isa.pin index cfbe3a5b16..81de0a707c 100644 --- a/pto_isa.pin +++ b/pto_isa.pin @@ -1 +1 @@ -be5ccb765a4ce5d14ca5da8b0e2f182d7f003369 +fe2f68dac50a608c3bf11ac8d3438cd78fef0d98 diff --git a/simpler_setup/pto_isa.py b/simpler_setup/pto_isa.py index 1e877a074d..59d3d17618 100644 --- a/simpler_setup/pto_isa.py +++ b/simpler_setup/pto_isa.py @@ -9,15 +9,19 @@ """PTO-ISA dependency management: resolve the pinned managed checkout. ``pto_isa.pin`` is the single source of truth for the PTO-ISA revision. -``ensure_pto_isa_root()`` always manages ``PROJECT_ROOT/build/pto-isa``: +``ensure_pto_isa_root()`` resolves ``PROJECT_ROOT/build/pto-isa``: 1. Read the required commit from ``pto_isa.pin``. -2. If a managed checkout already exists **clean and at exactly the pin**, use it +2. If a sibling ``../pto-isa`` checkout exists at exactly the pin, link + ``build/pto-isa`` to it and use that tree. This keeps paired simpler + + pto-isa workspaces on the same local revision during cross-repo migrations. +3. If a managed checkout already exists **clean and at exactly the pin**, use it as-is — it already *is* the pinned ISA, so no checkout and no network. -3. Otherwise (missing, wrong revision, or dirty) obtain the pin fresh: clone +4. Otherwise (missing, wrong revision, or dirty) obtain the pin fresh: clone over HTTPS (``--no-checkout`` so the default branch is never materialized) - and force-check-out the pin. GitHub is attempted three times; if it cannot - provide the complete pinned checkout, fall back to the GitCode mirror. + and force-check-out the pin. The configured source is attempted three times; + if it cannot provide the complete pinned checkout, fall back to the other + known PTO-ISA source. 4. Verify HEAD exactly matches the pin before returning. Two deliberate choices: @@ -41,6 +45,7 @@ import fcntl import json import logging +import os import re import shutil import subprocess @@ -59,9 +64,10 @@ logger = logging.getLogger(__name__) +_PTO_ISA_GITCODE_HTTPS = "https://gitcode.com/wxwnnzdyd/pto-isa.git" _PTO_ISA_GITHUB_HTTPS = "https://github.com/hw-native-sys/pto-isa.git" -_PTO_ISA_GITCODE_HTTPS = "https://gitcode.com/luohuan40/pto-isa.git" _PTO_ISA_PIN_RE = re.compile(r"^[0-9a-fA-F]{40}$") +PTO_ISA_CLONE_URL_ENV = "PTO_ISA_CLONE_URL" PTO_ISA_PIN_FILE = "pto_isa.pin" PTO_ISA_BUILD_METADATA = "pto_isa_build.json" @@ -234,6 +240,23 @@ def get_pto_isa_clone_path() -> Path: return PROJECT_ROOT / "build" / "pto-isa" +def get_pto_isa_clone_url() -> str: + """Clone URL for managed PTO-ISA checkouts.""" + return os.environ.get(PTO_ISA_CLONE_URL_ENV, _PTO_ISA_GITCODE_HTTPS).strip() or _PTO_ISA_GITCODE_HTTPS + + +def _clone_sources() -> list[str]: + """Return distinct PTO-ISA clone sources in preference order.""" + sources = [get_pto_isa_clone_url(), _PTO_ISA_GITCODE_HTTPS, _PTO_ISA_GITHUB_HTTPS] + return list(dict.fromkeys(sources)) + + +def get_sibling_pto_isa_checkout_path(clone_path: Optional[Path] = None) -> Path: + """Sibling PTO-ISA checkout used by paired local simpler/pto-isa worktrees.""" + managed = clone_path or get_pto_isa_clone_path() + return managed.parent.parent.parent / "pto-isa" + + def _is_cloned(path: Path) -> bool: """Return True if `path` looks like a valid PTO-ISA clone (has include/).""" return (path / "include").is_dir() @@ -312,6 +335,64 @@ def _remove_clone(target: Path, verbose: bool) -> None: target.unlink(missing_ok=True) +def _can_replace_with_sibling_link(target: Path, verbose: bool) -> bool: + """Return true when replacing target cannot discard local changes.""" + if not (target.exists() or target.is_symlink()): + return True + if target.is_symlink(): + return True + if not _is_cloned(target): + return True + + current_head = get_pto_isa_head(str(target)) + if current_head and _is_pristine_at_commit(target, current_head, verbose=verbose): + return True + + if verbose: + logger.warning( + f"Refusing to replace non-pristine pto-isa checkout at {target}; " + "move it aside before linking the sibling checkout." + ) + return False + + +def _link_sibling_checkout_if_pinned(clone_path: Path, required_commit: str, verbose: bool) -> Optional[str]: + """Point build/pto-isa at sibling ../pto-isa when it exactly matches the pin.""" + sibling = get_sibling_pto_isa_checkout_path(clone_path) + if sibling.resolve() == clone_path.resolve(): + return None + if not _is_cloned(sibling): + return None + + actual_commit = get_pto_isa_head(str(sibling)) + if actual_commit != required_commit: + if verbose: + logger.warning( + f"Ignoring sibling pto-isa checkout at {sibling}: " + f"expected {required_commit}, got {actual_commit or ''}" + ) + return None + + if clone_path.exists() or clone_path.is_symlink(): + if clone_path.resolve() == sibling.resolve(): + return str(clone_path.resolve()) + if not _can_replace_with_sibling_link(clone_path, verbose): + return None + _remove_clone(clone_path, verbose) + + try: + clone_path.parent.mkdir(parents=True, exist_ok=True) + clone_path.symlink_to(sibling.resolve(), target_is_directory=True) + except OSError as e: + if verbose: + logger.warning(f"Failed to link sibling pto-isa checkout {sibling} -> {clone_path}: {e}") + return None + + if verbose: + logger.info(f"Using sibling pto-isa checkout at {sibling} via {clone_path}") + return str(clone_path.resolve()) + + def _land_on_commit(clone_path: Path, commit: str, verbose: bool) -> bool: """Force-detach-checkout a freshly cloned tree onto `commit`. False on failure. @@ -427,9 +508,9 @@ def _clone(target: Path, commit: str, verbose: bool) -> bool: Any existing checkout at `target` is removed first, so this always yields a clean tree at exactly `commit` — the sync can never be blocked by local - modifications in a preexisting (cached/preset) managed checkout. GitHub is - tried three times for the complete clone-and-pin operation before GitCode is - used as a fallback source. + modifications in a preexisting (cached/preset) managed checkout. The + configured source is tried three times for the complete clone-and-pin + operation before the other known PTO-ISA source is used as a fallback. """ if not _is_git_available(): if verbose: @@ -444,20 +525,16 @@ def _clone(target: Path, commit: str, verbose: bool) -> bool: return False try: - if _clone_from_remote( - target, - commit, - _PTO_ISA_GITHUB_HTTPS, - attempts=_CLONE_ATTEMPTS, - verbose=verbose, - ): - return True - - logger.warning( - f"GitHub could not provide PTO-ISA commit {commit} after {_CLONE_ATTEMPTS} attempts; " - f"falling back to {_PTO_ISA_GITCODE_HTTPS}" - ) - return _clone_from_remote(target, commit, _PTO_ISA_GITCODE_HTTPS, attempts=1, verbose=verbose) + sources = _clone_sources() + for index, remote in enumerate(sources): + if _clone_from_remote(target, commit, remote, attempts=_CLONE_ATTEMPTS, verbose=verbose): + return True + if index + 1 < len(sources): + logger.warning( + f"PTO-ISA source {remote} could not provide commit {commit} " + f"after {_CLONE_ATTEMPTS} attempts; trying {sources[index + 1]}" + ) + return False except Exception as e: # noqa: BLE001 if verbose: logger.warning(f"Failed to clone pto-isa: {e}") @@ -481,15 +558,19 @@ def ensure_pto_isa_root(verbose: bool = False) -> str: f"PTO-ISA not available.\n" f" The managed checkout must live at {clone_path} and match {PROJECT_ROOT / PTO_ISA_PIN_FILE}.\n" f" If auto-clone failed, manually run:\n" - f" git clone {_PTO_ISA_GITHUB_HTTPS} {clone_path}\n" - f" Or use the fallback mirror:\n" - f" git clone {_PTO_ISA_GITCODE_HTTPS} {clone_path}" + f" git clone {get_pto_isa_clone_url()} {clone_path}\n" + f" Or use another known source:\n" + f" git clone {_PTO_ISA_GITHUB_HTTPS} {clone_path}" ) return resolved def _ensure_locked(clone_path: Path, required_commit: str, verbose: bool) -> Optional[str]: """Inner logic executed while holding the file lock.""" + sibling_checkout = _link_sibling_checkout_if_pinned(clone_path, required_commit, verbose=verbose) + if sibling_checkout is not None: + return sibling_checkout + # Reuse an existing checkout ONLY when it is already exactly the pin: a clean # working tree at HEAD == pin already *is* the pinned ISA (git objects are # content-addressed), so use it as-is — no checkout, no network. This is the diff --git a/simpler_setup/runtime_builder.py b/simpler_setup/runtime_builder.py index 9602a17cb4..f0dc34e6ae 100644 --- a/simpler_setup/runtime_builder.py +++ b/simpler_setup/runtime_builder.py @@ -245,9 +245,12 @@ def _build_cache_stamp(self, pto_isa_commit: Optional[str] = None) -> str: return "" if pto_isa_commit is None: pto_isa_commit = self._resolve_build_pto_isa_commit() + parts = [runtime_commit] if pto_isa_commit: - return f"{runtime_commit}:pto-isa={pto_isa_commit}" - return runtime_commit + parts.append(f"pto-isa={pto_isa_commit}") + if self._variant == "onboard": + parts.append(f"ascend-home={os.environ.get('ASCEND_HOME_PATH', '').strip()}") + return ":".join(parts) def _lookup_binaries(self, name: str, output_dir: Path) -> RuntimeBinaries: """Look up pre-built binaries from output_dir. diff --git a/src/a2a3/platform/onboard/host/CMakeLists.txt b/src/a2a3/platform/onboard/host/CMakeLists.txt index f1496f516c..836ce935d3 100644 --- a/src/a2a3/platform/onboard/host/CMakeLists.txt +++ b/src/a2a3/platform/onboard/host/CMakeLists.txt @@ -121,14 +121,19 @@ target_compile_options(host_runtime target_compile_definitions(host_runtime PRIVATE SIMPLER_PLATFORM_NAME="a2a3") if(SIMPLER_ENABLE_PTO_SDMA_WORKSPACE) - target_compile_definitions(host_runtime PRIVATE SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1) + target_compile_definitions(host_runtime PRIVATE + SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1 + PTO_COMM_WORKSPACE_SDMA_SUPPORTED=1 + PTO_COMM_WORKSPACE_URMA_SUPPORTED=0 + PTO_COMM_WORKSPACE_RDMA_SUPPORTED=0 + ) endif() set(SIMPLER_PTO_ISA_BUILD_COMMIT "" CACHE STRING "PTO-ISA commit used for host_runtime cache key") -# Bake the resolved pto-isa commit into the compile command. pto-isa headers -# (e.g. kSdmaMaxChan in sdma_workspace_manager.hpp) are compiled into this .so, +# Bake the resolved pto-isa commit into the compile command. pto-isa workspace +# headers (e.g. kSdmaWorkspaceBytes) are compiled into this .so, # but a pto-isa update leaves the runtime repo HEAD — and the pto-isa header # mtimes — untouched, so a plain reinstall serves a stale object from ccache. # This define perturbs the ccache key so a pto-isa bump forces a real recompile. @@ -172,6 +177,7 @@ endif() # CANN 9.x exposes the working non-V2 HCCL entry points through libhcomm. # Link it explicitly so comm_hccl.cpp can follow the same initialization path # as the pto-isa communication tests. +unset(HCOMM_LIB CACHE) find_library( HCOMM_LIB NAMES hcomm @@ -214,6 +220,10 @@ target_link_directories(host_runtime ${ASCEND_HOME_PATH}/runtime/lib64 ) +set_target_properties(host_runtime PROPERTIES + BUILD_RPATH "${ASCEND_HOME_PATH}/lib64;${ASCEND_HOME_PATH}/runtime/lib64;${ASCEND_HOME_PATH}/${CMAKE_SYSTEM_PROCESSOR}-linux/lib64" +) + set_target_properties(host_runtime PROPERTIES OUTPUT_NAME "host_runtime") # Apply compiler sanitizers to this host-compiled target. No-op unless diff --git a/src/a2a3/platform/onboard/host/comm_hccl.cpp b/src/a2a3/platform/onboard/host/comm_hccl.cpp index ec85e275c5..da905606f2 100644 --- a/src/a2a3/platform/onboard/host/comm_hccl.cpp +++ b/src/a2a3/platform/onboard/host/comm_hccl.cpp @@ -48,7 +48,7 @@ #include "hccl/hccl_comm.h" #include "hccl/hccl_types.h" #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE -#include "pto/comm/async/sdma/sdma_workspace_manager.hpp" +#include "pto/comm/workspace.hpp" #endif // Thin wrappers around the HCCL public APIs we use. Kept as a translation @@ -1076,14 +1076,6 @@ static int domain_alloc_via_ipc( return 0; } -// Host wrapper owning one SDMA-enabled Worker's provisioned resources. The -// opaque handle returned to the runner IS this manager; dma_workspace_release() -// destroys it. There is no per-device generation gate: an SDMA-enabled Worker -// owns its provider for its whole life and releases it at finalize. -#ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE -using SdmaManager = pto::comm::sdma::SdmaWorkspaceManager; -#endif - extern "C" uint32_t dma_workspace_supported_mask(void) { #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE return uint32_t{1} << DMA_WORKSPACE_SDMA; @@ -1095,9 +1087,8 @@ extern "C" uint32_t dma_workspace_supported_mask(void) { extern "C" uint32_t dma_workspace_channel_count(void) { #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE // kSdmaMaxChannelGroups, not the device-side kSdmaMaxChannel: the two are the - // same 48 (PTO static_asserts kPostMaxQueues == kSdmaMaxChannelGroups) but - // only this one comes in through the host-safe workspace-manager header, and - // it is what SdmaWorkspaceManager::Init actually creates streams for. + // same 48 (PTO static_asserts kPostMaxQueues == kSdmaMaxChannelGroups) and + // this is the host-visible count used by the unified SDMA workspace provider. return pto::comm::sdma::kSdmaMaxChannelGroups; #else return 0; @@ -1115,29 +1106,16 @@ extern "C" int dma_workspace_provision(uint32_t required_mask, uint64_t *addr_ou if ((required_mask & (uint32_t{1} << DMA_WORKSPACE_SDMA)) == 0) return 0; if (count <= DMA_WORKSPACE_SDMA) return -1; try { - auto manager = std::make_unique(); - bool init_ok = false; - try { - // Init creates the 48 STARS streams + 16KB workspace. It may fail - // after creating only a subset; destructing that partial manager on - // an error-state card can itself stall, so leak it on failure rather - // than risk the stall. - init_ok = manager->Init(); - } catch (...) { - LOG_ERROR("SdmaWorkspaceManager::Init threw; abandoning its partial resources"); - } - if (!init_ok) { - (void)manager.release(); + auto workspace = std::make_unique(); + pto::comm::WorkspaceRequest req{}; + const auto status = pto::comm::CreateWorkspace(pto::comm::DmaEngine::SDMA, req, workspace.get()); + if (status != pto::comm::WorkspaceStatus::Ok || workspace->addr == nullptr) { + pto::comm::AbandonWorkspace(workspace.get()); + LOG_ERROR("dma_workspace_provision: SDMA workspace creation failed (status=%d)", static_cast(status)); return -1; } - const uint64_t addr = reinterpret_cast(manager->GetWorkspaceAddr()); - if (addr == 0) { - (void)manager.release(); - LOG_ERROR("dma_workspace_provision: manager returned a null workspace address"); - return -1; - } - addr_out[DMA_WORKSPACE_SDMA] = addr; - *handle_out = manager.release(); + addr_out[DMA_WORKSPACE_SDMA] = reinterpret_cast(workspace->addr); + *handle_out = workspace.release(); return 0; } catch (...) { LOG_ERROR("dma_workspace_provision: exception while provisioning SDMA"); @@ -1152,8 +1130,8 @@ extern "C" void dma_workspace_release(void *handle) { #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE if (!handle) return; try { - std::unique_ptr manager(static_cast(handle)); - manager.reset(); + std::unique_ptr workspace(static_cast(handle)); + pto::comm::DestroyWorkspace(workspace.get()); } catch (...) { LOG_ERROR("dma_workspace_release: exception while releasing SDMA resources"); } @@ -1780,7 +1758,6 @@ extern "C" int comm_destroy(CommHandle h) try { if (rc == 0) rc = -1; } } - // NOTE: we do NOT destroy h->stream — it is caller-owned. // We also do NOT call aclrtResetDevice / aclFinalize here. Device/ACL // lifecycle belongs to DeviceRunner, whose finalize() releases all diff --git a/src/a2a3/runtime/tensormap_and_ringbuffer/runtime/backend/sdma/sdma_completion_kernel.h b/src/a2a3/runtime/tensormap_and_ringbuffer/runtime/backend/sdma/sdma_completion_kernel.h index 9186dc6dcd..03b1d2ffe9 100644 --- a/src/a2a3/runtime/tensormap_and_ringbuffer/runtime/backend/sdma/sdma_completion_kernel.h +++ b/src/a2a3/runtime/tensormap_and_ringbuffer/runtime/backend/sdma/sdma_completion_kernel.h @@ -108,6 +108,14 @@ register_pto_async_event(AsyncCtx &ctx, const PtoAsyncEvent &event, const PtoAsy defer_error(ctx, SIMPLER_ERROR_ASYNC_COMPLETION_INVALID); return; } + + ::pto::comm::sdma::SdmaSession sdma_session; + ::pto::comm::sdma::detail::LoadSdmaSession(session, sdma_session); + if (!sdma_session.valid || sdma_session.runtimeCtx.postDoneBase == nullptr) { + defer_error(ctx, SIMPLER_ERROR_ASYNC_COMPLETION_INVALID); + return; + } + for (uint32_t queue_id = 0; queue_id < queue_num; ++queue_id) { register_sdma_post_done_record( ctx, ::pto::comm::sdma::detail::GetPostDoneRecordAddr(post_done_base, queue_id), post_id diff --git a/src/a5/platform/onboard/host/CMakeLists.txt b/src/a5/platform/onboard/host/CMakeLists.txt index 978afcfe4b..8c9aed9665 100644 --- a/src/a5/platform/onboard/host/CMakeLists.txt +++ b/src/a5/platform/onboard/host/CMakeLists.txt @@ -131,7 +131,12 @@ target_compile_options(host_runtime target_compile_definitions(host_runtime PRIVATE SIMPLER_PLATFORM_NAME="a5") if(SIMPLER_ENABLE_PTO_SDMA_WORKSPACE) - target_compile_definitions(host_runtime PRIVATE SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1) + target_compile_definitions(host_runtime PRIVATE + SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1 + PTO_COMM_WORKSPACE_SDMA_SUPPORTED=1 + PTO_COMM_WORKSPACE_URMA_SUPPORTED=0 + PTO_COMM_WORKSPACE_RDMA_SUPPORTED=0 + ) endif() if(SIMPLER_ENABLE_PTO_URMA_WORKSPACE) target_compile_definitions(host_runtime PRIVATE SIMPLER_ENABLE_PTO_URMA_WORKSPACE=1) @@ -181,6 +186,7 @@ target_include_directories(host_runtime # Prefer libhccl when the CANN package ships it. Newer CANN packages may only # ship libhcomm, matching pto-isa's own A5 comm tests, so fall back to hcomm. +unset(HCCL_LIB CACHE) find_library(HCCL_LIB NAMES hccl PATHS @@ -188,6 +194,7 @@ find_library(HCCL_LIB ${ASCEND_ARCH_HOME}/lib64 NO_DEFAULT_PATH ) +unset(HCOMM_LIB CACHE) find_library(HCOMM_LIB NAMES hcomm PATHS diff --git a/src/a5/platform/onboard/host/comm_hccl.cpp b/src/a5/platform/onboard/host/comm_hccl.cpp index d8cefd20e0..0d636a580b 100644 --- a/src/a5/platform/onboard/host/comm_hccl.cpp +++ b/src/a5/platform/onboard/host/comm_hccl.cpp @@ -46,11 +46,8 @@ #include "acl/acl.h" #include "hccl/hccl_comm.h" #include "hccl/hccl_types.h" -#ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE -#include "pto/comm/async/sdma/sdma_workspace_manager.hpp" -#endif -#ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE -#include "pto/comm/async/urma/urma_workspace_manager.hpp" +#if defined(SIMPLER_ENABLE_PTO_SDMA_WORKSPACE) || defined(SIMPLER_ENABLE_PTO_URMA_WORKSPACE) +#include "pto/comm/workspace.hpp" #endif // Thin wrappers around the HCCL public APIs we use. Kept as a translation @@ -84,7 +81,7 @@ struct DomainAllocation { // these are released explicitly at domain teardown rather than left to reset. std::vector> peer_windows; #ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE - std::unique_ptr urma_workspace; + pto::comm::Workspace urma_workspace{}; #endif CommContext *device_ctx = nullptr; // aclrtMalloc'd CommContext mirror }; @@ -105,10 +102,10 @@ struct CommHandle_ { std::vector derived_contexts; std::unordered_map> domain_allocations; #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE - std::unique_ptr sdma_workspace; + pto::comm::Workspace sdma_workspace{}; #endif #ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE - std::unique_ptr urma_workspace; + pto::comm::Workspace urma_workspace{}; #endif }; @@ -737,18 +734,28 @@ static std::string domain_barrier_tag(uint64_t allocation_id, const char *phase) // aclnnShmemSdmaStarsQuery primitives. static void ensure_sdma_workspace(CommHandle h) { #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE - if (h->sdma_workspace) return; - h->sdma_workspace = std::make_unique(); - if (h->sdma_workspace->Init()) { - h->host_ctx.workSpace = reinterpret_cast(h->sdma_workspace->GetWorkspaceAddr()); - h->host_ctx.workSpaceSize = 16 * 1024; - } else { - // SDMA workspace initialization failed - this may occur due to: - // 1. Missing ACL symbols in libopapi.so (CANN version compatibility) - // 2. Device state issues (e.g., Critical health status) - // 3. Resource exhaustion from repeated test runs - // The system gracefully degrades to non-SDMA mode when this occurs. - h->sdma_workspace.reset(); + if (h->sdma_workspace.addr != nullptr || h->sdma_workspace.impl != nullptr) return; + pto::comm::WorkspaceRequest req{}; + const auto status = pto::comm::CreateWorkspace(pto::comm::DmaEngine::SDMA, req, &h->sdma_workspace); + if (status == pto::comm::WorkspaceStatus::Ok) { + h->host_ctx.workSpace = reinterpret_cast(h->sdma_workspace.addr); + h->host_ctx.workSpaceSize = h->sdma_workspace.bytes; + return; + } + pto::comm::AbandonWorkspace(&h->sdma_workspace); + h->host_ctx.workSpace = 0; + h->host_ctx.workSpaceSize = 0; +#else + (void)h; +#endif +} + +static void reset_base_sdma_workspace(CommHandle h) { +#ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE + if (h != nullptr) { + pto::comm::DestroyWorkspace(&h->sdma_workspace); + h->host_ctx.workSpace = 0; + h->host_ctx.workSpaceSize = 0; } #else (void)h; @@ -795,29 +802,37 @@ static bool rank_ids_are_dense_prefix(const uint32_t *rank_ids, size_t rank_coun static bool init_urma_workspace( CommHandle h, uint32_t rank_id, uint32_t rank_count, void *symmetric_addr, uint64_t symmetric_size, - std::unique_ptr &workspace + pto::comm::Workspace &workspace ) { - if (workspace) return workspace->GetWorkspaceAddr() != nullptr; + if (workspace.addr != nullptr || workspace.impl != nullptr) return workspace.addr != nullptr; if (h == nullptr || h->hccl_comm == nullptr || symmetric_addr == nullptr || symmetric_size == 0 || rank_id >= rank_count) { return false; } - auto manager = std::make_unique(); - if (!manager->Init(h->hccl_comm, rank_id, rank_count, symmetric_addr, symmetric_size)) { + pto::comm::WorkspaceRequest req{}; + req.hcclComm = h->hccl_comm; + req.rankId = rank_id; + req.rankNum = rank_count; + req.symmetricAddr = symmetric_addr; + req.symmetricBytes = symmetric_size; + const auto status = pto::comm::CreateWorkspace(pto::comm::DmaEngine::URMA, req, &workspace); + if (status != pto::comm::WorkspaceStatus::Ok) { LOG_WARN( - "[comm rank %d] URMA workspace init failed (rank_id=%u rank_count=%u size=%llu)", h->rank, rank_id, - rank_count, static_cast(symmetric_size) + "[comm rank %d] URMA workspace init failed (status=%d rank_id=%u rank_count=%u size=%llu)", h->rank, + static_cast(status), rank_id, rank_count, static_cast(symmetric_size) ); + pto::comm::AbandonWorkspace(&workspace); return false; } - workspace = std::move(manager); return true; } static bool ensure_base_urma_workspace(CommHandle h) { if (h == nullptr) return false; - if (h->urma_workspace) return h->host_ctx.workSpace != 0 && h->host_ctx.workSpaceSize != 0; + if (h->urma_workspace.addr != nullptr || h->urma_workspace.impl != nullptr) { + return h->host_ctx.workSpace != 0 && h->host_ctx.workSpaceSize != 0; + } void *local_buf = reinterpret_cast(static_cast(h->host_ctx.windowsIn[h->rank])); if (!init_urma_workspace( h, static_cast(h->rank), static_cast(h->nranks), local_buf, h->host_ctx.winSize, @@ -825,15 +840,15 @@ static bool ensure_base_urma_workspace(CommHandle h) { )) { return false; } - h->host_ctx.workSpace = reinterpret_cast(h->urma_workspace->GetWorkspaceAddr()); - h->host_ctx.workSpaceSize = urma_workspace_bytes(static_cast(h->nranks)); + h->host_ctx.workSpace = reinterpret_cast(h->urma_workspace.addr); + h->host_ctx.workSpaceSize = h->urma_workspace.bytes; return h->host_ctx.workSpace != 0 && h->host_ctx.workSpaceSize != 0; } #endif static void reset_domain_urma_workspace(DomainAllocation &alloc) { #ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE - alloc.urma_workspace.reset(); + pto::comm::DestroyWorkspace(&alloc.urma_workspace); #else (void)alloc; #endif @@ -841,7 +856,11 @@ static void reset_domain_urma_workspace(DomainAllocation &alloc) { static void reset_base_urma_workspace(CommHandle h) { #ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE - h->urma_workspace.reset(); + if (h != nullptr) { + pto::comm::DestroyWorkspace(&h->urma_workspace); + h->host_ctx.workSpace = 0; + h->host_ctx.workSpaceSize = 0; + } #else (void)h; #endif @@ -1036,9 +1055,9 @@ static int domain_alloc_via_ipc( uint64_t domain_workspace_addr = 0; uint64_t domain_workspace_size = 0; #ifdef SIMPLER_ENABLE_PTO_SDMA_WORKSPACE - if (h->sdma_workspace) { - domain_workspace_addr = reinterpret_cast(h->sdma_workspace->GetWorkspaceAddr()); - domain_workspace_size = 16 * 1024; + if (h->sdma_workspace.addr != nullptr || h->sdma_workspace.impl != nullptr) { + domain_workspace_addr = reinterpret_cast(h->sdma_workspace.addr); + domain_workspace_size = h->sdma_workspace.bytes; } #endif #ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE @@ -1051,8 +1070,8 @@ static int domain_alloc_via_ipc( release_own_vmm_window(localBuf, handle); return -1; } - domain_workspace_addr = reinterpret_cast(out->urma_workspace->GetWorkspaceAddr()); - domain_workspace_size = urma_workspace_bytes(static_cast(rank_count)); + domain_workspace_addr = reinterpret_cast(out->urma_workspace.addr); + domain_workspace_size = out->urma_workspace.bytes; } else { LOG_WARN("[comm rank %d] alloc_domain: URMA workspace disabled for non-dense rank mapping", h->rank); } @@ -1232,6 +1251,12 @@ extern "C" int comm_derive_context( CommContext ctx{}; ctx.workSpace = h->host_ctx.workSpace; ctx.workSpaceSize = h->host_ctx.workSpaceSize; +#ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE + if (!rank_ids_are_dense_prefix(rank_ids, rank_count)) { + ctx.workSpace = 0; + ctx.workSpaceSize = 0; + } +#endif ctx.rankId = domain_rank; ctx.rankNum = static_cast(rank_count); ctx.winSize = window_size; @@ -1446,6 +1471,7 @@ extern "C" int comm_destroy(CommHandle h) try { if (alloc->local_buf) release_own_vmm_window(alloc->local_buf, alloc->own_handle); } h->domain_allocations.clear(); + reset_base_sdma_workspace(h); reset_base_urma_workspace(h); if (h->hccl_comm) { HcclResult hret = hccl_comm_destroy(h->hccl_comm); diff --git a/tests/ut/py/test_a2a3_sdma_backend_source.py b/tests/ut/py/test_a2a3_sdma_backend_source.py new file mode 100644 index 0000000000..f5688f1ee0 --- /dev/null +++ b/tests/ut/py/test_a2a3_sdma_backend_source.py @@ -0,0 +1,47 @@ +# Copyright (c) PyPTO Contributors. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. +# ----------------------------------------------------------------------------------------------------------- +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] +SDMA_KERNEL = REPO_ROOT / "src/a2a3/runtime/tensormap_and_ringbuffer/runtime/backend/sdma/sdma_completion_kernel.h" +SDMA_WAIT = REPO_ROOT / "src/a2a3/runtime/tensormap_and_ringbuffer/runtime/async_wait.h" +SDMA_MAILBOX = REPO_ROOT / "src/a2a3/runtime/tensormap_and_ringbuffer/runtime/aicore_completion_mailbox.h" +SDMA_TYPES = REPO_ROOT / "src/a2a3/runtime/tensormap_and_ringbuffer/runtime/aicore_completion_mailbox_types.h" + + +def test_a2a3_sdma_backend_uses_post_done_completion_flow() -> None: + kernel = SDMA_KERNEL.read_text() + wait = SDMA_WAIT.read_text() + + assert "PrepareEventCheck" not in kernel + assert "GetEventRecord" not in kernel + assert "session.sdmaSession" not in kernel + assert "COMPLETION_TYPE_SDMA_EVENT_RECORD" in kernel + assert "LoadSdmaSession" in kernel + assert "runtimeCtx.postDoneBase" in kernel + assert "post_id" in kernel + assert "PTO2_ERROR" not in kernel + + assert "COMPLETION_TYPE_SDMA_EVENT_RECORD" in wait + assert "COMPLETION_TYPE_SDMA_POST_DONE" not in wait + assert "backend_cookie" in wait + assert "poll_sdma_post_done_record(cond.addr, cond.backend_cookie)" in wait + + +def test_a2a3_sdma_mailbox_carries_backend_cookie() -> None: + mailbox = SDMA_MAILBOX.read_text() + types = SDMA_TYPES.read_text() + + assert "uint64_t backend_cookie" in mailbox + assert "backend_cookie" in mailbox and "try_push_condition(" in mailbox + assert "backend_cookie" in types + assert "COMPLETION_TYPE_SDMA_EVENT_RECORD" in types + assert "COMPLETION_TYPE_SDMA_POST_DONE" not in types diff --git a/tests/ut/py/test_a2a3_sdma_workspace_source.py b/tests/ut/py/test_a2a3_sdma_workspace_source.py new file mode 100644 index 0000000000..033b318bcd --- /dev/null +++ b/tests/ut/py/test_a2a3_sdma_workspace_source.py @@ -0,0 +1,47 @@ +# Copyright (c) PyPTO Contributors. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. +# ----------------------------------------------------------------------------------------------------------- +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] +HOST_COMM = REPO_ROOT / "src/a2a3/platform/onboard/host/comm_hccl.cpp" +HOST_CMAKE = REPO_ROOT / "src/a2a3/platform/onboard/host/CMakeLists.txt" + + +def test_a2a3_host_uses_unified_sdma_workspace_interface() -> None: + source = HOST_COMM.read_text() + + assert '#include "pto/comm/workspace.hpp"' in source + assert "sdma_workspace_manager.hpp" not in source + assert "std::unique_ptr" not in source + assert "auto workspace = std::make_unique()" in source + assert "pto::comm::WorkspaceRequest req{}" in source + assert "pto::comm::CreateWorkspace(pto::comm::DmaEngine::SDMA, req, workspace.get())" in source + assert "addr_out[DMA_WORKSPACE_SDMA] = reinterpret_cast(workspace->addr)" in source + assert "16 * 1024" not in source + + +def test_a2a3_sdma_workspace_release_paths_are_explicit() -> None: + source = HOST_COMM.read_text() + + assert "pto::comm::AbandonWorkspace(workspace.get())" in source + assert "std::unique_ptr workspace(static_cast(handle))" in source + assert "pto::comm::DestroyWorkspace(workspace.get())" in source + assert "destroy_sdma_workspace" not in source + assert "abandon_sdma_workspace" not in source + + +def test_a2a3_host_cmake_maps_simpler_sdma_to_unified_workspace_macros() -> None: + cmake = HOST_CMAKE.read_text() + + assert "SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1" in cmake + assert "PTO_COMM_WORKSPACE_SDMA_SUPPORTED=1" in cmake + assert "PTO_COMM_WORKSPACE_URMA_SUPPORTED=0" in cmake + assert "PTO_COMM_WORKSPACE_RDMA_SUPPORTED=0" in cmake diff --git a/tests/ut/py/test_a5_sdma_workspace_source.py b/tests/ut/py/test_a5_sdma_workspace_source.py new file mode 100644 index 0000000000..953741d245 --- /dev/null +++ b/tests/ut/py/test_a5_sdma_workspace_source.py @@ -0,0 +1,62 @@ +# Copyright (c) PyPTO Contributors. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. +# ----------------------------------------------------------------------------------------------------------- +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] +HOST_COMM = REPO_ROOT / "src/a5/platform/onboard/host/comm_hccl.cpp" +HOST_CMAKE = REPO_ROOT / "src/a5/platform/onboard/host/CMakeLists.txt" + + +def test_a5_sdma_overlay_uses_unified_workspace_interface() -> None: + source = HOST_COMM.read_text() + + assert '#include "pto/comm/workspace.hpp"' in source + assert "pto/comm/async/sdma/sdma_workspace_manager.hpp" not in source + assert "pto/comm/async/urma/urma_workspace_manager.hpp" not in source + assert "std::unique_ptr" not in source + assert "pto::comm::Workspace sdma_workspace{}" in source + assert "pto::comm::WorkspaceRequest req{}" in source + assert "pto::comm::CreateWorkspace(pto::comm::DmaEngine::SDMA, req, &h->sdma_workspace)" in source + assert "h->host_ctx.workSpace = reinterpret_cast(h->sdma_workspace.addr)" in source + assert "h->host_ctx.workSpaceSize = h->sdma_workspace.bytes" in source + assert "16 * 1024" not in source + + +def test_a5_sdma_release_and_domain_paths_use_workspace_object() -> None: + source = HOST_COMM.read_text() + start = source.index("static void ensure_sdma_workspace") + ensure_sdma = source[start : source.index("#ifdef SIMPLER_ENABLE_PTO_URMA_WORKSPACE", start)] + + assert "pto::comm::DestroyWorkspace(&h->sdma_workspace)" in source + assert "pto::comm::AbandonWorkspace(&h->sdma_workspace)" in source + assert "domain_workspace_addr = reinterpret_cast(h->sdma_workspace.addr)" in source + assert "domain_workspace_size = h->sdma_workspace.bytes" in source + assert "GetWorkspaceAddr()" not in ensure_sdma + assert "h->sdma_workspace->" not in ensure_sdma + + +def test_a5_sdma_cmake_maps_to_unified_workspace_macros() -> None: + cmake = HOST_CMAKE.read_text() + + assert "SIMPLER_ENABLE_PTO_SDMA_WORKSPACE=1" in cmake + assert "PTO_COMM_WORKSPACE_SDMA_SUPPORTED=1" in cmake + assert "PTO_COMM_WORKSPACE_URMA_SUPPORTED=0" in cmake + assert "PTO_COMM_WORKSPACE_RDMA_SUPPORTED=0" in cmake + + +def test_a5_sdma_derived_context_inherits_workspace_outside_urma_mode() -> None: + source = HOST_COMM.read_text() + start = source.index('extern "C" int comm_derive_context') + derive_context = source[start : source.index("void *newDevMem", start)] + + assert "ctx.workSpace = h->host_ctx.workSpace" in derive_context + assert "ctx.workSpaceSize = h->host_ctx.workSpaceSize" in derive_context + assert "if (!rank_ids_are_dense_prefix(rank_ids, rank_count))" in derive_context diff --git a/tests/ut/py/test_pto_isa.py b/tests/ut/py/test_pto_isa.py index 9c68a36c1b..cea9690890 100644 --- a/tests/ut/py/test_pto_isa.py +++ b/tests/ut/py/test_pto_isa.py @@ -53,6 +53,7 @@ def test_clone_lands_on_pinned_commit(tmp_path, monkeypatch): target = tmp_path / "build" / "pto-isa" calls = [] + monkeypatch.delenv(pto_isa.PTO_ISA_CLONE_URL_ENV, raising=False) monkeypatch.setattr(pto_isa, "_is_git_available", lambda: True) def fake_run_git(args, cwd=None, timeout=30, check=False): @@ -64,12 +65,12 @@ def fake_run_git(args, cwd=None, timeout=30, check=False): assert pto_isa._clone(target, PIN_A, verbose=False) assert calls == [ - ["clone", "--no-checkout", "https://github.com/hw-native-sys/pto-isa.git", str(target)], + ["clone", "--no-checkout", "https://gitcode.com/wxwnnzdyd/pto-isa.git", str(target)], ["checkout", "--detach", "--force", PIN_A], ] -def test_clone_falls_back_to_gitcode_after_three_github_pin_failures(tmp_path, monkeypatch): +def test_clone_falls_back_to_github_after_three_gitcode_pin_failures(tmp_path, monkeypatch): target = tmp_path / "build" / "pto-isa" clone_remotes = [] land_results = iter([False, False, False, True]) @@ -88,15 +89,15 @@ def fake_run_git(args, cwd=None, timeout=30, check=False): assert pto_isa._clone(target, PIN_A, verbose=False) assert clone_remotes == [ + "https://gitcode.com/wxwnnzdyd/pto-isa.git", + "https://gitcode.com/wxwnnzdyd/pto-isa.git", + "https://gitcode.com/wxwnnzdyd/pto-isa.git", "https://github.com/hw-native-sys/pto-isa.git", - "https://github.com/hw-native-sys/pto-isa.git", - "https://github.com/hw-native-sys/pto-isa.git", - "https://gitcode.com/luohuan40/pto-isa.git", ] assert sleeps == [2, 4] -def test_clone_falls_back_to_gitcode_after_three_github_clone_failures(tmp_path, monkeypatch): +def test_clone_falls_back_to_github_after_three_gitcode_clone_failures(tmp_path, monkeypatch): target = tmp_path / "build" / "pto-isa" clone_remotes = [] @@ -107,7 +108,7 @@ def fake_run_git(args, cwd=None, timeout=30, check=False): assert args[:2] == ["clone", "--no-checkout"] remote = args[2] clone_remotes.append(remote) - returncode = 1 if remote == "https://github.com/hw-native-sys/pto-isa.git" else 0 + returncode = 1 if remote == "https://gitcode.com/wxwnnzdyd/pto-isa.git" else 0 return subprocess.CompletedProcess(["git", *args], returncode=returncode, stdout="", stderr="unavailable") monkeypatch.setattr(pto_isa, "_run_git", fake_run_git) @@ -115,10 +116,10 @@ def fake_run_git(args, cwd=None, timeout=30, check=False): assert pto_isa._clone(target, PIN_A, verbose=False) assert clone_remotes == [ + "https://gitcode.com/wxwnnzdyd/pto-isa.git", + "https://gitcode.com/wxwnnzdyd/pto-isa.git", + "https://gitcode.com/wxwnnzdyd/pto-isa.git", "https://github.com/hw-native-sys/pto-isa.git", - "https://github.com/hw-native-sys/pto-isa.git", - "https://github.com/hw-native-sys/pto-isa.git", - "https://gitcode.com/luohuan40/pto-isa.git", ] diff --git a/tests/ut/py/test_runtime_builder.py b/tests/ut/py/test_runtime_builder.py index 8d73e280a7..c942795c50 100644 --- a/tests/ut/py/test_runtime_builder.py +++ b/tests/ut/py/test_runtime_builder.py @@ -694,7 +694,7 @@ def test_composite_keeps_both_segments_visible(self): class TestBuildCacheStamp: - """Test cmake cache stamp composition (runtime HEAD + pto-isa commit).""" + """Test cmake cache stamp composition (runtime HEAD + pto-isa/CANN inputs).""" def _make_builder(self, platform): from simpler_setup.platform_info import parse_platform # noqa: PLC0415 @@ -714,7 +714,7 @@ def test_a2a3_onboard_folds_in_pto_isa_commit(self, monkeypatch): monkeypatch.setattr(pto_isa, "read_pto_isa_pin", lambda: "isa_sha") builder = self._make_builder("a2a3") - assert builder._build_cache_stamp() == "runtime_sha:pto-isa=isa_sha" + assert builder._build_cache_stamp() == "runtime_sha:pto-isa=isa_sha:ascend-home=" def test_a5_default_folds_in_pto_isa_commit(self, monkeypatch): """a5 default SDMA workspace folds the pto-isa pin into the cache stamp.""" @@ -726,7 +726,7 @@ def test_a5_default_folds_in_pto_isa_commit(self, monkeypatch): monkeypatch.setattr(pto_isa, "read_pto_isa_pin", lambda: "isa_sha") builder = self._make_builder("a5") - assert builder._build_cache_stamp() == "runtime_sha:pto-isa=isa_sha" + assert builder._build_cache_stamp() == "runtime_sha:pto-isa=isa_sha:ascend-home=" def test_non_a2a3_onboard_uses_pure_runtime_sha(self, monkeypatch): """Other arch/variant ignores pto-isa → stamp keyed on runtime HEAD only."""