Covariant obfuscation for private LLM inference¶
Scope: the pattern where a client transforms an open-weight model offline, ships the transformed weights to an untrusted operator, and then talks to that model in a private token alphabet. This page covers what "covariant" means precisely, the key-matrix construction that hides the secret permutation, which model components commute exactly and which only approximately, the multi-tenancy cost of per-client weights, and where the published privacy numbers hold and where they do not. The approach that keeps the secret in the activations and needs a confidential GPU is activation obfuscation on untrusted accelerators; the hardware-attested alternative is GPU confidential computing; the layer-split alternative is privacy-aware split inference.
Primary source: Yu Lin, Qizhi Zhang, Wenqiang Ruan, Daode Zhang, Jue Hong, Ye Wu (ByteDance) and Hanning Xia, Yunlong Mao, Sheng Zhong (Nanjing University), "Towards Privacy-Preserving LLM Inference via Covariant Obfuscation (Technical Report)", arXiv:2603.01499v2 [cs.CR], 30 March 2026. Technical report, no venue stated, no code released. The system is named AloePri.
What this page adds. Both Python blocks are executed and asserted (Python 3.12.3, numpy 2.4.6). The first implements the paper's Algorithm 1 key matrices and confirms the feed-forward network commutes exactly, then reproduces from first principles the mechanism behind the paper's own Table 4 ablation: key matrices leave attention scores bit-identical, and only the rotary block permutation moves them. It also separates the two components that do not commute exactly, one of them deliberately, and shows the residual shrinking as the model widens. The second bounds the paper's Theorem 4 privacy guarantee using only the definition of its own terms, and prices the per-tenant model copy the design requires. Figures with a table number are the paper's.
The efficiency headline is measured on a small workload. The stated setup is 17-token average prompts, 100 generated tokens, tensor parallelism 4, and request concurrency of 1 and 4. That description is given only for the 14B model; the paper states no setup at all for its DeepSeek-V3.1-Terminus rows. There is no throughput, batch-size, or sustained-load measurement anywhere in the paper.
What it is¶
A data-only obfuscation scheme perturbs the input and hopes the model still works. A covariant obfuscation transforms the input and the weights so that the obfuscated model computes exactly the obfuscated answer. Formally the paper defines the quintuple (phi_X, phi_Theta, phi_Y, psi_Y, f~): a data map, a weight map, an output map, an output inverse, and inference in the obfuscated space, satisfying a commutation condition f~(phi_X(x), phi_Theta(theta)) approximately equals phi_Y(f(x, theta)) within a stated error, and a de-obfuscation condition psi_Y . phi_Y = id. Data-only obfuscation is the degenerate case where the weight map is the identity.
The concrete system works in two phases.
Offline, on the client. The client holds the open-weight checkpoint. It samples a secret token permutation and applies it to the embedding table and the LM head, which is what makes the private alphabet work. On its own that permutation is trivially recoverable by comparing public and published weights, so it is buried under a stack of further transforms, the point being that an attacker holding the original checkpoint can otherwise line it up against the obfuscated one: Gaussian noise added to the embedding and head, and a set of key matrices applied to every weight matrix in the model. The key matrices come in pairs P of shape d x (d+2h) and Q of shape (d+2h) x d with P Q = I. Adjacent layers get matched pairs, so each transform is cancelled by the next layer's inverse as activations flow through the network. Attention picks up rotary-aware rotations and scalings, plus permutations of attention heads and of the rotary blocks; the FFN picks up a diagonal scaling and a permutation chosen so SiLU and the Hadamard product pass through; a mixture-of-experts router picks up an expert permutation.
Online. The client tokenizes the prompt locally, maps each token through the secret vocabulary mapping, detokenizes, and sends what looks like ordinary text. The server tokenizes it and runs a completely standard inference stack. The response comes back in the private alphabet and the client maps it home.
The property that matters for infrastructure is what is absent. There is no cryptographic protocol at inference time, no TEE, no per-token round trip, no engine modification. The server runs vLLM on weights that have the same shapes and the same dtypes as any other checkpoint, one dimension wider.
Why use it¶
The paper's framing is three industrial constraints, and they are the right ones for a serving fleet.
Hardware compatibility. Confidential computing exists on recent NVIDIA parts and nowhere else in a typical fleet. A cluster of mixed accelerators, older GPUs, NPUs, or FPGAs cannot run a TEE-anchored scheme at all. A weight transformation runs anywhere a matrix multiply runs.
Software compatibility. Paged attention, prefix caching, prefill and decode disaggregation, continuous batching, and speculative decoding are all engineering that a privacy scheme built on a custom protocol has to reimplement. Because the obfuscated model is structurally a normal model, the paper reports deploying it under vLLM 0.9.1 with no engine changes.
Accuracy and efficiency. Table 2 on Qwen2.5-14B-Instruct puts plaintext at 97.25 SST2, 81.95 MMLU, 77.45 PIQA and 77.09 IFEval, against 97.13, 80.61, 75.23 and 79.49 obfuscated. The obfuscation-based baselines it compares against are far behind: RANTEXT drops PIQA from 77.45 to 18.30, and SGT drops MMLU from 81.95 to 30.30 while still leaking over 90% of tokens to an inversion-model attack. Against those baselines the result is not close.
The scale of the demonstration is also unusual for this literature. Most privacy-preserving inference papers stop at a 7B model; this one obfuscates DeepSeek-V3.1-Terminus at 671B parameters and serves it.
Read the accuracy claim per model rather than from the abstract, which quotes "0.0% ~ 3.5%". Table 3 holds two regressions well outside that band: Qwen3-MoE-30B-A3B loses 7.34 points of PIQA (89.55 to 82.21) and Llama3-8B loses 5.61 points of C-Eval (50.16 to 44.55), which are 8.2% and 11.2% in relative terms. Both are larger than the generational gap the paper opens by arguing no privacy scheme should cost.
When to use it (and when not)¶
The decisive cost is that every client needs its own copy of the model. The client generates the secret and performs the obfuscation, so the weights deployed on the server are specific to that client. Two tenants cannot share a set of weights, because sharing them would mean sharing the secret. Everything that makes multi-tenant serving economic depends on the opposite: one resident copy of the weights amortised over many tenants. The paper never states this consequence, and it is the first thing to price. For DeepSeek-V3.1-Terminus at 671B, ten tenants means ten obfuscated checkpoints, about 13.9 TB at the bfloat16 the paper actually evaluates, once the widened residual stream is counted, and 80 hours of offline obfuscation. The executed block below tabulates it.
Privacy is not uniform across models, and the flagship number does not generalise. The abstract, the introduction and the conclusion all report "less than 5% of tokens" recovered, and all three scope it explicitly to DeepSeek-V3.1-Terminus, which measures 4.80%. That is fair as written, and Llama3-8B is lower still at 2.57%. The problem is that the same table reports 25.05% for Qwen3-14B and 19.64% for Qwen3-32B under the same vocabulary-matching attack.1 A quarter of tokens recovered is a different security posture from one in twenty, and nothing in the paper explains the spread. Run the attack against your own model rather than inheriting a number.
The formal guarantee does not carry the weight it appears to. Theorem 4 bounds a Renyi-metric differential privacy budget, and Remark 1 concludes AloePri's budget is strictly smaller than that of the token-perturbation mechanism it composes with. Both statements are true. The executed audit below shows the reduction is smaller than one part in a billion for any usable budget, and that the token-perturbation mechanism is optional and not used in the experiments that produce the accuracy numbers. The honest description of the security is the paper's own remark, that it "aims to provide adjustable security for constrained attackers in real-world scenarios, rather than offering ideal-world security guarantees against worst-case attackers."
Do not read the efficiency claim as a throughput result. The body says AloePri "achieves identical online efficiency to plaintext inference"; the abstract says "efficiency equivalent to that of plaintext inference". Table 7's largest measurement is concurrency 4 on 17-token prompts. On the 14B model the obfuscated build posts a lower time per output token than plaintext at both concurrency levels, by 3.0% and 2.6%, which cannot be a real effect given it does strictly more work on a wider residual stream. The measurement noise exceeds the quantity being measured, and it does so at the largest concurrency tested, not only at one. The genuine signal is in the 671B rows: TTFT rises 3.6% then 8.0%, and TPOT 2.6% then 4.6%.
Do not expect any integrity guarantee. The paper assumes an honest-but-curious server that runs the model faithfully and only tries to learn from what it sees. Nothing in the construction detects a server that substitutes weights, tampers with outputs, drops the obfuscation, or degrades quality, and the client has no way to verify which weights ran. The transforms are invertible, not authenticated. If the reason for not trusting the operator includes anything active, this scheme does not address it and GPU confidential computing, which attests what executed, is the relevant control.
Do not use it where the client cannot hold the plaintext model. The client must download the full checkpoint, obfuscate it locally, and upload the result. The paper's client is a CPU-only host with two Xeon 8457C processors. That is a heavy client for what is sold as a lightweight scheme, and it inverts the usual reason for using a hosted service.
Do not use it on an architecture without rotary embeddings without re-testing. The executed block below shows that key matrices, rotations, and scalings all cancel inside the attention score, leaving it bit-identical to plaintext. Only the rotary block permutation perturbs the score, and it does so only because rotary embeddings tie a fixed frequency to each position in the head vector. This is precisely why the paper's own Table 4 shows attention-score recovery stuck at 87.14% until head and block permutations are added.
Architecture¶
flowchart TB
subgraph CLIENT["Client, offline (CPU host)"]
OPEN["Open-weight checkpoint"] --> PERM["Secret token permutation tau<br/>applied to embedding + LM head"]
PERM --> HIDE["Hide tau: Gaussian noise,<br/>key matrices P/Q with P Q = I,<br/>RoPE rotations, head + block perms"]
HIDE --> OBF["Obfuscated checkpoint<br/>residual stream d -> d + 2h"]
end
subgraph SERVER["Untrusted operator"]
ENGINE["Stock vLLM / SGLang<br/>no engine changes"]
end
subgraph ONLINE["Online, per request"]
MAP["Client maps tokens<br/>through secret vocabulary"]
BACK["Client maps response back"]
end
OBF -->|"one-time upload, per tenant"| ENGINE
MAP -->|"obfuscated text"| ENGINE
ENGINE -->|"obfuscated text"| BACK
The arrow to watch is the one from the client's obfuscated checkpoint to the server. It is per tenant, it carries the whole model, and it is what buys the absence of any protocol on the request path.
How to use it¶
The paper's defaults are matrix coefficient lambda = 0.3, expansion size h = 128, embedding noise alpha_e = 1.0, head noise alpha_h = 0.2, and rotary block parameters beta = 8, gamma = 1e3. Three of those have measured cliffs on either side.
Embedding noise is the one that fails open. At alpha_e = 0.5 the vocabulary-matching attack recovers over 30% of tokens, because with small noise the plaintext and obfuscated weight pairs differ almost only by row and column permutations, and sorting then matching recovers the secret permutation directly. The noise is not a refinement; it is what makes the permutation unrecoverable. That cliff was measured on Qwen2.5-14B against C-Eval only, and the paper's own hyperparameter table records alpha_e = 0.5 for Llama3-8B in Table 3, the model that then posts the lowest token recovery of all seven at 2.57%. So the threshold is model-specific rather than a property of the mechanism: measure it on your checkpoint instead of inheriting 1.0.
The matrix coefficient fails closed, and it fails in bfloat16 first. At lambda = 3.0 accuracy collapses under bfloat16 while float32 survives. The paper attributes this to "numerical overflow" as the internal-state range widens, which cannot be the mechanism: bfloat16 and float32 share an 8-bit exponent and the same ceiling near 3.4e38, and the paper's own Figure 4b tops out around 140. The difference between the two formats here is mantissa width, 8 bits against 24, so the failure is lost precision on a widened dynamic range rather than overflow. The failure is numeric, not cryptographic, and it will present as a quality regression rather than as an error.
The expansion size is a straightforward compute tax. It widens the residual stream from d to d + 2h, which at the default is 5% on a 14B and 3.6% on DeepSeek-V3.1. Raising it to 512 costs under 10% additional time per output token, per Figure 5.
Validate on the model you intend to ship. Table 4's ablation is the template for how to do this: obfuscate incrementally, and run an internal-state inversion attack against attention scores and hidden states separately after each step. Noise alone leaves attention scores at 87.14% recovery and hidden states at 40.0%. Adding key matrices takes hidden states to 0.82% and leaves attention scores untouched at 87.14%. Only head and block permutation takes both to 0.0%.
How to develop with it¶
The mechanism is short enough to build and check directly. What follows constructs the paper's Algorithm 1 key matrices, verifies the FFN commutes, and then isolates why the attention score needs its own defence.
# Runnable on system python3 (numpy only). Covariant obfuscation (AloePri,
# arXiv:2603.01499v2 Sec 5.2): transform weights and data together so the obfuscated
# model computes the obfuscated answer exactly. We build the rectangular key matrices
# of Algorithm 1, assert the FFN commutes exactly, and then reproduce the mechanism
# behind the paper's own Table 4 ablation: key matrices leave attention scores
# untouched, and only the RoPE block permutation moves them.
import numpy as np
rng = np.random.default_rng(7)
d, h, lam = 64, 16, 0.3 # hidden size, expansion size, matrix coefficient
def haar(m, rng):
Q, R = np.linalg.qr(rng.standard_normal((m, m)))
return Q * np.sign(np.diag(R))
def key_matrices(d, h, lam, rng):
"""Algorithm 1. P is d x (d+2h) and Q is (d+2h) x d with P @ Q == I_d.
The cancellation needs C F == 0 and E D == 0, so C's rows live in the left
null space of F and D's columns live in the null space of E. Both null spaces
are non-trivial because E and F are built through a width-h/2 bottleneck."""
g = lambda *shape: rng.standard_normal(shape) / np.sqrt(d) # N(0, 1/d) per Alg. 1
B = haar(d, rng) + lam * g(d, d)
E = g(d, h // 2) @ g(h // 2, h) # each FACTOR is N(0,1/d), so E carries h/(2d^2)
F = g(h, h // 2) @ g(h // 2, d)
Z = haar(d + 2 * h, rng)
def null(M): # orthonormal basis of the null space of M
_, s, Vt = np.linalg.svd(M)
return Vt[np.sum(s > 1e-10 * max(M.shape) * s.max()):].T
nF, nE = null(F.T).T, null(E) # rows for C, columns for D
# Algorithm 1 leaves C's and D's scale unstated; keep them at the same N(0,1/d)
# as every other sampled block, which is what lambda is described as regulating.
C = g(d, nF.shape[0]) @ nF
D = nE @ g(nE.shape[1], d)
P = np.hstack([B, C, E]) @ Z
Q = Z.T @ np.vstack([np.linalg.inv(B), F, D])
return P, Q
P, Q = key_matrices(d, h, lam, rng)
assert P.shape == (d, d + 2 * h) and Q.shape == (d + 2 * h, d)
assert np.allclose(P @ Q, np.eye(d), atol=1e-9), "P Q must be the identity on R^d"
# The reverse product is deliberately not the identity: Q P is a rank-d projector
# inside the widened space, which is what hides the secret permutation.
assert not np.allclose(Q @ P, np.eye(d + 2 * h), atol=1e-3)
print(f"1. key matrices P {P.shape} Q {Q.shape} "
f"||PQ - I|| = {np.linalg.norm(P @ Q - np.eye(d)):.2e} "
f"rank(QP) = {np.linalg.matrix_rank(Q @ P)} of {d + 2*h}")
# 2. FFN covariance. Weights absorb the transforms; the output is the plaintext
# output right-multiplied by the next key matrix. Only the permutation Z commutes
# with SiLU and the Hadamard product. The scaling H does not: it survives solely
# because it is confined to the up branch, which SiLU never sees, and is undone by
# inv(H) before W_down. Moving H to the gate branch breaks the identity outright.
silu = lambda x: x / (1.0 + np.exp(-x))
d_ffn = 128
Wg, Wu = rng.standard_normal((d, d_ffn)), rng.standard_normal((d, d_ffn))
Wd = rng.standard_normal((d_ffn, d))
Zf = np.eye(d_ffn)[rng.permutation(d_ffn)]
Hf = np.diag(np.exp(rng.uniform(-0.5, 0.5, d_ffn)))
P_dn, Q_dn = key_matrices(d, h, lam, rng)
Wg_o, Wu_o = Q @ Wg @ Zf, Q @ Wu @ Hf @ Zf
Wd_o = np.linalg.inv(Zf) @ np.linalg.inv(Hf) @ Wd @ P_dn
x = rng.standard_normal((8, d))
plain = (silu(x @ Wg) * (x @ Wu)) @ Wd
obf = (silu((x @ P) @ Wg_o) * ((x @ P) @ Wu_o)) @ Wd_o
assert np.allclose(obf, plain @ P_dn, atol=1e-8), "FFN must commute exactly"
print(f"2. FFN covariance max abs err {np.abs(obf - plain @ P_dn).max():.2e}")
# 3. Attention scores. With Q_q/Q_k cancelling the input key matrix, an orthogonal
# rotation R, and a diagonal scaling H applied inversely to q and k, every factor
# cancels inside q k^T. Without RoPE the obfuscated scores equal the plaintext
# scores to float64 rounding, so an attacker reading attention scores learns
# everything. In the bfloat16 the paper actually serves, the cancellation is only
# approximate, but a few percent of drift does not impede an inversion attack.
d_head, n_tok = 32, 12
Wq, Wk = rng.standard_normal((d, d_head)), rng.standard_normal((d, d_head))
n_blk = d_head // 2
ang = rng.uniform(0, 2 * np.pi, n_blk)
R = np.zeros((d_head, d_head))
for i, a in enumerate(ang): # block-diagonal 2x2 rotations
R[2*i:2*i+2, 2*i:2*i+2] = [[np.cos(a), -np.sin(a)], [np.sin(a), np.cos(a)]]
s = np.exp(rng.uniform(-0.5, 0.5, n_blk))
Hqk = np.diag(np.repeat(s, 2))
perm = rng.permutation(n_blk)
Zb = np.zeros((d_head, d_head)) # permute the 2x2 RoPE blocks
for j, pj in enumerate(perm):
Zb[2*pj:2*pj+2, 2*j:2*j+2] = np.eye(2)
Wq_o = Q @ Wq @ R @ Hqk @ Zb
Wk_o = Q @ Wk @ R @ np.linalg.inv(Hqk) @ Zb
xs = rng.standard_normal((n_tok, d))
q_p, k_p = xs @ Wq, xs @ Wk
q_o, k_o = (xs @ P) @ Wq_o, (xs @ P) @ Wk_o
assert np.allclose(q_o @ k_o.T, q_p @ k_p.T, atol=1e-7), "scores leak without RoPE perm"
print(f"3. scores, no RoPE max abs err {np.abs(q_o @ k_o.T - q_p @ k_p.T).max():.2e}"
" (identical: fully leaked)")
def rope(v, base=10000.0):
"""Apply RoPE in place over 2-dim blocks; block i gets frequency base^(-2i/d)."""
pos = np.arange(v.shape[0])[:, None]
theta = base ** (-2.0 * np.arange(v.shape[1] // 2) / v.shape[1])
ang, out = pos * theta, np.empty_like(v)
ev, od = v[:, 0::2], v[:, 1::2]
out[:, 0::2] = ev * np.cos(ang) - od * np.sin(ang)
out[:, 1::2] = ev * np.sin(ang) + od * np.cos(ang)
return out
# 4. RoPE is what makes the block permutation bite. Because RoPE assigns a fixed
# frequency to each position in the head vector, shuffling the blocks changes which
# frequency each pair receives, and the scores move. The permutation is inert
# without RoPE (step 3) and load-bearing with it.
sc_p = rope(q_p) @ rope(k_p).T
sc_o = rope(q_o) @ rope(k_o).T
drift = np.abs(sc_o - sc_p).max() / np.abs(sc_p).max()
assert drift > 0.1, "RoPE plus block permutation must perturb the scores"
print(f"4. scores, RoPE + blockperm relative drift {drift:.2f} (perturbed: protected)")
# Control: with the identity block permutation the scores return to plaintext even
# under RoPE, confirming the drift comes from the permutation and not from R or H.
Wq_id, Wk_id = Q @ Wq @ R @ Hqk, Q @ Wk @ R @ np.linalg.inv(Hqk)
sc_id = rope((xs @ P) @ Wq_id) @ rope((xs @ P) @ Wk_id).T
assert np.allclose(sc_id, sc_p, atol=1e-6), "R and H alone never move the scores"
print(f"5. control, no blockperm max abs err {np.abs(sc_id - sc_p).max():.2e}")
# 6. Two components carry residual error, and only one of them is unwanted.
# Attention's error is deliberate: the RoPE block permutation IS the defence, so its
# e_attn term is the price of step 4. RMSNorm's is unavoidable: it divides by a norm,
# and ||x P|| is not proportional to ||x|| unless P is orthogonal, so no reweighting
# fixes it per token. Matching c * RMSNorm_{d+2h}(x P) to RMSNorm_d(x) P gives
# c = r * sqrt(d / (d+2h)) with r = ||x P|| / ||x||; the best a constant can do is E[r].
# Note the paper defines kappa = E[r] with no width factor (Sec 5.2.5), while its own
# Sec 5.4 derivation carries the sqrt. We use the form that actually cancels.
rms = lambda v: v / np.sqrt((v ** 2).mean(axis=-1, keepdims=True))
big = rng.standard_normal((4096, d))
r = np.linalg.norm(big @ P, axis=1) / np.linalg.norm(big, axis=1)
kappa = r.mean() * np.sqrt(d / (d + 2 * h))
target = rms(x) @ P
naive = np.linalg.norm(rms(x @ P) - target) / np.linalg.norm(target)
fixed = np.linalg.norm(kappa * rms(x @ P) - target) / np.linalg.norm(target)
cv = r.std() / r.mean()
print(f"6. RMSNorm kappa {kappa:.4f} spread of ||xP||/||x|| {100*cv:.1f}%")
print(f" uncorrected rel err {naive:.4f} corrected {fixed:.4f}")
assert fixed < naive, "the kappa correction must reduce the mismatch"
# The residual is not arbitrary: it is set by the per-token spread of r, so it should
# land within a small factor of that spread rather than at some tuned value.
assert 0.5 * cv < fixed < 2.0 * cv, "residual must track the spread of ||xP||/||x||"
# That spread shrinks as the model widens, because ||xP||^2 concentrates. The toy
# dimensions above overstate the residual a production model would see.
print(" residual vs width (h fixed at d/8, 200 probe tokens):")
for dd in (64, 128, 256, 512):
Pw, _ = key_matrices(dd, dd // 8, lam, rng)
xb = rng.standard_normal((200, dd))
rw = np.linalg.norm(xb @ Pw, axis=1) / np.linalg.norm(xb, axis=1)
kw = rw.mean() * np.sqrt(dd / (dd + 2 * (dd // 8)))
tg = rms(xb) @ Pw
fw = np.linalg.norm(kw * rms(xb @ Pw) - tg) / np.linalg.norm(tg)
print(f" d={dd:>4} spread {100*rw.std()/rw.mean():>5.2f}% residual {fw:.4f}")
assert fw < 0.02, "at the widest tested size the residual is under 2%"
# An orthogonal key matrix would make r constant and the layer exactly covariant,
# at the cost of revealing every hidden state's norm.
P_ortho = haar(d, rng)
err_o = np.linalg.norm(rms(x @ P_ortho) - rms(x) @ P_ortho) / np.linalg.norm(rms(x) @ P_ortho)
assert err_o < 1e-12, "an orthogonal key matrix makes RMSNorm exactly covariant"
print(f" orthogonal P control: rel err {err_o:.2e} (exact, but leaks the norm)")
print("all assertions passed")
Executed output:
1. key matrices P (64, 96) Q (96, 64) ||PQ - I|| = 1.40e-14 rank(QP) = 64 of 96
2. FFN covariance max abs err 9.55e-12
3. scores, no RoPE max abs err 2.64e-12 (identical: fully leaked)
4. scores, RoPE + blockperm relative drift 1.33 (perturbed: protected)
5. control, no blockperm max abs err 2.33e-12
6. RMSNorm kappa 0.9133 spread of ||xP||/||x|| 4.0%
uncorrected rel err 0.1042 corrected 0.0468
residual vs width (h fixed at d/8, 200 probe tokens):
d= 64 spread 3.57% residual 0.0357
d= 128 spread 2.47% residual 0.0247
d= 256 spread 1.89% residual 0.0189
d= 512 spread 1.35% residual 0.0135
orthogonal P control: rel err 4.23e-16 (exact, but leaks the norm)
all assertions passed
Steps 3 through 5 are the useful part. The attention score is an inner product between the query and key projections of the same input, so any transform applied to both sides cancels: the input key matrix cancels against its inverse, the rotation is orthogonal, and the diagonal scaling appears with opposite exponents. The result matches the plaintext score to float64 rounding, which is exactly what the paper's Table 4 measures when it reports attention-score recovery unchanged at 87.14% after key matrices are added. In the bfloat16 the paper actually serves, the cancellation is approximate rather than exact, but a few percent of drift is nowhere near enough to impede an inversion attack, so the security conclusion is unchanged. What breaks the identity is that rotary embeddings bind a specific frequency to a specific pair of coordinates, so permuting the pairs changes the frequency each one receives, and the control in step 5 confirms nothing else in the construction contributes.
Two implementation traps sit behind this result. The first is the rotary pairing convention. The block pairs coordinates (2i, 2i+1), the interleaved layout, and builds the rotation, the scaling and the block permutation on that same pairing. Llama, Qwen and DeepSeek as served by Hugging Face and vLLM use the half-split layout that pairs (i, i + d_head/2). Applying transforms built on one pairing to an engine that rotates on the other breaks the cancellation even with no block permutation at all, so the obfuscation must be constructed against the pairing the serving engine uses, not against the one in the paper's notation. The second is that Algorithm 2 as typeset applies the block permutation to the query weights and its transpose to the key weights, which would make the scores differ by more than 100% before RoPE is involved and cannot be what is meant.2
One caveat on the magnitude. The block permutes all rotary blocks uniformly, which is a stronger scramble than the paper ships: Algorithm 2 permutes only inside a sampled window of at most beta = 8 adjacent blocks, weighted so that high-index low-frequency blocks are preferred precisely because moving them costs the least accuracy. The direction of the result is unaffected, since the control shows the permutation is the only term that moves the score at all, but the drift measured here is an upper bound on what the shipped algorithm produces, not an estimate of it.
Step 6 separates the two components that carry residual error, which the paper's accuracy analysis also does but the earlier version of this page conflated. Attention is inexact on purpose: the rotary block permutation is the defence, so its error term is the price of step 4, not a flaw. RMSNorm is inexact unavoidably, because it divides by a norm and a non-orthogonal key matrix changes each token's norm by a different factor, so one rescaling constant can only correct the mean.
The size of that residual depends on how the key matrices are sampled, and it is easy to overstate. Algorithm 1 draws each factor of E and F from N(0, 1/d), so the products carry variance h/(2d^2); scaling the product once by 1/sqrt(d) instead inflates them by sqrt(d) and inflates the residual with them. With the sampling as specified, the spread of ||xP||/||x|| is 4.0% at the toy width used here and falls steadily as the model widens, reaching 1.35% by d = 512. Extrapolated to a production hidden size the residual is well under a percent, which is what makes the paper's reported accuracy loss achievable at all. An orthogonal key matrix would make the layer exact and would also make every hidden state's norm directly observable.
One notational trap: Section 5.2.5 defines the correction as kappa = E[||xP||/||x||] with no width factor, while the Section 5.4 derivation carries sqrt(d/(d+2h)). Only the second cancels, and it is the one used above.
Auditing the privacy bound and the tenancy cost¶
# Runnable on system python3 (numpy only). Two audits of AloePri
# (arXiv:2603.01499v2): what its Theorem 4 privacy bound is actually worth, and what
# per-client model obfuscation costs a serving fleet.
import numpy as np
# --- Audit 1: Theorem 4 -------------------------------------------------------
# Theorem 4 gives eps = eps1 - eps1^2 / (4 (n-1) eps2) when eps1 <= 2 (n-1) eps2,
# where n is the vocabulary size, eps1 is the budget of an OPTIONAL token-perturbation
# mechanism, and eps2 = pi^2 (eps_e + eps_h) with
# eps_e = alpha (lam1(We)^2 + lam2(We)^2) / (4 sigma_e^2), alpha = 2.
# Remark 1 concludes AloePri's budget is "strictly smaller" than eps1. It is. The
# question is by how much, and that is decided by the size of (n-1) eps2.
#
# We do not need the real embedding matrix. sigma_e is the entrywise standard
# deviation of We, so for a mean-zero We, ||We||_F^2 = n d sigma_e^2. At most
# min(n,d) = d singular values are non-zero, so lam1^2 >= ||We||_F^2 / d = n sigma_e^2.
# That is a bound, not an estimate: any real embedding matrix is more concentrated
# than the equality case, making eps_e larger still.
ALPHA = 2.0
def eps2_lower_bound(vocab, d):
"""Lower bound on eps2 from Theorem 4, using only lam1^2 >= n sigma_e^2."""
eps_e_min = ALPHA * vocab / 4.0 # (lam1^2 + lam2^2)/(4 sigma^2) >= n/4 * alpha
return np.pi ** 2 * eps_e_min # eps_h >= 0, so drop it
MODELS = {"Qwen2.5-14B / R1-Distill-14B": (152064, 5120),
"DeepSeek-V3.1-Terminus": (129280, 7168)}
print("Theorem 4: how much does model obfuscation reduce the token budget eps1?")
print(f"{'model':<30} {'(n-1)*eps2 >=':>16} {'eps1=1':>10} {'eps1=10':>10}")
print("-" * 70)
for name, (vocab, d) in MODELS.items():
floor = (vocab - 1) * eps2_lower_bound(vocab, d)
red = [e / (4.0 * floor) for e in (1.0, 10.0)] # relative reduction eps1/(4(n-1)eps2)
print(f"{name:<30} {floor:>16.3e} {red[0]:>10.2e} {red[1]:>10.2e}")
# The regime switch at eps1 > 2(n-1)eps2 is unreachable for any usable budget.
assert 2 * floor > 1e9, "the piecewise branch never activates in practice"
assert red[1] < 1e-9, "the improvement over eps1 alone is numerically negligible"
print("\nRemark 1 holds (eps < eps1) but the gap is below one part in a billion,")
print("so the theorem does not make a weak token mechanism meaningfully stronger.")
# And with no token mechanism at all, eps1 is unbounded and eps saturates at the cap,
# which is astronomically large: a formally vacuous guarantee.
vocab, d = MODELS["Qwen2.5-14B / R1-Distill-14B"]
cap = (vocab - 1) * eps2_lower_bound(vocab, d)
print(f"With no token mechanism, eps saturates at (n-1)eps2 >= {cap:.2e} "
"(no usable guarantee).")
assert cap > 1e10
# --- Audit 2: what per-client obfuscation costs -------------------------------
# The client generates the secret and obfuscates the weights, so each client needs
# its own obfuscated copy of the model deployed on the server. Weight sharing across
# tenants, which is what makes multi-tenant serving economic, is gone.
# The paper runs its experiments at dtype = bfloat16, so price weights at 2 bytes
# per parameter; FP8 would halve the storage column but is not what was measured.
print("\nPer-tenant cost of client-side model obfuscation (bf16, as evaluated)")
print(f"{'model':<26} {'params':>8} {'offline':>9} {'10 tenants':>12} {'weights x10':>13}")
print("-" * 74)
# Every weight matrix reads the residual stream, so obfuscation widens the checkpoint
# by (d+2h)/d as well. Apply it rather than pricing plaintext parameter counts.
FLEET = [("R1-Distill-14B", 14e9, 3.43, 5120), ("R1-Distill-32B", 32e9, 9.28, 5120),
("Qwen3-MoE-30B-A3B", 30e9, 4.58, 2048), ("DeepSeek-V3.1", 671e9, 482.38, 7168)]
H_DEF = 128
for name, params, minutes, dim in FLEET:
grow = (dim + 2 * H_DEF) / dim
tb = params * 2.0 * grow / 1e12 # 2 bytes/param at bf16, widened
print(f"{name:<26} {params/1e9:>7.0f}B {minutes/60:>8.1f}h "
f"{10*minutes/60:>11.1f}h {10*tb:>11.2f} TB")
# The expansion parameter widens the residual stream from d to d+2h, which inflates
# every matrix that reads it. This is a real cost the identical-efficiency claim
# is measured around, at the default h=128.
print(f"\nResidual-stream widening at the paper's defaults (h=128, and h=512):")
for name, (vocab, d) in MODELS.items():
for h in (128, 512):
print(f" {name:<30} h={h:<4} d {d} -> {d+2*h} (+{100*2*h/d:.1f}%)")
assert (5120 + 256) / 5120 < 1.06, "h=128 is a few percent on a 14B"
assert (5120 + 1024) / 5120 > 1.19, "h=512 is a fifth more width"
print("all assertions passed")
Executed output:
Theorem 4: how much does model obfuscation reduce the token budget eps1?
model (n-1)*eps2 >= eps1=1 eps1=10
----------------------------------------------------------------------
Qwen2.5-14B / R1-Distill-14B 1.141e+11 2.19e-12 2.19e-11
DeepSeek-V3.1-Terminus 8.248e+10 3.03e-12 3.03e-11
Remark 1 holds (eps < eps1) but the gap is below one part in a billion,
so the theorem does not make a weak token mechanism meaningfully stronger.
With no token mechanism, eps saturates at (n-1)eps2 >= 1.14e+11 (no usable guarantee).
Per-tenant cost of client-side model obfuscation (bf16, as evaluated)
model params offline 10 tenants weights x10
--------------------------------------------------------------------------
R1-Distill-14B 14B 0.1h 0.6h 0.29 TB
R1-Distill-32B 32B 0.2h 1.5h 0.67 TB
Qwen3-MoE-30B-A3B 30B 0.1h 0.8h 0.68 TB
DeepSeek-V3.1 671B 8.0h 80.4h 13.90 TB
Residual-stream widening at the paper's defaults (h=128, and h=512):
Qwen2.5-14B / R1-Distill-14B h=128 d 5120 -> 5376 (+5.0%)
Qwen2.5-14B / R1-Distill-14B h=512 d 5120 -> 6144 (+20.0%)
DeepSeek-V3.1-Terminus h=128 d 7168 -> 7424 (+3.6%)
DeepSeek-V3.1-Terminus h=512 d 7168 -> 8192 (+14.3%)
all assertions passed
The theorem's improvement over the token mechanism alone is real and negligible, because the vocabulary size multiplies the denominator. Since the token mechanism is optional and switching it on is what costs accuracy in the very baselines this work outperforms, the configuration that produced the published accuracy numbers has no meaningful formal budget at all. Treat the empirical attack results, which are extensive, as the actual evidence.
How to run it in production¶
Plan the fleet around per-tenant weights rather than per-tenant requests. The consequences follow directly: no cross-tenant KV cache or prefix reuse, no shared model server, and a GPU memory budget that scales with tenant count instead of with concurrency. A mixture-of-experts model does not rescue this: the binding constraint is resident weights per tenant, and MoE raises resident parameters per unit of active compute. It is also the architecture with the paper's largest accuracy regression.
Treat obfuscation as a build step with a real service-level objective. Eight hours for a 671B model is a deployment window, and a key rotation is a full rebuild plus a full redeploy, not a config change. There is no partial re-obfuscation.
Do not expect to withhold the plaintext checkpoint. The vocabulary-matching attack compares published weights against obfuscated ones, and the published half is public by construction, since the whole design starts from an open-weight model the client downloads. Separating the two checkpoints operationally buys nothing. The secret is the sampled noise itself together with the permutation and the key matrices, not the coefficients that scale them: the paper publishes its recommended alpha_e and alpha_h and tabulates them for every experiment, and the threat model already grants the attacker full knowledge of the mechanism. Treat alpha_e as a security-relevant tuning parameter whose value can be public, and protect the random draws.
Audit every server-side feature that reads token identities, because the private alphabet moves all of them. Stop sequences, the end-of-sequence and padding IDs, chat templates that the server splices in as control tokens, grammar and JSON-constrained decoding, logit bias, tool-call parsing, and content moderation all operate on the obfuscated vocabulary and will match the wrong things unless the served configuration is rewritten under the same permutation. Speculative decoding needs its draft model obfuscated under the identical secret. The paper reports compatibility at the level of the engine running, not at the level of these features behaving, and none of them appear in its evaluation.
Verify the token round trip rather than assuming it. The client permutes token IDs, detokenizes to text, and the server retokenizes; byte-pair merges are not guaranteed to re-segment that text into the same sequence the client intended. Test this on your own tokenizer and corpus before trusting the mapping, since a re-segmentation failure corrupts the prompt silently.
Instrument accuracy continuously against a plaintext reference. Both the numerical failure mode (large lambda under bfloat16) and the accuracy drift from the RMSNorm residual present as quality regressions with no error signal. A canary evaluation against the unobfuscated model is the only detector.
How to maintain it¶
Re-obfuscate on every checkpoint change; the transform is bound to specific weights. Re-run the attack suite after any architecture change, particularly to attention: the protection of attention scores depends on rotary block permutation, so a model with different positional encoding needs its own analysis rather than an inherited result. Re-tune alpha_e if the embedding table is resized or retrained, since the attack that it defeats works on the statistics of the weight matrix. Pin the serving engine version alongside the obfuscated checkpoint; the compatibility claim is measured against vLLM 0.9.1 and nothing else.
Failure modes¶
- Embedding noise set too low. At
alpha_e = 0.5the permutation is recoverable by sorting and matching, and over 30% of tokens fall out. Fails open, silently. - Matrix coefficient set too high under bfloat16. A widened internal-state range exhausts bfloat16's 8-bit mantissa; accuracy collapses while float32 is fine. Presents as a quality regression with no error, and the paper misattributes it to overflow.
- Model without rotary embeddings, or transforms built on the wrong rotary pairing. In the first case attention scores stay equal to plaintext and the internal-state attack recovers them at the unmitigated rate. In the second the cancellation breaks and accuracy degrades instead.
- Tenant count grows. Weights, not requests, drive memory. The failure is a capacity planning surprise rather than an incident.
- Plaintext checkpoint co-resident with the obfuscated one. Supplies exactly the comparison the vocabulary-matching attack needs.
- Client-side obfuscation host under-provisioned. The offline step is a dense linear-algebra job over the whole checkpoint on a CPU host.
- Assuming the token mapping hides structure. The mapping is a permutation of the vocabulary, so token frequency survives it. The paper measures this and finds recovery low, but the distribution-aware attacker is its strongest case: 16.51% top-100 recovery when the attacker knows the client's data distribution.
Open questions and validation¶
- No code is released, so the construction can only be checked against the paper's text. The block above is this page's independent reimplementation of Algorithm 1 and the component transforms, not the authors' code.
- Algorithm 2's
BlockPermcannot be run as printed. Itswhile t < mblocksloop never advancest, so it does not terminate, and the parametergamma, listed in the signature and fixed at 1e3 in the hyperparameter table, is never used in the body, which instead useszeta. Any reimplementation has to guess the intended update, and the attention-score defence rests entirely on this procedure. - Whether the approach survives a serving-scale workload is untested. The published latency work stops at concurrency 4 with 17-token prompts, and paged attention over a widened residual stream is exactly where a compatibility claim would be stressed.
- The gap between 4.80% token recovery on DeepSeek-V3.1 and 25.05% on Qwen3-14B is unexplained. Without a mechanism, neither number generalises to an untested model.
- Model confidentiality runs the wrong way for a hosted service: the client must hold plaintext weights. The paper sketches an FHE or CPU-TEE variant that would let a client obfuscate weights it cannot read, but does not build it.
- The interaction with quantisation is unaddressed. The key matrices are dense and randomly generated, and the bfloat16 failure at
lambda = 3.0suggests the transformed weights have a wider dynamic range than the originals, which is the thing post-training quantisation is most sensitive to.
References¶
- Lin, Y., Zhang, Q., Ruan, W., Zhang, D., Hong, J., Wu, Y., Xia, H., Mao, Y., Zhong, S. "Towards Privacy-Preserving LLM Inference via Covariant Obfuscation (Technical Report)." arXiv:2603.01499v2, 2026. https://arxiv.org/abs/2603.01499
- Dong, T., Meng, Y., Li, S., Chen, G., Liu, Z., Zhu, H. "Depth Gives a False Sense of Privacy: LLM Internal States Inversion." USENIX Security, 2025. https://www.usenix.org/conference/usenixsecurity25/presentation/dong-tian
- Lin, Y., Zhang, Q., Cai, Q., Hong, J., Ye, W., Liu, H., Duan, B. "An Inversion Attack Against Obfuscated Embedding Matrix in Language Model Inference." EMNLP, 2024. https://aclanthology.org/2024.emnlp-main.126/
- Thomas, R. K. et al. "Hidden No More: Attacking and Defending Private Third-Party LLM Inference." ICML, 2025. https://arxiv.org/abs/2505.18332
- Du, M., Yue, X., Chow, S. S. M., Wang, T., Huang, C., Sun, H. "DP-Forward: Fine-tuning and Inference on Language Models with Differential Privacy in Forward Pass." ACM CCS, 2023. https://arxiv.org/abs/2309.06746
- Roberts, J., Mylonakis, K., Roy, S., Kale, K. "Learning Obfuscations of LLM Embedding Sequences: Stained Glass Transform." arXiv:2506.09452, 2025. https://arxiv.org/abs/2506.09452
- Mironov, I. "Renyi Differential Privacy." IEEE CSF, 2017. https://arxiv.org/abs/1702.07476
- Kwon, W. et al. "Efficient Memory Management for Large Language Model Serving with PagedAttention." SOSP, 2023. https://arxiv.org/abs/2309.06180
- Tan, Y., Tan, C., Mi, Z., Chen, H. "PipeLLM: Fast and Confidential Large Language Model Services with Speculative Pipelined Encryption." ASPLOS, 2025, doi:10.1145/3669940.3707224. https://dl.acm.org/doi/10.1145/3669940.3707224
- Su, J. et al. "RoFormer: Enhanced Transformer with Rotary Position Embedding." Neurocomputing 568, 2024. https://arxiv.org/abs/2104.09864
Related: Activation obfuscation on untrusted accelerators · GPU confidential computing · Privacy-aware split inference · Membership inference against fine-tuned LLMs · Security, isolation and multi-tenancy · KV cache management · Serving OSS models · Disaggregated inference · Gradient leakage in distributed training · Glossary
-
arXiv:2603.01499v2, Table 3, "AloePri privacy against VMA", text-token recovery success ratio: R1-Distill-14B 12.36%, R1-Distill-32B 10.10%, Qwen3-14B 25.05%, Qwen3-32B 19.64%, Llama3-8B 2.57%, Qwen3-MoE-30B-A3B 5.91%, DeepSeek-V3.1-Terminus 4.80%. The abstract's "less than 5% of tokens recovered" and the conclusion's "attackers can only recover less than 5% of text tokens through inversion attacks" are both scoped to DeepSeek-V3.1-Terminus. Table 2 separately reports 13.51% under the same attack on Qwen2.5-14B-Instruct. Personally-identifiable-information recovery is uniformly low across all seven models (0% to 2.63%), so the spread is in ordinary tokens rather than in the sensitive spans. ↩
-
arXiv:2603.01499v2, Algorithm 2, steps 6 and 7. Extracting glyph positions from page 9 of the PDF shows a superscript
Tat y 459.4 to 464.7 sitting above the subscriptblockat y 465.0 to 470.4 on the key line, and no such superscript on the query line, so the printed algorithm readsZ_block^Tfor the key weights andZ_blockfor the query weights. Applying them that way makes the pre-rotary scores differ from plaintext by more than 100%, because the permutation used here is not an involution. Section 5.2.3 instead describes "simultaneously shuffling the RoPE's 2x2 blocks of query/key weights within a limited window" with "minimal impact on model accuracy", and the stated rationale that high-index blocks matter least is only coherent as a rotary-frequency effect. This page therefore applies the same permutation to both sides, which is almost certainly the intent, but the discrepancy is in the source and is not resolved there. ↩