Generating images in client-keyed mode
This page gives the complete procedure for generating Z-Image images on a rented GPU while the model key, the tokenizer and the VAE stay on the user’s machine. The cloud runs the text encoder and the diffusion transformer without a key and returns the final latents encrypted to the requester. Z-Image describes the scheme, and Client-keyed mode the client-keyed mode in general.
Roles and requirements
Machine |
Holds |
Install |
|---|---|---|
User |
Key file, the complete sealed pipeline (including |
|
Cloud |
The |
|
The image extra installs diffusers>=0.36, transformers, FastAPI, Uvicorn and
Pillow on top of seal. The user’s VAE decodes on CUDA, Apple MPS or the CPU.
Supported pipelines: Z-Image text-to-image with 2x2 latent patches. Image-conditioned variants and text encoders with attention biases are refused.
Procedure
Seal on the user’s machine. Create a key if none exists, then seal with
--client.--transformacceptsorth(the default) orperm.$ kllm keygen $ kllm seal ~/models/Z-Image -o ~/models/Z-Image-sealed --client
The command writes the user’s copy to
Z-Image-sealedand the cloud’s part toZ-Image-sealed-cloud(--cloud DIRselects another location). Sealing the 20 GB pipeline took 57 s.Upload only the cloud’s part.
$ rsync -a ~/models/Z-Image-sealed-cloud CLOUD:models/
Serve in the cloud without a key, bound to the loopback interface:
$ kllm serve models/Z-Image-sealed-cloud --client --host 127.0.0.1 --port 30010
For a client-keyed pipeline,
kllm serve --clientstarts kLLM’s latent server,python -m kllm.keyed.client.server, instead of an engine;--enginedoes not apply. Arguments after--go to the latent server, which accepts--device(defaultcuda) and the request defaults--size,--stepsand--guidance-scale.Open a tunnel from the user’s machine:
$ ssh -N -L 30010:127.0.0.1:30010 user@CLOUD
Generate on the user’s machine:
$ kllm client generate ~/models/Z-Image-sealed "A red fox standing in fresh snow at dawn" \ --url http://127.0.0.1:30010 -o fox.png
The command prints the output path, the cloud’s generation time and the device that decoded the latents.
--negative-prompt,--size(default1024x1024),--steps(28),--guidance-scale(4.0),--seed(0) and--deviceset the generation; see Z-Image for the full option table.
Warning
The latent server performs no authentication, does not bound steps or size, and
kllm serve binds 0.0.0.0 unless --host is given. The reply encryption has no
server identity, so an active intermediary on a plain-HTTP path can substitute its own
public key. Keep the server on the loopback interface behind an SSH tunnel, or behind an
authenticating TLS gateway.
What crosses the network
kllm client generate renders the prompt with the pipeline’s chat template, encodes it
with the permuted tokenizer (at most 512 token ids) and sends a request to /v1/latents:
the permuted prompt ids and, when the guidance scale is above 0, the permuted ids of the negative prompt (an empty prompt if none is given);
the size, number of steps, guidance scale and seed;
a fresh X25519 public key generated for this request.
The server runs the sealed text encoder and the stock sampler in the key’s latent basis, serializing requests, and encrypts the final latents to the request’s public key with its own one-off X25519 key (ECDH, HKDF-SHA256, AES-256-GCM). The response carries the ciphertext, and in the clear the latent tensor shape and the generation time. The server never loads a tokenizer or a VAE, writes nothing to disk and runs without request logging.
The client decrypts the latents, maps them back from the key’s patch basis and decodes them with the public VAE.
Measured results
Z-Image sealed with orth, 1024x1024, 28 steps, guidance 4, three prompts by two seeds
from the same noise, against the plain diffusers pipeline on the same NVIDIA A40
(benchmarks/zimage_client.py):
Latent relative error |
PSNR |
End to end |
|
|---|---|---|---|
Plain pipeline |
48.4 s |
||
Plain, DiT attention on PyTorch’s math kernel (noise floor) |
7.2% |
33.9 dB |
|
|
11.5% |
31.6 dB |
48.1 s |
Every pair showed the same scene, subject and composition. On the two prompts where the
noise floor itself was large, the client run drifted further; objects moved slightly.
Sealing under perm drifted as much. The drift comes from the DiT’s input and output
layers, which are rounded to bf16 again in the key’s patch basis, not from the text
encoder.
The encrypted reply carries 1 MB of float32 latents for a 1024x1024 image (1.4 MB in base64). VAE decode times on the user’s side (160 MB VAE):
Device |
Decode time |
|---|---|
NVIDIA A40 |
0.27 s |
Apple M3, 16 GB, MPS, fp16 |
3.1 s |
Apple M3, 16 GB, MPS, fp32 |
4.6 s |
Apple M3 CPU |
148 s |
The first image requested from the Apple M3 through an SSH tunnel took 70 s, including unlocking the key and loading the tokenizer and the VAE; 49 s of that was the cloud. These decode timings have no script in the repository.
What this protects
Protected: what the cloud’s software records or passes along. The request body holds permuted token ids, and the response body holds ciphertext. The size, number of steps, guidance scale, seed, latent shape and generation time are in the clear.
Not protected, and not claimed:
The image in GPU memory. The latents in the server’s GPU memory show the image; a principal component analysis of the cloud’s patch vectors, without the key, recovers a clear thumbnail. This is why the response is encrypted.
The DiT’s activations. Only five DiT tensors are re-based (the patch embedding, the final layer and the caption embedder); every block works in canonical coordinates.
The prompt against frequency analysis. Token ids are under a fixed permutation, which is a substitution cipher. The text encoder is a public Qwen3 checkpoint, and its sealed embedding has the structure that the measured weight attack exploits, so the permutation is expected to be recoverable from the cloud’s copy with the public weights (Weights of a public model, or of a fine-tune of one; measured on Qwen3-8B, not on the Z-Image encoder).
The serving process. A process that is attached to or modified sees everything it computes.
The figure in What this mode protects shows what the cloud holds and the thumbnail that the principal component analysis recovers. The full list of what client-keyed mode does not protect is in Not protected in client-keyed mode.
Z-Image without --client
kllm seal without --client writes a Z-Image pipeline under a separate
demonstration scheme (d1-v1) that ignores --transform and leaves the text encoder,
VAE and tokenizer unchanged. kllm serve --key refuses such a pipeline with
<path> uses scheme 'd1-v1'; reseal it with this kLLM; only kllm demo serves it.
For generation on a rented GPU, seal with --client as above.
Note
kllm demo up is under repair in 0.1.0.dev0 and does not start.
See also
Client-keyed mode for client-keyed mode in general.
Claims for what this mode does not claim.
Troubleshooting for client-keyed error messages.