loaderx 2.0.7__tar.gz → 2.2.1__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.0.7 → loaderx-2.2.1}/MANIFEST.in +1 -1
- {loaderx-2.0.7/loaderx.egg-info → loaderx-2.2.1}/PKG-INFO +79 -69
- {loaderx-2.0.7 → loaderx-2.2.1}/README.md +78 -68
- {loaderx-2.0.7 → loaderx-2.2.1}/build.zig +5 -3
- {loaderx-2.0.7 → loaderx-2.2.1}/build.zig.zon +1 -1
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/__init__.py +1 -1
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/_store.py +11 -9
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/utils.py +34 -3
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/zrecord.py +89 -64
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/zsampler.py +5 -4
- {loaderx-2.0.7 → loaderx-2.2.1/loaderx.egg-info}/PKG-INFO +79 -69
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx.egg-info/SOURCES.txt +3 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/bench_ragged.py +24 -25
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/test_loaderx.py +124 -42
- {loaderx-2.0.7 → loaderx-2.2.1}/src/record/engine.zig +110 -111
- {loaderx-2.0.7 → loaderx-2.2.1}/src/record/executor.zig +14 -8
- {loaderx-2.0.7 → loaderx-2.2.1}/src/record/storage.zig +105 -105
- {loaderx-2.0.7 → loaderx-2.2.1}/src/store.zig +14 -26
- {loaderx-2.0.7 → loaderx-2.2.1}/src/zsampler.zig +19 -28
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/bits.h +92 -87
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/bitstream.h +26 -29
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/compiler.h +36 -22
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/cpu.h +1 -1
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/debug.h +0 -9
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/error_private.c +1 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/error_private.h +0 -10
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/fse.h +2 -17
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/fse_decompress.c +2 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/huf.h +0 -9
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/mem.h +7 -11
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/pool.h +0 -9
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/portability_macros.h +22 -9
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/threading.h +0 -8
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/xxhash.h +93 -19
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/zstd_deps.h +12 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/zstd_internal.h +1 -69
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/zstd_trace.h +5 -12
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/hist.c +10 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/hist.h +7 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress.c +1057 -367
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_internal.h +227 -125
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_literals.c +1 -1
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_sequences.c +7 -7
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_sequences.h +7 -6
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_superblock.c +17 -17
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_cwksp.h +41 -24
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_double_fast.c +58 -50
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_double_fast.h +4 -12
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_fast.c +91 -74
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_fast.h +4 -12
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_lazy.c +64 -64
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_lazy.h +30 -39
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_ldm.c +48 -33
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_ldm.h +6 -14
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_opt.c +55 -51
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_opt.h +8 -16
- loaderx-2.2.1/vendor/zstd/lib/compress/zstd_preSplit.c +238 -0
- loaderx-2.2.1/vendor/zstd/lib/compress/zstd_preSplit.h +33 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstdmt_compress.c +134 -93
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstdmt_compress.h +4 -15
- loaderx-2.2.1/vendor/zstd/lib/decompress/huf_decompress_amd64.S +602 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_decompress.c +14 -11
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_decompress_block.c +6 -12
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_decompress_internal.h +5 -5
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/cover.c +60 -19
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/divsufsort.h +0 -10
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/zdict.c +2 -2
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/zdict.h +15 -8
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/zstd.h +241 -132
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/zstd_errors.h +1 -8
- {loaderx-2.0.7 → loaderx-2.2.1}/LICENSE +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/_lib.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx/dataloader.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx.egg-info/dependency_links.txt +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx.egg-info/requires.txt +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/loaderx.egg-info/top_level.txt +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/pyproject.toml +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/_bench_common.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/bench.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/bench_dense.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/build_wheels.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/prepare_tokens.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/scripts/requirements-bench.txt +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/setup.cfg +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/setup.py +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/src/zstd/c.zig +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/COPYING +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/LICENSE +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/allocations.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/debug.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/entropy_common.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/pool.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/threading.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/xxhash.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/common/zstd_common.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/clevels.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/fse_compress.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/huf_compress.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_literals.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_compress_superblock.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/compress/zstd_ldm_geartab.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/huf_decompress.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_ddict.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_ddict.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/decompress/zstd_decompress_block.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/cover.h +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/divsufsort.c +0 -0
- {loaderx-2.0.7 → loaderx-2.2.1}/vendor/zstd/lib/dictBuilder/fastcover.c +0 -0
|
@@ -8,7 +8,7 @@ recursive-include scripts *.py
|
|
|
8
8
|
include scripts/requirements-bench.txt
|
|
9
9
|
|
|
10
10
|
# Vendored zstd: the sdist rebuilds with Zig, so it needs the C sources.
|
|
11
|
-
recursive-include vendor *.c *.h
|
|
11
|
+
recursive-include vendor *.c *.h *.S
|
|
12
12
|
include vendor/zstd/LICENSE vendor/zstd/COPYING
|
|
13
13
|
|
|
14
14
|
exclude loaderx/lib/lib_here
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loaderx
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Rebuildable high-performance ordered record containers
|
|
5
5
|
Author-email: Ben0i0d <ben0i0d@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -121,25 +121,26 @@ reader. Open the result with ``Dense.open``:
|
|
|
121
121
|
batch = ds[:]
|
|
122
122
|
ds.close()
|
|
123
123
|
|
|
124
|
-
Python defines the exact record schema: ``dtype``
|
|
125
|
-
|
|
124
|
+
Python defines the exact record schema: both geometries store ``dtype`` and
|
|
125
|
+
``ndim``; Dense additionally stores ``item_shape``. The MsgPack bytes live opaquely in the
|
|
126
126
|
static page at the front of ``meta.zr``; Zig persists them but never interprets
|
|
127
127
|
them. The schema accepts no user metadata. Python selects the geometry and gives
|
|
128
128
|
the private native engine only the runtime record boundaries it needs. Each
|
|
129
|
-
Ragged record carries
|
|
129
|
+
Ragged record carries exactly ``ndim`` inline little-endian u64 dimensions.
|
|
130
130
|
One native physical
|
|
131
131
|
engine consumes the trusted Dense stride or Ragged offsets. Append inputs are
|
|
132
132
|
strictly NumPy arrays: Dense takes one batched ndarray and Ragged takes an
|
|
133
133
|
iterable of ndarrays. Raw bytes and pre-encoded images are made explicit with
|
|
134
134
|
``np.frombuffer(raw, dtype=np.uint8)`` and stored in a
|
|
135
|
-
``Ragged.create(path, dtype=np.uint8)`` rather than creating a second
|
|
135
|
+
``Ragged.create(path, dtype=np.uint8, ndim=1)`` rather than creating a second
|
|
136
136
|
public storage API.
|
|
137
137
|
|
|
138
138
|
### Records
|
|
139
139
|
One persistent format, two native execution contracts. ``Dense`` is the **dense** contract
|
|
140
140
|
where every record is exactly one row of the recorded ``item_shape``; reads
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
allocate a fixed-stride destination whose batch shape follows from the schema.
|
|
142
|
+
Physical records still use the shared ``RecordLoc[logical_id] -> payload``
|
|
143
|
+
pipeline, so compressed completion order never becomes a second Dense layout:
|
|
143
144
|
|
|
144
145
|
```python
|
|
145
146
|
import numpy as np
|
|
@@ -160,11 +161,11 @@ the same way.
|
|
|
160
161
|
|
|
161
162
|
``Ragged`` is the **ragged** contract for variable-length records. It is a
|
|
162
163
|
separate contract: :class:`Ragged` hands back a list of arrays, so a
|
|
163
|
-
loader never has to carry ``row_splits`` around. ``dtype``
|
|
164
|
-
explicit; each record keeps its **own
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
loader never has to carry ``row_splits`` around. ``dtype`` and ``ndim`` are
|
|
165
|
+
unified and explicit; each record keeps its **own dimension lengths**, recorded
|
|
166
|
+
per record and restored exactly on read. Every axis may vary, but every record
|
|
167
|
+
has the schema rank; nothing is inferred from the source. Ragged requires at
|
|
168
|
+
least one axis; scalar records use Dense with ``item_shape=()``.
|
|
168
169
|
zero-byte arrays are rejected because physical records are nonempty. Densifying a list into a dense
|
|
169
170
|
batch is the model's call — a plain numpy loop, wherever you need it:
|
|
170
171
|
|
|
@@ -172,8 +173,8 @@ batch is the model's call — a plain numpy loop, wherever you need it:
|
|
|
172
173
|
from loaderx.zrecord import Ragged
|
|
173
174
|
|
|
174
175
|
seqs = [np.arange(L, dtype=np.int32) for L in (3, 1, 4, 1, 5)]
|
|
175
|
-
with Ragged.create('tokens', np.int32) as rs:
|
|
176
|
-
rs.append(seqs) # dtype
|
|
176
|
+
with Ragged.create('tokens', np.int32, ndim=1) as rs:
|
|
177
|
+
rs.append(seqs) # dtype/rank fixed; lengths remain per-record
|
|
177
178
|
rs = Ragged.open('tokens')
|
|
178
179
|
|
|
179
180
|
records = rs[0, 2, 4] # list of ndarray — one per record, exact shapes
|
|
@@ -273,7 +274,7 @@ ds.append(images[i:i + 1024]) # synchronous native batch; returns None
|
|
|
273
274
|
ds.append(single_image[None]) # one sample is batch_size 1 — add the axis yourself
|
|
274
275
|
ds.close() # publish before opening
|
|
275
276
|
|
|
276
|
-
tok = Ragged.create('tokens', dtype=np.int32)
|
|
277
|
+
tok = Ragged.create('tokens', dtype=np.int32, ndim=1)
|
|
277
278
|
tok.append([seq_a, seq_b, seq_c])
|
|
278
279
|
tok.close()
|
|
279
280
|
|
|
@@ -288,12 +289,14 @@ appended to. ``close()`` on a writer publishes the container Header;
|
|
|
288
289
|
``with`` for scoped lifetimes. Content is never changed in place: rerun the
|
|
289
290
|
authoritative build at a new path, validate it, then switch consumers to it.
|
|
290
291
|
|
|
291
|
-
The exact schema is declared at creation and encoded by Python as MsgPack.
|
|
292
|
-
|
|
293
|
-
``
|
|
292
|
+
The exact schema is declared at creation and encoded by Python as MsgPack. Both
|
|
293
|
+
schemas contain ``dtype`` and ``ndim``; Dense additionally contains
|
|
294
|
+
``item_shape`` and requires its length to equal ``ndim``. Ragged requires every
|
|
295
|
+
appended record to have that rank, while every dimension length may vary.
|
|
296
|
+
Structured, subarray, object, metadata-bearing, and zero-itemsize dtypes are not
|
|
294
297
|
supported: their semantics do not round-trip through one canonical NumPy dtype
|
|
295
298
|
string. The encoded schema has 4064 bytes available in the fixed 4096-byte
|
|
296
|
-
metadata page. Ragged schema size is
|
|
299
|
+
metadata page. Ragged schema size is fixed; Dense schema size grows
|
|
297
300
|
only with the integer ``item_shape``, so the physical limit is far above any
|
|
298
301
|
practical NumPy array rank.
|
|
299
302
|
Unexpected fields are rejected. ``append`` validates dtype and shape in Python,
|
|
@@ -310,7 +313,7 @@ published without one.
|
|
|
310
313
|
for general-purpose compression — it is fast and the default.
|
|
311
314
|
|
|
312
315
|
`"zstd_dict"` trains a shared dictionary on a sample of the data before writing
|
|
313
|
-
any record, then compresses every record against it at level
|
|
316
|
+
any record, then compresses every record against it at level 15. The dictionary
|
|
314
317
|
captures structure shared across records that per-record compression cannot see —
|
|
315
318
|
a large win for many small, similar records (image tiles, token sequences).
|
|
316
319
|
|
|
@@ -353,7 +356,8 @@ from loaderx.zrecord import Dense, Ragged
|
|
|
353
356
|
d = train_dict(settled_array, tier="balanced")
|
|
354
357
|
|
|
355
358
|
# then any new store can install it and append explicitly
|
|
356
|
-
with Ragged.create('tokens', np.int32,
|
|
359
|
+
with Ragged.create('tokens', np.int32, ndim=1,
|
|
360
|
+
codec='zstd_dict', dict_bytes=d) as ds:
|
|
357
361
|
ds.append(token_generator)
|
|
358
362
|
with Dense.create('data', data.dtype, data.shape[1:],
|
|
359
363
|
codec='zstd_dict', dict_bytes=d) as ds:
|
|
@@ -377,7 +381,8 @@ with Dense.open("src") as s, \
|
|
|
377
381
|
```
|
|
378
382
|
|
|
379
383
|
``Ragged`` is the same shape: ``s[:]`` returns ``list[np.ndarray]``, which
|
|
380
|
-
is exactly its append input
|
|
384
|
+
is exactly its append input; a destination is created with ``s.dtype`` and
|
|
385
|
+
``s.ndim``. The
|
|
381
386
|
native compression path bounds its own working memory; there is no public chunk
|
|
382
387
|
parameter. ``dst`` must not already hold a store.
|
|
383
388
|
|
|
@@ -403,7 +408,7 @@ with Dense.create(root + "/joint", joint.dtype, joint.shape[1:]) as s:
|
|
|
403
408
|
s.append(joint)
|
|
404
409
|
with Dense.create(root + "/label", label.dtype, label.shape[1:]) as s:
|
|
405
410
|
s.append(label)
|
|
406
|
-
with Ragged.create(root + "/token", np.int32) as s:
|
|
411
|
+
with Ragged.create(root + "/token", np.int32, ndim=1) as s:
|
|
407
412
|
s.append(seqs)
|
|
408
413
|
|
|
409
414
|
streams = {
|
|
@@ -496,9 +501,12 @@ so CFFI, NumPy allocation, and Ragged list/shape reconstruction are timed.
|
|
|
496
501
|
|
|
497
502
|
### Methodology
|
|
498
503
|
|
|
499
|
-
The
|
|
500
|
-
|
|
501
|
-
|
|
504
|
+
The comparison matrix began as one complete run on a warm page cache. All
|
|
505
|
+
Zrecord Store rows and the two loaderx identity rows were refreshed on 2.2.0
|
|
506
|
+
after the libzstd 1.5.7 and runtime-dispatched Huffman kernel update. Unchanged
|
|
507
|
+
optional Store backends, sampler rows and external loader rows remain from the
|
|
508
|
+
immediately preceding 2.0.9 run. These are not three-run medians: an unexpected result is traced
|
|
509
|
+
separately instead of being hidden by repeated aggregation. Within each store workload every
|
|
502
510
|
backend receives identical source records. Correctness and timing use independent
|
|
503
511
|
deterministic `zsampler` IID streams. Loader backends receive
|
|
504
512
|
the same source and seed but use their own shipped samplers, so their exact
|
|
@@ -556,8 +564,8 @@ threads):
|
|
|
556
564
|
| NUMA | 1 node |
|
|
557
565
|
| memory | 31 GiB (not limited by cgroup) |
|
|
558
566
|
| shared memory | 16 GiB `/dev/shm` |
|
|
559
|
-
| OS | Debian GNU/Linux forky/sid, kernel 7.1.
|
|
560
|
-
| python | CPython 3.14.7 (standard GIL build), numpy 2.
|
|
567
|
+
| OS | Debian GNU/Linux forky/sid, kernel 7.1.8+deb13-amd64, x86_64 |
|
|
568
|
+
| python | CPython 3.14.7 (standard GIL build), numpy 2.4.6 |
|
|
561
569
|
|
|
562
570
|
The benchmark process sees all 24 threads and is not memory-limited by cgroup.
|
|
563
571
|
The 16 GiB shared-memory mount accommodates the four-worker, 36.8 MiB-batch torch
|
|
@@ -575,9 +583,9 @@ Fixed-resolution vision records — 147 KiB per record, 36.8 MiB per batch:
|
|
|
575
583
|
|
|
576
584
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
577
585
|
|---|---:|---:|---:|---:|---:|---:|
|
|
578
|
-
| zrecord-zstd |
|
|
579
|
-
| zrecord-zstdict |
|
|
580
|
-
| zrecord-raw |
|
|
586
|
+
| zrecord-zstd | 6796 MiB/s | 10816 MiB/s | 75.3 | 4.09 ms | 24.4 MiB | 14.69x |
|
|
587
|
+
| zrecord-zstdict | 102 MiB/s | 11844 MiB/s | 82.5 | 3.61 ms | 16.2 MiB | 22.10x |
|
|
588
|
+
| zrecord-raw | 2801 MiB/s | 14655 MiB/s | 102.1 | 2.92 ms | 358.9 MiB | 1.00x |
|
|
581
589
|
| npy-mmap-raw | 2004 MiB/s | 4749 MiB/s | 33.1 | 11.08 ms | 358.9 MiB | 1.00x |
|
|
582
590
|
| hdf5-raw | 2350 MiB/s | 1836 MiB/s | 12.8 | 29.68 ms | 359.0 MiB | 1.00x |
|
|
583
591
|
| hdf5-gzip | 277 MiB/s | 657 MiB/s | 4.6 | 62.53 ms | 26.1 MiB | 13.73x |
|
|
@@ -591,8 +599,8 @@ Fixed-resolution vision records — 147 KiB per record, 36.8 MiB per batch:
|
|
|
591
599
|
| tiledb-raw | 743 MiB/s | 630 MiB/s | 4.4 | 66.01 ms | 359.0 MiB | 1.00x |
|
|
592
600
|
| tiledb-zstd | 1498 MiB/s | 1503 MiB/s | 10.5 | 27.82 ms | 26.9 MiB | 13.36x |
|
|
593
601
|
|
|
594
|
-
At 147 KiB per record, Zrecord-raw reaches 14.
|
|
595
|
-
plain zstd gathers at 10.
|
|
602
|
+
At 147 KiB per record, Zrecord-raw reaches 14.4 GiB/s and is 3.1x npy-mmap-raw;
|
|
603
|
+
plain zstd gathers at 10.6 GiB/s while reducing the corpus 14.69x. LMDB and Arrow
|
|
596
604
|
IPC are competitive raw record
|
|
597
605
|
stores, while codecs tied to whole IPC batches or Parquet row groups pay read
|
|
598
606
|
amplification on random gathers. Dense demonstrates that
|
|
@@ -610,9 +618,9 @@ list or a one-dimensional variable-length abstraction is not enough.
|
|
|
610
618
|
|
|
611
619
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
612
620
|
|---|---:|---:|---:|---:|---:|---:|
|
|
613
|
-
| zrecord-zstd |
|
|
614
|
-
| zrecord-zstdict |
|
|
615
|
-
| zrecord-raw |
|
|
621
|
+
| zrecord-zstd | 2339 MiB/s | 9507 MiB/s | 57.1 | 5.16 ms | 26.9 MiB | 15.49x |
|
|
622
|
+
| zrecord-zstdict | 102 MiB/s | 10336 MiB/s | 62.1 | 4.85 ms | 18.2 MiB | 22.98x |
|
|
623
|
+
| zrecord-raw | 1420 MiB/s | 12929 MiB/s | 77.6 | 3.86 ms | 417.2 MiB | 1.00x |
|
|
616
624
|
| hdf5-raw | 1451 MiB/s | 1059 MiB/s | 6.4 | 44.91 ms | 418.0 MiB | 1.00x |
|
|
617
625
|
| hdf5-gzip | 250 MiB/s | 126 MiB/s | 0.7 | 359.23 ms | 29.9 MiB | 13.94x |
|
|
618
626
|
| lmdb-raw | 1717 MiB/s | 7269 MiB/s | 43.6 | 7.93 ms | 422.2 MiB | 0.99x |
|
|
@@ -626,15 +634,15 @@ list or a one-dimensional variable-length abstraction is not enough.
|
|
|
626
634
|
| tiledb-zstd | 675 MiB/s | 138 MiB/s | 0.8 | 328.05 ms | 27.0 MiB | 15.46x |
|
|
627
635
|
|
|
628
636
|
Zrecord-raw is 1.8x LMDB and 3.2x Arrow IPC in logical gather. Zrecord-zstd
|
|
629
|
-
delivers 9.
|
|
637
|
+
delivers 9.3 GiB/s of
|
|
630
638
|
logical payload while reducing the corpus to 26.9 MiB. HDF5, Arrow IPC, Parquet,
|
|
631
639
|
ArrayRecord and TileDB
|
|
632
640
|
show the same framework/codec tradeoffs in both tables; compressed batch, chunk
|
|
633
641
|
and row-group formats pay read amplification on random records.
|
|
634
642
|
|
|
635
643
|
The shared generator makes compression ratios directly comparable across
|
|
636
|
-
contracts: Zrecord zstd is 14.69x Dense versus 15.
|
|
637
|
-
22.
|
|
644
|
+
contracts: Zrecord zstd is 14.69x Dense versus 15.49x Ragged, and zstdict is
|
|
645
|
+
22.10x versus 22.98x. The remaining difference comes from the H/W distribution
|
|
638
646
|
and Ragged shape metadata, not a different image entropy model.
|
|
639
647
|
|
|
640
648
|
### Small Token Records
|
|
@@ -654,19 +662,19 @@ accumulate at least two seconds.
|
|
|
654
662
|
|
|
655
663
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
656
664
|
|---|---:|---:|---:|---:|---:|---:|
|
|
657
|
-
| zrecord-zstd |
|
|
658
|
-
| zrecord-zstdict |
|
|
659
|
-
| zrecord-raw |
|
|
660
|
-
| npy-mmap-raw |
|
|
665
|
+
| zrecord-zstd | 1103 MiB/s | 2083 MiB/s | 1066.3 | 0.33 ms | 193.2 MiB | 2.02x |
|
|
666
|
+
| zrecord-zstdict | 70 MiB/s | 2160 MiB/s | 1105.7 | 0.32 ms | 154.4 MiB | 2.53x |
|
|
667
|
+
| zrecord-raw | 2178 MiB/s | 6862 MiB/s | 3513.2 | 0.10 ms | 393.7 MiB | 0.99x |
|
|
668
|
+
| npy-mmap-raw | 1587 MiB/s | 9070 MiB/s | 4644.0 | 0.08 ms | 390.6 MiB | 1.00x |
|
|
661
669
|
| lmdb-raw | 639 MiB/s | 1104 MiB/s | 565.0 | 0.71 ms | 786.3 MiB | 0.50x |
|
|
662
670
|
| arrow-ipc-raw | 2068 MiB/s | 225 MiB/s | 115.0 | 2.81 ms | 390.8 MiB | 1.00x |
|
|
663
671
|
| arrayrecord-raw | 807 MiB/s | 150 MiB/s | 76.7 | 4.99 ms | 401.4 MiB | 0.97x |
|
|
664
672
|
| arrayrecord-zstd | 127 MiB/s | 135 MiB/s | 69.4 | 4.45 ms | 201.3 MiB | 1.94x |
|
|
665
673
|
|
|
666
674
|
The contiguous NumPy baseline is strongest when the whole corpus is one fixed
|
|
667
|
-
typed matrix. Zrecord-raw reaches 3.
|
|
675
|
+
typed matrix. Zrecord-raw reaches 3.51 Mrecords/s while retaining independent
|
|
668
676
|
record semantics; the per-record zstd codecs halve disk and still return
|
|
669
|
-
1.
|
|
677
|
+
1.07–1.11 Mrecords/s. LMDB's B-tree/page overhead is visible in both throughput
|
|
670
678
|
and disk.
|
|
671
679
|
|
|
672
680
|
#### Variable Token Sequences
|
|
@@ -678,16 +686,16 @@ with exact `int32` values and original one-dimensional shapes.
|
|
|
678
686
|
|
|
679
687
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
680
688
|
|---|---:|---:|---:|---:|---:|---:|
|
|
681
|
-
| zrecord-zstd |
|
|
682
|
-
| zrecord-zstdict |
|
|
683
|
-
| zrecord-raw |
|
|
689
|
+
| zrecord-zstd | 271 MiB/s | 336 MiB/s | 634.2 | 0.54 ms | 67.4 MiB | 1.57x |
|
|
690
|
+
| zrecord-zstdict | 58 MiB/s | 364 MiB/s | 688.2 | 0.50 ms | 49.1 MiB | 2.16x |
|
|
691
|
+
| zrecord-raw | 546 MiB/s | 420 MiB/s | 794.2 | 0.44 ms | 110.5 MiB | 0.96x |
|
|
684
692
|
| lmdb-raw | 318 MiB/s | 113 MiB/s | 214.3 | 1.48 ms | 153.0 MiB | 0.69x |
|
|
685
693
|
| arrow-ipc-raw | 562 MiB/s | 44 MiB/s | 83.1 | 4.00 ms | 109.9 MiB | 0.96x |
|
|
686
694
|
| arrayrecord-raw | 288 MiB/s | 26 MiB/s | 49.3 | 7.39 ms | 118.8 MiB | 0.89x |
|
|
687
695
|
| arrayrecord-zstd | 62 MiB/s | 33 MiB/s | 61.5 | 5.58 ms | 76.2 MiB | 1.39x |
|
|
688
696
|
|
|
689
697
|
Here the record contract, not bulk byte bandwidth, is the useful scale.
|
|
690
|
-
Zrecord's three codecs return
|
|
698
|
+
Zrecord's three codecs return 634–794 krecords/s with 0.44–0.54 ms p95;
|
|
691
699
|
the dictionary gives the best disk ratio and is slightly ahead of plain zstd in
|
|
692
700
|
this pass.
|
|
693
701
|
|
|
@@ -734,8 +742,8 @@ and Grain reads ArrayRecord.
|
|
|
734
742
|
|
|
735
743
|
| loader | model | storage | batches/s | p95 | steady PSS | peak PSS | peak RSS |
|
|
736
744
|
|---|---|---|---:|---:|---:|---:|---:|
|
|
737
|
-
| **loaderx** | threads | zrecord-zstd |
|
|
738
|
-
| loaderx-raw | threads | zrecord-raw |
|
|
745
|
+
| **loaderx** | threads | zrecord-zstd | 166.3 | 15.95 ms | 985 MiB | 986 MiB | 988 MiB |
|
|
746
|
+
| loaderx-raw | threads | zrecord-raw | 208.4 | 13.56 ms | 987 MiB | 987 MiB | 989 MiB |
|
|
739
747
|
| torch | fork | npy-mmap-raw | 109.4 | 32.70 ms | 1783 MiB | 1889 MiB | 6396 MiB |
|
|
740
748
|
| torch-spawn | spawn | npy-mmap-raw | 111.6 | 30.69 ms | 2835 MiB | 2913 MiB | 4880 MiB |
|
|
741
749
|
| grain | processes | arrayrecord-zstd | 46.6 | 93.48 ms | 1847 MiB | 1946 MiB | 2065 MiB |
|
|
@@ -745,8 +753,8 @@ the transform threads overlap Python-side collation with the next gather. The
|
|
|
745
753
|
memory is the source, Zrecord container and bounded in-flight batches. loaderx prefetches in
|
|
746
754
|
threads inside one process, so workers share one interpreter, one NumPy runtime
|
|
747
755
|
and one set of gather buffers. With source geometry and entropy held constant,
|
|
748
|
-
raw is 1.
|
|
749
|
-
Torch spawn and 3.
|
|
756
|
+
raw is 1.25x compressed loaderx; compressed loaderx is 1.52x Torch fork, 1.49x
|
|
757
|
+
Torch spawn and 3.57x Grain, while raw is 1.90x, 1.87x and 4.47x faster.
|
|
750
758
|
Torch's aggregate RSS is high because
|
|
751
759
|
Linux fork mappings are counted repeatedly; it is not a total-memory ratio
|
|
752
760
|
against Zrecord's unaccounted page cache. The explicit `torch-spawn` row removes
|
|
@@ -815,13 +823,13 @@ specialization for compression and a complete variable-shape persistence model.
|
|
|
815
823
|
copy are one path, so turning compression on costs part of a margin, not an
|
|
816
824
|
order of magnitude. The ratio is the data, not the
|
|
817
825
|
format: in the current Dense structured-vision workload, plain zstd reaches
|
|
818
|
-
14.69x and the balanced dictionary reaches 22.
|
|
826
|
+
14.69x and the balanced dictionary reaches 22.10x.
|
|
819
827
|
|
|
820
828
|
**Loader results combine architecture and storage.** loaderx uses threads and
|
|
821
829
|
never ends an epoch, so a step pays no IPC and never waits on an epoch boundary;
|
|
822
830
|
torch uses finite shuffled epochs, worker processes and shared-memory handoff.
|
|
823
|
-
Here compressed loaderx is 1.
|
|
824
|
-
raw loaderx is 1.
|
|
831
|
+
Here compressed loaderx is 1.52x Torch fork, 1.49x Torch spawn and 3.57x Grain;
|
|
832
|
+
raw loaderx is 1.90x, 1.87x and 4.47x faster, respectively.
|
|
825
833
|
Storage also differs per loader — each reads from what it was
|
|
826
834
|
built for — so the loader table is a different comparison from either store
|
|
827
835
|
table, not a rerun.
|
|
@@ -1059,7 +1067,7 @@ defend against bypassing the public Python API do not belong in zrecord.
|
|
|
1059
1067
|
byte geometry. Dense stores persist
|
|
1060
1068
|
one fixed-width physical record per logical record. Ragged stores
|
|
1061
1069
|
persist one variable-width physical record per logical record:
|
|
1062
|
-
`[u64le
|
|
1070
|
+
`[u64le dim] * schema.ndim + [payload]`. Shape and payload therefore share one
|
|
1063
1071
|
location, codec frame, and append publication.
|
|
1064
1072
|
4. The IO model (`append | read`) is batch-oriented and
|
|
1065
1073
|
shape-agnostic. Per-call adapters expose record boundaries through a compile-time
|
|
@@ -1076,7 +1084,7 @@ defend against bypassing the public Python API do not belong in zrecord.
|
|
|
1076
1084
|
|-------|-----------|----------------------------------------|
|
|
1077
1085
|
| 0 | raw | none |
|
|
1078
1086
|
| 1 | zstd | zstd (plain, level 3) |
|
|
1079
|
-
| 2 | zstdict | zstd with a trained dictionary (level
|
|
1087
|
+
| 2 | zstdict | zstd with a trained dictionary (level 15) |
|
|
1080
1088
|
```
|
|
1081
1089
|
|
|
1082
1090
|
7. Compression is transparent to the client:
|
|
@@ -1096,19 +1104,21 @@ defend against bypassing the public Python API do not belong in zrecord.
|
|
|
1096
1104
|
dictionary carries the structure shared across records, which per-record
|
|
1097
1105
|
compression cannot see. On many small, similar records (image tiles, token
|
|
1098
1106
|
sequences) this is a large win: the current Dense structured-vision set
|
|
1099
|
-
is 14.69x with plain zstd and 22.
|
|
1107
|
+
is 14.69x with plain zstd and 22.10x with the balanced dictionary. The dictionary is loaded once on
|
|
1100
1108
|
open and shared, lock-free, across all reader threads. The dictionary size
|
|
1101
1109
|
is chosen from the `DICT_TIERS` presets (see Codec notes).
|
|
1102
1110
|
* A `zstd_dict` store needs its dictionary to read every record; a `raw` or
|
|
1103
1111
|
`zstd` store rejects an unexpected dictionary as malformed state.
|
|
1104
1112
|
|
|
1105
1113
|
## Persistence format
|
|
1106
|
-
The
|
|
1107
|
-
the
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1114
|
+
The current format is the settled internal baseline for implementation work: optimizations keep
|
|
1115
|
+
the fixed files, Header/schema page, contiguous ``RecordLoc`` table and independent
|
|
1116
|
+
record payloads unless the product boundary is deliberately reopened. "Settled"
|
|
1117
|
+
does not promise cross-version persistence compatibility: there is no compatibility
|
|
1118
|
+
layer, migration, version dispatch, checksum, or recovery facility. Zrecord is not
|
|
1119
|
+
the authority for irreplaceable data. Keep authoritative source data and reproducible
|
|
1120
|
+
build scripts; after an interrupted build, storage failure, incompatible implementation
|
|
1121
|
+
change, or content change, rebuild a complete container at a new path.
|
|
1112
1122
|
|
|
1113
1123
|
Native storage uses a fixed file set:
|
|
1114
1124
|
```
|
|
@@ -1128,8 +1138,8 @@ pread/pwrite at `4096 + i * 16`; there is no variable table base, segment
|
|
|
1128
1138
|
mapping, or rollover fd table.
|
|
1129
1139
|
|
|
1130
1140
|
**1. Python schema** — bytes `32..32+schema_length` are exactly one immutable
|
|
1131
|
-
MsgPack object.
|
|
1132
|
-
|
|
1141
|
+
MsgPack object. Both stores contain ``dtype`` and ``ndim``; Dense additionally
|
|
1142
|
+
contains ``item_shape``. Native create persists these bytes together with
|
|
1133
1143
|
the physical container but does not decode them. Open acquires the native lifetime
|
|
1134
1144
|
lock before copying the schema to Python for validation, so schema and physical
|
|
1135
1145
|
metadata are one locked snapshot. Dense record width is derived once from
|
|
@@ -1192,8 +1202,8 @@ while `Ragged` supplies offsets for its shape-prefixed records.
|
|
|
1192
1202
|
The private ABI turns those inputs into compile-time record sources and
|
|
1193
1203
|
destinations; the engine has one append and one gather operation. Its shared
|
|
1194
1204
|
opaque handle remains private and carries no typed-store geometry.
|
|
1195
|
-
* Compressed append: workers claim
|
|
1196
|
-
|
|
1205
|
+
* Compressed append: workers claim record chunks and lease process-bounded
|
|
1206
|
+
``ExecutionSlot`` scratch, reserve physical offsets in completion order through a short frontier
|
|
1197
1207
|
lock, and issue positional payload writes in parallel. Logical IDs remain in
|
|
1198
1208
|
the loc table, so physical completion order does not change random gather.
|
|
1199
1209
|
The caller waits for every payload before publishing the new locations and
|