Запутанная древовидная структура заставляет GC останавливаться на неопределенный срок
Я занимаюсь самообучением, и в настоящее время я использую автоматическое дифференцирование в обратном режиме на практике.
Программа работает по существу путем перегрузки общих выражений, таких как умножение, сложение и т. Д., И построения дерева, узлы которого впоследствии будут вызываться при обратном проходе сверху вниз. Вероятно, это первый раз, когда я использовал замыкания в F#, и они мне очень нравятся, но, к сожалению, я подозреваю, что они душат GC, хотя я не знаю, как это проверить. Я предпочел бы сначала спросить здесь, чем перепроектировать алгоритм, чтобы он не использовал их, поскольку они довольно удобны.
Указанное дерево создается много раз во время выполнения программы, и я рассчитываю на сборщик мусора, чтобы избавиться от него.
В нижней части программы находится основной цикл, который обучает двухслойную сеть по проблеме XOR. Вызов GC.Collect() перед циклом или после цикла, а затем повторный запуск приводит его к неопределенному перерыву.
Вот структуры данных, которые я использую. Я хотел бы получить несколько советов о том, верна ли моя догадка, и мне следует изменить алгоритм, чтобы не использовать замыкания, или что-то еще не так.
type dMatrix(num_rows:int,num_cols,dArray: DeviceMemory<float32>) =
inherit DisposableObject()
new(num_rows,num_cols) =
new dMatrix(num_rows,num_cols,worker.Malloc<float32>(num_rows*num_cols))
member t.num_rows = num_rows
member t.num_cols = num_cols
member t.dArray = dArray
override net.Dispose(disposing:bool) =
if disposing then
dArray.Dispose()
type Df_rec = {
P: float32
mutable c : int
mutable A : float32
}
type DM_rec = {
P: dMatrix
mutable c : int
mutable A : dMatrix
}
type Rf =
| DfR_Df_DM of Df_rec * (float32 -> dMatrix) * RDM
| DfR_Df_Df of Df_rec * (float32 -> float32) * Rf
and RDM =
| DM of DM_rec
| DMRb of DM_rec * (dMatrix -> dMatrix) * (dMatrix -> dMatrix) * RDM * RDM // Outside node * left derivative function * right derivative func * prev left node * prev right node.
| DMRu of DM_rec * (dMatrix -> dMatrix) * RDM
Он использует многие функции, такие как приведенная ниже. Sgemm - это оболочка cuBLAS sgemm. затмение и затвор - это замыкания. Они удобны, но GC может столкнуться с трудностями при очистке дерева.
let matmult (a: RDM) (b:RDM) =
let mm va vb =
let c = sgemm nT nT 1.0f va vb
let fl out = sgemm nT T 1.0f out vb // The derivative with respect to the left. So the above argument gets inserted from the right left. Usually error * input.
let fr out = sgemm T nT 1.0f va out // The derivative with respect to the right. So the above argument gets inserted from the right side. Usually weights * error.
DMRb(DM_rec.create c,fl,fr,a,b)
let va = a.r.P
let vb = b.r.P
mm va vb
В идеале, я бы использовал дерево повторно, но поскольку AD может распространять градиенты ошибок через циклы и ветви, это не вариант, и программе приходится выполнять большое динамическое распределение, даже если оно медленное. Любой совет, как эффективно распоряжаться этими деревьями?
Благодарю.
Вот ссылка на страницу Github с полной программой.
Изменить: я преобразовал код, чтобы не использовать замыкания, и все же он все еще падает при сборке мусора. Я не уверен, что происходит под капотом.
Edit2: мне наконец пришло в голову использовать отладчик, и я вижу, что я получаю "System.AccessViolationException", брошенный библиотекой Alea. Это может быть связано с более ранней ошибкой, о которой я сообщил в модуле суммы. На самом деле я видел смещенный доступ один или два раза раньше, но мой мозг почему-то просто игнорировал его. Я постараюсь выделить ошибку завтра.
Edit3: решена проблема. Это происходило из-за неисправного модуля приведения Alea и не имело ничего общего с замыканиями или древовидной структурой, которые я использовал.
Также я убедился, что нет причин не использовать замыкания, так как они не предполагают какого-либо снижения производительности по сравнению с явной передачей параметров. Это приятно и удивительно.