Skip to content

延续队列

本页所有地址均适用于来自 libtpu-0.0.40-cp314 wheel 的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d)。该映像剥离符号;反修饰后的 C++ 符号名按原文逐字引用。.text VMA 等于文件偏移(.text 基址 0xe63c000);.data.rel.ro 带有 0x200000 的 VMA→文件差值;protodesc_cold(嵌入的 FileDescriptorProto 文本)位于 VMA==offset 0xbe8af30。其他版本会有所不同。

摘要

延续队列是 TPU 程序在停止芯片的情况下结束的方式。普通 LLO 程序以 scalar-haltLloOpcode 0x25)终止,这会阻塞 sequencer,直到主机提交下一个程序。当 TensorCore 延续队列特性启用(Target+0x628 的 bit-0)时,尾部 halt 会被抑制,并由一个单独编译的 continuator program 替代;它推进描述符环、DMA 一个逐程序控制记录、触发主机中断,并直接尾调用到下一个程序的入口点。链路按 program→continuator→program→…→scalar-halt 运行;只有没有已入队后继的程序才真正 halt。这是 libtpu 对启动延迟的回答:在 Megacore 运行中,设备在背靠背程序之间永不空闲。

三个协作层承载该机制。(1) 配置 protoTpuChipConfigProto.ContinuationQueue,按 core 注册到 Target 中的 xla::jellyfish::Target::ContinuationQueueConfig 区域(Target+0x580 + core*0x38)— 声明四个生产者 sync-flag 字偏移、completion/error sflags、主机中断号,以及一个由 {shared_memory_region, consumer_sync_flag} 组成的 per_core 向量。(2) 异步描述符记录是一个扁平的 int32 SMEM 控制块(位于 Target+0x808..+0x8b8 的逐程序块),由 ReservedSmemFiller::FillCoreBuffer 在主机侧构建,每个字段放在 TpuMemoryReservationType 预留的字槽中,然后 DMA 到共享内存环中。(3) 运行时环tpu::ContinuationQueue — 是带锁保护的主机侧生产者/消费者,并有一个异步工作线程:Enqueue 提交 RequestWorkerLoop 弹出它并派发延续(延续会用 WriteMemory DMA 该记录),Completed 是设备中断处理器。

本页负责说明配置 proto、异步描述符记录、运行时 drain 协议,以及 scalar-halt 抑制映射。描述符的 enums(mem_id, core_id) 内存空间编码、src/dst opcode 枚举、OCI 描述符字段布局 — 属于 芯片内 DMA 描述符,此处不重复;将记录实际移动到设备 SMEM 的主机↔设备传输见 UHI 主机接口。已经理解带生产者索引、槽位数量和 completion 标志的有界环形缓冲区的读者会立刻认出其形状;本页记录的两个意外之处是:(1) “descriptor” 不是打包 struct,而是一个扁平 int32 数组,通过 reservation-type 字槽寻址,也就是设备经由 Target 访问器读回的同一块内存;(2) 程序终止是一个拆分程序模型 — haltcontinuation 不是二选一,而是由 Target+0x628 bit 在两半之间切换。

对于重新实现,契约是:

  • 配置 proto — 10 字段的 ContinuationQueue 消息(字段 #2 是编号空洞)、其 PerCore 子消息、SharedMemoryRegion,以及 asic_sw.deepsea.SyncFlag 消费者标志句柄 — 包含字段号、类型和 C++ 成员偏移。
  • 异步描述符记录 — 扁平 int32 SMEM 控制块,说明哪个 TpuMemoryReservationType 槽持有哪个字段、值来源、poison 填充值,以及记录字节大小 = reserved_words*4取整到 max(0x200, count) 的倍数。
  • 运行时 draintpu::ContinuationQueue 对象布局、环窗口算术,以及 EnqueueWorkerLoopWriteMemoryCompleted 路径;再加上设备侧生产者环推进 EmitContinuationTailcall
  • scalar-halt 映射 — 五个 ShaltInternal 程序末尾发射点、CompileInternalTarget+0x628 bit-0 的抑制、更丰富的 LowerHloModuleImpl 逐 sequencer gate,以及 continuator 自己的收尾 halt。
配置 protoTpuChipConfigProto.ContinuationQueueDescriptorProto @ 0xc18e12d,len 649)
运行时配置区域xla::jellyfish::Target::ContinuationQueueConfig @ Target+0x580 + core*0x38(stride 0x38
设备生产者xla::jellyfish::continuations::EmitContinuationTailcall(LloRegionBuilder, long) @ 0x12718ca0
Continuator 编译deepsea_compiler_backend::CompileContinuationTailCall @ 0x10a28b00
记录构建器tpu::ReservedSmemFiller::FillCoreBuffer @ 0x1d4c1d60;大小 @ GetRequiredDescriptorBytes 0x1d4c2a20
运行时环tpu::ContinuationQueue ctor @ 0x1d160ae0Enqueue 0x1d161160WorkerLoop 0x1d162ba0Completed 0x1d1616a0
入队驱动器tpu::RealProgramContinuator::EnqueueProgram @ 0x1d153ca0
Halt opcodeShaltInternal @ 0x1d520d20 = CreateNullaryOp(0x25)("scalar-halt")+ AppendInstruction
启用 bitTarget+0x628 bit-0(存在 TC 延续队列区域)
证据等级重新实现级 / 已按字节对 IDA 反编译 + 切出的 FileDescriptorProto + TcParseTable 偏移数组确认

1. ContinuationQueueConfig Proto

目的

TpuChipConfigProto.ContinuationQueue 是静态配置记录,用于告诉主机运行时和设备生产者队列位于何处。每个 core 注册一条消息(TpuChipCommonImpl::RegisterContinuationQueueConfigs(AnySpan<const TpuChipConfig::ContinuationQueue>) @ 0xe72b340),并复制到 Target 中作为位于 Target+0x580 + core*0x38Target::ContinuationQueueConfig 区域。生产者 sync-flag 偏移位于消息头;描述符环区域和消费者标志位于 per_core 向量中。生产者和消费者寻址 sync flags 与环窗口所需的一切都能从这一条消息到达。

编码

字段映射从嵌入的 FileDescriptorProto 精确按字节切出(DescriptorProto body @ 0xc18e12d,len 0x289=649 的 protoc --decode_raw),并由生成的 TcParseTable 成员偏移数组 TpuChipConfigProto_ContinuationQueue::_table_ @ 0x2200cb48(12 字节 FieldEntry stride {u32 member_offset, u16 has_idx, u16 typecard})和 Clear() @ 0x20b0c5a0 佐证。

Field #名称Proto 类型C++ off
1core_typeenum .tpu.TpuCoreTypeProto(TC=0 / BarnaCore=2)+0x28
(2)—(不存在 — 字段编号空洞)
3completion_state_sync_flag_word_offsetint32+0x2c
4error_ack_sync_flag_word_offsetint32+0x30
5overwritten_error_sync_flag_word_offsetint32+0x34
6completion_interrupt_numberint32+0x38
7per_core(repeated ContinuationQueue.PerCoremessage+0x18(count +0x20
8producer_sync_flag_min_descriptor_word_offsetint32+0x3c
9producer_sync_flag_remaining_descriptors_word_offsetint32+0x40
10producer_sync_flag_countint32+0x44
11producer_sync_flag_index_word_offsetint32+0x48

has_bits 位于 struct+0x10。四个生产者 sflags(#8–#11)是顶层 ContinuationQueue 字段,而不是逐 core 字段 — 每个 core 已有一份队列配置,所以逐队列字段本身已经是逐 core 的。

注意 — 四个 producer_sync_flag_* 字段是 ContinuationQueue 字段 #8/#9/#10/#11;PerCore 只持有 #1/#2。设备生产者(EmitContinuationTailcall§4)从位于 config+0x0/+0x4/+0x8/+0x1c 的逐 core 队列头读取 producer base/remaining/count/index — 这是字段 #8/#9/#10/#11 的运行时头部重布局。

PerCore 子消息

ContinuationQueue.PerCoreDescriptorProto @ 0xc18e326,len 138;_table_ @ 0x2200cab8)恰好携带两个 message 类型字段:

Field #名称Proto 类型C++ off
1shared_memory_regionmessage .tpu.TpuChipConfigProto.SharedMemoryRegion+0x18
2consumer_sync_flagmessage .asic_sw.deepsea.SyncFlag+0x20

SharedMemoryRegionDescriptorProto @ 0xc18dd41,len 117;_table_ @ 0x2200c638)命名描述符环窗口:

Field #名称Proto 类型C++ off
1shared_memorymessage .tpu.TpuSharedMemoryOnChipProto(HBM / CMEM kind)+0x18
2word_countINT64+0x20
3word_offsetINT64+0x28

注意 — word_count/word_offset 在 proto 中是 int64。运行时 TargetMemoryPart 携带截断后的 int32 副本(+0x04 = word_offset+0x08 = word_count),因为片上字偏移适合 32 位;运行时 ctor 中的环窗口算术(§3)读取这些 int32 副本。不要在 proto 层假设 int32

consumer_sync_flag 是标准 asic_sw.deepsea.SyncFlag 句柄(DescriptorProto @ 0xc19228a,len 226)— 即设备在读取描述符槽之前等待的 sync flag:

Field #名称Proto 类型角色
1indexint32core 的 tier 内的 sflag 字索引
2coreenum Core哪个 engine 拥有该标志
3core_indexint32逐 core 实例索引
4tile_indexint32tile 索引(SparseCore tiles)

嵌套 Core enum 是 {TENSORCORE=0, BARNACORE=1, HOST_INTERFACE=2, SPARSECORE=3, SPARSECORE_TAC=4, SPARSECORE_TEC=5}。(这与 SparseCore sequencer enum 使用的 SCS/TAC/TEC 编号相同;构建中还存在另外两个不同的 SyncFlag 消息 — {is_host, word_offset} @ 0xc18de3b{word_offset, value, expected_transfer_size} @ 0xc1792af — 它们是不同类型,不是 consumer flag。)

运行时配置区域头

RegisterContinuationQueueConfigs 将 proto 复制到 Target 中时,设备生产者索引的头部重布局(Target+0x580 + core*0x38,stride 0x38)暴露四个生产者 sflags 加上 per_core 向量:

Off来源字段角色
+0x00#8 …min_descriptor_word_offset生产者描述符数组 base sflag("…available_count_sync_flag_base",len 0x31
+0x04#9 …remaining_descriptors_word_offsetremaining-descriptors sflag base("…_remaining_descriptors_base",len 0x47
+0x08#10 …count环槽位数量 — 2 的幂(popcnt==1 CHECK'd)
+0x1c#11 …index_word_offset生产者写索引 sflag("…available_count_sync_flag_index",len 0x32
+0x20#7 per_corevector .begin(TC core 0 为 Target+0x5a0
+0x28#7 per_corevector .end(TC 为 Target+0x5a8

每个 per_core 元素(28 字节的 Target::ContinuationQueueConfig::PerCore,stride 0x1c):+0x00 MemorySpace,+0x04 word_offset+0x08 word_count+0x0c consumer-sflag ptr(可选),+0x14 has-consumer bool,+0x18 producer-sflag word offset。C++ 成员类型名 Target::ContinuationQueueConfig::PerCore 是从生产者溢出路径引用的 std::vector<…ContinuationQueueConfig::PerCore>::__throw_length_error 符号 @ 0x1271a660 恢复的。


2. 异步描述符记录

目的

“continuation descriptor” 是生产者 DMA 的数据,用于让下一个程序知道要运行什么。它不是带显式字节偏移的打包 struct — 它是一个扁平 int32 字数组(逐程序 SMEM 控制块),其中每个程序控制字段占用 TpuMemoryReservationType 预留的字槽。主机逐字段填充它;设备通过 §4 中的 Target 字偏移访问器读回同一块内存。每个入队程序对应一条记录。

布局

ReservedSmemFiller::FillCoreBuffer(TpuCore*, TpuRunConfig const&, Inputs const&, bool, Span<int>) @ 0x1d4c1d60 写入记录。每次字段写入都经过 lambda $_0(TpuMemoryReservationType type, int value, int idx) @ 0x1d4c2860,它执行 span[GetRegionForType(type).word_offset] = valueGetRegionForType @ 0x20b16260[reservation+0x4c0 + type*0x18] 处索引逐代 region table,并返回 24 字节 {word_offset, word_count, …} 区域;type enum 有 50 个条目(< 0x32)。

已验证的写入集合(每行是一个 int32 槽;设备读回访问器位于 Targets overview 的 program-descriptor SMEM block,Target+0x808..+0x8b8):

TpuMemoryReservationType (idx)值来源(FillCoreBuffer / Inputs设备读回(Target off)
kXprofProgramId (0x07)TpuRunConfig::xprof_program_id()ProgramIdLocation (+0x808)
kRunIdLow (0x08)Inputs+0x08RunIdLowLocation (+0x820)
kRunIdHigh (0x09)Inputs+0x0cRunIdHighLocation (+0x828)
kSameAsLastProgram (0x12)0 / cache-hit flagSameAsLastProgram (+0x838)
kLaunchBarrierId (0x13)0LaunchBarrierId (+0x840)
kCrossProgramPrefetchSuccess (0x14)region / flagCrossProgramPrefetchSuccess (+0x848)
kProgramDescriptorSize (0x15)descriptor_bytes / words-perProgramDescriptorSize (+0x858)
kProgramDescriptorState (0x16)Inputs+0x4c(1=initial / 2=continuation)ProgramDescriptorState (+0x860)
kProgramEntryPointAddress (0x17)Inputs+0x38(下一个程序入口地址)ProgramEntryPointAddress (+0x868)
kProgramEntryPointSize (0x18)Inputs+0x3c(入口大小)ProgramEntryPointSize (+0x870)
kHbmStackOffset (0x05) / kCmemStackOffset (0x06) / kHostStackOffset (0x21)region word_offset(栈基址)
kHbmOffset (0x03) / kCmemOffset (0x04)GetHeapWordOffset(., HBM/CMEM)heap slots
kTensorCoreStackSize (0x1d) / kSparseCoreStackSize (0x1e)逐代栈大小(以字计)
kTrapId (0x23)trap-id(若无则为 0)
kSparseCorePTState (0x30)0xFFFFFFFF(unset sentinel)
kTensorCoreAssertionArgs (0x31)0xC0C0C0C0(poison fill)

kProgramDescriptorState / kProgramEntryPointAddress / kProgramEntryPointSize 写入已按字节确认:$_0(span, 22, *(Inputs+19), 0)Inputs+0x4c)、$_0(span, 23, *(Inputs+14), 0)Inputs+0x38)、$_0(span, 24, *(Inputs+15), 0)Inputs+0x3c);poison 填充是 $_0(span, 48, 0xFFFFFFFF, …)$_0(span, 49, 0xC0C0C0C0, …)

注意点 — 该记录没有 framing word、没有 version word、没有 field-tag bytes。它就是裸的 int32 SMEM 控制块;“哪个字段”完全由某个 reservation type 拥有的哪个字槽来编码。重新实现者如果没有逐代 reservation table(GetRegionForType),就无法解析该映像。记录结构(type→slot)在此已按字节精确确认;每个槽的字面字偏移是代际相关的(来自 chip-parts table)。consumer sync-flag 在记录中 — 它作为 WriteMemoryoptional<SyncFlagInfo> 参数在带外携带(§3)。

记录字节大小

ReservedSmemFiller::GetRequiredDescriptorBytes(TpuCore*, TpuRunConfig const&) @ 0x1d4c2a20

c
// tpu::ReservedSmemFiller::GetRequiredDescriptorBytes   sub_1D4C2A20
function GetRequiredDescriptorBytes(core, run_config):
    count    = chip_config.producer_sync_flag_count        // [chipcfg+0xc8]
    granule  = (count >= 513) ? count : 512                // alignment granule, floored at 512
    reserved = run_config[+0x28] + user_region.word_count  // reserved int32 word count
               - user_region.word_offset_pair             //   (GetUserRegion offsets)
    bytes    = reserved * 4                                 // int32 words -> bytes
    rem      = bytes % granule
    return (rem == 0) ? bytes : bytes + (granule - rem)     // round bytes UP to a multiple of granule

映像由 MaybeMappedBuffer functor 分配:先用 PremappedMemoryManager::Allocate(固定且 DMA 可映射的区域 — 返回设备字节偏移 [cont+0xc0]-[[cont+0xb8]+8]),失败时回退到 ContinuationDescriptor::DefaultAllocator @ 0x1d6272e0posix_memalign(&p, 0x20, size),32 字节对齐,free deleter)。ContinuationDescriptor ctor 会把整个映像 memset 为 0,然后 fill functor(通过 __bind_front 绑定的 FillCoreBuffer)写入字段。

怪癖 — 常量 0x200=512 出现三次,始终作为下限,绝不是 in-flight 上限:在这里作为字节大小对齐 granule(max(count, 512));作为运行时 min_descriptor_size_ 下限(obj+0x58 = max(descsize, 512)§3);以及在 ContinuationDescriptor::Terminator(int) @ 0x1d627260 中的 if (a2 < 513) a2 = 512; clamp。因此描述符映像永远不会小于 512 字节,而 Terminator 描述符(结束链的描述符)继承同样的 512 字节最小值。应将 512 视为描述符映像的最小大小,而不是三个独立的魔数 — 也不是 in-flight 描述符数量限制。


3. 运行时环 {#3-the-runtime-ring}

目的

tpu::ContinuationQueue 是拥有描述符环的主机侧生产者/消费者。它是带锁保护的队列,并有一个异步工作线程:XLA 侧 Enqueue 一个请求,工作线程弹出它并派发延续(延续会把描述符映像 DMA 到设备 SMEM),设备中断驱动 Completed。设备 SMEM 中的环窗口是所选 core 的 PerCore.shared_memory_region 给出的 [word_offset, word_offset + word_count)

对象布局

来自 tpu::ContinuationQueue::ContinuationQueue(...) ctor @ 0x1d160ae0per_core[core] 元素由 TpuCoreOnChip 参数的 int32 @+0x4 选择,vector stride 0x30):

Off字段来源 / 角色
+0x00config head(ymm copy)ContinuationQueue descriptor +0..+0x1f
+0x20int32descriptor +0x20
+0x28/+0x30/+0x38per_core vector {begin, size, cap}descriptor per_core 的堆副本
+0x40环窗口起点字节per_core[core]+0x10(word_offset) * wordsize
+0x48环窗口终点字节(per_core[core]+0x8(word_count) + +0x10) * wordsize
+0x50granule_bytes_原始 descriptor-size ctor 参数(所有三个 % granule_bytes_ CHECK 中的除数)
+0x58min_descriptor_size_max(arg, 0x200=512) — 512 字节下限(CHECK "min_descriptor_size_ % granule_bytes_ == 0"
+0x60max_descriptor_size_(END − START)/2 − descsize(CHECK "max_descriptor_size_ % granule_bytes_ == 0"
+0x68TpuHostWorkQueue*ctor 参数
+0x70core_type / completion interruptdescriptor +0xc
+0x78用户 dispatch functionstd::function 参数
+0x88WriteMemory writeback functorctor 参数
+0xa0worker threadInitialize 设置(0x160-B state)
+0xa8absl::Mutex(队列锁)Enqueue/WorkerLoop/Completed
+0xb0SyncFlagRefcountscount > 1 则构造;valid-flag +0xc8
+0xe0..+0x100in-flight std::deque<Request> + count+0x100 = producer count(在 Enqueue 时递增)
+0x110..+0x130completed container + idx+0x130 = consumer index(在 dispatch 时递增)
+0x1b0running flagInitialize 置 true,供 WorkerLoop 轮询

ctor 的环窗口算术按字节证明 per_core+0x10word_offsetper_core+0x8word_count:START = word_offset * wordsize,END = (word_count + word_offset) * wordsize — 一个半开 [base, base+size) 区域。反编译显示 obj+0x60 = v24/2 - descsize(容量)以及一个 CHECK(descriptor_state_word_offset_ < min_descriptor_size_ / sizeof(int32_t)),确认记录以 int32 字计量大小。

Drain 协议

c
// the host-side producer/consumer drain
function Enqueue(descriptor, device_byte_offset, completion_cb):     // sub_1D161160
    // bounds-check the descriptor byte offset against [min_descriptor_size_, max_descriptor_size_]
    if descriptor[+0x28] > obj[+0x60] || descriptor[+0x28] < obj[+0x58]:
        completion_cb(OutOfRange status); return                      // failure path
    append Request{descriptor, completion_cb, buffer functors} to in-flight deque
    obj[+0x100]++                                                     // producer count
    post deferred write onto host work queue obj[+0x68]

function WorkerLoop():                                                // sub_1D162BA0
    acquire obj[+0xa8] (mutex 0x21146de0)
    while obj[+0x1b0] (running):                                      // polled flag
        swap out the in-flight deque<Request>
        for each Request req:
            (*req[+0x58])()    // continuation dispatch -> drives WriteMemory -> DMA
            (*req[+0x50])()    // completion callback
            obj[+0x130]++      // consumer index
    release mutex (0x21147b40)

function WriteMemory(dev_off, image:Span<uchar>, len, sync_flag_info, cb):  // sub_1D163A40
    if obj[+0x70] (core_type) == 2: ...                              // host-interface gate
    memcpy the descriptor image into the shared-memory ring          // sub_1D163DD4
    post the write through host work queue with the optional SyncFlagInfo
    invoke writeback functor obj[+0x88]

function Completed(int idx, bool ok):                                // sub_1D1616A0  (device-VInt ISR)
    acquire obj[+0xa8]
    walk the completed container [obj+0x130], match descriptor by idx
    fire its completion callback (req[+0x50]); free the slot
    emplace follow-ons (EmplaceBackSlow 0x1d164d60)
    // ok=false maps to the error_ack / overwritten_error sflag roles (config #4/#5)

in-flight 容器是 std::deque<Request>;每个 Request0x80 字节(EmplaceBackSlow 执行 shl $0x7)。Request 携带 ContinuationDescriptorMaybeMappedBuffer ptr@+0、deleter +8、span-fill fn +0x10、mapped-buffer state +0x18、ring byte offset +0x28)、一个 WriteMemory functor +0x40、一个 completion callback +0x50,以及 continuation dispatch functor +0x58

入队驱动器

tpu::RealProgramContinuator::EnqueueProgram @ 0x1d153ca0 是更高层的驱动器,它构建描述符并调用 Enqueue。它调用 GetRequiredDescriptorBytes(→ size)、memcpy 程序映像、构建一个 0x140 字节的 Inputs struct 并通过 __bind_front 绑定到 FillCoreBuffer、构造 ContinuationDescriptor(size, fill_fn, alloc_fn)(@ 0x1d627220 — 调用 fill(Span<int>{buf, size>>2}) 的那个 variant),然后调用 Enqueue(descriptor, device_byte_offset, completion_cb),其中 offset 是 MaybeMappedBuffer 设备字节偏移($_3+0x30)。ProgramContinuator 暴露 AttachOnEnqueue/AttachOnComplete/AttachOnError hooks(@ 0x1d1453e0/0x1d145460/0x1d1454e0),并连接到它自己的 FSM(Init/Working/Draining/Drained/TearingDown/TearedDownoperator<< @ 0x1d145600)— 这是一个与设备侧描述符 State 字(§4不同的状态机。

注意 — 主机侧选择 kProgramDescriptorState = 1(initial)还是 2(continuation)的位置是 Inputs+0x4c;在入队时设置它的单一谓词尚未隔离(Inputs+0x4c来源证据为 LOW;对记录的写入已 CONFIRMED)。Megacore barrier 处的设备侧 SetProgramDescriptorState(2) 已按字节确认(§4)。


4. 设备侧生产者环推进 {#4-the-device-side-producer-ring-advance}

目的

运行时环的编译期对应物是 EmitContinuationTailcall — 这是 continuator program 在设备上运行的 LLO,用于推进生产者索引 sync flag、寻址描述符数组 sflags、从 SMEM 控制块读出下一个程序的入口点、DMA 描述符、触发主机中断,并执行尾调用。这就是在设备侧实际 drain 一个环槽并把控制权交给后继程序的机制。

入口点

text
TpuCompactionIsaEmitterCodegen::Create  (bit-0 gate @0x1090eb6e)
  └─ deepsea_compiler_backend::CompileContinuationTailCall  @0x10a28b00  ── builds a fresh continuator program
       ├─ EmitContinuationTailcall  @0x12718ca0              ── the ring-advance + DMA + tailcall body
       └─ ShaltInternal  @0x10a28d21 (iff Emit returned rax==1) ── the continuator's OWN closing halt

算法

c
// xla::jellyfish::continuations::EmitContinuationTailcall(LloRegionBuilder, long)   sub_12718CA0
function EmitContinuationTailcall(rb, nop_count):
    core = TpuCoreTypeForSequencer(module[+0x268])
    cfg  = Target + 0x580 + core*0x38           // the per-core ContinuationQueueConfig head
    // precheck: platform==iss & Megachip & CoresPerChip(SC)>0 & core==2 & Target+0x628 bit-0
    for nop_count iterations: Vnop(); CompilerBarrier()         // overlay-prelude padding

    idx_ptr = SflagImmPtr(cfg[+0x1c], "continuation queue available_count_sync_flag_index", 50)
    idx     = VsyncRead(idx_ptr)                                // current producer write index
    CHECK(absl::has_single_bit(cfg[+0x8]))                      // count must be power-of-2 (popcnt==1)
    new_idx = SandU32(SaddS32(idx, 1), cfg[+0x8] - 1)           // (idx+1) & (count-1)  — ring wrap
    VsyncSet(idx_ptr, new_idx)                                  // advance the producer index sflag

    base_ptr = SflagImmPtr(cfg[+0x0], "continuation queue available_count_sync_flag_base", 49)
    base     = CalcWordAddr(base_ptr, idx)                      // descriptor-array base, indexed by idx
    rem_ptr  = SflagImmPtr(cfg[+0x4], "...available_count_sync_flag_remaining_descriptors_base", 71)
    rem      = CalcWordAddr(rem_ptr, idx)                       // remaining-descriptors sflag

    // per_core loop: cfg[+0x20]..cfg[+0x28], stride 0x1c; each entry checked (+0x14==1 && +0xc==0)
    state = Sld(SmemWordImmPtr(target.ProgramDescriptorStateWordOffset()))   // Target+0x860
    addr  = Sld(SmemWordImmPtr(target.ProgramEntryPointAddressWordOffset())) // Target+0x868 (next program)
    size  = Sld(SmemWordImmPtr(target.ProgramEntryPointSizeWordOffset()))    // Target+0x870
    run_id= Sld(SmemWordImmPtr(target.RunIdLowLocationWordOffset()))         // Target+0x820

    EnqueueDmaLocalInGranules(...)                              // sub_1D540640 — DMA the descriptor
    VsyncAdd / VwaitEqSV / VwaitDone                            // descriptor-ring handshake
    VInt(0x80000000)                                           // host interrupt -> Completed
    tailcall(addr, size)                                       // jump into the next program
    return status                                              // rax==1 -> continuator emits its closing Shalt

__popcnt(cfg[+0x8]) != 1 检查以及断言字符串 "absl::has_single_bit(continuation_queue.available_count_sync_flag_count)" 已在反编译第 310 行按字节确认;SflagImmPtr(cfg[+0x1c], "…_index", 50) / SflagImmPtr(*cfg, "…_base", 49) / SflagImmPtr(cfg[+0x4], "…_remaining_descriptors_base", 71) 读取和 (idx+1)&(count-1) 环绕也都字节精确。core*0x38 + 0x580 索引(v6 = v3 + 56*v5 + 1408)确认了 0x38 的头部 stride。

描述符 State 握手

设备侧 ContinuationDescriptor::State 字(位于 Target+0x860 的 SMEM)是一个 2 值握手:

  • SetProgramDescriptorState(tpu::ContinuationDescriptor::State, rb) @ 0x1271a5e0SimmU32(State) Sst 到该槽。所有三个调用点(全二进制)都在 BarrierCoresWithIdVerificationInternal @ 0x12715c00 内(0x12717828/0x12718183/0x1271825b),并且每个都传入 mov $0x2,%edi — 即 State=2。另外,SynchronizeProgramDescriptorStatesMegacore @ 0x1c697540 调用 SetProgramDescriptorState;它读取 ProgramDescriptorStateWordOffset 处的 State 字并将其 EnqueueRemoteSst 到 twin core — 是在 Megacore 对之间传播(而不是设置)State。
  • GetProgramDescriptorState(rb) @ 0x1271a580 Sld 它。LowerHloModuleImpl 读取它,并基于 (State SeqS32 1) 对下一个 block 加谓词。

所以 State 1 = 第一次/初始运行State 2 = continuation/下一个程序就绪(Megacore barrier 会在尾调用前把两个 twin core 的 State 同步为 2)。该构建中没有独立的 enumerator names 描述符;只观察到了字面值 1 和 2。

怪癖 — 两个无关状态机共享 “State” 一词。设备描述符 State(本节,值 1/2)是 continuator 读取以决定是否对 continuation block 加谓词的内容。主机 tpu::ProgramContinuator::StateInit/Working/Draining/…operator<< @ 0x1d145600§3)是驱动器 FSM。二者不同;不要混淆。主机 FSM 驱动 Enqueue;设备 State 字 gate 片上谓词。


5. Scalar-Halt 映射

目的

程序终止是一个拆分程序模型。没有延续队列时,程序以阻塞式 scalar-halt 结束,由主机提交下一个程序。启用队列后,尾部 halt 被抑制,一个单独的 continuator program 会推进队列并尾调用后继。本节映射每个 scalar-halt 发射点以及切换它们的 gates,让重新实现者准确知道何时发射、抑制或替换 halt。

Halt opcode

scalar-haltLloOpcode 0x25,只由 LloRegionBuilder::ShaltInternal @ 0x1d520d20 发射(= CreateNullaryOp(0x25) + AppendInstruction)。opcode 名称来自 LloOpcodeString 表 @ 0x21cd0d60(R_X86_64_RELATIVE addends):

Opcode字符串
0x25"scalar-halt"
0x26"scalar-halt-yield-cond"(本构建中未作为 nullary 发射)
0x27"scalar-halt-on-error"LloRegionBuilder::ErrorIf / Error — error halt,不是程序末尾)

程序末尾发射 / 抑制点

五个 ShaltInternal 调用者,全二进制:

#调用者Site VMAGate / 角色
1barna_core::BcsLloProgramCreator::Build()0xf9ce922unconditional — BarnaCore sequencer LLO 程序末尾
2barna_core::BcsLloProgramCreator::BuildTop()0xf9cebddunconditional — BarnaCore 顶层程序末尾
3DeepseaCompilerBase::LowerHloModuleImpl()0x10920035逐 sequencer gate(见下)— 主 HLO lowering 程序末尾
4DeepseaCompilerBase::CompileInternal()0x10928095test [Target+0x628],1; jne skip — bit-0 SET 会抑制 main halt
5deepsea_compiler_backend::CompileContinuationTailCall()0x10a28d21continuator 自己的收尾 halt(当且仅当 EmitContinuationTailcall 返回 rax==1

Target+0x628 bit-0 使用者

Bit-0(存在 TC 延续队列区域)在三个位置测试:

Site VMA外层函数bit-0 SET 时的动作
0x10928083CompileInternal跳过尾部 ShaltInternal(main program 发射 halt)
0x1090eb6eTpuCompactionIsaEmitterCodegen::Create发射 continuator:调用 CompileContinuationTailCall @ 0x1090eba0
0x1091bcc7LowerHloModuleImpl一个邻近描述符状态谓词的 testb $0x4 IsMegachip 复查

因此 bit-0 同时做两件事:它抑制 main program 的 halt,并且启用 continuator 的编译。continuator(CompileContinuationTailCall)构建自己的 LloRegionBuilder,发射生产者环推进 + 描述符 DMA + 尾调用(EmitContinuationTailcall),并以自己的 Shalt 结束(已按字节确认:反编译显示 EmitContinuationTailcall(...),然后是一个条件性 ShaltInternal)。

LowerHloModuleImpl 逐 sequencer gate

上面的 site #3 使用比 CompileInternal 的简单 bit-0 测试更丰富的谓词(字节精确 @ 0x1091ffc0..0x10920035):

c
// the program-end halt decision in LowerHloModuleImpl
if Megachip() && CoresPerChip(SC) > 0 && sequencer == 2 (BarnaCore)
        && (((Target+0x628 bit-2) >> 2) | Target+0x540) & 1:
    emit Shalt                                   // megachip BarnaCore: blocking halt
else:
    bit = (1 << sequencer) & Target+0x628
    if bit == 0 || sequencer < 3:
        emit Shalt                               // no continuation-queue bit for this sequencer
    else:                                        // bit set && sequencer >= 3
        SKIP Shalt                               // continuation queue present for this sequencer

这里 sequencerTpuSequencerType 逐 core 索引,Target+0x540platform==iss。延续队列是否存在是一个逐 sequencer bit (1 << sequencer) & Target+0x628,不是单一全局标志 — Megachip 运行可以有些 sequencer halt,另一些继续。

注意点 — “halt”和“continuation”在芯片级并不互斥。把 bit-0 当成“任何地方都不 halt”的幼稚重新实现是错误的:main program 不发射 halt,但 continuator 仍然以 Shalt 结束,并且任何 (1 << sequencer) bit 未置位的 sequencer 仍会正常 halt。延续链中的终端程序 — 即没有已入队后继的程序,也就是 Terminator 描述符 — 才是真正 halt 整条链的那个。


相关组件

名称关系
tpu::RealProgramContinuator构建描述符并调用 Enqueue 的主机驱动器;拥有队列和 OnEnqueue/OnComplete/OnError hooks
tpu::ReservedSmemFiller渲染描述符记录(FillCoreBuffer)并计算其大小(GetRequiredDescriptorBytes
xla::jellyfish::continuations::EmitContinuationTailcall设备侧 LLO,用于 drain 一个槽、DMA 记录并尾调用
DeepseaCompilerBase / TpuCompactionIsaEmitterCodegen编译期 gate(Target+0x628 bit-0),用于抑制 main halt 并发射 continuator

交叉引用

  • 芯片内 DMA 描述符(mem_id, core_id) 内存空间编码、src/dst opcode 枚举,以及实际片上 DMA(这里由 EnqueueDmaLocalInGranules 发出)使用的 OCI 描述符字段布局;本页不重复这些枚举
  • UHI 主机接口 — 将 continuation descriptor 映像实际移动到设备 SMEM 的主机↔设备传输
  • 主机↔设备 DMA — 延续队列的记录传输所依附的主机路径 DMA 分类(DMA_TYPE_CHIP_TO_HOST / DMA_TYPE_LOCAL_OR_HOST
  • OCI 命令 DMA-ID — 用于 profile continuation DMAs 的描述符 begin/end trace-id 配对
  • SFLAG 协议 — producer index / remaining-descriptors / consumer flags 所在的 sync-flag tier
  • SMEM 标量内存 — 扁平 int32 描述符记录和 program-descriptor 控制块占用的标量内存 tier
  • 内存概览 — 更广泛片上内存模型中的延续队列小节
  • Targets 概览 — 记录在设备上通过其读回的 program-descriptor SMEM 控制块(Target+0x808..+0x8b8