ComfyUI Extension: comfyui-ageshift
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.
ComfyUI nodes for face age detection and age-conditioned prompt building
Looking for a different extension?
Custom Nodes (4)
README
ComfyUI-AgeShift
Detect a face's age, then generate identity-preserved portraits at any target age — from infant to 90+.
ComfyUI nodes for face age detection (ViT classifier) and age-conditioned prompt building. Pairs with ComfyUI-PuLID-Flux (or InstantID) to produce identity-preserving age-shifted portraits.
source face ──► AgeDetect ──► current age = 40
│
target age (e.g. 65) ──► AgePromptBuilder ──► positive + negative prompt
│
▼
PuLID-Flux + Flux 1 dev
│
▼
aged portrait (65 y/o)
✨ Features
- 🎂 Continuous age estimation — ViT classifier with weighted-mean
decoding over bin midpoints; produces smooth integer ages (
32,47) rather than snapping to bin centers. - 🎂 Concrete-feature prompts — translates
target_age=65into"deep facial wrinkles, gray or white hair often thinning, sagging jowls, age spots", which Flux follows much more reliably than numeric age alone. - 🎂 Anti-drift negatives — auto-injects contextual negatives so a 40-year-old source doesn't leak adult features into the "age 5" generation.
- 🎂 Identity-agnostic — works with PuLID-Flux, InstantID, IPAdapter FaceID, or any face-conditioning backbone. We don't reimplement ID injection; we just drive the prompt side.
- 🎂 Batch CLI — generate an age progression sequence (5 → 15 → 25 → 45 → 65) in one command from your laptop, talking to a remote ComfyUI server.
📦 Installation
1. Clone the node package
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/aadebuger/ComfyUI-AgeShift.git
cd ComfyUI-AgeShift
# in the ComfyUI venv:
source /path/to/ComfyUI/.venv/bin/activate
uv pip install -r requirements.txt
Dependencies are minimal (transformers, pillow, numpy) — almost
always already in a ComfyUI venv.
2. (Optional but recommended) Companion packages
The age-detect side works standalone. For identity-preserving generation you also need PuLID-Flux + Flux:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/balazik/ComfyUI-PuLID-Flux.git
cd ComfyUI-PuLID-Flux
uv pip install -r requirements.txt
| Asset | Where | Size | Notes |
|---|---|---|---|
| Flux 1 dev fp8 | models/unet/flux1-dev-fp8.safetensors | ~12 GB | from Kijai/flux-fp8 (no license required) |
| CLIP-L | models/clip/clip_l.safetensors | ~250 MB | standard Flux |
| T5-XXL | models/clip/t5xxl_fp8_*.safetensors | ~5 GB | scaled or e4m3fn both work |
| VAE | models/vae/ae.safetensors | ~330 MB | standard Flux |
| PuLID-Flux weights | models/pulid/pulid_flux_v0.9.1.safetensors | ~1.1 GB | from guozinan/PuLID |
| InsightFace antelopev2 | models/insightface/models/antelopev2/*.onnx | ~360 MB | manual placement required — see below |
| EVA02-CLIP-L | auto-downloaded | ~430 MB | downloads on first run |
⚠️ InsightFace
antelopev2does NOT auto-download (unlike the defaultbuffalo_l). You must place the 5 ONNX files manually atmodels/insightface/models/antelopev2/(note the doublemodels— that's how the InsightFace lib expects it). See INSTALL_NOTES.md for one-liner downloads.
3. Restart ComfyUI
Search the node palette for 🎂 — four nodes should appear.
Quick verification from the CLI:
curl -s http://127.0.0.1:8188/object_info | python3 -c "
import json,sys; d = json.load(sys.stdin)
for n in ['AgeDetectorLoader','AgeDetect','AgePromptBuilder','AgeRangeBatch',
'PulidFluxModelLoader','ApplyPulidFlux']:
print(f' {n}:', '✓' if n in d else '✗')"
🚀 Quick start
Detect only (UI)
Open example_workflows/age_detect_only.json in ComfyUI, drop a portrait
into the LoadImage node, queue. The summary PreviewAny shows:
Estimated age: 40 (adult) | top bin: 30-39 (46.8%) | top-3: 30-39=0.47, 40-49=0.43, 50-59=0.05
Full pipeline (UI)
Open example_workflows/age_shift_pulid_flux.json, set the target_age
on the AgePromptBuilder node, queue.
CLI
# Detect only — runs in ~2 seconds on CPU
uv run --script cli/age_shift_cli.py face.jpg --detect-only --ip <server>
# Single target age
uv run --script cli/age_shift_cli.py face.jpg --target-age 65 --ip <server>
# Age progression sequence
uv run --script cli/age_shift_cli.py face.jpg \
--target-ages 5,15,25,45,65 \
--gender male --subject man \
--pulid-weight 0.95 --guidance 3.0 --steps 28 --seed 42 \
--flux-unet flux1-dev-fp8.safetensors \
--flux-t5 t5xxl_fp8_scaled.safetensors \
--ip <server> --out ./out
Output: ./out/age_005_*.png, ./out/age_015_*.png, ...
See cli/README.md for the full argument list.
🧠 Node reference
🎂 Age Detector Loader
Loads a HF ViT age-classification model, with in-process cache.
| Input | Type | Default |
|---|---|---|
| model_id | STRING | nateraw/vit-age-classifier |
| device | enum | auto |
| dtype | enum | fp32 |
| force_reload | BOOL | False |
Output: AGE_DETECTOR.
🎂 Age Detect
| Input | Type | Default |
|---|---|---|
| detector | AGE_DETECTOR | — |
| image | IMAGE | — |
| method | enum | weighted_mean |
Outputs: age:INT, age_band:STRING, confidence:FLOAT, summary:STRING.
Note: this is whole-image classification. For non-portrait input, run a face-crop step upstream (e.g. via ComfyUI-Crystools or InsightFace).
🎂 Age Prompt Builder
| Input | Type | Default |
|---|---|---|
| target_age | INT | 30 |
| subject | STRING | person |
| gender | enum | unspecified |
| style | STRING | photorealistic portrait... |
| extra_positive | STRING | same identity... |
| negative_base | STRING | cartoon, illustration... |
| include_negative_age_drift | BOOL | True |
Outputs: positive:STRING, negative:STRING, summary:STRING, target_age:INT.
📖 See PROMPTS.md for the complete list of generated prompts across all 20 age ranges + examples at age 5/15/25/45/65/80.
🎂 Age Range Batch
CLI-oriented. Emits JSONL {age, band, prompt} for a comma-separated list
of target ages.
🎯 Identity preservation tuning
The default settings work for most cases. If the generated faces look like different people, walk this checklist:
1. Lock the seed across ages
Different seeds = different sub-identities. Pass --seed 42 (or any fixed
value) so all ages share the same noise schedule.
2. Bump PuLID weight
--pulid-weight 0.95 # default 0.9; up to 1.0
3. Lower the prompt guidance
--guidance 3.0 # default 3.5
The age prompt is a strong signal — lowering guidance leaves more room for the identity to assert itself.
4. More steps
--steps 28 # default 20
PuLID continues injecting identity throughout sampling, so more steps = more chances for ID to converge.
5. Crop the source to a clean portrait
PuLID extracts identity via InsightFace, which works best on a centered frontal face. If the input has background / side angle / multiple faces, crop first.
6. Strengthen identity anchors in the prompt
--extra-positive "exact same person, identical facial bone structure, same eye distance, same nose shape, same lip shape"
Direction-specific tips
| Direction | Tip |
|---|---|
| Aging up (40 → 70) | Default settings work well. Optionally --style "photorealistic portrait, soft natural lighting" (avoid sharp focus, which over-emphasizes wrinkles) |
| Aging down (40 → 20) | Default settings work well. |
| Going to child (adult → 5–12) | Drop --pulid-weight 0.75–0.80 — full strength preserves adult bone structure and produces unsettling "small adult" results. |
| Going to infant (adult → 0–3) | Hard. Consider face-landmark ControlNet weighted on upper face only, or accept that the result is "what this person's son/daughter might look like" rather than a literal regression. |
🐞 Troubleshooting
PulidFluxModelLoader not found
PuLID-Flux either isn't installed or failed to import. Check:
grep -B2 -A8 -iE "pulid|IMPORT FAILED" ~/comfy_start.log | tail -40
Most often facexlib / onnxruntime-gpu / insightface is missing:
cd /path/to/ComfyUI/custom_nodes/ComfyUI-PuLID-Flux
uv pip install -r requirements.txt --no-cache-dir
Then restart ComfyUI.
assert 'detection' in self.models (AssertionError from InsightFace)
The antelopev2 model files aren't at the expected path. Required layout
(note the double models directory):
/path/to/ComfyUI/models/insightface/models/antelopev2/
├── 1k3d68.onnx
├── 2d106det.onnx
├── genderage.onnx
├── glintr100.onnx
└── scrfd_10g_bnkps.onnx
Fix:
cd /path/to/ComfyUI/models/insightface
mkdir -p models && cd models
wget "https://github.com/deepinsight/insightface/releases/download/v0.7/antelopev2.zip"
unzip antelopev2.zip && rm antelopev2.zip
No module named 'websocket'
The CLI needs websocket-client (NOT websocket, which is a separate
abandoned package):
uv pip install websocket-client
Or just use uv run --script cli/age_shift_cli.py ... — the script's
PEP 723 header declares deps, uv handles them automatically.
Faces all look like different people
See the identity preservation tuning
section. Most often: missing --seed, or --pulid-weight too low.
"child" generation produces creepy mini-adults
PuLID weight is too high for child direction. Try --pulid-weight 0.75
for child-direction runs; keep it at 0.9–0.95 for aging-up runs.
Hangs on first generation
PuLID's first run downloads EVA02-CLIP-L (~430 MB) and (if you skipped the
manual antelopev2 step) tries to set up InsightFace. Watch the ComfyUI server
log — once it prints apply_pulid done, generations become fast.
🧰 Project layout
ComfyUI-AgeShift/
├── __init__.py # NODE_CLASS_MAPPINGS
├── nodes/
│ ├── _types.py # age → features mapping (the brain)
│ ├── detector_loader.py # _MODEL_CACHE pattern
│ ├── detect.py # ViT classifier execution
│ └── prompt_builder.py # AgePromptBuilder + AgeRangeBatch
├── cli/
│ ├── README.md
│ └── age_shift_cli.py # PEP 723 inline-deps script
├── example_workflows/
│ ├── age_detect_only.json # minimal UI workflow
│ └── age_shift_pulid_flux.json # full pipeline UI workflow
├── PROMPTS.md # complete prompt reference
├── INSTALL_NOTES.md # server-side install record
├── CHANGELOG.md
├── CONTRIBUTING.md
├── pyproject.toml
└── requirements.txt
🛣️ Roadmap
- [ ] Built-in face-crop node (so users don't need a separate package)
- [ ] CLI flag
--pulid-fusion {mean,concat}and--pulid-train-stepfor advanced ID tuning - [ ] InstantID + SDXL alternative workflow (for users without Flux)
- [ ] ControlNet face-landmark integration for stronger child-direction results
- [ ] Side-by-side grid output (concatenated PNG) for sequence runs
🤝 Contributing
PRs welcome. See CONTRIBUTING.md. The contribution most
likely to help right now: testing different age-feature phrasings on Flux
and submitting improvements to _types.py with before/after comparison
images.
📜 License
MIT — see LICENSE.
The age classifier (nateraw/vit-age-classifier) is MIT-licensed.
PuLID, Flux, and InsightFace weights have their own licenses — see those
projects for details.
🙏 Acknowledgments
- PuLID — Liu et al., the actual identity-preservation magic.
- ComfyUI-PuLID-Flux — balazik, the ComfyUI port we depend on.
- nateraw/vit-age-classifier — the age classifier we ship as default.
- Flux — Black Forest Labs.
Run ComfyUI workflows without the setup
No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.