• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Cudnn RNN operators."""
16from __future__ import absolute_import
17from __future__ import division
18from __future__ import print_function
19
20from tensorflow.contrib.cudnn_rnn.python.ops import cudnn_rnn_ops
21from tensorflow.python.framework import dtypes
22from tensorflow.python.framework import ops
23from tensorflow.python.framework import tensor_shape
24from tensorflow.python.keras.engine import input_spec
25from tensorflow.python.layers import base as base_layer
26from tensorflow.python.ops import array_ops
27from tensorflow.python.ops import init_ops
28from tensorflow.python.ops import variable_scope as vs
29from tensorflow.python.platform import tf_logging as logging
30
31
32CUDNN_RNN_UNIDIRECTION = cudnn_rnn_ops.CUDNN_RNN_UNIDIRECTION
33CUDNN_RNN_BIDIRECTION = cudnn_rnn_ops.CUDNN_RNN_BIDIRECTION
34CUDNN_LSTM = cudnn_rnn_ops.CUDNN_LSTM
35CUDNN_GRU = cudnn_rnn_ops.CUDNN_GRU
36CUDNN_RNN_RELU = cudnn_rnn_ops.CUDNN_RNN_RELU
37CUDNN_RNN_TANH = cudnn_rnn_ops.CUDNN_RNN_TANH
38
39# Half for cell input, half for hidden states.
40CUDNN_LSTM_PARAMS_PER_LAYER = cudnn_rnn_ops.CUDNN_LSTM_PARAMS_PER_LAYER
41CUDNN_GRU_PARAMS_PER_LAYER = cudnn_rnn_ops.CUDNN_GRU_PARAMS_PER_LAYER
42CUDNN_RNN_TANH_PARAMS_PER_LAYER = cudnn_rnn_ops.CUDNN_RNN_TANH_PARAMS_PER_LAYER
43CUDNN_RNN_RELU_PARAMS_PER_LAYER = cudnn_rnn_ops.CUDNN_RNN_RELU_PARAMS_PER_LAYER
44
45CUDNN_INPUT_LINEAR_MODE = cudnn_rnn_ops.CUDNN_INPUT_LINEAR_MODE
46CUDNN_INPUT_SKIP_MODE = cudnn_rnn_ops.CUDNN_INPUT_SKIP_MODE
47CUDNN_INPUT_AUTO_MODE = cudnn_rnn_ops.CUDNN_INPUT_AUTO_MODE
48
49
50__all__ = ["CudnnLSTM", "CudnnGRU", "CudnnRNNTanh", "CudnnRNNRelu"]
51
52
53class _CudnnRNN(base_layer.Layer):
54  # pylint:disable=line-too-long
55  """Abstract class for RNN layers with Cudnn implementation.
56
57  Cudnn RNNs have two major differences from other platform-independent RNNs tf
58  provides:
59  * Cudnn LSTM and GRU are mathematically different from their tf counterparts.
60    (e.g. `tf.contrib.rnn.LSTMBlockCell` and `tf.nn.rnn_cell.GRUCell`.
61  * Cudnn-trained checkpoints are not directly compatible with tf RNNs:
62    * They use a single opaque parameter buffer for the entire (possibly)
63      multi-layer multi-directional RNN; Whereas tf RNN weights are per-cell and
64      layer.
65    * The size and layout of the parameter buffers may change between
66      CUDA/CuDNN/GPU generations. Because of that, the opaque parameter variable
67      does not have a static shape and is not partitionable. Instead of using
68      partitioning to alleviate the PS's traffic load, try building a
69      multi-tower model and do gradient aggregation locally within the host
70      before updating the PS. See https://www.tensorflow.org/performance/performance_models#parameter_server_variables
71      for a detailed performance guide.
72
73  Consequently, if one plans to use Cudnn trained models on both GPU and CPU
74  for inference and training, one needs to:
75  * Create a CudnnOpaqueParamsSaveable subclass object to save RNN params in
76    canonical format. (This is done for you automatically during layer building
77    process.)
78  * When not using a Cudnn RNN class, use CudnnCompatibleRNN classes to load the
79    checkpoints. These classes are platform-independent and perform the same
80    computation as Cudnn for training and inference.
81  Similarly, CudnnCompatibleRNN-trained checkpoints can be loaded by CudnnRNN
82  classes seamlessly.
83
84  Below is a typical workflow(using LSTM as an example):
85  for detailed performance guide.
86
87  # Use Cudnn-trained checkpoints with CudnnCompatibleRNNs
88  ```python
89  with tf.Graph().as_default():
90    lstm = CudnnLSTM(num_layers, num_units, direction, ...)
91
92    outputs, output_states = lstm(inputs, initial_states, training=True)
93
94    # If user plans to delay calling the cell with inputs, one can do
95    # lstm.build(input_shape)
96
97    saver = Saver()
98
99    # training subgraph
100    ...
101
102    # Once in a while save the model.
103    saver.save(save_path)
104
105  # Inference subgraph for unidirectional RNN on, e.g., CPU or mobile.
106  with tf.Graph().as_default():
107    single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell(num_units)
108
109    # NOTE: Even if there's only one layer, the cell needs to be wrapped in
110    # MultiRNNCell.
111    cell = tf.nn.rnn_cell.MultiRNNCell(
112      [single_cell() for _ in range(num_layers)])
113
114    # Leave the scope arg unset.
115    outputs, final_state = tf.nn.dynamic_rnn(cell, inputs, initial_state, ...)
116
117    saver = Saver()
118
119    # Create session
120    sess = ...
121
122    # Restores
123    saver.restore(sess, save_path)
124
125  # Inference subgraph for bidirectional RNN
126  with tf.Graph().as_default():
127    single_cell = lambda: tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell(num_units)
128    cells_fw = [single_cell() for _ in range(num_layers)]
129    cells_bw = [single_cell() for _ in range(num_layers)]
130
131    # Leave the scope arg unset.
132    (outputs, output_state_fw,
133     output_state_bw) = tf.contrib.rnn.stack_bidirectional_dynamic_rnn(
134         cells_fw, cells_bw, inputs, ...)
135    saver = Saver()
136
137    # Create session
138    sess = ...
139
140    # Restores
141    saver.restore(sess, save_path)
142  ```
143  """
144  # pylint:enable=line-too-long
145
146  # TODO(allenl): Document object-based saving and checkpoint compatibility once
147  # it's implemented for more cuDNN Layers.
148
149  # The following are constants defined by subclasses.
150  # Type of RNN cell.
151  _rnn_mode = None
152  # Number of cell weights(or biases) per layer.
153  _num_params_per_layer = None
154  # Custom SaveableObject class for the CudnnRNN class.
155  _saveable_cls = None
156
157  def __init__(self,
158               num_layers,
159               num_units,
160               input_mode=CUDNN_INPUT_LINEAR_MODE,
161               direction=CUDNN_RNN_UNIDIRECTION,
162               dropout=0.,
163               seed=None,
164               dtype=dtypes.float32,
165               kernel_initializer=None,
166               bias_initializer=None,
167               name=None):
168    """Creates a CudnnRNN model from model spec.
169
170    Args:
171      num_layers: the number of layers for the RNN model.
172      num_units: the number of units within the RNN model.
173      input_mode: indicate whether there is a linear projection between the
174          input and the actual computation before the first layer. It can be
175          'linear_input', 'skip_input' or 'auto_select'.
176          'linear_input' (default) always applies a linear projection of input
177          onto RNN hidden state. (standard RNN behavior).
178          'skip_input' is only allowed when input_size == num_units;
179          'auto_select' implies 'skip_input' when input_size == num_units;
180          otherwise, it implies 'linear_input'.
181      direction: the direction model that the model operates. Can be either
182          'unidirectional' or 'bidirectional'
183      dropout: dropout rate, a number between [0, 1]. Dropout is applied between
184          each layer (no dropout is applied for a model with a single layer).
185          When set to 0, dropout is disabled.
186      seed: the op seed used for initializing dropout. See `tf.set_random_seed`
187          for behavior.
188      dtype: tf.float16, tf.float32 or tf.float64
189      kernel_initializer: starting value to initialize the weight.
190      bias_initializer: starting value to initialize the bias
191        (default is all zeros).
192      name: VariableScope for the created subgraph; defaults to class name.
193        This only serves the default scope if later no scope is specified when
194        invoking __call__().
195
196    Raises:
197      ValueError: if direction is invalid. Or dtype is not supported.
198    """
199    super(_CudnnRNN, self).__init__(dtype=dtype, name=name)
200    cudnn_rnn_ops.check_direction(direction)
201    cudnn_rnn_ops.check_input_mode(input_mode)
202
203    if dtype not in [dtypes.float16, dtypes.float32, dtypes.float64]:
204      raise ValueError(
205          "Only support float16, float32, float64, provided %s" % dtype)
206    # Layer self.dtype is type name, the original DType object is kept here.
207    self._plain_dtype = dtype
208    self._num_layers = num_layers
209    self._num_units = num_units
210    self._input_mode = input_mode
211    self._direction = direction
212    self._dropout = dropout
213    self._seed = seed
214    self._kernel_initializer = kernel_initializer
215    self._bias_initializer = bias_initializer
216    # Init input_size to None, which will be set after build().
217    self._input_size = None
218    self._saveable = None
219
220  @property
221  def num_layers(self):
222    return self._num_layers
223
224  @property
225  def num_units(self):
226    return self._num_units
227
228  @property
229  def input_mode(self):
230    """Input mode of first layer.
231
232    Indicates whether there is a linear projection between the input and the
233    actual computation before the first layer. It can be
234    * 'linear_input': (default) always applies a linear projection of input
235      onto RNN hidden state. (standard RNN behavior)
236    * 'skip_input': 'skip_input' is only allowed when input_size == num_units.
237    * 'auto_select'. implies 'skip_input' when input_size == num_units;
238      otherwise, it implies 'linear_input'.
239
240    Returns:
241      'linear_input', 'skip_input' or 'auto_select'.
242    """
243    return self._input_mode
244
245  @property
246  def input_size(self):
247    if not self._input_size:
248      raise ValueError(
249          "\'input_size\' is unknown since layer has not been built.")
250    return self._input_size
251
252  @property
253  def rnn_mode(self):
254    """Type of RNN cell used.
255
256    Returns:
257      `lstm`, `gru`, `rnn_relu` or `rnn_tanh`.
258    """
259    return self._rnn_mode
260
261  @property
262  def direction(self):
263    """Returns `unidirectional` or `bidirectional`."""
264    return self._direction
265
266  @property
267  def num_dirs(self):
268    return 1 if self._direction == CUDNN_RNN_UNIDIRECTION else 2
269
270  @property
271  def saveable(self):
272    return self._saveable
273
274  @property
275  def canonical_weight_shapes(self):
276    """Shapes of Cudnn canonical weight tensors."""
277    if not self._input_size:
278      raise RuntimeError(
279          "%s.canonical_weight_shapes invoked before input shape is known" %
280          type(self).__name__)
281
282    shapes = []
283    for i in range(self._num_layers):
284      shapes.extend(self._canonical_weight_shape(i))
285    return shapes
286
287  @property
288  def canonical_bias_shapes(self):
289    """Shapes of Cudnn canonical bias tensors."""
290    return self._canonical_bias_shape(0) * self._num_layers
291
292  def _update_trainable_weights(self, getter, *args, **kwargs):
293    """Custom getter for layer variables."""
294    # Add variables to layer's `(non_)trainable_weights` list(s).
295    variable = getter(*args, **kwargs)
296    trainable = kwargs.get("trainable", True)
297    if trainable and variable not in self._trainable_weights:
298      self._trainable_weights.append(variable)
299    elif not trainable and variable not in self._non_trainable_weights:
300      self._non_trainable_weights.append(variable)
301    return variable
302
303  def build(self, input_shape):
304    """Create variables of the Cudnn RNN.
305
306    It can be called manually before `__call__()` or automatically through
307    `__call__()`. In the former case, subsequent `__call__()`s will skip
308    creating variables.
309    Args:
310      input_shape: network input tensor shape, a python list or a TensorShape
311        object with 3 dimensions.
312    Raises:
313      ValueError: if input_shape has wrong dimension or unknown 3rd dimension.
314    """
315    if self.built:
316      return
317
318    input_shape = tensor_shape.TensorShape(input_shape)
319    if input_shape.ndims != 3:
320      raise ValueError("Expecting input_shape with 3 dims, got %d" %
321                       input_shape.ndims)
322    if input_shape[-1].value is None:
323      raise ValueError("The last dimension of the inputs to `CudnnRNN` "
324                       "should be defined. Found `None`.")
325    self._input_size = input_shape[-1].value
326    self.input_spec = input_spec.InputSpec(ndim=3, axes={-1: self._input_size})
327
328    self._set_scope(None)
329
330    # Not using base class `add_variable()` since the it calls
331    # `tf.get_variable()` with a callable initializer whereas here with a
332    # tensor. The difference is mandated to support forward-compatibility with
333    # Cudnn.
334    with vs.variable_scope(
335        self._scope,
336        reuse=self.built,
337        custom_getter=self._update_trainable_weights):
338      if self._kernel_initializer is None:
339        self._kernel_initializer = init_ops.glorot_uniform_initializer(
340            seed=self._seed, dtype=self._plain_dtype)
341      if self._bias_initializer is None:
342        self._bias_initializer = init_ops.constant_initializer(
343            0.0, dtype=self._plain_dtype)
344
345      weights = [
346          self._kernel_initializer(sp, dtype=self._plain_dtype)
347          for sp in self.canonical_weight_shapes
348      ]
349      biases = [
350          self._bias_initializer(sp, dtype=self._plain_dtype)
351          for sp in self.canonical_bias_shapes
352      ]
353      opaque_params_t = self._canonical_to_opaque(weights, biases)
354
355      if vs.get_variable_scope().partitioner is not None:
356        logging.warn(
357            "Partitioner is not supported for Cudnn RNN layer variables, using "
358            "it will create forward-compatibility issues with future "
359            "CUDA/CuDNN generations.")
360      # Initialize opaque params with a tensor with unknown shape, thus couldn't
361      # use self.add_variable(name, shape, initializer, ...)
362      self.kernel = vs.get_variable(
363          "opaque_kernel", dtype=self._plain_dtype,
364          initializer=opaque_params_t, validate_shape=False)
365    # Create saveable in the outer scope of the cudnn subgraph, such that
366    # alternative subgraph with platform-independent rnn cells can load the
367    # checkpoints directly.
368    if not (self.built or vs.get_variable_scope().reuse is True):
369      self._create_saveable()
370    self.built = True
371
372  def _gather_saveables_for_checkpoint(self):
373    raise NotImplementedError(
374        "This cell does not yet support object-based saving. File a feature "
375        "request if this limitation bothers you.")
376
377  def call(self,
378           inputs,
379           initial_state=None,
380           sequence_lengths=None,
381           time_major=True,
382           training=True):
383    """Runs the forward step for the RNN model.
384
385    Args:
386      inputs: `3-D` tensor. If `time_major` is True (default), the Tensor shape
387        is [time_len, batch_size, input_size]. If `time_major` is False, the
388        shape is [batch_size, time_len, input_size].
389      initial_state: a tuple of tensor(s) of shape
390        `[num_layers * num_dirs, batch_size, num_units]` if
391        `time_major` is True (default) or `[batch_size, num_layers * num_dirs,
392        num_units]` if `time_major` is False. If not provided, use
393        zero initial states. The tuple size is 2 for LSTM and 1 for other RNNs.
394      sequence_lengths: an int32 array representing the variable sequence
395        lengths in a batch. The size of the array has to equal the
396        batch_size. If not provided, the same sequence length will be assumed.
397      time_major: The shape format of the `inputs` and `outputs` Tensors. If
398        true, these Tensors must be shaped ['max_time', 'batch_size', 'depth'].
399        If false, these Tensors must be shaped ['batch_size', 'max_time',
400        'depth']. By default this function accepts input and emits output in
401        time-major form. This param is only effective when 'sequence_lengths'
402        is used.
403      training: whether this operation will be used in training or inference.
404    Returns:
405      output: a tensor of shape `[time_len, batch_size, num_dirs * num_units]`
406        if `time_major` is True (default) or `[batch_size, time_len,
407        num_dirs * num_units]` if `time_major` is False.
408        It is a `concat([fwd_output, bak_output], axis=2)`.
409      output_states: a tuple of tensor(s) of the same shape and structure as
410        `initial_state`.
411    Raises:
412      TypeError: initial_state is not a tuple.
413    """
414    if initial_state is not None and not isinstance(initial_state, tuple):
415      raise TypeError("Invalid initial_state type: %s, expecting tuple." %
416                      initial_state)
417    dtype = self.dtype
418    inputs = ops.convert_to_tensor(inputs, dtype=dtype)
419
420    batch_size = array_ops.shape(inputs)[1]
421    if initial_state is None:
422      initial_state = self._zero_state(batch_size)
423    if self._rnn_mode == CUDNN_LSTM:
424      h, c = initial_state  # pylint:disable=unbalanced-tuple-unpacking,unpacking-non-sequence
425    else:
426      h, = initial_state  # pylint:disable=unbalanced-tuple-unpacking,unpacking-non-sequence
427    h = ops.convert_to_tensor(h, dtype=dtype)
428    if self._rnn_mode == CUDNN_LSTM:
429      c = ops.convert_to_tensor(c, dtype=dtype)
430    else:
431      # For model that doesn't take input_c, replace with a dummy tensor.
432      c = array_ops.constant([], dtype=dtype)
433    outputs, (output_h, output_c) = self._forward(
434        inputs, h, c, self.kernel, sequence_lengths, time_major, training)
435    if self._rnn_mode == CUDNN_LSTM:
436      return outputs, (output_h, output_c)
437    else:
438      return outputs, (output_h,)
439
440  def state_shape(self, batch_size):
441    raise NotImplementedError
442
443  def _zero_state(self, batch_size):
444    res = []
445    for sp in self.state_shape(batch_size):
446      res.append(array_ops.zeros(sp, dtype=self.dtype))
447    return tuple(res)
448
449  def _canonical_weight_shape(self, layer):
450    """Shapes of Cudnn canonical weight tensors for given layer."""
451    if layer < 0 or layer >= self._num_layers:
452      raise ValueError("\'layer\' is not valid, got %s, expecting [%d, %d]" %
453                       (layer, 0, self._num_layers-1))
454    if not self._input_size:
455      raise RuntimeError(
456          "%s._canonical_weight_shape invoked before input shape is known" %
457          type(self).__name__)
458
459    input_size = self._input_size
460    num_units = self._num_units
461    num_gates = self._num_params_per_layer // 2
462    is_bidi = self._direction == CUDNN_RNN_BIDIRECTION
463
464    if layer == 0:
465      wts_applied_on_inputs = [(num_units, input_size)] * num_gates
466    else:
467      if is_bidi:
468        wts_applied_on_inputs = [(num_units, 2 * num_units)] * num_gates
469      else:
470        wts_applied_on_inputs = [(num_units, num_units)] * num_gates
471    wts_applied_on_hidden_states = [(num_units, num_units)] * num_gates
472    tf_wts = wts_applied_on_inputs + wts_applied_on_hidden_states
473    return tf_wts if not is_bidi else tf_wts * 2
474
475  def _canonical_bias_shape(self, unused_layer):
476    """Shapes of Cudnn canonical bias tensors for given layer."""
477    num_dirs = 1 if self._direction == CUDNN_RNN_UNIDIRECTION else 2
478    return [[self._num_units]] * num_dirs * self._num_params_per_layer
479
480  def _canonical_to_opaque(self, cu_weights, cu_biases):
481    if not self._input_size:
482      raise RuntimeError(
483          "%s._canonical_to_opaque invoked before input shape is known" %
484          type(self).__name__)
485    with ops.device("/gpu:0"):
486      return cudnn_rnn_ops.cudnn_rnn_canonical_to_opaque_params(
487          rnn_mode=self._rnn_mode,
488          num_layers=self._num_layers,
489          num_units=self._num_units,
490          input_size=self._input_size,
491          weights=cu_weights,
492          biases=cu_biases,
493          input_mode=self._input_mode,
494          seed=self._seed,
495          dropout=self._dropout,
496          direction=self._direction)
497
498  def _forward(self, inputs, h, c, opaque_params, sequence_lengths, time_major,
499               training):
500    output, output_h, output_c = cudnn_rnn_ops._cudnn_rnn(  # pylint:disable=protected-access
501        inputs,
502        h,
503        c,
504        opaque_params,
505        training,
506        self._rnn_mode,
507        sequence_lengths=sequence_lengths,
508        time_major=time_major,
509        input_mode=self._input_mode,
510        direction=self._direction,
511        dropout=self._dropout,
512        seed=self._seed)
513    return output, (output_h, output_c)
514
515  def _create_saveable(self):
516    """Create custom saveable for the Cudnn layer.
517
518    Called during layer building process to make sharing checkpoints between
519    Cudnn and Cudnn-compatible RNNs easy.
520    Returns:
521      a `CudnnOpaqueParamsSaveable` object.
522    Raises:
523      RuntimeError: if any custom saveable is already created for this layer.
524    """
525    if self._saveable is not None:
526      raise RuntimeError("Cudnn saveable already created.")
527    self._saveable = self._saveable_cls(  # pylint:disable=not-callable
528        opaque_params=self.trainable_variables[0],
529        num_layers=self.num_layers,
530        num_units=self.num_units,
531        input_size=self.input_size,
532        input_mode=self.input_mode,
533        direction=self.direction,
534        scope=vs.get_variable_scope(),
535        name="%s_saveable" % self.trainable_variables[0].name.split(":")[0])
536    self._saveable._add_trackable_dependencies(  # pylint: disable=protected-access
537        trackable=self, dtype=self._plain_dtype)
538    ops.add_to_collection(ops.GraphKeys.SAVEABLE_OBJECTS, self._saveable)
539
540
541class CudnnLSTM(_CudnnRNN):
542  """Cudnn implementation of LSTM layer."""
543  _rnn_mode = CUDNN_LSTM
544  _num_params_per_layer = CUDNN_LSTM_PARAMS_PER_LAYER
545  _saveable_cls = cudnn_rnn_ops.CudnnLSTMSaveable
546
547  def state_shape(self, batch_size):
548    """Shape of Cudnn LSTM states.
549
550    Shape is a 2-element tuple. Each is
551    [num_layers * num_dirs, batch_size, num_units]
552    Args:
553      batch_size: an int
554    Returns:
555      a tuple of python arrays.
556    """
557    return ([self.num_layers * self.num_dirs, batch_size, self.num_units],
558            [self.num_layers * self.num_dirs, batch_size, self.num_units])
559
560  @property
561  def _gather_saveables_for_checkpoint(self):
562    if self._direction == CUDNN_RNN_UNIDIRECTION:
563      # Skip one inheritance level to avoid NotImplementedError.
564      return super(_CudnnRNN, self)._gather_saveables_for_checkpoint
565    else:
566      raise NotImplementedError(
567          "Object-based saving does not currently support bidirectional LSTM "
568          "cells. File a feature request if this limitation bothers you.")
569
570
571class _CudnnRNNNoInputC(_CudnnRNN):
572  """Abstract simple CudnnRNN layer without input_c."""
573
574  def state_shape(self, batch_size):
575    """Shape of the state of Cudnn RNN cells w/o. input_c.
576
577    Shape is a 1-element tuple,
578    [num_layers * num_dirs, batch_size, num_units]
579    Args:
580      batch_size: an int
581    Returns:
582      a tuple of python arrays.
583    """
584    return [self.num_layers * self.num_dirs, batch_size, self.num_units],
585
586
587class CudnnGRU(_CudnnRNNNoInputC):
588  """Cudnn implementation of the GRU layer."""
589  _rnn_mode = CUDNN_GRU
590  _num_params_per_layer = CUDNN_GRU_PARAMS_PER_LAYER
591  _saveable_cls = cudnn_rnn_ops.CudnnGRUSaveable
592
593
594class CudnnRNNTanh(_CudnnRNNNoInputC):
595  """Cudnn implementation of the RNN-tanh layer."""
596  _rnn_mode = CUDNN_RNN_TANH
597  _num_params_per_layer = CUDNN_RNN_TANH_PARAMS_PER_LAYER
598  _saveable_cls = cudnn_rnn_ops.CudnnRNNTanhSaveable
599
600
601class CudnnRNNRelu(_CudnnRNNNoInputC):
602  """Cudnn implementation of the RNN-relu layer."""
603  _rnn_mode = CUDNN_RNN_RELU
604  _num_params_per_layer = CUDNN_RNN_RELU_PARAMS_PER_LAYER
605  _saveable_cls = cudnn_rnn_ops.CudnnRNNReluSaveable
606