You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
iter(batch_size=…) yields PythonCall wrappers instead of "julia"-format batches #105
In the default "julia" format, rows (for row in itds) and slices (ds[1:2]) are converted with py2jl. The batched iterator ds.iter(; batch_size), the only way to stream an IterableDataset in batches, is forwarded to Python instead. Its batches come back in PythonCall's default conversion: a PyIterable of PyDicts holding PyArrays, with string columns as PyArray{Py} of Python str.
using HuggingFaceDatasets # v0.4.3
ds =Dataset((; text=["a", "bb", "ccc"], n=Int32[1, 2, 3]))
b =first(ds.iter(; batch_size=2))
typeof(b) # PythonCall.PyDict{Any, Any}typeof(b["text"]) # PythonCall.PyArray{PythonCall.Py, 1, true, true, PythonCall.Wrap.UnsafePyObject}typeof(ds[1:2]) # Dict{String, Vector}first(ds.to_iterable_dataset().iter(; batch_size=2)) # the same PyDict
For streaming, batches are the fast path. On a local 400k-document TinyStories parquet, rows iterate at 26k rows/s in the "julia" format, while 10k-row batches converted with py2jl reach ~105k rows/s. Converting text batches is cheap: py2jl adds ~0.9 s per 100k FineWeb-Edu documents (482 MB of text). So with converted batches, a tokenization pipeline can work on Vector{String} batches and stay within 1–5% of keeping the text in Python (measured with the tokenizers GPT-2 tokenizer).
A possible fix: route iter through _method_override for Dataset and IterableDataset, so that it returns a Julia iterator applying the dataset's jltransform to each batch, e.g. Iterators.map(jltransform, py.iter(; kws...)). The current workaround is to call py2jl(b) on each batch.
Some data from the Flux GPT-2 example (FluxML/Flux.jl#2754), which keeps the text in Python today (data.jl: with_format(ds, nothing), then ds.py.select_columns("text").iter(; batch_size)):
The workaround in the description doesn't convert anything: ds.iter(...) yields PyDicts, and py2jl(::PyDict) returns its argument. What works now is py2jl(b) for b in ds.py.iter(; batch_size). py2jl could unwrap PythonCall's wrapper types (PyDict, PyList, PyArray, ...) through Py(x).
Julia strings cost nothing on the tokenizer side: tokenizers' encode_batch_fast accepts a Vector{String} as is, in 1.07 s against 1.11 s for the Python list (10k FineWeb-Edu documents, 10.3M tokens). End to end, streaming and tokenizing 300k FineWeb-Edu documents runs at 8.5 M tok/s with py2jl batches, and 8.6–9.3 M tok/s with raw Python batches.
select_columns is forwarded and keeps the "julia" format, so with this the example's text stream becomes ds.select_columns("text").iter(; batch_size).
Activity
CarloLucibello commentedon Oct 19, 2026
Some data from the Flux GPT-2 example (FluxML/Flux.jl#2754), which keeps the text in Python today (data.jl:
with_format(ds, nothing), thends.py.select_columns("text").iter(; batch_size)):ds.iter(...)yieldsPyDicts, andpy2jl(::PyDict)returns its argument. What works now ispy2jl(b)forb in ds.py.iter(; batch_size).py2jlcould unwrap PythonCall's wrapper types (PyDict,PyList,PyArray, ...) throughPy(x).tokenizers'encode_batch_fastaccepts aVector{String}as is, in 1.07 s against 1.11 s for the Python list (10k FineWeb-Edu documents, 10.3M tokens). End to end, streaming and tokenizing 300k FineWeb-Edu documents runs at 8.5 M tok/s withpy2jlbatches, and 8.6–9.3 M tok/s with raw Python batches.select_columnsis forwarded and keeps the"julia"format, so with this the example's text stream becomesds.select_columns("text").iter(; batch_size).Releasing each batch's Python memory is #107.
Batch iterator, identity-keyed numpy roots, GC for wrapped memory, ra…