diff --git a/.gitignore b/.gitignore index ab1d574e..e60698a4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ cmake-build-*/ # IDE files +.cache/ .idea/ .vscode/ *.swp diff --git a/CMakeLists.txt b/CMakeLists.txt index 41b37723..499de0b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,11 +108,13 @@ endif() # The S3 benchmark needs the io library (rest backend) and the benchmark tree. # Checked after the io gating above so a force-disabled io also disables it. -if(CUCASCADE_BUILD_S3_BENCHMARK AND (NOT CUCASCADE_BUILD_IO - OR NOT CUCASCADE_BUILD_BENCHMARKS)) +if(CUCASCADE_BUILD_S3_BENCHMARK + AND (NOT CUCASCADE_BUILD_IO + OR NOT CUCASCADE_BUILD_BENCHMARKS + OR NOT CUCASCADE_BUILD_CUDF)) message( STATUS - "CUCASCADE_BUILD_S3_BENCHMARK disabled: requires CUCASCADE_BUILD_IO=ON and CUCASCADE_BUILD_BENCHMARKS=ON" + "CUCASCADE_BUILD_S3_BENCHMARK disabled: requires CUCASCADE_BUILD_IO=ON, CUCASCADE_BUILD_BENCHMARKS=ON and CUCASCADE_BUILD_CUDF=ON (it links cucascade_cudf and kvikIO)" ) set(CUCASCADE_BUILD_S3_BENCHMARK OFF @@ -158,10 +160,15 @@ if(NOT CUCASCADE_TOPOLOGY_ONLY) pkg_check_modules(CURL REQUIRED IMPORTED_TARGET libcurl) find_package(OpenSSL REQUIRED) - # kvikIO — backs the local-file fallback ioctx (kvikio_context). Used - # directly (not via cudf) so the io library stays cudf-free. Not swappable: - # unlike moodycamel/invocable below there is no in-tree stand-in to replace. - find_package(kvikio REQUIRED CONFIG) + # kvikIO — backs the local-file fallback ioctx (kvikio_context). It reaches + # the environment only through libcudf's dependency closure (libkvikio is + # not a direct dependency), so it is tied to CUCASCADE_BUILD_CUDF: a + # cudf-free build drops kvikio_context and its catch-all registry entry, + # leaving uring/restful to claim paths. Consumers see the difference via the + # CUCASCADE_HAS_KVIKIO definition propagated by cucascade_io_thirdparty. + if(CUCASCADE_BUILD_CUDF) + find_package(kvikio REQUIRED CONFIG) + endif() # cucascade_io_thirdparty carries the swappable moodycamel + invocable # (abseil) usage requirements from a single place; the io object library, @@ -239,6 +246,15 @@ if(NOT CUCASCADE_TOPOLOGY_ONLY) target_compile_definitions(cucascade_io_thirdparty INTERFACE CUCASCADE_USE_ABSEIL_INVOCABLE) endif() + + # Gates the kvikIO fallback ioctx in io_config and the datasource registry. + # Carried on the same INTERFACE target as the definitions above so the io + # object library, its installable variants, and installed consumers all + # agree on the layout of io_config. + if(CUCASCADE_BUILD_CUDF) + target_compile_definitions(cucascade_io_thirdparty + INTERFACE CUCASCADE_HAS_KVIKIO) + endif() endif() # Find numa (provided by numactl-devel or libnuma-dev depending on the package @@ -407,7 +423,10 @@ if(CUCASCADE_BUILD_IO) # side by cuCascadeConfig.cmake (same names), mirroring the Numa::Numa # approach. set(CUCASCADE_IO_LINK_LIBS PkgConfig::LIBURING PkgConfig::CURL - OpenSSL::Crypto kvikio::kvikio) + OpenSSL::Crypto) + if(CUCASCADE_BUILD_CUDF) + list(APPEND CUCASCADE_IO_LINK_LIBS kvikio::kvikio) + endif() target_link_libraries( cucascade_io_objects PUBLIC cucascade_objects ${CUCASCADE_IO_LINK_LIBS} @@ -425,7 +444,7 @@ endif() target_include_directories(cucascade_topology_discovery_objects PUBLIC ${CUCASCADE_PUBLIC_INCLUDE_DIRS}) target_link_libraries(cucascade_topology_discovery_objects - PUBLIC CUDA::nvml_static rmm::rmm) + PUBLIC CUDA::nvml_static CUDA::cuda_driver) target_compile_features(cucascade_topology_discovery_objects PUBLIC cxx_std_20) target_compile_features(cucascade_topology_discovery_objects PRIVATE cuda_std_20) @@ -443,7 +462,7 @@ if(CUCASCADE_BUILD_STATIC_LIBS) cucascade_topology_discovery_static) target_link_libraries(cucascade_topology_discovery_static - PRIVATE CUDA::nvml_static rmm::rmm) + PRIVATE CUDA::nvml_static CUDA::cuda_driver) target_include_directories(cucascade_topology_discovery_static PUBLIC ${CUCASCADE_PUBLIC_INCLUDE_DIRS}) target_compile_features(cucascade_topology_discovery_static PUBLIC cxx_std_20) @@ -521,7 +540,7 @@ if(CUCASCADE_BUILD_SHARED_LIBS) cucascade_topology_discovery_shared) target_link_libraries(cucascade_topology_discovery_shared - PRIVATE CUDA::nvml_static rmm::rmm) + PRIVATE CUDA::nvml_static CUDA::cuda_driver) target_include_directories(cucascade_topology_discovery_shared PUBLIC ${CUCASCADE_PUBLIC_INCLUDE_DIRS}) target_compile_features(cucascade_topology_discovery_shared PUBLIC cxx_std_20) diff --git a/include/cucascade/io/config.hpp b/include/cucascade/io/config.hpp index 250120ba..f1b125ca 100644 --- a/include/cucascade/io/config.hpp +++ b/include/cucascade/io/config.hpp @@ -18,7 +18,9 @@ #pragma once #include +#ifdef CUCASCADE_HAS_KVIKIO #include +#endif #include #include #include @@ -35,7 +37,9 @@ namespace cucascade::io { * Sub-configs: * - @c local — uring reactor tunables (local-disk IO path). * - @c rest — REST reactor tunables (S3/object-store IO path). - * - @c kvikio — kvikIO fallback tunables (local-disk catch-all path). + * - @c kvikio — kvikIO fallback tunables (local-disk catch-all path); present + * only when the library is built with CUCASCADE_BUILD_CUDF, which is what + * supplies kvikIO. * - @c cache — prefetching cache tunables. * - @c object_store — object-store credentials and endpoint. */ @@ -59,11 +63,13 @@ struct io_config { /// retry policy, etc. rest::config rest{}; +#ifdef CUCASCADE_HAS_KVIKIO /// kvikIO fallback configuration — thread-pool size, task/bounce sizing, /// O_DIRECT, compat mode. All fields default to "unset", leaving kvikIO's /// own env-var-seeded defaults in place. Note these are process-global once /// applied; see @ref kvikio_config. kvikio_config kvikio{}; +#endif /// Prefetching cache configuration — in-flight budget, pool sizing, /// dispose-after-use policy. diff --git a/include/cucascade/memory/topology_discovery.hpp b/include/cucascade/memory/topology_discovery.hpp index a6057dbc..bac9e12a 100644 --- a/include/cucascade/memory/topology_discovery.hpp +++ b/include/cucascade/memory/topology_discovery.hpp @@ -12,6 +12,18 @@ namespace cucascade::memory { +/** + * @brief GPU runtime attributes. + * + * Attributes whose discovery requires initializing a CUDA context (via the CUDA + * driver API). Populated only when `discover()` / `discover_runtime_attributes()` + * are explicitly asked to. Kept separate so passive topology discovery via NVML + * and sysfs never has to spin up a CUDA context. + */ +struct gpu_runtime_attributes { + bool hw_decomp{false}; ///< Hardware-accelerated decompression engine present. +}; + /** * @brief GPU information. */ @@ -25,7 +37,9 @@ struct gpu_topology_info { std::vector cpu_cores; ///< List of CPU core IDs. std::vector memory_binding; ///< NUMA nodes for memory binding. std::vector network_devices; ///< Network devices (NICs) optimal for this GPU. - bool hw_decompression_available{false}; ///< Hardware-accelerated decompression engine present. + std::optional + runtime_attributes; ///< Runtime attributes (populated only when explicitly requested; empty + ///< means "not queried", not "unsupported"). }; /** @@ -192,11 +206,43 @@ class topology_discovery { * This method performs the actual discovery of GPUs, NUMA nodes, CPU affinity, * and network devices. It must be called before `get_topology()`. * + * By default this call uses only NVML and Linux sysfs and therefore does not + * touch the CUDA driver. Set @p with_runtime_attributes to true to also + * populate per-hardware runtime attributes (e.g. `gpu_runtime_attributes`), + * which queries CUDA driver device attributes. + * * @param net_verification Controls how strictly network devices are validated. + * @param with_runtime_attributes If true, also discover runtime attributes for + * each hardware class (see `discover_runtime_attributes`). Defaults to false so + * that discovery does not touch the CUDA driver. When true, the caller must + * have already initialized the CUDA driver API (see + * `discover_runtime_attributes` for the exact precondition). * @return true if discovery was successful, false otherwise. */ [[nodiscard]] bool discover( - NetworkDeviceVerification net_verification = NetworkDeviceVerification::EXISTS_ACTIVE_IP); + NetworkDeviceVerification net_verification = NetworkDeviceVerification::EXISTS_ACTIVE_IP, + bool with_runtime_attributes = false); + + /** + * @brief Discover runtime attributes for each hardware class in @p topology. + * + * Populates the `runtime_attributes` field of each entry in `topology.gpus` + * (and, in the future, other hardware classes). This is the only path in this + * component that issues CUDA driver calls — every other discovery step relies + * solely on NVML and sysfs. + * + * Safe to call multiple times; existing runtime attribute values are + * overwritten. + * + * @pre The CUDA driver API has already been initialized by the caller — + * either via an explicit `cuInit(0)` or via any prior CUDA runtime call that + * transitively initializes the driver. This function does not call `cuInit` + * and does not create a CUDA context; per-GPU queries that fail (e.g. + * because the driver is uninitialized) leave `hw_decomp` as `false`. + * + * @param topology Topology to enrich in place. + */ + static void discover_runtime_attributes(system_topology_info& topology); /** * @brief Get the discovered topology information. diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index f8f2d2ac..d6beb32a 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -27,10 +27,16 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/s3rdma/s3rdma_ioctx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/uring/uring_ioctx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/uring/uring_reactor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/kvikio/kvikio_context.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rest/s3/sigv4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rest/s3/sigv4_authorizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rest/s3/list_parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cache/types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cache/metadata_store.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cache/prefetching_cache.cpp) + +# kvikIO reaches the environment only via libcudf's dependency closure, so the +# fallback ioctx is built only alongside the cudf layer. +if(CUCASCADE_BUILD_CUDF) + target_sources(cucascade_io_objects + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/kvikio/kvikio_context.cpp) +endif() diff --git a/src/io/datasource_factory.cpp b/src/io/datasource_factory.cpp index b5c52ed2..c2a0bf9a 100644 --- a/src/io/datasource_factory.cpp +++ b/src/io/datasource_factory.cpp @@ -19,7 +19,9 @@ #include #include #include +#ifdef CUCASCADE_HAS_KVIKIO #include +#endif #include #include #include @@ -78,6 +80,7 @@ std::shared_ptr make_s3_authorizer(const object_store_ using scheme_checker_type = io_context_registry::scheme_checker_type; using factory_type = io_context_registry::factory_type; +#ifdef CUCASCADE_HAS_KVIKIO factory_type make_kvikio_ioctx_factory() { return [](const io_config& config) -> std::shared_ptr { @@ -91,6 +94,7 @@ factory_type make_kvikio_ioctx_factory() } }; } +#endif factory_type make_uring_ioctx_factory( cucascade::memory::memory_reservation_manager& reservation_manager) @@ -160,11 +164,14 @@ io_context_registry::io_context_registry( // uring / rest claim paths via their reactor's static supports() (local // files and s3:// URLs respectively). kvikio is the universal fallback — // it can open any local path — so it matches everything and lookup_path - // defers it behind the explicit backends. + // defers it behind the explicit backends. Without kvikIO (a cudf-free + // build) there is no catch-all and unmatched paths resolve to nothing. +#ifdef CUCASCADE_HAS_KVIKIO _entries.emplace( io_context_type::kvikio, entry{ io_context_type::kvikio, [](std::string_view) { return true; }, make_kvikio_ioctx_factory()}); +#endif _entries.emplace(io_context_type::uring, entry{io_context_type::uring, &uring::uring_reactor::supports, diff --git a/src/memory/topology_discovery.cpp b/src/memory/topology_discovery.cpp index c0445703..122bc29f 100644 --- a/src/memory/topology_discovery.cpp +++ b/src/memory/topology_discovery.cpp @@ -5,10 +5,8 @@ #include -#include -#include +#include -#include #include #include #include @@ -61,25 +59,43 @@ void report_nvml_error(nvmlReturn_t result, std::string const& context) } /** - * @brief Query whether a CUDA device supports hardware-accelerated decompression. + * @brief Query whether a GPU has a hardware-accelerated decompression engine. * - * Delegates to `rmm::detail::hwdecompress::is_supported()`, which checks the CUDA - * driver version. RMM's capability queries are scoped to the current device, so the - * call is wrapped in an `rmm::cuda_set_device_raii`. Best-effort: any failure while - * setting the device or probing yields false. + * The device is identified by PCI bus id rather than by ordinal. Device ordinals are + * not a stable identity across APIs: NVML enumerates in PCI-bus order while the CUDA + * runtime defaults to `CUDA_DEVICE_ORDER=FASTEST_FIRST`, so the index of a GPU in + * this discovery's list need not name the same device to CUDA on a heterogeneous + * host. `cuDeviceGetByPCIBusId` sidesteps both that reordering and any + * `CUDA_VISIBLE_DEVICES` remapping. * - * @param cuda_ordinal CUDA device ordinal (matches the runtime device index used - * elsewhere in discovery under the same CUDA_VISIBLE_DEVICES ordering). - * @return true iff the hardware decompression engine is available. + * `cuDeviceGetAttribute` takes its device explicitly, so no context is created and + * the calling thread's current device is left untouched. + * + * Best-effort: a bus id CUDA does not expose (e.g. masked out by + * `CUDA_VISIBLE_DEVICES`) or an attribute unsupported by the running driver both + * yield false. + * + * @note The caller is responsible for having invoked `cuInit(0)` beforehand. + * + * @param pci_bus_id PCI bus id of the GPU, in NVML's `domain:bus:device.function` + * form. For a MIG instance this is the parent physical GPU's bus id, which is the + * correct scope: the decompression engine is a property of the physical device. + * @return true iff the device reports at least one hardware decompression algorithm. */ -bool query_hw_decompression(unsigned int cuda_ordinal) +bool query_hw_decompression(std::string const& pci_bus_id) { - try { - rmm::cuda_set_device_raii set_device{rmm::cuda_device_id{static_cast(cuda_ordinal)}}; - return rmm::detail::hwdecompress::is_supported(); - } catch (...) { + if (pci_bus_id.empty()) { return false; } + + CUdevice device = 0; + if (cuDeviceGetByPCIBusId(&device, pci_bus_id.c_str()) != CUDA_SUCCESS) { return false; } + + int algorithm_mask = 0; + if (cuDeviceGetAttribute(&algorithm_mask, + CU_DEVICE_ATTRIBUTE_MEM_DECOMPRESS_ALGORITHM_MASK, + device) != CUDA_SUCCESS) { return false; } + return algorithm_mask != 0; } /** @@ -99,7 +115,6 @@ std::string read_file_content(std::string const& path) std::stringstream buffer; buffer << file.rdbuf(); std::string content = buffer.str(); - // Trim trailing newline if (!content.empty() && content.back() == '\n') { content.pop_back(); } return content; } @@ -125,14 +140,12 @@ std::vector parse_cpu_list(std::string const& cpulist) while (std::getline(iss, token, ',')) { size_t dash_pos = token.find('-'); if (dash_pos != std::string::npos) { - // Range, e.g., "0-31" int start = std::stoi(token.substr(0, dash_pos)); int end = std::stoi(token.substr(dash_pos + 1)); for (int i = start; i <= end; ++i) { cores.push_back(i); } } else { - // Single core, e.g., "5" cores.push_back(std::stoi(token)); } } @@ -158,7 +171,6 @@ std::string normalize_pci_bus_id(std::string const& pci_bus_id) std::string domain = pci_bus_id.substr(0, colon_pos); if (domain.length() > 4) { domain = domain.substr(domain.length() - 4); } - // Convert to lowercase std::string normalized_id = domain + pci_bus_id.substr(colon_pos); std::ranges::transform(normalized_id, normalized_id.begin(), ::tolower); @@ -381,7 +393,6 @@ PciePathType get_pcie_path_type(std::string const& gpu_pci_id, std::string const std::string gpu_norm = normalize_pci_bus_id(gpu_pci_id); std::string nic_norm = normalize_pci_bus_id(nic_pci_id); - // Read NUMA nodes int gpu_numa = -1, nic_numa = -1; std::string gpu_numa_str = read_file_content("/sys/bus/pci/devices/" + gpu_norm + "/numa_node"); std::string nic_numa_str = read_file_content("/sys/bus/pci/devices/" + nic_norm + "/numa_node"); @@ -389,7 +400,6 @@ PciePathType get_pcie_path_type(std::string const& gpu_pci_id, std::string const if (!gpu_numa_str.empty()) { gpu_numa = std::stoi(gpu_numa_str); } if (!nic_numa_str.empty()) { nic_numa = std::stoi(nic_numa_str); } - // If different NUMA nodes, it's a SYS connection if (gpu_numa != nic_numa && gpu_numa >= 0 && nic_numa >= 0) { return PciePathType::SYS; } // Use PCI bus number proximity as a heuristic for connection quality @@ -564,7 +574,6 @@ std::vector discover_network_devices_with_topology( NetworkDeviceWithTopology dev; dev.name = entry.path().filename().string(); - // Get device's NUMA node and PCI bus ID std::string numa_path = entry.path().string() + "/device/numa_node"; std::string numa_str = read_file_content(numa_path); dev.numa_node = numa_str.empty() ? -1 : std::stoi(numa_str); @@ -594,7 +603,6 @@ std::vector discover_storage_devices_with_topology() dev.name = entry.path().filename().string(); dev.type = StorageDriveType::NVME; - // Get device's NUMA node and PCI bus ID std::string numa_path = entry.path().string() + "/device/numa_node"; std::string numa_str = read_file_content(numa_path); dev.numa_node = numa_str.empty() ? -1 : std::stoi(numa_str); @@ -628,7 +636,6 @@ std::vector map_network_devices_to_gpu( { std::vector mapped_devices; - // Structure to hold NIC with its topology path type struct NicWithPath { std::string name; PciePathType path_type; @@ -636,7 +643,6 @@ std::vector map_network_devices_to_gpu( std::vector nics_with_paths; - // Query topology distance for each NIC for (auto const& dev : network_devices) { if (dev.pci_bus_id.empty()) { continue; // Skip devices without PCI info @@ -649,7 +655,6 @@ std::vector map_network_devices_to_gpu( nics_with_paths.push_back(nic); } - // Find the best (lowest) path type if (nics_with_paths.empty()) { return mapped_devices; } PciePathType best_path_type = PciePathType::SYS; @@ -657,19 +662,16 @@ std::vector map_network_devices_to_gpu( if (nic.path_type < best_path_type) { best_path_type = nic.path_type; } } - // Return all NICs with the best path type for (auto const& nic : nics_with_paths) { if (nic.path_type == best_path_type) { mapped_devices.push_back(nic.name); } } - // If no devices found, fall back to NUMA-based mapping if (mapped_devices.empty()) { for (auto const& dev : network_devices) { if (dev.numa_node == gpu_numa_node) { mapped_devices.push_back(dev.name); } } } - // Last resort: return all devices if (mapped_devices.empty() && !network_devices.empty()) { for (auto const& dev : network_devices) { mapped_devices.push_back(dev.name); @@ -827,7 +829,8 @@ nvmlReturn_t initialize_nvml_for_current_process() } // namespace -bool topology_discovery::discover(NetworkDeviceVerification net_verification) +bool topology_discovery::discover(NetworkDeviceVerification net_verification, + bool with_runtime_attributes) { system_topology_info topology; // NVML is initialized exactly once per process. Calling nvmlInit_v2 + @@ -848,7 +851,6 @@ bool topology_discovery::discover(NetworkDeviceVerification net_verification) // Continue anyway to report system info even without GPUs } - // Get GPU count unsigned int device_count = 0; bool nvml_available = false; if (result == NVML_SUCCESS) { @@ -861,18 +863,15 @@ bool topology_discovery::discover(NetworkDeviceVerification net_verification) } } - // Discover network devices std::vector network_devices_with_topology = discover_network_devices_with_topology(net_verification); - // Get system information topology.hostname = get_hostname(); topology.numa_nodes = discover_numa_nodes(); topology.num_numa_nodes = static_cast(topology.numa_nodes.size()); topology.num_gpus = device_count; topology.num_network_devices = static_cast(network_devices_with_topology.size()); - // Convert network devices to public format topology.network_devices.clear(); for (auto const& dev : network_devices_with_topology) { network_device_info info; @@ -884,7 +883,6 @@ bool topology_discovery::discover(NetworkDeviceVerification net_verification) topology.storage_devices = discover_storage_devices_with_topology(); - // Collect GPU information topology.gpus.clear(); std::vector nvml_gpus; @@ -995,17 +993,35 @@ bool topology_discovery::discover(NetworkDeviceVerification net_verification) for (size_t visible_idx = 0; visible_idx < visible_indices.size(); ++visible_idx) { size_t nvml_idx = visible_indices[visible_idx]; if (nvml_idx >= nvml_gpus.size()) { continue; } - auto gpu = nvml_gpus[nvml_idx]; - gpu.id = static_cast(visible_idx); - gpu.hw_decompression_available = query_hw_decompression(gpu.id); + auto gpu = nvml_gpus[nvml_idx]; + gpu.id = static_cast(visible_idx); topology.gpus.push_back(std::move(gpu)); } // Do not call nvmlShutdown here — NVML is initialized once per process via // the static-local in this function. See the comment at the top of discover(). + if (with_runtime_attributes) { discover_runtime_attributes(topology); } + _topology = std::move(topology); return true; } +void topology_discovery::discover_runtime_attributes(system_topology_info& topology) +{ + // Currently only GPUs expose runtime attributes. New hardware classes should + // be enriched here so callers have a single entry point that isolates the + // "needs a CUDA driver call" side of discovery from the passive NVML/sysfs + // side handled by discover(). + // + // Precondition: the CUDA driver API has already been initialized by the + // caller (cuInit(0), or any prior CUDA runtime call that transitively did so). + // This function does not call cuInit and does not create a CUDA context. + for (auto& gpu : topology.gpus) { + gpu_runtime_attributes attrs; + attrs.hw_decomp = query_hw_decompression(gpu.pci_bus_id); + gpu.runtime_attributes = attrs; + } +} + } // namespace cucascade::memory diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0c5eb19b..7fcbd341 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -65,7 +65,6 @@ if(NOT CUCASCADE_TOPOLOGY_ONLY AND CUCASCADE_BUILD_IO) io/test_dispatch_failure_hook.cpp io/test_uri_parser.cpp io/cache/test_metadata_store.cpp - io/kvikio/test_kvikio_config.cpp io/rest/test_object_store_lister.cpp io/rest/test_rest_footer_resolve.cpp io/rest/test_rest_perf_snapshot.cpp @@ -76,6 +75,12 @@ if(NOT CUCASCADE_TOPOLOGY_ONLY AND CUCASCADE_BUILD_IO) io/rest/s3/test_static_credentials.cpp # Main test runner unittest.cpp) + + # kvikIO-backed sources exist only in a cudf build; see src/io/CMakeLists.txt. + if(CUCASCADE_BUILD_CUDF) + target_sources(cucascade_io_tests PRIVATE io/kvikio/test_kvikio_config.cpp) + endif() + set_target_properties(cucascade_io_tests PROPERTIES CUDA_STANDARD 20 CUDA_STANDARD_REQUIRED ON)