Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
tokenizer_test think_split_test gguf_meta_test mem_admit_test pm_routes_test \
laya_encoder_test http_guard_test moe_gguf_index_test laya_bounds_test \
npu_model_bounds_test npu_forward_loader_test npu_sampling_test \
npu_flm_head_import_test \
npu_flm_head_import_test npu_elf_args_test \
tokenizer_linear_test hrx_placement_test hrx_cwsr_test npu_device_lock_test -j
- name: Test
# One hung test must not hold the job for hours: each test gets 10 minutes.
Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ add_dependencies(npu_sampling_test systemone_route_test)

# The plan.json loader (npu/plan/plan.h, engine #799 P1): schema, refusals and the decision fields that
# replaced the filename probes. No model, no container, no device.
add_executable(npu_elf_args_test tests/npu_elf_args_test.c npu/forward/src/q4nx_pack.c)
target_include_directories(npu_elf_args_test PRIVATE npu/forward/include)
target_link_libraries(npu_elf_args_test PRIVATE m)
add_test(NAME npu_elf_args COMMAND npu_elf_args_test)
add_executable(npu_plan_test tests/npu_plan_test.cpp)
target_include_directories(npu_plan_test PRIVATE ${CMAKE_SOURCE_DIR}/npu)
target_link_libraries(npu_plan_test PRIVATE nlohmann_json::nlohmann_json)
Expand Down
45 changes: 45 additions & 0 deletions npu/forward/engine-src/npu_engine_i8ctx_inc.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,55 @@ struct I8Ctx {
group_scales.resize(NL); // packB() writes group_scales[l]
for (int l = 0; l < NL; l++)
layerB[l] = mkbo(w_bytes());
if (!args_fit_elf(ep)) return false;
initialized = true;
return true;
}

// ── The launch contract, checked against the artifact ──
// A full ELF declares each argument's byte width in .dynsym (the driver patches those very
// symbols), and the kernel's shim DMA touches exactly that much. Passing a narrower buffer does
// not fail: the DMA walks past the mapping, the IOMMU logs IO_PAGE_FAULTs and the run returns
// garbage (measured 2026-11-02 on the 896-hidden OGA pack, whose ELF family is
// (weights, activations, output) = (655,360, 2,048, 4,096) B while the int8 launcher passed
// (16,384, 1,048,576, 65,536) B). The check is one-directional on purpose: a declared width may
// be *smaller* than the buffer (the tile-family ELFs address a tile of a larger BO through the
// control text), but it may never be larger.
bool args_fit_elf(const char* ep) {
std::vector<uint8_t> raw;
{
std::FILE* f = std::fopen(ep, "rb");
if (!f) return true; // cannot read it: nothing to check against
std::fseek(f, 0, SEEK_END);
const long len = std::ftell(f);
std::rewind(f);
if (len <= 0) { std::fclose(f); return true; }
raw.resize((size_t)len);
const size_t got = std::fread(raw.data(), 1, raw.size(), f);
std::fclose(f);
if (got != raw.size()) return true;
}
uint64_t declared[3] = {0, 0, 0};
if (q4nx_elf_arg_sizes(raw.data(), raw.size(), declared) < 0) return true; // not our shape
const uint64_t passed[3] = {(uint64_t)MD * (uint64_t)KD,
(uint64_t)w_bytes(),
(uint64_t)MD * (uint64_t)ND * 4u * (pb16i ? 2u : 1u)};
// I8Ctx carries no error string; the caller's err_ names the ELF, so print the numbers here.
static const char* const kWhat[3] = {"activations", "weights", "output"};
for (int i = 0; i < 3; i++) {
if (!declared[i] || declared[i] <= passed[i]) continue;
std::fprintf(stderr,
" I8Ctx::init_elf: %s declares argument %d (%s) = %llu B, this launch would "
"pass %llu B -- the kernel's DMA would run past the buffer (M=%d K=%d N=%d). "
"The artifact's argument width is its contract and this ELF family is not the "
"one this launcher drives; refusing before any dispatch.\n",
ep, i, kWhat[i], (unsigned long long)declared[i],
(unsigned long long)passed[i], MD, KD, ND);
return false;
}
return true;
}

// ── Resident-expert (MoE) helpers: pack/launch against an arbitrary BO ──
// Decode is M=1 with top-1 routing; re-streaming the selected expert's
// weights into a shared per-layer BO every token costs a memcpy + sync on
Expand Down
10 changes: 10 additions & 0 deletions npu/forward/include/q4nx_pack.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,17 @@ int q4nx_assemble_B(const Q4nxDesignGeom* g, const Q4nxProjections* p, float* B)
float q4nx_pack_B_int8(const float* B, int K, int N, int8_t* out, int ld,
float* col_scales);

/* The three buffer widths a full ELF's runtime sequence declares (its .dynsym STT_OBJECT symbols
* named "0".."2"), read from the artifact instead of assumed. A launcher must pass buffers at
* least that wide: the kernel's shim DMA touches exactly these widths, and a narrower buffer makes
* the DMA walk past the mapping (IO_PAGE_FAULTs, garbage results, no error). Measured 2026-11-02:
* a pack whose ELF family is (weights, activations, output) = (655,360, 2,048, 4,096) B driven by a
* launcher that passed (16,384, 1,048,576, 65,536) B. Returns the number of widths found (0..3) or
* -1 when the file is not a 32-bit LE ELF / has no .dynsym; undeclared entries stay 0. */
int q4nx_elf_arg_sizes(const void* elf, size_t n, uint64_t out[3]);

#ifdef __cplusplus

}
#endif
#endif /* NPU_INFER_Q4NX_PACK_H */
64 changes: 64 additions & 0 deletions npu/forward/src/q4nx_pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,67 @@ float q4nx_pack_B_int8(const float* B, int K, int N, int8_t* out, int ld,
}
return (float)(ssum / (double)N);
}

/* ── The launch contract: the argument widths a full ELF declares ───────────────────────────────
*
* An aiecc full ELF carries one STT_OBJECT symbol per kernel argument in .dynsym, named "0", "1",
* "2", whose st_size is the byte width the kernel's shim DMA touches — the driver patches those very
* symbols, so this is the artifact's own contract and not a copy of one. A launcher that passes a
* narrower buffer does not fail loudly: the DMA runs past the mapping, the IOMMU logs IO_PAGE_FAULTs
* and the kernel multiplies whatever follows (measured 2026-11-02 on the 896-hidden OGA pack, whose
* ELF family is (weights, activations, output) = (655,360, 2,048, 4,096) B while the int8 launcher
* passed (16,384, 1,048,576, 65,536) B: 639 KB of weight stream read past a 16 KB BO). Reading the
* widths here lets a load refuse instead of dispatching.
*
* Returns the number of argument widths found (0..3), or -1 when the file is not a 32-bit
* little-endian ELF or has no .dynsym. out[] entries that were not declared stay 0. */
int q4nx_elf_arg_sizes(const void* elf, size_t n, uint64_t out[3]) {
if (out) { out[0] = out[1] = out[2] = 0; }
if (!elf || !out || n < 52) return -1;
const uint8_t* b = (const uint8_t*)elf;
if (memcmp(b, "\x7f" "ELF", 4) || b[4] != 1 || b[5] != 1) return -1; /* ELF32, LSB */
#define RD16(p) ((uint32_t)(p)[0] | ((uint32_t)(p)[1] << 8))
#define RD32(p) ((uint64_t)(p)[0] | ((uint64_t)(p)[1] << 8) | ((uint64_t)(p)[2] << 16) | \
((uint64_t)(p)[3] << 24))
const uint64_t shoff = RD32(b + 0x20);
const uint32_t shentsize = RD16(b + 0x2E), shnum = RD16(b + 0x30);
if (shentsize < 40 || !shnum || shnum > 512) return -1; /* real artifacts have ~13 */
if (shoff + (uint64_t)shentsize * shnum > n) return -1;
struct sh { uint64_t off, size, link, entsize, type; };
struct sh sect[512];
for (uint32_t i = 0; i < shnum; i++) {
const uint8_t* p = b + shoff + (uint64_t)i * shentsize;
sect[i].type = RD32(p + 4);
sect[i].off = RD32(p + 16);
sect[i].size = RD32(p + 20);
sect[i].link = RD32(p + 24);
sect[i].entsize = RD32(p + 36);
if (sect[i].type != 8 /* SHT_NOBITS */ && sect[i].off + sect[i].size > n) return -1;
}
int found = 0;
for (uint32_t i = 0; i < shnum; i++) {
if (sect[i].type != 11 /* SHT_DYNSYM */ || sect[i].link >= shnum) continue;
const struct sh* st = &sect[sect[i].link]; /* the symbols' string table */
const uint32_t esz = sect[i].entsize ? (uint32_t)sect[i].entsize : 16;
if (esz < 16) continue;
for (uint64_t s = 1; s * esz + 16 <= sect[i].size; s++) {
const uint8_t* sym = b + sect[i].off + s * esz;
const uint64_t name = RD32(sym);
const uint64_t size = RD32(sym + 8);
const uint8_t info = sym[12];
if ((info & 0xF) != 1 /* STT_OBJECT */ || !size) continue;
if (name >= st->size) continue;
const uint8_t* nm = b + st->off + name;
const uint64_t room = st->size - name;
if (room < 1) continue;
/* the argument symbols are named by their index: "0", "1", "2" */
if (room >= 2 && nm[1] != 0) continue;
if (nm[0] < '0' || nm[0] > '2') continue;
const int idx = nm[0] - '0';
if (!out[idx]) { out[idx] = size; found++; }
}
}
#undef RD16
#undef RD32
return found ? found : -1;
}
117 changes: 117 additions & 0 deletions tests/npu_elf_args_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2026 bong-water-water-bong
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// npu_elf_args_test -- q4nx_elf_arg_sizes() reads the argument widths a full ELF declares.
//
// The widths are the launcher's contract: the kernel's shim DMA touches exactly the bytes the ELF's
// .dynsym declares for each argument, so a narrower buffer makes the DMA walk past the mapping.
// That is not hypothetical: on 2026-11-02 an OGA pack's ELF family -- (weights, activations,
// output) = (655,360, 2,048, 4,096) B -- was driven by the int8 launcher, which passes
// (MD*KD, KD*ND, MD*ND*4) = (16,384, 1,048,576, 65,536) B, and the array logged IO_PAGE_FAULTs
// while returning garbage instead of failing. The gate in I8Ctx::init_elf compares these two sets
// and refuses; this test pins the reader it depends on.
//
// Host-only: the fixtures are hand-built ELF32 images, so no aiecc build and no device are needed.
#include "q4nx_pack.h"

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

static int failures;

static void check(int cond, const char* what) {
printf("%s %s\n", cond ? "ok " : "FAIL", what);
if (!cond) failures++;
}

static void put32(unsigned char* p, uint32_t v) { memcpy(p, &v, 4); }
static void put16(unsigned char* p, uint16_t v) { memcpy(p, &v, 2); }

// A minimal ELF32 (LSB) with [ehdr][.dynsym][.strtab][.shstrtab][shtab] and one STT_OBJECT symbol
// per argument, named "0".."2" -- the shape aiecc --get-full-elf writes for a full ELF.
static unsigned char* build_elf(const uint32_t sizes[3], uint32_t* out_len) {
enum { NSYM = 4, NDYN = NSYM * 16, NSTR = 8, NSHSTR = 24, NSH = 5 };
const uint32_t off_dyn = 52, off_str = off_dyn + NDYN, off_shstr = off_str + NSTR;
const uint32_t off_sht = off_shstr + NSHSTR, total = off_sht + NSH * 40;
unsigned char* elf = calloc(1, total);
if (!elf) return NULL;
memcpy(elf, "\x7f" "ELF", 4);
elf[4] = 1; elf[5] = 1; elf[6] = 1; // ELF32, little-endian, version 1
put32(elf + 0x20, off_sht); put16(elf + 0x2E, 40); put16(elf + 0x30, NSH); put16(elf + 0x32, 4);
memcpy(elf + off_str, "\0" "0" "\0" "1" "\0" "2" "\0", NSTR); // names at 1, 3, 5
memcpy(elf + off_shstr, "\0.dynsym\0.strtab\0.shstrtab\0", NSHSTR);
for (int i = 1; i < NSYM; i++) {
unsigned char* sym = elf + off_dyn + i * 16;
put32(sym, (uint32_t)(2 * i - 1)); // 1, 3, 5
put32(sym + 8, sizes[i - 1]);
sym[12] = 0x11; // STB_GLOBAL | STT_OBJECT
put16(sym + 14, 5);
}
struct { const char* name; uint32_t type, off, size, link, entsize; } sh[NSH] = {
{"", 0, 0, 0, 0, 0},
{".dynsym", 11, off_dyn, NDYN, 2, 16},
{".strtab", 3, off_str, NSTR, 0, 0},
{".shstrtab", 3, off_shstr, NSHSTR, 0, 0},
{"", 0, 0, 0, 0, 0}};
const uint32_t name_off[NSH] = {0, 1, 9, 17, 0};
for (int i = 0; i < NSH; i++) {
unsigned char* p = elf + off_sht + i * 40;
const uint32_t f[10] = {name_off[i], sh[i].type, 0, 0, sh[i].off, sh[i].size, sh[i].link, 0, 4,
sh[i].entsize};
memcpy(p, f, sizeof f);
}
*out_len = total;
return elf;
}

int main(void) {
// the OGA pack's attention ELF: weights, activations, output
const uint32_t sizes[3] = {163840, 2048, 1024};
uint32_t len = 0;
unsigned char* elf = build_elf(sizes, &len);
if (!elf) { printf("FAIL out of memory\n"); return 1; }

uint64_t got[3] = {0, 0, 0};
check(q4nx_elf_arg_sizes(elf, len, got) == 3, "three argument widths are found");
check(got[0] == 163840 && got[1] == 2048 && got[2] == 1024, "widths match the declared symbols");

// the tile family declares much narrower arguments; the reader must report them as they are
const uint32_t tile[3] = {512, 1024, 4096};
unsigned char* e2 = build_elf(tile, &len);
uint64_t got2[3] = {0, 0, 0};
check(e2 && q4nx_elf_arg_sizes(e2, len, got2) == 3 && got2[0] == 512 && got2[2] == 4096,
"a narrow tile-family ELF reads back as declared");

// refusals: not an ELF, no .dynsym, a truncated header -- and out[] is zeroed first either way
uint32_t len0 = 0;
unsigned char* bad = build_elf(sizes, &len0);
bad[0] = 'X';
uint64_t z[3] = {7, 7, 7};
check(q4nx_elf_arg_sizes(bad, len0, z) == -1, "a non-ELF is refused");
check(z[0] == 0 && z[1] == 0 && z[2] == 0, "out[] is zeroed before the refusal");
bad[0] = 0x7f;
const uint32_t zero_type = 0;
const uint32_t off_dyn = 52, off_sht = off_dyn + 4 * 16 + 8 + 24;
memcpy(bad + off_sht + 40 + 4, &zero_type, 4); // .dynsym -> SHT_NULL
check(q4nx_elf_arg_sizes(bad, len0, z) == -1, "an ELF with no .dynsym is refused");
check(q4nx_elf_arg_sizes(elf, 16, z) == -1, "a truncated header is refused");
check(q4nx_elf_arg_sizes(NULL, len, z) == -1, "a null image is refused");

free(elf); free(e2); free(bad);
printf("%s\n", failures ? "FAILED" : "all cases ok");
return failures ? 1 : 0;
}
Loading