Skip to content

Commit 27bc1ca

Browse files
Merge branch 'main' into router/2385-collection.router_recipe_dispatch
Resolve conflicts from #2519 (parser/store) and #2548 (classifier services) landing on main after this branch forked: - routing_classifier_services.{h,cpp}: keep main's reviewed parse/validation body (validate_score_range, try_extract_chat_text) and re-add this branch's build_route_context + collect_text_from_content/content_has_image helpers, which #2385 dispatch needs. - routing_policy_store.{h,cpp}, test_routing_policy_store.cpp, test_routing_classifier_services.cpp: take main (strict superset — C++20 TODO, case-insensitive .json, added tests). - model_manager.cpp USER_DEFINED_MODEL_PROPS: keep "version" (router policies carry a required root version) on top of main's "routing". - server.cpp download-skip: is_model_collection_recipe (omni OR router) — a collection.router has no checkpoint and must skip the generic HF download; main's is_omni_collection_recipe would wrongly route it through HF. lemond builds; routing parser/engine/deterministic/classifier-services/ collection-validation unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents 9707009 + 75e9af2 commit 27bc1ca

41 files changed

Lines changed: 1746 additions & 98 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/cpp_server_build_test_release.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,16 @@ jobs:
11481148
extra_args: ""
11491149
backends: "cpu rocm vulkan"
11501150
runner: [Windows, rocm, vulkan, lemon-prod]
1151+
- name: audio-gen-thinksound
1152+
script: server_audio_generation.py
1153+
extra_args: "--wrapped-server thinksound"
1154+
backends: "vulkan rocm"
1155+
runner: [Windows, rocm, vulkan, lemon-prod]
1156+
- name: audio-gen-acestep
1157+
script: server_audio_generation.py
1158+
extra_args: "--wrapped-server acestep"
1159+
backends: "vulkan rocm"
1160+
runner: [Windows, rocm, vulkan, lemon-prod]
11511161
- name: text-to-speech
11521162
script: server_tts.py
11531163
extra_args: ""
@@ -1431,6 +1441,16 @@ jobs:
14311441
extra_args: ""
14321442
backends: "cpu rocm vulkan"
14331443
runner: [Linux, vulkan, rocm, lemon-prod]
1444+
- name: audio-gen-thinksound
1445+
script: server_audio_generation.py
1446+
extra_args: "--wrapped-server thinksound"
1447+
backends: "vulkan rocm"
1448+
runner: [Linux, vulkan, rocm, lemon-prod]
1449+
- name: audio-gen-acestep
1450+
script: server_audio_generation.py
1451+
extra_args: "--wrapped-server acestep"
1452+
backends: "vulkan rocm"
1453+
runner: [Linux, vulkan, rocm, lemon-prod]
14341454
- name: whisper
14351455
script: server_whisper.py
14361456
extra_args: "--wrapped-server whispercpp"

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ set(LEMON_BACKENDS
6464
"ryzenai-llm|ryzenai"
6565
"vllm|vllm"
6666
"cloud|cloud"
67+
"thinksound|thinksound"
68+
"acestep|acestep"
6769
)
6870

6971
# ============================================================

docs/api/lemonade.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ We have designed a set of Lemonade-specific endpoints to enable client applicati
1616
| `POST` | [`/v1/delete`](#post-v1delete) | Delete a model |
1717
| `POST` | [`/v1/load`](#post-v1load) | Load a model |
1818
| `POST` | [`/v1/unload`](#post-v1unload) | Unload a model |
19+
| `POST` | [`/v1/audio/generations`](#post-v1audiogenerations) | Generate audio (music or sound effects) from a text prompt |
1920
| `GET` | [`/v1/models/{id}/files`](#get-v1modelsidfiles) | List resolved local file metadata for one model |
2021
| `GET` | [`/v1/health`](#get-v1health) | Check server status, such as models loaded |
2122
| `GET` | [`/v1/stats`](#get-v1stats) | Performance statistics from the last request |
@@ -687,6 +688,45 @@ In case of an error, the status will be `error` and the message will contain the
687688

688689

689690

691+
## `POST /v1/audio/generations`
692+
<sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
693+
694+
Audio Generation API. You provide a text prompt and receive a generated audio clip. The loaded model decides the kind of audio: music with ACE-Step models (e.g. `ACE-Step-Music`), sound effects with ThinkSound models (e.g. `ThinkSound-SFX`).
695+
696+
This endpoint is not part of the OpenAI API (OpenAI's audio endpoints cover speech and transcription only), so it is a Lemonade-specific extension.
697+
698+
> **Performance:** generation runs on the GPU (Vulkan, ROCm, or CUDA) and takes from seconds (short sound effects) to minutes (full-length music) depending on duration and hardware.
699+
700+
### Parameters
701+
702+
| Parameter | Required | Description |
703+
|-----------|----------|-------------|
704+
| `model` | Yes | The audio-generation model to use (e.g., `ThinkSound-SFX`, `ACE-Step-Music`). |
705+
| `prompt` | Yes | Text description of the music or sound effect to generate. |
706+
| `duration` | No | Length of the clip in seconds. Defaults to the backend's native default. |
707+
| `steps` | No | Number of inference steps. Lower is faster, higher can improve quality. |
708+
| `cfg` | No | Classifier-free guidance strength (ThinkSound only). |
709+
| `seed` | No | Random seed for reproducibility. |
710+
| `response_format` | No | Output encoding. Only formats the backend natively produces are accepted (currently `wav`); other values are rejected with `400 Bad Request`. Default: `wav`. |
711+
712+
### Response
713+
714+
On success the raw audio bytes are returned with the matching content type (`audio/wav`). On failure the response is JSON with an `error` object: `400` for invalid requests, `404` for unknown models, `500` when the backend reports an error, and `502` when the backend produces no output.
715+
716+
### Example request
717+
718+
```bash
719+
curl -X POST http://localhost:13305/v1/audio/generations \
720+
-H "Content-Type: application/json" \
721+
-d '{
722+
"model": "ThinkSound-SFX",
723+
"prompt": "glass shattering on a stone floor",
724+
"duration": 5,
725+
"seed": 42
726+
}' \
727+
--output clip.wav
728+
```
729+
690730
## `GET /v1/health`
691731
<sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
692732

docs/assets/models.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ const RAW_BASE = 'https://raw.githubusercontent.com/lemonade-sdk/lemonade';
44

55
/* BEGIN GENERATED: models-js-recipes */
66
const RECIPE_PRIORITY = [
7+
'acestep',
78
'flm',
89
'kokoro',
910
'llamacpp',
1011
'moonshine',
1112
'ryzenai-llm',
1213
'sd-cpp',
14+
'thinksound',
1315
'vllm',
1416
'whispercpp'
1517
];
@@ -20,7 +22,9 @@ const RECIPE_DISPLAY_NAMES = {
2022
'sd-cpp': 'stable-diffusion.cpp',
2123
flm: 'FastFlowLM NPU',
2224
'ryzenai-llm': 'Ryzen AI SW NPU',
23-
vllm: 'vLLM ROCm (experimental)'
25+
vllm: 'vLLM ROCm (experimental)',
26+
thinksound: 'ThinkSound',
27+
acestep: 'ACE-Step'
2428
};
2529
/* END GENERATED: models-js-recipes */
2630

docs/dev/backends-reference.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ the generator instead. Prose outside the markers is preserved. -->
99
<!-- BEGIN GENERATED: backends-overview -->
1010
| Recipe | Name | Selectable backend | Uses ctx_size | Backends |
1111
|--------|------|--------------------|---------------|----------|
12+
| `acestep` | ACE-Step | yes | no | cuda, rocm, vulkan |
1213
| `flm` | FastFlowLM NPU | no | yes | npu |
1314
| `kokoro` | Kokoro | no | no | cpu, metal |
1415
| `llamacpp` | Llama.cpp GPU | yes | yes | cpu, cuda, metal, rocm, system, vulkan |
1516
| `moonshine` | Moonshine | no | no | cpu |
1617
| `ryzenai-llm` | Ryzen AI LLM | no | yes | npu |
1718
| `sd-cpp` | StableDiffusion.cpp | yes | no | cpu, cuda, metal, rocm, vulkan |
19+
| `thinksound` | ThinkSound | yes | no | cuda, rocm, vulkan |
1820
| `vllm` | vLLM ROCm (experimental) | yes | yes | rocm |
1921
| `whispercpp` | Whisper.cpp | yes | no | cpu, metal, npu, rocm, vulkan |
2022
<!-- END GENERATED: backends-overview -->
@@ -24,6 +26,9 @@ the generator instead. Prose outside the markers is preserved. -->
2426
<!-- BEGIN GENERATED: backends-matrix -->
2527
| Recipe | Backend | OS | Device families |
2628
|--------|---------|----|-----------------|
29+
| `acestep` | rocm | linux, windows | amd_gpu |
30+
| `acestep` | cuda | linux, windows | nvidia_gpu |
31+
| `acestep` | vulkan | linux, windows | amd_gpu; cpu (x86_64); nvidia_gpu |
2732
| `flm` | npu | linux, windows | amd_npu (XDNA2) |
2833
| `kokoro` | cpu | linux, windows | cpu (x86_64) |
2934
| `kokoro` | metal | macos | metal |
@@ -42,6 +47,9 @@ the generator instead. Prose outside the markers is preserved. -->
4247
| `sd-cpp` | vulkan | linux, windows | amd_gpu; cpu (x86_64); nvidia_gpu |
4348
| `sd-cpp` | cpu | linux, windows | cpu (x86_64) |
4449
| `sd-cpp` | metal | macos | metal |
50+
| `thinksound` | rocm | linux, windows | amd_gpu |
51+
| `thinksound` | cuda | linux, windows | nvidia_gpu |
52+
| `thinksound` | vulkan | linux, windows | amd_gpu; cpu (x86_64); nvidia_gpu |
4553
| `vllm` | rocm | linux | amd_gpu (gfx110X, gfx1150, gfx1151, gfx120X) |
4654
| `whispercpp` | npu | windows | amd_npu (XDNA2) |
4755
| `whispercpp` | rocm | linux, windows | amd_gpu (gfx110X, gfx1150, gfx1151, gfx120X) |
@@ -53,6 +61,12 @@ the generator instead. Prose outside the markers is preserved. -->
5361
## Recipe options
5462

5563
<!-- BEGIN GENERATED: backend-options -->
64+
#### `acestep` — ACE-Step
65+
66+
| Option | CLI flag | Type | Default | Description |
67+
|--------|----------|------|---------|-------------|
68+
| `acestep_backend` | `--acestep` | BACKEND | "" | ACE-Step backend to use |
69+
5670
#### `llamacpp` — Llama.cpp GPU
5771

5872
| Option | CLI flag | Type | Default | Description |
@@ -81,6 +95,12 @@ the generator instead. Prose outside the markers is preserved. -->
8195
| `sampling_method` || ARGS | "" | Sampling method |
8296
| `flow_shift` || SIZE | 0.0 | Flow shift |
8397

98+
#### `thinksound` — ThinkSound
99+
100+
| Option | CLI flag | Type | Default | Description |
101+
|--------|----------|------|---------|-------------|
102+
| `thinksound_backend` | `--thinksound` | BACKEND | "" | ThinkSound backend to use |
103+
84104
#### `vllm` — vLLM ROCm (experimental)
85105

86106
| Option | CLI flag | Type | Default | Description |
@@ -100,6 +120,12 @@ the generator instead. Prose outside the markers is preserved. -->
100120
## Models
101121

102122
<!-- BEGIN GENERATED: backend-models -->
123+
#### `acestep` — ACE-Step (1 models)
124+
125+
| Model | Size (GB) | Labels |
126+
|-------|-----------|--------|
127+
| `ACE-Step-Music` | 10.5 | audio-generation |
128+
103129
#### `collection.omni` — collection.omni (5 models)
104130

105131
| Model | Size (GB) | Labels |
@@ -307,6 +333,12 @@ the generator instead. Prose outside the markers is preserved. -->
307333
| `SDXL-Turbo` | 6.94 | image |
308334
| `Z-Image-Turbo` | 20.7 | image |
309335

336+
#### `thinksound` — ThinkSound (1 models)
337+
338+
| Model | Size (GB) | Labels |
339+
|-------|-----------|--------|
340+
| `ThinkSound-SFX` | 6.4 | audio-generation |
341+
310342
#### `vllm` — vLLM ROCm (experimental) (7 models)
311343

312344
| Model | Size (GB) | Labels |

docs/dev/contribute.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,36 @@ Each contribution needs to:
4141
- Reviewers: make sure to check the code *before* allowing CI to run!
4242
3. Sustain the overall code quality and standards of the rest of the repo.
4343

44+
### Making Pull Requests Easy to Review
45+
46+
Maintainers try to review contributions quickly, but review time is limited. The easiest pull requests to review are small, focused, and easy to verify.
47+
48+
Before opening a PR, please make sure that:
49+
50+
1. The PR solves one clear problem.
51+
2. The changes are limited to what is necessary.
52+
3. There are no unrelated local changes included.
53+
4. The PR description explains what changed and why.
54+
5. The PR describes how the change was tested, including any platforms that could not be tested locally.
55+
6. Larger refactoring is separated from bug fixes or feature changes.
56+
57+
Some areas of the codebase are more sensitive than they may appear. Small changes can sometimes have large side effects, especially in recently reworked, complex, or platform-specific code. In these cases, reviewers may ask you to reduce the scope, add tests, or follow a specific implementation approach. If you need help testing on a platform you do not have access to, please mention it in the PR and ask for support in the Lemonade Discord.
58+
59+
The fastest way to build trust as a new contributor is to submit small, clear, well-tested PRs that are easy to review and easy to verify.
60+
4461
### AI Policy
4562

46-
Contributors are encouraged to use AI to code their project. However, please review your AI's code yourself before asking another human to review it.
63+
Contributors are welcome to use AI tools while working on Lemonade. However, contributors remain fully responsible for the code they submit.
64+
65+
If you use AI-assisted coding, please make sure that you:
66+
67+
1. Understand the generated code before submitting it.
68+
2. Review the full diff yourself.
69+
3. Remove unrelated or unnecessary changes.
70+
4. Test the result locally where possible.
71+
5. Keep the PR especially small and focused.
72+
73+
AI can make it easy to generate large changes, but large or unfocused changes are harder for maintainers to review and are less likely to be accepted quickly.
4774

4875
Reviewers are expected to use tools like Claude Code's `/review` to save time by analyzing code quality and edge cases. If AI tools generate PR comments, please clearly identify which comments are AI-generated and which are authored by you.
4976

docs/guide/cli.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,18 @@ The following options are available depending on the recipe being used:
376376
| `--ctx-size SIZE` | Context size for the model | auto |
377377
| `--vllm BACKEND` | vLLM backend to use | Auto-detected |
378378
| `--vllm-args ARGS` | Custom arguments to pass to vllm-server | `""` |
379+
380+
#### ThinkSound (`thinksound` recipe)
381+
382+
| Option | Description | Default |
383+
|--------|-------------|---------|
384+
| `--thinksound BACKEND` | ThinkSound backend to use | Auto-detected |
385+
386+
#### ACE-Step (`acestep` recipe)
387+
388+
| Option | Description | Default |
389+
|--------|-------------|---------|
390+
| `--acestep BACKEND` | ACE-Step backend to use | Auto-detected |
379391
<!-- END GENERATED: cli-recipe-options -->
380392
**Notes:**
381393
- Unspecified options will use the backend's default values

docs/guide/configuration/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ Values set in the user's `config.json` always take precedence over these seeded
3535
```json
3636
{
3737
"_generated": "GENERATED by docs/tools/gen_backend_boilerplate.py -- do not hand-edit per-recipe sections (they come from each backend's descriptor config_defaults()). Global keys are hand-maintained in this file. Regenerate and verify with that script; CI --check fails on drift.",
38+
"acestep": {
39+
"backend": "auto",
40+
"cuda_bin": "builtin",
41+
"rocm_bin": "builtin",
42+
"vulkan_bin": "builtin"
43+
},
3844
"cloud_providers": [],
3945
"config_version": 2,
4046
"ctx_size": -1,
@@ -112,6 +118,12 @@ Values set in the user's `config.json` always take precedence over these seeded
112118
"send_batch_size": 100
113119
}
114120
},
121+
"thinksound": {
122+
"backend": "auto",
123+
"cuda_bin": "builtin",
124+
"rocm_bin": "builtin",
125+
"vulkan_bin": "builtin"
126+
},
115127
"vllm": {
116128
"args": "",
117129
"backend": "auto"

docs/guide/configuration/custom-models.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Supported registration flags:
7171
| Flag | Description |
7272
|------|-------------|
7373
| `--checkpoint TYPE CHECKPOINT` | Add a checkpoint entry. Repeat for multi-file models such as `main` + `mmproj` or `main` + `vae`. |
74-
| `--recipe RECIPE` | Recipe to associate with the new `user.*` model. Common values: <!-- BEGIN GENERATED: recipe-values -->`llamacpp`, `whispercpp`, `moonshine`, `kokoro`, `sd-cpp`, `flm`, `ryzenai-llm`, `vllm`, `collection.omni`<!-- END GENERATED: recipe-values -->. |
74+
| `--recipe RECIPE` | Recipe to associate with the new `user.*` model. Common values: <!-- BEGIN GENERATED: recipe-values -->`llamacpp`, `whispercpp`, `moonshine`, `kokoro`, `sd-cpp`, `flm`, `ryzenai-llm`, `vllm`, `thinksound`, `acestep`, `collection.omni`<!-- END GENERATED: recipe-values -->. |
7575
| `--label LABEL` | Add a label to the new model. Repeatable. Valid labels include `coding`, `embeddings`, `hot`, `mtp`, `reasoning`, `reranking`, `tool-calling`, `vision`. |
7676
| `--components MODEL [MODEL ...]` | Components for an omni collection (see below). Use with `--recipe collection.omni`. |
7777

src/app/src/renderer/ChatWindow.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import RerankingPanel from './components/panels/RerankingPanel';
1313
import TranscriptionPanel from './components/panels/TranscriptionPanel';
1414
import ImageGenerationPanel from './components/panels/ImageGenerationPanel';
1515
import TTSPanel from './components/panels/TTSPanel';
16+
import AudioGenerationPanel from './components/panels/AudioGenerationPanel';
1617
import LLMChatPanel from './components/panels/LLMChatPanel';
1718
import { RefreshIcon } from './components/Icons';
1819
import { isCollectionModel, getCollectionComponents } from './utils/collectionModels';
@@ -42,7 +43,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ isVisible, width }) => {
4243
const [addModelInitialValues, setAddModelInitialValues] = useState<AddModelInitialValues | undefined>(undefined);
4344
const addModelFromJSONRef = useRef<HTMLInputElement>(null);
4445

45-
type ModelType = 'llm' | 'embedding' | 'reranking' | 'transcription' | 'image' | 'tts';
46+
type ModelType = 'llm' | 'embedding' | 'reranking' | 'transcription' | 'image' | 'tts' | 'audio';
4647

4748
const modelType = useMemo((): ModelType => {
4849
if (!selectedModel) return 'llm';
@@ -60,6 +61,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ isVisible, width }) => {
6061
if (info.labels?.includes('transcription')) return 'transcription';
6162
if (info.labels?.includes('image')) return 'image';
6263
if (info.labels?.includes('tts')) return 'tts';
64+
if (info.labels?.includes('audio-generation')) return 'audio';
6365
return 'llm';
6466
}, [selectedModel, modelsData]);
6567

@@ -276,6 +278,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ isVisible, width }) => {
276278
: activeModelType === 'transcription' ? 'Lemonade Transcriber'
277279
: activeModelType === 'image' ? 'Lemonade Image Generator'
278280
: activeModelType === 'tts' ? 'Lemonade Text to Speech'
281+
: activeModelType === 'audio' ? 'Lemonade Audio Generator'
279282
: 'LLM Chat';
280283

281284
const sharedProps = {
@@ -313,6 +316,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({ isVisible, width }) => {
313316
{activeModelType === 'transcription' && <TranscriptionPanel key={resetKey} {...sharedProps} />}
314317
{activeModelType === 'image' && <ImageGenerationPanel key={resetKey} {...sharedProps} />}
315318
{activeModelType === 'tts' && <TTSPanel key={resetKey} {...sharedProps} />}
319+
{activeModelType === 'audio' && <AudioGenerationPanel key={resetKey} {...sharedProps} />}
316320
{activeModelType === 'llm' && (
317321
<LLMChatPanel
318322
key={resetKey}

0 commit comments

Comments
 (0)