Five self-contained, runnable scripts (CPU/numpy) for the Wan2.1-1.3B card on the v2 runtime, each bootstrapping the repo onto sys.path so they run from anywhere: - 01_basic_t2v.py minimal path: build engine → T2V request → run → video - 02_params_and_reproducibility.py DiffusionParams knobs + seeded bit-identical reproducibility - 03_streaming.py per-denoise-step preview chunks (OutputSpec stream) - 04_concurrent_interleaved.py step-interleaved batching + interleave parity gate + cache reuse - 05_async_serving.py AsyncEngine: concurrent generate, event stream, step-boundary cancel + README.md indexing them. All five run green; use only the public API.
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""05 — Async / concurrent serving of Wan2.1-1.3B (AsyncEngine).
|
|
|
|
`AsyncEngine` wraps the same step-scheduled engine with a request queue, lifecycle state, live event
|
|
streaming, and step-boundary cancellation. This shows the three async entry points:
|
|
|
|
* `generate_many(reqs)` — await several requests concurrently (steps interleave under the hood);
|
|
* `submit(req)` — an async iterator of `OmniEvent`s (`request.start` → `media.chunk`* →
|
|
`artifact.ready` → `request.complete`);
|
|
* `cancel(request_id)` — trips the CancelScope; the request stops at the next step boundary and ends
|
|
with `request.cancelled`.
|
|
|
|
Run: python3 v2_examples/inference/05_async_serving.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import pathlib
|
|
import sys
|
|
from collections import Counter
|
|
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
|
|
|
|
from v2.models import build_default_engine
|
|
from v2.request import DiffusionParams, OutputSpec, TaskType, make_request
|
|
from v2.runtime import AsyncEngine
|
|
|
|
MODEL = "wan2.1-1.3b"
|
|
|
|
|
|
def _t2v(prompt, seed, *, steps=8, stream=False):
|
|
outputs = OutputSpec(stream={"video": True}) if stream else OutputSpec()
|
|
return make_request(TaskType.T2V, MODEL, prompt,
|
|
diffusion=DiffusionParams(num_steps=steps, seed=seed), outputs=outputs)
|
|
|
|
|
|
async def main() -> None:
|
|
ae = AsyncEngine(build_default_engine())
|
|
|
|
# (1) concurrent generation
|
|
reqs = [_t2v(f"scene {i}", seed=i) for i in range(3)]
|
|
outs = await ae.generate_many(reqs)
|
|
print("concurrent generate_many:")
|
|
for r in reqs:
|
|
print(f" {r.request_id[:8]} video={tuple(outs[r.request_id].artifacts['video'].frames.shape)}")
|
|
|
|
# (2) live event stream for one request
|
|
print("\nlive event stream (submit):")
|
|
events: Counter[str] = Counter()
|
|
async for ev in ae.submit(_t2v("a streamed clip", seed=99, steps=6, stream=True)):
|
|
events[ev.type] += 1
|
|
print(f" events: {dict(events)}")
|
|
print(" (request.start → media.chunk per step → artifact.ready → request.complete)")
|
|
|
|
# (3) step-boundary cancellation: cancel after the first streamed chunk
|
|
print("\ncancellation (mid-rollout):")
|
|
req = _t2v("a clip we abort", seed=7, steps=20, stream=True)
|
|
seq = []
|
|
async for ev in ae.submit(req):
|
|
seq.append(ev.type)
|
|
if ev.type == "media.chunk" and "cancel-sent" not in seq:
|
|
ae.cancel(req.request_id) # stop at the next step boundary
|
|
seq.append("cancel-sent")
|
|
print(f" final state : {ae.state(req.request_id)}")
|
|
print(f" ended with : {seq[-1]} (cancelled before all {20} steps ran)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|