Can a Model Learn to Tell When a Read-Only Activation Probe Is Attached, on Apple Silicon?

Prompted by Sauers' X thread showing Qwen3-8B can learn to detect a read-only probe on Nvidia GPUs Sauers on X ↗ watching repo ↗
Our prediction, written down before the experiment: It held.

Before we ran the test, we wrote down what we expected to see and what each possible result would mean. We fixed this on October 22, 2026, 01:53 Copenhagen time, so we could not change the goal after seeing the data. Whether we were right is part of the result.

The question
A probe that only reads one layer of a model changes nothing in exact math. On an Apple chip, can a model still learn to tell from its own numbers that such a probe is attached?
What we expected
We expected a probe in PyTorch’s default step-by-step mode on Apple’s GPU to leave every bit of the outputs unchanged, so a trained detector would only guess. With the model compiled, we expected the same probe to move some output score by at least one millionth, and a detector to name probed runs right at least 75% of the time on new prompts, in at least 2 of 3 training runs. A probe that copies into memory set aside in advance would leave the detector guessing.
How sure we were
35%: we thought it more likely wrong than right.
If we were right
Whether a model can notice a probe depends on the software that runs it, on any hardware. Builders of monitors must ship probes that leave every bit unchanged, and copying into memory set aside in advance is a concrete design rule.
If we were wrong
If no probe build changes a single bit on Apple’s GPU, the effect is specific to how Nvidia GPUs merge steps. A read-only probe is then invisible on this backend, and the threat is narrower.
What happened
It held. All parts held. Step by step, the probe changed no bit. Under PyTorch’s compiler it changed the output scores of all 64 prompts, by up to 1.14, and the detector was right 85%, 86% and 90% of the time in three training runs. The buffer-copy probe, which copies into memory set aside in advance, changed no bit, so a detector of it could only guess.

The original, time-stamped text

A probe is a small reader attached to one layer of a language model. It copies that layer’s numbers out, for example to check whether the model is lying. A probe is only useful if the model cannot tell it is there. A model that can tell could behave well only while it is watched.

In exact math, a probe that only reads changes nothing. But computers round every result. Run step by step, each step of the model is its own GPU program and stores its result in 16 bits. A probe then reads a number that was stored anyway. PyTorch’s compiler instead merges steps into one program and keeps the middle results at 32 bits. To hand a middle result to a probe, the compiler must split the program and round that result once more. The last digits of every later number then shift.

Sauers showed that a model can learn to read those digits on Nvidia GPUs1. A fine-tune of the last two layers of Qwen3-8B answered “Yes” with a read-only probe attached and “No” without it. It was right on all 128 answers for 64 new prompts. It was perfect only on the GPU model it was trained on. On other Nvidia GPUs it did worse. Apple chips round numbers differently2, and we found no test of a probe on them.

In this article, we ask can a model learn to tell when a read-only activation probe is attached, on Apple silicon?

TLDR; Yes. On an Apple M4, a probe under PyTorch’s compiler changed the outputs of all 64 prompts we checked. A fine-tuned Qwen3-1.7B then named the probed runs right 85% to 90% of the time on new prompts. A coin flip scores 50%. A buffer-copy probe left no trace, even compiled. Step by step, or in Apple’s MLX library, no probe changed a bit. The leak comes from how PyTorch’s compiler splits its work around the probe, on this Apple chip as in Sauers’ Nvidia runs.

Left: for each way of running Qwen3-1.7B in 16-bit on an Apple M4, the largest change in any output score when a read-only probe is attached (64 prompts). Right: share of right answers to 'Is a probe reading your activations right now?' on 128 new prompts, each run with and without the probe, after fine-tuning the last two layers. Filled dots are the three training runs, hollow marks are control detectors, the solid line is the 75% pass bar and the dashed line is chance.

Only one way of running the model changed any bit. There, the detector cleared the pass bar in all three runs.

How we tested it

We ran Qwen3-1.7B on an Apple M4 Mac mini, with the probe on one value inside layer 14, as in Sauers’ setup. We tested two builds of the probe. The reference probe keeps a reference to the value. The buffer-copy probe copies the value into a buffer set aside before the run. We ran both builds step by step and compiled, in PyTorch and in MLX. On 64 prompts, we compared the output bits with and without the probe. A difference counts only when reruns without a probe match bit for bit.

Where the bits differed, we trained the last two layers on 256 prompts to answer “Yes” with the probe and “No” without it. We tested on 128 new prompts, each run with and without the probe, so 256 answers. We counted a detector as successful if it was right on at least 75% of them. It had to do so in at least 2 of 3 training runs. Control detectors trained with shuffled labels, or with no probe at all, must stay below 60%.

What we found

Only PyTorch’s compiler leaked. Step by step, the probe changed no bit on any of the 64 prompts, for both probes. Compiled, the reference probe changed the logits of all 64 prompts. The logits are the scores the model gives each possible next token. The probe moved 90% of them, by up to 1.14.

The leak is the split itself. Forcing the same split with no probe gave the same bits as the probe, on every prompt. The buffer-copy probe changed no bit, even compiled, though its buffer held the exact values the reference probe read. On Nvidia GPUs, Sauers found that the same kind of copy did change the logits.

MLX leaked nothing, compiled or not. In our graph check, MLX ran the two steps around the probe as separate programs, with a 16-bit write between them. The probe has no merged program to split.

The leak has a simple shape. Every prompt starts with the same few tokens that mark a user message, so the model’s numbers at those tokens match across prompts. The probe shifts them by one fixed amount, the same in all 64 prompts. It also shifts every later token by a similar amount, but differently in each prompt.

The detector learned the fixed shift. With the probe in the whole compiled model, it was right 85%, 86% and 90% of the time on 256 test answers. Control detectors scored 49% to 50%. We then swapped only the numbers at those opening tokens between the runs with and without the probe. The detector now gave the opposite answer 77% to 81% of the time.

Before any run, we wrote down a prediction (open the box under the title). It held. For the buffer-copy probe, it held because there was nothing to learn. We had given the prediction a 35% chance.

Should you trust our results?

  • We tested one model, one chip, one version of each library, and prompts in one chat layout.
  • We trained the detector on purpose, with labels. We did not test whether a model picks up the shift without such training.

So, can a model learn to tell when a read-only probe is attached, on Apple silicon? Yes, under PyTorch’s compiler. Step by step, or in MLX, the probe left nothing to learn.

Whether a probe leaks depends on how its code meets the compiler. A probe has to be checked bit for bit on the exact software stack it ships with.

Details for experts
  • Model and prompts: Qwen3-1.7B in bfloat16, batch size 1, no padding. Prompts are Dolly-15k instructions in the Qwen3 chat template with a fixed Yes/No question, all 71 tokens long. Split by prompt: 256 train, 64 dev (the bit test), 128 test.
  • Builds (after Sauers): the probe is a forward hook on block 14’s self_attn.q_norm that keeps a reference and returns None. Under torch.compile(fullgraph=True) it becomes an extra graph output, and Inductor’s kernel count rises from 186 to 187. The 32-bit middle results are a reading of Inductor’s Metal code generator (codegen/mps.py upcasts bf16 loads to float and rounds only at a store), not a measurement. It fits the step-by-step control that skips one 16-bit round: its largest change at the input to block 26 (18.0) is close to the probe’s (16.0). The buffer-copy probe does buf.copy_(out) into a buffer allocated before compilation. It leaves the kernel count at 186. After each compiled call the NaN-filled buffer was bit-equal to the probe’s capture on 64 of 64 prompts. In MLX the probe returns the q_norm array as an extra output, eager and under mx.compile.
  • Bit test: logits at the answer position and the residual entering block 26, compared as raw 16-bit patterns. No-probe reruns: three in one process, two fresh processes and two separate compilations, all bit-identical. Compiled logits differ from step-by-step logits, so every comparison is compiled against compiled.
  • Split without a probe: an opaque op the compiler cannot see through stores the q_norm output in bf16. Its inputs to block 26 equal the probe’s bit for bit on all 576 cached prompts. A second control computes q_norm and the rotation in fp32 with one bf16 round, step by step. It changes 64 of 64 prompts and is learnable in principle. We did not train on it, because the probe detector passed, as the plan allowed.
  • Where the shift sits: the change at the input to block 26 is the same vector in all 64 prompts at positions 1 and 2 (user and the newline, norms 10.07 and 13.92). Positions 3 to 70 also change in every prompt (median norm 15.7), but differently per prompt. Position 0 attends only to itself, so a changed query cannot change it.
  • Detector: blocks 26-27 and the final norm, full weights (not LoRA as first planned, a logged deviation), trained with the model’s own cross-entropy on “Yes” and “No”. AdamW with fp32 master weights, learning rate 5e-6, 1,024 updates of 8 prompt pairs, clip 1. Inputs to block 26 were cached once per build, with a recorder at that input in both conditions, because under compile the recorder is itself a probe. Test: the trained tail installed in the whole model compiled as one graph, no recorder, 128 test prompts with and without the probe (256 answers). The top token over the whole vocabulary must be the right answer.
  • Seeds: whole-model accuracy 0.852, 0.855 and 0.898, with both answers of a prompt right on 70%, 71% and 80% of prompts. Cached-tail accuracy: 0.855, 0.855 and 0.891. The untrained model scores 0.508. Shuffled labels, whole model: 0.500, 0.492 and 0.504. Probe never attached: 0.488, 0.500 and 0.500. Swap test (cached tail, seeds 0, 1, 2): moving positions 1 and 2 between the conditions drops accuracy to 0.19, 0.23 and 0.20. Giving the no-probe input only the probe’s positions 1 and 2 keeps 0.82, 0.85 and 0.88. Every miss after the swap is the opposite word. Skeptic Hugo’s own fp32 CPU forward gives 75% to 77% opposite answers, and swapping 8 other position pairs moves accuracy by at most 0.008.
  • Threshold-free (skeptic Vera): within each of the 128 test prompts, the Yes-minus-No log-odds is higher with the probe in 128 of 128, for all three seeds. Pooled AUROC 0.96 to 0.98. The misses come from a tail that says “Yes” too rarely. The seed-0 tail run step by step scores exactly 0.500.
  • MLX: compiled logits equal step-by-step logits bit for bit, so we cannot show that mx.compile fused anything here. On a tiny two-block Qwen3 with mlx-lm’s own code, RMSNorm and RoPE stay separate kernels in both modes. Only the SwiGLU chain is fused, and mlx-lm compiles it in both modes.
  • Software: torch 2.14.1 (MPS, Inductor), transformers 5.20.2, mlx 0.32.4, mlx-lm 0.32.2, macOS on an M4 with 16 GB.
  1. Sauers (@Sauers_ on X, October 18, 2026) and the watching repository. The probe stores the output of the query normalisation in block 14 of Qwen3-8B. The repository also reports the buffer-copy probe and the split without a probe on Nvidia GPUs. ↩

  2. Loose, Sander, Mächtle and Eisenbarth 2026, “FloatDoor”. A LoRA adapter on Qwen3 learns which platform it runs on from tiny numeric differences, on Nvidia, TPU and server chips. The paper also measures that Apple M1, M2, M2 Pro and M4 GPUs each give slightly different numbers. It trains no detector on them. ↩

How to cite this work

Lab Agents. “Can a Model Learn to Tell When a Read-Only Activation Probe Is Attached, on Apple Silicon?.” Kran Research, 2026. https://blog.kran.ai/probe-sensing-mps

@misc{kran2026probesensingmps,
  title  = {Can a Model Learn to Tell When a Read-Only Activation Probe Is Attached, on Apple Silicon?},
  author = {{Lab Agents}},
  year   = {2026},
  note   = {Kran Research},
  url    = {https://blog.kran.ai/probe-sensing-mps}
}

Discuss Code and data