diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 35bb08012e..ad49a2faa1 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -14,6 +14,7 @@ * A new runtime transport layer for remote/local executors is introduced. [(#3043)](https://github.com/PennyLaneAI/catalyst/pull/3043) + [(#3045)](https://github.com/PennyLaneAI/catalyst/pull/3045) * A `BufferizableOpInterface` implementation is now added for `catalyst.launch_kernel` operation and it is now bufferizable. [(#3024)](https://github.com/PennyLaneAI/catalyst/pull/3024) @@ -478,4 +479,5 @@ Mehrdad Malekmohammadi, River McCubbin, Shuli Shu, Paul Haochen Wang, -Jake Zaia. +Jake Zaia, +Hongsheng Zheng. diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 9fe951ed2a..8cf8c3a201 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -15,6 +15,7 @@ option(RUNTIME_ENABLE_WARNINGS "Enable -Wall and -Werror" ON) option(ENABLE_OPENQASM "Build OpenQasm backend device" OFF) option(ENABLE_OQD "Build OQD backend device" OFF) +option(ENABLE_TRANSPORT "Build the backend-agnostic transport loader (rt_transport)" OFF) set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/runtime/include/TransportBackend.h b/runtime/include/TransportBackend.h new file mode 100644 index 0000000000..734362510c --- /dev/null +++ b/runtime/include/TransportBackend.h @@ -0,0 +1,76 @@ +// Copyright 2026 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The plugin ABI for out-of-tree transport backends. +// +// A transport backend is a shared library that implements a TransportSession role (controller or +// coprocessor) and exports the matching factory symbol. + +#pragma once +#ifndef TRANSPORTBACKEND_H +#define TRANSPORTBACKEND_H + +#include +#include + +#include "Transport.hpp" + +#define CATALYST_TRANSPORT_CONTROLLER_FACTORY_SYMBOL "CatalystTransportControllerFactory" +#define CATALYST_TRANSPORT_COPROCESSOR_FACTORY_SYMBOL "CatalystTransportCoprocessorFactory" + +// The factory signatures backends must export with C linkage. +extern "C" { +using CatalystTransportControllerFactoryFn = catalyst::transport::ControllerSession *(const char *); +using CatalystTransportCoprocessorFactoryFn = + catalyst::transport::CoprocessorSession *(const char *); +} + +// A helper template macro to generate the Factory function. +// e.g. GENERATE_TRANSPORT_CONTROLLER_FACTORY(CatalystTransportController, make_controller) +// where `make_controller(const std::string &config) -> ControllerSession*`. +#define GENERATE_TRANSPORT_CONTROLLER_FACTORY(IDENTIFIER, BUILDER) \ + extern "C" catalyst::transport::ControllerSession *IDENTIFIER##Factory(const char *config) \ + { \ + try { \ + return (BUILDER)(config ? std::string(config) : std::string()); \ + } \ + catch (const std::exception &e) { \ + std::fprintf(stderr, "[transport] controller factory failed: %s\n", e.what()); \ + return nullptr; \ + } \ + catch (...) { \ + std::fprintf(stderr, "[transport] controller factory failed: unknown exception\n"); \ + return nullptr; \ + } \ + } + +// e.g. GENERATE_TRANSPORT_COPROCESSOR_FACTORY(CatalystTransportCoprocessor, make_coprocessor) +// where `make_coprocessor(const std::string &config) -> CoprocessorSession*`. +#define GENERATE_TRANSPORT_COPROCESSOR_FACTORY(IDENTIFIER, BUILDER) \ + extern "C" catalyst::transport::CoprocessorSession *IDENTIFIER##Factory(const char *config) \ + { \ + try { \ + return (BUILDER)(config ? std::string(config) : std::string()); \ + } \ + catch (const std::exception &e) { \ + std::fprintf(stderr, "[transport] coprocessor factory failed: %s\n", e.what()); \ + return nullptr; \ + } \ + catch (...) { \ + std::fprintf(stderr, "[transport] coprocessor factory failed: unknown exception\n"); \ + return nullptr; \ + } \ + } + +#endif // TRANSPORTBACKEND_H diff --git a/runtime/include/TransportCAPI.h b/runtime/include/TransportCAPI.h new file mode 100644 index 0000000000..c1fb6817a3 --- /dev/null +++ b/runtime/include/TransportCAPI.h @@ -0,0 +1,91 @@ +// Copyright 2026 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TransportCAPI.h - C entry points the Catalyst compiler emits to drive a transport session. +// +// The backend is a separate plugin `.so` the runtime dlopen's at session create (see +// TransportBackend.h) + +#pragma once +#ifndef TRANSPORTCAPI_H +#define TRANSPORTCAPI_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Opaque transport session handle +typedef struct CatalystTransportSession CatalystTransportSession; + +// Return codes: 0 == success; negative == error +enum { + CATALYST_TRANSPORT_OK = 0, + CATALYST_TRANSPORT_ERR = -1, // Generic exception + CATALYST_TRANSPORT_ERR_MEMORY = -2, // Memory error + CATALYST_TRANSPORT_ERR_TIMEOUT = -3, // Timeout error + CATALYST_TRANSPORT_ERR_STUCK = -4, // Something got stuck +}; + +// DataPath enum (mirrors catalyst::transport::DataPath) +enum { + CATALYST_TRANSPORT_PATH_CPU_VERBS = 0, + CATALYST_TRANSPORT_PATH_GPU_ENGINE = 1, + CATALYST_TRANSPORT_PATH_OTHER = 2, +}; + +// MemKind enum (mirrors catalyst::transport::MemKind) +enum { + CATALYST_TRANSPORT_MEM_CPU_RAM = 0, + CATALYST_TRANSPORT_MEM_GPU_HBM = 1, + CATALYST_TRANSPORT_MEM_DDR = 2, + CATALYST_TRANSPORT_MEM_OTHER = 3, +}; + +// Remote peer region descriptor +typedef struct { + uint32_t rkey; + uint64_t remote_addr; + uint64_t size; +} CatalystTransportPeerRef; + +// Create a controller session from a named backend plugin `.so` (dlopen'd by the runtime). +// `config` is the backend's "key=value;..." string. Returns NULL on failure. +CatalystTransportSession *__catalyst__transport__controller_create(const char *backend_lib, + const char *config); + +void __catalyst__transport__close(CatalystTransportSession *s); +int __catalyst__transport__connect(CatalystTransportSession *s, const char *peer, + uint16_t oob_port); +int __catalyst__transport__exchange_keys(CatalystTransportSession *s, + CatalystTransportPeerRef *out); +int __catalyst__transport__establish_channel(CatalystTransportSession *s, int32_t data_path, + const CatalystTransportPeerRef *peer); +int __catalyst__transport__commit_work_item(CatalystTransportSession *s, uint32_t work_item_idx, + uint64_t in_bytes, uint64_t out_bytes); +void *__catalyst__transport__data_slot(CatalystTransportSession *s); +void __catalyst__transport__start(CatalystTransportSession *s); +int __catalyst__transport__kick(CatalystTransportSession *s, uint32_t work_item_idx); +int __catalyst__transport__collect(CatalystTransportSession *s, void *correction, uint64_t bytes); +uint64_t __catalyst__transport__last_rtt_ns(CatalystTransportSession *s); +void __catalyst__transport__stop(CatalystTransportSession *s); +void __catalyst__transport__destroy(CatalystTransportSession *s); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // TRANSPORTCAPI_H diff --git a/runtime/lib/CMakeLists.txt b/runtime/lib/CMakeLists.txt index eb7a77b63d..64d07dcd95 100644 --- a/runtime/lib/CMakeLists.txt +++ b/runtime/lib/CMakeLists.txt @@ -68,3 +68,7 @@ add_subdirectory(QEC) if(ENABLE_OQD) add_subdirectory(OQDcapi) endif() + +if(ENABLE_TRANSPORT) +add_subdirectory(transport) +endif() diff --git a/runtime/lib/transport/CMakeLists.txt b/runtime/lib/transport/CMakeLists.txt new file mode 100644 index 0000000000..b2cb3a38c1 --- /dev/null +++ b/runtime/lib/transport/CMakeLists.txt @@ -0,0 +1,19 @@ +############################################### +# library rt_transport # +############################################### + +add_library(rt_transport SHARED TransportCAPI.cpp) + +target_include_directories(rt_transport + PUBLIC + ${runtime_includes} + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/lib/backend/common # DynamicLibraryLoader.hpp +) + +target_link_libraries(rt_transport PRIVATE + ${CMAKE_DL_LIBS} # dlopen/dlsym for backend plugins +) + +set_property(TARGET rt_transport PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/runtime/lib/transport/TransportCAPI.cpp b/runtime/lib/transport/TransportCAPI.cpp new file mode 100644 index 0000000000..429541508a --- /dev/null +++ b/runtime/lib/transport/TransportCAPI.cpp @@ -0,0 +1,251 @@ +// Copyright 2026 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "TransportCAPI.h" + +#include +#include +#include +#include +#include +#include + +#include "DynamicLibraryLoader.hpp" +#include "Transport.hpp" +#include "TransportBackend.h" + +using catalyst::transport::ChannelDesc; +using catalyst::transport::ConnectInfo; +using catalyst::transport::ControllerSession; +using catalyst::transport::DataPath; +using catalyst::transport::MemRegion; +using catalyst::transport::PeerRef; + +// The opaque handle +struct CatalystTransportSession { + std::unique_ptr backend; + ControllerSession *sess = nullptr; // heap-allocated by the backend factory +}; + +namespace { + +DataPath to_data_path(std::int32_t p) +{ + switch (p) { + case CATALYST_TRANSPORT_PATH_CPU_VERBS: + return DataPath::CpuVerbs; + case CATALYST_TRANSPORT_PATH_GPU_ENGINE: + return DataPath::GpuEngine; + case CATALYST_TRANSPORT_PATH_OTHER: + default: + return DataPath::Other; + } +} + +template int guard(Fn &&fn) +{ + try { + return fn(); + } + catch (const std::exception &e) { + std::cerr << "[transport] " << e.what() << "\n"; + return CATALYST_TRANSPORT_ERR; + } + catch (...) { + return CATALYST_TRANSPORT_ERR; + } +} + +} // namespace + +extern "C" { + +CatalystTransportSession *__catalyst__transport__controller_create(const char *backend_lib, + const char *config) +{ + try { + if (!backend_lib || !*backend_lib) { + std::cerr << "[transport] no backend library given\n"; + return nullptr; + } + auto h = std::make_unique(); + h->backend = std::make_unique(backend_lib); + auto *factory = h->backend->getSymbol( + CATALYST_TRANSPORT_CONTROLLER_FACTORY_SYMBOL); + h->sess = factory(config ? config : ""); + if (!h->sess) { + std::cerr << "[transport] backend factory returned null for config: " + << (config ? config : "") << "\n"; + return nullptr; + } + return h.release(); + } + catch (const std::exception &e) { + std::cerr << "[transport] controller_create: " << e.what() << "\n"; + return nullptr; + } + catch (...) { + return nullptr; + } +} + +int __catalyst__transport__connect(CatalystTransportSession *s, const char *peer, + std::uint16_t oob_port) +{ + if (!s || !s->sess) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { + ConnectInfo info; + info.peer = peer ? peer : ""; + info.oob_port = oob_port; + return s->sess->connect(info); + }); +} + +int __catalyst__transport__exchange_keys(CatalystTransportSession *s, CatalystTransportPeerRef *out) +{ + if (!s || !s->sess) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { + PeerRef p = s->sess->exchange_keys(MemRegion{}); + if (out) { + out->rkey = p.rkey; + out->remote_addr = p.remote_addr; + out->size = p.size; + } + return CATALYST_TRANSPORT_OK; + }); +} + +int __catalyst__transport__establish_channel(CatalystTransportSession *s, std::int32_t data_path, + const CatalystTransportPeerRef *peer) +{ + if (!s || !s->sess || !peer) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { + ChannelDesc desc; + desc.data_path = to_data_path(data_path); + PeerRef p; + p.rkey = peer->rkey; + p.remote_addr = peer->remote_addr; + p.size = peer->size; + s->sess->establish_channel(desc, MemRegion{}, p); + return CATALYST_TRANSPORT_OK; + }); +} + +int __catalyst__transport__commit_work_item(CatalystTransportSession *s, + std::uint32_t work_item_idx, std::uint64_t in_bytes, + std::uint64_t out_bytes) +{ + if (!s || !s->sess) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { + s->sess->commit_work_item(work_item_idx, in_bytes, out_bytes); + return CATALYST_TRANSPORT_OK; + }); +} + +void *__catalyst__transport__data_slot(CatalystTransportSession *s) +{ + if (!s || !s->sess) { + return nullptr; + } + + void *slot = nullptr; + try { + slot = s->sess->data_slot(); + } + catch (const std::exception &e) { + std::cerr << "[transport] data_slot: " << e.what() << "\n"; + return nullptr; + } + catch (...) { + return nullptr; + } + return slot; +} + +int __catalyst__transport__kick(CatalystTransportSession *s, std::uint32_t work_item_idx) +{ + if (!s || !s->sess) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { return s->sess->kick(work_item_idx); }); +} + +int __catalyst__transport__collect(CatalystTransportSession *s, void *correction, + std::uint64_t bytes) +{ + if (!s || !s->sess) { + return CATALYST_TRANSPORT_ERR; + } + return guard([&] { return s->sess->collect(correction, bytes); }); +} + +std::uint64_t __catalyst__transport__last_rtt_ns(CatalystTransportSession *s) +{ + if (!s || !s->sess) { + return 0; + } + return s->sess->last_rtt_ns(); +} + +void __catalyst__transport__start(CatalystTransportSession *s) +{ + if (!s || !s->sess) { + return; + } + try { + s->sess->start(); + } + catch (const std::exception &e) { + std::cerr << "[transport] start: " << e.what() << "\n"; + } + catch (...) { + } +} + +void __catalyst__transport__stop(CatalystTransportSession *s) +{ + if (s && s->sess) { + try { + s->sess->stop(); + } + catch (...) { + } + } +} + +void __catalyst__transport__destroy(CatalystTransportSession *s) +{ + if (!s) { + return; + } + delete s->sess; // owned by the backend factory + s->backend.reset(); + delete s; +} + +void __catalyst__transport__close(CatalystTransportSession *s) +{ + __catalyst__transport__stop(s); + __catalyst__transport__destroy(s); +} + +} // extern "C"