Commit 4cdafd92 authored by Brewer, Wes's avatar Brewer, Wes
Browse files

feat(network): add circulant inter-group dragonfly for Frontier



Replace the incorrect all-to-all Frontier dragonfly (49 groups, 48
routers/group, 4 hosts/router, port budget 99) with the correct
circulant topology matching real Frontier/SST:
- 74 groups, 32 routers/group, 2 hosts/router, 30 inter-group links
- Port budget: 2+31+30=63 (fits 64-port Slingshot) ✓

New builder build_dragonfly_circulant(G, R, P, H) connects each router
r in group g to router r in groups (g+offset)%G for H symmetric offsets,
returning (graph, inter_group_adj) for use in routing.  Minimal paths
use 1 global hop when dst is directly reachable, 2 hops otherwise.

All routing functions (minimal, UGAL, Valiant) gain an optional
inter_group_adj parameter defaulting to None (backward-compatible
all-to-all behavior preserved for existing configs).

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 19dbc9c6
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -86,9 +86,10 @@ network:
  network_max_bw: 25e9  # Slingshot 200 Gbps = 25 GB/s
  routing_algorithm: ugal
  ugal_threshold: 2.0
  dragonfly_d: 48  # Routers per group
  dragonfly_a: 48  # Global links per router (49 groups total)
  dragonfly_p: 4   # Compute nodes per router
  dragonfly_groups: 74  # Num groups (explicit; replaces a+1 formula)
  dragonfly_d: 32       # Routers per group
  dragonfly_p: 2        # Hosts per router (single-rail)
  dragonfly_inter: 30   # Inter-group links per router (circulant; port budget: 2+31+30=63 ≤ 64)
  latency: 1
  # Cassini stall/flit model parameters (from counter data: 25 GB/s / 215M pkts/s ≈ 116 B)
  mean_packet_size_bytes: 116
+40 −21
Original line number Diff line number Diff line
@@ -28,7 +28,13 @@ from .base import (

from .fat_tree import build_fattree, node_id_to_host_name, subsample_hosts
from .torus3d import build_torus3d, link_loads_for_job_torus, torus_host_from_real_index
from .dragonfly import build_dragonfly, dragonfly_node_id_to_host_name, build_dragonfly_idx_map
from .dragonfly import (
    build_dragonfly,
    build_dragonfly_circulant,
    dragonfly_node_id_to_host_name,
    build_dragonfly_idx_map,
    build_dragonfly_idx_map_circulant,
)
from raps.plotting import plot_fattree_hierarchy, plot_dragonfly, plot_torus2d, plot_torus3d

from raps.utils import get_current_utilization
@@ -116,25 +122,40 @@ class NetworkModel:

        elif self.topology == "dragonfly":
            D = self.config["DRAGONFLY_D"]
            A = self.config["DRAGONFLY_A"]
            A = self.config.get("DRAGONFLY_A")
            P = self.config["DRAGONFLY_P"]
            self.net_graph = build_dragonfly(D, A, P)

            # Store dragonfly params for routing
            self.dragonfly_d = D
            self.dragonfly_a = A
            self.dragonfly_p = P

            # total nodes seen by scheduler or job trace
            total_real_nodes = getattr(self, "available_nodes", None)
            if total_real_nodes is None:
                total_real_nodes = 4626  # fallback for Lassen
            G_explicit = self.config.get("DRAGONFLY_GROUPS")
            H = self.config.get("DRAGONFLY_INTER")
            offsets = self.config.get("DRAGONFLY_OFFSETS")

            # if available_nodes is a list, take its length
            # Resolve total_real_nodes from the available_nodes parameter
            total_real_nodes = available_nodes
            if not isinstance(total_real_nodes, int):
                total_real_nodes = len(total_real_nodes)
            if not total_real_nodes:
                total_real_nodes = 4626  # fallback for Lassen

            if G_explicit is not None and H is not None:
                # Circulant dragonfly (e.g. Frontier)
                self.net_graph, self.inter_group_adj = build_dragonfly_circulant(
                    G_explicit, D, P, H, offsets
                )
                self.dragonfly_d = D
                self.dragonfly_a = None
                self.dragonfly_p = P
                self.real_to_fat_idx = build_dragonfly_idx_map_circulant(
                    G_explicit, D, P, total_real_nodes
                )
                topo_info = f"circulant G={G_explicit} R={D} P={P} H={H}"
            else:
                # Legacy all-to-all dragonfly
                self.net_graph = build_dragonfly(D, A, P)
                self.inter_group_adj = None
                self.dragonfly_d = D
                self.dragonfly_a = A
                self.dragonfly_p = P
                self.real_to_fat_idx = build_dragonfly_idx_map(D, A, P, total_real_nodes)
                topo_info = f"all-to-all D={D} A={A} P={P}"

            # Initialize global link loads for adaptive routing
            self.global_link_loads = {tuple(sorted(edge)): 0.0 for edge in self.net_graph.edges()}
@@ -144,7 +165,7 @@ class NetworkModel:
                routing_info += f", threshold={self.ugal_threshold}"
            elif self.routing_algorithm == 'valiant':
                routing_info += f", bias={self.valiant_bias}"
            print(f"[DEBUG] Dragonfly: {len(self.real_to_fat_idx)} nodes, {routing_info}")
            print(f"[DEBUG] Dragonfly ({topo_info}): {len(self.real_to_fat_idx)} nodes, {routing_info}")

        elif self.topology == "capacity":
            # Capacity-only model: no explicit graph
@@ -203,9 +224,6 @@ class NetworkModel:
                        self.global_link_loads[edge_key] += load

        elif self.topology == "dragonfly":
            D = self.config["DRAGONFLY_D"]
            A = self.config["DRAGONFLY_A"]
            P = self.config["DRAGONFLY_P"]
            # Directly use mapped host names
            host_list = [self.real_to_fat_idx[real_n] for real_n in job.scheduled_nodes]
            if debug:
@@ -215,10 +233,11 @@ class NetworkModel:

            # Build dragonfly params for adaptive routing
            dragonfly_params = {
                'd': D,
                'a': A,
                'd': self.dragonfly_d,
                'a': self.dragonfly_a,
                'ugal_threshold': self.ugal_threshold,
                'valiant_bias': self.valiant_bias,
                'inter_group_adj': getattr(self, 'inter_group_adj', None),
            }

            loads = link_loads_for_pattern(
+1 −0
Original line number Diff line number Diff line
@@ -465,6 +465,7 @@ def link_loads_for_pattern(
            link_loads=link_loads,
            ugal_threshold=dragonfly_params.get('ugal_threshold', 2.0),
            valiant_bias=dragonfly_params.get('valiant_bias', 0.0),
            inter_group_adj=dragonfly_params.get('inter_group_adj'),
        )

    # Handle adaptive routing for Fat-tree
+228 −58
Original line number Diff line number Diff line
import random
import warnings
import networkx as nx
from itertools import combinations


def build_dragonfly(d, a, p):
    """
    Build a Dragonfly network graph.
@@ -45,7 +47,7 @@ def build_dragonfly(d, a, p):

def build_dragonfly2(D: int, A: int, P: int) -> nx.Graph:
    """
    Build a simple k-ary Dragonfly with:
    Build a "simple" k-ary Dragonfly with:
       D = # of groups
       A = # of routers per group
       P = # of hosts (endpoints) per router
@@ -56,7 +58,7 @@ def build_dragonfly2(D: int, A: int, P: int) -> nx.Graph:

    Topology:
      1. All routers within a group form a full clique.
      2. Each router r in group g has exactly one global link to router r in each other group.
      2. Each router r in group g has exactly one "global link" to router r in each other group.
      3. Each router r in group g attaches to P hosts ("h_{g}_{r}_{0..P−1}").

    Examples
@@ -79,7 +81,7 @@ def build_dragonfly2(D: int, A: int, P: int) -> nx.Graph:
        for u, v in combinations(routers_in_group, 2):
            G.add_edge(u, v)

    # 3) Inter‐group one‐to‐one global links
    # 3) Inter‐group "one‐to‐one" global links
    #    (router index r in group g  →  router index r in group g2)
    for g1 in range(D):
        for g2 in range(g1 + 1, D):
@@ -100,6 +102,89 @@ def build_dragonfly2(D: int, A: int, P: int) -> nx.Graph:
    return G


def build_dragonfly_circulant(
    G: int, R: int, P: int, H: int, offsets: list | None = None
) -> tuple[nx.Graph, dict]:
    """
    Build a Dragonfly network with circulant inter-group connectivity.

    Models the real Frontier topology:
      G = num groups (74)
      R = routers per group (32)
      P = hosts per router (2)
      H = inter-group links per router (30)

    Port budget: P + (R-1) + H must fit within switch port count.
    For Frontier: 2 + 31 + 30 = 63 ≤ 64-port Slingshot ✓

    Inter-group (circulant): router r in group g connects to router r in
    group (g + offset) % G for each offset.  Default offsets are symmetric
    ±1..±(H//2) around the ring.

    Returns
    -------
    (graph, inter_group_adj)
        graph           : NetworkX graph with all router and host nodes
        inter_group_adj : dict {(g, r): frozenset of directly reachable group indices}
    """
    if offsets is None:
        half = H // 2
        pos_offsets = list(range(1, half + 1))
        neg_offsets = list(range(G - half, G))
        offsets = pos_offsets + neg_offsets
        if H % 2 == 1:
            # odd H: add one more positive offset
            offsets.append(half + 1)

    if len(offsets) != H:
        raise ValueError(f"Expected {H} offsets, got {len(offsets)}")

    port_budget = P + (R - 1) + H
    if port_budget > 64:
        warnings.warn(
            f"Port budget {port_budget} exceeds 64-port switch limit",
            RuntimeWarning,
        )

    net_g = nx.Graph()

    # Routers and hosts
    for g in range(G):
        for r in range(R):
            router = f"r_{g}_{r}"
            net_g.add_node(router, type="router", group=g, index=r)
            for p in range(P):
                host = f"h_{g}_{r}_{p}"
                net_g.add_node(host, type="host", group=g, router=r, index=p)
                net_g.add_edge(router, host)

    # Intra-group full mesh
    for g in range(G):
        routers = [f"r_{g}_{r}" for r in range(R)]
        for u, v in combinations(routers, 2):
            net_g.add_edge(u, v)

    # Inter-group circulant links:
    # Router r in group g → router r in group (g + offset) % G
    for g in range(G):
        for r in range(R):
            src = f"r_{g}_{r}"
            for offset in offsets:
                dst_group = (g + offset) % G
                dst = f"r_{dst_group}_{r}"
                if not net_g.has_edge(src, dst):
                    net_g.add_edge(src, dst)

    # Build inter_group_adj: same reachable set for all routers in a group
    inter_group_adj = {}
    for g in range(G):
        reachable = frozenset((g + offset) % G for offset in offsets)
        for r in range(R):
            inter_group_adj[(g, r)] = reachable

    return net_g, inter_group_adj


def dragonfly_node_id_to_host_name(fat_idx: int, D: int, A: int, P: int) -> str:
    """
    Convert a contiguous Dragonfly host index to its hierarchical name.
@@ -142,6 +227,26 @@ def build_dragonfly_idx_map(d: int, a: int, p: int, total_real_nodes: int) -> di
    return mapping


def build_dragonfly_idx_map_circulant(
    G: int, R: int, P: int, total_real_nodes: int
) -> dict[int, str]:
    """
    Build a mapping {real_node_index: host_name} for circulant Dragonfly.

    Uses explicit G (num groups) instead of the a+1 formula.
    Wraps around if total_real_nodes > G*R*P.
    """
    total_hosts = G * R * P
    mapping = {}
    for i in range(total_real_nodes):
        fat_idx = i % total_hosts
        group = fat_idx // (R * P)
        router = (fat_idx // P) % R
        host = fat_idx % P
        mapping[i] = f"h_{group}_{router}_{host}"
    return mapping


# =============================================================================
# Adaptive Routing Functions for Dragonfly
# =============================================================================
@@ -188,7 +293,13 @@ def get_host_router(host_name: str) -> str:
    return f"r_{group}_{router}"


def dragonfly_minimal_path(src_host: str, dst_host: str, d: int, a: int) -> list[str]:
def dragonfly_minimal_path(
    src_host: str,
    dst_host: str,
    d: int,
    a: int,
    inter_group_adj: dict | None = None,
) -> list[str]:
    """
    Compute the minimal path between two hosts in a Dragonfly network.

@@ -196,11 +307,16 @@ def dragonfly_minimal_path(src_host: str, dst_host: str, d: int, a: int) -> list
    - Intra-group: host → router → [local hop] → router → host (max 2 router hops)
    - Inter-group: host → router → [local] → global → [local] → router → host (3 router hops)

    When inter_group_adj is provided (circulant topology):
    - If dst_group is directly reachable: 1 global hop
    - Otherwise: 2 global hops via a reachable intermediate group

    Args:
        src_host: Source host name (h_g_r_p format)
        dst_host: Destination host name (h_g_r_p format)
        d: Number of routers per group
        a: Number of global links per router (num_groups = a + 1)
        a: Number of global links per router (num_groups = a + 1, used when inter_group_adj is None)
        inter_group_adj: Optional {(g, r): frozenset of reachable groups} for circulant topology

    Returns:
        List of node names forming the minimal path
@@ -223,23 +339,52 @@ def dragonfly_minimal_path(src_host: str, dst_host: str, d: int, a: int) -> list
    if src_group == dst_group:
        return [src_host, src_r, dst_r, dst_host]

    # Inter-group: need to use global link
    # In build_dragonfly(), router r in group g connects to router (r % d) in other groups
    # So from src_router in src_group, the global link lands at router (src_router % d) in dst_group

    # Inter-group
    path = [src_host, src_r]

    # Global link destination router in dst_group
    if inter_group_adj is None:
        # All-to-all (original): router r connects to router (r % d) in every other group
        global_landing_router = src_router % d

    # Take the global link
        path.append(f"r_{dst_group}_{global_landing_router}")

    # If we didn't land at the destination router, add local hop
        if global_landing_router != dst_router:
            path.append(dst_r)

        path.append(dst_host)
    else:
        # Circulant: router r connects to router r in offset groups
        reachable = inter_group_adj.get((src_group, src_router), frozenset())

        if dst_group in reachable:
            # Direct 1 global hop: lands at same router index in dst_group
            landing = src_router
            path.append(f"r_{dst_group}_{landing}")
            if landing != dst_router:
                path.append(dst_r)
            path.append(dst_host)
        else:
            # 2 global hops: src_group → mid_group → dst_group
            # Find intermediate whose reachable set (from router src_router) includes dst_group
            mid_group = None
            for g in reachable:
                mid_reachable = inter_group_adj.get((g, src_router), frozenset())
                if dst_group in mid_reachable:
                    mid_group = g
                    break

            if mid_group is None:
                # Fallback: use first reachable group
                mid_group = next(iter(reachable)) if reachable else (src_group + 1)

            # First global hop: src_router → mid_group (lands at src_router)
            mid_landing = src_router
            path.append(f"r_{mid_group}_{mid_landing}")

            # Second global hop: mid_landing → dst_group (lands at mid_landing)
            dst_landing = mid_landing
            path.append(f"r_{dst_group}_{dst_landing}")
            if dst_landing != dst_router:
                path.append(dst_r)
            path.append(dst_host)

    return path


@@ -248,7 +393,8 @@ def dragonfly_nonminimal_path(
    dst_host: str,
    intermediate_group: int,
    d: int,
    a: int
    a: int,
    inter_group_adj: dict | None = None,
) -> list[str]:
    """
    Compute a non-minimal path via an intermediate group (Valiant routing).
@@ -262,6 +408,7 @@ def dragonfly_nonminimal_path(
        intermediate_group: Group index to route through
        d: Number of routers per group
        a: Number of global links per router
        inter_group_adj: Optional {(g, r): frozenset} for circulant topology

    Returns:
        List of node names forming the non-minimal path
@@ -276,25 +423,27 @@ def dragonfly_nonminimal_path(

    # Phase 1: Source group → Intermediate group
    if src_group != intermediate_group:
        # Global link from src_router lands at router (src_router % d) in intermediate
        if inter_group_adj is None:
            inter_landing = src_router % d
        else:
            # Circulant: same router index
            inter_landing = src_router
        path.append(f"r_{intermediate_group}_{inter_landing}")
        current_router = inter_landing
    else:
        # Already in intermediate group (shouldn't happen in valid Valiant)
        current_router = src_router

    # Phase 2: Intermediate group → Destination group
    if intermediate_group != dst_group:
        # Global link from current_router lands at router (current_router % d) in dst_group
        if inter_group_adj is None:
            dst_landing = current_router % d
        else:
            dst_landing = current_router
        path.append(f"r_{dst_group}_{dst_landing}")

        # Local hop to destination router if needed
        if dst_landing != dst_router:
            path.append(dst_r)
    else:
        # Intermediate is destination (shouldn't happen in valid Valiant)
        if current_router != dst_router:
            path.append(dst_r)

@@ -349,7 +498,8 @@ def ugal_select_path(
    link_loads: dict,
    d: int,
    a: int,
    threshold: float = 2.0
    threshold: float = 2.0,
    inter_group_adj: dict | None = None,
) -> list[str]:
    """
    UGAL (Universal Globally-Adaptive Load-balanced) path selection.
@@ -363,35 +513,41 @@ def ugal_select_path(
        dst_host: Destination host name
        link_loads: Current link load state {(u, v): bytes}
        d: Number of routers per group
        a: Number of global links per router
        a: Number of global links per router (used when inter_group_adj is None)
        threshold: Decision threshold (default 2.0, standard UGAL)
        inter_group_adj: Optional {(g, r): frozenset} for circulant topology

    Returns:
        Selected path as list of node names
    """
    num_groups = a + 1
    src_group, _, _ = parse_dragonfly_host(src_host)
    src_group, src_router, _ = parse_dragonfly_host(src_host)
    dst_group, _, _ = parse_dragonfly_host(dst_host)

    # Compute minimal path and its latency
    minimal_path = dragonfly_minimal_path(src_host, dst_host, d, a)
    minimal_path = dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)
    minimal_latency = estimate_path_latency(minimal_path, link_loads)

    # For intra-group traffic, always use minimal (no benefit from non-minimal)
    if src_group == dst_group:
        return minimal_path

    # Evaluate non-minimal paths through each intermediate group
    # Determine candidate intermediate groups
    if inter_group_adj is None:
        num_groups = a + 1
        candidates = range(num_groups)
    else:
        candidates = inter_group_adj.get((src_group, src_router), frozenset())

    # Evaluate non-minimal paths through each candidate intermediate group
    best_nonminimal_path = None
    best_nonminimal_latency = float('inf')

    for inter_group in range(num_groups):
        # Skip source and destination groups (not valid intermediate)
    for inter_group in candidates:
        if inter_group == src_group or inter_group == dst_group:
            continue

        nonminimal_path = dragonfly_nonminimal_path(
            src_host, dst_host, inter_group, d, a
            src_host, dst_host, inter_group, d, a, inter_group_adj
        )
        latency = estimate_path_latency(nonminimal_path, link_loads)

@@ -414,7 +570,8 @@ def valiant_select_path(
    dst_host: str,
    d: int,
    a: int,
    bias: float = 0.0
    bias: float = 0.0,
    inter_group_adj: dict | None = None,
) -> list[str]:
    """
    Valiant routing with configurable bias toward non-minimal paths.
@@ -423,37 +580,45 @@ def valiant_select_path(
        src_host: Source host name
        dst_host: Destination host name
        d: Number of routers per group
        a: Number of global links per router
        a: Number of global links per router (used when inter_group_adj is None)
        bias: Fraction of traffic to route non-minimally (0.0-1.0)
              0.0 = always minimal, 1.0 = always non-minimal
              0.05 = 5% non-minimal, 95% minimal
        inter_group_adj: Optional {(g, r): frozenset} for circulant topology

    Returns:
        Selected path as list of node names
    """
    num_groups = a + 1
    src_group, _, _ = parse_dragonfly_host(src_host)
    src_group, src_router, _ = parse_dragonfly_host(src_host)
    dst_group, _, _ = parse_dragonfly_host(dst_host)

    # Intra-group: always minimal (non-minimal makes no sense)
    if src_group == dst_group:
        return dragonfly_minimal_path(src_host, dst_host, d, a)
        return dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)

    # Probabilistic selection based on bias
    if random.random() >= bias:
        # Use minimal path (1 - bias probability)
        return dragonfly_minimal_path(src_host, dst_host, d, a)
        return dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)
    else:
        # Use non-minimal path via random intermediate group
        if inter_group_adj is None:
            num_groups = a + 1
            valid_intermediates = [
                g for g in range(num_groups)
                if g != src_group and g != dst_group
            ]
        else:
            reachable = inter_group_adj.get((src_group, src_router), frozenset())
            valid_intermediates = [
                g for g in reachable
                if g != src_group and g != dst_group
            ]

        if not valid_intermediates:
            return dragonfly_minimal_path(src_host, dst_host, d, a)
            return dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)

        inter_group = random.choice(valid_intermediates)
        return dragonfly_nonminimal_path(src_host, dst_host, inter_group, d, a)
        return dragonfly_nonminimal_path(src_host, dst_host, inter_group, d, a, inter_group_adj)


def dragonfly_route(
@@ -464,7 +629,8 @@ def dragonfly_route(
    a: int,
    link_loads: dict | None = None,
    ugal_threshold: float = 2.0,
    valiant_bias: float = 0.0
    valiant_bias: float = 0.0,
    inter_group_adj: dict | None = None,
) -> list[str]:
    """
    Main routing dispatcher for Dragonfly networks.
@@ -474,30 +640,31 @@ def dragonfly_route(
        dst_host: Destination host name
        algorithm: Routing algorithm ('minimal', 'ugal', 'valiant')
        d: Number of routers per group
        a: Number of global links per router
        a: Number of global links per router (used when inter_group_adj is None)
        link_loads: Current link loads (required for UGAL)
        ugal_threshold: UGAL decision threshold
        valiant_bias: Valiant non-minimal bias (0.0-1.0)
        inter_group_adj: Optional {(g, r): frozenset} for circulant topology

    Returns:
        Path as list of node names
    """
    if algorithm == 'minimal':
        return dragonfly_minimal_path(src_host, dst_host, d, a)
        return dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)

    elif algorithm == 'ugal':
        if link_loads is None:
            link_loads = {}
        return ugal_select_path(
            src_host, dst_host, link_loads, d, a, ugal_threshold
            src_host, dst_host, link_loads, d, a, ugal_threshold, inter_group_adj
        )

    elif algorithm == 'valiant':
        return valiant_select_path(src_host, dst_host, d, a, valiant_bias)
        return valiant_select_path(src_host, dst_host, d, a, valiant_bias, inter_group_adj)

    else:
        # Default to minimal
        return dragonfly_minimal_path(src_host, dst_host, d, a)
        return dragonfly_minimal_path(src_host, dst_host, d, a, inter_group_adj)


def link_loads_for_job_dragonfly_adaptive(
@@ -509,7 +676,8 @@ def link_loads_for_job_dragonfly_adaptive(
    a: int,
    link_loads: dict | None = None,
    ugal_threshold: float = 2.0,
    valiant_bias: float = 0.0
    valiant_bias: float = 0.0,
    inter_group_adj: dict | None = None,
) -> dict:
    """
    Compute link loads for a job using adaptive routing on Dragonfly.
@@ -520,10 +688,11 @@ def link_loads_for_job_dragonfly_adaptive(
        tx_volume_bytes: Traffic volume per host
        algorithm: Routing algorithm ('minimal', 'ugal', 'valiant')
        d: Number of routers per group
        a: Number of global links per router
        a: Number of global links per router (used when inter_group_adj is None)
        link_loads: Global link loads (for UGAL decisions)
        ugal_threshold: UGAL decision threshold
        valiant_bias: Valiant non-minimal bias
        inter_group_adj: Optional {(g, r): frozenset} for circulant topology

    Returns:
        Dict {(u, v): bytes} of link loads from this job
@@ -545,7 +714,8 @@ def link_loads_for_job_dragonfly_adaptive(
                src, dst, algorithm, d, a,
                link_loads=link_loads,
                ugal_threshold=ugal_threshold,
                valiant_bias=valiant_bias
                valiant_bias=valiant_bias,
                inter_group_adj=inter_group_adj,
            )

            for u, v in zip(path, path[1:]):
+2 −0
Original line number Diff line number Diff line
@@ -152,9 +152,11 @@ class SystemNetworkConfig(RAPSBaseModel):

    fattree_k: int | None = None

    dragonfly_groups: int | None = None
    dragonfly_d: int | None = None
    dragonfly_a: int | None = None
    dragonfly_p: int | None = None
    dragonfly_inter: int | None = None

    torus_x: int | None = None
    torus_y: int | None = None
Loading