Async API
AsyncModel wraps the synchronous tinyloop_py.Model bindings so every blocking CUDA call runs in a thread pool, letting you await inference from any asyncio coroutine without stalling the event loop.
Quick start
import asyncio
from tinyloop_tools.async_model import AsyncModel
async def main():
async with AsyncModel("model.tinyloop") as model:
tokens = await model.generate([1, 2, 3], max_tokens=32, loops=8)
print(tokens)
asyncio.run(main())
Concurrency model
The underlying C++ model is not thread-safe. AsyncModel serialises all calls behind an asyncio.Lock, so concurrent awaits queue automatically — no manual synchronisation needed.
A single-thread ThreadPoolExecutor runs the blocking CUDA work. This means:
- The event loop stays responsive during GPU compute.
- Multiple concurrent
await model.generate(...)calls are serialised, not parallelised. - The GIL is released during C++ execution via pybind11's
gil_scoped_release.
API reference
Constructor
AsyncModel(path, max_seq_len=2048, prefill_chunk=0, max_workers=1)
Scoring
logits = await model.score(tokens, loops=8) # [seq_len, vocab]
logits = await model.score_last(tokens, loops=8) # [vocab]
logits = await model.score_logit_lens(tokens, loops=8) # [loops, vocab]
traj = await model.score_trajectory(tokens, loops=8) # dict
Generation
tokens = await model.generate(prompt, max_tokens=128, loops=8,
temperature=0.0, top_k=50)
All generate parameters from the sync API are supported: cache_window, repetition_penalty, sample_seed, l_warmup_tokens, l_warmup_L, safety_check, safety_norm_threshold.
Streaming
async for token_id in model.generate_stream(prompt, max_tokens=128, loops=8):
print(token_id, end=" ")
The stream is bridged from the C++ callback via asyncio.Queue — tokens are yielded as soon as they are produced.
Speculative decode
result = await model.generate_speculative(prompt, draft_loops=2,
verify_loops=8, draft_ahead=4)
Batch generation
outputs = await model.generate_batch(
[prompt1, prompt2, prompt3],
max_tokens=64, loops=8,
per_lane_loops=[4, 8, 16] # optional L-aware batching
)
Context manager
async with AsyncModel("model.tinyloop") as model:
... # model.close() called automatically
Utilities
cfg = model.config() # sync — returns dict
vram = await model.vram_usage_mb()