Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Each release's page on GitHub has its notes and measurements. See [the 1.0.0 rel

## Unreleased

- speech: Drama requests accept `temperature`, `top_p`, `top_k` (1 through 64) and exact unsigned 64-bit `seed`. Whole replies, streams, references, guidance and long-form segments retain the requested settings; omitted or null fields keep existing defaults. Explicit seeds advance by each segment's attempt number on redraws. See [speech sampling](docs/speech-sampling.md).

- speech: long-form Drama 3 splits preserve complete special tokens, bracketed cues and nested paired direction tags, even when one span exceeds the character target. CJK punctuation supplies sentence and clause boundaries without requiring spaces, and topic-shift markers inside protected spans cannot split them. Existing input, prompt-token, segment and frame limits still apply ([docs](docs/drama-voices.md#long-form-replies)).
- speech: Drama 3's DAC activation planes are bounded by the decode chunk and causal halo rather than the utterance limit. QKV history retains absolute frame indexing and attention tile boundaries. `zig build drama-codec-chunk` builds `oracle/bin/tf-drama-codec-chunk CHECKPOINT`, a GPU byte-equality gate against the original full-plane layout; the trace diagnostic explicitly retains that reference layout.
- speech: `--silence-mid-s S` (CUDA; 0 for off, or 3 to below 10; default 0) ends a Drama 3 synthesis as `silence` when its end of speech comes inside S seconds or more below -40 dBFS after 1 s or more voiced: a reply that fell silent mid-way and stopped there, its last words never spoken, as one 4-bit take did after a 9.0 s hole. Holes that recover are left alone, since a client that shortens pauses never plays them, and a reply the frame cap stops is not judged. Such a draw is redrawn under the silence redraws' rules, a long-form segment in place, and whole replies count the draws it ended in `X-Silence-Mid-Trips`. Replayed at S = 3 over 2272 draws in the engine's frame accounting, it ended 15 of the 2047 that the 10 s rule left alone; all 8 with a text to judge by had lost words, and none of them had spoken in full. With S = 0 replies are as before ([docs](docs/drama-voices.md#holes-that-end-a-reply))
- speech: `--register-redraw-st R` (0 to 24, default 0: off) draws a whole Drama 3 reply once more when a phrase after the first opens R semitones or more above the median of its other phrase openings with `--register-redraw-high F` percent (default 20) of its voiced frames over `--register-high-hz H` (default 210), the thin, high phrase that sounds stitched into a reply. The redraw starts only if t + d fits the silence redraws' budget and `X-Deadline-S`, and the draw with the smaller jump is sent, never one that ended in silence or short; a tie keeps the first. Replies carry `X-Register-Trip` and `X-Register-Redraws`; streams and multi-segment long-form replies are not measured, and a draw with a NaN or infinite sample fails the reply before any measure. The measure ports the numpy one it was calibrated with: on 77 takes it decided every trip the same way at R = 4, F = 20, H = 210 (evidence on that corpus, not exact parity at every threshold; those settings tripped 6 of 51 replies of a day's use), at about 1.2 ms per second of audio. With R = 0 replies are as before ([docs](docs/drama-voices.md#register-redraws))
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ The API listens at `http://127.0.0.1:8080/v1` by default.
It serves OpenAI chat completions, completions and Responses, plus Anthropic Messages.
Use the model ID returned by `/v1/models`, or set one with `--name`.

Drama speech at `/v1/audio/speech` accepts [request sampling controls](docs/speech-sampling.md)
for temperature, top-p, top-k and exact 64-bit seeds, including streamed and long-form replies.

```sh
curl -fsS http://127.0.0.1:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
Expand Down
20 changes: 11 additions & 9 deletions docs/drama-voices.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ With `--long-form`, each segment may use up to N frames, and a reply may use 600
tensorfold serve DRAMA3_DIR --voices voices/ --default-voice NAME --cfg-gamma 2
```

On CUDA, `--cfg-gamma G` (0 for off, or 1 to 5; default 0) applies classifier-free guidance to a referenced prompt's semantic head. The main token of each frame is drawn from l_c + (G − 1)(l_c − l_u) instead of l_c: l_c are the logits of the prompt as sent (the voice's references, then the input), and l_u are those of the same turn as a plain prompt, the one the built-in voice gets. So the draw moves away from what the model says without the references. The mix covers all 4097 semantic logits, the end-of-speech logit included, so guidance also moves where a draw ends. It comes before the sampler, which is unchanged and not greedy: a draw seeded per request, at temperature 1, top-p 0.9 and top-k 30, with the repetition-aware redraw (RAS: a main token that repeats one of the last 16 frames' is replaced by a second draw at top-p 0.95). The tower, and with it codebooks 1 to 9, still reads the referenced prompt's hidden row.
On CUDA, `--cfg-gamma G` (0 for off, or 1 to 5; default 0) applies classifier-free guidance to a referenced prompt's semantic head. The main token of each frame is drawn from l_c + (G − 1)(l_c − l_u) instead of l_c: l_c are the logits of the prompt as sent (the voice's references, then the input), and l_u are those of the same turn as a plain prompt, the one the built-in voice gets. So the draw moves away from what the model says without the references. The mix covers all 4097 semantic logits, the end-of-speech logit included, so guidance also moves where a draw ends. It comes before the native sampler, using the [request sampling controls](speech-sampling.md) (defaults: temperature 1, top-p 0.9 and top-k 30), with the repetition-aware redraw (RAS: a main token that repeats one of the last 16 frames' is replaced by a second draw at top-p 0.95). The tower, and with it codebooks 1 to 9, still reads the referenced prompt's hidden row.

- **The second sequence.** The engine keeps l_u in a second backbone sequence that shares the model's weights: 0.33 GB at context 8192 (the KV caches of the 10 attention layers and the states of the 30 DeltaNet layers). It prefills the plain prompt before the referenced one, and then reads every frame the referenced one reads. That is one more backbone run per frame, and the server's startup line gives the sequence's size.
- **`--cfg-every K`** (default 1) would run the second sequence only every K frames, reading the K frames in one pass, its last logits guiding the draws in between. Only K = 1 is accepted for now: tests check that a catch-up run reads every frame once and in order, but nothing has yet compared its logits with K runs one frame each, so the server refuses K > 1 at startup.
Expand Down Expand Up @@ -270,7 +270,7 @@ A reply can fall silent mid-way for less than 10 s and then end, its last senten
tensorfold serve DRAMA3_DIR --voices voices/ --silence-redraws 2 --silence-redraw-plain
```

`--silence-redraws N` (0 to 8, default 0) draws a whole reply again when it trips, up to N times: when the guard ended it, or, with `--short-reply-ratio`, when it stopped far short of its text. A redraw keeps the request's voice and seeds with the server's salt plus k for the k-th redraw, so it is a different draw that is still deterministic. Salts overlap: with the same prompt, redraw k at salt s is the same draw as the first draw at salt s + k (`TENSORFOLD_SEED_SALT`), so a sweep over salts should leave N between the salts it compares.
`--silence-redraws N` (0 to 8, default 0) draws a whole reply again when it trips, up to N times: when the guard ended it, or, with `--short-reply-ratio`, when it stopped far short of its text. A redraw keeps the request's voice and sampling controls. Without an explicit seed it uses the server's salt plus k for the k-th redraw; an explicit seed instead advances by k modulo 2^64. See [speech sampling](speech-sampling.md). Salts overlap: with the same prompt, redraw k at salt s is the same draw as the first draw at salt s + k (`TENSORFOLD_SEED_SALT`), so a sweep over salts should leave N between the salts it compares.

- **Deadline.** Each redraw starts only if it should end within the budget: the lower of the request's `X-Deadline-S` (seconds the client waits, counted from when the speech route takes the request, before it reads the body) and `--silence-redraw-budget-s` (default 120). After a draw that tripped (ended `silence`, or a short `stop`, below) at t seconds from receipt and took d seconds, the next one is started only if t + d ≤ budget, since a stalled draw's duration predicts the next one's. Otherwise the last draw is sent as it is, with its own `X-Finish-Reason`. For example, a client that allows 96 s for a 509-character reply gets a redraw after a trip at 17 s (17 + 17 = 34 ≤ 96). After a 50 s stall it gets none (50 + 50 = 100 > 96), so it receives the tripped reply within its deadline instead of a timeout. A redraw that finds the engine busy with another request waits for it only until the latest start that still fits (budget − d after receipt), then sends the tripped draw. The deadline therefore binds every start; only a draw already generating can run past it. `--silence-redraw-budget-s` and `--silence-redraw-plain` need `--silence-redraws` of 1 or more.
- **Plain redraws.** With `--silence-redraw-plain`, every redraw speaks the input without its cues: each `[sighs]`, `[laughs]`, `[small laugh]`, `[pause]` and `[hesitates]` becomes a space, then white space is collapsed to single spaces and trimmed (`done. [pause] Next` is spoken `done. Next`; `word [sighs].` is spoken `word .`). An input without one of those cues is redrawn as it is. A plain redraw never speaks the cues again: if nothing is left without them, or the cue-free text cannot be prompted, there is no redraw, the tripped draw is sent, and the log says why. The cues drove the trips measured: on texts that tripped the guard, a cue-free draw finished 14 of 14, a draw with the cues and a new seed 5 of 14.
Expand Down Expand Up @@ -304,22 +304,24 @@ tensorfold serve DRAMA3_DIR --voices voices/ --default-voice NAME --long-form

With `--long-form`, a speech input longer than `--long-form-chars` (default 500 code points) is spoken in segments instead of in one synthesis. Each segment hears the segments before it: the earlier segments' text and generated codes are added as references after the request's references and the voice's, with the latest last. This follows the way Fish's long-form inference keeps earlier segments in context.

- **Segments.** The input is cut into the fewest segments that fit the limit, with cuts only at sentence ends (`.` `!` `?` `…` followed by white space, keeping closing quotes and brackets, and line breaks).
- **Segments.** The input is cut into the fewest segments that fit the character target, with cuts at sentence ends (`.` `!` `?` `…` followed by white space; `。` `!` `?` `;` without needing white space), keeping closing quotes and brackets, and at line breaks.
- Among the cuts that give that many segments, the server picks the most balanced lengths, and prefers a cut where the text turns.
- The text turns at a blank line, after a question, or where the next sentence opens a new move: it starts with So, Okay, OK, Now, Right, Anyway, Also, Next, Finally, But or Here's, or with a number and "thing"/"things" (One thing, Two things, 3 things).
- A turn wins over a cut that is up to about 15% of a segment better balanced.
- Only a sentence longer than the limit is cut inside, at `;` `:` `,`, then at spaces, then anywhere.
- Only a sentence longer than the target is cut inside, at `;` `:` `,` before white space or `,` `:` `、`, then at spaces, then between code points.
- A complete `<|...|>` token, balanced `[...]` cue or correctly nested `<name>...</name>` span stays whole. Simple tag names start with an ASCII letter and contain letters, digits, `_` or `-`; names match exactly, and attributes are not parsed. Nested spans stay with their outer span; adjacent controls remain separate. An unclosed or crossed delimiter is ordinary text, while complete spans inside or after it still stay whole.
- An indivisible span may exceed the character target; other text retains the target. Input, prompt-token, frame and segment-count limits still apply. The splitter preserves the original text and never inserts or reopens tags.
- **History.** `--long-form-history K` (0 to 3, default 1) sets how many earlier segments a segment hears. The oldest is dropped when the prompt would exceed the engine's prompt limit.
- **Joins.**
- **Pause.** At each join, the silence already there is measured: the end of one segment plus the start of the next, in 10 ms frames below -42 dBFS. Only the shortfall to the boundary's target is added, never cutting existing silence. Targets:
- `.` or `!`: 0.42 s
- `.` `!` `。` `!` or `;`: 0.42 s
- `…` or `...`: 0.55 s
- a turn (a question, or a sentence opening a new move): 0.60 s
- a blank line: 0.70 s
- a cut inside a sentence (at `,` `;` `:` or a space): 0.20 s
- a cut inside a sentence (at `,` `;` `:` `,` `:` `、` or a space): 0.20 s
- **Room tone.** The added pause is copied from the earlier segment's quietest 100 ms in its last second, played back and forth. It is digital silence when that segment has no quiet tail.
- **Level.** A segment whose speech level (the median dBFS of its frames at or above -42 dBFS) is more than 1.5 dB from the median of the earlier segments gets one constant gain. The gain brings it to 1.5 dB, is at most 3 dB either way, and never raises a peak past 0.99.
- **Seeds.** Each segment's seed comes from its own prompt rows, as a single synthesis's does.
- **Seeds.** Without an explicit request seed, each segment's seed comes from its own prompt rows, as a single synthesis's does. An explicit seed starts each segment's attempt count at zero; its redraws use seed + attempt modulo 2^64. Every segment keeps the same requested temperature, top-p and top-k; see [speech sampling](speech-sampling.md).
- **Limits.**
- More than 12 segments are refused up front.
- A reply may use 6000 frames in all; each segment gets at most what is left.
Expand All @@ -337,9 +339,9 @@ tensorfold serve DRAMA3_DIR --voices voices/ --default-voice NAME --long-form --

A client marks a topic shift with `¶` (U+00B6), sent as ` ¶ `. A caller's wrapper writes one for each blank line it collapses. With `--shift-pause-s T` (0 to 3 seconds, default 0: off; needs `--long-form`), the reply pauses for at least T seconds at each mark, and says where those pauses are.

- **Marks.** A mark counts only after `.` `!` `?` or `…` (closing quotes and brackets may sit between), and only with words on both sides of it. Here a word means a Unicode letter or number (general category L or N, so `次へ`, `Été` and `2` count, while `!!!`, `×` and `€` do not), outside `[…]` and `<…>` controls, between the mark and the last cut and between it and the next mark. A cue may open the text after a mark (`done. ¶ [curious] Should I…`), but a mark followed by punctuation, symbols or cues alone does not count. Several marks in a row count as one. Every other mark is dropped, and the server logs how many. Every mark, with the white space after it, is removed before anything else reads the input. The tokenizer, the long-form history, a plain redraw's text and the short-reply rule's character count never see one.
- **Marks.** A mark counts only after `.` `!` `?` `…` `。` `!` `?` or `;` (closing quotes and brackets may sit between), and only with words on both sides of it. Here a word means a Unicode letter or number (general category L or N, so `次へ`, `Été` and `2` count, while `!!!`, `×` and `€` do not), outside `[…]` and `<…>` controls, between the mark and the last cut and between it and the next mark. A cue may open the text after a mark (`done. ¶ [curious] Should I…`), but a mark followed by punctuation, symbols or cues alone does not count. Marks inside an indivisible control span do not force a cut. Several marks in a row count as one. Every other mark is dropped, and the server logs how many. Every mark, with the white space after it, is removed before anything else reads the input. The tokenizer, the long-form history, a plain redraw's text and the short-reply rule's character count never see one.
- **Cues that trail a sentence.** Put the mark after the sentence's end and before a cue that trails it: `nights in a row now. ¶ [small laugh] The calendar`. The cue then opens the next segment. Written after the cue (`now. [small laugh] ¶`), the mark follows `]`, not a sentence end, so it is dropped.
- **Cuts.** Each mark that counts cuts a long-form segment there, even in an input short enough for one segment. The text between marks is split as long-form splits any input, and the segment cap still applies.
- **Cuts.** Each mark that counts cuts a long-form segment there, even in an input short enough for one segment. The text between marks is split as long-form splits any input, including the indivisible-span exception to the character target.
- **Pauses.** At a mark's join, the silent 10 ms frames around the join (under -42 dBFS, counted on the reply's own grid from its first sample, as a client's pacing counts them) are made to last at least T seconds. Quiet already there counts, and is never cut. The fill is added, then measured in the audio as sent. If the crossfades or the grid leave it short, more room tone goes in its middle until it is long enough. The fill is room tone: the quietest silent 100 ms (but not digital silence, under -90 dBFS) of the earlier segment's last second. Failing that it comes from all audio earlier in the reply, then from the next segment. If none of these has one, it is noise at -65 dBFS, never zeros. Its samples are kept 6 dB under the floor, so every 10 ms of it is silent. The fill fades in from the earlier segment's last samples and out into the next segment's first samples, each over 15 ms at equal power. Both segments are mirrored at their seams, so neither segment's own audio changes.
- **`X-Pause-Spans: s1-e1,s2-e2,…`** lists one span per mark the reply reached, as sample offsets into the audio exactly as sent (start inclusive, end exclusive, ascending). A span runs from the start of the earlier segment's trailing quiet to the end of the next segment's leading quiet, fill included, and always contains the silent frames measured around the join, so it holds at least T seconds of them. With `stream: true` the spans come as a trailer, declared next to `X-Long-Form-Segments`. The header is absent when there are no spans.
- **Redraws.** Redraws work per segment as usual. The spans are measured on each segment's final take, so they always describe the audio delivered. Segments under 200 characters fall outside the short-reply rule, and only the silence guard covers them.
Expand Down
Loading