Abner Ayala
05/12/2026, 8:56 PMpreprocessed_image (gets deleted after inference) just creates too many numpy tensors that the model cannot consume fast enough.
2. I also have switch from using daft.func for preprocessing to using daft.cls in order to always make the max_concurrency of the preprocessing be bounded to 2 * num_gpus.
a. All my preprocessing are now using daft.cls with max_concurrency=num_gpus *2
b. It's my hacky way to control the queue size between preprocessing -> inference from exploding.
c. this is somewhat equivalent to prefetct_factor=2 in Pytorch Dataloaders.
3. For very big models that have slow images per second I am also having to throttle the max_connection of daft download function
a. why? to again ensure queue size between download + decode -> preprocess_udf does not explode.
That's 3 knobs I'm having to control differently per each model or pipeline in order from not having one step in the pipeline producing a queue too large for the next step. Which makes me wonder is there is a better solution for this or if we could have more control of the queue size or not slow down one steps if it's to fast for the next one?
Some possible solution:
The max_in_flight or queue_size can just be a parameter for all native or user-defined daft.func and daft.cls.
df = df.with_column("image_bytes", df["image_url"].url.download()).max_in_flight(64)
df = df.with_column("image", df["image_bytes"].decode_image()).max_in_flight(32)
df = df.with_column("preprocessed_image", preprocessing_udf(col("image"))).max_in_flight(16)
df = df.select("*", inference_udf(...)).max_in_flight(8)
The queue numbers are number of batches not number of rows.
[download]──[64]──[decode]──[32]──[preprocess]──[16]──[infer]──[8]──[write]
↑ ↑ ↑ ↑
image_bytes image preprocessed_image predictions
rows queued rows queued rows queued rows queued
Let me know what you guys think, hopefully this helps.Cory Grinstead
05/12/2026, 9:09 PMdefault_morsel_size. By default we use a very large morsel (queue) size. Adjusting this would reduce the global "queue" size used for every operator.
daft.set_execution_config(default_morsel_size=size)Cory Grinstead
05/12/2026, 9:12 PMAbner Ayala
05/12/2026, 10:10 PMjay
05/13/2026, 8:19 PMAbner Ayala
05/13/2026, 8:32 PMdaft.cls. The main thing is not like all operations have a mechanisms to enforce them to slow down.
Most of my issues with daft that have made it a bit tedious is there is no slow down option. Like if ops 1 is way faster than ops 2 and ops1 output has a big object (tensor) it just explodes and start filling the OOM or the object storage depending of pipeline setup.
My suggestion about controlling the queue is more like ops1 already fills the output buffer it should just block or slow down until it needs to fill the buffer again.
This pattern keeps happening across my pipelines. Like yesterday I had a pipeline that just read a large parquet did some very light calculation and then used daft.upload to upload a bin blob per row and when I ran on few rows it work perfectly on the first try but when running at scale I had to reduce the max_connections to avoid s3 PUT limits and then I had to add a into_partitions(small number) after the read_parquet and I had to change some of the execute config values. So 3 different knobs I needed to change just to tell daft please slow down all the other ops are running blazing fast but we cannot just do a billion s3 PUT at once.
Somehow it needs to be smarter about when to slow down so things are more plug and play and we don't need magic number or configs to get things to just work.