01
one per run
no pipeline ordering, registry, discovery system, or interaction semantics.
one narrow interface for observing execution or changing an owned activation in place. statically compiled, one per run, and intentionally unstable in v0.1.
// observation
tensor.values()
output remains numerically unchanged
// intervention
tensor.values_mut()
tensor.zero()
output may change
the Rust experiment interface is unstable in ember v0.1. it is not a dynamic plugin ABI or a semver compatibility promise.
ember exposes semantic points in ordinary generation without flattening the distinctions between LLaMA, Qwen, and Gemma or copying tensors just to make a hook possible.
the normal path constructs no experiment object. an active run owns exactly one statically compiled experiment and calls it at the approved layer and logits boundaries. the default operation is observation; changing execution requires explicit mutable access to an existing activation.
01
one per run
no pipeline ordering, registry, discovery system, or interaction semantics.
02
borrow, do not copy
contexts borrow metadata. tensor access wraps an owned mutable intermediate in place.
03
architecture stays visible
shared hook names do not erase family-specific normalization, RoPE, or layer semantics.
build ember once, then use one experiment option on a normal generation command. the examples below assume the model and tokenizer files are in the current directory.
cargo build --release
experiment notices and summaries are written to stderr. generated text keeps its existing stdout format. the two built-in options conflict because only one experiment can be active.
records norms, maxima, and fingerprints at every tensor-bearing hook.
this experiment reads existing f32 activation values and writes ordered JSON after successful generation. it never requests mutable tensor access and does not alter logits or generated tokens.
target/release/ember \
--arch qwen3 \
--model Qwen3-0.6B-Q8_0.gguf \
--tokenizer tokenizer-qwen3.json \
--prompt "The capital of France is" \
--max-tokens 4 \
--temperature 0 \
--activation-stats activation-stats.json
prefill or decodethe fingerprint reuses ember's structured-tracing algorithm and samples every 64th value. it is useful for locating divergence, not for cryptographic identity. norm calculation reads every value.
jq '.records[] | select(.stage == "after_layer") |
{phase, layer_index, sequence_length, l2_norm, fingerprint}' \
activation-stats.json
observation preserves numerical output, not timing. scanning every activation and storing records adds work, so benchmark it as a separate workload.
zeros one selected contribution during prefill and every decode evaluation.
pass a zero-based layer and semantic stage as
LAYER:STAGE. ember validates the layer
after loading the model, modifies the existing tensor
in place, and counts each intervention.
target/release/ember \
--arch gemma4 \
--model models/gemma-4-E2B-it.Q8_0.gguf \
--tokenizer tokenizer-gemma4.json \
--prompt "The capital of France is" \
--max-tokens 8 \
--temperature 0 \
--zero-layer-output 4:attention
| stage | tensor replaced with zero | location |
|---|---|---|
attention |
attention residual contribution | immediately before residual addition |
mlp |
MLP residual contribution | immediately before residual addition |
layer |
completed hidden state | after all family-specific layer work |
a malformed stage fails during CLI parsing. a layer outside the loaded model reports the model identifier, family, requested index, and valid range.
research experiment active: zero-layer-output layer=4 stage=attention; execution will be modified
experiment zero-layer-output: 8 intervention(s) at layer 4 stage attention
| surface | no experiment | active experiment |
|---|---|---|
| stdout | existing generated-text format | same formatting behavior |
| stderr | no experiment notice | activation notice and completion summary |
| run manifest | no experiment field | name, configuration, and mutation status |
| artifact | none | activation-stats writes JSON |
a manifest marks activation-stats with
"modifies_execution": false. the intervention
records its layer, stage, and
"modifies_execution": true.
| surface | status | notes |
|---|---|---|
| ordinary LLaMA generation | supported | prefill and fast single-token decode |
| ordinary Qwen3 generation | supported | family-specific RoPE and QK norm preserved |
| ordinary Gemma 4 generation | supported | post norms, PLE, and scaling preserved |
| GPT-2 generation | not integrated | no experiment hook path in v0.1 |
| hidden-state extraction and probes | active runs rejected | representation semantics are not defined yet |
| layer/logit dumps | active runs rejected | normal no-experiment surfaces are unchanged |
| demo, interactive, benchmark subcommands | active runs rejected | experiments currently target ordinary generation |
active experiments do not participate in hidden-state extraction, probing, layer dumps, or logits dumps. ember does not yet define whether those surfaces should expose pre-intervention or post-intervention values.
experiments are Rust code compiled into ember. keep the addition narrow and resist extending the framework to make one implementation convenient.
src/experiments/.Experiment; leave unused hooks at their defaults.TensorAccess only at an approved tensor hook.ExperimentError.read the hook contract before choosing an insertion point. the contract documents tensor ownership, family-specific semantics, context fields, failure metadata, and the disabled path.
the v0.1 interface intentionally does not provide dynamic shared libraries, WASM, Python bindings, runtime discovery, multiple experiments, async hooks, arbitrary weight mutation, custom tokenizers, custom backends, or a general event bus.