fix: non-atomic checkpoint save
Author: thundergolfer
Pull request: yes

## What does this PR do?

`transformers.trainer.Trainer` currently does not do atomic checkpointing. On checkpoint save, the code creates a checkpoint directory and then progressively adds more files to it. An exception can occur for various reasons in the middle of the checkpoint save process, leaving the checkpoint directory in an invalid state.

Some examples that can result in partial checkpoint save:

* Out of disk space
* Pre-emption of the host
* Serialization issues in trainer components

On restore, the trainer just looks up the latest available checkpoint by directory name. It does not check for and ignore directories with partial checkpoints.

This change adjusts the `_save_checkpoint` method to do all writing into a 'temporary directory' which is renamed to its final name only after all save operations have succeeded.

I don't put the temporary directory in `/tmp/` because that's sometimes a different filesystem mount to where checkpoints are saved and renaming in that situation produces `OSError: [Errno 18] Invalid cross-device link`. I don't use `shutil.move` because that's not atomic for directories.

**Fixes:** # (didn't bother making one, just submitted PR :))

## Before submitting
- [ ] This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
- [x] Did you read the [contributor guideline](https://github.com/huggingface/transformers/blob/main/CONTRIBUTING.md#start-contributing-pull-requests),
      Pull Request section?
- [ ] Was this discussed/approved via a Github issue or the [forum](https://discuss.huggingface.co/)? Please add a link
      to it if that's the case.
- [ ] Did you make sure to update the documentation with your changes? Here are the
      [documentation guidelines](https://github.com/huggingface/transformers/tree/main/docs), and
      [here are tips on formatting docstrings](https://github.com/huggingface/transformers/tree/main/docs#writing-source-documentation).
- [x] Did you write any new necessary tests?


## Who can review?

@muellerzr, @pacman100


Comments:
Test failures appear unrelated to diff: 

```
    @classmethod
    def setUpClass(cls):
        cls.user = "huggingface-hub-ci"
        cls.token = os.getenv("HUGGINGFACE_PRODUCTION_USER_TOKEN", None)
    
        if cls.token is None:
>           raise ValueError("Cannot run tests as secret isn't setup.")
E           ValueError: Cannot run tests as secret isn't setup.

tests/test_modeling_utils.py:1178: ValueError


ERROR tests/test_modeling_utils.py::ModelOnTheFlyConversionTester::test_safetensors_on_the_fly_conversion - ValueError: Cannot run tests as secret isn't setup.
ERROR tests/test_modeling_utils.py::ModelOnTheFlyConversionTester::test_safetensors_on_the_fly_conversion_gated - ValueError: Cannot run tests as secret isn't setup.
```

Also, the checkpoint loading code could arguably use more sophisticated checkpoint validation and automatic fallback in cases of validation failure, but that's a follow-up issue. 

The docs for this PR live [here](https://moon-ci-docs.huggingface.co/docs/transformers/pr_27820). All of your documentation changes will be reflected on that endpoint.

Thanks @muellerzr. Just rebased. 

**Edit:** Ok that revealed an issue! Are the CI tests fail fast? I didn't see test failures related to my diff before 🤔 

@thundergolfer you should see them now. Looks like you may not be cleaning up the temp dirs right during testing? (I'd also just maybe remove the tempdir if possible after you're done needing it?)

@muellerzr there's actually some test cases that reveal a behavior change in this diff. 

If a trainer run tries to reuse a checkpoint directory, with this diff that will now fail. I'm not sure how common this is in practice, but one of the test cases writes checkpoints `5, 10, 15, 20` and then restarts the training from checkpoint `15`. Thus when the trainer gets again to the checkpoint at step `20` it fails, because my diff does not support overwriting an existing checkpoint.

For now I can adjust the change to make it compatible with non-empty destination checkpoint directories, and log a warning that the checkpoint will be non-atomic. 

The alternative of deleting everything in the destination directory is destructive and could break things for people.

@muellerzr alright tests are passing now. There's still code quality CI steps failing because it doesn't find a `token`. Doesn't seem like something I can fix? Do I need to rebase again?

Don't quite see the token issue, can you do `pip install -e .[quality] -U; make style; make quality`? 

You can also try rebasing again, as I assume this got fixed somewhere else

On the build documentation step, if I click through I see: 

<img width="1120" alt="image" src="https://github.com/huggingface/transformers/assets/12058921/18a21074-0787-4bae-852a-d23941ba327b">


Will rebase

`pip install -e .[quality] -U; make style; make quality` works on my branch (exit `0`, no diff). The CI issues appear to be some step setup problem, not an actual failure.

Hmm I'm getting an error running with deepspeed. Is this what https://github.com/huggingface/transformers/pull/27929 is addressing?

```
Saving model checkpoint to /scratch/sanitycheck-20231211/tmp-checkpoint-10
Trainer.model is not a `PreTrainedModel`, only saving its state dict.
tokenizer config file saved in /scratch/sanitycheck-20231211/tmp-checkpoint-10/tokenizer_config.json
Special tokens file saved in /scratch/sanitycheck-20231211/tmp-checkpoint-10/special_tokens_map.json
[2023-12-12 06:20:12,261] [INFO] [logging.py:96:log_dist] [Rank 0] [Torch] Checkpoint global_step10 is about to be saved!
[2023-12-12 06:20:12,266] [INFO] [logging.py:96:log_dist] [Rank 0] Saving model checkpoint: /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/zero_pp_rank_0_mp_rank_00_model_states.pt
[2023-12-12 06:20:12,266] [INFO] [torch_checkpoint_engine.py:21:save] [Torch] Saving /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/zero_pp_rank_0_mp_rank_00_model_states.pt...
[2023-12-12 06:20:12,268] [INFO] [torch_checkpoint_engine.py:23:save] [Torch] Saved /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/zero_pp_rank_0_mp_rank_00_model_states.pt.
[2023-12-12 06:20:12,269] [INFO] [torch_checkpoint_engine.py:21:save] [Torch] Saving /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt...
[2023-12-12 06:20:12,270] [INFO] [torch_checkpoint_engine.py:23:save] [Torch] Saved /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt.
[2023-12-12 06:20:12,271] [INFO] [engine.py:3421:_save_zero_checkpoint] zero checkpoint saved /scratch/sanitycheck-20231211/tmp-checkpoint-10/global_step10/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt
[2023-12-12 06:20:12,274] [INFO] [torch_checkpoint_engine.py:33:commit] [Torch] Checkpoint global_step10 is ready now!
Traceback (most recent call last):
  File "/opt/venv/lib/python3.11/site-packages/transformers/trainer.py", line 1543, in train
    return inner_training_loop(
           ^^^^^^^^^^^^^^^^^^^^
  File "/opt/venv/lib/python3.11/site-packages/transformers/trainer.py", line 1942, in _inner_training_loop
    self._maybe_log_save_evaluate(tr_loss, model, trial, epoch, ignore_keys_for_eval)
  File "/opt/venv/lib/python3.11/site-packages/transformers/trainer.py", line 2302, in _maybe_log_save_evaluate
    self._save_checkpoint(model, trial, metrics=metrics)
  File "/opt/venv/lib/python3.11/site-packages/transformers/trainer.py", line 2405, in _save_checkpoint
    self.state.save_to_json(os.path.join(staging_output_dir, TRAINER_STATE_NAME))
  File "/opt/venv/lib/python3.11/site-packages/transformers/trainer_callback.py", line 114, in save_to_json
    with open(json_path, "w", encoding="utf-8") as f:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/scratch/sanitycheck-20231211/tmp-checkpoint-10/trainer_state.json'
```