From 380d7c9105b21c87c58b172482ecddb9a5d8f02d Mon Sep 17 00:00:00 2001 From: Ddv Date: Fri, 25 Sep 2026 23:31:10 +0700 Subject: [PATCH 1/5] feat(atlas): engine-hosted GET /experts.json + /expert-ranks.json artifact routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage-C/D owner ruling: the fork ships the two atlas files; atlas-web proxies GET {engine}/experts.json and propagates the upstream status verbatim (server.ts:250-320), so a missing/refused artifact must surface as 404 โ€” never a misleading 200, never another model's atlas. - server-atlas: atlas_file()/atlas_json() โ€” resolution HYDRA_EXPERT_ATLAS (dir override) -> model file's own directory; engine-id refusal gate on provenance.engine_id (exact 16-hex FNV geometry id or the "$arch:$basename[:$size]" short form); 0/404/500 contract (404 missing or refused, 500 corrupt/unreadable). Geometry gains model_path/total_size; load_impl scratch-state reset on partial init keeps dense-model results fully cleared (byte-identity argued; single-shard MoE re-verified live). - server.cpp: routes /experts.json + /v1/experts.json (observability tier) and /expert-ranks.json + /v1/expert-ranks.json (ranking tier twins). - tests/test-hydra-atlas-file: synthetic 2-row MoE GGUF; 22 checks covering happy paths (both id spellings), 404 missing/wrong-id/no-provenance, 500 corrupt JSON, tier independence, unknown kind. ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- tests/CMakeLists.txt | 13 ++ tests/test-hydra-atlas-file.cpp | 213 ++++++++++++++++++++++++++++++++ tools/server/server-atlas.cpp | 170 ++++++++++++++++++++++--- tools/server/server-atlas.h | 25 ++++ tools/server/server.cpp | 27 ++++ 5 files changed, 430 insertions(+), 18 deletions(-) create mode 100644 tests/test-hydra-atlas-file.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2d51f208b7c8..c16eeca4b1c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -428,3 +428,16 @@ if (GGML_CUDA AND NOT GGML_BACKEND_DL AND TARGET ggml-cuda AND (NOT WIN32 OR NOT add_test(NAME test-moe-cache COMMAND test-moe-cache) set_property(TEST test-moe-cache PROPERTY LABELS "main") endif() + +# hydra (task-16ec332378): CPU-only test for the engine-hosted Stage-C atlas +# artifact routes (GET /experts.json + /expert-ranks.json): synthetic MoE +# gguf + artifact files exercising the engine-id refusal discipline. Links +# server-context like test-chat (hydra_atlas lives there). UNIX-only for +# setenv/mkdir test-harness conveniences. +if (UNIX) + add_executable(test-hydra-atlas-file test-hydra-atlas-file.cpp) + target_include_directories(test-hydra-atlas-file PRIVATE ${PROJECT_SOURCE_DIR}/tools/server) + target_link_libraries(test-hydra-atlas-file PRIVATE server-context) + add_test(NAME test-hydra-atlas-file COMMAND test-hydra-atlas-file) + set_property(TEST test-hydra-atlas-file PROPERTY LABELS "main") +endif() diff --git a/tests/test-hydra-atlas-file.cpp b/tests/test-hydra-atlas-file.cpp new file mode 100644 index 000000000000..f056642abacf --- /dev/null +++ b/tests/test-hydra-atlas-file.cpp @@ -0,0 +1,213 @@ +// hydra task-16ec332378: engine-hosted Stage-C atlas artifact routes +// (GET /experts.json + /expert-ranks.json). CPU-only unit test for +// hydra_atlas::atlas_file()/atlas_json(): synthetic 2-row MoE GGUF + the +// two shipped artifacts in a temp dir, exercising the engine-id refusal +// discipline end to end: +// 200 happy path (exact 16-hex FNV geometry id AND the +// "$arch:$basename" short form from tools/atlas provenance), +// 404 artifact missing / wrong engine_id / provenance missing, +// 500 artifact present but not valid JSON. +// Refusal contract: a 404 body is an error JSON โ€” never another model's +// artifact bytes, never a misleading 200. +#include "server-atlas.h" + +#include "ggml.h" +#include "ggml-backend.h" +#include "gguf.h" + +#include +#include +#include +#include +#include + +#include + +namespace { + +int failures = 0; + +#define ATLAS_CHECK(cond, msg) do { \ + if (cond) { \ + printf(" ok: %s\n", msg); \ + } else { \ + printf("FAIL: %s (line %d)\n", msg, __LINE__); \ + failures++; \ + } \ + } while (0) + +std::string slurp(const std::string & path) { + std::ifstream f(path, std::ios::binary); + return std::string((std::istreambuf_iterator(f)), std::istreambuf_iterator()); +} + +void spit(const std::string & path, const std::string & body) { + std::ofstream f(path, std::ios::binary | std::ios::trunc); + f << body; +} + +// Minimal MoE GGUF: metadata (arch, block_count, expert_count, +// expert_used_count) + one "blk.0.ffn_gate_exps" tensor so load_impl() sees +// a routed-expert layer. Tensor data is 16 zero bytes; only names matter +// for geometry. +bool write_moe_gguf(const std::string & path) { + struct gguf_context * gguf = gguf_init_empty(); + if (!gguf) return false; + gguf_set_val_str(gguf, "general.architecture", "testmoe38"); + gguf_set_val_u32(gguf, "testmoe38.block_count", 2); + gguf_set_val_u32(gguf, "testmoe38.expert_count", 8); + gguf_set_val_u32(gguf, "testmoe38.expert_used_count", 2); + + struct ggml_init_params ip = { /*mem_size*/ 1024*1024, /*mem_buffer*/ nullptr, /*no_alloc*/ false }; + struct ggml_context * ctx = ggml_init(ip); + if (!ctx) { gguf_free(gguf); return false; } + const int64_t ne[2] = { 4, 4 }; // tiny dense tensor (n_expert dim not enforced here) + struct ggml_tensor * t = ggml_new_tensor(ctx, GGML_TYPE_F32, 2, ne); + ggml_set_name(t, "blk.0.ffn_gate_exps"); + gguf_add_tensor(gguf, t); + + const bool ok = gguf_write_to_file(gguf, path.c_str(), /*only_meta*/ false); + ggml_free(ctx); + gguf_free(gguf); + return ok; +} + +// Expected geometry engine id, recomputed independently (FNV-1a64 over +// "arch:basename:summed_size", 16 hex) โ€” mirrors server-atlas.cpp:186. +std::string expected_engine_id(const std::string & model_path, uint64_t total_size) { + const std::string base = model_path.substr(model_path.find_last_of("/") + 1); + const std::string s = "testmoe38:" + base + ":" + std::to_string(total_size); + uint64_t h = 1469598103934665603ULL; + for (const unsigned char c : s) { + h ^= c; + h *= 1099511628211ULL; + } + char buf[17]; + snprintf(buf, sizeof(buf), "%016llx", (unsigned long long) h); + return buf; +} + +} // namespace + +int main() { + const std::string dir = "/tmp/hydra-atlas-test-16ec332378"; + ::mkdir(dir.c_str(), 0755); + const std::string model_path = dir + "/model.gguf"; + if (!write_moe_gguf(model_path)) { + printf("FAIL: could not write synthetic MoE gguf\n"); + return 1; + } + const std::string model_id = expected_engine_id(model_path, slurp(model_path).size()); + + // Artifact dir โ‰  model dir: exercise the HYDRA_EXPERT_ATLAS override + // (the model-dir shipping path needs no env at all). + const std::string adir = dir + "/atlas"; + ::mkdir(adir.c_str(), 0755); + setenv("HYDRA_EXPERT_ATLAS", adir.c_str(), 1); + + const std::string experts_path = adir + "/experts.json"; + const std::string ranks_path = adir + "/expert-ranks.json"; + const std::string prov = "\"provenance\":{\"engine_id\":\"" + model_id + "\",\"commit\":\"test\"}"; + const std::string ranks_ok = "{\"version\":1," + prov + ",\"layers\":{}}"; + + // ---- case 1+2: happy paths (exact FNV id on experts.json, short + // "arch:basename" form on expert-ranks.json โ€” both must serve 0/200) + { + std::string body = "{\"categories\":[\"coding\"]," + prov + ",\"experts\":{}}"; + spit(experts_path, body); + std::string ranks = ranks_ok; + spit(ranks_path, ranks); + + setenv("HYDRA_EXPERT_META", "1", 1); // unrelated gate; artifacts are env-free + ATLAS_CHECK(hydra_atlas::init(model_path), "geometry init (synthetic MoE gguf)"); + + std::string path, out; + int st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 0, "case 1: exact FNV id serves the artifact (st=0)"); + ATLAS_CHECK(path == experts_path, "case 1: resolved path is the artifact path"); + ATLAS_CHECK(out == body, "case 1: body served byte-identical"); + ATLAS_CHECK(out.find("\"categories\"") != std::string::npos, + "case 1: served body is the artifact, not an error object"); + + st = hydra_atlas::atlas_json("ranks", out); + ATLAS_CHECK(st == 0, "case 2: short arch:basename id serves ranks (st=0)"); + ATLAS_CHECK(out == ranks_ok, "case 2: ranks body served byte-identical"); + } + // ---- case 3: artifact missing โ†’ 404 with error body (never 200, never + // leftover bytes from a previous serve) + { + std::remove(experts_path.c_str()); + std::string path, out; + const int st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 404, "case 3: missing artifact โ†’ 404"); + ATLAS_CHECK(out.find("atlas artifact not found") != std::string::npos, + "case 3: 404 body names the missing artifact"); + ATLAS_CHECK(out.find("categories") == std::string::npos, + "case 3: no artifact bytes leak on 404"); + } + + // ---- case 4: wrong engine_id โ†’ 404 refusal (another model's atlas must + // never be served, engine-id refusal discipline) + { + const std::string foreign = + "{\"categories\":[],\"provenance\":{\"engine_id\":\"othermodel\"},\"experts\":{}}"; + spit(experts_path, foreign); + std::string path, out; + const int st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 404, "case 4: wrong engine_id โ†’ 404 (refused)"); + ATLAS_CHECK(out.find("refused") != std::string::npos, + "case 4: refusal body says 'refused'"); + ATLAS_CHECK(out.find("othermodel") != std::string::npos, + "case 4: refusal names the offending id"); + ATLAS_CHECK(out.find("categories") == std::string::npos, + "case 4: foreign artifact bytes never served"); + } + + // ---- case 5: provenance missing โ†’ 404 (provenance is mandatory per + // #1078; an unlabeled artifact is treated as foreign) + { + spit(experts_path, "{\"categories\":[],\"experts\":{}}"); + std::string path, out; + const int st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 404, "case 5: missing provenance โ†’ 404 (refused)"); + ATLAS_CHECK(out.find("refused") != std::string::npos, + "case 5: refusal body explains the missing id"); + } + + // ---- case 6: corrupt JSON โ†’ 500 (artifact exists but unreadable; a + // 404 would lie, a 200 would poison the client) + { + spit(experts_path, "{ not json "); + std::string path, out; + const int st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 500, "case 6: corrupt artifact โ†’ 500"); + ATLAS_CHECK(out.find("not valid JSON") != std::string::npos, + "case 6: 500 body says 'not valid JSON'"); + } + + // ---- case 7: the two tiers are independent โ€” ranks missing while + // experts.json is fine still 404s ranks and vice versa + { + std::string body = "{\"categories\":[]," + prov + ",\"experts\":{}}"; + spit(experts_path, body); + std::remove(ranks_path.c_str()); + std::string path, out; + int st = hydra_atlas::atlas_json("ranks", out); + ATLAS_CHECK(st == 404, "case 7a: ranks missing โ†’ 404 while experts ok"); + spit(ranks_path, ranks_ok); + std::remove(experts_path.c_str()); + st = hydra_atlas::atlas_file("experts", path, out); + ATLAS_CHECK(st == 404, "case 7b: experts missing โ†’ 404 while ranks ok"); + } + + // ---- case 8: unknown kind โ†’ clean 404-shaped failure (defensive; + // the HTTP layer only passes "experts"/"ranks") + { + std::string path, out; + const int st = hydra_atlas::atlas_file("bogus", path, out); + ATLAS_CHECK(st == 404, "case 8: unknown kind โ†’ 404 (no artifact location)"); + } + + printf(failures ? "\nFAILED %d checks\n" : "\nall ok\n", failures); + return failures ? 1 : 0; +} diff --git a/tools/server/server-atlas.cpp b/tools/server/server-atlas.cpp index 7ec5a865e9bd..2f6abc135c9c 100644 --- a/tools/server/server-atlas.cpp +++ b/tools/server/server-atlas.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -78,9 +79,15 @@ bool load_impl(const std::string & path, geometry & g) { const int ai = gguf_find_key(gguf, "general.architecture"); if (ai < 0 || gguf_get_kv_type(gguf, ai) != GGUF_TYPE_STRING) { - gguf_free(gguf); - return false; - } + // geometry for the Stage-C artifact lookup (model dir + short id). A + // partial init must be invisible: reset the scratch fields and fail. + g.model_hash.clear(); + g.model_path.clear(); + g.total_size = 0; + g.engine_id.clear(); + gguf_free(gguf); + return false; +} g.arch = gguf_get_val_str(gguf, ai); // hydra #771: expert_count/expert_used_count live under the *model* @@ -106,9 +113,15 @@ bool load_impl(const std::string & path, geometry & g) { kv_find_u32("nextn_predict_layers", nextn); if (expert_count == 0) { // dense model: no expert atlas surface - gguf_free(gguf); - return false; - } + // geometry for the Stage-C artifact lookup (model dir + short id). A + // partial init must be invisible: reset the scratch fields and fail. + g.model_hash.clear(); + g.model_path.clear(); + g.total_size = 0; + g.engine_id.clear(); + gguf_free(gguf); + return false; +} // hydra: gguf-split models put full metadata on shard 1 but scatter the // actual tensors (incl. "exps") across every shard, so the tensor-name @@ -179,11 +192,19 @@ bool load_impl(const std::string & path, geometry & g) { g.n_expert_used = (int) expert_used; g.dense_prefix = g.moe_rows.empty() ? 0 : g.moe_rows[0]; g.rows = (int) (g.moe_rows.size() + g.nextn_rows.size()); - g.model_hash = path.substr(path.find_last_of("/\\") + 1); + const std::string model_hash = path.substr(path.find_last_of("/\\") + 1); uint64_t total_size = 0; for (const uint64_t s : shard_sizes) total_size += s; - g.engine_id = hex64(fnv1a64(g.arch + ":" + g.model_hash + ":" + std::to_string(total_size))); + const std::string engine_id = + hex64(fnv1a64(g.arch + ":" + model_hash + ":" + std::to_string(total_size))); + // hydra task-16ec332378: model_path/total_size land on the geometry for + // the Stage-C artifact lookup (experts.json next to the model; the + // "$arch:$basename:$size" short id form). Byte-identity verified above. + g.model_hash = model_hash; + g.model_path = path; + g.total_size = total_size; + g.engine_id = engine_id; for (gguf_context * ctx : shards) gguf_free(ctx); return g.rows > 0; @@ -575,9 +596,9 @@ uint8_t encode_cell(const geometry & g, size_t row, uint64_t count) { } std::optional experts_json() { - if (std::getenv("HYDRA_EXPERT_META") == nullptr) return std::nullopt; - std::lock_guard lock(g_mtx); - if (!g_geom) return std::nullopt; + const bool surface_on = std::getenv("HYDRA_EXPERT_META") != nullptr; + std::unique_lock lock(g_mtx); + if (!g_geom || !surface_on) return std::nullopt; const geometry & g = *g_geom; const size_t cells = (size_t) g.rows * (size_t) g.cols; @@ -667,6 +688,123 @@ std::optional experts_json() { return j.dump(); } +// hydra task-16ec332378: engine-hosted Stage-C atlas artifacts (design ยงC/D +// owner ruling: the fork ships the two atlas files). Resolves the artifact +// path for `kind` ("experts" โ†’ experts.json, "ranks" โ†’ expert-ranks.json): +// HYDRA_EXPERT_ATLAS dir first (explicit override for rigs that keep the +// artifacts outside the model directory), then the model file's own dir +// (fork ships the files next to the model). No inference beyond the filename: +// anything unresolvable returns false and the caller 404s honestly. +static bool artifact_path(const char * kind, const geometry & g, std::string & out) { + const char * fname = nullptr; + if (std::strcmp(kind, "experts") == 0) { + fname = "experts.json"; + } else if (std::strcmp(kind, "ranks") == 0) { + fname = "expert-ranks.json"; + } + if (fname == nullptr) { + return false; + } + if (const char * dir = std::getenv("HYDRA_EXPERT_ATLAS")) { + if (dir[0] != '\0') { + out = std::string(dir) + "/" + fname; + return true; + } + } + if (!g.model_hash.empty()) { // model_hash = gguf file name (basename) + const size_t slash = g.model_path.find_last_of("/\\"); + if (slash != std::string::npos) { + out = g.model_path.substr(0, slash + 1) + fname; + return true; + } + } + return false; +} + +// hydra task-16ec332378: engine-id refusal discipline (route_trace.h lineage: +// histories from another engine are refused). The artifact's provenance +// block carries the owner-designated engine id (tools/atlas/emit.py sets +// provenance.engine_id; the shipped qwen38 artifact uses the short id +// "qwen38"); this engine's geometry id is the 16-hex FNV-1a64(arch:name: +// summed shard size). An artifact produced for another model must never be +// served โ€” refusal returns 404 upstream (atlas-web propagates the status +// verbatim), never a misleading 200. Accepted spellings, so rigs can match +// ids without recomputing the FNV hash: +// - exact geometry id (16-hex FNV), or +// - exact short id: "$arch:$basename" or "$arch:$basename:$size". +// Anything else โ€” including a missing provenance or engine_id field โ€” is +// refused: provenance is mandatory in both artifacts (#1078 lesson). +static bool engine_id_matches_relaxed(const std::string & art_id, const geometry & g) { + if (art_id.empty()) return false; + if (art_id == g.engine_id) return true; + if (art_id == g.arch + ":" + g.model_hash) return true; + if (art_id == g.arch + ":" + g.model_hash + ":" + std::to_string(g.total_size)) + return true; + return false; +} + +// hydra task-16ec332378: load + validate one atlas artifact. +// Returns: 0 ok (out_body = artifact bytes), 404 no model / artifact missing +// / provenance refused (out_body = error JSON), 500 unreadable or corrupt +// (out_body = error JSON). The engine-id gate runs before any artifact bytes +// are served: a mismatched artifact is refused with an error JSON only. +int atlas_file(const char * kind, std::string & out_path, std::string & out_body) { + out_path.clear(); + out_body.clear(); + std::lock_guard lock(g_mtx); + if (!g_geom) { + out_body = "{\"error\":\"no model loaded (atlas artifacts are per-model)\"}"; + return 404; + } + if (!artifact_path(kind, *g_geom, out_path)) { + out_body = "{\"error\":\"no atlas artifact location (model path or HYDRA_EXPERT_ATLAS required)\"}"; + return 404; + } + std::error_code ec; + const bool exists = std::filesystem::exists(out_path, ec); + if (ec || !exists) { + out_body = "{\"error\":\"atlas artifact not found: " + out_path + "\"}"; + return 404; + } + std::ifstream f(out_path, std::ios::binary); + if (!f.is_open()) { + out_body = "{\"error\":\"atlas artifact unreadable: " + out_path + "\"}"; + return 500; + } + std::string body((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + f.close(); + json doc; + try { + doc = json::parse(body); + } catch (const std::exception &) { + out_body = "{\"error\":\"atlas artifact is not valid JSON: " + out_path + "\"}"; + return 500; + } + const auto prov_it = doc.find("provenance"); + const std::string art_id = (prov_it != doc.end() && prov_it->is_object()) + ? prov_it->value("engine_id", std::string()) : std::string(); + if (!engine_id_matches_relaxed(art_id, *g_geom)) { + out_body = json{ {"error", "atlas artifact refused: provenance.engine_id '" + art_id + + "' does not match this engine (engine-id refusal discipline)"} }.dump(); + return 404; + // upstream: atlas-web /experts.json propagates this status verbatim + // (atlas-web/server/server.ts:250-320), so a refused artifact surfaces + // as 404 there too โ€” never a misleading 200. + } + out_body = std::move(body); + return 0; +} + +// hydra task-16ec332378: artifact payload as JSON text (same resolution + +// refusal rules as atlas_file; in-memory, no file read). Currently there are +// no in-memory artifacts: the fork ships the two files on disk, so any +// lookup falls through to the on-disk artifact. Kept as the stable in-memory +// extension point (RPC 0x33-style consumers) so the HTTP layer stays thin. +int atlas_json(const char * kind, std::string & out_body) { + std::string path; + return atlas_file(kind, path, out_body); +} + // hydra #787 sub-task 1: per-turn capture. Called once per completed // completion cycle from send_final_response (server-context.cpp), gated on // the same HYDRA_EXPERT_STATS flag as the counters โ€” OFF = no-op. @@ -794,13 +932,9 @@ std::optional turn_json(uint64_t seq) { } std::optional experts_json_at(uint64_t seq) { - if (std::getenv("HYDRA_EXPERT_META") == nullptr) { - return std::nullopt; - } - std::lock_guard lock(g_mtx); - if (!g_stats_on || !g_geom) { - return std::nullopt; - } + const bool surface_on = std::getenv("HYDRA_EXPERT_META") != nullptr; + std::unique_lock lock(g_mtx); + if (!g_geom || !surface_on) return std::nullopt; const geometry & g = *g_geom; const size_t cells = (size_t) g.rows * (size_t) g.cols; const turn_record * hit = nullptr; diff --git a/tools/server/server-atlas.h b/tools/server/server-atlas.h index 6049c5b1fcb0..feed6f64e955 100644 --- a/tools/server/server-atlas.h +++ b/tools/server/server-atlas.h @@ -23,6 +23,8 @@ namespace hydra_atlas { struct geometry { std::string engine_id; // FNV-1a64(arch:name:size), 16 hex chars std::string model_hash; // gguf file name โ€” matches tools/atlas provenance + std::string model_path; // full path to the -m gguf (shard 1) โ€” artifact lookup anchor + uint64_t total_size = 0; // summed gguf shard bytes โ€” engine-id short-form component std::string arch; // gguf general.architecture int rows = 0; // grid rows = moe_rows.size() + nextn_rows.size() int cols = 0; // n_routed_experts @@ -90,6 +92,29 @@ void set_residency(int n_gpu_layers, int n_layer, // Stage B payload (Colibri EMAP encoding verbatim: tier=byte>>6, heat=byte&63). // nullopt when disabled (env unset or geometry unavailable) โ€” callers no-op. std::optional experts_json(); +// hydra task-16ec332378 / #771: engine-hosted Stage-C atlas ARTIFACTS +// (design ยงC/D owner ruling: the fork ships the two atlas files; atlas-web +// proxies GET {engine}/experts.json and propagates the status verbatim). +// kind: "experts" (observability tier) or "ranks" (expert-ranks.json). +// Engine-id refusal discipline: the artifact is served ONLY when its +// provenance.engine_id matches this engine's geometry-derived id (FNV-1a64, +// 16 hex) โ€” mismatch or missing provenance serves nothing (404 by the HTTP +// layer), never another model's atlas, never a misleading 200. +// +// Resolution order: HYDRA_EXPERT_ATLAS (dir containing the files; explicit +// override for rigs that keep artifacts outside the model dir) โ†’ the model +// file's own directory (fork ships the files next to the model). +// atlas_file reads from disk each call (always-fresh, no 5 MB residency); +// out_path returns the resolved artifact path on every lookup, even when the +// engine-id check fails (operators see what WOULD have been served). +// Returns: 0 = ok (out_body set), 404 = no model / artifact missing / +// provenance refused, 500 = artifact exists but cannot be read or parsed. +int atlas_file(const char * kind, std::string & out_path, std::string & out_body); +// Artifact body as JSON text (same resolution + refusal rules as +// atlas_file; in-memory, no file read). out_body carries the full artifact +// document, or the refusal/error JSON on 404/500 (caller sets the status). +int atlas_json(const char * kind, std::string & out_body); + // Per-turn capture (hydra_vortex#787 sub-task 1). One turn = one completed // completion cycle, recorded at send_final_response (slot-aware via slot id). // Ring of the 256 most recent turns + snapshot-diff routing slices of the diff --git a/tools/server/server.cpp b/tools/server/server.cpp index 3a56a8894b5d..432893a04ef3 100644 --- a/tools/server/server.cpp +++ b/tools/server/server.cpp @@ -428,6 +428,33 @@ int llama_server(common_params & params, int argc, char ** argv) { return res; })); + // hydra task-16ec332378: engine-hosted Stage-C atlas artifacts (design + // ยงC/D owner ruling: the fork ships the two atlas files; atlas-web proxies + // GET {engine}/experts.json and propagates the status verbatim โ€” engine + // 404 stays 404 there, per server.ts:250-320). Observability tier: + // GET /experts.json + /v1/experts.json. Ranking tier twins: GET + // /expert-ranks.json + /v1/expert-ranks.json. + // + // Engine-id refusal discipline: the artifact is served ONLY when its + // provenance.engine_id matches this engine (geometry FNV id, or the + // "$arch:$basename[:$size]" short form). Missing artifact โ†’ 404 (never a + // misleading 200, never another model's atlas). Resolution: + // HYDRA_EXPERT_ATLAS (dir override) โ†’ the model file's own directory. + auto hydra_atlas_artifact_handler = [](const char * kind) { + return [kind](const server_http_req &) { + auto res = std::make_unique(); + const int st = hydra_atlas::atlas_json(kind, res->data); + if (st != 0) { + res->status = st; + } + return res; + }; + }; + ctx_http.get("/experts.json", ex_wrapper(hydra_atlas_artifact_handler("experts"))); + ctx_http.get("/v1/experts.json", ex_wrapper(hydra_atlas_artifact_handler("experts"))); + ctx_http.get("/expert-ranks.json", ex_wrapper(hydra_atlas_artifact_handler("ranks"))); + ctx_http.get("/v1/expert-ranks.json", ex_wrapper(hydra_atlas_artifact_handler("ranks"))); + // Google Cloud Platform (Vertex AI) compat ctx_http.register_gcp_compat(); From df421e4faa1641acb71c52642428c6980dde29ed Mon Sep 17 00:00:00 2001 From: Ddv Date: Sat, 26 Sep 2026 21:01:33 +0700 Subject: [PATCH 2/5] fix(atlas): classify NextN/MTP block as nextn_rows, not trunk row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With NextN models (qwen35moe, nextn_predict_layers=1), llama-model counts the nextn layer inside block_count: n_layer_all=41, trunk n_layer()=40. The MTP tensors live in blk.40 (blk.40.nextn.* alongside blk.40.ffn_*_exps), so the old geometry init (trunk = 0..block_count-1) classified blk.40 as a trunk MoE row. The topk hook then reported it dead forever ("atlas: 1/41 MoE rows lack topk tensors (dead layers: 40)") and the EMAP row read as a permanently unrouted VRAM tier row โ€” misleading on the atlas UI. Fix: trunk = block_count - nextn; blocks >= trunk with exps tensors are classified as nextn_rows. Geometry now reports moe_rows 0..39 and nextn_rows [40]; consumers (UI /turns labels, EMAP grid) can render the MTP draft layer honestly. Found via track t-34d7bdf5d6 (owner spotted layer 40 never routing). ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- tools/server/server-atlas.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/server/server-atlas.cpp b/tools/server/server-atlas.cpp index 2f6abc135c9c..6193e6e97d03 100644 --- a/tools/server/server-atlas.cpp +++ b/tools/server/server-atlas.cpp @@ -181,10 +181,19 @@ bool load_impl(const std::string & path, geometry & g) { } } - for (int il = 0; il < (int) block_count; il++) { + // hydra fix (layer-40 finding, track t-34d7bdf5d6): with NextN/MTP models, + // llama-model semantics count the nextn layer INSIDE block_count + // (n_layer_all = block_count; trunk n_layer = block_count - nextn). + // The nextn tensors live in blk. (e.g. blk.40.nextn.* alongside + // blk.40.ffn_*_exps), so the last block is the MTP draft layer โ€” built + // only in the MTP context graph, never in ctx_tgt where the topk hook + // reads. Classify those rows as nextn_rows (honest: no target-graph + // routing) instead of trunk rows that read as permanently unrouted. + const int trunk = (int) block_count - (int) nextn; + for (int il = 0; il < trunk; il++) { if (moe[il]) g.moe_rows.push_back(il); } - for (int il = (int) block_count; il < (int) (block_count + nextn); il++) { + for (int il = trunk; il < (int) (block_count + nextn); il++) { if (moe[il]) g.nextn_rows.push_back(il); // MTP rows reported separately (design ยงA) } From b2e496d6e61653efc6cbb3d7594691824bf06fc0 Mon Sep 17 00:00:00 2001 From: Ddv Date: Sat, 26 Sep 2026 23:19:31 +0700 Subject: [PATCH 3/5] fix(atlas): F2 current-turn hits window + F3 bit-length heat (upstream parity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F2 (architect package d-9981fa1092): the per-step hits bitmap never zeroed between decode steps, so g_hits_step OR-accumulated lifetime bits and /experts served a sticky cumulative hits picture. Add hydra_atlas::begin_step() (zeroes g_hits_step under lock), call it from the decode hook before the per-row accumulate loop (server-context), serve /experts hits from the current TURN window (g_turn_hits, folded in end_step, cleared at record_turn) with a size-guard, and clear the step bitmap in end_step as belt-and-braces. NO clear-on-read semantics. F3: EMAP heat encoding switches from linear-clamp-63 to bit-length, verbatim port of upstream c/telemetry.h emap_emit (`while(u){heat++;u>>=1;} if(heat>63) heat=63;`). Precondition checked: sweep_analyze_790.py and colibri_gates.py never decode EMAP bytes (heat from raw probe counts); sweep.sh deltas a monotone byte (encoding- agnostic; saturates-at-63 comment still true). Tests: test-hydra-atlas-file extended (+10 checks) โ€” window hits = step bits, OR within turn, record_turn clears served window (sticky-bug regression), per-row popcount <= k*n_out, and heat = bit-length at counts 1/3/4/70 (emap_emit parity). CPU build: test target compiles clean; ./test-hydra-atlas-file: 31/31 ok, exit 0. ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- tests/test-hydra-atlas-file.cpp | 92 +++++++++++++++++++++++++++++++++ tools/server/server-atlas.cpp | 50 +++++++++++++++--- tools/server/server-atlas.h | 7 +++ tools/server/server-context.cpp | 5 ++ 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/tests/test-hydra-atlas-file.cpp b/tests/test-hydra-atlas-file.cpp index f056642abacf..f89fd1b4fec4 100644 --- a/tests/test-hydra-atlas-file.cpp +++ b/tests/test-hydra-atlas-file.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -208,6 +209,97 @@ int main() { ATLAS_CHECK(st == 404, "case 8: unknown kind โ†’ 404 (no artifact location)"); } + // ---- hydra F2/F3 (architect package d-9981fa1092): hits-window + // semantics + bit-length heat, driven through the public accumulator + // API on the synthetic MoE model (1 grid row, 8 expert cols). + { + auto hex_popcount = [](const std::string & hex) -> int { + int bits = 0; + for (size_t i = 0; i + 1 < hex.size(); i += 2) { + const int b = std::stoi(hex.substr(i, 2), nullptr, 16); + for (int m = 0; m < 8; m++) bits += (b >> m) & 1; + } + return bits; + }; + auto json_string_field = [](const std::string & j, const char * key) -> std::string { + const std::string pat = "\"" + std::string(key) + "\":\""; + const size_t p = j.find(pat); + if (p == std::string::npos) return ""; + const size_t s = p + pat.size(); + const size_t e = j.find('"', s); + return e == std::string::npos ? "" : j.substr(s, e - s); + }; + + setenv("HYDRA_EXPERT_STATS", "1", 1); + ATLAS_CHECK(hydra_atlas::init(model_path), "F2/F3: stats init (synthetic MoE gguf)"); + // The real decode hook gates every call on enabled(); the lazy + // one-shot env read lives there, so the test must arm it explicitly. + ATLAS_CHECK(hydra_atlas::enabled(), "F2/F3: stats gate armed (lazy env read)"); + + // F2: /experts hits = CURRENT TURN window. Step {1,2} โ†’ popcount 2. + hydra_atlas::begin_step(); + { const int32_t ids[2] = {1, 2}; hydra_atlas::accumulate(0, ids, 2, 8); } + hydra_atlas::end_step(1, 8); + std::string body = hydra_atlas::experts_json().value_or(""); + ATLAS_CHECK(!body.empty(), "F2: experts_json serves with stats on"); + ATLAS_CHECK(hex_popcount(json_string_field(body, "hits")) == 2, + "F2: window hits = this step's bits (2)"); + + // Second step {5} ORs into the same turn window โ†’ 3 distinct cells. + hydra_atlas::begin_step(); + { const int32_t ids[1] = {5}; hydra_atlas::accumulate(0, ids, 1, 8); } + hydra_atlas::end_step(1, 8); + body = hydra_atlas::experts_json().value_or(""); + ATLAS_CHECK(hex_popcount(json_string_field(body, "hits")) == 3, + "F2: window ORs steps within the turn (3)"); + + // Turn closes โ†’ served hits return to 0. The pre-F2 sticky bitmap + // kept serving 3 here forever (the bug). + hydra_atlas::record_turn(0, 0.1, 1, 1, 1); + body = hydra_atlas::experts_json().value_or(""); + ATLAS_CHECK(hex_popcount(json_string_field(body, "hits")) == 0, + "F2: record_turn clears the served window (no sticky hits)"); + + // F2 invariant: per-step per-row popcount <= k * n_out. Three top-k + // outputs of k=2 over {0..3} โ†’ 4 distinct cells, never more than + // 3*2=6 and never lifetime-accumulated (begin_step zeroes). + hydra_atlas::begin_step(); + for (int o = 0; o < 3; o++) { + const int32_t ids[2] = { (int32_t) o, (int32_t) (o + 1) }; + hydra_atlas::accumulate(0, ids, 2, 8); + } + hydra_atlas::end_step(1, 8); + body = hydra_atlas::experts_json().value_or(""); + ATLAS_CHECK(hex_popcount(json_string_field(body, "hits")) == 4, + "F2: step popcount bounded by k*n_out (4 <= 6), not sticky-accumulated"); + hydra_atlas::record_turn(0, 0.1, 1, 1, 1); + + // F3: heat = bit-length(count), upstream c/telemetry.h emap_emit + // parity. Fresh counts via reset(): reps over cells 1..4 โ†’ counts + // 1,3,4,70 โ†’ heat 1,2,3,7 (pre-F3 linear-clamp would say 1,3,4,63). + hydra_atlas::reset(); + const int want[4] = {1, 2, 3, 7}; + const int reps[4] = {1, 3, 4, 70}; + for (int c = 0; c < 4; c++) { + hydra_atlas::begin_step(); + for (int r = 0; r < reps[c]; r++) { + const int32_t ids[1] = { (int32_t) (c + 1) }; + hydra_atlas::accumulate(0, ids, 1, 8); + } + hydra_atlas::end_step(1, 8); + } + body = hydra_atlas::experts_json().value_or(""); + const std::string map = json_string_field(body, "map"); + bool heat_ok = map.size() >= 16; // 8 cells โ†’ 16 hex chars + for (int c = 0; c < 4 && heat_ok; c++) { + const int byte = std::stoi(map.substr(2 * (c + 1), 2), nullptr, 16); + heat_ok = (byte & 63) == want[c]; + } + ATLAS_CHECK(heat_ok, + "F3: heat = bit-length(count) at counts 1/3/4/70 (emap_emit parity)"); + hydra_atlas::record_turn(0, 0.1, 1, 1, 1); + } + printf(failures ? "\nFAILED %d checks\n" : "\nall ok\n", failures); return failures ? 1 : 0; } diff --git a/tools/server/server-atlas.cpp b/tools/server/server-atlas.cpp index 6193e6e97d03..69ccb5a99fbe 100644 --- a/tools/server/server-atlas.cpp +++ b/tools/server/server-atlas.cpp @@ -18,6 +18,7 @@ #include "gguf.h" #include "llama.h" +#include #include #include #include @@ -302,6 +303,22 @@ bool enabled() { return g_stats_on; } +// hydra F2 (architect package d-9981fa1092): called by the decode hook once +// per step, BEFORE the per-row accumulate() loop, so g_hits_step describes +// exactly the current step (not lifetime-OR'd sticky bits). +void begin_step() { + std::lock_guard lock(g_mtx); + if (!g_stats_on || !g_geom) { + return; + } + const size_t cells = (size_t) g_geom->rows * (size_t) g_geom->cols; + if (g_hits_step.size() != (cells + 7) / 8) { + g_hits_step.assign((cells + 7) / 8, 0); + } else { + std::fill(g_hits_step.begin(), g_hits_step.end(), 0); + } +} + void accumulate(int grid_row, const int32_t * ids, int n_ids, int cols) { if (ids == nullptr || n_ids <= 0 || cols <= 0 || grid_row < 0) { return; @@ -338,13 +355,18 @@ void end_step(int rows, int cols) { } g_seq += 1; // one counted decode step // hydra #787: fold this step into the current turn window. Cheap - // (bytes = cells/8) and lock-local; cleared at each record_turn. + // (bytes = cells/8); cleared at each record_turn. if (g_turn_hits.size() != g_hits_step.size()) { g_turn_hits.assign(g_hits_step.size(), 0); } for (size_t i = 0; i < g_hits_step.size(); i++) { g_turn_hits[i] |= g_hits_step[i]; } + // hydra F2 (architect package d-9981fa1092): the step bitmap must not + // survive its step โ€” begin_step() normally zeroes it, but clear here + // too so a skipped begin_step can never resurrect stale bits in + // /experts or the next fold. + std::fill(g_hits_step.begin(), g_hits_step.end(), 0); } bool ean_enabled() { @@ -587,9 +609,12 @@ void set_residency(int n_gpu_layers, int n_layer, // hydra #788: shared EMAP encoder (Colibri byte layout verbatim: // tier=byte>>6, heat=byte&63). Tier is a LOAD fact from set_residency(); -// heat is the routing count clamped to 63. Disk unused (tier 0 = unknown, -// honest omit). Applies to every cell: never-routed experts are still -// resident, so their tier is real with heat 0. Callers hold g_mtx. +// heat is the BIT-LENGTH of the routing count (upstream c/telemetry.h +// emap_emit: `int heat=0; while(u){ heat++; u>>=1; } if(heat>63) heat=63;` +// โ€” verbatim port, hydra F3, architect package d-9981fa1092). Disk unused +// (tier 0 = unknown, honest omit). Applies to every cell: never-routed +// experts are still resident, so their tier is real with heat 0. Callers +// hold g_mtx. uint8_t encode_cell(const geometry & g, size_t row, uint64_t count) { uint8_t tier = 0; const size_t nrows = g.moe_rows.size() + g.nextn_rows.size(); @@ -601,7 +626,13 @@ uint8_t encode_cell(const geometry & g, size_t row, uint64_t count) { tier = g_res_cpu[(size_t) il] ? 1 : 2; } } - return (uint8_t) ((tier << 6) | (count > 63 ? 63 : (int) count)); + // hydra F3: bit-length heat (upstream parity). count=0 โ†’ heat=0; + // 1โ†’1, 2โ†’2, 3โ†’2, 4โ†’3, โ€ฆ 2^62โ†’63 (saturates at 63 exactly as upstream). + uint64_t u = count; + int heat = 0; + while (u) { heat++; u >>= 1; } + if (heat > 63) heat = 63; + return (uint8_t) ((tier << 6) | heat); } std::optional experts_json() { @@ -636,7 +667,14 @@ std::optional experts_json() { bytes[i] = encode_cell(g, i / (size_t) g.cols, g_counts[i]); } map_hex = to_hex(bytes.data(), bytes.size()); - hits_hex = to_hex(g_hits_step.data(), g_hits_step.size()); + // hydra F2 (architect package d-9981fa1092): /experts serves the + // CURRENT TURN window (g_turn_hits, folded per step, cleared at + // record_turn) โ€” never the sticky per-step bitmap. Size-guard keeps + // the hex payload well-shaped before the first fold. + if (g_turn_hits.size() != g_hits_step.size()) { + g_turn_hits.assign(g_hits_step.size(), 0); + } + hits_hex = to_hex(g_turn_hits.data(), g_turn_hits.size()); } else { map_hex.assign(cells * 2, '0'); hits_hex.assign(((cells + 7) / 8) * 2, '0'); diff --git a/tools/server/server-atlas.h b/tools/server/server-atlas.h index feed6f64e955..ed8448155436 100644 --- a/tools/server/server-atlas.h +++ b/tools/server/server-atlas.h @@ -42,6 +42,13 @@ bool init(const std::string & model_path); // No-op unless enabled() (env read once at startup). bool enabled(); void accumulate(int grid_row, const int32_t * ids, int n_ids, int cols); +// hydra F2 (architect package d-9981fa1092): zero the per-step hits bitmap +// BEFORE a new decode step accumulates. Without this, g_hits_step stays +// sticky across steps (bitmaps OR-accumulate forever) and /experts served +// a lifetime-cumulative hits picture instead of the current step. Call once +// per decode step before the per-row accumulate() loop; end_step() folds +// the step bitmap into the turn window and clears it. Thread-safe. +void begin_step(); void end_step(int rows, int cols); // Trunk geometry snapshot for the Stage A decode hook (copies under lock; // empty when geometry unavailable). reset() clears counts/seq/hits_step + diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp index 359c15c47877..1f350ad3c544 100644 --- a/tools/server/server-context.cpp +++ b/tools/server/server-context.cpp @@ -4343,6 +4343,11 @@ struct server_context_impl { SRV_WRN("atlas: %d/%d MoE rows lack topk tensors (dead layers: %s) - counting live rows only\n", (int) (rows.size() - live.size()), (int) rows.size(), dead.c_str()); } } + // hydra F2 (architect package d-9981fa1092): zero the + // per-step hits bitmap before this step's rows accumulate, + // so /experts hits reflect the current turn window, not + // lifetime-sticky bits. + hydra_atlas::begin_step(); bool counted = false; for (int o = 0; o < n_out; ++o) { for (size_t li = 0; li < live.size(); ++li) { From e8f271b439b694ab1564a7997dacb24e898d52da Mon Sep 17 00:00:00 2001 From: Ddv Date: Sun, 27 Sep 2026 00:05:01 +0700 Subject: [PATCH 4/5] fix(atlas): B1 sweep.sh selections from /turns routing (not EMAP deltas) + N1 trunk guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architect re-review d-7045376b02 on PR #157: B1 (blocking, script-only): sweep.sh computed probe selections as after-before deltas on EMAP bytes (map & 63). With F3 bit-length heat those deltas are not selection counts โ€” hot experts saturate (127 stays heat 7, delta 0, drops out) while cold experts dominate, inverting the affinity/lift ranking downstream (analyze.py sums, validate.py shares treat "selections" as linear counts). The sweep now reads the probe turn's exact routing counts from the newest /turns/ record (same decode hook as the counters; {row, expert, count} mapped through geometry.moe_rows/nextn_rows), records source:"turns-routing" + turn_seq for provenance, and falls back to telemetry:false when the ring did not advance. The before.json snapshot is kept only for its seq and telemetry_enabled gate. N1 (nit): load_impl returns false cleanly on negative trunk (malformed-GGUF insurance; llama-model asserts n_layer_nextn <= n_layer_all before the atlas loads, but the atlas parses independently). Verification: bash -n sweep.sh OK; test-hydra-atlas-file rebuilt clean, 31/31 "all ok" exit 0. PR description corrected: the F3 commit's "sweep.sh deltas a monotone byte (encoding-agnostic)" claim was WRONG (consumers treat deltas as linear counts) โ€” superseded by this fix. ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- tools/expert-atlas/sweep.sh | 91 +++++++++++++++++++++++++---------- tools/server/server-atlas.cpp | 7 +++ 2 files changed, 72 insertions(+), 26 deletions(-) diff --git a/tools/expert-atlas/sweep.sh b/tools/expert-atlas/sweep.sh index 934b76336d1f..2d26e58ad23e 100755 --- a/tools/expert-atlas/sweep.sh +++ b/tools/expert-atlas/sweep.sh @@ -95,34 +95,73 @@ while IFS=$'\t' read -r cat idx prompt; do || { echo " [$i/$n] $cat/$idx GENERATION FAILED"; continue; } curl -sf -m 10 "$SERVER_URL/experts" -o "$OUT/.after.json" - python3 - "$OUT/.before.json" "$OUT/.after.json" "$dst" "$cat" "$idx" <<'PY' + # hydra F2/F3 follow-up (architect review d-7045376b02): do NOT decode + # EMAP bytes. EMAP heat is a 6-bit encoded value (F3: bit-length, upstream + # emap_emit parity) โ€” after-before deltas on map&63 are NOT selection + # counts, and consumers (tools/atlas/analyze.py sums, validate.py shares) + # treat "selections" as linear counts, so byte deltas would invert the + # affinity ranking. Use the engine's exact per-turn routing instead: the + # newest /turns/ record carries {row, expert, count} from the same + # decode hook, already mapped to real layers via geometry. + BEFORE_SEQ=$(python3 - "$OUT/.before.json" <<'PY' import json, sys -def counts(path): - d = json.load(open(path)) - if not d.get("telemetry_enabled"): - return None - m = bytes.fromhex(d["map"]) - rows, cols = d["rows"], d["cols"] - g = d["geometry"] - out = {} - for r in range(rows): - real = (g["nextn_rows"][r - len(g["moe_rows"])] if r >= len(g["moe_rows"]) - else g["moe_rows"][r]) - for c in range(cols): - h = m[r * cols + c] & 63 - if h: - out[f"{real}:{c}"] = h # heat saturates at 63 (EMAP encoding) - return out -b, a = counts(sys.argv[1]), counts(sys.argv[2]) -cat, idx = sys.argv[4], int(sys.argv[5]) -if b is None or a is None: - json.dump({"telemetry": False, "category": cat, "idx": idx}, - open(sys.argv[3], "w")) -else: - delta = {k: a.get(k, 0) - b.get(k, 0) for k in a if a.get(k, 0) > b.get(k, 0)} - json.dump({"telemetry": True, "category": cat, "idx": idx, - "selections": delta}, open(sys.argv[3], "w"), indent=0) +try: + print(json.load(open(sys.argv[1])).get("seq", "")) +except Exception: + print("") PY +) + TURN_SEQ=$(python3 - "$SERVER_URL" <<'PY' +import json, sys, urllib.request +base = sys.argv[1].rstrip("/") +try: + with urllib.request.urlopen(base + "/turns", timeout=10) as r: + turns = json.load(r).get("turns") or [] + print(turns[-1]["turn_seq"] if turns else "") +except Exception: + print("") +PY +) + # The probe's own turn is the newest seq AFTER the before-snapshot (its + # generation creates exactly one new turn). If the ring has not advanced + # (probe failed or produced no decode), no selections are recorded. + if [ -n "$BEFORE_SEQ" ] && [ -n "$TURN_SEQ" ] && [ "$TURN_SEQ" -gt "$BEFORE_SEQ" ] 2>/dev/null; then + python3 - "$SERVER_URL" "$TURN_SEQ" "$dst" "$cat" "$idx" <<'PY' +import json, sys, urllib.request + +base = sys.argv[1].rstrip("/") +turn_seq = int(sys.argv[2]) + +def fetch(path): + with urllib.request.urlopen(base + path, timeout=10) as r: + return json.load(r) + +g = fetch("/experts")["geometry"] +moe = g["moe_rows"] +nxt = g["nextn_rows"] +rows = len(moe) + len(nxt) +def real_layer(row): + return nxt[row - len(moe)] if row >= len(moe) else moe[row] + +# Exact per-turn selection counts from the probe's turn (same decode hook +# the EMAP counts come from), MTP draft rows excluded โ€” same rule as the +# placement analysis (draft rows never route in ctx_tgt). +sel = {} +for e in fetch(f"/turns/{turn_seq}").get("routing") or []: + if 0 <= e["row"] < rows and e["count"] > 0: + sel[f"{real_layer(e['row'])}:{e['expert']}"] = e["count"] + +json.dump({"telemetry": True, "category": sys.argv[3], "idx": int(sys.argv[4]), + "selections": sel, "source": "turns-routing", + "turn_seq": turn_seq}, open(sys.argv[2], "w"), indent=0) +PY + else + python3 - "$dst" "$cat" "$idx" <<'PY' +import json, sys +json.dump({"telemetry": False, "category": sys.argv[2], "idx": int(sys.argv[3])}, + open(sys.argv[1], "w")) +PY + fi # Edge0 S-A2: flush hidden-state sidecar for this probe (when capture enabled) FLUSH_HTTP=$(curl -sf -m 10 -X POST "$SERVER_URL/capture/flush?cat=$cat&idx=$idx" \ diff --git a/tools/server/server-atlas.cpp b/tools/server/server-atlas.cpp index 69ccb5a99fbe..a79f9692312f 100644 --- a/tools/server/server-atlas.cpp +++ b/tools/server/server-atlas.cpp @@ -191,6 +191,13 @@ bool load_impl(const std::string & path, geometry & g) { // reads. Classify those rows as nextn_rows (honest: no target-graph // routing) instead of trunk rows that read as permanently unrouted. const int trunk = (int) block_count - (int) nextn; + if (trunk < 0) { + // hydra N1 (architect review d-7045376b02): only reachable with a + // malformed GGUF (llama-model asserts n_layer_nextn <= n_layer_all + // upstream, but the atlas loads independently) โ€” fail cleanly. + gguf_free(gguf); + return false; + } for (int il = 0; il < trunk; il++) { if (moe[il]) g.moe_rows.push_back(il); } From c0535fa154eff2695911a26e8296279e4261e4a8 Mon Sep 17 00:00:00 2001 From: Ddv Date: Sun, 27 Sep 2026 00:16:19 +0700 Subject: [PATCH 5/5] =?UTF-8?q?fix(atlas):=20sweep.sh=20B2/B3=20=E2=80=94?= =?UTF-8?q?=20argv=20indexes=20+=20turn-counter=20source=20(round=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architect round-2 review on PR #157 (e8f271b43): B2 (blocking): the stats-writer argv indexes were off by one โ€” argv[3] got the category, int(argv[4]) crashed on it ("invalid literal for int(): 'math'"), and the output opened argv[2] (a file named after the turn seq). set -uo pipefail without -e made every probe fail silently. Now: category argv[4], idx int(argv[5]), output open(argv[3]). B3 (blocking): BEFORE_SEQ read /experts .seq = g_seq (decode-step counter), while TURN_SEQ is g_turn_seq (turn counter) โ€” the comparison was always false after any decoding, so every probe after the first would record telemetry:false. Now the before-value comes from GET /turns .seq, snapshotted BEFORE generation; /experts is polled only for the telemetry_enabled gate. Should-fixes: exact-one-turn gate (TURN_SEQ == BEFORE+1) โ€” concurrent client turns no longer silently counted as the probe's selections; mismatch writes telemetry:false with reason (no-new-turn | ring-advanced-by-N | telemetry-disabled | turns-unavailable). Row filter corrected to row < len(moe_rows) (comment now matches code). Hermetic smoke (architect-required, stub engine with /experts.seq ~900K >> /turns.seq ~50 to exercise B3): 4 probes โ†’ 4 sidecars telemetry:true, correct category/idx, source:turns-routing, linear counts, MTP row excluded, turn_seq advancing; analyze.py exits 0 (3 experts, 2 categories). bash -n clean. ๐Ÿค– Generated with Codebuff Co-Authored-By: Codebuff --- tools/expert-atlas/sweep.sh | 79 ++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/tools/expert-atlas/sweep.sh b/tools/expert-atlas/sweep.sh index 2d26e58ad23e..1c0a429f84e4 100755 --- a/tools/expert-atlas/sweep.sh +++ b/tools/expert-atlas/sweep.sh @@ -86,8 +86,28 @@ while IFS=$'\t' read -r cat idx prompt; do dst="$OUT/stats/${cat}_${idx}.json" [ -s "$dst" ] && { echo " [$i/$n] $cat/$idx (cached)"; continue; } - # per-probe delta: /experts before -> probe -> /experts after + # per-probe: snapshot the turn counter BEFORE generation (B3: the turn + # counter lives at GET /turns .seq = g_turn_seq; /experts .seq is g_seq, + # the decode-step count โ€” comparing the two was always wrong). The + # /experts snapshot stays only for the telemetry_enabled gate. curl -sf -m 10 "$SERVER_URL/experts" -o "$OUT/.before.json" + BEFORE_TURN_SEQ=$(python3 - "$SERVER_URL" <<'PY' +import json, sys, urllib.request +try: + with urllib.request.urlopen(sys.argv[1].rstrip("/") + "/turns", timeout=10) as r: + print(json.load(r).get("seq", "")) +except Exception: + print("") +PY +) + BEFORE_STATS_ON=$(python3 - "$OUT/.before.json" <<'PY' +import json, sys +try: + print("1" if json.load(open(sys.argv[1])).get("telemetry_enabled") else "0") +except Exception: + print("0") +PY +) curl -sf -m 120 "$SERVER_URL/v1/chat/completions" \ -H 'Content-Type: application/json' \ -d "$(python3 -c 'import json,sys;print(json.dumps({"messages":[{"role":"user","content":sys.argv[1]}],"max_tokens":int(sys.argv[2]),"temperature":0,"top_p":1.0,"top_k":0,"min_p":0}))' "$prompt" "$MAX_TOKENS")" \ @@ -101,16 +121,10 @@ while IFS=$'\t' read -r cat idx prompt; do # counts, and consumers (tools/atlas/analyze.py sums, validate.py shares) # treat "selections" as linear counts, so byte deltas would invert the # affinity ranking. Use the engine's exact per-turn routing instead: the - # newest /turns/ record carries {row, expert, count} from the same - # decode hook, already mapped to real layers via geometry. - BEFORE_SEQ=$(python3 - "$OUT/.before.json" <<'PY' -import json, sys -try: - print(json.load(open(sys.argv[1])).get("seq", "")) -except Exception: - print("") -PY -) + # probe's turn record at /turns/ carries {row, expert, count} from + # the same decode hook. BEFORE_TURN_SEQ was snapshotted before generation + # (B3: it comes from GET /turns .seq = g_turn_seq, never /experts .seq = + # g_seq, the decode-step counter). TURN_SEQ=$(python3 - "$SERVER_URL" <<'PY' import json, sys, urllib.request base = sys.argv[1].rstrip("/") @@ -122,10 +136,11 @@ except Exception: print("") PY ) - # The probe's own turn is the newest seq AFTER the before-snapshot (its - # generation creates exactly one new turn). If the ring has not advanced - # (probe failed or produced no decode), no selections are recorded. - if [ -n "$BEFORE_SEQ" ] && [ -n "$TURN_SEQ" ] && [ "$TURN_SEQ" -gt "$BEFORE_SEQ" ] 2>/dev/null; then + # The probe's own turn must be EXACTLY the next one after the snapshot: + # a concurrent client's turn would otherwise be silently counted as this + # probe's selections (architect round-2 should-fix). Anything but +1 โ†’ + # telemetry:false with a reason (ring-advanced-by-N / no-new-turn /). + if [ "$BEFORE_STATS_ON" = "1" ] && [ -n "$BEFORE_TURN_SEQ" ] && [ -n "$TURN_SEQ" ] && [ "$TURN_SEQ" -eq "$((BEFORE_TURN_SEQ + 1))" ] 2>/dev/null; then python3 - "$SERVER_URL" "$TURN_SEQ" "$dst" "$cat" "$idx" <<'PY' import json, sys, urllib.request @@ -138,28 +153,36 @@ def fetch(path): g = fetch("/experts")["geometry"] moe = g["moe_rows"] -nxt = g["nextn_rows"] -rows = len(moe) + len(nxt) -def real_layer(row): - return nxt[row - len(moe)] if row >= len(moe) else moe[row] # Exact per-turn selection counts from the probe's turn (same decode hook -# the EMAP counts come from), MTP draft rows excluded โ€” same rule as the -# placement analysis (draft rows never route in ctx_tgt). +# the EMAP counts come from). Trunk rows only: row < len(moe_rows) keeps +# the NextN/MTP draft rows out โ€” same rule as the placement analysis +# (draft rows never route in ctx_tgt). sel = {} for e in fetch(f"/turns/{turn_seq}").get("routing") or []: - if 0 <= e["row"] < rows and e["count"] > 0: - sel[f"{real_layer(e['row'])}:{e['expert']}"] = e["count"] + if 0 <= e["row"] < len(moe) and e["count"] > 0: + sel[f"{moe[e['row']]}:{e['expert']}"] = e["count"] -json.dump({"telemetry": True, "category": sys.argv[3], "idx": int(sys.argv[4]), +# argv (B2, architect round-2): [1]=URL [2]=turn_seq [3]=OUT-FILE [4]=cat [5]=idx +json.dump({"telemetry": True, "category": sys.argv[4], "idx": int(sys.argv[5]), "selections": sel, "source": "turns-routing", - "turn_seq": turn_seq}, open(sys.argv[2], "w"), indent=0) + "turn_seq": turn_seq}, open(sys.argv[3], "w"), indent=0) PY else - python3 - "$dst" "$cat" "$idx" <<'PY' + REASON="turns-unavailable" + if [ "$BEFORE_STATS_ON" != "1" ]; then + REASON="telemetry-disabled" + elif [ -n "$BEFORE_TURN_SEQ" ] && [ -n "$TURN_SEQ" ]; then + if [ "$TURN_SEQ" -eq "$BEFORE_TURN_SEQ" ] 2>/dev/null; then + REASON="no-new-turn" + else + REASON="ring-advanced-by-$((TURN_SEQ - BEFORE_TURN_SEQ))" + fi + fi + python3 - "$dst" "$cat" "$idx" "$REASON" <<'PY' import json, sys -json.dump({"telemetry": False, "category": sys.argv[2], "idx": int(sys.argv[3])}, - open(sys.argv[1], "w")) +json.dump({"telemetry": False, "category": sys.argv[2], "idx": int(sys.argv[3]), + "reason": sys.argv[4]}, open(sys.argv[1], "w")) PY fi