Skip to content
Merged
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
26 changes: 17 additions & 9 deletions server/src/common/daemon_loop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ static bool looks_like_path(const std::string & s) {
return s.find('/') != std::string::npos;
}

static void emit_generate_error(const GenerateResult & result) {
std::fprintf(stderr, "[daemon] generation failed: code=%s",
result.error_code().data());
if (!result.error_detail().empty()) {
std::fprintf(stderr, " detail=%s", result.error_detail().data());
}
std::fprintf(stderr, "\n");
std::printf("err %s\n", result.error_code().data());
std::fflush(stdout);
}

// Read a prompt file: raw int32 stream (file size implies token count).
static std::vector<int32_t> read_uncounted_i32(const std::string & path) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
Expand Down Expand Up @@ -318,9 +329,8 @@ int run_daemon(ModelBackend & backend, const DaemonLoopArgs & args) {
req.stream = false;

auto result = backend.generate(req, io);
if (!result.ok) {
std::printf("err %s\n", result.error.c_str());
std::fflush(stdout);
if (!result.ok()) {
emit_generate_error(result);
continue;
}
if (!write_counted_i32(out_path, result.tokens)) {
Expand Down Expand Up @@ -375,9 +385,8 @@ int run_daemon(ModelBackend & backend, const DaemonLoopArgs & args) {
req.snap_slot = snap_slot;

auto result = backend.restore_and_generate(slot, req, io);
if (!result.ok) {
std::printf("err %s\n", result.error.c_str());
std::fflush(stdout);
if (!result.ok()) {
emit_generate_error(result);
io.emit(-1);
continue;
}
Expand Down Expand Up @@ -424,10 +433,9 @@ int run_daemon(ModelBackend & backend, const DaemonLoopArgs & args) {
req.snap_slot = snap_slot;

auto result = backend.generate(req, io);
if (!result.ok) {
if (!result.ok()) {
io.emit(-1);
std::printf("err %s\n", result.error.c_str());
std::fflush(stdout);
emit_generate_error(result);
continue;
}
std::printf("ok N=%d gen=%zu prefill_s=%.3f decode_s=%.3f decode_tok_s=%.1f stream_fd=%d\n",
Expand Down
16 changes: 8 additions & 8 deletions server/src/common/layer_split_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,18 @@ GenerateResult LayerSplitBackend::run_from_state(const GenerateRequest & req,
bool reset_state) {
GenerateResult result;
if (!adapter_) {
result.error = "adapter_unavailable";
result.fail(GenerateErrorCode::AdapterUnavailable);
return result;
}

DaemonIO out_io = io.with_token_callback(req.on_token);
if (base_pos + (int)req.prompt.size() + req.n_gen + 1 > adapter_->max_context()) {
result.error = "context_overflow";
result.fail(GenerateErrorCode::ContextOverflow);
return result;
}
if (req.do_sample && req.sampler.needs_logit_processing() &&
!adapter_->supports_cpu_sampling()) {
result.error = "sampling_unsupported";
result.fail(GenerateErrorCode::SamplingUnsupported);
return result;
}

Expand All @@ -86,7 +86,7 @@ GenerateResult LayerSplitBackend::run_from_state(const GenerateRequest & req,
std::vector<int32_t> chunk(req.prompt.begin() + consumed,
req.prompt.begin() + consumed + n_tokens);
if (!adapter_->prefill(chunk, base_pos + consumed, last_tok)) {
result.error = "prefill_failed";
result.fail(GenerateErrorCode::PrefillFailed);
return result;
}
consumed += n_tokens;
Expand All @@ -104,7 +104,7 @@ GenerateResult LayerSplitBackend::run_from_state(const GenerateRequest & req,

if (req.n_gen > 0) {
if (last_tok < 0) {
result.error = "decode_seed_missing";
result.fail(GenerateErrorCode::DecodeSeedMissing);
return result;
}
auto t_decode_start = std::chrono::steady_clock::now();
Expand All @@ -118,14 +118,14 @@ GenerateResult LayerSplitBackend::run_from_state(const GenerateRequest & req,
result.tokens, out_io);
if (use_dflash) result.accept_rate = dflash_accept_rate;
if (!ok) {
result.error = "decode_failed";
result.fail(GenerateErrorCode::DecodeFailed);
return result;
}
result.decode_s = std::chrono::duration<double>(
std::chrono::steady_clock::now() - t_decode_start).count();
}

result.ok = true;
result.succeed();
return result;
}

Expand Down Expand Up @@ -166,7 +166,7 @@ GenerateResult LayerSplitBackend::restore_and_generate_impl(
int slot, const GenerateRequest & req, const DaemonIO & io) {
GenerateResult result;
if (!adapter_ || !adapter_->snapshot_restore(slot)) {
result.error = "invalid_snapshot_slot";
result.fail(GenerateErrorCode::InvalidSnapshotSlot);
io.emit(-1);
return result;
}
Expand Down
68 changes: 65 additions & 3 deletions server/src/common/model_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
#include <cstdint>
#include <cstdio>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "ggml.h"
Expand Down Expand Up @@ -126,9 +129,48 @@ struct GenerateRequest {
bool force_ar_decode = false;
};

// Stable, backend-independent generation failure categories. Backends should
// use these for recurrent failures so callers do not need to understand
// architecture-specific strings. `generate_error_code()` is the daemon/API
// wire representation and must remain backward-compatible once published.
enum class GenerateErrorCode {
Incomplete,
AdapterUnavailable,
ContextOverflow,
SamplingUnsupported,
PrefillFailed,
DecodeSeedMissing,
DecodeFailed,
InvalidSnapshotSlot,
ModelParked,
BackendSpecific,
};

constexpr std::string_view generate_error_code(GenerateErrorCode error) {
switch (error) {
case GenerateErrorCode::Incomplete: return "incomplete";
case GenerateErrorCode::AdapterUnavailable: return "adapter_unavailable";
case GenerateErrorCode::ContextOverflow: return "context_overflow";
case GenerateErrorCode::SamplingUnsupported: return "sampling_unsupported";
case GenerateErrorCode::PrefillFailed: return "prefill_failed";
case GenerateErrorCode::DecodeSeedMissing: return "decode_seed_missing";
case GenerateErrorCode::DecodeFailed: return "decode_failed";
case GenerateErrorCode::InvalidSnapshotSlot: return "invalid_snapshot_slot";
case GenerateErrorCode::ModelParked: return "model_parked";
case GenerateErrorCode::BackendSpecific: return "backend_specific";
}
return "unknown_error";
}

struct GenerateError {
GenerateErrorCode code = GenerateErrorCode::Incomplete;
std::string detail;
};

struct GenerateResult {
bool ok = false;
std::string error; // "prefill", "decode", etc.
// Default to an incomplete failure so a backend must explicitly call
// succeed() before returning a successful result.
std::optional<GenerateError> error = GenerateError{};
std::vector<int32_t> tokens;
double prefill_s = 0.0;
double decode_s = 0.0;
Expand All @@ -155,6 +197,26 @@ struct GenerateResult {
// to zero output for clients and should take the same AR retry path as
// an empty token vector.
bool empty_visible_output = false;

bool ok() const {
return !error.has_value();
}

std::string_view error_code() const {
return error ? generate_error_code(error->code) : std::string_view{};
}

std::string_view error_detail() const {
return error ? std::string_view(error->detail) : std::string_view{};
}

void succeed() {
error.reset();
}

void fail(GenerateErrorCode code, std::string detail = {}) {
error = GenerateError{code, std::move(detail)};
}
};

// ─── Backend interface ──────────────────────────────────────────────────
Expand Down Expand Up @@ -226,7 +288,7 @@ struct ModelBackend {
const GenerateResult & result) {
return req.n_gen > 0
&& !req.force_ar_decode
&& result.ok
&& result.ok()
&& result.spec_decode_ran
&& (result.tokens.empty() || result.empty_visible_output);
}
Expand Down
8 changes: 4 additions & 4 deletions server/src/deepseek4/deepseek4_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,13 +670,13 @@ GenerateResult DeepSeek4Backend::generate_impl(const GenerateRequest & req,
// Prefill
int committed = do_prefill(req.prompt, io);
if (committed < 0) {
result.error = "prefill";
result.fail(GenerateErrorCode::PrefillFailed);
return result;
}
result.prefill_s = elapsed_s(t0);

if (req.n_gen <= 0) {
result.ok = true;
result.succeed();
maybe_save_routing_stats();
return result;
}
Expand All @@ -689,11 +689,11 @@ GenerateResult DeepSeek4Backend::generate_impl(const GenerateRequest & req,
bool forced_close = false;
if (!do_decode(committed, req.n_gen, gen_tokens, io,
req.budget_hook, &forced_close)) {
result.error = "decode";
result.fail(GenerateErrorCode::DecodeFailed);
return result;
}

result.ok = true;
result.succeed();
result.tokens = std::move(gen_tokens);
result.decode_s = elapsed_s(t1);
result.budget_forced_close = forced_close;
Expand Down
Loading