Repository navigation
feat: Federated Learning v3 comprehensive enhancements (#536) - #911
Conversation
Replace string Strategy properties with type-safe enums in: - FederatedPersonalizationOptions (FederatedPersonalizationStrategy enum) - FederatedMetaLearningOptions (FederatedMetaLearningStrategy enum) - FederatedCompressionOptions (FederatedCompressionStrategy enum) Update all string comparisons in InMemoryFederatedTrainer and test files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add MOON, FedNTD, FedLC, FedDecorr, FedAlign, FedSAM, FedMA, and FedAA aggregation strategies with paper-accurate defaults and comprehensive docs. Expand FederatedAggregationStrategy enum with 13 new values (also covering #903). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add FLTrust, DnC, Bucketing, FLAME, BOBA, and OptiGradTrust with paper-accurate implementations, trust scoring, spectral analysis, and historical reputation tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add FedPETuning, FedAdapter, FLoRA, HierFedLoRA, SLoRA, DP-FedLoRA, FedMeZO adapters and FederatedRLHF, FederatedDPO, OpenFedLLMPipeline alignment modules with expanded FederatedAdapterType enum. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add FedBABU, FedRoD, FedCP, kNN-Per, FedSelect, pFedGate, FedAGHN, and FedPAC with expanded FederatedPersonalizationStrategy enum. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nalytics (#904) Add ShuffleModelDP for privacy amplification via shuffling, ProxyZKPVerifier for lightweight computation verification, and OptimizedPrivateSetAnalytics for count-min sketch based private analytics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hSGD, FedKD (#905) Add SignSGDCompressor for 1-bit gradient compression with majority vote, TopKSparsificationCompressor with error feedback, FetchSGDCompressor using count-sketch recovery, and FedKDCompressor for knowledge distillation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…p, FedBuff (#906) Add DFedAvgMProtocol for decentralized averaging with momentum, DeTAGProtocol for gradient tracking, SegmentedGossipProtocol for bandwidth-efficient gossip, TimeVaryingTopology for dynamic network graphs, and BufferedAsyncFederatedTrainer for buffered async aggregation (FedBuff). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lay, FedCIL (#907) Add FedAGCContinualLearning for adaptive gradient correction, FederatedExperienceReplay with reservoir sampling buffer, and FedCILContinualLearning for class-incremental learning with prototype consolidation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ey, FedFair (#908) Add AgnosticFairnessObjective for minimax fairness, QFairFederatedLearning for parameterized q-fair optimization, TiltedERMFairness for smooth avg/worst-case interpolation, LightweightShapleyEvaluator for O(n) Shapley values, and FedFairOptimizer for multi-objective Pareto scalarization. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds ~50 new federated‑learning components across adapters, aggregators, alignment, compression, continual learning, decentralization, fairness, personalization, privacy, trainers, PSI, options/enums, and tests; converts several string strategy fields to strongly‑typed enums. Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR implements Federated Learning v3 enhancements, adding 55+ production-ready classes across 10 research areas to address issues #900-#909. The changes convert string-based strategy parameters to type-safe enums and add comprehensive implementations for advanced aggregation, foundation model FL, personalization, Byzantine robustness, privacy enhancements, communication efficiency, async/decentralized protocols, continual learning, and fairness mechanisms.
Changes:
- Converted
FederatedCompressionStrategy,FederatedPersonalizationStrategy, andFederatedMetaLearningStrategyfrom strings to proper enums - Added 13 new aggregation strategy enum values (MOON, FedNTD, FedLC, FedDecorr, FedAlign, FedSAM, FedMA, FedAA, FLTrust, DivideAndConquer, Bucketing, Flame, Boba)
- Added 7 new adapter type enum values for foundation model FL
- Implemented 55+ new strategy classes with paper-accurate defaults and comprehensive XML documentation
Reviewed changes
Copilot reviewed 62 out of 64 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| Test files (4 files) | Updated tests to use enum values instead of strings for strategy selection |
| FederatedPersonalizationOptions.cs | Converted Strategy property from string to FederatedPersonalizationStrategy enum with 14 values |
| FederatedMetaLearningOptions.cs | Converted Strategy property from string to FederatedMetaLearningStrategy enum with 4 values |
| FederatedCompressionOptions.cs | Converted Strategy property from string to FederatedCompressionStrategy enum with 7 values |
| FederatedAggregationStrategy.cs | Added 13 new enum values for advanced aggregation strategies |
| FederatedAdapterOptions.cs | Added 7 new enum values for foundation model adapters |
| New aggregation strategies (8 files) | MOON, FedSAM, FedNTD, FedMA, FedLC, FedDecorr, FedAlign, FedAA |
| New Byzantine-robust strategies (5 files) | FLTrust, Bucketing, Boba, OptiGradTrust |
| New personalization strategies (8 files) | FedBABU, FedRoD, FedCP, KNNPer, FedSelect, pFedGate, FedAGHN, FedPAC |
| New adapter strategies (7 files) | FedPETuning, FederatedAdapterTuning, FLoRA, HierarchicalFedLoRA, SparseLoRA, FedMeZO |
| New alignment strategies (2 files) | FederatedRLHF, FederatedDPO |
| New privacy mechanisms (2 files) | ShuffleModelDP, ProxyZKPVerifier, OptimizedPrivateSetAnalytics |
| New compression strategies (4 files) | SignSGD, TopKSparsification, FetchSGD, FedKD |
| New decentralized protocols (5 files) | DFedAvgM, DeTAG, SegmentedGossip, TimeVaryingTopology, BufferedAsync |
| New continual learning (3 files) | FedAGC, FederatedExperienceReplay, FedCIL |
| New fairness implementations (5 files) | AgnosticFairness, QFairFL, TiltedERM, LightweightShapley, FedFair |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 119
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/FederatedLearning/Trainers/InMemoryFederatedTrainer.cs (1)
163-203:⚠️ Potential issue | 🟠 MajorAdd fail-fast enum validation for option strategies before use.
compressionOptions.Strategy,personalizationOptions.Strategy, andmetaLearningOptions.Strategyare consumed without validating that the value is defined. Invalid numeric enum values can enter from config binding and produce inconsistent behavior instead of a clear configuration error.🔧 Proposed fix
var compressionOptions = ResolveCompressionOptions(flOptions); + if (compressionOptions != null && + !Enum.IsDefined(typeof(FederatedCompressionStrategy), compressionOptions.Strategy)) + { + throw new InvalidOperationException($"Unknown compression strategy '{compressionOptions.Strategy}'."); + } bool useCompression = compressionOptions != null && compressionOptions.Strategy != FederatedCompressionStrategy.None; @@ var personalizationOptions = ResolvePersonalizationOptions(flOptions); + if (personalizationOptions != null && + !Enum.IsDefined(typeof(FederatedPersonalizationStrategy), personalizationOptions.Strategy)) + { + throw new InvalidOperationException($"Unknown personalization strategy '{personalizationOptions.Strategy}'."); + } bool usePersonalization = personalizationOptions != null && personalizationOptions.Enabled && personalizationOptions.Strategy != FederatedPersonalizationStrategy.None; @@ var metaLearningOptions = flOptions?.MetaLearning; + if (metaLearningOptions != null && + !Enum.IsDefined(typeof(FederatedMetaLearningStrategy), metaLearningOptions.Strategy)) + { + throw new InvalidOperationException($"Unknown meta-learning strategy '{metaLearningOptions.Strategy}'."); + } bool useMetaLearning = metaLearningOptions != null && metaLearningOptions.Enabled && metaLearningOptions.Strategy != FederatedMetaLearningStrategy.None;As per coding guidelines, production-ready code must avoid “missing validation of external inputs”.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Trainers/InMemoryFederatedTrainer.cs` around lines 163 - 203, Validate enum values for compressionOptions.Strategy, personalizationOptions.Strategy, and metaLearningOptions.Strategy immediately after resolving each options object (e.g., after ResolveCompressionOptions(flOptions), ResolvePersonalizationOptions(flOptions), and flOptions?.MetaLearning). Use Enum.IsDefined (or equivalent) to fail-fast and throw a clear ArgumentException/InvalidOperationException if the numeric value is not a valid FederatedCompressionStrategy / FederatedPersonalizationStrategy / FederatedMetaLearningStrategy; do this before any use such as computing useCompression/usePersonalization/useMetaLearning or assigning metadata fields so invalid config bindings produce a clear error instead of undefined behavior.src/Models/Options/FederatedCompressionOptions.cs (1)
74-75: 🧹 Nitpick | 🔵 TrivialUse enum cref in XML docs instead of a string literal.
After the enum migration, reference the actual member to keep docs IDE-linkable and less error-prone.
📝 Suggested XML doc update
- /// beyond what basic TopK/Quantization can achieve. Set <see cref="Strategy"/> to "Advanced" + /// beyond what basic TopK/Quantization can achieve. Set <see cref="Strategy"/> to + /// <see cref="FederatedCompressionStrategy.Advanced"/>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Models/Options/FederatedCompressionOptions.cs` around lines 74 - 75, The XML doc in FederatedCompressionOptions refers to the Strategy value as the string "Advanced"; update that to use a cref to the enum member instead (for example <see cref="CompressionStrategy.Advanced"/>), so the docs are IDE-linkable and robust. Edit the comment that mentions Strategy in the FederatedCompressionOptions class (the XML docs near the Strategy property) and replace the literal "Advanced" with a <see cref="..."/> reference to the actual enum type and member used by the property (e.g., CompressionStrategy.Advanced or the actual enum name in your codebase).
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
src/Generated/AiDotNet.Generators/AiDotNet.Generators.YamlConfigSourceGenerator/YamlRegisteredTypeNames.g.csis excluded by!**/generated/**src/Generated/AiDotNet.Generators/AiDotNet.Generators.YamlConfigSourceGenerator/YamlTypeRegistry.g.csis excluded by!**/generated/**
📒 Files selected for processing (62)
src/FederatedLearning/Adapters/DPFedLoRA.cssrc/FederatedLearning/Adapters/FLoRA.cssrc/FederatedLearning/Adapters/FedMeZO.cssrc/FederatedLearning/Adapters/FedPETuning.cssrc/FederatedLearning/Adapters/FederatedAdapterTuning.cssrc/FederatedLearning/Adapters/HierarchicalFedLoRA.cssrc/FederatedLearning/Adapters/SparseLoRA.cssrc/FederatedLearning/Aggregators/BobaAggregationStrategy.cssrc/FederatedLearning/Aggregators/BucketingAggregationStrategy.cssrc/FederatedLearning/Aggregators/DivideAndConquerAggregationStrategy.cssrc/FederatedLearning/Aggregators/FLTrustAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedAaAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedAlignAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedDecorrAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedLcAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedMaAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedNtdAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedSamAggregationStrategy.cssrc/FederatedLearning/Aggregators/FlameAggregationStrategy.cssrc/FederatedLearning/Aggregators/MoonAggregationStrategy.cssrc/FederatedLearning/Aggregators/OptiGradTrustAggregationStrategy.cssrc/FederatedLearning/Alignment/FederatedDPO.cssrc/FederatedLearning/Alignment/FederatedRLHF.cssrc/FederatedLearning/Alignment/OpenFedLLMPipeline.cssrc/FederatedLearning/Compression/FedKDCompressor.cssrc/FederatedLearning/Compression/FetchSGDCompressor.cssrc/FederatedLearning/Compression/SignSGDCompressor.cssrc/FederatedLearning/Compression/TopKSparsificationCompressor.cssrc/FederatedLearning/ContinualLearning/FedAGCContinualLearning.cssrc/FederatedLearning/ContinualLearning/FedCILContinualLearning.cssrc/FederatedLearning/ContinualLearning/FederatedExperienceReplay.cssrc/FederatedLearning/Decentralized/DFedAvgMProtocol.cssrc/FederatedLearning/Decentralized/DeTAGProtocol.cssrc/FederatedLearning/Decentralized/SegmentedGossipProtocol.cssrc/FederatedLearning/Decentralized/TimeVaryingTopology.cssrc/FederatedLearning/Fairness/AgnosticFairnessObjective.cssrc/FederatedLearning/Fairness/FedFairOptimizer.cssrc/FederatedLearning/Fairness/LightweightShapleyEvaluator.cssrc/FederatedLearning/Fairness/QFairFederatedLearning.cssrc/FederatedLearning/Fairness/TiltedERMFairness.cssrc/FederatedLearning/PSI/OptimizedPrivateSetAnalytics.cssrc/FederatedLearning/Personalization/FedAGHNPersonalization.cssrc/FederatedLearning/Personalization/FedBABUPersonalization.cssrc/FederatedLearning/Personalization/FedCPPersonalization.cssrc/FederatedLearning/Personalization/FedPACPersonalization.cssrc/FederatedLearning/Personalization/FedRoDPersonalization.cssrc/FederatedLearning/Personalization/FedSelectPersonalization.cssrc/FederatedLearning/Personalization/KNNPersonalization.cssrc/FederatedLearning/Personalization/PFedGatePersonalization.cssrc/FederatedLearning/Privacy/ShuffleModelDP.cssrc/FederatedLearning/Trainers/BufferedAsyncFederatedTrainer.cssrc/FederatedLearning/Trainers/InMemoryFederatedTrainer.cssrc/FederatedLearning/Verification/ProxyZKPVerifier.cssrc/Models/Options/FederatedAdapterOptions.cssrc/Models/Options/FederatedAggregationStrategy.cssrc/Models/Options/FederatedCompressionOptions.cssrc/Models/Options/FederatedMetaLearningOptions.cssrc/Models/Options/FederatedPersonalizationOptions.cstests/AiDotNet.Tests/FederatedLearning/FederatedLearningCompressionIntegrationTests.cstests/AiDotNet.Tests/FederatedLearning/FederatedLearningInvalidConfigurationTests.cstests/AiDotNet.Tests/FederatedLearning/FederatedLearningPersonalizationAndMetaLearningIntegrationTests.cstests/AiDotNet.Tests/FederatedLearning/InMemoryFederatedTrainerInternalHelperTests.cs
…coverage Missing files added: - FedDTCompressor.cs (#905) - decision-tree compression - DataFreeFCL.cs (#907) - data-free continual learning - DFedBCAProtocol.cs (#906) - block coordinate ascent - AsyncFedEDTrainer.cs (#906) - entropy-driven scheduling - OneShotFederatedTrainer.cs (#905) - single-round FL - SemiAsyncFederatedTrainer.cs (#906) - hybrid sync/async Enum/options updates: - FederatedAggregationStrategy: add OptiGradTrust (#903) - AdvancedCompressionStrategy: add SignSGD, FetchSGD, FedKD, FedDT (#905) - DecentralizedTopologyType: add DFedAvgM, DFedBCA, DeTAG, SegmentedGossip, TimeVarying (#906) - FederatedAsyncMode: add AsyncFedED, SemiAsync (#906) - FederatedContinualLearningStrategy: add FedAGC, ExperienceReplay, DataFreeFCL, FedCIL (#907) - FairnessConstraintType: add AgnosticFairness, QFairFL, TiltedERM, FedFair (#908) - DifferentialPrivacyMode: add Shuffle (#904) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 22
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/Models/Options/DifferentialPrivacyMode.cs (1)
6-11: 🧹 Nitpick | 🔵 TrivialConsider updating top-level remarks to mention Shuffle mode.
The
<remarks>block explains Local, Central, and Both modes but omits the newly added Shuffle mode. For completeness and discoverability, consider adding a bullet point for Shuffle.📝 Suggested documentation update
/// <remarks> /// <b>For Beginners:</b> Differential privacy can be applied at different points: /// - Local DP: each client adds noise before sending updates (stronger protection vs server). /// - Central DP: the server adds noise after aggregation (simpler and often higher utility). /// - Both: apply local and central DP for defense-in-depth. +/// - Shuffle DP: clients add local noise, then a shuffler permutes updates before the server +/// sees them, achieving central-DP-level accuracy with local-DP trust assumptions. /// </remarks>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Models/Options/DifferentialPrivacyMode.cs` around lines 6 - 11, The remarks for DifferentialPrivacyMode currently document Local, Central, and Both but omit the Shuffle mode; update the XML <remarks> for the DifferentialPrivacyMode enum to add a fourth bullet describing Shuffle (briefly explain that a trusted shuffler anonymizes client contributions before aggregation, offering a middle ground between local and central DP), referencing the DifferentialPrivacyMode symbol so reviewers can find and verify the new doc text matches the enum's Shuffle value.src/Models/Options/FederatedContinualLearningOptions.cs (1)
33-61:⚠️ Potential issue | 🔴 CriticalBLOCKING: Options class missing required Golden Pattern elements.
This Options class violates multiple BLOCKING requirements per the coding guidelines:
- Missing Copy Constructor — This is a silent data-loss bug. When users clone/copy options, properties won't transfer.
- Missing Explicit Default Constructor — Guidelines require
public FederatedContinualLearningOptions() { }- Missing
<para><b>Reference:</b>in class remarks — Must cite the foundational research papers for federated continual learning (e.g., FedWEIT, FedCurv).- Properties missing full XML docs — Each property requires
<value>, and<remarks>with<para><b>For Beginners:</b>explaining what the property controls.🔧 Proposed fix adding required constructors and documentation
/// <summary> /// Configuration options for federated continual learning (preventing catastrophic forgetting in FL). /// </summary> /// <remarks> /// <para><b>For Beginners:</b> When federated models learn new tasks over time, they can forget what they /// learned before (catastrophic forgetting). These strategies identify which model parameters are important /// for previous tasks and protect them during future training rounds. Each client computes importance locally, /// and the server aggregates these importance estimates across all clients.</para> +/// <para><b>Reference:</b> Foundational work includes Federated EWC (Kirkpatrick et al., PNAS 2017), +/// FedCurv (Shoham et al., 2019), and FedCIL (Qi et al., CVPR 2023).</para> /// </remarks> public class FederatedContinualLearningOptions : ModelOptions { + /// <summary> + /// Initializes a new instance of the <see cref="FederatedContinualLearningOptions"/> class with default values. + /// </summary> + public FederatedContinualLearningOptions() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="FederatedContinualLearningOptions"/> class + /// by copying values from another instance. + /// </summary> + /// <param name="other">The instance to copy.</param> + /// <exception cref="ArgumentNullException">Thrown when <paramref name="other"/> is null.</exception> + public FederatedContinualLearningOptions(FederatedContinualLearningOptions other) + { + ArgumentNullException.ThrowIfNull(other); + Strategy = other.Strategy; + RegularizationStrength = other.RegularizationStrength; + FisherSamples = other.FisherSamples; + ProjectionThreshold = other.ProjectionThreshold; + } + /// <summary> /// Gets or sets the strategy. Default: None. /// </summary> + /// <value>The federated continual learning strategy to use.</value> + /// <remarks> + /// <para><b>For Beginners:</b> Choose which forgetting-prevention technique to apply. + /// Start with <see cref="FederatedContinualLearningStrategy.None"/> for standard FL, + /// then try <see cref="FederatedContinualLearningStrategy.FederatedEWC"/> for basic protection.</para> + /// </remarks> public FederatedContinualLearningStrategy Strategy { get; set; } = FederatedContinualLearningStrategy.None; /// <summary> /// Gets or sets the regularization strength for EWC penalty. Default: 400.0. /// </summary> + /// <value>The lambda coefficient for EWC regularization.</value> /// <remarks> + /// <para><b>For Beginners:</b> Higher values mean "remember old tasks more strongly" but learn new tasks + /// more slowly. Start with 400 and increase if you see forgetting, or decrease if new learning stalls.</para> /// Higher values provide stronger protection against forgetting but may slow learning of new tasks. /// Typical range: 100–5000. /// </remarks> public double RegularizationStrength { get; set; } = 400.0; /// <summary> /// Gets or sets the number of data samples for Fisher information estimation. Default: 200. /// </summary> + /// <value>Sample count for computing diagonal Fisher information matrix.</value> + /// <remarks> + /// <para><b>For Beginners:</b> More samples = more accurate importance estimates but slower computation. + /// 200 is a good balance; increase to 500+ for complex models.</para> + /// </remarks> public int FisherSamples { get; set; } = 200; /// <summary> /// Gets or sets the projection threshold for orthogonal projection. Default: 0.01. /// </summary> + /// <value>Importance threshold below which directions are not protected.</value> /// <remarks> + /// <para><b>For Beginners:</b> Lower values protect more gradient directions (stronger forgetting prevention + /// but slower new learning). Start at 0.01; raise to 0.05 if new tasks struggle to learn.</para> /// Directions with importance above this threshold are protected. Lower values protect more directions. /// </remarks> public double ProjectionThreshold { get; set; } = 0.01; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Models/Options/FederatedContinualLearningOptions.cs` around lines 33 - 61, Add the required Golden Pattern elements to FederatedContinualLearningOptions: implement a public parameterless constructor FederatedContinualLearningOptions() { } and a copy constructor FederatedContinualLearningOptions(FederatedContinualLearningOptions other) that throws ArgumentNullException if other is null and copies Strategy, RegularizationStrength, FisherSamples, and ProjectionThreshold; update the class XML <remarks> to include a <para><b>Reference:</b> entry citing foundational federated continual learning papers (e.g., FedWEIT, FedCurv); and expand each property’s XML docs (Strategy, RegularizationStrength, FisherSamples, ProjectionThreshold) to include a <value> describing the property and a <remarks> containing a <para><b>For Beginners:</b> explanation of what the property controls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/FederatedLearning/Compression/FedDTCompressor.cs`:
- Around line 243-262: The EstimateCompressionRatio method lacks a null check
for the CompressedTree parameter and doesn't document that it treats all stored
elements equally regardless of type; update EstimateCompressionRatio to validate
that the tree argument is not null (throw ArgumentNullException or return a
sensible default like 1.0 consistently), then proceed to sum lengths from
tree.LeafValues, tree.SplitPoints and tree.LeafCounts as before; also add a
brief inline comment inside EstimateCompressionRatio noting that the calculation
treats doubles and ints equally as an approximation so callers are aware.
- Around line 276-297: Validate constructor inputs for CompressedTree by
guarding against nulls and invalid contents: in the CompressedTree constructor
check that splitPoints, leafValues, and leafCounts are not null and throw
ArgumentNullException if any are; also validate that none of the dictionaries
contain null values (e.g., any value arrays are non-null) and throw
ArgumentException describing which parameter is invalid; ensure the property
assignments (SplitPoints, LeafValues, LeafCounts) only happen after these checks
so the instance cannot be created in an invalid state.
- Around line 71-98: The public method Compress should validate its input and
throw an ArgumentNullException when parameterDelta is null; add a null-check at
the start of the Compress method (in class FedDTCompressor) that throws new
ArgumentNullException(nameof(parameterDelta)) before any access to
parameterDelta (e.g., before the foreach) so callers get a clear error instead
of a NullReferenceException while preserving existing behavior that builds and
returns a CompressedTree.
- Around line 149-189: Add null validation at the start of the public method
Decompress(CompressedTree tree, Dictionary<string, T[]> templateDelta): check if
tree is null and if templateDelta is null and throw ArgumentNullException (or
appropriate) naming the offending parameter. Update any callers/tests if they
rely on null inputs; leave the existing logic for reconstructing layers intact
(references: Decompress, CompressedTree, templateDelta, tree).
- Around line 198-235: MergeTrees is doing Decompress inside the inner (layer ×
client) loop causing L×C decompressions and allocations; instead, decompress
once per client and reuse the result. Specifically, for each clientId in
clientTrees call Decompress(tree, templateDelta) once, cache the returned
Dictionary<string, T[]> (e.g., decompressedPerClient[clientId]) and then in the
outer loop over templateDelta look up layer values from that cached dictionary;
remove the per-iteration new Dictionary<string, T[]> allocation and keep
accumulation into merged using clientWeights as currently done, then convert to
T[] as before.
- Around line 127-141: The code computes a median split value but actually
partitions by index in BuildTree (using start/mid/end), so either implement
value-based partitioning or remove the dead split-value work; to minimize
changes, delete the median computation and splits.Add(splitVal) and update the
comment in BuildTree to state that partitioning is index-based (spatial split of
parameter vector), and ensure CompressedTree.SplitPoints is no longer
populated/relied upon (or documented as unused) so Decompress continues to rely
on leafCounts; keep references to BuildTree, CompressedTree.SplitPoints,
Decompress, leafValues and leafCounts in your edits.
In `@src/FederatedLearning/ContinualLearning/DataFreeFCL.cs`:
- Line 34: The private field _accumulatedImportance in DataFreeFCL is mutated by
ComputeImportance and needs thread-safety and a reset mechanism: protect all
reads/writes to _accumulatedImportance with a dedicated lock object (e.g., a
private readonly object _importanceLock) or use Interlocked operations when
replacing the Vector<T>, and add a public ResetImportance() method that acquires
the same lock and sets _accumulatedImportance to null (or a zeroed Vector) to
allow reuse between runs; also update the class summary/comments on DataFreeFCL
and the ComputeImportance method to document that importance is accumulated
stateful data that must be reset between experiments.
- Around line 76-127: The parameter taskData in ComputeImportance is never used
which is confusing; update the method to clearly document this by adding an XML
doc comment on ComputeImportance (and/or on the interface
IFederatedContinualLearningStrategy<T> signature) stating that this
implementation is data-free and intentionally ignores taskData, or add an inline
comment and a pragma to suppress unused-parameter warnings if needed; reference
ComputeImportance and taskData so reviewers can find and read the new
doc/comment and ensure callers understand the interface mismatch is intentional.
In `@src/FederatedLearning/Decentralized/DFedBCAProtocol.cs`:
- Around line 78-84: ExtractBlock currently uses fullParameters.Keys.ToList()
which yields non-deterministic ordering across peers; update ExtractBlock to
produce a deterministic, reproducible layer list (e.g., sort the keys via
layerNames = fullParameters.Keys.OrderBy(k => k).ToList()) and explicitly
validate blockIndex (throw or clamp if blockIndex < 0 or blockIndex >=
_numBlocks) before computing layersPerBlock, start, and end so every peer maps
the same blockIndex to the same layer subset; keep existing logic for
layersPerBlock and slicing but base it on the sorted layerNames and validated
blockIndex.
- Around line 57-67: The ImportanceBased branch in SelectBlock currently falls
back to cyclic selection; replace this placeholder with a proper implementation
or fail fast: either (A) implement importance-based selection inside SelectBlock
using the component that supplies importance scores (e.g., query the importance
score provider or array and pick the block with highest/weighted probability
among _numBlocks) and ensure you use _currentBlock and _numBlocks correctly, or
(B) if the importance-score plumbing is not yet available, throw a
NotImplementedException (or ArgumentException) for
BlockSelectionStrategy.ImportanceBased so the code fails fast rather than
silently using cyclic fallback; update any callers/tests as needed.
- Line 26: The SelectBlock implementation currently contains a placeholder for
the ImportanceBased strategy—replace the stub in SelectBlock with a real
importance-based selection algorithm (or remove the ImportanceBased branch
entirely) so it returns deterministic, meaningful block indices; in ExtractBlock
stop relying on dictionary enumeration order and instead produce a deterministic
layer ordering (e.g., sort layer keys or use an explicit layer index list)
before partitioning so all peers map layers to blocks the same way; in
AverageBlock fix the normalization so totalWeight sums only the mixing weights
of the actually participating neighbors/layers (the actual contributors
returned/used in the aggregation) rather than all possible weights to avoid
bias; and if DFedBCAProtocol<T> is not intended as part of the public API,
change its visibility from public to internal to limit surface area.
- Around line 129-153: The denominator totalWeight is computed from
mixingWeights.Values.Sum() and can include neighbors that are not present in
clientBlocks or that don't contribute the current layer, causing biased
normalization in the averaging loop inside the method that builds result from
template; fix by computing a per-layer effective weight sum (e.g.,
effectiveTotal or layerWeightSum) inside the foreach over template using only
neighbors that actually contributed to that layer (use clientBlocks iteration
and GetValueOrDefault/matching neighborId checks like where you compute w =
mixingWeights.GetValueOrDefault(neighborId, 0) and only add w to the per-layer
sum when block.TryGetValue(layerName, out var neighborLayer) is true), then
normalize averaged[i] by this per-layer sum (falling back to 0 if that per-layer
sum is zero) when creating averagedT so the normalization uses only
participating neighbors.
In `@src/FederatedLearning/Trainers/AsyncFedEDTrainer.cs`:
- Around line 30-32: The two shared mutable fields _clientEntropies and
_lastParticipationRound are accessed concurrently and need synchronization:
replace them with thread-safe collections (e.g.,
ConcurrentDictionary<int,double> and ConcurrentDictionary<int,int>) or wrap all
access in a dedicated lock object (e.g., a private readonly object _sync) and
use lock(...) around every read/write in AsyncFedEDTrainer methods that touch
these fields (refer to usages in the methods that perform reads/writes between
lines ~72–173). Ensure adds/updates use atomic operations (TryAdd/TryUpdate or
lock-protected indexers) and any read-modify-write sequences are protected to
avoid race conditions.
- Around line 72-73: Add production-grade input guards to public entry points
(starting with UpdateClientEntropy) that accept external data: validate that
classLosses is not null and has expected length, clientId is non-negative, and
currentRound is >= 0 (or >= last known round if staleness math assumes monotonic
rounds); if validation fails throw appropriate exceptions (ArgumentNullException
/ ArgumentException) or return early, and ensure any staleness calculations
clamp or check for negative results before using them. Locate
UpdateClientEntropy and the other public methods that accept currentRound and
apply the same checks and defensive clamping to prevent NREs and negative
staleness math.
- Around line 176-193: The aggregation uses the first client's layer set as
`template` and always normalizes by global `totalWeight`, which drops layers
present only in other clients and mis-normalizes layers missing from some
clients; instead, build the union of all layer names from `clientUpdates` (not
`template`), iterate that union when creating `merged`, and for each layer
compute a per-layer weight sum from `weights` only over clients that actually
provide that layer; then normalize `merged` by that per-layer weight (falling
back to equal weighting if per-layer weight is zero) and continue using
`NumOps.ToDouble` for conversions so layer coverage and normalization are
correct (apply same fix to the subsequent aggregation block in
AsyncFedEDTrainer.cs).
In `@src/FederatedLearning/Trainers/OneShotFederatedTrainer.cs`:
- Around line 82-88: When returning from the aggregation switch in
OneShotFederatedTrainer, ensure LastEnsembleDiversity is reset whenever
_aggregationMode is not OneShotAggregationMode.EnsembleDistillation to avoid
exposing stale diversity values; update the return branch that calls
AggregateWeightedAverage and AggregateUniform (and the default) to set
LastEnsembleDiversity to its default (null or 0 consistent with its type) before
returning, and apply the same reset at the other aggregation switch location
that calls AggregateEnsembleDistillation (the second occurrence mentioned) so
only the EnsembleDistillation path preserves a computed diversity value.
- Around line 143-147: EnsembleDistillation currently only computes a weighted
average via AggregateWeightedAverage and ignores
DistillationTemperature/DistillationSteps; replace the placeholder by injecting
and invoking a real distiller: define or accept an IDistiller (e.g.,
Distill(ensembleModels, unlabeledDataset, temperature, steps)) and call it from
EnsembleDistillation using the AggregateWeightedAverage result as initialization
and passing DistillationTemperature and DistillationSteps, then return the
distilled model and recorded diversity; if no distiller is available yet, fail
fast by throwing a clear NotImplementedException mentioning EnsembleDistillation
so the missing implementation is explicit.
- Around line 113-114: The loops that use Math.Min(clientLayer.Length,
merged.Length) silently truncate mismatched layer arrays; instead, validate that
clientLayer and merged have identical shapes before merging and reject the
client (or throw a clear exception) when they differ. Locate the loops that
iterate using Math.Min over clientLayer and merged (the one that indexes i and
accesses clientLayer[i] and merged[i]) and replace the truncation logic with an
explicit check (e.g., compare lengths and/or tensor dimensions) that raises an
informative InvalidOperationException or logs and skips the client with details
(client id, layer index, expected vs actual shape); apply the same change to the
second occurrence that currently uses Math.Min so no silent truncation occurs.
- Around line 73-76: Validate inputs at the start of Aggregate: ensure
clientModels and clientSampleCounts are not null and throw/return early if they
are; then build a filtered client list containing only client IDs that exist in
both dictionaries and whose sample count is > 0 (exclude clients missing from
clientSampleCounts rather than defaulting them into the numerator); compute
totalSamples as the sum of these included sample counts and throw/return if
totalSamples == 0; compute each client's weight as clientSampleCounts[id] /
totalSamples and use those weights to do the per-parameter weighted aggregation
over matching parameter arrays in clientModels (also validate matching array
lengths for a given parameter name across included clients and throw if
inconsistent).
In `@src/FederatedLearning/Trainers/SemiAsyncFederatedTrainer.cs`:
- Around line 77-80: ReceiveUpdate currently stores a null-prone reference to
the caller's Dictionary and arrays and captures clientId which ApplyAsyncUpdates
never reads; add a null-check at the start of ReceiveUpdate (throw
ArgumentNullException or handle per project convention) and create a defensive
deep copy of the incoming update: allocate a new Dictionary<string, T[]> and for
each kvp copy the key and clone each T[] (or create new arrays and copy
contents) before constructing PendingUpdate; finally either remove the unused
clientId from PendingUpdate/ReceiveUpdate or mark a TODO/use clientId inside
ApplyAsyncUpdates to avoid dead code (referencing ReceiveUpdate, PendingUpdate,
and ApplyAsyncUpdates).
- Around line 32-36: The _updateBuffer List<T> is not thread-safe; replace it
with a thread-safe collection (e.g.,
System.Collections.Concurrent.ConcurrentQueue<PendingUpdate> or
ConcurrentBag<PendingUpdate>) by changing the field declaration for
_updateBuffer, initializing it in the constructor, and updating ReceiveUpdate to
Enqueue/Add into that concurrent collection; then modify ApplyAsyncUpdates to
atomically drain the queue (e.g., TryDequeue in a loop or use
GetConsumingEnumerable pattern) so it processes all pending updates safely
without races.
---
Outside diff comments:
In `@src/Models/Options/DifferentialPrivacyMode.cs`:
- Around line 6-11: The remarks for DifferentialPrivacyMode currently document
Local, Central, and Both but omit the Shuffle mode; update the XML <remarks> for
the DifferentialPrivacyMode enum to add a fourth bullet describing Shuffle
(briefly explain that a trusted shuffler anonymizes client contributions before
aggregation, offering a middle ground between local and central DP), referencing
the DifferentialPrivacyMode symbol so reviewers can find and verify the new doc
text matches the enum's Shuffle value.
In `@src/Models/Options/FederatedContinualLearningOptions.cs`:
- Around line 33-61: Add the required Golden Pattern elements to
FederatedContinualLearningOptions: implement a public parameterless constructor
FederatedContinualLearningOptions() { } and a copy constructor
FederatedContinualLearningOptions(FederatedContinualLearningOptions other) that
throws ArgumentNullException if other is null and copies Strategy,
RegularizationStrength, FisherSamples, and ProjectionThreshold; update the class
XML <remarks> to include a <para><b>Reference:</b> entry citing foundational
federated continual learning papers (e.g., FedWEIT, FedCurv); and expand each
property’s XML docs (Strategy, RegularizationStrength, FisherSamples,
ProjectionThreshold) to include a <value> describing the property and a
<remarks> containing a <para><b>For Beginners:</b> explanation of what the
property controls.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (13)
src/FederatedLearning/Compression/FedDTCompressor.cssrc/FederatedLearning/ContinualLearning/DataFreeFCL.cssrc/FederatedLearning/Decentralized/DFedBCAProtocol.cssrc/FederatedLearning/Trainers/AsyncFedEDTrainer.cssrc/FederatedLearning/Trainers/OneShotFederatedTrainer.cssrc/FederatedLearning/Trainers/SemiAsyncFederatedTrainer.cssrc/Models/Options/AdvancedCompressionStrategy.cssrc/Models/Options/DecentralizedFederatedOptions.cssrc/Models/Options/DifferentialPrivacyMode.cssrc/Models/Options/FairnessConstraintType.cssrc/Models/Options/FederatedAggregationStrategy.cssrc/Models/Options/FederatedAsyncMode.cssrc/Models/Options/FederatedContinualLearningOptions.cs
…orithms - FedMA: Full Hungarian algorithm (Jonker-Volgenant) for neuron matching with cosine similarity cost matrix and multi-iteration refinement - ProxyZKPVerifier: SHA-256 commitment verification with deterministic serialization, nonce generation, and hash recomputation - OptimizedPrivateSetAnalytics: Full HyperLogLog with MurmurHash3 for cardinality estimation, threshold queries via secret-shared count-min sketches, and bias-corrected estimators - DPFedLoRA: Per-layer noise calibration computing max gradient norm across clients per layer for sensitivity-proportional DP noise - FLoRA: Stacking + truncated SVD re-decomposition via power iteration for lossless federated LoRA aggregation per the original paper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 74 out of 76 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 11
♻️ Duplicate comments (8)
src/FederatedLearning/PSI/OptimizedPrivateSetAnalytics.cs (1)
69-83:⚠️ Potential issue | 🔴 CriticalBlocking: add complete argument and shape guards on all public boundaries.
Line 69 onward accepts external inputs without consistent null/shape validation, which can throw nondeterministic runtime exceptions (e.g., null item strings, null collections, mismatched sketch/register dimensions). Line 177 also silently truncates mismatched HLL register arrays instead of rejecting invalid input.
Concrete guard pattern to apply
public int[,] CreateSketch(IEnumerable<string> items) { + if (items is null) throw new ArgumentNullException(nameof(items)); var sketch = new int[_sketchDepth, _sketchWidth]; foreach (var item in items) { + if (string.IsNullOrWhiteSpace(item)) + throw new ArgumentException("Items must not contain null/empty values.", nameof(items)); int hash = GetStableHash(item); @@ public int EstimateFrequency(int[,] mergedSketch, string item) { + if (mergedSketch is null) throw new ArgumentNullException(nameof(mergedSketch)); + if (mergedSketch.GetLength(0) != _sketchDepth || mergedSketch.GetLength(1) != _sketchWidth) + throw new ArgumentException("Merged sketch dimensions must match configured depth/width.", nameof(mergedSketch)); + if (string.IsNullOrWhiteSpace(item)) + throw new ArgumentException("Item must not be null or empty.", nameof(item)); @@ public byte[] MergeHLLRegisters(IReadOnlyList<byte[]> registerSets) { + if (registerSets is null) throw new ArgumentNullException(nameof(registerSets)); if (registerSets.Count == 0) throw new ArgumentException("No register sets to merge.", nameof(registerSets)); @@ foreach (var registers in registerSets) { - for (int i = 0; i < Math.Min(registers.Length, _hllRegisterCount); i++) + if (registers is null || registers.Length != _hllRegisterCount) + throw new ArgumentException("All HLL register arrays must be non-null and match configured register count.", nameof(registerSets)); + for (int i = 0; i < _hllRegisterCount; i++) { if (registers[i] > merged[i]) merged[i] = registers[i]; } } @@ public double EstimateCardinality(byte[] registers) { + if (registers is null) throw new ArgumentNullException(nameof(registers)); + if (registers.Length != _hllRegisterCount) + throw new ArgumentException("Register length must match configured register count.", nameof(registers));As per coding guidelines: “missing validation of external inputs” is a blocking production-readiness issue.
Also applies to: 91-103, 110-130, 139-160, 167-187, 195-258, 269-312, 322-341
src/FederatedLearning/Adapters/DPFedLoRA.cs (3)
174-189:⚠️ Potential issue | 🔴 CriticalBlocking: validate
totalWeightbefore normalization.Line 188 performs
1.0 / totalWeightwithout guarding zero/negative totals.Proposed fix
foreach (var (clientId, adapters) in clipped) { double w = clientWeights?.GetValueOrDefault(clientId, 1.0) ?? 1.0; + if (w < 0) + { + throw new ArgumentOutOfRangeException(nameof(clientWeights), "Client weights must be non-negative."); + } totalWeight += w; @@ } + if (totalWeight <= 0) + { + throw new ArgumentException("Total client weight must be positive.", nameof(clientWeights)); + } var invTotal = NumOps.FromDouble(1.0 / totalWeight);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Adapters/DPFedLoRA.cs` around lines 174 - 189, The code computes invTotal using 1.0 / totalWeight without validating totalWeight; before calling NumOps.FromDouble(1.0 / totalWeight) in the block that computes invTotal and applies it to aggregated, add a guard that checks totalWeight (the variable accumulated in the foreach) is > 0 (or otherwise non-zero) and handle the zero/negative case (e.g., log/throw or skip normalization and leave aggregated as-is) so you never call NumOps.FromDouble with an infinite/NaN divisor; update the normalization loop that multiplies aggregated[i] by invTotal accordingly (referencing totalWeight, invTotal, aggregated and NumOps.FromDouble).
196-235:⚠️ Potential issue | 🔴 CriticalBlocking: DP noise is reseeded each call, repeating the same sequence.
Line 198 creates
new Random(_seed)per aggregation call, which reproduces identical noise streams across calls for the same input path. Initialize RNG once per instance (or use a crypto RNG) and reuse it safely.Proposed fix
- private readonly int _seed; + private readonly int _seed; + private readonly Random _noiseRng; + private readonly object _noiseLock = new(); @@ _seed = seed; + _noiseRng = new Random(_seed); @@ - var rng = new Random(_seed); int paramsPerLayer = adapterLen / Math.Max(_numAdaptedLayers, 1); @@ - double u1 = 1.0 - rng.NextDouble(); - double u2 = 1.0 - rng.NextDouble(); + double u1, u2; + lock (_noiseLock) + { + u1 = 1.0 - _noiseRng.NextDouble(); + u2 = 1.0 - _noiseRng.NextDouble(); + } double noise = layerNoiseStd * Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2);#!/bin/bash # Verify deterministic per-call seeding and related guard gaps in adapter strategies. rg -nP --type=cs -C2 'new Random\(_seed\)|start = totalParams - adapterCount|1\.0 / totalWeight|Values\.First\(\)\.Length'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Adapters/DPFedLoRA.cs` around lines 196 - 235, The aggregation routine currently instantiates new Random(_seed) inside the method (see usage of new Random(_seed) where _noiseMultiplier is applied), which reproduces the same Gaussian noise each call; change this to use a single, long-lived RNG instance (e.g., add a private readonly Random _rng or use System.Security.Cryptography.RandomNumberGenerator as a field in the DPFedLoRA class) and reuse it when generating u1/u2 for the Box‑Muller transform instead of creating Random(_seed) per call; ensure the chosen RNG is thread-safe (or protect access with a lock) and keep the existing noise scaling logic that computes layerNoiseStd and applies NumOps.Add to aggregated.
116-121:⚠️ Potential issue | 🔴 CriticalBlocking: prevent negative
startin merge.Line 120 goes negative when
aggregatedAdapters.Length > fullModelParameters.Length, causing invalid indexing at Line 132.Proposed fix
public Vector<T> MergeAdapterParameters(Vector<T> fullModelParameters, Vector<T> aggregatedAdapters) { int totalParams = fullModelParameters.Length; int adapterCount = aggregatedAdapters.Length; + if (adapterCount > totalParams) + { + throw new ArgumentException( + $"Adapter vector length ({adapterCount}) cannot exceed model parameter length ({totalParams}).", + nameof(aggregatedAdapters)); + } int start = totalParams - adapterCount;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Adapters/DPFedLoRA.cs` around lines 116 - 121, MergeAdapterParameters currently computes start = totalParams - adapterCount which can be negative when aggregatedAdapters.Length > fullModelParameters.Length; add a guard in MergeAdapterParameters to validate sizes before indexing: check that aggregatedAdapters.Length <= fullModelParameters.Length (or clamp adapterCount to totalParams) and either throw a clear ArgumentException (including both lengths) or adjust start to 0 and handle the mismatch explicitly, then proceed to copy/assign using start to avoid invalid indexing where parameters are merged; reference symbols: MergeAdapterParameters, fullModelParameters.Length, aggregatedAdapters.Length, totalParams, adapterCount, and start.src/FederatedLearning/Adapters/FLoRA.cs (2)
97-114:⚠️ Potential issue | 🔴 CriticalBlocking: guard merge path against adapter/model length mismatch.
Line 101 can make
startnegative whenaggregatedAdapters.Length > fullModelParameters.Length, causing invalid indexing at Line 113.Proposed fix
public Vector<T> MergeAdapterParameters(Vector<T> fullModelParameters, Vector<T> aggregatedAdapters) { int totalParams = fullModelParameters.Length; int adapterCount = aggregatedAdapters.Length; + if (adapterCount > totalParams) + { + throw new ArgumentException( + $"Adapter vector length ({adapterCount}) cannot exceed model parameter length ({totalParams}).", + nameof(aggregatedAdapters)); + } int start = totalParams - adapterCount;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Adapters/FLoRA.cs` around lines 97 - 114, In MergeAdapterParameters validate that aggregatedAdapters.Length (adapterCount) does not exceed fullModelParameters.Length (totalParams) before computing start; if adapterCount > totalParams either throw an ArgumentException (with a clear message) or handle by clamping/truncating adapters to totalParams, and return/abort early to avoid start becoming negative and invalid indexing when copying into merged; ensure you reference fullModelParameters, aggregatedAdapters, start, adapterCount and totalParams in the guard so the merge loop and subsequent NumOps.Multiply loop cannot run with a negative start.
50-77:⚠️ Potential issue | 🟠 MajorValidate all constructor hyperparameters before deriving adapter geometry.
Line 53 accepts non-positive
alpha, and Lines 54–56 accept non-positive layer topology values. That can produce invalidAdapterParameterCountand unstable downstream indexing behavior. Add explicit guards and checked arithmetic.As per coding guidelines, production-ready code must validate external inputs at boundaries.Proposed fix
public FLoRA( int modelDim, int rank = 8, double alpha = 16.0, int numAdaptedLayers = 4, int layerInputDim = 768, int layerOutputDim = 768) { @@ if (rank <= 0) { throw new ArgumentOutOfRangeException(nameof(rank), "Rank must be positive."); } + if (alpha <= 0) + { + throw new ArgumentOutOfRangeException(nameof(alpha), "Alpha must be positive."); + } + if (numAdaptedLayers <= 0) + { + throw new ArgumentOutOfRangeException(nameof(numAdaptedLayers), "Number of adapted layers must be positive."); + } + if (layerInputDim <= 0) + { + throw new ArgumentOutOfRangeException(nameof(layerInputDim), "Layer input dimension must be positive."); + } + if (layerOutputDim <= 0) + { + throw new ArgumentOutOfRangeException(nameof(layerOutputDim), "Layer output dimension must be positive."); + } @@ - int paramsPerLayer = _layerOutputDim * _rank + _rank * _layerInputDim; - AdapterParameterCount = _numAdaptedLayers * paramsPerLayer; + int paramsPerLayer = checked(_layerOutputDim * _rank + _rank * _layerInputDim); + AdapterParameterCount = checked(_numAdaptedLayers * paramsPerLayer);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Adapters/FLoRA.cs` around lines 50 - 77, Validate all constructor hyperparameters at the start of the FLoRA constructor: ensure alpha > 0, numAdaptedLayers > 0, layerInputDim > 0, and layerOutputDim > 0 and throw ArgumentOutOfRangeException with the parameter name if any are invalid; then compute paramsPerLayer and AdapterParameterCount using checked arithmetic (e.g., checked(...) or checked context) when calculating _layerOutputDim * _rank + _rank * _layerInputDim and when multiplying by _numAdaptedLayers to prevent overflow; finally compute CompressionRatio as (double)AdapterParameterCount / _modelDim (after validating _modelDim > 0).src/FederatedLearning/Verification/ProxyZKPVerifier.cs (2)
136-178:⚠️ Potential issue | 🔴 CriticalBLOCKING: Nonce-less
Verifycan return success without cryptographic commitment verification.This path performs only bounds checks and can return
IsValid=truefor any non-empty commitment. For a method namedVerify, this is an incomplete security path and should not pass as verified.Concrete fix
- /// <summary> - /// Overload for backward compatibility — verifies without nonce (commitment-only check skipped - /// if nonce is unavailable, but norm/element checks still apply). - /// </summary> - public ProxyVerificationResult Verify(Dictionary<string, T[]> update, string commitment) - { - if (string.IsNullOrEmpty(commitment)) - { - return new ProxyVerificationResult(false, "Missing commitment hash."); - } - - double totalNorm2 = 0; - foreach (var kvp in update) - { - for (int i = 0; i < kvp.Value.Length; i++) - { - double v = NumOps.ToDouble(kvp.Value[i]); - ... - } - } - - double norm = Math.Sqrt(totalNorm2); - if (norm > _maxNorm) - { - return new ProxyVerificationResult(false, - $"Update norm {norm:F4} exceeds limit {_maxNorm}."); - } - - return new ProxyVerificationResult(true, "Verified (commitment not re-verified without nonce).", commitment, norm); - } + /// <summary> + /// Legacy overload retained for compatibility. + /// Commitment verification requires a nonce; use Verify(update, commitment, nonce). + /// </summary> + [Obsolete("Nonce-less verification cannot verify commitments. Use Verify(update, commitment, nonce).")] + public ProxyVerificationResult Verify(Dictionary<string, T[]> update, string commitment) + { + return new ProxyVerificationResult(false, + "Nonce is required for commitment verification. Use Verify(update, commitment, nonce)."); + }As per coding guidelines: "Incomplete features ... where some code paths work but others silently do nothing" are blocking.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Verification/ProxyZKPVerifier.cs` around lines 136 - 178, The Verify overload in ProxyZKPVerifier.cs (method Verify(Dictionary<string, T[]> update, string commitment)) currently only does bounds checks and returns a successful ProxyVerificationResult even though it does not perform cryptographic commitment/nonce verification; change this so Verify does not report success when it cannot re-verify the commitment: either (preferred) make this overload return a failing ProxyVerificationResult (e.g., false with a message like "Cannot verify commitment without nonce") whenever the cryptographic commitment/nonce path cannot be executed, or have it call the full Verify that accepts the nonce (or a helper that performs commitment verification) and propagate its result; update any callers/tests accordingly so no code path silently returns IsValid=true without actual commitment verification.
60-63:⚠️ Potential issue | 🔴 CriticalBLOCKING: Missing input-shape guards can crash verifier methods on malformed external input.
update/layer arrays are assumed non-null in multiple public entry points and in serialization. A null dictionary or null layer value can still throw at runtime instead of returning a deterministic validation result.Concrete fix
+ private static string? ValidateUpdateShape(Dictionary<string, T[]>? update) + { + if (update is null || update.Count == 0) + return "Missing update."; + + foreach (var kvp in update) + { + if (string.IsNullOrWhiteSpace(kvp.Key)) + return "Layer name is required."; + if (kvp.Value is null) + return $"Layer '{kvp.Key}' parameter array is null."; + } + + return null; + } public string ComputeCommitment(Dictionary<string, T[]> update, byte[] nonce) { + var updateError = ValidateUpdateShape(update); + if (updateError is not null) + throw new ArgumentException(updateError, nameof(update)); + if (nonce is null || nonce.Length == 0) + throw new ArgumentException("Missing nonce.", nameof(nonce)); + byte[] updateBytes = SerializeUpdate(update); ... } public ProxyVerificationResult Verify(Dictionary<string, T[]> update, string commitment, byte[] nonce) { + var updateError = ValidateUpdateShape(update); + if (updateError is not null) + return new ProxyVerificationResult(false, updateError); + // Check 1: Commitment must be present. ... }As per coding guidelines: production-ready code must avoid "missing validation of external inputs."
Also applies to: 80-81, 103-106, 140-150, 193-201
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/FederatedLearning/Verification/ProxyZKPVerifier.cs` around lines 60 - 63, Add explicit null/shape validation at the start of all public verifier entry points that accept the update dictionary or layer arrays (e.g., ComputeCommitment and SerializeUpdate) so malformed external input cannot cause a runtime crash: check that the update Dictionary itself is non-null, that it contains no null keys or null value arrays, and that each layer array has expected length/shape; return a deterministic validation result (e.g., throw an ArgumentException/ArgumentNullException or return a defined error string/status) rather than letting subsequent code dereference nulls. Apply the same guards to the other public methods that consume update/layer arrays (the methods covering the ranges noted in the review) so all callers get consistent, safe failure behavior on bad input.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/FederatedLearning/Adapters/DPFedLoRA.cs`:
- Around line 146-167: The code assumes all client adapter vectors have the same
length (adapterLen) but only reads it from the first entry; before
clipping/aggregation (i.e., at the start of the loop over clientAdapters in
DPFedLoRA), validate that each adapters.Length equals adapterLen and throw a
clear ArgumentException (or return an error) if any mismatch is found, so you
won't index out of bounds when creating clippedAdapter and multiplying elements;
alternatively, if heterogenous lengths are acceptable, allocate clippedAdapter
using adapters.Length for each client and adjust subsequent aggregation logic to
handle variable-length vectors consistently.
- Around line 53-97: The DPFedLoRA constructor must validate alpha,
numAdaptedLayers, layerInputDim, and layerOutputDim and use checked arithmetic
for derived counts: add ArgumentOutOfRangeException guards ensuring alpha > 0,
numAdaptedLayers > 0, layerInputDim > 0 and layerOutputDim > 0 (using nameof for
each), then compute paramsPerLayer and AdapterParameterCount inside a checked
context (or using checked(...) expressions) to surface overflow issues when
calculating paramsPerLayer = _layerOutputDim * _rank + _rank * _layerInputDim
and AdapterParameterCount = _numAdaptedLayers * paramsPerLayer; leave
CompressionRatio logic but ensure it uses the validated values.
In `@src/FederatedLearning/Adapters/FLoRA.cs`:
- Around line 127-170: The code assumes all client adapter arrays match the
first client's length and divides by totalWeight without guarding zero; update
the aggregation in AggregateAdapters (use clientAdapters, adapterLen,
totalWeight, normalizedW, layerOffset, paramsPerLayer) to: first validate every
client's adapter length matches adapterLen and throw or skip mismatched clients;
compute totalWeight only over validated clients and if totalWeight <= 0
return/throw early; when iterating use each client's actual adapter array (not
assuming first length) and guard indexing by checking layerOffset +
paramsPerLayer <= adapters.Length before accessing B/A values so you never index
out of range; finally recompute normalizedW = w / totalWeight using the
validated totalWeight.
In `@src/FederatedLearning/Aggregators/FedMaAggregationStrategy.cs`:
- Around line 229-260: EstimateNeuronCount incorrectly treats all tensors as
[out×in] dense weight matrices (so biases and conv/attention tensors are
misclassified); update the function to handle layer-type hints or naming
conventions: detect tensor role by key/name suffixes like ".bias" and skip or
return 1 for biases, treat convolution and attention parameter names (e.g.,
containing "conv", "conv2d", "weight" with rank>2, "query", "key", "value",
"proj") differently (either skip heuristic or use configurable per-layer neuron
counts), and add an optional parameter or config map (e.g., layerTypeHints or
neuronCountOverrides) that FedMaAggregationStrategy can consult; modify
EstimateNeuronCount, its callers, and the code that builds model layer entries
to pass the hint/override so biases and non-dense tensors are not
misinterpreted.
- Around line 359-361: GetStrategyName currently embeds a non-ASCII character
(τ) which can cause encoding/logging issues; change the formatted string in
GetStrategyName to use an ASCII-safe label such as "thresh" or "t" (e.g.
$"FedMA(iters={_matchingIterations},thresh={_matchingThreshold})") so references
to _matchingIterations and _matchingThreshold remain but the output is
ASCII-safe.
- Around line 60-63: The single-client fast path in FedMaAggregationStrategy
returns clientModels.First().Value directly, exposing the original model to
mutation; change this to return a defensive copy instead (e.g., var model =
clientModels.First().Value; return model.Clone();), or if no Clone() exists
implement/call the model's copy constructor or a deep-copy helper (serialization
or utility method) to produce and return a new instance; update the code path
where clientModels.Count == 1 to use that clone/deep-copy so callers cannot
mutate the original client model.
- Around line 155-167: The current per-neuron threshold fallback breaks the
bijection by overriding srcNeuron inside the loop (see variables neuronCount,
assignment, costMatrix, _matchingThreshold, clientParams, permutedParams,
neuronSize in FedMaAggregationStrategy), so instead evaluate all similarities
first and if any sim < _matchingThreshold for any i then abandon the permutation
for this layer and perform an identity copy of the whole layer (copy
clientParams into permutedParams unchanged) to preserve bijection; otherwise
proceed to apply the assignment mapping exactly as returned by the Hungarian
algorithm.
- Around line 211-227: The CosineSimilarity method repeatedly calls
MathHelper.GetNumericOperations<T>(), causing heavy redundant lookups; change
the design so MatchAndPermute (or the caller building costMatrix) calls
MathHelper.GetNumericOperations<T>() once and passes the resulting
INumericOperations<T> (or cached value) into CosineSimilarity (e.g., add a
parameter like numOps or numericOps) or store it at class level, then update
CosineSimilarity to use that passed/cached numOps instead of calling
GetNumericOperations<T>() inside the loop and adjust all call sites (e.g., where
costMatrix[i,j] is set) to supply the pre-fetched numOps.
In `@src/FederatedLearning/PSI/OptimizedPrivateSetAnalytics.cs`:
- Around line 40-43: Constructor currently accepts any positive sketchWidth but
ComputeBucketIndex uses bitmasking with _sketchWidth-1, which requires a
power-of-two width; update the OptimizedPrivateSetAnalytics constructor to
validate that sketchWidth is a power of two (e.g., check (sketchWidth &
(sketchWidth - 1)) == 0) and throw an
ArgumentException/ArgumentOutOfRangeException with a clear message if it is not,
referencing the parameter name sketchWidth and the private field _sketchWidth so
callers cannot instantiate the class with a non-power-of-two width that would
break ComputeBucketIndex.
In `@src/FederatedLearning/Verification/ProxyZKPVerifier.cs`:
- Around line 185-190: The GenerateNonce method currently allocates a byte array
without validating the length parameter; add an explicit guard at the start of
GenerateNonce(int length = 32) that checks if length <= 0 and throws an
ArgumentOutOfRangeException (mentioning the parameter name "length") to prevent
invalid array sizes, then proceed with the existing
RandomNumberGenerator.Create() usage and nonce population; ensure the thrown
exception type is ArgumentOutOfRangeException and include a clear message or use
the constructor that takes the parameter name.
- Around line 197-201: The variable totalParams and its pre-pass loop over
sortedKeys in ProxyZKPVerifier.cs are dead code; either remove the totalParams
declaration and the foreach loop entirely, or if the intent was to validate the
update size, use totalParams to assert/validate against the expected parameter
count (e.g., compare totalParams to an expectedModelSize or throw/return an
error) inside the same method in class ProxyZKPVerifier (the block referencing
sortedKeys and update). Make the change consistently: delete the unused variable
and loop if not needed, or wire totalParams into the verification logic to
enforce a size check.
---
Duplicate comments:
In `@src/FederatedLearning/Adapters/DPFedLoRA.cs`:
- Around line 174-189: The code computes invTotal using 1.0 / totalWeight
without validating totalWeight; before calling NumOps.FromDouble(1.0 /
totalWeight) in the block that computes invTotal and applies it to aggregated,
add a guard that checks totalWeight (the variable accumulated in the foreach) is
> 0 (or otherwise non-zero) and handle the zero/negative case (e.g., log/throw
or skip normalization and leave aggregated as-is) so you never call
NumOps.FromDouble with an infinite/NaN divisor; update the normalization loop
that multiplies aggregated[i] by invTotal accordingly (referencing totalWeight,
invTotal, aggregated and NumOps.FromDouble).
- Around line 196-235: The aggregation routine currently instantiates new
Random(_seed) inside the method (see usage of new Random(_seed) where
_noiseMultiplier is applied), which reproduces the same Gaussian noise each
call; change this to use a single, long-lived RNG instance (e.g., add a private
readonly Random _rng or use System.Security.Cryptography.RandomNumberGenerator
as a field in the DPFedLoRA class) and reuse it when generating u1/u2 for the
Box‑Muller transform instead of creating Random(_seed) per call; ensure the
chosen RNG is thread-safe (or protect access with a lock) and keep the existing
noise scaling logic that computes layerNoiseStd and applies NumOps.Add to
aggregated.
- Around line 116-121: MergeAdapterParameters currently computes start =
totalParams - adapterCount which can be negative when aggregatedAdapters.Length
> fullModelParameters.Length; add a guard in MergeAdapterParameters to validate
sizes before indexing: check that aggregatedAdapters.Length <=
fullModelParameters.Length (or clamp adapterCount to totalParams) and either
throw a clear ArgumentException (including both lengths) or adjust start to 0
and handle the mismatch explicitly, then proceed to copy/assign using start to
avoid invalid indexing where parameters are merged; reference symbols:
MergeAdapterParameters, fullModelParameters.Length, aggregatedAdapters.Length,
totalParams, adapterCount, and start.
In `@src/FederatedLearning/Adapters/FLoRA.cs`:
- Around line 97-114: In MergeAdapterParameters validate that
aggregatedAdapters.Length (adapterCount) does not exceed
fullModelParameters.Length (totalParams) before computing start; if adapterCount
> totalParams either throw an ArgumentException (with a clear message) or handle
by clamping/truncating adapters to totalParams, and return/abort early to avoid
start becoming negative and invalid indexing when copying into merged; ensure
you reference fullModelParameters, aggregatedAdapters, start, adapterCount and
totalParams in the guard so the merge loop and subsequent NumOps.Multiply loop
cannot run with a negative start.
- Around line 50-77: Validate all constructor hyperparameters at the start of
the FLoRA constructor: ensure alpha > 0, numAdaptedLayers > 0, layerInputDim >
0, and layerOutputDim > 0 and throw ArgumentOutOfRangeException with the
parameter name if any are invalid; then compute paramsPerLayer and
AdapterParameterCount using checked arithmetic (e.g., checked(...) or checked
context) when calculating _layerOutputDim * _rank + _rank * _layerInputDim and
when multiplying by _numAdaptedLayers to prevent overflow; finally compute
CompressionRatio as (double)AdapterParameterCount / _modelDim (after validating
_modelDim > 0).
In `@src/FederatedLearning/Verification/ProxyZKPVerifier.cs`:
- Around line 136-178: The Verify overload in ProxyZKPVerifier.cs (method
Verify(Dictionary<string, T[]> update, string commitment)) currently only does
bounds checks and returns a successful ProxyVerificationResult even though it
does not perform cryptographic commitment/nonce verification; change this so
Verify does not report success when it cannot re-verify the commitment: either
(preferred) make this overload return a failing ProxyVerificationResult (e.g.,
false with a message like "Cannot verify commitment without nonce") whenever the
cryptographic commitment/nonce path cannot be executed, or have it call the full
Verify that accepts the nonce (or a helper that performs commitment
verification) and propagate its result; update any callers/tests accordingly so
no code path silently returns IsValid=true without actual commitment
verification.
- Around line 60-63: Add explicit null/shape validation at the start of all
public verifier entry points that accept the update dictionary or layer arrays
(e.g., ComputeCommitment and SerializeUpdate) so malformed external input cannot
cause a runtime crash: check that the update Dictionary itself is non-null, that
it contains no null keys or null value arrays, and that each layer array has
expected length/shape; return a deterministic validation result (e.g., throw an
ArgumentException/ArgumentNullException or return a defined error string/status)
rather than letting subsequent code dereference nulls. Apply the same guards to
the other public methods that consume update/layer arrays (the methods covering
the ranges noted in the review) so all callers get consistent, safe failure
behavior on bad input.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
src/FederatedLearning/Adapters/DPFedLoRA.cssrc/FederatedLearning/Adapters/FLoRA.cssrc/FederatedLearning/Aggregators/FedMaAggregationStrategy.cssrc/FederatedLearning/PSI/OptimizedPrivateSetAnalytics.cssrc/FederatedLearning/Verification/ProxyZKPVerifier.cs
… strategies - MOON: contrastive loss with cosine similarity, temperature scaling, previous representation tracking per client, numerically stable log-sum-exp - FedNTD: not-true distillation loss masking out true class before KL divergence, temperature-scaled softmax, batch processing support - FedLC: logit calibration with class distribution tracking, batch calibration, helper to compute distributions from labels - FedDecorr: decorrelation loss computing correlation matrix C = X^T*X/N and squared Frobenius norm ||C - I||_F^2, diagnostic correlation method - FedAlign: feature alignment with L2, CKA, and MMD distance metrics, anchor input management, median bandwidth for RBF kernel - FedSAM: full SAM perturbation with all 5 variants (Base, FedSMOO, FedSpeed, FedLESAM, FedSCAM), gradient history for approximation, variant-specific hyperparameters Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…d BOBA DnC: orthogonal projection via Gram-Schmidt, top singular vector via power iteration on A^T*A, iterative one-at-a-time outlier removal with recomputed spectral analysis each round. FLAME: HDBSCAN-inspired clustering using pairwise cosine distances, core distances, mutual reachability distances, Prim's MST, and largest-gap cluster extraction instead of static threshold. BOBA: two-class Bayesian mixture model (honest vs Byzantine) with full EM algorithm, cross-round belief propagation, numerically stable log-space posteriors, and proper variance estimation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 79 out of 81 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ent, options, tests) DataFreeFCL: thread-safe _accumulatedImportance with lock, doc unused taskData param, guard zero totalWeight in AggregateImportance. OpenFedLLMPipeline: defensive copy for LastCheckpoint property, fix totalWeight to include default weights for missing clients and guard divide-by-zero. FederatedRLHF: Guard.NotNull + array-length validation on ComputePPOLoss and ComputeRewardsWithKLPenalty. AdvancedCompressionOptions, AsyncFederatedLearningOptions, DecentralizedFederatedOptions, FederatedPersonalizationOptions: add <value> and <remarks> with For Beginners sections to new properties. InMemoryFederatedTrainer: handle FederatedCompressionStrategy.Advanced in CompressDelta by falling back to TopK. Tests: restore unknown compression strategy test (C# enums can hold undeclared values), fix PersonalizedFederatedLearningTests to use enum values instead of strings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nalization) PersonalizedFederatedLearning: use deterministic ordinal sort instead of unreliable dictionary enumeration order for LastN layer selection; update enum doc to match. TimeVaryingTopology: cache topology per round so GetMixingWeight uses same neighbor set as GetPeers; eliminate non-atomic _roundCounter-- race condition. BufferedAsyncFederatedTrainer: guard totalWeight <= 0 from staleness discount underflow, fall back to uniform weights. FedFairOptimizer: compute totalSamples from clientIds with same GetValueOrDefault(id,1) logic so default weights for missing clients are properly counted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 82 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 71
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/Models/Options/DecentralizedFederatedOptions.cs (2)
6-22: 🧹 Nitpick | 🔵 TrivialMinor: Consider adding explicit integer values to DecentralizedTopologyType for serialization stability.
The new enum members extend
DecentralizedTopologyType, but unlikeBlockSelectionMode, they don't have explicit integer values. If these values are ever serialized/persisted, adding explicit values prevents breaking changes if members are reordered.💡 Optional: Add explicit values for serialization stability
public enum DecentralizedTopologyType { /// <summary>Gossip — randomized peer selection each round.</summary> - Gossip, + Gossip = 0, /// <summary>Ring AllReduce — bandwidth-optimal ring-based averaging.</summary> - RingAllReduce, + RingAllReduce = 1, /// <summary>DFedAvgM — decentralized averaging with momentum for faster convergence. (Sun et al., TMLR 2023)</summary> - DFedAvgM, + DFedAvgM = 2, /// <summary>DFedBCA — block coordinate ascent with partial model sharing per round. (2024)</summary> - DFedBCA, + DFedBCA = 3, /// <summary>DeTAG — gradient tracking for exact convergence in decentralized non-convex optimization. (Li et al., 2023)</summary> - DeTAG, + DeTAG = 4, /// <summary>Segmented gossip — exchange only model segments per round for bandwidth efficiency. (Bellet et al., 2024)</summary> - SegmentedGossip, + SegmentedGossip = 5, /// <summary>Time-varying topology — dynamic graph that changes each round for better mixing. (2024)</summary> - TimeVarying + TimeVarying = 6 }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Models/Options/DecentralizedFederatedOptions.cs` around lines 6 - 22, The enum DecentralizedTopologyType lacks explicit integer values which can break serialized/persisted representations if members are reordered; update the DecentralizedTopologyType declaration to assign explicit integer values to each member (e.g., start from 0 and increment consistently for Gossip, RingAllReduce, DFedAvgM, DFedBCA, DeTAG, SegmentedGossip, TimeVarying) preserving the current ordering so existing serialized data remains stable and mirror the approach used in BlockSelectionMode.
32-55:⚠️ Potential issue | 🔴 CriticalBLOCKING: Missing required default and copy constructors per Golden Pattern.
The
DecentralizedFederatedOptionsclass is missing both the required parameterless default constructor and the copy constructor. Per the coding guidelines, every Options class MUST have:
- A parameterless constructor:
public DecentralizedFederatedOptions() { }- A copy constructor that copies ALL properties and throws
ArgumentNullExceptionif other is nullAdditionally, the older properties (
Enabled,Topology,GossipFanout,MixingRoundsPerTrainingRound) are missing the required<value>tag and<remarks>with<para><b>For Beginners:</b>sections. The class<remarks>should also include a<para><b>Reference:</b>citing the relevant research papers.🔧 Proposed fix: Add constructors and complete documentation
/// <summary> /// Configuration options for decentralized (serverless) federated learning. /// </summary> /// <remarks> /// <para><b>For Beginners:</b> Standard FL uses a central server to aggregate models. Decentralized FL /// removes this server — nodes communicate directly with each other using peer-to-peer protocols. /// This eliminates single point of failure and can be more robust in edge/IoT deployments.</para> +/// <para><b>Reference:</b> See Sun et al. (TMLR 2023) for DFedAvgM, Li et al. (2023) for DeTAG, +/// and Bellet et al. (2024) for Segmented Gossip.</para> /// </remarks> public class DecentralizedFederatedOptions : ModelOptions { + /// <summary> + /// Initializes a new instance of the <see cref="DecentralizedFederatedOptions"/> class with default values. + /// </summary> + public DecentralizedFederatedOptions() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="DecentralizedFederatedOptions"/> class by copying another instance. + /// </summary> + /// <param name="other">The instance to copy.</param> + /// <exception cref="ArgumentNullException">Thrown when <paramref name="other"/> is null.</exception> + public DecentralizedFederatedOptions(DecentralizedFederatedOptions other) + { + ArgumentNullException.ThrowIfNull(other); + Enabled = other.Enabled; + Topology = other.Topology; + GossipFanout = other.GossipFanout; + MixingRoundsPerTrainingRound = other.MixingRoundsPerTrainingRound; + DFedAvgMMomentum = other.DFedAvgMMomentum; + DFedBCANumBlocks = other.DFedBCANumBlocks; + DFedBCASelectionStrategy = other.DFedBCASelectionStrategy; + DeTAGLearningRate = other.DeTAGLearningRate; + SegmentedGossipNumSegments = other.SegmentedGossipNumSegments; + TimeVaryingSeed = other.TimeVaryingSeed; + } + /// <summary> /// Gets or sets whether decentralized mode is enabled. Default: false. /// </summary> + /// <value>True to enable decentralized (peer-to-peer) mode; false to use centralized aggregation.</value> + /// <remarks> + /// <para><b>For Beginners:</b> When enabled, clients communicate directly with each other + /// instead of through a central server. This is useful in edge computing scenarios where + /// a central server may be unavailable or undesirable.</para> + /// </remarks> public bool Enabled { get; set; } = false; /// <summary> /// Gets or sets the topology type. Default: Gossip. /// </summary> + /// <value>The communication topology used for peer-to-peer model exchange.</value> + /// <remarks> + /// <para><b>For Beginners:</b> The topology determines how nodes discover and communicate + /// with each other. Gossip is simple and robust; RingAllReduce is bandwidth-optimal; + /// newer methods like DFedAvgM and DeTAG offer better convergence guarantees.</para> + /// </remarks> public DecentralizedTopologyType Topology { get; set; } = DecentralizedTopologyType.Gossip; /// <summary> /// Gets or sets the gossip fanout (number of random peers per round). Default: 2. /// </summary> + /// <value>Number of peers each node contacts per gossip round. Must be positive.</value> + /// <remarks> + /// <para><b>For Beginners:</b> Higher fanout means faster information spread but more + /// bandwidth usage. A fanout of 2 is a common default that balances speed and efficiency.</para> + /// </remarks> public int GossipFanout { get; set; } = 2; /// <summary> /// Gets or sets the number of mixing rounds per training round. Default: 3. /// </summary> + /// <value>Number of communication rounds between local training steps.</value> /// <remarks> + /// <para><b>For Beginners:</b> After each local training step, nodes average their models + /// with peers multiple times. More mixing improves convergence (models become more similar) + /// but increases communication cost.</para> - /// More mixing rounds improve convergence but increase communication cost. /// </remarks> public int MixingRoundsPerTrainingRound { get; set; } = 3;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Models/Options/DecentralizedFederatedOptions.cs` around lines 32 - 55, Add a parameterless default constructor and a copy constructor on DecentralizedFederatedOptions (e.g., public DecentralizedFederatedOptions() { } and public DecentralizedFederatedOptions(DecentralizedFederatedOptions other) { if (other == null) throw new ArgumentNullException(nameof(other)); Enabled = other.Enabled; Topology = other.Topology; GossipFanout = other.GossipFanout; MixingRoundsPerTrainingRound = other.MixingRoundsPerTrainingRound; }) and update the XML documentation for each property (Enabled, Topology, GossipFanout, MixingRoundsPerTrainingRound) to include a <value> tag and a <remarks> section with a <para><b>For Beginners:</b>...</para> plus add a class-level <remarks> that includes a <para><b>Reference:</b> citing the relevant papers; ensure all property names used match the class members exactly.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (66)
src/FederatedLearning/Adapters/DPFedLoRA.cssrc/FederatedLearning/Adapters/FLoRA.cssrc/FederatedLearning/Adapters/FedMeZO.cssrc/FederatedLearning/Adapters/FedPETuning.cssrc/FederatedLearning/Adapters/FederatedAdapterTuning.cssrc/FederatedLearning/Adapters/HierarchicalFedLoRA.cssrc/FederatedLearning/Adapters/SparseLoRA.cssrc/FederatedLearning/Aggregators/BobaAggregationStrategy.cssrc/FederatedLearning/Aggregators/BucketingAggregationStrategy.cssrc/FederatedLearning/Aggregators/DivideAndConquerAggregationStrategy.cssrc/FederatedLearning/Aggregators/FLTrustAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedAaAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedAlignAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedDecorrAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedLcAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedMaAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedNtdAggregationStrategy.cssrc/FederatedLearning/Aggregators/FedSamAggregationStrategy.cssrc/FederatedLearning/Aggregators/FlameAggregationStrategy.cssrc/FederatedLearning/Aggregators/MoonAggregationStrategy.cssrc/FederatedLearning/Aggregators/OptiGradTrustAggregationStrategy.cssrc/FederatedLearning/Alignment/FederatedDPO.cssrc/FederatedLearning/Alignment/FederatedRLHF.cssrc/FederatedLearning/Alignment/OpenFedLLMPipeline.cssrc/FederatedLearning/Compression/FedDTCompressor.cssrc/FederatedLearning/Compression/FedKDCompressor.cssrc/FederatedLearning/Compression/FetchSGDCompressor.cssrc/FederatedLearning/Compression/SignSGDCompressor.cssrc/FederatedLearning/Compression/TopKSparsificationCompressor.cssrc/FederatedLearning/ContinualLearning/DataFreeFCL.cssrc/FederatedLearning/ContinualLearning/FedAGCContinualLearning.cssrc/FederatedLearning/ContinualLearning/FedCILContinualLearning.cssrc/FederatedLearning/ContinualLearning/FederatedExperienceReplay.cssrc/FederatedLearning/Decentralized/DFedAvgMProtocol.cssrc/FederatedLearning/Decentralized/DFedBCAProtocol.cssrc/FederatedLearning/Decentralized/DeTAGProtocol.cssrc/FederatedLearning/Decentralized/SegmentedGossipProtocol.cssrc/FederatedLearning/Decentralized/TimeVaryingTopology.cssrc/FederatedLearning/Fairness/AgnosticFairnessObjective.cssrc/FederatedLearning/Fairness/FedFairOptimizer.cssrc/FederatedLearning/Fairness/LightweightShapleyEvaluator.cssrc/FederatedLearning/Fairness/QFairFederatedLearning.cssrc/FederatedLearning/Fairness/TiltedERMFairness.cssrc/FederatedLearning/PSI/OptimizedPrivateSetAnalytics.cssrc/FederatedLearning/Personalization/FedAGHNPersonalization.cssrc/FederatedLearning/Personalization/FedBABUPersonalization.cssrc/FederatedLearning/Personalization/FedCPPersonalization.cssrc/FederatedLearning/Personalization/FedPACPersonalization.cssrc/FederatedLearning/Personalization/FedRoDPersonalization.cssrc/FederatedLearning/Personalization/FedSelectPersonalization.cssrc/FederatedLearning/Personalization/KNNPersonalization.cssrc/FederatedLearning/Personalization/PFedGatePersonalization.cssrc/FederatedLearning/Personalization/PersonalizedFederatedLearning.cssrc/FederatedLearning/Privacy/ShuffleModelDP.cssrc/FederatedLearning/Trainers/AsyncFedEDTrainer.cssrc/FederatedLearning/Trainers/BufferedAsyncFederatedTrainer.cssrc/FederatedLearning/Trainers/InMemoryFederatedTrainer.cssrc/FederatedLearning/Trainers/OneShotFederatedTrainer.cssrc/FederatedLearning/Trainers/SemiAsyncFederatedTrainer.cssrc/FederatedLearning/Verification/ProxyZKPVerifier.cssrc/Models/Options/AdvancedCompressionOptions.cssrc/Models/Options/AsyncFederatedLearningOptions.cssrc/Models/Options/DecentralizedFederatedOptions.cssrc/Models/Options/FederatedPersonalizationOptions.cstests/AiDotNet.Tests/FederatedLearning/FederatedLearningInvalidConfigurationTests.cstests/AiDotNet.Tests/FederatedLearning/PersonalizedFederatedLearningTests.cs
5e73977 to
66cc868
Compare
- Aggregators: null guards, defensive copies, deep copy for single-client, deterministic iteration, non-finite weight validation, threshold consistency - Compression: error feedback residual fix, shape validation, null guards, layer delta validation - Adapters: model dim validation, checked arithmetic for overflow, adapter length validation, crypto RNG documentation - Privacy: clip-norm noise calibration, strict aggregation validation, Laplace fallback for delta=0 - Alignment: client model structure validation, DPO layer validation - Personalization: gradient length strict checks, weight renormalization, deterministic ordering, prototype validation, defensive copies - Trainers: throw NotSupportedException for unsupported compression, defensive copy on buffer submit, weighted barrier aggregation, enum/array validation - Continual Learning: null guards, synthetic param validation, NumOps.Zero initialization - Fairness: non-finite rejection, Shapley zero-norm handling, proper tilt anchor - Verification: commitment validation, certificate constructor guards Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 80 out of 82 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (7)
src/FederatedLearning/Personalization/FedBABUPersonalization.cs:1
- The head/body split in
MaskHeadGradientsdepends onDictionarykey enumeration order (gradients.Keys.ToArray()), which is not stable across runtimes/targets and can cause the wrong layers to be frozen. Sort the layer names deterministically (e.g., ordinal sort like other personalization utilities in this PR) before computingheadStartand applying the mask.
src/FederatedLearning/Trainers/BufferedAsyncFederatedTrainer.cs:1 _currentGlobalRoundis written viaInterlocked.IncrementinAggregateBuffer, but read here without an atomic/volatile read and outside the lock; this can compute inconsistent staleness under concurrency. Read the round usingVolatile.Read(ref _currentGlobalRound)(or move the staleness computation inside the same lock that protects the buffer, or use a dedicated atomic access pattern) to make the staleness calculation thread-safe.
src/FederatedLearning/Trainers/BufferedAsyncFederatedTrainer.cs:1AggregateBufferderiveslayerNamesand layer lengths solely fromsnapshot[0]. If later buffered updates contain extra layers (orsnapshot[0]is missing a layer), those layers are silently dropped from the aggregation output. Consider validating that all buffered updates have the same layer set/lengths atSubmitUpdate, or compute the union of layer names (and enforce consistent lengths) when aggregating.
src/FederatedLearning/Decentralized/DFedAvgMProtocol.cs:1neighborModel[layerName]can throwKeyNotFoundExceptionwhen a neighbor model is missing a layer present incurrentModel. UseTryGetValue(layerName, out var np)with a clear exception (or an explicit skip/validation step) to avoid runtime crashes and make layer-schema mismatches diagnosable.
src/FederatedLearning/Trainers/InMemoryFederatedTrainer.cs:1- The
NotSupportedExceptionguidance mentions strategy names that don’t match the enum (RandomSparsification,Quantization). This is misleading for users debugging config. Update the message to reference the actual enum values (e.g.,RandomK,UniformQuantization,StochasticQuantization) and/or point toAdvancedCompressionStrategy/AdvancedCompressionOptionsif that’s the intended configuration surface.
src/FederatedLearning/Decentralized/TimeVaryingTopology.cs:1 GetOrGenerateTopologyForRoundmutates_roundCounter,_cachedTopology, and_lastQueriedRoundwithout synchronization. IfGetPeers/GetMixingWeightare called concurrently (or re-entrantly), this can produce inconsistent topologies and mixing weights. Consider avoiding mutation by refactoringGenerateTopologyto accept an explicitroundparameter (no shared state), or guard this block (and cache reads) with a lock.
src/FederatedLearning/Personalization/FedSelectPersonalization.cs:1- The sigmoid computation
1 / (1 + exp(-x))can overflow/underflow for large-magnitude logits (which can occur after manyUpdateMasksteps), leading tomaskbecoming 0/1 unexpectedly orexpoverflow. Use a numerically stable sigmoid implementation (branching on sign oflogits[i], or clamping logits to a safe range) in all places where sigmoid is computed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Regenerated YAML source generator output after merging master to include all type registrations from both branches. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
are we sure this still works in light of recent events? |
Summary
Comprehensive Federated Learning v3 implementation covering all 10 sub-issues of #536. Adds 55+ new production-ready classes across 10 FL research areas with paper-accurate defaults and full XML documentation. Includes a 27-task production-readiness audit fixing critical algorithmic gaps, security issues, and anti-patterns.
Issues Addressed
Production-Readiness Audit (27 tasks completed)
Critical fixes applied across all FL subsystems:
Aggregation (#32): Added local training methods (contrastive loss, NTD, logit calibration, decorrelation, alignment, SAM perturbation) to 6 FedAvg wrapper strategies
Byzantine Robustness (#33-35): DnC SVD-based spectral analysis, FLAME HDBSCAN clustering, BOBA Bayesian mixture model
Privacy (#36-37, #46, #54): ShuffleModelDP actual shuffling, ProxyZKP cryptographic commitments, DPFedLoRA correct sensitivity formula with Renyi DP accounting, OPSA cryptographic RNG for secret shares with power-of-2 validation
Adapters (#38-41, #52, #55): FedMeZO zeroth-order gradient estimation, HierFedLoRA two-tier aggregation, SparseLoRA top-k sparsification, FLoRA paper-accurate stacking, FedPETuning method-aware PEFT aggregation, adapter forward pass, PPO/GAE/DPO gradient computation, OpenFedLLM stage-aware pipeline
Compression (#42, #44, #53, #57): One-Shot ensemble distillation, FedDT value-based splits, FedKD server-side distillation, SignSGD error feedback + SIGNUM momentum, TopK sparse representation, FetchSGD 2-universal hash family
Decentralized (#43, #58): DeTAG neighbor tracker averaging, DFedBCA importance-based block selection with gradient magnitude tracking
Personalization (#56): All 8 strategies fixed — FedBABU head extraction/initialization, FedRoD balanced softmax, FedCP routing/load-balancing, kNN-Per cosine-similarity distance-weighted combined prediction, FedSelect STE mask learning, pFedGate loss-based gate updates, FedAGHN gradient similarity adaptive weights, FedPAC prototype computation and calibration loss
Continual Learning (#45, #58): DataFreeFCL synthetic data generation, FedAGC Fisher information approximation with adaptive per-parameter correction
Code Quality (#47-51): Fixed hardcoded double anti-pattern, deterministic seed reuse, string-based strategy params, missing interface implementations, strategy-specific hyperparameters in options classes
Key Design Decisions
FederatedLearningComponentBase<T>for NumOps accessParameterDictionaryAggregationStrategyBase<T><b>For Beginners:</b>documentation sectionsTest plan
Closes #900, Closes #901, Closes #902, Closes #903, Closes #904, Closes #905, Closes #906, Closes #907, Closes #908, Closes #909
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Lines Changed: ~14,000+