En apprentissage automatique distribué, le serveur de paramètres (Parameter Server) permet de gérer et de partager les paramètres du modèle. L'idée de base est de stocker les paramètres du modèle sur un ou plusieurs serveurs centraux, et de partager ces paramètres avec chaque machine informatique participant à la formation via le réseau.nœud. Chaque nœud de calcul peut obtenir les paramètres de modèle actuels à partir du serveur de paramètres et renvoyer les résultats de calcul au serveur de paramètres pour une mise à jour.
Afin de maintenir la cohérence du modèle, les deux méthodes suivantes sont généralement adoptées :
- Enregistrez les paramètres du modèle sur un nœud centralisé. Lorsqu'un nœud informatique doit effectuer une formation de modèle, il peut obtenir des paramètres à partir du nœud centralisé, effectuer une formation de modèle, puis repousser le modèle mis à jour vers le nœud centralisé. Étant donné que tous les nœuds de calcul obtiennent des paramètres du même nœud centralisé, la cohérence du modèle peut être garantie.
- Chaque nœud de calcul contient une copie des paramètres du modèle. Ainsi, pour forcer périodiquement la synchronisation de la copie du modèle, chaque nœud de calcul utilise sa propre partition de données d'apprentissage pour entraîner la copie du modèle local. Après chaque itération d'apprentissage, les copies du modèle stockées sur différents nœuds de calcul peuvent être différentes en raison de l'apprentissage avec des données d'entrée différentes. Par conséquent, une étape de synchronisation globale est insérée après chaque itération d'apprentissage, qui fera la moyenne des paramètres sur différents nœuds de calcul afin d'assurer la cohérence du modèle de manière entièrement distribuée, c'est-à-dire le paradigme All-Reduce.
Architecture PS
Dans cette architecture, il y a deux rôles : serveur de paramètres et travailleur
Le serveur de paramètres sera considéré comme le nœud maître dans l'architecture maître/travailleur, et le travailleur agira comme le nœud informatique responsable de la formation du modèle
Le flux de travail de l'ensemble du système est divisé en 4 étapes :
- Tirer les poids : tous les travailleurs obtiennent les paramètres de poids du serveur de paramètres
- Push Gradients : chaque travailleur utilise des données de formation locales pour former un modèle local, génère des gradients locaux, puis télécharge les gradients sur le serveur de paramètres
- Aggregate Gradients : après avoir collecté les gradients envoyés par tous les nœuds de calcul, additionnez les gradients
- Mise à jour du modèle : Calculez le gradient cumulé, et le serveur de paramètres utilise ce gradient cumulatif pour mettre à jour les paramètres du modèle sur le serveur centralisé
On peut voir que les poids tirés et les gradients poussés mentionnés ci-dessus impliquent une communication. Premièrement, pour les poids tirés, le serveur de paramètres envoie des poids aux travailleurs en même temps. Il s'agit d'un mode de communication un à plusieurs, appelé sortance. mode de communication. Supposons que la bande passante de communication de chaque nœud (serveur de paramètres et nœud de travail) est de 1. En supposant qu'il y a N nœuds de travail dans cette tâche de formation parallèle de données, puisque le serveur de paramètres centralisé doit envoyer le modèle à N nœuds de travail en même temps, la bande passante d'envoi (BW) de chaque nœud de travail n'est que de 1/N. D'autre part, la bande passante de réception de chaque nœud de travail est de 1, ce qui est beaucoup plus grand que la bande passante d'envoi 1/N du serveur de paramètres. Par conséquent, au stade de l'extraction des poids, il existe un goulot d'étranglement de communication du côté du serveur de paramètres.
Pour Push Gradients, tous les agents envoient simultanément des dégradés au serveur de paramètres, ce qui est appelé mode de communication fan-in, et le serveur de paramètres présente également un goulot d'étranglement de communication.
Sur la base de la discussion ci-dessus, le goulot d'étranglement de communication se produit toujours du côté du serveur de paramètres, et ce problème sera résolu par l'équilibrage de charge
Divisez le modèle en N serveurs de paramètres, et chaque serveur de paramètres est responsable de la mise à jour des paramètres de modèle 1/N. En fait, les paramètres du modèle sont partagés et stockés sur plusieurs serveurs de paramètres, ce qui peut atténuer le problème de goulot d'étranglement du réseau côté serveur de paramètres, réduire la charge de communication entre les serveurs de paramètres et améliorer l'efficacité globale de la communication.
Code
Définir la structure du réseau : un CNN simple est défini ci-dessus
Implémentez un serveur de paramètres :
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
if torch.cuda.is_available():
device = torch.device("cuda:0")
else:
device = torch.device("cpu")
self.conv1 = nn.Conv2d(1,32,3,1).to(device)
self.dropout1 = nn.Dropout2d(0.5).to(device)
self.conv2 = nn.Conv2d(32,64,3,1).to(device)
self.dropout2 = nn.Dropout2d(0.75).to(device)
self.fc1 = nn.Linear(9216,128).to(device)
self.fc2 = nn.Linear(128,20).to(device)
self.fc3 = nn.Linear(20,10).to(device)
def forward(self,x):
x = self.conv1(x)
x = self.dropout1(x)
x = F.relu(x)
x = self.conv2(x)
x = self.dropout2(x)
x = F.max_pool2d(x,2)
x = torch.flatten(x,1)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.relu(x)
x = self.fc3(x)
output = F.log_softmax(x,dim=1)
return output
Un CNN simple est défini ci-dessus
Implémentez un serveur de paramètres :
class ParamServer(nn.Module):
def __init__(self):
super().__init__()
self.model = Net()
if torch.cuda.is_available():
self.input_device = torch.device("cuda:0")
else:
self.input_device = torch.device("cpu")
self.optimizer = optim.SGD(self.model.parameters(),lr=0.5)
def get_weights(self):
return self.model.state_dict()
def update_model(self,grads):
for para,grad in zip(self.model.parameters(),grads):
para.grad = grad
self.optimizer.step()
self.optimizer.zero_grad()
get_weights obtient les paramètres de pondération, update_model met à jour le modèle et utilise l'optimiseur SGD
Mettre en œuvre les ouvriers :
class Worker(nn.Module):
def __init__(self):
super().__init__()
self.model = Net()
if torch.cuda.is_available():
self.input_device = torch.device("cuda:0")
else:
self.input_device = torch.device("cpu")
def pull_weights(self,model_params):
self.model.load_state_dict(model_params)
def push_gradients(self,batch_idx,data,target):
data,target = data.to(self.input_device),target.to(self.input_device)
output = self.model(data)
data.requires_grad = True
loss = F.nll_loss(output,target)
loss.backward()
grads = []
for layer in self.parameters():
grad = layer.grad
grads.append(grad)
print(f"batch {batch_idx} training :: loss {loss.item()}")
return grads
Pull_weights obtient les paramètres du modèle, push_gradients télécharge le dégradé
former
L'ensemble de données d'entraînement est MNIST
import torch
from torchvision import datasets,transforms
from network import Net
from worker import *
from server import *
train_loader = torch.utils.data.DataLoader(datasets.MNIST('./mnist_data', download=True, train=True,
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,))])),
batch_size=128, shuffle=True)
test_loader = torch.utils.data.DataLoader(datasets.MNIST('./mnist_data', download=True, train=False,
transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.1307,),(0.3081,))])),
batch_size=128, shuffle=True)
def main():
server = ParamServer()
worker = Worker()
for batch_idx, (data,target) in enumerate(train_loader):
params = server.get_weights()
worker.pull_weights(params)
grads = worker.push_gradients(batch_idx,data,target)
server.update_model(grads)
print("Done Training")
if __name__ == "__main__":
main()
Source : Apprentissage automatique distribué (serveur de paramètres) - N3ptune - Blog Park (cnblogs.com)