Per-run checkpoint directories vs. atomic cloud storage writes for retry-safe tf.train.CheckpointManager
0 reputation · 16 Oct 2024, 12:13 UTC
0 reputation · 16 Oct 2024, 12:13 UTC
I am designing checkpointing for a multi-worker TensorFlow training job that must survive preemption and automatic retries. When a worker crashes and the job restarts, I need to guarantee that retries never leave duplicate or partially written checkpoint files in the target directory.
One documented approach is to give every training run its own unique checkpoint directory (for example, a UUID or timestamp suffix) and let tf.train.CheckpointManager operate only inside that isolated path, so a retried run writes into a fresh location. The other is to keep a single shared directory but write through tf.io.gfile to a storage backend such as GCS or S3 that supports atomic file creation, relying on the backend to make each checkpoint write all-or-nothing.
The per-run directory approach adds cleanup and directory-churn overhead, while the shared-directory approach depends on atomicity guarantees that I cannot confirm hold across all filesystems in a multi-worker setup where several workers may write concurrently. Assume a recent TensorFlow 2.x release; behavior may differ by version.
tf.train.CheckpointManager guarantee atomic checkpoint creation on every supported filesystem, or only on backends with native atomic-write semantics?30775 reputation · 16 Oct 2024, 21:23 UTC
tf.train.CheckpointManager does not guarantee atomic checkpoint creation on any filesystem. It handles retention (max_to_keep) and checkpoint discovery within one directory; the atomicity story comes entirely from the underlying tf.io.gfile backend, and even there it is per-object, not per-checkpoint. For retry-heavy distributed jobs, the safer documented pattern is the per-run unique directory, ideally on object storage, with only the chief worker saving.
A TensorFlow 2.x checkpoint is not one file. A save produces a .index file, one or more sharded .data files, and an update to the checkpoint state proto that records the latest prefix. A crash anywhere in that sequence can leave a state file pointing at a prefix whose data files were never fully written. CheckpointManager has no transactional commit step that would make this multi-file set all-or-nothing, on any backend.
What the storage layer adds is narrower than it looks:
gs://): object writes are effectively all-or-nothing at the object level — an object becomes visible only on successful completion. That eliminates torn individual files, but a crash between objects still leaves an incomplete checkpoint set.s3://): support comes via filesystem plugins whose semantics vary by plugin and version. Multipart upload completion gives per-object atomicity, but there is no multi-object transaction, and rename-based patterns are unreliable.So the answer to your first question: atomicity is a property of the backend, and only at single-file granularity — never a CheckpointManager-level guarantee.
Give each run (or each attempt) its own directory, e.g. gs://bucket/job/<run-id>/ckpt. The decisive property is not atomicity but isolation: a retried run writes into a fresh path, so a partially written checkpoint from a crashed attempt can never be discovered by tf.train.latest_checkpoint in the new run's directory. This holds regardless of backend semantics, which is exactly what you want when you cannot confirm those semantics across filesystems and versions.
Two requirements make this work:
CheckpointManager.save. Concurrent saves from several workers into one directory are not a safe supported pattern on any backend, atomic or not.| Approach | Cost | Risk |
|---|---|---|
| Per-run directories | Orphaned directories accumulate; needs bucket lifecycle rules or a cleanup job; restore-path bookkeeping | Low — partial writes are never visible to retries |
| Shared directory on "atomic" storage | Minimal cleanup, lower churn | State proto can reference an incompletely written checkpoint set; guarantees vary by backend, plugin, and TF version |
Write latency is roughly comparable — both write the same bytes. The real cost difference is cleanup overhead versus correctness risk, and for preemptible jobs correctness should win.
Backend behavior is version- and plugin-dependent, so treat the above as a design default, not a substitute for testing:
tf.train.latest_checkpoint returns a prefix whose data files are missing or truncated.tf.io.gfile backend docs for your installed TensorFlow version's write/rename semantics for your scheme.One caveat worth flagging: exact GCS/S3 filesystem behavior differs across TensorFlow 2.x releases and plugin versions, so the per-object atomicity claims above should be re-verified against your pinned versions before you rely on them.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.