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#
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#
| Input | Required | Meaning |
|---|---|---|
--generator <GENERATOR> | yes | local 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 brama | The Brama route that writes the pairs, for example wisent-backend/chat/primary. Required with --generator brama; the local route takes none. |
--model <MODEL> | local route | Hugging Face model id or local model directory. Required by --generator local; --generator brama loads no weights and takes none. |
--trait <TRAIT> | yes | One-sentence description of the trait the positive side shows. |
--count <COUNT> | yes | Number of pairs to keep after refusal and duplicate rejection; must be above zero. |
--output <OUTPUT> | yes | Destination pair-set JSON; missing parent directories are created. |
--revision <REVISION> | no | Local route only. Immutable Hugging Face revision; Hub resolution defaults to main. |
--device <DEVICE> | no | Local route only. cpu (default), metal, or cuda. |
--trait-name <TRAIT_NAME> | no | Trait label written into the set; without it the set takes the whole trait description as its name. |
--opposite <OPPOSITE> | no | Skips the opposite-trait generation and uses this text instead. |
--retry-multiplier <MULTIPLIER> | yes | The attempt budget is --count times this value. Ster assumes none. |
--dedupe-bits <BITS> | yes | A pair within this SimHash Hamming distance of one already kept is dropped. |
--dedupe-bands <BANDS> | yes | The count must divide the 64-bit fingerprint evenly. |
--refusal-threshold <THRESHOLD> | yes | A generated side scoring at or above it reads as a refusal. |
--max-new-tokens <COUNT> | yes | The token budget of one generated side. The Brama route sends it as max_tokens, which the gateway holds between one and 32768. |
--temperature <TEMPERATURE> | yes | Must be above zero. The Brama route also holds it to the gateway's own bound of at most 2. |
--top-p <TOP_P> | yes | The nucleus the sampler draws from. Local route only: Brama's chat request has no such field, so it does not travel. |
--seed <SEED> | yes | On 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> | no | f32 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> | no | auto 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-por--seedis a usage error that names the flag, exit 2, before anything loads or is called. --generatortakeslocalorbramaand nothing else; any other value is refused asunknown generator "cloud"; expected local or brama. What each route needs is checked before any model work:pairs synthesize with --generator local requires --modelandpairs 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, andBRAMA_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 asthe 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 examplebrama refused the completion: 401 unauthorized. A body that is not that envelope becomesbrama 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 32768andtemperature must be finite and between zero and 2. --count 0is refused assynthesis requires a pair count above zero, and--retry-multiplier 0assynthesis requires a retry multiplier above zero.--temperature 0is refused assynthesis 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 32anddedupe 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-templatetakesautooroffand nothing else; any other value is refused asunknown 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.