feat: neuro: stable video diffusion
This commit is contained in:
+28
-1
@@ -26,12 +26,39 @@
|
|||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
cudaUnfreeNames = [
|
||||||
|
"cuda_nvcc"
|
||||||
|
"cuda_cudart"
|
||||||
|
"cuda_cuobjdump"
|
||||||
|
"cuda_cupti"
|
||||||
|
"cuda_nvdisasm"
|
||||||
|
"cuda_cccl"
|
||||||
|
"cuda_nvml_dev"
|
||||||
|
"cuda_nvrtc"
|
||||||
|
"cuda_nvtx"
|
||||||
|
"cuda_profiler_api"
|
||||||
|
|
||||||
|
"libcusparse_lt"
|
||||||
|
"libcublas"
|
||||||
|
"libcufft"
|
||||||
|
"libcufile"
|
||||||
|
"libcurand"
|
||||||
|
"libcusolver"
|
||||||
|
"libnvjitlink"
|
||||||
|
"libcusparse"
|
||||||
|
"cudnn"
|
||||||
|
];
|
||||||
|
|
||||||
|
cudaUnfreePredicate = pkg:
|
||||||
|
builtins.elem (nixpkgs.lib.getName pkg) cudaUnfreeNames;
|
||||||
|
|
||||||
forSystemsWithPkgs = supportedSystems: pkgOverlays: f:
|
forSystemsWithPkgs = supportedSystems: pkgOverlays: f:
|
||||||
builtins.foldl' (
|
builtins.foldl' (
|
||||||
acc: system: let
|
acc: system: let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
overlays = pkgOverlays;
|
overlays = pkgOverlays;
|
||||||
|
config.allowUnfreePredicate = cudaUnfreePredicate;
|
||||||
};
|
};
|
||||||
systemOutputs = f {
|
systemOutputs = f {
|
||||||
system = system;
|
system = system;
|
||||||
@@ -58,7 +85,7 @@
|
|||||||
else {};
|
else {};
|
||||||
in {
|
in {
|
||||||
# -- For all systems --
|
# -- For all systems --
|
||||||
inherit dotEnv minorEnvironment parseEnv forAllSystemsWithPkgs forSystemsWithPkgs commonSystems AllSystems;
|
inherit dotEnv minorEnvironment parseEnv forAllSystemsWithPkgs forSystemsWithPkgs commonSystems AllSystems cudaUnfreeNames cudaUnfreePredicate;
|
||||||
|
|
||||||
forSystems = systems: nixpkgs.lib.genAttrs systems;
|
forSystems = systems: nixpkgs.lib.genAttrs systems;
|
||||||
forAllSystems = nixpkgs.lib.genAttrs AllSystems;
|
forAllSystems = nixpkgs.lib.genAttrs AllSystems;
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
{ ... }: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.stable-video-diffusion;
|
||||||
|
in {
|
||||||
|
options.hectic.services.stable-video-diffusion = {
|
||||||
|
enable = lib.mkEnableOption "local Stable Video Diffusion HTTP API";
|
||||||
|
|
||||||
|
host = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address the Stable Video Diffusion API binds to.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 7861;
|
||||||
|
description = "Port the Stable Video Diffusion API binds to.";
|
||||||
|
};
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = pkgs.hectic.stable-video-diffusion-api;
|
||||||
|
defaultText = lib.literalExpression "pkgs.hectic.stable-video-diffusion-api";
|
||||||
|
description = "Package providing the Stable Video Diffusion API executable.";
|
||||||
|
};
|
||||||
|
|
||||||
|
modelId = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "stabilityai/stable-video-diffusion-img2vid-xt";
|
||||||
|
description = "Model identifier loaded by the Stable Video Diffusion API.";
|
||||||
|
};
|
||||||
|
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr (enum [ "cpu" "cuda" ]);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Torch device requested from the Stable Video Diffusion API. When null,
|
||||||
|
the package keeps its own auto-detection behavior.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
libraryPath = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
Runtime library paths added to LD_LIBRARY_PATH. CUDA/NVIDIA deployments
|
||||||
|
should include /run/opengl-driver/lib so libcuda.so is visible to torch.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
stateDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/stable-video-diffusion-api";
|
||||||
|
description = "Persistent state directory for the Stable Video Diffusion API.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/cache/stable-video-diffusion-api";
|
||||||
|
description = "Cache directory for downloaded model and runtime artifacts.";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "${cfg.stateDir}/outputs";
|
||||||
|
defaultText = lib.literalExpression ''"\${config.hectic.services.stable-video-diffusion.stateDir}/outputs"'';
|
||||||
|
description = "Directory where generated video outputs are written.";
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Optional environment file for secrets or runtime overrides. Values from
|
||||||
|
the unit environment define SVD_API_HOST, SVD_API_PORT, SVD_MODEL_ID,
|
||||||
|
SVD_STATE_DIR, SVD_CACHE_DIR, SVD_OUTPUT_DIR, and optionally SVD_DEVICE
|
||||||
|
and LD_LIBRARY_PATH by default.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to open the API port in the firewall.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.device != "cuda" || cfg.libraryPath != [];
|
||||||
|
message = "hectic.services.stable-video-diffusion.libraryPath must include the NVIDIA runtime library path when device is cuda.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.stable-video-diffusion = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "stable-video-diffusion";
|
||||||
|
};
|
||||||
|
users.groups.stable-video-diffusion = {};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.stateDir} 0750 stable-video-diffusion stable-video-diffusion -"
|
||||||
|
"d ${cfg.cacheDir} 0750 stable-video-diffusion stable-video-diffusion -"
|
||||||
|
"d ${cfg.outputDir} 0750 stable-video-diffusion stable-video-diffusion -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.stable-video-diffusion-api = {
|
||||||
|
description = "Stable Video Diffusion HTTP API";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = {
|
||||||
|
SVD_API_HOST = cfg.host;
|
||||||
|
SVD_API_PORT = toString cfg.port;
|
||||||
|
SVD_MODEL_ID = cfg.modelId;
|
||||||
|
SVD_STATE_DIR = cfg.stateDir;
|
||||||
|
SVD_CACHE_DIR = cfg.cacheDir;
|
||||||
|
SVD_OUTPUT_DIR = cfg.outputDir;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.device != null) {
|
||||||
|
SVD_DEVICE = cfg.device;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.libraryPath != []) {
|
||||||
|
LD_LIBRARY_PATH = lib.concatStringsSep ":" cfg.libraryPath;
|
||||||
|
};
|
||||||
|
serviceConfig = lib.mkMerge [
|
||||||
|
{
|
||||||
|
Type = "simple";
|
||||||
|
User = "stable-video-diffusion";
|
||||||
|
Group = "stable-video-diffusion";
|
||||||
|
WorkingDirectory = cfg.stateDir;
|
||||||
|
ExecStart = lib.getExe' cfg.package "stable-video-diffusion-api";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5s";
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
KillMode = "mixed";
|
||||||
|
StandardOutput = "journal";
|
||||||
|
StandardError = "journal";
|
||||||
|
}
|
||||||
|
(lib.mkIf (cfg.environmentFile != null) {
|
||||||
|
EnvironmentFile = cfg.environmentFile;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -15,32 +15,12 @@ in self.lib.nixpkgs-lib.nixosSystem {
|
|||||||
self.overlays.default
|
self.overlays.default
|
||||||
inputs.nix-minecraft.overlay
|
inputs.nix-minecraft.overlay
|
||||||
];
|
];
|
||||||
config.allowUnfreePredicate = pkg: builtins.elem (self.lib.nixpkgs-lib.getName pkg) [
|
config.allowUnfreePredicate = pkg:
|
||||||
|
self.lib.cudaUnfreePredicate pkg || builtins.elem (self.lib.nixpkgs-lib.getName pkg) [
|
||||||
"minecraft-server"
|
"minecraft-server"
|
||||||
"neoforge"
|
"neoforge"
|
||||||
|
|
||||||
"nvidia-x11"
|
"nvidia-x11"
|
||||||
|
|
||||||
"cuda_nvcc"
|
|
||||||
"cuda_cudart"
|
|
||||||
"cuda_cuobjdump"
|
|
||||||
"cuda_cupti"
|
|
||||||
"cuda_nvdisasm"
|
|
||||||
"cuda_cccl"
|
|
||||||
"cuda_nvml_dev"
|
|
||||||
"cuda_nvrtc"
|
|
||||||
"cuda_nvtx"
|
|
||||||
"cuda_profiler_api"
|
|
||||||
|
|
||||||
"libcusparse_lt"
|
|
||||||
"libcublas"
|
|
||||||
"libcufft"
|
|
||||||
"libcufile"
|
|
||||||
"libcurand"
|
|
||||||
"libcusolver"
|
|
||||||
"libnvjitlink"
|
|
||||||
"libcusparse"
|
|
||||||
"cudnn"
|
|
||||||
];
|
];
|
||||||
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
||||||
config.permittedInsecurePackages = [
|
config.permittedInsecurePackages = [
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
|
|
||||||
ollamaServiceBundledLibraryPath = "${ollamaPrebuilt}/lib/ollama:${ollamaPrebuilt}/lib/ollama/cuda_v12:${ollamaPrebuilt}/lib/ollama/cuda_v13";
|
ollamaServiceBundledLibraryPath = "${ollamaPrebuilt}/lib/ollama:${ollamaPrebuilt}/lib/ollama/cuda_v12:${ollamaPrebuilt}/lib/ollama/cuda_v13";
|
||||||
|
|
||||||
|
stableVideoDiffusionLibraryPath = lib.makeLibraryPath [
|
||||||
|
pkgs.stdenv.cc.cc.lib
|
||||||
|
pkgs.zlib
|
||||||
|
];
|
||||||
|
|
||||||
ollamaPrebuilt = pkgs.stdenvNoCC.mkDerivation {
|
ollamaPrebuilt = pkgs.stdenvNoCC.mkDerivation {
|
||||||
pname = "ollama";
|
pname = "ollama";
|
||||||
version = "0.22.1";
|
version = "0.22.1";
|
||||||
@@ -176,6 +181,19 @@ in {
|
|||||||
openFirewall = false;
|
openFirewall = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hectic.services.stable-video-diffusion = {
|
||||||
|
enable = true;
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = 7861;
|
||||||
|
package = pkgs.hectic.stable-video-diffusion-api;
|
||||||
|
device = "cuda";
|
||||||
|
libraryPath = [
|
||||||
|
stableVideoDiffusionLibraryPath
|
||||||
|
"/run/opengl-driver/lib"
|
||||||
|
];
|
||||||
|
openFirewall = false;
|
||||||
|
};
|
||||||
|
|
||||||
networking = {
|
networking = {
|
||||||
networkmanager.enable = true;
|
networkmanager.enable = true;
|
||||||
useDHCP = lib.mkDefault true;
|
useDHCP = lib.mkDefault true;
|
||||||
|
|||||||
@@ -174,5 +174,6 @@ in {
|
|||||||
pg-15-ext-smtp-client = buildSmtpExt pkgs "15";
|
pg-15-ext-smtp-client = buildSmtpExt pkgs "15";
|
||||||
pg-15-ext-plhaskell = buildPlHaskellExt pkgs "15";
|
pg-15-ext-plhaskell = buildPlHaskellExt pkgs "15";
|
||||||
pg-15-ext-plsh = buildPlShExt pkgs "15";
|
pg-15-ext-plsh = buildPlShExt pkgs "15";
|
||||||
|
stable-video-diffusion-api = pkgs.callPackage ./stable-video-diffusion-api {};
|
||||||
media-browser = pkgs.callPackage ./media-browser {};
|
media-browser = pkgs.callPackage ./media-browser {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Local HTTP API for Diffusers Stable Video Diffusion image-to-video."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
import uuid
|
||||||
|
from importlib import import_module
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
torch = import_module("torch")
|
||||||
|
uvicorn = import_module("uvicorn")
|
||||||
|
diffusers = import_module("diffusers")
|
||||||
|
diffusers_utils = import_module("diffusers.utils")
|
||||||
|
fastapi = import_module("fastapi")
|
||||||
|
pil_image = import_module("PIL.Image")
|
||||||
|
pil_unidentified_image_error = import_module("PIL").UnidentifiedImageError
|
||||||
|
|
||||||
|
FastAPI = fastapi.FastAPI
|
||||||
|
File = fastapi.File
|
||||||
|
Form = fastapi.Form
|
||||||
|
HTTPException = fastapi.HTTPException
|
||||||
|
Request = fastapi.Request
|
||||||
|
UploadFile = fastapi.UploadFile
|
||||||
|
StableVideoDiffusionPipeline = diffusers.StableVideoDiffusionPipeline
|
||||||
|
export_to_video = diffusers_utils.export_to_video
|
||||||
|
|
||||||
|
|
||||||
|
HOST = os.environ.get("SVD_API_HOST", "127.0.0.1")
|
||||||
|
PORT = int(os.environ.get("SVD_API_PORT", "8000"))
|
||||||
|
MODEL_ID = os.environ.get("SVD_MODEL_ID", "stabilityai/stable-video-diffusion-img2vid-xt")
|
||||||
|
CACHE_DIR = os.environ.get("SVD_CACHE_DIR") or os.environ.get("HF_HOME")
|
||||||
|
OUTPUT_DIR = Path(os.environ.get("SVD_OUTPUT_DIR", "/var/lib/stable-video-diffusion-api/outputs"))
|
||||||
|
DEVICE = os.environ.get("SVD_DEVICE", "cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
DTYPE = os.environ.get("SVD_DTYPE", "float16")
|
||||||
|
ENABLE_CPU_OFFLOAD = os.environ.get("SVD_ENABLE_CPU_OFFLOAD", "0").lower() in ("1", "true", "yes", "on")
|
||||||
|
DEFAULT_DECODE_CHUNK_SIZE = int(os.environ.get("SVD_DECODE_CHUNK_SIZE", "8"))
|
||||||
|
DEFAULT_FPS = int(os.environ.get("SVD_FPS", "7"))
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="Stable Video Diffusion API")
|
||||||
|
pipeline_lock = threading.Lock()
|
||||||
|
pipeline: Any | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def torch_dtype() -> Any:
|
||||||
|
dtypes = {
|
||||||
|
"float16": torch.float16,
|
||||||
|
"fp16": torch.float16,
|
||||||
|
"float32": torch.float32,
|
||||||
|
"fp32": torch.float32,
|
||||||
|
"bfloat16": torch.bfloat16,
|
||||||
|
"bf16": torch.bfloat16,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
return dtypes[DTYPE.lower()]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise RuntimeError(f"Unsupported SVD_DTYPE: {DTYPE}") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def get_pipeline() -> Any:
|
||||||
|
global pipeline
|
||||||
|
|
||||||
|
if pipeline is not None:
|
||||||
|
return pipeline
|
||||||
|
|
||||||
|
with pipeline_lock:
|
||||||
|
if pipeline is not None:
|
||||||
|
return pipeline
|
||||||
|
|
||||||
|
kwargs: dict[str, Any] = {"torch_dtype": torch_dtype()}
|
||||||
|
if CACHE_DIR:
|
||||||
|
kwargs["cache_dir"] = CACHE_DIR
|
||||||
|
|
||||||
|
loaded = StableVideoDiffusionPipeline.from_pretrained(MODEL_ID, **kwargs)
|
||||||
|
if ENABLE_CPU_OFFLOAD:
|
||||||
|
loaded.enable_model_cpu_offload()
|
||||||
|
else:
|
||||||
|
loaded.to(DEVICE)
|
||||||
|
|
||||||
|
pipeline = loaded
|
||||||
|
return loaded
|
||||||
|
|
||||||
|
|
||||||
|
def positive_int(name: str, value: Any, default: int) -> int:
|
||||||
|
if value in (None, ""):
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
parsed = int(value)
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=f"{name} must be an integer") from exc
|
||||||
|
if parsed <= 0:
|
||||||
|
raise HTTPException(status_code=400, detail=f"{name} must be greater than zero")
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def optional_int(name: str, value: Any) -> int | None:
|
||||||
|
if value in (None, ""):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=f"{name} must be an integer") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def optional_float(name: str, value: Any) -> float | None:
|
||||||
|
if value in (None, ""):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (TypeError, ValueError) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=f"{name} must be a number") from exc
|
||||||
|
|
||||||
|
|
||||||
|
def load_image_from_path(input_image_path: str) -> Any:
|
||||||
|
path = Path(input_image_path).expanduser()
|
||||||
|
if not path.exists() or not path.is_file():
|
||||||
|
raise HTTPException(status_code=400, detail="input_image_path does not exist or is not a file")
|
||||||
|
try:
|
||||||
|
return pil_image.open(path).convert("RGB")
|
||||||
|
except (OSError, pil_unidentified_image_error) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail="input_image_path could not be decoded as an image") from exc
|
||||||
|
|
||||||
|
|
||||||
|
async def load_uploaded_image(image: UploadFile) -> Any:
|
||||||
|
try:
|
||||||
|
return pil_image.open(image.file).convert("RGB")
|
||||||
|
except (OSError, pil_unidentified_image_error) as exc:
|
||||||
|
raise HTTPException(status_code=400, detail="uploaded image could not be decoded") from exc
|
||||||
|
|
||||||
|
|
||||||
|
async def json_payload(request: Request) -> dict[str, Any]:
|
||||||
|
content_type = request.headers.get("content-type", "")
|
||||||
|
if not content_type.startswith("application/json"):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
payload = await request.json()
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=400, detail="request body must be valid JSON") from exc
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise HTTPException(status_code=400, detail="JSON request body must be an object")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health() -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"model_loaded": pipeline is not None,
|
||||||
|
"model_id": MODEL_ID,
|
||||||
|
"device": DEVICE,
|
||||||
|
"dtype": DTYPE,
|
||||||
|
"cpu_offload": ENABLE_CPU_OFFLOAD,
|
||||||
|
"cache_dir": CACHE_DIR,
|
||||||
|
"output_dir": str(OUTPUT_DIR),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/generate")
|
||||||
|
async def generate(
|
||||||
|
request: Request,
|
||||||
|
image: UploadFile | None = File(default=None),
|
||||||
|
input_image_path: str | None = Form(default=None),
|
||||||
|
num_frames: int | None = Form(default=None),
|
||||||
|
num_inference_steps: int | None = Form(default=None),
|
||||||
|
fps: int | None = Form(default=None),
|
||||||
|
decode_chunk_size: int | None = Form(default=None),
|
||||||
|
seed: int | None = Form(default=None),
|
||||||
|
motion_bucket_id: int | None = Form(default=None),
|
||||||
|
noise_aug_strength: float | None = Form(default=None),
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
payload = await json_payload(request)
|
||||||
|
path = input_image_path or payload.get("input_image_path")
|
||||||
|
upload = image
|
||||||
|
|
||||||
|
if upload is None and not path:
|
||||||
|
raise HTTPException(status_code=400, detail="provide input_image_path or upload image")
|
||||||
|
if upload is not None and path:
|
||||||
|
raise HTTPException(status_code=400, detail="provide only one image source")
|
||||||
|
|
||||||
|
source_image = await load_uploaded_image(upload) if upload is not None else load_image_from_path(str(path))
|
||||||
|
chunk_size = positive_int("decode_chunk_size", decode_chunk_size if decode_chunk_size is not None else payload.get("decode_chunk_size"), DEFAULT_DECODE_CHUNK_SIZE)
|
||||||
|
output_fps = positive_int("fps", fps if fps is not None else payload.get("fps"), DEFAULT_FPS)
|
||||||
|
request_seed = optional_int("seed", seed if seed is not None else payload.get("seed"))
|
||||||
|
generator = None
|
||||||
|
if request_seed is not None:
|
||||||
|
generator = torch.Generator(device="cpu").manual_seed(request_seed)
|
||||||
|
|
||||||
|
call_args: dict[str, Any] = {
|
||||||
|
"image": source_image,
|
||||||
|
"decode_chunk_size": chunk_size,
|
||||||
|
}
|
||||||
|
for key, value in {
|
||||||
|
"num_frames": num_frames if num_frames is not None else payload.get("num_frames"),
|
||||||
|
"num_inference_steps": num_inference_steps if num_inference_steps is not None else payload.get("num_inference_steps"),
|
||||||
|
"motion_bucket_id": motion_bucket_id if motion_bucket_id is not None else payload.get("motion_bucket_id"),
|
||||||
|
}.items():
|
||||||
|
parsed = optional_int(key, value)
|
||||||
|
if parsed is not None:
|
||||||
|
call_args[key] = parsed
|
||||||
|
|
||||||
|
parsed_noise = optional_float("noise_aug_strength", noise_aug_strength if noise_aug_strength is not None else payload.get("noise_aug_strength"))
|
||||||
|
if parsed_noise is not None:
|
||||||
|
call_args["noise_aug_strength"] = parsed_noise
|
||||||
|
if generator is not None:
|
||||||
|
call_args["generator"] = generator
|
||||||
|
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path = OUTPUT_DIR / f"svd-{uuid.uuid4().hex}.mp4"
|
||||||
|
|
||||||
|
try:
|
||||||
|
frames = get_pipeline()(**call_args).frames[0]
|
||||||
|
export_to_video(frames, str(output_path), fps=output_fps)
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
return {
|
||||||
|
"output_path": str(output_path),
|
||||||
|
"model_id": MODEL_ID,
|
||||||
|
"seed": request_seed,
|
||||||
|
"fps": output_fps,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
uvicorn.run("app:app", host=HOST, port=PORT)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: let
|
||||||
|
torch = ps.torchWithCuda;
|
||||||
|
accelerate = ps.accelerate.override { inherit torch; };
|
||||||
|
in [
|
||||||
|
accelerate
|
||||||
|
ps.diffusers
|
||||||
|
ps.fastapi
|
||||||
|
ps.imageio
|
||||||
|
ps.imageio-ffmpeg
|
||||||
|
ps.pillow
|
||||||
|
ps.python-multipart
|
||||||
|
ps.safetensors
|
||||||
|
torch
|
||||||
|
ps.transformers
|
||||||
|
ps.uvicorn
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "stable-video-diffusion-api";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin $out/libexec/stable-video-diffusion-api
|
||||||
|
cp $src/app.py $out/libexec/stable-video-diffusion-api/app.py
|
||||||
|
chmod +x $out/libexec/stable-video-diffusion-api/app.py
|
||||||
|
|
||||||
|
makeWrapper ${pythonEnv}/bin/python3 $out/bin/stable-video-diffusion-api \
|
||||||
|
--add-flags $out/libexec/stable-video-diffusion-api/app.py \
|
||||||
|
--set-default SVD_API_HOST 127.0.0.1 \
|
||||||
|
--set-default SVD_API_PORT 8000 \
|
||||||
|
--set-default SVD_MODEL_ID stabilityai/stable-video-diffusion-img2vid-xt \
|
||||||
|
--set-default SVD_OUTPUT_DIR /var/lib/stable-video-diffusion-api/outputs
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user