[feat] Let the OpenAI server plug in a non-CUDA generator

This commit is contained in:
Ishan Vaish
2026-09-01 05:48:10 +00:00
committed by SolitaryThinker
parent 1ae39562dd
commit d74475ce06
3 changed files with 20 additions and 2 deletions
+11 -1
View File
@@ -62,6 +62,8 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
default_request: GenerationRequest | None = getattr(app.state, "default_request", None)
logger.info("Initializing %s generation runtime for %s ...", app.state.runtime, args.model_path)
# A non-CUDA runtime (e.g. MLX) supplies its own generator_factory instead
# of the default VideoGenerator/Executor path -- see MLXWanGenerator.
factory = app.state.generator_factory
generator = factory() if factory is not None else VideoGenerator.from_fastvideo_args(args)
serving_engine = OpenAIServingEngine(generator, app.state.video_request_validator)
@@ -98,7 +100,13 @@ def create_app(
video_request_validator: Callable[[VideoGenerationRequest], None] | None = None,
runtime: str = "cuda",
) -> FastAPI:
"""Build the FastAPI application with all routers mounted"""
"""Build the FastAPI application with all routers mounted.
``generator_factory``/``video_request_validator``/``runtime`` let a
non-CUDA backend (MLX) plug in its own generator and request rules
without a separate app-building path. Defaults reproduce the original
CUDA-only behavior exactly.
"""
app = FastAPI(
title="FastVideo OpenAI-Compatible API",
@@ -158,6 +166,8 @@ def create_app(
app.include_router(common_router)
app.include_router(video_router)
# The MLX runtime only wires video-with-audio generation (see
# fastvideo/mlx_runtime/); image generation has no MLX backend yet.
if runtime != "mlx":
app.include_router(image_router)
app.include_router(playground_router)
@@ -14,6 +14,11 @@ _T = TypeVar("_T")
class ServingGenerator(Protocol):
"""The minimal shape OpenAIServingEngine needs from a generator.
VideoGenerator (CUDA/mp/ray) satisfies this structurally already; an MLX
generator (e.g. MLXWanGenerator) implements just these two methods.
"""
def generate(self, request: GenerationRequest) -> Any:
...
@@ -47,6 +52,7 @@ class OpenAIServingEngine:
return self._generator
def validate_video_request(self, request: VideoGenerationRequest) -> None:
"""Runtime-specific request checks (e.g. MLX's supported-field allowlist)."""
if self._video_request_validator is not None:
self._video_request_validator(request)
@@ -143,4 +149,4 @@ class OpenAIServingEngine:
await asyncio.to_thread(self._generator.shutdown)
__all__ = ["OpenAIServingEngine"]
__all__ = ["OpenAIServingEngine", "ServingGenerator"]
@@ -331,6 +331,8 @@ async def _parse_video_request(raw_request: Request) -> VideoGenerationRequest:
async def _adapt_request(request_id: str, request: VideoGenerationRequest) -> GenerationRequest:
try:
# Runtime-specific checks (e.g. MLX's supported-field allowlist) run
# before the CUDA-oriented model/LoRA validation below.
get_serving_engine().validate_video_request(request)
validate_model_and_lora(request, get_server_args(), get_served_model_name())
await prepare_reference_media(request_id, request, get_output_dir())