Knonik DataLoader

This guide explains how to use the Knonik multidataloader from the wheel-based Knonik package. The dataloader is primarily a Python API.

1. What The Dataloader Does

The Knonik multidataloader streams training batches from KHLP datasets produced by knonik pack.

It supports:

  • Single sharded datasets with ShardedDataLoader.
  • Multi-dataset training with MultiShardedDataLoader.
  • Local filesystems and S3.
  • NumPy output for JAX / TensorFlow / framework-neutral pipelines.
  • Torch output for PyTorch pipelines.
  • Ordered threaded/process augment execution.
  • Optional ordered batch prefetch.
  • Optional zero-copy batching on the fast paths.
  • Global sampling for better training diversity.

2. Install

Create or activate the training environment:

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip

Install the Knonik wheel:

python -m pip install /path/to/knonik-0.1.0-cp311-cp311-manylinux*.whl

Verify the install:

python -c "import knonik_multidataloader; print('dataloader import OK')"

Install PyTorch (Use the PyTorch build that matches your CUDA/runtime environment.)

python -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128

3. Log In

Log in once with an account entitled for multidataloader:

knonik login --product multidataloader

On headless Linux machines without an OS keyring:

knonik login --product multidataloader --allow-file-key-store

Check status:

knonik status --product multidataloader

Log out:

knonik logout --product multidataloader

4. Single-Dataset Python Example

Use ShardedDataLoader when training from one KHLP manifest.

from knonik_multidataloader.api.sharded_dataloader import ShardedDataLoader

loader = ShardedDataLoader(
    loader_cfg={
        "decode_mode": "random_access",
        "hlp_manifest": "/data/knonik_thread_velcro_hlp/hlp_manifest.json",
        "chunk_len": 10,
        "batch_size": 64,
        "framework": "numpy",
        "fs_type": "local",
        "fs_params": {"root": "/"},
        "shuffle_mode": "global_triplet",
        "num_decoders": 8,
        "augment_execution": "threaded",
        "precollate_batches": 2
    },
    stream_cfg={},          # empty means auto-discover streams from manifest
    dataset_name="thread_velcro",
)

try:
    for batch in loader:
        # train(batch)
        print(batch.keys())
        break
finally:
    loader.shutdown()

5. Multi-Dataset Python Example

Use MultiShardedDataLoader when mixing several KHLP datasets.

from knonik_multidataloader.api.multi_sharded_dataloader import MultiShardedDataLoader

datasets = [
    (
        "thread_velcro",
        {
            "loader": {
                "hlp_manifest": "/data/thread_velcro_hlp/hlp_manifest.json",
                "fs_type": "local",
                "fs_params": {"root": "/"}
            },
            "streams": {}
        },
    ),
    (
        "bimanual_transfer",
        {
            "loader": {
                "hlp_manifest": "/data/bimanual_transfer_hlp/hlp_manifest.json",
                "fs_type": "local",
                "fs_params": {"root": "/"}
            },
            "streams": {}
        },
    ),
]

loader = MultiShardedDataLoader(
    datasets=datasets,
    weights=[0.7, 0.3],
    loader_cfg={
        "decode_mode": "random_access",
        "chunk_len": 10,
        "batch_size": 64,
        "framework": "numpy",
        "shuffle_mode": "global_triplet",
        "num_decoders": 8,
        "augment_execution": "threaded",
        "precollate_batches": 2
    },
)

try:
    batch = next(iter(loader))
    print(batch["_sample_dataset_names"][:8])
finally:
    loader.shutdown()

Multi-Dataset Epoch & Mixing Keys

Alongside weights, MultiShardedDataLoader accepts two loader_cfg keys that control how the mix behaves over an epoch:

KeyMeaning
dataset_cycleWhen enabled, smaller datasets cycle so the configured weights ratio holds across the whole epoch. Disable it for classic exhaust-and-renormalize, where the ratio drifts as datasets run dry.
epoch_chunksEpoch length. "largest" = one pass over the largest dataset's shard pool; an integer = an explicit number of total draws per epoch; None = drain to natural exhaustion (only useful with dataset_cycle disabled).

6. Batch Shape

Each batch is a dictionary.

Typical stream keys:

batch["rgb_cam_high"]    # shape: (B, T, H, W, 3)
batch["state"]           # shape: (B, T, state_dim)
batch["action"]          # shape: (B, T, action_dim)

How a batch is built

B is batch_size. The loader picks B sample anchors (the order/diversity of anchors is what shuffle_mode / the planner control), then gathers the per-stream frames for each anchor and stacks the B samples along axis 0. The time dimension T is set per stream by chunk_len and delta_timestamps:

Plain chunk_len. Every stream gets the same contiguous run of chunk_len frames starting at the anchor, so T = chunk_len for all streams:

# chunk_len=16  -> every stream has T=16
batch["rgb_cam_high"]  # (B, 16, H, W, 3)
batch["state"]         # (B, 16, state_dim)
batch["action"]        # (B, 16, action_dim)

With delta_timestamps. Each stream you list samples frames at the given offsets in seconds around the anchor, so its T is the number of offsets. Unlisted streams fall back to the contiguous chunk_len window — so streams can have different T in the same batch:

# chunk_len=16, fps=50
loader_cfg["delta_timestamps"] = {
    "state":  [-0.10, -0.05, 0.0, 0.05, 0.10],     # 5 offsets  -> T=5
    "action": [i / 50 for i in range(16)],         # 16 offsets -> T=16
}
# rgb_cam_high is not listed -> contiguous chunk_len window -> T=16

batch["state"]         # (B, 5,  state_dim)
batch["action"]        # (B, 16, action_dim)
batch["rgb_cam_high"]  # (B, 16, H, W, 3)

pad_missing=True clamps offsets that fall outside the episode to its boundaries (instead of dropping the sample); when padding happens the batch also carries a <stream>_valid_mask so you can tell real frames from padded ones. Within one anchor every stream is aligned to the same anchor frame, so element t of one stream and element t of another correspond to the offsets you requested.

Typical metadata keys:

batch["_dataset_name"]          # single-dataset name
batch["_dataset_names"]         # dataset names represented in the batch
batch["_sample_dataset_names"]  # one dataset name per sample
batch["_hlp_ids"]
batch["_episode_ids"]
batch["_shard_idxs"]
batch["_chunk_starts"]
batch["_stream_names"]

With framework="numpy", arrays are numpy.ndarray. With framework="torch", arrays are CPU torch tensors.

JAX / TensorFlow / NumPy

Recommended:

{
  "framework": "numpy",
  "augment_execution": "threaded",
  "augment_workers": 4,
  "precollate_batches": 2
}

This path is:

decode numpy -> numpy augment -> numpy batch

This avoids numpy -> torch -> numpy conversion churn.

PyTorch

Recommended:

{
  "framework": "torch",
  "augment_execution": "threaded",
  "augment_workers": 4,
  "precollate_batches": 2
}

This path is:

decode numpy -> torch augment -> torch batch

The dataloader does not move batches to GPU. Move to GPU in your training loop.

8. Augment Functions

augment_fn runs per sample before collation.

NumPy example:

def augment_numpy(sample):
    image = sample["streams"]["rgb_cam_high"]
    sample = dict(sample)
    streams = dict(sample["streams"])
    streams["rgb_cam_high"] = image[..., ::-1, :] 
    sample["streams"] = streams
    return sample

loader_cfg = {
    "framework": "numpy",
    "augment_fn": augment_numpy,
    "augment_execution": "threaded",
    "augment_workers": 4
}

Torch example:

def augment_torch(sample):
    image = sample["streams"]["rgb_cam_high"]
    sample = dict(sample)
    streams = dict(sample["streams"])
    streams["rgb_cam_high"] = image.float()
    sample["streams"] = streams
    return sample

loader_cfg = {
    "framework": "torch",
    "augment_fn": augment_torch,
    "augment_execution": "threaded",
    "augment_workers": 4
}

Augment execution modes:

ModeMeaning
threadedDefault. Ordered thread-pool augment. Good first choice.
inlineRuns augment in the batch assembly path. Useful for debugging.
processProcess-pool augment. Function must be top-level and pickleable.

For JAX/OpenPI-style training, prefer threaded unless the transform is very CPU-heavy and safely pickleable.

9. Ordered Batch Prefetch (precollate_batches)

precollate_batches keeps fully-collated, ready-to-consume batches in a background queue so your training step never waits on batch assembly. It is enabled by default with a depth of 2; raise it when model steps are expensive, or set it to 0 to disable.

{
  "precollate_batches": 2,
  "batch_prefetch_timeout_s": 0.1,
  "batch_prefetch_daemon": true
}

Properties:

  • Preserves exact batch order.
  • Prefetches fully collated batches.
  • Works for single and multi sharded loaders.
  • Does not change planner or sampling behavior.
  • Default is 2; set precollate_batches: 0 to disable.

10. KHLP Storage-Aware Loader Modes

The packer chooses how RGB frames are stored. The loader chooses how to read those bytes. Good throughput comes from matching the two choices.

Packed profileBest loader modeNotes
compact_videoglobal_tripletSmall storage, but sparse random access may decode extra GOP frames.
training_fastglobal_triplet_fastFast random access. Each requested frame has its own image payload.
training_compressedglobal_triplet_fastSmaller storage than training_fast and higher throughput than compact_video.

global_triplet_fast does not change the planner, seed behavior, sample order, or batch metadata relative to global_triplet. The only difference is how the bytes are read, via partial decode.

Training-Fast Loader Recipe

Use this for local or cloud training when loader throughput matters more than minimum storage size. The simple surface is just the shuffle mode:

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/datasets/my_dataset_kimg/hlp_manifest.json",
    "chunk_len": 4,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet_fast",
    "num_decoders": 8,            # raise if the GPU is input-starved
}

Training-Compressed Loader Recipe

Use this when storage size matters but sparse training access is still required. On local files, add the single toggle kdelta_direct_local_decode to read KDLT byte ranges straight from the KHLP file; on S3 leave it off (the default).

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/datasets/my_dataset_kdelta/hlp_manifest.json",
    "chunk_len": 1,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet_fast",
    "delta_timestamps": {
        "rgb_cam_high": [0.0, 0.1, 0.2, 0.3],
        "state": [0.0, 0.1, 0.2, 0.3],
        "action": [0.0, 0.1, 0.2, 0.3],
    },
    "pad_missing": True,
    "kdelta_direct_local_decode": True,   # local files only; omit for S3
}

That is the whole simple surface. Everything else the direct path needs: request bundling, per-worker slot sizing, byte-range merging, file-handle caching, orchestration threads, is auto-defaulted to sane values, so you do not set it. If profiling later shows a specific bottleneck, the individual override knobs live in Fine-grained control.

Delta-Timestamp Sampling With global_triplet_fast

delta_timestamps is the right interface when streams are sampled around an anchor step. For example, at 50 FPS, offsets [0, 5, 10, 15] frames are:

offsets = [0 / 50, 5 / 50, 10 / 50, 15 / 50]
loader_cfg["delta_timestamps"] = {
    "rgb_cam_high": offsets,
    "state": offsets,
    "action": offsets
}

The fast path resolves these offsets to concrete per-stream frame indices before decoding. It does not silently fall back to sequential frames unless your timestamps themselves describe adjacent frames.

11. Zero-Copy Batching

Zero-copy batching (zerocopy) is an opt-in fast-path optimization that can roughly double loader throughput (~2×) in exchange for a larger RAM footprint. It is off by default — every existing run behaves exactly as before unless you turn it on — and it requires shuffle_mode="global_triplet_fast" or shuffle_mode="global_triplet".

When you enable it, declare each RGB stream's frame shape up front with zerocopy_frame_shapes:

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_kimg_hlp/hlp_manifest.json",
    "chunk_len": 4,
    "batch_size": 64,
    "framework": "torch",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet_fast",
    "zerocopy": True,
    "zerocopy_frame_shapes": {"rgb_cam_high": [480, 640, 3]},   # [H, W, C] per RGB stream
}
KeyMeaning
zerocopyEnable zero-copy batching. Requires shuffle_mode "global_triplet_fast" or "global_triplet".
zerocopy_frame_shapesRequired when zerocopy is on. {stream: [H, W, C]} for each RGB stream, e.g. {"rgb_cam_high": [480, 640, 3]}. Must be provided explicitly; it cannot be inferred automatically.
zerocopy_prefetchHow many batches may be prepared ahead at once.
zerocopy_window_prefetchHow many prefetch windows to keep in flight. Raising it increases the loader's memory footprint.

Constraints:

  • augment_fn is not supported with zerocopy=True (it raises). Apply the transform to the batched tensor in your training loop instead.
  • pin_memory is ignored under zero-copy (it warns).

12. Useful Loader Config Keys

These are the keys a normal training job sets. Each loader_cfg key is optional except the four required ones; the rest have sensible defaults, and internal / auto-tuned knobs are kept out of this list (see Fine-grained control). The keys fall into a few groups, described before each table below: required (what to load and the sample shape), throughput (how fast to read and decode), sampling (which frames become samples and in what order), diversity & memory (the RAM-vs-shuffle-quality trade), and a single storage-aware toggle for the training_fast / training_compressed profiles.

Required / common — identify the dataset, the sample shape, the output array type, and where the files live. These four (decode_mode, hlp_manifest, chunk_len, batch_size) are the only mandatory keys.

KeyMeaning
decode_modeUse "random_access".
hlp_manifestPath to hlp_manifest.json (produced by knonik pack). Required for ShardedDataLoader.
chunk_lenFrames per sample.
batch_sizeSamples per batch.
frameworkOutput array type: "numpy", "torch", or "native".
fs_type"local" or "s3".
fs_paramsFilesystem config: {"root": "/"} for local; bucket/prefix/region for S3.
base_seedSeed for deterministic sampling and shuffles.

Throughput — control how far ahead the loader reads and how many shards it decodes in parallel. Raise these when the GPU is waiting on input; lower them under CPU/RAM pressure. For most runs num_decoders is the only one you touch.

KeyDefaultMeaning
num_decoders4The decode-parallelism knob. Read concurrency (num_fetchers), decode worker processes (decode_concurrency), decode slots, orchestration threads, and augment workers all auto-scale from it. Raise it if the GPU is input-starved; that's usually the only concurrency knob you touch. To tune the decode-process count independently of fetch/IO, see decode_concurrency in Fine-grained control.
hlps_in_flight4How many KHLP shard files the loader keeps open and reads ahead at the same time. With several files in flight, fetching the next file's shards overlaps with decoding the current one, which hides read latency and keeps the decoders fed. Raise it (8–16) on S3 / high-latency storage, or when shards are small so each file is consumed quickly; lower it to reduce open connections, file handles, and in-flight memory.
prefetch_shards12Shard fetch/decode lookahead on the global-triplet path (planner-batches' worth of shards). Larger = more throughput and more memory.
precollate_batches2Background queue of fully-collated, ready-to-consume batches. 0 disables.
augment_executionthreadedPer-sample augment mode (threaded / inline / process).

Sampling & shape — decide which frames become samples and in what order.

KeyMeaning
shuffle_mode"global_triplet", or "global_triplet_fast"
sampling_policyepisode_uniform (multi-dataset) / shard_uniform (single, default).
strideFrame step between possible sample starts.
overlapAlternative to stride for window overlap.
allow_cross_shardAllow samples to span shard boundaries.
delta_timestampsLeRobot-style temporal offsets (frames at chosen seconds around an anchor).
pad_missingPad/clamp out-of-range delta_timestamps offsets instead of dropping samples.

Diversity & memory — trade shuffle quality against RAM use.

KeyMeaning
planner_localityActive-frontier width: globalhlp_boundedlow_memultra_low_mem. Tighter = less RAM, slightly less diversity.
low_memoryBound the active decoded working set (auto-tuned). Turn on if RSS grows without bound, batch_size ≫ episode count, or chunk_len ≪ per-shard frames.
ram_cache_bytesDecoded-shard RAM cache budget (speeds up epoch 2+ when shards are revisited).
mmap_cache_root / mmap_cache_bytesOptional disk-backed decoded cache (survives restarts) and its budget.

Run control & output — cap epoch length, pin the output for faster GPU transfer, and preset the loader for training.

KeyMeaning
max_stepsCap the number of batches per epoch (a positive integer). Omit for natural exhaustion. Back-compat alias: epoch_batches.
fpsOverride the dataset FPS used to resolve delta_timestamps offsets into frame indices. Falls back to the FPS recorded in the manifest.
pin_memoryTorch output only. CUDA-pin the collated batch so a downstream .to(device, non_blocking=True) is a true async DMA instead of a staged copy from pageable memory. Ignored under zero-copy.
training_profileOne-flag training preset (also accepted as profile: "training"). Auto-configures throughput-related settings — read/decode concurrency, prefetch depth, RAM cache — plus training-friendly output defaults, in one shot. Any key you set explicitly always wins.

Zero-copy — see Zero-Copy Batching for zerocopy and its keys.

Storage-aware reads — a single toggle for datasets packed with the training_fast or training_compressed storage profiles.

KeyDefaultMeaning
kdelta_direct_local_decodeFalseOn local files only: read byte ranges directly inside the decode workers, which makes sparse random access faster. Leave it off for S3. When on, all bundle sizing is auto-derived from the dataset — see Fine-grained control.

Fine-grained Control (Advanced)

You should not need any of these for a normal run. They are auto-tuned or have sane fixed defaults; setting one overrides the automatic value. Reach for them only when profiling points to a specific bottleneck.

Concurrency & decode internals:

The loader pipeline has two work stages with separate parallelism:

  • Fetch / IO — threads that issue byte-range reads from the filesystem (or S3) to pull shard bytes. Sized by num_fetchers.
  • Decode — worker processes that turn those shard bytes into numpy arrays. Sized by decode_concurrency.

num_decoders is the single high-level knob that sets both at once. decode_concurrency exists to override just the decode-process count, decoupling it from the fetch side, useful when the two stages bottleneck differently:

  • Decode-bound on a CPU-rich box → raise decode_concurrency without inflating fetch threads.
  • S3/latency-bound with cheap decode, or limited CPU/RAM → keep num_decoders (hence num_fetchers) high but cap decode_concurrency lower.

In short: num_decoders = "overall decode parallelism" (drives fetch + decode); decode_concurrency = "decode-process count only" (overrides the decode half). Everything below auto-scales from these; override one only to decouple it.

KeyDefaultMeaning
decode_concurrency= num_decodersDecode-pool worker process count. Also sizes decode_slots and num_orch_threads. Override to decouple decode parallelism from the fetch/IO side (num_fetchers and augment_workers, which follow num_decoders).
num_fetchersauto-set from num_decodersRead concurrency. Raise for S3 if fetch-bound.
decode_slotsauto-set from decode_concurrencyShared-memory decode slot count.
num_orch_threadsauto-set from decode_concurrencyDecode-orchestration threads (global-triplet paths).
augment_workersauto-set from num_decodersAugment worker count.
io_loops1Async I/O event loops; raise to 2–4 for S3.
shard_max_sizeauto (from manifest)Per-decoded-shard byte budget. Set only on a "slot capacity exceeded" error; set slightly above the largest decoded shard.

Global-planner internals (under global_triplet):

KeyDefaultMeaning
planner_recent_batches4Recent batches the planner avoids reusing samples from.
planner_drop_lasttrueDrop the trailing partial batch.
episode_window_sizefrom pack metadataEpisodes per locality window.
runtime_episode_subwindow_sizeautoNarrow the active window further for memory control.

Partial decode (Only with global_triplet_fast):

global_triplet_fast does partial decode: it figures out exactly which frame indices each planned batch needs and decodes only those, skipping the rest of the shard. This is what makes the training_fast and training_compressed profiles fast for sparse random access.

You normally just set shuffle_mode="global_triplet_fast" and leave the rest at their auto defaults. The knobs that tune the partial-decode path — partial_decode_coalesce_batches, partial_decode_window_prefetch — live in Fine-grained control;

in short:

  • partial_decode_coalesce_batches — how many upcoming planned batches are merged into one decode "window", so frames from nearby batches that land in the same shard are decoded together (fewer, larger decode jobs).
  • partial_decode_window_prefetch — how many such windows to work on ahead.
KeyDefaultMeaning
partial_decode_coalesce_batches4Planned batches merged into one partial-decode lookahead window.
partial_decode_window_prefetch1Partial-decode windows submitted ahead.

Precollate queue tuning:

KeyDefaultMeaning
batch_prefetch_timeout_s0.1Queue poll timeout for precollate_batches.
batch_prefetch_daemontrueRun the precollate thread as a daemon.

13. Worked Examples

Each example is a loader_cfg you drop into the standard call from section 4:

loader = ShardedDataLoader(loader_cfg=<one of the dicts below>,
                           stream_cfg={}, dataset_name="my_dataset")

Copy one and adapt the paths. All assume you are logged in (knonik login --product multidataloader).

13.1 Minimal local run

Defaults for everything except the dataset and sample shape.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": 16,
    "batch_size": 8,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
}

13.2 EC2 / cloud training from S3

Reading shards from S3 on an EC2 box. Widen the I/O side to hide network latency — more event loops, more files in flight — and keep a decoded-shard RAM cache so epoch 2+ is fast. Needs pip install aiobotocore and valid AWS credentials (standard credential chain).

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "s3://my-bucket/datasets/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": 16,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "s3",
    "fs_params": {
        "bucket": "my-bucket",
        "prefix": "datasets/my_dataset_hlp",   # "" if manifest URIs already include it
        "region": "us-east-1",
        "max_pool_connections": 64,
    },
    # cloud-friendly I/O
    "io_loops": 4,
    "hlps_in_flight": 16,
    "num_decoders": 16,
    "ram_cache_bytes": 16 << 30,   # 16 GiB
}

13.3 Memory-constrained training

Small RAM footprint: no decoded cache, shallow prefetch, fewer workers, fewer files in flight, bounded working set.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": 16,
    "batch_size": 8,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet",
    "planner_locality": "hlp_bounded",
    "ram_cache_bytes": 0,
    "prefetch_shards": 4,
    "num_decoders": 4,
    "hlps_in_flight": 2,
    "low_memory": True,
}

13.4 High-throughput on a big box

Lots of RAM and cores — widen everything.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": 16,
    "batch_size": 32,
    "framework": "torch",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet",
    "planner_locality": "global",
    "num_decoders": 16,
    "hlps_in_flight": 8,
    "prefetch_shards": 16,
    "ram_cache_bytes": 32 << 30,
    "precollate_batches": 3,
}

13.5 Single-frame samples (chunk_len=1)

One frame per sample with a large batch — bound the active working set.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": 1,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet",
    "planner_locality": "low_mem",
    "low_memory": True,
    "ram_cache_bytes": 16 << 30,
}

13.6 LeRobot-style delta_timestamps

Sample state history and action lookahead at chosen offsets (seconds) around the anchor frame. Streams not listed fall back to a contiguous chunk_len window.

FPS = 50
CHUNK_LEN = 16
loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_hlp/hlp_manifest.json",
    "chunk_len": CHUNK_LEN,
    "batch_size": 8,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet",
    "delta_timestamps": {
        "state":  [-0.10, -0.05, 0.0, 0.05, 0.10],
        "action": [i / FPS for i in range(CHUNK_LEN)],
    },
    "pad_missing": True,
}

13.7 training_fast dataset

For a dataset packed with storage_profile="training_fast". Use the fast partial-decode path; each frame is an independent image so random access is cheap.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_kimg_hlp/hlp_manifest.json",
    "chunk_len": 4,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet_fast",
    "num_decoders": 8,
}

13.8 training_compressed dataset

For a dataset packed with storage_profile="training_compressed" on local disk. Add the single kdelta_direct_local_decode toggle — bundle sizing is auto-derived from the dataset.

loader_cfg = {
    "decode_mode": "random_access",
    "hlp_manifest": "/data/my_dataset_kdelta_hlp/hlp_manifest.json",
    "chunk_len": 1,
    "batch_size": 64,
    "framework": "numpy",
    "fs_type": "local",
    "fs_params": {"root": "/"},
    "shuffle_mode": "global_triplet_fast",
    "kdelta_direct_local_decode": True,    # local only; omit for S3
    "delta_timestamps": {
        "rgb_cam_high": [0.0, 0.1, 0.2, 0.3],
        "state": [0.0, 0.1, 0.2, 0.3],
        "action": [0.0, 0.1, 0.2, 0.3],
    },
    "pad_missing": True,
}

13.9 Multi-dataset mixing

Mix two datasets at fixed weights with MultiShardedDataLoader; the smaller one cycles so the ratio holds for the whole epoch.

from knonik_multidataloader.api.multi_sharded_dataloader import MultiShardedDataLoader

datasets = [
    ("dataset_a", {"loader": {"hlp_manifest": "/data/a_hlp/hlp_manifest.json",
                              "fs_type": "local", "fs_params": {"root": "/"}},
                   "streams": {}}),
    ("dataset_b", {"loader": {"hlp_manifest": "/data/b_hlp/hlp_manifest.json",
                              "fs_type": "local", "fs_params": {"root": "/"}},
                   "streams": {}}),
]

loader = MultiShardedDataLoader(
    datasets=datasets,
    weights=[0.7, 0.3],            # 70% A / 30% B, guaranteed per batch
    loader_cfg={
        "decode_mode": "random_access",
        "chunk_len": 16,
        "batch_size": 8,
        "framework": "numpy",
        "shuffle_mode": "global_triplet",
        "planner_locality": "hlp_bounded",
        "num_decoders": 8,
        "ram_cache_bytes": 16 << 30,
    },
)
try:
    for batch in loader:
        # batch["_sample_dataset_names"] -> which dataset each sample came from
        pass
finally:
    loader.shutdown()

14. Tuning By Symptom

Skip the reference; come here when something is wrong.

GPU is waiting on input (low utilization). In order: raise num_decoders (4 → 8 → 16) — fetchers, slots, and orchestration scale with it; raise prefetch_shards (12 → 16 or 24); for S3 raise io_loops (1 → 2 → 4); add or grow ram_cache_bytes.

Memory usage too high / RSS grows without bound. Lower ram_cache_bytes; lower prefetch_shards (12 → 4–8); set low_memory: true; tighten planner_locality (globalhlp_boundedlow_memultra_low_mem); lower num_decoders if the pressure is from the decode pool.

Batches look temporally correlated. Loosen planner_locality (ultra_low_memlow_memhlp_boundedglobal); raise planner_recent_batches (4 → 16 → 32).

Too many cross-shard samples (still correct, just costlier). Increase --shard-size at pack time; reduce chunk_len; increase stride.

"Slow start" — first batch is slow. That is warmup. On large datasets it can take seconds to tens of seconds, paid once per loader. If it never arrives, lower num_decoders / num_fetchers.

Throughput swings wildly batch-to-batch. Add or grow ram_cache_bytes; increase prefetch_shards; check storage (variable I/O latency → variable batch latency).

slot capacity exceeded. Per-decode budget too small for your data. Set shard_max_size (bytes) slightly above the largest decoded shard (or, on the KDLT direct path, lower kdelta_direct_bundle_target_frames).

15. Shutdown Correctly

Always call shutdown() when the loader is no longer needed:

loader = ShardedDataLoader(...)
try:
    for batch in loader:
        train_step(batch)
finally:
    loader.shutdown()

This closes decode pools, augment executors, prefetch threads, caches, and metrics files.