Fix `resume_from_checkpoint` not recognising `"last-checkpoint"`
Author: IsaacBreen
Pull request: yes

# What does this PR do?

The documentation suggests that users can resume training easily by calling:

```python
trainer.train(resume_from_checkpoint="last-checkpoint")
```

However, the original logic in `Trainer.train()` doesn't look for `last-checkpoint` inside `output_dir` as it should. The above would succeed if the CWD happens to be `output_dir`, but fail otherwise with:

```
ValueError: Can't find a valid checkpoint at last-checkpoint
```

Furthermore, due to a separate issue, when `True` is passed, it'll fails to discover `last-checkpoint`:

```python
trainer.train(resume_from_checkpoint=True)
```

```
ValueError: No valid checkpoint found in output directory
```

## Solution

This PR adds logic to `Trainer.train()` to resolve the `resume_from_checkpoint` argument to an absolute path:

```python
class Trainer:
    def train(...):
        ...
        # Resolve resume_from_checkpoint to an absolute path (assuming self.output_dir is absolute)
        if resume_from_checkpoint is not None and not os.path.isabs(resume_from_checkpoint):
            # Look inside self.output_dir
            candidate = os.path.join(args.output_dir, resume_from_checkpoint)
            if os.path.isdir(candidate):
                print(f"Resolved resume_from_checkpoint {resume_from_checkpoint} to {candidate}. If this was not intended, consider specifying a full path to the checkpoint.")
                resume_from_checkpoint = candidate
        ...
```

It also updates `get_last_checkpoint` to return `last-checkpoint`, if it exists:

```python
def get_last_checkpoint(folder):
    content = os.listdir(folder)
    if "last-checkpoint" in content and os.path.isdir(os.path.join(folder, "last-checkpoint")):
        return os.path.join(folder, "last-checkpoint")
    checkpoints = [
        path
        for path in content
        if _re_checkpoint.search(path) is not None and os.path.isdir(os.path.join(folder, path))
    ]
    if len(checkpoints) == 0:
        return
    return os.path.join(folder, max(checkpoints, key=lambda x: int(_re_checkpoint.search(x).groups()[0])))

```

Both of the following now work as expected:

- `trainer.train(resume_from_checkpoint="last-checkpoint")`
- `trainer.train(resume_from_checkpoint=True)`

## Minimal Working Example

The following MWE demonstrates the issue and the fix. It simulates training, pushing to the Hub, downloading the checkpoint, and then attempting to resume.

<details>
<summary>MWE code</summary>

```python
import os
import shutil
import subprocess
import tempfile
import traceback
import huggingface_hub
from transformers import AutoModelForSequenceClassification, AutoTokenizer  # Corrected model class
from transformers import TrainingArguments, Trainer
from datasets import Dataset, load_dataset
import torch

# --- Setup: Temporary Directory and Constants ---
base_dir = tempfile.mkdtemp()
output_dir = os.path.join(base_dir, "trainer_output")
repo_id = "carbon12/test_repo"  # REPLACE with your HF username and a test repo
branch_name = "main"
model_name = "prajjwal1/bert-tiny"

print(f"Working directory: {base_dir}")

# --- Phase 1: Initial Training and Push to Hub ---
print("\n--- PHASE 1: Initial Training & Push ---")

dataset = load_dataset("rotten_tomatoes", split="validation[:5]")
tokenizer = AutoTokenizer.from_pretrained(model_name)

def tokenize_function(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True, max_length=32)

tokenized_dataset = dataset.map(tokenize_function, batched=True)
tokenized_dataset = tokenized_dataset.remove_columns(["text"])
tokenized_dataset = tokenized_dataset.rename_column("label", "labels")
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

training_args = TrainingArguments(
    output_dir=output_dir,
    save_strategy="steps",
    save_steps=2,
    hub_strategy="checkpoint",
    push_to_hub=True,
    hub_model_id=repo_id,
    hub_private_repo=True,
    no_cuda=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    tokenizer=tokenizer
)
trainer.train()
trainer.push_to_hub()

# --- Phase 2: Simulate New Session / Download from Hub ---
print("\n--- PHASE 2: Simulate New Session ---")

# Delete the local output directory
shutil.rmtree(output_dir)
os.makedirs(output_dir)

huggingface_hub.snapshot_download(
    repo_id=repo_id,
    revision=branch_name,
    local_dir=output_dir
)

print(subprocess.run(['tree', output_dir], capture_output=True, text=True, check=True).stdout)

# --- Phase 3: Demonstrate the Problem and Solution ---
print("\n--- PHASE 3: Demonstrate Issue and Solution ---")

# Create a *new* Trainer instance
new_model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
new_training_args = TrainingArguments(
    output_dir=output_dir,
    no_cuda=True,
)
new_trainer = Trainer(
    model=new_model,
    args=new_training_args,
    train_dataset=tokenized_dataset,
    tokenizer=tokenizer,
)

# Attempt to resume with "last-checkpoint" (EXPECTED TO FAIL)
print("\nAttempting resume_from_checkpoint='last-checkpoint' (EXPECTED TO FAIL)")
try:
    new_trainer.train(resume_from_checkpoint="last-checkpoint")
    print("ERROR: 'last-checkpoint' should have failed, but it didn't!")
except ValueError as e:
    print(traceback.format_exc())
    print(f"FAILED (as expected)")

# 3. Attempt to resume with True (SHOULD WORK WITH PR)
print("\nAttempting resume_from_checkpoint=True (SHOULD WORK WITH PR)")
try:
    new_trainer.train(resume_from_checkpoint=True)
    print("SUCCESS: Resumed training with resume_from_checkpoint=True")
except Exception as e:
    print(f"ERROR: resume_from_checkpoint=True failed: {e}")


# --- Cleanup ---
shutil.rmtree(base_dir)
print(f"\nCleaned up temporary directory: {base_dir}")
```

</details>

<details>
<summary>MWE output (example)</summary>

```
--- PHASE 1: Initial Training & Push ---
  trainer = Trainer(
{'train_runtime': 0.4366, 'train_samples_per_second': 34.359, 'train_steps_per_second': 6.872, 'train_loss': 0.6365988254547119, 'epoch': 3.0}                                
100%

--- PHASE 3: Demonstrate Issue and Solution ---
Attempting resume_from_checkpoint='last-checkpoint' (EXPECTED TO FAIL)
Traceback (most recent call last):
  File "<ipython-input-5-ca1a7acb53b2>", line 86, in run_checkpoint_test
    new_trainer.train(resume_from_checkpoint="last-checkpoint")
  File "...rainer.py", line 2218, in train
    self._load_from_checkpoint(resume_from_checkpoint)
  File ".../python3.12/site-packages/transformers/trainer.py", line 2796, in _load_from_checkpoint
    raise ValueError(f"Can't find a valid checkpoint at {resume_from_checkpoint}")
ValueError: Can't find a valid checkpoint at last-checkpoint

FAILED (as expected)

Attempting resume_from_checkpoint=True (SHOULD WORK WITH PR)
[...]
{'train_runtime': 0.2694, 'train_samples_per_second': 55.684, 'train_steps_per_second': 11.137, 'train_loss': 0.21732711791992188, 'epoch': 3.0}
SUCCESS: Resumed training with resume_from_checkpoint=True
```

</details>

## Before submitting
- [x] 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#create-a-pull-request), 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.
- [x] 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).
- [ ] Did you write any new necessary tests?

Comments:
Hi 👋, thank you for opening this pull request! The pull request is converted to draft by default. When it is ready for review, please click the `Ready for review` button (at the bottom of the PR page).