flip.nvflare.components

FLIP Components module containing reusable FL components.

Components include event handlers, model locators, JSON generators, and persistence utilities.

Exports:
  • ClientEventHandler: Client-side event handler

  • ServerEventHandler: Server-side event handler

  • PTModelLocator: PyTorch model locator

  • InitialPTModelLocator: PyTorch model locator for initial models with safehouse fallback

  • EvaluationPTModelLocator: PyTorch model locator for evaluation workflows (multi-model COLLECTION)

  • EvaluationModelLocator: Single-model checkpoint locator for Client-API evaluation (standard interface)

  • InitialCheckpointPTModelPersistor: Seeds the initial global model from a server-side backbone checkpoint

  • KeepOnlyVars: Include-only DXO filter (keep matching weights) — head-only per-round updates

  • TrimBroadcastVars: Server-side filter — broadcast only the trainable vars after round 0

  • TrimEvalBroadcastVars: Server-side filter — broadcast only the trainable vars for cross-site eval

  • ReconstructFullModel: Client-side filter — rebuild the full model from a trimmed broadcast

  • ReconstructFullModelForEval: Client-side filter — rebuild the full model for train AND eval

  • ValidationJsonGenerator: Validation results JSON generator

  • EvaluationJsonGenerator: Evaluation results JSON generator

  • PersistToS3AndCleanup: S3 persistence and cleanup component

  • PercentilePrivacy: Percentile-based privacy filter

  • StagePercentilePrivacy: Stage-aware percentile-based privacy filter

  • CleanupImages: Image cleanup executor

  • FlipAnalyticsBridge: Bridges Client API analytics events to FlipEvents.SEND_RESULT

  • ClientExceptionReporter: Reports client task failures to the FLIP hub

Submodules

Classes

TrimBroadcastVars

Server-side task_data_filter: after the first round, broadcast ONLY the trainable weights

TrimEvalBroadcastVars

Server-side task_data_filter for the validate task: broadcast ONLY the trainable head.

CleanupImages

ClientExceptionReporter

Report client task failures to the FLIP hub without changing the result.

PercentilePrivacy

Stock NVFLARE PercentilePrivacy differential-privacy filter plus an off toggle.

EvaluationJsonGenerator

Evaluation results generator, recording both the metrics and the failed validate tasks.

FlipAnalyticsBridge

Translates NVFLARE analytics events fired by Client API scripts into

ClientEventHandler

ClientEventHandler is a generic component that handles system events triggered by nvflare

ServerEventHandler

ServerEventHandler is a generic component that handles system events triggered by nvflare

KeepOnlyVars

Keep ONLY the weights whose key matches include_vars (regex); drop the rest.

PersistToS3AndCleanup

EvaluationModelLocator

Locate uploaded checkpoint(s) for Client-API evaluation under the standard ModelLocator interface.

EvaluationPTModelLocator

InitialPTModelLocator

PTModelLocator

InitialCheckpointPTModelPersistor

Persistor that seeds the initial global model from a large backbone checkpoint

ReconstructFullModel

Client-side task_data_filter: rebuild the full global model from a trimmed broadcast.

ReconstructFullModelForEval

Client-side task_data_filter for BOTH train and validate: rebuild the full model.

StagePercentilePrivacy

Percentile-privacy filter that groups parameters by training stage before filtering.

ValidationJsonGenerator

Stock NVFLARE ValidationJsonGenerator, dispatched manually by FLIP's ServerEventHandler.

Package Contents

class flip.nvflare.components.TrimBroadcastVars(include_vars: str | None = None, data_kinds: list[str] | None = None)

Bases: nvflare.apis.dxo_filter.DXOFilter

Server-side task_data_filter: after the first round, broadcast ONLY the trainable weights (those whose key matches include_vars) instead of the full global model.

The server→client mirror of KeepOnlyVars (which trims the client→server update). For a frozen-backbone fine-tune the backbone is identical on every client after round 0, so re-broadcasting all ~759 MiB of it every round is pure waste: it saturates the wire and drives server-side heap growth (each broadcast is serialised into hundreds of MB-sized chunks). This trims the broadcast down to just the head after round 0; the client’s ReconstructFullModel filter merges it back onto the backbone delivered at round 0, so the trainer still receives a full state dict — no change to user training code is required.

Round 0 is passed through UNTOUCHED so every client receives the backbone once (FLIP always runs start_round == 0; the first round is where the backbone must ship). The filter is non-mutating: it builds a new dict rather than popping keys from dxo.data, which on the server aliases the retained global model — popping would corrupt the server’s own weights.

include_vars = None
pattern
process_dxo(dxo: nvflare.apis.dxo_filter.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None
_trim_to_pattern(dxo: nvflare.apis.dxo_filter.DXO, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None

Trim dxo.data to only the weight keys matching self.pattern.

Shared by the round-gated training filter and the always-trim evaluation variant (TrimEvalBroadcastVars). Non-mutating on the caller’s dict — it builds a new dict — because on the server dxo.data aliases the retained global model, so popping keys would corrupt the server’s own weights.

Parameters:
  • dxo (DXO) – the outgoing broadcast DXO whose data (a weights dict) is trimmed.

  • fl_ctx (FLContext) – the FL context, used only for logging.

Returns:

the DXO with its data trimmed to the matching (head) keys, or None (leave the DXO untouched, i.e. broadcast the full model) when the regex matches no keys.

Return type:

DXO | None

class flip.nvflare.components.TrimEvalBroadcastVars(include_vars: str | None = None, data_kinds: list[str] | None = None)

Bases: TrimBroadcastVars

Server-side task_data_filter for the validate task: broadcast ONLY the trainable head.

The evaluation counterpart of TrimBroadcastVars. Post-training cross-site validation (GlobalModelEval) re-broadcasts the full ~759 MiB global model to every client purely so it can be scored — but for a frozen-backbone fine-tune the backbone is byte-identical to the pretrained checkpoint each client already received at training round 0, so re-shipping it is pure waste. This trims the validate broadcast down to just the head; the client’s ReconstructFullModelForEval merges it back onto the backbone it cached during training, so the validator still receives a full state dict — no change to user validation code is required.

Unlike TrimBroadcastVars, the trim is unconditional: the cross-site-validation task carries no CURRENT_ROUND header (the round-gated parent would therefore never trim it), and the client always already holds the backbone by the time validation runs.

process_dxo(dxo: nvflare.apis.dxo_filter.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None
class flip.nvflare.components.CleanupImages

Bases: nvflare.apis.executor.Executor

execute(task_name: str, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext, abort_signal: nvflare.apis.signal.Signal) nvflare.apis.shareable.Shareable
class flip.nvflare.components.ClientExceptionReporter

Bases: nvflare.apis.filter.Filter

Report client task failures to the FLIP hub without changing the result.

The model ID is job-scoped data and is therefore resolved exclusively from meta.json['custom_props']['model_id']. The sending client is obtained from NVFLARE’s peer context, which is populated before server result filters run.

flip
process(shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.shareable.Shareable
class flip.nvflare.components.PercentilePrivacy(percentile=10, gamma=0.01, data_kinds: list[str] | None = None, off: bool = False)

Bases: nvflare.app_common.filters.percentile_privacy.PercentilePrivacy

Stock NVFLARE PercentilePrivacy differential-privacy filter plus an off toggle.

Subclasses nvflare.app_common.filters.percentile_privacy.PercentilePrivacy so the percentile/gamma filtering maths (Shokri and Shmatikov, “Privacy-preserving deep learning”, CCS ‘15) tracks upstream instead of drifting from a vendored copy. The sole FLIP addition is off: it lets the filter stay wired into config_fed_client.json’s result-filter chain while being switched on/off from config (e.g. running DP-on vs DP-off experiments) without adding or removing the component.

off = False
process_dxo(dxo: nvflare.apis.dxo.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) None | nvflare.apis.dxo.DXO

Return the DXO unchanged when off; otherwise delegate to stock filtering.

Parameters:
  • dxo (DXO) – Information from the client.

  • shareable (Shareable) – The shareable the DXO belongs to.

  • fl_ctx (FLContext) – Context provided by the workflow.

Returns:

The unchanged DXO when off, else the stock-filtered DXO (or None if the stock guard clauses reject it).

Return type:

None | DXO

class flip.nvflare.components.EvaluationJsonGenerator(results_dir=PTConstants.EvalDir, json_file_name=PTConstants.EvalResultsFilename, failures_file_name=PTConstants.EvalFailuresFilename)

Bases: flip.nvflare.components.validation_json_generator.ValidationJsonGenerator

Evaluation results generator, recording both the metrics and the failed validate tasks.

Deduped against FLIP’s ValidationJsonGenerator: it inherits the manual-dispatch mechanism (the no-op handle_event — ServerEventHandler drives handle_evaluation_events) and the numpy-float JSON encoding / path-safe output. It differs in the results directory / file name, in tracking per-task failures, and in how results are keyed.

Results shape. Each entry is keyed by data site, then by model name. Which controller drives the run decides where the model name comes from:

  • GlobalModelEval (evaluation_client_api) sends one validate task per (model, client) and sets AppConstants.MODEL_OWNER, so results nest under it. Keying on the client alone would let each model’s metrics overwrite the previous model’s (FLIP#754).

  • FLIP’s own ModelEval (evaluation) sends every model in one task and sets no MODEL_OWNER; the evaluator returns its own model-keyed dict, which is stored as-is.

Failures. Both eval controllers fire VALIDATION_RESULT_RECEIVED before they inspect the result’s return code, so a failed or aborted task arrives here carrying no DXO. Each such task is recorded in _eval_failures and written to PTConstants.EvalFailuresFilename, and all_tasks_failed() lets ServerEventHandler fail the run rather than report success on an empty results file (FLIP#754).

_eval_results: dict
_eval_failures: list[dict] = []
_failures_file_name = 'evaluation_failures.json'
all_tasks_failed() bool

Whether every validate task this run produced a failure and none produced metrics.

Returns:

True when at least one task failed and none succeeded. False for a clean run, for a

partial failure (there are still results worth uploading), and for a job that ran no validate tasks at all — a training job’s END_RUN must not be reported as a failure.

Return type:

bool

_record_failure(fl_ctx: nvflare.apis.fl_context.FLContext, model_owner: str | None, data_client: str, return_code: str) None

Record one failed validate task so it survives into the results bundle.

_record_result(model_owner: str | None, data_client: str, metrics: Any) None

Store one client’s metrics, nesting under the model name when the controller supplies one.

_handle_validation_result(fl_ctx: nvflare.apis.fl_context.FLContext) None

Sort one received validate result into either the metrics store or the failure list.

_write_json(fl_ctx: nvflare.apis.fl_context.FLContext, run_dir: str, file_name: str, payload: Any, description: str) None

Write one json artefact into the run’s evaluation results directory.

handle_evaluation_events(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None

FLIP integration point — ServerEventHandler dispatches events here, not via handle_event.

class flip.nvflare.components.FlipAnalyticsBridge

Bases: nvflare.apis.fl_component.FLComponent

Translates NVFLARE analytics events fired by Client API scripts into FLIP’s FlipEvents.SEND_RESULT federated events.

Trainer scripts written against the NVFLARE Client API publish metrics through SummaryWriter / flare.log. The InProcessClientAPIExecutor forwards those records onto the client process as ANALYTIC_EVENT_TYPE events. This widget rewraps each record as a DataKind.METRICS DXO and re-fires it under FlipEvents.SEND_RESULT with federation scope, so the server-side controller (ScatterAndGather) keeps receiving metrics through the same pipeline that send_metrics_value used to drive.

handle_event(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None
class flip.nvflare.components.ClientEventHandler

Bases: nvflare.apis.fl_component.FLComponent

ClientEventHandler is a generic component that handles system events triggered by nvflare or custom flip events. It executes logic inside its own event handler but may also call other component’s event handlers directly to help overcome the non-deterministic order in which nvflare handles events.

Args:

Raises:

handle_event(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None
class flip.nvflare.components.ServerEventHandler(model_id: str = '', validation_json_generator_id: str = 'json_generator', persist_and_cleanup_id: str = 'persist_and_cleanup', flip: flip.FLIP = FLIP())

Bases: nvflare.apis.fl_component.FLComponent

ServerEventHandler is a generic component that handles system events triggered by nvflare or custom flip events. It executes logic inside its own event handler but may also call other component’s event handlers directly to overcome the non-deterministic order in which nvflare handles events i.e handling ValidationJsonGenerator component events.

Parameters:
  • model_id (str, optional) – ID of the model. When empty, the model ID is resolved lazily from job metadata via get_flip_model_id on first status update.

  • validation_json_generator_id (str, optional) – Component ID for the validation JSON generator.

  • persist_and_cleanup_id (str, optional) – Component ID for the persist-and-cleanup component.

  • flip (FLIP, optional) – FLIP client instance.

_model_id_fallback = ''
_model_id: str | None = None
validation_json_generator_id = 'json_generator'
validation_json_generator = None
persist_and_cleanup_id = 'persist_and_cleanup'
persist_and_cleanup = None
flip
fatal_error = False
final_status: flip.constants.ModelStatus | None = None
_resolve_model_id(fl_ctx: nvflare.apis.fl_context.FLContext) str

Resolve model ID lazily from job metadata, falling back to the constructor arg.

Parameters:

fl_ctx (FLContext) – The FL context for the current job.

Returns:

The resolved model ID.

Return type:

str

_update_status(fl_ctx: nvflare.apis.fl_context.FLContext, status: flip.constants.ModelStatus | None) None

Resolve model ID lazily and update training status.

Parameters:
  • fl_ctx (FLContext) – The FL context for the current job.

  • status (ModelStatus | None) – The new model status to set.

_relay_round_event(fl_ctx: nvflare.apis.fl_context.FLContext, event: flip.schemas.FLLogEvent) None

Relay a stock round boundary to the hub as a typed fact.

Facts only — display text is composed hub-side. NVFLARE’s CURRENT_ROUND prop is 0-based; the wire contract is 1-based. ROUND_AGGREGATED counts come from the sticky props seeded here at round start (“0 of m”) and overwritten by the FLIP ScatterAndGather controller as it accepts client results.

Parameters:
  • fl_ctx (FLContext) – The FL context carrying the round props.

  • event (FLLogEvent) – ROUND_STARTED or ROUND_AGGREGATED.

_expected_client_count(fl_ctx: nvflare.apis.fl_context.FLContext) int | None

Best-effort count of the clients targeted this round; None when unavailable.

Parameters:

fl_ctx (FLContext) – The FL context to read the engine from.

Returns:

len(engine.get_clients()), or None when the engine cannot be read (the round then closes with the uncounted wording).

Return type:

int | None

_evaluation_wholly_failed() bool

Whether this is an evaluation job in which every validate task failed.

Training jobs wire the base ValidationJsonGenerator, which tracks no failures, so the isinstance check also serves as the “is this an evaluation job” test.

Returns:

True when the evaluation produced failures and no results at all.

Return type:

bool

_terminal_status(default: flip.constants.ModelStatus) flip.constants.ModelStatus

Resolve the run’s terminal status, most salient cause first.

A recorded fatal system error is the root cause and outranks everything. A user-requested abort outranks the evaluation failures it necessarily caused (aborting a run cancels its in-flight validate tasks, which must not be reported as ERROR). An evaluation in which every validate task failed outranks default — otherwise a wholly failed run reports success on an empty results file (FLIP#754), or reports a trailing upload failure as its cause.

Parameters:

default (ModelStatus) – The status to use when no failure cause is recorded — the outcome of the upload itself.

Returns:

The status to report to the hub.

Return type:

ModelStatus

handle_event(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None
__set_dependencies(fl_ctx: nvflare.apis.fl_context.FLContext) None
class flip.nvflare.components.KeepOnlyVars(include_vars: str | None = None, data_kinds: list[str] | None = None)

Bases: nvflare.apis.dxo_filter.DXOFilter

Keep ONLY the weights whose key matches include_vars (regex); drop the rest.

The include-only inverse of NVFLARE’s ExcludeVars (which is exclude-only). Used as a client-side task_result_filter to shrink a per-round update to just the trainable parameters: a frozen-backbone fine-tune sends only its head (~KB) instead of the full model (~759 MiB), which fixes the large-payload client→server SubmitUpdate timeout (FLIP#684).

Keeping only the head is correct for a frozen backbone: the backbone is identical on every client after round 0, so it need not be re-aggregated; the server retains it from the round-0 global model (delivered by InitialCheckpointPTModelPersistor) and the aggregator merges the head diff into it. The server’s WEIGHT_DIFF reconstruction is partial-safe (only keys present in the diff are applied), so a head-only update reconstructs correctly.

include_vars = None
skip
pattern
process_dxo(dxo: nvflare.apis.dxo_filter.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None
class flip.nvflare.components.PersistToS3AndCleanup(model_id: str = '', persistor_id: str = AppConstants.DEFAULT_PERSISTOR_ID, flip: flip.FLIP = FLIP())

Bases: nvflare.apis.fl_component.FLComponent

_model_id_fallback = ''
_model_id: str | None = None
persistor_id: str
model_persistor: nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor | None = None
model_inventory: dict
model_dir: str = ''
bucket_name: str = ''
flip
_resolve_model_id(fl_ctx: nvflare.apis.fl_context.FLContext) str

Resolve model ID lazily from job metadata, falling back to the constructor arg.

Parameters:

fl_ctx (FLContext) – The FL context for the current job.

Returns:

The resolved model ID.

Return type:

str

execute(fl_ctx: nvflare.apis.fl_context.FLContext) None
upload_results_to_s3_bucket(fl_ctx: nvflare.apis.fl_context.FLContext) None

Uploads the final aggregated model and reports to an S3 bucket as a zip file.

cleanup(fl_ctx: nvflare.apis.fl_context.FLContext) None

Cleans up the workspace by deleting the transfer and save directories for the model ID.

class flip.nvflare.components.EvaluationModelLocator

Bases: nvflare.app_common.abstract.model_locator.ModelLocator

Locate uploaded checkpoint(s) for Client-API evaluation under the standard ModelLocator interface.

Unlike EvaluationPTModelLocator — which returns a single DataKind.COLLECTION DXO for the bespoke ModelEval controller — this exposes the stock get_model_names + locate_model(model_name, fl_ctx) contract so it drives NVFLARE’s stock CrossSiteModelEval validate workflow directly. Each model named in config.json['models'] becomes one DataKind.WEIGHTS DXO that the server broadcasts to clients as a single FLModel for the Client-API is_evaluate() path.

The checkpoints are loaded server-side only — from the app’s custom/ directory when bundled (simulator / legacy bundling), else from the FL API’s de-bundled staging volume at <SERVER_CHECKPOINT_ROOT>/<model_id>/ (production; same resolution order as EvaluationPTModelLocator). Clients never read the .pt files — they receive the weights over the validate task. model_id is resolved lazily from meta.json['custom_props'] (recipe-built job types carry no component args).

models: dict | None = None
_resolve_checkpoint_path(fl_ctx: nvflare.apis.fl_context.FLContext, app_dir: str, name: str, model_checkpoint: str)

Locate a checkpoint: bundled custom/ first, then the FL API’s shared staging volume.

_load_models(fl_ctx: nvflare.apis.fl_context.FLContext) None

Read config.json['models'] and load each named checkpoint into self.models.

get_model_names(fl_ctx: nvflare.apis.fl_context.FLContext) list[str]
locate_model(model_name: str, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo.DXO | None
class flip.nvflare.components.EvaluationPTModelLocator(exclude_vars=None, model_id: str = '')

Bases: nvflare.app_common.abstract.model_locator.ModelLocator

models = None
exclude_vars = None
model_id = ''
_resolve_checkpoint_path(fl_ctx: nvflare.apis.fl_context.FLContext, app_dir: str, name: str, model_checkpoint: str)

Locate a model checkpoint for server-side loading.

Resolution order:
  1. <app_dir>/custom/<checkpoint> — a checkpoint bundled in the app. Used by the local simulator (which copies the .pt into custom/) and any legacy bundling.

  2. <SERVER_CHECKPOINT_ROOT>/<model_id>/<checkpoint> — the de-bundled checkpoint the FL API staged on the hub-local shared volume (production). Read straight from disk; the checkpoint is intentionally NOT shipped in the app bundle, so it never reaches the clients. This mirrors the Flower backend’s /app/src shared mount.

Returns the resolved path, or None (after logging an error) when neither exists.

locate_model(fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo.DXO | None
class flip.nvflare.components.InitialPTModelLocator(exclude_vars=None, model=None)

Bases: nvflare.app_common.abstract.model_locator.ModelLocator

model = None
exclude_vars = None
get_model_names(fl_ctx: nvflare.apis.fl_context.FLContext) list[str]
locate_model(model_name: str, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo.DXO | None
class flip.nvflare.components.PTModelLocator(exclude_vars=None, model=None)

Bases: nvflare.app_common.abstract.model_locator.ModelLocator

model = None
exclude_vars = None
get_model_names(fl_ctx: nvflare.apis.fl_context.FLContext) list[str]
locate_model(model_name: str, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo.DXO | None
class flip.nvflare.components.InitialCheckpointPTModelPersistor(model=None, model_id: str = '', **kwargs)

Bases: nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor

Persistor that seeds the initial global model from a large backbone checkpoint staged server-side, so the checkpoint never has to be bundled into the job app (bundling a ~759 MiB file collapses NVFLARE’s app-deploy to remote clients).

The backbone filename is declared in the job’s config.json under SERVER_CHECKPOINT. At load time it is resolved in this order:

  1. <app>/custom/<checkpoint> — a checkpoint bundled in the app. Used by the local simulator (which copies the file into custom/) and any legacy bundling.

  2. <SERVER_CHECKPOINT_ROOT>/<model_id>/<checkpoint> — the de-bundled checkpoint the FL API staged on the hub-local shared volume (production). Read straight from disk; the checkpoint is intentionally NOT shipped in the app bundle, so it never reaches the clients. Mirrors the Flower backend’s /app/src shared mount and the eval EvaluationPTModelLocator.

The resolved checkpoint is loaded (strict=False) into the get_model() architecture so the round-0 global model that ScatterAndGather broadcasts carries the backbone plus freshly-initialised heads — a full state dict the clients can load. Clients therefore build only a bare architecture and receive all weights at round 0; they need no checkpoint file.

When config.json declares no SERVER_CHECKPOINT (every other standard training job), this behaves exactly like the stock PTFileModelPersistor (initial weights from the model object), so it is a safe drop-in for the shared standard base app.

_model_id_arg = ''
_resolve_backbone(fl_ctx: nvflare.apis.fl_context.FLContext)

Locate the declared backbone checkpoint, or None if none is declared/found.

load_model(fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.app_common.abstract.model.ModelLearnable
class flip.nvflare.components.ReconstructFullModel(data_kinds: list[str] | None = None)

Bases: nvflare.apis.dxo_filter.DXOFilter

Client-side task_data_filter: rebuild the full global model from a trimmed broadcast.

Pairs with the server-side TrimBroadcastVars. After round 0 the server broadcasts only the trainable head; this filter retains the full model received at round 0 (frozen backbone + head) and merges each subsequent round’s head into it, so the client’s executor always receives a full state dict. User training code is therefore unchanged — it never sees the head-only wire payload, only the reconstructed full model.

Stateful: the retained model persists across rounds (the filter is a job-scoped component, instantiated once for the run). If a trimmed update arrives before any full model was cached — e.g. a client that (re)joined after round 0 and so never received the backbone — the filter raises, which NVFLARE surfaces as a task-data-filter error, failing the round loudly rather than silently training on a partial model.

_full_weights: dict | None = None
process_dxo(dxo: nvflare.apis.dxo_filter.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None
class flip.nvflare.components.ReconstructFullModelForEval(data_kinds: list[str] | None = None, evaluate_task_name: str = AppConstants.TASK_VALIDATION)

Bases: ReconstructFullModel

Client-side task_data_filter for BOTH train and validate: rebuild the full model.

Extends ReconstructFullModel to also reconstruct the full model for post-training cross-site validation (GlobalModelEval), whose head-only broadcast is produced by the server-side TrimEvalBroadcastVars.

Why one component on two tasks. The frozen backbone the client needs to reconstruct against is the one it received at training round 0 — in production the pretrained checkpoint is de-bundled server-side and never shipped to clients (see InitialCheckpointPTModelPersistor), so the client’s ONLY copy of the backbone is the round-0 broadcast cached during training. NVFLARE builds a fresh filter instance per filter-chain occurrence, so the training cache is shared with the evaluation phase ONLY when the same component instance handles both tasks — i.e. one filter chain whose tasks are ["train", "validate"]. This class is therefore wired onto both.

Behaviour is dispatched by the current task name (set in fl_ctx before client task-data filters run):

  • train (or any non-evaluation task): delegate to ReconstructFullModel — the proven round-gated cache-at-round-0 / merge-thereafter logic is unchanged.

  • validate: merge the broadcast (head-only, or a full model if the server fell back) onto the retained full model from training and hand the validator a full state dict. Fails loudly if the client never cached a full model (it never trained → no backbone) or if the broadcast carries keys absent from the retained model (a mismatch that would otherwise validate wrong weights).

_evaluate_task_name
process_dxo(dxo: nvflare.apis.dxo_filter.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) nvflare.apis.dxo_filter.DXO | None
class flip.nvflare.components.StagePercentilePrivacy(percentile=10, gamma=0.01, data_kinds: list[str] | None = None, off: bool = False)

Bases: flip.nvflare.components.custom_percentile_privacy.PercentilePrivacy

Percentile-privacy filter that groups parameters by training stage before filtering.

Subclasses FLIP’s PercentilePrivacy to inherit its constructor (percentile / gamma / data_kinds / off) and — transitively — stock NVFLARE’s differential-privacy maths. It overrides process_dxo to compute the percentile cutoff per stage (from FlipMetaKey.STAGE) rather than across all parameters at once, so each stage of a staged model (e.g. autoencoder then diffusion model) is filtered independently.

process_dxo(dxo: nvflare.apis.dxo.DXO, shareable: nvflare.apis.shareable.Shareable, fl_ctx: nvflare.apis.fl_context.FLContext) None | nvflare.apis.dxo.DXO

Compute the percentile on the abs delta_W, per stage.

Only shares the params whose absolute delta_W is greater than the percentile value, grouping parameters by the stages listed in FlipMetaKey.STAGE.

Parameters:
  • dxo (DXO) – Information from the client.

  • shareable (Shareable) – The shareable the DXO belongs to.

  • fl_ctx (FLContext) – Context provided by the workflow.

Returns:

The filtered DXO, the unchanged DXO (off / no stage info), or None (invalid gamma/percentile).

Return type:

None | DXO

class flip.nvflare.components.ValidationJsonGenerator

Bases: nvflare.app_common.widgets.validation_json_generator.ValidationJsonGenerator

Stock NVFLARE ValidationJsonGenerator, dispatched manually by FLIP’s ServerEventHandler.

Subclasses nvflare.app_common.widgets.validation_json_generator.ValidationJsonGenerator so the results accumulation (METRICS and COLLECTION / T2 leaf handling), numpy-float JSON encoding and path-safe output track upstream instead of a drifting vendored copy (the fork had only the METRICS branch and a plain json.dump). The default results_dir / json_file_name are identical to stock, so the constructor is inherited unchanged.

The only FLIP-specific behaviour is dispatch. NVFLARE would auto-handle events through handle_event; FLIP instead routes them through ServerEventHandler, which calls handle_evaluation_events. Because this component is still registered (so its handle_event is auto-fired for every event), handle_event is suppressed here — otherwise each validation result would be recorded twice — and the real work is delegated to stock’s implementation from handle_evaluation_events.

handle_event(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None
handle_evaluation_events(event_type: str, fl_ctx: nvflare.apis.fl_context.FLContext) None

FLIP integration point — ServerEventHandler dispatches events here, not via handle_event.