Skip to content

Commit 6f494af

Browse files
jarcherNVgtong-nvaidanfnv
committed
Add unified inference runtime and demo API
Introduce shared runtime/session contracts, demo specs, run contexts, session drivers, step pipelines, model input providers, output sinks, metrics, benchmark hooks, and compatibility wrappers for the old runner surface. Migrate OmniDreams, LingBot, and text-to-video demo paths onto the unified runtime across replay, null, MP4, WebRTC, and realtime modes. Move model-specific input preparation into providers, including replay traces, keyboard-driven controls, text events, WebRTC input catch-up, and Ludus replay inputs. Unify WebRTC viewer and serving plumbing around shared session management, browser event handling, realtime input sources, session validation, transport edges, and packaged viewer resources while keeping legacy launch paths available for later cleanup. Add focused CPU/GPU tests, demo-runtime CI workflows, artifact validation, README commands, and migration-plan tracking for remaining legacy runner, mapping, and WebRTC compatibility debt. Co-authored-by: Gangzheng Tong <gtong@nvidia.com> Co-authored-by: Aidan Foster <aidanf@nvidia.com>
1 parent 996417e commit 6f494af

228 files changed

Lines changed: 44532 additions & 10197 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
name: LingBot Demo Runtime
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- "pull-request/[0-9]+"
8+
paths:
9+
- ".github/workflows/lingbot-demo-runtime.yml"
10+
- "pyproject.toml"
11+
- "uv.lock"
12+
- "flashdreams/pyproject.toml"
13+
- "flashdreams/flashdreams/core/**"
14+
- "flashdreams/flashdreams/infra/**"
15+
- "flashdreams/flashdreams/runtime/**"
16+
- "flashdreams/flashdreams/serving/**"
17+
- "flashdreams/flashdreams/recipes/taehv/**"
18+
- "flashdreams/flashdreams/recipes/wan/**"
19+
- "flashdreams/tests/test_webrtc_*.py"
20+
- "integrations/lingbot/**"
21+
workflow_dispatch:
22+
23+
permissions:
24+
contents: read
25+
26+
jobs:
27+
demo-runtime:
28+
name: null and MP4
29+
runs-on: linux-amd64-gpu-rtxpro6000-latest-2
30+
timeout-minutes: 180
31+
defaults:
32+
run:
33+
shell: bash
34+
container:
35+
image: nvidia/cuda:13.2.1-cudnn-devel-ubuntu24.04
36+
options: --gpus all
37+
env:
38+
UV_PROJECT_ENVIRONMENT: /tmp/flashdreams-venv
39+
UV_LINK_MODE: copy
40+
UV_PYTHON: "3.12"
41+
MAX_JOBS: 8
42+
HF_HOME: /tmp/huggingface
43+
FLASHDREAMS_CACHE_DIR: /tmp/flashdreams-cache
44+
# Streaming avoids the old duplicate merged safetensors cache. CI uses
45+
# the generic reserve so the model-specific 200 GiB first-run budget does
46+
# not reject runners that have enough room for the streamed shards.
47+
FLASHDREAMS_MIN_CACHE_FREE_GB: "20"
48+
ARTIFACT_DIR: artifacts/lingbot_demo_runtime
49+
PRESET_ID: lingbot-world-v2-14b-causal-fast-taehv-window15-sink3
50+
BLOCKS: "5"
51+
FPS: "16"
52+
WIDTH: "640"
53+
HEIGHT: "352"
54+
EXPECTED_WIDTH: "640"
55+
EXPECTED_HEIGHT: "352"
56+
MIN_DURATION_SECONDS: "3"
57+
MAX_DURATION_SECONDS: "5"
58+
steps:
59+
- name: Detect GPU architecture
60+
id: gpu-arch
61+
run: |
62+
nvidia-smi
63+
compute_cap=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d '[:space:]')
64+
arch=$(echo "${compute_cap}" | tr -d '.')
65+
echo "arch=${arch}" >> "$GITHUB_OUTPUT"
66+
echo "Detected GPU compute capability: ${compute_cap} -> sm_${arch}"
67+
68+
- name: Checkout
69+
uses: actions/checkout@v4
70+
71+
- name: Install system dependencies
72+
run: |
73+
apt-get update -qq
74+
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
75+
python3 python3-dev python3-venv \
76+
ffmpeg \
77+
gcc g++ ninja-build \
78+
libnccl-dev \
79+
curl git ca-certificates jq unzip
80+
rm -rf /var/lib/apt/lists/*
81+
82+
- name: Setup proxy cache
83+
uses: nv-gha-runners/setup-proxy-cache@main
84+
85+
- name: Setup uv
86+
uses: astral-sh/setup-uv@v6
87+
with:
88+
enable-cache: true
89+
cache-suffix: "lingbot-demo-runtime-sm${{ steps.gpu-arch.outputs.arch }}"
90+
prune-cache: false
91+
92+
- name: Install dependencies
93+
env:
94+
NVTE_CUDA_ARCHS: ${{ steps.gpu-arch.outputs.arch }}
95+
run: |
96+
uv venv --clear
97+
uv sync --locked --package flashdreams-lingbot --no-dev
98+
99+
- name: Verify GPU availability
100+
run: nvidia-smi
101+
102+
- name: Run LingBot demo modes
103+
env:
104+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
105+
run: |
106+
set -uo pipefail
107+
108+
log_dir="${ARTIFACT_DIR}/logs"
109+
output_dir="${ARTIFACT_DIR}/outputs"
110+
summary="${ARTIFACT_DIR}/summary.md"
111+
status_file="${ARTIFACT_DIR}/command-status.env"
112+
mkdir -p "${log_dir}" "${output_dir}"
113+
: > "${status_file}"
114+
115+
ldemo() {
116+
uv run --no-sync --package flashdreams-lingbot lingbot-demo "$@"
117+
}
118+
119+
run_demo() {
120+
local name="$1"
121+
shift
122+
local log="${log_dir}/${name}.log"
123+
124+
{
125+
printf '$'
126+
printf ' %q' "$@"
127+
printf '\n\n'
128+
"$@"
129+
} 2>&1 | tee "${log}"
130+
131+
local rc="${PIPESTATUS[0]}"
132+
echo "${name}=${rc}" >> "${status_file}"
133+
echo "${name} exit code: ${rc}" | tee -a "${summary}"
134+
return 0
135+
}
136+
137+
{
138+
echo "# LingBot Demo Runtime CI"
139+
echo
140+
echo "| Mode | Expected blocks | Output |"
141+
echo "| --- | ---: | --- |"
142+
echo "| null | ${BLOCKS} | none |"
143+
echo "| MP4 | ${BLOCKS} | lingbot-demo-replay.mp4 |"
144+
echo
145+
echo "## Command Status"
146+
} > "${summary}"
147+
148+
run_demo null \
149+
ldemo replay \
150+
--device cuda:0 \
151+
--preset-id "${PRESET_ID}" \
152+
--example-idx 0 \
153+
--total-blocks "${BLOCKS}" \
154+
--fps "${FPS}" \
155+
--pixel-height "${HEIGHT}" \
156+
--pixel-width "${WIDTH}" \
157+
--output-mode null
158+
159+
run_demo mp4 \
160+
ldemo replay \
161+
--device cuda:0 \
162+
--preset-id "${PRESET_ID}" \
163+
--example-idx 0 \
164+
--total-blocks "${BLOCKS}" \
165+
--fps "${FPS}" \
166+
--pixel-height "${HEIGHT}" \
167+
--pixel-width "${WIDTH}" \
168+
--output "${output_dir}/lingbot-demo-replay.mp4"
169+
170+
- name: Validate LingBot demo artifacts
171+
run: |
172+
set -euo pipefail
173+
174+
log_dir="${ARTIFACT_DIR}/logs"
175+
output_dir="${ARTIFACT_DIR}/outputs"
176+
probe_dir="${ARTIFACT_DIR}/ffprobe"
177+
summary="${ARTIFACT_DIR}/summary.md"
178+
status_file="${ARTIFACT_DIR}/command-status.env"
179+
mkdir -p "${probe_dir}"
180+
181+
status_of() {
182+
awk -F= -v name="$1" '$1 == name { print $2 }' "${status_file}"
183+
}
184+
185+
assert_exit_zero() {
186+
local name="$1"
187+
local rc
188+
rc="$(status_of "${name}")"
189+
if [ "${rc}" != "0" ]; then
190+
echo "${name} command failed with exit code ${rc}" >&2
191+
exit 1
192+
fi
193+
}
194+
195+
assert_clean_log() {
196+
local log="$1"
197+
if grep -En "ERROR|Traceback|Exception|status=failed|Run failed|failed run" "${log}"; then
198+
echo "failure marker found in ${log}" >&2
199+
exit 1
200+
fi
201+
}
202+
203+
assert_log_contains() {
204+
local log="$1"
205+
local pattern="$2"
206+
local label="$3"
207+
if ! grep -Eq "${pattern}" "${log}"; then
208+
echo "expected ${label} in ${log}" >&2
209+
exit 1
210+
fi
211+
}
212+
213+
assert_log_not_contains() {
214+
local log="$1"
215+
local pattern="$2"
216+
local label="$3"
217+
if grep -Eq "${pattern}" "${log}"; then
218+
echo "unexpected ${label} in ${log}" >&2
219+
exit 1
220+
fi
221+
}
222+
223+
validate_mp4() {
224+
local mode="$1"
225+
local mp4="$2"
226+
local metadata="${probe_dir}/${mode}.json"
227+
228+
if [ ! -s "${mp4}" ]; then
229+
echo "expected non-empty MP4 at ${mp4}" >&2
230+
exit 1
231+
fi
232+
233+
ffprobe \
234+
-v error \
235+
-select_streams v:0 \
236+
-show_entries stream=width,height,r_frame_rate,avg_frame_rate,nb_frames,duration:format=duration \
237+
-of json \
238+
"${mp4}" > "${metadata}"
239+
240+
local stream_count width height duration
241+
stream_count="$(jq '.streams | length' "${metadata}")"
242+
width="$(jq -r '.streams[0].width // ""' "${metadata}")"
243+
height="$(jq -r '.streams[0].height // ""' "${metadata}")"
244+
duration="$(jq -r '.streams[0].duration // .format.duration // "0"' "${metadata}")"
245+
246+
if [ "${stream_count}" -lt 1 ]; then
247+
echo "ffprobe found no video stream in ${mp4}" >&2
248+
exit 1
249+
fi
250+
251+
if [ "${width}" != "${EXPECTED_WIDTH}" ] || [ "${height}" != "${EXPECTED_HEIGHT}" ]; then
252+
echo "unexpected ${mode} resolution ${width}x${height}; expected ${EXPECTED_WIDTH}x${EXPECTED_HEIGHT}" >&2
253+
exit 1
254+
fi
255+
256+
awk \
257+
-v duration="${duration}" \
258+
-v min_duration="${MIN_DURATION_SECONDS}" \
259+
-v max_duration="${MAX_DURATION_SECONDS}" \
260+
'BEGIN {
261+
if ((duration + 0) < min_duration || (duration + 0) > max_duration) {
262+
exit 1
263+
}
264+
}' || {
265+
echo "unexpected ${mode} duration ${duration}s; expected ${MIN_DURATION_SECONDS}-${MAX_DURATION_SECONDS}s" >&2
266+
exit 1
267+
}
268+
}
269+
270+
null_log="${log_dir}/null.log"
271+
mp4_log="${log_dir}/mp4.log"
272+
273+
assert_exit_zero null
274+
assert_exit_zero mp4
275+
276+
assert_clean_log "${null_log}"
277+
assert_clean_log "${mp4_log}"
278+
279+
assert_log_contains "${null_log}" "Streaming sharded safetensors checkpoint" "null streaming checkpoint load"
280+
assert_log_contains "${null_log}" "Finished streaming .* safetensors shard" "null streamed checkpoint completion"
281+
assert_log_contains "${null_log}" "AR 4 encode" "null final AR block"
282+
assert_log_contains "${null_log}" "Lingbot runtime step 4 frames=" "null final replay step"
283+
assert_log_not_contains "${null_log}" "Loading merged sharded checkpoint from cache|Saved merged sharded checkpoint" "merged safetensors cache usage"
284+
285+
assert_log_contains "${mp4_log}" "Streaming sharded safetensors checkpoint" "MP4 streaming checkpoint load"
286+
assert_log_contains "${mp4_log}" "Finished streaming .* safetensors shard" "MP4 streamed checkpoint completion"
287+
assert_log_contains "${mp4_log}" "AR 4 encode" "MP4 final AR block"
288+
assert_log_contains "${mp4_log}" "Lingbot runtime step 4 frames=" "MP4 final replay step"
289+
assert_log_not_contains "${mp4_log}" "Loading merged sharded checkpoint from cache|Saved merged sharded checkpoint" "merged safetensors cache usage"
290+
291+
validate_mp4 mp4 "${output_dir}/lingbot-demo-replay.mp4"
292+
293+
{
294+
echo
295+
echo "## Validation"
296+
echo
297+
echo "- Null and MP4 commands exited zero."
298+
echo "- Logs contained expected streaming-checkpoint and final AR-step markers."
299+
echo "- Logs did not contain merged-safetensors cache markers."
300+
echo "- MP4 output was non-empty and passed ffprobe stream checks."
301+
} >> "${summary}"
302+
303+
- name: Trim uv cache for upload
304+
if: always()
305+
run: |
306+
cache_dir="${UV_CACHE_DIR:-/github/home/.cache/uv}"
307+
echo "=== Cache size before trim ==="
308+
du -sh "${cache_dir}" || true
309+
du -sh "${cache_dir}"/*/ 2>/dev/null || true
310+
311+
rm -rf "${cache_dir}/wheels-v6"
312+
rm -rf "${cache_dir}/archive-v0"
313+
314+
find "${cache_dir}/git-v0/checkouts" \
315+
\( -name "build" -o -name "*.egg-info" -o -name "__pycache__" \) \
316+
-type d -exec rm -rf {} + 2>/dev/null || true
317+
318+
rm -rf "${cache_dir}/sdists-v9/editable"
319+
320+
echo ""
321+
echo "=== Cache size after trim ==="
322+
du -sh "${cache_dir}" || true
323+
du -sh "${cache_dir}"/*/ 2>/dev/null || true
324+
echo ""
325+
echo "=== Cached built wheels (sdists-v9) ==="
326+
find "${cache_dir}/sdists-v9" -name "*.whl" -exec ls -lh {} \; 2>/dev/null || true
327+
328+
- name: Upload demo runtime artifacts
329+
if: always()
330+
uses: actions/upload-artifact@v4
331+
with:
332+
name: lingbot-demo-runtime
333+
path: ${{ env.ARTIFACT_DIR }}
334+
if-no-files-found: ignore

0 commit comments

Comments
 (0)