Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Tokenization throughput: process pool capped at 4 workers, GIL-bound thread path (benchmark: caladan 059 batches)

Aperta
#926 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@shobhitagnihotri69 ci sta già lavorando.

Dal 23/9/2026.

  • #930 di @shobhitagnihotri69 — chiusa senza merge
  • #944 di @shobhitagnihotri69 — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
48/100
Tipo di issue
Refactoring
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
python

Direzione di ricerca

Inizia da src/art/trajectories/_parallel.py e dal punto di ingresso art.tokenize, quindi riproduci il carico di lavoro descritto in caladan/experiments/059-robust-policy.py usando una piccola porzione di traiettoria. Misura il throughput di processi e thread, il warm-up, la serializzazione e il comportamento delle chiamate concorrenti. Il lavoro è completato quando un miglioramento di parallelizzazione scelto è stato sottoposto a benchmark rispetto al limite superiore riportato di 84k tokens/s senza regressioni nel comportamento esistente.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

enhancement

Summary

art.tokenize parallelizes through src/art/trajectories/_parallel.py, but on a 64-CPU driver the steady-state ceiling is ~84k tokens/s because the process pool is hard-capped at _PROCESS_MAX_WORKERS = 4 and the thread path is GIL-bound at ~30k tokens/s. Proposing caladan experiment 059's per-step tokenization as a concrete benchmark workload for improving this.

Benchmark workload (caladan experiments/059-robust-policy.py)

Per training step the driver tokenizes two things back to back:

  1. art.tokenize(groups, multi_history=True, base_model="Qwen/Qwen3.6-35B-A3B") — 8 TrajectoryGroups x 5 trajectories (1 real corpus trajectory + 4 dynamics-model rollouts), each rollout carrying two ~20k-token histories (policy view + adversary/dynamics view with captured sampled tokens and logprobs). ~40 leaves, ~1.5M tokens.
  2. art.tokenize([cal.dynamics.trajectory(t, model=adversary) for t in reals], model=adversary, base_model=...) — 8 single-history adversary views of the real trajectories, ~20k tokens each.

The corpus is tau-bench retail-49 transcripts (corpus4096, ~7 MB per trajectory materialized). A self-contained reproduction only needs 8–16 of those trajectories; happy to share a slice.

Measurements (this box: 120 CPUs, cgroup limit 64, tokenizer cached)

16 adversary-view trajectories, ~300k tokens, art.tokenize(..., model="adv", base_model="Qwen/Qwen3.6-35B-A3B"):

run wall throughput backend
0 11.8 s 26k tok/s threads (4 workers)
1 9.2 s 33k tok/s threads
2 26.7 s 11k tok/s process pool spawn + tokenizer load in 4 workers
3 3.6 s 84k tok/s processes (4 workers)
8 + 8 sequential, warm 4.0 s processes
8 + 8 asyncio.gather, warm 3.5 s processes, shared pool

Observations from _parallel.py

  • _PROCESS_MAX_WORKERS = 4 regardless of _cpu_capacity(); with 64 CPUs available, 60 sit idle.
  • Processes are only enabled after a tuning key has >= 2 thread runs each >= 1 s (_consider_processes). The tuning key includes _size_bucket(len(values)), so every distinct batch-size bucket pays that warm-up again, and the first process run also pays a ~25 s spawn + tokenizer load.
  • Thread path: trajectory.tokenize is mostly Python (chat template render, message/sampled-token matching, flag construction); only the Rust encode releases the GIL, so workers > ~4 doesn't help.
  • Process path: one pickle round trip per trajectory (_process_payloads serial on one thread, _deserialize_process_result + _rebind_process_result per item). For ~7 MB trajectories this is a nontrivial share of the 3.6 s.
  • The autotuner (_observe_process) assumes exclusive use of the pool; concurrent art.tokenize calls contend for the same 4 workers and skew each other's rate measurements (it disables processes if they don't beat threads by 5%).

Ideas

  • Derive the process cap from capacity (e.g. min(capacity, 16)), or expose it (ART_TOKENIZE_PROCESSES).
  • Let callers opt into processes up front instead of waiting for two slow thread runs per size bucket; or warm the pool at import/first call in the background.
  • Batch several trajectories per process task to amortize pickling and the per-task scheduling overhead.
  • Reuse the pickled payload for tensorize rather than re-pickling.
  • Make the tuner concurrency-aware (share next_workers across in-flight calls, or measure per-pool rather than per-call).

Would be glad to run any candidate against the 059 shapes above.

Lingua principale
Python
Stelle
10.8k
Fork
1k
Merge medio
16h 14m
PR unite (30g)
148

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di OpenPipe/ART

Tutte le issue di OpenPipe/ART

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.