0x00 сводка
Paracel — это среда распределенных вычислений, разработанная Douban, которая решает задачи машинного обучения на основе парадигмы сервера параметров: логистическая регрессия, SVD, матричная факторизация (BFGS, sgd, als, cg), LDA, Lasso... .
Paracel поддерживает параллелизм данных и моделей, предоставляет пользователям простой в использовании коммуникационный интерфейс и является более гибким, чем системы в стиле mapreduce. Paracel также поддерживает асинхронный режим обучения, что ускоряет сходимость итерационных задач. Кроме того, структура программы Paracel очень похожа на структуру последовательной программы, пользователи могут больше сосредоточиться на самом алгоритме и не должны уделять слишком много внимания распределенной логике.
Потому что ps-lite не углубляется в SSP, а Paracel реализует SSP более глубоко, поэтому мы рассмотрим, как реализован SSP в этой статье.
Другие статьи из этой серии:
[Анализ исходного кода] Сервер параметров машинного обучения ps-lite (1) ----- PostOffice
[Анализ исходного кода] (3) сервера параметров машинного обучения ps-lite ----- Агент Заказчик
[Анализ исходного кода] Сервер параметров машинного обучения Paracel (1) ----- общая архитектура
При разборе этой статьи некоторый код, не относящийся к теме, будет удален.
0x01 Базовые знания
Когда разные воркеры работают параллельно в одно и то же время, прогресс разных воркеров может отличаться из-за внешних причин, таких как конфигурация сети и машины.Как управлять механизмом синхронизации воркеров — относительно важная тема.
1.1 Протокол асинхронного управления
Многие проблемы машинного обучения можно преобразовать в итерационные задачи. Для итеративного управления, как правило, существует три уровня протоколов асинхронного управления: BSP (Bulk Synchronous Parallel), SSP (Stalness Synchronous Parallel) и ASP (Asynchronous Parallel), и их ограничения на синхронизацию в свою очередь ослабляются. В погоне за более высокой скоростью вычислений алгоритм может выбрать более свободный протокол синхронизации.
Чтобы лучше объяснить и завершить текст, мы снова выносим абзацы, введенные в ps-lite.
Эти три соглашения заключаются в следующем:
-
ASP: Нет необходимости ждать друг друга между задачами, а порядок воркеров полностью игнорируется.Каждый воркер ходит в своем темпе и обновляется после итерации.Задача, выполненная первой, переходит к следующему раунду обучения.
-
Плюсы: устраняет время ожидания для медленных задач, сокращает время простоя графического процессора и, следовательно, повышает эффективность оборудования по сравнению с BSP. Скорость вычислений высокая, что позволяет максимально использовать вычислительную мощность кластера, а машины, на которых находятся все рабочие, не должны ждать.
-
недостаток:
- Этот процесс может привести к вычислению градиентов с устаревшими весами, что снижает статистическую эффективность.
- Плохая применимость, сходимость не гарантируется в некоторых случаях
-
-
BSP: это протокол синхронизации, используемый в общих распределенных вычислениях.В каждом раунде итерации ему необходимо дождаться завершения всех вычислений задачи. Каждый рабочий процесс должен выполняться в одной итерации. Только когда все рабочие процессы итеративной задачи будут завершены, будет выполнена синхронизация и обновление сегментов между рабочим процессом и сервером.
-
Режим BSP и одномашинный последовательный режим абсолютно одинаковы с точки зрения сходимости моделей, поскольку разница заключается только в размере пакета. В то же время, поскольку каждый рабочий процесс может выполнять параллельные вычисления за один цикл, он обладает определенной способностью к параллельной работе. Это то, что использует искра.
-
Преимущества: Широкий спектр приложений, высокое качество сходимости на каждой итерации.
-
Недостатки: В каждой итерации BSP требует, чтобы каждый воркер ждал или приостанавливал градиент от других воркеров, так что ему нужно ждать самую медленную задачу, что значительно снижает эффективность оборудования и приводит к длительному времени вычислений для общей задачи. Производительность всей рабочей группы определяется самым медленным рабочим среди них, такого рабочего обычно называют отставшим.
-
-
SSP: допускает определенную степень несогласованности хода выполнения задачи, но у этой несогласованности есть верхний предел, называемый значением устаревания, то есть самая быстрая задача опережает самую медленную задачу устаревающей не более чем на одну итерацию.
-
Это компромисс между ASP и BSP. Поскольку ASP позволяет интервалу итераций между разными рабочими процессами быть сколь угодно большим, а BSP допускает только 0, я беру константу s. С помощью SSP BSP можно получить, указав s=0. ASP также может быть достигнута формулировкой s=∞.
-
Преимущества: Время ожидания между задачами в определенной степени сокращается, а скорость вычислений выше.
-
Недостатки: Качество сходимости каждого раунда итераций не такое хорошее, как у BSP.Для достижения такого же эффекта сходимости может потребоваться больше раундов итераций, а применимость не такая хорошая, как у BSP.Некоторые алгоритмы не применимый.
-
1.2 Проблема отставших
Традиционный способ — использовать BSP для выполнения итерации, что означает, что мы должны синхронизироваться в конце каждого итератора. Это приводит к проблеме отставания: из-за некоторых аппаратных и программных причин вычислительная мощность узлов часто не одинакова. Для итерационной задачи в конце каждого раунда быстрые узлы должны дождаться, пока медленные узлы закончат вычисления, а затем перейти к следующему раунду итерации. Это ожидание становится особенно заметным по мере увеличения количества узлов, что снижает общую производительность.
Есть два способа решить эту проблему:
- Во-первых, нам нужно написать сложный код, чтобы разбалансировать нагрузку, чтобы мы могли быстро обучить больше данных.
- Во-вторых, мы можем выполнить некоторый асинхронный контроль, чтобы ослабить условие синхронности.
Paracel использует второй метод, который ослабляет условие синхронизации, то есть ослабляет ограничение «ожидание каждого шага итерации»:
Это компромисс между сходимостью на итерацию и общим временем сходимости при условии, что синхронизация между самым быстрым и самым медленным рабочим процессом не превышает ограниченного параметра. В конце одной итерации самый быстрый узел может перейти к следующей итерации, но не может опередить самый медленный узел на количество шагов итерации параметра s. Когда опережение превышает s шагов итерации, Paracel заставит подождать.
Этот асинхронный метод управления не только экономит время ожидания в целом, но и косвенно помогает медленным узлам наверстать упущенное. С точки зрения проблемы оптимизации, хотя один шаг итерации сходится медленно, затраты времени на каждый шаг итерации уменьшаются, а общая сходимость происходит быстрее.
Этот подход называется Staleness Synchronous Parallel (SSP).Основная идея состоит в том, чтобы позволить каждой машине обновлять модель с асинхронной настройкой, но добавить ограничение, чтобы разница между прогрессом самой быстрой машины и прогрессом самой медленной машины была не слишком большой. Преимущество этого в том, что он не только освобождает медленную машину от перетаскивания всей системы, но и обеспечивает окончательную сходимость модели.
0x02 Реализация
Сначала мы вспомним архитектуру, описанную ранее.
2.1 ssp_switch
ssp_switch используется для управления использованием ssp.
В качестве примера возьмем paracel_read из include/ps.hpp.
Если sup включен, то:
-
Если на часах 0 или total_iters, значит запущен ssp или истек временной интервал (количество итераций), в это время нужно повторно получить соответствующее значение и обновить кеш.
-
Если кеш попал, он вернется напрямую.
-
Если Miss, если текущие часы уже больше определенного значения
(stale_cache + limit_s < clock)
, цикл while ждет.- То есть узел, который считает быстрее, может перейти к следующей итерации, но не может опередить самый медленный узел на количество шагов итерации параметра s. Paracel заставляет ждать, когда опережение превышает s итераций. так что используйте
pull_int(paracel::str_type("server_clock")
для увеличения часов сервера. Вспомните основную идею SSP, упомянутую ранее (допускается определенная степень несогласованности хода выполнения задачи, но у этой несогласованности есть верхний предел, называемый значением устаревания, то есть самая быстрая задача опережает самые медленные раунды устаревания задачи). - server_clock предназначен для координации часов SSP. «server_clock» — это часы сервера, и работники получают это значение, чтобы увидеть, отстают они или опережают.
- Начальное значение stale_cache равно 0, и оно будет установлено на значение, возвращаемое «server_clock» в каждом принудительном цикле ожидания.
- То есть узел, который считает быстрее, может перейти к следующей итерации, но не может опередить самый медленный узел на количество шагов итерации параметра s. Paracel заставляет ждать, когда опережение превышает s итераций. так что используйте
Где определение кеша:
paracel::dict_type<paracel::str_type, boost::any> cached_para;
template <class V>
bool paracel_read(const paracel::str_type & key,
V & val,
int replica_id = -1) {
if(ssp_switch) {
if(clock == 0 || clock == total_iters) { // check total_iters for last
// 说明是ssp启动或者时间间隔(迭代次数)到了,这时候需要重新获取对应数值,更新cache。
cached_para[key] = boost::any_cast<V>(ps_obj->
kvm[ps_obj->p_ring->get_server(key)].
pull<V>(key));
val = boost::any_cast<V>(cached_para[key]);
} else if(stale_cache + limit_s > clock) {
// cache hit 如果命中缓存,则直接返回
val = boost::any_cast<V>(cached_para[key]);
} else {
// cache miss
// 如果Miss,如果当前时钟已经大于某个数值 ,则 while 循环等待
// pull from server until leading slowest less than s clocks
while(stale_cache + limit_s < clock) {
// 时间同步
stale_cache = ps_obj->
kvm[clock_server].pull_int(paracel::str_type("server_clock"));
}
// 获取key对应权重的最新数值
cached_para[key] = boost::any_cast<V>(ps_obj->
kvm[ps_obj->p_ring->get_server(key)].
pull<V>(key));
val = boost::any_cast<V>(cached_para[key]);
}
return true;
}
return ps_obj->kvm[ps_obj->p_ring->get_server(key)].pull(key, val);
}
int pull_int(const paracel::str_type & key) {
if(p_ssp_sock == nullptr) {
p_ssp_sock.reset(create_req_sock(ports_lst[4]));
}
auto scrip = paste(paracel::str_type("pull_int"), key);
int val = -1;
bool r = req_send_recv(*p_ssp_sock, scrip, val);
if(!r) ERROR_ABORT("key: pull_int does not exist");
return val;
}
2.2 thrd_exec_ssp
В kvclt есть метод pull_int, который взаимодействует с сервером Clock для синхронизации времени:
Конкретный код выглядит следующим образом:
В include/server.hpp thrd_exec_ssp — это поток, который обрабатывает ssp.
Используемый ssp_tbl находится в include/kv_def.hpp.
namespace paracel {
paracel::kvs<paracel::str_type, int> ssp_tbl; // 这里是ssp专用KV存储
paracel::kvs<paracel::str_type, paracel::str_type> tbl_store;
}
// thread entry for ssp
void thrd_exec_ssp(zmq::socket_t & sock) {
paracel::packer<> pk;
paracel::ssp_tbl.set("server_clock", 0);
while(1) {
zmq::message_t s;
sock.recv(&s);
auto scrip = paracel::str_type(static_cast<const char *>(s.data()), s.size());
auto msg = paracel::str_split_by_word(scrip, paracel::seperator);
auto indicator = pk.unpack(msg[0]);
//std::cout << indicator << std::endl;
if(indicator == "push_int") { // 推送数据
auto key = pk.unpack(msg[1]);
paracel::packer<int> pk_i;
auto val = pk_i.unpack(msg[2]);
paracel::ssp_tbl.set(key, val);
bool result = true;
rep_pack_send(sock, result);
}
if(indicator == "incr_int") { // 更改数据
auto key = pk.unpack(msg[1]);
if(paracel::startswith(key, "client_clock_")) {
if(paracel::ssp_tbl.get(key)) {
paracel::ssp_tbl.incr(key, 1);
} else {
paracel::ssp_tbl.set(key, 1);
}
if(paracel::ssp_tbl.get(key) >= paracel::ssp_tbl.get("worker_sz")) {
paracel::ssp_tbl.incr("server_clock", 1);
paracel::ssp_tbl.set(key, 0);
}
}
paracel::packer<int> pk_i;
int delta = pk_i.unpack(msg[2]);
paracel::ssp_tbl.incr(key, delta);
bool result = true;
rep_pack_send(sock, result);
}
if(indicator == "pull_int") { // 拉取数据
auto key = pk.unpack(msg[1]);
int result = 0;
auto exist = paracel::ssp_tbl.get(key, result); // 获取对应的key
if(!exist) {
paracel::str_type tmp = "nokey";
rep_send(sock, tmp);
}
rep_pack_send(sock, result);
}
} // while
}
+------------------+ worker + server
| paralg | |
| | |
| | |
| parasrv *ps_obj | |
| + | | +------------------+
| | | | | start_server |
+------------------+ | | |
| | | |
| | | |
v | | |
+------------+-----+ +------------------+ +---------+ | | |
| parasrv | |kvclt | | kvclt | | | |
| | | | | | | | thrd_exec |
| | | host | | | | | |
| servers | | | | | | | ssp_tbl |
| | | ports_lst | | | | | |
| kvm +-----------> | |.....| | | | tbl_store |
| | | context | | | | | |
| p_ring | | | | | | | thrd_exec_ssp |
| + | | conn_prefix | | | | | |
| | | | | | | | | ^ |
+------------------+ | p_ssp_sock | | | | | | |
| | + | | | | | | |
| | | | | | | | | |
| | | | | | | | | |
v | | | | | | | | |
+------------+------+ +------------------+ +---------+ | | | |
| ring | | | +------------------+
| | | | |
| | | | |
| srv_hashring | | | |
| | | | |
| srv_hashring_dct | +------------------------------------+
| | |
+-------------------+ +
Телефон такой:
Логика следующая (обратите внимание, что из-за нехватки места некоторые переменные на приведенном выше рисунке здесь опущены, а добавлены новые переменные и логика):
Конкретный код thrd_exec_ssp выглядит следующим образом:
В качестве примера возьмем команду pull_int, которая извлекает данные, соответствующие «выделенному хранилищу ssp KV», с сервера.
2.3 Преобразование
Пользователи могут преобразовать процесс BSP в асинхронный процесс, добавив всего несколько строк кода. Как очень простой пример.
Главное — использовать iter_commit() для отправки локального результата обновления на сервер параметров после каждой итерации.
class logistic_regression: public paracel::paralg {
public:
logistic_regression(paracel::Comm comm,
std::string hosts_dct_str,
std::string _output,
int _rounds,
int _limit_s,
bool _ssp_switch) :
paracel::paralg(hosts_dct_str,
comm,
_output,
_rounds,
_limit_s,
_ssp_switch) {}
void training() {
theta = paracel::random_double_list(data_dim);
paracel_write("theta", theta); // init push
for(int iter = 0; iter < rounds; ++iter) {
for(int i = 0; i < data_dim; ++i) {
delta[i] = 0.;
}
random_shuffle(idx.begin(), idx.end());
// pull theta
theta = paracel_read<vector<double> >("theta");
for(auto sample_id : idx) {
for(int i = 0; i < data_dim; ++i) {
delta[i] += coff1 *
samples[sample_id][i] - coff2 * theta[i];
}
} // traverse
// update theta with delta
paracel_bupdate("theta",
delta,
"update.so",
"lg_theta_update");
// commit to server at the end of each iteration
iter_commit(); // 这里是添加的,在每次迭代结束之后,把本地更新结果提交到参数服务器
}
// last pull
theta = paracel_read<vector<double> >("theta");
}
void solve() {
// init training data
auto parser = [](const std::vector<std::string>) {
/* ... */
};
auto lines = paracel_load(input);
parser(lines);
paracel_sync();
// set total iterations of your training process
set_total_iters(rounds);
// training
training();
}
}; // class logistic regression
paralg(paracel::str_type hosts_dct_str,
paracel::Comm comm,
paracel::str_type _output = "",
int _rounds = 1,
int _limit_s = 0,
bool _ssp_switch = false) : worker_comm(comm),
output(_output),
nworker(comm.get_size()),
rounds(_rounds),
limit_s(_limit_s),
ssp_switch(_ssp_switch) {
ps_obj = new parasrv(hosts_dct_str);
init_output(_output);
clock = 0;
stale_cache = 0;
clock_server = 0;
total_iters = rounds;
if(worker_comm.get_rank() == 0) {
paracel::str_type key = "worker_sz";
(ps_obj->kvm[clock_server]).
push_int(key, worker_comm.get_size()); // 设置为 5
}
paracel_sync();
}
// put where you want to control iter with ssp
void iter_commit() {
paracel::str_type clock_key;
if(limit_s == 0) {
clock_key = "client_clock_0";
} else {
clock_key = "client_clock_" + std::to_string(clock % limit_s);
}
ps_obj->kvm[clock_server].incr_int(paracel::str_type(clock_key), 1); // value 1 is not important
clock += 1;
if(clock == total_iters) { // 如果已经达到了总体迭代数值,就减少服务器 "worker_sz" 数值
ps_obj->kvm[clock_server].incr_int(paracel::str_type("worker_sz"), -1);
}
}
bool incr_int(const paracel::str_type & key,
int delta) {
if(p_ssp_sock == nullptr) {
p_ssp_sock.reset(create_req_sock(ports_lst[4]));
}
auto scrip = paste(paracel::str_type("incr_int"),
key,
delta);
bool stat;
auto r = req_send_recv(*p_ssp_sock, scrip, stat);
return r && stat;
}
int pull_int(const paracel::str_type & key) {
if(p_ssp_sock == nullptr) {
p_ssp_sock.reset(create_req_sock(ports_lst[4]));
}
auto scrip = paste(paracel::str_type("pull_int"), key);
int val = -1;
bool r = req_send_recv(*p_ssp_sock, scrip, val);
assert(val != -1);
assert(r);
if(!r) ERROR_ABORT("key: pull_int does not exist");
return val;
}
if(indicator == "incr_int") {
auto key = pk.unpack(msg[1]);
if(paracel::startswith(key, "client_clock_")) {
if(paracel::ssp_tbl.get(key)) {
paracel::ssp_tbl.incr(key, 1); // 把对应的key增加对应的数值
} else {
paracel::ssp_tbl.set(key, 1 // 添加这个数值
}
if(paracel::ssp_tbl.get(key) >= paracel::ssp_tbl.get("worker_sz"))
//所有worker 都完成了一轮迭代
paracel::ssp_tbl.incr("server_clock", 1); //服务器迭代增加1
paracel::ssp_tbl.set(key, 0); //重置为 0,说明需要考虑下次迭代了,因为本次迭代中,所有client都完成了,下次迭代又要重新计算
}
}
paracel::packer<int> pk_i;
int delta = pk_i.unpack(msg[2]);
paracel::ssp_tbl.incr(key, delta);
bool result = true;
rep_pack_send(sock, result);
}
-
Если ключ "client_clock_", то
-
Увеличьте соответствующий ключ на соответствующее значение или добавьте это значение;
-
Если значение ключа больше значения «worker_sz», это означает, что все воркеры завершили раунд итерации, поэтому необходимо:
- Увеличьте значение server_clock на 1. «server_clock» — это часы сервера, и рабочие получают это значение, чтобы увидеть, отстают они или опережают;
- Сброс соответствующего «client_clock_» на 0 означает, что необходимо рассмотреть следующую итерацию.
-
-
Для остальных ключей увеличьте значение параметра;
В thread_exec_ssp часть кода incr_int выглядит следующим образом:
Сервер получает запрос, перенаправленный kvclt, и пример обработки выглядит следующим образом:
2.4.3 Сервер incr_int
В kvclt есть следующий код, который фактически пересылает запрос на сервер, так что мы можем его пропустить:
- iter_commit должен увеличивать локальные часы на каждой итерации;
- Если (clock == total_iters), этот рабочий процесс достиг общего значения итерации, то уменьшите значение сервера "worker_sz". То есть: рабочий закончил обучение, значит, количество рабочих, которые будут обучаться вместе, нужно уменьшить на 1.
В iter_commit логика следующая.
2.4.2 рабочая сторона iter_commit
Значение «worker_sz»: сколько рабочих должно быть обучено вместе в настоящее время.
В функции построения paralg будут инициализированы различные данные.Важным моментом здесь является то, что значение, соответствующее серверному ключу «worker_sz», установлено в worker_comm.get_size() , что является рабочим значением 5.
2.4.1 Инициализация
Предположим, что воркеров 5, а limit_s равно 3, т. е. самый быстрый узел не может опережать самый медленный узел на параметр 3 шага итерации. Paracel вызывает ожидание при опережении более 3 итераций.
На самом деле, мы не объяснили подробно каждую из предыдущих частей, и здесь нужно соединить их последовательно.
2.4 Логическая конкатенация
2.4.4 Конкатенация
Связывая всю логику воедино, существительные объясняются следующим образом:
- client_clock_X, что указывает на то, что в фактической итерации в этом раунде виртуальной итерации несколько рабочих процессов завершили работу, 0
- worker_szУказывает, сколько рабочих должно быть обучено вместе в настоящее время.
- server_clockЭто часы сервера, что означает, что общее обучение завершило несколько итераций (фактических итераций), и рабочий должен получить это значение, чтобы увидеть, отстает он или впереди.
детали следующим образом:
-
limit_s равен 3, т. е. самый быстрый узел не может опережать по параметру 3 шага итерации перед самым медленным узлом. Paracel вызывает ожидание при опережении более 3 итераций. Таким образом, существует два вида итераций:
- Большие итерации — это фиктивные итерации, состоящие из 3 маленьких шагов итерации (количество limit s).
- Малая итерация — это фактический шаг итерации, который представлен client_clock_X, а clock_key_0 представляет, что в первой фактической итерации в этом раунде виртуальных итераций есть несколько рабочих для завершения операции.
-
В функции построения paralg рабочего процесса будут инициализированы различные данные.Важным моментом здесь является то, что значение, соответствующее серверному ключу «worker_sz», установлено в worker_comm.get_size() , что является рабочим значением 5.
Значение «worker_sz»: сколько рабочих должно быть обучено вместе в настоящее время.
-
В воркере paracel_read всегда используйте локальные часы и удаленные "
server_clock
" Сравните, если меньше limit_s, заставьте воркер ждать; -
В рабочем iter_commit:
-
Увеличить значение локальных часов
- Часы увеличиваются с 0, что является фактическим количеством локальных итераций;
- Если (clock == total_iters), это означает, что локальное обучение этого воркера достигло общего значения итерации, тогда уменьшите значение сервера "worker_sz". То есть: рабочий закончил обучение, значит, количество рабочих, которые будут обучаться вместе, нужно уменьшить на 1;
-
Если limit_s равен 3, то clock_key равен client_clock_0, client_clock_1, client_clock_2, в соответствии со значением локальных часов добавьте 1 к серверу (часы % limit_s); clock_key_0 указывает, что в первой фактической итерации в этом раунде виртуальной итерации есть несколько Рабочий заканчивает работу;
-
-
После отправки iter_commit на сервере:
-
Если ключ "client_clock_", то
-
Увеличить соответствующий ключ на соответствующее значение;
-
Если значение ключа больше значения «worker_sz», это означает, что все воркеры завершили раунд итерации, поэтому необходимо:
- Увеличьте значение server_clock на 1. «server_clock» — это часы сервера, и рабочие получают это значение, чтобы увидеть, отстают они или опережают;
- Сброс соответствующего «client_clock_» на 0 означает, что необходимо рассмотреть следующую итерацию.
-
-
Для остальных ключей увеличьте значение параметра;
-
Мы можем посмотреть на логическую схему:
worker 1 + Server 1
|
快 |
+-----------------------------------------+ | +-------------------------------------+
| paracel_read() { | | | |
| | | |auto key = pk.unpack(msg[1]); |
| while(stale_cache + limit_s < clock) { | | |if(startswith(key, "client_clock_")){|
| stale_cache = get("server_clock") | | | if(ssp_tbl.get(key)) { |
| } | | | incr(key, 1); |
| } | | | } else { |
+-----------------------------------------+ | | set(key, 1); |
| | } |
+---------------------------------------------+ | if(get(key) >= get("worker_sz")) { |
worker 2 | | incr("server_clock", 1); |
慢 | | set(key, 0); |
+-----------------------------------------+ | | } |
| iter_commit() { | | |} |
| | | |ssp_tbl.incr(key, delta); |
| if(limit_s == 0) { | | | |
| clock_key = "client_clock_0" | | +-------------------------------------+
| } else { | |
| clock_key = "client_clock_" + | |
| (clock % limit_s) | |
| } | |
| | |
| incr_int(clock_key, 1); | |
| | |
| clock += 1; | |
| | |
| if(clock == total_iters) { | |
| incr_int("worker_sz"), +1); | |
| } | |
| } | |
| } | |
+-----------------------------------------+ +
Телефон такой:
Мы также можем использовать диаграмму, чтобы показать логический процесс, где:
- client_clock_1 обозначается аббревиатурой c_c_1, что указывает на то, что в фактической итерации в этом раунде виртуальной итерации несколько рабочих завершили операцию;
- worker_sz обозначается аббревиатурой w_sz и указывает, сколько серверов в настоящее время следует обучать вместе.
- server_clock сокращенно s_c. «server_clock» — это часы сервера, что означает, что общее обучение завершило несколько итераций (фактических итераций), и рабочий процесс должен получить это значение, чтобы увидеть, отстает он или опережает.
- Эти переменные являются переменными на стороне сервера.
Сначала начните обучение и выполняйте его в порядке сверху вниз в таблице.
Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 5 | Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker2 | ||||||
worker3 | ||||||
worker4 | ||||||
worker5 |
Второй рабочий начинает обучение и фактически обучается в два этапа, добавляя c_c_0, c_c_1
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 5 | Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker2 | 2 | 2 | 5 | Второй рабочий начинает обучение и фактически обучается в два этапа, добавляя c_c_0, c_c_1 | ||
worker3 | ||||||
worker4 | ||||||
worker5 |
Третий рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1
Третий рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 5 | Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker2 | 2 | 2 | 5 | Второй рабочий начинает обучение и фактически обучается в два этапа, добавляя c_c_0, c_c_1 | ||
worker3 | 3 | 3 | 5 | Третий рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker4 | ||||||
worker5 |
Четвертый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 5 | Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker2 | 2 | 2 | 5 | Второй рабочий начинает обучение и фактически обучается в два этапа, добавляя c_c_0, c_c_1 | ||
worker3 | 3 | 3 | 5 | Третий рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker4 | 4 | 4 | 5 | Четвертый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker5 |
Пятый рабочий начинает обучение, фактически обучает один шаг и увеличивает c_c_0.Поскольку одна фактическая итерация была завершена, server_clock увеличивается на 1.
На данный момент worker 5 отстает на одну итерацию (server_clock = 1).
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 5 | Первый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker2 | 2 | 2 | 5 | Второй рабочий начинает обучение и фактически обучается в два этапа, добавляя c_c_0, c_c_1 | ||
worker3 | 3 | 3 | 5 | Третий рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker4 | 4 | 4 | 5 | Четвертый рабочий начинает обучение, а фактическое обучение состоит из двух шагов, добавляя c_c_0, c_c_1 | ||
worker5 | 5 --> 0 | 5 | 1 | Пятый рабочий начинает обучение, и фактический шаг обучения заключается в увеличении c_c_0.Поскольку все 5 рабочих выполнили одну фактическую итерацию, server_clock увеличивается на 1, а затем соответствующий «client_clock_0» сбрасывается на 0, что означает, что он необходимо рассмотреть в следующий раз. |
Посмотрите нижеОсобые случаи.
Во-первых, все 4 воркера выполнили 3 шага, но воркер 5 не работает. Статус следующий:
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 1 | 5 | Первый рабочий в этом раунде фактически обучает три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker2 | 2 | 2 | 2 | 5 | Второй рабочий в этом раунде фактически тренируется на три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker3 | 3 | 3 | 3 | 5 | Третий рабочий в этом раунде фактически тренируется на три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker4 | 4 | 4 | 4 | 5 | Четвертый рабочий в этом раунде фактически тренируется в три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker5 |
Предполагая, что в iter_commit воркера 5, если воркер 5 находит себя (часы == total_iters), это означает, что этот воркер 5 достиг общего значения итерации и уменьшает значение сервера "worker_sz". То есть: рабочий закончил обучение, значит, количество рабочих, которые будут обучаться вместе, нужно уменьшить на 1;
Поскольку воркер 5 выполняет сразу 3 шага обучения, s_c становится равным 3, то есть общее количество итераций равно 3.
Поскольку в этой виртуальной итерации все 5 воркеров прошли обучение, поэтому c_c_1 ~ c_c_2 сначала становятся 5, а затем сбрасываются до 0.
c_c_0 | c_c_1 | c_c_2 | w_sz | s_c | инструкция | |
---|---|---|---|---|---|---|
worker1 | 1 | 1 | 1 | 5 | Первый рабочий в этом раунде фактически обучает три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker2 | 2 | 2 | 2 | 5 | Второй рабочий в этом раунде фактически тренируется на три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker3 | 3 | 3 | 3 | 5 | Третий рабочий в этом раунде фактически тренируется на три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker4 | 4 | 4 | 4 | 5 | Четвертый рабочий в этом раунде фактически тренируется в три шага, добавляя c_c_0, c_c_1, c_c_2. | |
worker5 | 5 --> 0 | 5 --> 0 | 5 --> 0 | 4 | 3 | Обучение пятого рабочего в этом раунде завершено, и рабочий 5 снова оказывается (часы == total_iters), тогда значение "worker_sz" уменьшается на 1, и в дальнейшем нужно смотреть только 4 рабочих. |
На данный момент мы завершили анализ SSP и последующую загрузку данных анализа/модели.
0xEE Личная информация
★★★★★★Думая о жизни и технологиях★★★★★★
Публичный аккаунт WeChat:мысли Росси