Skip to content

TpuProgram 名册

本页所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未剥离)。.text VMA 等于文件偏移。C-ABI 名称(TpuProgram_*XLA_TpuProgram)由 IDA 从 .rodata 引用字符串以及二进制中内嵌的 tpu_program_c_api.cc 源路径/行号断言恢复。其他版本会有所不同。

摘要

TpuProgram_*已编译程序句柄 的 C-ABI 名册,也就是 TPU 编译产出的不透明对象,StreamExecutor TPU executable 层会携带它,直到运行时将其加载到某个 core 上。TpuCompiler_* 将 HLO module 转成这种对象,TpuExecutable_* 包装其运行中形态,而 TpuProgram_* 拥有两者之间可序列化、可查询的形态:它是编译缓存指纹所针对的对象,会被序列化为 GetTpuProgramResponse proto 以便传输,也会被拆回 executor 所需的元数据(executable info、host-transfer 描述符、HLO module proto、may-modify-variables 标志)。这 18 个自由函数正是 learning/45eac/tfrc/executor/stream_executor/tpu_program_c_api.ccextern "C" 表面,该路径来自二进制中每个 CHECKMakeErrorImpl 调用点内嵌的源路径。

名册背后的句柄是 XLA_TpuProgram,二进制让它的形状毫无歧义:TpuProgram_New 执行 operator new(0xB8)(184 字节),并将 tensorflow::TPUExecutableProto 构造成对象自身的基类,也就是说该句柄就是 executable proto,并在 proto body 后缝接了五个额外的尾随指针槽。这些尾随槽保存 compiler-metadata HLO proto、一个引用计数 shared object、已加载的 tpu::TpuCoreProgram,以及两个递归拥有的 XLA_TpuProgram 子对象,即 sharding 子程序。本页每个 accessor 都读取其中一个槽;每个生命周期函数都管理它们的所有权。

本页负责逐函数名册、impl 符号/地址映射,以及 XLA_TpuProgram 结构布局。支撑本名册的 opaque-handle + *ApiFn() accessor 模型(host 如何通过函数指针槽到达 TpuProgram_New,而不是直接按符号调用)见 TfTpu C-API Shim;最终驱动序列化程序的 PJRT executable 生命周期见 PJRT Executable Execution;将 TpuCoreProgram 加载到 core 并入队的运行时路径见 Load Program & Enqueue

对于重新实现,契约是:

  • XLA_TpuProgram 句柄布局 —— 一个 TPUExecutableProto 基类加五个尾随槽,以及每个 accessor 读取哪个槽。
  • 18 函数名册 —— 生命周期(New/Free/NewArray/FreeArray/UnloadAndDestroy)、序列化(SerializeTpuExecutable/SerializeCompilerMetadata/DeserializeFromGetTpuProgramResponseProto/GetExecutableInfo)、元数据 accessor(GetProgramSize/GetHostTransferInfo/GetHloMetadata/GetMayModifyVariables/GetFingerprint/DestroyFingerprint/GetTpuProgram/HasSharding),以及内存摘要日志。
  • out-param/status 习惯用法 —— 序列化和元数据函数接收一个输出 {ptr,len} 对以及一个由被调用方填充的 StatusRep**(ptr,len) 字节 blob 由 operator new 在堆上拥有,并由调用方释放。
  • fetch-target enum 与 sharding 拆分 —— GetTpuProgramCompilationCacheFetchTarget 值切换,返回主句柄或两个 sharding 子对象之一。
名册规模18 个 extern "C" TpuProgram_* 函数(已按函数表验证)
句柄类型XLA_TpuProgramoperator new(0xB8) = 184 字节
句柄基类tensorflow::TPUExecutableProto(句柄就是 proto)
句柄析构函数XLA_TpuProgram::~XLA_TpuProgram @ 0xe8bdb20(171 字节)
源文件learning/45eac/tfrc/executor/stream_executor/tpu_program_c_api.cc
地址范围0xe8bda60New)→ 0xe8bef20DestroyFingerprint
到达方式ExecutorApiFn() 槽(见 overview
证据等级重新实现级 / 已按 IDA 反编译逐字节确认

范围 — *ApiFn() accessor 模式、不透明句柄约定,以及 host 如何通过函数指针槽调用这些函数,均由 TfTpu C-API Shim 负责。本页只记录 TpuProgram_* 函数自身以及它们操作的 XLA_TpuProgram 句柄。


1. XLA_TpuProgram 句柄

布局

TpuProgram_New @ 0xe8bda60 分配 0xB8(184)字节,在前 0x98 字节上运行 tensorflow::TPUExecutableProto::TPUExecutableProto(this, /*arena=*/0),随后将剩余尾随槽清零。因此该句柄是一个追加了五个额外指针槽的 TPUExecutableProto;每个 accessor 都读取其中之一。析构函数 XLA_TpuProgram::~XLA_TpuProgram @ 0xe8bdb20 反向遍历这些槽,并通过释放方式确认每个槽的类型。

字段偏移类型含义
executable_proto+0tensorflow::TPUExecutableProto(基类)可序列化的 executable proto;New 构造它,SharedDtor 终结它
isa_program+104 (q13)xla::IsaProgramProto*后备 ISA program;host-transfer info 的来源;在 UnloadAndDestroy 中记录
may_modify_variables+136 (q17)uint8程序是否可能改变 resource variables
compiler_metadata+144 (q18)HLO/CompilerMetadata proto ptrHLO module proto + program-memory metadata;由 GetHloMetadataGetProgramSizeLogProgramMemorySummarySerializeCompilerMetadata 读取
shared_obj+152 (q19)引用计数 shared object通过 __shared_weak_count::__release_weak 释放;反序列化期间设置
tpu_core_program+160 (q20)unique_ptr<tpu::TpuCoreProgram const>已加载的 core program;fingerprint 字节位于其内部 +648
sharding_child_0+168 (q21)XLA_TpuProgram*Sharding 子程序;递归销毁;fetch-target 2
sharding_child_1+176 (q22)XLA_TpuProgram*第二个 sharding 子程序;递归销毁;fetch-target 3
c
// XLA_TpuProgram::~XLA_TpuProgram(this)                          0xe8bdb20
void ~XLA_TpuProgram(this):
    child1 = this[+176]; this[+176] = 0                  // q22 — sharding child 1
    if child1: ~XLA_TpuProgram(child1); free(child1)     // recursive
    child0 = this[+168]; this[+168] = 0                  // q21 — sharding child 0
    if child0: ~XLA_TpuProgram(child0); free(child0)     // recursive
    unique_ptr_reset(&this[+160], 0)                     // q20 — drop TpuCoreProgram
    rc = this[+152]                                      // q19 — refcounted shared object
    if rc && atomic_dec(rc.weak_count) == 0:             // weak-count release
        rc.vtable[+16](rc); __release_weak(rc)
    TPUExecutableProto::SharedDtor(this, 0)              // finalize the proto base
```text

> **怪异点 —** 句柄不是包在 proto *外面*的 wrapper;它本身*就是* proto,并在原地扩展。若重新实现者把 `XLA_TpuProgram` 建模为持有 `TPUExecutableProto*` 的小结构体,每个偏移都会不匹配:`GetProgramSize` 直接对句柄指针调用 `Message::SpaceUsedLong(handle)`,把它当作 proto。proto 基类和尾随指针槽共享同一个 184 字节分配。
>
> **陷阱 —** `+168`/`+176` 的两个 sharding 子对象自身也是完整的 `XLA_TpuProgram` 对象,通过*同一个*析构函数递归释放。若重新实现用普通 `free()` 释放它们(跳过 `~XLA_TpuProgram`),会泄漏每个子对象自己的 `TpuCoreProgram`、引用计数对象和孙对象。`TpuProgram_Free` 是唯一正确入口:`~XLA_TpuProgram(p); free(p)`。

---

## 2. 生命周期

### 目的

分配、释放和卸载 `XLA_TpuProgram` 句柄,并分配/释放 compiler 发出的扁平 `XLA_TpuProgram*[]` 数组(多程序编译中每个程序一个条目)。`New`/`NewArray` 负责分配;`Free`/`FreeArray`/`UnloadAndDestroy` 负责释放。

### 算法

```c
// TpuProgram_New()                                              0xe8bda60
XLA_TpuProgram* New():
    p = operator new(0xB8)                       // 184-byte handle
    zero(p, 0xB8)
    TPUExecutableProto::TPUExecutableProto(p, 0) // construct proto base, arena=null
    p[+144] = 0                                  // clear compiler_metadata slot
    p[+176] = 0                                  // clear trailing sharding slot
    return p

// TpuProgram_Free(p)                                            0xe8bdae0
void Free(p):
    if p: ~XLA_TpuProgram(p); free(p)            // dtor handles children + proto

// TpuProgram_NewArray(count)                                    0xe8bdbe0
XLA_TpuProgram** NewArray(count):
    CHECK(count > 0, "count > 0")                // tpu_program_c_api.cc:44 — fatal if 0
    return operator new(8 * count)               // array of handle pointers

// TpuProgram_FreeArray(arr)                                     0xe8bdc60
void FreeArray(arr): if arr: free(arr)           // frees the pointer array only

// TpuProgram_UnloadAndDestroy(p, status_out)                    0xe8bdc80
void UnloadAndDestroy(p, status_out):
    if LogEveryNSec():                           // tpu_program_c_api.cc:54
        hex = BytesToHexString(p.isa_program.id) // q13 ISA program id
        LOG("Unloading and destroying TPU program(%s)", hex)
    ... unload the core program from the device, fill status_out ...

怪异点 — FreeArray 只释放指针数组,绝不释放其指向的句柄。NewArrayoperator new(8*count))与 FreeArrayfree)对称,但数组中的每个 XLA_TpuProgram* 都必须通过 TpuProgram_Free / UnloadAndDestroy 单独释放。若重新实现者释放数组并以为元素也随之释放,会泄漏每个程序。NewArray 还会在 count == 0致命中止(第 44 行 CHECK(count > 0)),因此调用方绝不能请求空数组。

函数映射

函数地址大小角色
TpuProgram_New0xe8bda60107分配并构造一个 184 字节句柄
TpuProgram_Free0xe8bdae039~XLA_TpuProgram + free
TpuProgram_NewArray0xe8bdbe0103分配 XLA_TpuProgram*[count](CHECK count>0)
TpuProgram_FreeArray0xe8bdc6010free 指针数组(不释放其元素)
TpuProgram_UnloadAndDestroy0xe8bdc80382从设备卸载 core program + status-out

3. 序列化

目的

在内存中的 XLA_TpuProgram 与用于传输和编译缓存的 wire 形态之间转换。两个方向:将 executable proto 或 compiler-metadata proto 序列化为堆 blob,并将完整 GetTpuProgramResponse proto 反序列化回已填充的句柄。GetExecutableInfo 是第三种 serializer,会为只消费元数据的调用方发出一个剥离后的 executable proto(清除 ISA program 和 profile)。

out-param / status 习惯用法

每个 serializer 都接收一个 _QWORD out[2],并将其填充为 {void* bytes, size_t len}(字节 blob 由 operator new 分配,由调用方拥有),另接收一个失败时设置的 StatusRep** status_out。成功路径不触碰 status 并返回 blob;失败时会清零 blob 槽并安装一个 absl::Status(bytes, len) blob 由调用方释放,而不是由成对的 TpuProgram_* 函数释放;它是原始 operator new 缓冲区。

c
// TpuProgram_SerializeTpuExecutable(handle, out, status_out)    0xe8be720
void SerializeTpuExecutable(handle, out, status_out):
    blob = GetTpuProgramResponseExternal_Blob()           // wrapper proto
    s = ArenaStringPtr::Mutable(&blob.data)
    if !MessageLite::SerializeToString(handle, s):         // serialize proto base
        status_out := MakeError("Failed to serialize proto, "
                                "invalid executable buffer.")   // line 201
        return
    len  = blob.ByteSizeLong()
    buf  = operator new(len)
    CHECK(blob.SerializePartialToArray(buf, len))          // proto_helper.h:45
    out[0] = buf; out[1] = len                             // {bytes, len}

// TpuProgram_SerializeCompilerMetadata(handle, out, status_out) 0xe8be840
void SerializeCompilerMetadata(handle, out, status_out):
    meta = handle[+144]                                    // compiler_metadata slot
    ... same blob/serialize/CHECK shape; error line 218 ...

// TpuProgram_GetExecutableInfo(handle, out, status_out)         0xe8bdf40
void GetExecutableInfo(handle, out, status_out):
    proto = copy of *handle                                // TPUExecutableProto copy
    proto.clear_isa_program(); proto.clear_profile()       // strip heavy fields
    if proto.ByteSizeLong() == 0:
        out := {0,0}
        status_out := MakeError("TPU executable proto to be "
                                "serialized is empty.")     // line 99
        return
    len = proto.ByteSizeLong(); buf = operator new(len)
    CHECK(proto.SerializePartialToArray(buf, len))
    out[0] = buf; out[1] = len
```text

```c
// TpuProgram_DeserializeFromGetTpuProgramResponseProto(
//     exec_bytes, exec_len, handle, status_out)                 0xe8be960
void DeserializeFromGetTpuProgramResponseProto(exec_bytes, exec_len, handle, status_out):
    resp = DeserializeProto<GetTpuProgramResponseExternal>(...) // outer wrapper
    if !MessageLite::ParseFromString(handle, resp.blob):        // executable proto
        status_out := MakeError("Failed to deserialize proto, "
                                "invalid executable buffer.")   // line 241
        return
    meta = CompilerMetadata()
    if !meta.ParseFromString(resp.compiler_metadata_blob):
        status_out := MakeError("Failed to deserialize proto, "
                                "invalid compiler metadata buffer.")  // line 253
        return
    // install compiler metadata at handle[+144], refcount at handle[+152]
    handle[+144] = new CompilerMetadata(meta); handle[+136] = ...
    platform  = GetRegisteredDeepseaPlatform()
    topology  = platform.GetTopology()
    isa       = handle.isa_program                          // q13
    core_prog = TpuCoreProgramFromIsaProgramProto(topology, isa, /*flag=*/1)
    if core_prog.ok():
        handle[+160] = move(core_prog)                      // loaded TpuCoreProgram
    else:
        status_out := core_prog.status()

陷阱 — serializer 返回的 blob 是裸 operator new 缓冲区,不是TpuProgram_* 管理的对象。没有 TpuProgram_FreeSerialized;调用方自行释放 out[0]。若把这个 blob 与 fingerprint(有自己的 DestroyFingerprint)或数组(FreeArray)混淆,就会用错释放器。每个生产者都要匹配对应释放方式:serialized blob → 调用方 free;fingerprint → DestroyFingerprint;指针数组 → FreeArray;句柄 → Free

说明 — GetExecutableInfo 会先在 proto 的副本上刻意执行 clear_isa_program()clear_profile(),再进行序列化,因此结果是没有笨重 ISA/profile 负载的 executable metadata。序列化空 proto 被视为错误(第 99 行),而不是成功返回空内容;重新实现者必须复现“全部清除后仍为空”这一失败情形。

函数映射

函数地址大小角色
TpuProgram_SerializeTpuExecutable0xe8be720268序列化完整 executable proto → {bytes,len} blob(错误第 201 行)
TpuProgram_SerializeCompilerMetadata0xe8be840275序列化 compiler-metadata proto(槽 +144)→ blob(错误第 218 行)
TpuProgram_GetExecutableInfo0xe8bdf40277序列化剥离后的 executable proto(无 ISA/profile)→ blob(错误第 99 行)
TpuProgram_DeserializeFromGetTpuProgramResponseProto0xe8be960792解析 GetTpuProgramResponse proto → 填充句柄 + 加载 core program

4. 元数据 Accessor

目的

在不重新解析 proto 的情况下,从已填充的句柄读取结构化信息。这些信息支撑 executor 侧的 TpuProgramGroup::Construct* / accessor 方法,用来创建 XLA 消费的 C++ 视图:HLO module proto、host-transfer 描述符、may-modify-variables 列表、fingerprint,以及按 fetch target 索引的原始 TpuCoreProgram 指针。

算法

c
// TpuProgram_GetMayModifyVariables(handle, out_bool)            0xe8be520
void GetMayModifyVariables(handle, out_bool):
    CHECK(out_bool != null, "may_modify_variables != nullptr")  // line 163
    *out_bool = (uint8) handle[+136]                            // q17

// TpuProgram_HasSharding(handle) -> bool                        0xe8be580
bool HasSharding(handle):
    CHECK(handle != null, "tpu_program != nullptr")             // line 168
    return handle[+168] != 0 && handle[+176] != 0               // both children present

// TpuProgram_GetTpuProgram(handle, fetch_target) -> void*       0xe8be600
void* GetTpuProgram(handle, fetch_target):
    switch (fetch_target):                                      // CompilationCacheFetchTarget
        case 1: return handle                                   // MAIN — the handle itself
        case 2: return handle[+168]                             // sharding child 0
        case 3: return handle[+176]                             // sharding child 1
        default: LOG_FATAL("Invalid fetch target: " + enum_name) // line 185

// TpuProgram_GetProgramSize(handle) -> int64                    0xe8bde00
int64 GetProgramSize(handle):
    n = Message::SpaceUsedLong(handle)                          // proto base size
    m = handle[+144]                                            // compiler_metadata
    if m: n += Message::SpaceUsedLong(m)
    return n

// TpuProgram_GetFingerprint(handle) -> char* (heap copy)        0xe8bed60
char* GetFingerprint(handle):
    core = handle[+160]                                         // q20 TpuCoreProgram
    if !handle || !core:
        VLOG("The underlying `TpuProgram` was not initialized. "
             "Returning empty fingerprint.")                    // line 275
        return null
    bytes = core[+648]                                          // fingerprint string
    return strdup-style heap copy of bytes                      // operator new + memcpy

// TpuProgram_DestroyFingerprint(p)                              0xe8bef20
void DestroyFingerprint(p): if p: free(p)

// TpuProgram_GetHostTransferInfo(handle, out, status_out)       0xe8be060
void GetHostTransferInfo(handle, out, status_out):
    isa = handle.isa_program                                    // q13
    if isa.host_transfer_count > 0:
        info = TPUHostTransferInfoProto()
        for each transfer in isa: info.add(...)                 // SerializeAsCord per entry
        serialize info → out blob
    // else out left empty

// TpuProgram_GetHloMetadata(handle, out, status_out)           0xe8be260
void GetHloMetadata(handle, out, status_out):
    out := {0,0}
    meta = handle[+144]                                         // compiler_metadata
    if meta && meta.hlo_module:
        hlo = HloProto(); copy hlo_module + input_output_alias
        serialize hlo → out blob

// TpuProgram_LogProgramMemorySummary(handle) -> bool           0xe8bde40
bool LogProgramMemorySummary(handle):
    meta = handle[+144]
    if !meta: return false
    if meta.flags[+17] & 0x10:                                  // has memory metadata
        summary = ProgramMemorySummary(meta.program_memory_metadata)  // +152 inside meta
        LOG("\n" + summary)                                     // line 80
    return true
```text

> **怪异点 —** `GetTpuProgram` 的参数是 `CompilationCacheFetchTarget` enum,而不是索引。`1`(`MAIN`)原样返回句柄;`2`/`3` 返回两个 sharding 子对象;任何 `> 3` 或未知值都会触发带有解码 enum 名称的 `LOG_FATAL`(第 185 行)。重新实现者必须将它接到 cache 使用的同一个 enum 上;传入原始 0 或越界值会中止进程,而不是返回 null。`HasSharding` 是应先调用的安全谓词:只有*两个* sharding 槽都已填充时才返回 true
>
> **陷阱 —** `GetFingerprint` 返回一个*新的堆副本*(对 `core_program+648` 处 fingerprint 字符串执行 `operator new` + `memcpy`),调用方必须用 `TpuProgram_DestroyFingerprint` 释放。若 `TpuCoreProgram` 槽(`+160`)未设置,它会在发出 `VLOG` 后返回 `null`,而不是空字符串。假设 fingerprint 非 null,或用错误释放器释放,都会出问题。fingerprint 与 serialized blob 是不同分配,生命周期也不同。

### 函数映射

| 函数 | 地址 | 大小 | 角色 |
|---|---|---|---|
| `TpuProgram_GetProgramSize` | `0xe8bde00` | 50 | `SpaceUsedLong(proto) + SpaceUsedLong(compiler_metadata)` |
| `TpuProgram_GetMayModifyVariables` | `0xe8be520` | 83 | 将 `+136` bool 读入 out-param(CHECK out!=null,第 163 行) |
| `TpuProgram_HasSharding` | `0xe8be580` | 98 | 当且仅当两个 sharding 子对象(`+168`,`+176`)均已设置时为 true(CHECK handle,第 168 行) |
| `TpuProgram_GetTpuProgram` | `0xe8be600` | 282 | Fetch-target switch → handle / child0 / child1(否则 FATAL,第 185 行) |
| `TpuProgram_GetFingerprint` | `0xe8bed60` | 447 | 复制 `core+648` 处 fingerprint 到堆;未初始化时为 null(VLOG 第 275 行) |
| `TpuProgram_DestroyFingerprint` | `0xe8bef20` | 10 | `free` fingerprint 缓冲区 |
| `TpuProgram_GetHostTransferInfo` | `0xe8be060` | 508 | 从 ISA program 序列化 `TPUHostTransferInfoProto` → blob |
| `TpuProgram_GetHloMetadata` | `0xe8be260` | 694 | 从 compiler metadata 序列化 `HloProto`(module + IO-alias)→ blob |
| `TpuProgram_LogProgramMemorySummary` | `0xe8bde40` | 249 | 若存在则 `LOG` program-memory summary(第 80 行);无 metadata 时为 false |

---

## 5. 名册速览

完整 18 函数 `TpuProgram_*` 表面,按区域分组,来自函数表恢复。范围 `0xe8bda60`–`0xe8bef20`。

| 函数 | 地址 | 区域 |
|---|---|---|
| `TpuProgram_New` | `0xe8bda60` | 生命周期 |
| `TpuProgram_Free` | `0xe8bdae0` | 生命周期 |
| `TpuProgram_NewArray` | `0xe8bdbe0` | 生命周期 |
| `TpuProgram_FreeArray` | `0xe8bdc60` | 生命周期 |
| `TpuProgram_UnloadAndDestroy` | `0xe8bdc80` | 生命周期 |
| `TpuProgram_GetProgramSize` | `0xe8bde00` | 元数据 |
| `TpuProgram_LogProgramMemorySummary` | `0xe8bde40` | 元数据 |
| `TpuProgram_GetExecutableInfo` | `0xe8bdf40` | 序列化 |
| `TpuProgram_GetHostTransferInfo` | `0xe8be060` | 元数据 |
| `TpuProgram_GetHloMetadata` | `0xe8be260` | 元数据 |
| `TpuProgram_GetMayModifyVariables` | `0xe8be520` | 元数据 |
| `TpuProgram_HasSharding` | `0xe8be580` | 元数据 |
| `TpuProgram_GetTpuProgram` | `0xe8be600` | 元数据 |
| `TpuProgram_SerializeTpuExecutable` | `0xe8be720` | 序列化 |
| `TpuProgram_SerializeCompilerMetadata` | `0xe8be840` | 序列化 |
| `TpuProgram_DeserializeFromGetTpuProgramResponseProto` | `0xe8be960` | 序列化 |
| `TpuProgram_GetFingerprint` | `0xe8bed60` | 元数据 |
| `TpuProgram_DestroyFingerprint` | `0xe8bef20` | 元数据 |

> **说明 —** 此构建中没有 `TpuProgram_SerializedSize` 或 `TpuProgram_HasSparseCoreProgram`,大小查询是 `GetProgramSize`(proto `SpaceUsedLong`,不是序列化字节数),SparseCore 是否存在在这里也不是 `TpuProgram_*` 谓词。最接近的 sharding/sub-program 谓词是 `HasSharding`,host-transfer 表面是 `GetHostTransferInfo`。名册正好就是上面的 18 个函数。

---

## 相关组件

| 名称 | 关系 |
|---|---|
| `XLA_TpuProgram` (`~XLA_TpuProgram @ 0xe8bdb20`) | 每个 `TpuProgram_*` 函数操作的不透明句柄 |
| `tensorflow::TPUExecutableProto` | *作为*句柄基类的 proto;`New` 构造它,serializer 发出它 |
| `tensorflow::tpu::GetTpuProgramResponseExternal` / `_Blob` | serialize/deserialize 对使用的 wrapper proto |
| `tensorflow::tpu::TpuProgramGroup` (`0xe978a20` et al.) | host 侧消费者;`ConstructExecutableInfo` / `ConstructHostTransferInfo` / `ConstructHloMetadata` 调用这里的 accessor |
| `tpu::TpuCoreProgram` | 句柄 `+160` 处的已加载程序;在 `+648` 持有 fingerprint;由运行时加载 |
| `xla::IsaProgramProto` | 句柄 `+104`;host-transfer info 的来源,以及卸载时记录的 program id |

## 交叉引用

- [TfTpu C-API Shim](overview.md) —— `*ApiFn()` accessor 模式、不透明句柄约定,以及如何通过函数指针槽到达 `TpuProgram_*`
- [TpuCompiler 名册](tpu-compiler-roster.md) —— *产生* `XLA_TpuProgram` 的 `TpuCompiler_*` / `TpuCompile_*` 表面
- [TpuExecutable 名册](tpu-executable-roster.md) —— 包装已加载程序的 `TpuExecutable_*` running-executable 句柄
- [TpuExecutor 名册](tpu-executor-roster.md) —— 已加载程序运行其上的 `TpuExecutor_*` 单设备运行时
- [TpuTransferManager 名册](tpu-transfer-manager.md) —— 消费本页暴露的 host-transfer info 的 host↔device transfer C ABI
- [PJRT Executable Execution](../pjrt/executable-execution.md) —— 最终驱动序列化程序的 PJRT 级 executable 生命周期
- [Load Program & Enqueue](../runtime/load-program-enqueue.md) —— 将 `TpuCoreProgram` 加载到 core 并入队的运行时路径