NVIDIA FLARを用いたリファクタリング負担軽減の連合学習
本文の状態
日本語全文を表示中
詳細モードで約11分の本文を読めます。
NVIDIAはFLARツールを用いることで、医療機関などがデータ移動の制約下でも既存コードを改修せず連合学習を実践的に運用可能だと説明している。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
フェデレーテッドラーニング(FL)はもはや研究上の興味深い話題ではなく、ある厳しい制約に対する実用的な回答です。最も価値のあるデータは、しばしば移動が困難であるという事実です。規制の境界、データ主権に関する規則、そして組織内のリスク許容度は、中央集約型のデータ集計を日常的に妨げます。同時に、膨大なデータの重力(data gravity)により、許可されたデータ転送でさえも、大規模なスケールでは遅く、高価で、脆弱なものになりがちです。
NVIDIA FLARE の最新バージョンは、この現実に対応するため、トレーニングロジックをデータ側に移動させ、生データはその場に残すというフェデレーテッドコンピューティングランタイムを提供しています。高リスクな環境では、データを中央集約して集計することが不可能または実用的でないことが多いため、現代のフェデレーテッドプラットフォームは、データの分離、コンプライアンス、およびプライバシー強化技術(privacy-enhancing technologies)を第一級の要件として扱う必要があります。
歴史的にFLの普及を遅らせてきたのは、その概念自体ではなく、開発者の体験(developer experience)です。「ローカルのスクリプトで学習する」から「フェデレーテッドサイト全体でジョブを実行する」への移行に、深いコードのリファクタリングや新しいクラス階層、あるいは脆い設定が必要となる場合、多くのプロジェクトはパイロット段階で停滞してしまいます。
FLARE API の進化はまさにこの課題に対応するもので、リファクタリングのオーバーヘッドを排除し、チームが実際にMLシステムを構築・納品する方法に明確に対応する2つの具体的なステップに分けて作業を行います。
- ステップ1(クライアントAPI):既存のローカルトレーニングスクリプトを、学習ループの構造を変更することなく、約5〜6行のコードでフェデレーテッドクライアントに変換します。
- ステップ 2(ジョブリシピ):FL ワークフローを選択し、クライアントのトレーニングスクリプトにバインドします。その後、実行環境を切り替えるだけで、シミュレーション、PoC(概念実証)、本番環境のすべてで同じジョブを実行できます。
システム要件としての「データコピーなし」
規制の厳しい環境や高機密性が求められる場面では、「データを一元化する」という選択肢は次第に現実味を失っています。実用的なフェデレーテッドコンピューティング(Federated Computing)プラットフォームには、以下のサポートが必要です。
- データコピーなし:データはローカルに留まり、モデルの更新(または同等のシグナル)のみが移動します。
- 準拠姿勢:主権と監査要件をサポートする、デプロイメントおよびガバナンス制御。
- プライバシー強化技術:複数の防御層(例:同種暗号、差分プライバシー、機密コンピューティング)。
image*図 1. フェデレーテッドコンピューティングはデータをその場に保持し、モデルの更新を通じてコラボレーションを可能にすると同時に、準拠およびプライバシー強化保護をサポートします。***
リファクタリングの崖:FL プロジェクトが停滞する理由
チームは、パイロット段階後に通常、以下の 2 つの「崖」のいずれかに直面します。
- コードの崖:動作する PyTorch/TensorFlow/Lightning のトレーニングコードを FL に変換するには、侵入的なリストラクチャリングが必要になる場合があります。これには、新しい抽象化、メッセージングの接着剤、およびフレームワーク固有の足場が含まれます。
- ライフサイクルの崖:シミュレーションが正常に動作している場合でも、PoC や本番環境への移行は、ジョブの再定義、再構成、および環境固有の分岐による書き換えをトリガーします。
FLARE は、ワークフローを 2 つのステップに標準化することで、両方の課題(「崖」)を平坦化します。
- スクリプトをフェデレーテッド(クライアント API)にする
- ポータブルジョブとして実行する(ジョブリシピ)
意図された体験は、これらを組み合わせることで、運用可能なフェデレーテッドジョブをゼロから迅速に構築できるようにすることです。
ステップ 1: ローカルのトレーニングスクリプトをフェデレーテッドクライアントに変換する(クライアント API)
対象者: 既存のトレーニングコードを持ち、最小限の変更で済ませたい実務家や ML エンジニア。
メンタルモデルは意図的にシンプルに設計されています:
- クライアントランタイムを初期化する
- ジョブが実行されている間ループする
- 現在のグローバルモデルを受信する
- ローカルでトレーニングする(あなたのコード)
- 更新された重みとメトリクスを送信する
FLARE のクライアント API は、最小限のコード変更を可能にするように設計されており、重い「Executor/Learner」継承を強制しません。FLModel 構造体または単純なデータ交換を使用して、ランタイムと通信してください。
例 1a: PyTorch から FLARE への変換
以下は、多くのスクリプトに適用できる具体的なパターンです。重要な接点は、flare.init()、flare.receive()、モデル重みの読み込み、そして更新された重みとメトリクスを伴う flare.send() です。
左側にローカルトレーニングコード、右側にフェデレーテッド版を示し、import、flare.init()、receive()、send() を強調表示しています。
train.py
# train.py
import torch
import torchvision
import torchvision.transforms as transforms
from model import Net
batch_size = 4
epochs = 1
lr = 0.01
model = Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
transform = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]
)
train_dataset = torchvision.datasets.CIFAR10(
root="/tmp/data/cifar10", transform=transform, download=True, train=True
)
trainloader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, shuffle=True
)
model.to(device)
for epoch in range(epochs):
running_loss = 0.0
for i, batch in enumerate(trainloader):
images, labels = batch[0].to(device), batch[1].to(device)
optimizer.zero_grad()
predictions = model(images)
cost = loss(predictions, labels)
cost.backward()
optimizer.step()
running_loss += cost.cpu().detach().numpy() / batch_size
if i % 3000 == 2999:
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / 3000}"
)
running_loss = 0.0
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / (i + 1)}"
)
print("Finished Training")
torch.save(model.state_dict(), "./cifar_net.pth")
client.py
# client.py
1. クライアント API のインポート
import nvflare.client as flare
import torch
import torchvision
import torchvision.transforms as transforms
from model import Net
batch_size = 4
epochs = 1
lr = 0.01
model = Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
transform = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]
)
train_dataset = torchvision.datasets.CIFAR10(
root="/tmp/data/cifar10", transform=transform, download=True, train=True
)
trainloader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, shuffle=True
)
2. FLARE の初期化
flare.init()
FLARE が実行されている間、各ラウンドで
while flare.is_running():
# 3. グローバルモデルの受信
input_model = flare.receive()
# 4. グローバルモデルの読み込み
model.load_state_dict(input_model.params)
model.to(device)
for epoch in range(epochs):
running_loss = 0.0
for i, batch in enumerate(trainloader):
images, labels = batch[0].to(device), batch[1].to(device)
optimizer.zero_grad()
predictions = model(images)
cost = loss(predictions, labels)
cost.backward()
optimizer.step()
running_loss += cost.cpu().detach().numpy() / batch_size
if i % 3000 == 2999:
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / 3000}"
)
running_loss = 0.0
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / (i + 1)}"
)
print("Finished Training")
torch.save(model.state_dict(), "./cifar_net.pth")
# 5. Send back the updated model
output_model = flare.FLModel(
params=model.cpu().state_dict(),
meta={"NUM_STEPS_CURRENT_ROUND": len(trainloader) * epochs},
)
flare.send(output_model)
Example 1b: PyTorch Lightning client The Lightning integration keeps the same
The Lightning integration keeps the same **intent—receive global model, train, send updates—but exposes it in a Lightning-friendly way: import the Lightning client adapter and patch the Trainer.
The typical flow is: import, patch, (optional) validate, train as usual.
lightning_client.py
import pytorch_lightning as pl
from pytorch_lightning import Trainer
import nvflare.client.lightning as flare # Lightning Client API
from model import LitNet
from data import CIFAR10DataModule
def main():
model = LitNet()
dm = CIFAR10DataModule()
trainer = Trainer(max_epochs=1, accelerator="gpu", devices=1)
# Patch trainer to participate in FL
flare.patch(trainer)
while flare.is_running():
オプション:現在のグローバルモデルを検証する(サーバー側の選択フローに有用)
trainer.validate(model, datamodule=dm)
# 受信したグローバルモデルから学習を開始する(パッチ適用後、内部で処理される)
trainer.fit(model, datamodule=dm)
if __name__ == "__main__":
main()
ポイント:Lightning ユーザーはカスタム連合メッセージングに切り替える必要はありません。Trainer の抽象化を維持したまま、FL ラウンドに正しく参加できます。
ステップ 2:連合ジョブをパッケージ化し、どこでも実行する(ジョブリシピ)
対象者: 環境を問わず安定したコードファーストのジョブ定義を求めるデータサイエンティストおよび適用チーム。
ステップ 1 で連合クライアントスクリプトを作成しました。ステップ 2 では、それを繰り返し実行可能でライフサイクルを明確に追跡できる連合ジョブに変換します。
ジョブリシピは、JSON ベースのジョブ設定に代わる Python ベースのジョブ定義を提供することを目的としています:
- コードファースト:複雑な設定ファイルではなく、Python で完全な FL ジョブを定義
- 1 回記述、どこでも実行:同じレシピがシミュレーター、PoC(概念実証)、または本番環境で動作
- 展開までの速度:コード構造を変更せずに、実験から展開へ移行可能
例 2a:シミュレーションで FedAvg レシピを実行する
重要なリンクは、レシピがステップ 1 で作成したクライアント学習スクリプト(例:train_script="client.py")を参照している点です。その後、環境内でそれを実行します。
job.py
from nvflare.app_common.workflows.job import FedAvgRecipe
from nvflare.job_config import SimEnv # exact import path can vary by NVFlare version
from model import SimpleNetwork
def main():
n_clients = 3
num_rounds = 5
batch_size = 32
recipe = FedAvgRecipe(
name="hello-pt",
min_clients=n_clients,
num_rounds=num_rounds,
model=SimpleNetwork(),
train_script="client.py", # <-- Step A script
train_args=f"--batch_size {batch_size} --epochs 1",
)
env = SimEnv(num_clients=n_clients, num_threads=n_clients)
recipe.execute(env=env)
if __name__ == "__main__":
main()
This is the “write once” idea in practice: Once the recipe correctly references your client script, the rest becomes an execution concern.
Example 2b: Move from simulation to real-world with an environment swap.
Job recipes formalize a progressive workflow by swapping the execution environment:
- SimEnv (Simulation): Easy development, rapid debugging
- PocEnv (Proof-of-Concept): Local runtime, multi-process, realistic testing
- ProdEnv (Production): Distributed deployment on secure, scalable infrastructure
imageFigure 2. One JobRecipe, multiple execution environments: Debug in SimEnv, validate in PocEnv, and deploy in ProdEnv without rewriting the job definition
Getting started
- Start with a script you already trust.
- Step 1: Add the client API handshake (or patch your Lightning Trainer).
- ステップ 2: ジョブリシピでラップし、環境を切り替えて、まずシミュレーションで実行し、次に PoC(概念実証)、そして本番環境へと展開します。
ニュースでの FLARE
FLARE は実際のデプロイメントで登場しています。Rhino Federated Computing によって NVFlare を使用して構築された Eli Lilly TuneLab の連合学習プラットフォーム、台湾 MOHW の国立医療連合学習イニシアチブ、そして機密データセットにわたる Tri-labs (Sandia/LANL/LLNL) の連合 AI パイロット などがその例です。
さらに進むには
すでに信頼しているスクリプトから始めます。最小限の FLARE クライアントハンドシェイク(受信 → 学習 → 送信)を追加します。その後、準備ができたら単一ノードのシミュレーションからマルチサイトデプロイメントへとスケールアップします。
- ここから始める: Hello World の例(最初の連合実行への最速パス) — NVFlare Hello World
- ウォークスルーを見る: 簡素化された API スタックの動作を確認 — ウェビナー録画
- クライアント API ドキュメント
- JobRecipe ドキュメント
- GitHub 上の NVFlare
著者について
原文を表示
Federated learning (FL) is no longer a research curiosity—it’s a practical response to a hard constraint: the most valuable data is often the least movable. Regulatory boundaries, data sovereignty rules, and organizational risk tolerance routinely prevent centralized aggregation. Meanwhile, sheer data gravity makes even permitted transfers slow, expensive, and fragile at scale.
The latest version of NVIDIA FLARE addresses this reality with a federated computing runtime that moves the training logic to the data, while raw data stays put. In high-stakes environments, centrally aggregating data is often not possible or practical, so a modern federated platform must treat data isolation, compliance, and privacy-enhancing technologies as first-class requirements.
What has historically slowed adoption isn’t the concept of FL—it’s the developer experience. If the path from “my local script trains” to “my job runs across federated sites” requires deep refactoring, new class hierarchies, or brittle configuration, many projects stall after the pilot.
The FLARE API evolution targets exactly that: eliminating the refactoring overhead by splitting the work into two concrete steps that map cleanly onto how teams actually build and ship ML systems:
- Step 1 (client API): Turn an existing local training script into a federated client with ~5–6 lines of code, without changing your training loop structure.
- Step 2 (job recipes): Select the FL workflow and bind it to your client training script, then run the same job across simulation, PoC, and production by swapping only the execution environment.
‘No data copy’ as a system requirement
In regulated or high-sensitivity settings, “just centralize the dataset” is increasingly off the table. A practical federated computing platform needs to support:
- No data copy: Data stays local, and only model updates (or equivalent signals) move.
- Compliance posture: Deployment and governance controls that support sovereignty and audit requirements.
- Privacy-enhancing techniques: Multiple layers of defenses (examples include homomorphic encryption, differential privacy, and confidential computing).

The refactoring cliff: Why FL projects stall
Teams typically hit one of two cliffs after the pilot:
- The code cliff: Converting working PyTorch/TensorFlow/Lightning training into FL can require invasive restructuring—new abstractions, messaging glue, and framework-specific scaffolding.
- The lifecycle cliff: Even when simulation works, moving to PoC and production triggers rewrites via job redefinition, reconfiguration, and environment-specific branching.
FLARE flattens both cliffs by standardizing the workflow into two steps:
- Make your script federated (client API)
- Execute it as a portable job (job recipe)
The intended experience is explicitly to combine these so you can go from zero to an operational federated job quickly.
Step 1: Convert your local training script into a federated client (client API)
Who it’s for: Practitioners and ML engineers with existing training code who want the smallest possible difference.
The mental model is intentionally simple:
- Initialize the client runtime
- Loop while the job is running
- Receive the current global model
- Train locally (your code)
- Send updated weights + metrics back
FLARE’s client API is designed for minimal code changes and avoids forcing you into heavy “Executor/Learner” inheritance—use the FLModel structure or simple data exchange to communicate with the runtime.
Example 1a: Convert PyTorch to FLARE
Below is a concrete pattern you can apply to many scripts. The key touchpoints are: flare.init(), flare.receive(), loading model weights, and flare.send() with updated weights and metrics.
We show the local training code on the left and the federated version on the right, highlighting: import, flare.init(), receive(), send().
train.py
# train.py
import torch
import torchvision
import torchvision.transforms as transforms
from model import Net
batch_size = 4
epochs = 1
lr = 0.01
model = Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
transform = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]
)
train_dataset = torchvision.datasets.CIFAR10(
root="/tmp/data/cifar10", transform=transform, download=True, train=True
)
trainloader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, shuffle=True
)
model.to(device)
for epoch in range(epochs):
running_loss = 0.0
for i, batch in enumerate(trainloader):
images, labels = batch[0].to(device), batch[1].to(device)
optimizer.zero_grad()
predictions = model(images)
cost = loss(predictions, labels)
cost.backward()
optimizer.step()
running_loss += cost.cpu().detach().numpy() / batch_size
if i % 3000 == 2999:
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / 3000}"
)
running_loss = 0.0
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / (i + 1)}"
)
print("Finished Training")
torch.save(model.state_dict(), "./cifar_net.pth")client.py
# client.py
# 1. Import client API
import nvflare.client as flare
import torch
import torchvision
import torchvision.transforms as transforms
from model import Net
batch_size = 4
epochs = 1
lr = 0.01
model = Net()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=lr, momentum=0.9)
transform = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]
)
train_dataset = torchvision.datasets.CIFAR10(
root="/tmp/data/cifar10", transform=transform, download=True, train=True
)
trainloader = torch.utils.data.DataLoader(
train_dataset, batch_size=batch_size, shuffle=True
)
# 2. Initialize FLARE
flare.init()
# At each round while FLARE is running
while flare.is_running():
# 3. Receive the global model
input_model = flare.receive()
# 4. Load global model
model.load_state_dict(input_model.params)
model.to(device)
for epoch in range(epochs):
running_loss = 0.0
for i, batch in enumerate(trainloader):
images, labels = batch[0].to(device), batch[1].to(device)
optimizer.zero_grad()
predictions = model(images)
cost = loss(predictions, labels)
cost.backward()
optimizer.step()
running_loss += cost.cpu().detach().numpy() / batch_size
if i % 3000 == 2999:
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / 3000}"
)
running_loss = 0.0
print(
f"Epoch: {epoch + 1}/{epochs}, batch: {i + 1}, Loss: {running_loss / (i + 1)}"
)
print("Finished Training")
torch.save(model.state_dict(), "./cifar_net.pth")
# 5. Send back the updated model
output_model = flare.FLModel(
params=model.cpu().state_dict(),
meta={"NUM_STEPS_CURRENT_ROUND": len(trainloader) * epochs},
)
flare.send(output_model)Example 1b: PyTorch Lightning client The Lightning integration keeps the same
The Lightning integration keeps the same intent—receive global model, train, send updates—but exposes it in a Lightning-friendly way: import the Lightning client adapter and patch the Trainer.
The typical flow is: import, patch, (optional) validate, train as usual.
# lightning_client.py
import pytorch_lightning as pl
from pytorch_lightning import Trainer
import nvflare.client.lightning as flare # Lightning Client API
from model import LitNet
from data import CIFAR10DataModule
def main():
model = LitNet()
dm = CIFAR10DataModule()
trainer = Trainer(max_epochs=1, accelerator="gpu", devices=1)
# Patch trainer to participate in FL
flare.patch(trainer)
while flare.is_running():
# Optional: validate current global model (useful for server-side selection flows)
trainer.validate(model, datamodule=dm)
# Train starting from received global model (handled internally after patch)
trainer.fit(model, datamodule=dm)
if __name__ == "__main__":
main()
The point: Lightning users don’t have to drop into custom federated messaging—they keep the Trainer abstraction and still participate correctly in FL rounds.
Step 2: Package and execute the federated job anywhere (job recipes)
Who it’s for: Data scientists and applied teams who want a code-first job definition that remains stable across environments.
After step 1, you have a federated client script. Step 2 makes it a federated job you can run repeatedly and move through the lifecycle cleanly.
Job recipes are designed to replace JSON-based job configuration with a Python-based job definition:
- Code-first: Define complete FL jobs in Python, not complex config files
- Write once, run anywhere: Same recipe runs in simulator, PoC, or production
- Speed to deployment: Go from experimentation to deployment without changing code structure
Example 2a: Execute a FedAvg recipe in simulation
The key linkage is that your recipe references the client training script you created in step 1 (e.g., train_script="client.py"), then you execute it in an environment.
# job.py
from nvflare.app_common.workflows.job import FedAvgRecipe
from nvflare.job_config import SimEnv # exact import path can vary by NVFlare version
from model import SimpleNetwork
def main():
n_clients = 3
num_rounds = 5
batch_size = 32
recipe = FedAvgRecipe(
name="hello-pt",
min_clients=n_clients,
num_rounds=num_rounds,
model=SimpleNetwork(),
train_script="client.py", # <-- Step A script
train_args=f"--batch_size {batch_size} --epochs 1",
)
env = SimEnv(num_clients=n_clients, num_threads=n_clients)
recipe.execute(env=env)
if __name__ == "__main__":
main()
This is the “write once” idea in practice: Once the recipe correctly references your client script, the rest becomes an execution concern.
Example 2b: Move from simulation to real-world with an environment swap.
Job recipes formalize a progressive workflow by swapping the execution environment:
- SimEnv (Simulation): Easy development, rapid debugging
- PocEnv (Proof-of-Concept): Local runtime, multi-process, realistic testing
- ProdEnv (Production): Distributed deployment on secure, scalable infrastructure

Getting started
- Start with a script you already trust.
- Step 1: Add the client API handshake (or patch your Lightning Trainer).
- Step 2: Wrap it in a job recipe and execute first in simulation, then PoC, then production by swapping environments.
FLARE in the News
FLARE is showing up in real deployments—from Eli Lilly TuneLab’s federated learning platform (built by Rhino Federated Computing using NVFlare) to Taiwan MOHW’s national healthcare federated learning initiative, and a Tri-labs (Sandia/LANL/LLNL) federated AI pilot across sensitive datasets.
Going further
Start with a script you already trust. Add the minimal FLARE client handshake (receive → train → send). Then scale from single-node simulation to multi-site deployment when you’re ready.
- Start here: Hello World examples (fastest path to your first federated run) — NVFlare Hello World
- Watch the walkthrough: see the simplified API stack in action — Webinar recording
- Client API docs
- JobRecipe docs
- NVFlare on GitHub
About the Authors
同じ出来事を2媒体で確認
同じ出来事を扱う別媒体の記事です。見出しと公開時刻を比較できます。
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み