Back to portfolio
detail
00 2023 · Product constraint

AnimePics let users upload photos and see themselves as anime.

Built in 2023, before later image models made this interaction feel instant and trivial. AnimePics was a consumer funnel that collected subject info, payment, and training photos, then used GPU-backed ML to create personalized portrait galleries.

Risk
Non-deterministic ML
input quality drives output quality
GPU work is slow and expensive
users leave before async value arrives
needed product and systems design
Shape
Order pipeline
fast mobile purchase flow
validated, cropped training images
async GPU fulfillment and gallery delivery
AnimePics product screenshot
product screenshot · upload photos, get generated anime portraits
Grounding · why now
Why now?

People were already paying artists on marketplaces such as Etsy to turn personal photos into anime portraits. Stable Diffusion 1.5 created an opening to deliver that familiar outcome through software, while new personalization techniques made it possible to preserve a specific customer’s identity across many scenes.

validated demand
A service people bought
personal photos → anime portraits

The customer desire was visible before the product existed. The opportunity was to make fulfillment repeatable.

new capability
Personalization became practical
Stable Diffusion 1.5 + DreamBooth

Recent open models made it possible to train identity and generate many new scenes from a small photo set.

founder fit
I could own the full loop
product, backend, infrastructure, ML

My frontend, backend, and infrastructure experience let me build the consumer product and the GPU system together.

detail
01 Funnel

Product funnel

01
Choose

User selects package, themes, and subject traits needed by prompts.

02
Pay

Stripe checkout creates a durable paid order before expensive GPU work starts.

03
Upload

Browser validates faces and crops images to the model's expected 512x512 input.

04
Wait

Backend queues a training job and shows progress as an order, not a blocking request.

05
Share

Gallery supports public/private viewing, trial unlocks, downloads, and bonus content.

Product decision Keep the customer-facing steps familiar: choose, pay, upload, leave, return by email. The expensive GPU work runs after the product has the inputs it needs.
Grounding · GPU-shaped product
Heavy ML path shaped the product

A key product decision was not to promise instant results. With one GPU, a single personalized job could take more than an hour, and a queue of orders could push delivery into multiple hours. The user experience was built around that reality: pay, upload validated photos, leave, then come back from email when the gallery was ready.

expectation
Async by design
quality over immediacy

The wait was not hidden. The product flow made long-running generation normal.

constraint
Heavy ML path
serial work per order

Identity training, model merges, and prompt batches made queueing and status part of fulfillment.

delivery
Email integration
return path

Payment, upload, and fulfillment emails brought users back to finish uploads or view completed galleries.

detail
Grounding · input quality
Good photos were the strongest product engineering challenge.

Input quality had an outsized effect on the final gallery. The browser rejected bad photos and produced model-ready crops before the backend committed scarce GPU time.

  • Face checks. Reject missing or likely multiple faces.
  • Minimum quality. Reject images below 512 by 512.
  • Smart crop. Center the face and emit a 512 by 512 input.
AnimePics transformation product graphic AnimePics generated result AnimePics generated result AnimePics generated result
async fulfillment loop
Upload endpointvalidated photo keys
order → training
->
Training queuedurable job
small payload
->
GPU workeridentity training
merge + inference
S3 resultsportrait objects
by order ID
->
Result queuegallery-save
request
->
Web servicegallery rows
order → trained
Postgresorder state
photo metadata
+
02 End-to-end system

Product stack

The frontend collected payment, subject traits, and validated photos. The web service persisted the order and handed a bounded job to the GPU worker. Postgres stored durable state, S3 stored image binaries, and Kafka connected the long-running stages.

Web UI
Next.js

Tailwind
Zustand
React Query
NextAuth + Stripe
face-api.js + smartcrop

Web service
Python API

Litestar
SQLAlchemy
Stripe webhooks
Redis authz

Persistence
Postgres + S3

orders and users
theme selection
training photo keys
result photo keys

Orchestration
Kafka

training jobs
result-save jobs
manual commits
Pydantic schema

Generation
GPU worker

training
inference
PyTorch merging
prompt batches
high-res correction
upscaling

03 Model pipeline

Model pipeline

The product trained identity once, then branched into selected styles.

The GPU server used my own DreamBooth implementation to train a personalized checkpoint from the uploaded photos. Custom PyTorch code then merged that identity checkpoint into each selected style model. Reusing the checkpoint avoided training once per style and saved substantial GPU time. GPU compute was by far the product's largest expense.

  • Identity once: one person checkpoint captures the uploaded subject.
  • Style branches: selected package themes decide which custom style checkpoints get merged.
  • Prompt batches: sex, age, hair, eyes, and glasses fill themed prompt templates.
model tree · package-selected branches
Uploaded photosvalidated 512x512 face crops from the browser
Person identity checkpointtrained once per order from customer photos
AnimePics generated portrait in cartoon style
cartoon-base-> Cartoon portraits
AnimePics generated portrait in American illustration style
pixar-base-> American portraits
AnimePics generated portrait in anime style
ghibli-base-> Ghibli portraits
AnimePics generated portrait in cinematic anime style
shinkai-base-> Shinkai portraits
04 ML engineering reality
Training models was harder than writing software... imo

Software gives me a fast, mostly deterministic feedback loop. Model training does not. The hardest part of this project was ensuring good results. I made my own DreamBooth implementation. I also customized training for each customer: I selected class-specific regularization data based on the customer’s subject class and normalized epoch count against the number of uploaded photos to target approximately 1,700 total training steps. I built prompt templates around characteristics such as hair, eyes, and age. I tested sampler settings and seeds, spent weeks curating better training sets, and added an upscaler step when customers paid for larger outputs. A major breakthrough was model merging. Instead of training each customer in four different styles, each with a different base model, I trained the identity once and transferred its learned delta into each style checkpoint. Model merging was still novel then, but it cut GPU time in half on average while preserving quality in the final outputs.

feedback loop
Painfully slow evals
quality was slow to measure

Just as gradient descent only tells you directionally where to go, improving the model felt similar. Each change had to be tested in isolation and run against multiple types of users.

failed pain
GPU environments were fragile
PyTorch, CUDA, xformers

The exact package versions had to line up with the GPU before training could run. Changing hardware often meant rebuilding the environment.

lesson
Data quality mattered most
the highest-leverage variable

Great training data and an okay model > okay training data and a great model.

optimization
Model merging
~50% less GPU time

Train identity once, then transfer its learned delta into each style checkpoint: style + 0.9 × (personalized − base). That replaced four separate training runs while preserving output quality.

05 Failure mode

The product was technically differentiated.
The business did not have distribution.

Failure

No GTM
flywheel

The product was better than competitors at the time, but marketing was weak and there was no built-in distribution loop.

Constraint

Low utilization
hurt economics

At low volume, dedicated GPU capacity sat underused while paid acquisition failed to create enough demand to amortize it.

Product miss

Not magical enough

The result took more than an hour and could include only one person. People often wanted anime photos with friends, family, or pets.

Market lesson The idea was directionally right. Later, when the OpenAI image model made "Ghibli-fying" yourself a fad, the mass-market version won on speed, quality, and breadth: roughly seconds instead of an hour, plus support for multiple people, pets, and more arbitrary scenes.
left/right advance · detail toggle / space flips detail
1/10