Skip to content

Commit 206a63a

Browse files
committed
Add semaphore-aware Sequence evalAsync overloads
Allow submit-level GPU synchronization by letting Sequence submissions wait on and signal Vulkan semaphores without CPU-side waits. This keeps existing evalAsync behavior while adding validation and coverage for the new overload path. Made-with: Cursor
1 parent 7f81331 commit 206a63a

3 files changed

Lines changed: 147 additions & 3 deletions

File tree

src/Sequence.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ Sequence::eval(std::shared_ptr<OpBase> op)
108108

109109
std::shared_ptr<Sequence>
110110
Sequence::evalAsync()
111+
{
112+
return this->evalAsync({}, {}, {});
113+
}
114+
115+
std::shared_ptr<Sequence>
116+
Sequence::evalAsync(const std::vector<vk::Semaphore>& waitSemaphores,
117+
const std::vector<vk::PipelineStageFlags>& waitDstStageMasks,
118+
const std::vector<vk::Semaphore>& signalSemaphores)
111119
{
112120
if (this->isRecording()) {
113121
this->end();
@@ -125,8 +133,35 @@ Sequence::evalAsync()
125133
this->mOperations[i]->preEval(*this->mCommandBuffer);
126134
}
127135

136+
if (!waitDstStageMasks.empty() &&
137+
waitSemaphores.size() != waitDstStageMasks.size()) {
138+
throw std::runtime_error("Kompute Sequence evalAsync wait semaphore "
139+
"count must match wait dst stage mask count");
140+
}
141+
142+
std::vector<vk::PipelineStageFlags> resolvedWaitDstStageMasks =
143+
waitDstStageMasks;
144+
if (resolvedWaitDstStageMasks.empty() && !waitSemaphores.empty()) {
145+
resolvedWaitDstStageMasks.resize(waitSemaphores.size(),
146+
vk::PipelineStageFlagBits::eAllCommands);
147+
}
148+
149+
const vk::Semaphore* waitSemaphoresPtr =
150+
waitSemaphores.empty() ? nullptr : waitSemaphores.data();
151+
const vk::PipelineStageFlags* waitDstStageMasksPtr =
152+
resolvedWaitDstStageMasks.empty() ? nullptr
153+
: resolvedWaitDstStageMasks.data();
154+
const vk::Semaphore* signalSemaphoresPtr =
155+
signalSemaphores.empty() ? nullptr : signalSemaphores.data();
156+
128157
vk::SubmitInfo submitInfo(
129-
0, nullptr, nullptr, 1, this->mCommandBuffer.get());
158+
static_cast<uint32_t>(waitSemaphores.size()),
159+
waitSemaphoresPtr,
160+
waitDstStageMasksPtr,
161+
1,
162+
this->mCommandBuffer.get(),
163+
static_cast<uint32_t>(signalSemaphores.size()),
164+
signalSemaphoresPtr);
130165

131166
KP_LOG_DEBUG(
132167
"Kompute sequence submitting command buffer into compute queue");
@@ -140,11 +175,20 @@ Sequence::evalAsync()
140175

141176
std::shared_ptr<Sequence>
142177
Sequence::evalAsync(std::shared_ptr<OpBase> op)
178+
{
179+
return this->evalAsync(op, {}, {}, {});
180+
}
181+
182+
std::shared_ptr<Sequence>
183+
Sequence::evalAsync(std::shared_ptr<OpBase> op,
184+
const std::vector<vk::Semaphore>& waitSemaphores,
185+
const std::vector<vk::PipelineStageFlags>& waitDstStageMasks,
186+
const std::vector<vk::Semaphore>& signalSemaphores)
143187
{
144188
this->clear();
145189
this->record(op);
146-
this->evalAsync();
147-
return shared_from_this();
190+
return this->evalAsync(
191+
waitSemaphores, waitDstStageMasks, signalSemaphores);
148192
}
149193

150194
std::shared_ptr<Sequence>

src/include/kompute/Sequence.hpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,25 @@ class Sequence : public std::enable_shared_from_this<Sequence>
161161
* @return Boolean stating whether execution was successful.
162162
*/
163163
std::shared_ptr<Sequence> evalAsync();
164+
/**
165+
* Eval Async sends all recorded operations as a submit job and allows
166+
* submit-level GPU synchronization by providing wait and signal semaphores.
167+
* EvalAwait() must ALWAYS be called after to ensure the sequence is
168+
* terminated correctly.
169+
*
170+
* @param waitSemaphores Semaphores that must be signaled before this submit
171+
* starts executing.
172+
* @param waitDstStageMasks Pipeline stages at which to wait for each
173+
* semaphore. If empty and waitSemaphores is not empty, defaults to
174+
* vk::PipelineStageFlagBits::eAllCommands for each wait semaphore.
175+
* @param signalSemaphores Semaphores that this submit will signal when it
176+
* completes.
177+
* @return shared_ptr<Sequence> of the Sequence class itself
178+
*/
179+
std::shared_ptr<Sequence> evalAsync(
180+
const std::vector<vk::Semaphore>& waitSemaphores,
181+
const std::vector<vk::PipelineStageFlags>& waitDstStageMasks,
182+
const std::vector<vk::Semaphore>& signalSemaphores);
164183
/**
165184
* Clears currnet operations to record provided one in the vector of
166185
* operations into the gpu as a submit job without a barrier. EvalAwait()
@@ -170,6 +189,27 @@ class Sequence : public std::enable_shared_from_this<Sequence>
170189
* @return Boolean stating whether execution was successful.
171190
*/
172191
std::shared_ptr<Sequence> evalAsync(std::shared_ptr<OpBase> op);
192+
/**
193+
* Clears current operations, records the provided one and submits with
194+
* optional wait/signal semaphores for submit-level GPU synchronization.
195+
* EvalAwait() must ALWAYS be called after to ensure the sequence is
196+
* terminated correctly.
197+
*
198+
* @param op Operation to record prior to submit.
199+
* @param waitSemaphores Semaphores that must be signaled before this submit
200+
* starts executing.
201+
* @param waitDstStageMasks Pipeline stages at which to wait for each
202+
* semaphore. If empty and waitSemaphores is not empty, defaults to
203+
* vk::PipelineStageFlagBits::eAllCommands for each wait semaphore.
204+
* @param signalSemaphores Semaphores that this submit will signal when it
205+
* completes.
206+
* @return shared_ptr<Sequence> of the Sequence class itself
207+
*/
208+
std::shared_ptr<Sequence> evalAsync(
209+
std::shared_ptr<OpBase> op,
210+
const std::vector<vk::Semaphore>& waitSemaphores,
211+
const std::vector<vk::PipelineStageFlags>& waitDstStageMasks,
212+
const std::vector<vk::Semaphore>& signalSemaphores);
173213
/**
174214
* Eval sends all the recorded and stored operations in the vector of
175215
* operations into the gpu as a submit job with a barrier.

test/TestSequence.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,63 @@ TEST(TestSequence, CorrectSequenceRunningError)
243243

244244
EXPECT_EQ(tensorOut->vector(), std::vector<float>({ 2, 4, 6 }));
245245
}
246+
247+
TEST(TestSequence, EvalAsyncSemaphoreOverloadSupportsEmptySyncLists)
248+
{
249+
kp::Manager mgr;
250+
251+
std::shared_ptr<kp::Sequence> sq = mgr.sequence();
252+
253+
std::shared_ptr<kp::TensorT<float>> tensorA = mgr.tensor({ 1, 2, 3 });
254+
std::shared_ptr<kp::TensorT<float>> tensorB = mgr.tensor({ 2, 2, 2 });
255+
std::shared_ptr<kp::TensorT<float>> tensorOut = mgr.tensor({ 0, 0, 0 });
256+
257+
sq->eval<kp::OpSyncDevice>({ tensorA, tensorB, tensorOut });
258+
259+
std::vector<uint32_t> spirv = compileSource(R"(
260+
#version 450
261+
262+
layout (local_size_x = 1) in;
263+
264+
layout(set = 0, binding = 0) buffer bina { float tina[]; };
265+
layout(set = 0, binding = 1) buffer binb { float tinb[]; };
266+
layout(set = 0, binding = 2) buffer bout { float tout[]; };
267+
268+
void main() {
269+
uint index = gl_GlobalInvocationID.x;
270+
tout[index] = tina[index] * tinb[index];
271+
}
272+
)");
273+
274+
std::shared_ptr<kp::Algorithm> algo =
275+
mgr.algorithm({ tensorA, tensorB, tensorOut }, spirv);
276+
277+
sq->record<kp::OpAlgoDispatch>(algo)->record<kp::OpSyncLocal>(
278+
{ tensorA, tensorB, tensorOut });
279+
280+
EXPECT_NO_THROW(sq->evalAsync({}, {}, {}));
281+
EXPECT_NO_THROW(sq->evalAwait());
282+
283+
EXPECT_EQ(tensorOut->vector(), std::vector<float>({ 2, 4, 6 }));
284+
}
285+
286+
TEST(TestSequence, EvalAsyncSemaphoreOverloadValidatesWaitMaskCount)
287+
{
288+
kp::Manager mgr;
289+
290+
std::shared_ptr<kp::Sequence> sq = mgr.sequence();
291+
292+
std::shared_ptr<kp::TensorT<float>> tensorA = mgr.tensor({ 1, 2, 3 });
293+
294+
sq->record<kp::OpSyncDevice>({ tensorA });
295+
296+
std::vector<vk::Semaphore> waitSemaphores = { vk::Semaphore{} };
297+
std::vector<vk::PipelineStageFlags> waitDstStageMasks = {
298+
vk::PipelineStageFlagBits::eComputeShader,
299+
vk::PipelineStageFlagBits::eTransfer
300+
};
301+
std::vector<vk::Semaphore> signalSemaphores = {};
302+
303+
EXPECT_ANY_THROW(
304+
sq->evalAsync(waitSemaphores, waitDstStageMasks, signalSemaphores));
305+
}

0 commit comments

Comments
 (0)