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
214227CudaExecutor::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
667813std::optional<std::vector<Token>> CudaExecutor::sample_on_device (
0 commit comments