Signed-off-by: Mengjie Zhao <zmj0129@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
525 lines
19 KiB
Python
525 lines
19 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
"""E2E online serving tests for Gepard-1.0 via ``/v1/audio/speech``.
|
|
|
|
Zero-shot default voice. Needs a GPU and the NeMo NanoCodec; the weekly
|
|
``TTS · Gepard-1.0 · L4`` step installs NeMo then runs this file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import array
|
|
import os
|
|
import statistics
|
|
import struct
|
|
import time
|
|
import wave
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from dataclasses import dataclass
|
|
from io import BytesIO
|
|
|
|
os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
|
|
|
|
import pytest
|
|
import requests
|
|
import soundfile as sf
|
|
|
|
from tests.helpers.mark import hardware_test
|
|
from tests.helpers.media import convert_audio_bytes_to_text
|
|
from tests.helpers.runtime import OmniServerParams
|
|
from tests.helpers.stage_config import get_deploy_config_path
|
|
|
|
pytest.importorskip("nemo.collections.tts.models")
|
|
|
|
pytestmark = [pytest.mark.slow, pytest.mark.tts]
|
|
|
|
MODEL = "nineninesix/gepard-1.0"
|
|
SAMPLE_RATE = 22050
|
|
SAMPLES_PER_FRAME = 1024
|
|
PCM16_BYTES_PER_FRAME = SAMPLES_PER_FRAME * 2
|
|
DEFAULT_TIMEOUT_S = 180.0
|
|
|
|
_DISTINGUISHABLE_PROMPTS = {
|
|
"He drinks coffee every morning.": "coffee",
|
|
"Machine learning is interesting.": "learning",
|
|
"Please close the window before leaving.": "window",
|
|
"My favorite color is purple.": "purple",
|
|
}
|
|
|
|
tts_server_params = [
|
|
pytest.param(
|
|
OmniServerParams(
|
|
model=MODEL,
|
|
stage_config_path=get_deploy_config_path("gepard.yaml"),
|
|
server_args=["--trust-remote-code", "--disable-log-stats"],
|
|
stage_init_timeout=900,
|
|
),
|
|
id="gepard",
|
|
)
|
|
]
|
|
|
|
|
|
def _base_config(omni_server, text: str, **extra) -> dict:
|
|
cfg = {
|
|
"model": omni_server.model,
|
|
"input": text,
|
|
"voice": "default",
|
|
"timeout": DEFAULT_TIMEOUT_S,
|
|
"seed": 7,
|
|
# NanoCodec is quieter than the 24 kHz models the PCM HNR helper was
|
|
# calibrated on; keep the catastrophic-failure check, not a quality gate.
|
|
"min_hnr_db": -2.0,
|
|
"transcript_escalation_model": "large-v3",
|
|
}
|
|
cfg.update(extra)
|
|
return cfg
|
|
|
|
|
|
def _wav_sample_rate(wav_bytes: bytes) -> int:
|
|
return struct.unpack_from("<I", wav_bytes, 24)[0]
|
|
|
|
|
|
def _wav_pcm_payload_len(wav_bytes: bytes) -> int:
|
|
data, _sr = sf.read(BytesIO(wav_bytes), dtype="int16")
|
|
return int(data.size) * 2
|
|
|
|
|
|
def _pcm16le_to_wav(pcm: bytes, sample_rate: int = SAMPLE_RATE) -> bytes:
|
|
"""Whisper helpers expect a container; streamed speech is raw s16le."""
|
|
buf = BytesIO()
|
|
with wave.open(buf, "wb") as wf:
|
|
wf.setnchannels(1)
|
|
wf.setsampwidth(2)
|
|
wf.setframerate(sample_rate)
|
|
wf.writeframes(pcm)
|
|
return buf.getvalue()
|
|
|
|
|
|
def _pcm16_samples(pcm: bytes) -> array.array:
|
|
samples = array.array("h")
|
|
samples.frombytes(pcm)
|
|
return samples
|
|
|
|
|
|
def _pcm16_diag(label: str, left: bytes, right: bytes) -> None:
|
|
extra_frames = (len(left) - len(right)) // PCM16_BYTES_PER_FRAME
|
|
overlap = min(len(left), len(right))
|
|
first_frame_match = (
|
|
overlap >= PCM16_BYTES_PER_FRAME and left[:PCM16_BYTES_PER_FRAME] == right[:PCM16_BYTES_PER_FRAME]
|
|
)
|
|
worst = None
|
|
corr = None
|
|
first_diff_sample = None
|
|
if overlap >= 2 and overlap % 2 == 0:
|
|
a = _pcm16_samples(left[:overlap])
|
|
b = _pcm16_samples(right[:overlap])
|
|
worst = max(abs(x - y) for x, y in zip(a, b, strict=True))
|
|
for i, (x, y) in enumerate(zip(a, b, strict=True)):
|
|
if x != y:
|
|
first_diff_sample = i
|
|
break
|
|
try:
|
|
corr = round(statistics.correlation(a, b), 6)
|
|
except statistics.StatisticsError:
|
|
corr = None
|
|
print(
|
|
f"[Gepard pcm-diag] {label} left={len(left)} right={len(right)} "
|
|
f"extra_frames={extra_frames} first_frame_match={first_frame_match} "
|
|
f"first_diff_sample={first_diff_sample} max_abs={worst} corr={corr}"
|
|
)
|
|
|
|
|
|
def _scan_logs_for_preemption(omni_server) -> None:
|
|
log_paths = getattr(omni_server, "_stage_log_paths", {}) or {}
|
|
hits: list[str] = []
|
|
for path in log_paths.values():
|
|
if path is None or not path.exists():
|
|
continue
|
|
text = path.read_text(encoding="utf-8", errors="replace").lower()
|
|
if "preempt" in text:
|
|
hits.append(str(path))
|
|
assert not hits, f"preemption mentioned in stage logs: {hits}"
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
@pytest.mark.parametrize("response_format", ["wav", "pcm"])
|
|
def test_text_to_audio_basic(omni_server, online_client, response_format: str, run_level: str) -> None:
|
|
text = "Hello, this is Gepard speaking."
|
|
# whisper-small/large-v3 mishear 22.05 kHz WAV of this prompt as
|
|
# "Jeb Ard" / "Jeff Bard" (cosine 0.80 / 0.75). Offline uses keyword
|
|
# containment; the PCM twin below still goes through the 0.9 ASR gate.
|
|
if response_format == "wav":
|
|
url = f"http://{omni_server.host}:{omni_server.port}/v1/audio/speech"
|
|
payload = {
|
|
"model": omni_server.model,
|
|
"input": text,
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": False,
|
|
"response_format": "wav",
|
|
}
|
|
r = requests.post(url, json=payload, timeout=DEFAULT_TIMEOUT_S)
|
|
r.raise_for_status()
|
|
audio = r.content
|
|
assert _wav_sample_rate(audio) == SAMPLE_RATE
|
|
pcm_len = _wav_pcm_payload_len(audio)
|
|
else:
|
|
[resp] = online_client.send_audio_speech_request(
|
|
_base_config(omni_server, text, stream=False, response_format=response_format)
|
|
)
|
|
assert resp.audio_bytes
|
|
pcm_len = len(resp.audio_bytes)
|
|
assert pcm_len % PCM16_BYTES_PER_FRAME == 0
|
|
if run_level in {"advanced_model", "full_model"}:
|
|
duration = pcm_len / 2 / SAMPLE_RATE
|
|
assert 0.5 < duration < 30.0, f"implausible duration {duration:.2f}s"
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_seed_determinism(omni_server, online_client) -> None:
|
|
text = "Hello, this is Gepard speaking."
|
|
[a] = online_client.send_audio_speech_request(_base_config(omni_server, text, seed=7, response_format="pcm"))
|
|
[b] = online_client.send_audio_speech_request(_base_config(omni_server, text, seed=7, response_format="pcm"))
|
|
[c] = online_client.send_audio_speech_request(_base_config(omni_server, text, seed=11, response_format="pcm"))
|
|
assert a.audio_bytes == b.audio_bytes
|
|
assert a.audio_bytes != c.audio_bytes
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_streaming_pcm_incremental(omni_server, online_client) -> None:
|
|
text = "Hello, this is Gepard speaking."
|
|
url = f"http://{omni_server.host}:{omni_server.port}/v1/audio/speech"
|
|
payload = {
|
|
"model": omni_server.model,
|
|
"input": text,
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": True,
|
|
"stream_format": "audio",
|
|
"response_format": "pcm",
|
|
}
|
|
start = time.perf_counter()
|
|
arrivals: list[tuple[float, int]] = []
|
|
streamed = bytearray()
|
|
with requests.post(url, json=payload, stream=True, timeout=DEFAULT_TIMEOUT_S) as resp:
|
|
resp.raise_for_status()
|
|
content_type = resp.headers.get("content-type") or ""
|
|
assert "pcm" in content_type or "octet-stream" in content_type
|
|
for chunk in resp.iter_content(chunk_size=None):
|
|
if not chunk:
|
|
continue
|
|
arrivals.append((time.perf_counter(), len(chunk)))
|
|
streamed.extend(chunk)
|
|
assert len(arrivals) >= 2, f"expected incremental chunks, got {len(arrivals)}"
|
|
assert arrivals[1][0] > arrivals[0][0]
|
|
ttfa_ms = (arrivals[0][0] - start) * 1000.0
|
|
cadence_ms = [(arrivals[i][0] - arrivals[i - 1][0]) * 1000.0 for i in range(1, len(arrivals))]
|
|
print(f"[Gepard streaming] TTFA_ms={ttfa_ms:.1f} chunk_cadence_ms={cadence_ms}")
|
|
|
|
[non_stream] = online_client.send_audio_speech_request(
|
|
_base_config(omni_server, text, seed=7, stream=False, response_format="pcm")
|
|
)
|
|
assert bytes(streamed) == non_stream.audio_bytes
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_seeded_streaming_stops(omni_server) -> None:
|
|
url = f"http://{omni_server.host}:{omni_server.port}/v1/audio/speech"
|
|
payload = {
|
|
"model": omni_server.model,
|
|
"input": "Hello, this is Gepard speaking.",
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": True,
|
|
"stream_format": "audio",
|
|
"response_format": "pcm",
|
|
}
|
|
with requests.post(url, json=payload, stream=True, timeout=DEFAULT_TIMEOUT_S) as resp:
|
|
resp.raise_for_status()
|
|
audio = b"".join(chunk for chunk in resp.iter_content(chunk_size=None) if chunk)
|
|
duration = len(audio) / 2 / SAMPLE_RATE
|
|
assert duration < 30.0, f"seeded stream ran {duration:.1f}s — stop constraint dropped?"
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_sse_stream_emits_delta_then_done(omni_server) -> None:
|
|
url = f"http://{omni_server.host}:{omni_server.port}/v1/audio/speech"
|
|
payload = {
|
|
"model": omni_server.model,
|
|
"input": "Hello, this is Gepard speaking.",
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": True,
|
|
"stream_format": "sse",
|
|
"response_format": "pcm",
|
|
}
|
|
with requests.post(url, json=payload, stream=True, timeout=DEFAULT_TIMEOUT_S) as resp:
|
|
resp.raise_for_status()
|
|
assert resp.headers.get("content-type", "").startswith("text/event-stream")
|
|
body = b"".join(resp.iter_content(chunk_size=None)).decode("utf-8", errors="replace")
|
|
assert "speech.audio.delta" in body
|
|
assert "speech.audio.done" in body
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_concurrent_requests_stay_isolated(omni_server, online_client, run_level: str) -> None:
|
|
def _one(text: str):
|
|
return online_client.send_audio_speech_request(_base_config(omni_server, text, seed=7, response_format="wav"))[
|
|
0
|
|
]
|
|
|
|
texts = list(_DISTINGUISHABLE_PROMPTS)
|
|
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
responses = list(pool.map(_one, texts))
|
|
|
|
assert all(r.success for r in responses)
|
|
for i in range(len(responses)):
|
|
for j in range(i + 1, len(responses)):
|
|
assert responses[i].audio_bytes != responses[j].audio_bytes
|
|
|
|
if run_level in {"advanced_model", "full_model"}:
|
|
for text, resp in zip(texts, responses, strict=True):
|
|
keyword = _DISTINGUISHABLE_PROMPTS[text]
|
|
transcript = convert_audio_bytes_to_text(resp.audio_bytes, language="en").lower()
|
|
assert keyword in transcript, f"expected {keyword!r} in {transcript!r} for {text!r}"
|
|
|
|
_scan_logs_for_preemption(omni_server)
|
|
|
|
|
|
def _speech_url(omni_server) -> str:
|
|
return f"http://{omni_server.host}:{omni_server.port}/v1/audio/speech"
|
|
|
|
|
|
def _stream_payload(omni_server, text: str, **extra) -> dict:
|
|
payload = {
|
|
"model": omni_server.model,
|
|
"input": text,
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": True,
|
|
"stream_format": "audio",
|
|
"response_format": "pcm",
|
|
}
|
|
payload.update(extra)
|
|
return payload
|
|
|
|
|
|
@dataclass
|
|
class _StreamCapture:
|
|
pcm: bytes
|
|
arrivals: list[tuple[float, int]]
|
|
|
|
@property
|
|
def duration_s(self) -> float:
|
|
return len(self.pcm) / 2 / SAMPLE_RATE
|
|
|
|
|
|
def _capture_stream(
|
|
url: str,
|
|
payload: dict,
|
|
timeout_s: float,
|
|
*,
|
|
sleep_after_first_s: float = 0.0,
|
|
) -> _StreamCapture:
|
|
start = time.perf_counter()
|
|
arrivals: list[tuple[float, int]] = []
|
|
pcm = bytearray()
|
|
with requests.post(url, json=payload, stream=True, timeout=timeout_s) as resp:
|
|
resp.raise_for_status()
|
|
first = True
|
|
for chunk in resp.iter_content(chunk_size=None):
|
|
if not chunk:
|
|
continue
|
|
arrivals.append((time.perf_counter() - start, len(chunk)))
|
|
pcm.extend(chunk)
|
|
if first and sleep_after_first_s > 0:
|
|
time.sleep(sleep_after_first_s)
|
|
first = False
|
|
return _StreamCapture(bytes(pcm), arrivals)
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_concurrent_streaming_stays_isolated(omni_server, run_level: str) -> None:
|
|
url = _speech_url(omni_server)
|
|
texts = list(_DISTINGUISHABLE_PROMPTS)
|
|
|
|
def _one(text: str) -> tuple[str, _StreamCapture]:
|
|
return text, _capture_stream(url, _stream_payload(omni_server, text), DEFAULT_TIMEOUT_S)
|
|
|
|
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
results = list(pool.map(_one, texts))
|
|
|
|
captures = [cap for _, cap in results]
|
|
assert all(len(c.arrivals) >= 2 for c in captures)
|
|
for i in range(len(captures)):
|
|
for j in range(i + 1, len(captures)):
|
|
assert captures[i].pcm != captures[j].pcm
|
|
|
|
if run_level in {"advanced_model", "full_model"}:
|
|
for text, cap in results:
|
|
keyword = _DISTINGUISHABLE_PROMPTS[text]
|
|
transcript = convert_audio_bytes_to_text(_pcm16le_to_wav(cap.pcm), language="en").lower()
|
|
assert keyword in transcript, f"expected {keyword!r} in {transcript!r} for {text!r}"
|
|
|
|
_scan_logs_for_preemption(omni_server)
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_slow_client_still_completes(omni_server, online_client) -> None:
|
|
"""Slow reader must finish, and seed=7 must match a prior non-stream.
|
|
|
|
Runs before ``test_client_disconnect_releases_capacity`` so an abort
|
|
storm cannot contaminate this comparison. Prints overlap diagnostics
|
|
before the bit-exact asserts.
|
|
"""
|
|
text = "Hello, this is Gepard speaking."
|
|
[baseline] = online_client.send_audio_speech_request(
|
|
_base_config(omni_server, text, seed=7, stream=False, response_format="pcm")
|
|
)
|
|
cap = _capture_stream(
|
|
url=_speech_url(omni_server),
|
|
payload=_stream_payload(omni_server, text),
|
|
timeout_s=DEFAULT_TIMEOUT_S,
|
|
sleep_after_first_s=3.0,
|
|
)
|
|
assert len(cap.arrivals) >= 2
|
|
assert len(cap.pcm) % PCM16_BYTES_PER_FRAME == 0
|
|
assert 0.5 < cap.duration_s < 30.0
|
|
print(f"[Gepard slow-client] chunks={len(cap.arrivals)} duration_s={cap.duration_s:.3f}")
|
|
_pcm16_diag("slow-vs-prior-nonstream", cap.pcm, baseline.audio_bytes)
|
|
[after] = online_client.send_audio_speech_request(
|
|
_base_config(omni_server, text, seed=7, stream=False, response_format="pcm")
|
|
)
|
|
_pcm16_diag("after-nonstream-vs-prior", after.audio_bytes, baseline.audio_bytes)
|
|
assert cap.pcm == baseline.audio_bytes
|
|
assert after.audio_bytes == baseline.audio_bytes
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_client_disconnect_releases_capacity(omni_server, online_client) -> None:
|
|
"""A follow-up request must complete after in-flight streams are closed.
|
|
|
|
Logs PCM diagnostics against a pre-abort ``seed=7`` baseline. Bit-exact
|
|
equality after abort is not asserted here: a dying stream can still share
|
|
a GPU batch with the next generate after ``abort()`` returns, and Gepard's
|
|
in-model stop head / bf16 GEMM are not row-stable in that batch.
|
|
"""
|
|
url = _speech_url(omni_server)
|
|
text = "Hello, this is Gepard speaking."
|
|
[baseline] = online_client.send_audio_speech_request(
|
|
_base_config(omni_server, text, seed=7, stream=False, response_format="pcm")
|
|
)
|
|
long_payload = _stream_payload(
|
|
omni_server,
|
|
"Please keep talking about the weather, the window, coffee, and purple flowers.",
|
|
max_new_tokens=80,
|
|
)
|
|
for _ in range(4):
|
|
resp = requests.post(url, json=long_payload, stream=True, timeout=DEFAULT_TIMEOUT_S)
|
|
resp.raise_for_status()
|
|
try:
|
|
chunk = next(c for c in resp.iter_content(chunk_size=None) if c)
|
|
assert chunk
|
|
finally:
|
|
resp.close()
|
|
|
|
t0 = time.perf_counter()
|
|
follow = requests.post(
|
|
url,
|
|
json={
|
|
"model": omni_server.model,
|
|
"input": text,
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": False,
|
|
"response_format": "pcm",
|
|
},
|
|
timeout=DEFAULT_TIMEOUT_S,
|
|
)
|
|
follow.raise_for_status()
|
|
elapsed = time.perf_counter() - t0
|
|
pcm_len = len(follow.content)
|
|
assert pcm_len % PCM16_BYTES_PER_FRAME == 0
|
|
print(f"[Gepard disconnect] followup_s={elapsed:.2f} pcm_bytes={pcm_len}")
|
|
_pcm16_diag("disconnect-followup-vs-prior", follow.content, baseline.audio_bytes)
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_mixed_stream_and_nonstream_isolated(omni_server, online_client, run_level: str) -> None:
|
|
url = _speech_url(omni_server)
|
|
stream_text = "He drinks coffee every morning."
|
|
nonstream_text = "My favorite color is purple."
|
|
|
|
def _stream():
|
|
return _capture_stream(url, _stream_payload(omni_server, stream_text), DEFAULT_TIMEOUT_S)
|
|
|
|
def _nonstream():
|
|
return online_client.send_audio_speech_request(
|
|
_base_config(omni_server, nonstream_text, seed=7, response_format="wav")
|
|
)[0]
|
|
|
|
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
stream_future = pool.submit(_stream)
|
|
nonstream_future = pool.submit(_nonstream)
|
|
streamed = stream_future.result()
|
|
nonstream = nonstream_future.result()
|
|
|
|
assert streamed.pcm
|
|
assert nonstream.success
|
|
assert streamed.pcm != nonstream.audio_bytes
|
|
if run_level in {"advanced_model", "full_model"}:
|
|
stream_tr = convert_audio_bytes_to_text(_pcm16le_to_wav(streamed.pcm), language="en").lower()
|
|
nonstream_tr = convert_audio_bytes_to_text(nonstream.audio_bytes, language="en").lower()
|
|
assert "coffee" in stream_tr, stream_tr
|
|
assert "purple" in nonstream_tr, nonstream_tr
|
|
_scan_logs_for_preemption(omni_server)
|
|
|
|
|
|
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
|
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
|
|
def test_max_new_tokens_caps_whole_frames(omni_server) -> None:
|
|
frames = 16
|
|
url = _speech_url(omni_server)
|
|
r = requests.post(
|
|
url,
|
|
json={
|
|
"model": omni_server.model,
|
|
"input": "Hello, this is Gepard speaking.",
|
|
"voice": "default",
|
|
"seed": 7,
|
|
"stream": False,
|
|
"response_format": "pcm",
|
|
"max_new_tokens": frames,
|
|
},
|
|
timeout=DEFAULT_TIMEOUT_S,
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
pcm_len = len(r.content)
|
|
assert pcm_len % PCM16_BYTES_PER_FRAME == 0
|
|
assert pcm_len == frames * PCM16_BYTES_PER_FRAME, f"got {pcm_len} bytes, want {frames} frames"
|
|
duration = pcm_len / 2 / SAMPLE_RATE
|
|
assert duration < 1.0, f"budget cap should stop near 16 frames, got {duration:.2f}s"
|
|
|
|
over = requests.post(
|
|
url,
|
|
json={
|
|
"model": omni_server.model,
|
|
"input": "Hello, this is Gepard speaking.",
|
|
"voice": "default",
|
|
"max_new_tokens": 4097,
|
|
},
|
|
timeout=DEFAULT_TIMEOUT_S,
|
|
)
|
|
assert over.status_code == 400
|
|
assert "max_new_tokens" in over.text
|