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 0b,tpu_topology_args=18 02 20 01 28 0b,incarnation_id=18 03 20 01 28 03(type 3 = INT64)。
| 字段 # | 名称 | 标签 | 类型 | 含义 |
|---|---|---|---|---|
| 1 | address_mapping | optional | xla.megascale.runtime.NetworkAddressMapping | 此进程的 (slice_id, host_id) 加上 MegaScaleTransport 绑定点。 |
| 2 | tpu_topology_args | optional | tpu.TpuTopologyArgsProto | 每个 slice 的拓扑 proto(芯片布局、ICI 维度)。同一 slice 的所有 host 必须一致。 |
| 3 | incarnation_id | optional | int64 | 每个进程的 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]) — 线路上没有 port 或 transport_type 字段;address 是完整的端点字符串。
请求在 CommunicationBackend::DiscoverTopologyAndAddressBindings( int local_slice_id, TpuTopologyArgsProto args, int local_host_id, int num_slices) 内构造(反编译地址 0x1ccacb80)。请求逐字段构建:
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.ccrodata(0x878fb6f)。
0x1ccac380..0x1ccac553 反汇编显示:
0x1ccac3a9加载lea 0xe0(%r15)— 位于backend.+0xe0的TracedMutex— 且0x1ccac3b4构造TracedMutexLock。0x1ccac3b9从backend.+0x1a0加载topology_coordinator_。0x1ccac3c0/0x1ccac3c3(test/je 1ccac426)在topology_coordinator_为 null 时分支到 "not initialized" 路径。0x1ccac3dd(call *0x10(%rbx))移出入站AnyInvocablecallback;0x1ccac3e5将其 manager 暂存到栈槽-0x70。0x1ccac409(call *0x10(%rax))转发到 coordinator 在(*tc->vtable)[+0x10]处的虚函数。
0x1ccac5c0 处的 OnBarrierRequestReceived 遵循相同模式,但有两个差异:
- 查找经过位于
backend.+0x1b0的flat_hash_map<string, unique_ptr<BarrierCoordinator>>(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_成员。 - 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::ProcessRequest(0x1cf524c0,topology_coordinator.cc)内,req.address_mapping.slice_id 为 coordinator 对象上的两个 map 提供 key:
- 位于 coordinator+232(
0xe8)的flat_hash_map<int, SliceState>,以slice_id为 key — 每个 slice 的缓存拓扑(通过FlatHashMapPolicy<int, TopologyCoordinator::SliceState>调用处的PrepareInsertSmallNonSoo((char*)this + 232, ...)插入)。 - 位于 coordinator+200(
0xc8)的flat_hash_map<tuple<int,int>, NetworkAddressMapping>,以(slice_id, host_id)为 key — 每个 host 的 address mapping。
验证很严格:不匹配会返回错误 Status,而不是静默丢弃。伪代码重建如下:
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 进程是如何被选出的。