Skip to content

ExecuteAsyncOnStream

本页所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.solibtpu_lts_20260413_b_RC00,BuildID 89edbbe81c5b328a958fe628a9f2207d,ELF x86-64,~745 MB)。其他构建会有所不同。

摘要

ExecuteAsyncOnStream 是 libtpu legacy StreamExecutor 执行路径中每次执行的单一 C++ 入口点:它是 xla::Executable virtual,会把一组 xla::ExecutionInput 缓冲区转换为填充好的 xla::ExecutionOutput,并将设备程序入队。它是 upstream XLA 的 xla::Executable::ExecuteAsyncOnStream 在 TPU 上的对应物,经由 xla::LocalClient::Compilexla::LocalExecutable::RunAsync 到达,而不是经由现代 PJRT C-API。具体 override 是 xla::legacy::TpuExecutableInterface::ExecuteAsyncOnStream @ 0x1342cd20(3650 B),本页重建的就是这个函数。

注意 — 任务框架将其称为 "TpuExecutable::ExecuteAsyncOnStream",并引用 xla::PjRtStreamExecutorLoadedExecutable。这个 binary 中不存在这两个 symbol(HIGH — 对 884,843 个反编译函数做穷尽 rgPjRtStreamExecutorLoadedExecutable 零命中)。此构建中的 PJRT client 是 xla::TpuClient(派生自 xla::CommonPjRtClient : xla::PjRtClient),位于 TFRT-native tpu::System async-value runtime 之上,完全不会路由到 ExecuteAsyncOnStream;它的执行路径是 PJRT_LoadedExecutable_ExecuteCommonPjRtLoadedExecutable::Executetpu::System::Execute。实际存在的 ExecuteAsyncOnStream virtual 属于并行的 xla::LocalClient / xla::Service StreamExecutor 栈,其 TPU executable 是 xla::legacy::TpuExecutableInterface(以及其子类 xla::jellyfish::DeepseaExecutable)。本页记录这个真实入口;当 contract 使用 PJRT 概念名称时,会映射到承担该角色的 StreamExecutor 对象。

该入口按顺序做四件事:将 host ExecutableRunOptionsExecutionInput 参数 marshal 成缓冲区树;用输入缓冲区 donation/aliasing 分配输出 ScopedShapedBuffer;通过 vtable slot 将设备工作 dispatch 到 enqueue lower half;并为调用者组装 ExecutionOutput(或 absl::Status)。enqueue lower half,即 DeepseaExecutable::LoadProgramAndEnqueueToStream @ 0x13426260,见 Load Program and Enqueue;本页负责入口、参数/输出 marshaling,以及到该层的 dispatch

对重新实现而言,contract 是:

  • Dispatch 格子。 三个调用者到达同一个 virtual:LocalExecutable::RunAsyncExecutable::ExecuteAsyncOnStreamWrapper → vtable slot +24ExecuteAsyncOnStream),以及第二个 public door,即 C-ABI shim TpuExecutable_ExecuteAsyncOnStream @ 0xeabd500,它在 un-marshal C struct 后调用同一个 +24 slot。
  • 参数 marshaling。 如何将 xla::ExecutionInput vector 走访成由 IndexTable 索引的扁平 DeviceAddressBase 数组,以及 dynamic-shape side channel。
  • 输出构造。 AllocateOutputMemoryWithInputReuse(@ 0x1342ba00)构造 ScopedShapedBuffer,执行由 HloInputOutputAliasConfig 驱动的 input→output aliasing fixup,以及 MarkToBeReleasedArguments
  • 交接。LoadProgramAndEnqueueToStream 的 vtable +96 间接调用,以及把返回的 root buffer 移入调用者的 ExecutionOutput
入口点xla::legacy::TpuExecutableInterface::ExecuteAsyncOnStream @ 0x1342cd20(3650 B)
C-ABI shimTpuExecutable_ExecuteAsyncOnStream @ 0xeabd500(4708 B)— tpu_executor_c_api.cc
Public C++ wrapperxla::Executable::ExecuteAsyncOnStreamWrapper @ 0x1dad98a0(579 B,ExecutionInput overload)
Client driverxla::LocalClientxla::LocalExecutable::RunAsync @ 0x1084d140(2489 B)
Vtable dispatch slot+24(进入 ExecuteAsyncOnStream);+96(叶子 LoadProgramAndEnqueueToStream
输出 allocatorTpuExecutableInterface::AllocateOutputMemoryWithInputReuse @ 0x1342ba00(4828 B)
Enqueue lower halfxla::jellyfish::DeepseaExecutable::LoadProgramAndEnqueueToStream @ 0x13426260(7512 B)
Arg typestd::vector<xla::ExecutionInput>(192 B/element)
Result typexla::ExecutionOutput(包装 ScopedShapedBuffer),以 StatusOr 按值返回
Source file(asserts)stream_executor/tpu/tpu_executable_interface.cc

对象模型与类层次

目的

ExecuteAsyncOnStream 是 upstream xla::Executable base 上的 virtual。在 TPU 上,override 位于 xla::legacy::TpuExecutableInterface,这是一个抽象类:它一次性实现参数/输出 marshaling,并将真正的设备 enqueue 延迟到由具体 xla::jellyfish::DeepseaExecutable 实现的 pure-virtual leaf。

继承

text
xla::Executable                              (upstream base; ExecuteAsyncOnStream is virtual @ vtable+24)
  └─ xla::legacy::TpuExecutableInterface      ── implements ExecuteAsyncOnStream @ 0x1342cd20
       │                                          (marshal args, allocate outputs, dispatch via +96)
       └─ xla::jellyfish::DeepseaExecutable    ── implements LoadProgramAndEnqueueToStream @ 0x13426260
                                                   (the leaf invoked through vtable+96; load-program-enqueue.md)
```text

`TpuExecutableInterface` 上的 `legacy::` namespace 是 binary 自己的标签(mangled `ZN3xla6legacy22TpuExecutableInterface…`),也是最清晰的单一信号:整条代码路径是为 `LocalClient`/`Service` 和 TF-TPU op kernels 保留的 deprecated StreamExecutor 执行模型。现代 PJRT front door 使用的是 `xla::TpuClient`。

> **特性 —** 两个 vtable slot 属于不同对象。`ExecuteAsyncOnStream` 在 `Executable` vtable 的 **+24** 处 dispatch(由 `ExecuteAsyncOnStreamWrapper` 第 45 行和 C shim 第 703 行调用)。在 `ExecuteAsyncOnStream` 内部,设备工作在 **+96** 处 dispatch(`(*(...)(*v89 + 96))`,interface 第 650 行),也就是 pure-virtual `LoadProgramAndEnqueueToStream`。把两者折叠成一个方法的重新实现者,会丢失 abstract/concrete 拆分,而这个拆分让 `DeepseaExecutable` 可以在不触碰 marshaling 的情况下替换设备 backend。

### 函数映射

| 函数 | 地址 | 大小 | 角色 |
|---|---|---|---|
| `xla::legacy::TpuExecutableInterface::ExecuteAsyncOnStream` | `0x1342cd20` | 3650 B | 入口:marshal → allocate → dispatch → output |
| `xla::legacy::TpuExecutableInterface::AllocateOutputMemoryWithInputReuse` | `0x1342ba00` | 4828 B | 构建输出 `ScopedShapedBuffer`,遵守 donation/aliasing |
| `xla::jellyfish::DeepseaExecutable::LoadProgramAndEnqueueToStream` | `0x13426260` | 7512 B | Pure-virtual leaf(vtable+96)— 设备 enqueue |
| `xla::Executable::ExecuteAsyncOnStreamWrapper`(`ExecutionInput`) | `0x1dad98a0` | 579 B | +24 virtual 周围的带 profile public wrapper |
| `xla::Executable::ExecuteAsyncOnStreamWrapper`(`ShapedBuffer`) | `0x1dad9780` | 259 B | Legacy `ShapedBuffer`-span overload |
| `xla::ExecuteWrapperAfterExecution` | `0x1dad9b00` | 266 B | 执行后 profiling/HLO-profile finalize |
| `xla::LocalExecutable::RunAsync` | `0x1084d140` | 2489 B | `LocalClient` driver → wrapper |
| `TpuExecutable_ExecuteAsyncOnStream` | `0xeabd500` | 4708 B | C-ABI shim:C structs ↔ C++ objects |

---

## 入口点与 Dispatch 格子

### 目的

这里恰好有一个执行 virtual,从两个方向到达:进程内 C++ client(`LocalExecutable::RunAsync`)和 C-ABI 边界(`TpuExecutable_ExecuteAsyncOnStream`,导出给 StreamExecutor `TpuExecutor` shim)。

### 入口点

```text
xla::LocalExecutable::RunAsync (0x1084d140)             ── LocalClient per-call driver
  └─ xla::Executable::ExecuteAsyncOnStreamWrapper (0x1dad98a0)
       ├─ xla::ExecuteWrapperBeforeExecution            ── start HLO execution profile
       ├─ [vtable+24] ExecuteAsyncOnStream  ───────────┐
       └─ xla::ExecuteWrapperAfterExecution (0x1dad9b00)│ ── finalize profile, stamp Status

TpuExecutable_ExecuteAsyncOnStream (0xeabd500)          │ ── C-ABI: FromC args, build RunOptions
  └─ [vtable+24] ExecuteAsyncOnStream ──────────────────┤    then ToC the ExecutionOutput

  xla::legacy::TpuExecutableInterface::ExecuteAsyncOnStream (0x1342cd20)  <─┘
    ├─ AllocateOutputMemoryWithInputReuse (0x1342ba00)  ── ScopedShapedBuffer + aliasing
    ├─ xla::Executable::MarkToBeReleasedArguments       ── donation bookkeeping
    └─ [vtable+96] DeepseaExecutable::LoadProgramAndEnqueueToStream (0x13426260)  ── device enqueue

算法 — wrapper

ExecuteAsyncOnStreamWrapper 很薄:它用 profiling hooks 包住 virtual call。即使 profiling 被禁用,两个 hook 也存在(在 null HloExecutionProfile 上 no-op)。

c
function ExecuteAsyncOnStreamWrapper(self, run_options, args):   // 0x1dad98a0
    state = ExecuteWrapperBeforeExecution(run_options)           // start span; capture stream
    out   = (*self.vtable[24])(self, run_options, &args)         // -> ExecuteAsyncOnStream; moves args
    stream = run_options->stream()                               // line 70
    status = ExecuteWrapperAfterExecution(self, &state,          // 0x1dad9b00
                                          out.status, stream)    //   finalize profile
    return out                                                   // ExecutionOutput by value
```text

> **注意 —** wrapper 会从调用者的 `args` vector *move out*(第 36-44 行清零源 vector header,然后销毁 moved-from 的 `ExecutionInput`)。参数 vector 会被该调用消费;重新实现者不得在之后复用它。

### C-ABI shim

`TpuExecutable_ExecuteAsyncOnStream`(`0xeabd500`,来自 `tpu_executor_c_api.cc`)是 StreamExecutor `TpuExecutor` C-shim 跨越的边界。它只围绕同一个 +24 virtual 做 marshaling:

```c
function TpuExecutable_ExecuteAsyncOnStream(self, c_run_opts, c_args[], n, c_out, status_out):  // 0xeabd500
    run_options.set_device_ordinal(c_run_opts->device_ordinal)        // a2+32
    if c_run_opts->allocator:                                          // a2+8
        run_options.set_allocator(new DeviceAddressAllocator{          // 0x18 B object
            GetUnderlyingDeepseaPlatform(), c_run_opts })              //   wraps deepsea platform
    run_options.set_stream(*c_run_opts->stream)                        // a2+40
    if c_run_opts->host_to_device_stream:                             // a2+48
        run_options.set_host_to_device_stream(...)
    if c_run_opts->device_assignment:                                 // a2+56
        proto = DeserializeProto<DeviceAssignmentProto>(...)          // TpuSerializedProto
        run_options.set_device_assignment(DeviceAssignment::Deserialize(proto))
    run_options.set_rng_seed(c_run_opts->rng_seed)                    // a2+72
    run_options.set_run_id(c_run_opts->run_id)                        // a2+80
    // -- marshal each C SE_ExecutionInput into an xla::ExecutionInput --
    for i in 0..n:                                                    // line 261
        arg = ExecutionInput(ApiConverter::FromC(c_args[i]))          // shape
        TF_CHECK_OK(arg.SetDynamicShape(FromC(c_args[i]+560)))        // dynamic shape side channel
        for each buffer in c_args[i].buffers (stride 72, base +536):  // line 312
            arg.SetUnownedBuffer / SetBuffer(FromC(buffer))           //   MaybeOwningDeviceAddress
        for each aliased index (stride 72, base +544, count +552):    // line 504
            arg.MutableBuffers()->insert(ShapeIndex)                  //   IndexTable entry
        args.push_back(move(arg))
    out = (*self.vtable[24])(&out, self, &run_options, &args)         // line 703 -> ExecuteAsyncOnStream
    if out.ok():
        scoped = ScopedShapedBuffer(out.result)
        ApiConverter::ToC(c_out, scoped.release())                    // populate SE_ExecutionOutput
    else:
        *status_out = out.status                                     // line 887
    // destroy args, run_options, allocator

陷阱 — SetDynamicShape 通过 TF_CHECK_OK assert(shim 在 tpu_executor_c_api.cc:1190 assert)。来自 C 侧的畸形 dynamic-shape blob 会导致硬 LogMessageFatal,而不是返回错误。dynamic shape 位于每个 C argument struct 固定的 +560 偏移,独立于偏移 0 处的 static shape。


参数 Marshaling

目的

interface 入口接收的 args 已经是 std::vector<xla::ExecutionInput>。它的第一项工作是把每个参数的每个叶子缓冲区扁平化为单个连续的 DeviceAddressBase 数组,供 enqueue 层按位置消费,同时通过每个参数的 IndexTable 保留树形 shape。

算法

c
function TpuExecutableInterface::ExecuteAsyncOnStream(self, run_options, args):  // 0x1342cd20
    n = args.size()                                              // a4[1]
    // ---- Stage 1: flatten argument leaf buffers ----
    flat = new DeviceAddressBase[n]                              // 24 B/elem (line 142)
    for i in 0..n:                                               // walk arg[i].Buffers()
        entry = IndexTable::GetEntry(arg[i].buffers, root, /*index*/0)   // tuple_tree.h:332 CHECK
        addr  = entry.AsDeviceAddress()                         // MaybeOwningDeviceAddress
        flat[i] = addr                                          // moved into flat array
    // ---- Stage 2: fetch result shape + aliasing config from the program ----
    if program (a2):
        result_shape = program->result_shape()                  // vtable+40 (line 238)
        alias_config = program->input_output_alias_config()     // HloInputOutputAliasConfig (+2840…)
    else:
        result_shape = Shape{}                                  // empty (line 249)
        alias_config = ShapeTree<optional<Alias>>{}             // line 334
    CHECK(run_options->allocator() != nullptr)                  // tpu_executable_interface.cc:219
    ...
```text

参数 vector 的元素 stride 是 **192 字节**(一个 `xla::ExecutionInput`),在每个析构循环中都可见(`192 * count`,例如 C shim 第 666711945 行和 wrapper 第 53 行)。每个 `ExecutionInput` 携带一个 `Shape`、一个 dynamic `Shape`,以及通过 `xla::internal::IndexTable` 索引的 `ShapeTree<MaybeOwningDeviceAddress>` 叶子缓冲区。叶子设备地址是 24 字节的 `DeviceAddressBase` 记录(opaque pointer + size + memory-space tag);flatten 循环会 assert 每个 `AsDeviceAddress().opaque() != nullptr`(`tuple_tree.h:332`)。

> **特性 —** flatten 数组使用带有 `2*cap` doubling 策略的手写 growable vector(`v16 = 2*v7`,interface 第 195 行)和 `0xAAAAAAAAAAAAAAAA` length-error guard(interface 第 139196 行,即 24 字节 `DeviceAddressBase` stride 的最大元素数)。这不是带默认增长策略的 `std::vector<DeviceAddressBase>`:leaf 数量预先已知(`24 * n`,第 142 行),所以重新实现可以精确预分配并完全跳过 reallocation 路径(interface 第 142-218 行)。(`0xAAAAAAAAAAAAAAAB` division magic 用于从字节 span 恢复 /192 元素计数,属于 *wrapper* 在 `0x1dad98a0` 第 64 行的 `ExecutionInput` vector teardown,不属于这个 24 字节 flat array。)

---

## 输出构造与 Aliasing

### 目的

输出 `ScopedShapedBuffer` 会在设备运行*之前*分配,这样 enqueue 层可以直接把结果写入其中。Donation 允许输入缓冲区原地成为输出缓冲区,从而避免一次分配和一次复制。

### 算法

```c
    // ---- Stage 3: allocate outputs, reusing donated input buffers ----
    out_or = AllocateOutputMemoryWithInputReuse(                // 0x1342ba00
                 result_shape, alias_config,
                 run_options->allocator(),
                 args,                                          // donor source
                 run_options->stream(),
                 run_options->host_to_device_stream())
    if !out_or.ok():
        return out_or.status.AddSourceLocation(tpu_executable_interface.cc:228)
    output = ScopedShapedBuffer(out_or)                         // line 344

    // ---- Stage 4: re-wire donated input buffers into the output tree ----
    if program->aliasing_table (a2+3488, count a2+3496):        // line 382
        for (param, output_index) in aliasing_table:            // 6-qword stride
            CHECK(param < args.size())                          // ...interface.cc:236
            in_entry  = IndexTable::GetEntry(args[param], index) //  ...:242
            CHECK(!in_entry.AsDeviceAddress().is_null())         //  ...:243
            CHECK(in_entry.is_owning /* offset */)               //  ...:244
            aliased.push_back(in_entry)                          //  donor address list
            buffers_to_release.push_back(param_index)            //  uint32 vector

    // ---- Stage 5: bookkeeping + dispatch ----
    MarkToBeReleasedArguments(program, args[0], n, output)       // line 643
    root = output.root_buffer()                                  // line 644
    status = (*program.vtable[96])(                              // line 650 -> LoadProgramAndEnqueueToStream
                 program, run_options,
                 flat /*device addresses*/, n,
                 aliased /*donor list*/, buffers_to_release,
                 root.opaque())
    if status.ok():
        result.set_result(move(output))                         // ScopedShapedBuffer into ExecutionOutput
    else:
        result.status = status.AddSourceLocation(...:266)
    return result                                                // ExecutionOutput

AllocateOutputMemoryWithInputReuse0x1342ba00)使用 ShapeUtil::ForEachMutableSubshapeHelper(callback @ 0x1342dc00)走访 result Shape;对每个 leaf subshape,它查询 HloInputOutputAliasConfig,以决定通过 DeviceAddressAllocator 分配新的设备内存,还是 claim 一个 donated input buffer。结果是 ScopedShapedBuffer,即一个 owning ShapedBuffer,其 leaf address 会在给定 stream 上由 RAII 释放,除非显式 release()ExecutionOutput

陷阱 — aliasing fixup 会通过 IndexTable::GetEntry 重新读取输入缓冲区,并 assert it != arguments[parameter].MutableBuffers()->end()...interface.cc:242)。如果 HLO input_output_alias_config 为一个调用者实际未传入的 parameter index(或传入但缺少该 leaf)声明 donation,会触发 fatal CHECK,而不是优雅 fallback。donor 必须存在且 owning(offset 为真,:244)。

特性 — MarkToBeReleasedArguments 在设备 enqueue 之前运行,而不是之后。它记录程序允许消费哪些 argument buffer,避免调用者的 ExecutionInput destructor double-free executable 现在拥有的缓冲区。实际 release 延迟到持有最终 ExecutionOutput 的对象。把这一步重新实现在 enqueue 之后会让设备与 argument teardown 发生竞态。


到 Enqueue 层的 Dispatch

目的

这是从 marshaling 到设备工作的唯一交接。上方全部是 target-independent 的缓冲区管线;+96 调用以下全部属于 DeepseaExecutable 设备 backend。

跨越边界的内容

vtable+96 调用(LoadProgramAndEnqueueToStream)依次接收:ServiceExecutableRunOptions(携带 stream、allocator、device assignment、run id、rng seed)、扁平 DeviceAddressBase 参数数组及其计数、donor-buffer span、buffers_to_release index vector,以及输出 root buffer 的 opaque pointer。leaf 返回 absl::Status;成功时,预先分配的 output 现在已在设备上填充,并被移入返回的 ExecutionOutput

text
ExecuteAsyncOnStream                       LoadProgramAndEnqueueToStream (load-program-enqueue.md)
─────────────────────                      ──────────────────────────────────────────────────────
  run_options  ───────────────────────────▶  stream / allocator / device_assignment / run_id
  flat[] (DeviceAddressBase, n)  ──────────▶  positional input device addresses
  aliased[] (donated input addrs) ─────────▶  in-place output reuse
  buffers_to_release[] (uint32)  ──────────▶  donation index list
  output.root_buffer().opaque()  ──────────▶  output root device pointer (written by device)
  ◀──────────────────────────────  absl::Status (ok ⇒ output is live)
```text

Stream ordering、设备端 program load(`tpu::System::LoadProgram` / `TpuCoreProgram`)和 completion event **不**在此处建立:它们完全位于 +96 边界以下。见交叉引用。

### 注意事项

- **Replica / partition 处理。** `ExecuteAsyncOnStream` 每次调用是单设备的。device assignment 通过 `run_options->device_assignment()` 到达(在 C shim 中由 `DeviceAssignmentProto` 反序列化,interface 第 236-245 行)。多 replica fan-out 是调用者职责:`LocalExecutable::RunAsync` 会在 wrapper 调用前从 run options 解析 stream 和 device ordinal(RunAsync 第 195 行)。入口本身内部没有 replica loop。
- **错误表面。** 两种错误风格共存。Buffer-shape 和 aliasing 违规是 fatal `CHECK`/`LogMessageFatal`(binary 将畸形 buffer tree 视为 programming error)。Allocation failure 和 device-enqueue failure 会以带有 `AddSourceLocationImpl` stamp 的 `absl::Status` 返回(allocation 为 `:228`,enqueue 为 `:266`),并以失败的 `ExecutionOutput` 传播给调用者。
- **不创建 StreamExecutor `Stream`。** 尽管名称如此,此路径不会创建 stream。`run_options->stream()` 由调用者提供(`LocalExecutable::RunAsync` 或 C shim 的 `c_run_opts->stream`)。这里的 "async" 是 StreamExecutor stream 模型,区别于 adapter 页面记录的 PJRT `tpu::System` async-value 模型。

---

## 相关组件

| 名称 | 关系 |
|---|---|
| `xla::jellyfish::DeepseaExecutable::LoadProgramAndEnqueueToStream` | 本入口 dispatch 到的 vtable+96 leaf,即设备 enqueue lower half |
| `xla::Executable::ExecuteAsyncOnStreamWrapper` | 调用 +24 virtual 的带 profile public C++ wrapper |
| `xla::LocalExecutable::RunAsync` | 解析 stream/ordinal 并调用 wrapper 的 `LocalClient` per-call driver |
| `TpuExecutable_ExecuteAsyncOnStream` | 跨 StreamExecutor 边界 marshal C struct 的 C-ABI shim |
| [`xla::TpuClient`](../pjrt/client-and-device.md)(PJRT 路径) | *现代*执行路径;通过 `tpu::System::Execute` 完全绕过 `ExecuteAsyncOnStream` |

## 交叉引用

- [Load Program and Enqueue](load-program-enqueue.md) — vtable+96 leaf(`LoadProgramAndEnqueueToStream`);设备 program load 和 command-stream enqueue
- [Stream 语义](stream-semantics.md) — `run_options->stream()` 如何对入队工作排序
- [Completion Loop](completion-loop.md) — enqueue 层生成的 async completion event
- [Allocator 集成](allocator-integration.md) — `AllocateOutputMemoryWithInputReuse` 和 C shim 构建的 `DeviceAddressAllocator`
- [Host Callbacks](host-callbacks.md) — 执行期间的 host-side infeed/outfeed 和 callback dispatch
- [PJRT Executable 执行](../pjrt/executable-execution.md) — 现代 `PJRT_LoadedExecutable_Execute` → `CommonPjRtLoadedExecutable::Execute` 路径,本入口是其 legacy counterpart
- [Runtime 概览](overview.md) — StreamExecutor 执行路径相对于 PJRT 路径的位置