feat(server): add Gemma4 target-layer-split adapter#273
Conversation
01c6842 to
c421bc7
Compare
c421bc7 to
79abba9
Compare
Record the clean integration of PRs Luce-Org#265 and Luce-Org#273, the refreshed conflict probes for the remaining selective-port PRs, and the current validation results.
There was a problem hiding this comment.
8 issues found across 37 files
You’re at about 91% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/src/common/layer_split_utils.cpp">
<violation number="1" location="server/src/common/layer_split_utils.cpp:75">
P2: Peer-access setup silently ignores pairwise enable failures and always reports success through a `bool` return that never reflects reality.</violation>
</file>
<file name="server/src/qwen35/qwen35_target_graph.cpp">
<violation number="1" location="server/src/qwen35/qwen35_target_graph.cpp:1440">
P2: Snapshot/restore mismatch validation only checks aggregate pair existence with XOR (`(!sk || !sv) != (!dk || !dv)`), so an internally broken pair on both sides silently skips instead of failing. Similar pattern exists for SSM/conv pairs.</violation>
</file>
<file name="server/src/gemma4/gemma4_loader.cpp">
<violation number="1" location="server/src/gemma4/gemma4_loader.cpp:362">
P2: Backend buffer `out.buf` is leaked on the per-tensor allocation failure path</violation>
</file>
<file name="server/src/common/layer_split_backend.cpp">
<violation number="1" location="server/src/common/layer_split_backend.cpp:221">
P2: Destructor calls shutdown() but shutdown() does not clear adapter_, enabling double adapter shutdown if explicitly called before destruction</violation>
</file>
<file name="server/src/placement/placement_config.cpp">
<violation number="1" location="server/src/placement/placement_config.cpp:25">
P2: `layer_split_weights` validation is silently skipped when `layer_split_gpus` is empty, allowing invalid config to pass validation</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| : ""); | ||
| } | ||
|
|
||
| if (!dp.layer_split_gpus.empty()) { |
There was a problem hiding this comment.
P2: layer_split_weights validation is silently skipped when layer_split_gpus is empty, allowing invalid config to pass validation
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/placement/placement_config.cpp, line 25:
<comment>`layer_split_weights` validation is silently skipped when `layer_split_gpus` is empty, allowing invalid config to pass validation</comment>
<file context>
@@ -0,0 +1,65 @@
+ : "");
+ }
+
+ if (!dp.layer_split_gpus.empty()) {
+ if (dp.layer_split_gpus.size() < 2) {
+ return "layer_split_gpus must have at least 2 entries";
</file context>
| ggml_tensor * dk = cache.attn_k[i]; | ||
| ggml_tensor * sv = snap.attn_v_snap[i]; | ||
| ggml_tensor * dv = cache.attn_v[i]; | ||
| if ((!sk || !sv) != (!dk || !dv)) { |
There was a problem hiding this comment.
P2: Snapshot/restore mismatch validation only checks aggregate pair existence with XOR ((!sk || !sv) != (!dk || !dv)), so an internally broken pair on both sides silently skips instead of failing. Similar pattern exists for SSM/conv pairs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/qwen35/qwen35_target_graph.cpp, line 1440:
<comment>Snapshot/restore mismatch validation only checks aggregate pair existence with XOR (`(!sk || !sv) != (!dk || !dv)`), so an internally broken pair on both sides silently skips instead of failing. Similar pattern exists for SSM/conv pairs.</comment>
<file context>
@@ -1428,6 +1437,11 @@ bool restore_target_cache(const PrefixSnapshot & snap, TargetCache & cache) {
ggml_tensor * dk = cache.attn_k[i];
ggml_tensor * sv = snap.attn_v_snap[i];
ggml_tensor * dv = cache.attn_v[i];
+ if ((!sk || !sv) != (!dk || !dv)) {
+ set_last_error("restore_target_cache: KV shard layout mismatch");
+ return false;
</file context>
| set_last_error("gemma4: tensor alloc failed"); | ||
| mmap.close_map(); | ||
| gguf_free(gctx); return false; |
There was a problem hiding this comment.
P2: Backend buffer out.buf is leaked on the per-tensor allocation failure path
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/gemma4/gemma4_loader.cpp, line 362:
<comment>Backend buffer `out.buf` is leaked on the per-tensor allocation failure path</comment>
<file context>
@@ -254,35 +322,65 @@ bool load_gemma4_gguf(const std::string & path,
+ for (const Gemma4TensorAlloc & a : allocs) {
+ if (ggml_backend_tensor_alloc(out.buf, a.tensor,
+ base + a.buffer_offset) != GGML_STATUS_SUCCESS) {
+ set_last_error("gemma4: tensor alloc failed");
+ mmap.close_map();
+ gguf_free(gctx); return false;
</file context>
| set_last_error("gemma4: tensor alloc failed"); | |
| mmap.close_map(); | |
| gguf_free(gctx); return false; | |
| set_last_error("gemma4: tensor alloc failed"); | |
| ggml_backend_buffer_free(out.buf); | |
| out.buf = nullptr; | |
| mmap.close_map(); | |
| gguf_free(gctx); return false; |
| return adapter_ && adapter_->supports_remote_draft(); | ||
| } | ||
|
|
||
| void LayerSplitBackend::shutdown() { |
There was a problem hiding this comment.
P2: Destructor calls shutdown() but shutdown() does not clear adapter_, enabling double adapter shutdown if explicitly called before destruction
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/common/layer_split_backend.cpp, line 221:
<comment>Destructor calls shutdown() but shutdown() does not clear adapter_, enabling double adapter shutdown if explicitly called before destruction</comment>
<file context>
@@ -0,0 +1,225 @@
+ return adapter_ && adapter_->supports_remote_draft();
+}
+
+void LayerSplitBackend::shutdown() {
+ if (adapter_) adapter_->shutdown();
+}
</file context>
| if (!peer_access) return true; | ||
| for (size_t i = 0; i < gpus.size(); ++i) { | ||
| for (size_t j = i + 1; j < gpus.size(); ++j) { | ||
| (void)enable_peer_access_pair(gpus[i], gpus[j]); |
There was a problem hiding this comment.
P2: Peer-access setup silently ignores pairwise enable failures and always reports success through a bool return that never reflects reality.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/common/layer_split_utils.cpp, line 75:
<comment>Peer-access setup silently ignores pairwise enable failures and always reports success through a `bool` return that never reflects reality.</comment>
<file context>
@@ -39,51 +43,70 @@ std::vector<std::pair<int,int>> compute_layer_ranges(
+ if (!peer_access) return true;
+ for (size_t i = 0; i < gpus.size(); ++i) {
+ for (size_t j = i + 1; j < gpus.size(); ++j) {
+ (void)enable_peer_access_pair(gpus[i], gpus[j]);
}
+ }
</file context>
… backend rollback - dflash_draft_ipc_daemon: stream get/set_feature_range in bounded 256-token chunks so daemon host memory is O(chunk) not O(n_tokens), avoiding large transient allocations / OOM on long-context snapshot/restore. Wire bytes and client read protocol unchanged. - layer_split_utils: roll back already-created snapshot backends when init_layer_split_snapshot_backends fails partway, fixing a CPU-backend leak. Co-Authored-By: WOZCODE <contact@withwoz.com>
## What Adds a richer GGUF identity reader on top of Howard Su's existing `gguf_inspect` (PR Luce-Org#305): - `GgufMetadata` struct — captures `general.*` + `<arch>.*` header fields (architecture, name, file_type, quantization_version, block_count, embedding_length, context_length, vocab_size) with -1 / "" sentinels distinguishing "not in GGUF" from legitimate zero. - `read_gguf_metadata(path, compute_sha256)` — best-effort header read; optional SHA-256 of the whole file. - Self-contained SHA-256 mini-impl (RFC 6234) — no OpenSSL dependency added for one hash. - `<path>.sha256` sidecar caching — first server start hashes the file (~30s for a 17 GB GGUF on NVMe), subsequent starts read the sidecar. Sidecar I/O failures are non-fatal. - `llama_ftype_name` decode — maps `general.file_type` ints to human-readable names ("Q4_K_M", "IQ4_XS", etc.) for /props. ## Why `/props` schema-4 wants a single authoritative "exactly what binary + GGUF + quant + sha256 is loaded" payload so benchmarking and provenance tooling can pin model identity across runs without re-parsing GGUF headers in every consumer. The sidecar makes the SHA-256 free after the first boot, which is what makes it usable as a default-on identity field. ## Dependencies None. This is purely additive on top of `gguf_inspect.{cpp,h}` as merged in PR Luce-Org#305 — zero deletions, 333 insertions total. No other server files or build rules change in this PR; consumers will be wired up separately. ## Scope note This PR is the extracted-and-cleaned remnant of the previously closed PR Luce-Org#336 after a provenance audit; everything else from that branch (c2_gate, qwen3 drafter changes, structural-defense loaders, and the inadvertent reverts of Luce-Org#273/Luce-Org#295/Luce-Org#297) is either landing through its canonical PR (Luce-Org#274) or being dropped entirely.
## What Adds a richer GGUF identity reader on top of Howard Su's existing `gguf_inspect` (PR Luce-Org#305): - `GgufMetadata` struct — captures `general.*` + `<arch>.*` header fields (architecture, name, file_type, quantization_version, block_count, embedding_length, context_length, vocab_size) with -1 / "" sentinels distinguishing "not in GGUF" from legitimate zero. - `read_gguf_metadata(path, compute_sha256)` — best-effort header read; optional SHA-256 of the whole file. - Self-contained SHA-256 mini-impl (RFC 6234) — no OpenSSL dependency added for one hash. - `<path>.sha256` sidecar caching — first server start hashes the file (~30s for a 17 GB GGUF on NVMe), subsequent starts read the sidecar. Sidecar I/O failures are non-fatal. - `llama_ftype_name` decode — maps `general.file_type` ints to human-readable names ("Q4_K_M", "IQ4_XS", etc.) for /props. ## Why `/props` schema-4 wants a single authoritative "exactly what binary + GGUF + quant + sha256 is loaded" payload so benchmarking and provenance tooling can pin model identity across runs without re-parsing GGUF headers in every consumer. The sidecar makes the SHA-256 free after the first boot, which is what makes it usable as a default-on identity field. ## Dependencies None. This is purely additive on top of `gguf_inspect.{cpp,h}` as merged in PR Luce-Org#305 — zero deletions, 333 insertions total. No other server files or build rules change in this PR; consumers will be wired up separately. ## Scope note This PR is the extracted-and-cleaned remnant of the previously closed PR Luce-Org#336 after a provenance audit; everything else from that branch (c2_gate, qwen3 drafter changes, structural-defense loaders, and the inadvertent reverts of Luce-Org#273/Luce-Org#295/Luce-Org#297) is either landing through its canonical PR (Luce-Org#274) or being dropped entirely.
## What Adds a richer GGUF identity reader on top of Howard Su's existing `gguf_inspect` (PR Luce-Org#305): - `GgufMetadata` struct — captures `general.*` + `<arch>.*` header fields (architecture, name, file_type, quantization_version, block_count, embedding_length, context_length, vocab_size) with -1 / "" sentinels distinguishing "not in GGUF" from legitimate zero. - `read_gguf_metadata(path, compute_sha256)` — best-effort header read; optional SHA-256 of the whole file. - Self-contained SHA-256 mini-impl (RFC 6234) — no OpenSSL dependency added for one hash. - `<path>.sha256` sidecar caching — first server start hashes the file (~30s for a 17 GB GGUF on NVMe), subsequent starts read the sidecar. Sidecar I/O failures are non-fatal. - `llama_ftype_name` decode — maps `general.file_type` ints to human-readable names ("Q4_K_M", "IQ4_XS", etc.) for /props. ## Why `/props` schema-4 wants a single authoritative "exactly what binary + GGUF + quant + sha256 is loaded" payload so benchmarking and provenance tooling can pin model identity across runs without re-parsing GGUF headers in every consumer. The sidecar makes the SHA-256 free after the first boot, which is what makes it usable as a default-on identity field. ## Dependencies None. This is purely additive on top of `gguf_inspect.{cpp,h}` as merged in PR Luce-Org#305 — zero deletions, 333 insertions total. No other server files or build rules change in this PR; consumers will be wired up separately. ## Scope note This PR is the extracted-and-cleaned remnant of the previously closed PR Luce-Org#336 after a provenance audit; everything else from that branch (c2_gate, qwen3 drafter changes, structural-defense loaders, and the inadvertent reverts of Luce-Org#273/Luce-Org#295/Luce-Org#297) is either landing through its canonical PR (Luce-Org#274) or being dropped entirely.
## What Adds a richer GGUF identity reader on top of Howard Su's existing `gguf_inspect` (PR Luce-Org#305): - `GgufMetadata` struct — captures `general.*` + `<arch>.*` header fields (architecture, name, file_type, quantization_version, block_count, embedding_length, context_length, vocab_size) with -1 / "" sentinels distinguishing "not in GGUF" from legitimate zero. - `read_gguf_metadata(path, compute_sha256)` — best-effort header read; optional SHA-256 of the whole file. - Self-contained SHA-256 mini-impl (RFC 6234) — no OpenSSL dependency added for one hash. - `<path>.sha256` sidecar caching — first server start hashes the file (~30s for a 17 GB GGUF on NVMe), subsequent starts read the sidecar. Sidecar I/O failures are non-fatal. - `llama_ftype_name` decode — maps `general.file_type` ints to human-readable names ("Q4_K_M", "IQ4_XS", etc.) for /props. ## Why `/props` schema-4 wants a single authoritative "exactly what binary + GGUF + quant + sha256 is loaded" payload so benchmarking and provenance tooling can pin model identity across runs without re-parsing GGUF headers in every consumer. The sidecar makes the SHA-256 free after the first boot, which is what makes it usable as a default-on identity field. ## Dependencies None. This is purely additive on top of `gguf_inspect.{cpp,h}` as merged in PR Luce-Org#305 — zero deletions, 333 insertions total. No other server files or build rules change in this PR; consumers will be wired up separately. ## Scope note This PR is the extracted-and-cleaned remnant of the previously closed PR Luce-Org#336 after a provenance audit; everything else from that branch (c2_gate, qwen3 drafter changes, structural-defense loaders, and the inadvertent reverts of Luce-Org#273/Luce-Org#295/Luce-Org#297) is either landing through its canonical PR (Luce-Org#274) or being dropped entirely.
Summary
Adds Gemma4 support to the C++ server target-layer-split path. Gemma4 can now use the same
LayerSplitBackendadapter flow as Qwen, so same-backend multi-GPU target placement is a reusable server capability rather than a Qwen-only path.Changes
arch=gemma4+--target-devicesinto a newGemma4LayerSplitAdapter, reusing the sharedLayerSplitBackendintroduced by feat(server): add target-layer-split backend adapter path #265.LayerSplitLoadPlan: each shard uploads only its owned block tensors, while the final shard owns output norm / lm-head tensors.Notes
hip:0,hip:1with prefix-cache hit, and the existing Qwen3.6 27B target-layer-split path passed the same HIP regression smoke.