server: sampling overrides on the speech endpoint
The speech body accepts seed, max_new_tokens, temperature, top_k, top_p, and repetition_penalty. Unset fields keep the engine defaults, a temperature of zero selects greedy decoding, and the subtalker mirrors the talker knobs. A fixed seed makes a request reproducible.
This commit is contained in:
@@ -139,9 +139,15 @@ curl -X POST localhost:8080/v1/voices -H "Content-Type: application/json" \
|
|||||||
\"spk_b64\":\"$(base64 -w0 ref.spk)\",\"rvq_b64\":\"$(base64 -w0 ref.rvq)\"}"
|
\"spk_b64\":\"$(base64 -w0 ref.spk)\",\"rvq_b64\":\"$(base64 -w0 ref.rvq)\"}"
|
||||||
|
|
||||||
curl -X POST localhost:8080/v1/audio/speech -H "Content-Type: application/json" \
|
curl -X POST localhost:8080/v1/audio/speech -H "Content-Type: application/json" \
|
||||||
-d '{"input":"Hello world.","voice":"freeman","response_format":"wav"}' -o out.wav
|
-d '{"input":"Hello world.","voice":"freeman","response_format":"wav",
|
||||||
|
"seed":42,"temperature":0.8}' -o out.wav
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The speech body accepts optional sampling overrides (`seed`,
|
||||||
|
`max_new_tokens`, `temperature`, `top_k`, `top_p`,
|
||||||
|
`repetition_penalty`); unset fields keep the engine defaults and a
|
||||||
|
fixed seed makes the request reproducible.
|
||||||
|
|
||||||
## Embedding the library
|
## Embedding the library
|
||||||
|
|
||||||
The CLI tools are thin wrappers over a public ABI. Single-header,
|
The CLI tools are thin wrappers over a public ABI. Single-header,
|
||||||
|
|||||||
@@ -626,7 +626,13 @@ Endpoints :
|
|||||||
```
|
```
|
||||||
POST /v1/audio/speech OAI text-to-speech; response_format "pcm"
|
POST /v1/audio/speech OAI text-to-speech; response_format "pcm"
|
||||||
streams s16le 24 kHz mono chunked as it is
|
streams s16le 24 kHz mono chunked as it is
|
||||||
generated, "wav" returns a one-shot RIFF file
|
generated, "wav" returns a one-shot RIFF file.
|
||||||
|
Optional sampling overrides ride in the same
|
||||||
|
body: seed, max_new_tokens, temperature,
|
||||||
|
top_k, top_p, repetition_penalty. Unset
|
||||||
|
fields keep the engine defaults, temperature
|
||||||
|
0 selects greedy decoding, the subtalker
|
||||||
|
mirrors the talker knobs
|
||||||
GET /v1/models single loaded model
|
GET /v1/models single loaded model
|
||||||
GET /v1/voices model speakers plus registered cloned voices
|
GET /v1/voices model speakers plus registered cloned voices
|
||||||
POST /v1/voices register a cloned voice: {name, ref_text,
|
POST /v1/voices register a cloned voice: {name, ref_text,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "audio-io.h"
|
#include "audio-io.h"
|
||||||
#include "yyjson.h"
|
#include "yyjson.h"
|
||||||
|
|
||||||
|
#include <cfloat>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -42,6 +43,15 @@ struct tts_request {
|
|||||||
std::string instructions; // OAI instructions, mapped to the ABI instruct field
|
std::string instructions; // OAI instructions, mapped to the ABI instruct field
|
||||||
std::string format; // "pcm" (stream) or "wav" (one-shot)
|
std::string format; // "pcm" (stream) or "wav" (one-shot)
|
||||||
float speed; // OAI speed, parsed then ignored (no time stretch in the ABI)
|
float speed; // OAI speed, parsed then ignored (no time stretch in the ABI)
|
||||||
|
|
||||||
|
// Optional sampling overrides. -1 (ints) and NaN (floats) mark a
|
||||||
|
// field the client left unset, keeping the engine defaults.
|
||||||
|
int64_t seed; // forwarded verbatim, -1 draws a random seed
|
||||||
|
int max_new_tokens; // strictly positive
|
||||||
|
int top_k; // 0 disables the top-k filter
|
||||||
|
float temperature; // 0 selects greedy decoding
|
||||||
|
float top_p; // in (0, 1]
|
||||||
|
float repetition_penalty; // strictly positive
|
||||||
};
|
};
|
||||||
|
|
||||||
// One voice registration parsed from the POST /v1/voices JSON body.
|
// One voice registration parsed from the POST /v1/voices JSON body.
|
||||||
@@ -165,8 +175,54 @@ static bool tts_parse_request(const std::string & body, tts_request & req, std::
|
|||||||
yyjson_val * speed = yyjson_obj_get(root, "speed");
|
yyjson_val * speed = yyjson_obj_get(root, "speed");
|
||||||
req.speed = yyjson_is_num(speed) ? (float) yyjson_get_num(speed) : 1.0f;
|
req.speed = yyjson_is_num(speed) ? (float) yyjson_get_num(speed) : 1.0f;
|
||||||
|
|
||||||
|
// Optional sampling overrides. A missing field keeps its unset
|
||||||
|
// marker; a present field must be well typed and in domain.
|
||||||
|
req.seed = -1;
|
||||||
|
req.max_new_tokens = -1;
|
||||||
|
req.top_k = -1;
|
||||||
|
req.temperature = NAN;
|
||||||
|
req.top_p = NAN;
|
||||||
|
req.repetition_penalty = NAN;
|
||||||
|
|
||||||
|
auto opt_int = [&](const char * key, int64_t lo, int64_t hi, int64_t & out) -> bool {
|
||||||
|
yyjson_val * v = yyjson_obj_get(root, key);
|
||||||
|
if (!v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!yyjson_is_int(v) || yyjson_get_sint(v) < lo || yyjson_get_sint(v) > hi) {
|
||||||
|
err = std::string("'") + key + "' is out of domain";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = yyjson_get_sint(v);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto opt_num = [&](const char * key, double lo, double hi, float & out) -> bool {
|
||||||
|
yyjson_val * v = yyjson_obj_get(root, key);
|
||||||
|
if (!v) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!yyjson_is_num(v) || yyjson_get_num(v) < lo || yyjson_get_num(v) > hi) {
|
||||||
|
err = std::string("'") + key + "' is out of domain";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = (float) yyjson_get_num(v);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
int64_t max_new = -1;
|
||||||
|
int64_t top_k = -1;
|
||||||
|
bool ok = opt_int("seed", INT64_MIN, INT64_MAX, req.seed) && opt_int("max_new_tokens", 1, INT32_MAX, max_new) &&
|
||||||
|
opt_int("top_k", 0, INT32_MAX, top_k) && opt_num("temperature", 0.0, FLT_MAX, req.temperature) &&
|
||||||
|
opt_num("top_p", DBL_MIN, 1.0, req.top_p) &&
|
||||||
|
opt_num("repetition_penalty", DBL_MIN, FLT_MAX, req.repetition_penalty);
|
||||||
|
req.max_new_tokens = (int) max_new;
|
||||||
|
req.top_k = (int) top_k;
|
||||||
|
|
||||||
yyjson_doc_free(doc);
|
yyjson_doc_free(doc);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (req.format != "pcm" && req.format != "wav") {
|
if (req.format != "pcm" && req.format != "wav") {
|
||||||
err = "response_format must be 'pcm' or 'wav'";
|
err = "response_format must be 'pcm' or 'wav'";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "rvq-file.h"
|
#include "rvq-file.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -227,6 +228,35 @@ int main(int argc, char ** argv) {
|
|||||||
p.instruct = req.instructions.c_str();
|
p.instruct = req.instructions.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sampling overrides ride straight into the ABI; the subtalker
|
||||||
|
// mirrors the talker knobs so the HTTP surface stays a single
|
||||||
|
// coherent set. A temperature of zero selects greedy decoding
|
||||||
|
// on both.
|
||||||
|
p.seed = req.seed;
|
||||||
|
if (req.max_new_tokens != -1) {
|
||||||
|
p.max_new_tokens = req.max_new_tokens;
|
||||||
|
}
|
||||||
|
if (req.top_k != -1) {
|
||||||
|
p.top_k = req.top_k;
|
||||||
|
p.subtalker_top_k = req.top_k;
|
||||||
|
}
|
||||||
|
if (!std::isnan(req.temperature)) {
|
||||||
|
if (req.temperature == 0.0f) {
|
||||||
|
p.do_sample = false;
|
||||||
|
p.subtalker_do_sample = false;
|
||||||
|
} else {
|
||||||
|
p.temperature = req.temperature;
|
||||||
|
p.subtalker_temperature = req.temperature;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!std::isnan(req.top_p)) {
|
||||||
|
p.top_p = req.top_p;
|
||||||
|
p.subtalker_top_p = req.top_p;
|
||||||
|
}
|
||||||
|
if (!std::isnan(req.repetition_penalty)) {
|
||||||
|
p.repetition_penalty = req.repetition_penalty;
|
||||||
|
}
|
||||||
|
|
||||||
// Trampoline : the C ABI on_chunk forwards to the C++ sink.
|
// Trampoline : the C ABI on_chunk forwards to the C++ sink.
|
||||||
const tts_sink * sink_ptr = &sink;
|
const tts_sink * sink_ptr = &sink;
|
||||||
p.on_chunk = [](const float * s, int ns, void * u) -> bool {
|
p.on_chunk = [](const float * s, int ns, void * u) -> bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user