Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</div>

<p align="center">
| <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo.exe"><b>Download for Windows</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Mac-arm64.dmg"><b>Download for macOS</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Linux-x86_64.AppImage"><b>Download for Linux</b></a> | <a href="https://freevideo-community.pages.dev/#gallery"><b>Gallery</b></a> | <a href="https://discord.gg/MsA277cJzZ"><b>Discord</b></a> | <a href="https://freevideo-community.pages.dev/qq"><b>QQ Group</b></a> | <a href="https://freevideo-community.pages.dev/wechat"><b>WeChat Group</b></a> |
| <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo.exe"><b>Windows</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Mac-arm64.dmg"><b>macOS</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Linux-x86_64.AppImage"><b>Linux</b></a> | <a href="docs/community-results.md"><b>Community Results</b></a> | <a href="https://freevideo-community.pages.dev/#gallery"><b>Gallery</b></a> | <a href="https://discord.gg/MsA277cJzZ"><b>Discord</b></a> | <a href="https://freevideo-community.pages.dev/qq"><b>QQ Group</b></a> | <a href="https://freevideo-community.pages.dev/wechat"><b>WeChat Group</b></a> |
</p>

<p align="center">English · <a href="README.zh-CN.md">中文</a></p>
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</div>

<p align="center">
| <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo.exe"><b>Windows 下载</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Mac-arm64.dmg"><b>macOS 下载</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Linux-x86_64.AppImage"><b>Linux 下载</b></a> | <a href="https://freevideo-community.pages.dev/#gallery"><b>作品展示</b></a> | <a href="https://discord.gg/MsA277cJzZ"><b>Discord</b></a> | <a href="https://freevideo-community.pages.dev/qq"><b>QQ 群</b></a> | <a href="https://freevideo-community.pages.dev/wechat"><b>微信群</b></a> |
| <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo.exe"><b>Windows</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Mac-arm64.dmg"><b>macOS</b></a> | <a href="https://github.com/FlashML-org/FreeVideo/releases/latest/download/FreeVideo-Linux-x86_64.AppImage"><b>Linux</b></a> | <a href="docs/zh-CN/community-results.md"><b>社区实测</b></a> | <a href="https://freevideo-community.pages.dev/#gallery"><b>作品展示</b></a> | <a href="https://discord.gg/MsA277cJzZ"><b>Discord</b></a> | <a href="https://freevideo-community.pages.dev/qq"><b>QQ 群</b></a> | <a href="https://freevideo-community.pages.dev/wechat"><b>微信群</b></a> |
</p>

<p align="center"><a href="README.md">English</a> · 中文</p>
Expand Down
2 changes: 2 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ async def comfy_entrypoint():
update_routes()
from .freevideo_engine.comfy_library import register as library_routes
library_routes()
from .freevideo_engine.comfy_capabilities import register as capabilities_routes
capabilities_routes()
from .freevideo_engine.comfy_share import register as share_routes
share_routes()
from .freevideo_engine.comfy_metadata import register as workflow_backfill
Expand Down
11 changes: 11 additions & 0 deletions freevideo_engine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,16 @@
# Release builds stamp one UTC version into both the GUI and deployed engine.
from pathlib import Path as _Path
_build_version = _Path(__file__).with_name('build-version.txt')
try:
from .long_paths import io_path as _io_path
except ModuleNotFoundError as _error:
# Version-stamp probes may copy only this initializer, without the engine.
# A complete launcher always has long_paths; do not hide its import errors.
if _error.name != __name__ + '.long_paths':
raise
else:
_build_version = _io_path(_build_version)
from .long_paths import start_process as _start_process
_start_process()
if _build_version.is_file():
__version__ = _build_version.read_text(encoding='utf-8').strip()
16 changes: 12 additions & 4 deletions freevideo_engine/adaln.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@ def __init__(self, root, source_id, steps, task='t2va', *, manifest=None,
from src.inference.render import KEYFRAME_NOISE_AUG
if old.get('keyframe_noise_aug') != KEYFRAME_NOISE_AUG:
continue
legacy_times = [t.tolist() for t in schedule_timesteps(steps, device='cpu', task=old['task'])]
# A legacy table follows the grid this machine's own linspace
# gave the version that wrote it. Where that is not the
# engine's grid the table belongs to other timesteps: keep
# it on disk and do not relabel it.
legacy_times = [t.tolist() for t in schedule_timesteps(steps, device='cpu', task=old['task'],
upstream_grid=True)]
if legacy_times != rows:
continue
files = []
Expand Down Expand Up @@ -219,12 +224,15 @@ def save(self, index, module, *, producer_device=None):


@torch.no_grad()
def schedule_timesteps(steps, video_shift=12., audio_shift=3., device='cuda', task='t2va'):
def schedule_timesteps(steps, video_shift=12., audio_shift=3., device='cuda', task='t2va', upstream_grid=False):
from .media_request import TASKS
if task not in TASKS:
raise ValueError('Unsupported AdaLN task')
from diffusers import MiniMaxH3Scheduler
video, audio = MiniMaxH3Scheduler(shift=video_shift), MiniMaxH3Scheduler(shift=audio_shift)
if upstream_grid:
from diffusers import MiniMaxH3Scheduler as scheduler
else:
from .step_grid import scheduler
video, audio = scheduler(shift=video_shift), scheduler(shift=audio_shift)
video.set_timesteps(steps, device=device)
audio.set_timesteps(steps, device=device)
return modality_timesteps(video.timesteps, audio.timesteps, task)
Expand Down
9 changes: 8 additions & 1 deletion freevideo_engine/adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,16 @@ def execute_attempts(history, identity, profile, canvas, launch, *,
on_retry(index, row, current, decision)
continue
# Failure to commit success must not trigger another GPU attempt.
history.finish(attempt, 'success', observation=observation,
settled = history.finish(attempt, 'success', observation=observation,
details={'reason': 'Complete request, retained output and metrics verified.',
'prediction_error': report.get('resource_prediction_error')})
settlement = (settled or {}).get('details', {})
if settlement.get('compatibility') is not None:
# Preserve the level/changes actually used by this request. Recovery
# applies to the next request, but belongs in this one's diagnostics.
report.setdefault('compatibility', {}).update(settlement['compatibility'])
if 'compatibility_error' in settlement:
row['compatibility_error'] = settlement['compatibility_error']
row.update(state='success', phase='complete', observation=observation, resources=telemetry,
seconds=time.perf_counter() - started, metrics=attempt_metrics(metrics))
report['profile'] = current
Expand Down
5 changes: 3 additions & 2 deletions freevideo_engine/background_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import time

from . import processes
from .long_paths import io_path

START_DELAY = 30.
MAX_DELAY = 600.
Expand Down Expand Up @@ -93,7 +94,7 @@ def _released(self, deadline):
from .locking import runtime_lock
while True:
try:
with runtime_lock(self.lock, inherit=False):
with runtime_lock(io_path(self.lock), inherit=False):
return True
except BlockingIOError:
if time.monotonic() >= deadline:
Expand Down Expand Up @@ -123,7 +124,7 @@ def _work(self, command, environment, cwd, log, busy, delay):
'a generation request stops it.')
row = dict(started=time.time())
self.attempts.append(row)
with log.open('ab') as stream:
with io_path(log).open('ab') as stream:
process = self.process = processes.popen(
command, env=environment, cwd=cwd, stdin=subprocess.DEVNULL, stdout=stream,
stderr=subprocess.STDOUT, start_new_session=True, supervise=True)
Expand Down
25 changes: 15 additions & 10 deletions freevideo_engine/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import zipfile
from urllib.parse import unquote, urlsplit

from . import execution_paths
from .hardware import Hardware, GiB, cgroup_capacity
from . import __version__
from .policy import resource_budget
Expand All @@ -34,6 +35,7 @@
from . import network
from . import processes
from .system import install_root, venv_python, system_memory, nvidia_smi, memory_sample, curl_executable, missing_curl_message
from .system import inference_headroom

PACKAGE = Path(__file__).resolve().parent
SOURCE = PACKAGE.parent
Expand Down Expand Up @@ -61,8 +63,9 @@ def _permission_limited_memory_sample(row):
return all(type(error) is dict and error.get('winerror') == 5 for error in errors)


def setup_memory_status(row, verbose=False):
available = row.get('effective_available_bytes', row['system_available_bytes'])
def setup_memory_status(row, verbose=False, *, system=None):
available = (inference_headroom(row) if (system or platform.system()) == 'Linux'
else row.get('effective_available_bytes', row['system_available_bytes']))
if memory_sample(row) is None:
if row.get('monitor_status') == 'degraded':
return 'Process RAM partially unavailable · %.1f GiB available · continuing with system floor' % (available/GiB)
Expand All @@ -88,11 +91,13 @@ def setup_memory_pressure(row, budget, system):
The budget already excludes the OS reserve. Model SDKs additionally
switch to bounded streaming below 2 GiB; stopping the whole installer at
that same threshold prevented the low-memory path from continuing.
Linux retains its existing container/cache floor.
Linux uses inference's clean-file-cache headroom, retaining the 1 GiB
floor and the separate process budget. Raw cgroup charges remain recorded.
"""
reserve = (2 if system == 'Windows' else 1) * GiB
floor = min(256 * 2**20, int(budget * .05)) if system == 'Windows' else reserve
available = row.get('effective_available_bytes', row['system_available_bytes'])
available = (inference_headroom(row) if system == 'Linux'
else row.get('effective_available_bytes', row['system_available_bytes']))
commit = row.get('system_commit_available_bytes')
used = memory_sample(row)
reasons = []
Expand Down Expand Up @@ -206,7 +211,7 @@ def dependency_status(root, layout='unified', system=None):
if python.exists():
code = ('import importlib.metadata as m,json; '
'print(json.dumps({d.metadata["Name"].lower().replace("_","-"):d.version for d in m.distributions()}))')
check = subprocess.run([str(python), '-B', '-c', code], capture_output=True, text=True)
check = execution_paths.run([str(python), '-B', '-c', code], capture_output=True, text=True)
if check.returncode:
row['error'] = check.stderr[-1000:]
else:
Expand Down Expand Up @@ -286,7 +291,7 @@ def inventory(gpu=None):
# Unknown must not be presented as an unsupported old GPU architecture.
capability = (0, 0)
ram = system_memory()
limit, available = cgroup_capacity()
limit, available = cgroup_capacity(include_reclaimable=True)
hardware = Hardware(selected['name'], capability,
int(float(selected['memory.total']) * 2**20), int(float(selected['memory.free']) * 2**20),
ram['total_bytes'], min(ram['available_bytes'], available) if available is not None else ram['available_bytes'],
Expand Down Expand Up @@ -1172,7 +1177,7 @@ def sample():
row['installation_guard'] = pressure
stream.write(json.dumps(dict(row, elapsed_seconds=time.monotonic()-self.started, epoch_seconds=time.time())) + '\n')
available = pressure['available_bytes']
self.ui.resource = setup_memory_status(row, self.ui.verbose)
self.ui.resource = setup_memory_status(row, self.ui.verbose, system=self.system)
# Windows helpers can exit/change while their tree is
# sampled. Allow two resamples, while checking the live
# exhaustion floor on every attempt. The larger planning
Expand Down Expand Up @@ -1459,13 +1464,13 @@ def python_install(self, uv):
# and unpack another copy. Existing installations keep their own base.
if prepared.is_dir() and not any(current.glob('cpython-' + self.versions['python'] + '-*')):
env = dict(self.env, UV_PYTHON_INSTALL_DIR=str(prepared))
found = subprocess.run([str(uv), '--no-config', 'python', 'find', '--managed-python',
found = execution_paths.run([str(uv), '--no-config', 'python', 'find', '--managed-python',
'--no-python-downloads', self.versions['python']],
env=env, capture_output=True, text=True, timeout=30)
if found.returncode == 0:
candidate = Path(found.stdout.strip())
if candidate.is_file() and candidate.resolve().is_relative_to(prepared.resolve()):
check = subprocess.run([str(candidate), '-I', '-B', '-c',
check = execution_paths.run([str(candidate), '-I', '-B', '-c',
'import platform; print(platform.python_version())'], capture_output=True, text=True, timeout=30)
if check.returncode == 0 and check.stdout.strip() == self.versions['python']:
self.env['UV_PYTHON_INSTALL_DIR'] = str(prepared)
Expand Down Expand Up @@ -1988,7 +1993,7 @@ def main(argv=None):
from .curl_windows import ensure
def curl_progress(message):
print(json.dumps(message), flush=True)
os.environ['FREEVIDEO_CURL'] = ensure(args.root, progress=curl_progress)
os.environ['FREEVIDEO_CURL'] = execution_paths.execution_path(ensure(args.root, progress=curl_progress))
value = plan(args, local_progress=progress_output if os.environ.get('FREEVIDEO_UI_EVENTS') == '1' or not args.json else None)
except (OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as error:
print('Preflight failed: ' + str(error), file=sys.stderr)
Expand Down
Loading