@@ -90,6 +90,37 @@ Result<int> step_width(const MethodMeta& meta, const char* method) {
9090 return static_cast <int >(token_sizes[1 ]);
9191}
9292
93+ // kEmbeddingsMethod's decoder input: [1, T, hidden] in a float dtype, with
94+ // Long[T] positions and a rank-one Long selector. Returns T's upper bound.
95+ Result<std::pair<int , std::int64_t >> embeddings_width (
96+ const MethodMeta& meta,
97+ ::executorch::aten::ScalarType* dtype) {
98+ const auto embeddings = meta.input_tensor_meta (0 );
99+ const auto positions = meta.input_tensor_meta (1 );
100+ const auto selector = meta.input_tensor_meta (2 );
101+ ET_CHECK_OR_RETURN_ERROR (
102+ meta.num_inputs () == 3 && embeddings.ok () && positions.ok () &&
103+ selector.ok (),
104+ InvalidProgram,
105+ " CudaExecutor: %s must take embeddings, positions and a logits selector" ,
106+ kEmbeddingsMethod );
107+ const auto sizes = embeddings->sizes ();
108+ const auto position_sizes = positions->sizes ();
109+ ET_CHECK_OR_RETURN_ERROR (
110+ is_supported_logits_type (embeddings->scalar_type ()) &&
111+ sizes.size () == 3 && sizes[0 ] == 1 && sizes[1 ] > 0 && sizes[2 ] > 0 &&
112+ positions->scalar_type () == ::executorch::aten::ScalarType::Long &&
113+ position_sizes.size () == 1 && position_sizes[0 ] == sizes[1 ] &&
114+ selector->scalar_type () == ::executorch::aten::ScalarType::Long &&
115+ selector->sizes ().size () == 1 ,
116+ InvalidProgram,
117+ " CudaExecutor: %s must take [1, T, hidden] embeddings, Long[T] positions "
118+ " and a rank-one Long selector" ,
119+ kEmbeddingsMethod );
120+ *dtype = embeddings->scalar_type ();
121+ return std::pair<int , std::int64_t >{static_cast <int >(sizes[1 ]), sizes[2 ]};
122+ }
123+
93124// The logits width `method` produces.
94125Result<std::int64_t > logits_width (const MethodMeta& meta, const char * method) {
95126 ET_CHECK_OR_RETURN_ERROR (
@@ -490,8 +521,27 @@ Result<std::unique_ptr<CudaExecutor>> CudaExecutor::create(
490521 max_sessions,
491522 seq_limit.value_or (0 ));
492523
524+ std::optional<EmbeddingsMethod> embeddings_method;
525+ if (names.count (kEmbeddingsMethod ) != 0 ) {
526+ ET_ASSIGN_OR_RETURN (meta, module ->method_meta (kEmbeddingsMethod ));
527+ ::executorch::aten::ScalarType dtype{};
528+ ET_ASSIGN_OR_RETURN (width, embeddings_width (meta, &dtype));
529+ ET_ASSIGN_OR_RETURN (vocab, logits_width (meta, kEmbeddingsMethod ));
530+ ET_CHECK_OK_OR_RETURN_ERROR (backend_of (meta, kEmbeddingsMethod ).error ());
531+ ET_CHECK_OR_RETURN_ERROR (
532+ vocab == method_vocab && width.first <= max_step_tokens &&
533+ min_selected_rows <= width.first ,
534+ InvalidProgram,
535+ " CudaExecutor: %s disagrees with the token forwards" ,
536+ kEmbeddingsMethod );
537+ // Exported, like forward_others, from min_selected_rows tokens; narrower
538+ // steps pad up to it.
539+ embeddings_method =
540+ EmbeddingsMethod{width.first , min_selected_rows, width.second , dtype};
541+ }
542+
493543 ET_CHECK_OK_OR_RETURN_ERROR (set_backend_options (options, methods));
494- return std::unique_ptr<CudaExecutor>(new CudaExecutor (
544+ auto executor = std::unique_ptr<CudaExecutor>(new CudaExecutor (
495545 std::move (module ),
496546 std::move (cache),
497547 max_sessions,
@@ -502,6 +552,8 @@ Result<std::unique_ptr<CudaExecutor>> CudaExecutor::create(
502552 min_selected_rows,
503553 has_sampler,
504554 has_argmax));
555+ executor->embeddings_method_ = embeddings_method;
556+ return executor;
505557}
506558
507559bool CudaExecutor::initialize () {
@@ -524,6 +576,15 @@ bool CudaExecutor::initialize() {
524576 return false ;
525577 }
526578 }
579+ if (embeddings_method_ &&
580+ module_->load_method (
581+ kEmbeddingsMethod ,
582+ /* planned_memory=*/ nullptr ,
583+ /* event_tracer=*/ nullptr ,
584+ &options_map) != Error::Ok) {
585+ ET_LOG (Error, " CudaExecutor: could not load %s" , kEmbeddingsMethod );
586+ return false ;
587+ }
527588 for (const auto & [present, name] :
528589 {std::pair{has_sampler_, kSampleMethod },
529590 std::pair{has_argmax_, kArgmaxMethod }}) {
@@ -532,6 +593,11 @@ bool CudaExecutor::initialize() {
532593 return false ;
533594 }
534595 }
596+ if (embedder_ && embedder_->initialize &&
597+ embedder_->initialize () != Error::Ok) {
598+ ET_LOG (Error, " CudaExecutor: the input embedder failed to initialize" );
599+ return false ;
600+ }
535601 initialized_ = true ;
536602 return true ;
537603}
@@ -555,6 +621,25 @@ void CudaExecutor::set_sampling(
555621 sessions_.set_sampling (session, params, seed);
556622}
557623
624+ bool CudaExecutor::accepts (const llm_batching::PreparedInput& input) const {
625+ return embedder_ && embedder_->accepts && embedder_->accepts (input);
626+ }
627+
628+ Error CudaExecutor::set_input_embedder (InputEmbedder embedder) {
629+ ET_CHECK_OR_RETURN_ERROR (
630+ !initialized_ && embedder.accepts && embedder.materialize ,
631+ InvalidArgument,
632+ " CudaExecutor: an embedder needs accepts and materialize, before "
633+ " initialize()" );
634+ ET_CHECK_OR_RETURN_ERROR (
635+ embeddings_method_.has_value (),
636+ NotSupported,
637+ " CudaExecutor: the program exports no %s" ,
638+ kEmbeddingsMethod );
639+ embedder_ = std::move (embedder);
640+ return Error::Ok;
641+ }
642+
558643bool CudaExecutor::execute_raw (
559644 const RawTargetStep& step,
560645 const std::function<bool (const RawTargetOutput&)>& consume) {
@@ -590,6 +675,9 @@ bool CudaExecutor::execute_raw(
590675 }
591676 bool & flag;
592677 } reset (raw_executing_);
678+ if (step.embeddings ) {
679+ return execute_embeddings (step, consume);
680+ }
593681 for (const auto & slice : plan_slices (total, methods_)) {
594682 const int off = slice.offset ;
595683 const int n = slice.length ;
@@ -756,6 +844,18 @@ bool CudaExecutor::execute(const BatchInput& batch, BatchOutput& out) {
756844 }
757845
758846 RawTargetStep raw;
847+ if (step->prepared ) {
848+ if (!embedder_) {
849+ ET_LOG (Error, " CudaExecutor: a prepared input needs an input embedder" );
850+ return false ;
851+ }
852+ auto embeddings = embedder_->materialize (batch.inputs );
853+ if (!embeddings.ok ()) {
854+ ET_LOG (Error, " CudaExecutor: could not materialize the batch's inputs" );
855+ return false ;
856+ }
857+ raw.embeddings = std::move (*embeddings);
858+ }
759859 raw.tokens = std::move (step->tokens );
760860 raw.positions = std::move (step->positions );
761861 raw.seq_ids = std::move (step->seq_ids );
@@ -954,11 +1054,112 @@ OffGraphKVMetrics CudaExecutor::kv_metrics() const {
9541054
9551055std::vector<ForwardMethodCalls> CudaExecutor::method_calls () const {
9561056 std::vector<ForwardMethodCalls> calls;
957- calls.reserve (methods_.size ());
1057+ calls.reserve (methods_.size () + 1 );
9581058 for (std::size_t i = 0 ; i < methods_.size (); ++i) {
9591059 calls.push_back ({methods_[i].name , calls_[i]});
9601060 }
1061+ if (embeddings_method_) {
1062+ calls.push_back ({kEmbeddingsMethod , embeddings_method_->calls });
1063+ }
9611064 return calls;
9621065}
9631066
1067+ bool CudaExecutor::execute_embeddings (
1068+ const RawTargetStep& step,
1069+ const std::function<bool (const RawTargetOutput&)>& consume) {
1070+ using ::executorch::aten::SizesType;
1071+ const auto & embeddings = *step.embeddings ;
1072+ const int total = static_cast <int >(step.positions .size ());
1073+ if (!embeddings_method_ || embeddings.dim () != 3 || embeddings.size (0 ) != 1 ||
1074+ embeddings.size (1 ) != total ||
1075+ embeddings.size (2 ) != embeddings_method_->hidden ||
1076+ embeddings.scalar_type () != embeddings_method_->dtype ||
1077+ !embeddings.device ().is_cpu () || !is_dense (embeddings) || total <= 0 ||
1078+ embeddings.const_data_ptr () == nullptr ) {
1079+ ET_LOG (
1080+ Error,
1081+ " CudaExecutor: embeddings must be host [1, %d, %" PRId64 " ] tensors" ,
1082+ total,
1083+ embeddings_method_ ? embeddings_method_->hidden : 0 );
1084+ return false ;
1085+ }
1086+ const auto & method = *embeddings_method_;
1087+ const std::size_t row_bytes = static_cast <std::size_t >(method.hidden ) *
1088+ ::executorch::runtime::elementSize (method.dtype);
1089+ const auto * data =
1090+ static_cast <const std::uint8_t *>(embeddings.const_data_ptr ());
1091+ for (int off = 0 ; off < total; off += method.max_tokens ) {
1092+ const int n = std::min (method.max_tokens , total - off);
1093+ const int width = std::max (n, method.min_tokens );
1094+ std::vector<std::int64_t > selected;
1095+ std::vector<std::size_t > indices;
1096+ for (std::size_t i = 0 ; i < step.logit_indices .size (); ++i) {
1097+ const auto index = step.logit_indices [i];
1098+ if (index >= off && index < off + n) {
1099+ selected.push_back (index - off);
1100+ indices.push_back (i);
1101+ }
1102+ }
1103+ if (selected.size () > static_cast <std::size_t >(method.max_tokens )) {
1104+ return false ;
1105+ }
1106+ const std::size_t rows =
1107+ std::max<std::size_t >(selected.size (), min_selected_rows_);
1108+ selected.resize (rows, selected.empty () ? 0 : selected.back ());
1109+ if (!ctl_->declare_step (std::vector<std::int32_t >(
1110+ step.seq_ids .begin () + off, step.seq_ids .begin () + off + n))) {
1111+ return false ;
1112+ }
1113+ // Rows past n are padding the cache writes to its scratch cell.
1114+ std::vector<std::uint8_t > rows_bytes (
1115+ static_cast <std::size_t >(width) * row_bytes, 0 );
1116+ std::copy (
1117+ data + static_cast <std::size_t >(off) * row_bytes,
1118+ data + static_cast <std::size_t >(off + n) * row_bytes,
1119+ rows_bytes.begin ());
1120+ auto input = make_tensor_ptr (
1121+ {1 ,
1122+ static_cast <SizesType>(width),
1123+ static_cast <SizesType>(method.hidden )},
1124+ std::move (rows_bytes),
1125+ method.dtype );
1126+ std::vector<std::int64_t > position_values (
1127+ step.positions .begin () + off, step.positions .begin () + off + n);
1128+ position_values.resize (width, 0 );
1129+ auto positions = make_tensor_ptr (
1130+ {static_cast <SizesType>(width)}, std::move (position_values));
1131+ auto selector =
1132+ make_tensor_ptr ({static_cast <SizesType>(rows)}, std::move (selected));
1133+ auto result =
1134+ module_->execute (kEmbeddingsMethod , {input, positions, selector});
1135+ if (!result.ok () || result->empty () || !result->at (0 ).isTensor ()) {
1136+ ET_LOG (Error, " CudaExecutor: %s failed" , kEmbeddingsMethod );
1137+ return false ;
1138+ }
1139+ ++embeddings_method_->calls ;
1140+ const auto logits = result->at (0 ).toTensor ();
1141+ if (logits.dim () < 2 || logits.size (logits.dim () - 1 ) != vocab_size_ ||
1142+ logits.numel () != static_cast <std::int64_t >(rows) * vocab_size_ ||
1143+ !is_supported_logits_type (logits.scalar_type ()) || !is_dense (logits)) {
1144+ return false ;
1145+ }
1146+ std::optional<::executorch::aten::Tensor> taps;
1147+ if (result->size () > 1 ) {
1148+ if (!result->at (1 ).isTensor ()) {
1149+ return false ;
1150+ }
1151+ taps = result->at (1 ).toTensor ();
1152+ if (taps->dim () != 3 || taps->size (0 ) != 1 || taps->size (1 ) != width ||
1153+ taps->size (2 ) <= 0 ||
1154+ !is_supported_logits_type (taps->scalar_type ()) || !is_dense (*taps)) {
1155+ return false ;
1156+ }
1157+ }
1158+ if (!consume ({off, n, logits, taps ? &*taps : nullptr , indices})) {
1159+ return false ;
1160+ }
1161+ }
1162+ return true ;
1163+ }
1164+
9641165} // namespace executorch::backends::cuda::batching
0 commit comments