Nekochu's picture
merge Kimodo-cpp into MoMask as two tabs
a8e08c2
|
Raw History Blame Contribute Delete
4.87 kB
---
title: Kimodo cpp + MoMask
emoji: 🕺
colorFrom: pink
colorTo: purple
sdk: gradio
sdk_version: 6.3.0
python_version: "3.10"
app_file: app.py
pinned: false
fullWidth: true
license: other
license_name: nvidia-open-model-license
license_link: https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/
short_description: Two CPU text-to-motion models, Kimodo GGML and MoMask ONNX
startup_duration_timeout: 1h
preload_from_hub:
- LocalAI-io/Kimodo-SOMA-RP-v1.1-GGML models/kimodo-soma-rp-v1.1-f32.gguf
- LocalAI-io/Kimodo-SOMA-SEED-v1.1-GGML models/kimodo-soma-seed-v1.1-f32.gguf
- LocalAI-io/Kimodo-G1-RP-v1-GGML models/kimodo-g1-rp-v1-f32.gguf
- LocalAI-io/Kimodo-G1-SEED-v1-GGML models/kimodo-g1-seed-v1-f32.gguf
- LocalAI-io/Llama-3-Kimodo-GGML
---
# Kimodo cpp + MoMask
Two text-to-motion models on one free CPU Space, a tab each. Both take a plain
English description and return a skeletal animation plus a BVH file for Blender.
This Space merges [WeReCooking2/Kimodo-cpp](https://huggingface.co/spaces/WeReCooking2/Kimodo-cpp)
into what was `Luminia/momask-codes`.
## Which tab to use
| | Kimodo | MoMask |
| --- | --- | --- |
| Runtime | kimodo.cpp, GGML | ONNX Runtime |
| Rigs | SOMA 30 joint human, Unitree G1 34 joint robot | 22 joint human |
| Outputs | glTF orbit preview, BVH, MP4 | MP4, BVH |
| First run on a new prompt | ~2 min, one LLM2Vec encode | ~10 s |
| Repeat prompt | sampling only | same |
MoMask is the one to reach for first because it is roughly an order of magnitude
cheaper. Kimodo is worth the wait when you want the robot rig, a longer clip, or
its markedly better motion quality.
Kimodo's cost is not the sampling, it is the text encode. It conditions on a
4096 float LLM2Vec embedding produced by a Llama 3 bundle of 35 files, and
`kmd-encode` reopens all of them per call, so one prompt costs one full pass
regardless of length. Embeddings are memoised on the prompt text, so re-rolling a
seed or changing the frame count afterwards costs nothing extra. MoMask has no
equivalent step because CLIP text encoding is inside its ONNX graph.
## Keeping two native stacks in one process
The two pipelines do not share anything, and the merge is mostly about making
sure they do not stand on each other.
**Import order is load-bearing.** matplotlib's `ft2font` is a pybind11 extension
built against the system libstdc++ and fails to initialise if a differently
linked libstdc++ is already resident. So `app.py` imports the drawing stack
first, before anything native.
**MoMask's backends are deferred.** torch, onnxruntime and CLIP each bring their
own libstdc++ and libgomp, and Kimodo dlopens `libkimodo.so` together with its
own bundled `libgomp.so.1` into the same process. `momask_core._ensure_backends()`
holds them back until the MoMask tab is actually used, so a visitor who only
generates with Kimodo never loads them at all.
**Each pipeline keeps its own weights.** Kimodo runs GGUF checkpoints, MoMask
runs its own ONNX graphs. Nothing is deduplicated or substituted across them,
which keeps any quality complaint attributable to one model.
Two vCPUs run one job at a time, so a second visitor queues. The browser shows a
queue position; an API caller does not.
## Models
Kimodo, about 1.13 GB each, all preloaded at build time so a wake costs no
download. One is resident at a time.
| Checkpoint | Rig | Joints |
| --- | --- | --- |
| SOMA RP v1.1 | `soma30` | 30 |
| SOMA SEED v1.1 | `soma30` | 30 |
| G1 RP v1 | `g1skel34` | 34 |
| G1 SEED v1 | `g1skel34` | 34 |
MoMask, about 417 MB of ONNX in this repo: CLIP text encoder 254 MB, mask
transformer 56 MB, residual transformer 55 MB, VQ-VAE decoder 46 MB, length
estimator 0.5 MB.
## Calling it from the API
Two named endpoints, `/generate` for Kimodo and `/momask_generate` for MoMask.
Read them from `/gradio_api/info` rather than guessing.
The Space is also an MCP server at `/gradio_api/mcp/sse`, so an LLM client can
call either tab as a tool.
Watch out for one thing on the streamed response. `data: null` is the heartbeat
payload, not a result and not a failure. It arrives every 15 seconds for as long
as the job is queued or running, so read the `event:` line and treat only
`complete` and `error` as terminal:
```python
event = None
for line in stream:
if line.startswith("event: "):
event = line[7:].strip()
elif line.startswith("data: ") and event in ("complete", "error"):
payload = line[6:]
break
```
`POST /gradio_api/call/generate` returns 200 with an event id whatever happens
next, so judging success by that status is misleading. The outcome is only on
the stream.
## Credits
[NVIDIA Kimodo](https://www.nvidia.com/) through
[kimodo.cpp](https://github.com/localai-org/kimodo.cpp).
[MoMask](https://github.com/EricGuo5513/momask-codes) by Chuan Guo et al.