Skip to content

Commit f6bb20a

Browse files
[executorch][muse] Draft CUDA DFlash decode from pending tokens (#24041)
This PR was created by the merge bot to help merge the original PR into the main branch. ghstack PR number: #23983 by @Gasoonjia ^ Please use this as the source of truth for the PR details, comments, and reviews ghstack PR base: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/82/base ghstack PR head: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/82/head Merge bot PR base: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/81/orig Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/82/orig Differential Revision: [D125705715](https://our.internmc.facebook.com/intern/diff/D125705715/) @diff-train-skip-merge Co-authored-by: gasoonjia <gasoonjia@icloud.com>
1 parent f4d695c commit f6bb20a

6 files changed

Lines changed: 815 additions & 17 deletions

File tree

‎.github/workflows/cuda.yml‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,8 @@ jobs:
607607
cmake -S "${PWD}/examples/models/muse-glimmer" -B "${BATCHING_BIN_DIR}" \
608608
-DMUSE_GLIMMER_BUILD_BATCHING_TESTS=ON
609609
bash .ci/scripts/build_muse_glimmer_batching_smoke.sh "${BATCHING_BIN_DIR}"
610-
cmake --build "${BATCHING_BIN_DIR}" --target muse_glimmer_batching_test --parallel
610+
cmake --build "${BATCHING_BIN_DIR}" \
611+
--target muse_glimmer_batching_test muse_glimmer_cuda_dflash_test --parallel
611612
ctest --test-dir "${BATCHING_BIN_DIR}" \
612613
-R '^muse_glimmer_batching$' --output-on-failure --no-tests=error
613614
test -x "${BATCHING_BIN_DIR}/muse_glimmer_batching_runner"
@@ -619,8 +620,23 @@ jobs:
619620
python -m pytest -v -o "addopts=" \
620621
examples/models/muse-glimmer/tests/test_cuda_batching_pipeline.py \
621622
examples/models/muse-glimmer/tests/test_cuda_dflash_packed.py \
623+
examples/models/muse-glimmer/tests/test_dflash_token_sampler.py \
622624
examples/models/muse-glimmer/tests/test_checkpoint_context.py
623625
626+
# CUDA DFlash executor on a tiny exported target + draft. The native
627+
# tests skip without the artifact, so fail if any skipped.
628+
export ET_MUSE_DFLASH_TINY_DIR="${RUNNER_TEMP:-/tmp}/muse_glimmer_dflash_tiny"
629+
python examples/models/muse-glimmer/tests/export_cuda_dflash_tiny.py \
630+
--output-dir "${ET_MUSE_DFLASH_TINY_DIR}"
631+
ctest --test-dir "${BATCHING_BIN_DIR}" -R '^muse_glimmer_cuda_dflash$' \
632+
--output-on-failure --no-tests=error -V > /tmp/muse_glimmer_cuda_dflash.log 2>&1 \
633+
|| { cat /tmp/muse_glimmer_cuda_dflash.log; exit 1; }
634+
cat /tmp/muse_glimmer_cuda_dflash.log
635+
if grep -q "SKIPPED" /tmp/muse_glimmer_cuda_dflash.log; then
636+
echo "::error::muse_glimmer_cuda_dflash_test skipped"
637+
exit 1
638+
fi
639+
624640
unittest-cuda-quantized-gemm:
625641
name: unittest-cuda-quantized-gemm (${{ matrix.shard }})
626642
needs: [changed-files, run-decision]

‎examples/models/muse-glimmer/CMakeLists.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,17 @@ if(MUSE_GLIMMER_BUILD_BATCHING OR MUSE_GLIMMER_BUILD_BATCHING_TESTS)
366366
target_compile_definitions(gtest PUBLIC GTEST_HAS_POSIX_RE=0)
367367
add_executable(
368368
muse_glimmer_cuda_dflash_test tests/cuda_dflash_executor_test.cpp
369+
tests/cuda_dflash_native_test.cpp
369370
)
370371
target_link_libraries(
371372
muse_glimmer_cuda_dflash_test PRIVATE muse_glimmer_batching_runtime
372373
GTest::gtest_main
373374
)
375+
if(NOT APPLE AND NOT MSVC)
376+
target_link_options(
377+
muse_glimmer_cuda_dflash_test PRIVATE "LINKER:--export-dynamic"
378+
)
379+
endif()
374380
add_test(NAME muse_glimmer_cuda_dflash
375381
COMMAND muse_glimmer_cuda_dflash_test
376382
)

‎examples/models/muse-glimmer/runtime/cuda_dflash_executor.cpp‎

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,7 @@ std::optional<SessionId> MuseGlimmerCudaDFlashExecutor::clone(
635635
#endif
636636
State copy{*target_id, *draft_id};
637637
copy.frontier = upto;
638+
copy.pending = upto == state.frontier ? state.pending : std::nullopt;
638639
copy.hidden = state.hidden;
639640
if (!df::trim_hidden(copy.hidden, row_bytes_, draft_position, upto)) {
640641
target_->cache_control().seq_rm(*target_id);
@@ -680,6 +681,7 @@ bool MuseGlimmerCudaDFlashExecutor::rewind(State& state, int position) {
680681
return false;
681682
}
682683
state.frontier = position;
684+
state.pending.reset();
683685
return true;
684686
}
685687

@@ -887,7 +889,8 @@ bool MuseGlimmerCudaDFlashExecutor::draft(
887889

888890
bool MuseGlimmerCudaDFlashExecutor::speculate(
889891
const batching::BatchInput& batch,
890-
batching::BatchOutput& out) {
892+
batching::BatchOutput& out,
893+
bool pending_decode) {
891894
std::size_t cursor = 0;
892895
while (cursor < batch.inputs.size()) {
893896
std::vector<SessionId> owners;
@@ -899,8 +902,9 @@ bool MuseGlimmerCudaDFlashExecutor::speculate(
899902
auto& state = sessions_.at(input.sid);
900903
const auto end =
901904
static_cast<std::int64_t>(input.position) + input.offset + input.size;
902-
if (!out.outputs[cursor] || end != state.frontier ||
903-
end > max_context_ - block_) {
905+
const int seed_rows = pending_decode ? 1 : 0;
906+
if (!out.outputs[cursor] || end != state.frontier + seed_rows ||
907+
end - seed_rows > max_context_ - block_) {
904908
++cursor;
905909
continue;
906910
}
@@ -917,7 +921,10 @@ bool MuseGlimmerCudaDFlashExecutor::speculate(
917921
}
918922
context += static_cast<int>(rows);
919923
owners.push_back(input.sid);
920-
anchors.push_back(out.outputs[cursor]->tokens.front());
924+
anchors.push_back(
925+
pending_decode ? (*std::get<batching::TokenInputPtr>(
926+
input.payload))[input.offset]
927+
: out.outputs[cursor]->tokens.front());
921928
indices.push_back(cursor++);
922929
}
923930
if (owners.empty()) {
@@ -982,9 +989,9 @@ bool MuseGlimmerCudaDFlashExecutor::speculate(
982989
return false;
983990
}
984991
state.frontier = committed;
985-
// The regular preamble's prediction is new output, now committed by
986-
// verification. Append only predictions after that anchor. The last
987-
// prediction remains uncommitted: frontier = input.end + out.size() - 1.
992+
// Pending decode consumes Input's already-emitted token; preamble mode
993+
// consumes its new anchor. Both append only new predictions, leaving
994+
// correction/bonus pending: frontier = input.end + out.size() - 1.
988995
auto& output_tokens = out.outputs[indices[i]]->tokens;
989996
output_tokens.insert(
990997
output_tokens.end(), burst.tokens.begin(), burst.tokens.end());
@@ -1009,6 +1016,7 @@ bool MuseGlimmerCudaDFlashExecutor::fail_batch(
10091016
failed_ = true;
10101017
for (auto& item : sessions_) {
10111018
item.second.poisoned = true;
1019+
item.second.pending.reset();
10121020
if (fenced) {
10131021
item.second.hidden.clear();
10141022
}
@@ -1045,6 +1053,7 @@ bool MuseGlimmerCudaDFlashExecutor::execute(
10451053
std::vector<SessionId> token_owners;
10461054
std::unordered_map<SessionId, int> cursor;
10471055
std::vector<std::pair<SessionId, int>> rewinds;
1056+
bool pending_decode = !batch.inputs.empty();
10481057
for (const auto& input : batch.inputs) {
10491058
const auto it = sessions_.find(input.sid);
10501059
const auto* payload =
@@ -1082,6 +1091,13 @@ bool MuseGlimmerCudaDFlashExecutor::execute(
10821091
if (start < at->second) {
10831092
rewinds.emplace_back(input.sid, static_cast<int>(start));
10841093
}
1094+
// A one-token prompt/replay is not necessarily emitted decode feedback.
1095+
// Stay on the legacy path unless every owner has an exact pending seed,
1096+
// one input, and room for the complete trained draft block.
1097+
pending_decode = pending_decode && first && input.produce_output &&
1098+
input.size == 1 && start == it->second.frontier &&
1099+
start <= max_context_ - block_ && it->second.pending &&
1100+
*it->second.pending == (**payload)[input.offset];
10851101
at->second = static_cast<int>(end);
10861102
for (std::size_t j = 0; j < input.size; ++j) {
10871103
const auto token = (**payload)[input.offset + j];
@@ -1104,19 +1120,49 @@ bool MuseGlimmerCudaDFlashExecutor::execute(
11041120
return fail_batch(batch, out);
11051121
}
11061122
}
1107-
std::vector<Token> predictions;
1108-
if (!run_target(step, token_owners, predictions, true)) {
1123+
if (pending_decode) {
1124+
++stats_.pending_decode_batches;
1125+
stats_.pending_decode_owners += batch.inputs.size();
1126+
for (std::size_t i = 0; i < batch.inputs.size(); ++i) {
1127+
out.outputs[i] = batching::Output{batch.inputs[i].sid, {}};
1128+
}
1129+
} else {
1130+
std::vector<Token> predictions;
1131+
if (!run_target(step, token_owners, predictions, true)) {
1132+
return fail_batch(batch, out);
1133+
}
1134+
for (std::size_t i = 0; i < batch.inputs.size(); ++i) {
1135+
sessions_.at(batch.inputs[i].sid).pending.reset();
1136+
if (batch.inputs[i].produce_output) {
1137+
out.outputs[i] =
1138+
batching::Output{batch.inputs[i].sid, {predictions[i]}};
1139+
}
1140+
}
1141+
}
1142+
if (!speculate(batch, out, pending_decode)) {
11091143
return fail_batch(batch, out);
11101144
}
11111145
for (std::size_t i = 0; i < batch.inputs.size(); ++i) {
1112-
if (batch.inputs[i].produce_output) {
1113-
out.outputs[i] =
1114-
batching::Output{batch.inputs[i].sid, {predictions[i]}};
1146+
if (out.outputs[i]) {
1147+
const auto& tokens = out.outputs[i]->tokens;
1148+
const auto& input = batch.inputs[i];
1149+
auto& state = sessions_.at(input.sid);
1150+
const auto end = static_cast<std::int64_t>(input.position) +
1151+
input.offset + input.size;
1152+
if (tokens.empty()) {
1153+
return fail_batch(batch, out);
1154+
}
1155+
const auto expected =
1156+
end + static_cast<std::int64_t>(tokens.size()) - 1;
1157+
if (pending_decode && expected != state.frontier) {
1158+
return fail_batch(batch, out);
1159+
}
1160+
// Only the final input for a repeated owner may seed the next cycle.
1161+
if (expected == state.frontier) {
1162+
state.pending = tokens.back();
1163+
}
11151164
}
11161165
}
1117-
if (!speculate(batch, out)) {
1118-
return fail_batch(batch, out);
1119-
}
11201166
++stats_.executions;
11211167
return true;
11221168
#if ET_HAS_EXCEPTIONS

‎examples/models/muse-glimmer/runtime/cuda_dflash_executor.h‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ struct CudaDFlashStats {
9797
std::uint64_t draft_forwards = 0;
9898
std::uint64_t warmed_context_tokens = 0;
9999
// Calls are packed execute_raw attempts; forwards count slice callbacks.
100+
std::uint64_t pending_decode_batches = 0;
101+
std::uint64_t pending_decode_owners = 0;
100102
std::uint64_t target_preamble_calls = 0;
101103
std::uint64_t target_verify_calls = 0;
102104
std::uint64_t target_preamble_forwards = 0;
@@ -145,6 +147,7 @@ class MuseGlimmerCudaDFlashExecutor final : public batching::Executor {
145147
}
146148

147149
private:
150+
friend struct CudaDFlashTestPeer;
148151
using Target = ::executorch::backends::cuda::batching::CudaExecutor;
149152
using RawStep = ::executorch::backends::cuda::batching::RawTargetStep;
150153
struct State {
@@ -153,6 +156,8 @@ class MuseGlimmerCudaDFlashExecutor final : public batching::Executor {
153156
std::int32_t target_id;
154157
std::int32_t draft_id;
155158
int frontier = 0;
159+
// Last emitted correction/bonus, not yet consumed by the target cache.
160+
std::optional<batching::Token> pending;
156161
// Host-owned committed BF16 rows [draft frontier, target frontier).
157162
// Verification appends only the consumed anchor and accepted proposals.
158163
std::vector<std::uint8_t> hidden;
@@ -183,7 +188,10 @@ class MuseGlimmerCudaDFlashExecutor final : public batching::Executor {
183188
const std::vector<batching::Token>& anchors,
184189
int context_limit,
185190
std::vector<std::vector<batching::Token>>* proposals);
186-
bool speculate(const batching::BatchInput& batch, batching::BatchOutput& out);
191+
bool speculate(
192+
const batching::BatchInput& batch,
193+
batching::BatchOutput& out,
194+
bool pending_decode);
187195
bool fail_batch(
188196
const batching::BatchInput& batch,
189197
batching::BatchOutput& out);

0 commit comments

Comments
 (0)