Skip to content

Commit 58ca1a3

Browse files
[executorch][cuda] Expose raw packed steps on CudaExecutor (#24037)
This PR was created by the merge bot to help merge the original PR into the main branch. ghstack PR number: #23979 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/78/base ghstack PR head: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/78/head Merge bot PR base: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/77/orig Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/Gasoonjia/78/orig Differential Revision: [D125705711](https://our.internmc.facebook.com/intern/diff/D125705711/) @diff-train-skip-merge Co-authored-by: gasoonjia <gasoonjia@icloud.com>
1 parent 5467c27 commit 58ca1a3

4 files changed

Lines changed: 255 additions & 63 deletions

File tree

‎backends/cuda/batching/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ target_link_libraries(
2020
cuda_batching
2121
PUBLIC aoti_cuda_backend extension_llm_batching extension_llm_batching_util
2222
extension_llm_cache extension_module extension_tensor
23+
PRIVATE extension_llm_batching_executor_utils extension_llm_sampler
2324
)
2425

2526
install(

‎backends/cuda/batching/cuda_executor.cpp‎

Lines changed: 209 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
#include <executorch/backends/cuda/runtime/backend_options.h>
2424
#include <executorch/extension/cuda/caller_stream.h>
25+
#include <executorch/extension/llm/batching/executor_utils.h>
2526
#include <executorch/extension/llm/runner/model_metadata.h>
26-
#include <executorch/extension/tensor/tensor.h>
27+
#include <executorch/extension/llm/sampler/sampler.h>
28+
#include <executorch/extension/tensor/tensor_ptr.h>
2729
#include <executorch/runtime/backend/backend_options_map.h>
2830
#include <executorch/runtime/backend/interface.h>
2931
#include <executorch/runtime/backend/options.h>
@@ -209,6 +211,17 @@ Error set_backend_options(
209211
kCudaBackendId, backend_options.view());
210212
}
211213

214+
bool is_dense(const ::executorch::aten::Tensor& tensor) {
215+
std::int64_t stride = 1;
216+
for (auto d = tensor.dim() - 1; d >= 0; --d) {
217+
if (tensor.size(d) > 1 && tensor.strides()[d] != stride) {
218+
return false;
219+
}
220+
stride *= tensor.size(d);
221+
}
222+
return true;
223+
}
224+
212225
} // namespace
213226

214227
CudaExecutor::CudaExecutor(
@@ -516,6 +529,7 @@ bool CudaExecutor::initialize() {
516529
return false;
517530
}
518531
}
532+
initialized_ = true;
519533
return true;
520534
}
521535

@@ -538,86 +552,221 @@ void CudaExecutor::set_sampling(
538552
sessions_.set_sampling(session, params, seed);
539553
}
540554

541-
bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
542-
out.outputs.clear();
543-
out.outputs.resize(batch.inputs.size());
544-
545-
const Result<llm_batching::util::PackedStep> step = sessions_.pack(batch);
546-
if (!step.ok()) {
555+
bool CudaExecutor::execute_raw(
556+
const RawTargetStep& step,
557+
const std::function<bool(const RawTargetOutput&)>& consume) {
558+
if (!initialized_ || raw_executing_ || !consume ||
559+
step.tokens.size() >
560+
static_cast<std::size_t>(std::numeric_limits<int>::max()) ||
561+
step.positions.size() != step.tokens.size() ||
562+
step.seq_ids.size() != step.tokens.size()) {
547563
return false;
548564
}
549-
550-
// In order, so a slice attends the cells its predecessors wrote. Each
551-
// input's logits row falls in exactly one slice.
552-
const int total = static_cast<int>(step->tokens.size());
553-
for (const StepSlice& slice : plan_slices(total, methods_)) {
565+
const int total = static_cast<int>(step.tokens.size());
566+
for (int i = 0; i < total; ++i) {
567+
if (step.tokens[i] < 0 || step.tokens[i] >= vocab_size_ ||
568+
step.positions[i] < 0 || step.positions[i] >= max_session_tokens_) {
569+
return false;
570+
}
571+
}
572+
for (const auto index : step.logit_indices) {
573+
if (index < -1 || index >= total) {
574+
return false;
575+
}
576+
}
577+
struct Reset {
578+
explicit Reset(bool& target) : flag(target) {
579+
flag = true;
580+
}
581+
Reset(const Reset&) = delete;
582+
Reset& operator=(const Reset&) = delete;
583+
Reset(Reset&&) = delete;
584+
Reset& operator=(Reset&&) = delete;
585+
~Reset() {
586+
flag = false;
587+
}
588+
bool& flag;
589+
} reset(raw_executing_);
590+
for (const auto& slice : plan_slices(total, methods_)) {
554591
const int off = slice.offset;
555592
const int n = slice.length;
556-
const ForwardMethod& method = methods_[slice.method];
557-
// Only the real tokens are declared; the cache sends a static method's
558-
// padding rows to its scratch row.
593+
const auto& method = methods_[slice.method];
594+
std::vector<std::int64_t> selected;
595+
std::vector<std::size_t> indices;
596+
for (std::size_t i = 0; i < step.logit_indices.size(); ++i) {
597+
const auto index = step.logit_indices[i];
598+
if (index >= off && index < off + n) {
599+
selected.push_back(index - off);
600+
indices.push_back(i);
601+
}
602+
}
603+
// Static selector widths are fixed; dynamic selectors have an independent
604+
// axis but are still bounded by the exported maximum step.
605+
const auto row_limit = static_cast<std::size_t>(
606+
method.is_static ? slice.width : method.max_tokens);
607+
if (selected.size() > row_limit) {
608+
ET_LOG(
609+
Error,
610+
"CudaExecutor: too many raw selectors for %s",
611+
method.name.c_str());
612+
return false;
613+
}
614+
const std::size_t rows = method.is_static
615+
? slice.width
616+
: std::max<std::size_t>(selected.size(), min_selected_rows_);
617+
selected.resize(rows, selected.empty() ? 0 : selected.back());
559618
if (!ctl_->declare_step(std::vector<std::int32_t>(
560-
step->seq_ids.begin() + off, step->seq_ids.begin() + off + n))) {
561-
ET_LOG(Error, "CudaExecutor: the cache refused a slice of %d", n);
619+
step.seq_ids.begin() + off, step.seq_ids.begin() + off + n))) {
562620
return false;
563621
}
564-
// Padding tokens are token 0 at position 0: valid inputs whose rows
565-
// nothing reads.
566622
std::vector<std::int64_t> token_values(
567-
step->tokens.begin() + off, step->tokens.begin() + off + n);
623+
step.tokens.begin() + off, step.tokens.begin() + off + n);
568624
std::vector<std::int64_t> position_values(
569-
step->positions.begin() + off, step->positions.begin() + off + n);
625+
step.positions.begin() + off, step.positions.begin() + off + n);
570626
token_values.resize(slice.width, 0);
571627
position_values.resize(slice.width, 0);
572628
auto tokens = make_tensor_ptr({1, slice.width}, std::move(token_values));
573629
auto positions = make_tensor_ptr({slice.width}, std::move(position_values));
574-
575-
// A static method selects exactly its width in rows, and the dynamic one
576-
// at least its exported minimum. Extra rows repeat the last real one (or
577-
// row 0 when the slice finishes no input); nothing reads them.
578-
auto selected = llm_batching::util::select_rows(*step, off, n);
579-
const std::size_t rows_needed = method.is_static
580-
? static_cast<std::size_t>(slice.width)
581-
: std::max<std::size_t>(
582-
selected.selector.size(),
583-
static_cast<std::size_t>(min_selected_rows_));
584-
const std::int64_t fill =
585-
selected.selector.empty() ? 0 : selected.selector.back();
586-
selected.selector.resize(rows_needed, fill);
587-
const int rows = static_cast<int>(selected.selector.size());
588-
auto selector = make_tensor_ptr({rows}, std::move(selected.selector));
589-
630+
auto selector = make_tensor_ptr(
631+
{static_cast<::executorch::aten::SizesType>(rows)},
632+
std::move(selected));
590633
auto result = module_->execute(method.name, {tokens, positions, selector});
591-
if (!result.ok()) {
592-
ET_LOG(
593-
Error,
594-
"CudaExecutor: %s failed with 0x%x",
595-
method.name.c_str(),
596-
static_cast<unsigned>(result.error()));
634+
if (!result.ok() || result->empty() || !result->at(0).isTensor()) {
635+
ET_LOG(Error, "CudaExecutor: raw %s failed", method.name.c_str());
597636
return false;
598637
}
599638
++calls_[slice.method];
600-
if (result->empty() || !result->at(0).isTensor()) {
601-
ET_LOG(Error, "CudaExecutor: %s returned no logits", method.name.c_str());
602-
return false;
603-
}
604-
auto logits = result->at(0).toTensor();
639+
const auto logits = result->at(0).toTensor();
605640
if (logits.dim() < 2 || logits.size(logits.dim() - 1) != vocab_size_ ||
606-
logits.numel() != static_cast<std::int64_t>(rows) * vocab_size_) {
641+
logits.numel() != static_cast<std::int64_t>(rows) * vocab_size_ ||
642+
!is_supported_logits_type(logits.scalar_type()) || !is_dense(logits)) {
607643
ET_LOG(
608644
Error,
609-
"CudaExecutor: %s returned logits that are not [%d, %d]",
645+
"CudaExecutor: %s logits are not a dense [%zu, %d] tensor",
610646
method.name.c_str(),
611647
rows,
612648
vocab_size_);
613649
return false;
614650
}
615-
if (selected.inputs.empty()) {
616-
continue;
651+
std::optional<::executorch::aten::Tensor> taps;
652+
if (result->size() > 1) {
653+
if (!result->at(1).isTensor()) {
654+
return false;
655+
}
656+
taps = result->at(1).toTensor();
657+
if (taps->dim() != 3 || taps->size(0) != 1 ||
658+
taps->size(1) != slice.width || taps->size(2) <= 0 ||
659+
!is_supported_logits_type(taps->scalar_type()) || !is_dense(*taps)) {
660+
ET_LOG(
661+
Error,
662+
"CudaExecutor: %s taps are not a dense [1, %d, H] tensor",
663+
method.name.c_str(),
664+
slice.width);
665+
return false;
666+
}
667+
}
668+
if (!consume({off, n, logits, taps ? &*taps : nullptr, indices})) {
669+
return false;
670+
}
671+
}
672+
return true;
673+
}
674+
675+
std::optional<std::vector<Token>> CudaExecutor::argmax_rows(
676+
const ::executorch::aten::Tensor& logits,
677+
std::size_t rows) {
678+
using ::executorch::aten::ScalarType;
679+
using ::executorch::aten::SizesType;
680+
if (!initialized_ || logits.dim() < 2 || !is_dense(logits) ||
681+
!is_supported_logits_type(logits.scalar_type()) ||
682+
logits.size(logits.dim() - 1) != vocab_size_ ||
683+
rows > static_cast<std::size_t>(std::numeric_limits<SizesType>::max()) ||
684+
rows > static_cast<std::size_t>(logits.numel() / vocab_size_)) {
685+
return std::nullopt;
686+
}
687+
if (rows == 0) {
688+
return std::vector<Token>{};
689+
}
690+
if (logits.device_type() != ::executorch::aten::DeviceType::CUDA) {
691+
std::vector<Token> result;
692+
metadata::Sampler sampler(vocab_size_, 0.0f, 1.0f, 1);
693+
for (std::size_t row = 0; row < rows; ++row) {
694+
auto token =
695+
llm_batching::sample_from_row(logits, static_cast<int>(row), sampler);
696+
if (!token) {
697+
return std::nullopt;
698+
}
699+
result.push_back(*token);
700+
}
701+
return result;
702+
}
703+
if (!has_argmax_ || logits.scalar_type() != ScalarType::Float) {
704+
ET_LOG(Error, "CudaExecutor: device raw logits require %s", kArgmaxMethod);
705+
return std::nullopt;
706+
}
707+
auto view = make_tensor_ptr(
708+
{static_cast<SizesType>(rows), static_cast<SizesType>(vocab_size_)},
709+
logits.mutable_data_ptr(),
710+
logits.scalar_type(),
711+
::executorch::aten::Device(
712+
::executorch::aten::DeviceType::CUDA, logits.device_index()));
713+
auto result =
714+
module_->execute(kArgmaxMethod, std::vector<runtime::EValue>{view});
715+
if (!result.ok() || result->size() != 1 || !result->at(0).isTensor()) {
716+
return std::nullopt;
717+
}
718+
const auto tokens = result->at(0).toTensor();
719+
if (tokens.scalar_type() != ScalarType::Long ||
720+
tokens.numel() != static_cast<std::int64_t>(rows)) {
721+
return std::nullopt;
722+
}
723+
std::vector<std::int64_t> host(rows);
724+
const auto stream = ::executorch::extension::cuda::getCallerStream().value_or(
725+
cudaStreamPerThread);
726+
const auto kind = tokens.device_type() == ::executorch::aten::DeviceType::CUDA
727+
? cudaMemcpyDeviceToHost
728+
: cudaMemcpyHostToHost;
729+
if (cudaMemcpyAsync(
730+
host.data(),
731+
tokens.const_data_ptr(),
732+
rows * sizeof(std::int64_t),
733+
kind,
734+
stream) != cudaSuccess ||
735+
cudaStreamSynchronize(stream) != cudaSuccess) {
736+
return std::nullopt;
737+
}
738+
if (std::any_of(host.begin(), host.end(), [this](auto token) {
739+
return token < 0 || token >= vocab_size_;
740+
})) {
741+
return std::nullopt;
742+
}
743+
return std::vector<Token>(host.begin(), host.end());
744+
}
745+
746+
bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
747+
out.outputs.clear();
748+
out.outputs.resize(batch.inputs.size());
749+
750+
Result<llm_batching::util::PackedStep> step = sessions_.pack(batch);
751+
if (!step.ok()) {
752+
return false;
753+
}
754+
755+
RawTargetStep raw;
756+
raw.tokens = std::move(step->tokens);
757+
raw.positions = std::move(step->positions);
758+
raw.seq_ids = std::move(step->seq_ids);
759+
raw.logit_indices.assign(
760+
step->logit_indices.begin(), step->logit_indices.end());
761+
return execute_raw(raw, [&](const RawTargetOutput& result) {
762+
auto logits = result.logits;
763+
const auto& inputs = result.selected_indices;
764+
if (inputs.empty()) {
765+
return true;
617766
}
618767
std::vector<SessionId> row_sessions;
619-
row_sessions.reserve(selected.inputs.size());
620-
for (const std::size_t input_index : selected.inputs) {
768+
row_sessions.reserve(inputs.size());
769+
for (const std::size_t input_index : inputs) {
621770
row_sessions.push_back(batch.inputs[input_index].sid);
622771
}
623772
std::vector<Token> tokens_out;
@@ -626,8 +775,7 @@ bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
626775
if (device_logits_.value_or(device_logits) != device_logits) {
627776
ET_LOG(
628777
Error,
629-
"CudaExecutor: %s returns %s logits, unlike the forwards before it",
630-
method.name.c_str(),
778+
"CudaExecutor: target returns %s logits, unlike the forwards before it",
631779
device_logits ? "device" : "host");
632780
return false;
633781
}
@@ -636,8 +784,7 @@ bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
636784
if (!has_sampler_) {
637785
ET_LOG(
638786
Error,
639-
"CudaExecutor: %s returns device logits but the program has no %s",
640-
method.name.c_str(),
787+
"CudaExecutor: target returns device logits but the program has no %s",
641788
kSampleMethod);
642789
return false;
643790
}
@@ -656,12 +803,11 @@ bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
656803
tokens_out.push_back(*token);
657804
}
658805
}
659-
for (std::size_t row = 0; row < selected.inputs.size(); ++row) {
660-
out.outputs[selected.inputs[row]] =
661-
Output{row_sessions[row], {tokens_out[row]}};
806+
for (std::size_t row = 0; row < inputs.size(); ++row) {
807+
out.outputs[inputs[row]] = Output{row_sessions[row], {tokens_out[row]}};
662808
}
663-
}
664-
return true;
809+
return true;
810+
});
665811
}
666812

667813
std::optional<std::vector<Token>> CudaExecutor::sample_on_device(

0 commit comments

Comments
 (0)