8.8 KiB
INSTALL_NOTES — ComfyUI-LensTurbo
The deployment story for this pack — every pitfall discovered while bringing it online, with root cause and fix. If you're hitting an error, scan the pitfall library below first.
Confirmed working configuration
- Ubuntu 22.04, dual RTX 3090 (24 GB each, sm_86)
- CUDA 12.6, Python 3.13, torch 2.11.0+cu126
- ComfyUI venv managed by
uv HF_HOMEon a large disk (/mnt/largedisk/hf_cacheor similar)TMPDIRon the same large disk (avoid the small root partition)- System RAM 48 GB+ (required, see Why 48 GB RAM)
Deployment recipe
# 0. Pre-flight env
export HF_HOME=$HOME/hf_cache # or wherever your big disk is
export TMPDIR=$HOME/tmp
mkdir -p "$HF_HOME" "$TMPDIR"
cd $COMFY_ROOT
source .venv/bin/activate
# 1. Drop the pack in
cd custom_nodes/
git clone https://github.com/aadebuger/ComfyUI-LensTurbo
cd ComfyUI-LensTurbo
# 2. Vendor upstream
bash scripts/fetch_upstream.sh
ls lens_source/lens/ | head # should show __init__.py, pipeline.py, ...
# 3. Install deps with override + kernels uninstall
bash scripts/install_deps.sh
# 4. HF login + license agreement
bash scripts/hf_login_check.sh
# If any FAILs:
# - Visit https://huggingface.co/microsoft/Lens-Turbo and click Agree
# - Visit https://huggingface.co/black-forest-labs/FLUX.2-dev and click Agree
# - hf auth login (paste token)
# 5. Pre-download (~58 GB, foreground, resumable)
bash scripts/download_models.sh
# Use tmux/screen if you want to detach. Ctrl-C + re-run resumes.
# 6. Restart ComfyUI with the right env
pkill -9 -f main.py; sleep 5
cd $COMFY_ROOT && source .venv/bin/activate
export HF_HOME=$HOME/hf_cache
export TMPDIR=$HOME/tmp
unset HF_HUB_ENABLE_HF_TRANSFER # deprecated in huggingface_hub 1.x
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
nohup stdbuf -oL -eL python main.py --listen 0.0.0.0 --port 8188 \
> $TMPDIR/comfy.log 2>&1 &
sleep 30
# 7. Banner check
grep -iE "ComfyUI-LensTurbo|lens-turbo|IMPORT FAILED" $TMPDIR/comfy.log | head -20
# 8. Node-registered check
curl -s http://127.0.0.1:8188/object_info | python3 -c "
import json, sys
d = json.load(sys.stdin)
for n in ['LensTurboLoader', 'LensTurboSampler']:
print(f' {n}: {\"OK\" if n in d else \"MISSING\"}')"
# 9. Smoke test via CLI
python cli/lens_turbo_cli.py "a cat" \
--ip 127.0.0.1 --gpu 0 \
--steps 4 --cfg 1.0 --seed 0 \
--ws-recv-timeout 1800 --timeout 3600 \
--out ./out
Pitfall library (L1–L10)
The full story of what went wrong while bringing this online. Cross-reference against the v3 README pitfall framework (modes A-F).
| # | Symptom | Root cause | Fix | Framework |
|---|---|---|---|---|
| L1 | huggingface-cli prints a deprecation warning, some commands fail |
huggingface-cli deprecated in huggingface_hub ≥1.x |
Migrate to hf (hf auth login, hf download, hf cache scan) |
env hygiene |
| L2 | HF_HUB_ENABLE_HF_TRANSFER FutureWarning on every script |
Variable deprecated, replaced by Xet backend | unset HF_HUB_ENABLE_HF_TRANSFER. Optional opt-in: export HF_XET_HIGH_PERFORMANCE=1 |
env hygiene |
| L3 | from_pretrained returns 401 on microsoft/Lens-Turbo |
Gated repo, needs license Agree + token | hf auth login + click Agree on the HF web page |
gated repos |
| L4 | httpx.LocalProtocolError: Illegal header value b'kernels/0.14.0; ...' during from_pretrained |
kernels==0.14.0 builds the user_agent with a trailing ; ; httpx ≥0.28 rejects headers with trailing whitespace |
uv pip uninstall kernels. RTX 3090 (sm_86) can't run MXFP4 native (needs sm_90+ / H100) so transformers gracefully dequantizes to bf16. The kernels package is unused on Ampere. |
Mode F (uninstall unused optimization) |
| L5 | OOM at ~22.9 GB on first sample using enable_model_cpu_offload() |
Dequantized GPT-OSS-20B is ~40 GB bf16, doesn't fit on a 24 GB card even when "offloaded" because model_cpu_offload puts the WHOLE sub-model on GPU during its forward | Use offload_mode="cpu_text_encoder" (the default) |
Hybrid placement |
| L6 | NotImplementedError: Cannot copy out of meta tensor; no data! when using enable_sequential_cpu_offload() |
accelerate ≥1.x wraps pre_forward with torch.compile (_compiled_fn). Combined with Lens's custom TextEncoder forward and meta-device weight loading, residual meta tensors leak into send_to_device(args, ...). TORCHDYNAMO_DISABLE=1 does not fix this — _fn is on every code path. |
Abandon sequential mode entirely. Use cpu_text_encoder instead. |
Hybrid placement (forced) |
| L7 | AttributeError: property '_execution_device' has no setter |
diffusers exposes _execution_device as @property with no setter, inferred from sub-model devices |
Patch the class descriptor, not the instance: type(pipe)._execution_device = property(lambda self: target_device) |
Monkey-patch A |
| L8 | ValueError: Cannot generate a cpu tensor from a generator of type cuda in prepare_latents |
_execution_device returned cpu because text_encoder is on CPU, so latents got allocated on CPU and clashed with the cuda Generator |
Same fix as L7 (auto-applied by cpu_text_encoder mode) |
Monkey-patch A |
| L9 | RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu in transformer's txt_norm[i](encoder_hidden_states[i]) |
LensPipeline doesn't .to(execution_device) the text encoder outputs; they stay on CPU (encoder's device) and clash with the GPU transformer |
Wrap text_encoder.encode_layers: send inputs → CPU, outputs → target device. Auto-applied by cpu_text_encoder mode. |
Monkey-patch B |
| L10 | CLI workflow validation fails: Required input is missing: offload_mode |
Server pack upgraded to new schema, but client-side lens_turbo_cli.py builds the old workflow |
Update both sides: extract the same tarball on the Mac client and on the server. | deployment discipline |
Framework summary
These pitfalls map to broader patterns useful for other custom nodes:
- Mode F (L4): when a Python package's "fast path" doesn't apply to your hardware AND the fallback path is acceptable, just uninstall the package. Don't try to patch around its bugs.
- Hybrid CPU/GPU placement (L5–L9): for any pipeline composed of "huge text encoder + small generative network + VAE", keep the text encoder on CPU and the generative parts on GPU. This requires three monkey-patches (override
_execution_device, wrap encoder I/O for device crossings) that are reusable across Flux+T5XXL, Lumina, and similar architectures. - Deployment discipline (L10): when you change a node's input schema, both the server-side pack and any client-side CLI need to be re-deployed. Use a single tarball + identical extraction commands on both sides.
Verifying the cpu_text_encoder patches fire
If cpu_text_encoder mode looks like it's not working (still OOMs, still device mismatches), add the diagnostic:
print(f"[diag] _execution_device = {pipe._execution_device}")
print(f"[diag] text_encoder = {next(pipe.text_encoder.parameters()).device}")
print(f"[diag] transformer = {next(pipe.transformer.parameters()).device}")
print(f"[diag] vae = {next(pipe.vae.parameters()).device}")
print(f"[diag] encode_layers id = {id(pipe.text_encoder.encode_layers)}")
Expected output (target_device = cuda:0):
[diag] _execution_device = cuda:0
[diag] text_encoder = cpu
[diag] transformer = cuda:0
[diag] vae = cuda:0
[diag] encode_layers id = 140392847102464 # different from original function id
If _execution_device is cpu or encode_layers id matches the unwrapped function, the patches didn't apply — check that you're really using offload_mode="cpu_text_encoder".
Recovery: a bad .py broke ComfyUI startup
# Clear pycache
find $COMFY_ROOT/custom_nodes/ComfyUI-LensTurbo -name __pycache__ -type d -exec rm -rf {} + 2>/dev/null
find $COMFY_ROOT/custom_nodes/ComfyUI-LensTurbo -name "*.pyc" -delete
# Temporarily disable the pack (MOVE OUT of custom_nodes/, not rename to .bak!)
mkdir -p $HOME/node_backups
mv $COMFY_ROOT/custom_nodes/ComfyUI-LensTurbo \
$HOME/node_backups/ComfyUI-LensTurbo.$(date +%Y%m%d_%H%M)
# Restart and confirm baseline still works
pkill -9 -f main.py; sleep 5
# ... launch ...
⚠️ Don't rename to .bak inside custom_nodes/ — ComfyUI scans every subdirectory and .bak shadows can silently override NODE_CLASS_MAPPINGS. Move the backup OUT of the directory entirely.
When to bump --ws-recv-timeout
First-time loads pull ~58 GB from HF. On a 100 Mbps connection that's ≥80 minutes with zero WebSocket traffic. The default --ws-recv-timeout=600 will time out; use 1200+ or 3600 for first run. After the cache warms, subsequent runs are <5 s of loader work.