Skip to content
Open
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
33 changes: 33 additions & 0 deletions UNFIX_license
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Copyright (c) 2026 UNFIX Entertainment, Inc. All rights reserved.

PROPRIETARY AND CONFIDENTIAL

This software, including all accompanying documentation, source code, object code,
and related materials (collectively, the "Software"), is the proprietary property
of UNFIX Entertainment, Inc.

Unauthorized copying, reproduction, modification, distribution, display, or use
of this Software, or any portion thereof, via any medium is strictly prohibited
without the express prior written permission of UNFIX Entertainment, Inc.

---
THIRD-PARTY COMPONENT ATTRIBUTION & ACKNOWLEDGMENTS

Portions of this Software are based on or incorporate modifications to software
licensed under the Apache License, Version 2.0 (the "Apache License").

This Software also acknowledges the use of concepts and/or foundational code from
the "Large Avatar Model (LAM)", as detailed in the following academic publication:
He, Y., Gu, X., Ye, X., Xu, C., Zhao, Z., Dong, Y., Yuan, W., Dong, Z., & Bo, L. (2025).
"LAM: Large Avatar Model for One-shot Animatable Gaussian Head." Proceedings of the
Special Interest Group on Computer Graphics and Interactive Techniques Conference.

You may not use the Apache-licensed portions of the Software except in compliance
with the Apache License. You may obtain a copy of the Apache License at:

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed
under the Apache License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
OR CONDITIONS OF ANY KIND, either express or implied. See the Apache License
for the specific language governing permissions and limitations under the License.
156 changes: 156 additions & 0 deletions concierge_modal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Copyright (c) 2024-2025, The Alibaba 3DAIGC Team Authors.
# Modal environment and pipeline initialization for LAM avatar generation.

import os
import modal

MINUTES = 60

# ---------------------------------------------------------------------------
# Modal Volume: holds model weights, assets, and sample motions
# ---------------------------------------------------------------------------
vol = modal.Volume.from_name("lam-model-vol", create_if_missing=True)

# ---------------------------------------------------------------------------
# Modal Image: full build environment for LAM inference
# ---------------------------------------------------------------------------
image = (
modal.Image.from_registry(
"nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04",
add_python="3.10",
)
# System deps: Blender, FBX SDK, GL libs, ffmpeg, zip
.apt_install(
"git", "wget", "unzip", "xz-utils", "libgl1", "libglib2.0-0",
"libsm6", "libxrender1", "libxext6", "ffmpeg", "zip",
)
# Blender 4.0.2 (for GLB conversion via generateARKITGLBWithBlender)
.run_commands(
"wget -q https://download.blender.org/release/Blender4.0/blender-4.0.2-linux-x64.tar.xz"
" -O /tmp/blender.tar.xz",
"tar xf /tmp/blender.tar.xz -C /opt/",
"ln -s /opt/blender-4.0.2-linux-x64/blender /usr/local/bin/blender",
"rm /tmp/blender.tar.xz",
)
# FBX SDK (required by generateARKITGLBWithBlender)
.run_commands(
"pip install --no-cache-dir fbx-sdk-py",
)
# Core Python deps
.pip_install(
"torch==2.5.1", "torchvision==0.20.1",
"--index-url", "https://download.pytorch.org/whl/cu121",
)
.pip_install(
"safetensors", "omegaconf", "trimesh", "Pillow",
"opencv-python-headless", "scipy", "einops",
"transformers", "accelerate", "huggingface_hub",
"moviepy==1.0.3", "imageio[ffmpeg]",
"chumpy", "numpy==1.23.0",
"kiui", "plyfile",
)
# pytorch3d (pre-built wheel for py310/cu121/pt251)
.pip_install(
"pytorch3d",
find_links="https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt251/download.html",
)
# DINOv2 dependencies
.pip_install("xformers==0.0.29.post1")
# Copy the LAM source tree into the container
.copy_local_dir(".", "/root/LAM", ignore=[
".git", "__pycache__", "*.pyc", "output", "exps",
"train_data", ".venv", "node_modules",
])
.env({
"NUMBA_THREADING_LAYER": "forksafe",
"PYTHONPATH": "/root/LAM",
"BLENDER_PATH": "/usr/local/bin/blender",
})
)

# ---------------------------------------------------------------------------
# Pipeline initialisation (called once per container warm-up)
# ---------------------------------------------------------------------------

def _init_lam_pipeline():
"""
Initialise and return (cfg, lam, flametracking).

Mirrors the official app_lam.py launch_gradio_app() init sequence.
"""
import torch
from omegaconf import OmegaConf
from safetensors.torch import load_file

os.chdir("/root/LAM")

# ---- env ----
os.environ.update({
"APP_ENABLED": "1",
"APP_MODEL_NAME": "./model_zoo/lam_models/releases/lam/lam-20k/step_045500/",
"APP_INFER": "./configs/inference/lam-20k-8gpu.yaml",
"APP_TYPE": "infer.lam",
"NUMBA_THREADING_LAYER": "forksafe",
})

# ---- parse config (simplified, no argparse) ----
cfg = OmegaConf.create()
cfg_train = OmegaConf.load("./configs/inference/lam-20k-8gpu.yaml")
cfg.source_size = cfg_train.dataset.source_image_res # 512
cfg.render_size = cfg_train.dataset.render_image.high # 512
cfg.src_head_size = getattr(cfg_train.dataset, "src_head_size", 112)
cfg.motion_video_read_fps = 30
cfg.blender_path = os.environ.get("BLENDER_PATH", "/usr/local/bin/blender")
cfg.model_name = os.environ["APP_MODEL_NAME"]

model_name = cfg.model_name
_relative_path = os.path.join(
cfg_train.experiment.parent,
cfg_train.experiment.child,
os.path.basename(model_name).split("_")[-1],
)
cfg.save_tmp_dump = os.path.join("exps", "save_tmp", _relative_path)
cfg.image_dump = os.path.join("exps", "images", _relative_path)
cfg.video_dump = os.path.join("exps", "videos", _relative_path)
cfg.model = cfg_train.model

# ---- build model ----
from lam.models import ModelLAM

model = ModelLAM(**cfg.model)
resume = os.path.join(model_name, "model.safetensors")
print("=" * 80)
print(f"Loading pretrained weights from: {resume}")
if resume.endswith("safetensors"):
ckpt = load_file(resume, device="cpu")
else:
ckpt = torch.load(resume, map_location="cpu")
state_dict = model.state_dict()
for k, v in ckpt.items():
if k in state_dict:
if state_dict[k].shape == v.shape:
state_dict[k].copy_(v)
else:
print(f"[WARN] shape mismatch {k}: ckpt {v.shape} vs model {state_dict[k].shape}")
else:
print(f"[WARN] unexpected key {k}: {v.shape}")
print(f"Finished loading weights from: {resume}")
print("=" * 80)

lam = model
lam.to("cuda")
lam.eval()

# ---- FLAME tracking ----
from tools.flame_tracking_single_image import FlameTrackingSingleImage

flametracking = FlameTrackingSingleImage(
output_dir="output/tracking",
alignment_model_path="./model_zoo/flame_tracking_models/68_keypoints_model.pkl",
vgghead_model_path="./model_zoo/flame_tracking_models/vgghead/vgg_heads_l.trcd",
human_matting_path="./model_zoo/flame_tracking_models/matting/stylematte_synth.pt",
facebox_model_path="./model_zoo/flame_tracking_models/FaceBoxesV2.pth",
detect_iris_landmarks=False,
)

return cfg, lam, flametracking
Loading