English | 日本語
HY-Motion 1.0 generates human motion from text. Its output requires humanoid
bone mappings and a portable file before a VRM application can
retarget it. vroom writes the body rotations and root translation as an
avatar-independent
VRM Animation 1.0
(.vrma) file.
A VRMA-aware player can apply the clip to a VRM humanoid. vroom creates and
checks animation files. It does not render or preview them.
| Command | Purpose |
|---|---|
generate |
Generate one VRMA clip from an English motion prompt |
curate |
Turn sampled LINEAR VRMA into a bounded clip with selected rotations and optional closed root motion |
inspect |
Summarize a VRM or VRMA GLB |
validate |
Validate one or more VRMA files, or every VRMA file in a ZIP archive |
The GLB, VRM, and VRMA code uses the Python standard library and no runtime
dependencies. The base package runs curate, inspect, and validate. The
inference extra adds Torch and the HY-Motion stack for generate. The
generation path stays inside the Python process, where vroom writes the GLB
data.
| Data | Generated VRMA |
|---|---|
| Humanoid hierarchy | Self-contained VRM T-pose nodes for retargeting |
| Root motion | Local hips translation |
| Body motion | Local quaternion tracks for the 22 joints produced by the released HY-Motion checkpoints |
| Fingers | Omitted because the released checkpoints do not generate finger rotations |
| Face and gaze | No expression or look-at animation |
The commands below assume a Git checkout and a Bash shell.
vroom does not bundle HY-Motion code or checkpoints in its wheels and source
distributions. HY-Motion code, checkpoints, and outputs fall under the
Tencent HY-Motion 1.0 Community License,
which contains territorial and use restrictions. Read that license before you
download or run HY-Motion.
Initialize the HY-Motion submodule, fetch its Git LFS runtime assets, and install the locked inference environment:
./scripts/bootstrap_hy_motion.sh
uv sync --extra inferenceThe bootstrap script fetches the runtime statistics and skeleton assets. It does not fetch a motion checkpoint. Download the full checkpoint:
uv run --extra inference huggingface-cli download \
tencent/HY-Motion-1.0 \
--include "HY-Motion-1.0/*" \
--local-dir vendor/HY-Motion-1.0/ckpts/tencentGenerate and validate a clip:
uv run --extra inference vroom generate \
"A person takes three steps forward, waves with the right hand, and stops." \
--output output/wave.vrma
uv run vroom validate output/wave.vrmagenerate uses the full checkpoint at its default path, a four-second
duration, seed 42, and device auto-selection. The command creates the output
directory when needed. Unless both text encoders exist in the HY-Motion
checkout, the first generation downloads Qwen3-8B and CLIP-L from
Hugging Face and places them in the Hugging Face cache.
The Lite model uses the same download command as the full model. Replace
HY-Motion-1.0/* with HY-Motion-1.0-Lite/*. Inference with either model
needs substantial memory; the
upstream model table
lists hardware guidance for both checkpoints.
A wheel contains vroom, but not the HY-Motion runtime. Install the wheel with
the inference extra:
python3.12 -m pip install \
"/absolute/path/to/vroom-0.1.0-py3-none-any.whl[inference]"Clone HY-Motion in a separate directory and follow its setup instructions to fetch the Git LFS assets and a motion checkpoint. Then set both paths:
export VROOM_HY_MOTION_ROOT=/absolute/path/to/HY-Motion-1.0
export HY_MOTION_MODEL_PATH=/absolute/path/to/HY-Motion-1.0/ckpts/tencent/HY-Motion-1.0You can pass the same locations to one command with --hy-motion-root and
--model-path.
--text-models auto uses local Qwen3-8B and CLIP-L directories when both
exist. If either directory is missing, HY-Motion downloads the encoders from
Hugging Face during the first generation.
Download both encoders into the locations that auto detects:
uv run --extra inference huggingface-cli download \
Qwen/Qwen3-8B \
--local-dir vendor/HY-Motion-1.0/ckpts/Qwen3-8B
uv run --extra inference huggingface-cli download \
openai/clip-vit-large-patch14 \
model.safetensors \
config.json \
merges.txt \
special_tokens_map.json \
tokenizer.json \
tokenizer_config.json \
vocab.json \
--local-dir vendor/HY-Motion-1.0/ckpts/clip-vit-large-patch14Use --text-models local to require those directories and fail before
inference if either one is missing. Git ignores downloaded model weights. The
inference extra includes Hugging Face's Xet transfer helper for large weight
shards.
The selected checkpoint controls the accepted duration range. The released
checkpoints accept about 0.667 to 12 seconds and produce 30 fps motion.
vroom rejects a duration outside the checkpoint's range.
HY-Motion recommends an English prompt shorter than 60 words. Describe the torso and limb movement rather than appearance, scenery, or camera movement. Upstream's lower-memory guidance uses one seed, a prompt shorter than 30 words, and a motion shorter than five seconds.
Use explicit options when you need repeatable output or a fixed device:
uv run --extra inference vroom generate \
"A person crouches, stands, and raises both arms." \
--duration 3 \
--seed 7 \
--device mps \
--output output/raise-arms.vrmaRun uv run --extra inference vroom generate --help for the complete option
list, including CFG scale, ODE inference steps, text-encoder placement, model
paths, and ground alignment.
Python callers that need several deterministic variants of one prompt can use
HyMotionBackend.generate_variants. It accepts 1 to 16 unique seeds, encodes
a prompt batch with at most 16 entries, and generates every seed after one
model load. generate provides the single-seed API.
| Target | Arguments | Behavior |
|---|---|---|
| Auto | --device auto |
Chooses CUDA, then MPS, then CPU |
| Apple Silicon | --device mps |
Runs diffusion on MPS and text encoding on CPU |
| Apple Silicon, all MPS | --device mps --text-device same |
Attempts text encoding on MPS |
| NVIDIA GPU | --device cuda:0 |
Uses the first CUDA device |
| CPU | --device cpu |
Runs diffusion and text encoding on CPU |
HY-Motion's upstream device helper selects CUDA or CPU. vroom adds MPS
device discovery, creates MPS noise from a CPU seed, enables PyTorch's MPS CPU
fallback, and places the Qwen encoder on CPU by default. Throughput depends on
the installed PyTorch and macOS versions.
An embedding application may import Torch before vroom can enable the MPS
fallback. In that case, set PYTORCH_ENABLE_MPS_FALLBACK=1 before starting
the process.
The pinned PyPI Torch build pulls CUDA dependencies on Linux x86-64. The committed lock does not define a Windows CUDA environment.
curate accepts sampled LINEAR VRMA that passes the structural checks and
writes a deterministic clip with selected rotation tracks. By default it keeps
the original rotation-only behavior and removes root translation:
uv run vroom curate output/wave.vrma \
--output output/wave-upper-body.vrma \
--bones spine chest upperChest neck head leftUpperArm rightUpperArm \
--duration 2.0 \
--fade-in 0.25 \
--fade-out 0.25 \
--rotation-gain 1.25The default command removes root translation and unselected tracks. It
expresses each selected rotation as inverse(first) * frame, keeps quaternion
signs on the shortest path, and retimes the retained samples. Smoothstep fades
place the first and last keyframes at the identity rotation.
An amplified or unfaded winding track may lack a continuous quaternion path back to exact identity. The command then fails before writing output and asks for a longer fade-out or a narrower source crop.
| Option | Effect |
|---|---|
--bones |
Keeps the listed humanoid rotation tracks |
--start-frame, --end-frame |
Selects a source range; the end frame is inclusive |
--duration |
Retimes the selected main samples while preserving their relative spacing; a return tail adds time |
--fade-in, --fade-out |
Applies smoothstep fades at the endpoints |
--rotation-gain |
Scales first-frame-relative rotation from 0 through 4; the default is 1 |
--rotation-projection |
Keeps all rotation with none, or keeps local-axis twist with twist-x, twist-y, or twist-z |
--bone-rotation-gain BONE=GAIN |
Overrides gain for one selected bone; repeat for different bones |
--bone-rotation-projection BONE=PROJECTION |
Overrides projection for one selected bone; repeat for different bones |
--hips-translation |
Uses the default strip mode or opt-in relative-return mode |
--hips-return-duration |
Sets the return-tail duration required by relative-return |
--name |
Sets the output animation name |
Rotation gain uses quaternion interpolation before the fades. A value above
1 amplifies subtle motion. Twist projection uses swing-twist decomposition,
so --rotation-projection twist-y can keep yaw and remove the other head and
neck rotation without an Euler-angle conversion.
Per-bone options override the global gain or projection only for their named selected bone. This lets one curation retain a full-body bone set while, for example, projecting head motion without projecting the hips, legs, feet, or arms:
uv run vroom curate output/bow.vrma \
--output output/bow-full-body.vrma \
--bones hips spine chest upperChest neck head \
leftUpperLeg rightUpperLeg leftLowerLeg rightLowerLeg \
leftFoot rightFoot leftToes rightToes \
leftUpperArm rightUpperArm leftLowerArm rightLowerArm \
--duration 2.0 \
--fade-in 0 \
--fade-out 0 \
--bone-rotation-projection head=twist-x \
--bone-rotation-gain leftUpperLeg=0.9 \
--bone-rotation-gain rightUpperLeg=0.9 \
--hips-translation relative-return \
--hips-return-duration 0.5relative-return retains each cropped hips sample as
rest + (frame - first). It does not fade or shorten the main root trajectory.
If the first LINEAR segment leaves rest, curation prepends one rest hold with
the same duration as that segment. After the final main sample, it continues
the incoming velocity and samples a cubic Hermite return to rest with zero
terminal velocity. The last two root samples are exact rest, and every
selected rotation receives a corresponding smooth Hermite return whose last
two samples are exact identity. The output duration is therefore the requested
main --duration, plus the optional start hold, plus
--hips-return-duration.
The return tail never attenuates or removes a main sample. A root return may
continue beyond the main trajectory by at most the larger of 25 percent or
5 cm, and the entire curated root trajectory must remain within 0.5 m of rest.
Curation rejects motion beyond those caps. Rotation returns may pass the main
peak but must remain finite and on a continuous shortest quaternion path to
identity. Kokage or another consumer can enforce tighter per-asset bounds from
the measured output metadata. Set --fade-out 0 when the return tail, rather
than the legacy main-clip fade, should bring rotations back to identity.
The output retains the humanoid nodes needed for retargeting. It records the
source SHA-256, source byte length, and transform settings under
asset.extras.vroomCuration. Metadata version 3 records the effective policy
for every selected bone and the root mode, basis, hold, return duration, sample
count, interpolation, and endpoint contract.
The version 3 top-level policy and bound fields have this closed vocabulary:
{
"perBonePolicies": [
{
"bone": "leftUpperLeg",
"rotationGain": 0.9,
"rotationProjection": "none"
}
],
"hipsTranslationPolicy": "relative-return",
"mainDurationMicroseconds": 2000000,
"returnDurationMicroseconds": 500000,
"durationMicroseconds": 2533333,
"maximumRootDisplacementMeters": 0.03999999910593033,
"maximumRootVelocityMetersPerSecond": 0.11374999582767487
}durationMicroseconds is measured from the final encoded output and includes
the optional start hold. Root displacement and segment velocity maxima are
also measured from the final float32 output and rounded upward to a float32
bound. Strip mode writes hipsTranslationPolicy: "none", a zero return
duration, and zero root bounds. Hermite construction details remain under
transform.hipsTranslationReturn.
Python callers use the same closed policies. Global rotation settings remain the fallback for selected bones that have no override:
from vroom import BoneCurationPolicy, HipsTranslationPolicy, curate_vrma
curated = curate_vrma(
"output/bow.vrma",
bones=(
"hips",
"spine",
"leftUpperLeg",
"rightUpperLeg",
"leftFoot",
"rightFoot",
),
duration=2.0,
fade_out=0.0,
bone_policies=(
BoneCurationPolicy("leftUpperLeg", rotation_gain=0.9),
BoneCurationPolicy("rightUpperLeg", rotation_gain=0.9),
),
hips_translation_policy=HipsTranslationPolicy(
mode="relative-return",
return_duration=0.5,
),
)BoneCurationPolicy names must be unique members of bones.
HipsTranslationPolicy() defaults to strip, so existing API calls keep
removing translation and using the original endpoint-fade behavior.
curate rejects oversized input before it writes output:
| Resource | Limit |
|---|---|
| Source file | 64 MiB |
| Nodes | 512 |
| Samples per accessor | 18,000 |
| Decoded numeric accessor components | 1,000,000 |
| Animation channels or samplers | 256 |
| Curated samples | 18,000 |
| Curated duration | 3,600 seconds |
relative-return root displacement |
0.5 m from rest |
| Serialized output | 16 MiB |
uv run vroom inspect path/to/avatar.vrm
uv run vroom inspect output/wave.vrma --json
uv run vroom validate output/wave.vrma
uv run vroom validate path/to/motion-pack.zipThe validator checks the GLB container, accessors, keyframe times, quaternion
normalization, humanoid mappings, and the humanoid animation restrictions
implemented by vroom. Humanoid bones cannot carry scale tracks. hips is
the sole humanoid bone that can carry a translation track.
When vroom reads a VRM 0.x avatar, it maps the legacy thumb bone names to VRM
1.0 semantics.
HY-Motion stores each rotation as two interleaved columns of a rotation
matrix. vroom applies HY-Motion's Gram-Schmidt conversion, converts the
result to glTF's [x, y, z, w] quaternion order, and preserves quaternion
sign continuity over time.
HY-Motion's wooden mannequin skeleton uses the VRM 1.0 basis:
right-handed, meters, +Y up, +Z forward, and anatomical left on +X. VRMA body
motion needs rotations and root translation, so vroom disables the
memory-intensive mesh-skinning step during inference. A forward-kinematics
pass accounts for the source rest pose's floor offset, places the motion on
the floor, and writes each keyframe's local hips translation.
The fast test suite needs no Torch installation or model weights:
uv sync --group dev
uv run ruff check src tests
uv run pytestAfter you download the models, run the opt-in inference smoke tests:
HY_MOTION_MODEL_PATH=vendor/HY-Motion-1.0/ckpts/tencent/HY-Motion-1.0 \
uv run --extra inference pytest -m integrationSet HY_MOTION_TEST_DEVICE=mps or HY_MOTION_TEST_DEVICE=cuda:0 to select an
accelerator for the end-to-end generation test.
AliciaSolid_vrm-0.51.vrm and VRMA_MotionPack.zip are optional local
fixtures with redistribution restrictions. Git ignores them, packages exclude
them, and the related tests skip when the files are absent.