Overview
Traces and trace preparation
Each rank’s trace is a list of operations, called nodes, with the dependencies between them. A node lists the nodes it depends on (ctrlDeps and dataDeps), and the simulator treats both lists alike. The node types the simulation replays are:
Every collective runs within a process group, a set of ranks that the trace declares, and every rank’s trace declares its process groups. A process group of N ranks orders its members as the trace lists them, and the algorithms below refer to that order: “the next rank” in a ring is the next one in the list, wrapping around.
Trace preparation does three things before the simulator starts:
- Expansion. Each collective node is replaced by sends and receives between the members of its process group, using the algorithm selected for that collective type (Collectives). The transfers that make up one collective carry its identity, so the collective records can report on the collective as a whole.
- Ordering options. Process group serialization and collective synchronization add dependencies and synchronization nodes (Ordering options).
- Annotation. Each rank’s trace is written in the form the simulator reads: each send and receive node gets its partner rank, each node lists the nodes that depend on it, and the nodes that are ready to run at the start are listed alongside the trace.
AbortOnChakraWarning set to true, a warning or an error that trace preparation logs stops the run before the simulation starts.
Placement
The simulation runs one Chakra application for each rank, namedChakraApp-<rank>, on the server the rank is placed on. The number of ranks is NumOfChakraFiles, which the platform sets to the traceset’s rank count when you attach the traceset.
- Without a mapping file, ranks are placed in order: rank 0 on the server with the lowest server ID (the
serverIda mapping file names), rank 1 on the next, and so on. - With a mapping file, each rank runs on the server the file names, and the file can also place ranks in scale-up groups (Scale-up groups). Chakra mapping files describes the file format and how to attach one.
chakra-capable-hosts and chakra-mapping records show which servers took part and where each rank ran (Records).
Dependencies
A rank starts every node that has no dependencies when the simulation starts. Each time a node finishes, every node that depends on it has that dependency removed, and a node whose last dependency is removed starts at once. Dependencies link nodes in the same rank’s trace only. Ranks are ordered against one another only through their sends and receives. A receive finishes when the transfer it matches has been received, and a transfer matches the receive with the same communication tag (comm_tag) between the same sender and receiver. A receive may start before or after its matching transfer arrives: a transfer that arrives first is held until its receive starts.
Compute
ACOMP_NODE that runs on the simulated NPU waits for its durationMicros. How that time passes when several of them run at once depends on TraceFamily:
synthesizedandcompiled(the default issynthesized): the trace’s durations are the time each node needs with the NPU to itself. Compute nodes running at the same time on one rank share the rank’s compute capacity equally. Whenever one starts or finishes, each running node’s remaining time is recomputed as its remaining work multiplied by the number of nodes now running.collected: the durations were measured on real hardware, where any sharing has already happened. Each compute node finishesdurationMicrosafter it starts, whatever else is running.
is_cpu_op set runs on the host CPU rather than the NPU. It, and every memory node, finishes its durationMicros after it starts, and never shares capacity with the NPU’s compute nodes. Sharing is per rank: compute on one rank never slows compute on another. Compute and communication do not slow each other.
Worked example: two compute nodes
TwoCOMP_NODEs, each with durationMicros 10, start together at 0 µs on one rank of a synthesized trace.
TraceFamily set to collected, both nodes finish at 10 µs.
Communication
ACOMM_SEND_NODE sends comm_size bytes to its partner rank as one RDMA WRITE, posted to the sending server’s NIC. The NIC carries the transfer over the simulated network to the partner rank’s server, so the transfer is subject to the NIC’s transport and the network’s congestion and flow control. A Scala RoCE NIC carries it with RoCEv2 over its queue pairs (Scala RoCE NIC); a Scala UET NIC carries it with Ultra Ethernet Transport (UET); a Scala MRC NIC carries it with Multipath Reliable Connection (MRC), over a new queue pair for each transfer (Scala MRC NIC). Each NIC’s own documentation describes its transport. A transfer of zero bytes is sent as well.
The send node finishes, and releases the nodes that depend on it, when the NIC reports that every byte has been sent. The transfer is acknowledged later, when the receiver’s acknowledgment reaches the sender, and at that moment the chakra-perf record writes one row for the transfer, with the time from the send node’s start (Records). On the receiving rank, the receive node finishes when the whole transfer has been received.
Transfers between two ranks in the same scale-up group use the scale-up fabric instead (Scale-up groups).
Collectives
Trace preparation replaces each collective with the transfers of the algorithm selected for its type. In the table, N is the size of the collective’s process group, S is the collective’s size in the trace (comm_size), and C is S divided by N, rounded up to a whole byte. halving and halving-doubling need a process group whose size is a power of two.
The root of a Broadcast, Gather, or Scatter is the collective’s root rank, or the group’s first rank when the trace names none. The sizes in the table show what
comm_size stands for in each collective: AllGather and Gather send S from every rank, while AllReduce ring and halving-doubling, ReduceScatter, Scatter, and AllToAll divide S among the group.
Worked example: one AllReduce, three algorithms
One AllReduce of S = 1,000 bytes over a process group of N = 8 ranks. C = 1,000 / 8 = 125 bytes.ring sends many small transfers in a long chain, tree sends a few full-size transfers, and halving-doubling sits between them.
Ordering options
Two options, both on by default, add ordering to the traces during trace preparation. Process group serialization (ProcessGroupSerialization). Where the trace records the order in which a rank issued its collectives, each collective of a process group depends on the previous collective of the same group on that rank, so the group runs one collective at a time, in that order. Where the trace records an issue order for the group’s point-to-point sends as well, those sends are ordered with its collectives. Receives are never ordered this way.
Collective synchronization (CommCollectiveSynchronization). Before each collective, every rank of its process group passes a barrier: zero-byte transfers in the tree pattern, 2(N − 1) of them, whatever BarrierAlgorithm is. The collective’s own transfers start only after the barrier, so they start together on every rank, as if the ranks had waited for one another. The barrier’s transfers are not counted in the collective records or in the completion counters; chakra-perf lists them with is_added_synchronization_flow set to true. With synchronization off, each rank starts its part of a collective as soon as its own dependencies have finished.
Scale-up groups
A mapping file can place ranks in scale-up groups, and in subgroups within a group, and give each group a bandwidth, a per-hop switch latency, and an NPU cable latency (Chakra mapping files). A transfer between two ranks of the same group uses a modeled scale-up fabric instead of the NIC and the simulated network. Transfers to any other rank use the NIC as usual. Without a mapping file there are no scale-up groups. The scale-up fabric carries a transfer as packets:- Packets. The transfer is cut into packets of at most
ScaleUpMaxPayloadSizebytes of data, and each packet also carriesScaleUpHeaderSizebytes of header. - Sending. The sending rank keeps one queue for each receiver and takes packets from those queues in turn. A packet takes its size (data plus header) at the group’s bandwidth to send, multiplied by the number of receivers the rank is sending to at that moment, so the rank’s bandwidth is shared equally among them.
- Crossing the fabric. Between two ranks of the same subgroup, a packet crosses one switch hop and two cable segments. Between subgroups of the same group, it crosses three switch hops and four cable segments.
- Receiving. The receiving rank keeps one queue for each sender, and each packet again takes its size at the group’s bandwidth multiplied by the number of senders the rank is receiving from. The receive node finishes when every byte of the transfer has arrived.
chakra-perf row is written. Scale-up traffic does not appear in the network’s traffic records, but the topology report shows the scale-up fabric (Topology Report). The Chakra records count it with the other transfers, and ScaleUpStatsEnabled adds rows for it to nd-stats (Records).
A run is reproducible for a fixed placement of ranks on cores. A different core count or placement can change results where packets from different senders reach one receiver at exactly the same time. The receiver then serves them in a different order, and its shared receive bandwidth gives different completion times. Network packets that cross cores behave the same way.
The crossing time of a packet does not depend on the placement: it is the same on one core and across cores. A scale-up group whose members run on more than one core can lower the lookahead of those cores (how far each core may run ahead of the others between synchronizations) to the smallest crossing between them. Another link, or the largest link delay in the cluster, may already set a smaller value. A smaller lookahead can make all cores synchronize more often, so the run can take longer in wall-clock time. Simulated times do not change.
A run on more than one core stops on every core when the lookahead is 0. The lookahead is 0 when a link between nodes on different cores has a delay of 0, when a device that reports its own smallest remote delay reports 0, or, in a run that sets up remote delivery between cores, when the largest link delay, which sets the remote delivery delay, is 0. Each core whose lookahead is 0 writes an error that names the link, the device, or the delay that caused it.
Scaling
WithScaling set to true, every node’s comm_size and durationMicros is multiplied by ScalingFactor (0.0 to 1.0) as the node is read, and the result is truncated to a whole byte or microsecond. A factor of 0.5 halves every transfer and every duration, and a 1 µs node becomes 0 µs. At 0.0 every transfer has zero bytes and every node takes no time. The trace’s dependencies are unchanged, so the run keeps the trace’s pattern of computation and communication at a coarser granularity. With Scaling set to false, ScalingFactor has no effect.
How a run ends
A rank ends when every node in its trace has finished and every transfer it sent has been acknowledged. For each transfer it received through the NIC, it also waits until the sender has received that transfer’s acknowledgment. A rank ends at once if its trace has no node that is ready at the start. When every rank has ended, the simulation stops oneBeaconReportInterval later.
The simulation also stops at its stop time, whether or not the ranks have finished. Every rank starts at StartTime, and the stop time is StartTime + WarmUp + RunTime + CoolDown, all global simulation parameters set with PATCH /api/v1/configurations/{config_id}/parameters. Set RunTime long enough for the traces to finish. A rank that is still running at the stop writes its completion row and its trace file with what it completed (Records).