clab / clab/dynet

Lockless support for parallelism?

Open
#748 3 comments 0 reactions 0 assignees View on GitHub
question
Dominant language
C++
Stars
3.4k
Forks
701
PR merge metrics
No merged PRs in 30d

Description

Hello,

I am the studying the support for parallelism on Dynet and I've come across this description:

> Internally, DyNet launches a pool of training processes and automatically handles passing data examples to each worker. Each worker process individually processes a datum, computing the results of the forward and backward passes, computes gradients with respect to each parameter, and passes these results back to the parent process via a shared memory variable. **Whenever the parent process, which is also processing data, completes a gradient computation, it averages all of the gradients currently in the shared memory gradient storage and updates all parameters with respect to that average gradient**. In this way running training on n cores is similar to training with a stochastic minibatch size with expected value of approximately n. This method is quite efficient, achieving nearly linear speedups with increasing numbers of cores, **due to its lockless nature**.

from here https://github.com/clab/dynet/blob/master/doc/source/multiprocessing.rst

Is this still true?

Then, I found this snippet of code from https://github.com/clab/dynet/blob/v2.0/dynet/mp.h#L252:

bool do_update = !header.is_dev_set && cid == 0;
unsigned counter = 0;
if (!header.is_dev_set) {
shared_object->counter_mutex.wait();
counter = ++shared_object->counter;
if (do_update) { shared_object->counter = 0; }
shared_object->counter_mutex.post();
}
if (do_update && trainer != nullptr) {
shared_object->update_mutex.wait();
trainer->update(1.0 / counter);
shared_object->update_mutex.post();
}
if (batch_counter == header.report_frequency) {
if (cid == 0) {
std::cerr << (header.is_dev_set ? "dev" : "train") << " loss: " << batch_loss << std::endl;
}
batch_loss = S();
batch_counter = 0;
}
}
if (header.end_of_epoch && trainer != nullptr) {
trainer->update_epoch();
}

// Let the parent know that we're done and return the loss value
write_data(workloads[cid].c2p[1], total_loss);

From my understanding, the _update_mutex_ is acquired before the gradients are updated. So in fact, locks are used?

Also, could you please tell me in which part of the code this is performed by the parent process?

_it averages all of the gradients currently in the shared memory gradient storage and updates all parameters with respect to that average gradient_

I could found how you pass the losses from the children to the parents, but not the aforementioned averaging of gradients.

Thanks in advance.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with doc/source/multiprocessing.rst and dynet/mp.h around the cited v2.0 code. Trace the counter_mutex, update_mutex, shared gradient storage, and parent/worker paths to determine whether the lockless description is still accurate and where averaging occurs. Done means the documentation answers both questions and identifies the relevant implementation path.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
distributed-systems
Issue type
Documentation
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.