Skip to content

Commit 5fd8d2c

Browse files
authored
✨ improve FL metrics collection and add mac environment setup (open-cluster-management-io#72)
* hello Signed-off-by: Meng Yan <myan@redhat.com> * test Signed-off-by: Meng Yan <myan@redhat.com> * print the value Signed-off-by: Meng Yan <myan@redhat.com> * test Signed-off-by: Meng Yan <myan@redhat.com> * update metrics Signed-off-by: Meng Yan <myan@redhat.com> --------- Signed-off-by: Meng Yan <myan@redhat.com>
1 parent 68f18d2 commit 5fd8d2c

3 files changed

Lines changed: 110 additions & 18 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash
2+
3+
cd $(dirname ${BASH_SOURCE})
4+
5+
set -e
6+
7+
hub=${HUB:-hub}
8+
c1=${CLUSTER1:-cluster1}
9+
c2=${CLUSTER2:-cluster2}
10+
11+
hubctx="kind-${hub}"
12+
c1ctx="kind-${c1}"
13+
c2ctx="kind-${c2}"
14+
15+
cat <<EOF | kind create cluster --name "${hub}" --config=-
16+
kind: Cluster
17+
apiVersion: kind.x-k8s.io/v1alpha4
18+
nodes:
19+
- role: control-plane
20+
extraPortMappings:
21+
- containerPort: 30090
22+
hostPort: 30090
23+
protocol: TCP
24+
EOF
25+
26+
kind create cluster --name "${c1}"
27+
kind create cluster --name "${c2}"
28+
29+
echo "Initialize the ocm hub cluster"
30+
clusteradm init --wait --context ${hubctx}
31+
joincmd=$(clusteradm get token --context ${hubctx} | grep clusteradm)
32+
33+
echo "Join clusters to hub"
34+
eval "${joincmd//<cluster_name>/local-cluster} --force-internal-endpoint-lookup --wait --context ${hubctx}"
35+
eval "${joincmd//<cluster_name>/${c1}} --force-internal-endpoint-lookup --wait --context ${c1ctx}"
36+
eval "${joincmd//<cluster_name>/${c2}} --force-internal-endpoint-lookup --wait --context ${c2ctx}"
37+
38+
echo "Accept join of clusters"
39+
clusteradm accept --context ${hubctx} --clusters ${c1},${c2},local-cluster --wait
40+
41+
# label local-cluster
42+
kubectl label managedclusters local-cluster local-cluster=true --context ${hubctx}
43+
kubectl get managedclusters --all-namespaces --context ${hubctx}

federated-learning-controller/examples/flower/app-torch/app_torch/client_app.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,49 @@ def __init__(self, net, trainloader, valloader, local_epochs):
2424

2525
def fit(self, parameters, config):
2626
set_weights(self.net, parameters)
27-
27+
28+
# the aggregated model from the previous round
29+
2830
optimizer = optim.Adadelta(self.net.parameters(), lr=1.0)
2931
scheduler = StepLR(optimizer, step_size=1, gamma=0.7)
3032

31-
epoch_loss = 0.0
33+
train_loss = 0.0
3234
for epoch in range(1, self.local_epochs + 1):
3335
# test_loss = test(model, device, test_loader)
3436
# test_losses.append(test_loss)
3537
loss = train(self.net, self.device, self.trainloader, optimizer, epoch)
36-
metrics = {
37-
"train_loss": loss,
38-
}
39-
labels = {
40-
"round": config["server_round"],
41-
"epoch": epoch,
42-
}
43-
write_metrics(metrics, labels)
44-
epoch_loss += loss
38+
train_loss += loss
4539
scheduler.step()
46-
40+
41+
# client loss and accuracy
42+
loss, accuracy = test(self.net, self.device, self.valloader)
43+
metrics = {
44+
"train_loss": loss,
45+
"train_accuracy": accuracy,
46+
}
47+
labels = {
48+
"round": config["server_round"],
49+
}
50+
write_metrics(metrics, labels)
51+
4752
return (
4853
get_weights(self.net),
4954
len(self.trainloader.dataset),
50-
{"train_loss": epoch_loss / self.local_epochs},
55+
{"train_loss": train_loss / self.local_epochs},
5156
)
5257

5358
def evaluate(self, parameters, config):
59+
# the aggregated model from the current round
5460
set_weights(self.net, parameters)
55-
5661
loss, accuracy = test(self.net, self.device, self.valloader)
57-
print(f"Evaluation Loss: {loss}, Accuracy: {accuracy}")
5862
metrics = {
63+
"accuracy": accuracy,
5964
"loss": loss,
60-
"accuracy": accuracy,
6165
}
6266
labels = {
6367
"round": config["server_round"],
6468
}
65-
write_metrics(metrics, labels)
69+
write_metrics(metrics, labels)
6670
return loss, len(self.valloader.dataset), {"accuracy": accuracy, "loss": loss}
6771

6872
def client_fn(context: Context):

federated-learning-controller/internal/sidecar/exporter/exporter.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,59 @@ func (r *Reporter) UpdateMetrics(newMetrics map[string]float64, newLabels map[st
110110
r.mu.Lock()
111111
defer r.mu.Unlock()
112112

113+
// Check if metrics values have actually changed (excluding timestamp)
114+
metricsChanged := false
115+
116+
// Compare metrics count (excluding timestamp which we'll add/update later)
117+
currentMetricsCount := len(r.metrics)
118+
if _, hasTimestamp := r.metrics["timestamp"]; hasTimestamp {
119+
currentMetricsCount--
120+
}
121+
122+
if len(newMetrics) != currentMetricsCount {
123+
metricsChanged = true
124+
} else {
125+
for name, newValue := range newMetrics {
126+
// Skip timestamp comparison since we always update it
127+
if name == "timestamp" {
128+
continue
129+
}
130+
if oldValue, exists := r.metrics[name]; !exists || oldValue != newValue {
131+
metricsChanged = true
132+
break
133+
}
134+
}
135+
}
136+
137+
// Check if labels have changed
138+
labelsChanged := false
139+
if len(newLabels) != len(r.labels) {
140+
labelsChanged = true
141+
} else {
142+
for name, newValue := range newLabels {
143+
if oldValue, exists := r.labels[name]; !exists || oldValue != newValue {
144+
labelsChanged = true
145+
break
146+
}
147+
}
148+
}
149+
150+
// Only update if values have actually changed
151+
if !metricsChanged && !labelsChanged {
152+
log.Println("No metric or label changes detected, skipping update")
153+
return
154+
}
155+
113156
// Update the metrics and labels
114157
r.metrics = newMetrics
115158
r.labels = newLabels
116159

117160
// Always overwrite "timestamp" metric with the current Unix time.
118-
// This ensures we have a fresh timestamp regardless of input.
161+
// This ensures we have a fresh timestamp only when other metrics change.
119162
r.metrics["timestamp"] = float64(time.Now().Unix())
120163

164+
log.Printf("Metrics updated: %d metrics, %d labels", len(r.metrics), len(r.labels))
165+
121166
// Step 1: Detect if the set of metrics has changed
122167
// (different number of metrics or different names)
123168
needsUpdate := false

0 commit comments

Comments
 (0)