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 |
Vocabulary permutation Π |
Checkpoint on disk |
Cloud storage |
The transforms below, with the weights locked (Locked weights)
unless the model was sealed with |
Unlocked weights |
VM file system, in the cache directory of |
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 ( |
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 cdhandkllm unlockunlock 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/andvae/directories), files other than safetensors shards and tensors of other dtypes are not locked. The tokenizer is protected separately, insidetrusted.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 |
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.weightandall_final_layer.2-1.linear.bias(the final layer writes patches in the basis B),cap_embedder.0.weightandcap_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 3records 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, onlyinput_idsandoutput_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) |
|
Persistent |
Passphrase |
Terminal, or |
Process |
Unlocked key |
Memory of |
Process |
Decrypted permuted tokenizer, |
|
Seconds |
Unlocked weights, |
|
Until removed |
Decrypted permuted tokenizer, |
|
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
Threat model for the adversary of each profile.
Attacks for what can be recovered from the exposed material.
Sealed checkpoint format for the contents of a sealed checkpoint.