@@ -82,7 +82,9 @@ DEFINE_bool(
8282
8383namespace llm = ::executorch::extension::llm;
8484using ::executorch::extension::from_blob;
85+ using ::executorch::extension::make_tensor_ptr;
8586using ::executorch::extension::Module;
87+ using ::executorch::extension::TensorPtr;
8688using ::executorch::runtime::Error;
8789using ::executorch::runtime::EValue;
8890#ifdef EXECUTORCH_BUILD_CUDA
@@ -91,18 +93,23 @@ using ::executorch::extension::clone_tensor_ptr_to;
9193
9294using SizesType = executorch::aten::SizesType;
9395
94- // Read a sampled token ID from a scalar float output (CUDA path).
96+ // Read a sampled token ID from a scalar int64 output (CUDA path).
97+ //
98+ // The model now emits the sampled token as int64 (see sampler.py), matching
99+ // the decode method's int64 token input so the on-device output buffer can be
100+ // aliased directly as the next step's input. We still copy the 8-byte scalar
101+ // back to the host here for EOS detection and detokenization.
95102static uint64_t read_token (const executorch::aten::Tensor& output) {
96103 const void * ptr = output.const_data_ptr ();
97- float val = 0 . 0f ;
104+ int64_t val = 0 ;
98105
99106#ifdef EXECUTORCH_BUILD_CUDA
100107 cudaPointerAttributes attrs{};
101108 bool on_device = cudaPointerGetAttributes (&attrs, ptr) == cudaSuccess &&
102109 attrs.type == cudaMemoryTypeDevice;
103110 if (on_device) {
104111 cudaError_t err =
105- cudaMemcpy (&val, ptr, sizeof (float ), cudaMemcpyDeviceToHost);
112+ cudaMemcpy (&val, ptr, sizeof (int64_t ), cudaMemcpyDeviceToHost);
106113 if (err != cudaSuccess) {
107114 ET_LOG (
108115 Error,
@@ -111,13 +118,13 @@ static uint64_t read_token(const executorch::aten::Tensor& output) {
111118 return 0 ;
112119 }
113120 } else {
114- memcpy (&val, ptr, sizeof (float ));
121+ memcpy (&val, ptr, sizeof (int64_t ));
115122 }
116123#else
117- memcpy (&val, ptr, sizeof (float ));
124+ memcpy (&val, ptr, sizeof (int64_t ));
118125#endif
119126
120- return static_cast <uint64_t >(llrintf ( val) );
127+ return static_cast <uint64_t >(val);
121128}
122129
123130int main (int argc, char ** argv) {
@@ -295,6 +302,12 @@ int main(int argc, char** argv) {
295302 // ---------------------------------------------------------------
296303 uint64_t cur_token = 0 ;
297304 int64_t prefill_pos = 0 ;
305+ #ifdef EXECUTORCH_BUILD_CUDA
306+ // Alias of the most recent forward's on-device int64 output token. The last
307+ // prefill chunk's output seeds the first decode step (no token H2D); each
308+ // decode step then re-aliases its own output for the next step.
309+ TensorPtr device_out_token;
310+ #endif
298311 while (prefill_pos < num_prompt_tokens) {
299312 int64_t chunk_len =
300313 std::min (num_prompt_tokens - prefill_pos, max_prefill_chunk);
@@ -337,7 +350,11 @@ int main(int argc, char** argv) {
337350 }
338351
339352#ifdef EXECUTORCH_BUILD_CUDA
340- cur_token = read_token (result.get ()[0 ].toTensor ());
353+ const auto & out_tensor = result.get ()[0 ].toTensor ();
354+ cur_token = read_token (out_tensor);
355+ // Keep the sampled token on device: alias the output buffer so it feeds
356+ // straight into the next forward as the int64 token input (zero copy).
357+ device_out_token = make_tensor_ptr (out_tensor);
341358#else
342359 cur_token = static_cast <uint64_t >(
343360 llm::logits_to_token (result.get ()[0 ].toTensor (), temp_val));
@@ -369,53 +386,48 @@ int main(int argc, char** argv) {
369386 // Decode loop
370387 // ---------------------------------------------------------------
371388 int64_t pos = num_prompt_tokens;
372- std::vector<int64_t > decode_token_data = {static_cast <int64_t >(cur_token)};
373389 std::vector<int64_t > decode_pos_data = {pos};
374- auto decode_tokens_cpu = from_blob (
375- decode_token_data.data (), {1 , 1 }, executorch::aten::ScalarType::Long);
376390 auto decode_pos_cpu = from_blob (
377391 decode_pos_data.data (), {1 }, executorch::aten::ScalarType::Long);
378392#ifdef EXECUTORCH_BUILD_CUDA
379- // skip_h2d: keep fixed device-resident input buffers across decode steps
380- // (seeded here with a one-time H2D). Their 8-byte contents are refreshed
381- // each step from the host (see loop), since the sampled id round-trips to
382- // the host for EOS detection.
383- auto decode_tokens = clone_tensor_ptr_to (decode_tokens_cpu, cuda_device);
393+ // The token input is the aliased on-device output (device_out_token); only
394+ // the position still needs a fixed device buffer refreshed by a per-round
395+ // H2D (one int64 / round).
384396 auto decode_pos = clone_tensor_ptr_to (decode_pos_cpu, cuda_device);
385397#else
386- auto decode_tokens = decode_tokens_cpu;
398+ // Non-CUDA (MLX) path: keep host token/pos buffers; the backend stages them
399+ // and the host samples from the returned logits.
400+ std::vector<int64_t > decode_token_data = {static_cast <int64_t >(cur_token)};
401+ auto decode_tokens = from_blob (
402+ decode_token_data.data (), {1 , 1 }, executorch::aten::ScalarType::Long);
387403 auto decode_pos = decode_pos_cpu;
388404#endif
389405
390406 uint64_t prev_token = cur_token;
391407 bool hit_eos = eos_ids.find (cur_token) != eos_ids.end ();
392408 for (int32_t step = 0 ; step < FLAGS_max_new_tokens && !hit_eos; step++) {
393- decode_token_data[0 ] = static_cast <int64_t >(cur_token);
394409 decode_pos_data[0 ] = pos;
395410
396411#ifdef EXECUTORCH_BUILD_CUDA
397- // skip_h2d: refresh the device-resident token/pos buffers ourselves (the
398- // backend no longer inserts the H2D copy). The prior step's sampled id
399- // already came back to the host via read_token, so re-upload the 8-byte
400- // token and position into the fixed device buffers.
401- ET_CHECK_MSG (
402- cudaMemcpy (
403- decode_tokens->mutable_data_ptr (),
404- decode_token_data.data (),
405- sizeof (int64_t ),
406- cudaMemcpyHostToDevice) == cudaSuccess,
407- " Failed to upload decode token H2D" );
412+ // Token stays on device (aliased from the previous forward's output); only
413+ // the 8-byte position is uploaded each round. No token D2H->H2D round-trip.
408414 ET_CHECK_MSG (
409415 cudaMemcpy (
410416 decode_pos->mutable_data_ptr (),
411417 decode_pos_data.data (),
412418 sizeof (int64_t ),
413419 cudaMemcpyHostToDevice) == cudaSuccess,
414420 " Failed to upload decode position H2D" );
421+ #else
422+ decode_token_data[0 ] = static_cast <int64_t >(cur_token);
415423#endif
416424
417425 std::vector<EValue> inputs;
426+ #ifdef EXECUTORCH_BUILD_CUDA
427+ inputs.push_back (EValue (device_out_token));
428+ #else
418429 inputs.push_back (EValue (decode_tokens));
430+ #endif
419431 inputs.push_back (EValue (decode_pos));
420432
421433#ifdef EXECUTORCH_BUILD_CUDA
@@ -432,7 +444,10 @@ int main(int argc, char** argv) {
432444
433445 prev_token = cur_token;
434446#ifdef EXECUTORCH_BUILD_CUDA
435- cur_token = read_token (result.get ()[0 ].toTensor ());
447+ const auto & out_tensor = result.get ()[0 ].toTensor ();
448+ cur_token = read_token (out_tensor);
449+ // Alias this step's on-device output token as the next step's token input.
450+ device_out_token = make_tensor_ptr (out_tensor);
436451#else
437452 cur_token = static_cast <uint64_t >(
438453 llm::logits_to_token (result.get ()[0 ].toTensor (), temp_val));
0 commit comments