// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) #include #endif #include #include #include #include "gtest/gtest.h" #include "paddle/phi/api/include/context_pool.h" #include "paddle/phi/backends/gpu/gpu_context.h" #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) namespace { using StreamCallbackGate = std::atomic; #ifdef PADDLE_WITH_HIP void BlockingStreamCallback(hipStream_t /*stream*/, hipError_t /*status*/, void* user_data) { auto* gate = static_cast(user_data); while (!gate->load(std::memory_order_acquire)) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void CreateRawStream(hipStream_t* stream) { C10_CUDA_CHECK(hipStreamCreate(stream)); } void DestroyRawStream(hipStream_t stream) { C10_CUDA_CHECK(hipStreamDestroy(stream)); } void ClearLastStreamError() { (void)hipGetLastError(); } #else void CUDART_CB BlockingStreamCallback(void* user_data) { auto* gate = static_cast(user_data); while (!gate->load(std::memory_order_acquire)) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } void ClearLastStreamError() { (void)cudaGetLastError(); } #endif } // namespace #endif // Test device_count() works in both CPU and CUDA builds TEST(StreamTest, DeviceCount) { c10::DeviceIndex count = c10::cuda::device_count(); #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) // In CUDA builds, should return actual device count (>= 0) EXPECT_GE(count, 0); #else // In CPU-only builds, should return 0 EXPECT_EQ(count, 0); #endif } // ==================== native_handle ==================== #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) // CUDA stream: native_handle() should return the underlying cudaStream_t // encoded as void*. For the default (null) stream the id is 0, so the // pointer is nullptr; for a real stream it must be non-null. TEST(StreamTest, NativeHandleCudaDefaultStream) { if (!at::cuda::is_available()) { return; } c10::Stream s = c10::cuda::getDefaultCUDAStream().unwrap(); // Default stream encodes nullptr (id == 0), so native_handle() == nullptr. EXPECT_EQ(s.native_handle(), nullptr); } TEST(StreamTest, NativeHandleCudaCurrentStream) { if (!at::cuda::is_available()) { return; } auto cuda_stream = c10::cuda::getCurrentCUDAStream(); c10::Stream s = cuda_stream.unwrap(); // getCurrentCUDAStream wraps the real phi stream handle; calling // native_handle() must not throw. EXPECT_NO_THROW({ (void)s.native_handle(); }); } #endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP // CPU stream: native_handle() is not supported and must throw. TEST(StreamTest, NativeHandleCpuStreamThrows) { c10::Stream cpu_stream(c10::Stream::DEFAULT, c10::Device(c10::DeviceType::CPU, 0)); EXPECT_THROW({ (void)cpu_stream.native_handle(); }, std::exception); } // ==================== query ==================== // CPU stream is always ready. TEST(StreamTest, QueryCpuStreamReturnsTrue) { c10::Stream cpu_stream(c10::Stream::DEFAULT, c10::Device(c10::DeviceType::CPU, 0)); EXPECT_TRUE(cpu_stream.query()); } #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) // A freshly-obtained CUDA stream with no pending work must report ready. TEST(StreamTest, QueryCudaStreamReady) { if (!at::cuda::is_available()) { return; } auto cuda_stream = c10::cuda::getCurrentCUDAStream(); c10::Stream s = cuda_stream.unwrap(); // synchronize first to ensure no pending work, then query should be true. EXPECT_NO_THROW(s.synchronize()); EXPECT_TRUE(s.query()); } #endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP // ==================== synchronize ==================== // CPU stream: synchronize() is a no-op and must not throw. TEST(StreamTest, SynchronizeCpuStream) { c10::Stream cpu_stream(c10::Stream::DEFAULT, c10::Device(c10::DeviceType::CPU, 0)); EXPECT_NO_THROW(cpu_stream.synchronize()); } #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) // CUDA stream: synchronize() must complete without error. TEST(StreamTest, SynchronizeCudaStream) { if (!at::cuda::is_available()) { return; } auto cuda_stream = c10::cuda::getCurrentCUDAStream(); c10::Stream s = cuda_stream.unwrap(); EXPECT_NO_THROW(s.synchronize()); } #endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP // ==================== getDefaultCUDAStream ==================== #if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) // getDefaultCUDAStream must always return the null stream (id == 0), // which corresponds to cudaStreamDefault on the device. TEST(CUDAStreamTest, DefaultStreamIsNullStream) { if (!at::cuda::is_available()) { return; } auto default_stream = c10::cuda::getDefaultCUDAStream(); // id == 0 encodes cudaStreamDefault (the null stream, handle nullptr). EXPECT_EQ(default_stream.id(), static_cast(0)); } // getDefaultCUDAStream must be stable: calling it twice returns equal streams. TEST(CUDAStreamTest, DefaultStreamIsStable) { if (!at::cuda::is_available()) { return; } auto s1 = c10::cuda::getDefaultCUDAStream(); auto s2 = c10::cuda::getDefaultCUDAStream(); EXPECT_EQ(s1, s2); } TEST(CUDAStreamTest, GetStreamFromPoolBoolOverloadPreservesHighPriority) { if (!at::cuda::is_available()) { return; } auto low_priority_stream = c10::cuda::getStreamFromPool(/*isHighPriority=*/false); auto high_priority_stream = c10::cuda::getStreamFromPool(/*isHighPriority=*/true); auto explicit_high_priority_stream = c10::cuda::getStreamFromPool(-1); const int low_priority = low_priority_stream.priority(); const int high_priority = high_priority_stream.priority(); const int explicit_high_priority = explicit_high_priority_stream.priority(); if (low_priority == explicit_high_priority) { return; } EXPECT_EQ(high_priority, explicit_high_priority); EXPECT_NE(high_priority, low_priority); } // After setCurrentCUDAStream redirects the current stream, // getDefaultCUDAStream must still return the null stream. TEST(CUDAStreamTest, DefaultStreamUnaffectedBySetCurrentCUDAStream) { if (!at::cuda::is_available()) { return; } // Snapshot the current stream before we touch it so we can // restore it afterward and avoid polluting subsequent tests. auto original_stream = c10::cuda::getCurrentCUDAStream(); // Obtain a non-default stream from the pool. auto pool_stream = c10::cuda::getStreamFromPool(/*isHighPriority=*/false); // Redirect the current stream. c10::cuda::setCurrentCUDAStream(pool_stream); auto default_stream = c10::cuda::getDefaultCUDAStream(); auto current_stream = c10::cuda::getCurrentCUDAStream(); auto place = phi::GPUPlace(current_stream.device_index()); // Default stream is still null; current stream has changed. EXPECT_EQ(default_stream.id(), static_cast(0)); EXPECT_NE(default_stream, current_stream); EXPECT_EQ(paddle::GetCurrentCUDAStream(place)->raw_stream(), current_stream.stream()); // Restore the original current stream. c10::cuda::setCurrentCUDAStream(original_stream); EXPECT_EQ(paddle::GetCurrentCUDAStream(place)->raw_stream(), original_stream.stream()); } #endif // PADDLE_WITH_CUDA || PADDLE_WITH_HIP