StreamExecutor 平台与执行器模型
本页所有地址均适用于来自
libtpu-0.0.40-cp314wheel 的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未 strip;反混淆后的 C++ 符号按原文引用)。.textVMA 等于文件偏移。其他版本会有所不同。
摘要
stream_executor 是 XLA 所依赖的旧式设备抽象层,角色类似于 CUDA 程序中的 CUDA driver API:一组与设备无关的类族 — Platform、StreamExecutor、Stream、Event、DeviceDescription — 供 XLA service 和 PjRtClient 枚举设备、创建逐设备执行器、分配设备内存并启动有序工作。它早于 PJRT 出现;PJRT 是分层构建在其之上的公共 C ABI(见 PJRT 概览)。本页负责说明平台注册握手与 SE 对象模型:TPU 和 host 后端如何把 Platform 注册到全局 registry,Platform 如何通过 ExecutorCache 为每个 ordinal 发放 StreamExecutor,以及具体的 host 侧 SE 对象(HostExecutor、HostStream、host Event)在内存中实际是什么样子。本页有意不重新记录逐执行 enqueue 路径,也不记录跨 stream 的 wait 模型 — 这些内容在其他页面中(见下面的范围说明)。
链接进来的后端有两个。TPU 后端(tensorflow::tpu::TpuPlatform + stream_executor::tpu::TpuExecutor + TpuStream)是一个很薄的 C-ABI shim:每个 SE 调用都会通过单例 TfTpu_ExecutorApiFn 函数表(由 stream_executor::tpu::ExecutorApiFn() 解析)转发到 libtpu 的 TPU driver core,SE 对象只持有不透明的 SE_StreamExecutor* / SE_Stream* 句柄。host/CPU 后端(stream_executor::host::HostPlatform + HostExecutor + HostStream)则是一个真实的二进制内实现,构建在 tsl::thread::ThreadPool 和 absl::Notification 之上;它是同步变体(没有 worker queue),用于 host-memory staging 和简单 host ops。这里没有 InterpreterExecutor — XLA 的独立 interpreter device 没有被链接;host-interpreter 角色由 xla::HloEvaluator(编译期)和 CPU thunk runtime 填补,它们都不是 SE Platform。
本页按自顶向下的顺序展开:registry(PlatformManager)、安装 TpuPlatform 的注册入口、Platform → ExecutorCache → StreamExecutor 创建路径、三级类层次,最后是精确到字节的 host SE 对象模型(executor、stream、event)。每个 Platform/Executor 单元都使用相同的 ### 目的 / ### 入口点 / ### 算法 / ### 函数映射 词汇。
对于重新实现,契约是:
- registry + 注册握手 — 一个进程全局的
PlatformManager单例(匿名命名空间Impl,由__cxa_guard构建),以及RegisterTpuPlatform:它new一个0x98字节的TpuPlatform,存入全局变量,注册它,并 CHECK 成功 — 由 once-flag 保护,并受IsStreamExecutorEnabled控制。 Platform::ExecutorForDevice→ExecutorCache::GetOrCreate→ factory 路径 — 逐 ordinal 的StreamExecutor如何缓存在受absl::Mutex保护的FlatHashMap<int, unique_ptr<StreamExecutor>>中,并且只在 cache miss 时构建。- 三级类层次 —
StreamExecutor(抽象)→StreamExecutorCommon(持有const Platform*)→{HostExecutor, TpuExecutor},包含 vtable 地址和对象大小。 - host SE 对象模型 —
HostExecutor(thread pool)、HostStream(0x80字节,派生自StreamCommon,同步)以及 hostEvent(基于absl::Notification,双对象拆分)的字节级布局。
| Registry | stream_executor::PlatformManager — 单例 Impl @ anon-ns,由 __cxa_guard 构建,0x48 字节(absl::Mutex + 两个 map) |
| 注册入口(TPU) | tensorflow::tpu::RegisterTpuPlatform @ 0xe99a3a0 → CHECK @ tpu_platform.cc:178 |
PlatformManager::RegisterPlatform | 0x1d0fe120 — CHECK platform != nullptr @ platform_manager.cc:93 |
| TPU platform 对象 | tensorflow::tpu::TpuPlatform,new(0x98),全局 tpu_registered_platform |
| Host platform 对象 | stream_executor::host::HostPlatform(ctor 0xfe6d380) |
| Executor cache | stream_executor::ExecutorCache::GetOrCreate @ 0x1d0fd2e0 — FlatHashMap<int, unique_ptr<SE>> + absl::Mutex,log @ executor_cache.cc:44 |
| TpuExecutor | new(0x48),vtable off_21612AC8,: StreamExecutorCommon,SE_StreamExecutor* @ +0x38,ordinal @ +0x40 |
| HostExecutor | Init @ 0xfe6d780 — tsl::thread::ThreadPool("host-executor", NumSchedulableCPUs) |
| HostStream | ctor 0xfe6ec80,new(0x80),vtable off_217B0228,: StreamCommon |
| Host Event | CreateEvent @ 0xfe6d9e0 — new(0x18) wrapper(off_217B0138)+ new(0x28) event(off_215FC128,Notification @ +24) |
| 证据等级 | 重新实现级 / 已用 IDA 反编译按字节确认 |
范围 — 逐执行 enqueue(
ExecuteAsyncOnStream/LoadProgramAndEnqueueToStream)由 ExecuteAsyncOnStream 和 LoadProgramAndEnqueueToStream 负责。跨 stream wait/dependency 模型(WaitFor/RecordEvent、FIFO 排序契约、compute/transfer stream 拆分)由 Stream 语义与依赖 负责。SE → PJRT bridge(TpuClient/ CommonPjRt)由 StreamExecutor → PJRT 适配器 负责。本页只覆盖平台注册 + SE 对象模型层;它链接到那些页面,而不是复述它们。
1. Platform Registry — PlatformManager
目的
stream_executor::PlatformManager 是进程全局 registry,把平台名和平台 id 映射到 Platform*。XLA 的设备枚举代码按名称/id 向它请求“TPU platform”或“host platform”,并取得能够创建 executors 的单例 Platform 对象。每个进程恰好有一个 PlatformManager,并且是惰性构造的。
说明 — 较老的 XLA 把这个类称作
MultiPlatformManager。此二进制中不存在MultiPlatformManager符号;上游 XLA 已将MultiPlatformManager→PlatformManager,去掉了Multi前缀,本构建只包含新名称。重新实现者应以stream_executor::PlatformManager为准。
对象布局
registry 状态是单个匿名命名空间单例 — stream_executor::(anonymous namespace)::Impl()::impl — 一个在 __cxa_guard 下构建的 0x48 字节对象。每个 public PlatformManager static method 都会先 materialise 它。从 RegisterPlatform(0x1d0fe120)和 PlatformWithName(0x1d0fe5c0)读到的布局完全一致:
// Impl(): the PlatformManager singleton (anon-ns, __cxa_guard-built)
struct Impl { // operator new(0x48)
absl::Mutex mu; // +0x00 guard (init {0, 1, 0})
FlatHashMap id_map; // +0x10 PlatformId -> Platform*
FlatHashMap name_map; // +0x28 name -> Platform* (XMM-zeroed at +0x28)
// ...
};
// every accessor begins with:
function PlatformManager_AnyAccessor():
if (!guard_for(impl)): // __cxa_guard_acquire
impl = new Impl(); // zero-init both maps under the guard
guard_release();
...
```text
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `PlatformManager::RegisterPlatform` | `0x1d0fe120` | 安装一个 `unique_ptr<Platform>`;CHECK `platform != nullptr` @ `platform_manager.cc:93`;通过 `platform->vtable+24` 查询 id |
| `PlatformManager::PlatformWithName(string_view)` | `0x1d0fe5c0` | 名称查找 |
| `PlatformManager::PlatformWithName(string_view, bool)` | `0x1d0fe820` | 带 init flag 的名称查找 |
| `PlatformManager::PlatformWithId(const PlatformIdInfo*)` | `0x1d0fe680` | Id 查找 |
| `PlatformManager::PlatformsWithFilter(...)` | `0x1d0ff140` / `0x1d0ff160` | 过滤枚举 |
| `(anon)::PlatformManagerImpl::PlatformWithName` | `0x1d0fe8e0` | static wrappers 后面的真实查找主体 |
| `(anon)::PlatformManagerImpl::InitializedPlatformNames...` | `0x1d100260` | 枚举已初始化的平台(一个 `std::function` filter) |
> **注意 —** `RegisterPlatform` 在插入前通过 `(*platform)->vtable[+24]` 读取平台 id — id 是每个 `Platform` 的*虚拟*属性,而不是构造函数参数。TPU id 来自 `tensorflow::tpu::GetTpuPlatformId() @ 0x20818ec0`。重新实现必须把 id 暴露为 virtual,使 registry 能在不知道具体类型的情况下以它为 key。
---
## 2. 注册 TPU Platform
### 目的
`tensorflow::tpu::RegisterTpuPlatform()` 是构造单例 `TpuPlatform` 并将其安装到 registry 的一次性入口。它在 libtpu 初始化期间运行(由 TPU-driver bring-up 路径驱动,而不是由 PJRT 调用驱动)。返回之后,`PlatformManager::PlatformWithName("TPU")` 可以解析,PjRtClient 就能创建 executors。
### 算法
```c
// tensorflow::tpu::RegisterTpuPlatform() sub_E99A3A0
function RegisterTpuPlatform():
apifn = ExecutorApiFn() // resolve TfTpu_ExecutorApiFn singleton
if (IsStreamExecutorEnabled(apifn) // SE backend must be enabled
&& !tpu_platform_registered): // function-local once-flag
p = (TpuPlatform*)operator new(0x98) // 152-byte platform object
TpuPlatform::TpuPlatform(p) // ctor sub_E999960
tpu_registered_platform = p // stash in a process global
st = PlatformManager::RegisterPlatform(p) // 0x1d0fe120 (moves the unique_ptr)
if (st != OkStatus): // st != 1
LOG(FATAL) "stream_executor::PlatformManager::RegisterPlatform( std::move(platform)) is OK"
@ tpu_platform.cc:178 // brings the process down
tpu_platform_registered = 1
return 1 // always returns true三个对重新实现至关重要的事实。(1) 注册受 IsStreamExecutorEnabled 控制 — 如果 SE backend 被禁用,platform 永远不会安装,该函数会作为 no-op 返回 true。(2) once-flag tpu_platform_registered 是函数局部 static,因此重复调用是幂等的。(3) RegisterPlatform 的成功在 tpu_platform.cc:178 处是 hard CHECK — 注册失败是 fatal,而不是可恢复错误。构造出的对象大小为 0x98(152)字节;全局 tpu_registered_platform 会由 TpuPlatform::GetRegisteredPlatform() @ 0xe999aa0 读回,该函数是一行 return tpu_registered_platform。
TpuPlatform::Initialize
与注册不同,Initialize(0xe999ac0)在 platform 下 bring up TPU driver。它是纯 C-shim 转发:
// tensorflow::tpu::TpuPlatform::Initialize() sub_E999AC0
function TpuPlatform_Initialize(this):
status_obj = ExecutorApiFn()[+360](this) // device/status scratch object
ExecutorApiFn()[+16](this->se_platform/*this+1*/, status_obj) // TfTpu init the platform
if (!ExecutorApiFn()[+408](status_obj)): // status not OK?
code = ExecutorApiFn()[+400](status_obj) // status code
msg = ExecutorApiFn()[+392](status_obj) // status message
rep = absl::Status::MakeRep(code, msg, ..., status_helper.h:38)
ExecutorApiFn()[+384](status_obj) // free the status scratch
return rep_or_Ok
```text
`+360 / +408 / +400 / +392 / +384` slot pattern — *取得 status scratch object,执行 op,查询 ok/code/message,释放 scratch* — 是通用的 “TfTpu C-shim → `absl::Status`” 习惯用法;每个 TPU SE method(executor `Init`、`Allocate`、stream `WaitFor`)都使用它,并且总是锚定在 `status_helper.h:38`。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `tpu::RegisterTpuPlatform` | `0xe99a3a0` | 一次性构造 + 注册;once-flag;fatal CHECK @ `:178` |
| `tpu::GetTpuPlatformId` | `0x20818ec0` | registry 用作 key 的 TPU `PlatformId` |
| `TpuPlatform::TpuPlatform` (C2) | `0xe999960` | `0x98` 字节 platform 的 ctor |
| `TpuPlatform::Initialize` | `0xe999ac0` | 通过 `ExecutorApiFn()+16` 进行 driver bring-up;status @ `status_helper.h:38` |
| `TpuPlatform::GetRegisteredPlatform` | `0xe999aa0` | `return tpu_registered_platform` |
| `TpuPlatform::FindExisting(int)` | `0xe99a4a0` | 逐 ordinal 缓存 executor 查找(不构建) |
| `TpuPlatform::VisibleDeviceCount` | `0xe999d20` | 通过 shim(`ExecutorApiFn()+48`)取得设备数 |
| `TpuPlatform::Insert/Lookup/EraseEvent` | `0xe999fa0`/`0xe99a100`/`0xe99a160` | `Event*` ↔ `SE_Event*` registry(由 `WaitFor`/`RecordEvent` 使用) |
| `TpuPlatform::TpuMemoryLimit` | `0xe99a2c0` | HBM 大小查询 |
| `TpuPlatform::GetTopologyPtr` | `0xe999f40` | 设备 topology 句柄 |
> **注意 —** `TpuPlatform::Insert/Lookup/EraseEvent` 维护 `Event* → SE_Event*` map,TPU stream 的 `WaitFor(Event*)`/`RecordEvent` 会解引用该 map。该 map 的*使用方式*记录在 [Stream 语义](../runtime/stream-semantics.md);platform *拥有*这个 map,因此这些 method 位于 `TpuPlatform` 上。
---
## 3. 创建 Executors — `ExecutorForDevice` → `ExecutorCache`
### 目的
`Platform` 不会为每次请求创建新的 `StreamExecutor`;它按设备 ordinal 为每个设备缓存一个。`Platform::ExecutorForDevice(int ordinal)` 返回已缓存的 executor,或在首次使用时构建它;`GetUncachedExecutor` 是 cache 在 miss 时调用的 factory。这是 `StreamExecutor` 得以存在的唯一路径。
### 入口点
```text
TpuPlatform::ExecutorForDevice(int) 0xe999d40 ── public entry
└─ ExecutorCache::GetOrCreate(cache@+40, ordinal, factory) 0x1d0fd2e0
├─ ExecutorCache::Get(ordinal) 0x1d0fd580 ── fast path: hit
└─ factory() == $_0 lambda ─────────► TpuPlatform::GetUncachedExecutor(int) 0xe999da0
└─ ExecutorApiFn()+32 ── mint SE_StreamExecutor*
└─ new(0x48) TpuExecutor : StreamExecutorCommon算法
ExecutorForDevice(0xe999d40)把 GetUncachedExecutor 调用包装在 std::function factory 中,并把 cache(位于 platform+40)传给 GetOrCreate:
// TpuPlatform::ExecutorForDevice(int ordinal) sub_E999D40
function TpuPlatform_ExecutorForDevice(this, ordinal):
factory = bind(TpuPlatform::GetUncachedExecutor, this, ordinal) // $_0 lambda
ExecutorCache::GetOrCreate(this->cache /*this+40*/, ordinal, factory) // 0x1d0fd2e0
// returns StatusOr<StreamExecutor*>
```text
`ExecutorCache::GetOrCreate`(`0x1d0fd2e0`)是 cache 核心:
```c
// ExecutorCache::GetOrCreate(int ordinal, factory) sub_1D0FD2E0
function ExecutorCache_GetOrCreate(cache, ordinal, factory):
if (ExecutorCache::Get(ordinal) is OK): // 0x1d0fd580 — lock-free-ish hit
return cached // fast path
VLOG(2) "building executor" @ executor_cache.cc:44
se = factory() // run GetUncachedExecutor
if (!se.ok()): return se.status()
lock(cache->mu /*ordinal-keyed table mutex*/)
table = FlatHashMap<int, unique_ptr<StreamExecutor>>(cache+8)
slot = table.find_or_prepare_insert(ordinal) // double-check under lock
if (slot existed): // raced — keep the existing one
unlock; drop our `se`; return existing
table[ordinal] = move(se) // install
unlock
return secache 是由 absl::Mutex 保护的 FlatHashMap<int, unique_ptr<StreamExecutor>>,并采用 double-checked insert:先运行 Get fast path,factory 在锁外构建,然后在锁下 find_or_prepare_insert,保留竞态中获胜的对象。executor_cache.cc:44 处的 VLOG "building executor" 在常见情况下每个 ordinal 只触发一次。
GetUncachedExecutor — factory
// TpuPlatform::GetUncachedExecutor(int ordinal) sub_E999DA0
function TpuPlatform_GetUncachedExecutor(this, ordinal):
status_obj = ExecutorApiFn()[+360](this) // status scratch
se_handle = ExecutorApiFn()[+32](this->se_platform, ordinal, status_obj) // mint SE_StreamExecutor*
if (ExecutorApiFn()[+408](status_obj)): // ok?
exec = (TpuExecutor*)operator new(0x48) // 72-byte executor
StreamExecutorCommon::StreamExecutorCommon(exec, this/*Platform*/)
exec->vtable = off_21612AC8
exec->platform = this // +0x30 (this+6)
exec->se_handle = se_handle // +0x38 (this+7) opaque SE_StreamExecutor*
exec->ordinal = ordinal // +0x40 (this+16, 32-bit)
return StatusOr::ok(exec)
else: // mint failed
return harvest_status(status_obj) @ status_helper.h:38
// (status scratch freed via ExecutorApiFn()+384)
```text
`TpuExecutor` 是 `0x48`(72)字节对象:`StreamExecutorCommon` base(持有 `const Platform*`)、vtable `off_21612AC8`、位于 `+0x30` 的*第二份* `Platform*` 副本、位于 `+0x38` 的不透明 driver 句柄 `SE_StreamExecutor*`,以及位于 `+0x40` 的设备 ordinal。它自身不拥有设备状态 — 每个 method 都通过 `ExecutorApiFn()` 针对 `+0x38` 句柄转发。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `TpuPlatform::ExecutorForDevice` | `0xe999d40` | public mint-or-cache 入口;cache @ `platform+40` |
| `TpuPlatform::GetUncachedExecutor` | `0xe999da0` | Factory:`ExecutorApiFn()+32` mint + `new(0x48)` `TpuExecutor` |
| `ExecutorCache::GetOrCreate` | `0x1d0fd2e0` | double-checked 逐 ordinal cache;log @ `executor_cache.cc:44` |
| `ExecutorCache::Get` | `0x1d0fd580` | fast-path cached lookup |
| `TpuExecutor::Init` | `0xe996980` | 通过 shim 的逐 executor init |
| `TpuExecutor::CreateStream` | `0xe996ca0` | 创建 `TpuStream` |
| `TpuExecutor::CreateEvent` | `0xe996e60` | 创建 `TpuEvent` |
| `TpuExecutor::Allocate` | `0xe996fa0` | 通过 shim 分配设备内存 |
---
## 4. 类层次
### 三级层次
```text
stream_executor::StreamExecutor (abstract base, vtable off_21FDAD98)
└ stream_executor::StreamExecutorCommon (concrete intermediate; holds const Platform*)
├ stream_executor::host::HostExecutor (CPU thread pool; in-binary)
└ stream_executor::tpu::TpuExecutor (C-shim over TfTpu_ExecutorApiFn)
stream_executor::Stream (abstract base)
└ stream_executor::StreamCommon (holds parent StreamExecutor* @ +0x48)
├ stream_executor::host::HostStream (synchronous, in-binary)
└ tensorflow::tpu::TpuStream (: TpuStreamInterface, C-shim)StreamExecutorCommon 是与设备无关的中间层:它持有 const Platform*,并实现 GetPlatform、GetDeviceDescription、Activate、GetMemoryLimitBytes — 所有不需要触碰设备的内容。两个具体 executor 的区别只在于其 primitive operations 落到哪里:HostExecutor 落到 tsl::thread::ThreadPool,TpuExecutor 落到 TfTpu_ExecutorApiFn 表。Stream 层次精确镜像这一点,StreamCommon 持有 parent back-pointer(记录于 Stream 语义 §1)。
Base / common 层
| 类 | 角色 | 关键符号 |
|---|---|---|
StreamExecutor | 抽象设备 executor;vtable off_21FDAD98 | ctor 0x208193e0,D2 0x20819440 |
StreamExecutorCommon | 具体中间层;持有 const Platform* | ctor 0x1d0f03e0,GetDeviceDescription 0x1d0f04a0 |
Platform | 抽象 platform;FindExisting(int) | FindExisting 0xe718b60 |
ExecutorCache | 逐 ordinal executor cache | GetOrCreate 0x1d0fd2e0,Get 0x1d0fd580 |
DeviceDescription | 可复制的 device-info bag | copy-ctor 0xe6b5ee0 |
RuntimeAbiVersionManager | 单例 ABI guard | GetInstance 0xe6b8040 |
特性 — libtpu 中没有
stream_executor::interpreter::InterpreterExecutor。XLA 独立 interpreter device(xla/backends/interpreter/)没有被链接。唯一的 “interpreter” 符号是 ANTLR parser runtime 和mlir::interpreter::*,二者都与设备执行无关。期望有一个 interpreterPlatform可回退的重新实现者不会找到它 — host-interpreter 角色被拆分到编译期xla::HloEvaluator(constant folding)和运行时 CPU thunk backend,它们都不是 SEPlatform。当 compiler 无法 fold 某个内容时,该节点只是留在图中交给真实 backend。
5. Host SE 对象模型
目的
host 后端(stream_executor::host::*)是唯一一个有真实二进制内实现、而不是 C shim 的 SE 后端。它用于 host-memory staging、简单 host ops,以及 CPU-only PjRtClient 会驱动的 executor。它是同步变体:没有 worker thread,没有 task queue — 每个操作都在调用线程上 inline 运行,因此 stream 显然总是 drained。
HostPlatform 和 HostExecutor::Init
HostPlatform(ctor 0xfe6d380)镜像 TpuPlatform 的 ExecutorForDevice / GetUncachedExecutor 形状(0xfe6d580 / 0xfe6d5e0),但构建的是 HostExecutor,而不是 shim wrapper。HostExecutor::Init(0xfe6d780)会创建 host 唯一的真实资源 — thread pool:
// stream_executor::host::HostExecutor::Init() sub_FE6D780
function HostExecutor_Init(this):
env = tsl::Env::Default()
n = tsl::port::NumSchedulableCPUs() // one thread per schedulable CPU
pool = operator new(0x40) // tsl::thread::ThreadPool
pool->vtable = off_217B00E8
construct_at<ThreadPool>(pool+3, env, "host-executor", n) // named pool
this->thread_pool = pool + 3 // +0x38 (this+7)
old = this->pool_owner // +0x40 (this+8)
this->pool_owner = pool // refcounted; release the old one
if (old && --old.refcount == 0): destroy(old)
return OkStatus
```text
`Allocate`/`Deallocate` 是普通的 `operator new` / `free`(host memory 只是进程内存,路由到 tcmalloc);`HostMemoryAllocate`(`0xfe6e0c0`)用 `AnyInvocable` deleter 包装 `new`,使返回的 `MemoryAllocation` 能释放自身。
### `HostStream` — 同步 stream 对象
`HostStream::HostStream`(`0xfe6ec80`)是两行 ctor:chain 到 `StreamCommon`,然后设置 vtable。
```c
// stream_executor::host::HostStream::HostStream(StreamExecutor*) sub_FE6EC80
function HostStream_ctor(this, parent):
StreamCommon::StreamCommon(this, parent) // 0x1d100280 — sets parent @ +0x48, base layout
this->vtable = off_217B0228 // 0x80-byte object它是 0x80(128)字节对象(由 HostExecutor::CreateStream 分配 new(0x80))。StreamCommon base 提供 parent pointer 和 FIFO/sub-stream slots;HostStream 增加 host-specific notification slot。CreateStream(0xfe6de20)通过可插拔 factory 路由,并且只有在没有注册 factory 时才 new 一个默认 HostStream:
// stream_executor::host::HostExecutor::CreateStream(...) sub_FE6DE20
function HostExecutor_CreateStream(this):
f = HostStreamFactory::GetFactory() // 0xfe6eb40
if (f): s = f->CreateStream(this) // pluggable override
else: s = new(0x80) HostStream(this) // default path
return StatusOr::ok(s)
```text
stream 的运行时行为由其同步性质决定:`BlockHostUntilDone`(`0xfe6f000`)字面上就是 `return 1`(所有事情已经运行完),而 `DoHostCallbackWithStatus`(`0xfe6efe0`)会在调用线程上 inline 调用 callback。`WaitFor`/`RecordEvent` surface(no-op `WaitFor(Stream*)`、基于 `Notification` 的 `WaitFor(Event*)`/`RecordEvent`)记录在 [Stream 语义 §3](../runtime/stream-semantics.md) — wait 模型由该页面负责。
### Host `Event` — 双对象拆分
`HostExecutor::CreateEvent`(`0xfe6d9e0`)把 host event 构建成**两个**堆对象:
```c
// stream_executor::host::HostExecutor::CreateEvent() sub_FE6D9E0
function HostExecutor_CreateEvent(this):
wrapper = operator new(0x18) // outer Event handle
wrapper->vtable = off_217B0138
inner = operator new(0x28) // the real event state
inner->vtable = off_215FC128
inner->[+24] = absl::Notification{} // single-shot notification
inner->[+32] = 0 // notified flag (byte)
wrapper[+1] = &inner[+24] // ptr to the Notification
wrapper[+2] = inner // ptr to the inner object (for refcount)
return StatusOr::ok(wrapper)这种拆分是有意为之:外层 0x18 字节 wrapper 是 SE API 发放的 Event(vtable off_217B0138);内层 0x28 字节对象(vtable off_215FC128)在 +24 处持有 absl::Notification,在 +32 处持有 notified-flag byte,并带有 shared_weak_count,这样 inner object 可以在 WaitFor/RecordEvent 之间被准确释放一次。host event 是单次触发的 — recording 一个已经 notified 的 event 会在 host_stream.cc:92 CHECK-fail(见 Stream 语义 §3)。
函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
HostPlatform::HostPlatform | 0xfe6d380 | Host platform ctor |
HostPlatform::ExecutorForDevice | 0xfe6d580 | Mint-or-cache(镜像 TPU) |
HostPlatform::GetUncachedExecutor | 0xfe6d5e0 | 构建 HostExecutor |
HostExecutor::Init | 0xfe6d780 | ThreadPool("host-executor", NumSchedulableCPUs) |
HostExecutor::CreateStream | 0xfe6de20 | Factory 或默认 new(0x80) HostStream |
HostExecutor::CreateEvent | 0xfe6d9e0 | 基于双对象 Notification 的 event |
HostExecutor::HostMemoryAllocate | 0xfe6e0c0 | new + AnyInvocable deleter |
HostStream::HostStream | 0xfe6ec80 | StreamCommon + vtable off_217B0228,0x80 B |
HostStream::BlockHostUntilDone | 0xfe6f000 | return 1(同步) |
HostStream::DoHostCallbackWithStatus | 0xfe6efe0 | 在 caller thread 上 inline 调用 |
HostStreamFactory::GetFactory | 0xfe6eb40 | 可插拔 stream-factory accessor |
HostStreamFactory::Register | 0xfe6ea20 | 安装 custom stream factory |
注意 —
HostStreamFactory::Register(0xfe6ea20)提供了一个 hook,可覆盖默认HostStream,但在任何已追踪 call site 中都没有发现非默认 factory registration —GetFactory路径返回 null,CreateStream落入默认new HostStream。该 override 在此构建中可达的置信度:LOW。重新实现者可以忽略这个 factory hook,除非需要 custom host stream。
6. 给重新实现者的注意事项
- 注册是一次性、失败即 fatal 的握手。 镜像
RegisterTpuPlatform:受 backend-enabled flag 控制,用 once-flag 保护,并把RegisterPlatform失败视作 fatal(tpu_platform.cc:178的 CHECK)。静默失败的注册会让设备枚举断掉且没有错误。 - 以
PlatformManager为准,而不是MultiPlatformManager。 旧类名已从此构建中消失(§1 说明)。registry 以 virtual platform-id 为 key。 - Executors 是带 double-checked cache 的逐 ordinal 单例。 不要每次调用都构建新 executor。复制
ExecutorCache:fast-pathGet,在锁外构建 factory 结果,然后在锁下find_or_prepare_insert,并丢弃竞态中的失败者。 - TPU executor 不拥有设备状态。 它是一个
0x48字节的句柄持有者(SE_StreamExecutor*@+0x38,ordinal @+0x40)。每个 method 都通过ExecutorApiFn()转发;status 习惯用法始终是 scratch / op / ok?-code-msg / free @status_helper.h:38。 - host 后端按设计是同步的。
BlockHostUntilDone返回true,callbacks inline 运行,host events 是 single-shot(host_stream.cc:92CHECK)。如果添加 async host worker queue(上游 XLA 有一个 — 此处未链接),就必须同时改变这三点。不存在 asyncHostStream符号是基于推断(未找到符号),因此对此链接构建纯同步的置信度:HIGH。 - 不存在 interpreter fallback
Platform。 不要期望编译失败会路由到 interpreter device。编译期求值是xla::HloEvaluator(constant folding);运行时 host 执行是 CPU thunks;二者都在 SEPlatform模型之外(§4 特性)。
相关组件
| 名称 | 关系 |
|---|---|
stream_executor::PlatformManager | 本页记录的进程全局 registry(由 MultiPlatformManager 改名而来) |
tensorflow::tpu::TpuPlatform | TPU Platform;RegisterTpuPlatform 安装它,ExecutorForDevice 创建 TpuExecutors |
stream_executor::host::HostPlatform / HostExecutor / HostStream | 同步的二进制内 host SE 对象模型 |
stream_executor::ExecutorCache | ExecutorForDevice 背后的逐 ordinal FlatHashMap<int, unique_ptr<SE>> cache |
TfTpu_ExecutorApiFn table | TPU 后端转发到的 C-ABI(ExecutorApiFn() slots +16/+32/+360/+384/+392/+400/+408) |
xla::HloEvaluator / CPU thunks | 不是 SE Platform 的 host-interpreter 角色(编译期 fold + 运行时 CPU backend) |
交叉引用
- PJRT 概览 — 分层构建在此 SE stack 之上的公共 C ABI;StreamExecutor 是 PJRT 包装的旧式抽象
- StreamExecutor → PJRT 适配器 — 从此 SE 层向上到 PJRT 的
TpuClient/ CommonPjRt bridge - Client 和 Device — 消费本页所创建 executors 的 PjRtClient/device surface
- Stream 语义与依赖 — 负责
WaitFor/RecordEventwait 模型、FIFO 排序契约以及 compute/transfer stream 拆分 - Runtime 概览 — 从 PJRT 下到 stream 的 execute 路径,以及逐执行章节所在的框架
- ExecuteAsyncOnStream — 到 compute stream 的逐执行 enqueue(不同于本页的注册层)
- LoadProgramAndEnqueueToStream — program load + enqueue,compute-stream work 的 producer