Skip to content

refactor: unify aggregator strategy configuration and CLI params - #1290

Open
ale18V wants to merge 1 commit into
mainfrom
refactor/aggregator-params
Open

ale18V wants to merge 1 commit into
mainfrom
refactor/aggregator-params

Conversation

@ale18V

@ale18V ale18V commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Currently the local client trainer is decoupled from the aggregator, which is limiting to implementing more complex algorithms.

What

This PR unifies the configuration of aggregation strategies across discojs and the CLI.
Previously, aggregator parameters were scattered across ad-hoc fields like privacy.byzantineFaultTolerance and root maxShareValue, causing CLI argument sprawl.
This refactor introduces a typed aggregationParams schema discriminated by aggregationStrategy and a generic --aggregatorParams CLI flag.

Individual flags (--clippingRadius, --beta, --maxIterations, --maxShareValue) are removed in favor of comma-separated key-value pairs, keeping argument parsing clean and extensible.

TODO

@ale18V
ale18V force-pushed the refactor/aggregator-params branch 6 times, most recently from c7cd58c to 9726da6 Compare November 12, 2026 14:28
@ale18V
ale18V force-pushed the refactor/aggregator-params branch from 9726da6 to 9f2fe66 Compare November 12, 2026 14:37
@ale18V
ale18V added this pull request to stack #1292 November 12, 2026 15:58
@ale18V
ale18V requested a review from JulienVig November 12, 2026 15:58
@ale18V
ale18V marked this pull request as ready for review November 12, 2026 15:58

@JulienVig JulienVig left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this refactor is a good idea but I see a few blockers right now:

  • The zod schema is too loose, it allows incorrect arguments without error, and doesn't let TypeScript narrow types correctly
  • The CLI parsing is quite shaky, it looks like passing CLI argument as JSON would let us avoid reimplementing a possible incorrect CLI parser even if less convenient
  • The CLI isn't clear about its API: what are the accepted parameters, and it doesn't fail clearly when passing incorrect arguments

Let me know what you think on keeping JSON arguments for the CLI

Comment thread cli/src/args.ts
const key = trimmedPair.slice(0, eqIdx).trim();
const valStr = trimmedPair.slice(eqIdx + 1).trim();

if (valStr === "true") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (valStr === "true") {
if (valStr.toLowerCase() === "true") {

Just to avoid be more flexible

Comment thread cli/src/args.ts

if (valStr === "true") {
params[key] = true;
} else if (valStr === "false") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Comment thread cli/src/args.ts
Comment on lines +407 to +410
task.trainingInformation.aggregationStrategy =
aggregationConfig.aggregationStrategy;
task.trainingInformation.aggregationParams =
aggregationConfig.aggregationParams;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no aggregationParams is specified in the CLI I think you should fallback to the default task's values before falling back to zod's default values

Comment thread cli/src/args.ts
const aggregationConfig = aggregationConfigSchema.parse({
aggregationStrategy: unsafeArgs.aggregator,
aggregationParams: unsafeArgs.aggregatorParams,
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we mispell an argument or enter a wrong argument, parse will just drop it and fill with the default value. The CLI should raise an error if it doesn't recognize an argument

Comment thread cli/src/args.ts
Comment on lines +262 to +265
aggregatorParams: {
type: parseAggregatorParams,
description:
"Momentum coefficient to smooth the aggregation over multiple rounds",
optional: true,
},

// Secure aggregator
maxShareValue: {
type: Number,
description: "Maximum absolute value over all the weights",
'Parameters for the chosen aggregator as comma-separated key=value pairs (e.g. "clippingRadius=1.0,beta=0.8")',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this refactor users now have to look into training_information.ts to find what are possible arguments and their types I think a CLI should be very clear on what parameters it accepts or not

Comment on lines +90 to 93
aggregationParams: z
.record(z.string(), aggregationParamValueSchema)
.optional(),
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a default loose type for aggregationParams to the baseSchema allows entering mispelled or wrong parameters that are just ignored without error. This also prevents TypeScript from narrowing the type so we can write aggregationParams?.clipingRadius without the compiler raising a type errorwk

I think you should remove the base optional aggregationParams and define it for each network type with strictObject. You could set aggregationStrategy to z.undefined() or mean for local

Comment thread cli/src/args.ts
Comment on lines 101 to +104
// Aggregator
aggregator: AggregationStrategy;
// Byzantine aggregator
clippingRadius?: number;
maxIterations?: number;
beta?: number;
// Secure aggregator
maxShareValue?: number;
aggregatorParams?: Record<string, unknown>;
aggregationConfig: AggregationConfig;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aggregationConfig is deduced and validated from aggregator and aggregatorParams so I think we should only expose aggregationConfig to avoid exposing unvalidated arguments and reduce redundancy

if (task.trainingInformation.aggregationStrategy === "byzantine") {
this.#bftClippingRadius =
task.trainingInformation.aggregationParams?.clippingRadius;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clipping radius is initialized with a fallback value of 1.0 in the zod schema and the aggregator get.ts but here it's ignored if uninitialized. Could you unify the aggregationParams parsing that sets the default values in one place to avoid this situation?

if isinstance(v, dict):
items.extend(flatten_params(v, prefix=key))
else:
items.append(f"{key}={v}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python would set booleans to "True" and "False", currently the JS parser would not parse it correctly

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for null vs None

def flatten_params(params, prefix=""):
items = []
for k, v in params.items():
key = f"{prefix}.{k}" if prefix else str(k)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI doesn't parse this dotted format and no experiment uses it

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants