Skip to content

[feat][SFT] Cloud-path (S3/GCS) dataset ingestion for pretokenized and text datasets - #1933

Draft
avigyabb wants to merge 1 commit into
NovaSky-AI:mainfrom
avigyabb:pretokenized-cloud-paths
Draft

[feat][SFT] Cloud-path (S3/GCS) dataset ingestion for pretokenized and text datasets#1933
avigyabb wants to merge 1 commit into
NovaSky-AI:mainfrom
avigyabb:pretokenized-cloud-paths

Conversation

@avigyabb

@avigyabb avigyabb commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What

Follow-up to #1927 (now merged and rebased onto — this PR is a single commit on top of main), per review: cloud-path support was split out of the pretokenized PR so it can land once for both SFT data paths.

After this PR, s3://, gs://, and gcs:// URIs are accepted by:

  • pretokenized_dataset_paths / eval_pretokenized_dataset_paths (pretokenized stores), and
  • train_datasets / eval_datasets (raw text-format datasets fed to online tokenization).

How

New shared module skyrl/train/dataset/remote_storage.py, built on the existing fsspec io layer (skyrl.backends.skyrl_train.utils.io, with its S3 retry/credential-refresh handling):

  • materialize_local(...) — context-managed download for the pretokenized loader (rows are materialized in memory inside the context, so an uncached temp download is cleaned up at context exit).
  • materialize_remote_dataset(...) — persistent resolution for the text tokenization path: the parallel tokenization workers re-open the dataset by path, so the local copy must outlive the call. The original URI (not the resolved local path) remains the tokenization cache key, keeping caches stable across nodes/runs.
  • Downloads cache under cache_dir keyed by remote path, with atomic tmp-dir-then-rename (NFS-safe for multi-node runs); disable_cache / force_recache are honored. Note the cache is keyed by remote path only — replacing a store's contents in place requires force_recache=true once.

Testing

  • CPU: S3 download/cache/force-redownload tests for the pretokenized path (monkeypatched io) plus unit tests for materialize_remote_dataset (local passthrough, missing-path error, cache reuse). 148 passed across the pretokenized + SFT config/dataloader suites.
  • GPU e2e on 1×L4 (Qwen2.5-0.5B-Instruct, FSDP), covering both routes:

🤖 Generated with Claude Code

SumanthRH pushed a commit that referenced this pull request Jul 22, 2026
## What

Adds a pretokenized data path to the SFT trainer: point
`pretokenized_dataset_paths` at local store(s) of already-tokenized rows
and train directly, skipping online tokenization
(`tokenize_chat_example` / `tokenize_sft_example`) entirely. Useful when
a data pipeline tokenizes offline (e.g. on a Spark/Ray data cluster).

> **Scope note (per review):** this PR supports **local paths only**.
Cloud-path (S3/GCS) ingestion is split into the follow-up draft #1933,
where it lands once for both pretokenized stores and text-format
datasets.

## How it works

**New module `skyrl/train/dataset/pretokenized.py`** —
`load_from_pretokenized(path, max_length)`, the pretokenized counterpart
of `SFTTrainer._load_and_tokenize` (same `list[dict]` return shape, so
everything downstream — collators, sequence packing, samplers,
checkpoint/resume — works unchanged):

- **Formats** (auto-detected, single file or directory of shards):
Parquet, JSON-lines, raw Arrow IPC, or a HF `Dataset.save_to_disk`
directory.
- **Row schema**: unpadded `input_ids` plus a full-sequence 0/1
`loss_mask` (same length). `num_actions` is *inferred* from the first
nonzero mask entry; window-form masks, `num_actions` columns, and
HF-style `labels` are rejected with clear errors. `attention_mask` is
optional and must be all-ones (padding stays collation-internal). The
mask form covers instruction-following (1s on the response) and
multi-turn conversational data (1s on every assistant turn).
- **VLM ingestion**: rows carrying `pixel_values` + `image_grid_thw`
pass through to the collator's `TensorList` path. Over-length VLM rows
are dropped with a warning rather than truncated (consistent with the
online VLM path); mixed text+VLM stores handle the null image columns
parquet materializes on text rows.
- **`max_length`** truncation mirrors the online path (prompt prefix
kept, action window shrinks, empty-loss rows dropped).

**Config (`SFTConfig`)** — integrated with multi-dataset SFT (#1883):

- `pretokenized_dataset_paths: List[str]` — multiple stores are
concatenated and mixed per `train_dataset_weights` via
`DataMixingSampler`, exactly like `train_datasets`. Exclusive with
`train_datasets`/`train_dataset_splits` (explicit error, no silent
precedence): a pretokenized store is the output of a preprocessing job,
so the user is assumed to have pre-split/filtered it — HF split syntax
does not apply.
- `eval_pretokenized_dataset_paths: List[str]` — each store becomes one
eval set logged under `eval/{name}/`, named by `eval_dataset_names`
(default: path basenames; collisions error).
- `eval_interval` / `eval_before_train` accept pretokenized eval stores.

## Testing

- **CPU**: 32 tests in `tests/train/test_sft_pretokenized.py` covering
format detection, schema validation/normalization, truncation, VLM
pass-through + collation, multi-store concatenation + mixing-sampler
wiring, and config validation. Full SFT suite passes alongside.
- **GPU e2e** (Qwen2.5-0.5B-Instruct, FSDP on 1×L4, alpaca tokenized
offline with this repo's own tokenizer helpers into parquet, ingested
from a local directory of shards + a separate local eval store):
**https://wandb.ai/sky-posttraining-uc-berkeley/skyrl_sft/runs/lkpv0ip1**
- Loss parity: pretokenized runs reproduce the online-tokenization runs'
loss curves at identical seeds (e.g. step-5 train loss 1.084 vs 1.081).

## Notes / limitations

- The offline pipeline must apply the same chat template as the trained
model — token ids can't be verified at train time.
- Streaming ingestion is out of scope (non-requirement).
- VLM ingestion is covered at unit/collation level; no GPU VLM e2e in
this PR.
- Cloud paths (S3/GCS): follow-up draft #1933.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Avi Basnet <avigyabb@stanford.edu>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…atasets

Follow-up to NovaSky-AI#1927 (merged): factor cloud materialization into
skyrl/train/dataset/remote_storage.py and use it from both SFT data
paths:

- pretokenized stores: pretokenized_dataset_paths /
  eval_pretokenized_dataset_paths accept s3:// gs:// gcs:// URIs
  (context-managed download; rows are materialized in memory before an
  uncached temp download is cleaned up).
- text-format datasets: train_datasets / eval_datasets entries may be
  cloud URIs; they are resolved to a persistent local copy before
  load_dataset (persistent because parallel tokenization workers re-open
  the dataset by path). The original URI stays the tokenization cache
  key so caches are stable across nodes.

Downloads cache under cache_dir keyed by remote path with atomic
tmp-then-rename (NFS-safe); disable_cache / force_recache honored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Avi Basnet <avigyabb@stanford.edu>
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.

1 participant