Skip to content

TpuTransferManager 名册

本页所有地址均适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未剥离;反修饰后的 C++ 符号和 IDA 恢复的 C 名称均按原样引用)。.text VMA 等于文件偏移。其他版本会不同。

摘要

TpuTransferManager_* 是支撑 TfTpu C-API shim 中跨 xla::TransferManager 的 C-ABI 函数组。它是主机↔设备的数据移动表面:把主机上的 xla::Literal 编组进 TPU xla::ShapedBuffer(以及反向),推送/拉取流式 infeed/outfeed 字面量,查询某个形状在设备端的字节大小,把字面量线性化为原始设备布局缓冲区,并回答两个“我现在能否触碰这个缓冲区”的谓词。IDA 从 .rodata 引用和调用目标恢复出十九个 extern "C" 自由函数,它们都命名为 TpuTransferManager_<Method>,并且都位于 .text 中紧凑的 0xeaba0a00xeabb827 区间内(一个例外是 GetInfeedLayout @ 0xf6a1a80,位于不同翻译单元)。IDA 源路径字符串 learning/45eac/tfrc/executor/stream_executor/tpu_transfer_manager_c_api.ccGetInfeedLayout 第 28 行)锁定了该函数组的来源文件。

定义性的结构事实,也是把这个名册同 TpuExecutor_* 区分开的事实,是 C 函数如何抵达真正实现TpuExecutor_* 函数接收一个不透明的 SE_StreamExecutor*,并通过它的 vtable 分派进 deepsea 驱动。TpuTransferManager_* 函数接收 executor 句柄;相反,每个函数都会即时解析 TPU 平台的单例 xla::TransferManagerGetRegisteredDeepseaPlatform()(缓存在 GetUnderlyingDeepseaPlatform::platform Meyers-static guard 后面)→ xla::TransferManager::GetForPlatform(platform) @ 0x1342f180(一个 StatusOr<TransferManager*>),然后通过该 manager 的 C++ vtable 中的固定字节偏移弹跳调用。因此整个函数组是一个先解析、再弹跳的 shim:用 ApiConverter::FromC 把 C 结构编组进 C++,查找每个平台的 TransferManager 单例,调用一个 vtable 槽位,用 ApiConverter::ToC 把结果编组回来,并销毁临时对象。

本页负责函数名册 + 每个函数的实现符号 / vtable 槽位映射。不透明句柄 / ApiConverter::ToC/FromC 约定以及三张 *ApiFn 表的访问器模型见 shim 概览运行时层面的 infeed/outfeed 队列机制,即 PJRT 原生的 tpu::System 路径、span 分块、阻塞语义、片上队列驱动,见 Infeed / Outfeed Queues;这里的两个 TpuTransferManager_TransferLiteral{ToInfeed,FromOutfeed} 行是进入该子系统的遗留 StreamExecutor 入口,该页面也把它们记录为遗留半边。设备端 ShapedBuffer 所属的 PJRT 缓冲区/内存 ABI 见 PJRT Buffer & Memory

对于重新实现,契约是:

  • 先解析、再弹跳惯用法 — 不传递 SE_* 句柄;每个函数都会解析每个平台的 xla::TransferManager 单例(GetForPlatform @ 0x1342f180)并调用一个 vtable 槽位。C++ TransferManager vtable 偏移就是分派键。
  • 编组纪律 — 每个作为 shape / layout / literal / shaped-buffer 的 C 参数,都会在弹跳调用前通过 ApiConverter::FromC 转成栈上的 C++ 对象,并在之后调用 ~Dtor;out-param 通过 ApiConverter::ToC 回写;absl::Status 结果会作为写入调用方 out 指针的引用计数 StatusRep* 返回。
  • vtable 槽位映射 — 下表把每个 C 函数固定到它的 xla::TransferManager 虚函数偏移(+16, +24, +32, +40, +48, +56, +64, +72, +80, +88, +104, +112, +120, +136),这是重新实现者必须保持字节稳定的唯一关键。
  • 两个非 vtable 成员New/Free 是平凡堆操作,而 LinearizeToBuffers / GetInfeedLayout 完全绕过 manager vtable,直接基于解析出的 TpuTopology 调用 xla::jellyfish 线性化器 / TransferSizeUtil
名册大小19 个 extern "C" TpuTransferManager_* 自由函数(与概览计数一致)
地址区间0xeaba0a00xeabb827 连续(18 个函数;ReadDynamicShapes 结束于 0xeabb827,随后是 TpuComputationPlacer_New @ 0xeabb840)+ GetInfeedLayout @ 0xf6a1a80(离群 TU)
背后的 C++ 类xla::TransferManager(TPU 子类;每次调用解析,不持有)
单例解析GetRegisteredDeepseaPlatformxla::TransferManager::GetForPlatform @ 0x1342f180 (StatusOr)
平台缓存GetUnderlyingDeepseaPlatform::platform(函数局部 static,受 __cxa_guard 保护)
分派键C++ xla::TransferManager vtable 字节偏移(*(*manager + off))(manager, …)
编组ApiConverter::FromC(入)/ ToC(出);xla::Shape / Layout / LiteralSlice / MutableBorrowingLiteral / ShapedBuffer 临时对象
状态输出写入调用方 out-ptr 的 absl::status_internal::StatusRep*,旧值会 Unref
抵达方式ExecutorApiFn 表槽位(由 TfTpu_Initialize Bootstrap 填充)
来源文件learning/45eac/tfrc/executor/stream_executor/tpu_transfer_manager_c_api.cc
证据等级可重新实现级 / 已按 IDA 反编译字节确认(已检查 19/19 个函数体)

范围 — 指向这些函数中每一个的逐函数 ExecutorApiFn 槽位(以及何时填充)属于 TfTpu_Initialize Bootstrap。infeed/outfeed 对最终落到的流式队列机制Infeed / Outfeed Queues 负责。本页只记录 C-ABI 名册和 C++ vtable 弹跳调用。


1. 每个函数的先解析、再弹跳形态

目的

每个非平凡的 TpuTransferManager_* 函数都有同一个骨架,重新实现者只要内化一次,就能通过检查槽位偏移和参数编组读懂全部十四个 vtable 后备成员。这里没有需要穿线传递的逐调用 executor 句柄;平台是进程全局单例。

算法

c
// canonical body — sub_EABA*  (e.g. GetByteSizeRequirement @ 0xeaba4c0)
function TpuTransferManager_<Method>(<C args...>):
    // 1. marshal every rich C struct arg into a stack C++ object
    Shape         host_shape;  ApiConverter::FromC(&host_shape, c_shape)    // 320-byte xla::Shape
    // (literals → MutableBorrowingLiteral/LiteralSlice; buffers → 784-byte xla::ShapedBuffer)

    // 2. resolve the per-platform TransferManager singleton (cached after first call)
    if !GetUnderlyingDeepseaPlatform::platform.guard:                       // __cxa_guard_acquire
        GetUnderlyingDeepseaPlatform::platform =
            deepsea::executor::GetRegisteredDeepseaPlatform()               // one-time
    StatusOr<TransferManager*> mgr =
        xla::TransferManager::GetForPlatform(platform)                      // 0x1342f180
    if mgr.is_error():                                                      // payload ptr != &dword_0+1
        absl::internal_statusor::ThrowBadStatusOrAccess(mgr)               // never returns on TPU

    // 3. bounce through ONE C++ vtable slot — the offset is the dispatch key
    TransferManager* m = mgr.value();
    result = (*(*(void**)m + <VTABLE_OFFSET>))(m, <unwrapped args>, host_shape)

    // 4. marshal results back out; destroy temporaries (reverse order)
    ApiConverter::ToC(&out_cpp, c_out)         // for out-param functions
    xla::Shape::~Shape(&host_shape)            // every FromC'd temp gets its ~Dtor
    return result
```text

`mgr.is_error()` 检查是恢复出的 `StatusOr` 惯用法:成功哨兵是等于 `&dword_0 + 1` 的 payload 指针(带标记的 “ok” 值)。其他任何值都是状态,因此代码会引用/抛出 `ThrowBadStatusOrAccess`。在正确初始化的 TPU 平台上 manager 总能解析成功,所以该分支实践中是死分支,但为了 ABI 对等必须复现。

### 为什么没有 executor 句柄

上游 XLA 中的 `xla::TransferManager` 是一个从静态注册表获取的*每平台*对象(`TransferManager::GetForPlatform`),不是逐设备对象。TPU 构建保留了这个模型:进程中恰好有一个 TPU `TransferManager`,所以 C shim 没有设备特定内容可传,而是自行解析单例。对比 [`TpuExecutor_*`](tpu-executor-roster.md):那里 executor *就是*逐设备句柄,必须穿线传入每次调用。

> **QUIRK —** manager 指针永远不会由这些函数释放。`New`/`Free` 分配和释放一个 1 字节占位符(见 §2),主机把它当作“transfer manager 句柄”,但*真正的* `xla::TransferManager` 是每次调用解析出的进程单例,并且比所有句柄都活得更久。试图把设备状态存进 `New` 出来的对象的重新实现者会发现它只是哑对象;所有状态都在单例里。

---

## 2. 生命周期(`New` / `Free`)

### 函数映射

| 函数 | 地址 | 大小 | 实现 |
|---|---|---|---|
| `TpuTransferManager_New` | `0xeaba0a0` | 10 | `return operator new(1u);` — 一个 1 字节不透明占位句柄 |
| `TpuTransferManager_Free` | `0xeaba0c0` | 16 | `if (h) free(h);` — 释放占位符 |

```c
// TpuTransferManager_New   sub_EABA0A0
void* TpuTransferManager_New():  return operator new(1)   // dummy handle, no fields

// TpuTransferManager_Free  sub_EABA0C0
void TpuTransferManager_Free(void* h):  if (h) free(h)

GOTCHA — New 返回的句柄不携带状态。实际传输机制是 GetForPlatform 单例,它在第一次方法调用时惰性解析并缓存在 GetUnderlyingDeepseaPlatform::platform 中。New/Free 只存在于让主机端 TpuTransferManager C++ shim 有一个不透明 this 可传;不匹配的 New/Free 调用会泄漏/重复释放 1 字节,对设备状态无害,但属于主机侧分配器错误。


3. 主机 → 设备

目的

把主机 xla::Literal 移入设备内存(ShapedBuffer),并回答调用方在分配该设备缓冲区前需要的布局/大小问题。四者都通过 TransferManager vtable 弹跳调用。

函数映射

函数地址大小Vtable 槽位C++ 调用(解包后)
TpuTransferManager_TransferLiteralToDeviceAsync0xeaba240287+40m->TransferLiteralToDevice(stream, LiteralSlice(lit), shaped_buf, opts=0)
TpuTransferManager_GetByteSizeRequirement0xeaba4c0165+80m->GetByteSizeRequirement(host_shape)int64
TpuTransferManager_HostShapeToDeviceShape0xeaba160207+24m->HostShapeToDeviceShape(host_shape)Shape(通过 ToC 输出)
TpuTransferManager_ChooseCompactLayoutForShape0xeaba580339+88m->ChooseCompactLayoutForShape(host_shape)StatusOr<Shape>

算法 — TransferLiteralToDeviceAsync

c
// TpuTransferManager_TransferLiteralToDeviceAsync   sub_EABA240
// args: (a1=handle, a2=&SE_Stream, a3=XLA_Literal*, a4=XLA_ShapedBuffer*, a5=StatusRep** out)
ApiConverter::FromC(&lit,  a3)          // 24-byte MutableBorrowingLiteral
ApiConverter::FromC(&buf,  a4)          // 784-byte xla::ShapedBuffer
m      = GetForPlatform(platform).value()
stream = *a2                            // raw SE_Stream pointer, not converted
LiteralSlice slice(&lit)                // wrap the borrowing literal as a slice
status = (*(*m + 40))(m, stream, &slice, &buf, /*opts*/0)   // vtable +40
write_status_out(a5, status)            // *a5 = status; Unref(old) unless aliased
~LiteralBase(&slice); ~ShapedBuffer(&buf); ~MutableBorrowingLiteral(&lit)
```text

`XLA_Literal` 作为 `MutableBorrowingLiteral` 到达(它借用调用方仍拥有的主机内存),并被重新包装为 `LiteralSlice` 传给传输调用;该传输只读主机字面量,从不写入。`SE_Stream` 原样传递(`*a2`),这是这个函数组唯一触碰 stream 句柄的地方;它是异步顺序令牌,传输会排入该 stream。

> **NOTE —** `GetByteSizeRequirement`(`+80`)和 `HostShapeToDeviceShape`(`+24`)是纯 shape→scalar / shape→shape 查询:它们只对主机 `Shape`(320 字节栈对象)执行 `FromC`,弹跳调用,并且(对 `HostShapeToDeviceShape`)用 `ToC` 把结果设备 `Shape` 写入 out-param。它们不分配设备内存,也不触碰 stream。`ChooseCompactLayoutForShape` 是第三个仅 shape 查询;它的 `+88` 槽位直接读自 `(*(*m + 88))(…)` 弹跳调用(out `StatusRep**` 为第一个参数,`StatusOr<Shape>` shape)。

---

## 4. 设备 → 主机

### 目的

把设备 `ShapedBuffer` 拉回主机字面量,并读取设备缓冲区携带的动态维度(用于动态形状程序)。两者都基于完成回调。

### 函数映射

| 函数 | 地址 | 大小 | Vtable 槽位 | C++ 调用(解包后) |
|---|---|---|---|---|
| `TpuTransferManager_TransferLiteralFromDevice` | `0xeaba360` | 352 | `+32` | `m->TransferLiteralFromDevice(stream, shaped_buf, MutableBorrowingLiteral, done_cb, opts=0)` |
| `TpuTransferManager_ReadDynamicShapes` | `0xeabb660` | 455 | `+48` | `m->ReadDynamicShapes(stream, shaped_buf, &out_shape)` |

### 算法 — `TransferLiteralFromDevice`

```c
// TpuTransferManager_TransferLiteralFromDevice   sub_EABA360
// args: (a1, a2=&SE_Stream, a3=XLA_ShapedBuffer*, a4=XLA_Literal*, a5=StatusRep*, a6=callback ctx)
ApiConverter::FromC(&buf, a3)           // 784-byte ShapedBuffer
ApiConverter::FromC(&lit, a4)           // MutableBorrowingLiteral (host dest)
m      = GetForPlatform(platform).value()
stream = *a2
copy_construct(&lit2, &lit); copy_construct(&lit3, &lit2)   // 2 MBL copies for the closure
// build std::function<void(absl::Status)> closure capturing (a5=StatusRep out, a6=ctx)
done_cb = { __call_func = TpuTransferManager_TransferLiteralFromDevice::$_0,
            policy      = …::__create<$_0>() }
(*(*m + 32))(m, stream, &buf, &lit3, &done_cb, /*opts*/0)   // vtable +32, async
if done_cb.policy.dtor: done_cb.policy.dtor(captured)        // tear down closure
~MutableBorrowingLiteral3); ~ShapedBuffer(&buf)

与主机→设备路径不同,这里会构造一个真正的 std::function<void(absl::Status)> 完成回调($_0 lambda + 一个 __policy_func thunk;两个 lambda thunk 都作为 _ZNSt3__u…TransferLiteralFromDeviceE3$_0E… 存活在符号表中)。异步设备读取完成时,回调会把最终状态写入调用方的 StatusRep* 槽位。两次 MutableBorrowingLiteral 拷贝是闭包按借用值捕获目标字面量,使其生命周期越过同步返回。

NOTE — ReadDynamicShapes+48)读取动态形状设备缓冲区的运行时已解析维度;它对 ShapedBuffer 执行 FromC,弹跳调用,并用 ToC 输出一个 Shape。槽位 +48 直接读自 call *0x30(%rax)*(*m + 48))弹跳调用;它紧挨在 TransferLiteralToDevice+40)之上,而不是像朴素名册顺序可能暗示的那样位于谓词之间。


5. Infeed / Outfeed(遗留 StreamExecutor 路径)

目的

流式主机↔设备通道:把主机字面量排入片上 infeed FIFO,从 outfeed FIFO 出队结果字面量。这些是进入队列子系统的遗留入口:它们通过 C-shim 和 TransferManager vtable 编组进 deepsea 驱动。现代 PJRT 原生路径(tpu::System::EnqueueInfeed)完全绕过这个函数组。队列机制、span 分块和阻塞语义见 Infeed / Outfeed Queues;本节只记录两个 C-ABI 行及其 vtable 弹跳。

函数映射

函数地址大小Vtable 槽位C++ 调用(解包后)
TpuTransferManager_TransferLiteralToInfeed0xeabafa0241+56m->TransferLiteralToInfeed(executor, LiteralSlice(lit))
TpuTransferManager_TransferBuffersToInfeed0xeabb0a0638+136预线性化设备缓冲区的 infeed
TpuTransferManager_TransferLiteralFromOutfeed0xeabb320260+64m->TransferLiteralFromOutfeed(executor, MutableBorrowingLiteral)
TpuTransferManager_GetInfeedLayout0xf6a1a80163(无 vtable — 见 §7)TransferSizeUtil::ChooseGoodInfeedLayout(topology, shape)

算法 — TransferLiteralToInfeed / FromOutfeed

c
// TpuTransferManager_TransferLiteralToInfeed   sub_EABAFA0
// args: (a1, a2=&SE_StreamExecutor, a3=XLA_Literal*, a4=StatusRep** out)
ApiConverter::FromC(&lit, a3)                  // MutableBorrowingLiteral
m  = GetForPlatform(platform).value()
ex = *a2                                       // the executor IS the queue selector here
LiteralSlice slice(&lit)
status = (*(*m + 56))(m, ex, &slice)           // vtable +56  → driver EnqueueInfeed
write_status_out(a4, status); ~LiteralBase(&slice); ~MutableBorrowingLiteral(&lit)

// TpuTransferManager_TransferLiteralFromOutfeed   sub_EABB320
// args: (a1, a2=&SE_StreamExecutor, a3=XLA_Shape*, a4=XLA_Literal*, a5=StatusRep** out)
ApiConverter::FromC(&shape, a3)                // 320-byte Shape (the expected outfeed shape)
m  = GetForPlatform(platform).value()
ex = *a2
ApiConverter::FromC(&lit, a4)                  // MutableBorrowingLiteral (host dest)
status = (*(*m + 64))(m, ex, &lit)             // vtable +64  → driver DequeueOutfeed
write_status_out(a5, status); ~MutableBorrowingLiteral(&lit); ~Shape(&shape)
```text

两者都会把 `SE_StreamExecutor*`(`*a2`)传入 vtable 调用;这里 executor 指明*哪个设备的* infeed/outfeed 队列,这是该函数组中除了设备传输那一对之外 executor 的唯一作用。infeed 调用把字面量包装成只读 `LiteralSlice`;outfeed 调用传入可写 `MutableBorrowingLiteral` 作为结果。槽位 `+56` 落入 `DeepseaExecutor::EnqueueInfeed`,`+64` 落入 `DequeueOutfeed`(驱动叶子函数映射见 [Infeed / Outfeed page](../runtime/infeed-outfeed.md) 的 §legacy path)。

> **Note —** 两个不同地址支撑同一个 infeed/outfeed 调用,重新实现者不能混淆。[Infeed / Outfeed page](../runtime/infeed-outfeed.md) 把*主机侧 `TpuTransferManager::TransferLiteralToInfeed` C++ shim* 锚定在 `0xe9721c0`,把 `FromOutfeed` 锚定在 `0xe972660`,它们通过 `ExecutorApiFn()+560`/`+576` 抵达;这是**调用方**半边(转发进 `*ApiFn` 槽位的 SE shim)。本文记录的 `0xeabafa0`/`0xeabb320` 函数是**被调用方**半边,即槽位指向的 C-ABI 实现。由于 XLA 被静态链接,两半都存在于这个二进制中。
>
> **NOTE —** `TransferBuffersToInfeed`(`0xeabb0a0`,638 字节)是预线性化变体:它不接收字面量,而是接收已经是设备布局的缓冲区数组并将其排入队列,从而跳过线性化器。它通过 vtable 槽位 `+136`(`call *0x88(%rax)`)弹跳调用,这是一个比 `TransferLiteralToInfeed` 的 `+56` *更高且独立*的槽位,不是共享的 infeed 分支。缓冲区指针/长度数组参数(类似 `LinearizeToBuffers` 的输出)会在弹跳调用前循环执行 `FromC`。

---

## 6. 形状、布局与缓冲区访问谓词

### 目的

同步元数据侧:向设备缓冲区写入 tuple 索引表(让设备能找到每个 tuple 元素),以及两个询问缓冲区*现在*是否可在主机上读/写且无需设备同步的谓词。

### 函数映射

| 函数 | 地址 | 大小 | Vtable 槽位 | C++ 调用(解包后) |
|---|---|---|---|---|
| `TpuTransferManager_WriteSingleTupleIndexTable` | `0xeaba840` | 689 | `+120` | `m->WriteSingleTupleIndexTable(stream, device_addrs[], shape, &out_addr)` |
| `TpuTransferManager_CanShapedBufferBeAccessedNow` | `0xeaba6e0` | 174 | `+104` | `m->CanShapedBufferBeAccessedNow(executor, shaped_buf)` → `bool` |
| `TpuTransferManager_CanBufferBeAccessedNow` | `0xeaba7a0` | 141 | `+112` | `m->CanBufferBeAccessedNow(executor, device_addr)` → `bool` |
| `TpuTransferManager_PlatformId` | `0xeaba0e0` | 117 | `+16` | `m->PlatformId()` → `se::Platform::Id` |
| `TpuTransferManager_ResetDevices` | `0xeabb440` | 525 | `+72` | `m->ResetDevices(executors[])` |

### 算法 — 谓词和 `WriteSingleTupleIndexTable`

```c
// TpuTransferManager_CanBufferBeAccessedNow   sub_EABA7A0
// args: (a1, a2=&SE_StreamExecutor, a3=SE_DeviceAddressBase*)
ApiConverter::FromC(&addr, a3)                 // 24-byte DeviceAddressBase
m  = GetForPlatform(platform).value()
return (*(*m + 112))(m, *a2, &addr)            // vtable +112 → bool

// TpuTransferManager_CanShapedBufferBeAccessedNow   sub_EABA6E0
ApiConverter::FromC(&buf, a3)                   // 784-byte ShapedBuffer
m  = GetForPlatform(platform).value()
r  = (*(*m + 104))(m, *a2, &buf)                // vtable +104 → bool
~ShapedBuffer(&buf); return r

// TpuTransferManager_WriteSingleTupleIndexTable   sub_EABA840
// builds a heap vector<DeviceAddressBase> from the C address array (24 bytes each),
ApiConverter::FromC per element into operator new(24 * count)   // FromC each region
ApiConverter::FromC(&shape, a_shape)
m  = GetForPlatform(platform).value()
(*(*m + 120))(m, stream, addrs_vec, count, &shape, &out_c_addr, opts)   // vtable +120

PlatformId+16)是最简单的 vtable 成员:不做编组,只解析并返回平台 id 整数。两个 CanBe...AccessedNow 谓词是 XLA 用来判断设备可见内存中的主机指针在不强制 stream 同步的情况下是否一致的廉价主机侧检查;两者都接收 SE_StreamExecutor* 加缓冲区/地址并返回 bool。WriteSingleTupleIndexTable 是最重的 vtable 成员(689 字节),因为它在 +120 弹跳调用前,会从 C 数组构造一个堆上的 std::vector<DeviceAddressBase>(每个元素 FromC 进 24 字节槽位)。

GOTCHA — CanBufferBeAccessedNow+112)接收单个 SE_DeviceAddressBase(24 字节);CanShapedBufferBeAccessedNow+104)接收完整 XLA_ShapedBuffer(784 字节),调用后必须 ~ShapedBuffer。它们是相邻 vtable 槽位,但数值顺序与命名次序相反(shaped-buffer 变体是更低的偏移 +104);假设命名↔偏移单调的重新实现者会接错表。ResetDevices+72)接收 executor 数组;它的 call *0x48(%rax)*(*m + 72))弹跳调用直接读自反汇编。


7. 两个非 vtable 成员 — 直接线性化器调用

目的

LinearizeToBuffersGetInfeedLayout 是该函数组中奇特的一对:它们通过 xla::TransferManager vtable 分派。相反,它们直接解析 tpu::TpuTopology 并调用 xla::jellyfish 机制,也就是 PJRT 原生 infeed 路径使用的同一套线性化器 / 大小工具(见 Infeed / Outfeed Queues 的 §layout contract)。这是 C-shim 与现代运行时共享代码的地方。

函数映射

函数地址大小直接被调方
TpuTransferManager_LinearizeToBuffers0xeabab00594xla::jellyfish::LiteralLinearizer::LinearizeToBuffers(topology, …)
TpuTransferManager_GetInfeedLayout0xf6a1a80163xla::jellyfish::TransferSizeUtil::ChooseGoodInfeedLayout(topology, shape)

算法

c
// TpuTransferManager_GetInfeedLayout   sub_F6A1A80   (tpu_transfer_manager_c_api.cc:28)
// args: (a1=XLA_Shape* in, a2=XLA_Shape* out)
ApiConverter::FromC(&shape, a1)
topology = GetTopology(GetUnderlyingDeepseaPlatform::platform)
CHECK(topology != nullptr)        // FATAL "topology != nullptr" if null
TransferSizeUtil::ChooseGoodInfeedLayout(&out_shape, topology, &shape)
ApiConverter::ToC(&out_shape, a2)
~Shape(&out_shape); ~Shape(&shape)

// TpuTransferManager_LinearizeToBuffers   sub_EABAB00
// args: (…, a3=XLA_Literal*, a4=XLA_Shape*, a5/a6 = out buffer ptr/size arrays, a8=StatusRep**)
ApiConverter::FromC(&lit,   a3)
ApiConverter::FromC(&shape, a4)
topology = platform->topology   // *(*(platform+8)+184)
status = LiteralLinearizer::LinearizeToBuffers(topology, &lit, &shape, &out_vec,
                                               InvokeObject<$_0…unique_ptr<uchar[]>>)
// hand the device-layout buffer vector back to C as two parallel new[] arrays:
*a6 = operator new(8 * count)   // sizes
*a5 = operator new(8 * count)   // pointers; each buffer memcpy'd into a fresh operator new
// (each entry copied from the linearizer's internal 56-byte-stride buffer descriptors)
```text

`GetInfeedLayout` 是纯 shape→layout helper(它的 FATAL-check 源字符串 `tpu_transfer_manager_c_api.cc:28` 锚定了该函数组的 TU)。`LinearizeToBuffers` 很重:它把主机字面量线性化成设备分 tile 的字节缓冲区,并把每个缓冲区复制进新 `operator new` 出来的块,返回两个平行的 `new[]` 数组(指针 + 大小),主机稍后必须用 `FreeBuffers` 释放。

### `FreeBuffers` — 匹配的释放器

```c
// TpuTransferManager_FreeBuffers   sub_EABAF20
// args: (ptr_array, size_array, count)
void TpuTransferManager_FreeBuffers(void** ptrs, void* sizes, int64 count):
    for i in 0..count:  if ptrs[i]: free(ptrs[i])   // each buffer
    free(ptrs)                                       // the pointer array
    if sizes: free(sizes)                            // the size array
函数地址大小角色
TpuTransferManager_FreeBuffers0xeabaf20114释放 LinearizeToBuffers 指针数组 + 每个缓冲区 + 大小数组

GOTCHA — LinearizeToBuffers 使用 operator new 分配(逐缓冲区 + 两个 new[] 数组),但 FreeBuffers 使用 free() 释放。在这个 glibc 构建中 operator new 转发到 malloc,所以这对操作是平衡的;但如果重新实现者把 operator new/operator delete[] 接到与 malloc/free 不同的分配器,就会破坏堆。ABI 契约是“分配出的内容必须能由 free() 释放”。每次 LinearizeToBuffers 都要恰好配对一次 FreeBuffers(ptrs, sizes, count)


8. 完整 Vtable 槽位映射

重新实现者需要的一张表:每个 C 函数、它的地址,以及它弹跳经过的 xla::TransferManager vtable 字节偏移。每个偏移都直接读自反编译的 (*(*m + N))(…) 表达式,并与 call *0xNN(%rax) 反汇编交叉核对,因此所有行都是 CERTAIN。

C 函数地址Vtable 偏移C++ 方法(推断)
New0xeaba0a0operator new(1)
Free0xeaba0c0free
PlatformId0xeaba0e0+16PlatformId()
HostShapeToDeviceShape0xeaba160+24HostShapeToDeviceShape(Shape)
TransferLiteralFromDevice0xeaba360+32TransferLiteralFromDevice(…, cb)
TransferLiteralToDeviceAsync0xeaba240+40TransferLiteralToDevice(…)
ReadDynamicShapes0xeabb660+48ReadDynamicShapes(…)
TransferLiteralToInfeed0xeabafa0+56TransferLiteralToInfeed(exec, LiteralSlice)
TransferLiteralFromOutfeed0xeabb320+64TransferLiteralFromOutfeed(exec, MBL)
ResetDevices0xeabb440+72ResetDevices(executors)
GetByteSizeRequirement0xeaba4c0+80GetByteSizeRequirement(Shape)
ChooseCompactLayoutForShape0xeaba580+88ChooseCompactLayoutForShape(Shape)
CanShapedBufferBeAccessedNow0xeaba6e0+104CanShapedBufferBeAccessedNow(exec, buf)
CanBufferBeAccessedNow0xeaba7a0+112CanBufferBeAccessedNow(exec, addr)
WriteSingleTupleIndexTable0xeaba840+120WriteSingleTupleIndexTable(…)
TransferBuffersToInfeed0xeabb0a0+136设备缓冲区的 infeed
LinearizeToBuffers0xeabab00—(direct)jellyfish::LiteralLinearizer::LinearizeToBuffers
FreeBuffers0xeabaf20—(free)n/a
GetInfeedLayout0xf6a1a80—(direct)jellyfish::TransferSizeUtil::ChooseGoodInfeedLayout

QUIRK — vtable 偏移并不按名册顺序连续。C 函数按源码顺序发出(New, Free, PlatformId, …),但它们的槽位跟随 xla::TransferManager 基类 vtable 布局(+16, +24, +32, …),其中交错着 C shim 不暴露的基类虚函数。构建 C++ TransferManager 子类的重新实现者必须复现基类 vtable 顺序,而不是 C 名册顺序,否则上面的每个槽位偏移都会错一个 frame。


相关组件

名称关系
xla::TransferManager(TPU 子类)每个 vtable 后备行分派进入的 C++ 类
xla::TransferManager::GetForPlatform @ 0x1342f180每个函数调用的每平台单例解析器
deepsea::executor::GetRegisteredDeepseaPlatform解析 TPU Platform,缓存在 GetUnderlyingDeepseaPlatform::platform
ApiConverter::ToC / FromC在边界两侧编组 XLA_Shape / XLA_Literal / XLA_ShapedBuffer / SE_DeviceAddressBase
xla::jellyfish::LiteralLinearizer / TransferSizeUtilLinearizeToBuffers / GetInfeedLayout 的直接被调方(无 vtable)
ExecutorApiFn其槽位指向这些 C 函数的函数指针结构
TpuExecutor_* 名册形成对照的 shim:传递 SE_StreamExecutor* 句柄,而不是解析单例

交叉引用

  • TfTpu C-API Shim — 本名册依赖的 *ApiFn 访问器模型、不透明句柄约定和 ApiConverter 编组
  • TpuExecutor 名册 — 形成对照的逐设备函数组,它确实SE_StreamExecutor* 句柄穿线传过每次调用
  • TpuProgram 名册 — 通过同一 ExecutorApiFn 表抵达的兄弟序列化程序 C-ABI 函数组
  • Infeed / Outfeed QueuesTransferLiteral{ToInfeed,FromOutfeed} 行作为遗留 StreamExecutor 入口进入的运行时队列机制
  • PJRT Buffer & Memory — 设备侧 ShapedBuffer 所属的 PJRT 缓冲区/内存 ABI
  • TfTpu_Initialize Bootstrap — 一次性填充指向这些函数的 ExecutorApiFn 槽位