Change:
cuDNN backend for RNN stabilizes at large mem usage. The following code stabilizes for me at 14+GiB reported by nvidia-smi. The same RNN without cuDNN uses <2.5GiB.
Oops... any idea what's going on with the garbage collector in this case?
are you running this on a P100 GPU?
Yes, I observe similar behavior on Titan X Pascal
hmmm, do cudnn RNNs have weird memory-oversubscribing properties? If it wasn't for CuDNN RNN, then it's not unreasonable. we use a custom CUDA allocator that will oversubscribe if needed, but will free the memory blocks it's holding onto if it hits OOM / failed allocations.
Tried the same RNN in torch and am getting ~2.6GiB on nvidia-smi.
With cuDNN I'm getting a constant usage of 2241MB on my machine using PyTorch (M40).
are you using cudnnv5, or v6?
v5
no, cudnn does not do memory oversubscribing, and already tried the same network in lua torch with cudnn, where it gets expected memory usage. Also, he is getting OOMs on slightly bigger networks, where apparently caching allocator should have tried to free previous blocks. Interesting that can not repro this.
Just to make sure did you set when you ran? I left it as False in the repro.
I can repro in the docker image built from the current version on P100, and I go OOM after 8 batches with the same image on 1080, which generally should not happen - if there is enough memory to go through 1 batch, memory usage should not increase afterwards.
No, I just copied your script and ran it. Weird thing is that it goes OOM after 18 batches once I enable . Without it memory remains stable.
A lot of cudaMalloc allocations go not through THCudaMalloc, and never get freed:
So not going through THCudaMalloc is actually expected for storages allocated with THCStorage_(newWithSize), but what's not expected is to every time cudaMalloc bytes of rnn workspace, instead of reusing earlier allocations. (~784 MB allocations are for workspace, ~156MB allocations happen somewhere in criterion).
My guess is that the bug is at the Python level. Either: Something is holding on to a Tensor longer than it should Something is creating a circular reference which prevents quick clean-up
The issue is that in creates cyclic references. It's made worse by the fact that it holds on to more memory than it should (like the workspace). You can see this by adding a as the first line in your for loop. With the explicit call to the GC every iteration, the memory usage doesn't explode.
how can these cyclic references be fixed?
Not sure yet, but I will fix it tomorrow with

Discussion:
TEST_CUDA = torch.cuda.is_available() TEST_MULTIGPU = TEST_CUDA and torch.cuda.device_count() >= 2 TEST_CUDNN = TEST_CUDA and torch.backends.cudnn.is_acceptable(torch.cuda.FloatTensor(1)) <ins> TEST_CUDNN_VERSION = TEST_CUDNN and torch.backends.cudnn.version() </ins> PRECISION = 1e-5 module_tests = [

from torch.autograd import Variable from torch.nn import Parameter from common_nn import NNTestCase, ModuleTest, CriterionTest, TestBase,  <del> module_tests, criterion_tests, TEST_CUDA, TEST_MULTIGPU, TEST_CUDNN, PRECISION </del> <ins> module_tests, criterion_tests, TEST_CUDA, TEST_MULTIGPU, TEST_CUDNN,  TEST_CUDNN_VERSION, PRECISION </ins> from common import freeze_rng_state, run_tests

(hx + cx).sum().backward() <del> @unittest.skipIf(not TEST_CUDNN, "needs cudnn") @default_tensor_type(torch.FloatTensor)  # FIXME: just until torch.cuda.DoubleTensor.sum() implemented def test_RNN_cpu_vs_cudnn(self): </del> <ins> def _test_RNN_cpu_vs_cudnn(self, dropout): </ins> def forward_backward(cuda, rnn, input_val, hx_val, weights_val): is_lstm = type(rnn) == nn.LSTM

for module in (nn.RNN, nn.LSTM, nn.GRU): for bias in (True, False): for bidirectional in (False, True): <del> for dropout in (0, 1):  # Because of dropout randomness, can only compare 0 and 1 for batch_first in (False, True): num_directions = 2 if bidirectional else 1 if batch_first: input_val = torch.randn(batch, seq_length, input_size) else: input_val = torch.randn(seq_length, batch, input_size) hx_val = torch.randn(num_layers * num_directions, batch, hidden_size) rnn = module(input_size, </del> <ins> for batch_first in (False, True): num_directions = 2 if bidirectional else 1 if batch_first: input_val = torch.randn(batch, seq_length, input_size) else: input_val = torch.randn(seq_length, batch, input_size) hx_val = torch.randn(num_layers * num_directions, batch, hidden_size) rnn = module(input_size, hidden_size, num_layers, bias=bias, dropout=dropout, bidirectional=bidirectional, batch_first=batch_first) outputs_cpu = forward_backward( False, rnn, input_val, hx_val, rnn.all_weights) rnn_gpu = module(input_size, </ins> hidden_size, num_layers, bias=bias,

bidirectional=bidirectional, batch_first=batch_first) <del> outputs_cpu = forward_backward( False, rnn, input_val, hx_val, rnn.all_weights) rnn_gpu = module(input_size, hidden_size, num_layers, bias=bias, dropout=dropout, bidirectional=bidirectional, batch_first=batch_first) </del> <ins> outputs_gpu = forward_backward( True, rnn_gpu, input_val, hx_val, rnn.all_weights) </ins> <del> outputs_gpu = forward_backward( True, rnn_gpu, input_val, hx_val, rnn.all_weights) compare_cpu_gpu(outputs_cpu, outputs_gpu) </del> <ins> compare_cpu_gpu(outputs_cpu, outputs_gpu) </ins> for nonlinearity in ('tanh', 'relu'): hx_val = torch.randn(num_layers, batch, hidden_size)

compare_cpu_gpu(outputs_cpu, outputs_gpu) @unittest.skipIf(not TEST_CUDNN, "needs cudnn") <ins> @default_tensor_type(torch.FloatTensor)  # FIXME: just until torch.cuda.DoubleTensor.sum() implemented def test_RNN_cpu_vs_cudnn_no_dropout(self): self._test_RNN_cpu_vs_cudnn(0) @unittest.skipIf(not (TEST_CUDNN and TEST_CUDNN_VERSION >= 5103), "needs cudnn >= 5.1") @default_tensor_type(torch.FloatTensor)  # FIXME: just until torch.cuda.DoubleTensor.sum() implemented def test_RNN_cpu_vs_cudnn_with_dropout(self): # Because of dropout randomness, can only compare dropout=0 and dropout=1 self._test_RNN_cpu_vs_cudnn(1) @unittest.skipIf(not (TEST_CUDNN and TEST_CUDNN_VERSION >= 5103), "needs cudnn >= 5.1") </ins> def test_RNN_dropout(self): # checking the assumption that cuDNN sticks dropout in between # RNN layers

self.assertEqual(hy.data[0][0][0], 10) self.assertEqual(hy.data[1][0][0], output_val) <del> @unittest.skipIf(not TEST_CUDNN, "needs cudnn") </del> <ins> @unittest.skipIf(not (TEST_CUDNN and TEST_CUDNN_VERSION >= 5103), "needs cudnn >= 5.1") </ins> def test_RNN_dropout_state(self): import sys if sys.version_info[0] == 2:

return _iter <ins> def _unflatten(input, proto): # unflatten a list or tuple input into a nested list/tuple structure # specified by proto def unflatten_helper(input, proto): res = [] if not isinstance(proto, (list, tuple)): return input[0], input[1:] for e in proto: res_e, input = unflatten_helper(input, e) res.append(res_e) return type(proto)(res), input return unflatten_helper(input, proto)[0] </ins> _iter_variables = _iter_filter(lambda o: isinstance(o, torch.autograd.Variable)) _iter_tensors = _iter_filter(torch.is_tensor) _iter_None_tensors = _iter_filter(lambda o: o is None or torch.is_tensor(o)) _map_variable_tensor = _nested_map(lambda o: isinstance(o, torch.autograd.Variable), lambda o: o.data) <del> def _map_tensor_fromiter(itr): return _nested_map(lambda o: torch.is_tensor(o), lambda o: next(itr)) </del> class NestedIOFunction(Function): def _do_forward(self, *input):

flat_input = tuple(_iter_variables(input)) flat_output = super(NestedIOFunction, self)._do_forward(*flat_input) nested_output = self._nested_output <del> nested_variables = _map_tensor_fromiter(iter(flat_output))(self._nested_output) </del> <ins> nested_variables = _unflatten(flat_output, self._nested_output) </ins> return nested_variables def backward(self, *gradients): <del> nested_gradients = _map_tensor_fromiter(iter(gradients))(self._nested_output) </del> <ins> nested_gradients = _unflatten(gradients, self._nested_output) </ins> del self._nested_output result = self.backward_extended(*nested_gradients) del self._to_save_nested

@property def saved_tensors(self): flat_tensors = super(NestedIOFunction, self).saved_tensors <del> return _map_tensor_fromiter(iter(flat_tensors))(self._to_save_nested) </del> <ins> return _unflatten(flat_tensors, self._to_save_nested) </ins> def mark_dirty(self, *args, **kwargs): self.dirty_tensors = tuple(_iter_tensors((args, kwargs)))