loaderx 0.2.0__tar.gz → 0.3.0__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loaderx
3
- Version: 0.2.0
4
- Summary: Minimal data loader for Flax
3
+ Version: 0.3.0
4
+ Summary: A record-based data runtime, focused on delivering extreme throughput and low latency
5
5
  Author-email: Ben0i0d <ben0i0d@foxmail.com>
6
6
  License: MIT License
7
7
 
@@ -25,16 +25,16 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Project-URL: Homepage, https://github.com/eoeair/loaderx
29
- Project-URL: Documentation, https://github.com/eoeair/loaderx
30
- Project-URL: Source, https://github.com/eoeair/loaderx
31
- Project-URL: Bug Tracker, https://github.com/eoeair/loaderx/issues
28
+ Project-URL: Homepage, https://codeberg.org/eoelab/loaderx
29
+ Project-URL: Documentation, https://codeberg.org/eoelab/loaderx
30
+ Project-URL: Source, https://codeberg.org/eoelab/loaderx
31
+ Project-URL: Bug Tracker, https://codeberg.org/eoelab/loaderx
32
32
  Keywords: flax,python,dataloader
33
33
  Classifier: Development Status :: 3 - Alpha
34
34
  Classifier: Intended Audience :: Developers
35
35
  Classifier: License :: OSI Approved :: MIT License
36
36
  Classifier: Programming Language :: Python :: 3
37
- Requires-Python: >=3.10
37
+ Requires-Python: ==3.13
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: numpy
@@ -45,19 +45,16 @@ Dynamic: license-file
45
45
  # loaderx
46
46
  A record-based data runtime, focused on delivering extreme throughput and low latency
47
47
 
48
- **Important Reminder**: Only support Linux_amd64. This is because we use a dynamic library built with Zig (we build it on Alpine and test it on Debian).
48
+ **Only Python3.13_Linux_amd64**
49
49
 
50
50
  ## Sampler
51
51
  a high-performance sampler implemented in Zig
52
- ### Build
53
- zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/libsampler.so
54
52
  ### Index Generator
55
53
  1. Sequential generation: indices are produced by traversing the index space in order.
56
54
  * Sliding traversal: indices are obtained using a fixed-size sliding window. Note that in this case, the index space is treated as a circular queue to avoid truncation at the tail.
57
55
  2. Random generation: indices are sampled randomly from the index space.
58
56
  * Global random: a set of samples is drawn randomly from the entire index space.
59
57
 
60
-
61
58
  ## zrecord
62
59
  基于Zig实现一个并发友好、实现更简单、单机性能更优的 record 存储系统
63
60
 
@@ -85,27 +82,24 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
85
82
  | flate | Deflate |
86
83
  ```
87
84
  3. 通过offset支持高效随机访问
88
- * offset表项是一个三元组 [[chunk_id : u16, offset : u40, physical_length : u24], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
89
- * chunk_id为u16,表示具体chunk
90
- * offset为u40, 表示具体chunk内的偏移
91
- * physical_length为u24,表示record数据大小
92
- * 为了字节对齐和性能,offset项建议选择 8 的整数倍字节宽
85
+ * offset表项是一个三元组 [[chunk_id : u32, offset : u64, physical_length : u32], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
86
+ * chunk_id为u32,表示具体chunk
87
+ * offset为u64, 表示具体chunk内的偏移
88
+ * physical_length为u32,表示record数据大小
89
+ * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
90
+ * offset表是mmap访问模式,由于等长,直接将 idx 转换为 ptr + 16*idx
93
91
  * 不同位宽表示的数值范围
94
92
  ```
95
93
  u16 → 2^16 - 1 (65535)
96
- u24 → 2^24 - 1 (16777215)
97
94
  u32 → 2^32 - 1 (4.29e9)
98
95
  u64 → 2^64 - 1 (1.84e19)
99
96
  ```
100
97
  * 不同位宽表示的数据大小
101
98
  ```
102
99
  u16 → 64 KiB
103
- u24 → 16 MiB
104
100
  u32 → 4 GiB
105
- u40 → 1 TiB
106
101
  u64 → 16 EiB
107
102
  ```
108
- * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
109
103
  ```
110
104
  outside idx (global)
111
105
 
@@ -118,10 +112,9 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
118
112
  * meta.json:描述全局元数据,包括全局参数与Field参数
119
113
  ```
120
114
  {
121
- "version": 1,
122
115
  "length": 65536,
123
- "chunks": {"num": 12, "size": 8192, "unfull": [8, 9, 10, 11]},
124
- "fields": [{"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"}]
116
+ "chunks": { "num": 12, "size": 8192, "unfull": {[8, write_pos, record_count], ... } },
117
+ "fields": [ {"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"} ]
125
118
  }
126
119
  ```
127
120
  * Field_offset.zr:索引表
@@ -137,8 +130,8 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
137
130
  ```
138
131
  dataset
139
132
  ├── meta.json
140
- ├── A_offset.zr
141
- ├── B_offset.zr
133
+ ├── A.offset
134
+ ├── B.offset
142
135
  ├── chunk
143
136
  │ ├── 0.zr
144
137
  └──└── 1.zr
@@ -168,6 +161,9 @@ dataset
168
161
  1. mmap:维护全局offset、chunk文件的handle
169
162
  * 初始化阶段注册,关闭前释放
170
163
  * 对于full的chunk,使用mmap+close(只读),对于unfull的chunk使用mmap(读写)。一旦写满(writer提交),新建chunk并返回给writer,原mmap close。(避免过多占用fd)
164
+ * 对于chunk/offset文件,动态完成分块增长,基于ftruncate+mremap实现
165
+ * 扩容offset表,一次增长 1M项(16MiB)
166
+ * 扩容chunk文件,一次增长 1GiB
171
167
  2. batch:维护每次返回batch的handle,也就是[ptr, logical_length]中ptr的合规性
172
168
  * 工作线程完成后注册
173
169
  * 必须调用函数来手动释放,释放时batch压入GC队列
@@ -175,9 +171,12 @@ dataset
175
171
  #### 在线任务
176
172
 
177
173
  1. 写任务:写入只允许chunk-level append-only,其余操作基于offset重定向,缺失的field构造为全为0的项(physical_length为0代表无)
178
- 1. 追加:追加一个数据,record追加到chunk末尾,offset表追加条目,全局长度增加,如果超过chunk_size,则新建chunk
174
+ 1. 追加:在chunk当前写入位置追加一个recordoffset表更新length+1条目,全局长度增加
175
+ * 写满:写入完成时检查,如果record_count达到chunk_size, 新建chunk
176
+ * 写入越界:捕捉错误,并对chunk扩容
177
+ * offset[ length+1 ]不存在:捕捉错误,并对offset扩容
179
178
  2. 修改:追加一个record,并修改offset项
180
- 3. 删除:删除offset项(将目标项替换为最后一项),全局长度减小(越界访问属于UB行为)
179
+ 3. 删除:删除offset项(将目标项替换为最后一项),length减一(越界访问属于UB行为)
181
180
 
182
181
  2. 读任务:用一个indices(索引数组),从原数组里访问指定位置的元素(gather)
183
182
  1. 对于无压缩的数据,将直接返回文件内地址实现zero-copy
@@ -1,19 +1,16 @@
1
1
  # loaderx
2
2
  A record-based data runtime, focused on delivering extreme throughput and low latency
3
3
 
4
- **Important Reminder**: Only support Linux_amd64. This is because we use a dynamic library built with Zig (we build it on Alpine and test it on Debian).
4
+ **Only Python3.13_Linux_amd64**
5
5
 
6
6
  ## Sampler
7
7
  a high-performance sampler implemented in Zig
8
- ### Build
9
- zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/libsampler.so
10
8
  ### Index Generator
11
9
  1. Sequential generation: indices are produced by traversing the index space in order.
12
10
  * Sliding traversal: indices are obtained using a fixed-size sliding window. Note that in this case, the index space is treated as a circular queue to avoid truncation at the tail.
13
11
  2. Random generation: indices are sampled randomly from the index space.
14
12
  * Global random: a set of samples is drawn randomly from the entire index space.
15
13
 
16
-
17
14
  ## zrecord
18
15
  基于Zig实现一个并发友好、实现更简单、单机性能更优的 record 存储系统
19
16
 
@@ -41,27 +38,24 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
41
38
  | flate | Deflate |
42
39
  ```
43
40
  3. 通过offset支持高效随机访问
44
- * offset表项是一个三元组 [[chunk_id : u16, offset : u40, physical_length : u24], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
45
- * chunk_id为u16,表示具体chunk
46
- * offset为u40, 表示具体chunk内的偏移
47
- * physical_length为u24,表示record数据大小
48
- * 为了字节对齐和性能,offset项建议选择 8 的整数倍字节宽
41
+ * offset表项是一个三元组 [[chunk_id : u32, offset : u64, physical_length : u32], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
42
+ * chunk_id为u32,表示具体chunk
43
+ * offset为u64, 表示具体chunk内的偏移
44
+ * physical_length为u32,表示record数据大小
45
+ * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
46
+ * offset表是mmap访问模式,由于等长,直接将 idx 转换为 ptr + 16*idx
49
47
  * 不同位宽表示的数值范围
50
48
  ```
51
49
  u16 → 2^16 - 1 (65535)
52
- u24 → 2^24 - 1 (16777215)
53
50
  u32 → 2^32 - 1 (4.29e9)
54
51
  u64 → 2^64 - 1 (1.84e19)
55
52
  ```
56
53
  * 不同位宽表示的数据大小
57
54
  ```
58
55
  u16 → 64 KiB
59
- u24 → 16 MiB
60
56
  u32 → 4 GiB
61
- u40 → 1 TiB
62
57
  u64 → 16 EiB
63
58
  ```
64
- * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
65
59
  ```
66
60
  outside idx (global)
67
61
 
@@ -74,10 +68,9 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
74
68
  * meta.json:描述全局元数据,包括全局参数与Field参数
75
69
  ```
76
70
  {
77
- "version": 1,
78
71
  "length": 65536,
79
- "chunks": {"num": 12, "size": 8192, "unfull": [8, 9, 10, 11]},
80
- "fields": [{"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"}]
72
+ "chunks": { "num": 12, "size": 8192, "unfull": {[8, write_pos, record_count], ... } },
73
+ "fields": [ {"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"} ]
81
74
  }
82
75
  ```
83
76
  * Field_offset.zr:索引表
@@ -93,8 +86,8 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
93
86
  ```
94
87
  dataset
95
88
  ├── meta.json
96
- ├── A_offset.zr
97
- ├── B_offset.zr
89
+ ├── A.offset
90
+ ├── B.offset
98
91
  ├── chunk
99
92
  │ ├── 0.zr
100
93
  └──└── 1.zr
@@ -124,6 +117,9 @@ dataset
124
117
  1. mmap:维护全局offset、chunk文件的handle
125
118
  * 初始化阶段注册,关闭前释放
126
119
  * 对于full的chunk,使用mmap+close(只读),对于unfull的chunk使用mmap(读写)。一旦写满(writer提交),新建chunk并返回给writer,原mmap close。(避免过多占用fd)
120
+ * 对于chunk/offset文件,动态完成分块增长,基于ftruncate+mremap实现
121
+ * 扩容offset表,一次增长 1M项(16MiB)
122
+ * 扩容chunk文件,一次增长 1GiB
127
123
  2. batch:维护每次返回batch的handle,也就是[ptr, logical_length]中ptr的合规性
128
124
  * 工作线程完成后注册
129
125
  * 必须调用函数来手动释放,释放时batch压入GC队列
@@ -131,9 +127,12 @@ dataset
131
127
  #### 在线任务
132
128
 
133
129
  1. 写任务:写入只允许chunk-level append-only,其余操作基于offset重定向,缺失的field构造为全为0的项(physical_length为0代表无)
134
- 1. 追加:追加一个数据,record追加到chunk末尾,offset表追加条目,全局长度增加,如果超过chunk_size,则新建chunk
130
+ 1. 追加:在chunk当前写入位置追加一个recordoffset表更新length+1条目,全局长度增加
131
+ * 写满:写入完成时检查,如果record_count达到chunk_size, 新建chunk
132
+ * 写入越界:捕捉错误,并对chunk扩容
133
+ * offset[ length+1 ]不存在:捕捉错误,并对offset扩容
135
134
  2. 修改:追加一个record,并修改offset项
136
- 3. 删除:删除offset项(将目标项替换为最后一项),全局长度减小(越界访问属于UB行为)
135
+ 3. 删除:删除offset项(将目标项替换为最后一项),length减一(越界访问属于UB行为)
137
136
 
138
137
  2. 读任务:用一个indices(索引数组),从原数组里访问指定位置的元素(gather)
139
138
  1. 对于无压缩的数据,将直接返回文件内地址实现zero-copy
@@ -2,4 +2,4 @@ from .dataset import BaseDataset, NPDataset, ARDataset
2
2
  from .dataloader import DataLoader
3
3
  from .utils import converter
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.3.0"
@@ -0,0 +1,69 @@
1
+ import os
2
+ import time
3
+ import ctypes
4
+ import numpy as np
5
+ from typing import Union
6
+
7
+ from lib import libsampler
8
+
9
+ class Sampler:
10
+ class Mode:
11
+ SEQUENTIAL = 0
12
+ IID = 1
13
+
14
+ def __init__(self, length: int, batch_size: int, mode: int, seed: int = 42):
15
+ # sampler
16
+ self.sampler = libsampler.Sampler.init(length, batch_size, mode, seed)
17
+ # indices
18
+ self.indices = np.zeros(batch_size, dtype=np.uint64)
19
+
20
+ # step
21
+ def next(self):
22
+ self.sampler.next(self.indices)
23
+
24
+ # iterator
25
+ def __iter__(self):
26
+ return self
27
+ def __next__(self):
28
+ self.next()
29
+ return self.indices
30
+
31
+ def run():
32
+ from itertools import islice
33
+ length = 10
34
+ batch_size = 4
35
+ n_steps = 5
36
+
37
+ print("=== Sampler SEQUENTIAL ===")
38
+ sampler = Sampler(length, batch_size, Sampler.Mode.SEQUENTIAL)
39
+ for indices in islice(sampler, n_steps):
40
+ print(indices)
41
+
42
+ print("=== Sampler IID ===")
43
+ sampler = Sampler(length, batch_size, Sampler.Mode.IID)
44
+ for indices in islice(sampler, n_steps):
45
+ print(indices)
46
+
47
+ # Benchmark, zig-sampler speeder 2.2x
48
+ def bench():
49
+ length = 1_000_000
50
+ batch_size = 8192
51
+ n_steps = 10_000
52
+
53
+ print("=== NumPy IID ===")
54
+ t0 = time.time()
55
+ batch = np.zeros(batch_size, dtype=np.uint64)
56
+ for _ in range(n_steps):
57
+ batch[:] = np.random.randint(0, length, size=batch_size, dtype=np.uint64)
58
+ print(f"{(time.time() - t0)*1000:.2f} ms")
59
+
60
+ print("=== Sampler IID ===")
61
+ t0 = time.time()
62
+ sampler = Sampler(length, batch_size, Sampler.Mode.IID)
63
+ for _ in range(n_steps):
64
+ sampler.next()
65
+ print(f"{(time.time() - t0)*1000:.2f} ms")
66
+
67
+ if __name__ == "__main__":
68
+ run()
69
+ bench()
@@ -26,12 +26,10 @@ class DataLoader:
26
26
  if len(dataset) != len(labelset):
27
27
  raise ValueError("dataset and labelset must have the same length")
28
28
 
29
- self.rng = np.random.default_rng(seed)
30
-
31
29
  self.indices = Queue(maxsize=prefetch_size)
32
30
  self.batches = Queue(maxsize=prefetch_size)
33
31
 
34
- self.sampler = Sampler(len(dataset), batch_size, mode)
32
+ self.sampler = Sampler(len(dataset), batch_size, mode, seed)
35
33
 
36
34
  self.stop_signal = threading.Event()
37
35
 
@@ -49,8 +47,8 @@ class DataLoader:
49
47
  Sample indices from the dataset and put them into the index queue.
50
48
  """
51
49
  while not self.stop_signal.is_set():
52
- self.sampler.next()
53
- self.indices.put(self.sampler.batch_indices.copy())
50
+ self.sampler.next()
51
+ self.indices.put(self.sampler.indices.copy())
54
52
 
55
53
  def _prefetch(self, transform):
56
54
  """
@@ -73,7 +71,7 @@ class DataLoader:
73
71
  return self
74
72
  def __next__(self):
75
73
  # debug: monitor bottlenecks
76
- # print(self.indices.qsize(), self.rawes.qsize(), self.batches.qsize())
74
+ # print(self.indices.qsize(), self.batches.qsize())
77
75
  return self.batches.get()
78
76
 
79
77
  # statement
Binary file
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loaderx
3
- Version: 0.2.0
4
- Summary: Minimal data loader for Flax
3
+ Version: 0.3.0
4
+ Summary: A record-based data runtime, focused on delivering extreme throughput and low latency
5
5
  Author-email: Ben0i0d <ben0i0d@foxmail.com>
6
6
  License: MIT License
7
7
 
@@ -25,16 +25,16 @@ License: MIT License
25
25
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
26
  SOFTWARE.
27
27
 
28
- Project-URL: Homepage, https://github.com/eoeair/loaderx
29
- Project-URL: Documentation, https://github.com/eoeair/loaderx
30
- Project-URL: Source, https://github.com/eoeair/loaderx
31
- Project-URL: Bug Tracker, https://github.com/eoeair/loaderx/issues
28
+ Project-URL: Homepage, https://codeberg.org/eoelab/loaderx
29
+ Project-URL: Documentation, https://codeberg.org/eoelab/loaderx
30
+ Project-URL: Source, https://codeberg.org/eoelab/loaderx
31
+ Project-URL: Bug Tracker, https://codeberg.org/eoelab/loaderx
32
32
  Keywords: flax,python,dataloader
33
33
  Classifier: Development Status :: 3 - Alpha
34
34
  Classifier: Intended Audience :: Developers
35
35
  Classifier: License :: OSI Approved :: MIT License
36
36
  Classifier: Programming Language :: Python :: 3
37
- Requires-Python: >=3.10
37
+ Requires-Python: ==3.13
38
38
  Description-Content-Type: text/markdown
39
39
  License-File: LICENSE
40
40
  Requires-Dist: numpy
@@ -45,19 +45,16 @@ Dynamic: license-file
45
45
  # loaderx
46
46
  A record-based data runtime, focused on delivering extreme throughput and low latency
47
47
 
48
- **Important Reminder**: Only support Linux_amd64. This is because we use a dynamic library built with Zig (we build it on Alpine and test it on Debian).
48
+ **Only Python3.13_Linux_amd64**
49
49
 
50
50
  ## Sampler
51
51
  a high-performance sampler implemented in Zig
52
- ### Build
53
- zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/libsampler.so
54
52
  ### Index Generator
55
53
  1. Sequential generation: indices are produced by traversing the index space in order.
56
54
  * Sliding traversal: indices are obtained using a fixed-size sliding window. Note that in this case, the index space is treated as a circular queue to avoid truncation at the tail.
57
55
  2. Random generation: indices are sampled randomly from the index space.
58
56
  * Global random: a set of samples is drawn randomly from the entire index space.
59
57
 
60
-
61
58
  ## zrecord
62
59
  基于Zig实现一个并发友好、实现更简单、单机性能更优的 record 存储系统
63
60
 
@@ -85,27 +82,24 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
85
82
  | flate | Deflate |
86
83
  ```
87
84
  3. 通过offset支持高效随机访问
88
- * offset表项是一个三元组 [[chunk_id : u16, offset : u40, physical_length : u24], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
89
- * chunk_id为u16,表示具体chunk
90
- * offset为u40, 表示具体chunk内的偏移
91
- * physical_length为u24,表示record数据大小
92
- * 为了字节对齐和性能,offset项建议选择 8 的整数倍字节宽
85
+ * offset表项是一个三元组 [[chunk_id : u32, offset : u64, physical_length : u32], ...],长度为N。将外部索引空间(outside[0, N])指向一个实际存储地址
86
+ * chunk_id为u32,表示具体chunk
87
+ * offset为u64, 表示具体chunk内的偏移
88
+ * physical_length为u32,表示record数据大小
89
+ * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
90
+ * offset表是mmap访问模式,由于等长,直接将 idx 转换为 ptr + 16*idx
93
91
  * 不同位宽表示的数值范围
94
92
  ```
95
93
  u16 → 2^16 - 1 (65535)
96
- u24 → 2^24 - 1 (16777215)
97
94
  u32 → 2^32 - 1 (4.29e9)
98
95
  u64 → 2^64 - 1 (1.84e19)
99
96
  ```
100
97
  * 不同位宽表示的数据大小
101
98
  ```
102
99
  u16 → 64 KiB
103
- u24 → 16 MiB
104
100
  u32 → 4 GiB
105
- u40 → 1 TiB
106
101
  u64 → 16 EiB
107
102
  ```
108
- * offset表每项的字节大小最好为 8 的整数倍,提升对齐和缓存效率
109
103
  ```
110
104
  outside idx (global)
111
105
 
@@ -118,10 +112,9 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
118
112
  * meta.json:描述全局元数据,包括全局参数与Field参数
119
113
  ```
120
114
  {
121
- "version": 1,
122
115
  "length": 65536,
123
- "chunks": {"num": 12, "size": 8192, "unfull": [8, 9, 10, 11]},
124
- "fields": [{"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"}]
116
+ "chunks": { "num": 12, "size": 8192, "unfull": {[8, write_pos, record_count], ... } },
117
+ "fields": [ {"name": "A", "dtype": "f32", "compress": "flate"}, {"name": "B", "dtype": "i32", "compress": "raw"} ]
125
118
  }
126
119
  ```
127
120
  * Field_offset.zr:索引表
@@ -137,8 +130,8 @@ zig build-lib -dynamic -OReleaseFast sampler/root.zig -femit-bin=loaderx/lib/lib
137
130
  ```
138
131
  dataset
139
132
  ├── meta.json
140
- ├── A_offset.zr
141
- ├── B_offset.zr
133
+ ├── A.offset
134
+ ├── B.offset
142
135
  ├── chunk
143
136
  │ ├── 0.zr
144
137
  └──└── 1.zr
@@ -168,6 +161,9 @@ dataset
168
161
  1. mmap:维护全局offset、chunk文件的handle
169
162
  * 初始化阶段注册,关闭前释放
170
163
  * 对于full的chunk,使用mmap+close(只读),对于unfull的chunk使用mmap(读写)。一旦写满(writer提交),新建chunk并返回给writer,原mmap close。(避免过多占用fd)
164
+ * 对于chunk/offset文件,动态完成分块增长,基于ftruncate+mremap实现
165
+ * 扩容offset表,一次增长 1M项(16MiB)
166
+ * 扩容chunk文件,一次增长 1GiB
171
167
  2. batch:维护每次返回batch的handle,也就是[ptr, logical_length]中ptr的合规性
172
168
  * 工作线程完成后注册
173
169
  * 必须调用函数来手动释放,释放时batch压入GC队列
@@ -175,9 +171,12 @@ dataset
175
171
  #### 在线任务
176
172
 
177
173
  1. 写任务:写入只允许chunk-level append-only,其余操作基于offset重定向,缺失的field构造为全为0的项(physical_length为0代表无)
178
- 1. 追加:追加一个数据,record追加到chunk末尾,offset表追加条目,全局长度增加,如果超过chunk_size,则新建chunk
174
+ 1. 追加:在chunk当前写入位置追加一个recordoffset表更新length+1条目,全局长度增加
175
+ * 写满:写入完成时检查,如果record_count达到chunk_size, 新建chunk
176
+ * 写入越界:捕捉错误,并对chunk扩容
177
+ * offset[ length+1 ]不存在:捕捉错误,并对offset扩容
179
178
  2. 修改:追加一个record,并修改offset项
180
- 3. 删除:删除offset项(将目标项替换为最后一项),全局长度减小(越界访问属于UB行为)
179
+ 3. 删除:删除offset项(将目标项替换为最后一项),length减一(越界访问属于UB行为)
181
180
 
182
181
  2. 读任务:用一个indices(索引数组),从原数组里访问指定位置的元素(gather)
183
182
  1. 对于无压缩的数据,将直接返回文件内地址实现zero-copy
@@ -5,9 +5,9 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "loaderx"
7
7
  dynamic = ["version"]
8
- description = "Minimal data loader for Flax"
8
+ description = "A record-based data runtime, focused on delivering extreme throughput and low latency"
9
9
  readme = "README.md"
10
- requires-python = ">=3.10"
10
+ requires-python = "==3.13"
11
11
  authors = [
12
12
  {name = "Ben0i0d", email = "ben0i0d@foxmail.com"},
13
13
  ]
@@ -31,7 +31,7 @@ packages = ["loaderx"]
31
31
  version = {attr = "loaderx.__version__"}
32
32
 
33
33
  [project.urls]
34
- "Homepage" = "https://github.com/eoeair/loaderx"
35
- "Documentation" = "https://github.com/eoeair/loaderx"
36
- "Source" = "https://github.com/eoeair/loaderx"
37
- "Bug Tracker" = "https://github.com/eoeair/loaderx/issues"
34
+ "Homepage" = "https://codeberg.org/eoelab/loaderx"
35
+ "Documentation" = "https://codeberg.org/eoelab/loaderx"
36
+ "Source" = "https://codeberg.org/eoelab/loaderx"
37
+ "Bug Tracker" = "https://codeberg.org/eoelab/loaderx"
@@ -1,99 +0,0 @@
1
- import os
2
- import time
3
- import ctypes
4
- import numpy as np
5
- from typing import Union
6
-
7
- class Sampler:
8
- class Mode:
9
- SEQUENTIAL = 0
10
- IID = 1
11
- # lazy import
12
- _lib = None
13
- @classmethod
14
- def _load_lib(cls):
15
- if cls._lib is None:
16
- # linux/windows/macos
17
- names = ['lib/libsampler.so', 'lib/sampler.dll', 'lib/libsampler.dylib']
18
- lib_paths = [os.path.join(os.path.dirname(__file__), name) for name in names]
19
-
20
- for path in lib_paths:
21
- if os.path.exists(path):
22
- cls._lib = ctypes.CDLL(path)
23
- cls._setup_function_signatures()
24
- return cls._lib
25
-
26
- @classmethod
27
- def _setup_function_signatures(cls):
28
- lib = cls._lib
29
-
30
- # init
31
- lib.init.argtypes = [
32
- ctypes.c_uint64, # length
33
- ctypes.c_uint32, # batch_size
34
- ctypes.c_uint8, # mode
35
- ctypes.c_uint64 # seed
36
- ]
37
- lib.init.restype = ctypes.c_void_p
38
-
39
- # deinit
40
- lib.deinit.argtypes = [ctypes.c_void_p] # sampler
41
- lib.deinit.restype = None
42
-
43
- # next
44
- lib.next.argtypes = [
45
- ctypes.c_void_p, # sampler
46
- ctypes.POINTER(ctypes.c_uint64) # batch_indices
47
- ]
48
- lib.next.restype = None
49
-
50
- def __init__(self, length: int, batch_size: int, mode: int, seed: int = 42):
51
- # import lib
52
- self.lib = self._load_lib()
53
-
54
- # init sampler
55
- self.ptr = self.lib.init(length, batch_size, mode, seed)
56
- if not self.ptr:
57
- raise RuntimeError(f"Failed to create sampler with params: length={length}, batch_size={batch_size}, mode={mode}, seed={seed}")
58
-
59
- # batch_indices
60
- self.batch_indices = np.zeros(batch_size, dtype=np.uint64)
61
-
62
- # step
63
- def next(self):
64
- self.lib.next(self.ptr, self.batch_indices.ctypes.data_as(ctypes.POINTER(ctypes.c_uint64)))
65
-
66
- # iterator
67
- def __iter__(self):
68
- return self
69
- def __next__(self):
70
- self.next()
71
- return self.batch_indices
72
-
73
- # close
74
- def deinit(self):
75
- self.lib.deinit(self.ptr)
76
- def __del__(self):
77
- if hasattr(self, 'ptr') and self.ptr:
78
- self.deinit()
79
-
80
- # Benchmark, zig-sampler is better
81
- if __name__ == "__main__":
82
- length = 1_000_000
83
- batch_size = 8192
84
- n_steps = 10_000
85
-
86
- print("=== Python NumPy IID ===")
87
- t0 = time.time()
88
- batch = np.zeros(batch_size, dtype=np.uint64)
89
- for _ in range(n_steps):
90
- batch[:] = np.random.randint(0, length, size=batch_size, dtype=np.uint64)
91
- print(f"{(time.time() - t0)*1000:.2f} ms")
92
-
93
- print("=== Sampler IID ===")
94
- t0 = time.time()
95
- s = Sampler(length, batch_size, Sampler.Mode.IID)
96
- for _ in range(n_steps):
97
- s.next()
98
- print(f"{(time.time() - t0)*1000:.2f} ms")
99
- s.deinit()
Binary file
File without changes
File without changes
File without changes
File without changes