Skip to content

Commit 698ea14

Browse files
committed
Merge remote-tracking branch 'origin/main' into dev
2 parents b266f35 + cd91110 commit 698ea14

41 files changed

Lines changed: 756 additions & 10 deletions

File tree

Some content is hidden

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

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ add_library(engine_runtime STATIC
147147
src/framework/core/backend.cpp
148148
src/framework/core/deferred_tensor_writer.cpp
149149
src/framework/core/execution_context.cpp
150+
src/framework/core/host_memory.cpp
150151
src/framework/runtime/registry.cpp
151152
src/framework/runtime/model.cpp
152153
src/framework/runtime/session.cpp

app/cli/main.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include "engine/framework/audio/chunking.h"
1111
#include "engine/framework/audio/conversion.h"
1212
#include "engine/framework/debug/trace.h"
13+
#include "engine/framework/io/json.h"
1314
#include "engine/framework/runtime/registry.h"
15+
#include "engine/framework/runtime/session.h"
1416

1517
#include <algorithm>
1618
#include <cmath>
@@ -141,7 +143,7 @@ void print_task_list_help() {
141143
<< " --mode streaming uses the selected model's default streaming policy\n"
142144
<< " Utility:\n"
143145
<< " --inspect\n"
144-
<< " --list-loaders\n"
146+
<< " --list-loaders [--json]\n"
145147
<< "\n"
146148
<< " Tasks:\n"
147149
<< " vad voice activity detection\n"
@@ -564,10 +566,67 @@ int audiocpp_cli_main(int argc, char ** argv) {
564566
return 0;
565567
}
566568
if (has_arg(argc, argv, "--list-loaders")) {
567-
const auto families = registry.families();
568-
std::cout << "registered_loaders=" << registry.size() << "\n";
569-
for (const auto & family : families) {
570-
std::cout << family << "\n";
569+
const auto advertisements = registry.advertise_loaders();
570+
if (has_arg(argc, argv, "--json")) {
571+
engine::io::json::Value::Object loaders_object;
572+
for (const auto & row : advertisements) {
573+
engine::io::json::Value::Object tasks_object;
574+
for (const auto & task_cap : row.capabilities.supported_tasks) {
575+
engine::io::json::Value::Array modes;
576+
for (const auto mode : task_cap.modes) {
577+
modes.push_back(engine::io::json::Value::make_string(engine::runtime::to_string(mode)));
578+
}
579+
tasks_object.emplace(
580+
engine::runtime::to_string(task_cap.task),
581+
engine::io::json::Value::make_array(std::move(modes)));
582+
}
583+
engine::io::json::Value::Array endpoints;
584+
for (const auto & endpoint : row.api_endpoints) {
585+
endpoints.push_back(engine::io::json::Value::make_string(endpoint));
586+
}
587+
engine::io::json::Value::Object loader_object;
588+
loader_object.emplace("tasks", engine::io::json::Value::make_object(std::move(tasks_object)));
589+
loader_object.emplace(
590+
"instructions_policy",
591+
engine::io::json::Value::make_string(row.instructions_policy));
592+
loader_object.emplace(
593+
"api_endpoints",
594+
engine::io::json::Value::make_array(std::move(endpoints)));
595+
loaders_object.emplace(
596+
row.family,
597+
engine::io::json::Value::make_object(std::move(loader_object)));
598+
}
599+
engine::io::json::Value::Object root;
600+
root.emplace("schema_version", engine::io::json::Value::make_number(1));
601+
root.emplace("loaders", engine::io::json::Value::make_object(std::move(loaders_object)));
602+
std::cout << engine::io::json::stringify(engine::io::json::Value::make_object(std::move(root)))
603+
<< "\n";
604+
} else {
605+
std::cout << "registered_loaders=" << advertisements.size() << "\n";
606+
for (const auto & row : advertisements) {
607+
std::cout << row.family;
608+
if (!row.capabilities.supported_tasks.empty()) {
609+
std::cout << ":";
610+
for (size_t i = 0; i < row.capabilities.supported_tasks.size(); ++i) {
611+
const auto & task_cap = row.capabilities.supported_tasks[i];
612+
if (i > 0) {
613+
std::cout << ",";
614+
}
615+
std::cout << " " << engine::runtime::to_string(task_cap.task);
616+
if (!task_cap.modes.empty()) {
617+
std::cout << " (";
618+
for (size_t m = 0; m < task_cap.modes.size(); ++m) {
619+
if (m > 0) {
620+
std::cout << "|";
621+
}
622+
std::cout << engine::runtime::to_string(task_cap.modes[m]);
623+
}
624+
std::cout << ")";
625+
}
626+
}
627+
}
628+
std::cout << "\n";
629+
}
571630
}
572631
return 0;
573632
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
5+
namespace engine::core {
6+
7+
// Physical memory the host still has free, or 0 when it cannot be determined
8+
// (no portable query on this platform). Callers must treat 0 as "unknown" and
9+
// fall back to whatever they would have done anyway, never as "no memory".
10+
size_t available_host_memory_bytes();
11+
12+
} // namespace engine::core

include/engine/framework/runtime/model.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ class ILoadedVoiceModel {
105105
const SessionOptions & options) const = 0;
106106
};
107107

108+
struct LoaderAdvertisement {
109+
std::string family;
110+
CapabilitySet capabilities;
111+
std::string instructions_policy;
112+
std::vector<std::string> api_endpoints;
113+
};
114+
115+
/** Map advertised capabilities to the HTTP surfaces they normally use. */
116+
std::vector<std::string> default_api_endpoints_for_capabilities(const CapabilitySet & capabilities);
117+
108118
class IVoiceModelLoader {
109119
public:
110120
virtual ~IVoiceModelLoader() = default;
@@ -113,6 +123,15 @@ class IVoiceModelLoader {
113123
virtual bool can_load(const ModelLoadRequest & request) const = 0;
114124
virtual ModelInspection inspect(const ModelLoadRequest & request) const = 0;
115125
virtual std::unique_ptr<ILoadedVoiceModel> load(const ModelLoadRequest & request) const = 0;
126+
127+
/**
128+
* Path-free loader catalog for ``--list-loaders --json``.
129+
* Override ``advertised_capabilities`` (and policy when non-default) on each loader.
130+
*/
131+
virtual CapabilitySet advertised_capabilities() const;
132+
virtual std::string advertised_instructions_policy() const;
133+
virtual std::vector<std::string> advertised_api_endpoints() const;
134+
LoaderAdvertisement advertise() const;
116135
};
117136

118137
} // namespace engine::runtime

include/engine/framework/runtime/registry.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class ModelRegistry {
2222
size_t size() const noexcept;
2323
std::vector<std::string> families() const;
2424
bool supports_family(const std::string & family) const noexcept;
25+
/** Path-free loader catalog for ``--list-loaders --json``. */
26+
std::vector<LoaderAdvertisement> advertise_loaders() const;
2527
ModelInspection inspect(const ModelLoadRequest & request) const;
2628
ModelInspection inspect(const std::filesystem::path & model_path) const;
2729
std::unique_ptr<ILoadedVoiceModel> load(const ModelLoadRequest & request) const;

include/engine/models/qwen3_tts/session.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class Qwen3TTSSession final
6363
size_t speech_decoder_graph_arena_bytes_ = 32ull * 1024ull * 1024ull;
6464
size_t speaker_encoder_graph_arena_bytes_ = 32ull * 1024ull * 1024ull;
6565
// No-alloc GGML context capacities for reusable constant tensor descriptors.
66+
// Generous on purpose; ConstantTensorCache fits them to the host when it has
67+
// to, so a small machine is not asked to reserve what it does not have.
6668
size_t talker_constant_context_bytes_ = 4ull * 1024ull * 1024ull * 1024ull;
6769
size_t code_predictor_constant_context_bytes_ = 1536ull * 1024ull * 1024ull;
6870
size_t speech_decoder_constant_context_bytes_ = 1536ull * 1024ull * 1024ull;

src/framework/core/host_memory.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#include "engine/framework/core/host_memory.h"
2+
3+
#if defined(_WIN32)
4+
#include <windows.h>
5+
#elif defined(__linux__)
6+
#include <cstdio>
7+
#include <cstring>
8+
#elif defined(__unix__) || defined(__APPLE__)
9+
#include <unistd.h>
10+
#endif
11+
12+
namespace engine::core {
13+
14+
#if defined(__linux__)
15+
namespace {
16+
17+
// MemAvailable: what the kernel estimates a new allocation can get without
18+
// swapping, counting reclaimable page cache. sysconf(_SC_AVPHYS_PAGES) reports
19+
// MemFree instead, which sits near zero on any busy machine because spare RAM
20+
// is used for cache -- reading it as memory pressure would be wrong.
21+
size_t mem_available_bytes() {
22+
std::FILE * meminfo = std::fopen("/proc/meminfo", "r");
23+
if (meminfo == nullptr) {
24+
return 0;
25+
}
26+
char line[256];
27+
size_t bytes = 0;
28+
while (std::fgets(line, sizeof(line), meminfo) != nullptr) {
29+
unsigned long long kib = 0;
30+
if (std::sscanf(line, "MemAvailable: %llu kB", &kib) == 1) {
31+
bytes = static_cast<size_t>(kib) * 1024ull;
32+
break;
33+
}
34+
}
35+
std::fclose(meminfo);
36+
return bytes;
37+
}
38+
39+
} // namespace
40+
#endif
41+
42+
size_t available_host_memory_bytes() {
43+
#if defined(_WIN32)
44+
MEMORYSTATUSEX status{};
45+
status.dwLength = sizeof(status);
46+
if (GlobalMemoryStatusEx(&status)) {
47+
return static_cast<size_t>(status.ullAvailPhys);
48+
}
49+
#elif defined(__linux__)
50+
if (const size_t bytes = mem_available_bytes(); bytes > 0) {
51+
return bytes;
52+
}
53+
#elif defined(_SC_AVPHYS_PAGES) && defined(_SC_PAGE_SIZE)
54+
const long pages = sysconf(_SC_AVPHYS_PAGES);
55+
const long page_size = sysconf(_SC_PAGE_SIZE);
56+
if (pages > 0 && page_size > 0) {
57+
return static_cast<size_t>(pages) * static_cast<size_t>(page_size);
58+
}
59+
#endif
60+
// macOS has no _SC_AVPHYS_PAGES, and any query can fail: report unknown.
61+
return 0;
62+
}
63+
64+
} // namespace engine::core

src/framework/runtime/model.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,65 @@ const NamedAsset * select_named_asset(
9696
return &assets.front();
9797
}
9898

99+
std::vector<std::string> default_api_endpoints_for_capabilities(const CapabilitySet & capabilities) {
100+
bool has_asr = false;
101+
bool has_speech = false;
102+
bool has_other = false;
103+
for (const auto & task : capabilities.supported_tasks) {
104+
switch (task.task) {
105+
case VoiceTaskKind::Asr:
106+
has_asr = true;
107+
break;
108+
case VoiceTaskKind::Tts:
109+
case VoiceTaskKind::VoiceCloning:
110+
case VoiceTaskKind::VoiceDesign:
111+
has_speech = true;
112+
break;
113+
default:
114+
has_other = true;
115+
break;
116+
}
117+
}
118+
if (has_asr && !has_speech && !has_other) {
119+
return {"/v1/audio/transcriptions"};
120+
}
121+
if (has_speech && !has_other) {
122+
return {"/v1/audio/speech"};
123+
}
124+
if (has_speech && has_other) {
125+
return {"/v1/tasks/run", "/v1/audio/speech"};
126+
}
127+
return {"/v1/tasks/run"};
128+
}
129+
130+
CapabilitySet IVoiceModelLoader::advertised_capabilities() const {
131+
// Path-free catalog entry. Each loader overrides with the same task/mode
132+
// contract it exposes via inspect/load (without requiring a model path).
133+
return {};
134+
}
135+
136+
std::string IVoiceModelLoader::advertised_instructions_policy() const {
137+
// Generic default from advertised tasks. Loaders with a different contract override.
138+
for (const auto & task : advertised_capabilities().supported_tasks) {
139+
if (task.task == VoiceTaskKind::Tts || task.task == VoiceTaskKind::VoiceDesign
140+
|| task.task == VoiceTaskKind::VoiceCloning) {
141+
return "openai_instruct";
142+
}
143+
}
144+
return "none";
145+
}
146+
147+
std::vector<std::string> IVoiceModelLoader::advertised_api_endpoints() const {
148+
return default_api_endpoints_for_capabilities(advertised_capabilities());
149+
}
150+
151+
LoaderAdvertisement IVoiceModelLoader::advertise() const {
152+
LoaderAdvertisement row;
153+
row.family = family();
154+
row.capabilities = advertised_capabilities();
155+
row.instructions_policy = advertised_instructions_policy();
156+
row.api_endpoints = advertised_api_endpoints();
157+
return row;
158+
}
159+
99160
} // namespace engine::runtime

src/framework/runtime/registry.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ bool ModelRegistry::supports_family(const std::string & family) const noexcept {
120120
return false;
121121
}
122122

123+
std::vector<LoaderAdvertisement> ModelRegistry::advertise_loaders() const {
124+
std::vector<LoaderAdvertisement> out;
125+
out.reserve(loaders_.size());
126+
for (const auto & loader : loaders_) {
127+
if (loader == nullptr) {
128+
continue;
129+
}
130+
out.push_back(loader->advertise());
131+
}
132+
std::sort(out.begin(), out.end(), [](const LoaderAdvertisement & a, const LoaderAdvertisement & b) {
133+
return a.family < b.family;
134+
});
135+
return out;
136+
}
137+
123138
ModelInspection ModelRegistry::inspect(const ModelLoadRequest & request) const {
124139
engine::assets::ScopedModelPackageSpecOverride spec_override(request.model_spec_override, request.model_path);
125140
validate_request(request);

src/models/ace_step/loader.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ class AceStepLoader final : public runtime::IVoiceModelLoader {
9090
return "ace_step";
9191
}
9292

93+
runtime::CapabilitySet advertised_capabilities() const override {
94+
runtime::CapabilitySet out;
95+
out.supported_tasks = {
96+
{runtime::VoiceTaskKind::AudioGeneration, {runtime::RunMode::Offline}},
97+
};
98+
return out;
99+
}
100+
93101
bool can_load(const runtime::ModelLoadRequest & request) const override {
94102
try {
95103
const auto root = resolve_model_root(request.model_path);

0 commit comments

Comments
 (0)