loaderx 2.3.3__tar.gz → 2.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loaderx-2.3.3/loaderx.egg-info → loaderx-2.3.5}/PKG-INFO +84 -66
- {loaderx-2.3.3 → loaderx-2.3.5}/README.md +81 -65
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/__init__.py +4 -2
- loaderx-2.3.5/loaderx/converter.py +204 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/zrecord.py +2 -2
- {loaderx-2.3.3 → loaderx-2.3.5/loaderx.egg-info}/PKG-INFO +84 -66
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/SOURCES.txt +1 -0
- loaderx-2.3.5/loaderx.egg-info/requires.txt +6 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/pyproject.toml +3 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/test_loaderx.py +195 -16
- loaderx-2.3.3/loaderx.egg-info/requires.txt +0 -3
- {loaderx-2.3.3 → loaderx-2.3.5}/LICENSE +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/MANIFEST.in +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/build.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/build.zig.zon +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/_lib.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/_store.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/dataloader.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/utils.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/zsampler.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/dependency_links.txt +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/top_level.txt +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/_bench_common.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench_dense.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench_ragged.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/build_wheels.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/prepare_tokens.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/scripts/requirements-bench.txt +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/setup.cfg +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/setup.py +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/record/engine.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/record/executor.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/record/storage.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/store.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/zsampler.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/src/zstd/c.zig +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/COPYING +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/LICENSE +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/allocations.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/bits.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/bitstream.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/compiler.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/cpu.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/debug.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/debug.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/entropy_common.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/error_private.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/error_private.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/fse.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/fse_decompress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/huf.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/mem.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/pool.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/pool.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/portability_macros.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/threading.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/threading.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/xxhash.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/xxhash.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_common.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_deps.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_internal.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_trace.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/clevels.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/fse_compress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/hist.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/hist.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/huf_compress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_internal.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_literals.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_literals.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_sequences.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_sequences.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_superblock.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_superblock.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_cwksp.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_double_fast.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_double_fast.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_fast.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_fast.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_lazy.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_lazy.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm_geartab.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_opt.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_opt.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_preSplit.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_preSplit.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstdmt_compress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstdmt_compress.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/huf_decompress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/huf_decompress_amd64.S +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_ddict.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_ddict.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_block.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_block.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_internal.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/cover.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/cover.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/divsufsort.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/divsufsort.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/fastcover.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/zdict.c +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/zdict.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/zstd.h +0 -0
- {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/zstd_errors.h +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loaderx
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.5
|
|
4
4
|
Summary: Rebuildable high-performance ordered record containers
|
|
5
5
|
Author-email: Ben0i0d <ben0i0d@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,6 +21,8 @@ License-File: LICENSE
|
|
|
21
21
|
Requires-Dist: numpy
|
|
22
22
|
Requires-Dist: cffi
|
|
23
23
|
Requires-Dist: msgpack
|
|
24
|
+
Provides-Extra: converter
|
|
25
|
+
Requires-Dist: datasets[vision]>=2.19; extra == "converter"
|
|
24
26
|
Dynamic: license-file
|
|
25
27
|
|
|
26
28
|
# Loaderx
|
|
@@ -73,9 +75,9 @@ from loaderx.dataloader import DataLoader
|
|
|
73
75
|
|
|
74
76
|
data = np.load('data.npy', mmap_mode='r')
|
|
75
77
|
label = np.load('label.npy', mmap_mode='r')
|
|
76
|
-
with Dense.create('train_data', data.dtype, data.shape[1:]) as ds:
|
|
78
|
+
with Dense.create('train_data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
77
79
|
ds.append(data)
|
|
78
|
-
with Dense.create('train_label', label.dtype, label.shape[1:]) as ds:
|
|
80
|
+
with Dense.create('train_label', label.dtype, label.shape[1:], codec='zstd') as ds:
|
|
79
81
|
ds.append(label)
|
|
80
82
|
|
|
81
83
|
data_store = Dense.open('train_data')
|
|
@@ -100,13 +102,70 @@ at the same indices, so record ``i`` lines up across them. The ``transform``
|
|
|
100
102
|
callback is the collate step — reshape, cast, stack — where ``values`` is the
|
|
101
103
|
plain dense batch ready for the model.
|
|
102
104
|
|
|
105
|
+
### Offline Hugging Face conversion
|
|
106
|
+
|
|
107
|
+
The optional converter uses Hugging Face Datasets for remote discovery,
|
|
108
|
+
download, caching, revision handling and source-format decoding. It then writes
|
|
109
|
+
explicit typed Zrecord streams, so training needs neither `datasets` nor Arrow:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install 'loaderx[converter]'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from loaderx.converter import convert, huggingface
|
|
117
|
+
|
|
118
|
+
dataset = huggingface("ylecun/mnist", revision="main", token=None)
|
|
119
|
+
convert(
|
|
120
|
+
dataset,
|
|
121
|
+
"mnist",
|
|
122
|
+
codec="zstd",
|
|
123
|
+
batch_size=256,
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`huggingface` resolves the requested branch, tag or commit to an immutable
|
|
128
|
+
snapshot, then downloads and returns the repository's DatasetDict. Repositories
|
|
129
|
+
without one default config require `config=...`. `convert` is the independent
|
|
130
|
+
persistence stage: it accepts that DatasetDict or one already normalized
|
|
131
|
+
Dataset. `batch_size` bounds records per Python batch; actual bytes also depend
|
|
132
|
+
on record sizes and Ragged packing.
|
|
133
|
+
For private or gated repositories, pass `token="hf_..."`; `None` uses the
|
|
134
|
+
standard HF environment, local login, or anonymous access.
|
|
135
|
+
Static or regularly batched numeric arrays become Dense stores, while dynamic
|
|
136
|
+
arrays become Ragged stores; source dtype and record shape are preserved.
|
|
137
|
+
|
|
138
|
+
Zrecord persists numeric tensors rather than Python objects. A standard HF
|
|
139
|
+
feature that cannot be represented losslessly fails with its column name;
|
|
140
|
+
conversion does not filter, pad, tokenize, reshape, or run a transform stage.
|
|
141
|
+
Arrow dtypes, including float64 and uint64, are preserved. Only the current
|
|
142
|
+
batch is formatted; `with_format` never materializes the complete dataset.
|
|
143
|
+
|
|
144
|
+
The result groups aligned streams under one published root:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
mnist/
|
|
148
|
+
train/
|
|
149
|
+
image/
|
|
150
|
+
label/
|
|
151
|
+
test/
|
|
152
|
+
image/
|
|
153
|
+
label/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Conversion happens under a temporary sibling directory. The root is renamed
|
|
157
|
+
into place only after all stores close and their record counts agree; a failed
|
|
158
|
+
build that raises a Python exception is discarded. A process kill may leave the
|
|
159
|
+
hidden temporary sibling for manual cleanup. No collection manifest is added;
|
|
160
|
+
the output is only split directories containing ordinary column Zrecord stores.
|
|
161
|
+
|
|
103
162
|
### Creating a dense store
|
|
104
163
|
```python
|
|
105
164
|
import numpy as np
|
|
106
165
|
from loaderx.zrecord import Dense
|
|
107
166
|
|
|
108
167
|
data = np.load('data.npy', mmap_mode='r')
|
|
109
|
-
with Dense.create('train_data', data.dtype, data.shape[1:]) as ds:
|
|
168
|
+
with Dense.create('train_data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
110
169
|
ds.append(data)
|
|
111
170
|
```
|
|
112
171
|
One record per slice along axis 0; a 1-D array (the usual shape of a label set)
|
|
@@ -132,7 +191,7 @@ engine consumes the trusted Dense stride or Ragged offsets. Append inputs are
|
|
|
132
191
|
strictly NumPy arrays: Dense takes one batched ndarray and Ragged takes an
|
|
133
192
|
iterable of ndarrays. Raw bytes and pre-encoded images are made explicit with
|
|
134
193
|
``np.frombuffer(raw, dtype=np.uint8)`` and stored in a
|
|
135
|
-
``Ragged.create(path, dtype=np.uint8, ndim=1)`` rather than creating a second
|
|
194
|
+
``Ragged.create(path, dtype=np.uint8, ndim=1, codec="zstd")`` rather than creating a second
|
|
136
195
|
public storage API.
|
|
137
196
|
|
|
138
197
|
### Records
|
|
@@ -147,7 +206,7 @@ import numpy as np
|
|
|
147
206
|
from loaderx.zrecord import Dense
|
|
148
207
|
|
|
149
208
|
data = np.arange(64, dtype=np.float32).reshape(8, 2, 4)
|
|
150
|
-
with Dense.create('data', data.dtype, data.shape[1:]) as ds:
|
|
209
|
+
with Dense.create('data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
151
210
|
ds.append(data)
|
|
152
211
|
ds = Dense.open('data')
|
|
153
212
|
ds[0, 5, 2] # (3, 2, 4) — shape from the persisted schema
|
|
@@ -173,7 +232,7 @@ batch is the model's call — a plain numpy loop, wherever you need it:
|
|
|
173
232
|
from loaderx.zrecord import Ragged
|
|
174
233
|
|
|
175
234
|
seqs = [np.arange(L, dtype=np.int32) for L in (3, 1, 4, 1, 5)]
|
|
176
|
-
with Ragged.create('tokens', np.int32, ndim=1) as rs:
|
|
235
|
+
with Ragged.create('tokens', np.int32, ndim=1, codec='zstd') as rs:
|
|
177
236
|
rs.append(seqs) # dtype/rank fixed; lengths remain per-record
|
|
178
237
|
rs = Ragged.open('tokens')
|
|
179
238
|
|
|
@@ -260,8 +319,10 @@ warmup belongs outside build or loader benchmark timing.
|
|
|
260
319
|
### Creating containers
|
|
261
320
|
|
|
262
321
|
``Dense.create`` and ``Ragged.create`` return append-only ordered-sequence
|
|
263
|
-
builders. ``
|
|
264
|
-
|
|
322
|
+
builders. The ``codec`` keyword is required: callers must explicitly select
|
|
323
|
+
``"raw"``, ``"zstd"``, or ``"zstd_dict"``. ``append`` is explicit — one input
|
|
324
|
+
batch extends the logical sequence without exposing native physical completion
|
|
325
|
+
order.
|
|
265
326
|
Dense append is synchronous and borrows an already-contiguous ndarray without a
|
|
266
327
|
snapshot copy. Ragged append consumes its iterable once into one owned packed
|
|
267
328
|
buffer, then completes the native append before returning. Nothing is inferred.
|
|
@@ -269,12 +330,13 @@ buffer, then completes the native append before returning. Nothing is inferred.
|
|
|
269
330
|
```python
|
|
270
331
|
from loaderx.zrecord import Dense, Ragged
|
|
271
332
|
|
|
272
|
-
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28),
|
|
333
|
+
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28),
|
|
334
|
+
codec='zstd', data_shards=4)
|
|
273
335
|
ds.append(images[i:i + 1024]) # synchronous native batch; returns None
|
|
274
336
|
ds.append(single_image[None]) # one sample is batch_size 1 — add the axis yourself
|
|
275
337
|
ds.close() # publish before opening
|
|
276
338
|
|
|
277
|
-
tok = Ragged.create('tokens', dtype=np.int32, ndim=1)
|
|
339
|
+
tok = Ragged.create('tokens', dtype=np.int32, ndim=1, codec='zstd')
|
|
278
340
|
tok.append([seq_a, seq_b, seq_c])
|
|
279
341
|
tok.close()
|
|
280
342
|
|
|
@@ -315,7 +377,7 @@ published without one.
|
|
|
315
377
|
### Codec notes
|
|
316
378
|
|
|
317
379
|
`"zstd"` compresses each record independently with plain zstd (level 3). Use it
|
|
318
|
-
for general-purpose compression
|
|
380
|
+
for general-purpose compression; it is fast and must be selected explicitly.
|
|
319
381
|
|
|
320
382
|
`"zstd_dict"` trains a shared dictionary on a sample of the data before writing
|
|
321
383
|
any record, then compresses every record against it at level 15. The dictionary
|
|
@@ -409,11 +471,11 @@ from loaderx.zrecord import Dense, Ragged
|
|
|
409
471
|
from loaderx.dataloader import DataLoader
|
|
410
472
|
|
|
411
473
|
root = "xsub/train"
|
|
412
|
-
with Dense.create(root + "/joint", joint.dtype, joint.shape[1:]) as s:
|
|
474
|
+
with Dense.create(root + "/joint", joint.dtype, joint.shape[1:], codec="zstd") as s:
|
|
413
475
|
s.append(joint)
|
|
414
|
-
with Dense.create(root + "/label", label.dtype, label.shape[1:]) as s:
|
|
476
|
+
with Dense.create(root + "/label", label.dtype, label.shape[1:], codec="zstd") as s:
|
|
415
477
|
s.append(label)
|
|
416
|
-
with Ragged.create(root + "/token", np.int32, ndim=1) as s:
|
|
478
|
+
with Ragged.create(root + "/token", np.int32, ndim=1, codec="zstd") as s:
|
|
417
479
|
s.append(seqs)
|
|
418
480
|
|
|
419
481
|
streams = {
|
|
@@ -895,57 +957,13 @@ The loader rows used Numba 0.67.0, Torch 2.13.0 and Grain 0.2.18; all benchmark
|
|
|
895
957
|
are pinned in `scripts/requirements-bench.txt`. Temporary stores used the ordinary
|
|
896
958
|
disk-backed `/tmp` filesystem, not `/dev/shm`.
|
|
897
959
|
|
|
898
|
-
## Real-data verification
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
`.npy` outputs of the standard preprocessing pipeline were treated as ground
|
|
906
|
-
truth. This verification was not rerun with the synthetic benchmarks above;
|
|
907
|
-
its throughput is retained as a separate historical 12-core result.
|
|
908
|
-
|
|
909
|
-
**Correctness — the read path is bit-exact against the ground truth:**
|
|
910
|
-
|
|
911
|
-
* Full scan of all **228,356 records** (joint float32 + label int64, all four
|
|
912
|
-
splits) through `Dense`: byte-for-byte identical to the reference npy.
|
|
913
|
-
* A `DataLoader` over joint + label + an index stream, run under all three
|
|
914
|
-
sampler modes (`sequential`, `iid`, `cyclic`): every received batch is
|
|
915
|
-
bit-exact to the ground truth at its own declared indices, and the streams
|
|
916
|
-
stay index-aligned.
|
|
917
|
-
* Sampler semantics hold on real index spaces: `sequential` walks in order,
|
|
918
|
-
`cyclic` draws a full cycle without replacement, `iid` is deterministic per
|
|
919
|
-
seed.
|
|
920
|
-
|
|
921
|
-
**Storage — zstd on this data:**
|
|
922
|
-
|
|
923
|
-
| store | on disk | ratio |
|
|
924
|
-
|-----------------------|---------|-------|
|
|
925
|
-
| npy (raw float32) | 6.4 GB | 1.00x |
|
|
926
|
-
| zrecord raw | 6.86 GB | 1.00x |
|
|
927
|
-
| zrecord zstd | 0.79 GB | 8.66x |
|
|
928
|
-
| zrecord zstd_dict | 0.75 GB | 9.13x |
|
|
929
|
-
|
|
930
|
-
Sizes above are for one split (`xview/val`, 38,132 records); across all four
|
|
931
|
-
splits the zstd `joint` stores total 4.97 GB against 41 GB of raw npy (~8x).
|
|
932
|
-
|
|
933
|
-
**Throughput** (180 KB per record, warm page cache, 12 physical cores):
|
|
934
|
-
|
|
935
|
-
| path | throughput |
|
|
936
|
-
|-------------------------------------|-----------|
|
|
937
|
-
| random-batch gather, zstd store | 4.1–4.5 GiB/s |
|
|
938
|
-
| same, npy-mmap fancy indexing | 0.6–1.3 GiB/s |
|
|
939
|
-
| DataLoader, 4 prefetch threads | 5.7–6.6 GiB/s (123–144 batches/s) |
|
|
940
|
-
|
|
941
|
-
zstd decompression reads ~8x fewer bytes than raw storage, so the compressed
|
|
942
|
-
store gathers *faster* than the raw one (zstd 4587 MiB/s vs raw 1792 MiB/s on
|
|
943
|
-
the same split).
|
|
944
|
-
|
|
945
|
-
**The npy intermediate is optional.** Parse the skeleton files in parallel and
|
|
946
|
-
feed each fixed-shape ndarray produced by the parser directly to
|
|
947
|
-
`Dense.append`. This writes the store in one pass with no npy staging or
|
|
948
|
-
second read; zrecord bounds its compression working memory independently.
|
|
960
|
+
## Real-data verification
|
|
961
|
+
|
|
962
|
+
Dataset-specific preprocessing and verification remain in the
|
|
963
|
+
**[DataPipe](https://codeberg.org/eoelab/DataPipe)** repository. The built-in
|
|
964
|
+
converter covers standardized Hugging Face datasets; DataPipe handles sources
|
|
965
|
+
without a common remote protocol and implements derived modalities as loader
|
|
966
|
+
transforms without NumPy dump intermediates.
|
|
949
967
|
|
|
950
968
|
## Current Limitations
|
|
951
969
|
* Single-host only; multi-host training is not supported.
|
|
@@ -48,9 +48,9 @@ from loaderx.dataloader import DataLoader
|
|
|
48
48
|
|
|
49
49
|
data = np.load('data.npy', mmap_mode='r')
|
|
50
50
|
label = np.load('label.npy', mmap_mode='r')
|
|
51
|
-
with Dense.create('train_data', data.dtype, data.shape[1:]) as ds:
|
|
51
|
+
with Dense.create('train_data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
52
52
|
ds.append(data)
|
|
53
|
-
with Dense.create('train_label', label.dtype, label.shape[1:]) as ds:
|
|
53
|
+
with Dense.create('train_label', label.dtype, label.shape[1:], codec='zstd') as ds:
|
|
54
54
|
ds.append(label)
|
|
55
55
|
|
|
56
56
|
data_store = Dense.open('train_data')
|
|
@@ -75,13 +75,70 @@ at the same indices, so record ``i`` lines up across them. The ``transform``
|
|
|
75
75
|
callback is the collate step — reshape, cast, stack — where ``values`` is the
|
|
76
76
|
plain dense batch ready for the model.
|
|
77
77
|
|
|
78
|
+
### Offline Hugging Face conversion
|
|
79
|
+
|
|
80
|
+
The optional converter uses Hugging Face Datasets for remote discovery,
|
|
81
|
+
download, caching, revision handling and source-format decoding. It then writes
|
|
82
|
+
explicit typed Zrecord streams, so training needs neither `datasets` nor Arrow:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install 'loaderx[converter]'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from loaderx.converter import convert, huggingface
|
|
90
|
+
|
|
91
|
+
dataset = huggingface("ylecun/mnist", revision="main", token=None)
|
|
92
|
+
convert(
|
|
93
|
+
dataset,
|
|
94
|
+
"mnist",
|
|
95
|
+
codec="zstd",
|
|
96
|
+
batch_size=256,
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`huggingface` resolves the requested branch, tag or commit to an immutable
|
|
101
|
+
snapshot, then downloads and returns the repository's DatasetDict. Repositories
|
|
102
|
+
without one default config require `config=...`. `convert` is the independent
|
|
103
|
+
persistence stage: it accepts that DatasetDict or one already normalized
|
|
104
|
+
Dataset. `batch_size` bounds records per Python batch; actual bytes also depend
|
|
105
|
+
on record sizes and Ragged packing.
|
|
106
|
+
For private or gated repositories, pass `token="hf_..."`; `None` uses the
|
|
107
|
+
standard HF environment, local login, or anonymous access.
|
|
108
|
+
Static or regularly batched numeric arrays become Dense stores, while dynamic
|
|
109
|
+
arrays become Ragged stores; source dtype and record shape are preserved.
|
|
110
|
+
|
|
111
|
+
Zrecord persists numeric tensors rather than Python objects. A standard HF
|
|
112
|
+
feature that cannot be represented losslessly fails with its column name;
|
|
113
|
+
conversion does not filter, pad, tokenize, reshape, or run a transform stage.
|
|
114
|
+
Arrow dtypes, including float64 and uint64, are preserved. Only the current
|
|
115
|
+
batch is formatted; `with_format` never materializes the complete dataset.
|
|
116
|
+
|
|
117
|
+
The result groups aligned streams under one published root:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
mnist/
|
|
121
|
+
train/
|
|
122
|
+
image/
|
|
123
|
+
label/
|
|
124
|
+
test/
|
|
125
|
+
image/
|
|
126
|
+
label/
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Conversion happens under a temporary sibling directory. The root is renamed
|
|
130
|
+
into place only after all stores close and their record counts agree; a failed
|
|
131
|
+
build that raises a Python exception is discarded. A process kill may leave the
|
|
132
|
+
hidden temporary sibling for manual cleanup. No collection manifest is added;
|
|
133
|
+
the output is only split directories containing ordinary column Zrecord stores.
|
|
134
|
+
|
|
78
135
|
### Creating a dense store
|
|
79
136
|
```python
|
|
80
137
|
import numpy as np
|
|
81
138
|
from loaderx.zrecord import Dense
|
|
82
139
|
|
|
83
140
|
data = np.load('data.npy', mmap_mode='r')
|
|
84
|
-
with Dense.create('train_data', data.dtype, data.shape[1:]) as ds:
|
|
141
|
+
with Dense.create('train_data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
85
142
|
ds.append(data)
|
|
86
143
|
```
|
|
87
144
|
One record per slice along axis 0; a 1-D array (the usual shape of a label set)
|
|
@@ -107,7 +164,7 @@ engine consumes the trusted Dense stride or Ragged offsets. Append inputs are
|
|
|
107
164
|
strictly NumPy arrays: Dense takes one batched ndarray and Ragged takes an
|
|
108
165
|
iterable of ndarrays. Raw bytes and pre-encoded images are made explicit with
|
|
109
166
|
``np.frombuffer(raw, dtype=np.uint8)`` and stored in a
|
|
110
|
-
``Ragged.create(path, dtype=np.uint8, ndim=1)`` rather than creating a second
|
|
167
|
+
``Ragged.create(path, dtype=np.uint8, ndim=1, codec="zstd")`` rather than creating a second
|
|
111
168
|
public storage API.
|
|
112
169
|
|
|
113
170
|
### Records
|
|
@@ -122,7 +179,7 @@ import numpy as np
|
|
|
122
179
|
from loaderx.zrecord import Dense
|
|
123
180
|
|
|
124
181
|
data = np.arange(64, dtype=np.float32).reshape(8, 2, 4)
|
|
125
|
-
with Dense.create('data', data.dtype, data.shape[1:]) as ds:
|
|
182
|
+
with Dense.create('data', data.dtype, data.shape[1:], codec='zstd') as ds:
|
|
126
183
|
ds.append(data)
|
|
127
184
|
ds = Dense.open('data')
|
|
128
185
|
ds[0, 5, 2] # (3, 2, 4) — shape from the persisted schema
|
|
@@ -148,7 +205,7 @@ batch is the model's call — a plain numpy loop, wherever you need it:
|
|
|
148
205
|
from loaderx.zrecord import Ragged
|
|
149
206
|
|
|
150
207
|
seqs = [np.arange(L, dtype=np.int32) for L in (3, 1, 4, 1, 5)]
|
|
151
|
-
with Ragged.create('tokens', np.int32, ndim=1) as rs:
|
|
208
|
+
with Ragged.create('tokens', np.int32, ndim=1, codec='zstd') as rs:
|
|
152
209
|
rs.append(seqs) # dtype/rank fixed; lengths remain per-record
|
|
153
210
|
rs = Ragged.open('tokens')
|
|
154
211
|
|
|
@@ -235,8 +292,10 @@ warmup belongs outside build or loader benchmark timing.
|
|
|
235
292
|
### Creating containers
|
|
236
293
|
|
|
237
294
|
``Dense.create`` and ``Ragged.create`` return append-only ordered-sequence
|
|
238
|
-
builders. ``
|
|
239
|
-
|
|
295
|
+
builders. The ``codec`` keyword is required: callers must explicitly select
|
|
296
|
+
``"raw"``, ``"zstd"``, or ``"zstd_dict"``. ``append`` is explicit — one input
|
|
297
|
+
batch extends the logical sequence without exposing native physical completion
|
|
298
|
+
order.
|
|
240
299
|
Dense append is synchronous and borrows an already-contiguous ndarray without a
|
|
241
300
|
snapshot copy. Ragged append consumes its iterable once into one owned packed
|
|
242
301
|
buffer, then completes the native append before returning. Nothing is inferred.
|
|
@@ -244,12 +303,13 @@ buffer, then completes the native append before returning. Nothing is inferred.
|
|
|
244
303
|
```python
|
|
245
304
|
from loaderx.zrecord import Dense, Ragged
|
|
246
305
|
|
|
247
|
-
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28),
|
|
306
|
+
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28),
|
|
307
|
+
codec='zstd', data_shards=4)
|
|
248
308
|
ds.append(images[i:i + 1024]) # synchronous native batch; returns None
|
|
249
309
|
ds.append(single_image[None]) # one sample is batch_size 1 — add the axis yourself
|
|
250
310
|
ds.close() # publish before opening
|
|
251
311
|
|
|
252
|
-
tok = Ragged.create('tokens', dtype=np.int32, ndim=1)
|
|
312
|
+
tok = Ragged.create('tokens', dtype=np.int32, ndim=1, codec='zstd')
|
|
253
313
|
tok.append([seq_a, seq_b, seq_c])
|
|
254
314
|
tok.close()
|
|
255
315
|
|
|
@@ -290,7 +350,7 @@ published without one.
|
|
|
290
350
|
### Codec notes
|
|
291
351
|
|
|
292
352
|
`"zstd"` compresses each record independently with plain zstd (level 3). Use it
|
|
293
|
-
for general-purpose compression
|
|
353
|
+
for general-purpose compression; it is fast and must be selected explicitly.
|
|
294
354
|
|
|
295
355
|
`"zstd_dict"` trains a shared dictionary on a sample of the data before writing
|
|
296
356
|
any record, then compresses every record against it at level 15. The dictionary
|
|
@@ -384,11 +444,11 @@ from loaderx.zrecord import Dense, Ragged
|
|
|
384
444
|
from loaderx.dataloader import DataLoader
|
|
385
445
|
|
|
386
446
|
root = "xsub/train"
|
|
387
|
-
with Dense.create(root + "/joint", joint.dtype, joint.shape[1:]) as s:
|
|
447
|
+
with Dense.create(root + "/joint", joint.dtype, joint.shape[1:], codec="zstd") as s:
|
|
388
448
|
s.append(joint)
|
|
389
|
-
with Dense.create(root + "/label", label.dtype, label.shape[1:]) as s:
|
|
449
|
+
with Dense.create(root + "/label", label.dtype, label.shape[1:], codec="zstd") as s:
|
|
390
450
|
s.append(label)
|
|
391
|
-
with Ragged.create(root + "/token", np.int32, ndim=1) as s:
|
|
451
|
+
with Ragged.create(root + "/token", np.int32, ndim=1, codec="zstd") as s:
|
|
392
452
|
s.append(seqs)
|
|
393
453
|
|
|
394
454
|
streams = {
|
|
@@ -870,57 +930,13 @@ The loader rows used Numba 0.67.0, Torch 2.13.0 and Grain 0.2.18; all benchmark
|
|
|
870
930
|
are pinned in `scripts/requirements-bench.txt`. Temporary stores used the ordinary
|
|
871
931
|
disk-backed `/tmp` filesystem, not `/dev/shm`.
|
|
872
932
|
|
|
873
|
-
## Real-data verification
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
`.npy` outputs of the standard preprocessing pipeline were treated as ground
|
|
881
|
-
truth. This verification was not rerun with the synthetic benchmarks above;
|
|
882
|
-
its throughput is retained as a separate historical 12-core result.
|
|
883
|
-
|
|
884
|
-
**Correctness — the read path is bit-exact against the ground truth:**
|
|
885
|
-
|
|
886
|
-
* Full scan of all **228,356 records** (joint float32 + label int64, all four
|
|
887
|
-
splits) through `Dense`: byte-for-byte identical to the reference npy.
|
|
888
|
-
* A `DataLoader` over joint + label + an index stream, run under all three
|
|
889
|
-
sampler modes (`sequential`, `iid`, `cyclic`): every received batch is
|
|
890
|
-
bit-exact to the ground truth at its own declared indices, and the streams
|
|
891
|
-
stay index-aligned.
|
|
892
|
-
* Sampler semantics hold on real index spaces: `sequential` walks in order,
|
|
893
|
-
`cyclic` draws a full cycle without replacement, `iid` is deterministic per
|
|
894
|
-
seed.
|
|
895
|
-
|
|
896
|
-
**Storage — zstd on this data:**
|
|
897
|
-
|
|
898
|
-
| store | on disk | ratio |
|
|
899
|
-
|-----------------------|---------|-------|
|
|
900
|
-
| npy (raw float32) | 6.4 GB | 1.00x |
|
|
901
|
-
| zrecord raw | 6.86 GB | 1.00x |
|
|
902
|
-
| zrecord zstd | 0.79 GB | 8.66x |
|
|
903
|
-
| zrecord zstd_dict | 0.75 GB | 9.13x |
|
|
904
|
-
|
|
905
|
-
Sizes above are for one split (`xview/val`, 38,132 records); across all four
|
|
906
|
-
splits the zstd `joint` stores total 4.97 GB against 41 GB of raw npy (~8x).
|
|
907
|
-
|
|
908
|
-
**Throughput** (180 KB per record, warm page cache, 12 physical cores):
|
|
909
|
-
|
|
910
|
-
| path | throughput |
|
|
911
|
-
|-------------------------------------|-----------|
|
|
912
|
-
| random-batch gather, zstd store | 4.1–4.5 GiB/s |
|
|
913
|
-
| same, npy-mmap fancy indexing | 0.6–1.3 GiB/s |
|
|
914
|
-
| DataLoader, 4 prefetch threads | 5.7–6.6 GiB/s (123–144 batches/s) |
|
|
915
|
-
|
|
916
|
-
zstd decompression reads ~8x fewer bytes than raw storage, so the compressed
|
|
917
|
-
store gathers *faster* than the raw one (zstd 4587 MiB/s vs raw 1792 MiB/s on
|
|
918
|
-
the same split).
|
|
919
|
-
|
|
920
|
-
**The npy intermediate is optional.** Parse the skeleton files in parallel and
|
|
921
|
-
feed each fixed-shape ndarray produced by the parser directly to
|
|
922
|
-
`Dense.append`. This writes the store in one pass with no npy staging or
|
|
923
|
-
second read; zrecord bounds its compression working memory independently.
|
|
933
|
+
## Real-data verification
|
|
934
|
+
|
|
935
|
+
Dataset-specific preprocessing and verification remain in the
|
|
936
|
+
**[DataPipe](https://codeberg.org/eoelab/DataPipe)** repository. The built-in
|
|
937
|
+
converter covers standardized Hugging Face datasets; DataPipe handles sources
|
|
938
|
+
without a common remote protocol and implements derived modalities as loader
|
|
939
|
+
transforms without NumPy dump intermediates.
|
|
924
940
|
|
|
925
941
|
## Current Limitations
|
|
926
942
|
* Single-host only; multi-host training is not supported.
|
|
@@ -4,11 +4,13 @@ Public API is imported from its layer, not flattened here, so the abstraction
|
|
|
4
4
|
boundaries stay visible:
|
|
5
5
|
|
|
6
6
|
from loaderx.utils import train_dict
|
|
7
|
+
from loaderx.converter import convert, huggingface
|
|
7
8
|
from loaderx.zrecord import Dense, Ragged, ZrecordError
|
|
8
9
|
from loaderx.dataloader import DataLoader
|
|
9
10
|
from loaderx.zsampler import Sampler
|
|
10
11
|
|
|
11
|
-
``utils`` holds the manual, standalone ``train_dict`` step. ``
|
|
12
|
+
``utils`` holds the manual, standalone ``train_dict`` step. ``converter`` owns
|
|
13
|
+
offline remote ingestion into completed Zrecord streams. ``zrecord`` exposes
|
|
12
14
|
``Dense`` and ``Ragged`` are rebuildable ordered containers: ``create`` extends
|
|
13
15
|
the sequence in append order, ``open`` is read-only, and ``close`` is the sole
|
|
14
16
|
public publication boundary.
|
|
@@ -16,4 +18,4 @@ public publication boundary.
|
|
|
16
18
|
step-based sampler + prefetch pipeline over them.
|
|
17
19
|
"""
|
|
18
20
|
|
|
19
|
-
__version__ = "2.3.
|
|
21
|
+
__version__ = "2.3.5"
|