这是我试图并行化的伪代码(取自 word2vec C 代码)。首先,我将列出具有相应大小的数据结构,然后是伪代码:
1. long long sen[MAX_SENTENCE_LENGTH]
// In the C code, MAX_SENTENCE_LENGTH = 1000. Increasing this should be
//fine.
2. float neu1[N] (hidden layer values)
//N is the length of each vector. For now, max N = 400
3. float neu1e[N] (hidden layer error values)
4. float syn0[V * N] (input to hidden layer weight matrix)
// For now, we can assume that V * N is small enough to be stored on the GPU
// In the test data, V = 72k words
5. float syn1neg[V * N] (back propagation weights used during negative
sampling)
6. float exptable[1000]
程序的输入是一个文本文件。然后程序一次处理一个单词以建立一个词汇表。例如,如果我的文本文件有一个句子
“并行编程非常有趣”</p>
那么词汇表看起来像这样(因为代码根据单词的频率对词汇表进行排序):
{“Very:2”, “Parallel:1”, “programming:1”, “is:1”, “interesting:1”}
0 1 2 3 4
构建词汇表后,代码再次开始处理文本,一次处理 1000 个单词。前 1000 个单词存储在 中sen[MAX_SENTENCE_LENGTH]
,然后针对 中的所有单词训练一个神经网络sen
,这个过程一直持续到文件末尾。对于上面的句子,sen
将如下所示[1,2,3,0,0,4]
。
假设训练只在一次迭代中完成,伪代码如下:
for sen in text
{
for word in sen
{
for (c = 0; c < N; c++)
neu1[c] = 0;
for (c = 0; c < N; c++)
neu1e[c] = 0;
/*The variable window is a user supplied parameter.
It is used to consider the context around a word in a sentence.
For example, if I am looking at the first word in the sentence
(target word is word1), and window = 5, then the words in the
window = {word2, word3, word4, word5}.
If I am looking at the third word in the sentence
(target word is word3), then window = {word1, word2, word4, word5}*/
for word in window
{
for (c = 0; c < N; c++)
neu1[c] += syn0[c + word * N];
}
for (c = 0; c < N; c++)
neu1[c] /= window;
//negative: number of negative samples to provide (assume it to be
//between 5 to 25)
for (d = 0; d < negative + 1; d++)
{
target = sen[random_index]
l2 = target * N;
f = 0;
for (c = 0; c < N; c++)
f += neu1[c] * syn1neg[c + l2];
gradient = exptable[function of f] //f is calculated in the loop above
for (c = 0; c < N; c++)
neu1e[c] += gradient * syn1neg[c + l2];
for (c = 0; c < N; c++)
syn1neg[c + l2] += gradient * neu1[c];
} //Negative Sampling ends
for word in window
{
for (c = 0; c < N; c++)
syn0[c + word * N] += neu1e[c];
}
} // word in sen loop ends
} // sen in text loop ends
我认为并行化的最佳方法是并行处理句子中的单词。考虑到所有循环,我认为我应该N
每个单词使用线程,以便单个线程在syn0, syn1neg
每个循环中只访问一次全局内存()。此外,由于所有neu1
和neu1e
更新都是独立的,它们可以驻留在线程的私有内存中并独立更新。
我现在主要担心以下几点:
- 全局内存访问以随机方式发生,因为
syn0
和syn1neg
是根据word
变量的值(词汇表中的索引)访问的。而且,正如我们所见,句子中的单词不会以任何顺序出现。
这是一个大问题吗?或者,我们可以通过为 GPU 提供足够数量的线程来隐藏内存延迟吗?另外,我不明白这种访问模式是否真的是随机的,因为 N 个线程/字将访问 syn0 和 syn1neg 中的顺序数据,但下一组 N 个线程可能会访问位于内存中很远的顺序数据。
- 在负采样循环中,需要进行归约操作。该变量
f
是点积的总和。问题是我打算存储neu1
在每个线程的私有内存中,而syn1neg
在全局内存中。
负采样是否需要单独的内核?看起来它需要一种与仅启动 N 个线程/单词不同的方法,但我不确定哪种方法最有效。
除了这些问题,请提出我处理此代码的方式是否存在问题。