Spaces:
Runtime error
Runtime error
File size: 5,613 Bytes
a550c4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | """Minimal Kimodo text-to-motion API for Hugging Face ZeroGPU."""
from __future__ import annotations
import os
import tempfile
from pathlib import Path
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
os.environ.setdefault("TEXT_ENCODER_MODE", "local")
os.environ.setdefault("TEXT_ENCODER_DEVICE", "cuda")
# ZeroGPU must patch CUDA before torch or any Kimodo module imports it.
import spaces
import gradio as gr
import torch
from kimodo import load_model
from kimodo.exports.bvh import save_motion_bvh
from kimodo.exports.motion_io import save_kimodo_npz
from kimodo.tools import seed_everything
from space_utils import estimate_zero_gpu_duration, validate_motion_request
MODEL_NAME = os.environ.get("KIMODO_MODEL", "Kimodo-SOMA-RP-v1.1")
# Eager module-scope placement is required by ZeroGPU's weight packing mechanism.
MODEL, RESOLVED_MODEL_NAME = load_model(
MODEL_NAME,
device="cuda",
default_family="Kimodo",
return_resolved_name=True,
)
def _without_progress(iterable):
return iterable
def _single_sample(output: dict) -> dict:
"""Remove the leading batch dimension from a one-sample model result."""
sample = {}
for key, value in output.items():
if hasattr(value, "shape") and len(value.shape) > 0 and int(value.shape[0]) == 1:
sample[key] = value[0]
else:
sample[key] = value
return sample
@spaces.GPU(duration=estimate_zero_gpu_duration, size="large")
def generate_motion(
prompt: str,
duration_seconds: float,
seed: int,
diffusion_steps: int,
standard_tpose: bool,
) -> tuple[str, str, dict]:
"""Generate one text-conditioned human motion and return BVH, NPZ, and metadata."""
request = validate_motion_request(
prompt,
duration_seconds,
seed,
diffusion_steps,
standard_tpose,
)
seed_everything(request.seed)
num_frames = max(1, int(round(request.duration_seconds * float(MODEL.fps))))
output = MODEL(
request.prompt,
num_frames,
num_denoising_steps=request.diffusion_steps,
multi_prompt=False,
constraint_lst=[],
cfg_weight=[2.0, 2.0],
num_samples=1,
return_numpy=True,
post_processing=False,
progress_bar=_without_progress,
)
sample = _single_sample(output)
output_dir = Path(tempfile.mkdtemp(prefix="kimodo-motion-"))
bvh_path = output_dir / "motion.bvh"
npz_path = output_dir / "motion.npz"
save_kimodo_npz(str(npz_path), sample)
skeleton = MODEL.output_skeleton
local_rot_mats = torch.as_tensor(sample["local_rot_mats"], device="cuda")
posed_joints = torch.as_tensor(sample["posed_joints"], device="cuda")
root_positions = posed_joints[:, int(skeleton.root_idx), :]
save_motion_bvh(
bvh_path,
local_rot_mats,
root_positions,
skeleton=skeleton,
fps=float(MODEL.fps),
standard_tpose=request.standard_tpose,
)
metadata = {
"prompt": request.prompt,
"duration_seconds": request.duration_seconds,
"seed": request.seed,
"diffusion_steps": request.diffusion_steps,
"model": MODEL_NAME,
"resolved_model": RESOLVED_MODEL_NAME,
"fps": float(MODEL.fps),
"frames": num_frames,
"skeleton": str(skeleton.name),
"standard_tpose": request.standard_tpose,
"post_processing": False,
}
return str(bvh_path), str(npz_path), metadata
with gr.Blocks(title="Kimodo Motion API") as demo:
gr.Markdown(
"""
# Kimodo Motion API
Generate a text-conditioned SOMA motion on ZeroGPU. Download the BVH for Blender
or keep the NPZ for a later Kimodo workflow. The first call after an idle period may
take longer while ZeroGPU restores model weights.
"""
)
prompt_input = gr.Textbox(
label="Motion prompt",
lines=3,
max_length=1_000,
value="A person walks forward cautiously, looks over the left shoulder, then stops.",
)
with gr.Row():
duration_input = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="Duration (seconds)")
seed_input = gr.Number(value=42, precision=0, minimum=0, maximum=2**31 - 1, label="Seed")
steps_input = gr.Slider(10, 100, value=50, step=5, label="Diffusion steps")
standard_tpose_input = gr.Checkbox(
value=True,
label="Export a standard T-pose rest skeleton",
info="Recommended for Blender retargeting.",
)
generate_button = gr.Button("Generate motion", variant="primary")
with gr.Row():
bvh_output = gr.File(label="Blender BVH")
npz_output = gr.File(label="Kimodo NPZ")
metadata_output = gr.JSON(label="Generation metadata")
generate_button.click(
fn=generate_motion,
inputs=[prompt_input, duration_input, seed_input, steps_input, standard_tpose_input],
outputs=[bvh_output, npz_output, metadata_output],
api_name="generate_motion",
api_description="Generate a Kimodo motion as BVH and NPZ files.",
concurrency_limit=1,
)
gr.Examples(
examples=[
["A person takes three slow steps forward and waves with the right hand."],
["A person crouches, jumps upward, lands, and regains balance."],
["A person performs a short defensive boxing combination."],
],
inputs=[prompt_input],
cache_examples=False,
)
demo.queue(default_concurrency_limit=1)
if __name__ == "__main__":
demo.launch(mcp_server=True)
|