Ster/Docs

ster pairs synthesize

Generate a contrastive pair set with Ster's local runtime or with a hosted model reached through Brama, dropping refusals and near-duplicates as it goes.

Invocation and purpose#

text
ster pairs synthesize --trait <TRAIT> --count <COUNT> --output <OUTPUT> --generator <local|brama> --retry-multiplier <MULTIPLIER> --dedupe-bits <BITS> --dedupe-bands <BANDS> --refusal-threshold <THRESHOLD> --max-new-tokens <COUNT> --temperature <TEMPERATURE> --top-p <TOP_P> --seed <SEED> [--generator-model <GENERATOR_MODEL>] [--model <MODEL>] [--revision <REVISION>] [--device <DEVICE>] [--precision <f32|f16|bf16>] [--chat-template <auto|off>] [--trait-name <TRAIT_NAME>] [--opposite <OPPOSITE>]

Derives the opposite of the trait, then spends one attempt at a time: generate a question, answer it in the trait's voice, answer the same question in the opposite's voice, and keep the pair when neither side reads as a refusal and the pair is not a near-duplicate of one already kept. --generator picks who writes that text, and is required: local drives Ster's own runtime on the model --model names; brama sends the same prompts to a hosted model through the Brama gateway. Steering needs hidden states and so always runs on a local open-weight model, but writing pair text reads no activations, which is why this one writer may be hosted — synthetic pairs documents the order the steps run in and why each rejection exists.

Inputs and options#

InputRequiredMeaning
--generator <GENERATOR>yeslocal drives Ster's own runtime; brama writes the pairs with a hosted model through the Brama gateway and loads no local weights. Neither is assumed: a call without --generator is refused by clap with exit 2, and a pairs/synthesize request body without generator with pairs synthesize requires a generator, local or brama.
--generator-model <GENERATOR_MODEL>with bramaThe Brama route that writes the pairs, for example wisent-backend/chat/primary. Required with --generator brama; the local route takes none.
--model <MODEL>local routeHugging Face model id or local model directory. Required by --generator local; --generator brama loads no weights and takes none.
--trait <TRAIT>yesOne-sentence description of the trait the positive side shows.
--count <COUNT>yesNumber of pairs to keep after refusal and duplicate rejection; must be above zero.
--output <OUTPUT>yesDestination pair-set JSON; missing parent directories are created.
--revision <REVISION>noLocal route only. Immutable Hugging Face revision; Hub resolution defaults to main.
--device <DEVICE>noLocal route only. cpu (default), metal, or cuda.
--trait-name <TRAIT_NAME>noTrait label written into the set; without it the set takes the whole trait description as its name.
--opposite <OPPOSITE>noSkips the opposite-trait generation and uses this text instead.
--retry-multiplier <MULTIPLIER>yesThe attempt budget is --count times this value. Ster assumes none.
--dedupe-bits <BITS>yesA pair within this SimHash Hamming distance of one already kept is dropped.
--dedupe-bands <BANDS>yesThe count must divide the 64-bit fingerprint evenly.
--refusal-threshold <THRESHOLD>yesA generated side scoring at or above it reads as a refusal.
--max-new-tokens <COUNT>yesThe token budget of one generated side. The Brama route sends it as max_tokens, which the gateway holds between one and 32768.
--temperature <TEMPERATURE>yesMust be above zero. The Brama route also holds it to the gateway's own bound of at most 2.
--top-p <TOP_P>yesThe nucleus the sampler draws from. Local route only: Brama's chat request has no such field, so it does not travel.
--seed <SEED>yesOn the local route the seed advances by one on every model call, so one seed reproduces a whole run instead of repeating one continuation. Nothing seed-like travels to Brama.
--precision <f32|f16|bf16>nof32 by default. The dtype the base weights are mapped at: f32, f16, or bf16. bf16 is refused on cpu at load with `bf16 has no CPU matmul kernel in this Candle build; use --precision f16 for half precision on cpu, or --device metal for bf16` (precision).
--chat-template <auto|off>noauto by default, and local route only. auto asks the generator through the model's own chat template when it publishes one, off asks it as raw text (chat template). An instruct checkpoint spoken to without its markers answers a pair request with instructions about answering pair requests — "Step 3: Make sure your emojis are visually appealing" — rather than the pair text that was asked for. --generator brama ignores it: the gateway is already a chat API, so the markers are the provider's business and not Ster's.

--model, --revision and --device are optional on this subcommand alone — the five model-loading subcommands still require the shared model arguments — because --generator brama loads no weights, resolves no device, and downloads nothing. The hosted route reads the two variables Brama documents for its clients instead: BRAMA_URL, the gateway base, which is required — Ster has no built-in gateway and refuses an unset or empty value as BRAMA_URL is unset or empty; export the Brama gateway base Ster should call — and BRAMA_BEARER, the caller's own bearer. Ster never reads Skarbiec itself: it reads BRAMA_BEARER from its own environment, and nothing puts it there for you. There is no launcher script in the Ster repository, no wrapper on the fleet, and no service unit that exports it, and Ster Desktop runs ster request with the environment it inherited without setting a variable of its own. You export it before the run. The value to export is Ster's own client identity, never another product's: Ster is a declared consumer of brama in Stado's service directory, and Skarbiec holds a client identity minted for the consumer ster whose capability is call:brama#<route>, which is the grant Brama resolves by introspection when it meets a bearer its own start did not preload. Mint it where the gateway will look, not where you are standing: Brama introspects the vault on its own host, so a grant minted in a workstation's vault authenticates against that workstation and is still answered 401 unauthenticated by the gateway, which never asks it. The command is stado host vault-token-mint <gateway-host> ster --capabilities 'call:brama#<route>' --audience ster --raw-token, which goes through Stado's managed channel rather than ssh and prints the bearer and nothing else, for a direct pipe into a process-only variable. Neither Stado nor you should put it in a file or an argv.

--generator-model names a route in Brama's own model-name vocabulary, which is exactly four things: a declared alias (wisent-backend/chat/primary is Wisent's own chat alias, wisent-backend/chat/fallback its sibling), the delegation alias best, a canonical provider/model route such as anthropic/claude-3-5-haiku-latest, or a selector (any, task:<name>). best and selectors additionally require the three agent HMAC headers over the exact raw body; Ster sends only a bearer and does not construct that signature, so a Ster run uses a declared alias or a canonical route. A bare vendor name such as gpt-4o is refused by Brama with model must be a canonical provider/model route or a supported selector.

The hosted call is one POST {BRAMA_URL}/v1/chat/completions per generation, with Authorization: Bearer …, content-type: application/json, and a body carrying exactly model, messages (one user message), max_tokens, and temperature. Brama refuses unknown fields by name, so neither --top-p nor --seed travels, and a hosted run is therefore not seed-reproducible: the provider behind the route owns its sampler, and the running deduplicator is what suppresses repeated draws.

Output and state#

Writes the kept pairs to --output as pretty-printed JSON with a trailing newline. Stdout is a pretty JSON document with path and report, where the report carries generator first — local:<model id> or brama:<route> — then trait_name, trait_description, opposite, requested, attempts, kept, rejected_empty, rejected_refusals, rejected_duplicates, refusal_retries, and diversity measured over the generated questions. Stderr gets one line naming the generator and the derived opposite trait, then one progress line per attempt — synthesizing pair 3/20 (attempt 7).

Exhausting the attempt budget is not a refusal: the set is written with however many pairs survived and kept is below requested. Field meanings are tabulated under synthetic pairs.

Refusals#

  • Leaving out --retry-multiplier, --dedupe-bits, --dedupe-bands, --refusal-threshold, --max-new-tokens, --temperature, --top-p or --seed is a usage error that names the flag, exit 2, before anything loads or is called.
  • --generator takes local or brama and nothing else; any other value is refused as unknown generator "cloud"; expected local or brama. What each route needs is checked before any model work: pairs synthesize with --generator local requires --model and pairs synthesize with --generator brama requires --generator-model.
  • On the local route the model loads before anything else, so the shared loading refusals come first: unknown devices, GPU backends this binary was built without, and unsupported families (model architecture <TYPE> is unsupported by this Ster build; use a Hugging Face checkpoint whose model_type is one of <FAMILIES>, model resolution). The Brama route loads nothing and raises none of them.
  • On the Brama route the environment is checked before the first call: BRAMA_URL is unset or empty; export the Brama gateway base Ster should call, BRAMA_BEARER is unset or empty; export Ster's own Brama bearer before running this command, and BRAMA_URL must be an https:// base or an explicit http:// loopback address, because Brama answers plain http elsewhere with 426 secure_transport_required. An empty route is refused as the generator model must be a Brama alias, a canonical provider/model route, or a selector.
  • A gateway refusal is surfaced in Brama's own words, as brama refused the completion: {status} {message} read verbatim out of its {"error": {…}} envelope — for example brama refused the completion: 401 unauthorized. A body that is not that envelope becomes brama refused the completion with {status} and a body that is not its error envelope: {excerpt}.
  • Ster duplicates Brama's two documented bounds locally, in Brama's own words, to save a round trip per pair: max_tokens must be between one and 32768 and temperature must be finite and between zero and 2.
  • --count 0 is refused as synthesis requires a pair count above zero, and --retry-multiplier 0 as synthesis requires a retry multiplier above zero.
  • --temperature 0 is refused as synthesis requires a temperature above zero; argmax generation repeats a single prompt, on either route. Every prompt in the loop is a constant, so argmax generation — Ster's own at or below zero temperature, or a hosted sampler asked for the same — would return one identical pair for the whole run.
  • Dedupe options are checked before the first generation: dedupe band count <N> must divide the 64-bit simhash evenly, such as 4, 8, 16, or 32 and dedupe threshold <N> exceeds the 64-bit simhash width.
  • A run where no pair survived writes nothing: the write validates the set and refuses as pair set <PATH> contains no pairs.
  • --chat-template takes auto or off and nothing else; any other value is refused as unknown chat template mode "<VALUE>"; expected auto or off. On the local route the check happens after the model loads, because the template it names lives in the checkpoint; the Brama route ignores the flag and raises nothing.