federer
Version:
Experiments in asynchronous federated learning and decentralized learning
126 lines • 5.12 kB
JavaScript
"use strict";
var _ClientPool_training, _ClientPool_available;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientPool = void 0;
const tslib_1 = require("tslib");
const assert = require("assert");
const Reservoir = require("reservoir");
/**
* Helper class to keep track of the state of clients. Clients can be marked as
* either "available" (meaning that they are not currently training, and are
* eligible to start training), or "training" (meaning that they have been given
* a training task, and we are awaiting results).
*
* Clients are tracked by their socket ID, which is a `string`.
*
* The methods in this class use assertions to enforce the following invariants:
*
* - All clients start in the available state
* - A client is either training or available, but never both
* - A client can only transition from available to training, or from training
* to available; it cannot transition to the state that it is currently in
*
* These invariants are helpful to debug server implementations, and to ensure
* the reported numbers are accurate.
*/
class ClientPool {
constructor() {
_ClientPool_training.set(this, new Set());
_ClientPool_available.set(this, new Set());
}
/**
* Adds a client to the pool, in the "available" state.
*
* @throws if the client is already in the pool.
*/
addAvailableClient(client) {
assert(!this.has(client));
tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").add(client);
}
/** Returns whether the client is in the "available" state */
isAvailable(client) {
assert(this.has(client));
return tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").has(client);
}
/** Returns whether the client is in the "training" state. */
isTraining(client) {
assert(this.has(client));
return tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").has(client);
}
/**
* Changes the state of a client.
*
* @throws if the client is already in the target state
*/
transition(client, newState) {
if (newState === "training") {
assert(this.isAvailable(client));
tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").delete(client);
tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").add(client);
}
else if (newState === "available") {
assert(this.isTraining(client));
tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").delete(client);
tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").add(client);
}
else {
throw new Error(`Unknown state '${newState}'`);
}
}
/**
* Removes a client from the pool.
*
* @returns `true` if it was removed, `false` if it wasn't in the pool.
*/
delete(client) {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").delete(client) || tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").delete(client);
}
/** Returns whether a client is in the pool. */
has(client) {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").has(client) || tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").has(client);
}
/** Number of clients in the pool in total. */
get size() {
return this.numberTraining + this.numberAvailable;
}
/** Number of clients in the "training" state. */
get numberTraining() {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f").size;
}
/** Number of clients in the "available" state. */
get numberAvailable() {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f").size;
}
get clients() {
return new Set(...tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f"), tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f"));
}
get available() {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f");
}
get training() {
return tslib_1.__classPrivateFieldGet(this, _ClientPool_training, "f");
}
get percentTraining() {
const percent = (this.numberTraining / this.numberAvailable) * 100;
assert(0 <= percent);
assert(percent <= 100);
return percent;
}
/**
* Samples `n` clients without replacement from the list of available clients,
* using reservoir sampling.
*
* @returns array of `n` sampled client IDs
* @throws if there are not enough available clients
*/
sampleAvailable(n) {
assert(0 <= n);
assert(n <= this.numberAvailable);
const reservoir = Reservoir(n);
reservoir.pushSome(...tslib_1.__classPrivateFieldGet(this, _ClientPool_available, "f"));
return reservoir;
}
}
exports.ClientPool = ClientPool;
_ClientPool_training = new WeakMap(), _ClientPool_available = new WeakMap();
//# sourceMappingURL=ClientPool.js.map