-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathalien_reco.cpp
More file actions
237 lines (187 loc) · 8.51 KB
/
Copy pathalien_reco.cpp
File metadata and controls
237 lines (187 loc) · 8.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#include <cuda_runtime_api.h>
#include <tbb/task_arena.h>
#include <cstddef>
#include <iostream>
#include "CoroutineTests/alien/counting_scope.hpp"
#include "CoroutineTests/alien/subtool.hpp"
#include "CoroutineTests/alien/sync_wait.hpp"
#include "CoroutineTests/alien/tool.hpp"
#include "logging_utils.hpp" // log, format_name
#define ERROR_CHECK_CUDA(EXP) \
do { \
cudaError_t errorCode = EXP; \
if (errorCode != cudaSuccess) { \
throw std::runtime_error( \
std::format("CUDA error at {}:{}: {}", __FILE__, __LINE__, \
cudaGetErrorString(errorCode))); \
} \
} while (false)
using namespace CoroutineTests::alien;
/// Awaitable that resumes a coroutine when a CUDA stream reaches a certain
/// point. Internally cudaLaunchHostFunc is used to set up a resumption callback
/// on the stream.
class StreamAwaitable {
public:
StreamAwaitable(cudaStream_t stream) : m_stream(stream) {}
bool await_ready() const noexcept { return false; }
template <typename Promise>
void await_suspend(std::coroutine_handle<Promise> handle) {
m_error = cudaLaunchHostFunc(m_stream, resumption_callback<Promise>,
handle.address());
// If the callback couldn't be registered, we need to reschedule the
// coroutine immediately to avoid deadlock.
if (m_error != cudaSuccess) {
handle.promise().reschedule();
}
}
cudaError_t await_resume() const noexcept { return m_error; }
private:
cudaStream_t m_stream;
cudaError_t m_error = cudaSuccess;
template <typename Promise>
static void resumption_callback(void* userData) {
auto handle = std::coroutine_handle<Promise>::from_address(userData);
handle.promise().reschedule();
}
};
template <typename T>
struct DeviceBuffer {
T* ptr = nullptr;
std::size_t size = 0;
};
subtool::Task<DeviceBuffer<int>> clusterization(DeviceBuffer<int> cells,
cudaStream_t stream,
std::string_view parent) {
const auto self = format_name(parent, "clusterization");
log(self) << "Starting clusterization" << std::endl;
const auto nCells = static_cast<int>(cells.size);
// Copy cells back to host to count non-zero entries
auto h_cells = std::vector<int>(nCells);
ERROR_CHECK_CUDA(cudaMemcpyAsync(h_cells.data(), cells.ptr,
nCells * sizeof(int),
cudaMemcpyDeviceToHost, stream));
ERROR_CHECK_CUDA(co_await StreamAwaitable{stream});
auto nClusters = 0;
for (auto v : h_cells)
if (v != 0)
++nClusters;
log(self) << "Found " << nClusters << " clusters" << std::endl;
// Allocate clusters of appropiate size on device
int* d_clusters = nullptr;
ERROR_CHECK_CUDA(cudaMallocAsync(reinterpret_cast<void**>(&d_clusters),
nClusters * sizeof(int), stream));
// Write some dummy data to the clusters buffer to simulate work
ERROR_CHECK_CUDA(
cudaMemsetAsync(d_clusters, 0, nClusters * sizeof(int), stream));
ERROR_CHECK_CUDA(
cudaMemsetAsync(d_clusters, 1, nClusters / 2 * sizeof(int), stream));
co_return DeviceBuffer<int>{d_clusters,
static_cast<std::size_t>(nClusters)};
}
subtool::Task<DeviceBuffer<int>> seeding(DeviceBuffer<int> clusters,
cudaStream_t stream,
std::string_view parent) {
const auto self = format_name(parent, "seeding");
log(self) << "Starting seeding" << std::endl;
const auto nClusters = static_cast<int>(clusters.size);
// Copy clusters to host to count non-zero entries
auto h_clusters = std::vector<int>(nClusters);
ERROR_CHECK_CUDA(cudaMemcpyAsync(h_clusters.data(), clusters.ptr,
nClusters * sizeof(int),
cudaMemcpyDeviceToHost, stream));
ERROR_CHECK_CUDA(co_await StreamAwaitable{stream});
int nSeeds = 0;
for (auto v : h_clusters)
if (v != 0)
++nSeeds;
log(self) << "Found " << nSeeds << " seeds" << std::endl;
// Allocate clusters of appropiate size on device
int* d_seeds = nullptr;
ERROR_CHECK_CUDA(cudaMallocAsync(reinterpret_cast<void**>(&d_seeds),
nSeeds * sizeof(int), stream));
// Write some dummy data to the clusters buffer to simulate work
ERROR_CHECK_CUDA(cudaMemsetAsync(d_seeds, 0, nSeeds * sizeof(int), stream));
ERROR_CHECK_CUDA(
cudaMemsetAsync(d_seeds, 1, nSeeds / 2 * sizeof(int), stream));
co_return DeviceBuffer<int>{d_seeds, static_cast<std::size_t>(nSeeds)};
}
tool::Task<tool::StatusCode> reconstruct(cudaStream_t stream,
std::string_view parent) {
const auto self = format_name(parent, "reconstruction");
log(self) << "Starting reconstruction" << std::endl;
// Allocate some dummy input data on the device
auto cells = DeviceBuffer<int>{nullptr, 1000};
ERROR_CHECK_CUDA(cudaMallocAsync(reinterpret_cast<void**>(&cells.ptr),
cells.size * sizeof(int), stream));
ERROR_CHECK_CUDA(
cudaMemsetAsync(cells.ptr, 1, cells.size * sizeof(int), stream));
// Run the clusterization and seeding steps
auto clusters = co_await clusterization(cells, stream, self);
auto seeds = co_await seeding(clusters, stream, self);
// Cleanup
ERROR_CHECK_CUDA(cudaFreeAsync(cells.ptr, stream));
ERROR_CHECK_CUDA(cudaFreeAsync(clusters.ptr, stream));
ERROR_CHECK_CUDA(cudaFreeAsync(seeds.ptr, stream));
ERROR_CHECK_CUDA(co_await StreamAwaitable{stream});
log(self) << "Finishing reconstruction" << std::endl;
co_return tool::StatusCode::SUCCESS;
}
int main() {
int deviceCount = 0;
auto error_id = cudaGetDeviceCount(&deviceCount);
if (error_id != cudaSuccess) {
std::cout << "cudaGetDeviceCount returned "
<< static_cast<int>(error_id) << "\n"
<< cudaGetErrorString(error_id) << "\n";
return EXIT_FAILURE;
}
if (deviceCount == 0) {
std::cout << "No CUDA devices found.\n";
return EXIT_FAILURE;
}
log() << "main Starting" << std::endl;
tbb::task_arena task_arena{2};
auto scheduler = [&task_arena](std::coroutine_handle<> handle) {
task_arena.enqueue([handle]() { handle.resume(); });
};
{
cudaStream_t stream;
ERROR_CHECK_CUDA(cudaStreamCreate(&stream));
std::cout << "--- Single event, synchronous wait for completion ---\n";
log() << "main Launching algorithm..." << std::endl;
auto status = sync_wait(scheduler, reconstruct(stream, "main"));
log() << "main Final status of algorithm " << status << "" << std::endl;
ERROR_CHECK_CUDA(cudaStreamDestroy(stream));
}
{
std::cout << "--- Multiple events, wait for all to complete ---\n";
auto streams = std::vector<cudaStream_t>(2);
auto status = std::vector<tool::StatusCode>(streams.size());
for (auto& stream : streams) {
ERROR_CHECK_CUDA(cudaStreamCreate(&stream));
}
auto scope = counting_scope{};
log() << "main Launching algorithms..." << std::endl;
auto payload = [](std::vector<cudaStream_t> streams,
std::vector<tool::StatusCode>& statuses,
int i) -> tool::Task<void> {
const auto name = std::format("event{}:main", i);
auto& stream = streams.at(i);
auto& status = statuses.at(i);
status = co_await reconstruct(stream, name);
};
for (std::size_t i = 0; i < streams.size(); ++i) {
scope.spawn(scheduler, payload(streams, status, i));
}
scope.join();
for (auto& stream : streams) {
ERROR_CHECK_CUDA(cudaStreamDestroy(stream));
}
log() << "main All algorithms completed. Final statuses: ";
for (auto s : status) {
std::cout << s << ' ';
}
std::cout << std::endl;
}
return 0;
}