Equivalent to running `loop_fn` `iters` times and stacking the outputs. `pfor` has functionality similar to `for_loop`, i.e. running `loop_fn` `iters` times, with input from 0 to `iters - 1`, and stacking corresponding output of each iteration. However the implementation does not use a tf.whi
(loop_fn, iters, parallel_iterations=None)
| 134 | |
| 135 | |
| 136 | def pfor(loop_fn, iters, parallel_iterations=None): |
| 137 | """Equivalent to running `loop_fn` `iters` times and stacking the outputs. |
| 138 | |
| 139 | `pfor` has functionality similar to `for_loop`, i.e. running `loop_fn` `iters` |
| 140 | times, with input from 0 to `iters - 1`, and stacking corresponding output of |
| 141 | each iteration. However the implementation does not use a tf.while_loop. |
| 142 | Instead it adds new operations to the graph that collectively compute the same |
| 143 | value as what running `loop_fn` in a loop would compute. |
| 144 | |
| 145 | |
| 146 | This is an experimental feature and currently has a lot of limitations: |
| 147 | - There should be no data dependency between the different iterations. For |
| 148 | example, a future iteration should not depend on a value or side-effect of |
| 149 | a previous iteration. |
| 150 | - Stateful kernels may mostly not be supported since these often imply a |
| 151 | data dependency or ordering of the iterations. We do support a limited set |
| 152 | of such stateful kernels though (like RandomFoo, Variable operations like |
| 153 | reads, etc). |
| 154 | - Conversion works only on a limited set of kernels for which a converter |
| 155 | has been registered. |
| 156 | - loop_fn has limited support for control flow operations. tf.cond in |
| 157 | particular is not supported. |
| 158 | - `loop_fn` should return nested structure of Tensors or Operations. However |
| 159 | if an Operation is returned, it should have zero outputs. |
| 160 | - The shape and dtype of `loop_fn` outputs should not depend on the input |
| 161 | to loop_fn. |
| 162 | |
| 163 | Args: |
| 164 | loop_fn: A function that takes an int32 scalar tf.Tensor object representing |
| 165 | the iteration number, and optionally a keyword argument `pfor_config` set |
| 166 | to a PForConfig object. It returns a possibly nested structure of Tensor |
| 167 | or Operation objects. Note that if setting `parallel_iterations` argument |
| 168 | to something other than None, `loop_fn` may be called more than once |
| 169 | during graph construction. So it may need to avoid mutating global state. |
| 170 | iters: Number of iterations for which to run loop_fn. |
| 171 | parallel_iterations: A knob to control how many iterations are vectorized |
| 172 | and dispatched in parallel. The default value of None corresponds to |
| 173 | vectorizing all the iterations. If `parallel_iterations` is smaller than |
| 174 | `iters`, then chunks of at most that many iterations are dispatched in |
| 175 | sequence. This knob can be used to control the total memory usage. |
| 176 | |
| 177 | Returns: |
| 178 | Returns a nested structure of stacked tensor objects with the same nested |
| 179 | structure as the output of `loop_fn`. |
| 180 | Raises: |
| 181 | ValueError: If parallel_iterations is not None and not an integer > 1. |
| 182 | """ |
| 183 | def f(): |
| 184 | return _pfor_impl(loop_fn, iters, parallel_iterations=parallel_iterations) |
| 185 | # Note that we wrap into a tf.function if in eager execution mode or under |
| 186 | # XLA compilation. The latter is so that we don't compile operations like |
| 187 | # tf.placeholder that are created by the loop body. |
| 188 | if context.executing_eagerly() or _is_under_xla_context(): |
| 189 | f = function.defun(f) |
| 190 | return f() |
| 191 | |
| 192 | |
| 193 | def _loop_fn_has_config(loop_fn): |
no test coverage detected