From 738dc4baa578dd159889ee53dd171f633eefccb0 Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Thu, 22 Aug 2024 11:33:25 +0100 Subject: [PATCH 01/19] Refactor orca qpu --- runtime/cudaq/platform/orca/CMakeLists.txt | 8 +- runtime/cudaq/platform/orca/OrcaExecutor.cpp | 55 ++++ runtime/cudaq/platform/orca/OrcaExecutor.h | 49 ++++ runtime/cudaq/platform/orca/OrcaQPU.cpp | 271 +----------------- .../cudaq/platform/orca/OrcaRemoteRESTQPU.cpp | 112 ++++++++ .../cudaq/platform/orca/OrcaRemoteRESTQPU.h | 128 +++++++++ .../cudaq/platform/orca/OrcaServerHelper.cpp | 127 ++++++++ .../cudaq/platform/orca/OrcaServerHelper.h | 119 ++++++++ 8 files changed, 599 insertions(+), 270 deletions(-) create mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.cpp create mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.h create mode 100644 runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp create mode 100644 runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h create mode 100644 runtime/cudaq/platform/orca/OrcaServerHelper.cpp create mode 100644 runtime/cudaq/platform/orca/OrcaServerHelper.h diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index 779a2cf794..c3d593ab10 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -8,8 +8,14 @@ set(LIBRARY_NAME cudaq-orca-qpu) message(STATUS "Building ORCA REST QPU.") +set(ORCA_SRC + OrcaExecutor.cpp + OrcaServerHelper.cpp + OrcaRemoteRESTQPU.cpp + OrcaQPU.cpp +) -add_library(${LIBRARY_NAME} SHARED OrcaQPU.cpp) +add_library(${LIBRARY_NAME} SHARED ${ORCA_SRC}) target_include_directories(${LIBRARY_NAME} PRIVATE . PUBLIC diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.cpp b/runtime/cudaq/platform/orca/OrcaExecutor.cpp new file mode 100644 index 0000000000..aae2854b2a --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaExecutor.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "OrcaExecutor.h" +#include "common/Logger.h" + +namespace cudaq::orca { +cudaq::details::future OrcaExecutor::execute(TBIParameters params) { + + serverHelper->setShots(shots); + + cudaq::info("Executor creating job to execute with the {} helper.", + serverHelper->name()); + + // Create the Job Payload, composed of job post path, headers, + // and the job json messages themselves + auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); + auto job = jobs[0]; + auto config = serverHelper->getConfig(); + + std::vector ids; + // for (std::size_t i = 0; auto &job : jobs) { + cudaq::info("Job created, posting to {}", jobPostPath); + + // Post it, get the response + auto response = client.post(jobPostPath, "", job, headers); + cudaq::info("Job posted, response was {}", response.dump()); + + // // Add the job id and the job name. + // auto task_id = serverHelper->extractJobId(response); + // if (task_id.empty()) { + // nlohmann::json tmp(job.at("tasks")); + // serverHelper->constructGetJobPath(tmp[0]); + // task_id = tmp[0].at("task_id"); + // } + // cudaq::info("Task ID is {}", task_id); + // ids.emplace_back(task_id, codesToExecute[i].name); + // config["output_names." + task_id] = codesToExecute[i].output_names.dump(); + + // nlohmann::json jReorder = codesToExecute[i].mapping_reorder_idx; + // config["reorderIdx." + task_id] = jReorder.dump(); + + // i++; + // } + + // config.insert({"shots", std::to_string(shots)}); + std::string name = serverHelper->name(); + return cudaq::details::future(ids, name, config); +} +} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.h b/runtime/cudaq/platform/orca/OrcaExecutor.h new file mode 100644 index 0000000000..e24451c762 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaExecutor.h @@ -0,0 +1,49 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once +#include "OrcaServerHelper.h" +#include "common/ExecutionContext.h" +#include "common/RestClient.h" +// #include "cudaq.h" +#include "orca_qpu.h" + +namespace cudaq::orca { + +/// @brief The Executor provides an abstraction for executing compiled +/// quantum codes targeting a remote REST server. This type provides a +/// clean abstraction launching a vector of Jobs for sampling and observation +/// tasks, both synchronously and asynchronously. +class OrcaExecutor : public registry::RegisteredType { +protected: + /// @brief The REST Client used to interact with the remote system + RestClient client; + + /// @brief The ServerHelper, providing system-specific JSON-formatted + /// job posts and results translation + OrcaServerHelper *serverHelper; + + /// @brief The number of shots to execute + std::size_t shots = 100; + +public: + OrcaExecutor() = default; + virtual ~OrcaExecutor() = default; + + /// @brief Set the server helper + void setServerHelper(OrcaServerHelper *helper) { serverHelper = helper; } + + /// @brief Set the number of shots to execute + void setShots(std::size_t s) { shots = s; } + + /// @brief Execute the provided quantum codes and return a future object + /// The caller can make this synchronous by just immediately calling .get(). + cudaq::details::future execute(TBIParameters params); +}; + +} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaQPU.cpp b/runtime/cudaq/platform/orca/OrcaQPU.cpp index 8c6a414b5a..d1fc08d74d 100644 --- a/runtime/cudaq/platform/orca/OrcaQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaQPU.cpp @@ -11,19 +11,17 @@ #include "common/FmtCore.h" #include "common/Logger.h" -#include "common/RestClient.h" -#include "common/ServerHelper.h" #include "cudaq.h" #include "nvqpp_config.h" +#include "OrcaExecutor.h" +#include "OrcaRemoteRESTQPU.h" #include "cudaq/platform/qpu.h" #include "cudaq/platform/quantum_platform.h" #include "cudaq/qis/qubit_qis.h" #include "cudaq/spin_op.h" #include "orca_qpu.h" -#include "llvm/Support/Base64.h" - #include #include #include @@ -61,268 +59,3 @@ cudaq::sample_result sample(std::vector &input_state, return context.result; } } // namespace cudaq::orca - -namespace { - -/// @brief The OrcaRemoteRESTQPU is a subtype of QPU that enables the -/// execution of CUDA-Q kernels on remotely hosted quantum computing -/// services via a REST Client / Server interaction. This type is meant -/// to be general enough to support any remotely hosted service. -/// Moreover, this QPU handles launching kernels under the Execution Context -/// that includs sampling via synchronous client invocations. -class OrcaRemoteRESTQPU : public cudaq::QPU { -protected: - /// The number of shots - std::optional nShots; - - /// @brief the platform file path, CUDAQ_INSTALL/platforms - std::filesystem::path platformPath; - - /// @brief The name of the QPU being targeted - std::string qpuName; - - /// @brief The base URL - std::string baseUrl; - - /// @brief The machine we are targeting - std::string machine = "PT-1"; - - /// @brief Mapping of general key-values for backend - /// configuration. - std::map backendConfig; - - /// @brief Flag indicating whether we should emulate - /// execution locally. - bool emulate = false; - -private: - /// @brief RestClient used for HTTP requests. - cudaq::RestClient client; - -public: - /// @brief The constructor - OrcaRemoteRESTQPU() : QPU() { - std::filesystem::path cudaqLibPath{cudaq::getCUDAQLibraryPath()}; - platformPath = cudaqLibPath.parent_path().parent_path() / "targets"; - } - - OrcaRemoteRESTQPU(OrcaRemoteRESTQPU &&) = delete; - - /// @brief The destructor - virtual ~OrcaRemoteRESTQPU() = default; - - /// Enqueue a quantum task on the asynchronous execution queue. - void enqueue(cudaq::QuantumTask &task) override { - execution_queue->enqueue(task); - } - - /// @brief Return true if the current backend is a simulator - bool isSimulator() override { return emulate; } - - /// @brief Return true if the current backend supports conditional feedback - bool supportsConditionalFeedback() override { return false; } - - /// Provide the number of shots - void setShots(int _nShots) override { nShots = _nShots; } - - /// Clear the number of shots - void clearShots() override { nShots = std::nullopt; } - - /// @brief Return true if the current backend is remote - virtual bool isRemote() override { return !emulate; } - - /// Store the execution context for launchKernel - void setExecutionContext(cudaq::ExecutionContext *context) override { - if (!context) - return; - - cudaq::info("Remote Rest QPU setting execution context to {}", - context->name); - - // Execution context is valid - executionContext = context; - } - - /// Reset the execution context - void resetExecutionContext() override { - // do nothing here - executionContext = nullptr; - } - - /// @brief This setTargetBackend override is in charge of reading the - /// specific target backend configuration file. - void setTargetBackend(const std::string &backend) override; - - /// @brief Creates a quantum computation job using the provided kernel - /// executions and returns the corresponding payload. - cudaq::ServerJobPayload createJob(cudaq::orca::TBIParameters params); - - /// @brief Given a completed job response, map back to the sample_result - cudaq::sample_result processResults(cudaq::ServerMessage &postJobResponse); - - /// @brief Returns the name of the server helper. - const std::string name() const { return "orca"; } - - /// @brief Returns the headers for the server requests. - cudaq::RestHeaders getHeaders(); - - /// @brief Initializes the server helper with the provided backend - /// configuration. - void initialize(); - - /// @brief Launch the kernel. Handle all pertinent - /// modifications for the execution context. - void launchKernel(const std::string &kernelName, void (*kernelFunc)(void *), - void *args, std::uint64_t voidStarSize, - std::uint64_t resultOffset) override; - void launchKernel(const std::string &kernelName, - const std::vector &rawArgs) override { - throw std::runtime_error("launch kernel on raw args not implemented"); - } -}; - -/// @brief This setTargetBackend override is in charge of reading the -/// specific target backend configuration file. -void OrcaRemoteRESTQPU::setTargetBackend(const std::string &backend) { - cudaq::info("Remote REST platform is targeting {}.", backend); - - // First we see if the given backend has extra config params - auto mutableBackend = backend; - if (mutableBackend.find(";") != std::string::npos) { - auto split = cudaq::split(mutableBackend, ';'); - mutableBackend = split[0]; - // Must be key-value pairs, therefore an even number of values here - if ((split.size() - 1) % 2 != 0) - throw std::runtime_error( - "Backend config must be provided as key-value pairs: " + - std::to_string(split.size())); - - // Add to the backend configuration map - for (std::size_t i = 1; i < split.size(); i += 2) { - // No need to decode trivial true/false values - if (split[i + 1].starts_with("base64_")) { - split[i + 1].erase(0, 7); // erase "base64_" - std::vector decoded_vec; - if (auto err = llvm::decodeBase64(split[i + 1], decoded_vec)) - throw std::runtime_error("DecodeBase64 error"); - std::string decodedStr(decoded_vec.data(), decoded_vec.size()); - cudaq::info("Decoded {} parameter from '{}' to '{}'", split[i], - split[i + 1], decodedStr); - backendConfig.insert({split[i], decodedStr}); - } else { - backendConfig.insert({split[i], split[i + 1]}); - } - } - } - - /// Once we know the backend, we should search for the config file - /// from there we can get the URL/PORT and other inforation used in the - /// pipeline. - // Set the qpu name - qpuName = mutableBackend; - initialize(); -} - -/// @brief Launch the kernel. -void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, - void (*kernelFunc)(void *), void *args, - std::uint64_t voidStarSize, - std::uint64_t resultOffset) { - cudaq::info("launching ORCA remote rest kernel ({})", kernelName); - - // TODO future iterations of this should support non-void return types. - if (!executionContext) - throw std::runtime_error("Remote rest execution can only be performed " - "via cudaq::sample() or cudaq::observe()."); - - cudaq::orca::TBIParameters params = - *((struct cudaq::orca::TBIParameters *)args); - std::size_t shots = params.n_samples; - - setShots(shots); - executionContext->shots = shots; - - cudaq::info("Executor creating job to execute with the {} helper.", name()); - - // Create the Job Payload, composed of job post path, headers, - // and the job json messages themselves - auto [jobPostPath, headers, jobs] = createJob(params); - auto job = jobs[0]; - cudaq::info("Job (name={}) created, posting to {}", kernelName, jobPostPath); - - // Post it, get the response - auto response = client.post(jobPostPath, "", job, headers); - - cudaq::sample_result counts = processResults(response); - - // // return the results synchronously - executionContext->result = counts; -} - -// Initialize the ORCA server helper with a given backend configuration -void OrcaRemoteRESTQPU::initialize() { - // Set the machine - auto iter = backendConfig.find("machine"); - if (iter != backendConfig.end()) - machine = iter->second; - - // Set a base URL if provided - iter = backendConfig.find("url"); - if (iter != backendConfig.end()) { - baseUrl = iter->second; - } -} - -// Create a job for the ORCA QPU -cudaq::ServerJobPayload -OrcaRemoteRESTQPU::createJob(cudaq::orca::TBIParameters params) { - std::vector jobs; - cudaq::ServerMessage job; - - // Construct the job message - job["target"] = machine; - - job["input_state"] = params.input_state; - job["loop_lengths"] = params.loop_lengths; - job["bs_angles"] = params.bs_angles; - job["ps_angles"] = params.ps_angles; - job["n_samples"] = params.n_samples; - - jobs.push_back(job); - - // Return a tuple containing the job path, headers, and the job message - auto ret = std::make_tuple(baseUrl, getHeaders(), jobs); - return ret; -} - -// Process the results from a job -cudaq::sample_result -OrcaRemoteRESTQPU::processResults(cudaq::ServerMessage &postJobResponse) { - auto results = postJobResponse.at("results"); - - cudaq::CountsDictionary counts; - // Process the results - for (const auto &key : results) { - counts[key] += 1; - } - - // Create an execution result - cudaq::ExecutionResult executionResult(counts); - // Return a sample result - auto ret = cudaq::sample_result(executionResult); - return ret; -} - -// Get the headers for the API requests -cudaq::RestHeaders OrcaRemoteRESTQPU::getHeaders() { - // Construct the headers - cudaq::RestHeaders headers; - headers["Authorization"] = "apiKey "; - headers["Content-Type"] = "application/json"; - // Return the headers - return headers; -} - -} // namespace - -CUDAQ_REGISTER_TYPE(cudaq::QPU, OrcaRemoteRESTQPU, orca) diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp new file mode 100644 index 0000000000..049f2f9f66 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ +#include "OrcaRemoteRESTQPU.h" +#include "common/Future.h" +#include "common/Logger.h" +#include "common/Registry.h" +#include "llvm/Support/Base64.h" + +namespace cudaq{ +/// @brief This setTargetBackend override is in charge of reading the +/// specific target backend configuration file. +void OrcaRemoteRESTQPU::setTargetBackend(const std::string &backend) { + cudaq::info("Remote REST platform is targeting {}.", backend); + + // First we see if the given backend has extra config params + auto mutableBackend = backend; + if (mutableBackend.find(";") != std::string::npos) { + auto split = cudaq::split(mutableBackend, ';'); + mutableBackend = split[0]; + // Must be key-value pairs, therefore an even number of values here + if ((split.size() - 1) % 2 != 0) + throw std::runtime_error( + "Backend config must be provided as key-value pairs: " + + std::to_string(split.size())); + + // Add to the backend configuration map + for (std::size_t i = 1; i < split.size(); i += 2) { + // No need to decode trivial true/false values + if (split[i + 1].starts_with("base64_")) { + split[i + 1].erase(0, 7); // erase "base64_" + std::vector decoded_vec; + if (auto err = llvm::decodeBase64(split[i + 1], decoded_vec)) + throw std::runtime_error("DecodeBase64 error"); + std::string decodedStr(decoded_vec.data(), decoded_vec.size()); + cudaq::info("Decoded {} parameter from '{}' to '{}'", split[i], + split[i + 1], decodedStr); + backendConfig.insert({split[i], decodedStr}); + } else { + backendConfig.insert({split[i], split[i + 1]}); + } + } + } + + /// Once we know the backend, we should search for the config file + /// from there we can get the URL/PORT and other information used in the + /// pipeline. + // Set the qpu name + qpuName = mutableBackend; + serverHelper = registry::get(qpuName); + serverHelper->initialize(backendConfig); + + // Give the server helper to the executor + executor->setServerHelper(serverHelper.get()); +} + +/// @brief Launch the kernel. +void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, + void (*kernelFunc)(void *), void *args, + std::uint64_t voidStarSize, + std::uint64_t resultOffset) { + cudaq::info("launching ORCA remote rest kernel ({})", kernelName); + + // TODO future iterations of this should support non-void return types. + if (!executionContext) + throw std::runtime_error("Remote rest execution can only be performed " + "via cudaq::sample() or cudaq::observe()."); + + cudaq::orca::TBIParameters params = + *((struct cudaq::orca::TBIParameters *)args); + std::size_t shots = params.n_samples; + + setShots(shots); + executionContext->shots = shots; + + cudaq::details::future future; + future = executor->execute(params); + + // Keep this asynchronous if requested + if (executionContext->asyncExec) { + executionContext->futureResult = future; + return; + } + + // Otherwise make this synchronous + executionContext->result = future.get(); + + // // Create the Job Payload, composed of job post path, headers, + // // and the job json messages themselves + // auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); + // auto job = jobs[0]; + // cudaq::info("Job (name={}) created, posting to {}", kernelName, + // jobPostPath); + + // // Post it, get the response + // auto response = client.post(jobPostPath, "", job, headers); + // cudaq::info("Job (name={}) posted, response was {}", kernelName, + // response.dump()); + + // cudaq::sample_result counts = serverHelper->processResults(response); + + // // return the results synchronously + // executionContext->result = counts; +} + +} // namespace + +CUDAQ_REGISTER_TYPE(cudaq::QPU, cudaq::OrcaRemoteRESTQPU, orca) \ No newline at end of file diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h new file mode 100644 index 0000000000..f69e16a4f4 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h @@ -0,0 +1,128 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ +#pragma once + +#include "common/ExecutionContext.h" +#include "common/FmtCore.h" + +#include "OrcaExecutor.h" +#include "OrcaServerHelper.h" + +#include "common/RestClient.h" +#include "cudaq.h" +#include "nvqpp_config.h" +#include "orca_qpu.h" + +#include "cudaq/platform/qpu.h" +#include "cudaq/platform/quantum_platform.h" + +namespace cudaq{ + +/// @brief The OrcaRemoteRESTQPU is a subtype of QPU that enables the +/// execution of CUDA-Q kernels on remotely hosted quantum computing +/// services via a REST Client / Server interaction. This type is meant +/// to be general enough to support any remotely hosted service. +/// Moreover, this QPU handles launching kernels under the Execution Context +/// that includes sampling via synchronous client invocations. +class OrcaRemoteRESTQPU : public cudaq::QPU { +protected: + /// The number of shots + std::optional nShots; + + /// @brief the platform file path, CUDAQ_INSTALL/platforms + std::filesystem::path platformPath; + + /// @brief The name of the QPU being targeted + std::string qpuName; + + /// @brief Flag indicating whether we should emulate + /// execution locally. + bool emulate = false; + + // Pointer to the concrete Executor for this QPU + std::unique_ptr executor; + + /// @brief Pointer to the concrete ServerHelper, provides + /// specific JSON payloads and POST/GET URL paths. + std::unique_ptr serverHelper; + + /// @brief Mapping of general key-values for backend + /// configuration. + std::map backendConfig; + +private: + /// @brief RestClient used for HTTP requests. + cudaq::RestClient client; + +public: + /// @brief The constructor + OrcaRemoteRESTQPU() : QPU() { + std::filesystem::path cudaqLibPath{cudaq::getCUDAQLibraryPath()}; + platformPath = cudaqLibPath.parent_path().parent_path() / "targets"; + // Default is to run sampling via the remote rest call + executor = std::make_unique(); + } + + OrcaRemoteRESTQPU(OrcaRemoteRESTQPU &&) = delete; + + /// @brief The destructor + virtual ~OrcaRemoteRESTQPU() = default; + + /// Enqueue a quantum task on the asynchronous execution queue. + void enqueue(cudaq::QuantumTask &task) override { + execution_queue->enqueue(task); + } + + /// @brief Return true if the current backend is a simulator + bool isSimulator() override { return emulate; } + + /// @brief Return true if the current backend supports conditional feedback + bool supportsConditionalFeedback() override { return false; } + + /// Provide the number of shots + void setShots(int _nShots) override { nShots = _nShots; } + + /// Clear the number of shots + void clearShots() override { nShots = std::nullopt; } + + /// @brief Return true if the current backend is remote + virtual bool isRemote() override { return !emulate; } + + /// Store the execution context for launchKernel + void setExecutionContext(cudaq::ExecutionContext *context) override { + if (!context) + return; + + cudaq::info("Remote Rest QPU setting execution context to {}", + context->name); + + // Execution context is valid + executionContext = context; + } + + /// Reset the execution context + void resetExecutionContext() override { + // do nothing here + executionContext = nullptr; + } + + /// @brief This setTargetBackend override is in charge of reading the + /// specific target backend configuration file. + void setTargetBackend(const std::string &backend) override; + + /// @brief Launch the kernel. Handle all pertinent + /// modifications for the execution context. + void launchKernel(const std::string &kernelName, void (*kernelFunc)(void *), + void *args, std::uint64_t voidStarSize, + std::uint64_t resultOffset) override; + void launchKernel(const std::string &kernelName, + const std::vector &rawArgs) override { + throw std::runtime_error("launch kernel on raw args not implemented"); + } +}; +} // namespace diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp new file mode 100644 index 0000000000..ea22ca4a48 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ +#include "OrcaServerHelper.h" +#include "common/Future.h" +#include "common/Logger.h" +#include "common/Registry.h" +#include "orca_qpu.h" + +namespace cudaq { + +// Initialize the ORCA server helper with a given backend configuration +void OrcaServerHelper::initialize(BackendConfig config) { + backendConfig = config; + + // Set the machine + auto iter = backendConfig.find("machine"); + if (iter != backendConfig.end()) + machine = iter->second; + + // Set an alternate base URL if provided + iter = backendConfig.find("url"); + if (iter != backendConfig.end()) { + baseUrl = iter->second; + if (!baseUrl.ends_with("/")) + baseUrl += "/"; + } +} + +// Create a job for the ORCA QPU +ServerJobPayload +OrcaServerHelper::createJob(cudaq::orca::TBIParameters params) { + std::vector jobs; + ServerMessage job; + + // Construct the job message + job["target"] = machine; + + job["input_state"] = params.input_state; + job["loop_lengths"] = params.loop_lengths; + job["bs_angles"] = params.bs_angles; + job["ps_angles"] = params.ps_angles; + job["n_samples"] = params.n_samples; + + jobs.push_back(job); + + // Return a tuple containing the job path, headers, and the job message + auto ret = std::make_tuple(baseUrl + "v1/submit", getHeaders(), jobs); + return ret; +} + +// ServerJobPayload +// OrcaServerHelper::createJob(std::vector &circuitCodes) { +// throw std::runtime_error("createJob on circuitCodes args not implemented"); +// } + +// Process the results from a job +sample_result OrcaServerHelper::processResults(ServerMessage &postJobResponse) { + auto results = postJobResponse.at("results"); + + CountsDictionary counts; + // Process the results + for (const auto &key : results) { + counts[key] += 1; + } + + // Create an execution result + ExecutionResult executionResult(counts); + // Return a sample result + auto ret = sample_result(executionResult); + return ret; +} + +std::map +OrcaServerHelper::generateRequestHeader() const { + std::string apiKey, refreshKey, timeStr; + std::map headers{ + // {"Authorization", apiKey}, + {"Content-Type", "application/json"}, + // {"Connection", "keep-alive"}, + // {"Accept", "*/*"} + }; + return headers; +} + +// Get the headers for the API requests +RestHeaders OrcaServerHelper::getHeaders() { return generateRequestHeader(); } + +// From a server message, extract the job ID +std::string OrcaServerHelper::extractJobId(ServerMessage &postResponse) { + // If the response does not contain the key 'id', throw an exception + if (!postResponse.contains("job_id")) + throw std::runtime_error("ServerMessage doesn't contain 'job_id' key."); + + // Return the job ID from the response + auto ret = postResponse.at("job_id"); + return ret; +} + +std::string OrcaServerHelper::constructGetJobPath(ServerMessage &postResponse) { + return baseUrl + "v1/get_job/" + extractJobId(postResponse); +} + +std::string OrcaServerHelper::constructGetJobPath(std::string &jobId) { + return baseUrl + "v1/get_job/" + jobId; +} + +bool OrcaServerHelper::jobIsDone(ServerMessage &getJobResponse) { + auto status = getJobResponse["status"].get(); + if (status == "failed") { + std::string msg = ""; + if (getJobResponse.count("error")) + msg = getJobResponse["error"]["text"].get(); + throw std::runtime_error("Job failed to execute msg = [" + msg + "]"); + } + + return status == "completed"; +} + +} // namespace cudaq + +LLVM_INSTANTIATE_REGISTRY(cudaq::OrcaServerHelper::RegistryType) +CUDAQ_REGISTER_TYPE(cudaq::OrcaServerHelper, cudaq::OrcaServerHelper, orca) diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.h b/runtime/cudaq/platform/orca/OrcaServerHelper.h new file mode 100644 index 0000000000..a960e28f25 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.h @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ +#pragma once + +#include "common/Future.h" +#include "common/Registry.h" +#include "nlohmann/json.hpp" +// #include "common/ServerHelper.h" +#include "orca_qpu.h" +#include "llvm/Support/Base64.h" + +namespace cudaq { +using BackendConfig = std::map; + +/// @brief Responses / Submissions to the Server are modeled via JSON +using ServerMessage = nlohmann::json; + +/// @brief Each REST interaction will require headers +using RestHeaders = std::map; + +// A Server Job Payload consists of a job post URL path, the headers, +// and a vector of related Job JSON messages. +using ServerJobPayload = + std::tuple>; + +// /// @brief Information about a result coming from a backend +// struct ResultInfoType { +// std::size_t qubitNum; +// std::string registerName; +// }; + +// /// @brief Results information, indexed by 0-based result number +// using OutputNamesType = std::map; + +class OrcaServerHelper +// : public ServerHelper { + : public registry::RegisteredType { +protected: + /// @brief All ServerHelpers can be configured at the `nvq++` command line. + /// This map holds those configuration key-values + BackendConfig backendConfig; + + /// @brief The number of shots to execute + std::size_t shots = 100; + + // /// @brief Output names indexed by jobID/taskID + // std::map outputNames; + + /// @brief The base URL + std::string baseUrl = "http://localhost:8080/"; + + /// @brief The machine we are targeting + std::string machine = "PT-1"; + + /// @brief Time string, when the last tokens were retrieved + std::string timeStr = ""; + + /// @brief The refresh token + std::string refreshKey = ""; + + /// @brief Orca requires the API token be updated every so often, + /// using the provided refresh token. This function will do that. + void refreshTokens(bool force_refresh = false); + + /// @brief Return the headers required for the REST calls + RestHeaders generateRequestHeader() const; + +public: + OrcaServerHelper() = default; + virtual ~OrcaServerHelper() = default; + + /// @brief Return the name of this server helper, must be the + /// same as the qpu config file. + const std::string name() const { return "orca"; } + + /// @brief Return the POST/GET required headers. + /// @return + RestHeaders getHeaders(); + + /// @brief Return the current server configuration + /// @return + BackendConfig getConfig() { return backendConfig; } + + /// @brief Set the number of shots to execute + void setShots(std::size_t s) { shots = s; } + + /// @brief Set the server configuration. + void initialize(BackendConfig config); + + /// @brief Create a job payload for the provided TBI parameters + ServerJobPayload createJob(cudaq::orca::TBIParameters params); + + // /// @brief Create a job payload for the provided quantum codes + // ServerJobPayload createJob(std::vector &circuitCodes); + + /// @brief Return the job id from the previous job post + std::string extractJobId(ServerMessage &postResponse); + + /// @brief Return the URL for retrieving job results + std::string constructGetJobPath(ServerMessage &postResponse); + std::string constructGetJobPath(std::string &jobId); + + /// @brief Return true if the job is done + bool jobIsDone(ServerMessage &getJobResponse); + + /// @brief Given a completed job response, map back to the sample_result + sample_result processResults(ServerMessage &postJobResponse); + + /// @brief Given a completed job response, map back to the sample_result + sample_result processResults(ServerMessage &postJobResponse, + std::string &jobID ); +}; + +} // namespace cudaq From ee1518d5a34b80b5262863ad35390444fd74cf30 Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Tue, 27 Aug 2024 12:53:46 +0100 Subject: [PATCH 02/19] Refactor --- docs/sphinx/examples/cpp/providers/orca.cpp | 30 +++- runtime/common/ExecutionContext.h | 5 + runtime/common/Executor.cpp | 46 +++++- runtime/common/Executor.h | 7 + runtime/common/ServerHelper.h | 26 ++- runtime/cudaq/platform/orca/CMakeLists.txt | 2 +- runtime/cudaq/platform/orca/OrcaExecutor.cpp | 55 ------- runtime/cudaq/platform/orca/OrcaExecutor.h | 49 ------ runtime/cudaq/platform/orca/OrcaFuture.cpp | 109 ++++++++++++ runtime/cudaq/platform/orca/OrcaFuture.h | 155 ++++++++++++++++++ runtime/cudaq/platform/orca/OrcaQPU.cpp | 75 ++++++--- .../cudaq/platform/orca/OrcaRemoteRESTQPU.cpp | 36 +--- .../cudaq/platform/orca/OrcaRemoteRESTQPU.h | 29 ++-- .../cudaq/platform/orca/OrcaServerHelper.cpp | 31 ++-- .../cudaq/platform/orca/OrcaServerHelper.h | 78 +++------ runtime/cudaq/platform/orca/orca.yml | 2 +- runtime/cudaq/platform/orca/orca_qpu.h | 14 +- 17 files changed, 493 insertions(+), 256 deletions(-) delete mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.cpp delete mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.h create mode 100644 runtime/cudaq/platform/orca/OrcaFuture.cpp create mode 100644 runtime/cudaq/platform/orca/OrcaFuture.h diff --git a/docs/sphinx/examples/cpp/providers/orca.cpp b/docs/sphinx/examples/cpp/providers/orca.cpp index a23f7aa49e..ec0a40a94e 100644 --- a/docs/sphinx/examples/cpp/providers/orca.cpp +++ b/docs/sphinx/examples/cpp/providers/orca.cpp @@ -1,6 +1,7 @@ // Compile and run with: // ``` -// nvq++ --target orca --orca-url $ORCA_ACCESS_URL orca.cpp -o out.x && ./out.x +// nvq++ --target orca --orca-url $ORCA_ACCESS_URL orca.cpp -o out.x && +// CUDAQ_LOG_LEVEL=info ./out.x // ``` // To use the ORCA Computing target you will need to set the ORCA_ACCESS_URL // environment variable or pass the URL to the `--orca-url` flag. @@ -8,6 +9,8 @@ #include "cudaq/orca.h" #include "cudaq.h" +#include + // define helper function to generate linear spaced vectors template void linear_spaced_vector(std::vector &xs, T min, T max, std::size_t N) { @@ -65,6 +68,9 @@ int main() { auto counts = cudaq::orca::sample(input_state, loop_lengths, bs_angles, n_samples); + // Print the results + counts.dump(); + // If the system includes phase shifters, the phase shifter angles can be // included in the call @@ -73,8 +79,26 @@ int main() { // ps_angles, n_samples); // ``` - // Print the results - counts.dump(); + + // auto async_results = + // cudaq::orca::sample_async(input_state, loop_lengths, bs_angles, n_samples); + + // // Can write the future to file: + // { + // std::ofstream out("saveMe.json"); + // out << async_results; + // } + + // // Then come back and read it in later. + // cudaq::async_result readIn; + // std::ifstream in("saveMe.json"); + // in >> readIn; + + // // Get the results of the read in future. + // auto async_counts = readIn.get(); + // async_counts.dump(); + + return 0; } \ No newline at end of file diff --git a/runtime/common/ExecutionContext.h b/runtime/common/ExecutionContext.h index 70c0827e49..0f41409898 100644 --- a/runtime/common/ExecutionContext.h +++ b/runtime/common/ExecutionContext.h @@ -14,6 +14,7 @@ #include "SimulationState.h" #include "Trace.h" #include "cudaq/algorithms/optimizer.h" +#include "cudaq/platform/orca/OrcaFuture.h" #include #include @@ -62,6 +63,10 @@ class ExecutionContext { /// the expected results as a cudaq::future here. details::future futureResult; + /// @brief When execution asynchronously, store + /// the expected results as a cudaq::orca::details::Orcafuture here. + orca::details::Orcafuture orcaFutureResult; + /// @brief Pointer to simulation-specific simulation data. std::unique_ptr simulationState; diff --git a/runtime/common/Executor.cpp b/runtime/common/Executor.cpp index 448442c214..60c0abe7e4 100644 --- a/runtime/common/Executor.cpp +++ b/runtime/common/Executor.cpp @@ -55,4 +55,48 @@ Executor::execute(std::vector &codesToExecute) { std::string name = serverHelper->name(); return details::future(ids, name, config); } -} // namespace cudaq + +cudaq::orca::details::Orcafuture +Executor::execute(cudaq::orca::TBIParameters params, + const std::string &kernelName) { + + serverHelper->setShots(shots); + + cudaq::info("Executor creating job to execute with the {} helper.", + serverHelper->name()); + + // Create the Job Payload, composed of job post path, headers, + // and the job json messages themselves + auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); + auto job = jobs[0]; + auto config = serverHelper->getConfig(); + + std::vector ids; + // for (std::size_t i = 0; auto &job : jobs) { + cudaq::info("Job created, posting to {}", jobPostPath); + + // Post it, get the response + auto response = client.post(jobPostPath, "", job, headers); + cudaq::info("Job posted, response was {}", response.dump()); + + // // Add the job id and the job name. + auto job_id = serverHelper->extractJobId(response); + if (job_id.empty()) { + nlohmann::json tmp(job.at("job_id")); + serverHelper->constructGetJobPath(tmp[0]); + job_id = tmp[0].at("job_id"); + } + ids.emplace_back(job_id, kernelName); + config["output_names." + job_id] = kernelName; + + // nlohmann::json jReorder = codesToExecute[i].mapping_reorder_idx; + // config["reorderIdx." + task_id] = jReorder.dump(); + + // i++; + // } + + config.insert({"shots", std::to_string(shots)}); + std::string name = serverHelper->name(); + return cudaq::orca::details::Orcafuture(ids, name, config); +} +} // namespace cudaq \ No newline at end of file diff --git a/runtime/common/Executor.h b/runtime/common/Executor.h index 667dda6f32..8c720a018e 100644 --- a/runtime/common/Executor.h +++ b/runtime/common/Executor.h @@ -10,6 +10,7 @@ #include "common/ExecutionContext.h" #include "common/RestClient.h" #include "common/ServerHelper.h" +#include "cudaq/platform/orca/OrcaFuture.h" namespace cudaq { @@ -42,6 +43,12 @@ class Executor : public registry::RegisteredType { /// @brief Execute the provided quantum codes and return a future object /// The caller can make this synchronous by just immediately calling .get(). details::future execute(std::vector &codesToExecute); + + /// @brief Execute the provided orca quantum parameters and return a future + /// object The caller can make this synchronous by just immediately calling + /// .get(). + cudaq::orca::details::Orcafuture execute(cudaq::orca::TBIParameters params, + const std::string &kernelName); }; } // namespace cudaq diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index 2e23c316b2..15b2e2821f 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -14,6 +14,7 @@ #include "Future.h" #include "MeasureCounts.h" #include "Registry.h" +#include "cudaq/orca.h" #include namespace cudaq { @@ -101,7 +102,30 @@ class ServerHelper : public registry::RegisteredType { /// @brief Given a vector of compiled quantum codes for submission /// create and return the Job payload that is compatible with this server. virtual ServerJobPayload - createJob(std::vector &circuitCodes) = 0; + createJob(std::vector &circuitCodes) { + std::vector jobs; + ServerMessage job; + jobs.push_back(job); + + std::map headers; + + // Return a tuple containing the job path, headers, and the job message + auto ret = std::make_tuple("", headers, jobs); + return ret; + }; + + /// @brief Create a job payload for the provided TBI parameters + virtual ServerJobPayload createJob(cudaq::orca::TBIParameters params) { + std::vector jobs; + ServerMessage job; + jobs.push_back(job); + + std::map headers; + + // Return a tuple containing the job path, headers, and the job message + auto ret = std::make_tuple("", headers, jobs); + return ret; + }; /// @brief Extract the job id from the server response from posting the job. virtual std::string extractJobId(ServerMessage &postResponse) = 0; diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index c3d593ab10..6220dc3658 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -9,7 +9,7 @@ set(LIBRARY_NAME cudaq-orca-qpu) message(STATUS "Building ORCA REST QPU.") set(ORCA_SRC - OrcaExecutor.cpp + OrcaFuture.cpp OrcaServerHelper.cpp OrcaRemoteRESTQPU.cpp OrcaQPU.cpp diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.cpp b/runtime/cudaq/platform/orca/OrcaExecutor.cpp deleted file mode 100644 index aae2854b2a..0000000000 --- a/runtime/cudaq/platform/orca/OrcaExecutor.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#include "OrcaExecutor.h" -#include "common/Logger.h" - -namespace cudaq::orca { -cudaq::details::future OrcaExecutor::execute(TBIParameters params) { - - serverHelper->setShots(shots); - - cudaq::info("Executor creating job to execute with the {} helper.", - serverHelper->name()); - - // Create the Job Payload, composed of job post path, headers, - // and the job json messages themselves - auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); - auto job = jobs[0]; - auto config = serverHelper->getConfig(); - - std::vector ids; - // for (std::size_t i = 0; auto &job : jobs) { - cudaq::info("Job created, posting to {}", jobPostPath); - - // Post it, get the response - auto response = client.post(jobPostPath, "", job, headers); - cudaq::info("Job posted, response was {}", response.dump()); - - // // Add the job id and the job name. - // auto task_id = serverHelper->extractJobId(response); - // if (task_id.empty()) { - // nlohmann::json tmp(job.at("tasks")); - // serverHelper->constructGetJobPath(tmp[0]); - // task_id = tmp[0].at("task_id"); - // } - // cudaq::info("Task ID is {}", task_id); - // ids.emplace_back(task_id, codesToExecute[i].name); - // config["output_names." + task_id] = codesToExecute[i].output_names.dump(); - - // nlohmann::json jReorder = codesToExecute[i].mapping_reorder_idx; - // config["reorderIdx." + task_id] = jReorder.dump(); - - // i++; - // } - - // config.insert({"shots", std::to_string(shots)}); - std::string name = serverHelper->name(); - return cudaq::details::future(ids, name, config); -} -} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.h b/runtime/cudaq/platform/orca/OrcaExecutor.h deleted file mode 100644 index e24451c762..0000000000 --- a/runtime/cudaq/platform/orca/OrcaExecutor.h +++ /dev/null @@ -1,49 +0,0 @@ -/****************************************************************-*- C++ -*-**** - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#pragma once -#include "OrcaServerHelper.h" -#include "common/ExecutionContext.h" -#include "common/RestClient.h" -// #include "cudaq.h" -#include "orca_qpu.h" - -namespace cudaq::orca { - -/// @brief The Executor provides an abstraction for executing compiled -/// quantum codes targeting a remote REST server. This type provides a -/// clean abstraction launching a vector of Jobs for sampling and observation -/// tasks, both synchronously and asynchronously. -class OrcaExecutor : public registry::RegisteredType { -protected: - /// @brief The REST Client used to interact with the remote system - RestClient client; - - /// @brief The ServerHelper, providing system-specific JSON-formatted - /// job posts and results translation - OrcaServerHelper *serverHelper; - - /// @brief The number of shots to execute - std::size_t shots = 100; - -public: - OrcaExecutor() = default; - virtual ~OrcaExecutor() = default; - - /// @brief Set the server helper - void setServerHelper(OrcaServerHelper *helper) { serverHelper = helper; } - - /// @brief Set the number of shots to execute - void setShots(std::size_t s) { shots = s; } - - /// @brief Execute the provided quantum codes and return a future object - /// The caller can make this synchronous by just immediately calling .get(). - cudaq::details::future execute(TBIParameters params); -}; - -} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaFuture.cpp b/runtime/cudaq/platform/orca/OrcaFuture.cpp new file mode 100644 index 0000000000..03466f5808 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaFuture.cpp @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "OrcaFuture.h" +#include "common/Logger.h" +#include "common/RestClient.h" +#include "common/ServerHelper.h" +#include + +namespace cudaq::orca::details { + +cudaq::sample_result Orcafuture::get() { + if (wrapsFutureSampling) + return inFuture.get(); + + RestClient client; + auto serverHelper = registry::get(qpuName); + serverHelper->initialize(serverConfig); + auto headers = serverHelper->getHeaders(); + + std::vector results; + for (auto &id : jobs) { + cudaq::info("Future retrieving results for {}.", id.first); + + auto jobGetPath = serverHelper->constructGetJobPath(id.first); + + cudaq::info("Future got job retrieval path as {}.", jobGetPath); + auto resultResponse = client.get(jobGetPath, "", headers); + while (!serverHelper->jobIsDone(resultResponse)) { + auto polling_interval = + serverHelper->nextResultPollingInterval(resultResponse); + std::this_thread::sleep_for(polling_interval); + resultResponse = client.get(jobGetPath, "", headers); + } + + auto c = serverHelper->processResults(resultResponse, id.first); + + // If there are multiple jobs, this is likely a spin_op. + // If so, use the job name instead of the global register. + if (jobs.size() > 1) { + results.emplace_back(c.to_map(), id.second); + results.back().sequentialData = c.sequential_data(); + } else { + // For each register, add the results into result. + for (auto ®Name : c.register_names()) { + results.emplace_back(c.to_map(regName), regName); + results.back().sequentialData = c.sequential_data(regName); + } + } + } + + return cudaq::sample_result(results); +} + +Orcafuture &Orcafuture::operator=(Orcafuture &other) { + jobs = other.jobs; + qpuName = other.qpuName; + serverConfig = other.serverConfig; + if (other.wrapsFutureSampling) { + wrapsFutureSampling = true; + inFuture = std::move(other.inFuture); + } + return *this; +} + +Orcafuture &Orcafuture::operator=(Orcafuture &&other) { + jobs = other.jobs; + qpuName = other.qpuName; + serverConfig = other.serverConfig; + if (other.wrapsFutureSampling) { + wrapsFutureSampling = true; + inFuture = std::move(other.inFuture); + } + return *this; +} + +std::ostream &operator<<(std::ostream &os, Orcafuture &f) { + if (f.wrapsFutureSampling) + throw std::runtime_error( + "Cannot persist a Orcafuture for a local kernel execution."); + + nlohmann::json j; + j["jobs"] = f.jobs; + j["qpu"] = f.qpuName; + j["config"] = f.serverConfig; + os << j.dump(4); + return os; +} + +std::istream &operator>>(std::istream &is, Orcafuture &f) { + nlohmann::json j; + try { + is >> j; + } catch (...) { + throw std::runtime_error( + "Formatting error; could not parse input as json."); + } + f.jobs = j["jobs"].get>(); + f.qpuName = j["qpu"].get(); + f.serverConfig = j["config"].get>(); + return is; +} + +} // namespace cudaq::orca::details diff --git a/runtime/cudaq/platform/orca/OrcaFuture.h b/runtime/cudaq/platform/orca/OrcaFuture.h new file mode 100644 index 0000000000..34af185efe --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaFuture.h @@ -0,0 +1,155 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once +#include "common/MeasureCounts.h" +#include "common/ObserveResult.h" + +#include +#include +#include + +namespace cudaq::orca { +namespace details { +/// @brief The Orcafuture type models the expected result of a +/// CUDA-Q kernel execution under a specific execution context. +/// This type is returned from asynchronous execution calls. It +/// encapsulates the job-specific circuit execution identifiers, the +/// name of the QPU the job executed on, and any extra configuration +/// information needed to retrieve the results later from the server. +/// This type can be persisted to file and read in later to retrieve +/// execution results. +/// It also optionally wraps a std::future type, and in this case, +/// persistence to file is not allowed, .get() must be invoked at some +/// later point within the same runtime context. +class Orcafuture { +public: + using Job = std::pair; + +protected: + /// @brief Vector of job ids that make up the execution + /// that this Orcafuture corresponds to. + std::vector jobs; + + // std::vector jobNames; + + /// @brief The name of the QPU this execution is targeting + std::string qpuName; + + /// @brief Any pertinent server configuration this Orcafuture + /// will require to retrieve results at a later time. + std::map serverConfig; + + /// @brief + std::future inFuture; + bool wrapsFutureSampling = false; + +public: + /// @brief The constructor + Orcafuture() = default; + + /// @brief move constructor + Orcafuture(Orcafuture &&) = default; + Orcafuture(std::future &f) : inFuture(std::move(f)) { + wrapsFutureSampling = true; + } + + Orcafuture(std::future &&f) : inFuture(std::move(f)) { + wrapsFutureSampling = true; + } + + /// @brief The constructor, takes all info required to + /// be able to retrieve results at a later date, even after file persistence. + Orcafuture(std::vector &_jobs, std::string &qpuNameIn, + std::map &config) + : jobs(_jobs), qpuName(qpuNameIn), serverConfig(config) {} + + Orcafuture &operator=(Orcafuture &other); + Orcafuture &operator=(Orcafuture &&other); + + cudaq::sample_result get(); + + friend std::ostream &operator<<(std::ostream &, Orcafuture &); + friend std::istream &operator>>(std::istream &, Orcafuture &); +}; + +std::ostream &operator<<(std::ostream &os, Orcafuture &f); +std::istream &operator>>(std::istream &os, Orcafuture &f); +} // namespace details + +/// @brief the orca_async_result type is a user facing, future-like +/// type that is returned from CUDA-Q public asynchronous +/// API functions. It wraps a details::Orcafuture type, which can +/// itself be constructed from a std::future or a collection of +/// data pertinent to remote QPU REST invocation. +template +class orca_async_result { +protected: + /// @brief The Orcafuture holding data required to get the results later. + details::Orcafuture result; + + /// @brief A spin operator, used for observe Orcafuture tasks + spin_op *spinOp = nullptr; + +public: + orca_async_result() = default; + orca_async_result(spin_op *s) : spinOp(s) {} + orca_async_result(details::Orcafuture &&f, spin_op *op = nullptr) + : result(std::move(f)), spinOp(op) {} + + /// @brief Return the asynchronously computed data, will + /// wait until the data is ready. + T get() { + auto data = result.get(); + + if constexpr (std::is_same_v) + return data; + + if constexpr (std::is_same_v) { + auto checkRegName = spinOp->to_string(false); + if (data.has_expectation(checkRegName)) + return observe_result(data.expectation(checkRegName), *spinOp, data); + + if (!spinOp) + throw std::runtime_error( + "Returning an observe_result requires a spin_op."); + + // this assumes we ran in shots mode. + double sum = 0.0; + spinOp->for_each_term([&](spin_op &term) { + if (term.is_identity()) + sum += term.get_coefficient().real(); + else + sum += data.expectation(term.to_string(false)) * + term.get_coefficient().real(); + }); + + return observe_result(sum, *spinOp, data); + } + + return T(); + } + + template + friend std::ostream &operator<<(std::ostream &, orca_async_result &); + + template + friend std::istream &operator>>(std::istream &, orca_async_result &); +}; + +template +std::ostream &operator<<(std::ostream &os, orca_async_result &ar) { + return os << ar.result; +} + +template +std::istream &operator>>(std::istream &is, orca_async_result &ar) { + return is >> ar.result; +} + +} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaQPU.cpp b/runtime/cudaq/platform/orca/OrcaQPU.cpp index d1fc08d74d..4da23df05c 100644 --- a/runtime/cudaq/platform/orca/OrcaQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaQPU.cpp @@ -6,29 +6,10 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ - +#include "OrcaFuture.h" #include "common/ExecutionContext.h" -#include "common/FmtCore.h" - -#include "common/Logger.h" -#include "cudaq.h" -#include "nvqpp_config.h" - -#include "OrcaExecutor.h" -#include "OrcaRemoteRESTQPU.h" -#include "cudaq/platform/qpu.h" -#include "cudaq/platform/quantum_platform.h" -#include "cudaq/qis/qubit_qis.h" -#include "cudaq/spin_op.h" #include "orca_qpu.h" -#include -#include -#include -#include -#include -#include - namespace cudaq::orca { cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, @@ -36,26 +17,68 @@ cudaq::sample_result sample(std::vector &input_state, std::vector &ps_angles, int n_samples) { TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - cudaq::ExecutionContext context("sample", n_samples); + int qpu_id = 0; + auto ctx = std::make_unique("sample", n_samples); auto &platform = get_platform(); - platform.set_exec_ctx(&context, 0); + platform.set_exec_ctx(ctx.get(), qpu_id); + platform.set_current_qpu(qpu_id); + cudaq::altLaunchKernel("orca_launch", nullptr, ¶meters, sizeof(TBIParameters), 0); - return context.result; + platform.reset_exec_ctx(qpu_id); + return ctx->result; } + cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples) { std::vector ps_angles = {}; TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - cudaq::ExecutionContext context("sample", n_samples); + int qpu_id = 0; + auto ctx = std::make_unique("sample", n_samples); auto &platform = get_platform(); - platform.set_exec_ctx(&context, 0); + platform.set_exec_ctx(ctx.get(), qpu_id); + platform.set_current_qpu(qpu_id); + cudaq::altLaunchKernel("orca_launch", nullptr, ¶meters, sizeof(TBIParameters), 0); - return context.result; + platform.reset_exec_ctx(qpu_id); + return ctx->result; } + +// async_sample_result sample_async(std::vector &input_state, +// std::vector &loop_lengths, +// std::vector &bs_angles, +// int n_samples) { +// std::vector ps_angles = {}; +// TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, +// n_samples}; +// details::Orcafuture *futureResult = nullptr; + +// int qpu_id = 0; +// auto ctx = std::make_unique("sample", n_samples); +// // Indicate that this is an async exec +// ctx->asyncExec = futureResult != nullptr; + +// auto &platform = get_platform(); +// platform.set_exec_ctx(ctx.get(), qpu_id); +// platform.set_current_qpu(qpu_id); + +// cudaq::altLaunchKernel("orca_launch", nullptr, ¶meters, +// sizeof(TBIParameters), 0); + +// // // If we have a non-null future, set it and return +// // if (futureResult) { +// // *futureResult = ctx->futureResult; +// // return std::nullopt; +// // } + +// // platform.reset_exec_ctx(qpu_id); +// // return async_sample_result(std::move(futureResult)); +// return ctx->result; +// } + } // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp index 049f2f9f66..134c67f10b 100644 --- a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp @@ -5,13 +5,13 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ + #include "OrcaRemoteRESTQPU.h" -#include "common/Future.h" +#include "OrcaFuture.h" #include "common/Logger.h" -#include "common/Registry.h" #include "llvm/Support/Base64.h" -namespace cudaq{ +namespace cudaq { /// @brief This setTargetBackend override is in charge of reading the /// specific target backend configuration file. void OrcaRemoteRESTQPU::setTargetBackend(const std::string &backend) { @@ -51,7 +51,7 @@ void OrcaRemoteRESTQPU::setTargetBackend(const std::string &backend) { /// pipeline. // Set the qpu name qpuName = mutableBackend; - serverHelper = registry::get(qpuName); + serverHelper = registry::get(qpuName); serverHelper->initialize(backendConfig); // Give the server helper to the executor @@ -74,39 +74,21 @@ void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, *((struct cudaq::orca::TBIParameters *)args); std::size_t shots = params.n_samples; - setShots(shots); executionContext->shots = shots; - cudaq::details::future future; - future = executor->execute(params); + cudaq::orca::details::Orcafuture future; + future = executor->execute(params, kernelName); // Keep this asynchronous if requested if (executionContext->asyncExec) { - executionContext->futureResult = future; + executionContext->orcaFutureResult = future; return; } // Otherwise make this synchronous executionContext->result = future.get(); - - // // Create the Job Payload, composed of job post path, headers, - // // and the job json messages themselves - // auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); - // auto job = jobs[0]; - // cudaq::info("Job (name={}) created, posting to {}", kernelName, - // jobPostPath); - - // // Post it, get the response - // auto response = client.post(jobPostPath, "", job, headers); - // cudaq::info("Job (name={}) posted, response was {}", kernelName, - // response.dump()); - - // cudaq::sample_result counts = serverHelper->processResults(response); - - // // return the results synchronously - // executionContext->result = counts; } -} // namespace - +} // namespace cudaq +// LLVM_INSTANTIATE_REGISTRY(cudaq::orca::OrcaRemoteRESTQPU::RegistryType) CUDAQ_REGISTER_TYPE(cudaq::QPU, cudaq::OrcaRemoteRESTQPU, orca) \ No newline at end of file diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h index f69e16a4f4..8f652186be 100644 --- a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h @@ -8,20 +8,15 @@ #pragma once #include "common/ExecutionContext.h" -#include "common/FmtCore.h" - -#include "OrcaExecutor.h" -#include "OrcaServerHelper.h" - +#include "common/Executor.h" +#include "common/Future.h" #include "common/RestClient.h" -#include "cudaq.h" -#include "nvqpp_config.h" -#include "orca_qpu.h" - +#include "common/ServerHelper.h" #include "cudaq/platform/qpu.h" -#include "cudaq/platform/quantum_platform.h" -namespace cudaq{ +#include "orca_qpu.h" + +namespace cudaq { /// @brief The OrcaRemoteRESTQPU is a subtype of QPU that enables the /// execution of CUDA-Q kernels on remotely hosted quantum computing @@ -45,11 +40,11 @@ class OrcaRemoteRESTQPU : public cudaq::QPU { bool emulate = false; // Pointer to the concrete Executor for this QPU - std::unique_ptr executor; + std::unique_ptr executor; /// @brief Pointer to the concrete ServerHelper, provides /// specific JSON payloads and POST/GET URL paths. - std::unique_ptr serverHelper; + std::unique_ptr serverHelper; /// @brief Mapping of general key-values for backend /// configuration. @@ -57,15 +52,15 @@ class OrcaRemoteRESTQPU : public cudaq::QPU { private: /// @brief RestClient used for HTTP requests. - cudaq::RestClient client; + RestClient client; public: /// @brief The constructor OrcaRemoteRESTQPU() : QPU() { - std::filesystem::path cudaqLibPath{cudaq::getCUDAQLibraryPath()}; + std::filesystem::path cudaqLibPath{getCUDAQLibraryPath()}; platformPath = cudaqLibPath.parent_path().parent_path() / "targets"; // Default is to run sampling via the remote rest call - executor = std::make_unique(); + executor = std::make_unique(); } OrcaRemoteRESTQPU(OrcaRemoteRESTQPU &&) = delete; @@ -125,4 +120,4 @@ class OrcaRemoteRESTQPU : public cudaq::QPU { throw std::runtime_error("launch kernel on raw args not implemented"); } }; -} // namespace +} // namespace cudaq diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index ea22ca4a48..a319b8374c 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -53,13 +53,9 @@ OrcaServerHelper::createJob(cudaq::orca::TBIParameters params) { return ret; } -// ServerJobPayload -// OrcaServerHelper::createJob(std::vector &circuitCodes) { -// throw std::runtime_error("createJob on circuitCodes args not implemented"); -// } - // Process the results from a job -sample_result OrcaServerHelper::processResults(ServerMessage &postJobResponse) { +sample_result OrcaServerHelper::processResults(ServerMessage &postJobResponse, + std::string &jobID) { auto results = postJobResponse.at("results"); CountsDictionary counts; @@ -110,18 +106,21 @@ std::string OrcaServerHelper::constructGetJobPath(std::string &jobId) { } bool OrcaServerHelper::jobIsDone(ServerMessage &getJobResponse) { - auto status = getJobResponse["status"].get(); - if (status == "failed") { - std::string msg = ""; - if (getJobResponse.count("error")) - msg = getJobResponse["error"]["text"].get(); - throw std::runtime_error("Job failed to execute msg = [" + msg + "]"); + cudaq::info("getJobResponse {}.", getJobResponse.dump()); + std::string job_status = ""; + auto error = getJobResponse["error_message"].is_null(); + auto status = getJobResponse["job_status"].is_null(); + // if (!status){ + // job_status = getJobResponse["job_status"].get();} + cudaq::info("status {}, error {}", status, error); + if (error & status) { + return true; + } else { + return false; } - - return status == "completed"; } } // namespace cudaq -LLVM_INSTANTIATE_REGISTRY(cudaq::OrcaServerHelper::RegistryType) -CUDAQ_REGISTER_TYPE(cudaq::OrcaServerHelper, cudaq::OrcaServerHelper, orca) +// LLVM_INSTANTIATE_REGISTRY(cudaq::orca::OrcaServerHelper::RegistryType) +CUDAQ_REGISTER_TYPE(cudaq::ServerHelper, cudaq::OrcaServerHelper, orca) diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.h b/runtime/cudaq/platform/orca/OrcaServerHelper.h index a960e28f25..522bb1ef14 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.h +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.h @@ -7,50 +7,18 @@ ******************************************************************************/ #pragma once -#include "common/Future.h" #include "common/Registry.h" -#include "nlohmann/json.hpp" -// #include "common/ServerHelper.h" +#include "common/ServerHelper.h" +#include "cudaq/utils/cudaq_utils.h" #include "orca_qpu.h" -#include "llvm/Support/Base64.h" - -namespace cudaq { -using BackendConfig = std::map; - -/// @brief Responses / Submissions to the Server are modeled via JSON -using ServerMessage = nlohmann::json; -/// @brief Each REST interaction will require headers -using RestHeaders = std::map; - -// A Server Job Payload consists of a job post URL path, the headers, -// and a vector of related Job JSON messages. -using ServerJobPayload = - std::tuple>; +#include "nlohmann/json.hpp" -// /// @brief Information about a result coming from a backend -// struct ResultInfoType { -// std::size_t qubitNum; -// std::string registerName; -// }; +namespace cudaq { -// /// @brief Results information, indexed by 0-based result number -// using OutputNamesType = std::map; +class OrcaServerHelper : public ServerHelper { -class OrcaServerHelper -// : public ServerHelper { - : public registry::RegisteredType { protected: - /// @brief All ServerHelpers can be configured at the `nvq++` command line. - /// This map holds those configuration key-values - BackendConfig backendConfig; - - /// @brief The number of shots to execute - std::size_t shots = 100; - - // /// @brief Output names indexed by jobID/taskID - // std::map outputNames; - /// @brief The base URL std::string baseUrl = "http://localhost:8080/"; @@ -76,44 +44,38 @@ class OrcaServerHelper /// @brief Return the name of this server helper, must be the /// same as the qpu config file. - const std::string name() const { return "orca"; } + const std::string name() const override { return "orca"; } /// @brief Return the POST/GET required headers. /// @return - RestHeaders getHeaders(); - - /// @brief Return the current server configuration - /// @return - BackendConfig getConfig() { return backendConfig; } - - /// @brief Set the number of shots to execute - void setShots(std::size_t s) { shots = s; } + RestHeaders getHeaders() override; /// @brief Set the server configuration. - void initialize(BackendConfig config); + void initialize(BackendConfig config) override; /// @brief Create a job payload for the provided TBI parameters - ServerJobPayload createJob(cudaq::orca::TBIParameters params); + ServerJobPayload createJob(cudaq::orca::TBIParameters params) override; - // /// @brief Create a job payload for the provided quantum codes - // ServerJobPayload createJob(std::vector &circuitCodes); + // // /// @brief Create a job payload for the provided quantum codes + // ServerJobPayload + // createJob(std::vector &circuitCodes) override; /// @brief Return the job id from the previous job post - std::string extractJobId(ServerMessage &postResponse); + std::string extractJobId(ServerMessage &postResponse) override; /// @brief Return the URL for retrieving job results - std::string constructGetJobPath(ServerMessage &postResponse); - std::string constructGetJobPath(std::string &jobId); + std::string constructGetJobPath(ServerMessage &postResponse) override; + std::string constructGetJobPath(std::string &jobId) override; /// @brief Return true if the job is done - bool jobIsDone(ServerMessage &getJobResponse); + bool jobIsDone(ServerMessage &getJobResponse) override; - /// @brief Given a completed job response, map back to the sample_result - sample_result processResults(ServerMessage &postJobResponse); + // /// @brief Given a completed job response, map back to the sample_result + // sample_result processResults(ServerMessage &postJobResponse); - /// @brief Given a completed job response, map back to the sample_result + /// @brief Given a completed job response, map back to the sample_result sample_result processResults(ServerMessage &postJobResponse, - std::string &jobID ); + std::string &jobID) override; }; } // namespace cudaq diff --git a/runtime/cudaq/platform/orca/orca.yml b/runtime/cudaq/platform/orca/orca.yml index 6367600bd3..d55951f643 100644 --- a/runtime/cudaq/platform/orca/orca.yml +++ b/runtime/cudaq/platform/orca/orca.yml @@ -22,7 +22,7 @@ target-arguments: - key: url required: false type: string - platform-arg: url + platform-arg: url help-string: "Specify URL." - key: machine required: false diff --git a/runtime/cudaq/platform/orca/orca_qpu.h b/runtime/cudaq/platform/orca/orca_qpu.h index fe95f6aeb0..944fe1e321 100644 --- a/runtime/cudaq/platform/orca/orca_qpu.h +++ b/runtime/cudaq/platform/orca/orca_qpu.h @@ -7,9 +7,11 @@ ******************************************************************************/ #pragma once - +#include "OrcaFuture.h" +// #include "OrcaSample.h" #include "cudaq.h" #include "cudaq/platform/quantum_platform.h" + #include #include @@ -25,14 +27,24 @@ struct TBIParameters { int n_samples; }; +/// @brief Return type for asynchronous sampling. +using async_sample_result = orca_async_result; + /// @brief Implementation of the sample method of the cudaq::orca namespace cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, int n_samples = 10000); + cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples = 10000); + +// async_sample_result sample_async(std::vector &input_state, +// std::vector &loop_lengths, +// std::vector &bs_angles, +// int n_samples = 10000); + }; // namespace cudaq::orca \ No newline at end of file From 895cba1883e3c37fc1055f26f1ebd85b2c1bfd8c Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Wed, 28 Aug 2024 11:04:04 +0100 Subject: [PATCH 03/19] Refactor --- runtime/common/ExecutionContext.h | 5 - runtime/common/Executor.cpp | 16 +- runtime/common/Executor.h | 5 +- runtime/common/ServerHelper.h | 12 +- runtime/cudaq/platform/orca/CMakeLists.txt | 1 - runtime/cudaq/platform/orca/OrcaFuture.cpp | 109 ------------ runtime/cudaq/platform/orca/OrcaFuture.h | 155 ------------------ runtime/cudaq/platform/orca/OrcaQPU.cpp | 108 ++++++------ .../cudaq/platform/orca/OrcaRemoteRESTQPU.cpp | 6 +- .../cudaq/platform/orca/OrcaServerHelper.h | 14 +- runtime/cudaq/platform/orca/orca_qpu.h | 24 ++- 11 files changed, 95 insertions(+), 360 deletions(-) delete mode 100644 runtime/cudaq/platform/orca/OrcaFuture.cpp delete mode 100644 runtime/cudaq/platform/orca/OrcaFuture.h diff --git a/runtime/common/ExecutionContext.h b/runtime/common/ExecutionContext.h index 0f41409898..70c0827e49 100644 --- a/runtime/common/ExecutionContext.h +++ b/runtime/common/ExecutionContext.h @@ -14,7 +14,6 @@ #include "SimulationState.h" #include "Trace.h" #include "cudaq/algorithms/optimizer.h" -#include "cudaq/platform/orca/OrcaFuture.h" #include #include @@ -63,10 +62,6 @@ class ExecutionContext { /// the expected results as a cudaq::future here. details::future futureResult; - /// @brief When execution asynchronously, store - /// the expected results as a cudaq::orca::details::Orcafuture here. - orca::details::Orcafuture orcaFutureResult; - /// @brief Pointer to simulation-specific simulation data. std::unique_ptr simulationState; diff --git a/runtime/common/Executor.cpp b/runtime/common/Executor.cpp index 60c0abe7e4..07ee31062a 100644 --- a/runtime/common/Executor.cpp +++ b/runtime/common/Executor.cpp @@ -56,9 +56,8 @@ Executor::execute(std::vector &codesToExecute) { return details::future(ids, name, config); } -cudaq::orca::details::Orcafuture -Executor::execute(cudaq::orca::TBIParameters params, - const std::string &kernelName) { +details::future Executor::execute(cudaq::orca::TBIParameters params, + const std::string &kernelName) { serverHelper->setShots(shots); @@ -71,8 +70,7 @@ Executor::execute(cudaq::orca::TBIParameters params, auto job = jobs[0]; auto config = serverHelper->getConfig(); - std::vector ids; - // for (std::size_t i = 0; auto &job : jobs) { + std::vector ids; cudaq::info("Job created, posting to {}", jobPostPath); // Post it, get the response @@ -89,14 +87,8 @@ Executor::execute(cudaq::orca::TBIParameters params, ids.emplace_back(job_id, kernelName); config["output_names." + job_id] = kernelName; - // nlohmann::json jReorder = codesToExecute[i].mapping_reorder_idx; - // config["reorderIdx." + task_id] = jReorder.dump(); - - // i++; - // } - config.insert({"shots", std::to_string(shots)}); std::string name = serverHelper->name(); - return cudaq::orca::details::Orcafuture(ids, name, config); + return cudaq::details::future(ids, name, config); } } // namespace cudaq \ No newline at end of file diff --git a/runtime/common/Executor.h b/runtime/common/Executor.h index 8c720a018e..809ab77cc1 100644 --- a/runtime/common/Executor.h +++ b/runtime/common/Executor.h @@ -10,7 +10,6 @@ #include "common/ExecutionContext.h" #include "common/RestClient.h" #include "common/ServerHelper.h" -#include "cudaq/platform/orca/OrcaFuture.h" namespace cudaq { @@ -47,8 +46,8 @@ class Executor : public registry::RegisteredType { /// @brief Execute the provided orca quantum parameters and return a future /// object The caller can make this synchronous by just immediately calling /// .get(). - cudaq::orca::details::Orcafuture execute(cudaq::orca::TBIParameters params, - const std::string &kernelName); + details::future execute(cudaq::orca::TBIParameters params, + const std::string &kernelName); }; } // namespace cudaq diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index 15b2e2821f..e0f2286077 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -102,17 +102,7 @@ class ServerHelper : public registry::RegisteredType { /// @brief Given a vector of compiled quantum codes for submission /// create and return the Job payload that is compatible with this server. virtual ServerJobPayload - createJob(std::vector &circuitCodes) { - std::vector jobs; - ServerMessage job; - jobs.push_back(job); - - std::map headers; - - // Return a tuple containing the job path, headers, and the job message - auto ret = std::make_tuple("", headers, jobs); - return ret; - }; + createJob(std::vector &circuitCodes) = 0; /// @brief Create a job payload for the provided TBI parameters virtual ServerJobPayload createJob(cudaq::orca::TBIParameters params) { diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index 6220dc3658..995934d8b2 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -9,7 +9,6 @@ set(LIBRARY_NAME cudaq-orca-qpu) message(STATUS "Building ORCA REST QPU.") set(ORCA_SRC - OrcaFuture.cpp OrcaServerHelper.cpp OrcaRemoteRESTQPU.cpp OrcaQPU.cpp diff --git a/runtime/cudaq/platform/orca/OrcaFuture.cpp b/runtime/cudaq/platform/orca/OrcaFuture.cpp deleted file mode 100644 index 03466f5808..0000000000 --- a/runtime/cudaq/platform/orca/OrcaFuture.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#include "OrcaFuture.h" -#include "common/Logger.h" -#include "common/RestClient.h" -#include "common/ServerHelper.h" -#include - -namespace cudaq::orca::details { - -cudaq::sample_result Orcafuture::get() { - if (wrapsFutureSampling) - return inFuture.get(); - - RestClient client; - auto serverHelper = registry::get(qpuName); - serverHelper->initialize(serverConfig); - auto headers = serverHelper->getHeaders(); - - std::vector results; - for (auto &id : jobs) { - cudaq::info("Future retrieving results for {}.", id.first); - - auto jobGetPath = serverHelper->constructGetJobPath(id.first); - - cudaq::info("Future got job retrieval path as {}.", jobGetPath); - auto resultResponse = client.get(jobGetPath, "", headers); - while (!serverHelper->jobIsDone(resultResponse)) { - auto polling_interval = - serverHelper->nextResultPollingInterval(resultResponse); - std::this_thread::sleep_for(polling_interval); - resultResponse = client.get(jobGetPath, "", headers); - } - - auto c = serverHelper->processResults(resultResponse, id.first); - - // If there are multiple jobs, this is likely a spin_op. - // If so, use the job name instead of the global register. - if (jobs.size() > 1) { - results.emplace_back(c.to_map(), id.second); - results.back().sequentialData = c.sequential_data(); - } else { - // For each register, add the results into result. - for (auto ®Name : c.register_names()) { - results.emplace_back(c.to_map(regName), regName); - results.back().sequentialData = c.sequential_data(regName); - } - } - } - - return cudaq::sample_result(results); -} - -Orcafuture &Orcafuture::operator=(Orcafuture &other) { - jobs = other.jobs; - qpuName = other.qpuName; - serverConfig = other.serverConfig; - if (other.wrapsFutureSampling) { - wrapsFutureSampling = true; - inFuture = std::move(other.inFuture); - } - return *this; -} - -Orcafuture &Orcafuture::operator=(Orcafuture &&other) { - jobs = other.jobs; - qpuName = other.qpuName; - serverConfig = other.serverConfig; - if (other.wrapsFutureSampling) { - wrapsFutureSampling = true; - inFuture = std::move(other.inFuture); - } - return *this; -} - -std::ostream &operator<<(std::ostream &os, Orcafuture &f) { - if (f.wrapsFutureSampling) - throw std::runtime_error( - "Cannot persist a Orcafuture for a local kernel execution."); - - nlohmann::json j; - j["jobs"] = f.jobs; - j["qpu"] = f.qpuName; - j["config"] = f.serverConfig; - os << j.dump(4); - return os; -} - -std::istream &operator>>(std::istream &is, Orcafuture &f) { - nlohmann::json j; - try { - is >> j; - } catch (...) { - throw std::runtime_error( - "Formatting error; could not parse input as json."); - } - f.jobs = j["jobs"].get>(); - f.qpuName = j["qpu"].get(); - f.serverConfig = j["config"].get>(); - return is; -} - -} // namespace cudaq::orca::details diff --git a/runtime/cudaq/platform/orca/OrcaFuture.h b/runtime/cudaq/platform/orca/OrcaFuture.h deleted file mode 100644 index 34af185efe..0000000000 --- a/runtime/cudaq/platform/orca/OrcaFuture.h +++ /dev/null @@ -1,155 +0,0 @@ -/****************************************************************-*- C++ -*-**** - * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * - * All rights reserved. * - * * - * This source code and the accompanying materials are made available under * - * the terms of the Apache License 2.0 which accompanies this distribution. * - ******************************************************************************/ - -#pragma once -#include "common/MeasureCounts.h" -#include "common/ObserveResult.h" - -#include -#include -#include - -namespace cudaq::orca { -namespace details { -/// @brief The Orcafuture type models the expected result of a -/// CUDA-Q kernel execution under a specific execution context. -/// This type is returned from asynchronous execution calls. It -/// encapsulates the job-specific circuit execution identifiers, the -/// name of the QPU the job executed on, and any extra configuration -/// information needed to retrieve the results later from the server. -/// This type can be persisted to file and read in later to retrieve -/// execution results. -/// It also optionally wraps a std::future type, and in this case, -/// persistence to file is not allowed, .get() must be invoked at some -/// later point within the same runtime context. -class Orcafuture { -public: - using Job = std::pair; - -protected: - /// @brief Vector of job ids that make up the execution - /// that this Orcafuture corresponds to. - std::vector jobs; - - // std::vector jobNames; - - /// @brief The name of the QPU this execution is targeting - std::string qpuName; - - /// @brief Any pertinent server configuration this Orcafuture - /// will require to retrieve results at a later time. - std::map serverConfig; - - /// @brief - std::future inFuture; - bool wrapsFutureSampling = false; - -public: - /// @brief The constructor - Orcafuture() = default; - - /// @brief move constructor - Orcafuture(Orcafuture &&) = default; - Orcafuture(std::future &f) : inFuture(std::move(f)) { - wrapsFutureSampling = true; - } - - Orcafuture(std::future &&f) : inFuture(std::move(f)) { - wrapsFutureSampling = true; - } - - /// @brief The constructor, takes all info required to - /// be able to retrieve results at a later date, even after file persistence. - Orcafuture(std::vector &_jobs, std::string &qpuNameIn, - std::map &config) - : jobs(_jobs), qpuName(qpuNameIn), serverConfig(config) {} - - Orcafuture &operator=(Orcafuture &other); - Orcafuture &operator=(Orcafuture &&other); - - cudaq::sample_result get(); - - friend std::ostream &operator<<(std::ostream &, Orcafuture &); - friend std::istream &operator>>(std::istream &, Orcafuture &); -}; - -std::ostream &operator<<(std::ostream &os, Orcafuture &f); -std::istream &operator>>(std::istream &os, Orcafuture &f); -} // namespace details - -/// @brief the orca_async_result type is a user facing, future-like -/// type that is returned from CUDA-Q public asynchronous -/// API functions. It wraps a details::Orcafuture type, which can -/// itself be constructed from a std::future or a collection of -/// data pertinent to remote QPU REST invocation. -template -class orca_async_result { -protected: - /// @brief The Orcafuture holding data required to get the results later. - details::Orcafuture result; - - /// @brief A spin operator, used for observe Orcafuture tasks - spin_op *spinOp = nullptr; - -public: - orca_async_result() = default; - orca_async_result(spin_op *s) : spinOp(s) {} - orca_async_result(details::Orcafuture &&f, spin_op *op = nullptr) - : result(std::move(f)), spinOp(op) {} - - /// @brief Return the asynchronously computed data, will - /// wait until the data is ready. - T get() { - auto data = result.get(); - - if constexpr (std::is_same_v) - return data; - - if constexpr (std::is_same_v) { - auto checkRegName = spinOp->to_string(false); - if (data.has_expectation(checkRegName)) - return observe_result(data.expectation(checkRegName), *spinOp, data); - - if (!spinOp) - throw std::runtime_error( - "Returning an observe_result requires a spin_op."); - - // this assumes we ran in shots mode. - double sum = 0.0; - spinOp->for_each_term([&](spin_op &term) { - if (term.is_identity()) - sum += term.get_coefficient().real(); - else - sum += data.expectation(term.to_string(false)) * - term.get_coefficient().real(); - }); - - return observe_result(sum, *spinOp, data); - } - - return T(); - } - - template - friend std::ostream &operator<<(std::ostream &, orca_async_result &); - - template - friend std::istream &operator>>(std::istream &, orca_async_result &); -}; - -template -std::ostream &operator<<(std::ostream &os, orca_async_result &ar) { - return os << ar.result; -} - -template -std::istream &operator>>(std::istream &is, orca_async_result &ar) { - return is >> ar.result; -} - -} // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaQPU.cpp b/runtime/cudaq/platform/orca/OrcaQPU.cpp index 4da23df05c..d881d0de20 100644 --- a/runtime/cudaq/platform/orca/OrcaQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaQPU.cpp @@ -6,20 +6,20 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ -#include "OrcaFuture.h" -#include "common/ExecutionContext.h" + +// #include "common/ExecutionContext.h" +// #include "common/Future.h" +#include "cudaq/platform.h" #include "orca_qpu.h" namespace cudaq::orca { -cudaq::sample_result sample(std::vector &input_state, - std::vector &loop_lengths, - std::vector &bs_angles, - std::vector &ps_angles, int n_samples) { - TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, - n_samples}; + +cudaq::sample_result runSampling(TBIParameters ¶meters) { + std::size_t shots = parameters.n_samples; int qpu_id = 0; - auto ctx = std::make_unique("sample", n_samples); - auto &platform = get_platform(); + auto ctx = std::make_unique("sample", shots); + + auto &platform = cudaq::get_platform(); platform.set_exec_ctx(ctx.get(), qpu_id); platform.set_current_qpu(qpu_id); @@ -30,14 +30,15 @@ cudaq::sample_result sample(std::vector &input_state, return ctx->result; } -cudaq::sample_result sample(std::vector &input_state, - std::vector &loop_lengths, - std::vector &bs_angles, int n_samples) { - std::vector ps_angles = {}; - TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, - n_samples}; +async_sample_result runAsyncSampling(TBIParameters ¶meters) { + std::size_t shots = parameters.n_samples; int qpu_id = 0; - auto ctx = std::make_unique("sample", n_samples); + auto ctx = std::make_unique("sample", shots); + + // Indicate that this is an async exec + cudaq::details::future futureResult; + ctx->asyncExec = true; + auto &platform = get_platform(); platform.set_exec_ctx(ctx.get(), qpu_id); platform.set_current_qpu(qpu_id); @@ -45,40 +46,49 @@ cudaq::sample_result sample(std::vector &input_state, cudaq::altLaunchKernel("orca_launch", nullptr, ¶meters, sizeof(TBIParameters), 0); + // If we have a non-null future, set it + futureResult = ctx->futureResult; + platform.reset_exec_ctx(qpu_id); - return ctx->result; + return async_sample_result(std::move(futureResult)); +} + +cudaq::sample_result sample(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, + std::vector &ps_angles, int n_samples) { + TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, + n_samples}; + return runSampling(parameters); +} + +cudaq::sample_result sample(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, int n_samples) { + std::vector ps_angles = {}; + TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, + n_samples}; + return runSampling(parameters); } -// async_sample_result sample_async(std::vector &input_state, -// std::vector &loop_lengths, -// std::vector &bs_angles, -// int n_samples) { -// std::vector ps_angles = {}; -// TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, -// n_samples}; -// details::Orcafuture *futureResult = nullptr; - -// int qpu_id = 0; -// auto ctx = std::make_unique("sample", n_samples); -// // Indicate that this is an async exec -// ctx->asyncExec = futureResult != nullptr; - -// auto &platform = get_platform(); -// platform.set_exec_ctx(ctx.get(), qpu_id); -// platform.set_current_qpu(qpu_id); - -// cudaq::altLaunchKernel("orca_launch", nullptr, ¶meters, -// sizeof(TBIParameters), 0); - -// // // If we have a non-null future, set it and return -// // if (futureResult) { -// // *futureResult = ctx->futureResult; -// // return std::nullopt; -// // } - -// // platform.reset_exec_ctx(qpu_id); -// // return async_sample_result(std::move(futureResult)); -// return ctx->result; -// } +async_sample_result sample_async(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, + std::vector &ps_angles, + int n_samples) { + TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, + n_samples}; + return runAsyncSampling(parameters); +} + +async_sample_result sample_async(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, + int n_samples) { + std::vector ps_angles = {}; + TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, + n_samples}; + return runAsyncSampling(parameters); +} } // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp index 134c67f10b..cbcd03cb3a 100644 --- a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp @@ -7,7 +7,6 @@ ******************************************************************************/ #include "OrcaRemoteRESTQPU.h" -#include "OrcaFuture.h" #include "common/Logger.h" #include "llvm/Support/Base64.h" @@ -76,12 +75,12 @@ void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, executionContext->shots = shots; - cudaq::orca::details::Orcafuture future; + cudaq::details::future future; future = executor->execute(params, kernelName); // Keep this asynchronous if requested if (executionContext->asyncExec) { - executionContext->orcaFutureResult = future; + executionContext->futureResult = future; return; } @@ -90,5 +89,4 @@ void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, } } // namespace cudaq -// LLVM_INSTANTIATE_REGISTRY(cudaq::orca::OrcaRemoteRESTQPU::RegistryType) CUDAQ_REGISTER_TYPE(cudaq::QPU, cudaq::OrcaRemoteRESTQPU, orca) \ No newline at end of file diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.h b/runtime/cudaq/platform/orca/OrcaServerHelper.h index 522bb1ef14..14ae7fe66b 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.h +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.h @@ -57,8 +57,18 @@ class OrcaServerHelper : public ServerHelper { ServerJobPayload createJob(cudaq::orca::TBIParameters params) override; // // /// @brief Create a job payload for the provided quantum codes - // ServerJobPayload - // createJob(std::vector &circuitCodes) override; + ServerJobPayload + createJob(std::vector &circuitCodes) override { + std::vector jobs; + ServerMessage job; + jobs.push_back(job); + + std::map headers; + + // Return a tuple containing the job path, headers, and the job message + auto ret = std::make_tuple("", headers, jobs); + return ret; + }; /// @brief Return the job id from the previous job post std::string extractJobId(ServerMessage &postResponse) override; diff --git a/runtime/cudaq/platform/orca/orca_qpu.h b/runtime/cudaq/platform/orca/orca_qpu.h index 944fe1e321..6a0bf2c4f6 100644 --- a/runtime/cudaq/platform/orca/orca_qpu.h +++ b/runtime/cudaq/platform/orca/orca_qpu.h @@ -5,11 +5,11 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ - #pragma once -#include "OrcaFuture.h" -// #include "OrcaSample.h" -#include "cudaq.h" + +#include "common/ExecutionContext.h" +#include "common/Future.h" +#include "common/MeasureCounts.h" #include "cudaq/platform/quantum_platform.h" #include @@ -28,7 +28,7 @@ struct TBIParameters { }; /// @brief Return type for asynchronous sampling. -using async_sample_result = orca_async_result; +using async_sample_result = cudaq::async_result; /// @brief Implementation of the sample method of the cudaq::orca namespace cudaq::sample_result sample(std::vector &input_state, @@ -42,9 +42,15 @@ cudaq::sample_result sample(std::vector &input_state, std::vector &bs_angles, int n_samples = 10000); -// async_sample_result sample_async(std::vector &input_state, -// std::vector &loop_lengths, -// std::vector &bs_angles, -// int n_samples = 10000); +async_sample_result sample_async(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, + std::vector &ps_angles, + int n_samples = 10000); + +async_sample_result sample_async(std::vector &input_state, + std::vector &loop_lengths, + std::vector &bs_angles, + int n_samples = 10000); }; // namespace cudaq::orca \ No newline at end of file From e6cf082d751f7230ed0a72eed6aa4262669582f3 Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Wed, 28 Aug 2024 11:55:46 +0100 Subject: [PATCH 04/19] Python bindings --- docs/sphinx/examples/cpp/providers/orca.cpp | 45 ++++++++++--------- docs/sphinx/examples/python/providers/orca.py | 30 +++++++++++++ python/extension/CMakeLists.txt | 2 + python/extension/CUDAQuantumExtension.cpp | 17 +++++++ .../cudaq/platform/orca/OrcaServerHelper.cpp | 1 - 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/docs/sphinx/examples/cpp/providers/orca.cpp b/docs/sphinx/examples/cpp/providers/orca.cpp index ec0a40a94e..577c75f7b1 100644 --- a/docs/sphinx/examples/cpp/providers/orca.cpp +++ b/docs/sphinx/examples/cpp/providers/orca.cpp @@ -1,7 +1,6 @@ // Compile and run with: // ``` -// nvq++ --target orca --orca-url $ORCA_ACCESS_URL orca.cpp -o out.x && -// CUDAQ_LOG_LEVEL=info ./out.x +// nvq++ --target orca --orca-url $ORCA_ACCESS_URL orca.cpp -o out.x && ./out.x // ``` // To use the ORCA Computing target you will need to set the ORCA_ACCESS_URL // environment variable or pass the URL to the `--orca-url` flag. @@ -10,6 +9,7 @@ #include "cudaq.h" #include +#include // define helper function to generate linear spaced vectors template @@ -63,14 +63,15 @@ int main() { // we can also set number of requested samples int n_samples{10000}; - // Submit to ORCA synchronously (e.g., wait for the job result to be returned - // before proceeding with the rest of the execution). + // Submit to ORCA synchronously (e.g., wait for the job result to be + // returned before proceeding with the rest of the execution). + std::cout << "Submitting to ORCA Server synchronously" << std::endl; auto counts = cudaq::orca::sample(input_state, loop_lengths, bs_angles, n_samples); // Print the results counts.dump(); - + // If the system includes phase shifters, the phase shifter angles can be // included in the call @@ -79,26 +80,26 @@ int main() { // ps_angles, n_samples); // ``` + // Alternatively we can submit to ORCA asynchronously (e.g., continue + // executing code in the file until the job has been returned). + std::cout << "Submitting to ORCA Server asynchronously" << std::endl; + auto async_results = cudaq::orca::sample_async(input_state, loop_lengths, + bs_angles, n_samples); - // auto async_results = - // cudaq::orca::sample_async(input_state, loop_lengths, bs_angles, n_samples); - - // // Can write the future to file: - // { - // std::ofstream out("saveMe.json"); - // out << async_results; - // } - - // // Then come back and read it in later. - // cudaq::async_result readIn; - // std::ifstream in("saveMe.json"); - // in >> readIn; - - // // Get the results of the read in future. - // auto async_counts = readIn.get(); - // async_counts.dump(); + // Can write the future to file: + { + std::ofstream out("saveMe.json"); + out << async_results; + } + // Then come back and read it in later. + cudaq::async_result readIn; + std::ifstream in("saveMe.json"); + in >> readIn; + // Get the results of the read in future. + auto async_counts = readIn.get(); + async_counts.dump(); return 0; } \ No newline at end of file diff --git a/docs/sphinx/examples/python/providers/orca.py b/docs/sphinx/examples/python/providers/orca.py index 5f91aad305..9825fe1d16 100644 --- a/docs/sphinx/examples/python/providers/orca.py +++ b/docs/sphinx/examples/python/providers/orca.py @@ -45,9 +45,11 @@ # we can also set number of requested samples n_samples = 10000 +# Option A: # By using the synchronous `cudaq.orca.sample`, the execution of # any remaining classical code in the file will occur only # after the job has been returned from ORCA Server. +print("Submitting to ORCA Server synchronously") counts = cudaq.orca.sample(input_state, loop_lengths, bs_angles, n_samples) # If the system includes phase shifters, the phase shifter angles can be @@ -59,3 +61,31 @@ # Print the results print(counts) + + +# Option B: +# By using the asynchronous `cudaq.orca.sample_async`, the remaining +# classical code will be executed while the job is being handled +# by Orca. This is ideal when submitting via a queue over +# the cloud. +print("Submitting to ORCA Server asynchronously") +async_results = cudaq.orca.sample_async(input_state, loop_lengths, bs_angles, n_samples) +# ... more classical code to run ... + +# We can either retrieve the results later in the program with +# ``` +# async_counts = async_results.get() +# ``` +# or we can also write the job reference (`async_results`) to +# a file and load it later or from a different process. +file = open("future.txt", "w") +file.write(str(async_results)) +file.close() + +# We can later read the file content and retrieve the job +# information and results. +same_file = open("future.txt", "r") +retrieved_async_results = cudaq.AsyncSampleResult(str(same_file.read())) + +counts = retrieved_async_results.get() +print(counts) diff --git a/python/extension/CMakeLists.txt b/python/extension/CMakeLists.txt index 425bc1eea8..00e12016f0 100644 --- a/python/extension/CMakeLists.txt +++ b/python/extension/CMakeLists.txt @@ -72,6 +72,8 @@ declare_mlir_python_extension(CUDAQuantumPythonSources.Extension ../../runtime/cudaq/platform/common/QuantumExecutionQueue.cpp ../../runtime/cudaq/platform/default/rest_server/RemoteRuntimeClient.cpp ../../runtime/cudaq/platform/orca/OrcaQPU.cpp + ../../runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp + ../../runtime/cudaq/platform/orca/OrcaServerHelper.cpp ../../runtime/common/ArgumentConversion.cpp EMBED_CAPI_LINK_LIBS diff --git a/python/extension/CUDAQuantumExtension.cpp b/python/extension/CUDAQuantumExtension.cpp index 91ae7e2a24..3c09bba142 100644 --- a/python/extension/CUDAQuantumExtension.cpp +++ b/python/extension/CUDAQuantumExtension.cpp @@ -178,6 +178,23 @@ PYBIND11_MODULE(_quakeDialects, m) { "ORCA's backends", py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), py::arg("n_samples") = 10000); + orcaSubmodule.def( + "sample_async", + py::overload_cast &, std::vector &, + std::vector &, std::vector &, int>( + &cudaq::orca::sample_async), + "Performs Time Bin Interferometer (TBI) boson sampling experiments on " + "ORCA's backends", + py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), + py::arg("ps_angles") = nullptr, py::arg("n_samples") = 10000); + orcaSubmodule.def( + "sample_async", + py::overload_cast &, std::vector &, + std::vector &, int>(&cudaq::orca::sample_async), + "Performs Time Bin Interferometer (TBI) boson sampling experiments on " + "ORCA's backends", + py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), + py::arg("n_samples") = 10000); cudaqRuntime.def("cloneModule", [](MlirModule mod) { return wrap(unwrap(mod).clone()); }); cudaqRuntime.def("isTerminator", [](MlirOperation op) { diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index a319b8374c..2343d93631 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -122,5 +122,4 @@ bool OrcaServerHelper::jobIsDone(ServerMessage &getJobResponse) { } // namespace cudaq -// LLVM_INSTANTIATE_REGISTRY(cudaq::orca::OrcaServerHelper::RegistryType) CUDAQ_REGISTER_TYPE(cudaq::ServerHelper, cudaq::OrcaServerHelper, orca) From bb815a2fceccd1433c5ca4682da59750261e9761 Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Wed, 11 Sep 2024 12:01:12 +0100 Subject: [PATCH 05/19] Add bearer token --- docs/sphinx/using/backends/hardware.rst | 8 +++++ .../cudaq/platform/orca/OrcaRemoteRESTQPU.cpp | 4 +-- .../cudaq/platform/orca/OrcaServerHelper.cpp | 30 ++++++++++++------- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/sphinx/using/backends/hardware.rst b/docs/sphinx/using/backends/hardware.rst index 203e0268b1..1a861e3f6b 100644 --- a/docs/sphinx/using/backends/hardware.rst +++ b/docs/sphinx/using/backends/hardware.rst @@ -312,6 +312,14 @@ configuration. export ORCA_ACCESS_URL="https://" + +Sometimes the requests to the PT-1 require an authentication token. This token can be set as an +environment variable named ``ORCA_AUTH_TOKEN``. For example, if the token is :code:`AbCdEf123456`, +you can set the environment variable as follows: + +.. code:: bash + export ORCA_AUTH_TOKEN="AbCdEf123456" + Submission from C++ ````````````````````````` diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp index cbcd03cb3a..3d91848206 100644 --- a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp @@ -57,12 +57,12 @@ void OrcaRemoteRESTQPU::setTargetBackend(const std::string &backend) { executor->setServerHelper(serverHelper.get()); } -/// @brief Launch the kernel. +/// @brief Launch the experiment. void OrcaRemoteRESTQPU::launchKernel(const std::string &kernelName, void (*kernelFunc)(void *), void *args, std::uint64_t voidStarSize, std::uint64_t resultOffset) { - cudaq::info("launching ORCA remote rest kernel ({})", kernelName); + cudaq::info("launching ORCA remote rest experiment ({})", kernelName); // TODO future iterations of this should support non-void return types. if (!executionContext) diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index 2343d93631..a6ca2e00ad 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -73,13 +73,17 @@ sample_result OrcaServerHelper::processResults(ServerMessage &postJobResponse, std::map OrcaServerHelper::generateRequestHeader() const { - std::string apiKey, refreshKey, timeStr; + std::string token, refreshKey, timeStr; + if (auto auth_token = std::getenv("ORCA_AUTH_TOKEN")) + token = "Bearer " + std::string(auth_token); + else + token = "Bearer "; + std::map headers{ - // {"Authorization", apiKey}, + {"Authorization", token}, {"Content-Type", "application/json"}, - // {"Connection", "keep-alive"}, - // {"Accept", "*/*"} - }; + {"Connection", "keep-alive"}, + {"Accept", "*/*"}}; return headers; } @@ -106,17 +110,23 @@ std::string OrcaServerHelper::constructGetJobPath(std::string &jobId) { } bool OrcaServerHelper::jobIsDone(ServerMessage &getJobResponse) { - cudaq::info("getJobResponse {}.", getJobResponse.dump()); - std::string job_status = ""; auto error = getJobResponse["error_message"].is_null(); auto status = getJobResponse["job_status"].is_null(); - // if (!status){ - // job_status = getJobResponse["job_status"].get();} cudaq::info("status {}, error {}", status, error); if (error & status) { return true; - } else { + } else if (!status) { + auto job_status = getJobResponse["job_status"].get(); + cudaq::info("job_status {}", job_status); return false; + } else { + auto error_message = getJobResponse["error_message"].get(); + cudaq::info("error_message {}", error_message); + if (error_message == "Job can't be found") { + return false; + } else { + throw std::runtime_error(error_message); + } } } From f3402225715a851a9055751af5fabb7cbe5880ac Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Fri, 13 Sep 2024 15:46:24 +0100 Subject: [PATCH 06/19] Wait for samples --- docs/sphinx/examples/cpp/providers/orca.cpp | 6 ++++++ docs/sphinx/examples/python/providers/orca.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/sphinx/examples/cpp/providers/orca.cpp b/docs/sphinx/examples/cpp/providers/orca.cpp index 577c75f7b1..5cddfad42d 100644 --- a/docs/sphinx/examples/cpp/providers/orca.cpp +++ b/docs/sphinx/examples/cpp/providers/orca.cpp @@ -11,6 +11,9 @@ #include #include +#include +#include + // define helper function to generate linear spaced vectors template void linear_spaced_vector(std::vector &xs, T min, T max, std::size_t N) { @@ -23,6 +26,8 @@ void linear_spaced_vector(std::vector &xs, T min, T max, std::size_t N) { } int main() { + using namespace std::this_thread; // sleep_for, sleep_until + using namespace std::chrono_literals; // ns, us, ms, s, h, etc. // A time-bin boson sampling experiment: An input state of 4 indistinguishable // photons mixed with 4 vacuum states across 8 time bins (modes) enter the @@ -97,6 +102,7 @@ int main() { std::ifstream in("saveMe.json"); in >> readIn; + sleep_for(200ms); // wait for the job to be processed // Get the results of the read in future. auto async_counts = readIn.get(); async_counts.dump(); diff --git a/docs/sphinx/examples/python/providers/orca.py b/docs/sphinx/examples/python/providers/orca.py index 9825fe1d16..4eac62b66d 100644 --- a/docs/sphinx/examples/python/providers/orca.py +++ b/docs/sphinx/examples/python/providers/orca.py @@ -1,4 +1,5 @@ import cudaq +import time import numpy as np import os @@ -84,6 +85,7 @@ # We can later read the file content and retrieve the job # information and results. +time.sleep(0.2) # wait for the job to be processed same_file = open("future.txt", "r") retrieved_async_results = cudaq.AsyncSampleResult(str(same_file.read())) From e83928baf97f8063f4d73a315d5aff2426b3b4d0 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Mon, 16 Sep 2024 15:47:49 -0700 Subject: [PATCH 07/19] * Code formatting * Spell check --- docs/sphinx/examples/cpp/providers/orca.cpp | 4 ++-- docs/sphinx/examples/python/providers/orca.py | 6 +++--- runtime/common/Executor.h | 2 +- runtime/cudaq/platform/orca/OrcaServerHelper.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/sphinx/examples/cpp/providers/orca.cpp b/docs/sphinx/examples/cpp/providers/orca.cpp index 5cddfad42d..9df9e79f81 100644 --- a/docs/sphinx/examples/cpp/providers/orca.cpp +++ b/docs/sphinx/examples/cpp/providers/orca.cpp @@ -26,8 +26,8 @@ void linear_spaced_vector(std::vector &xs, T min, T max, std::size_t N) { } int main() { - using namespace std::this_thread; // sleep_for, sleep_until - using namespace std::chrono_literals; // ns, us, ms, s, h, etc. + using namespace std::this_thread; // sleep_for, sleep_until + using namespace std::chrono_literals; // `ns`, `us`, `ms`, `s`, `h`, etc. // A time-bin boson sampling experiment: An input state of 4 indistinguishable // photons mixed with 4 vacuum states across 8 time bins (modes) enter the diff --git a/docs/sphinx/examples/python/providers/orca.py b/docs/sphinx/examples/python/providers/orca.py index 4eac62b66d..53cadb09d5 100644 --- a/docs/sphinx/examples/python/providers/orca.py +++ b/docs/sphinx/examples/python/providers/orca.py @@ -63,14 +63,14 @@ # Print the results print(counts) - # Option B: # By using the asynchronous `cudaq.orca.sample_async`, the remaining # classical code will be executed while the job is being handled # by Orca. This is ideal when submitting via a queue over # the cloud. print("Submitting to ORCA Server asynchronously") -async_results = cudaq.orca.sample_async(input_state, loop_lengths, bs_angles, n_samples) +async_results = cudaq.orca.sample_async(input_state, loop_lengths, bs_angles, + n_samples) # ... more classical code to run ... # We can either retrieve the results later in the program with @@ -85,7 +85,7 @@ # We can later read the file content and retrieve the job # information and results. -time.sleep(0.2) # wait for the job to be processed +time.sleep(0.2) # wait for the job to be processed same_file = open("future.txt", "r") retrieved_async_results = cudaq.AsyncSampleResult(str(same_file.read())) diff --git a/runtime/common/Executor.h b/runtime/common/Executor.h index 809ab77cc1..12d1415525 100644 --- a/runtime/common/Executor.h +++ b/runtime/common/Executor.h @@ -43,7 +43,7 @@ class Executor : public registry::RegisteredType { /// The caller can make this synchronous by just immediately calling .get(). details::future execute(std::vector &codesToExecute); - /// @brief Execute the provided orca quantum parameters and return a future + /// @brief Execute the provided ORCA quantum parameters and return a future /// object The caller can make this synchronous by just immediately calling /// .get(). details::future execute(cudaq::orca::TBIParameters params, diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.h b/runtime/cudaq/platform/orca/OrcaServerHelper.h index 14ae7fe66b..3abb3cce74 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.h +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.h @@ -31,7 +31,7 @@ class OrcaServerHelper : public ServerHelper { /// @brief The refresh token std::string refreshKey = ""; - /// @brief Orca requires the API token be updated every so often, + /// @brief ORCA requires the API token be updated every so often, /// using the provided refresh token. This function will do that. void refreshTokens(bool force_refresh = false); @@ -43,7 +43,7 @@ class OrcaServerHelper : public ServerHelper { virtual ~OrcaServerHelper() = default; /// @brief Return the name of this server helper, must be the - /// same as the qpu config file. + /// same as the QPU configuration file. const std::string name() const override { return "orca"; } /// @brief Return the POST/GET required headers. From 721162bb53b34a3ecdc8728b2f14b89ee7a5925e Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Wed, 18 Sep 2024 10:53:34 -0700 Subject: [PATCH 08/19] * Fix documentation errors --- docs/sphinx/api/languages/cpp_api.rst | 4 +++- docs/sphinx/using/backends/hardware.rst | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 99e0b2eb07..bb2cb7a50e 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -166,7 +166,9 @@ Platform .. doxygenclass:: cudaq::BaseRemoteSimulatorQPU -.. doxygenclass:: cudaq::BaseNvcfSimulatorQPU +.. doxygenclass:: cudaq::BaseNvcfSimulatorQPU + +.. doxygenclass:: cudaq::OrcaRemoteRESTQPU .. doxygenclass:: cudaq::quantum_platform :members: diff --git a/docs/sphinx/using/backends/hardware.rst b/docs/sphinx/using/backends/hardware.rst index 1a861e3f6b..0dbb53a3d8 100644 --- a/docs/sphinx/using/backends/hardware.rst +++ b/docs/sphinx/using/backends/hardware.rst @@ -318,8 +318,10 @@ environment variable named ``ORCA_AUTH_TOKEN``. For example, if the token is :co you can set the environment variable as follows: .. code:: bash + export ORCA_AUTH_TOKEN="AbCdEf123456" + Submission from C++ ````````````````````````` From 93779972c3565f272211b2c625ff7255bbc19c20 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Wed, 18 Sep 2024 22:14:25 -0700 Subject: [PATCH 09/19] * Addressing review comments --- runtime/common/Executor.h | 2 +- runtime/common/ServerHelper.h | 3 +-- runtime/cudaq/platform/orca/OrcaServerHelper.cpp | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/runtime/common/Executor.h b/runtime/common/Executor.h index 12d1415525..eb1ef3f2e0 100644 --- a/runtime/common/Executor.h +++ b/runtime/common/Executor.h @@ -44,7 +44,7 @@ class Executor : public registry::RegisteredType { details::future execute(std::vector &codesToExecute); /// @brief Execute the provided ORCA quantum parameters and return a future - /// object The caller can make this synchronous by just immediately calling + /// object. The caller can make this synchronous by just immediately calling /// .get(). details::future execute(cudaq::orca::TBIParameters params, const std::string &kernelName); diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index e0f2286077..0b3a8c8cbf 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -113,8 +113,7 @@ class ServerHelper : public registry::RegisteredType { std::map headers; // Return a tuple containing the job path, headers, and the job message - auto ret = std::make_tuple("", headers, jobs); - return ret; + return std::make_tuple("", headers, jobs); }; /// @brief Extract the job id from the server response from posting the job. diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index a6ca2e00ad..5225508fcd 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -49,8 +49,7 @@ OrcaServerHelper::createJob(cudaq::orca::TBIParameters params) { jobs.push_back(job); // Return a tuple containing the job path, headers, and the job message - auto ret = std::make_tuple(baseUrl + "v1/submit", getHeaders(), jobs); - return ret; + return std::make_tuple(baseUrl + "v1/submit", getHeaders(), jobs); } // Process the results from a job From ef06e3615bafea434bb01131298f745a4a4cb10b Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Fri, 27 Sep 2024 14:02:59 +0000 Subject: [PATCH 10/19] Add `qpu_id` parameter to sample and sample_async --- python/extension/CUDAQuantumExtension.cpp | 24 ++++++++------- runtime/cudaq/platform/orca/OrcaQPU.cpp | 30 ++++++++++--------- .../cudaq/platform/orca/OrcaServerHelper.cpp | 1 - runtime/cudaq/platform/orca/orca_qpu.h | 8 ++--- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/python/extension/CUDAQuantumExtension.cpp b/python/extension/CUDAQuantumExtension.cpp index 6a1b5c4fe9..22dfd8dfd0 100644 --- a/python/extension/CUDAQuantumExtension.cpp +++ b/python/extension/CUDAQuantumExtension.cpp @@ -164,37 +164,41 @@ PYBIND11_MODULE(_quakeDialects, m) { orcaSubmodule.def( "sample", py::overload_cast &, std::vector &, - std::vector &, std::vector &, int>( - &cudaq::orca::sample), + std::vector &, std::vector &, int, + std::size_t>(&cudaq::orca::sample), "Performs Time Bin Interferometer (TBI) boson sampling experiments on " "ORCA's backends", py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), - py::arg("ps_angles") = nullptr, py::arg("n_samples") = 10000); + py::arg("ps_angles"), py::arg("n_samples") = 10000, + py::arg("qpu_id") = 0); orcaSubmodule.def( "sample", py::overload_cast &, std::vector &, - std::vector &, int>(&cudaq::orca::sample), + std::vector &, int, std::size_t>( + &cudaq::orca::sample), "Performs Time Bin Interferometer (TBI) boson sampling experiments on " "ORCA's backends", py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), - py::arg("n_samples") = 10000); + py::arg("n_samples") = 10000, py::arg("qpu_id") = 0); orcaSubmodule.def( "sample_async", py::overload_cast &, std::vector &, - std::vector &, std::vector &, int>( - &cudaq::orca::sample_async), + std::vector &, std::vector &, int, + std::size_t>(&cudaq::orca::sample_async), "Performs Time Bin Interferometer (TBI) boson sampling experiments on " "ORCA's backends", py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), - py::arg("ps_angles") = nullptr, py::arg("n_samples") = 10000); + py::arg("ps_angles"), py::arg("n_samples") = 10000, + py::arg("qpu_id") = 0); orcaSubmodule.def( "sample_async", py::overload_cast &, std::vector &, - std::vector &, int>(&cudaq::orca::sample_async), + std::vector &, int, std::size_t>( + &cudaq::orca::sample_async), "Performs Time Bin Interferometer (TBI) boson sampling experiments on " "ORCA's backends", py::arg("input_state"), py::arg("loop_lengths"), py::arg("bs_angles"), - py::arg("n_samples") = 10000); + py::arg("n_samples") = 10000, py::arg("qpu_id") = 0); auto photonicsSubmodule = cudaqRuntime.def_submodule("photonics"); photonicsSubmodule.def( diff --git a/runtime/cudaq/platform/orca/OrcaQPU.cpp b/runtime/cudaq/platform/orca/OrcaQPU.cpp index d881d0de20..63883a7af3 100644 --- a/runtime/cudaq/platform/orca/OrcaQPU.cpp +++ b/runtime/cudaq/platform/orca/OrcaQPU.cpp @@ -14,9 +14,9 @@ namespace cudaq::orca { -cudaq::sample_result runSampling(TBIParameters ¶meters) { +cudaq::sample_result runSampling(TBIParameters ¶meters, + std::size_t qpu_id = 0) { std::size_t shots = parameters.n_samples; - int qpu_id = 0; auto ctx = std::make_unique("sample", shots); auto &platform = cudaq::get_platform(); @@ -30,9 +30,9 @@ cudaq::sample_result runSampling(TBIParameters ¶meters) { return ctx->result; } -async_sample_result runAsyncSampling(TBIParameters ¶meters) { +async_sample_result runAsyncSampling(TBIParameters ¶meters, + std::size_t qpu_id = 0) { std::size_t shots = parameters.n_samples; - int qpu_id = 0; auto ctx = std::make_unique("sample", shots); // Indicate that this is an async exec @@ -56,39 +56,41 @@ async_sample_result runAsyncSampling(TBIParameters ¶meters) { cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, - std::vector &ps_angles, int n_samples) { + std::vector &ps_angles, int n_samples, + std::size_t qpu_id) { TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - return runSampling(parameters); + return runSampling(parameters, qpu_id); } cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, - std::vector &bs_angles, int n_samples) { + std::vector &bs_angles, int n_samples, + std::size_t qpu_id) { std::vector ps_angles = {}; TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - return runSampling(parameters); + return runSampling(parameters, qpu_id); } async_sample_result sample_async(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, - std::vector &ps_angles, - int n_samples) { + std::vector &ps_angles, int n_samples, + std::size_t qpu_id) { TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - return runAsyncSampling(parameters); + return runAsyncSampling(parameters, qpu_id); } async_sample_result sample_async(std::vector &input_state, std::vector &loop_lengths, - std::vector &bs_angles, - int n_samples) { + std::vector &bs_angles, int n_samples, + std::size_t qpu_id) { std::vector ps_angles = {}; TBIParameters parameters{input_state, loop_lengths, bs_angles, ps_angles, n_samples}; - return runAsyncSampling(parameters); + return runAsyncSampling(parameters, qpu_id); } } // namespace cudaq::orca diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index 5225508fcd..5c32b6fe30 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -111,7 +111,6 @@ std::string OrcaServerHelper::constructGetJobPath(std::string &jobId) { bool OrcaServerHelper::jobIsDone(ServerMessage &getJobResponse) { auto error = getJobResponse["error_message"].is_null(); auto status = getJobResponse["job_status"].is_null(); - cudaq::info("status {}, error {}", status, error); if (error & status) { return true; } else if (!status) { diff --git a/runtime/cudaq/platform/orca/orca_qpu.h b/runtime/cudaq/platform/orca/orca_qpu.h index 6a0bf2c4f6..643a1faf91 100644 --- a/runtime/cudaq/platform/orca/orca_qpu.h +++ b/runtime/cudaq/platform/orca/orca_qpu.h @@ -35,22 +35,22 @@ cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, - int n_samples = 10000); + int n_samples = 10000, std::size_t qpu_id = 0); cudaq::sample_result sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, - int n_samples = 10000); + int n_samples = 10000, std::size_t qpu_id = 0); async_sample_result sample_async(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, - int n_samples = 10000); + int n_samples = 10000, std::size_t qpu_id = 0); async_sample_result sample_async(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, - int n_samples = 10000); + int n_samples = 10000, std::size_t qpu_id = 0); }; // namespace cudaq::orca \ No newline at end of file From 669754ac6af1b3de2f6ab8bb79c4370ec0691e1c Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Fri, 27 Sep 2024 14:13:39 +0000 Subject: [PATCH 11/19] Throw exception instead of returning a dummy payload --- runtime/common/ServerHelper.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index 0b3a8c8cbf..8152ccb94d 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -106,14 +106,7 @@ class ServerHelper : public registry::RegisteredType { /// @brief Create a job payload for the provided TBI parameters virtual ServerJobPayload createJob(cudaq::orca::TBIParameters params) { - std::vector jobs; - ServerMessage job; - jobs.push_back(job); - - std::map headers; - - // Return a tuple containing the job path, headers, and the job message - return std::make_tuple("", headers, jobs); + throw std::runtime_error("Not implemented"); }; /// @brief Extract the job id from the server response from posting the job. From 339d7d85da8a477f53372a6b3c61d297bab6609a Mon Sep 17 00:00:00 2001 From: Omar Bacarreza Date: Fri, 27 Sep 2024 14:17:13 +0000 Subject: [PATCH 12/19] Typo in Executor.cpp --- runtime/common/Executor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/common/Executor.cpp b/runtime/common/Executor.cpp index 07ee31062a..53b9f24f75 100644 --- a/runtime/common/Executor.cpp +++ b/runtime/common/Executor.cpp @@ -77,7 +77,7 @@ details::future Executor::execute(cudaq::orca::TBIParameters params, auto response = client.post(jobPostPath, "", job, headers); cudaq::info("Job posted, response was {}", response.dump()); - // // Add the job id and the job name. + // Add the job id and the job name. auto job_id = serverHelper->extractJobId(response); if (job_id.empty()) { nlohmann::json tmp(job.at("job_id")); From e336abd5afbf46a4ec32a695b6c656b53e28840b Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Mon, 30 Sep 2024 14:40:09 -0700 Subject: [PATCH 13/19] * C++ source code formatting --- runtime/common/ServerHelper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index 8152ccb94d..7af1868ad8 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -106,7 +106,7 @@ class ServerHelper : public registry::RegisteredType { /// @brief Create a job payload for the provided TBI parameters virtual ServerJobPayload createJob(cudaq::orca::TBIParameters params) { - throw std::runtime_error("Not implemented"); + throw std::runtime_error("Not implemented"); }; /// @brief Extract the job id from the server response from posting the job. From 96679b460980a5c6a01aa57ab8874ceff53b517a Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Mon, 30 Sep 2024 14:42:57 -0700 Subject: [PATCH 14/19] * Doxygen fixes --- docs/sphinx/api/languages/cpp_api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index bb2cb7a50e..eaa5292617 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -231,5 +231,5 @@ Namespaces .. doxygennamespace:: cudaq::orca :desc-only: -.. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples = 10000) -.. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, int n_samples = 10000) +.. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples = 10000, std::size_t qpu_id = 0) +.. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, int n_samples = 10000, std::size_t qpu_id = 0) From efca8753c24e3874b2971e22dc352bbba9c451df Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Mon, 30 Sep 2024 14:50:40 -0700 Subject: [PATCH 15/19] * Missing doxygen for the newly added asynchronous sampling. --- docs/sphinx/api/languages/cpp_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index eaa5292617..50fce80289 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -233,3 +233,5 @@ Namespaces .. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples = 10000, std::size_t qpu_id = 0) .. doxygenfunction:: cudaq::orca::sample(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, int n_samples = 10000, std::size_t qpu_id = 0) +.. doxygenfunction:: cudaq::orca::sample_async(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, int n_samples = 10000, std::size_t qpu_id = 0) +.. doxygenfunction:: cudaq::orca::sample_async(std::vector &input_state, std::vector &loop_lengths, std::vector &bs_angles, std::vector &ps_angles, int n_samples = 10000, std::size_t qpu_id = 0) From 4e199f9c7616dee0a4b55dcf22716cabdadeb6c5 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Tue, 1 Oct 2024 16:02:19 -0700 Subject: [PATCH 16/19] * Doxygen fix for the async sample result --- docs/sphinx/api/languages/cpp_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/api/languages/cpp_api.rst b/docs/sphinx/api/languages/cpp_api.rst index 50fce80289..115463ffe9 100644 --- a/docs/sphinx/api/languages/cpp_api.rst +++ b/docs/sphinx/api/languages/cpp_api.rst @@ -44,6 +44,8 @@ Common .. doxygenclass:: cudaq::async_result :members: +.. doxygentypedef:: async_sample_result + .. doxygenstruct:: cudaq::ExecutionResult :members: From 9b979f2bd2ee0c5ad558c70211416a4aff26f442 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Thu, 3 Oct 2024 22:48:41 -0700 Subject: [PATCH 17/19] * Addressing review comment -- refactored to remove orca-specific dependency at common level (i.e. `Executor` and `ServerHelper` classes) --- python/extension/CMakeLists.txt | 3 +- runtime/common/Executor.cpp | 35 -------------- runtime/common/Executor.h | 6 --- runtime/common/ServerHelper.h | 6 --- runtime/cudaq/platform/orca/CMakeLists.txt | 12 ++--- runtime/cudaq/platform/orca/OrcaExecutor.cpp | 47 +++++++++++++++++++ runtime/cudaq/platform/orca/OrcaExecutor.h | 26 ++++++++++ .../cudaq/platform/orca/OrcaRemoteRESTQPU.h | 8 ++-- .../cudaq/platform/orca/OrcaServerHelper.cpp | 1 + .../cudaq/platform/orca/OrcaServerHelper.h | 4 +- 10 files changed, 86 insertions(+), 62 deletions(-) create mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.cpp create mode 100644 runtime/cudaq/platform/orca/OrcaExecutor.h diff --git a/python/extension/CMakeLists.txt b/python/extension/CMakeLists.txt index 977adce448..de6ba323bd 100644 --- a/python/extension/CMakeLists.txt +++ b/python/extension/CMakeLists.txt @@ -70,12 +70,13 @@ declare_mlir_python_extension(CUDAQuantumPythonSources.Extension ../runtime/utils/PyRemoteSimulatorQPU.cpp ../runtime/utils/PyRestRemoteClient.cpp ../utils/LinkedLibraryHolder.cpp + ../../runtime/common/ArgumentConversion.cpp ../../runtime/cudaq/platform/common/QuantumExecutionQueue.cpp ../../runtime/cudaq/platform/default/rest_server/RemoteRuntimeClient.cpp + ../../runtime/cudaq/platform/orca/OrcaExecutor.cpp ../../runtime/cudaq/platform/orca/OrcaQPU.cpp ../../runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.cpp ../../runtime/cudaq/platform/orca/OrcaServerHelper.cpp - ../../runtime/common/ArgumentConversion.cpp EMBED_CAPI_LINK_LIBS CUDAQuantumMLIRCAPI diff --git a/runtime/common/Executor.cpp b/runtime/common/Executor.cpp index 53b9f24f75..330d2c425f 100644 --- a/runtime/common/Executor.cpp +++ b/runtime/common/Executor.cpp @@ -56,39 +56,4 @@ Executor::execute(std::vector &codesToExecute) { return details::future(ids, name, config); } -details::future Executor::execute(cudaq::orca::TBIParameters params, - const std::string &kernelName) { - - serverHelper->setShots(shots); - - cudaq::info("Executor creating job to execute with the {} helper.", - serverHelper->name()); - - // Create the Job Payload, composed of job post path, headers, - // and the job json messages themselves - auto [jobPostPath, headers, jobs] = serverHelper->createJob(params); - auto job = jobs[0]; - auto config = serverHelper->getConfig(); - - std::vector ids; - cudaq::info("Job created, posting to {}", jobPostPath); - - // Post it, get the response - auto response = client.post(jobPostPath, "", job, headers); - cudaq::info("Job posted, response was {}", response.dump()); - - // Add the job id and the job name. - auto job_id = serverHelper->extractJobId(response); - if (job_id.empty()) { - nlohmann::json tmp(job.at("job_id")); - serverHelper->constructGetJobPath(tmp[0]); - job_id = tmp[0].at("job_id"); - } - ids.emplace_back(job_id, kernelName); - config["output_names." + job_id] = kernelName; - - config.insert({"shots", std::to_string(shots)}); - std::string name = serverHelper->name(); - return cudaq::details::future(ids, name, config); -} } // namespace cudaq \ No newline at end of file diff --git a/runtime/common/Executor.h b/runtime/common/Executor.h index eb1ef3f2e0..667dda6f32 100644 --- a/runtime/common/Executor.h +++ b/runtime/common/Executor.h @@ -42,12 +42,6 @@ class Executor : public registry::RegisteredType { /// @brief Execute the provided quantum codes and return a future object /// The caller can make this synchronous by just immediately calling .get(). details::future execute(std::vector &codesToExecute); - - /// @brief Execute the provided ORCA quantum parameters and return a future - /// object. The caller can make this synchronous by just immediately calling - /// .get(). - details::future execute(cudaq::orca::TBIParameters params, - const std::string &kernelName); }; } // namespace cudaq diff --git a/runtime/common/ServerHelper.h b/runtime/common/ServerHelper.h index 7af1868ad8..2e23c316b2 100644 --- a/runtime/common/ServerHelper.h +++ b/runtime/common/ServerHelper.h @@ -14,7 +14,6 @@ #include "Future.h" #include "MeasureCounts.h" #include "Registry.h" -#include "cudaq/orca.h" #include namespace cudaq { @@ -104,11 +103,6 @@ class ServerHelper : public registry::RegisteredType { virtual ServerJobPayload createJob(std::vector &circuitCodes) = 0; - /// @brief Create a job payload for the provided TBI parameters - virtual ServerJobPayload createJob(cudaq::orca::TBIParameters params) { - throw std::runtime_error("Not implemented"); - }; - /// @brief Extract the job id from the server response from posting the job. virtual std::string extractJobId(ServerMessage &postResponse) = 0; diff --git a/runtime/cudaq/platform/orca/CMakeLists.txt b/runtime/cudaq/platform/orca/CMakeLists.txt index 995934d8b2..3610b902a3 100644 --- a/runtime/cudaq/platform/orca/CMakeLists.txt +++ b/runtime/cudaq/platform/orca/CMakeLists.txt @@ -9,9 +9,10 @@ set(LIBRARY_NAME cudaq-orca-qpu) message(STATUS "Building ORCA REST QPU.") set(ORCA_SRC + OrcaExecutor.cpp + OrcaQPU.cpp + OrcaRemoteRESTQPU.cpp OrcaServerHelper.cpp - OrcaRemoteRESTQPU.cpp - OrcaQPU.cpp ) add_library(${LIBRARY_NAME} SHARED ${ORCA_SRC}) @@ -35,9 +36,4 @@ target_link_libraries(${LIBRARY_NAME} install(TARGETS ${LIBRARY_NAME} DESTINATION lib) install(TARGETS ${LIBRARY_NAME} EXPORT cudaq-orca-qpu-targets DESTINATION lib) -# install(EXPORT cudaq-orca-qpu-targets -# FILE CUDAQQPUOrcaTargets.cmake -# NAMESPACE cudaq::orca:: -# DESTINATION lib/cmake/cudaq) - -add_target_config(orca) \ No newline at end of file +add_target_config(orca) diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.cpp b/runtime/cudaq/platform/orca/OrcaExecutor.cpp new file mode 100644 index 0000000000..94413a24c5 --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaExecutor.cpp @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#include "OrcaExecutor.h" +#include "OrcaServerHelper.h" +#include "common/Logger.h" + +namespace cudaq { + +details::future OrcaExecutor::execute(cudaq::orca::TBIParameters params, + const std::string &kernelName) { + auto orcaServerHelper = dynamic_cast(serverHelper); + assert(orcaServerHelper); + orcaServerHelper->setShots(shots); + cudaq::info("Executor creating job to execute with the {} helper.", + orcaServerHelper->name()); + // Create the Job Payload, composed of job post path, headers, + // and the job json messages themselves + auto [jobPostPath, headers, jobs] = orcaServerHelper->createJob(params); + auto job = jobs[0]; + auto config = orcaServerHelper->getConfig(); + std::vector ids; + cudaq::info("Job created, posting to {}", jobPostPath); + // Post it, get the response + auto response = client.post(jobPostPath, "", job, headers); + cudaq::info("Job posted, response was {}", response.dump()); + // Add the job id and the job name. + auto job_id = orcaServerHelper->extractJobId(response); + if (job_id.empty()) { + nlohmann::json tmp(job.at("job_id")); + orcaServerHelper->constructGetJobPath(tmp[0]); + job_id = tmp[0].at("job_id"); + } + ids.emplace_back(job_id, kernelName); + config["output_names." + job_id] = kernelName; + + config.insert({"shots", std::to_string(shots)}); + std::string name = orcaServerHelper->name(); + return cudaq::details::future(ids, name, config); +} + +} // namespace cudaq \ No newline at end of file diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.h b/runtime/cudaq/platform/orca/OrcaExecutor.h new file mode 100644 index 0000000000..2396bc751e --- /dev/null +++ b/runtime/cudaq/platform/orca/OrcaExecutor.h @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2022 - 2024 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include "common/Executor.h" +#include "orca_qpu.h" + +namespace cudaq { + +/// @brief The Executor subclass for 'orca' target which has a distinct sampling +/// API. +class OrcaExecutor : public Executor { +public: + /// @brief Execute the provided ORCA quantum parameters and return a future + /// object. The caller can make this synchronous by just immediately calling + /// .get(). + details::future execute(cudaq::orca::TBIParameters params, + const std::string &kernelName); +}; +} // namespace cudaq diff --git a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h index 97120bb7ed..3e14194d23 100644 --- a/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h +++ b/runtime/cudaq/platform/orca/OrcaRemoteRESTQPU.h @@ -5,15 +5,15 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ + #pragma once +#include "OrcaExecutor.h" #include "common/ExecutionContext.h" -#include "common/Executor.h" #include "common/Future.h" #include "common/RestClient.h" #include "common/ServerHelper.h" #include "cudaq/platform/qpu.h" - #include "orca_qpu.h" namespace cudaq { @@ -40,7 +40,7 @@ class OrcaRemoteRESTQPU : public cudaq::QPU { bool emulate = false; // Pointer to the concrete Executor for this QPU - std::unique_ptr executor; + std::unique_ptr executor; /// @brief Pointer to the concrete ServerHelper, provides /// specific JSON payloads and POST/GET URL paths. @@ -60,7 +60,7 @@ class OrcaRemoteRESTQPU : public cudaq::QPU { std::filesystem::path cudaqLibPath{getCUDAQLibraryPath()}; platformPath = cudaqLibPath.parent_path().parent_path() / "targets"; // Default is to run sampling via the remote rest call - executor = std::make_unique(); + executor = std::make_unique(); } OrcaRemoteRESTQPU(OrcaRemoteRESTQPU &&) = delete; diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp index 5c32b6fe30..87adff66b9 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.cpp +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.cpp @@ -5,6 +5,7 @@ * This source code and the accompanying materials are made available under * * the terms of the Apache License 2.0 which accompanies this distribution. * ******************************************************************************/ + #include "OrcaServerHelper.h" #include "common/Future.h" #include "common/Logger.h" diff --git a/runtime/cudaq/platform/orca/OrcaServerHelper.h b/runtime/cudaq/platform/orca/OrcaServerHelper.h index 3abb3cce74..c3c5837ccb 100644 --- a/runtime/cudaq/platform/orca/OrcaServerHelper.h +++ b/runtime/cudaq/platform/orca/OrcaServerHelper.h @@ -54,9 +54,9 @@ class OrcaServerHelper : public ServerHelper { void initialize(BackendConfig config) override; /// @brief Create a job payload for the provided TBI parameters - ServerJobPayload createJob(cudaq::orca::TBIParameters params) override; + ServerJobPayload createJob(cudaq::orca::TBIParameters params); - // // /// @brief Create a job payload for the provided quantum codes + /// @brief Create a job payload for the provided quantum codes ServerJobPayload createJob(std::vector &circuitCodes) override { std::vector jobs; From 229a8f9372cc273dc31885ffcf1e4f4110b0d991 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Thu, 3 Oct 2024 22:58:10 -0700 Subject: [PATCH 18/19] * Spell check --- runtime/cudaq/platform/orca/OrcaExecutor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/cudaq/platform/orca/OrcaExecutor.h b/runtime/cudaq/platform/orca/OrcaExecutor.h index 2396bc751e..11f0dd76ad 100644 --- a/runtime/cudaq/platform/orca/OrcaExecutor.h +++ b/runtime/cudaq/platform/orca/OrcaExecutor.h @@ -13,7 +13,7 @@ namespace cudaq { -/// @brief The Executor subclass for 'orca' target which has a distinct sampling +/// @brief The Executor subclass for ORCA target which has a distinct sampling /// API. class OrcaExecutor : public Executor { public: From b8b2de129513d9d25dbd00145561d1fa95c5ff0d Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Mon, 7 Oct 2024 13:34:25 -0700 Subject: [PATCH 19/19] * Restoring file --- runtime/common/Executor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/common/Executor.cpp b/runtime/common/Executor.cpp index 330d2c425f..448442c214 100644 --- a/runtime/common/Executor.cpp +++ b/runtime/common/Executor.cpp @@ -55,5 +55,4 @@ Executor::execute(std::vector &codesToExecute) { std::string name = serverHelper->name(); return details::future(ids, name, config); } - -} // namespace cudaq \ No newline at end of file +} // namespace cudaq