Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
bdd3cd5
train: one rule for the recurrent states a graph keeps, so training r…
joelteply Oct 13, 2026
e966b47
metal: OUT_PROD runs batched, broadcast and narrow shapes on the GPU,…
joelteply Oct 14, 2026
7468826
metal: a native OUT_PROD kernel reads the weight in place; the compos…
joelteply Oct 14, 2026
22f7556
metal: left padding, strided norm/softmax backward and GET_ROWS_BACK,…
joelteply Oct 14, 2026
32b8657
batch: a per-token label makes a position a training row, carried int…
joelteply Oct 14, 2026
0b8ddce
llama: a labelled ubatch is a training step inside decode; train_chun…
joelteply Oct 14, 2026
725c893
llama: the trainable layer applies to labelled rows only, and a train…
joelteply Oct 14, 2026
ee9b179
llama: the exact walk's K/V gradient holds the reply being trained, n…
joelteply Oct 14, 2026
0e6e0d9
ggml: cross-entropy takes one target index per row, so training build…
joelteply Oct 14, 2026
e231baa
ggml-opt: a training step's head is computed a slice of rows at a time
joelteply Oct 14, 2026
88c5c1e
ggml: the attention backward is one op (ggml_flash_attn_back), on CPU…
joelteply Oct 14, 2026
999d682
llama: connect labelled decode to physical cache row gradients
joelteply Oct 14, 2026
87a0f17
llama: keep serving indices and compose training admission in memory …
joelteply Oct 14, 2026
44abe56
test: compare multi-sequence label steps and isolate recompute
joelteply Oct 14, 2026
07af30f
ggml: recount graph uses after checkpoint rewriting
joelteply Oct 14, 2026
343f369
llama: separate labelled head rows from public outputs
joelteply Oct 14, 2026
eda8574
ggml: acc_or_set's zero is a node, not a full-size leaf
joelteply Oct 14, 2026
8ec4f9d
metal: the attention backward runs on simdgroup matrices, tiled, with…
joelteply Oct 14, 2026
22c112c
metal: the tiled attention backward is chosen only when its tiles fit…
joelteply Oct 15, 2026
6fc7bc6
llama: llama_opt_learn trains one reply on a live sequence of a servi…
joelteply Oct 15, 2026
4c0ee17
llama: llama_opt_learn refuses null inputs and an overlong reply befo…
joelteply Oct 15, 2026
e740f89
metal: the tiled attention backward needs simdgroup matrices (Apple7+…
joelteply Oct 15, 2026
018ab27
server: POST /learn trains a reply on the slot that served it, in the…
joelteply Oct 15, 2026
56918a7
Merge fable/metal-train-ops (native OUT_PROD, PAD left padding, strid…
joelteply Oct 15, 2026
3ee6d39
server: the learn restore runs under her own adapter set
joelteply Oct 15, 2026
e39b2b6
server: learning takes at most a tenth of the loop while another slot…
joelteply Oct 15, 2026
b75293d
ggml-cpu: the attention backward converts each K and V row once per b…
joelteply Oct 15, 2026
264f54b
Merge remote-tracking branch 'origin/cormac/cpu-attn-back' into fable…
joelteply Oct 15, 2026
85cac9d
server: /learn takes the served conversation and checks her slot stil…
joelteply Oct 15, 2026
e77b823
ggml: cross-entropy over target indices takes a weight per row
joelteply Oct 15, 2026
0135932
train: the attention backward computes K/V gradients for the run's ro…
joelteply Oct 15, 2026
8bbdf01
ggml-cpu: OUT_PROD with a quantized src0 dequantizes each src0 tile o…
joelteply Oct 15, 2026
f0045c0
ggml-cpu: OUT_PROD with an f16 src0 takes the same blocked kernel; th…
joelteply Oct 15, 2026
a0705b0
train: the training graph no longer grows with the window
joelteply Oct 15, 2026
cde8ffe
Merge remote-tracking branch 'origin/cormac/cpu-out-prod' into fable/…
joelteply Oct 15, 2026
5b8500e
ggml-opt: recompute never clones an in-place write into a buffer the …
joelteply Oct 15, 2026
c010269
tests: the CPU grad suite runs further: EXPM1 grad cases in a measura…
joelteply Oct 15, 2026
de1d97d
ggml: ggml_node_has_backward is the one answer to "can a backward run…
joelteply Oct 15, 2026
0fbbae2
Merge remote-tracking branch 'origin/cormac/grad-suite-fix' into fabl…
joelteply Oct 15, 2026
3a1c7e2
server: /learn builds the served prompt through the same parser /v1/c…
joelteply Oct 15, 2026
56507cc
server: one serving tokenizer for chat and /learn; /learn refuses rep…
joelteply Oct 15, 2026
b7c92db
llama: a labelled position carries a weight in the loss (llama_batch.…
joelteply Oct 15, 2026
9a10993
server: a completion can carry a learn, run on its own slot before th…
joelteply Oct 15, 2026
0cb014e
server: a learn the request could not run (an empty prompt) is report…
joelteply Oct 15, 2026
d5a4c77
server: every final-response format carries the learn outcome; the An…
joelteply Oct 15, 2026
cc2e05f
server tests: learn-every-format.py imports what it uses (the committ…
joelteply Oct 15, 2026
35b8eed
server: every training path refuses repacked CPU weights alike (conti…
joelteply Oct 15, 2026
3424de0
server: share learning duty admission across completion and learn
joelteply Oct 16, 2026
4996479
server: return duty deferral without waiting for unrelated slot release
joelteply Oct 16, 2026
a8ae539
server: document replayable context custody for deferred learning
joelteply Oct 16, 2026
75fdf35
Expose bounded operation-specific restore IO progress
joelteply Oct 16, 2026
bda4665
server: expose owned model load progress through health
joelteply Oct 16, 2026
b276aa3
server: use declared JSON type at health boundary
joelteply Oct 16, 2026
fc74417
fix(server): learn structured replies through the served template
joelteply Oct 16, 2026
f443dfd
test(server): distinguish unsupported reasoning templates
joelteply Oct 16, 2026
c56533d
opt: checkpoint trusted completed optimizer state
joelteply Oct 16, 2026
e45d750
test: reject truncated optimizer state without mutation
joelteply Oct 16, 2026
a02fcb1
server: retain optimizer-bound shadow during adapter unload
joelteply Oct 16, 2026
49fdd3e
merge optimizer checkpoint primitive with shadow retirement guard
joelteply Oct 16, 2026
5adf089
Prepare shadow generation components before in-place restore
joelteply Oct 16, 2026
69d2da4
Enforce shadow quarantine at shared labelled graph boundary
joelteply Oct 16, 2026
4e8759b
Verify changed shadow weights and resumed step restoration
joelteply Oct 16, 2026
83d5fb8
Select live generation fixture independently of cache matrix
joelteply Oct 16, 2026
fc69795
test: attach shadow for restored next-step comparison
joelteply Oct 16, 2026
1c29103
opt: allocate fresh restore state without evaluating a sample
joelteply Oct 16, 2026
58c2c62
test: restore dynamic optimizer from matching graph policy
joelteply Oct 16, 2026
3e7795d
opt: prepare fresh resident restore without consuming experience
joelteply Oct 16, 2026
4b1ba49
docs: state failed restore preparation custody
joelteply Oct 16, 2026
882e6c2
fix: preserve optimizer custody when restore preparation refuses
joelteply Oct 16, 2026
69cb794
feat: seal explicit zero-step optimizer state without learning
joelteply Oct 16, 2026
d83f4cd
test: rebuild transient metadata for each dynamic optimizer step
joelteply Oct 16, 2026
f7078e5
Quote exact slot file bytes and enforce admitted save ceilings
joelteply Oct 16, 2026
3332886
Validate save ceilings through the fork JSON interface
joelteply Oct 16, 2026
da8c689
Advertise exact checkpoint size admission capability
joelteply Oct 16, 2026
94ae1ec
Retain expert container custody through native reader and packer life…
joelteply Oct 16, 2026
1d35e72
Compile custody fixtures against the real backend declarations
joelteply Oct 16, 2026
3794294
test: verify retained writable mapping excludes container preparation
joelteply Oct 16, 2026
f0b8389
grammar: preserve unions in transparent singleton allOf
joelteply Oct 17, 2026
d3e6279
test: keep tagged edit grammar checks active in Release
joelteply Oct 17, 2026
032e2bc
fix: initialize speculative batch label weights explicitly
joelteply Oct 17, 2026
50b02b8
fix: build private walk fixture under supported library linkage
joelteply Oct 17, 2026
76a1d60
server: checkpoint at a client-named user message (checkpoint_user_me…
joelteply Oct 20, 2026
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
13 changes: 13 additions & 0 deletions common/chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,19 @@ struct common_chat_msg_spans {
return false;
}

// token position of the k-th user message (0-based), or -1 when there is none
int32_t user_message_pos(int32_t k) const {
if (k < 0) {
return -1;
}
for (const auto & span : spans) {
if (span.role == COMMON_CHAT_ROLE_USER && k-- == 0) {
return (int32_t) span.pos;
}
}
return -1;
}

int32_t last_user_message_pos() const {
for (auto it = spans.rbegin(); it != spans.rend(); ++it) {
if (it->role == COMMON_CHAT_ROLE_USER) {
Expand Down
13 changes: 13 additions & 0 deletions common/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2401,8 +2401,21 @@ bool common_lora_write_fresh(const llama_model * model, const std::string & path
std::vector<shape> shapes;
const int32_t n_layer = llama_model_n_layer(model);
const int32_t first = top_layers > 0 && top_layers < n_layer ? n_layer - top_layers : 0;
for (const auto & target : targets) {
if (target == "token_embd.weight") {
LOG_ERR("%s: embedding adapters require the embedding-specific layout\n", __func__);
return false;
}
const ggml_tensor * w = llama_model_get_tensor(model, target.c_str());
if (w != nullptr && ggml_n_dims(w) == 2) {
shapes.push_back({ target, w->ne[0], w->ne[1] });
}
}
for (int32_t il = first; il < n_layer; ++il) {
for (const auto & target : targets) {
if (llama_model_get_tensor(model, target.c_str()) != nullptr) {
continue;
}
const std::string name = "blk." + std::to_string(il) + "." + target + ".weight";
const ggml_tensor * w = llama_model_get_tensor(model, name.c_str());
if (w != nullptr && ggml_n_dims(w) == 2) {
Expand Down
21 changes: 21 additions & 0 deletions common/json-schema-to-grammar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,27 @@ class common_schema_converter {
properties, required, name,
schema.contains("additionalProperties") ? schema["additionalProperties"] : json()));
}
// A singleton union wrapper adds no constraint unless a sibling does.
if (schema.contains("allOf") && schema["allOf"].is_array() && schema["allOf"].size() == 1) {
bool transparent = true;
for (const auto & item : schema.items()) {
const auto & key = item.key();
if (key != "allOf" && key != "description" && key != "title" && key != "$comment" &&
key != "default" && key != "examples" && key != "definitions" && key != "$defs") {
transparent = false;
}
}
const json * component = &schema["allOf"][0];
if (component->is_object() && component->contains("$ref")) {
auto ref = _refs.find((*component)["$ref"]);
if (ref != _refs.end() && component->size() == 1) {
component = &ref->second;
}
}
if (transparent && (component->contains("oneOf") || component->contains("anyOf"))) {
return visit(*component, name);
}
}
if ((schema_type.is_null() || schema_type == "object" || schema_type == "string") && schema.contains("allOf")) {
std::unordered_set<std::string> required;
std::vector<std::pair<std::string, json>> properties;
Expand Down
4 changes: 4 additions & 0 deletions common/speculative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ struct common_speculative_impl_draft_eagle3 : public common_speculative_impl {
/*.n_seq_id =*/ nullptr,
/*.seq_id =*/ nullptr,
/*.logits =*/ nullptr,
/*.label =*/ nullptr,
/*.label_weight =*/ nullptr,
};
const int32_t rc = llama_encode(ctx_dft, enc_batch);
if (rc != 0) {
Expand Down Expand Up @@ -1126,6 +1128,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl {
/*.n_seq_id =*/ nullptr,
/*.seq_id =*/ nullptr,
/*.logits =*/ nullptr,
/*.label =*/ nullptr,
/*.label_weight =*/ nullptr,
};

int32_t rc = llama_encode(ctx_dft, enc_batch);
Expand Down
2 changes: 2 additions & 0 deletions examples/parallel/parallel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@ int main(int argc, char ** argv) {
batch.n_seq_id + i,
batch.seq_id + i,
batch.logits + i,
batch.label ? batch.label + i : nullptr,
batch.label && batch.label_weight ? batch.label_weight + i : nullptr,
};

const int ret = llama_decode(ctx, batch_view);
Expand Down
86 changes: 85 additions & 1 deletion ggml/include/ggml-opt.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ggml-backend.h"

#include <stdint.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
Expand All @@ -32,6 +33,10 @@ extern "C" {
GGML_OPT_LOSS_TYPE_SUM,
GGML_OPT_LOSS_TYPE_CROSS_ENTROPY,
GGML_OPT_LOSS_TYPE_MEAN_SQUARED_ERROR,
// GGML_OPT_LOSS_TYPE_CROSS_ENTROPY with one I32 target per output row (-1: no loss) in place of a one-hot
// row as wide as the outputs: what a language model's training step needs (its outputs are
// vocabulary-wide logits)
GGML_OPT_LOSS_TYPE_CROSS_ENTROPY_TARGETS,
};

// ====== Dataset ======
Expand Down Expand Up @@ -154,7 +159,13 @@ extern "C" {
// if not using static graphs these pointers become invalid with the next call to ggml_opt_alloc
GGML_API struct ggml_tensor * ggml_opt_inputs( ggml_opt_context_t opt_ctx); // forward graph input tensor
GGML_API struct ggml_tensor * ggml_opt_outputs( ggml_opt_context_t opt_ctx); // forward graph output tensor
GGML_API struct ggml_tensor * ggml_opt_labels( ggml_opt_context_t opt_ctx); // labels to compare outputs against
GGML_API struct ggml_tensor * ggml_opt_labels( ggml_opt_context_t opt_ctx);
// the per-row weights of a weighted cross-entropy step (F32, one per label), or NULL when the
// graph prepared last is unweighted (ggml_opt_set_next_label_weights)
GGML_API struct ggml_tensor * ggml_opt_label_weights(ggml_opt_context_t opt_ctx);
// whether the next graph's cross-entropy weighs each row (by ggml_opt_label_weights); an
// unweighted step keeps the unweighted loss, whose kernels every backend has
GGML_API void ggml_opt_set_next_label_weights(ggml_opt_context_t opt_ctx, bool weighted); // labels to compare outputs against
GGML_API struct ggml_tensor * ggml_opt_loss( ggml_opt_context_t opt_ctx); // scalar tensor that contains the loss
GGML_API struct ggml_tensor * ggml_opt_pred( ggml_opt_context_t opt_ctx); // predictions made by outputs
GGML_API struct ggml_tensor * ggml_opt_ncorrect(ggml_opt_context_t opt_ctx); // number of matching predictions between outputs and labels
Expand All @@ -166,6 +177,37 @@ extern "C" {

GGML_API const char * ggml_opt_optimizer_name(enum ggml_opt_optimizer_type);

// Checkpoint optimizer state at a successful update boundary. Parameters are saved separately.
// binding identifies the caller's model, adapter topology and optimizer policy; it must match exactly.
// Mutable callback/schedule state must be saved by the caller in the same generation, not inferred from a callback address.
// Import requires allocated optimizer tensors and no prepared evaluation; it validates all input before changing state.
// The caller owns file integrity, atomic publication and serialization with compute.
// Explicit generation zero: only a fresh, allocated, import-pending optimizer is eligible.
// Initializes persistent optimizer state to zero, without a weight/counter/RNG update. Writes
// a distinct initial-boundary artifact; never enables evaluation or claims sample consumption.
// Caller must publish weights/policy/integrity atomically and restore before first evaluation.
GGML_API bool ggml_opt_state_write_initial(ggml_opt_context_t opt_ctx, FILE * file, const char * binding);
GGML_API bool ggml_opt_state_write(ggml_opt_context_t opt_ctx, FILE * file, const char * binding);
GGML_API bool ggml_opt_state_read (ggml_opt_context_t opt_ctx, FILE * file, const char * binding);
// Prepare owns validated host payload without changing the destination. The caller must keep
// the context alive and serialize prepare/apply with all evaluation and allocation operations.
struct ggml_opt_state;
// Allocate a genuine supplied graph only for importing a fresh optimizer generation. Executes
// no graph and consumes no sample; refuses used, pending, partial or failed contexts. The
// caller must restore before evaluation. Existing lifetimes are unchanged: retain parameter
// and static graph storage for the optimizer lifetime; dynamic graph storage follows the
// existing prepare/allocator custody contract. This operation does not release that storage.
// A failed allocation attempt is teardown-only: evaluation, retry and import are refused.
// An eligibility refusal leaves the existing optimizer untouched.
GGML_API bool ggml_opt_alloc_for_restore(ggml_opt_context_t opt_ctx);
// Side-effect-free eligibility, owned by the same allocator. Check before replacing caller
// graph/scheduler state; allocation checks again under the caller's serialized custody.
GGML_API bool ggml_opt_can_prepare_restore(ggml_opt_context_t opt_ctx);
GGML_API struct ggml_opt_state * ggml_opt_state_prepare(ggml_opt_context_t opt_ctx, FILE * file, const char * binding);
GGML_API bool ggml_opt_state_apply(ggml_opt_context_t opt_ctx, struct ggml_opt_state * state);
GGML_API void ggml_opt_state_free(struct ggml_opt_state * state);


// ====== Optimization Result ======

GGML_API ggml_opt_result_t ggml_opt_result_init(void);
Expand All @@ -188,6 +230,48 @@ extern "C" {
struct ggml_tensor * inputs,
struct ggml_tensor * outputs);

// The same, for an `outputs` that is a row-wise function of `hidden` (a language model's
// head: logits = W x, with its adapters and scales; MUL_MAT, MUL, ADD and SCALE nodes): that
// head is taken out of gf and computed for `rows` rows of hidden at a time, and the loss is
// the mean over every row (GGML_OPT_LOSS_TYPE_CROSS_ENTROPY_TARGETS only). Under recompute
// (checkpoint_prefix) only one slice's outputs and their gradient exist at a time, never the
// [n_outputs x rows] tensor and its gradient. A head with any other node is not sliced.
// Returns the number of slices (1: not sliced; accuracy is reported only then).
GGML_API int ggml_opt_prepare_alloc_head(
ggml_opt_context_t opt_ctx,
struct ggml_context * ctx_compute,
struct ggml_cgraph * gf,
struct ggml_tensor * inputs,
struct ggml_tensor * hidden,
struct ggml_tensor * outputs,
int64_t rows);

// Project the existing row-wise head before consumers such as sampling are built.
// Row indices address hidden's token rows. A null public_rows requests no public logits.
// Returns zero without changing gf when the head cannot be replayed.
GGML_API int ggml_opt_project_head(
struct ggml_context * ctx_compute,
struct ggml_cgraph * gf,
struct ggml_tensor * hidden,
struct ggml_tensor * outputs,
struct ggml_tensor * shadow_rows,
struct ggml_tensor * public_rows,
struct ggml_tensor * label_rows,
struct ggml_tensor * public_shadow_rows,
struct ggml_tensor ** slice_shadow_rows,
int64_t rows,
struct ggml_tensor ** public_outputs,
struct ggml_tensor ** slices,
size_t slices_capacity);

GGML_API void ggml_opt_prepare_alloc_slices(
ggml_opt_context_t opt_ctx,
struct ggml_context * ctx_compute,
struct ggml_cgraph * gf,
struct ggml_tensor * inputs,
struct ggml_tensor ** slices,
size_t n_slices);

// allocate the next graph for evaluation, either forward or forward + backward
// must be called exactly once prior to calling ggml_opt_eval
// returns false when the graph could not be allocated (device memory): the graph is then NOT
Expand Down
57 changes: 54 additions & 3 deletions ggml/include/ggml.h
Original file line number Diff line number Diff line change
Expand Up @@ -2480,14 +2480,47 @@ extern "C" {
struct ggml_tensor * a,
struct ggml_tensor * sinks);

// TODO: needs to be adapted to ggml_flash_attn_ext
// The gradients of ggml_flash_attn_ext(q, k, v, mask, scale) (no ALiBi, softcap or sinks), given
// d, the gradient of its output in the output's layout [Dv, H, N]. One op, recomputing the
// scores per block instead of keeping them: memory is the gradients alone. The result is F32:
// dQ ([D, N, H]), dK (k's shape), dV (v's shape), each contiguous at
// ggml_flash_attn_back_offset, then each query row's log-sum-exp and D = sum_j p_ij dp_ij,
// which the kernels pass between their passes, and the power of two dO is scaled by in a
// kernel's reduced-precision tiles (ggml_flash_attn_back_scale).
// dK and dV cover the key rows [kv0, M) only (row j at j - kv0): keys before kv0 are constants
// whose gradient nobody reads (a frozen served prefix), so it is neither computed nor stored.
// dQ always sums over every key.
GGML_API struct ggml_tensor * ggml_flash_attn_back(
struct ggml_context * ctx,
struct ggml_tensor * q,
struct ggml_tensor * k,
struct ggml_tensor * v,
struct ggml_tensor * mask,
struct ggml_tensor * d,
bool masked);
float scale,
int64_t kv0);

// byte offset in a ggml_flash_attn_back result of: 0 dQ, 1 dK, 2 dV, 3 the per-row scratch
// the power of two that brings the largest |dO| to [4, 8): training gradients sit far below half
// precision's normal range (6.1e-5) and lose their precision in half tiles; 1 when dO is zero
// (the Metal kernel computes the same rule, kernel_flash_attn_back_scale)
GGML_API float ggml_flash_attn_back_scale(float max_abs_do);
GGML_API size_t ggml_flash_attn_back_offset(const struct ggml_tensor * back, int part);

// Where FLASH_ATTN_EXT's K/V gradient goes when only the last rows of its K/V can carry one:
// k_tap / v_tap (or NULL) and k_cur / v_cur, each [D, Hkv, rows], hold the values of K/V's last
// rows in order, ending at M (a run's cached rows, then the chunk's own). The backward computes
// dK/dV from the first of those rows only and adds each part to its sink; the K/V the attention
// reads (the whole window) get no gradient. The sinks are not read by the forward.
GGML_API void ggml_flash_attn_ext_set_grad_sinks(
struct ggml_tensor * a,
struct ggml_tensor * k_tap,
struct ggml_tensor * k_cur,
struct ggml_tensor * v_tap,
struct ggml_tensor * v_cur);

// the backward of FLASH_ATTN_EXT as the one op above (ggml_flash_attn_ext_set_back_tile)
#define GGML_FA_BACK_NATIVE (-1)

GGML_API struct ggml_tensor * ggml_ssm_conv(
struct ggml_context * ctx,
Expand Down Expand Up @@ -2779,7 +2812,21 @@ extern "C" {
int n_tasks,
void * userdata);

// loss function
// loss function: the mean over rows of -sum(labels * log softmax(logits)). The labels are
// either a distribution per row (F32, the logits' shape) or one target index per row (I32,
// one per row of the logits, -1 for a row that carries no loss): a target index never builds
// a vocabulary-wide one-hot tensor beside the logits.

GGML_API bool ggml_cross_entropy_labels_ok(const struct ggml_tensor * logits, const struct ggml_tensor * labels);

// The same with one target index per row (I32) and a weight per row (F32, one per row): row i
// contributes w_i * -log softmax(logits_i)[t_i] / rows, and its gradient scales by w_i. How much a
// row counts is the caller's to say (a mind's relevance on what it perceived), not only whether.
GGML_API struct ggml_tensor * ggml_cross_entropy_loss_weighted(
struct ggml_context * ctx,
struct ggml_tensor * logits,
struct ggml_tensor * targets,
struct ggml_tensor * weights);

GGML_API struct ggml_tensor * ggml_cross_entropy_loss(
struct ggml_context * ctx,
Expand Down Expand Up @@ -2852,6 +2899,10 @@ extern "C" {
struct ggml_cgraph * cgraph,
struct ggml_tensor * tensor);

// whether a backward can be built through this node (ggml_build_backward_expand aborts on one
// that cannot: an op without a backward, or an in-place write)
GGML_API bool ggml_node_has_backward(const struct ggml_tensor * tensor);

GGML_API void ggml_build_backward_expand(
struct ggml_context * ctx, // context for gradient computation
struct ggml_cgraph * cgraph,
Expand Down
15 changes: 11 additions & 4 deletions ggml/src/ggml-backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1610,16 +1610,19 @@ static bool ggml_backend_sched_alloc_splits(ggml_backend_sched_t sched) {
static ggml_moe::DirectReadFetcher moe_direct_fetcher;
#endif
static ggml_moe::MmapFaultFetcher moe_mmap_fetcher;
static ggml_moe::DirContainerFetcher & moe_container_fetcher() {
static ggml_moe::DirContainerFetcher fetcher(ggml_moe::moe_config().container_dir);
return fetcher;
}
static ggml_moe::ExpertFetcher & moe_pick_base_fetcher() {
// Container-serve (#268): when a packed per-layer expert container is configured, read from it.
// Its records are contiguous + 16KiB-aligned, so this is the honest ~GB/s sequential path — vs
// DirectRead scraping scattered offsets out of the raw GGUF's mmap. The serving caller packs
// (layer, byte_offset) into `src` via DirContainerFetcher::pack_src; the cache passes it opaquely.
// Constructed once with the configured dir (banks open lazily); a bad dir opens no banks and every
// fetch fails LOUD (zeroed record -> downstream validation), never a silent wrong-source serve.
if (const char * cdir = ggml_moe::moe_config().container_dir) {
static ggml_moe::DirContainerFetcher container_fetcher(cdir);
return container_fetcher;
if (ggml_moe::moe_config().container_dir) {
return moe_container_fetcher();
}
#ifdef _WIN32
if (ggml_moe::moe_config().direct_read) { return moe_direct_fetcher; }
Expand Down Expand Up @@ -1757,6 +1760,10 @@ static ggml_backend_moe_gather_entry_t moe_gather_entry_fn(ggml_backend_t backen

static enum ggml_status ggml_backend_sched_compute_splits(ggml_backend_sched_t sched) {
GGML_ASSERT(sched);
if (ggml_moe::moe_config().container_dir && !moe_container_fetcher().ok()) {
GGML_LOG_ERROR("%s: expert container custody unavailable; refusing graph execution\n", __func__);
return GGML_STATUS_FAILED;
}
struct ggml_backend_sched_split * splits = sched->splits;

ggml_tensor * prev_ids_tensor = nullptr;
Expand Down Expand Up @@ -2114,7 +2121,7 @@ static enum ggml_status ggml_backend_sched_compute_splits(ggml_backend_sched_t s
// parses from the tensor name's `blk.N.` (names may be backend-prefixed, so
// anchor on the substring, not the string start).
static const uint64_t moec_record_bytes =
ggml_moe::dir_container_record_bytes(ggml_moe::moe_config().container_dir);
ggml_moe::moe_config().container_dir ? moe_container_fetcher().record_bytes() : 0;
uint32_t moec_layer = 0;
const char * moec_blk = std::strstr(tname, "blk.");
const bool moec_active = moec_record_bytes > 0 && moec_blk != nullptr &&
Expand Down
Loading
Loading