Qora-FL
Quorum-Oriented Robust Aggregation for Federated Learning
Problem
Federated learning systems are fragile under adversarial or faulty clients. Standard aggregation methods silently fail under model poisoning, gradient manipulation, or non-IID drift.
Qora-FL provides Rust-backed robust aggregation methods for federated learning. Each method has explicit assumptions and tolerance conditions, documented below. The included experiments evaluate selected attacks with up to 30% adversarial clients.
Currently implemented
Behavior available through tested public paths:
- FedAvg baseline, with optional sample-count weighting
- Coordinate-wise median
- Coordinate-wise trimmed mean
- Krum and Multi-Krum, with their preconditions enforced
- Input validation: dimensions, non-finite rejection, client-ID alignment
- Krum quorum enforcement (
n >= 2f + 3) - Multi-Krum selection-bound enforcement (
1 <= m <= n - 2f - 2) - Reputation tracking and participation gating
- Optional norm-bound filtering, opt-in and off by default
- Fail-closed behavior when filtering rejects every client
- Reputation numeric invariants (scores finite and within
[0, 1]) - Rust API and Python bindings
- Flower-compatible strategy adapter
- Rust CI across three operating systems; Python CI including the adapter
Experimental or planned
Present in the codebase but not wired into the normal aggregation path, or not yet implemented:
- Reputation-weighted robust aggregation (cubic influence weighting exists as a utility only)
- Adaptive or percentile-derived norm bounds (today the caller picks a fixed bound)
- Audit records in Python. The Rust API returns a typed, versioned record from
aggregate_with_audit; the bindings do not expose it yet, so that a binding design is not frozen while the schema is still experimental. - Cross-platform bit-identical end-to-end aggregation
- Independent security review
- Real-world deployment validation
- Additional attack and dataset evaluations
Architecture
Clients produce model updates
│
▼
┌───────────────────────────┐
│ Qora Robust Aggregator │
│ │
│ ┌─────────────────────┐ │
│ │ Trimmed Mean │ │
│ │ Median │ │
│ │ Krum (n ≥ 2f+3) │ │
│ │ FedAvg (baseline) │ │
│ └─────────────────────┘ │
└─────────────┬─────────────┘
│
Deviation signal (not trust)
│
▼
┌─────────────────┐
│ Reputation │
│ Manager │
│ │
│ Scores / Gates │
│ / Weighting │
└────────┬────────┘
│
▼
Verified Global Update
flowchart LR
C1[Client 1] --> U[Client Updates]
C2[Client 2] --> U
Cn[Client N] --> U
U --> A[Qora Aggregator]
A -->|Deviation Signal| R[Reputation Manager]
R -->|Scores / Gates| A
A --> G[Aggregated Model]
style A fill:#f3f6ff,stroke:#4a6cf7,stroke-width:2px
style R fill:#fff7e6,stroke:#f59e0b,stroke-width:2px
Determinism
Qora-FL uses integer arithmetic for Krum distance and score calculations after BFP-16 encoding (block floating-point, 16-bit mantissas, per-vector shared exponent). Identical BFP-encoded inputs therefore produce deterministic Krum scores and rankings.
Full cross-platform bit-identical aggregation is not currently guaranteed. The pipeline is only partly integer:
| Stage | Arithmetic |
|---|---|
| BFP-16 encoding | floating-point (log2, scaling, rounding) |
| Krum distances and scores | integer (i32 shifts, i64 accumulation, saturating) |
| Krum result | the selected original f32 update |
| Multi-Krum result | mean of selected original updates, in f32 |
| Median, trimmed mean, FedAvg | floating-point throughout |
So the determinism claim covers the scoring stage, not the end-to-end result.
Selection rankings are reproducible; the returned values are f32 and inherit
whatever the encoding and averaging stages do.
Reputation
Reputation is not trust. It is a deviation-derived signal measuring how far a client's update sits from the aggregate that round.
What it does today:
- Tracks scores across rounds, persisted via JSON serialization
- Gates participation: clients below the configured threshold are excluded before aggregation
- Fails closed: if every identified client is below the threshold, the round
returns
AllUpdatesRejectedrather than reinstating them - Maintains numeric invariants: scores are finite and within
[0, 1], and invalid mutation inputs are rejected rather than clamped
Norm-bound filtering does not feed reputation: a client excluded by the bound is left out of the round and its score is untouched.
What it does not do yet: cubic influence weighting (min(rep^3, 0.8)) and
its cap are available as utilities, and a caller may use those values directly.
The primary aggregation path does not currently consume them, so the cap does
not provide protection for the default aggregation workflow.
Reputation-weighted robust aggregation is roadmap work.
Details that affect configuration:
- Unknown clients start at the default score of 0.5. A ban threshold above 0.5 therefore rejects every first-round participant -- which now fails the round rather than passing it through ungated.
- Through the Flower adapter, reputation is computed from the complete flattened model, and each client receives at most one update per round. The adapter performs no gating of its own.
Quick Start (Python + Flower)
Flower is an optional extra. The core bindings work without it:
import qora, ByzantineAggregator, and ReputationManager all function with
Flower absent. Only qora.QoraStrategy requires it, and importing it without
Flower installed raises an ImportError naming the extra to install.
# Quote the extra: some shells (zsh, fish) treat brackets as globs.
pip install "qora-fl[flower]"
Supported range flwr>=1.5,<2, upper-bounded because the adapter targets
Flower's 1.x strategy interface. CI tests both ends explicitly: 1.5.0 and
1.32.1. Versions in between are covered by the declared promise but are not
individually exercised.
import flwr as fl
from qora import QoraStrategy
strategy = QoraStrategy(
aggregation_method="trimmed_mean",
trim_fraction=0.2,
min_fit_clients=5,
# norm_bound=None by default: no filtering, no norm computation.
)
fl.server.start_server(
server_address="0.0.0.0:8080",
config=fl.server.ServerConfig(num_rounds=10),
strategy=strategy,
)
QoraStrategy inherits from FedAvg, so standard Flower parameters
(fraction_fit, min_fit_clients, initial_parameters, accept_failures,
fit_metrics_aggregation_fn) are accepted. It is a compatibility adapter,
not a behavioral replacement for every Flower built-in strategy.
What the adapter does:
- One client result becomes one complete Rust update. Each client's model is flattened into a single update, aggregated in one call, then split back into the original layer shapes. Aggregating layer by layer would let a selection method pick a different client per layer and return a model no client submitted.
- Validates the complete model structure against the first client as
reference: layer count, per-layer shape, floating-point dtype, and finiteness.
A malformed successful result raises
ValueErrornaming the client and layer rather than being dropped -- discarding it would change the round's effective adversarial fraction. - Aggregates at
float32.float64input is accepted and converted; its extra precision is not preserved. Integer parameter arrays are rejected rather than silently coerced into trainable floats. - FedAvg weights by
num_examples. Robust methods (median, trimmed mean, Krum, Multi-Krum) treat each accepted client update as one participant --num_examplesis never reinterpreted as an algorithmic weight there, since an attacker claiming a large sample count would otherwise gain proportional influence over a median. - Honors
accept_failures. Reported failures refuse the round when it is False, returning(None, {})without invoking Rust, moving reputation, or calling the metrics callback. - Aggregates metrics only through
fit_metrics_aggregation_fn. With none configured it returns{}. Reputation is available throughQoraStrategy.get_reputation(client_id). - Performs no gating of its own. Reputation gating and updates happen once, in the Rust aggregator.
- Enables norm-bound filtering only when
norm_boundis passed, and applies it to the complete flattened model rather than to individual layers.
Python (Standalone)
pip install qora-fl
import numpy as np
from qora import ByzantineAggregator
agg = ByzantineAggregator("trimmed_mean", 0.3)
# 7 honest clients + 3 Byzantine attackers
updates = [np.array([[1.0, 2.0]], dtype=np.float32) for _ in range(7)]
updates += [np.array([[100.0, 200.0]], dtype=np.float32) for _ in range(3)]
result = agg.aggregate(updates)
# Result is [1.0, 2.0] -- attackers rejected
Norm-bound filtering
Opt-in, and off by default. Without norm_bound, no norm is computed and
behavior is unchanged.
from qora import ByzantineAggregator
agg = ByzantineAggregator("median", 0.0, norm_bound=10.0)
print(agg.norm_bound) # 10.0, or None when disabled
Nonedisables it. A supplied bound must be finite and strictly positive; zero, negative, NaN, and infinite bounds raiseValueErrorrather than being clamped.- The norm is computed in
f64and compared inclusively -- a norm exactly equal to the bound participates. - A rejected update takes its client ID and its FedAvg sample weight with it, so a rejected weight never reaches the numerator or the denominator.
- A rejected update does not affect reputation. A large norm is a policy violation, not proof of malicious intent; this filter does not claim to detect Byzantine behavior.
- If every update is rejected -- by reputation gating, by the bound, or by a
mixture -- the round raises
ValueErrorrather than aggregating a partial or best-effort result. - Method quorum is checked after filtering, so filtering can shrink a valid cohort into one Krum or Multi-Krum refuses. That is reported, not repaired.
Through the Flower adapter, pass norm_bound= to QoraStrategy. The bound then
applies to each client's complete flattened model rather than to individual
layers.
Audit records are Rust-only in 0.4.0
The Rust API returns a typed, versioned AggregationAuditEntry from
aggregate_with_audit, recording one decision per submitted update with typed
rejection reasons. The Python bindings do not expose it. Mirroring the schema
as a hierarchy of Python classes would freeze a binding design while the schema
is still experimental; a later release can return the serialized shape as a
dictionary instead.
What Python does expose is the configuration (norm_bound) and the behavior it
produces: an over-bound update is excluded, and a round that rejects everything
raises ValueError. The per-client reasons behind that error are not available
from Python yet.
Reputation Tracking
from qora import ReputationManager
rep = ReputationManager(ban_threshold=0.2)
rep.reward("hospital_A", 0.02)
rep.penalize("hospital_bad", 0.08)
print(rep.is_banned("hospital_bad")) # True after enough penalties
print(rep.active_clients()) # Only non-banned clients
# Persist between server restarts
json_state = rep.to_json()
rep2 = ReputationManager.from_json(json_state, ban_threshold=0.2)
Rust
cargo add qora-fl
use qora_fl::{ByzantineAggregator, AggregationMethod};
use ndarray::array;
let mut agg = ByzantineAggregator::new(AggregationMethod::TrimmedMean, 0.3);
let updates = vec![
array![[1.0, 2.0]], // Honest
array![[1.1, 2.1]], // Honest
array![[0.9, 1.9]], // Honest
array![[100.0, 200.0]], // Byzantine attacker
];
let result = agg.aggregate(&updates, None).unwrap();
// Result is close to [1.0, 2.0], attacker ignored
Individual Functions
use qora_fl::{trimmed_mean, median, fedavg};
use ndarray::array;
let updates = vec![array![[1.0]], array![[2.0]], array![[3.0]]];
let tm = trimmed_mean(&updates, 0.2).unwrap();
let med = median(&updates).unwrap();
let avg = fedavg(&updates, None).unwrap();
Aggregation methods and their assumptions
n is the number of accepted client updates; f is the configured Byzantine
bound; m is the number of Multi-Krum candidates selected.
| Method | Enforced precondition | Assumption for robustness |
|---|---|---|
TrimmedMean |
0.0 <= trim_fraction <= 0.5, and at least one value must survive trimming |
Depends on trim fraction, adversarial proportion, attack model, and the distribution of honest updates |
Median |
none beyond shared input validation | Strictly fewer than 50% adversarial values per coordinate |
Krum |
n >= 2f + 3 |
Blanchard et al. (2017) |
MultiKrum |
n >= 2f + 3 and 1 <= m <= n - 2f - 2 |
Blanchard et al. (2017) |
FedAvg |
weights, when supplied, must be finite and non-negative with a positive total | None. Conventional sample-weighted baseline; provides no Byzantine robustness by itself |
Trimmed mean removes the configured fraction of the smallest and largest
values independently in each coordinate. A trim fraction is not an attacker
percentage: trim_fraction = 0.2 does not establish tolerance of 30% malicious
clients, and no such universal guarantee is claimed here.
Median requires an honest majority per coordinate. Exactly 50% adversarial is not strictly fewer than half, so "tolerates 50%" would be wrong.
Krum rejects configurations violating n >= 2f + 3 with
InsufficientQuorum. There is no best-effort path below quorum.
Multi-Krum rejects an explicit m outside 1..=n - 2f - 2 with
InvalidMultiKrumSelection. When m is omitted, Qora-FL uses
min(3, n - 2f - 2); explicit unsafe values are rejected rather than silently
reduced.
Experiments
Qora-FL includes an attack-evaluation script for comparing aggregation methods across selected attacks and adversarial-client fractions. The repository does not currently publish verified benchmark results from a pinned dataset artifact and repeated multi-seed evaluation. Run the included experiment to evaluate the methods in your own environment.
See the root README for the full configuration and its limitations.
# Aggregation overhead (Python)
python examples/benchmark_overhead.py
# MNIST poisoning: FedAvg vs Qora-FL under 30% attack
pip install matplotlib scikit-learn
python examples/mnist_poisoning_demo.py
# Criterion benchmarks (Rust)
cargo bench
# Rust examples
cargo run --example quickstart
cargo run --example compare_methods
Validation status
Qora-FL is currently validated through:
- the Rust unit and integration test suite
- Python binding and Flower adapter tests
- cross-platform Rust CI (Linux, macOS, Windows) and Python CI
- Flower compatibility tests at both ends of the supported version range
- algorithm preconditions enforced in code and covered by tests
- the included examples and benchmarks, under their documented configurations
This is engineering validation. It is not an independent security review, and it does not establish robustness outside each algorithm's stated assumptions. See SECURITY.md for the boundaries.
Background
The aggregation algorithms in Qora-FL (trimmed mean, coordinate-wise median, Krum, Multi-Krum) are standard Byzantine-robust methods from the federated learning literature [1][2].
Project history
Qora-FL grew out of aggregation and distributed-trust experiments explored in the earlier QRES project (CavinKrenik/QRES_RaaS, historical work). Design choices here -- deviation-derived reputation, the BFP-16 scoring path, adaptive trim fraction, and the influence cap -- were informed by those earlier experiments and long-horizon simulations.
Qora-FL is a separate implementation with its own tests, experiments, and assumptions. Results or guarantees from QRES do not automatically apply to Qora-FL, and none of the evidence in this repository is inherited from it.
Design Philosophy
Qora-FL treats aggregation as a decision process, not a statistical convenience. The system favors explicit constraints, observable signals, and predictable failure modes over opaque optimization.
Roadmap
- Weighted robust aggregation (reputation-scaled trimmed mean)
- Adaptive trim selection based on detected attack intensity
- TensorFlow Federated adapter
- Formal verification experiments for the integer scoring path
License
Licensed under either of Apache License, Version 2.0 or MIT License at your option.
Metadata
Release files for qora-fl 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| qora_fl-0.4.0.tar.gz | 195.4 kB | With provenance | Details for qora_fl-0.4.0.tar.gz |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| qora_fl-0.4.0-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details for qora_fl-0.4.0-cp38-abi3-win_amd64.whl |
| qora_fl-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.34+ x86-64 | Details for qora_fl-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl |
| qora_fl-0.4.0-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details for qora_fl-0.4.0-cp38-abi3-macosx_11_0_arm64.whl |
Total release size: 1.5 MB
Release files / qora_fl-0.4.0.tar.gz
| Download URL | qora_fl-0.4.0.tar.gz |
|---|---|
| Size | 195.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
203b9d378d6013afd087c916c4cc17b95e33f78ce59a83f5a395bca0af24ee23
|
|
BLAKE2b-256 checksum How to use checksums |
529a73d5d5bb4557ad7763a380094f3a4fcda045a7c46820d78bbbda7fa3797e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / qora_fl-0.4.0-cp38-abi3-win_amd64.whl
| Download URL | qora_fl-0.4.0-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 342.5 kB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
366326eda7a655a37e8e4ea97f1867fb75434d67534ab467f7f4c5c7c41138a7
|
|
BLAKE2b-256 checksum How to use checksums |
f194cb76c7aba6658d5a7f290e35542e5a2b490ba49c099caec88ad9d837bc46
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / qora_fl-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl
| Download URL | qora_fl-0.4.0-cp38-abi3-manylinux_2_34_x86_64.whl |
|---|---|
| Size | 525.1 kB |
| Tags | CPython 3.8 Linux glibc 2.34+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f53829105227a23349f0888188f7cf3bb64c98256fa42629a6c9bcde42e03b7c
|
|
BLAKE2b-256 checksum How to use checksums |
4b2f45a9dc4bbc85af2fced767ba2eae30131b79b4c3a2e93df52d7e65cecaea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / qora_fl-0.4.0-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | qora_fl-0.4.0-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 453.9 kB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8215390b81cc18cc9942490ae7373737bb880557151ffd99c833a14e19ac2971
|
|
BLAKE2b-256 checksum How to use checksums |
2dfefa8f9a0500f89b94b2ed90defec2c5a25a2d64f5b7bfb99d221f7103b997
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logSecurity for qora-fl 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 4 files in this release have been uploaded with provenance metadata.
Attestation for qora_fl-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.