> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/topograph/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/topograph/_mcp/server.

# Topograph NFD Engine

The `nfd` engine publishes Topograph topology through
[Node Feature Discovery](https://kubernetes-sigs.github.io/node-feature-discovery/)
custom resources instead of writing Kubernetes node labels directly.

It creates:

- one `NodeFeature` per topology node, carrying Topograph topology as
  `spec.features.attributes.topograph.network.elements`
- one `NodeFeatureGroup` per distinct fabric tier, accelerator domain, or
  accelerator sub-domain value

NFD master evaluates those features and writes matching nodes to
`NodeFeatureGroup.status.nodes`.

Fabric topology is variable-depth. The engine publishes `fabric-tier-N` for
every discovered tier, where tier 0 is closest to the node and higher tiers
progress outward; there is no fixed number of fabric attributes or groups.
Accelerator domain and optional sub-domain attributes are published separately when
the provider supplies them.

## When to Use

Use `engine: nfd` only when a downstream component already consumes NFD
`NodeFeatureGroup` objects. For native Kubernetes scheduling with
`podAffinity.topologyKey`, use the [`k8s`](/topograph/engines/kubernetes) engine instead.

## Install NFD

The `NodeFeatureGroupAPI` feature gate is **disabled by default** in NFD. It has
been Alpha since NFD v0.16 and must be enabled explicitly before using this
engine. See the upstream
[feature-gate reference](https://kubernetes-sigs.github.io/node-feature-discovery/master/reference/feature-gates.html#nodefeaturegroupapi)
for its current status.

The following example limits the NFD worker to nodes labeled
`nfd-enabled=true`. Label each intended worker node first:

```bash
kubectl label node <node-name> nfd-enabled=true
```

Then install NFD with the `NodeFeatureGroupAPI` feature gate enabled:

```bash
helm repo add nfd https://kubernetes-sigs.github.io/node-feature-discovery/charts

helm repo update

helm install nfd nfd/node-feature-discovery \
  --namespace node-feature-discovery \
  --create-namespace \
  --set-string worker.nodeSelector.nfd-enabled=true \
  --set featureGates.NodeFeatureGroupAPI=true
```

Omit `worker.nodeSelector.nfd-enabled` if the NFD worker should run on all
eligible nodes.

## Configuration

```yaml
provider:
  name: infiniband-k8s
engine:
  name: nfd
  params:
    nodeSelector:
      nvidia.com/gpu.present: "true"
    cleanup: true
    acceleratorDomainSourceLabel: example.com/accelerator-domain
kubeClient:
  qps: 50
  burst: 100
nfdNamespace: node-feature-discovery
```

Parameters:

| Parameter | Required | Default | Description |
|---|---:|---|---|
| `nodeSelector` | No | all nodes | Limits the Kubernetes nodes used as provider input. Same meaning as the `k8s` engine selector. |
| `cleanup` | No | `true` | Deletes stale Topograph-managed `NodeFeature` and `NodeFeatureGroup` objects that are no longer present in the generated topology. If generation produces no objects, the engine returns an error and preserves the existing topology. |
| `acceleratorDomainSourceLabel` | No | none | Existing Kubernetes Node label used as the authoritative accelerator-domain source. Values replace the provider domain and suppress the provider sub-domain for matching nodes. |

`nfdNamespace` is a deployment-level Helm value, not an engine request
parameter. It must be the namespace where NFD master runs because NFD updates
`NodeFeatureGroup.status` there. The Helm value defaults to
`node-feature-discovery`.

When `rbac.create` is enabled, the chart creates a `Role` and `RoleBinding` in
that namespace and configures the Topograph deployment with the same value
through `NFD_NAMESPACE`. Outside Helm, set `NFD_NAMESPACE` on the Topograph
process. The NFD engine returns an error if the variable is unset or blank.

Topology requests cannot select an NFD namespace; the deployment environment is
authoritative.

### Kubernetes API rate limiting

The NFD engine uses a typed Kubernetes client to list Nodes and a dynamic client
to reconcile `NodeFeature` and `NodeFeatureGroup` objects. Helm configures their
shared token-bucket limiter through `kubeClient.qps` and `kubeClient.burst`,
which become `KUBE_QPS` and `KUBE_BURST` in the Topograph deployment. The
configured QPS is the aggregate request rate rather than a separate allowance
for each client. If only QPS or burst is configured, the other value uses the
client-go default. Without either setting, each client retains its client-go
default limiter. Outside Helm, set the environment variables directly.

## Generated Objects

For a node on a three-tier fabric, the engine writes a `NodeFeature` like:

```yaml
apiVersion: nfd.k8s-sigs.io/v1alpha1
kind: NodeFeature
metadata:
  name: topograph-node-node-a-...
  namespace: node-feature-discovery
  labels:
    nfd.node.kubernetes.io/node-name: node-a
    app.kubernetes.io/managed-by: topograph
    topograph.run/engine: nfd
spec:
  features:
    attributes:
      system.name:
        elements:
          nodename: node-a
      topograph.network:
        elements:
          accelerator-domain: nvl3
          accelerator-sub-domain: nvl3.rack01
          fabric-tier-0: leaf-12
          fabric-tier-1: spine-2
          fabric-tier-2: core-1
```

For each distinct value, it writes a matching `NodeFeatureGroup`:

```yaml
apiVersion: nfd.k8s-sigs.io/v1alpha1
kind: NodeFeatureGroup
metadata:
  name: topograph-fabric-tier-0-leaf-12-...
  namespace: node-feature-discovery
  labels:
    app.kubernetes.io/managed-by: topograph
    topograph.run/engine: nfd
    topograph.run/group-type: fabric-tier-0
  annotations:
    topograph.run/label-key: fabric.topograph.run/tier-0
    topograph.run/label-value: leaf-12
spec:
  featureGroupRules:
    - name: fabric-tier-0 equals leaf-12
      matchFeatures:
        - feature: topograph.network
          matchExpressions:
            fabric-tier-0:
              op: In
              value: ["leaf-12"]
```

Topograph includes `system.name.elements.nodename` so NFD can populate group
membership even when an NFD worker does not run on the node, as with simulated
KWOK nodes. Topograph does not write `status.nodes`; NFD owns status updates.

When `acceleratorDomainSourceLabel` is configured and a Kubernetes node has a
non-empty value for that label, the engine uses the value as the authoritative
`accelerator-domain` NFD attribute instead of the provider domain. The matching
`NodeFeatureGroup` records the configured key as its source label. The
provider-supplied `accelerator-sub-domain` is suppressed for that node, while all
fabric-tier attributes are still published. Nodes without the configured label
retain their provider domain and sub-domain. When the parameter is omitted, no
existing Kubernetes label receives special treatment.

## Caveats

`NodeFeatureGroup` objects enumerate topology groups. A scheduler that wants to
place a workload within one leaf switch must still decide which leaf group to
use. This is different from native pod affinity, where the scheduler can compare
candidate nodes using a single `topologyKey`.

Large clusters may create many CRs and large `status.nodes` arrays. The design
notes in [`docs/design/nfd-engine-sdd.md`](../design/nfd-engine-sdd.md) include
the storage estimate and possible future NFD improvements such as group unions
and compressed node-name ranges.