webdataset-streaming

Use when streaming large datasets from tar shards with WebDataset, replacing file-based DataLoaders, or precomputing encoder latents into shards. Triggers: "WebDataset", "tar shards", "wds", "streaming data", "ShardWriter", "latent shards"

10 stars

Best use case

webdataset-streaming is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when streaming large datasets from tar shards with WebDataset, replacing file-based DataLoaders, or precomputing encoder latents into shards. Triggers: "WebDataset", "tar shards", "wds", "streaming data", "ShardWriter", "latent shards"

Teams using webdataset-streaming should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/webdataset-streaming/SKILL.md --create-dirs "https://raw.githubusercontent.com/dongzhuoyao/tao-research-skills/main/skills/experiments/webdataset-streaming/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/webdataset-streaming/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How webdataset-streaming Compares

Feature / Agentwebdataset-streamingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when streaming large datasets from tar shards with WebDataset, replacing file-based DataLoaders, or precomputing encoder latents into shards. Triggers: "WebDataset", "tar shards", "wds", "streaming data", "ShardWriter", "latent shards"

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# WebDataset Streaming

## When to Use

- Training on datasets too large for file-based random access
- Streaming precomputed encoder latents from tar shards
- Replacing HuggingFace or file-based DataLoaders with streaming pipelines
- Precomputing expensive encoder outputs into reusable shard archives

## Workflow

- [ ] **Preprocess**: Convert raw data to tar shards with `ShardWriter`
- [ ] **Sizes**: Generate `sizes.json` for progress bars
- [ ] **Verify**: Spot-check decoded samples from shards
- [ ] **Configure**: Set `num_workers >= 4`, shuffle buffer, glob pattern
- [ ] **Integrate**: Wire into training loop (skip `accelerator.prepare()` for WebDataset)
- [ ] **Monitor**: Check GPU util stays > 90% (not flashing 0-100%)

## Core Concept

WebDataset stores samples as consecutive files in tar archives. Each sample is a group of files sharing the same key (basename):

```
shard_000000.tar
  ├── sample_001.flac    # audio
  ├── sample_001.json    # metadata
  ├── sample_002.flac
  ├── sample_002.json
  └── ...
```

The key advantage: sequential reads from tar files are much faster than random file I/O, especially on network filesystems (NFS, GPFS, Lustre).

## Two-Stage Shard Creation

### Stage 1: Preprocess

Convert raw data into WebDataset-compatible pairs:

```python
import webdataset as wds

with wds.ShardWriter("shards/train_%06d.tar", maxcount=50) as sink:
    for item in dataset:
        key = f"{item['id']:06d}"
        sink.write({
            "__key__": key,
            "flac": encode_flac(item["audio"]),
            "json": json.dumps(item["metadata"]).encode(),
        })
```

**`maxcount=50`**: Keep shards small enough for good shuffle granularity but large enough to amortize tar overhead. Adjust based on sample size.

### Stage 2: Create sizes.json

Track shard metadata for progress bars and epoch estimation:

```python
import json, tarfile, glob

sizes = {}
for tar_path in sorted(glob.glob("shards/train_*.tar")):
    with tarfile.open(tar_path) as tf:
        # Count unique keys (each sample has multiple files)
        keys = set(m.name.rsplit(".", 1)[0] for m in tf.getmembers() if not m.isdir())
        sizes[os.path.basename(tar_path)] = len(keys)

with open("shards/sizes.json", "w") as f:
    json.dump(sizes, f)
```

## DataLoader Construction

### Glob Resolution Before WebDataset

`wds.WebDataset` supports brace expansion (`{000..010}`) but NOT shell globs (`*.tar`). Always resolve globs first:

```python
import glob as globmod
import webdataset as wds

if globmod.has_magic(tar_pattern):
    urls = sorted(globmod.glob(tar_pattern))
    if not urls:
        raise FileNotFoundError(f"No tar files match: {tar_pattern}")
else:
    urls = tar_pattern

dataset = (
    wds.WebDataset(urls, shardshuffle=False)
    .shuffle(100)  # sample-level shuffle buffer
    .to_tuple("flac", "json", "__key__")
    .map(decode_sample)
)
```

### Shard Shuffle vs Sample Shuffle

For small datasets (< 100 shards): use `shardshuffle=False` + sample-level `.shuffle(N)`. Shard shuffling with few shards gives poor randomization.

For large datasets (1000+ shards): use `shardshuffle=True` for shard-level randomization, plus `.shuffle(N)` for within-buffer mixing.

### Conditional DataLoader kwargs

`persistent_workers` and `prefetch_factor` raise errors when `num_workers=0`:

```python
dl_kwargs: dict = {}
if num_workers > 0:
    dl_kwargs["persistent_workers"] = persistent_workers
    if prefetch_factor is not None:
        dl_kwargs["prefetch_factor"] = prefetch_factor

loader = torch.utils.data.DataLoader(
    dataset,
    batch_size=batch_size,
    collate_fn=collate_fn,
    num_workers=num_workers,
    pin_memory=pin_memory,
    **dl_kwargs,
)
```

## Latent Shard Variant

For expensive encoders (e.g., frozen audio encoders), precompute encoder outputs and store them as `.pth` + `.json` pairs in tar shards:

```
latent_shard_000000.tar
  ├── sample_001.pth     # torch.save({"encoder_hidden": tensor, "encoder_length": int})
  ├── sample_001.json    # {"chord_indices": [...], "code": "...", "title": "..."}
  └── ...
```

This eliminates encoder forward passes during training, dramatically reducing GPU memory and compute:

```python
dataset = (
    wds.WebDataset(urls, shardshuffle=False)
    .shuffle(100)
    .to_tuple("pth", "json", "__key__")
    .map(decode_latent_sample)
)
```

### Decoding `.pth` Payloads

```python
def decode_latent_sample(sample: dict) -> dict:
    payload = sample["pth"]
    if isinstance(payload, bytes):
        payload = torch.load(io.BytesIO(payload), map_location="cpu", weights_only=False)

    hidden = payload["encoder_hidden"]  # (T, D) tensor
    # ... validate shape, extract metadata from sample["json"] ...
    return {"encoder_hidden": hidden, "targets": targets, ...}
```

## Placeholder Dataset for Progress Bars

WebDataset's `IterableDataset` has no `__len__`. For tqdm progress bars and epoch tracking, wrap with an estimated size:

```python
class SizedIterableWrapper:
    """Wraps an IterableDataset with an estimated __len__ for progress bars."""

    def __init__(self, dataset, estimated_size: int):
        self.dataset = dataset
        self.estimated_size = estimated_size

    def __iter__(self):
        return iter(self.dataset)

    def __len__(self):
        return self.estimated_size
```

Load `estimated_size` from `sizes.json` or compute as `total_samples // batch_size`.

## Gotchas

### Accelerate + IterableDataset

**Never** pass a WebDataset-backed DataLoader through `accelerator.prepare()`. Accelerate's `concatenate` fails on non-tensor batch values (strings, lists of tuples):

```python
# Bad: crashes with TypeError in concatenate
dataloader = accelerator.prepare(wds_dataloader)

# Good: only prepare model + optimizer
model, optimizer = accelerator.prepare(model, optimizer)
# Use wds_dataloader directly
```

### Separate num_workers Settings

WebDataset DataLoader and file-based DataLoader often need different worker counts. Keep them as independent config keys:

```yaml
# data config
webdataset:
  num_workers: 4      # for tar shard streaming
  shuffle_buffer: 100

# training config
loader:
  num_workers: 2      # for file-based HF DataLoader
```

Confusing these causes silent performance issues — the wrong DataLoader gets too many or too few workers.

### GPU Utilization Flashing 0-100%

`num_workers: 0` (the default) causes data starvation on GPU. The GPU idles while the main process reads and decodes the next batch:

```yaml
# Bad: GPU starved
webdataset:
  num_workers: 0

# Good: 4+ workers keep GPU fed
webdataset:
  num_workers: 4
```

Monitor with `watch nvidia-smi` — stable 90%+ utilization means workers are keeping up.

### __key__ Naming Conventions

WebDataset groups files by key (the part before the last `.`). If your filenames have extra dots, samples get split incorrectly:

```
# Bad: "track.v2" is the key, ".flac" is the extension
track.v2.flac
track.v2.json

# Good: use underscores in keys
track_v2.flac
track_v2.json
```

## Anti-Patterns

- **Passing WebDataset loaders to `accelerator.prepare()`**: Guaranteed crash. Only prepare model + optimizer.
- **`num_workers: 0` in production**: Starves the GPU. Always use 4+ workers for real training.
- **Shell globs in `wds.WebDataset()`**: WebDataset doesn't expand `*.tar`. Resolve with `glob.glob()` first.
- **Shard shuffling with few shards**: With < 10 shards, `shardshuffle=True` gives poor randomization. Use sample-level `.shuffle(N)` instead.
- **Forgetting `weights_only=False` for `.pth` loading**: `torch.load` defaults to `weights_only=True` in newer PyTorch, which rejects dict payloads. Explicitly set `weights_only=False` for latent shards.

## See Also

- `gpu-training-acceleration` — Latent-space training with pre-computed encoder features
- `hf-dataset-management` — Data directory conventions and preflight verification

Related Skills

We are still matching the closest adjacent skills for this page. In the meantime, continue through the full directory.