The v2 top level had grown to ~27 dirs + 13 loose files — half of them
single-concept abstraction shells (memory/ 86 LOC, transport/ 222, parity/ 178,
extend/ 226) and half vendored fastvideo code sitting as peers to the actual v2
design. Regroup to mirror README section 3 "Planes & dependency order":
core/ enums+types, card, loop, program, parity, request, parallel
(the model-native contracts; no kernels)
runtime/ + folded-in substrate: cache, memory, transport, extend
(the import graph shows only runtime consumes them; compile/cudagraph
already lived here)
serving/ + deploy/ (products / fleet)
_vendor/ all copied fastvideo: models, layers, attention, configs, distributed,
platforms, api, hooks, logging_utils, third_party + fastvideo_args/
utils/logger/envs/forward_context — internal layout unchanged, still
mirrors upstream for diffing
28 dirs + 13 root files -> 8 dirs + 6 root files. Pure mechanical move: 947
absolute-import paths rewritten (v2.X -> v2.{core,runtime,serving,_vendor}.X),
boundary-anchored so platform/ (native dispatch) and platforms/ (vendored CUDA
detect) no longer collide and neither does hooks/ vs extend/. Deleted the empty
v2/loader/. README section 3 + 16 updated; stale test count corrected
(34 files/216 tests -> 22 files/143 tests).
Validated: `import v2` stays torch-free; `v2/run_tests.py` and `pytest v2/tests/`
-> 143 passed, 2 skipped on the numpy toy backend. (On a GPU box force the toy
backend with CUDA_VISIBLE_DEVICES="" or detect() picks cuda.) No external importer
changed — examples use the re-exported `from v2 import VideoGenerator`.
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._vendor.models import build_default_engine
|
|
from v2.core.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())
|