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.
Files changed (110) hide show
  1. {loaderx-2.3.3/loaderx.egg-info → loaderx-2.3.5}/PKG-INFO +84 -66
  2. {loaderx-2.3.3 → loaderx-2.3.5}/README.md +81 -65
  3. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/__init__.py +4 -2
  4. loaderx-2.3.5/loaderx/converter.py +204 -0
  5. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/zrecord.py +2 -2
  6. {loaderx-2.3.3 → loaderx-2.3.5/loaderx.egg-info}/PKG-INFO +84 -66
  7. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/SOURCES.txt +1 -0
  8. loaderx-2.3.5/loaderx.egg-info/requires.txt +6 -0
  9. {loaderx-2.3.3 → loaderx-2.3.5}/pyproject.toml +3 -0
  10. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/test_loaderx.py +195 -16
  11. loaderx-2.3.3/loaderx.egg-info/requires.txt +0 -3
  12. {loaderx-2.3.3 → loaderx-2.3.5}/LICENSE +0 -0
  13. {loaderx-2.3.3 → loaderx-2.3.5}/MANIFEST.in +0 -0
  14. {loaderx-2.3.3 → loaderx-2.3.5}/build.zig +0 -0
  15. {loaderx-2.3.3 → loaderx-2.3.5}/build.zig.zon +0 -0
  16. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/_lib.py +0 -0
  17. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/_store.py +0 -0
  18. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/dataloader.py +0 -0
  19. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/utils.py +0 -0
  20. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx/zsampler.py +0 -0
  21. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/dependency_links.txt +0 -0
  22. {loaderx-2.3.3 → loaderx-2.3.5}/loaderx.egg-info/top_level.txt +0 -0
  23. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/_bench_common.py +0 -0
  24. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench.py +0 -0
  25. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench_dense.py +0 -0
  26. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/bench_ragged.py +0 -0
  27. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/build_wheels.py +0 -0
  28. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/prepare_tokens.py +0 -0
  29. {loaderx-2.3.3 → loaderx-2.3.5}/scripts/requirements-bench.txt +0 -0
  30. {loaderx-2.3.3 → loaderx-2.3.5}/setup.cfg +0 -0
  31. {loaderx-2.3.3 → loaderx-2.3.5}/setup.py +0 -0
  32. {loaderx-2.3.3 → loaderx-2.3.5}/src/record/engine.zig +0 -0
  33. {loaderx-2.3.3 → loaderx-2.3.5}/src/record/executor.zig +0 -0
  34. {loaderx-2.3.3 → loaderx-2.3.5}/src/record/storage.zig +0 -0
  35. {loaderx-2.3.3 → loaderx-2.3.5}/src/store.zig +0 -0
  36. {loaderx-2.3.3 → loaderx-2.3.5}/src/zsampler.zig +0 -0
  37. {loaderx-2.3.3 → loaderx-2.3.5}/src/zstd/c.zig +0 -0
  38. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/COPYING +0 -0
  39. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/LICENSE +0 -0
  40. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/allocations.h +0 -0
  41. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/bits.h +0 -0
  42. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/bitstream.h +0 -0
  43. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/compiler.h +0 -0
  44. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/cpu.h +0 -0
  45. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/debug.c +0 -0
  46. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/debug.h +0 -0
  47. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/entropy_common.c +0 -0
  48. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/error_private.c +0 -0
  49. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/error_private.h +0 -0
  50. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/fse.h +0 -0
  51. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/fse_decompress.c +0 -0
  52. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/huf.h +0 -0
  53. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/mem.h +0 -0
  54. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/pool.c +0 -0
  55. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/pool.h +0 -0
  56. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/portability_macros.h +0 -0
  57. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/threading.c +0 -0
  58. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/threading.h +0 -0
  59. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/xxhash.c +0 -0
  60. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/xxhash.h +0 -0
  61. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_common.c +0 -0
  62. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_deps.h +0 -0
  63. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_internal.h +0 -0
  64. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/common/zstd_trace.h +0 -0
  65. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/clevels.h +0 -0
  66. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/fse_compress.c +0 -0
  67. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/hist.c +0 -0
  68. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/hist.h +0 -0
  69. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/huf_compress.c +0 -0
  70. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress.c +0 -0
  71. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_internal.h +0 -0
  72. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_literals.c +0 -0
  73. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_literals.h +0 -0
  74. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_sequences.c +0 -0
  75. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_sequences.h +0 -0
  76. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_superblock.c +0 -0
  77. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_compress_superblock.h +0 -0
  78. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_cwksp.h +0 -0
  79. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_double_fast.c +0 -0
  80. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_double_fast.h +0 -0
  81. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_fast.c +0 -0
  82. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_fast.h +0 -0
  83. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_lazy.c +0 -0
  84. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_lazy.h +0 -0
  85. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm.c +0 -0
  86. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm.h +0 -0
  87. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_ldm_geartab.h +0 -0
  88. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_opt.c +0 -0
  89. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_opt.h +0 -0
  90. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_preSplit.c +0 -0
  91. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstd_preSplit.h +0 -0
  92. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstdmt_compress.c +0 -0
  93. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/compress/zstdmt_compress.h +0 -0
  94. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/huf_decompress.c +0 -0
  95. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/huf_decompress_amd64.S +0 -0
  96. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_ddict.c +0 -0
  97. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_ddict.h +0 -0
  98. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress.c +0 -0
  99. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_block.c +0 -0
  100. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_block.h +0 -0
  101. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/decompress/zstd_decompress_internal.h +0 -0
  102. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/cover.c +0 -0
  103. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/cover.h +0 -0
  104. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/divsufsort.c +0 -0
  105. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/divsufsort.h +0 -0
  106. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/fastcover.c +0 -0
  107. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/dictBuilder/zdict.c +0 -0
  108. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/zdict.h +0 -0
  109. {loaderx-2.3.3 → loaderx-2.3.5}/vendor/zstd/lib/zstd.h +0 -0
  110. {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
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. ``append`` is explicit one input batch extends the logical sequence
264
- without exposing native physical completion order.
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), data_shards=4)
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 it is fast and the default.
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: NTU RGB-D skeletons
899
-
900
- The vision tables above are synthetic; the token tables use real WikiText-103.
901
- As a separate historical ground-truth check,
902
- loaderx was run end to end on **NTU RGB-D skeleton data** — 114,480 raw `.skeleton` files,
903
- 120 action classes, 25 joints processed into the ST-GCN `N C T V M` layout
904
- (per-sample `(3, 300, 25, 2)` float32) for the `xsub`/`xview` protocols. The
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. ``append`` is explicit one input batch extends the logical sequence
239
- without exposing native physical completion order.
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), data_shards=4)
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 it is fast and the default.
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: NTU RGB-D skeletons
874
-
875
- The vision tables above are synthetic; the token tables use real WikiText-103.
876
- As a separate historical ground-truth check,
877
- loaderx was run end to end on **NTU RGB-D skeleton data** — 114,480 raw `.skeleton` files,
878
- 120 action classes, 25 joints processed into the ST-GCN `N C T V M` layout
879
- (per-sample `(3, 300, 25, 2)` float32) for the `xsub`/`xview` protocols. The
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. ``zrecord`` exposes
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.3"
21
+ __version__ = "2.3.5"