Skip to content

TpuExecutor 名册

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,ELF x86-64 DYN,未剥离;.text VMA 等于文件偏移)。其他版本会不同。

摘要

TpuExecutor_* 是 TfTpu C-ABI shim 中按设备运行时划分的函数簇:25 个 extern "C" 自由函数,支撑 stream_executor::tpu::TpuExecutor,也就是拥有一个 TPU core 的分配、流、事件以及主机/设备传输的 StreamExecutor 对象。在开源树中,StreamExecutor 是一个较大的 C++ 虚接口;而闭源插件把它暴露为一张扁平函数表。XLA 的 StreamExecutor 调用所需的每个方法——分配 HBM、创建事件、在流上记录事件、等待事件、向主机或从主机复制缓冲区、阻塞直到流排空——都是 TfTpu_ExecutorApiFn 结构体中的一个槽位,而该槽位指向此处列出的某个函数。

这些函数共享同一种形态,也是重新实现者唯一必须复现的东西。每个函数都把一个不透明的 SE_StreamExecutor* 句柄作为第一个参数;该句柄的第一个 qword 是指向真实设备执行器 C++ 对象的指针(IDA 将其恢复为 deepsea::executor::DeepseaExecutor),而该对象的第一个 qword 是它的 vtable。C-ABI 函数读取 *handle 取得驱动对象,然后通过驱动 vtable 中固定字节偏移调用——Allocate 跳转到 vtable+136Deallocatevtable+144BlockHostUntilDonevtable+120,依此类推。偏移就是完整的派发契约:没有名称查找,没有动态解析,只有 (*(vtable + N))(driver, args...)。少数函数跳过 vtable,直接调用具名的 DeepseaExecutor::* 方法(GetCoreLocationEnqueueInfeedDequeueOutfeed),另有一个函数(Free)只是裸 free()

本页只负责名册以及逐函数 impl-symbol→vtable-slot 映射。位于表前面的 *ApiFn() 访问器模式、不透明句柄 / ApiConverter 编组约定,以及所有函数簇的名册映射,见 shim 概览——这里只链接,不再重复解释。通过此表发起调用的主机侧 stream_executor::tpu::TpuExecutor C++ shim 见 StreamExecutor 平台与执行器模型。内存操作最终落到的 HBM 分配器见 分配器集成

对重新实现而言,契约是:

  • 句柄间接层——arg0SE_StreamExecutor**arg0DeepseaExecutor* 驱动对象;**arg0 是它的 vtable。派发前有两次解引用。
  • 逐函数 vtable 偏移——每个函数加到驱动 vtable 上的字节偏移,列在下表。这是派发键;必须精确复现偏移和布局。
  • status-out 习惯用法——多数函数带有尾随的 absl::status_internal::StatusRep** 输出槽,会覆盖它(并修正引用计数),返回原始 StatusRep*(&dword_0 + 1) 这个哨兵(带奇数标记的 inline-OK 指针)表示成功。
  • ApiConverter::FromC 边界——接收 DeviceMemoryBase 的函数会收到扁平 C 对偶,并调用 ApiConverter::FromC,在 vtable 调用前把 C++ DeviceAddressBase 重建到栈上临时缓冲区中。
函数簇TpuExecutor_*——按设备运行时,25 个 extern "C" 函数
地址范围0xeab90c0 (Init) – 0xeab9cc0 (CreateDeviceDescription)
TfTpu_ExecutorApiFn(通过 ExecutorApiFn() @ 0x20819360 到达)——见 overview
句柄 (arg0)SE_StreamExecutor**handle = deepsea::executor::DeepseaExecutor***handle = driver vtable
派发(*(driver_vtable + offset))(driver, marshalled_args...)
状态约定尾随 StatusRep** 输出槽;OK 哨兵 (&dword_0 + 1);对被替换的 rep 执行 Unref
证据等级可重新实现级 / 经 IDA 反编译逐字节确认

范围—— 单例 ExecutorApiFn() 访问器、IsStreamExecutorEnabled 探针,以及 SE_*/XLA_* 不透明句柄 + ApiConverter::ToC/FromC/Destroy 约定见 shim 概览DeviceMemoryBase/XLA_Literal 的扁平 C 对偶也在那里描述;本页引用它们,但不重新定义。Infeed/outfeed 的语义(队列模型、transfer-manager 协作)见 Infeed/Outfeed;这里的 TpuExecutor_* 函数只是 SE 层的 enqueue/dequeue 入口桩。


速览——按领域划分的函数

领域数量函数
生命周期4Init, GetStatus, CreateDeviceDescription, Free
内存4Allocate, Deallocate, GetAllocatorStats, DeviceMemoryUsage
2CreateStreamDependency, DeallocateStream
事件3AllocateEvent, RecordEvent, WaitForEvent
传输 (memcpy)4SynchronousMemcpyToHost, SynchronousMemcpyFromHost, MemcpyToHost, MemcpyFromHost
传输 (infeed/outfeed)2EnqueueInfeed, DequeueOutfeed
同步3BlockHostUntilDone, SynchronizeAllActivity, WaitForEvent(也属于事件)
HBM / 主机侧3EnqueueCompactionOnStreamForHbm, HostCallback, UnloadAllPrograms
拓扑1GetCoreLocation

QUIRK—— 名册中没有 TpuExecutor_CreateStream。流的创建不委托给 C-ABI 槽位:主机侧 stream_executor::tpu::TpuExecutor::CreateStream (0xe996ca0) 会自行构造 SE stream 对象,并直接调用 deepsea::executor::DeepseaExecutor::CreateStream (0x1d0db000)。C-ABI 表面只暴露创建之后的流操作——CreateStreamDependency(连接等待边)和 DeallocateStream(销毁)。如果重新实现者期望在 TfTpu_ExecutorApiFn 中找到 CreateStream 槽位,将找不到;执行器流操作的槽位数是两个,不是三个。


生命周期

目的

让设备执行器上线(Init)、报告其健康状态(GetStatus)、向 SE 层描述其硬件(CreateDeviceDescription),并释放插件返回给主机的堆分配(Free)。InitGetStatus 是同一个 vtable 槽位(+32)——二者都向驱动询问“你是否正常?”,一次在启动时,一次按需。

函数映射

函数地址大小驱动派发
TpuExecutor_Init0xeab90c060driver_vtable+32(状态查询)
TpuExecutor_GetStatus0xeab92e060driver_vtable+32(与 Init 相同槽位)
TpuExecutor_CreateDeviceDescription0xeab9cc0991driver_vtable+272,然后构建 stream_executor::DeviceDescription
TpuExecutor_Free0xeab910016无——if (p) free(p)

算法

c
// TpuExecutor_Init / TpuExecutor_GetStatus            0xeab90c0 / 0xeab92e0
int64 Init(SE_StreamExecutor* handle, StatusRep** out):
    rep = (*(*handle->driver + 32))(handle->driver)   // driver vtable slot +32
    return store_status(out, rep)                      // status-out idiom (see below)

// TpuExecutor_Free                                    0xeab9100
void Free(void* p):
    if (p) free(p)        // releases a plugin-allocated heap buffer handed to the host

// TpuExecutor_CreateDeviceDescription                 0xeab9cc0  (991 bytes, largest in cluster)
void CreateDeviceDescription(SE_StreamExecutor* handle, ..., StatusRep** out):
    (*(*handle->driver + 272))(&scratch)               // driver fills a device-info struct
    // ... marshals name/vendor/HBM-size/clock fields into stream_executor::DeviceDescription

CreateDeviceDescription 是此函数簇中的异常项,大小为 991 字节:它通过 driver_vtable+272 拉取一个已填充的 device-info blob,然后逐一复制字符串和数值字段,组装出 stream_executor::DeviceDescription(反编译中的长 v17v29 链就是字符串字段编组)。逐字段复制是它远大于其他名册项的原因;派发本身仍然只是一次 vtable 调用。

NOTE—— status-out 习惯用法在 25 个函数中约 16 个里重复出现,这里只描述一次。函数调用驱动,驱动返回原始 absl::status_internal::StatusRep*。C-ABI wrapper 把它写入调用者的 StatusRep** out,并正确处理引用计数(它会对槽位先前持有的 rep 执行 Unref;对于 inline-OK rep——低位被置位、即 (&dword_0 + 1) 哨兵——跳过 unref)。返回值就是 rep 本身;主机的 TpuStatus 风格 wrapper 会查询它的 ok/code/message。下面的函数只展示派发行,省略这类样板。


内存

目的

分配和释放设备内存(Allocate/Deallocate),并报告容量(DeviceMemoryUsage)和详细分配器统计(GetAllocatorStats)。这些是 分配器集成 中记录的 HBM 分配器在 SE 层的表面;这里的 vtable 槽位会转发到驱动内部的该分配器。

函数映射

函数地址大小驱动派发编组
TpuExecutor_Allocate0xeab912055driver_vtable+136按值返回 16 字节 DeviceMemoryBase
TpuExecutor_Deallocate0xeab916050driver_vtable+144DeviceMemoryBase 参数执行 ApiConverter::FromC
TpuExecutor_GetAllocatorStats0xeab91a0120driver_vtable+320复制可选的 AllocatorStats 字段
TpuExecutor_DeviceMemoryUsage0xeab922064driver_vtable+256两个输出参数:free, total

算法

c
// TpuExecutor_Allocate                                0xeab9120
DeviceMemoryBase* Allocate(DeviceMemoryBase* out, SE_StreamExecutor* handle /*+args*/):
    (*(*handle->driver + 136))(&tmp)        // driver allocates; returns {opaque, ptr, size}
    out->lo = tmp.lo; out->hi = tmp.hi      // 16-byte struct copied via xmm move
    return out

// TpuExecutor_Deallocate                              0xeab9160
int64 Deallocate(SE_StreamExecutor* handle, SE_DeviceMemoryBase* cmem):
    DeviceAddressBase mem = ApiConverter::FromC(cmem)   // flat C twin -> C++ object on stack
    return (*(*handle->driver + 144))(handle->driver, &mem)

// TpuExecutor_DeviceMemoryUsage                       0xeab9220
bool DeviceMemoryUsage(SE_StreamExecutor* handle, int64* free_out, int64* total_out):
    ok = (*(*handle->driver + 256))(handle->driver, &free, &total)
    if (ok): *free_out = free; *total_out = total
    return ok

// TpuExecutor_GetAllocatorStats                       0xeab91a0
char GetAllocatorStats(SE_StreamExecutor* handle, SE_AllocatorStats* out):
    (*(*handle->driver + 320))(&stats)      // driver fills a stats struct on the stack
    if (stats.valid == 1):                  // last byte is the "present" flag
        copy num_allocs / bytes_in_use / peak_bytes ... into *out
        // each optional field (bytes_limit, largest_alloc) guarded by its own bool
    return stats.valid

GOTCHA—— Allocate 通过输出指针(arg0 是结果槽,执行器句柄是 arg1按值返回 16 字节的 DeviceMemoryBase,反编译中用内联 vmovups xmm0 对复制它,而不是字段赋值。把 DeviceMemoryBase 当作单个指针的重新实现会丢失第二个 qword(大小 / 元素计数)。该结构体是 16 字节:{opaque_or_base, payload};两个 qword 都要复制。

QUIRK—— GetAllocatorStats 把驱动填充的 stats 结构体的最后一个字节读作存在标志,只有它为 1 时才复制字段。子字段(bytes_limitlargest_alloc_size)在值之前各自带有自己的 bool guard,对应 std::optional<int64>。跳过 guard 会把未初始化的 qword 复制进主机的 stats——这些 guard 不是可选的。


目的

操作主机已经创建的流。CreateStreamDependency 记录 happens-before 边(一条流等待另一条流);DeallocateStream 拆除一条流并释放插件的引用。流的创建本身在主机侧完成(见 速览 中的 QUIRK)。

函数映射

函数地址大小驱动派发
TpuExecutor_CreateStreamDependency0xeab92a048*arg1->vtable+56(在对象上)
TpuExecutor_DeallocateStream0xeab926058driver_vtable+224,然后执行 stream dtor vtable+8

算法

c
// TpuExecutor_CreateStreamDependency                  0xeab92a0
bool CreateStreamDependency(_, Stream** dependent, Stream** dependency):
    rep = (*(*(*dependent) + 56))(*dependent, *dependency)   // dispatch is on the STREAM vtable
    if (!is_ok_inline(rep)): Unref(rep)
    return rep == OK_SENTINEL                                 // (&dword_0 + 1)

// TpuExecutor_DeallocateStream                        0xeab9260
int64 DeallocateStream(SE_StreamExecutor* handle, Stream** stream):
    rep = (*(*handle->driver + 224))(handle->driver, *stream) // detach from driver
    s = *stream; *stream = 0
    if (s): (*(*s + 8))(s)                                    // virtual destructor at vtable+8
    return rep

QUIRK—— CreateStreamDependency流对象的 vtable 上派发(*arg1 是 dependent stream,+56 索引它的 vtable),而不是在执行器的驱动 vtable 上。它是唯一完全绕过执行器句柄的流领域函数——执行器作为 arg0 传入但未使用。应把等待边复现为 dependent stream 上的方法,而不是执行器上的方法。


事件

目的

创建设备事件(AllocateEvent)、标记它已在某条流上到达(RecordEvent),并让一条流等待它(WaitForEvent)。事件是 SE 的跨流同步原语;这三个函数加上 BlockHostUntilDone 构成了完整的设备侧事件表面。

函数映射

函数地址大小驱动派发
TpuExecutor_AllocateEvent0xeab9340179driver_vtable+72(创建事件),引用计数管线
TpuExecutor_RecordEvent0xeab940063*arg1->vtable+72(在上:记录事件)
TpuExecutor_WaitForEvent0xeab944063*arg1->vtable+64(在上:等待事件)

算法

c
// TpuExecutor_AllocateEvent                           0xeab9340
int64 AllocateEvent(SE_StreamExecutor* handle, Event** out_event, StatusRep** out_status):
    (*(*handle->driver + 72))(&tmp)        // driver constructs the event (refcounted handle)
    if (tmp.status == OK_SENTINEL):
        move tmp.event into *out_event     // transfer ownership; null the temp
    store_status(out_status, tmp.status)   // with the usual Unref bookkeeping

// TpuExecutor_RecordEvent / TpuExecutor_WaitForEvent  0xeab9400 / 0xeab9440
int64 RecordEvent(_, Stream** stream, Event** event, StatusRep** out):
    rep = (*(*(*stream) + 72))(*stream, *event)   // RecordEvent: stream vtable +72
    // WaitForEvent is identical with offset +64
    return store_status(out, rep)

NOTE—— RecordEventWaitForEvent 都在 vtable 上派发(偏移分别为 +72+64),不是执行器。执行器句柄是 arg0 且被忽略;arg1 是流,arg2 是事件。只有 AllocateEvent 触碰驱动 vtable(+72),因为只有事件构造属于执行器。AllocateEvent 是此函数簇第二大的函数(179 字节),完全是因为双重引用计数修正——它同时管理事件句柄的 refcount 和 status rep 的 refcount。


传输——Memcpy

目的

在主机和设备之间复制缓冲区。四个变体沿两个轴拆分:方向ToHost = D→H,FromHost = H→D)和同步性Synchronous* 阻塞调用线程;普通 Memcpy* 入队到流并返回)。四者在派发前都会通过 ApiConverter::FromC 编组 DeviceMemoryBase

函数映射

函数地址大小方向同步驱动派发
TpuExecutor_SynchronousMemcpyToHost0xeab9480112D→Hblockingdriver_vtable+216
TpuExecutor_SynchronousMemcpyFromHost0xeab9500109H→Dblockingdriver_vtable+208
TpuExecutor_MemcpyToHost0xeab9580109D→Hstreamdriver_vtable+80
TpuExecutor_MemcpyFromHost0xeab9600109H→Dstreamdriver_vtable+88

算法

c
// All four share this body; only the vtable offset and arg order differ.
// TpuExecutor_SynchronousMemcpyToHost                 0xeab9480 (offset +216)
int64 SyncMemcpyToHost(SE_StreamExecutor* handle, void* host_dst,
                       SE_DeviceMemoryBase* src, int64 size, StatusRep** out):
    DeviceAddressBase dev = ApiConverter::FromC(src)   // flat twin -> C++ object on stack
    rep = (*(*handle->driver + 216))(handle->driver, host_dst, &dev, size)
    return store_status(out, rep)

// Offset map:
//   SynchronousMemcpyToHost   +216   (D->H, blocking)
//   SynchronousMemcpyFromHost +208   (H->D, blocking)  -- FromC on the H->D dest buffer
//   MemcpyToHost              +80    (D->H, enqueue on arg2 stream)
//   MemcpyFromHost           +88    (H->D, enqueue on arg2 stream)

QUIRK—— 同步和异步变体使用相距很远的驱动 vtable 槽位:阻塞的一对位于 +208/+216,而流入队的一对位于 +80/+88。它们既不相邻,也不按方向交错——驱动先按同步性分组。布局驱动 vtable 的重新实现者不能假设四个 memcpy 方法是连续的。异步 Memcpy* 多接受一个流参数(入队所针对的缓冲区);Synchronous* 没有。

GOTCHA—— 每个 memcpy 都会在 vtable 调用之前,用 ApiConverter::FromCDeviceMemoryBase 重建到 64 字节栈临时区(v10/v11/v12)中,并传递 &scratch。扁平 C 的 SE_DeviceMemoryBase 从不直接交给驱动——驱动期望的是 C++ DeviceAddressBase。跳过 FromC 会让驱动读取形状错误的结构体。ToC/FromC 契约见 overview


传输——Infeed / Outfeed

目的

把 literal 推入设备的 infeed 队列(EnqueueInfeed),以及从 outfeed 队列拉取一个 literal(DequeueOutfeed)。这些是 SE 层的入口桩;队列模型和 transfer-manager 协作见 Infeed/Outfeed

函数映射

函数地址大小驱动派发
TpuExecutor_EnqueueInfeed0xeab968059直接调用 DeepseaExecutor::EnqueueInfeed
TpuExecutor_DequeueOutfeed0xeab96c068直接调用 DeepseaExecutor::DequeueOutfeed,带一个 AnyInvocable completion

算法

c
// TpuExecutor_EnqueueInfeed                           0xeab9680
int64 EnqueueInfeed(SE_StreamExecutor* handle, ..., StatusRep** out):
    rep = deepsea::executor::DeepseaExecutor::EnqueueInfeed(*handle)  // NOT a vtable slot
    return store_status(out, rep)

// TpuExecutor_DequeueOutfeed                          0xeab96c0
int64 DequeueOutfeed(SE_StreamExecutor* handle, ..., callback):
    // wraps the host callback in an absl::AnyInvocable (LocalInvoker/LocalManagerTrivial)
    DeepseaExecutor::DequeueOutfeed(*handle, ..., &any_invocable)
    return any_invocable.invoke()   // fires the trivially-managed completion

QUIRK—— EnqueueInfeedDequeueOutfeed 直接调用具名的 deepsea::executor::DeepseaExecutor::EnqueueInfeed / DequeueOutfeed 符号,不是像所有内存/同步函数那样通过 vtable 偏移调用。DequeueOutfeed 还会构建一个 absl::internal_any_invocable(一对 LocalManagerTrivial / LocalInvoker),把主机的 completion callback 带入驱动,并在返回时调用它。这两个是唯一不是纯 vtable bounce 的传输函数。


同步

目的

阻塞主机线程直到工作完成:直到一条流排空(BlockHostUntilDone),或直到执行器的所有流都静止(SynchronizeAllActivity)。WaitForEvent(见 事件)是流到流的对应项。

函数映射

函数地址大小驱动派发
TpuExecutor_BlockHostUntilDone0xeab972060*arg1->vtable+120(在上)
TpuExecutor_SynchronizeAllActivity0xeab976012driver_vtable+168

算法

c
// TpuExecutor_BlockHostUntilDone                      0xeab9720
int64 BlockHostUntilDone(_, Stream** stream, StatusRep** out):
    rep = (*(*(*stream) + 120))(*stream)   // dispatch on the STREAM vtable +120
    return store_status(out, rep)

// TpuExecutor_SynchronizeAllActivity                  0xeab9760  (12 bytes — bare bounce)
int64 SynchronizeAllActivity(SE_StreamExecutor* handle):
    return (*(*handle->driver + 168))(handle->driver)

SynchronizeAllActivity 大小为 12 字节,是函数簇中最小的非平凡函数:一次通过 driver_vtable+168 的尾调用,没有 status-out 管线(它原样返回驱动值)。它是 vtable-bounce 模式最纯粹的例子——加载 *handle,加载 *(driver),调用 +168,返回。

NOTE—— BlockHostUntilDone vtable(+120)上派发,与 RecordEvent/WaitForEvent/CreateStreamDependency 一致——任何作用于特定流的操作都会寻址该流的 vtable,而执行器范围的操作(SynchronizeAllActivityAllocate、memcpy)会寻址驱动 vtable。这种流作用域与执行器作用域的派发拆分,是整个函数簇最清晰的组织规则。


HBM / 主机侧

目的

三个不适合归入常规流/事件/memcpy 的操作:在流上压缩 HBM 分配器(EnqueueCompactionOnStreamForHbm)、把主机侧回调作为设备流节点运行(HostCallback),以及从执行器卸载所有已加载程序(UnloadAllPrograms)。

函数映射

函数地址大小驱动派发
TpuExecutor_EnqueueCompactionOnStreamForHbm0xeab97c066driver_vtable+432
TpuExecutor_HostCallback0xeab9c20149*arg1->vtable+128(在上),包装一个 std::function
TpuExecutor_UnloadAllPrograms0xeab978063driver_vtable+424

算法

c
// TpuExecutor_UnloadAllPrograms                       0xeab9780
int64 UnloadAllPrograms(SE_StreamExecutor* handle, StatusRep** out):
    rep = (*(*handle->driver + 424))(handle->driver)
    return store_status(out, rep)

// TpuExecutor_EnqueueCompactionOnStreamForHbm         0xeab97c0
int64 EnqueueCompaction(SE_StreamExecutor* handle, Stream** stream, StatusRep** out):
    rep = (*(*handle->driver + 432))(handle->driver, *stream)
    return store_status(out, rep)

// TpuExecutor_HostCallback                            0xeab9c20
bool HostCallback(_, Stream** stream, fn_ptr, fn_ctx):
    closure = operator new(0x20)                  // {fn_ptr, fn_ctx, policy thunks}
    wrap closure in absl::AnyInvocable (RemoteManagerNontrivial/RemoteInvoker)
    rep = (*(*(*stream) + 128))(*stream, &any_invocable)   // stream vtable +128
    destroy any_invocable
    return rep == OK_SENTINEL

QUIRK—— HostCallback 会用 operator new(0x20) 堆分配一个 32 字节闭包,保存 {fn_ptr, fn_ctx, __call_func thunk, __policy ptr},将其包装成 std::functionabsl::AnyInvocable非平凡 RemoteManager 路径,不同于 DequeueOutfeed 的平凡路径),并交给流的 +128 槽位。它是五个流 vtable 派发函数中唯一同时进行堆分配的函数——重新实现者必须释放闭包(反编译显示无论状态如何都会在调用后销毁 any-invocable),否则每次 host callback 都会泄漏一次分配。EnqueueCompactionOnStreamForHbm+432)和 UnloadAllPrograms+424)是此函数簇中最高的驱动 vtable 偏移,位于 memcpy/sync 块之后。


拓扑

目的

返回执行器的 TpuCoreLocation——它在网格中的 chip/core 坐标。它是此函数簇中唯一的异类成员:没有状态,没有通过 *arg0 的句柄间接层,而且函数体最小(8 字节)。

函数映射

函数地址大小驱动派发
TpuExecutor_GetCoreLocation0xeab93208直接尾调用 DeepseaExecutor::GetCoreLocation(*handle)

算法

c
// TpuExecutor_GetCoreLocation                         0xeab9320  (8 bytes)
int64 GetCoreLocation(DeepseaExecutor** handle):
    return deepsea::executor::DeepseaExecutor::GetCoreLocation(*handle)  // pure tail call

NOTE—— 8 字节的函数体就是一个到 C++ 方法的 jmp——没有 vtable 间接层,没有状态包装。返回的 TpuCoreLocationTpuCoreLocation_* 函数簇操作的同一个不透明坐标句柄(见 TpuTopology & TpuCoreLocation)。执行器暴露自身位置;关于网格的其他一切都来自 TpuTopology_*


派发模式提炼

函数簇中的每个函数都可归约为四种派发形态之一。重新实现者只要内化这些形态和偏移表,就不需要其他内容。

形态成员形式
驱动 vtable bounceAllocate, Deallocate, GetAllocatorStats, DeviceMemoryUsage, 全部 4 个 memcpy, SynchronizeAllActivity, UnloadAllPrograms, EnqueueCompactionOnStreamForHbm, AllocateEvent, Init/GetStatus, CreateDeviceDescription(*(*handle->driver + N))(driver, args)
流 vtable bounceCreateStreamDependency, RecordEvent, WaitForEvent, BlockHostUntilDone, HostCallback(*(*(*stream) + N))(stream, args)——执行器句柄未使用
直接具名调用GetCoreLocation, EnqueueInfeed, DequeueOutfeedDeepseaExecutor::Method(*handle, ...)
纯 CFreeif (p) free(p)
text
SE_StreamExecutor* handle  (arg0)
   │  *handle

DeepseaExecutor* driver    ── executor-scoped ops dispatch here:  *(driver_vtable + N)
   │  *driver

driver vtable  [+32 status][+72 alloc-event][+80/+88 async-memcpy]
               [+136 alloc][+144 dealloc][+168 sync-all]
               [+208/+216 sync-memcpy][+224 dealloc-stream][+256 mem-usage]
               [+272 device-desc][+320 alloc-stats][+424 unload][+432 compact]

Stream* (arg1/arg2)        ── stream-scoped ops dispatch here:  *(stream_vtable + N)
   └─ stream vtable [+56 add-dependency][+64 wait-event][+72 record-event]
                    [+120 block-host][+128 host-callback]

GOTCHA—— 上面两张图中的偏移属于两个不同的 vtable驱动上的 +72 是“allocate event”;上的 +72 是“record event”。把二者拍平成同一个偏移空间的实现会错误派发每个流操作。判别依据是 arg0(执行器 → 驱动 vtable)还是 arg1/arg2(流 → 流 vtable)。


相关组件

名称关系
TfTpu_ExecutorApiFn (struct)这些 25 个函数填充的函数指针表,通过 ExecutorApiFn() @ 0x20819360 到达
stream_executor::tpu::TpuExecutor (0xe996ca0+)调用进入这些槽位的主机侧 C++ shim;它自行拥有 CreateStream
deepsea::executor::DeepseaExecutorSE_StreamExecutor 句柄背后的真实驱动对象;拥有这些函数派发所经由的 vtable
ApiConverter::FromC为每个 memcpy/deallocate 从扁平 SE_DeviceMemoryBase 重建 DeviceAddressBase
TpuTransferManager_*位于执行器原始 memcpy/infeed/outfeed 原语之上的 literal 级主机↔设备传输表面

交叉引用