Overview
TrainingConfig feeds the client executor on every round. It has a typed surface for the common knobs plus an extra bag for anything unusual.
Fixed Fields
from resontech import TrainingConfig
TrainingConfig(
num_classes=10, # 0 → let the adapter infer from the manifest
local_epochs=2, # epochs per round per worker
batch_size=32,
learning_rate=1e-3,
img_size=224, # square input resolution
manifest_file="manifest.ndjson",
)Every fixed field lands in executors[0].executor.args inside the generated config_fed_client.json, exactly as the web wizard produces them.
The extra Bag
Anything not covered by the fixed fields goes into extra. It's a plain dict[str, Any] and is merged into the executor args verbatim. Keys in extra win over fixed fields if they collide — useful for one-off overrides.
TrainingConfig(
num_classes=10,
local_epochs=3,
extra={
"weight_decay": 5e-4,
"mixup_alpha": 0.2,
"optimizer": "adamw",
"grad_clip": 1.0,
},
)In the generated config this becomes:
{
"executors": [{
"tasks": ["train", "validate", "submit_model"],
"executor": {
"path": "custom_client_executor.GenericClientExecutor",
"args": {
"data_root": "/var/tmp/nvflare/data",
"adapter_module": "model_def",
"train_fn": "fl_train_model",
"manifest_file": "manifest.ndjson",
"local_epochs": 3,
"batch_size": 32,
"learning_rate": 0.001,
"img_size": 224,
"num_classes": 10,
"weight_decay": 0.0005,
"mixup_alpha": 0.2,
"optimizer": "adamw",
"grad_clip": 1.0
}
}
}]
}How Your Code Reads Hyperparameters
From model_def.py (the default path)
The generated adapter passes the executor args to your code; you usually don't have to do anything. A change to TrainingConfig.learning_rate shows up in the generated JSON right away.
From a custom executor
If you pass executor=MyExecutor to rt_submit, your class __init__ receives every executor arg as a kwarg. Subclass the FL runtime's Executor base class and declare what you need:
from nvflare.apis.executor import Executor
class MyExecutor(Executor):
def __init__(
self,
local_epochs: int,
batch_size: int,
learning_rate: float,
weight_decay: float = 0.0, # picks up from extra
mixup_alpha: float = 0.0,
**kwargs, # everything else passes through
):
super().__init__()
...See Custom classes for the full workflow.
Federation Knobs
FederationConfig drives the server-side orchestration. Fields map 1:1 to the default Scatter-and-Gather workflow + persistor setup.
from resontech import FederationConfig
FederationConfig(
num_rounds=10,
min_clients=2,
wait_time_after_min_received=15,
heart_beat_timeout=900,
job_name="my-fl-job",
)Federation knobs go into config_fed_server.json and are not customisable at runtime by workers — set them correctly at submit time.
Message quantization
Add quantization="<value>" to FederationConfig to compress per-round model traffic (server → client global weights and client → server trained weights). The SDK installs symmetric ModelQuantizer / ModelDequantizer filters on the train task — other tasks pass through unfiltered.
FederationConfig(
num_rounds=10,
min_clients=3,
job_name="my-job",
quantization="float16", # one of the values below, or None to disable
)Quick pick for your setup:
- Safe default, no extra deps:
"float16"— ~50% bandwidth cut, works on CPU workers. - Bandwidth-constrained, GPU workers:
"blockwise8"for ~75% bandwidth reduction with minimal convergence impact. - Multi-billion-param / LLM federated runs (GPU):
"normfloat4". - CPU-only worker pool, want smarter compression than fp16:
"adaquant". - Debugging convergence:
Nonefirst — quantization is opt-in for a reason.
The blockwise8, float4, and normfloat4 modes require bitsandbytes in your requirements.txt and a CUDA GPU on every worker (the NVFlare filter forcibly .cuda()s tensors). These modes also need a GPU on the FL server, which the default central server doesn't have. Contact Support before you use them. bf16 is not a valid value here despite showing up in NVIDIA marketing material.
Inspecting the Rendered Configs Before Submit
Always a good idea when tweaking hyperparameters:
from resontech.rt.builder import RTJobBuilder
builder = RTJobBuilder(
training_config=training,
federation_config=federation,
)
configs = builder.render_configs()
print(configs["client"])
print(configs["server"])Nothing touches disk — these are plain strings.