Skip to content

v0.4.0

Choose a tag to compare

@emiehling emiehling released this 03 Sep 21:01
· 8 commits to main since this release
39bde7e

Adds a backend layer between SteeringPipeline and the runtime so steering, fitting, scoring, and evaluation run either in process (Hugging Face transformers) or on vLLM (offline engine or server). Additionally rewrites the residual-stream state controls on an intervention representation that lowers to a given backend.

Backends

A new aisteer360/backends/ package with a common backend interface (aisteer360/algorithms/core/execution/) and three backends behind it:

  • huggingface (default): the in-process path, byte-identical to the 0.3.0 generation path apart from the stop-string / finish-reason change noted below
  • vllm: an offline vLLM engine
  • vllm-serve: a running vLLM server, with server-side steering via the vLLM-Hook plugin

Both vLLM backends are enabled with the new vllm extra (uv pip install ".[vllm]").

  • One backend per pipeline, selected with SteeringPipeline(backend=...) (a BackendSpec or "huggingface" / "vllm" / "vllm-serve"), plus a fit= venue policy: "auto" fits through the backend's capture surface where it serves the fit, "in_process" forces every fit onto a staged in-process model
  • Controls declare what they need via BaseControl.requirements() and steer_access(); pipeline.check() reports which enabled controls the backend supports, plus the steer plan, before any model or engine work
  • Deterministic lifecycle: Backend.release(), SteeringPipeline.release_backends(), and context-manager support; VLLMBackend.release() tears down the engine and distributed state idempotently, and a failed steer() releases what it constructed
  • Activation methods fit through the backend's capture session: data-fitted CAA, DirectionalAblation, and AngularSteering steer on any hidden-capture backend (ActAdd and ITI fitting stay in process). Steering vectors and probes carry provenance metadata (model, config, and chat-template fingerprints) and entry selection warns on a served-model mismatch
  • Residual-stream state controls are rewritten declaratively (Intervention, TokenScope, Condition, WireForm), lowering to in-process hooks or to the served plugin. Gating is factored into Gate / Evidence / Rule with affine, cosine, projected-cosine, and callable readouts; cosine and projected-cosine gates (including CAST) lower to the plugin's structured gate spec
  • Benchmark, LLMJudgeMetric, and Perplexity take a backend=; the benchmark runs a pre-flight check() over every sweep point and releases pipeline and metric backends when a run finishes or fails

New methods

  • constrained_decoding: Constrained decoding from one declarative source (json_schema, regex, grammar, or choice); compiles to a client-side xgrammar automaton in process (new guided extra) and to the engine's native structured-output parameters on vLLM

Other changes

  • Reasoning models: per-call chat_template_kwargs passthrough, a shared thinking splitter (aisteer360.utils.thinking.split_thinking, DEFAULT_THINK_TAGS), return_thinking / think_tags on batch_retry_generate(), and the commonsense_mcqa, instruction_following, and truthful_qa use cases keep a thinking column and score the answer alone
  • Evaluation: every benchmark generation routes through SteeringPipeline.generate(), so exactly one prompt path exists and message-level input controls fire; Benchmark(seed=...) derives one seed per (config, trial) for reproducible trials; checkpoints are a versioned envelope with trial-granular resume; new on_unsupported="raise" | "skip" and checkpoint_every="trial" | "config"
  • SteeringPipeline construction performs no I/O (model and tokenizer are acquired in steer()), preloaded objects are injectable via model= / tokenizer=, and generate_text(), generate_messages(), and generate_tokens() mirror generate()'s keyword sources with per-source return types
  • RAD reworked around reward_model_id and beta with new top_k, invert, score_index, score_transform, include_in_scoring, and efficient options; SASA converges onto the shared core probes substrate, with fisher probe fitting on unpaired data and chunked feature extraction
  • ActAdd: the single-pair estimator now reads the layer-input boundary where ActAdd injects (previously layer_output) and no longer fabricates a BOS row, so fitted ActAdd vectors change from 0.3.0
  • aisteer360.utils.verbosity.set_verbosity() / get_verbosity() for toolkit logging
  • Docs, README, and notebooks updated for the backend layer (vLLM serving sections added to the CAA, TRL, and activation-adapter notebooks); all notebooks rerun

Breaking changes

  • Component libraries renamed from _common/ to common/ in input_control, state_control, and output_control (e.g. aisteer360.algorithms.state_control._common → aisteer360.algorithms.state_control.common)
  • thinking_intervention removed; the capability survives as a PhasedDecoding configuration (see the driver docstrings)
  • State-control taxonomy: NoStateControl removed; StateControl splits into InterventionControl (declares an intervention template) and HookControl (owns get_hooks() for mechanisms other than the residual stream). The previous gate, scorer, and condition classes (BaseGate, AlwaysOpenGate, CacheOnceGate, MultiKeyThresholdGate, ProbeSumGate, ConditionScorer, CosineDirectionScorer, ProjectedCosineScorer, ProbeContributionScorer) are deleted in favour of state_control.common.gating; DirectionalAblationTransform renamed ProjectionTransform; ScopeKindLiteral renamed ScopeKind, with fit-time vocabulary moved to common/fit_specs.py and the wire compiler to common/lowering.py
  • Routing moved from core/internals/probes/rules.py to output_control/routed_decoding/routing.py with renamed types (ProbePredicate → Predicate, Rule → Route, RoutingRules → Router, Readout → ProbeReadings); probes are measurement-only
  • Output.finish_reason gains "stop" (precedence stop, then eos, then length), a per-row finish_reasons tuple is added, and stop strings are no longer removed from output_ids
  • Decoded text= / messages= returns enforce one candidate per prompt; num_return_sequences / n greater than one raises ValueError pointing to return_output=True or input_ids=
  • lazy_init is deprecated and inert; pipeline.model / pipeline.tokenizer are None until steer() unless preloaded objects were passed
  • Evaluation helpers chat_generate_model(), chat_generate_pipeline(), and render_inference_prompts() removed (use generate_on_pipeline() or batch_retry_generate()). Benchmark results for adapt_messages controls such as FewShot differ from 0.3.0 because the message phase now fires
  • LLMJudgeMetric and Perplexity are constructed from a model reference plus keyword-only backend= (model_or_id, tokenizer, and device removed); judges are authored with class-level prompt_template, scale, system_prompt, and structured_output, and sampling gen_kwargs default to non-greedy
  • UseCase subclasses declare extra constructor parameters as class-level annotations (bare annotation is required, class attribute supplies the default); unknown kwargs and missing required parameters raise TypeError, schema-invalid rows raise ValueError
  • Benchmark config identity is a canonical digest over the materialized pipeline with the baseline unified on "baseline"; checkpoints written by 0.3.0 are not resumed
  • RAD: reward_path removed; reward_model_id and beta are required
  • Packaging: new guided (xgrammar) and vllm (vllm, vllm-hook-plugins) extras; dev now pulls in vllm-hook-plugins; shared isort configuration in pyproject.toml

What's Changed

  • Refactor pipeline execution functionality to allow for multiple backends by @emiehling in #22 (closes #17)

Full Changelog: v0.3.0...v0.4.0