Skip to content
Open
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
20 changes: 19 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ if(TRTMC_BUILD_SERVER)
PRIVATE ${PROJECT_SOURCE_DIR}/core/runtime/include
)
target_link_libraries(trtmc_server
PUBLIC trtmc_runtime
PUBLIC trtmc_runtime trtmc_c
PRIVATE
nlohmann_json::nlohmann_json
)
Expand Down Expand Up @@ -664,6 +664,24 @@ if(TRTMC_BUILD_TESTS)

add_dependencies(test_sdk_cli trtmc_api_test_text_family trtmc_api_test_stream_family)

if(TRTMC_BUILD_SERVER)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
add_executable(test_server_sdk_worker apps/server/tests/test_sdk_worker.cpp)
target_link_libraries(test_server_sdk_worker PRIVATE
trtmc_server trtmc_c nlohmann_json::nlohmann_json)
target_compile_options(test_server_sdk_worker PRIVATE -Wall -Wextra -Wpedantic -Werror)
add_dependencies(test_server_sdk_worker trtmc-server trtmc_api_test_backend
trtmc_api_test_family trtmc_api_test_text_family)
add_test(NAME server_sdk_worker
COMMAND test_server_sdk_worker "${_trtmc_api_test_runtime_root}")
add_test(NAME server_sdk_worker_process
COMMAND "${Python3_EXECUTABLE}"
"${PROJECT_SOURCE_DIR}/apps/server/tests/test_sdk_worker_process.py"
--binary "$<TARGET_FILE:trtmc-server>"
--runtime-root "${_trtmc_api_test_runtime_root}")
set_tests_properties(server_sdk_worker server_sdk_worker_process PROPERTIES LABELS "cpu;e2e")
endif()

add_executable(test_sdk_features_cli apps/cli/tests/test_sdk_features_cli.cpp)
target_link_libraries(test_sdk_features_cli PRIVATE trtmc_cli trtmc_c nlohmann_json::nlohmann_json)
target_compile_options(test_sdk_features_cli PRIVATE -Wall -Wextra -Wpedantic -Werror)
Expand Down
6 changes: 3 additions & 3 deletions apps/server/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

#include "server/native_worker.h"
#include "trtmc/runtime/family_loader.h"
#include "trtmc/core.hpp"

#include <array>
#include <cerrno>
Expand Down Expand Up @@ -63,8 +63,8 @@ int worker_main(int argc, char** argv) {
else
throw std::invalid_argument("unknown _serve-worker option: " + option);
}
auto task = trtmc::load_task(bundle, runtime_root, kv_cache_size, runtime_cache, cuda_graphs);
return trtmc::server::run_text_worker(*task, std::cin, std::cout);
const trtmc::LoadOptions options{runtime_root, kv_cache_size, runtime_cache, cuda_graphs};
return trtmc::server::run_bundle_worker(bundle, options, std::cin, std::cout);
}

std::filesystem::path executable_path() {
Expand Down
143 changes: 126 additions & 17 deletions apps/server/native_worker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

#include "server/native_worker.h"

#include "config.h"
#include "task_runtime.h"
#include "trtmc/control.hpp"
#include "trtmc/runtime/family_loader.h"
#include "trtmc/task.h"
#include "trtmc/text.hpp"

#include <algorithm>
#include <cmath>
Expand Down Expand Up @@ -130,17 +135,53 @@ Json request_id(const Json& request) {
return *id;
}

} // namespace
Config parse_sdk_config(const Json& request, const std::vector<ConfigField>& fields) {
Config result;
const auto found = request.find("config");
if (found == request.end())
return result;
if (!found->is_object())
throw ProtocolError("config must be an object");
for (auto entry = found->begin(); entry != found->end(); ++entry) {
const auto field = std::find_if(fields.begin(), fields.end(),
[&](const auto& item) { return item.name == entry.key(); });
if (field == fields.end())
throw ProtocolError("config." + entry.key() + " is unsupported");
if (field->kind == ConfigKind::String && !entry->is_string())
throw ProtocolError("config." + entry.key() + " must be a string");
try {
const auto value =
field->kind == ConfigKind::String ? entry->get<std::string>() : entry->dump();
result.add(entry.key(), app::parse_config_value(value, *field));
} catch (const std::invalid_argument& error) {
throw ProtocolError(error.what());
}
}
return result;
}

int run_text_worker(ITask& task, std::istream& input, std::ostream& output) {
auto* text = dynamic_cast<ITextGeneration*>(&task);
if (text == nullptr)
throw std::invalid_argument("bundle task does not implement text generation");
std::int64_t default_token_limit(const std::vector<ConfigField>& fields) {
for (const auto& field : fields) {
if (field.name == "max_new_tokens" && field.kind == ConfigKind::I64 &&
field.default_value) {
const auto value = field.default_value->get<std::int64_t>();
if (value > 0)
return value;
}
}
// Existing HTTP safety default for a family-derived limit. This does not
// insert a default into the Config passed to the Task.
return 128;
}

template <class Generate>
int run_protocol(std::int64_t default_tokens, Generate&& generate, std::istream& input,
std::ostream& output) {
if (!write_json(output, {{"event", "ready"},
{"protocol_version", 1},
{"capabilities", Json::array({ITextGeneration::kTask})},
{"default_max_new_tokens", text->default_max_new_tokens()}}))
// This names the private protocol operation, not a Task ABI.
{"capabilities", Json::array({"text_generation"})},
{"default_max_new_tokens", default_tokens}}))
return 2;

std::vector<char> buffer(kMaxLineBytes + 2U);
Expand Down Expand Up @@ -180,16 +221,7 @@ int run_text_worker(ITask& task, std::istream& input, std::ostream& output) {
shutdown = true;
} else if (operation == "generate") {
const auto prompt = string_field(request, "prompt", true);
const auto result =
text->generate(prompt, parse_config(request, text->default_max_new_tokens()));
response = {{"id", id},
{"ok", true},
{"result",
{{"text", result.text},
{"completion_tokens", result.token_ids.size()},
{"setup_ms", result.setup_ms},
{"prefill_ms", result.prefill_ms},
{"decode_ms", result.decode_ms}}}};
response = {{"id", id}, {"ok", true}, {"result", generate(prompt, request)}};
} else {
throw ProtocolError("unknown operation: " + operation);
}
Expand All @@ -214,4 +246,81 @@ int run_text_worker(ITask& task, std::istream& input, std::ostream& output) {
return input.bad() ? 2 : 0;
}

template <class Task, class Request>
int run_sdk_worker(const Task& task, Request&& make_request, std::istream& input,
std::ostream& output) {
const auto fields = task.config_fields();
return run_protocol(
default_token_limit(fields),
[&](const std::string& prompt, const Json& request) {
const auto config = parse_sdk_config(request, fields);
try {
const auto result = task.run(make_request(prompt), config);
return Json{{"text", std::string(result.text())},
{"completion_tokens", result.token_ids().size()},
{"setup_ms", result.setup_ms()},
{"prefill_ms", result.prefill_ms()},
{"decode_ms", result.decode_ms()}};
} catch (const Error& error) {
if (error.code() == TRTMC_INVALID_ARGUMENT ||
error.code() == TRTMC_INVALID_CONFIG) {
// Family errors may contain implementation details. Keep
// client mistakes recoverable without publishing those details.
throw ProtocolError("generation request is invalid for this model");
}
throw;
}
},
input, output);
}

} // namespace

int run_text_worker(ITask& task, std::istream& input, std::ostream& output) {
auto* text = dynamic_cast<ITextGeneration*>(&task);
if (text == nullptr)
throw std::invalid_argument("bundle task does not implement text generation");
return run_protocol(
text->default_max_new_tokens(),
[&](const std::string& prompt, const Json& request) {
const auto result =
text->generate(prompt, parse_config(request, text->default_max_new_tokens()));
return Json{{"text", result.text},
{"completion_tokens", result.token_ids.size()},
{"setup_ms", result.setup_ms},
{"prefill_ms", result.prefill_ms},
{"decode_ms", result.decode_ms}};
},
input, output);
}

int run_text_worker(const Model& model, std::istream& input, std::ostream& output) {
const auto primary = model.info().bundle_task;
if (primary == ConditionalTextGeneration::kTask)
return run_sdk_worker(
model.task<ConditionalTextGeneration>(),
[](const std::string& prompt) { return ConditionalTextGenerationRequest{prompt}; },
input, output);
if (primary == TextTranslation::kTask)
return run_sdk_worker(
model.task<TextTranslation>(),
[](const std::string& prompt) { return TextTranslationRequest{prompt}; }, input,
output);
return run_sdk_worker(
model.task<TextContinuation>(),
[](const std::string& prompt) { return TextContinuationRequest{prompt}; }, input, output);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

int run_bundle_worker(const std::string& bundle, const LoadOptions& options, std::istream& input,
std::ostream& output) {
const auto primary = Bundle::open(bundle).info().task;
if (app::uses_existing_task_runtime(primary)) {
auto task = load_task(bundle, options.runtime_root, options.kv_cache_size_bytes,
options.runtime_cache_path, options.cuda_graphs);
return run_text_worker(*task, input, output);
}
const auto model = Model::load(bundle, options);
return run_text_worker(model, input, output);
}

} // namespace trtmc::server
8 changes: 7 additions & 1 deletion apps/server/native_worker.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
#pragma once

#include <iosfwd>
#include <string>

namespace trtmc {
class ITask;
}
class Model;
struct LoadOptions;
} // namespace trtmc

namespace trtmc::server {

// Runs the private serialized protocol used by the Python serving control plane.
int run_text_worker(ITask& task, std::istream& input, std::ostream& output);
int run_text_worker(const Model& model, std::istream& input, std::ostream& output);
int run_bundle_worker(const std::string& bundle, const LoadOptions& options, std::istream& input,
std::ostream& output);

} // namespace trtmc::server
Loading
Loading