Address ref objects and seq overhead in autograd
Following refactoring of the autograd in #333, we got a huge perf an memory usage improvement by not creating a graph at all when in inference mode: 21% on example 06 Shakespeare text generation.

This is probably due to nodes created during inference not being cleaned up and choking the GC. 

However, isolating the proc in charge of registering nodes into the graph `foo_cache` and `register_node` highlighted a critical overhead.

See how much time linear_cache, sigmoid_cross_entropy_cache, relu_cache takes compared to the proc, with a special attention to the `register_node` proc

![2018-12-16_00-32-17](https://user-images.githubusercontent.com/22738317/50048394-2e7fcf80-00cb-11e9-9884-913ab157bcad.png)

The cache procs are pretty much self-contained, for example for relu_cache:

```Nim
proc relu_cache[TT](result: Variable[TT], a: Variable[TT]) =
  # Gate
  var gate: ReluActivation[TT]
  new gate
  gate.cache = result.value

  # Result setup
  result.grad = zeros_like(result.value)
  result.requires_grad = true

  # Add to graph
  register_node(
    "Relu",
    gate,
    relu_backward_ag[TT],
    result,
    a
  )
```

And `register_node`
```Nim
func push[TT](ctx: Context[TT], node: Node[TT]) {.inline.}=
  ## Append a new operation to the context
  ctx.nodes.add(node)

func register_node*[TT](
        name: static string,
        gate: Gate[TT],
        backward: Backward[TT],
        result: Variable[TT] or seq[Variable[TT]],
        parents: varargs[Variable[TT]]) =
  ## Add an operation / gate as a new node in the computation graph
  var node: Node[TT]

  node.gate = gate
  node.backward = backward
  node.parents = @parents

  when result is Variable:
    node.payload = Payload[TT](kind: pkVar, variable: result)
  else:
    node.payload = Payload[TT](kind: pkSeq, sequence: result)

  when defined(debug):
    node.name = name

  parents[0].context.push node
```
