Skip to content

feat: unified resume system with checkpoint_utils - #187

Merged
Hecate0821 merged 28 commits into
mainfrom
chengxili/unified-resume
Mar 10, 2026
Merged

Hecate0821 merged 28 commits into
mainfrom
chengxili/unified-resume

Conversation

@Hecate0821

@Hecate0821 Hecate0821 commented Mar 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the dual-path resume logic (ResumeConfig + setup_resume regex parsing) with a single source of truth: checkpoints.jsonl (tinker_cookbook format) via a rewritten checkpoint_utils module. Also adopts SupervisedDatasetFromHFDataset for SFT and adds RLPromptDataset for batch-indexed RL iteration.

What we import from tinker_cookbook

Import Used for
get_last_checkpoint(log_dir) Read last checkpoint from checkpoints.jsonl at resume time
CHECKPOINTS_BASE_NAME Filename constant "checkpoints.jsonl"
SupervisedDatasetFromHFDataset Batch-indexed SFT dataset with get_batch(i) + set_epoch(seed)
renderers (already used) No change

What we implement ourselves (and why)

Function Why not from tinker_cookbook
save_checkpoint tinker_cookbook's save_checkpoint_async stores logical names as state_path; Fireworks cross-job resume needs cross_job:// refs resolved via resolve_checkpoint_path at save time
resolve_resume tinker_cookbook creates a new TrainingClient from checkpoint; Fireworks RLOR loads into an existing client bound to a trainer job via load_state_with_optimizer
Both above (sync) save_checkpoint_async requires async context; our RL callbacks (finish_step) run sync via asyncio.to_thread; tinker_cookbook's sync wrapper uses asyncio.run() which fails inside an existing event loop
RLPromptDataset tinker_cookbook's RLDataset.get_batch() returns EnvGroupBuilder (RL gymnasium types we don't use); ours returns raw row dicts

Checkpoint format

checkpoints.jsonl — append-only JSONL in log_path. Each line written after every DCP save:

{"name": "step-2", "step": 2, "data_consumed": 16, "source_job_id": "abc123", "state_path": "cross_job://abc123/step-2"}

The state_path is a cross_job:// reference resolved at save time, so any future trainer job can load it directly.

Key changes

  • checkpoint_utils.py (rewritten, 115 lines): ResumeInfo, resolve_resume(), save_checkpoint(). No validation helpers (removed dataset_fingerprint, validate_dataset, validate_training_shape).
  • resume.py (deleted): old setup_resume + ResumeConfig removed
  • sft_loop.py: Uses SupervisedDatasetFromHFDataset for batch-indexed iteration with per-epoch reshuffling
  • rl_loop.py: Uses RLPromptDataset for batch-indexed iteration; passes fw_api_key to ReconnectableClient (fixes shared dev auth)
  • supervised.py: Removed backward-compat try/except ImportError fallbacks for datum_from_tokens_weights
  • log_path: Required field (no default) on all recipe Configs — prevents checkpoint collisions
  • pyproject.toml: tinker-cookbook pinned to d7fb423 (latest)

Files changed (38)

Category Files
Checkpoint checkpoint_utils.py (rewrite), resume.py (delete)
Config/infra config.py, __init__.py, validation.py, client.py, infra.py
Recipes rl_loop.py, sft_loop.py, dpo_loop.py, orpo_loop.py
Dataset data.py (add RLPromptDataset), supervised.py (remove fallbacks)
RL losses grpo.py, dapo.py, gspo.py, cispo.py, losses.py
Examples train_frozen_lake.py, train_deepmath.py, train_sft.py
Tests 15 test files updated

E2E test results

Test Result Duration Date
SFT resume PASSED 22m27s 2026-03-10
GRPO resume PASSED 32m32s 2026-03-10
Unit tests (208) PASSED 5s 2026-03-10

SFT: Phase 1 trained 5 steps (loss 4.11 → 0.00001), saved to checkpoints.jsonl with cross_job:// refs. Phase 2 created a new job, loaded checkpoint, continued from step 5 to step 8. data_consumed increased from 40 to 64 (dataloader continued, not restarted).

GRPO: Phase 1 trained 3 steps with hotloading + deployment on qwen3-30b-a3b (MoE). Phase 2 reused the deployment, loaded cross-job checkpoint, continued training. data_consumed increased correctly.

Test plan

  • 208 unit/smoke tests pass
  • SFT e2e resume: cross-job checkpoint load + dataloader continuation verified
  • GRPO e2e resume: cross-job checkpoint load + deployment reuse + dataloader continuation verified
  • DPO e2e resume (checkpoint-only migration, not yet tested e2e)

Chengxi Li added 19 commits March 9, 2026 18:00
Replace dual-path resume logic (ResumeConfig + setup_resume) with a
single source of truth: local_checkpoint_state.jsonl via checkpoint_utils.

- Add checkpoint_utils.py: ResumeState, resolve_resume(), load_dcp(),
  save_loop_state(), dataset_fingerprint(), validate_dataset()
- Delete resume.py and ResumeConfig dataclass
- Track data_consumed in SFT and RL loops for dataset position persistence
- Add init_from_checkpoint config for loading pretrained DCP weights
  on a fresh dataset (supports cross-job "job_id:checkpoint_name" format)
- Make ref_logprobs optional in RL losses (grpo, dapo, gspo, cispo) so
  kl_beta=0 works without a reference model
- Log perf/dcp_load_time to wandb on resume
- Update all 4 recipe loops (rl, sft, dpo, orpo) and e2e resume tests

Made-with: Cursor
When init_from_checkpoint is explicitly set, it now takes priority over
the existing local_checkpoint_state.jsonl. The old state file is cleared
so the new run starts fresh from step=0 with the specified DCP weights.

Previously, resolve_resume checked the state file first (priority 1),
which silently ignored init_from_checkpoint when a state file existed.
This caused resumed runs to complete immediately with no new training.

Made-with: Cursor
- Add save_weights_for_sampler_ext() to ReconnectableClient wrapper
  so recipes never need to reach into client.inner
- Replace all client.inner.save_state() with client.save_state()
  (SFT loop had 2 occurrences)
- Replace all client.inner.save_weights_for_sampler_ext() with
  client.save_weights_for_sampler_ext() (SFT + ORPO)
- Add missing DCP save to ORPO final checkpoint (was only saving
  sampler/HF format, no optimizer state for resume)
- Add missing DCP save to DPO final checkpoint (was conditional on
  hotload interval, now always saves DCP for resume)

Made-with: Cursor
- Fix test_smoke_imports: replace deleted training.utils.resume with
  training.utils.checkpoint_utils
- Fix test_shape_override_paths: validated path now passes
  max_context_length from profile (not None)
- Add 22 unit tests for checkpoint_utils: resolve_resume (fresh start,
  state file resume, init_from_checkpoint, cross-job, override),
  data_consumed slicing, dataset_fingerprint, validate_dataset,
  validate_training_shape, save_loop_state

Made-with: Cursor
wandb_log({"train/step": 0, "infra/total_boot_time": infra_boot_time}, step=0)

step_offset, _ = setup_resume(policy, ResumeConfig())
from training.utils.checkpoint_utils import resolve_resume, load_dcp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move this to the top


step_offset, _ = setup_resume(policy, ResumeConfig())
from training.utils.checkpoint_utils import resolve_resume, load_dcp
state = resolve_resume("./frozen_lake_logs")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this path defined

Chengxi Li added 8 commits March 10, 2026 00:14
Switch checkpoint persistence from custom local_checkpoint_state.jsonl
to tinker_cookbook's checkpoints.jsonl format. Import get_last_checkpoint
from tinker_cookbook for reading; implement sync save_checkpoint that
stores cross-job refs resolvable by any future trainer job.

- Rewrite checkpoint_utils.py: ResumeState -> ResumeInfo, save_loop_state
  -> save_checkpoint, load_dcp -> resolve_resume (loads directly)
- SFT loop: use SupervisedDatasetFromHFDataset for batch-indexed iteration
  with per-epoch reshuffling via set_epoch()
- RL loop: add RLPromptDataset for batch-indexed iteration, pass
  fw_api_key to ReconnectableClient (fixes shared dev auth)
- Remove backward-compat fallbacks in supervised.py (datum_from_tokens_weights)
- Pin tinker-cookbook to latest commit (d7fb423)
- Update all recipe files, examples, and tests

Made-with: Cursor
Phase 2 was missing deployment_shape, causing creation failure.
Now captures Phase 1's deployment_id and reuses it. Added
try/finally to scale deployment to zero after test completes.

Made-with: Cursor
Remove hardcoded log_path defaults from SFT, RL, DPO, and ORPO Config
dataclasses. Callers must now provide log_path explicitly, preventing
checkpoint collisions between runs and test pollution. Matches
tinker_cookbook's pattern.

Add log_path to FrozenLakeConfig and replace the inline hardcoded string.
Update all __main__ blocks, examples, and test Configs.

Made-with: Cursor
Verify all 4 recipe Configs reject construction without log_path,
accept it when provided, and test the full save -> resume roundtrip
with directory creation.

Made-with: Cursor
@Hecate0821
Hecate0821 force-pushed the chengxili/unified-resume branch from 0e5709c to 134db61 Compare March 10, 2026 22:13
Delete dataset_fingerprint, validate_dataset, validate_training_shape
from checkpoint_utils -- these were Fireworks-specific validation checks
that added complexity without real value.

Remove dataset_fingerprint and training_shape_id from ResumeInfo and
all loop_state dicts in save_checkpoint calls.

Made-with: Cursor
@Hecate0821
Hecate0821 merged commit d6c10c8 into main Mar 10, 2026
4 checks passed
@Hecate0821
Hecate0821 deleted the chengxili/unified-resume branch March 10, 2026 22:54
Hecate0821 pushed a commit that referenced this pull request Mar 11, 2026
ResumeConfig was removed in #187. Update configuration reference
to describe log_path + checkpoints.jsonl resume pattern.

Made-with: Cursor
Hecate0821 added a commit that referenced this pull request Mar 11, 2026
ResumeConfig was removed in #187. Update configuration reference
to describe log_path + checkpoints.jsonl resume pattern.

Made-with: Cursor

Co-authored-by: Chengxi Li <chengxili@Chengxis-MacBook-Pro.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants