Skip to content

Worker 注册

每个 Megascale worker,包括 coordinator 自身的 host-process,都会通过发出一次 GetMultiSliceTopology gRPC 向 coordinator 注册自身。请求携带一个 NetworkAddressMapping(worker 的 (slice_id, host_id) 身份以及它本地绑定的网络端点)、每个 slice 的 TpuTopologyArgsProto,以及一个让 coordinator 检测静默重启的 incarnation_id

请求

线路路径: /xla.megascale.runtime.MegaScaleTransport/GetMultiSliceTopology (rodata 0x84b9e72 处的字符串)。

消息:xla.megascale.runtime.GetMultiSliceTopologyRequest,package xla.megascale.runtime,syntax proto3。序列化后的 FileDescriptorProto 位于 descriptor_table_protodef_0ymGVhRfurb(符号位于 0xbf814f0);请求的 descriptor 文本从 VA 0xbf81634 开始。

已确认 — 字段编号、标签和类型逐字节从 0xbf81634 的 descriptor blob 解码:address_mapping = 18 01 20 01 28 0btpu_topology_args = 18 02 20 01 28 0bincarnation_id = 18 03 20 01 28 03(type 3 = INT64)。

字段 #名称标签类型含义
1address_mappingoptionalxla.megascale.runtime.NetworkAddressMapping此进程的 (slice_id, host_id) 加上 MegaScaleTransport 绑定点。
2tpu_topology_argsoptionaltpu.TpuTopologyArgsProto每个 slice 的拓扑 proto(芯片布局、ICI 维度)。同一 slice 的所有 host 必须一致。
3incarnation_idoptionalint64每个进程的 id。允许 coordinator 检测同一 (slice_id, host_id) 槽位内的进程重启。

NetworkAddressMapping(定义于 addresses.proto,descriptor 位于 0xc1795bd)携带 (int32 slice_id [1], int32 host_id [2], repeated HostNetworkAddress addresses [3])HostNetworkAddress(descriptor 位于 0xc17954a)携带 (string address [1], string interface_name [2], string host_name_for_debugging [3], int32 numa_node [4]) — 线路上没有 porttransport_type 字段;address 是完整的端点字符串。

请求在 CommunicationBackend::DiscoverTopologyAndAddressBindings( int local_slice_id, TpuTopologyArgsProto args, int local_host_id, int num_slices) 内构造(反编译地址 0x1ccacb80)。请求逐字段构建:

c
GetMultiSliceTopologyRequest req(/*arena=*/nullptr);

// 1. address_mapping (field 1) — a NetworkAddressMapping default-
//    constructed on the request's arena. Its scalar int32 fields
//    are stored at offsets +40 / +44 of the sub-message, and the
//    addresses[] repeated field is appended into via
//    proto2::Arena::DefaultConstruct<HostNetworkAddress>.
NetworkAddressMapping* am = req.mutable_address_mapping();
am->set_slice_id(/*+40*/ ...);
am->set_host_id(/*+44*/ ...);
//    addresses[] is populated from the locally bound
//    HostNetworkAddress list (built into NetworkAddressMapping v85
//    at function entry, then copied/swapped element-wise).

// 2. tpu_topology_args (field 2) — CopyFrom / InternalSwap of args.
*req.mutable_tpu_topology_args() = args;
```text

随后请求会通过 `VLOG(3) << "Sending topology discovery request: " << req`(第 1046 行)记录日志,并通过 transport 在 `*(transport->vtable + 40)` 处的虚拟发送函数派发。

> **推断** — 该字段反编译为嵌套的 `NetworkAddressMapping` 写入(偏移 `+40`/`+44`),而不是直接在 request 上调用 `set_slice_id`/`set_host_id`,因为 `slice_id`/`host_id` 位于 `address_mapping` 子消息上,而不在 `GetMultiSliceTopologyRequest` 自身上。`0x1ccad780` 是 `PopulateMockMultiSliceTopologyInfo`(fake-discovery 路径),不是 `NetworkAddressMapping` 构造函数。

## 响应

`xla.megascale.runtime.GetMultiSliceTopologyResponse` 携带单个字段,即序列化为 bytes 的已组装集群描述:

| 字段 # | 名称                       | 标签     | 类型    |
|--------:|----------------------------|----------|---------|
|  1      | `serialized_topology_info` | optional | `bytes` |

> **已确认** — `0xbf8179a` 处的 descriptor:`serialized_topology_info`
> = `18 01 20 01 28 0c`(type 12 = BYTES),后跟字段选项
> `42 02 08 01`。响应消息上没有 `shared_seed`、`endpoint_addresses`,也没有顶层的 `MultiSliceTopologyInfo`/`...AndLocationProto` 字段。

`bytes` 载荷是序列化后的 `MultiSliceTopologyInfo`(调用方通过 `MessageLite::MergeFromString` 解析它,可在 `DiscoverTopologyAndAddressBindings` 第 1064 行记录 "Received topology discovery response: " 的位置看到)。`MultiSliceTopologyInfo`(descriptor 位于 `0xbf816ea`)包含:

- `repeated SliceInfo slice_info`(字段 1),
- `repeated NetworkAddressMapping address_mappings`(字段 2),
- `int64 incarnation_id`(字段 3)。

## 服务端:`OnTopologyRequestReceived`

gRPC server stub `WithCallbackMethod_GetMultiSliceTopology`(typeinfo *name* `_ZTS...` 位于 `0xb46ad48`;typeinfo 对象本身位于 `0x21c46e50`)将每个入站请求派发到 `CommunicationBackend::OnTopologyRequestReceived(req, reply_cb)`(反编译地址 `0x1ccac380`,源码 `communication_backend.cc`)。

handler 很短,只是通过 vtable 转发给 `TopologyCoordinator`:

```cpp
void OnTopologyRequestReceived(
    GetMultiSliceTopologyRequest const& req,
    AnyInvocable<void(StatusOr<GetMultiSliceTopologyResponse> const&)> cb) {
  VLOG(3) << "Received topology request: " << req;   // line 937

  TracedMutexLock lock(&this->mu_);    // backend.+0xe0 (224)

  TopologyCoordinator* tc = this->topology_coordinator_;  // +0x1a0 (416)
  if (tc == nullptr) {
    // status #1: INTERNAL "TopologyCoordinator not initialized." (line 1514)
    Status err = InternalError("TopologyCoordinator not initialized.");
    lock.Release();
    LOG(INFO) << "Received toplogy request message with no topology "
                 "coordinator. This occurs if CreateCommunicator has "
                 "not been called.";                  // line 941
    // status #2 actually delivered to the caller:
    cb(MakeError<UNAVAILABLE/*14*/>(
        "Topology Coordinator is not ready. Try later.", line 944));
    return;
  }

  // Forward to the coordinator via its virtual at vtable[+0x10],
  // moving the callback in (the request's registration and callback
  // bookkeeping happen inside the coordinator).
  (*tc->vtable[+0x10])(tc, req, std::move(cb));
}

已确认 — null 路径构造了 两个 status: MakeErrorImpl<13>(INTERNAL)"TopologyCoordinator not initialized." 位于第 0x5ea=1514 行(调用 0x1ccac441),而实际交给 callback 的 status 是 MakeErrorImpl<14> "Topology Coordinator is not ready. Try later.",位于第 0x3b0=944 行 (调用 0x1ccac49e,通过 0x1ccac4ae 处的 *(cb+0x18) 调用)。二者之间的 INFO 日志在第 0x3ad=941 行。所有字符串都位于 communication_backend.cc rodata(0x878fb6f)。

0x1ccac380..0x1ccac553 反汇编显示:

  • 0x1ccac3a9 加载 lea 0xe0(%r15) — 位于 backend.+0xe0TracedMutex — 且 0x1ccac3b4 构造 TracedMutexLock
  • 0x1ccac3b9backend.+0x1a0 加载 topology_coordinator_
  • 0x1ccac3c0/0x1ccac3c3test/je 1ccac426)在 topology_coordinator_ 为 null 时分支到 "not initialized" 路径。
  • 0x1ccac3ddcall *0x10(%rbx))移出入站 AnyInvocable callback;0x1ccac3e5 将其 manager 暂存到栈槽 -0x70
  • 0x1ccac409call *0x10(%rax))转发到 coordinator 在 (*tc->vtable)[+0x10] 处的虚函数。

0x1ccac5c0 处的 OnBarrierRequestReceived 遵循相同模式,但有两个差异:

  1. 查找经过位于 backend.+0x1b0flat_hash_map<string, unique_ptr&lt;BarrierCoordinator&gt;>(432,在 0x1ccac5fc 处的 add $0x1b0,%r15 后、0x1ccac60e 处的 operator[] 前设置),以 req.barrier_id 为 key(请求在 req+0x18 处的 barrier_id 字符串)。缺失条目会触发 operator[],它会即时构造一个新的 BarrierCoordinator( barrier_id, req.num_participants)(ctor 位于 0x1ccb3fa0);计数是请求自身的 num_participants 字段(req+0x28),不是 backend 的 num_workers_ 成员。
  2. reply 类型是 BarrierResponse。请求的 (slice_id, host_id, barrier_id)req+0x20 / req+0x24 / req+0x18 读取(用于第 954 行的 VLOG(3): "Received barrier request from (SliceId, HostId, barrier_id) ")。

请求携带并由 coordinator 验证的内容

coordinator 不会盲目合并传入请求。在 TopologyCoordinator::ProcessRequest0x1cf524c0topology_coordinator.cc)内,req.address_mapping.slice_id 为 coordinator 对象上的两个 map 提供 key:

  • 位于 coordinator+2320xe8)的 flat_hash_map<int, SliceState>,以 slice_id 为 key — 每个 slice 的缓存拓扑(通过 FlatHashMapPolicy<int, TopologyCoordinator::SliceState> 调用处的 PrepareInsertSmallNonSoo((char*)this + 232, ...) 插入)。
  • 位于 coordinator+2000xc8)的 flat_hash_map<tuple<int,int>, NetworkAddressMapping>,以 (slice_id, host_id) 为 key — 每个 host 的 address mapping。

验证很严格:不匹配会返回错误 Status,而不是静默丢弃。伪代码重建如下:

cpp
int slice = req.address_mapping.slice_id;   // sub-message +40
int host  = req.address_mapping.host_id;    // sub-message +44

// SliceId bounds check (line 183):
if (slice < 0 || slice >= num_slices_)
  return MakeError<INVALID_ARGUMENT/*3*/>(
      "SliceId out of bounds. Expected num slices: $0. Request: $1");

// If this slice already registered, compare topology_args:
if (auto* slot = slice_state_.find(slice)) {
  MessageDifferencer diff; diff.set_message_field_comparison(EQUIVALENT);
  if (!diff.Compare(args_from(req.tpu_topology_args), slot->args))
    return MakeError<INVALID_ARGUMENT/*3*/>(            // line 202
        "Received topology that differs from previously registered "
        "topology at same sliceID. SliceID: $0 Previous HostId: $1 "
        "New HostId: $2 Addresses: $3 Diff: $4");

  // ... and compare the per-host NetworkAddressMapping:
  if (!diff.Compare(req.address_mapping, addr_map_[{slice,host}]))
    return MakeError<INVALID_ARGUMENT/*3*/>(            // line 216
        "Received host address mapping that differs from previous "
        "mapping SliceID: $0 HostId: $1 Prev Address: $2 "
        "New Addresses: $3");

  // ... and compare incarnation_id:
  if (req.incarnation_id != slot->incarnation[host])
    return MakeError<INVALID_ARGUMENT/*3*/>(            // line 226
        "Received incarnation ID that is different from previous "
        "incarnation ID. SliceID: $0 HostId: $1 "
        "Prev IncarnationId: $2 New IncarnationId: $3");
}
// HostId bounds check (lines 235 / 250) also returns INVALID_ARGUMENT.
```text

> **已确认** — 每个不匹配路径都以 `MakeErrorImpl<3>`(INVALID_ARGUMENT)结束,而不是记录日志后丢弃。`topology_coordinator.cc` 中的源码行:SliceId-OOB 183,topology-diff 202,address-mapping-diff 216,incarnation-diff 226,HostId-OOB 235/250。`incarnation_id` 不匹配 → `MakeErrorImpl<3>` 与文档化的 INVALID_ARGUMENT 规范一致。

`substitute_internal::SubstituteAndAppend` 格式字符串位于 rodata `0x9b27486`(topology drift)、`0x9c14204`(address mapping drift)和 `0x9c14456`(incarnation drift)。

## 请求不携带的内容

- **没有 magic / sanity 字段。** protobuf schema 验证被信任 — 没有等价于 libnccom 的 `0x61796c69` "ilya" sanity 字段。畸形的入站消息在到达 handler 前就会被 proto2 解析拒绝。
- **应用层没有认证 token。** 认证由通过 `MEGASCALE_AUTHENTICATION` 选择的 gRPC `ServerCredentials` 完成。一旦 peer 被 gRPC 认证,它请求中的每个字段都会被信任。
- **没有 retry counter。** Megascale 的 bootstrap 是带 deadline 的一次性 RPC。重试策略由调用方负责(XLA Megascale runtime 内部不会重试 GetMultiSliceTopology)。
- **没有 request-level lease。** 注册会保留在 coordinator 中直到响应触发;不存在 worker “续租”其注册的概念。HeartBeat 在 bootstrap 之后运行于单独的 RPC family 上,只服务于 liveness,而不是重新注册。

## 交叉引用

- [Bootstrap 概览](overview.md) — worker 注册位于 rendezvous 序列中的位置,以及其背后的 gRPC server 机制。
- [拓扑交换](topology-exchange.md) — 消费本页所记录 `GetMultiSliceTopologyRequest` 的 `TopologyCoordinator::ProcessRequest` 路径。
- [Coordinator 选举](coordinator-election.md) — 每个 worker 要注册到的单一 coordinator 进程是如何被选出的。