| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5164442 commit 3c2e6f8
1 file changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,396 @@ | |||
| 1 | + # Using TPUs | ||
| 2 | + | ||
| 3 | + This document walks through the principal TensorFlow APIs necessary to make | ||
| 4 | + effective use of a [Cloud TPU](https://cloud.google.com/tpu/), and highlights | ||
| 5 | + the differences between regular TensorFlow usage, and usage on a TPU. | ||
| 6 | + | ||
| 7 | + This doc is aimed at users who: | ||
| 8 | + | ||
| 9 | + * Are familiar with TensorFlow's `Estimator` and `Dataset` APIs | ||
| 10 | + * Have maybe [tried out a Cloud TPU](https://cloud.google.com/tpu/docs/quickstart) | ||
| 11 | + using an existing model. | ||
| 12 | + * Have, perhaps, skimmed the code of an example TPU model | ||
| 13 | + [[1]](https://github.com/tensorflow/models/blob/master/official/mnist/mnist_tpu.py) | ||
| 14 | + [[2]](https://github.com/tensorflow/tpu-demos/tree/master/cloud_tpu/models). | ||
| 15 | + * Are interested in porting an existing `Estimator` model to | ||
| 16 | + run on Cloud TPUs | ||
| 17 | + | ||
| 18 | + ## TPUEstimator | ||
| 19 | + | ||
| 20 | + @{tf.estimator.Estimator$Estimators} are TensorFlow's model-level abstraction. | ||
| 21 | + Standard `Estimators` can drive models on CPU and GPUs. You must use | ||
| 22 | + @{tf.contrib.tpu.TPUEstimator} to drive a model on TPUs. | ||
| 23 | + | ||
| 24 | + Refer to TensorFlow's Getting Started section for an introduction to the basics | ||
| 25 | + of using a @{$get_started/premade_estimators$pre-made `Estimator`}, and | ||
| 26 | + @{$get_started/custom_estimators$custom `Estimator`s}. | ||
| 27 | + | ||
| 28 | + The `TPUEstimator` class differs somewhat from the `Estimator` class. | ||
| 29 | + | ||
| 30 | + The simplest way to maintain a model that can be run both on CPU/GPU or on a | ||
| 31 | + Cloud TPU is to define the model's inference phase (from inputs to predictions) | ||
| 32 | + outside of the `model_fn`. Then maintain separate implementations of the | ||
| 33 | + `Estimator` setup and `model_fn`, both wrapping this inference step. For an | ||
| 34 | + example of this pattern compare the `mnist.py` and `mnist_tpu.py` implementation in | ||
| 35 | + [tensorflow/models](https://github.com/tensorflow/models/tree/master/official/mnist). | ||
| 36 | + | ||
| 37 | + ### Running a `TPUEstimator` locally | ||
| 38 | + | ||
| 39 | + To create a standard `Estimator` you call the constructor, and pass it a | ||
| 40 | + `model_fn`, for example: | ||
| 41 | + | ||
| 42 | + ``` | ||
| 43 | + my_estimator = tf.estimator.Estimator( | ||
| 44 | + model_fn=my_model_fn) | ||
| 45 | + ``` | ||
| 46 | + | ||
| 47 | + The changes required to use a @{tf.contrib.tpu.TPUEstimator} on your local | ||
| 48 | + machine are relatively minor. The constructor requires two additional arguments. | ||
| 49 | + You should set the `use_tpu` argument to `False`, and pass a | ||
| 50 | + @{tf.contrib.tpu.RunConfig} as the `config` argument, as shown below: | ||
| 51 | + | ||
| 52 | + ``` python | ||
| 53 | + my_tpu_estimator = tf.contrib.tpu.TPUEstimator( | ||
| 54 | + model_fn=my_model_fn, | ||
| 55 | + config=tf.contrib.tpu.RunConfig() | ||
| 56 | + use_tpu=False) | ||
| 57 | + ``` | ||
| 58 | + | ||
| 59 | + Just this simple change will allow you to run a `TPUEstimator` locally. | ||
| 60 | + The majority of example TPU models can be run in this local mode, | ||
| 61 | + by setting the command line flags as follows: | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + ``` | ||
| 65 | + $> python mnist_tpu.py --use_tpu=false --master='' | ||
| 66 | + ``` | ||
| 67 | + | ||
| 68 | + Note: This `use_tpu=False` argument is useful for trying out the `TPUEstimator` | ||
| 69 | + API. It is not meant to be a complete TPU compatibility test. Successfully | ||
| 70 | + running a model locally in a `TPUEstimator` does not guarantee that it will | ||
| 71 | + work on a TPU. | ||
| 72 | + | ||
| 73 | + | ||
| 74 | + ### Building a `tpu.RunConfig` | ||
| 75 | + | ||
| 76 | + While the default `RunConfig` is sufficient for local training, these settings | ||
| 77 | + cannot be ignored in real usage. | ||
| 78 | + | ||
| 79 | + A more typical setup for a `RunConfig`, that can be switched to use a Cloud | ||
| 80 | + TPU, might be as follows: | ||
| 81 | + | ||
| 82 | + ``` python | ||
| 83 | + import tempfile | ||
| 84 | + import subprocess | ||
| 85 | + | ||
| 86 | + class FLAGS(object): | ||
| 87 | + use_tpu=False | ||
| 88 | + tpu_name=None | ||
| 89 | + # Use a local temporary path for the `model_dir` | ||
| 90 | + model_dir = tempfile.mkdtemp() | ||
| 91 | + # Number of training steps to run on the Cloud TPU before returning control. | ||
| 92 | + iterations = 50 | ||
| 93 | + # A single Cloud TPU has 8 shards. | ||
| 94 | + num_shards = 8 | ||
| 95 | + | ||
| 96 | + if FLAGS.use_tpu: | ||
| 97 | + my_project_name = subprocess.check_output([ | ||
| 98 | + 'gcloud','config','get-value','project']) | ||
| 99 | + my_zone = subprocess.check_output([ | ||
| 100 | + 'gcloud','config','get-value','compute/zone']) | ||
| 101 | + cluster_resolver = tf.contrib.cluster_resolver.TPUClusterResolver( | ||
| 102 | + tpu_names=[FLAGS.tpu_name], | ||
| 103 | + zone=my_zone, | ||
| 104 | + project=my_project) | ||
| 105 | + master = tpu_cluster_resolver.get_master() | ||
| 106 | + else: | ||
| 107 | + master = '' | ||
| 108 | + | ||
| 109 | + my_tpu_run_config = tf.contrib.tpu.RunConfig( | ||
| 110 | + master=master, | ||
| 111 | + evaluation_master=master, | ||
| 112 | + model_dir=FLAGS.model_dir, | ||
| 113 | + session_config=tf.ConfigProto( | ||
| 114 | + allow_soft_placement=True, log_device_placement=True), | ||
| 115 | + tpu_config=tf.contrib.tpu.TPUConfig(FLAGS.iterations, | ||
| 116 | + FLAGS.num_shards), | ||
| 117 | + ) | ||
| 118 | + ``` | ||
| 119 | + | ||
| 120 | + Then you must pass the @{tf.contrib.tpu.RunConfig} to the constructor: | ||
| 121 | + | ||
| 122 | + ``` python | ||
| 123 | + my_tpu_estimator = tf.contrib.tpu.TPUEstimator( | ||
| 124 | + model_fn=my_model_fn, | ||
| 125 | + config = my_tpu_run_config, | ||
| 126 | + use_tpu=FLAGS.use_tpu) | ||
| 127 | + ``` | ||
| 128 | + | ||
| 129 | + Typically the `FLAGS` would be set by command line arguments. To switch from | ||
| 130 | + training locally to training on a cloud TPU you would need to: | ||
| 131 | + | ||
| 132 | + 1) Set `FLAGS.use_tpu` to `True` | ||
| 133 | + 1) Set `FLAGS.tpu_name` so the | ||
| 134 | + `tf.contrib.cluster_resolver.TPUClusterResolver` can find it | ||
| 135 | + 1) Set `FLAGS.model_dir` to a Google Cloud Storage bucket url (`gs://`). | ||
| 136 | + | ||
| 137 | + | ||
| 138 | + ## Optimizer | ||
| 139 | + | ||
| 140 | + When training on a cloud TPU you **must** wrap the optimizer in a | ||
| 141 | + @{tf.contrib.tpu.CrossShardOptimizer}, which uses an `allreduce` to aggregate | ||
| 142 | + gradients and broadcast the result to each shard (each TPU core). | ||
| 143 | + | ||
| 144 | + The `CrossShardOptimizer` is not compatible with local training. So, to have | ||
| 145 | + the same code run both locally and on a Cloud TPU, add lines like the following: | ||
| 146 | + | ||
| 147 | + ``` python | ||
| 148 | + optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate) | ||
| 149 | + if FLAGS.use_tpu: | ||
| 150 | + optimizer = tf.contrib.tpu.CrossShardOptimizer(optimizer) | ||
| 151 | + ``` | ||
| 152 | + | ||
| 153 | + If you prefer to avoid a global `FLAGS` variable in your model code, one | ||
| 154 | + approach is to set the optimizer as one of the `Estimator`'s params, | ||
| 155 | + as follows: | ||
| 156 | + | ||
| 157 | + ``` python | ||
| 158 | + my_tpu_estimator = tf.contrib.tpu.TPUEstimator( | ||
| 159 | + model_fn=my_model_fn, | ||
| 160 | + config = my_tpu_run_config, | ||
| 161 | + use_tpu=FLAGS.use_tpu, | ||
| 162 | + params={'optimizer':optimizer}) | ||
| 163 | + ``` | ||
| 164 | + | ||
| 165 | + ## Model Function | ||
| 166 | + | ||
| 167 | + This section details the changes you must make to the model function | ||
| 168 | + (`model_fn()`) to make it `TPUEstimator` compatible. | ||
| 169 | + | ||
| 170 | + ### Static shapes | ||
| 171 | + | ||
| 172 | + During regular usage TensorFlow attempts to determine the shapes of each | ||
| 173 | + `tf.Tensor` during graph construction. During execution any unknown shape | ||
| 174 | + dimensions are determined dynamically, | ||
| 175 | + see @{$programmers_guide/tensors#shape$Tensor Shapes} for more details. | ||
| 176 | + | ||
| 177 | + To run on Cloud TPUs TensorFlow models are compiled using @{$xla$XLA}. | ||
| 178 | + XLA uses a similar system for determining shapes at compile time. XLA requires | ||
| 179 | + that all tensor dimensions be statically defined at compile time. All shapes | ||
| 180 | + must evaluate to a constant, and not depend on external data, or stateful | ||
| 181 | + operations like variables or a random number generator. | ||
| 182 | + | ||
| 183 | + | ||
| 184 | + ### Summaries | ||
| 185 | + | ||
| 186 | + Remove any use of `tf.summary` from your model. | ||
| 187 | + | ||
| 188 | + @{$summaries_and_tensorboard$TensorBoard summaries} are a great way see inside | ||
| 189 | + your model. A minimal set of basic summaries are automatically recorded by the | ||
| 190 | + `TPUEstimator`, to `event` files in the `model_dir`. Custom summaries, however, | ||
| 191 | + are currently unsupported when training on a Cloud TPU. So while the | ||
| 192 | + `TPUEstimator` will still run locally with summaries, it will fail if used on a | ||
| 193 | + TPU. | ||
| 194 | + | ||
| 195 | + ### Metrics | ||
| 196 | + | ||
| 197 | + Build your evaluation metrics dictionary in a stand-alone `metric_fn`. | ||
| 198 | + | ||
| 199 | + <!-- TODO(markdaoust) link to programmers_guide/metrics when it exists --> | ||
| 200 | + | ||
| 201 | + Evaluation metrics are an essential part of training a model. These are fully | ||
| 202 | + supported on Cloud TPUs, but with a slightly different syntax. | ||
| 203 | + | ||
| 204 | + A standard @{tf.metrics} returns two tensors. The first returns the running | ||
| 205 | + average of the metric value, while the second updates the running average and | ||
| 206 | + returns the value for this batch: | ||
| 207 | + | ||
| 208 | + ``` | ||
| 209 | + running_average, current_batch = tf.metrics.accuracy(labels, predictions) | ||
| 210 | + ``` | ||
| 211 | + | ||
| 212 | + In a standard `Estimator` you create a dictionary of these pairs, and return it | ||
| 213 | + as part of the `EstimatorSpec`. | ||
| 214 | + | ||
| 215 | + ```python | ||
| 216 | + my_metrics = {'accuracy': tf.metrics.accuracy(labels, predictions)} | ||
| 217 | + | ||
| 218 | + return tf.estimator.EstimatorSpec( | ||
| 219 | + ... | ||
| 220 | + eval_metric_ops=my_metrics | ||
| 221 | + ) | ||
| 222 | + ``` | ||
| 223 | + | ||
| 224 | + In a `TPUEstimator` you instead pass a function (which returns a metrics | ||
| 225 | + dictionary) and a list of argument tensors, as shown below: | ||
| 226 | + | ||
| 227 | + ```python | ||
| 228 | + def my_metric_fn(labels, predictions): | ||
| 229 | + return {'accuracy': tf.metrics.accuracy(labels, predictions)} | ||
| 230 | + | ||
| 231 | + return tf.contrib.tpu.TPUEstimatorSpec( | ||
| 232 | + ... | ||
| 233 | + eval_metrics=(my_metric_fn, [labels, predictions]) | ||
| 234 | + ) | ||
| 235 | + ``` | ||
| 236 | + | ||
| 237 | + ### Use `TPUEstimatorSpec` | ||
| 238 | + | ||
| 239 | + `TPUEstimatorSpec` do not support hooks, and require function wrappers for | ||
| 240 | + some fields. | ||
| 241 | + | ||
| 242 | + An `Estimator`'s `model_fn` must return an `EstimatorSpec`. An `EstimatorSpec` | ||
| 243 | + is a simple structure of named fields containing all the `tf.Tensors` of the | ||
| 244 | + model that the `Estimator` may need to interact with. | ||
| 245 | + | ||
| 246 | + `TPUEstimators` use a @{tf.contrib.tpu.TPUEstimatorSpec}. There are a few | ||
| 247 | + differences between it and a standard @{tf.estimator.EstimatorSpec}: | ||
| 248 | + | ||
| 249 | + | ||
| 250 | + * The `eval_metric_ops` must be wrapped into a `metrics_fn`, this field is | ||
| 251 | + renamed `eval_metrics` ([see above](#metrics)). | ||
| 252 | + * The @{tf.train.SessionRunHook$hooks} are unsupported, so these fields are | ||
| 253 | + omitted. | ||
| 254 | + * The @{tf.train.Scaffold$`scaffold`}, if used, must also be wrapped in a | ||
| 255 | + function. This field is renamed to `scaffold_fn`. | ||
| 256 | + | ||
| 257 | + `Scaffold` and `Hooks` are for advanced usage, and can typically be omitted. | ||
| 258 | + | ||
| 259 | + ## Input functions | ||
| 260 | + | ||
| 261 | + Input functions work mainly unchanged as they run on the host computer, not the | ||
| 262 | + Cloud TPU itself. This section explains the two necessary adjustments. | ||
| 263 | + | ||
| 264 | + ### Params argument | ||
| 265 | + | ||
| 266 | + <!-- TODO(markdaoust) link to input_fn doc when it exists --> | ||
| 267 | + | ||
| 268 | + The `input_fn` for a standard `Estimator` _can_ include a | ||
| 269 | + `params` argument; the `input_fn` for a `TPUEstimator` *must* include a | ||
| 270 | + `params` argument. This is necessary to allow the estimator to set the batch | ||
| 271 | + size for each replica of the input stream. So the minimum signature for an | ||
| 272 | + `input_fn` for a `TPUEstimator` is: | ||
| 273 | + | ||
| 274 | + ``` | ||
| 275 | + def my_input_fn(params): | ||
| 276 | + pass | ||
| 277 | + ``` | ||
| 278 | + | ||
| 279 | + Where `params['batch-size']` will contain the batch size. | ||
| 280 | + | ||
| 281 | + ### Static shapes and batch size | ||
| 282 | + | ||
| 283 | + The input pipeline generated by your `input_fn` is run on CPU. So it is mostly | ||
| 284 | + free strict static shape requirements imposed by the XLA/TPU environment. The | ||
| 285 | + one requirement is that the batches of data fed from your input pipeline to | ||
| 286 | + the TPU have a static shape, as determined by the standard TensorFlow shape | ||
| 287 | + inference algorithm. Intermediate tensors are free to have a dynamic shapes. | ||
| 288 | + If shape inference has failed, but the shape is known it is possible to | ||
| 289 | + impose the correct shape using `tf.set_shape()`. | ||
| 290 | + | ||
| 291 | + In the example below the shape | ||
| 292 | + inference algorithm fails, but it is corrected using `set_shape`: | ||
| 293 | + | ||
| 294 | + ``` | ||
| 295 | + >>> x = tf.zeros(tf.constant([1,2,3])+1) | ||
| 296 | + >>> x.shape | ||
| 297 | + | ||
| 298 | + TensorShape([Dimension(None), Dimension(None), Dimension(None)]) | ||
| 299 | + | ||
| 300 | + >>> x.set_shape([2,3,4]) | ||
| 301 | + ``` | ||
| 302 | + | ||
| 303 | + In many cases the batch size is the only unknown dimension. | ||
| 304 | + | ||
| 305 | + A typical input pipeline, using `tf.data`, will usually produce batches of a | ||
| 306 | + fixed size. The last batch of a finite `Dataset`, however, is typically smaller, | ||
| 307 | + containing just the remaining elements. Since a `Dataset` does not know its own | ||
| 308 | + length or finiteness, the standard @{tf.data.Dataset.batch$`batch`} method | ||
| 309 | + cannot determine if all batches will have a fixed size batch on its own: | ||
| 310 | + | ||
| 311 | + ``` | ||
| 312 | + >>> params = {'batch_size':32} | ||
| 313 | + >>> ds = tf.data.Dataset.from_tensors([0, 1, 2]) | ||
| 314 | + >>> ds = ds.repeat().batch(params['batch-size']) | ||
| 315 | + >>> ds | ||
| 316 | + | ||
| 317 | + <BatchDataset shapes: (?, 3), types: tf.int32> | ||
| 318 | + ``` | ||
| 319 | + | ||
| 320 | + The most straightforward fix is to | ||
| 321 | + @{tf.data.Dataset.apply$apply} @{tf.contrib.data.batch_and_drop_remainder} | ||
| 322 | + as follows: | ||
| 323 | + | ||
| 324 | + ``` | ||
| 325 | + >>> params = {'batch_size':32} | ||
| 326 | + >>> ds = tf.data.Dataset.from_tensors([0, 1, 2]) | ||
| 327 | + >>> ds = ds.repeat().apply( | ||
| 328 | + ... tf.contrib.data.batch_and_drop_remainder(params['batch-size'])) | ||
| 329 | + >>> ds | ||
| 330 | + | ||
| 331 | + <_RestructuredDataset shapes: (32, 3), types: tf.int32> | ||
| 332 | + ``` | ||
| 333 | + | ||
| 334 | + The one downside to this approach is that, as the name implies, this batching | ||
| 335 | + method throws out any fractional batch at the end of the dataset. This is fine | ||
| 336 | + for an infinitely repeating dataset being used for training, but could be a | ||
| 337 | + problem if you want to train for an exact number of epochs. | ||
| 338 | + | ||
| 339 | + To do an exact 1-epoch of _evaluation_ you can work around this by manually | ||
| 340 | + padding the length of the batches, and setting the padding entries to have zero | ||
| 341 | + weight when creating your `tf.metrics`. | ||
| 342 | + | ||
| 343 | + ## Datasets | ||
| 344 | + | ||
| 345 | + Efficient use of the `tf.data.Dataset` API is critical when using a Cloud | ||
| 346 | + TPU, as it is impossible to use the Cloud TPU's unless you can feed it data | ||
| 347 | + quickly enough. See @{$datasets_performance} for details on dataset performance. | ||
| 348 | + | ||
| 349 | + For all but the simplest experimentation (using | ||
| 350 | + @{tf.data.Dataset.from_tensor_slices} or other in-graph data) you will need to | ||
| 351 | + store all data files read by the `TPUEstimator`'s `Dataset` in Google Cloud | ||
| 352 | + Storage Buckets. | ||
| 353 | + | ||
| 354 | + <!--TODO(markdaoust): link to the `TFRecord` doc when it exists.--> | ||
| 355 | + | ||
| 356 | + For most use-cases, we recommend converting your data into `TFRecord` | ||
| 357 | + format and using a @{tf.data.TFRecordDataset} to read it. This, however, is not | ||
| 358 | + a hard requirement and you can use other dataset readers | ||
| 359 | + (`FixedLengthRecordDataset` or `TextLineDataset`) if you prefer. | ||
| 360 | + | ||
| 361 | + Small datasets can be loaded entirely into memory using | ||
| 362 | + @{tf.data.Dataset.cache}. | ||
| 363 | + | ||
| 364 | + Regardless of the data format used, it is strongly recommended that you | ||
| 365 | + @{$performance_guide#use_large_files$use large files}, on the order of | ||
| 366 | + 100MB. This is especially important in this networked setting as the overhead | ||
| 367 | + of opening a file is significantly higher. | ||
| 368 | + | ||
| 369 | + It is also important, regardless of the type of reader used, to enable buffering | ||
| 370 | + using the `buffer_size` argument to the constructor. This argument is specified | ||
| 371 | + in bytes. A minimum of a few MB (`buffer_size=8*1024*1024`) is recommended so | ||
| 372 | + that data is available when needed. | ||
| 373 | + | ||
| 374 | + The TPU-demos repo includes | ||
| 375 | + [a script](https://github.com/tensorflow/tpu-demos/blob/master/cloud_tpu/datasets/imagenet_to_gcs.py) | ||
| 376 | + for downloading the imagenet dataset and converting it to an appropriate format. | ||
| 377 | + This together with the imagenet | ||
| 378 | + [models](https://github.com/tensorflow/tpu-demos/tree/master/cloud_tpu/models) | ||
| 379 | + included in the repo demonstrate all of these best-practices. | ||
| 380 | + | ||
| 381 | + | ||
| 382 | + ## What Next | ||
| 383 | + | ||
| 384 | + For details on how to actually set up and run a Cloud TPU see: | ||
| 385 | + | ||
| 386 | + * [Google Cloud TPU Documentation](https://cloud.google.com/tpu/docs/) | ||
| 387 | + | ||
| 388 | + This document is by no means exhaustive. The best source of more detail on how | ||
| 389 | + to make a Cloud TPU compatible model are the example models published in: | ||
| 390 | + | ||
| 391 | + * The [TPU Demos Repository.](https://github.com/tensorflow/tpu-demos/) | ||
| 392 | + | ||
| 393 | + For more information about tuning TensorFlow code for performance see: | ||
| 394 | + | ||
| 395 | + * The @{$performance$Performance Section.} | ||
| 396 | + | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments