diff --git a/README.md b/README.md index d6648ef..aa0b66e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ supervises Vili. - **Problem:** Personas and games need generated motion, but cold per-request model loads cost minutes. One daemon owns job identity, worker lifecycle, the thread budget and artifact references, so callers touch no model state. - **Entry points:** `scripts/vili-daemon.mjs` (the daemon; `npm start`, `npm run health`, `npm test`), `scripts/kimodo-resident-worker.py` (the worker it spawns), `scripts/kimodo-text-encode.py` (the one-shot encoder child), `POST /motion/generate` (see Motion Generation), `docs/verse-service-contract.md` and `docs/motion-pipeline-target.md`. - **Live:** the process tree below, run by hand: `POST /motion/generate`, `GET /health`, `GET /operator-state`, and the CultCache service store. It produces `motion.npz` and optional BVH on the SOMA skeleton; there is no FBX export. -- **Planned:** admission to Idunn on Yggdrasil with a locked virtual environment, then FBX export and a text-prompt-to-Unity-clip path with no hand steps (`docs/motion-pipeline-target.md`). The Raven instance still runs a retired branch until the admission cut tears it down. +- **Planned:** admission to Idunn on Yggdrasil with a locked virtual environment, then FBX export and a text-prompt-to-Unity-clip path with no hand steps (`docs/motion-pipeline-target.md`). ## Authority @@ -34,7 +34,7 @@ One Linux CPU process tree: ```text vili-daemon.mjs job intake, worker lifecycle, thread cap, nice 10 kimodo-resident-worker.py resident Kimodo model, one generation at a time - kimodo-text-encode.py one child per request with cache misses, assembly B on CPU + kimodo-text-encode.py one child per request with cache misses, on CPU ``` Setup: `git submodule update --init`; in `vendor/CultLib`, `npm ci --ignore-scripts` and @@ -59,7 +59,7 @@ The daemon starts the worker on the first job, on a port the worker binds itself and reports in its ready line, and respawns it after an exit. The worker runs at nice 10 with `torch` limited to `--threads` (also `OMP_NUM_THREADS` and `MKL_NUM_THREADS`), so a shared host keeps its other services. Its log is -`/logs/worker.log`. SIGTERM stops the whole tree. +`/logs/worker.log`. The daemon owns the whole worker process group: it is killed when the worker exits for any reason, on SIGTERM or SIGINT, and at start-up if a crashed daemon left one (`/worker.pid`). A service unit should also own the tree (systemd `KillMode=control-group`). `POST /motion/generate` refuses any POST that carries an `Origin` or is not `application/json` with `403 cross-site`; no route answers a preflight. @@ -143,16 +143,18 @@ worker answers Kimodo's encoder call from a CultCache store of float32 vectors, `/embeddings/text-embeddings.cc` (`gamecult.vili.text_embedding.v0`, through the cultcache-py in `vendor/CultLib`). A vector's key is `sha256(encoderIdentity + newline + text)`. The identity names the three Hugging -Face snapshot revisions, the encoder assembly, the device, dtype, the torch, +Face snapshot revisions, the encoder assembly and prompt form, the device, dtype, the torch, transformers, peft and accelerate versions, the Kimodo commit and the sha256 of `kimodo-text-encode.py`, so a changed input misses instead of serving a stale vector. Thread count is provenance, recorded in the worker identity, not identity. -The encoder is assembly B: the Llama 3 base with the MNTP adapter merged, then the -supervised adapter, in bfloat16 on CPU. Kimodo's own loader never applies the MNTP -weights under the pinned transformers; the A/B comparison is in -`docs/research/vili-on-yggdrasil-cpu-2026-10.md`, which also holds the measured -timings. Kimodo normalizes the prompt before encoding (`A person shrugs sadly.`), +The encoder is the Llama 3 base with the MNTP adapter merged, then the supervised +adapter, in bfloat16 on CPU, and `kimodo-text-encode.py` wraps every prompt in +Llama 3 Instruct's chat header itself (`text_encoding.chat_header`, pooled after +LLM2Vec's query marker), as Kimodo's LLM2Vec does when loaded from the repo ids. The identity names that prompt form. +A fresh child vector matches Kimodo's own encoder from the repo ids +(`test_text_encoding.ReferenceVector`, run on Yggdrasil). The measured timings are +in `docs/research/vili-on-yggdrasil-cpu-2026-10.md`. Kimodo normalizes the prompt before encoding (`A person shrugs sadly.`), and the cache keys on that text. A request's misses go to one transient child that loads the encoder, encodes, writes its answer and exits, so the OS reclaims its RAM; hits never touch it. Each job carries the encoder record (hits, misses, diff --git a/docs/verse-service-contract.md b/docs/verse-service-contract.md index 25bb563..fb5edb9 100644 --- a/docs/verse-service-contract.md +++ b/docs/verse-service-contract.md @@ -2,43 +2,29 @@ ## Objective -Vili turns Persona performance intent into animation work. The near-term body is -Kimodo on Raven; the durable contract is a service surface that can accept a -gesture/action prompt, run or queue motion synthesis, and publish inspectable -state for Odin and Idunn. - -Vili is an animation service, not a per-request CLI wrapper. Its Kimodo backend -is a resident worker container that owns model lifetime for generation requests. +Vili turns Persona performance intent into animation work. It is an animation +service, not a per-request CLI wrapper: one daemon accepts a gesture or action +prompt, owns a resident Kimodo worker on CPU, and publishes inspectable state +for Odin and Idunn. The request fields and the runtime flags are in `README.md` +(Motion Generation, Local Runtime); this page holds the contract around them. ## CultMesh Verses -- `vili.service`: service identity, provider advertisement, health, and runtime. +- `vili.service`: service identity, health, and runtime. - `vili.animation`: animation jobs, prompts, backend choices, and artifact refs. -- `vili.operator`: daemon status, backend readiness, and Eve/CultUI surface. +- `vili.operator`: daemon status and worker readiness, as typed state. Operator + surfaces wait for Thing 2. ## Witnesses -- `.vili/vili.service.cc`: Vili-owned CultCache store for provider - advertisement, operator state, Eve surface, command boundary, and transport - profile records. -- `.vili/provider-advertisement.json`: compatibility export derived from the - provider advertisement record. -- `.vili/operator-state.json`: compatibility export derived from daemon health - and backend readiness. -- `.vili/eve-operator-surface.json`: compatibility export of the current - operator surface. -- `.vili/command-boundary.json`: compatibility export of Vili command - authority. -- `.vili/transport-profile.json`: compatibility export of Vili transport - state. +- `.vili/vili.service.cc`: the daemon-owned CultCache store for operator state, + the command boundary and the transport profile. - `.vili/jobs/{jobId}.json`: accepted animation job records. - `.vili/artifacts/{jobId}/`: generated motion outputs when generation succeeds. -- `vili-kimodo-worker`: resident Docker container exposing the loaded Kimodo - model to Vili inside Raven's `Ubuntu-24.04` WSL runtime. - -JSON is a compatibility witness. Vili's service/operator/command/transport -truth is the daemon-owned `.cc` store; animation job schemas may still be -promoted from JSON once that request/receipt shape stabilizes. +- `.vili/embeddings/text-embeddings.cc`: the worker's text-embedding cache. +- `.vili/worker.pid`: the process group of the running worker, so a restarted + daemon can find and kill a worker its crashed predecessor left behind. +- `.vili/logs/worker.log`: the worker's stdout and stderr. ## Command Boundary @@ -47,112 +33,33 @@ supported Kimodo generation controls to the resident worker. Vili owns job identity, request persistence, worker lifecycle, timeout handling, and the artifact stem. Kimodo owns synthesis and model-specific export. -```json -{ - "prompt": "a person gives a small friendly wave", - "duration": 1, - "diffusionSteps": 20, - "seed": 123, - "bvh": true -} -``` - -Kimodo metadata can be sent directly when callers need multi-segment or -multi-sample generation: - -```json -{ - "meta": { - "texts": [ - "a person gives a small friendly wave", - "a person takes one careful step" - ], - "durations": [0.5, 0.5], - "num_samples": 1, - "diffusion_steps": 20 - }, - "model": "Kimodo-SOMA-RP-v1.1", - "numTransitionFrames": 3, - "cfgType": "regular", - "cfgWeight": [1.5], - "seed": 6101, - "bvh": true, - "bvhStandardTpose": true, - "saveExampleDir": true -} -``` - -Request fields accepted by Vili: - -- `prompt` / `utterance` plus `duration` / `durationSeconds` -- `texts` plus `durations`, or a full `meta` object -- `inputFolder` / `input_folder` for a container-visible Kimodo input folder - containing `meta.json` and optional `constraints.json` -- `model`; the daemon keeps one resident model loaded, so a model change - restarts `vili-kimodo-worker` -- `diffusionSteps` / `diffusion_steps` -- `numSamples` / `num_samples` -- `numTransitionFrames` / `num_transition_frames` -- `constraints` as an object/list, or `constraints`, `constraintsPath`, or - `constraints_path` as a container-visible path -- `seed` -- `cfg`, `cfgType` / `cfg_type`, `cfgWeight` / `cfg_weight` -- `bvh`, `bvhStandardTpose` / `bvh_standard_tpose` -- `noPostprocess` / `no_postprocess` -- `saveExampleDir` / `save_example_dir` -- `timeoutMs`, `workerTimeoutMs` - Vili does not accept arbitrary `output` paths from callers. Every generation -writes beneath `.vili/artifacts/{jobId}/`, with the worker receiving -`/outputs/{jobId}/motion`. The job record is the authority for artifact refs. -Model-specific exports are preserved: SOMA writes NPZ and optional BVH, SMPLX -also writes AMASS NPZ, and G1 also writes CSV. +writes beneath `.vili/artifacts/{jobId}/`, and the job record is the authority +for artifact refs. SOMA writes NPZ and optional BVH, SMPLX also writes AMASS NPZ, +and G1 also writes CSV. -The daemon may reject or defer requests when: +A POST that carries an `Origin` header or is not `application/json` is refused +`403 cross-site` before its body is read. -- Kimodo image is unavailable. -- Docker/GPU backend is unhealthy. -- Hugging Face token is absent and the selected backend requires gated model - access. -- The resident Kimodo worker cannot start or does not become healthy before the - request timeout. -- The request omits concrete prompt, metadata, batch text, or input-folder - intent. -- Batch `texts` are provided without matching `durations`. +The daemon refuses or fails a request when: -## Credential State +- the models root, Kimodo commit or Python environment id is not configured + (`503 worker-unconfigured`, before anything spawns); +- the worker cannot start or does not become healthy in time (a `502` job); +- the request omits concrete prompt, metadata, batch text, or input-folder + intent; +- batch `texts` are provided without matching `durations`. -Raven has a Hugging Face token installed in the `Ubuntu-24.04` WSL cache, and -Vili mounts it into Kimodo containers read-only. Verified on `2026-06-12`: a -Kimodo container launched by Raven could read gated metadata for -`meta-llama/Meta-Llama-3-8B-Instruct`. +## Worker Boundary -## Kimodo Worker Boundary +The daemon spawns the resident worker as a child on the first job, in its own +process group, at nice 10, with a closed environment and the thread budget +`--threads`. The worker binds its own port and reports it in a ready line. The +daemon owns the group: it kills the whole group when the worker exits for any +reason (so no encoder child survives), on SIGTERM or SIGINT, and at start-up for +a group left by a crashed daemon. Under a service manager, the unit should also +own the tree from outside (systemd `KillMode=control-group`). -The resident worker is started by Vili as Docker container `vili-kimodo-worker`. -Because Raven's Docker Engine lives inside WSL, Vili also starts a minimal WSL -keepalive process recorded at `/tmp/vili-wsl-keepalive.pid`; it owns no service -truth and exists only to keep the Docker substrate alive while the resident -worker holds the model. - -It mounts: - -- `/root/.cache/huggingface` for model/token access. -- `E:\Projects\Vili\scripts\kimodo-resident-worker.py` as the worker program. -- `E:\Projects\Vili\.vili\artifacts` as `/outputs` for generated artifacts. - -Inside the worker, Kimodo owns synthesis. Vili owns worker lifecycle, -request/response persistence, timeout handling, and artifact references. Direct -user requests, programmatic requests, and smoke-started warmup all use the same -resident worker health and generation path. - -Observed on `2026-06-12`: first worker model load took about 100 seconds. Once -resident, five 1 second / 20 step service-path generations averaged about -54.4 seconds per clip, or about `0.0184x` realtime. - -## First Runtime Invariant - -Health checks must be cheap. They may verify process, port, backend metadata, -and cached readiness, but they must not launch a Kimodo generation job or start -model load. Smoke checks may start the resident worker because their purpose is -backend readiness, not cheap liveness. +Inside the worker, Kimodo owns synthesis and a one-shot encoder child owns +text encoding on a cache miss. Health checks must be cheap: `/health` reads the +daemon's own worker state and never starts a model load or a generation.