← ember v0.1 release notes source
ember / experiments / guide

research experiments

one narrow interface for observing execution or changing an owned activation in place. statically compiled, one per run, and intentionally unstable in v0.1.

guide v0.1 contract LLaMA · Qwen3 · Gemma 4
v0.1 surface
// observation
tensor.values()
output remains numerically unchanged

// intervention
tensor.values_mut()
tensor.zero()
output may change
one compiled experiment per generation run
experimental API

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.

quick start

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.

built-in experiments

activation-stats

records norms, maxima, and fingerprints at every tensor-bearing hook.

observation-only

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

artifact fields

phase
prefill or decode
stage
semantic hook name
layer_index
zero-based; absent at logits
token range
start, input count, sequence length
tensor
2D shape and f32 dtype
values
L2 norm, absolute max, fingerprint

the 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
timing

observation preserves numerical output, not timing. scanning every activation and storing records adds work, so benchmark it as a separate workload.

zero-layer-output

zeros one selected contribution during prefill and every decode evaluation.

intervention

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

output and provenance

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.

compatibility boundary

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
important current limitation

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.

adding another built-in

experiments are Rust code compiled into ember. keep the addition narrow and resist extending the framework to make one implementation convenient.

  1. add an implementation under src/experiments/.
  2. implement Experiment; leave unused hooks at their defaults.
  3. keep experiment-specific records inside the implementation.
  4. use TensorAccess only at an approved tensor hook.
  5. add one narrow typed CLI option and explicit construction path.
  6. propagate failures with ExperimentError.
  7. add hook-order, family, parity, allocation, and benchmark coverage.

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.

non-goals

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.