TpuExecutor 名册
地址适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,ELF x86-64 DYN,未剥离;.textVMA 等于文件偏移)。其他版本会不同。
摘要
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+136,Deallocate 到 vtable+144,BlockHostUntilDone 到 vtable+120,依此类推。偏移就是完整的派发契约:没有名称查找,没有动态解析,只有 (*(vtable + N))(driver, args...)。少数函数跳过 vtable,直接调用具名的 DeepseaExecutor::* 方法(GetCoreLocation、EnqueueInfeed、DequeueOutfeed),另有一个函数(Free)只是裸 free()。
本页只负责名册以及逐函数 impl-symbol→vtable-slot 映射。位于表前面的 *ApiFn() 访问器模式、不透明句柄 / ApiConverter 编组约定,以及所有函数簇的名册映射,见 shim 概览——这里只链接,不再重复解释。通过此表发起调用的主机侧 stream_executor::tpu::TpuExecutor C++ shim 见 StreamExecutor 平台与执行器模型。内存操作最终落到的 HBM 分配器见 分配器集成。
对重新实现而言,契约是:
- 句柄间接层——
arg0是SE_StreamExecutor*;*arg0是DeepseaExecutor*驱动对象;**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 入口桩。
速览——按领域划分的函数
| 领域 | 数量 | 函数 |
|---|---|---|
| 生命周期 | 4 | Init, GetStatus, CreateDeviceDescription, Free |
| 内存 | 4 | Allocate, Deallocate, GetAllocatorStats, DeviceMemoryUsage |
| 流 | 2 | CreateStreamDependency, DeallocateStream |
| 事件 | 3 | AllocateEvent, RecordEvent, WaitForEvent |
| 传输 (memcpy) | 4 | SynchronousMemcpyToHost, SynchronousMemcpyFromHost, MemcpyToHost, MemcpyFromHost |
| 传输 (infeed/outfeed) | 2 | EnqueueInfeed, DequeueOutfeed |
| 同步 | 3 | BlockHostUntilDone, SynchronizeAllActivity, WaitForEvent(也属于事件) |
| HBM / 主机侧 | 3 | EnqueueCompactionOnStreamForHbm, HostCallback, UnloadAllPrograms |
| 拓扑 | 1 | GetCoreLocation |
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)。Init 和 GetStatus 是同一个 vtable 槽位(+32)——二者都向驱动询问“你是否正常?”,一次在启动时,一次按需。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_Init | 0xeab90c0 | 60 | driver_vtable+32(状态查询) |
TpuExecutor_GetStatus | 0xeab92e0 | 60 | driver_vtable+32(与 Init 相同槽位) |
TpuExecutor_CreateDeviceDescription | 0xeab9cc0 | 991 | driver_vtable+272,然后构建 stream_executor::DeviceDescription |
TpuExecutor_Free | 0xeab9100 | 16 | 无——if (p) free(p) |
算法
// 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::DeviceDescriptionCreateDeviceDescription 是此函数簇中的异常项,大小为 991 字节:它通过 driver_vtable+272 拉取一个已填充的 device-info blob,然后逐一复制字符串和数值字段,组装出 stream_executor::DeviceDescription(反编译中的长 v17…v29 链就是字符串字段编组)。逐字段复制是它远大于其他名册项的原因;派发本身仍然只是一次 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_Allocate | 0xeab9120 | 55 | driver_vtable+136 | 按值返回 16 字节 DeviceMemoryBase |
TpuExecutor_Deallocate | 0xeab9160 | 50 | driver_vtable+144 | 对 DeviceMemoryBase 参数执行 ApiConverter::FromC |
TpuExecutor_GetAllocatorStats | 0xeab91a0 | 120 | driver_vtable+320 | 复制可选的 AllocatorStats 字段 |
TpuExecutor_DeviceMemoryUsage | 0xeab9220 | 64 | driver_vtable+256 | 两个输出参数:free, total |
算法
// 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.validGOTCHA——
Allocate通过输出指针(arg0是结果槽,执行器句柄是arg1)按值返回 16 字节的DeviceMemoryBase,反编译中用内联vmovups xmm0对复制它,而不是字段赋值。把DeviceMemoryBase当作单个指针的重新实现会丢失第二个 qword(大小 / 元素计数)。该结构体是 16 字节:{opaque_or_base, payload};两个 qword 都要复制。QUIRK——
GetAllocatorStats把驱动填充的 stats 结构体的最后一个字节读作存在标志,只有它为1时才复制字段。子字段(bytes_limit、largest_alloc_size)在值之前各自带有自己的boolguard,对应std::optional<int64>。跳过 guard 会把未初始化的 qword 复制进主机的 stats——这些 guard 不是可选的。
流
目的
操作主机已经创建的流。CreateStreamDependency 记录 happens-before 边(一条流等待另一条流);DeallocateStream 拆除一条流并释放插件的引用。流的创建本身在主机侧完成(见 速览 中的 QUIRK)。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_CreateStreamDependency | 0xeab92a0 | 48 | *arg1->vtable+56(在流对象上) |
TpuExecutor_DeallocateStream | 0xeab9260 | 58 | driver_vtable+224,然后执行 stream dtor vtable+8 |
算法
// 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 repQUIRK——
CreateStreamDependency在流对象的 vtable 上派发(*arg1是 dependent stream,+56索引它的 vtable),而不是在执行器的驱动 vtable 上。它是唯一完全绕过执行器句柄的流领域函数——执行器作为arg0传入但未使用。应把等待边复现为 dependent stream 上的方法,而不是执行器上的方法。
事件
目的
创建设备事件(AllocateEvent)、标记它已在某条流上到达(RecordEvent),并让一条流等待它(WaitForEvent)。事件是 SE 的跨流同步原语;这三个函数加上 BlockHostUntilDone 构成了完整的设备侧事件表面。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_AllocateEvent | 0xeab9340 | 179 | driver_vtable+72(创建事件),引用计数管线 |
TpuExecutor_RecordEvent | 0xeab9400 | 63 | *arg1->vtable+72(在流上:记录事件) |
TpuExecutor_WaitForEvent | 0xeab9440 | 63 | *arg1->vtable+64(在流上:等待事件) |
算法
// 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——
RecordEvent和WaitForEvent都在流 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_SynchronousMemcpyToHost | 0xeab9480 | 112 | D→H | blocking | driver_vtable+216 |
TpuExecutor_SynchronousMemcpyFromHost | 0xeab9500 | 109 | H→D | blocking | driver_vtable+208 |
TpuExecutor_MemcpyToHost | 0xeab9580 | 109 | D→H | stream | driver_vtable+80 |
TpuExecutor_MemcpyFromHost | 0xeab9600 | 109 | H→D | stream | driver_vtable+88 |
算法
// 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::FromC把DeviceMemoryBase重建到 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_EnqueueInfeed | 0xeab9680 | 59 | 直接调用 DeepseaExecutor::EnqueueInfeed |
TpuExecutor_DequeueOutfeed | 0xeab96c0 | 68 | 直接调用 DeepseaExecutor::DequeueOutfeed,带一个 AnyInvocable completion |
算法
// 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 completionQUIRK——
EnqueueInfeed和DequeueOutfeed直接调用具名的deepsea::executor::DeepseaExecutor::EnqueueInfeed/DequeueOutfeed符号,不是像所有内存/同步函数那样通过 vtable 偏移调用。DequeueOutfeed还会构建一个absl::internal_any_invocable(一对LocalManagerTrivial/LocalInvoker),把主机的 completion callback 带入驱动,并在返回时调用它。这两个是唯一不是纯 vtable bounce 的传输函数。
同步
目的
阻塞主机线程直到工作完成:直到一条流排空(BlockHostUntilDone),或直到执行器的所有流都静止(SynchronizeAllActivity)。WaitForEvent(见 事件)是流到流的对应项。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_BlockHostUntilDone | 0xeab9720 | 60 | *arg1->vtable+120(在流上) |
TpuExecutor_SynchronizeAllActivity | 0xeab9760 | 12 | driver_vtable+168 |
算法
// 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,而执行器范围的操作(SynchronizeAllActivity、Allocate、memcpy)会寻址驱动 vtable。这种流作用域与执行器作用域的派发拆分,是整个函数簇最清晰的组织规则。
HBM / 主机侧
目的
三个不适合归入常规流/事件/memcpy 的操作:在流上压缩 HBM 分配器(EnqueueCompactionOnStreamForHbm)、把主机侧回调作为设备流节点运行(HostCallback),以及从执行器卸载所有已加载程序(UnloadAllPrograms)。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_EnqueueCompactionOnStreamForHbm | 0xeab97c0 | 66 | driver_vtable+432 |
TpuExecutor_HostCallback | 0xeab9c20 | 149 | *arg1->vtable+128(在流上),包装一个 std::function |
TpuExecutor_UnloadAllPrograms | 0xeab9780 | 63 | driver_vtable+424 |
算法
// 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_SENTINELQUIRK——
HostCallback会用operator new(0x20)堆分配一个 32 字节闭包,保存{fn_ptr, fn_ctx, __call_func thunk, __policy ptr},将其包装成std::function→absl::AnyInvocable(非平凡RemoteManager路径,不同于DequeueOutfeed的平凡路径),并交给流的+128槽位。它是五个流 vtable 派发函数中唯一同时进行堆分配的函数——重新实现者必须释放闭包(反编译显示无论状态如何都会在调用后销毁 any-invocable),否则每次 host callback 都会泄漏一次分配。EnqueueCompactionOnStreamForHbm(+432)和UnloadAllPrograms(+424)是此函数簇中最高的驱动 vtable 偏移,位于 memcpy/sync 块之后。
拓扑
目的
返回执行器的 TpuCoreLocation——它在网格中的 chip/core 坐标。它是此函数簇中唯一的异类成员:没有状态,没有通过 *arg0 的句柄间接层,而且函数体最小(8 字节)。
函数映射
| 函数 | 地址 | 大小 | 驱动派发 |
|---|---|---|---|
TpuExecutor_GetCoreLocation | 0xeab9320 | 8 | 直接尾调用 DeepseaExecutor::GetCoreLocation(*handle) |
算法
// TpuExecutor_GetCoreLocation 0xeab9320 (8 bytes)
int64 GetCoreLocation(DeepseaExecutor** handle):
return deepsea::executor::DeepseaExecutor::GetCoreLocation(*handle) // pure tail callNOTE—— 8 字节的函数体就是一个到 C++ 方法的
jmp——没有 vtable 间接层,没有状态包装。返回的TpuCoreLocation是TpuCoreLocation_*函数簇操作的同一个不透明坐标句柄(见 TpuTopology & TpuCoreLocation)。执行器暴露自身位置;关于网格的其他一切都来自TpuTopology_*。
派发模式提炼
函数簇中的每个函数都可归约为四种派发形态之一。重新实现者只要内化这些形态和偏移表,就不需要其他内容。
| 形态 | 成员 | 形式 |
|---|---|---|
| 驱动 vtable bounce | Allocate, Deallocate, GetAllocatorStats, DeviceMemoryUsage, 全部 4 个 memcpy, SynchronizeAllActivity, UnloadAllPrograms, EnqueueCompactionOnStreamForHbm, AllocateEvent, Init/GetStatus, CreateDeviceDescription | (*(*handle->driver + N))(driver, args) |
| 流 vtable bounce | CreateStreamDependency, RecordEvent, WaitForEvent, BlockHostUntilDone, HostCallback | (*(*(*stream) + N))(stream, args)——执行器句柄未使用 |
| 直接具名调用 | GetCoreLocation, EnqueueInfeed, DequeueOutfeed | DeepseaExecutor::Method(*handle, ...) |
| 纯 C | Free | if (p) free(p) |
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::DeepseaExecutor | SE_StreamExecutor 句柄背后的真实驱动对象;拥有这些函数派发所经由的 vtable |
ApiConverter::FromC | 为每个 memcpy/deallocate 从扁平 SE_DeviceMemoryBase 重建 DeviceAddressBase |
TpuTransferManager_* | 位于执行器原始 memcpy/infeed/outfeed 原语之上的 literal 级主机↔设备传输表面 |
交叉引用
- TfTpu C-API Shim——这些函数依赖的
*ApiFn()访问器模式、不透明句柄约定和ApiConverter编组 - StreamExecutor 平台与执行器模型——调用此表的主机侧
TpuExecutorC++ shim - TpuTransferManager 名册——使用执行器 memcpy/infeed/outfeed 原语的更高层传输 C ABI
- TpuProgram 名册——
UnloadAllPrograms从执行器释放的程序对象 - TpuPlatform 与 TpuNodeContext——创建
TpuExecutor的平台,以及GetCoreLocation返回的TpuCoreLocation - 分配器集成——
Allocate/Deallocate/GetAllocatorStats/EnqueueCompactionOnStreamForHbm背后的 HBM 分配器 - Infeed/Outfeed——
EnqueueInfeed/DequeueOutfeed背后的队列模型