StyleGAN
StyleGAN maps `z` to an intermediate `w`, then *injects* `w` at each resolution level through AdaIN. The split gives a latent space whose axes roughly separate coarse style from fine style.
Learning Objectives
- Explain how a mapping network plus per-resolution AdaIN injection reduces the entanglement of a DCGAN-style latent.
- Run a toy mapping network, AdaIN, and per-layer noise in pure Python, and say what each printout does and does not show.
- Apply the truncation trick and choose psi to trade sample diversity against quality.
- Compare what StyleGAN2 and StyleGAN3 fixed, and decide when StyleGAN (including inversion-based editing) beats diffusion.
The Problem
StyleGAN is a GAN generator that maps z to an intermediate w first, then injects w at each resolution level through AdaIN, instead of feeding z straight into the convolutions.
The motivation is entanglement. A DCGAN maps z to an image through a stack of transposed convolutions, and z controls pose, lighting, identity, and background all at once. Move along one axis of z and all four change, so you can't ask the model for "same person, different pose." The representation doesn't factor that way.
Karras et al. (2019, NVIDIA) changed the plumbing. The network starts from a constant 4×4×512 tensor, and, in the published model, an 8-layer MLP maps z ∈ Z to w ∈ W. Each resolution gets w through adaptive instance normalization (AdaIN): normalize each conv feature map, then scale and shift it with affine projections of w. Per-layer noise adds stochastic detail like skin pores and hair strands.
The result is that W has roughly orthogonal axes for high-level style (pose, identity) versus fine style (lighting, color). To swap styles between two images, use image A's w for the low-resolution levels and image B's w for the high ones. That made editing, cross-domain stylization, and the StyleGAN-inversion line of research possible.
The Concept
Mapping network. f: Z → W, an 8-layer MLP in the published model, with Z = N(0, I)^512. W isn't forced to be Gaussian. It learns a data-adapted shape.
Synthesis network. In the published model it starts from a learned constant 4×4×512. Each resolution block runs: upsample → conv → AdaIN(w_i) → noise → conv → AdaIN(w_i) → noise. The resolution doubles from block to block: 4, 8, 16, 32, 64, 128, 256, 512, 1024.
AdaIN.
AdaIN(x, y) = y_scale · (x - mean(x)) / std(x) + y_bias
where, in the published model, y_scale and y_bias come from per-channel affine projections of w. Normalize per feature map, then restyle. "Style" here means the first- and second-order statistics of the feature map.
Per-layer noise. Single-channel Gaussian noise is added to each feature map and scaled by a learned per-channel factor in the published model. It controls stochastic detail and leaves global structure alone.
Truncation trick. At inference, sample z, compute w = mapping(z), then w' = ŵ + ψ·(w - ŵ), where ŵ is the mean w over many samples. ψ < 1 trades diversity for quality. Most StyleGAN demos use ψ ≈ 0.7.
What the toy in main.py does instead. It keeps the data flow and drops almost everything else. The mapping network is 4 layers (depth=4), not 8, with z_dim = w_dim = 8. The constant is a 6-vector drawn once from gauss(0, 0.3), and nothing is ever trained. There are three synthesis blocks, each a matmul, a leaky ReLU, AdaIN, and noise, with no upsampling, convolutions, or resolution ladder. AdaIN's scale and bias are each one scalar per block, computed as a dot product of w with a fixed random vector, and the noise sigma is a single fixed scalar. Everything above in the published-model wording (8 layers, 512-d Z, 4×4×512, resolutions 4 to 1024, learned constant, per-channel parameters) describes the real StyleGAN, not the toy.
StyleGAN 1 → 2 → 3
| Version | Year | Innovation |
|---|---|---|
| StyleGAN | 2019 | Mapping network + AdaIN + noise + progressive growing. |
| StyleGAN2 | 2020 | Weight demodulation replaces AdaIN (fixes droplet artifacts); skip/residual architecture; path-length regularization. |
| StyleGAN3 | 2021 | Alias-free convolution + equivariant kernels; eliminates texture sticking to pixel grid. |
| StyleGAN-XL | 2022 | Class-conditional, 1024², ImageNet. |
| R3GAN | 2024 | Rebrands with stronger reg; closes gap to diffusion on FFHQ-1024 with 20x fewer params. |
In 2026 StyleGAN3 remains the default for three jobs: (a) narrow-domain photorealism at high FPS, (b) few-shot domain adaptation (train on a new dataset with 100 images, freeze mapping), and (c) inversion-based editing (find the w that reconstructs a real photo, then edit that w). For open-domain text-to-image, reach for diffusion instead.
Build It
code/main.py implements a toy "style-GAN lite" in 1-D with three parts: a mapping MLP, a synthesis function that takes a constant vector (drawn once, never trained) and modulates it with w-derived scale/bias, and per-layer noise. The toy shows the mechanics of mapping, AdaIN modulation, truncation, and noise. It has no concatenation baseline and does not train anything, so it can't tell you whether modulating with w beats concatenating z.
The snippets below are illustrative. They show the idea in a compact form and don't match main.py's signatures exactly: there, mapping(z, layers) takes a list of (W, b) pairs, adain(features, scale, bias) uses a mean_std helper, and noise is added inline in stylegan_forward.
Step 1: mapping network
def mapping(z, M):
h = z
for i in range(num_layers):
h = leaky_relu(add(matmul(M[f"W{i}"], h), M[f"b{i}"]))
return hStep 2: adaptive instance normalization
def adain(x, w_scale, w_bias):
mu = mean(x)
sd = std(x)
x_norm = [(xi - mu) / (sd + 1e-8) for xi in x]
return [w_scale * xi + w_bias for xi in x_norm]In the published model w supplies a per-channel scale and bias through a learned linear projection. In the toy, each block's scale and bias are single scalars, each a dot product of w with a fixed random vector.
Step 3: per-layer noise
def add_noise(x, sigma, rng):
return [xi + sigma * rng.gauss(0, 1) for xi in x]In the published model sigma is a learned per-channel factor. In the toy it is one fixed scalar noise_sigma, applied once per block.
Pitfalls
- Droplet artifacts. StyleGAN 1 produced a blobby droplet in the feature maps because AdaIN zeroed out the mean. StyleGAN 2's weight demodulation fixes it by scaling the convolution weights instead.
- Texture sticking. In StyleGAN 1 and 2, textures followed pixel coordinates instead of object coordinates, which shows when you interpolate. StyleGAN 3's alias-free convolutions fix this with windowed sinc filters.
- Mode coverage. Truncation
ψ < 0.7looks clean but samples from a narrow cone. Useψ = 1.0when you need diversity. - Inversion is lossy. Inverting a real photo into
Wis usually done through optimization or an encoder (e4e, ReStyle, HyperStyle). Results drift over many iterations.
Use It
| Use case | Approach |
|---|---|
| Photoreal human faces (anime, product, narrow) | StyleGAN3 FFHQ / custom fine-tune |
| Face editing from a photo | e4e inversion + StyleSpace / InterFaceGAN directions |
| Face swap / reenactment | StyleGAN + encoder + blending |
| Avatar pipelines | StyleGAN3 w/ ADA for low-data fine-tune |
| Domain adaptation from a few images | Freeze mapping network, fine-tune synthesis |
| Multi-modal or text-conditioned generation | Don't — use diffusion |
When the product is a photo of a person's face, StyleGAN beats diffusion on inference cost (single forward pass, roughly 10 ms or less on a high-end GPU; an approximate figure that varies with hardware and implementation) and on sharpness at the same quality bar.
Ship It
Save outputs/skill-stylegan-inversion.md. The skill takes a real photo and outputs four things: the inversion method (e4e / ReStyle / HyperStyle), the expected latent loss, an editing budget (how far in W you can move before artifacts), and a list of known-good editing directions (age, expression, pose).
I'd treat main.py as a demo of the mechanics and not as evidence for the claims in the prose. Its mapping network is 4 layers deep where the Concept section says 8, and its constant is 6 numbers drawn once from a Gaussian and never trained. The AdaIN comparison prints the mean and std of five outputs with AdaIN on and off. The two modes draw different z samples, so the printed numbers aren't a paired comparison, and nothing in the file concatenates z.
My bet is the truncation printout is the one worth running. It builds ŵ from 200 sampled w vectors, then prints the output for one fixed w at ψ of 0.0, 0.5, 0.7, and 1.0. At ψ = 0.0 you're feeding the mean w itself, so the rows show the output moving from the mean-w result to the full-w result as ψ rises. It prints no diversity or quality measure, so the trade in the formula is something you'd have to add.
The noise printout is a weaker check than it looks. It holds w fixed and adds noise with sigma 0.1 under three seeds, so the outputs differ slightly by construction. All it confirms is that noise perturbs the output while w stays put.
Exercises
- Easy. Run
code/main.py.main()already loops over both modes (adain_onis a parameter ofstylegan_forward), so you get the AdaIN on/off printout in one run. Then change it to feed the same fivezsamples to both modes, and compare the spread of outputs for a fixed latent versus a perturbed latent. - Medium. Implement mixing regularization. For a training batch, compute
w_aandw_b, applyw_afor the first half of synthesis andw_bfor the second half, and check whether the decoder learns disentangled styles. - Hard. Take a pretrained StyleGAN3 FFHQ model (ffhq-1024.pkl). Find the
wdirection that controls "smile" by training an SVM on labelled samples, and report how far you can push before identity drifts.
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Mapping network | "The MLP" | f: Z → W, 8 layers in the published model, decouples latent geometry from data statistics. |
| W space | "The style space" | Output of the mapping network; roughly disentangled. |
| AdaIN | "Adaptive instance norm" | Normalize feature map, then scale + shift by w-projection. |
| Truncation trick | "Psi" | w = mean + ψ·(w - mean), ψ<1 trades diversity for quality. |
| Path-length regularization | "PL reg" | Penalizes large changes in image per unit change in w; makes W smoother. |
| Weight demodulation | "The StyleGAN2 fix" | Normalize conv weights instead of activations; kills droplet artifacts. |
| Alias-free | "StyleGAN3's trick" | Windowed sinc filters; eliminates texture sticking to the pixel grid. |
| Inversion | "Find w for a real image" | Optimize or encode x → w so G(w) ≈ x. |
Production note: why StyleGAN still ships in 2026
StyleGAN3 on a 4090 generates a 1024² FFHQ face in roughly 10 ms or less (an approximate, hardware-dependent figure) with num_steps = 1, no VAE decode, and no cross-attention pass. In production terms that's the floor latency for an image generator. A 50-step SDXL + VAE-decode pipeline at the same resolution takes on the order of a few seconds (again approximate). That is a gap of roughly two orders of magnitude, and for narrow-domain products (avatar services, ID document pipelines, stock face generation) it wins on TCO.
Two operational consequences:
- No scheduler, no batcher. A static batch at the target occupancy is the right fit. Continuous batching, which matters for LLMs and diffusion, has little to offer when each request takes the same FLOPs.
- Truncation
ψis the safety knob.ψ < 0.7samples from a narrow cone of the mapping network's range, and that is the lever the serving layer has over sample variance. Lowerψat peak load, raise it for premium users.
Further Reading
- Karras et al. (2019). A Style-Based Generator Architecture for GANs: StyleGAN.
- Karras et al. (2020). Analyzing and Improving the Image Quality of StyleGAN: StyleGAN2.
- Karras et al. (2021). Alias-Free Generative Adversarial Networks: StyleGAN3.
- Tov et al. (2021). Designing an Encoder for StyleGAN Image Manipulation: e4e inversion.
- Sauer et al. (2022). StyleGAN-XL: Scaling StyleGAN to Large Diverse Datasets: StyleGAN-XL.
- Huang et al. (2024). R3GAN: The GAN is dead; long live the GAN!: modern minimal GAN recipe.
Code
- main.py (python, 130 lines)