Exposure

This page lists what each party holds in each deployment profile, and under which transform. The adversary of each profile is defined in Threat model.

Serving in a confidential VM

In the Attested CPU profile the serving stack runs in a confidential VM and the GPU is an ordinary passthrough device. The cloud can read everything that is placed on the GPU.

Item

Where

Under what

Model key

VM memory, released by the deployer’s key broker after attestation

Not applicable

Tokenizer

VM memory, decrypted from trusted.kllm

Vocabulary permutation Π

Checkpoint on disk

Cloud storage

The transforms below, with the weights locked (Locked weights) unless the model was sealed with --no-lock

Unlocked weights

VM file system, in the cache directory of kllm serve (KLLM_UNLOCKED_DIR)

The transforms below, not locked

Weights

GPU

The transforms below

Token ids

GPU

Π⁻¹(token): fixed per token

Activations

GPU

A fixed secret basis per stream (orth) or a fixed signed permutation of channels (perm)

Logits

GPU

Over the permuted vocabulary; sampling runs in the engine on the GPU

MiniMax H3 latents

GPU

A fixed permutation of the latent channels

MiniMax H3 decoded video and audio

GPU

None: the VAE decoders run in the clear

The “under what” column describes what a cloud without further reference material sees. A cloud that also holds the public model from which the weights derive, whether the sealed model is a public model or a fine-tune of one, recovers the transforms and Π from the weights it serves (Weights of a public model, or of a fine-tune of one); for such a cloud, the rows for weights, token ids and logits hide nothing. Locking does not change this: the GPU holds the unlocked weights. Whether the cloud can also read the unlocked copy in the cache directory depends on where the VM stores that directory.

The transforms

The transforms are static and derived from the model key with HKDF. kllm seal --transform selects how the residual stream is hidden; the remaining transforms are shared:

  • Residual stream: a random orthogonal matrix Q (orth, the default), with the RMSNorm gains folded into the projections, or a random signed permutation (perm), with the gains permuted.

  • Attention: whole-group head permutations (per KV group) and quarter turns of RoPE pairs for Q and K; a per-head rotation (orth) or signed permutation (perm) for V and O.

  • MLP: a permutation of the intermediate dimension with ±2^k scales.

  • Vocabulary: a permutation Π of the regular vocabulary. Special tokens are fixed.

For MiniMax H3, the residual stream of the diffusion transformer (DiT) is always a signed permutation, because the AdaLN scales and gates act channel by channel, and the latent channels of both modalities are permuted. The VAE interiors are not transformed: only the channel order of their latent interface changes.

In mixture-of-experts models the experts are also permuted (router rows and expert tensors), and each expert receives its own MLP permutation and scales. In FP8 checkpoints every permutation moves whole quantization blocks and shuffles channels inside them, and the ±2^k scales are constant per block, because the scales are stored per block: the cloud sees which 128 channels share a block, but not their order inside it or the order of the blocks. Q, K and V biases (Qwen2) follow their heads. FP8 checkpoints are sealed under perm only; orth would require re-quantization.

Exactness

The transforms are exact in exact arithmetic. Float64 tests confirm this to 1e-9 on small random models (tests/test_schemes.py, tests/test_families.py, tests/test_h3.py). In bf16 they change rounding and accumulation order only. Under perm the sealed weights are bit-exact rearrangements of the original values; FP8 values are not rounded at all, only moved and sign-flipped, with the powers of two absorbed into the scales. Under orth the rotated weights are rounded again to the checkpoint dtype. The measured effect on outputs is reported in Benchmarking plain and sealed serving and in the project README.

Note

kllm serve --int8 replaces the MiniMax H3 DiT kernels with INT8 kernels. It is a kernel optimization, separate from sealing, with measured numerical drift. Only the INT8 attention kernel is verified to commute with the signed permutations of perm. The INT8 linear path applies a group-wise rotation before quantization that does not commute with them, so a sealed model under --int8 linear or --int8 all does not compute exactly what the plain model computes under the same kernels. See Running the H3 diffusion transformer on INT8 kernels.

Locked weights

Unless a model is sealed with kllm seal --no-lock, sealing ends by locking its weights (kllm.keyed.lock). Every floating-point value of every weight tensor, in bf16, fp16, fp32 or one of the two FP8 formats, is read as its sign-magnitude code and shifted, modulo the number of finite codes up to the tensor’s largest power of two, by an AES-256-CTR key stream derived from the model key and the tensor’s name. Unlocking reverses the shift exactly.

A locked checkpoint keeps:

  • its file names, tensor names, shapes and dtypes and its configuration files, so any engine loads it, and computes nothing useful with it;

  • only finite values, none larger than the largest power of two of the original tensor;

  • in the clear, the domain size of each tensor, that is, its largest binary exponent, recorded in the safetensors header, and a lock flag in the seal metadata (Sealed checkpoint format).

Without the key, the values of a locked tensor are, up to the security of AES, uniformly random codes in that range. The public base therefore has nothing to be matched against: the weight-recovery attack (Weights of a public model, or of a fine-tune of one) needs the unlocked weights. This follows from the construction; a unit test checks that the sorted-magnitude profile of a column, which the attack under perm uses, no longer matches the base. The attack script itself operates on unlocked weights, as a GPU holds them.

What locking does not cover:

  • A running model. kllm serve --key, kllm serve --key-source cdh and kllm unlock unlock the weights into a cache directory before the engine loads them, and the GPU holds the unlocked weights. In client-keyed mode the cloud’s copy is unlocked on the user’s side (kllm seal --client, kllm cloud --key) and uploaded unlocked. Whoever runs the model holds the unlocked weights, and with the public base at hand can match them to it.

  • The layout. Names, shapes, dtypes and the configuration identify the architecture, and for a published model the model card and the manifest name the base.

  • Other files. The user’s side of a client-keyed pipeline (the tokenizer/, processor/ and vae/ directories), files other than safetensors shards and tensors of other dtypes are not locked. The tokenizer is protected separately, inside trusted.kllm.

  • Models sealed with --no-lock, or by a kLLM version without locking. Their weights are readable on disk, and the storage thief who holds the public base is in the case of Weights of a public model, or of a fine-tune of one.

Client-keyed serving

In the Private Seal profile, kllm seal --client writes the sealed model and, next to it, a -cloud directory (--cloud DIR overrides the location). Only that directory is uploaded. The sealed model on the user’s machine is locked; the -cloud directory is written with its weights unlocked, because the keyless server cannot unlock them. kllm cloud MODEL -o DIR --key KEYFILE writes the same directory from an existing sealed model, for example a catalog model. It excludes trusted.kllm, every tokenizer file (tokenizer.json, vocab.json, merges.txt, tokenizer_config.json, special_tokens_map.json, added_tokens.json, tokenizer.model, spiece.model), every chat_template* file, and the tokenizer/, processor/ and vae/ directories.

Item

Where

What the cloud has

Model key, tokenizer

User

Nothing

Sealed weights

Cloud: the -cloud directory and GPU memory

The transforms, not locked: the cloud’s copy is unlocked on the user’s side before upload. For a public model, or a fine-tune of one, the transforms and Π are recoverable from them with the public checkpoint (Weights of a public model, or of a fine-tune of one)

LLM prompt and reply

Cloud: request and response bodies, engine logs

Token ids under Π; readable as text by a cloud that has recovered Π from the weights of a public model

Z-Image prompt

Cloud: request body

Token ids under Π

Z-Image result

Cloud: response body

Latents, AES-256-GCM encrypted to a one-off X25519 key of the requester

Z-Image result

Cloud: GPU memory of the server

Latents in the key’s 2×2 patch basis: the image is visible

VAE

User

Nothing

The cloud copy keeps config.json and kllm.json, which record the scheme, the transform and the key fingerprint. The fingerprint is 64 bits of HKDF output and reveals nothing about the key, but it allows the cloud to link models sealed under the same key.

What the Z-Image client seal changes

The client-keyed Z-Image seal transforms the Qwen3 text encoder under the LLM transforms and Π, and re-bases the latent interface of the DiT in a key-derived orthogonal basis B of 2×2 latent patches (a 64-dimensional basis for 16 latent channels). It changes exactly five DiT tensors:

  • all_x_embedder.2-1.weight (the patch embedding reads patches in the basis B),

  • all_final_layer.2-1.linear.weight and all_final_layer.2-1.linear.bias (the final layer writes patches in the basis B),

  • cap_embedder.0.weight and cap_embedder.1.weight (the caption embedder reads the text encoder’s transformed features).

Every other DiT tensor, including all transformer blocks, refiners and AdaLN weights, is byte-identical to the public checkpoint. After the two embedders, the DiT’s hidden states are therefore in the same coordinates as in a plain run. The client seal transforms the latent input and output and the text-encoder stream; it does not hide the DiT’s activations.

Warning

A cloud that holds the sealed all_x_embedder.2-1.weight (the public weight W multiplied by Bᵀ) and the public W can recover B by least squares. This follows analytically and has not been measured. It concerns the weights, which the Private Seal profile declares out of scope.

Measured log check

The log check was run on A40 GPUs with Qwen3-1.7B under SGLang and vLLM, and with Z-Image, by searching the logs for the prompt and the reply (benchmarks/client.sh, benchmarks/zimage_client.py):

  • SGLang started with --log-requests --log-requests-level 3 records every request in full. Served with --key, its log held 25 of the 27 three-word phrases of the test prompt and reply. Served with --client, its log held none of them, only input_ids and output_ids. The vLLM request log records sampling parameters only, under either mode.

  • The Z-Image server logs no request bodies, and its response holds ciphertext only. The server encrypts each result to the public key that came with the request, using its own one-off key, and keeps no key afterwards (kllm.keyed.client.reply).

Where keys and decrypted tokenizers live

Item

Location

Lifetime

Key file (scrypt, AES-256-GCM)

~/.kllm/keys/default.key by default, mode 0600

Persistent

Passphrase

Terminal, or KLLM_PASSPHRASE

Process

Unlocked key

Memory of kllm seal, kllm client proxy and kllm client generate

Process

Decrypted permuted tokenizer, kllm client

/dev/shm/kllm-tok-*, removed once loaded

Seconds

Unlocked weights, kllm serve --key, --key-source cdh and kllm unlock

~/.cache/kllm/unlocked/NAME-DIGEST (KLLM_UNLOCKED_DIR overrides the directory), mode 0700; written once per key and set of weight files

Until removed

Decrypted permuted tokenizer, kllm serve --key, --key-source cdh and kllm unlock

/dev/shm/kllm-tok-* (for MiniMax H3, a view of the sealed pipeline with the tokenizer files in place)

Until reboot

Reply keys (Z-Image)

Client memory, one per request; the server’s one-off key is discarded

Request

Outside a confidential VM, /dev/shm is host memory, and the cache of unlocked weights is an ordinary directory on the host’s file system. kLLM removes neither. The consequence for kllm serve --key is stated in Threat model.

See also