| 248 | } |
| 249 | |
| 250 | Status XlaComputationLaunchContext::PopulateOutputs( |
| 251 | OpKernelContext* ctx, const XlaCompiler::CompilationResult* kernel, |
| 252 | ScopedShapedBuffer output, int missing_ctx_input_prefix) { |
| 253 | se::Stream* stream = |
| 254 | ctx->op_device_context() ? ctx->op_device_context()->stream() : nullptr; |
| 255 | |
| 256 | // Computation output should always be a tuple. |
| 257 | if (VLOG_IS_ON(2)) { |
| 258 | VLOG(2) << "Result tuple shape: " << output.on_host_shape().DebugString(); |
| 259 | VLOG(2) << "Result tuple shape (on device): " |
| 260 | << output.on_device_shape().DebugString(); |
| 261 | } |
| 262 | CHECK_EQ(ctx->num_outputs(), kernel->outputs.size()); |
| 263 | |
| 264 | // If the on-host-shape isn't a tuple, create a new single-element tuple |
| 265 | // buffer with a nullptr root index table. This allows the code below to treat |
| 266 | // output as a tuple unconditionally. |
| 267 | if (!output.on_host_shape().IsTuple()) { |
| 268 | ShapedBuffer nontuple_buffer = output.release(); |
| 269 | ShapedBuffer buffer( |
| 270 | xla::ShapeUtil::MakeTupleShape({nontuple_buffer.on_host_shape()}), |
| 271 | xla::ShapeUtil::MakeTupleShape({nontuple_buffer.on_device_shape()}), |
| 272 | output.platform(), output.device_ordinal()); |
| 273 | buffer.buffers().CopySubtreeFrom(nontuple_buffer.buffers(), |
| 274 | /*source_base_index=*/{}, |
| 275 | /*target_base_index=*/{0}); |
| 276 | output = ScopedShapedBuffer(std::move(buffer), output.memory_allocator()); |
| 277 | } |
| 278 | |
| 279 | std::shared_ptr<se::Event> definition_event; |
| 280 | if (use_multiple_streams_) { |
| 281 | definition_event = std::make_shared<se::Event>(stream->parent()); |
| 282 | if (!definition_event->Init()) { |
| 283 | return errors::Internal("Failed to initialize tensor definition event."); |
| 284 | } |
| 285 | stream->ThenRecordEvent(definition_event.get()); |
| 286 | } |
| 287 | |
| 288 | // Copy XLA results to the OpOutputList. |
| 289 | int output_num = 0; |
| 290 | for (int i = 0; i < ctx->num_outputs(); ++i) { |
| 291 | Allocator* allocator = ctx->device()->GetAllocator({}); |
| 292 | if (kernel->outputs[i].is_constant) { |
| 293 | // Output is a constant. |
| 294 | const Tensor& const_tensor = kernel->outputs[i].constant_value; |
| 295 | Tensor* output_tensor; |
| 296 | const size_t total_bytes = const_tensor.TotalBytes(); |
| 297 | if (stream && total_bytes > 0) { |
| 298 | // Copy host -> device. (Empty tensors don't have backing buffers.) |
| 299 | // Manually allocate memory using an XlaTensorBuffer so we can allocate |
| 300 | // as much memory as the device requires (as given by |
| 301 | // GetByteSizeRequirement). This avoids XlaTransferManager having to |
| 302 | // reallocate the device buffer later. |
| 303 | VLOG(1) << "Constant output tensor on device"; |
| 304 | |
| 305 | TF_RETURN_IF_ERROR( |
| 306 | ctx->allocate_output(i, const_tensor.shape(), &output_tensor)); |
| 307 |
no test coverage detected