diff --git a/CMakeLists.txt b/CMakeLists.txt index a6f147adf7..a96a52a7c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1925,7 +1925,6 @@ install(DIRECTORY ${PROJECT_SOURCE_DIR}/skills/daslang/ DESTINATION ${DAS_INSTALL_DOCDIR}/skills/daslang FILES_MATCHING REGEX "/daslang/README\\.md$" EXCLUDE - REGEX "/LAWS\\.md$" EXCLUDE PATTERN "*.md" ) # The review constitution ships with the review skills that route to it, and the @@ -2028,7 +2027,6 @@ install(DIRECTORY ${PROJECT_SOURCE_DIR}/skills/daslang/ DESTINATION .claude/skills/daslang FILES_MATCHING REGEX "/daslang/README\\.md$" EXCLUDE - REGEX "/LAWS\\.md$" EXCLUDE PATTERN "*.md" ) install(FILES ${PROJECT_SOURCE_DIR}/.claude/agents/dragon.md DESTINATION .claude/agents) @@ -2380,7 +2378,7 @@ install(CODE [[ get_filename_component(_name "${_f}" NAME) get_filename_component(_f_abs "${_f}" ABSOLUTE) if((_name MATCHES "^REVIEW.*\.(md|das)$" - OR _name MATCHES "^(ARCHITECTURE[A-Za-z_]*|LAWS|MASTERPLAN[A-Za-z_]*|PERF_LEDGER|PROFILE|THINKING)\.md$" + OR _name MATCHES "^(ARCHITECTURE[A-Za-z_]*|MASTERPLAN[A-Za-z_]*|PERF_LEDGER|PROFILE|THINKING)\.md$" OR _name MATCHES "^followup_.*\.md$") AND NOT _f_abs STREQUAL _root_common AND NOT _f_abs STREQUAL _root_arch_common) diff --git a/modules/dasLLAMA/ARCHITECTURE.md b/modules/dasLLAMA/ARCHITECTURE.md index 5be84dfabf..84e2b78367 100644 --- a/modules/dasLLAMA/ARCHITECTURE.md +++ b/modules/dasLLAMA/ARCHITECTURE.md @@ -51,9 +51,9 @@ re-transcoding `$LCPP/src/unicode-data.cpp`). - `ARCHITECTURE_GPU_VULKAN.md` - sec.2.2j-2.2q: the Vulkan resident driver - the prefill window chain, the cm2 decode lanes and tile pick, the class-pipeline build seat, the residency plan, the GPU-slot marks swap, the Q8 requant byte store, and the MoE expert chain on the cm2 tiles. -- `ARCHITECTURE_GPU_VULKAN_DECODE.md` - sec.2.2r-2.2t: the per-op tier's decode era - the decode - attention block over per-layer K/V mirrors, the streamed expert layer's GPU/CPU split, and the - whole-token decode span. +- `ARCHITECTURE_GPU_VULKAN_DECODE.md` - sec.2.2r-2.2u: the per-op tier's decode era - the decode + attention block over per-layer K/V mirrors, the streamed expert layer's GPU/CPU split, the + whole-token decode span, and the deltanet decode step's per-session resident state. - `ARCHITECTURE_GPU_MTP.md` - sec.2.28-2.39: the Metal speculative round over the batch driver's same-slab verify, the box knob that sets the depth a round drafts, and the kernel argument-alignment contract enforced at every dispatch. diff --git a/modules/dasLLAMA/ARCHITECTURE_ENGINE.md b/modules/dasLLAMA/ARCHITECTURE_ENGINE.md index cef5ea0856..d60f29f4b9 100644 --- a/modules/dasLLAMA/ARCHITECTURE_ENGINE.md +++ b/modules/dasLLAMA/ARCHITECTURE_ENGINE.md @@ -246,7 +246,7 @@ file builds an `ArchDesc` (name * `configure` * the `ArchBlocks` fn-ptr quad * ` asr_bench process per cell; macOS only) - the interim footprint instrument until a footprint leg lands in `gen_bench_records`; its numbers live in `PERF_LEDGER.md`, never the stores. -### 1.9 Serving +### 1.9 Serving {#scheduler-step} - **`dasllama_scheduler.das`** - the continuous-batching scheduler, the serving layer over the facade (its one engine require is `dasllama/dasllama`). One synchronous thread: each @@ -255,4 +255,7 @@ file builds an `ArchDesc` (name * `configure` * the `ArchBlocks` fn-ptr quad * ` streams' KV pages to the prefix cache. Results flow out as `SchedEvent`s - no HTTP here. `utils/dasllama-server` owns the writers; `tutorials/dasLLAMA/13_serving.das` is the teaching consumer; `tests/test_scheduler.das` gates it against `generate()` references. + The step clears its gather arrays (`batch_rows`, `batch_toks`, `batch_idx`) before it reaps + finished streams: `batch_rows` holds borrowed pointers into the sessions the reap deletes, + and a validating heap collect between steps walks every pointer the array still holds. diff --git a/modules/dasLLAMA/ARCHITECTURE_GPU.md b/modules/dasLLAMA/ARCHITECTURE_GPU.md index f53125dc64..bfaf72ea82 100644 --- a/modules/dasLLAMA/ARCHITECTURE_GPU.md +++ b/modules/dasLLAMA/ARCHITECTURE_GPU.md @@ -85,7 +85,8 @@ that a question answered for one backend has an obvious address in the other. Th route/mark/want/status state, engine-facing forwarders. Vulkan implements it (per-op offload plus resident plumbing, and the decode-era seats it alone fills: the cm2 expert chain `set_moe_gpu_ffn_xf_hooks` / `_async_hooks`, the decode attention block - `set_moe_gpu_attn_dec_hooks`, the decode FFN tail `set_moe_gpu_ffn_tail_hooks`, the + `set_moe_gpu_attn_dec_hooks`, the decode FFN tail `set_moe_gpu_ffn_tail_hooks`, the deltanet + decode step's state seams `set_moe_gpu_dn_state_hooks` (flush, invalidate, release), the whole-token span `set_moe_gpu_span_dec_hook` - the span rides common's decode override registry as `vulkan_moe_span`, selected by the MoE placement and declining per token). The installs are one-way: a test that arms the tier installs the seats and never restores them, @@ -136,6 +137,10 @@ Sections 2.28-2.30, the Metal speculative round and the dispatch alignment contr **The allowed asymmetries between the backends - this list is closed; a new one lands with its entry here:** +- **The `dasllama_gpu_tier` cooperation SPI is Vulkan-only**: every hook seat the tier + exposes (`install_moe_gpu_tier` and the `set_moe_gpu_*_hooks` setters) is registered by the + Vulkan family alone, and the role row above enumerates the seats; a new seat lands in that + row, not as a new entry here. - **Metal sits ABOVE `dasllama_common`** (typed `Model`/`Session` access, shapes unconditional); **Vulkan sits BELOW it** (untyped pointer/array seams - the family never requires common). Both tiers ENTER from the transformer umbrella (`?das_metal` requires; the single `?vulkan` diff --git a/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN.md b/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN.md index b0c25a8dd6..937f400713 100644 --- a/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN.md +++ b/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN.md @@ -228,7 +228,15 @@ codec, and the device-embed arm). The save moves the installed state out and lea reading as no-model; the restore is its exact inverse. The whole-model drop clears the same set and deselects the `"vulkan"` overrides, so a dropped model's prefill and decode take the plain CPU path and a later re-arm passes `resident_upload`'s no-active-override gate. The three carry -the same set, which is why a model's device state never survives into the next. +the same set, which is why a model's device state never survives into the next. The upload +rail enforces it from its own side: a load that finds marks still installed - a model deleted +without the drop, the shape every test process and single-model tool takes - drops that +model's device state before uploading its own, carrying the load's MoE layer request across +the drop (the one mark the drop's reset would otherwise zero before the rail reads it). +Without that drop the second model's stacks +land beside the first's, and the offset-keyed stack lookup serves whichever model's plane +registered that offset first: the decode attention block asserts on the geometry change, and a +model whose geometry matches decodes the earlier model's weights. ### 2.2p The Q8 requant writers store one quant per byte {#q8-requant-byte-store} diff --git a/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_DECODE.md b/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_DECODE.md index bb1aa2b885..546ccae06b 100644 --- a/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_DECODE.md +++ b/modules/dasLLAMA/ARCHITECTURE_GPU_VULKAN_DECODE.md @@ -1,8 +1,9 @@ # dasLLAMA Architecture - the Vulkan per-op tier's decode era Companion to `ARCHITECTURE_GPU_VULKAN.md`; section numbers are `ARCHITECTURE.md`'s. This -document carries sections 2.2r-2.2t: the decode attention block over per-layer K/V mirrors, -the streamed expert layer's GPU/CPU split, and the whole-token decode span. The prefill window +document carries sections 2.2r-2.2u: the decode attention block over per-layer K/V mirrors, +the streamed expert layer's GPU/CPU split, the whole-token decode span, and the deltanet decode +step's per-session resident state. The prefill window chain, the cm2 tiles and the MoE expert chain these build on are `ARCHITECTURE_GPU_VULKAN.md` sections 2.2j-2.2q. @@ -138,3 +139,32 @@ arch binds a non-standard attention or FFN block declines whole (`span_model_ok` span's recorder declares those edges by hand (`vhz_dep` on the span's own region bits) after each requant and after the top-k. The loader carves the router planes with the mirrors (`set_moe_gpu_dat_need`). + +### 2.2u The deltanet decode step's resident state follows its session {#dn-step-owner} + +**The deltanet decode step keeps one device copy of a recurrent layer's state and conv ring, +and that copy belongs to one session at a time.** The step (`vk_moe_dn_step`) records, per +layer, the host address of the session state it uploaded (`DnStep.owner_state_addr`, with the +conv history's address beside it): a step from the owner runs on the resident copy; a step from a +different session sends the resident copy home to its owner's buffers when it is dirty, then +cold-uploads its own state and takes the slot. A flush request (`vk_dn_step_flush`) writes only +when the requester is the owner - a foreign session's state is already on its host, so the +request is a no-op there. Two sessions decoding turn about on the tier therefore pay a flush +and an upload per recurrent layer per switch, and each reads its own state. + +**A session's identity outlives nothing.** Every Session with deltanet state carries a +`DnOwner` token whose range is that state's host addresses; the token's finalizer - run by the +Session's own finalizer, at every `delete` - releases the device copies the range owns without +writing them (`vk_dn_step_release`), so a later switch never flushes into freed memory and a +new session at the same addresses never inherits a dead one's copy. The token and its +finalizer live beside Session in the engine module: a struct's finalizer is resolved in the +scope of the module that deletes it, and a Session dies in modules that see the engine and +never the tier (the scheduler reaps its streams through the facade). A session's own +position-zero reset releases its own copies the same way (`dn_reset`), and so does its +speculative-round rollback (`mtp_state_restore`): the session's host state is authoritative, +and the other sessions' copies stay theirs. The whole-device invalidate +(`vk_dn_step_invalidate`) remains the seam for a dropped model, where every host copy is +authoritative at once. The engine reaches those seams through the tier's forwarders: +`dn_flush_layer` per layer before any CPU read of `dn_state` or the conv history, +`moe_gpu_dn_release` for one session's copies, and `moe_gpu_dn_invalidate` for every copy at +once. diff --git a/modules/dasLLAMA/PERF_LEDGER.md b/modules/dasLLAMA/PERF_LEDGER.md index 6841cfb087..cfb8b1d2d8 100644 --- a/modules/dasLLAMA/PERF_LEDGER.md +++ b/modules/dasLLAMA/PERF_LEDGER.md @@ -24,6 +24,15 @@ what it costs today and what the fix would change. against the 07-25 row; done when both rows sit in `performance/records/zen2.json` and the site records are regenerated. +- **OWED ROWS - the parity pass's ten 1B pp512 vehicles.** The mirror pass + (`plans/kernel_parity_pass.md`, 2026-09-03) read Q3_K_L at 1.08, IQ3_M at 1.06 and IQ2_XXS + at 0.98 of the reference on the `lcpp_bench --for-debug-purposes --plen 512 --ngen 0 --reps + 12` rig bracketed by `llama-bench -p 512 -n 0 -r 6`, stage readings only; the board's rows + for those vehicles are two-rep readings and stand until re-minted. Same refusal as above - + the hardware stamp declines this box while the remote-access daemon runs. Owed: the ten + vehicles' vulkan pp512 rows re-minted by `gen_bench_records`, direction-grade against the + rows they replace; done when they sit in `performance/records/zen2.json`. + - **OPEN (narrowed) - the gemma3v encode residual after the tower flash: ~0.92x vs the pair.** The slab road closed in three landings: the 96 head pad (guarded AV columns, 668 -> 486 -> 452), then the LIFTED dk72 flash (MetalTowerFlash + the per-head-contiguous diff --git a/modules/dasLLAMA/REVIEW.md b/modules/dasLLAMA/REVIEW.md index b759fa8305..15c3b0ed51 100644 --- a/modules/dasLLAMA/REVIEW.md +++ b/modules/dasLLAMA/REVIEW.md @@ -9,7 +9,8 @@ the followup ledgers). **A dasLLAMA `[test]` file, wherever the diff puts it, answers to this module's `tests/REVIEW.md`.** -**A timing rig - a script whose output is a measured wall-clock time or rate - or a kernel race, +**A timing rig - a script whose output is a measured wall-clock time or rate - or a kernel +race - a run that times two kernel variants (arms) against each other in one process - wherever the diff puts it (`harness/`, `dasllama/`, a test), answers to this folder's `benchmarks/REVIEW.md`.** @@ -22,22 +23,19 @@ format, modality, family, or backend, applies `REVIEW_MEASUREMENT.md`.** answers to it too. A model file here is a `.gguf`, a `.dlim`, an mmproj, or an image or audio fixture. A test or tool merely opening a stocked model file by name does not route. -**A change to the sidecar-exchange client or schema answers to `performance/REVIEW.md`.** The -sidecar exchange is the code that downloads tune winners to a box and submits that box's -winners back. - -**A change to the sidecar-exchange client (`dasllama/dasllama_exchange.das`), or to a -tune-boot path that reaches it, applies `REVIEW_EXCHANGE.md`.** +**A change to the sidecar-exchange client (`dasllama/dasllama_exchange.das`) - the code that +downloads tune winners to a box and submits that box's winners back - its schema, or a +tune-boot path that reaches it, applies `performance/REVIEW.md` and `REVIEW_EXCHANGE.md`.** **Every `dasllama/` change applies this folder's `tests/REVIEW.md` - open it explicitly: the folder walk does not surface it for a `dasllama/`-only diff.** **A GPU kernel, driver, dispatch class (a class a `[metal_dispatch]` or `[vk_dispatch]` declares), or K/V-mirror (the device-side copy of the key/value cache a GPU decode reads and -writes) change - and a GPU kernel A/B race, a knockout (an arm that skips a stage to price -it), or a hand-binding arm (one that writes buffer or kargs (kernel-argument struct) binding -numbers as literals instead of taking the kernel class's declared ones), wherever the diff -puts it - applies `REVIEW_GPU.md`.** +writes) change - and a GPU kernel A/B race, a knockout (an arm that skips a stage to measure +that stage's cost), or a hand-binding arm (one that writes buffer or kargs (kernel-argument +struct) binding numbers as literals instead of taking the kernel class's declared ones), +wherever the diff puts it - applies `REVIEW_GPU.md`.** **A kernel body or a function a kernel calls - a `[metal_kernel]` def, a class a `[metal_dispatch]` / `[vk_dispatch]` declares, or a fixture either emitter compiles - wherever @@ -62,8 +60,8 @@ splices a stream carrying decoded media - pixels or audio samples - into a promp schedules such a stream, applies `REVIEW_VISION.md`.** **A `dasllama/dasllama_tower.das` change - the shared encoder-tower home - applies -`REVIEW_AUDIO.md` and `REVIEW_VISION.md`;** a family file that only CALLS a shared rail does -not thereby pick up the other modality's checklist. +`REVIEW_AUDIO.md` and `REVIEW_VISION.md`;** a family file that only CALLS a shared tower +function does not thereby pick up the other modality's checklist. **A change to `dasllama/dasllama_tts.das`, `dasllama/dasllama_tts_types.das`, `dasllama/dasllama_tts_blocks.das`, `dasllama/dasllama_styletts2.das`, a TTS family file - @@ -91,8 +89,8 @@ sidecars stay valid across code changes, and per-change invalidation lives in th mechanisms - `IMAGE_VERSION` and `layout_fingerprint()` (`dasllama/dasllama_image.das`). **A value that cannot change between dispatches of one compiled kernel never reaches that -kernel as a uniform, a kargs field, an `@off` bind offset, or a helper parameter.** A value -that can change between dispatches goes in a uniform, a kargs field, or an `@off` bind offset. +kernel as a uniform, a kargs field, an `@off` bind offset, or a helper parameter - stamp it +into the class as a `@template_constant` instead.** **A function-typed global with a declaration initializer lands in a `dasllama/` file and joins that file's boot-restore `[init]`; landing one where `REVIEW.das`'s restore-check walk over @@ -101,7 +99,7 @@ function value arrives null and the first invoke to reach it dies at exe runtime `-jit` gate stays green. **Never reorder or merge the float multiplies in a function that builds a RoPE angle table -(`dasllama/dasllama_rope.das`) - keep the multiply order the code already has.** A regrouping +(`dasllama/dasllama_rope.das`).** A regrouping moves the angles in the last bits and flips token-exact fixtures. **A diff that changes a predicate in `dasllama/` picking between kernel forms that both @@ -112,7 +110,7 @@ commits, says which way the wall-clock time moved, not which implementation to a **A change to an allocation reached from a load, bake, or convert path (judge a shared helper at each call site) that trades footprint for speed ships the measured pair - peak footprint and -wall-clock - and a stated decision.** +wall-clock - in `PERF_LEDGER.md` with the decision it settles.** **A new call to an f32 matmul (`matmul_batch`, `mm_blob_b`, per-head `gemm_f32` / `gemm_f32_jo`, or an f32 GPU mm) outside a correctness-comparison path (one whose only job is @@ -132,28 +130,31 @@ instrumentation goes through the profiling rails - `profile_tag` / `profile_mark are `ARCHITECTURE_MEASUREMENT.md` sec.2.10. **A clock value that changes what the program DOES - control flow, eviction, a generated -name; not a reported wall or a best-of reduction over reported walls - is marked -`// clock: control`, in an engine file (`dasllama/`)** - unmarked, it looks like ad-hoc -profiling to the sweep that runs over the same tree. +name; not a reported wall-clock time or a best-of reduction over reported wall-clock times - +is marked `// clock: control`, in an engine file (`dasllama/`)** - unmarked, it cannot be told +apart from the ad-hoc profiling an engine file may not carry. **Every new kernel or loop the runtime re-enters per token, per frame, or per prefill quantum - one batch of prompt tokens the prefill path processes in a single pass - is COVERED -by an annotated region entry** - `[hot_path]`, any of the `[no_alloc]` / -`[no_env]` / `[no_io]` contracts, or `[cold_path]` on its only reaching entry. Covered means -an annotated entry reaches it: the contracts arm down the call graph, so an interior -function carries nothing of its own. A region entry is the outermost such function (a kernel -`*_encode` / `*_decode`, a step driver, the CPU decoder's `forward_*` entries); a loop -reached only from a load, stage, bake, or convert path is not one. - -**A new function that no annotated entry reaches but the runtime re-enters per token, per -frame, or per prefill quantum - a step driver, or a backend entry called from dispatch or -harness paths - carries its annotation itself.** A renamed function is not new: its -annotation moves with the name in the same change. +by an annotated region entry** - `[hot_path]`, any of the `[no_alloc]` / `[no_env]` / +`[no_io]` contracts, or `[cold_path]` on its only reaching entry. Covered means an annotated +entry reaches it: an annotation binds every function the annotated entry calls, so an +interior function carries nothing of its own; an entry no annotated entry reaches carries the +annotation itself, and a function reached only through a registered function value is +reached by no annotated entry. A region entry is the outermost such function (a kernel `*_encode` / +`*_decode`, a step driver, the CPU decoder's `forward_*` entries); a loop reached only from a +load, stage, bake, or convert path is not one. + +**A renamed per-token function is not new: its annotation moves with the name in the same +change.** **A change to code or data of `encode`/`bpe_encode` or anything they reach in `dasllama/dasllama_spm.das` / `dasllama/dasllama_bpe.das` / `dasllama/dasllama_pretok.das` -ships before/after `--tok` rows (this folder's `benchmarks/lcpp_bench.das`) for the affected -backend, and a wall-clock time that grows faster than linearly with input size is a defect.** +ships before/after `--tok` rows (this folder's `benchmarks/lcpp_bench.das`) for a model using +the affected tokenizer.** + +**A tokenizer wall-clock time that grows faster than linearly with input size is a defect** - +the `--tok` rows cover at least two input sizes so the growth is readable. **A change to code or data in `dasllama/dasllama_tokenizer.das`, `dasllama/dasllama_spm.das`, `dasllama/dasllama_bpe.das`, or `dasllama/dasllama_pretok.das`, or to the special-token or @@ -171,9 +172,9 @@ outcome, naming it by the spelling a user would set - the environment variable n sidecar or file key, or the setter's function name. Per-site repeats are fine; a set-but-inert override stays silent. -**A change to user-facing API updates every place it is shown: a tutorial source, `.rst` page, -docstring, help string, `README.md`, or checked-in document still showing the old call, flag, or -default is a defect of the change, not of the docs.** User-facing means anything a consumer +**A tutorial source, `.rst` page, docstring, help string, `README.md`, or checked-in document +left showing the old call, flag, or default after a change to user-facing API is a defect of +the change, not of the docs.** User-facing means anything a consumer outside this repo can depend on - what it calls, types, requires, or parses (facade functions, CLI flags, environment knobs, file formats, defaults, what the installed SDK lets a program `require`) - plus the in-repo rig and tool surface: any output another tool parses. A @@ -193,16 +194,16 @@ for several twins - does not fire this rule: the one body left still carries its **`options _dasllama_internal` belongs only in a file whose job is to reach engine internals: an engine file under `dasllama/`, a test, harness, benchmark, or rig this module owns, or a consumer `ARCHITECTURE_ENGINE.md` sec.1.8 names as ruled** - a symbol the facade -lacks is added to `dasllama/dasllama.das`, not reached around with this option. A +lacks is added to `dasllama/dasllama.das`, not obtained by adding this option to the consumer. A `require ... public` that re-exports an engine module OUT of a file carrying this option, -beyond what that consumer's ruled charter (`ARCHITECTURE_ENGINE.md` sec.1.8) grants, breaks -this rule too. +beyond what that consumer's ruled charter grants, breaks this rule too. -**Weakening `REVIEW.das` (beside this file) is a defect:** dropping a check, adding a name to -a check's licensed set - the names that check does not flag - unless the check's own finding -text names that set as its extension point and the diff lands the paired architecture edit it -asks for, or a finding text that no longer names what failed. What the gate enforces is read -from the gate itself; each check's finding text states its own rule. +**Weakening `REVIEW.das` (beside this file) is a defect:** dropping a check, rewriting a +finding text so it no longer names what failed, or adding a name to a check's licensed set - +the names that check does not flag. A name joins a licensed set only when that check's own +finding text names the set as its extension point and the diff lands the paired architecture +edit that text asks for. What the gate enforces is read from the gate itself; each check's +finding text states its own rule. **A new `REVIEW.das` check ships its line on the checked file's sec.1 charter - in an `ARCHITECTURE_*.md` companion, never `ARCHITECTURE.md` - in the same change.** The line names the check and the names it licenses. A licensed name is one that check does not @@ -212,18 +213,18 @@ flag. When the check licenses no names, the line says so. patching, or reproducing work against the reference build describes an upstream mechanism in our own terms: no "lifted/ported verbatim from", and no name belonging to the reference build - symbol, header, constant, binary, project or organization - write "the reference exe" or -"upstream" instead.** The reference build is the -third-party engine this module measures itself against - the checkout -`benchmarks/setup_lcpp_ref.das` pins. A symbol the file carrying that prose calls or holds as -a value is its own name, not attribution. - -**A file or line whose job is to locate, patch, or reproduce work against the reference build -names that build's binaries and symbols outright.** The job decides, not the artifact kind - a -regeneration path, an env-knob row, a command line in a methodology or how-to document, a -ledger row whose subject is a reading of the reference build (the compared row, the command -that reproduces it), and a source patch applied TO the reference build all qualify. A row that -cites upstream while proposing our own work is not a reading of the reference build, so it -names no upstream symbol. +"upstream" instead.** The reference build is the third-party engine this module measures +itself against - the checkout `benchmarks/setup_lcpp_ref.das` pins. A symbol the file +carrying that prose calls or holds as a value is its own name, not attribution. + +**Prose whose job is to locate, patch, or reproduce work against the reference build names +that build's binaries and symbols outright, and keeps that naming inside the sentences doing +that job.** The job decides, not the artifact kind - a regeneration path, an env-knob row, a +command line in a methodology or how-to document, a ledger row whose subject is a reading of +the reference build (the compared row, the command that reproduces it), and a source patch +applied TO the reference build all qualify. A paragraph that mixes a reading of the reference +build with a proposal of our own keeps them in separate sentences; a row that cites upstream +while proposing our own work is a proposal, not a reading, so it names no upstream symbol. **A diff that changes what authoring a new weight format entails - a step added or dropped, a file the author must touch, a fixture or probe entry the format must supply, or a gate it must @@ -261,8 +262,9 @@ folder where another file has its own sec.1 charter line lands the new file's ch too. A module-root doc file - a ledger, a plan - has no charter line and needs no charter edit. -**A diff that adds or removes an `ARCHITECTURE_*.md` companion, or moves a section between -companions, lands `ARCHITECTURE.md`'s index line and section range and repoints every prose +**A diff that adds, removes, or moves a section of an `ARCHITECTURE_*.md` companion, or adds +or removes a companion, lands `ARCHITECTURE.md`'s index line and section range, the +companion's own opening (its range and the sections it names), and every repointed prose `sec.N` / file citation of the moved sections, in the same change.** The `[arch]` citations are LINT026-gated; the prose ones are not, and a prose citation of a section that left its file sends the reader to nothing. diff --git a/modules/dasLLAMA/REVIEW_GPU.md b/modules/dasLLAMA/REVIEW_GPU.md index b95cd3dea6..db4acd32ac 100644 --- a/modules/dasLLAMA/REVIEW_GPU.md +++ b/modules/dasLLAMA/REVIEW_GPU.md @@ -16,7 +16,7 @@ builder the tower dispatches, the `[metal_dispatch]` emission those builders are from (`dasllama/dasllama_metal_lens.das`), or the Metal ASR decoder (`dasllama/dasllama_metal_asr_dec.das`) applies `REVIEW_TOWER.md` too.** -**A diff touching the Vulkan tier - `dasllama/dasllama_vulkan_*.das`, +**A diff touching the Vulkan tier - `dasllama/dasllama_*vulkan*.das`, `dasllama/dasllama_gpu_resident.das`, a `[vk_dispatch]` class, a `[spirv_decode]` callback, or a cm2 tile class (an NV_cooperative_matrix2 GEMM class stamped per weight format and column) - wherever the diff puts it - applies `REVIEW_GPU_VULKAN.md` too.** @@ -26,8 +26,9 @@ the choice at compile time instead.** A `class template` / `def abstract` / `def splice is compile-time and conforms - check the emission, not the das spelling. **Never give a `*_decline_caps` predicate a parameter beyond the model, the row count, and -whether the call carries a uniform attention span - however that parameter is derived; -window-setup state is asked by `prefill_decline` / `decode_decline` instead.** +whether the call carries a uniform attention span - however that parameter is derived; window +readiness, whether this window's rope tables are staged, is asked by `prefill_decline` / +`decode_decline` instead.** **A bounds or tail guard that branches per iteration in a kernel's main loop, where the host already knows its answer as it picks the pipeline, is a defect - stamp the guard instead.** @@ -41,12 +42,15 @@ receives - a `params=` name or a kargs (kernel-argument struct) field - as one ` in `requires =`, comma-separated.** The generated builder then trips on the first misaligned dispatch instead of reading the next row. -**A driver that keeps misaligned shapes off a kernel whose main loop steps one fixed-size chunk -and never checks for a partial last chunk gates every dispatch site of that kernel on that -site's own K - the extent the loop steps along - never on one gate covering every site, and -each site's gate divides by a multiple of the chunk that site's kernel steps.** A kernel that -steps 128 behind a gate that checks 256 never sees a shape it could serve; a kernel that steps -256 behind a gate that checks 128 silently drops a tail. +**A driver that keeps misaligned shapes off a chunk-stepping kernel - one whose main loop +steps a fixed-size chunk and never checks for a partial last chunk - gates each dispatch site +of that kernel on that site's own K, the extent that site's loop steps along, never on one +gate covering every site.** + +**A dispatch site's alignment gate whose divisor is not the chunk the kernel that site +dispatches steps is a defect.** A kernel that steps 128 behind a gate that checks 256 never +sees a shape it could serve; a kernel that steps 256 behind a gate that checks 128 silently +drops a tail. **Weakening the MSL emitter's refusal to compile an unlicensed float `matmul2d` A operand - `[metal_kernel(float_a_ok=true)]` is the license - or its gate @@ -67,13 +71,15 @@ each lane a consecutive run of elements, or a lane-coalesced stride (`i += 32`), device-to-device copy loop is already coalesced and conforms. **Never decide a kernel row's validity or owner by scanning the per-bucket base and count -arrays - read the one per-row entry instead.** The bucket-building kernel writes that per-row +arrays - a bucket is the run of rows one expert owns in the bucket-ordered buffer - read the +one per-row entry instead.** The bucket-building kernel writes that per-row entry. The scan repeats on every thread of every row's threadgroup, and it grows with the bucket count. -**Never test a bucket row's validity against the pad sentinel `0xFFFFFFFF` - compare the row's -per-row bucket entry, the one the bucket-building kernel writes, with the live entry count -(positions x experts per token, `npos * nk`) instead.** Rows past the last expert's stamped +**Never test the validity of a row in the bucket-ordered buffer - where each expert owns one +run of rows - against the pad sentinel `0xFFFFFFFF`; compare the row's per-row bucket entry, +the one the bucket-building kernel writes, with the live entry count (positions x experts per +token, `npos * nk`) instead.** Rows past the last expert's stamped tail hold stale pool bytes, not the sentinel, and an equality test sends their token index out of bounds. @@ -104,10 +110,6 @@ wider-row site passes the full stride or dispatches the padded tile.** A split r `row x dispatched-width`, so a wider-row caller lands its split rows on top of the row beside them. -**A prefill GEMM dispatched at a nonzero start row never asks `cm2_split_k` for a split - it -encodes unsplit.** The split-k reduce sums partial planes counted from row 0, so a dispatch -starting above row 0 would reduce the wrong rows. - **A scratch buffer a dispatch writes is never rebound for a new write before the reader of its previous write is encoded - rotate through as many buffers as the chain has dispatches in flight between a write and its read.** One shared scratch serializes the whole chain through @@ -136,8 +138,9 @@ axis - one compile-time choice, such as single/batch, format, or single-pass/chu the stamp axis is.** Body divergence is carried by a `@template_constant`, or by an overridden method spliced flat at emission. -**A dummy-bound field where a gate serves is a defect - a stamp-varying binding is carried by -`@template_gate` instead.** +**A kernel class template that binds a real buffer to a field the stamp's own body never +reads - a dummy bind that exists only to fill the slot - is a defect: gate that field with +`@template_gate` so the stamps whose bodies do not read it do not carry it.** **A diff that forks a kernel class out of a shared template shows that the bodies no longer differ on the compile-time choice the template carried, and names that choice in the @@ -155,8 +158,8 @@ defect; a per-encode field either omits `@role` or names the access its body per `[vk_dispatch]` declaration, or a new instance of a template carrying one - covers that class in `tests/test_kernel_coverage.das`, one of two ways.** Either a census row there dispatches the class, or the diff names it in that file's `CENSUS_NEVER_DISPATCHED` with the reason no -row can reach it - a class in neither place leaves `CENSUS_NEVER_DISPATCHED` claiming coverage -the census does not have. +row can reach it - the two lists together are the file's coverage claim, and a class in +neither makes that claim false. **Every field of a new kernel class declared in `dasllama/` carries at least one of the annotations its `[metal_dispatch]` / `[vk_dispatch]` builder reads - `@binding`, `@role`, @@ -171,7 +174,8 @@ stride the full output width, so a span computed from the tile width leaves the every row outside the tracked hazard range. **A NEW hand-written `enc_*` body is a defect unless it is a wrapper - a format or twin pick, a -default-filling wrapper, or a composite over generated builders.** +default-filling wrapper, or a composite over generated builders - declare the class so the +`[metal_dispatch]` / `[vk_dispatch]` lens generates the builder instead.** **A hand-rolled bind list on a dispatch that serves a user call, in `dasllama/` or `performance/`, is a defect: dispatch through the kernel's `enc_*` builder instead.** @@ -203,24 +207,25 @@ own init/release pair. **A decline counter beside the decline site is a defect - decline counting lives in `dasllama/dasllama_metal_common.das`.** -**A diff that changes what one backend can serve and the other cannot - a Metal-only or -Vulkan-only hook, role, served path, or backend-only capability, added or removed - lands its -entry in `ARCHITECTURE_GPU.md` sec.1.5's closed asymmetry list in the same change.** One -backend serving the same path faster or slower is not such a change. The duty holds when that -list already carries the class of asymmetry, and it covers sec.1.5's per-driver lists of -registered hooks and borrowed kernels. Extending a file's sec.1.5 role row does not discharge -it. An asymmetry the list does not carry does not exist. +**A diff that adds or removes a Metal-only or Vulkan-only hook, role, served path, or +backend-only capability - a hook in sec.1.5's per-driver registered-hook or borrowed-kernel +lists included, a seat of the `dasllama_gpu_tier` cooperation SPI excluded (the closed list's +standing entry sends those to the tier's role row) - lands its own entry in +`ARCHITECTURE_GPU.md` sec.1.5's closed asymmetry list in the same change, even when that list +already carries an asymmetry of the same class, and even when the diff also extends the file's +sec.1.5 role row.** One backend serving the same +path faster or slower is not such a change. **A change to code that a served GPU decode or prefill path executes ships GPU-vs-CPU parity -on one q8 and one kq (K-quant) model with the armed mirror codec.** That code is anything a -served GPU decode or prefill call executes OR that selects what it executes - a driver, a -kernel class it dispatches, that class's builder, a servability gate, a race that picks which -kernel serves, a forwarder default, a weight-region or residency path, the tier forwarders and -the Vulkan tier-dispatch seams (`dasllama/dasllama_vulkan_seams.das`) the call routes through; -never the bake paths, never a comment. The parity run is `harness/parity.das` on either -backend, `benchmarks/lcpp_bench.das --parity` (`performance/model_specs.das`'s fixed model -list) on either backend, or - on Metal only - an in-suite `tests/test_metal_*_parity.das` -instrument run through `tests/run.das`. +on one q8 and one kq (K-quant) model the changed path serves, with the mirror codec armed +where the changed path reads a K/V mirror.** That code is anything a served GPU decode or prefill call executes OR that +selects what it executes - a driver, a kernel class it dispatches, that class's builder, a +servability gate, a race that picks which kernel serves, a forwarder default, a weight-region +or residency path, the tier forwarders and the Vulkan tier-dispatch seams +(`dasllama/dasllama_vulkan_seams.das`) the call routes through; never the bake paths, never a +comment. The parity run is `harness/parity.das` on either backend, `benchmarks/lcpp_bench.das +--parity` (`performance/model_specs.das`'s fixed model list) on either backend, or - on Metal +only - an in-suite `tests/test_metal_*_parity.das` instrument run through `tests/run.das`. **Parity evidence counts only when its backend was armed: the Metal arm ran with `--ngl`; the Vulkan arm ran with `DASLLAMA_GPU=1` - never `--ngl` - and its log shows the tier that serves @@ -231,13 +236,6 @@ resident` for the per-op tier).** The Vulkan driver declines codec-mismatched se ships a `dasllama-convert --trim` bake plus a serve of the trimmed image, on one q8 and one kq (K-quant) model.** Parity runs never reach it. -**Never leave a K/V codec unserved by the kernels that read or write the whole-model driver's -`k_mirror`/`v_mirror` slabs, or the decode block's per-layer `DatLayer.k_mir`/`v_mir` pair - a -K/V codec is the mirror's element type, f16 or f32.** Two shapes serve both: instances of one -template cover both codecs, or a single-codec kernel has a sibling that serves the other codec -behind an arming gate that keys on `kv16`. The whole-model driver serves both codecs, so a -codec no kernel covers silently drops that codec's GPU path. - **An f16 store into any GPU-resident K/V that does not clamp to the f16 finite range (+/-65504) is a defect.** @@ -264,11 +262,11 @@ routes the next model's dispatches at the old model's planes. **A diff that changes how a dev-W resident panel's cache key is built - a dev-W panel is a weight plane dequantized once into a device f16 panel - changes both the seed site and the lookup site in the same change** - `pf_devw_seed_baked` and `pf_devw_resident_panel` in -`dasllama/dasllama_metal_prefill.das`. A seed keyed differently from the forward never hits, +`dasllama/dasllama_metal_prefill.das`. A seed keyed differently from the lookup never hits, and every baked site silently re-dequantizes. **A servability gate in `dasllama/dasllama_metal_shapes.das` never reads process-global runtime state - the active kernel backend, a mode toggle - on its mint-time path: such a read -sits behind the gate's `mint_time` flag, and the mint-time verdict tests the model's own -fields.** The load selects the repacking CPU backend before the GPU backend is decided, so a -mint-time read bakes a verdict the drivers do not share. +runs only where the gate's `mint_time` flag is false, and the mint-time verdict tests the +model's own fields.** The load selects the repacking CPU backend before the GPU backend is +decided, so a mint-time read bakes a verdict the drivers do not share. diff --git a/modules/dasLLAMA/REVIEW_GPU_VULKAN.md b/modules/dasLLAMA/REVIEW_GPU_VULKAN.md index f7fd0030f0..2c72d6bffe 100644 --- a/modules/dasLLAMA/REVIEW_GPU_VULKAN.md +++ b/modules/dasLLAMA/REVIEW_GPU_VULKAN.md @@ -4,46 +4,60 @@ docs: `ARCHITECTURE_GPU_VULKAN.md` and `ARCHITECTURE_GPU_VULKAN_DECODE.md`. Planned work: `followup_vulkan.md`. -**Routed from `REVIEW_GPU.md`: a diff touching the Vulkan tier - `dasllama/dasllama_vulkan_*.das`, -`dasllama/dasllama_gpu_resident.das`, a `[vk_dispatch]` class, a `[spirv_decode]` callback, or a -cm2 tile class (an NV_cooperative_matrix2 GEMM class stamped per weight format and column) - -wherever the diff puts it - applies this list together with `REVIEW_GPU.md`'s and -`REVIEW.md`'s.** +**Routed from `REVIEW_GPU.md`: a diff that checklist routes here applies this list together +with `REVIEW_GPU.md`'s and `REVIEW.md`'s.** -**A hand-written Vulkan pipeline build anywhere in the engine is a defect - a Vulkan pipeline -is created only by a `[vk_dispatch]`-generated `ensure_*` and torn down by -`vk_drop_model_state`.** +**A hand-written Vulkan pipeline build is a defect - a Vulkan pipeline is created only by a +`[vk_dispatch]`-generated `ensure_*`.** + +**A diff that adds a Vulkan pipeline tears it down in `vk_drop_model_state`, in the same +change.** **Never size a buffer bound as one SSBO (shader storage buffer) range above -`vk_max_storage_range()` - check the size where it is NEGOTIATED, not where it binds.** The -bind site cannot shrink a buffer that was sized wrong. +`vk_max_storage_range()` - check the size at the site that computes it, not at the site that +binds it.** The bind site cannot shrink a buffer that was sized wrong. -**Never cache a descriptor set across dispatches in state that `vk_drop_model_state` does not -clear** - put it in a `*_ready` latch, or in a holder that function already clears in -`dasllama/dasllama_vulkan_common.das`. +**Never cache a descriptor set or a host address - a pointer into CPU memory - across +dispatches in state that `vk_drop_model_state` does not clear** - hold it in that function's +own state in `dasllama/dasllama_vulkan_common.das`, the way the `*_ready` latches are. **Never read a `[spirv_decode]` callback's quant bytes by indexing `unpack8` of a 32-bit word -with a runtime value - read them as 16-bit lanes instead: an `int16[N]` block member selected -with `unpack8(w)[i & 1u]`, sub-fields pulled out by shift and mask.** The vendor driver's shader -compiler pattern-matches only the 16-bit spelling into its block-load path, and a runtime byte -select drops the whole kernel off it. - -**A diff that changes when the resident prefill that takes token ids rather than embeddings -accepts a call updates the engine's GPU-embed probe in the same change.** That prefill is -`vk_rdec_prefill_ids` and the resident prefill override that routes to it; the probe is -`vulkan_embed_gpu_gate` in `dasllama/dasllama_gpu_resident.das`, registered through -`register_embed_gpu_gate`. The engine skips the CPU embed on a true probe, so a probe that is -true where that path declines hands the next consumer an unfilled residual stream. - -**A diff that adds a cm2 tile format instance, changes a format's cm2 decode body, or changes a -format's four-wide twin (`decode_v4`) or its `DECV4` or `DECVEC` constant puts that format's -`cm2:` probe rows (`harness/vk_gemm_probe.das`), both the `DASLLAMA_VK_DECVEC=1` and the -`=0` rows, in the PR body.** A cm2 tile is the NV_cooperative_matrix2 GEMM class stamped per -(weight format, column) pair in `dasllama/dasllama_vulkan_classes.das`. +with a runtime value - read them as 16-bit lanes instead: load the lane `w` from an `int16[N]` +block member, select the byte with `unpack8(w)[i & 1u]`, and pull sub-fields out by shift and +mask.** The vendor driver's shader compiler pattern-matches only the 16-bit spelling into its +block-load path, and a runtime byte select loses that path for the whole kernel. + +**A diff that changes when `vk_rdec_prefill_ids` - the resident prefill that takes token ids +rather than embeddings - accepts a call, or changes the override that routes to it +(`vulkan_resident_prefill`), updates `vulkan_embed_gpu_gate` in the same change** - the +override and the gate live in `dasllama/dasllama_gpu_resident.das`, `vk_rdec_prefill_ids` in +`dasllama/dasllama_vulkan_prefill.das`. The engine skips the CPU embed when that gate returns +true, so a gate true where the prefill path declines hands the next consumer an unfilled +residual stream. + +**A prefill GEMM dispatched at a nonzero start row never asks `cm2_split_k` for a split - it +encodes unsplit.** The split-k reduce sums partial planes counted from row 0, so a dispatch +starting above row 0 would reduce the wrong rows. + +**Never leave a K/V codec unserved by the kernels that read or write the whole-model driver's +`k_mirror`/`v_mirror` slabs, or the decode block's per-layer `DatLayer.k_mir`/`v_mir` pair - a +K/V codec is the mirror's element type, f16 or f32.** Two shapes serve both: instances of one +template cover both codecs, or a single-codec kernel has a sibling that serves the other codec +behind an arming gate that keys on `kv16`. The whole-model driver serves both codecs, so a +codec no kernel covers silently drops that codec's GPU path. + +**A diff that adds a cm2 tile instance for a kq superblock format - one `kq_sb` +(`dasllama/dasllama_kqformat.das`) accepts - changes such a format's cm2 decode body, or +changes its four-wide twin (`decode_v4`) or its `DECV4` or `DECVEC` constant puts that +format's `cm2:` probe rows (`harness/vk_gemm_probe.das`), both the +`DASLLAMA_VK_DECVEC=1` and the `=0` rows, in the PR body.** A cm2 tile is the +NV_cooperative_matrix2 GEMM class stamped per weight format and token-column width (the +class's `BN`) in `dasllama/dasllama_vulkan_classes.das`. **A cm2 tile format instance whose `DASLLAMA_VK_DECVEC=1` probe row is slower than its `=0` row -carries, in the same change, either a four-wide twin that wins - a hand-written `decode_v4` -under `override DECV4 = true` on that format's class (`dasllama/dasllama_vulkan_classes.das`) - -or the scalar callback: `override DECV4 = false` and `override DECVEC = false` together.** With +ships one of two fixes in the same change: a hand-written `decode_v4` under +`override DECV4 = true` on that format's class (`dasllama/dasllama_vulkan_classes.das`), +re-measured so its `=1` row now beats its `=0` row; or `override DECV4 = false` and +`override DECVEC = false` together, which puts the format back on the scalar callback.** With `DECV4 = true` the class never reads `DECVEC`, so `override DECVEC = false` alone leaves the hand-written twin running. diff --git a/modules/dasLLAMA/REVIEW_MEASUREMENT.md b/modules/dasLLAMA/REVIEW_MEASUREMENT.md index 4d4383b4a5..7a253eef13 100644 --- a/modules/dasLLAMA/REVIEW_MEASUREMENT.md +++ b/modules/dasLLAMA/REVIEW_MEASUREMENT.md @@ -30,11 +30,16 @@ board row. two processes or two commits, and `out-of-process` when the wall was measured from outside the benchmark process.** +**A `--for-debug-purposes` reading never overturns a committed board row - the board answers +to a re-minted board cell.** A `--for-debug-purposes` row is the `-jit` script's own output. + **A `PERF_LEDGER.md` entry carrying a figure from another project names the source and the report it came from and tags it `external`.** **A diff never rests an adoption decision on a figure from another project - the decision -rests on a self-measured board cell.** +rests on a self-measured board cell.** A board cell is one `performance/gen_bench_records.das` +spawns, or a manual cell of this folder's `benchmarks/lcpp_bench.das` that `PROFILE.md` +documents in a section. **A diff that adds an entry to `PERF_LEDGER.md` never records a selection timing - a timing that picks a winner between candidate kernel forms.** That timing settles its adoption @@ -56,8 +61,12 @@ skipping it. Where no row exercises the path, the diff mints one. The board is t public memory of what serving costs; a kernel win that never lands there is invisible to the next regression check. -**A diff that claims to make an already-served path faster, from an author whose box does not -mint that path, names the owed row in `PERF_LEDGER.md` and in the PR body.** +**A timing figure this module writes down as a measurement rather than as a prediction - +tok/s, latency, a whole-turn model or engine comparison, and the 512-token prefill (pp512) +and 128-token decode (tg128) rates - carries either a board cell behind it or a provenance +line, covering its passage or its section, naming harness, flags, box, and the exe or script +that ran it.** The board cell states its quant mode and stamps box and engine provenance, so a +number can never silently describe a format nobody serves or a kernel set nobody ships. **A timing figure PRESENTED AS A MEASUREMENT of a served turn as a whole - tok/s, latency, a whole-turn model or engine comparison - is a defect wherever this module writes it down with diff --git a/modules/dasLLAMA/dasllama/dasllama_arch_qwen35.das b/modules/dasLLAMA/dasllama/dasllama_arch_qwen35.das index 5c70f4f640..a72ce7fcd0 100644 --- a/modules/dasLLAMA/dasllama/dasllama_arch_qwen35.das +++ b/modules/dasLLAMA/dasllama/dasllama_arch_qwen35.das @@ -170,8 +170,8 @@ def private dn_token_core(t : Model; var s : Session; l : int64; qkv : float con } // The deltanet decode step as ONE device chain (npos=1 through MoeGpuDnFn): CPU keeps norm + -// raw β/α + the q8 image. State/conv history live DEVICE-SIDE across steps — dn_flush_layer / -// moe_gpu_dn_invalidate are the sync seams for every CPU consumer. +// raw β/α + the q8 image. +[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] def private deltanet_decode_gpu(t : Model; var s : Session; l : int64) { let c = t.config let dim = c.dim diff --git a/modules/dasLLAMA/dasllama/dasllama_common.das b/modules/dasLLAMA/dasllama/dasllama_common.das index 3b52f17154..72154697c7 100644 --- a/modules/dasLLAMA/dasllama/dasllama_common.das +++ b/modules/dasLLAMA/dasllama/dasllama_common.das @@ -1903,6 +1903,18 @@ struct SamplingParams { penalty_last_n : int64 = 64l // penalty window (most recent N generated tokens) } +//! A Session's identity for its device-resident deltanet state; the finalizer drops the GPU tier's +//! copies that state owns before its addresses can be reused. +struct DnOwner { + state_lo : uint64 + state_hi : uint64 +} + +[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] +def finalize(var o : DnOwner) { + moe_gpu_dn_release(o.state_lo, o.state_hi) +} + //! Per-step scratch buffers and the KV cache (allocated once, reused every token). struct Session { // Stable identity for device-side mirrors (Metal decode KV mirror keys on it): monotonic per @@ -2126,6 +2138,7 @@ struct Session { @exact_size dn_state : array // per recurrent layer: S — dt_rank heads x (d_state x d_state) f32 dn_conv_prefix : array // per-layer element offsets into the two above; -1 = attention layer dn_state_prefix : array + dn_owner : DnOwner dn_qkv : array // fused [q;k;v] projection out (conv_dim) dn_z : array // z gate projection (d_inner) dn_beta : array // per-v-head β (dt_rank) @@ -2272,6 +2285,10 @@ def make_run_state(c : Config; kdt : KVDtype = KVDtype.f32; vdt : KVDtype = KVDt } s.dn_conv_state |> reserve_resize(co) s.dn_state |> reserve_resize(so) // 27B-class: 48 recurrent layers x 48 heads x 128^2 = 144 MB in one grow + if (so > 0l) { + let lo = intptr(unsafe(addr(s.dn_state[0]))) + s.dn_owner = DnOwner(state_lo = lo, state_hi = lo + uint64(so * 4l)) + } s.dn_qkv |> resize(cd) s.dn_z |> resize(c.ssm_d_inner) s.dn_beta |> resize(c.ssm_dt_rank) @@ -4004,6 +4021,7 @@ let DN_CHUNK = 64l def dn_cws_head_size(c : Config) : int64 => (8l * DN_CHUNK * c.ssm_d_state + 2l * DN_CHUNK * DN_CHUNK + 3l * DN_CHUNK) +[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] def dn_reset(var s : Session) { for (i in range64(long_length(s.dn_conv_state))) { s.dn_conv_state[i] = 0.0 @@ -4011,7 +4029,7 @@ def dn_reset(var s : Session) { for (i in range64(long_length(s.dn_state))) { s.dn_state[i] = 0.0 } - moe_gpu_dn_invalidate() // CPU zeros are now authoritative — drop any device-resident copies + moe_gpu_dn_release(s.dn_owner.state_lo, s.dn_owner.state_hi) } // Sync one layer's device-resident deltanet state down before a CPU read (no-op when clean). @@ -4711,11 +4729,12 @@ def mtp_state_snapshot(var s : Session) { s.mtp_snap_pos = s.dn_pos } +[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] def mtp_state_restore(var s : Session) { copy_floats_par(s.mtp_snap_conv, s.dn_conv_state, long_length(s.mtp_snap_conv)) copy_floats_par(s.mtp_snap_state, s.dn_state, long_length(s.mtp_snap_state)) s.dn_pos = s.mtp_snap_pos - moe_gpu_dn_invalidate() // restored CPU state is authoritative — device copies are stale + moe_gpu_dn_release(s.dn_owner.state_lo, s.dn_owner.state_hi) } // After a 2-row verify forward_prefill([tok, draft], 2, pos): row 0's post-norm hidden and diff --git a/modules/dasLLAMA/dasllama/dasllama_gpu_resident.das b/modules/dasLLAMA/dasllama/dasllama_gpu_resident.das index 5b848fe4be..7573e5f6c0 100644 --- a/modules/dasLLAMA/dasllama/dasllama_gpu_resident.das +++ b/modules/dasLLAMA/dasllama/dasllama_gpu_resident.das @@ -1062,11 +1062,26 @@ def moe_gpu_drop_model_() { set_moe_gpu_model_support("") } +//! The upload rail's first step: a model still installed on the tier (its marks armed, or the +//! resident driver active) is dropped before this load uploads, with the load's own MoE layer +//! request - the one mark the drop's reset would zero - carried across. +[arch(at="../ARCHITECTURE_GPU_VULKAN.md#gpu-slot-marks")] +def moe_gpu_drop_installed_before_load(n_layers : int64) { + if (!moe_gpu_tier_installed() || !(g_rdec_active || moe_gpu_installed_marks_armed())) { + return + } + to_log(LOG_INFO, "dasLLAMA: the GPU tier still holds a model - dropping its device state before loading this one ({n_layers} layers)\n") + let want_layers = moe_gpu_layer_count() + moe_gpu_drop_model_() + set_moe_gpu_layer_count(want_layers) +} + // Upload the offloaded layers' expert stacks to the GPU tier from the PREPARED planes. Called at // the end of every load rail so DASLLAMA_GPU_MOE_LAYERS behaves identically however the model // arrived. Layers go resident from the end while the tier's VRAM budget holds. -[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#decode-attention-block"), arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#whole-token-decode-span")] +[arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#decode-attention-block"), arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#whole-token-decode-span"), arch(at="../ARCHITECTURE_GPU_VULKAN.md#gpu-slot-marks")] def moe_gpu_upload_resident(t : Model) { // nolint:STYLE037,STYLE038 — seven sequential rail walks sharing one rollback cursor; splitting them hides the partial-group unwind + moe_gpu_drop_installed_before_load(t.config.n_layers) set_moe_gpu_from_layer(t.config.n_layers) // nothing resident until an upload lands set_moe_gpu_stream_from(t.config.n_layers) set_moe_gpu_total_layers(t.config.n_layers) diff --git a/modules/dasLLAMA/dasllama/dasllama_gpu_tier.das b/modules/dasLLAMA/dasllama/dasllama_gpu_tier.das index 252026a516..f0c979875a 100644 --- a/modules/dasLLAMA/dasllama/dasllama_gpu_tier.das +++ b/modules/dasLLAMA/dasllama/dasllama_gpu_tier.das @@ -358,20 +358,27 @@ def public set_moe_gpu_dn_route(on : bool) { g_moe_gpu_dn_route = on } +//! The deltanet chain's routing gate. +def public moe_gpu_dn_route() : bool => g_moe_gpu_dn_route + // ===== deltanet DECODE step ===== // Same MoeGpuDnFn hook at npos == 1, same dn marks/uploads; separate routing gate (--dnd-ab). // State lives device-side across steps — flush/invalidate seams guard every CPU state consumer. typedef MoeGpuDnFlushFn = function<(l : int64; var stp : float?; var histp : float?) : void> typedef MoeGpuDnInvalidateFn = function<() : void> +typedef MoeGpuDnReleaseFn = function<(state_lo : uint64; state_hi : uint64) : void> [unused_argument(l, stp, histp)] def private moe_gpu_noop_dn_flush(l : int64; var stp : float?; var histp : float?) {} def private moe_gpu_noop_dn_invalidate() {} +[unused_argument(state_lo, state_hi)] +def private moe_gpu_noop_dn_release(state_lo : uint64; state_hi : uint64) {} var g_moe_gpu_dnd_route = true var g_moe_gpu_dn_flush = @@moe_gpu_noop_dn_flush var g_moe_gpu_dn_invalidate = @@moe_gpu_noop_dn_invalidate +var g_moe_gpu_dn_release = @@moe_gpu_noop_dn_release def public moe_gpu_dnd_on_gpu(l : int64) : bool { return g_moe_gpu_installed && g_moe_gpu_dnd_route && key_exists(g_moe_gpu_dn_layers, l) @@ -382,10 +389,19 @@ def public set_moe_gpu_dnd_route(on : bool) { g_moe_gpu_dnd_route = on } +//! The deltanet decode step's routing gate. +def public moe_gpu_dnd_route() : bool => g_moe_gpu_dnd_route + +//! The installed state-residency seams, for a cell that overrides one and restores the set. +def public moe_gpu_dn_state_hooks() : tuple { + return (flush = g_moe_gpu_dn_flush, inval = g_moe_gpu_dn_invalidate, release = g_moe_gpu_dn_release) +} + //! Tier registration of the state-residency seams (beside install_moe_gpu_tier). -def public set_moe_gpu_dn_state_hooks(flush : MoeGpuDnFlushFn; inval : MoeGpuDnInvalidateFn) { +def public set_moe_gpu_dn_state_hooks(flush : MoeGpuDnFlushFn; inval : MoeGpuDnInvalidateFn; release : MoeGpuDnReleaseFn) { g_moe_gpu_dn_flush = flush g_moe_gpu_dn_invalidate = inval + g_moe_gpu_dn_release = release } //! Sync one layer's device-resident deltanet state back into the engine's buffers (no-op when @@ -399,6 +415,12 @@ def public moe_gpu_dn_invalidate() { invoke(g_moe_gpu_dn_invalidate) } +//! Drop, unwritten, the device-resident deltanet copies one session's state owns: the session +//! zeroed that state, or it is dying. +def public moe_gpu_dn_release(state_lo : uint64; state_hi : uint64) { + invoke(g_moe_gpu_dn_release, state_lo, state_hi) +} + //! Loader contract: mark one full-attention layer's q/k/v/o plane quad resident on the tier — //! the prefill attention block then runs as one device chain. Reset per load. def public set_moe_gpu_attn_layer(l : int64) { @@ -666,9 +688,24 @@ def public moe_gpu_marks_save(var m : MoeGpuMarks) { //! load's arm outcome to the slot that captured it — the process-global VRAM counter cannot //! tell WHOSE planes are resident. def public moe_gpu_marks_armed(m : MoeGpuMarks) : bool { - return (!empty(m.dense_offs) || !empty(m.dn_layers) || !empty(m.attn_layers) - || !empty(m.shexp_layers) || !empty(m.qkv_layers) || m.cls_off >= 0l - || (m.total_layers > 0l && (m.from_layer < m.total_layers || m.stream_from < m.total_layers))) + return moe_gpu_marks_armed_of(m.dense_offs, m.dn_layers, m.attn_layers, m.shexp_layers, m.qkv_layers, + m.cls_off, m.total_layers, m.from_layer, m.stream_from) +} + +//! Do the INSTALLED marks carry any armed state? A load asks before installing its model: a +//! model deleted without the drop leaves its marks and its device state behind. +def public moe_gpu_installed_marks_armed() : bool { + return moe_gpu_marks_armed_of(g_moe_gpu_dense_offs, g_moe_gpu_dn_layers, g_moe_gpu_attn_layers, + g_moe_gpu_shexp_layers, g_moe_gpu_qkv_layers, g_moe_gpu_cls_off, g_moe_gpu_total_layers, + g_moe_gpu_from_layer, g_moe_gpu_stream_from) +} + +def private moe_gpu_marks_armed_of(dense_offs : table; dn_layers : table; + attn_layers, shexp_layers, qkv_layers : table; + cls_off, total_layers, from_layer, stream_from : int64) : bool { + return (!empty(dense_offs) || !empty(dn_layers) || !empty(attn_layers) + || !empty(shexp_layers) || !empty(qkv_layers) || cls_off >= 0l + || (total_layers > 0l && (from_layer < total_layers || stream_from < total_layers))) } // Release what is installed, then take `src`'s. The restore is public and nothing enforces the @@ -1503,6 +1540,9 @@ def private restore_gpu_tier_fn_defaults { // nolint:STYLE037,STYLE038 — flat if (g_moe_gpu_dn_invalidate == null) { g_moe_gpu_dn_invalidate = @@moe_gpu_noop_dn_invalidate } + if (g_moe_gpu_dn_release == null) { + g_moe_gpu_dn_release = @@moe_gpu_noop_dn_release + } if (g_moe_gpu_heat_query == null) { g_moe_gpu_heat_query = @@moe_gpu_unset_heat_query } diff --git a/modules/dasLLAMA/dasllama/dasllama_math_vulkan.das b/modules/dasLLAMA/dasllama/dasllama_math_vulkan.das index d22806f8e1..0642e706e1 100644 --- a/modules/dasLLAMA/dasllama/dasllama_math_vulkan.das +++ b/modules/dasLLAMA/dasllama/dasllama_math_vulkan.das @@ -356,7 +356,7 @@ def dasllama_math_vulkan_register() { install_moe_gpu_resident(@@vk_arena_reserve, @@vk_arena_place, @@vk_rdec_prepare, @@vk_rdec_upload_norms, @@vk_rdec_set_layer, @@vk_rdec_set_cls, @@vk_rdec_token, @@vk_rdec_prefill, @@vk_rdec_sync_kv, @@vk_rdec_read_kv, @@vk_rdec_read_kv_bulk) - set_moe_gpu_dn_state_hooks(@@vk_dn_step_flush, @@vk_dn_step_invalidate) + set_moe_gpu_dn_state_hooks(@@vk_dn_step_flush, @@vk_dn_step_invalidate, @@vk_dn_step_release) set_moe_gpu_heat_hooks(@@vk_moe_heat_query, @@vk_moe_heat_advise, @@vk_moe_ffn_begin, @@vk_moe_ffn_join) set_moe_gpu_qkv_hook(@@vk_moe_qkv) set_moe_gpu_drop_model_hook(@@vk_drop_model) diff --git a/modules/dasLLAMA/dasllama/dasllama_sampling.das b/modules/dasLLAMA/dasllama/dasllama_sampling.das index 057cbedc02..374e5a0f82 100644 --- a/modules/dasLLAMA/dasllama/dasllama_sampling.das +++ b/modules/dasLLAMA/dasllama/dasllama_sampling.das @@ -70,12 +70,12 @@ def sample_(var s : Session; params : SamplingParams) : int64 { apply_penalties_(s, params, s.logits, 0l) return argmax(s, vocab) } - return sample_row_(s, params, s.logits, 0l, vocab) + return sample_logits_row(s, params, s.logits, 0l, vocab) } //! sample_'s row form: the same sampler over the `vocab`-wide logits row at `off` in `row` //! (consumed in place). The accept walk draws each verify row through it. -def sample_row_(var s : Session; params : SamplingParams; var row : array; off, vocab : int64) : int64 { // nolint:STYLE037,STYLE038 — a flat dispatch over the sampling modes; each arm is independent and splitting it just hides the menu +def sample_logits_row(var s : Session; params : SamplingParams; var row : array; off, vocab : int64) : int64 { // nolint:STYLE037,STYLE038 — a flat dispatch over the sampling modes; each arm is independent and splitting it just hides the menu apply_penalties_(s, params, row, off) if (params.temp <= 0.0) { var mi = 0l @@ -167,7 +167,7 @@ def sample_row_(var s : Session; params : SamplingParams; var row : array } def private mtp_sample_row(var s : Session; var rows : array; off, vocab : int64) : int64 { - return sample_row_(s, deref(s.spec_params), rows, off, vocab) + return sample_logits_row(s, deref(s.spec_params), rows, off, vocab) } [init] diff --git a/modules/dasLLAMA/dasllama/dasllama_scheduler.das b/modules/dasLLAMA/dasllama/dasllama_scheduler.das index aab34059b0..2c8cdcab99 100644 --- a/modules/dasLLAMA/dasllama/dasllama_scheduler.das +++ b/modules/dasLLAMA/dasllama/dasllama_scheduler.das @@ -348,6 +348,7 @@ def evict(var sch : Scheduler; id : int64; var events : array) : boo //! over every decoding stream, at most ONE chunk_tokens prefill chunk (FCFS; the final chunk's //! logits sample the first token inline), then reap finished streams. Events append to `events` //! (the caller drains it). Returns true while any stream or queued request remains. +[arch(at="../ARCHITECTURE_ENGINE.md#scheduler-step")] def scheduler_step(m : Model; var sch : Scheduler; var events : array) : bool { // nolint:STYLE037,STYLE038 - admit/decode/prefill/reap, one linear pass // (0) drop finished-stream linger entries past their poll window (bounded backstop at 32). // get_time_nsec, NOT get_time_usec: the usec helper returns int and wraps at ~35.8 min, @@ -463,6 +464,9 @@ def scheduler_step(m : Model; var sch : Scheduler; var events : array clear() + sch.batch_toks |> clear() + sch.batch_idx |> clear() // (3) at most ONE prefill chunk, FCFS — decode-priority with a bounded prefill quantum for (i in range64(long_length(sch.streams))) { if (sch.streams[i].state != StreamState.prefilling) { diff --git a/modules/dasLLAMA/dasllama/dasllama_vulkan_common.das b/modules/dasLLAMA/dasllama/dasllama_vulkan_common.das index 24c311f537..c469c98ef0 100644 --- a/modules/dasLLAMA/dasllama/dasllama_vulkan_common.das +++ b/modules/dasLLAMA/dasllama/dasllama_vulkan_common.das @@ -440,6 +440,8 @@ struct DnStep { tok : HostBuf // the step's TokMeta block (parity, host-poked per step) valid : bool dirty : bool + owner_state_addr : uint64 + owner_hist_addr : uint64 } struct QkvCmd { diff --git a/modules/dasLLAMA/dasllama/dasllama_vulkan_decode.das b/modules/dasLLAMA/dasllama/dasllama_vulkan_decode.das index bf142d1b6d..3aa9cf4296 100644 --- a/modules/dasLLAMA/dasllama/dasllama_vulkan_decode.das +++ b/modules/dasLLAMA/dasllama/dasllama_vulkan_decode.das @@ -654,6 +654,7 @@ def vk_rdec_read_kv(l, pos : int64; kp : uint8?; vp : uint8?) { // One pre-recorded submit per layer per token: qkv+z GEMVs into a SHARED y buffer -> the fused // step kernel -> out GEMV -> y DMA. State/ring stay device-resident; flush/invalidate = the seams. +[cold_path] def private ensure_dnd_state { if (g_gpu.dnd_ready) { return @@ -669,6 +670,7 @@ def private ensure_dnd_state { // create one layer's step chain: per-layer state/smalls buffers, the three sets, the once-filled // GEMV metas (dn stacks never serve FFN GEMVs, so their host metas are ours to pin), and the // warm cmd — per token only acts/β/γ refresh and the cmd resubmits as-is. +[cold_path] def private dnd_step_make(s_qkv, s_z, s_o : int; woq, woz, woo : int64; dim, cd, kd, di, nvh, nkh, ds, taps : int64; eps : float) { ensure_gemv_scratch(s_qkv) @@ -686,7 +688,8 @@ def private dnd_step_make(s_qkv, s_z, s_o : int; woq, woz, woo : int64; smalls_dev = make_device_buf(DND_SMALLS_BYTES), cmd = alloc_cmd(), s_qkv = s_qkv, s_z = s_z, s_o = s_o, state_bytes = nvh * ds * ds * 4l, ring_bytes = cd * taps * 4l, - parity = 0, tok <- make_host_buf(16l, true), valid = false, dirty = false) + parity = 0, tok <- make_host_buf(16l, true), valid = false, dirty = false, + owner_state_addr = 0ul, owner_hist_addr = 0ul) let fqkv = g_gpu.stacks[s_qkv].fmt let fz = g_gpu.stacks[s_z].fmt let fo = g_gpu.stacks[s_o].fmt @@ -766,6 +769,7 @@ def private dnd_cold_upload(st : DnStep; stp : float const?; histp : float const submit_wait(g_gpu.batch_cmd) } +[hot_path, arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] def vk_moe_dn_step(var yp : float?; s_qkv, s_z, s_o : int; woq, woz, woo : int64; xqp : int8 const?; xsp : float const?; bp : float const?; gp : float const?; convw : float const?; wnormp : float const?; ap : float const?; dtp : float const?; @@ -780,10 +784,23 @@ def vk_moe_dn_step(var yp : float?; s_qkv, s_z, s_o : int; woq, woz, woo : int64 if (!key_exists(g_gpu.dnd_steps, woq)) { dnd_step_make(s_qkv, s_z, s_o, woq, woz, woo, dim, cd, kd, di, nvh, nkh, ds, taps, eps) } - var st = g_gpu.dnd_steps[woq] + var st : DnStep? + unsafe { + st = g_gpu.dnd_steps?[woq] // the make above made the key: a read, never an insert + } + let caller_state_addr = intptr(stp) + if (st.valid && st.owner_state_addr != caller_state_addr) { + if (st.dirty) { + dnd_flush_down(*st) + } + st.valid = false + st.dirty = false + } if (!st.valid) { - dnd_cold_upload(st, stp, histp, convw, wnormp, ap, dtp, cd, taps, dconv, nvh, ds, eps) - st.parity = 0 // the cold image is ring image 0 + dnd_cold_upload(*st, stp, histp, convw, wnormp, ap, dtp, cd, taps, dconv, nvh, ds, eps) + st.parity = 0 + st.owner_state_addr = caller_state_addr + st.owner_hist_addr = intptr(histp) } let ts0 = ref_time_ticks() fill_stack_acts(s_qkv, xqp, xsp, dim) // the z GEMV set shares this image @@ -800,18 +817,15 @@ def vk_moe_dn_step(var yp : float?; s_qkv, s_z, s_o : int; woq, woz, woo : int64 unsafe { memcpy(reinterpret(yp), g_gpu.stacks[s_o].y.mapped, dim * 4l) } - st.parity = 1 - st.parity // the step wrote the other image; it now holds current history + st.parity = 1 - st.parity st.valid = true st.dirty = true - g_gpu.dnd_steps[woq] = st if (vk_prof()) { - to_log(LOG_INFO, "vk_dnd prep {get_time_usec(ts0) - get_time_usec(ts1)} submit {get_time_usec(ts1) - get_time_usec(ts2)} copy {get_time_usec(ts2)}\n") + to_log(LOG_INFO, "vk_dnd prep {get_time_usec(ts0) - get_time_usec(ts1)} submit {get_time_usec(ts1) - get_time_usec(ts2)} copy {get_time_usec(ts2)}\n") // nolint:PERF026,PERF028 — vk_prof()-gated report leg } } -// MoeGpuDnFlushFn: sync one layer's device-resident state/ring down into the engine's buffers. -// Any flush retires the device copy (the CPU is about to read AND may then advance on its own) — -// the next GPU step cold-uploads from the engine's then-current state. +[hot_path, arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner"), unused_argument(histp)] def vk_dn_step_flush(l : int64; var stp : float?; var histp : float?) { if (g_gpu == null || !g_gpu.dnd_ready) { return @@ -820,36 +834,63 @@ def vk_dn_step_flush(l : int64; var stp : float?; var histp : float?) { if (woq < 0l || !key_exists(g_gpu.dnd_steps, woq)) { return } - var st = g_gpu.dnd_steps[woq] + var st : DnStep? + unsafe { + st = g_gpu.dnd_steps?[woq] // the key_exists above made sure of the key: a read, never an insert + } + let caller_state_addr = intptr(stp) + if (st.owner_state_addr != caller_state_addr) { + return + } if (st.dirty) { - let raw = g_gpu.batch_cmd - let rf : VkCommandBufferResetFlags - vk_check(vkResetCommandBuffer(raw, rf), null) - let begin = VkCommandBufferBeginInfo() - vk_check(vkBeginCommandBuffer(raw, begin), null) - cmd_copy_whole(raw, st.state_dev, g_gpu.dn_state_host.buf, st.state_bytes) - cmd_copy_range(raw, st.smalls_dev, DN_SM_HIST * 4l + int64(st.parity) * st.ring_bytes, - g_gpu.dn_tail_host.buf, 0l, st.ring_bytes) - vk_check(vkEndCommandBuffer(raw), null) - submit_wait(g_gpu.batch_cmd) - unsafe { - memcpy(reinterpret(stp), g_gpu.dn_state_host.mapped, st.state_bytes) - memcpy(reinterpret(histp), g_gpu.dn_tail_host.mapped, st.ring_bytes) - } + dnd_flush_down(*st) } - st.dirty = false + dnd_drop_slot(*st) +} + +def private dnd_drop_slot(var st : DnStep) { st.valid = false - g_gpu.dnd_steps[woq] = st + st.dirty = false + st.owner_state_addr = 0ul + st.owner_hist_addr = 0ul } -// MoeGpuDnInvalidateFn: the engine rebuilt/zeroed its CPU state — every device copy is stale +def private dnd_flush_down(st : DnStep) { + let raw = g_gpu.batch_cmd + let rf : VkCommandBufferResetFlags + vk_check(vkResetCommandBuffer(raw, rf), null) + let begin = VkCommandBufferBeginInfo() + vk_check(vkBeginCommandBuffer(raw, begin), null) + cmd_copy_whole(raw, st.state_dev, g_gpu.dn_state_host.buf, st.state_bytes) + cmd_copy_range(raw, st.smalls_dev, DN_SM_HIST * 4l + int64(st.parity) * st.ring_bytes, + g_gpu.dn_tail_host.buf, 0l, st.ring_bytes) + vk_check(vkEndCommandBuffer(raw), null) + submit_wait(g_gpu.batch_cmd) + unsafe { + memcpy(reinterpret(st.owner_state_addr), g_gpu.dn_state_host.mapped, st.state_bytes) + memcpy(reinterpret(st.owner_hist_addr), g_gpu.dn_tail_host.mapped, st.ring_bytes) + } +} + +[cold_path, arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] // a model drop, never the served token def vk_dn_step_invalidate { if (g_gpu == null) { return } for (st in values(g_gpu.dnd_steps)) { - st.valid = false - st.dirty = false + dnd_drop_slot(st) + } +} + +[hot_path, arch(at="../ARCHITECTURE_GPU_VULKAN_DECODE.md#dn-step-owner")] // per session reset, rollback and delete +def vk_dn_step_release(state_lo : uint64; state_hi : uint64) { + if (g_gpu == null) { + return + } + for (st in values(g_gpu.dnd_steps)) { + if (st.owner_state_addr >= state_lo && st.owner_state_addr < state_hi) { + dnd_drop_slot(st) + } } } diff --git a/modules/dasLLAMA/followup_vulkan.md b/modules/dasLLAMA/followup_vulkan.md index 8572844f1e..dfa4d790da 100644 --- a/modules/dasLLAMA/followup_vulkan.md +++ b/modules/dasLLAMA/followup_vulkan.md @@ -551,6 +551,17 @@ module) is independent and can land any time - it is pure structure. the quant block; (4) pad N to the tile width. `harness/vk_gemm_probe.das` already carries the isolation arms (`ref` = llama.cpp's own coopmat2 blob in our harness, `k6x flat` = compose without scale reads). Boris 2026-08-30: this one bothers him at 0.7. Plan: `plans/kernel_parity_pass.md`. + CLOSED at the debug rig (2026-09-03, after #3926: the split-k group, the hand-laid twins, the + 32-row last layer, the parallel embed; `lcpp_bench --for-debug-purposes --plen 512 --ngen 0 + --reps 12` on the RTX 5060 Ti bracketed by `llama-bench -p 512 -n 0 -r 6` of the vector build, + rows in `plans/kernel_parity_pass.md`; the board rows stay OWED in `PERF_LEDGER.md`): nine of + ten 1B vehicles at or past llama.cpp on pp512, IQ2_XXS at 0.98. What remains is per tile, not per + board. Read against llama.cpp's per-role windows (`GGML_VK_PERF_LOGGER=1 llama-bench` beside + `DASLLAMA_GPU_PROF=1 lcpp_bench`, the role table in `plans/kernel_parity_pass.md`): the iq2xxs + gate/up tile at 1.15 of llama.cpp's rate (its grid decode still shows where theirs is hidden), + k3 and iq3s gate/up at 1.05-1.09, down at 1.04-1.14 on the 2048-wide shape. The work for + those is the memo's delta 1 (scale hoist), delta 2 (scale interleave) and delta 8 (codebook + and prologue) rows, each behind its own probe A/B. 35. **The grid-format GEMV workgroup re-stage is a fixed per-workgroup cost - amplified on small models.** Every u64-grid gemv (iq2s 8 KB, iq2xs 4 KB) stages the codebook into @@ -592,3 +603,27 @@ module) is independent and can land any time - it is pure structure. methods; a gather class needs the same members or a shared free decode), then `rdec_set_emb` for `cls_kq`. Done = the embed bucket gone and the x upload out of prep on the Q4_K_M window, parity pregate token-for-token on Q8_0 and a kq vehicle. + +38. **Per-session device slots for the deltanet decode step.** The step keeps one resident + copy of each recurrent layer's state, owned by one session at a time + (`ARCHITECTURE_GPU_VULKAN_DECODE.md` sec.2.2u): two streams decoding turn about on the + per-op tier pay a flush and a cold upload per recurrent layer per switch - correct since + 2026-09-03 (a user's two concurrent requests on Qwen3.5-9B-MTP had read each other's + state), slow by construction. Lever: N state slots per layer keyed by owner, the + scheduler's rows mapped to slots, the fused step kernel taking a slot index - the same + shape the batched deltanet step needs anyway. Done = two streams' tg on the 0.8B within + the single-stream rate's band, `test_scheduler_batching_deltanet_gpu` green. + +39. **CLOSED - `tests/test_scheduler.das` under the armed tier.** Two causes, both landed with + the session-ownership fix. The order dependence (a model loaded before the deltanet cell + moved its first decode step; the MTP cell red behind the SmolLM cells) was the model-swap + defect: a deleted model's device state stayed installed and the next load uploaded beside + it, so the offset-keyed stack lookup served the earlier model's planes - the upload rail + now drops a still-installed model first (`ARCHITECTURE_GPU_VULKAN.md` 2.2o, + `tests/test_gpu_model_swap.das`). The SmolLM reds (chunk sizes, batching, evict, media + splice, mrope) were cross-lane numerics, not defects: the resident driver's device prefill, + its batch decode and the CPU prefill agree on every argmax over a few steps and drift by + about a logit on a scale of twenty, enough for a 135M model to flip a near-tie; those cells + claim bit-exact mechanics, so they pin the CPU lane (`moe_gpu_drop_model` after the load). + Done = the file green, twice in a row, under `DASLLAMA_GPU=1` on the 5060 Ti, the cell + order free. diff --git a/modules/dasLLAMA/tests/CLAUDE.md b/modules/dasLLAMA/tests/CLAUDE.md index 08ed165259..855b08aeea 100644 --- a/modules/dasLLAMA/tests/CLAUDE.md +++ b/modules/dasLLAMA/tests/CLAUDE.md @@ -249,6 +249,17 @@ thinking family's wire shape, whole-string and per-chunk down to 1 byte. serializers and call parsers for harmony/gemma4/mistral/llama_json against verbatim fixtures. `test_scheduler.das` - the continuous-batching scheduler (dasllama_scheduler) against `generate()` references; skips honestly without SmolLM2-135M / the MTP fixture, `-jit` only. +The SmolLM cells drop the loaded model's GPU state (`moe_gpu_drop_model`) so they serve on +the CPU rails under `DASLLAMA_GPU=1` too: their bit-exact claims hold on one lane, and the +tier's device prefill, resident batch decode and CPU prefill round differently. Its two-stream +deltanet cell needs Qwen3.5-0.8B-Q8_0 and `DASLLAMA_GPU=1` on a box whose tier serves the +deltanet decode step, and skips otherwise. +`test_gpu_model_swap.das` - two models through one process on the armed tier (Qwen3-0.6B, +SmolLM2-135M, `DASLLAMA_GPU=1`): a model reloaded behind the other decodes its own weights, +the pin on the upload rail dropping a still-installed model's device state first; skips +without both models or the armed tier. Its model-free cell drives that pre-load drop through +its own entry with a stale mark installed and checks the MoE layer request survives it +(needs only an installed tier). `test_program_roots.das` - model-free: every dasllama program root (tutorials, examples, server tools) declares `options stack = 524288`, and every model-loading root declares its prefill intent. @@ -319,7 +330,9 @@ branches that decide what lands on the public board, which no model suite reache grows past the `max_unreserved_size` guard that must not panic. `test_deltanet.das` - model-free: the deltanet session-state sizing at 27B geometry through `make_run_state` (S state + widened-conv history past the guard); model-gated: the -chunked-vs-recurrent prefill equivalence probe on Qwen3.5-0.8B. +chunked-vs-recurrent prefill equivalence probe on Qwen3.5-0.8B, in the forced-feed +logits-tolerance form with the tier's deltanet chain and decode step pinned off (its claim is +the CPU chunk algebra). `test_vision.das` - model-free: the vision preprocessing rail (geometry, letterbox, normalize) bit-exact against pinned mtmd oracle hashes (dumps + mint scripts in the models dir's `gemma4-vision-oracle/` and `qwen3vl-vision-oracle/` - the qwen rail letterboxes at align 32), diff --git a/modules/dasLLAMA/tests/REVIEW.md b/modules/dasLLAMA/tests/REVIEW.md index ef04881d6c..7db19a7458 100644 --- a/modules/dasLLAMA/tests/REVIEW.md +++ b/modules/dasLLAMA/tests/REVIEW.md @@ -1,7 +1,8 @@ # dasLLAMA tests Code Review Checklist **Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture -doc: `CLAUDE.md`. Planned work: the parent folder's `../followup_*.md` ledgers. +doc: `CLAUDE.md`. Planned work: `../followup_general.md`, `../followup_vulkan.md`, +`../followup_metal.md`. **Every PR runs `run.das -- --suite model-free`, plus every test here the change reaches - never the whole directory.** A change reaches a test when it alters anything the test's result @@ -24,10 +25,12 @@ a run of skips is not the coverage the suite owes. **A diff that registers a test file in this folder in a `CMakeLists.txt` is a defect - a `run.das` suite listing is the only registration these files get.** -**A diff that adds a gate, removes a gate, or moves a gate to another suite or arm, in a file -whose `CLAUDE.md` paragraph LISTS its gates, updates that paragraph in the same change.** A -paragraph that only names the file (a brace list, a suite roster) carries nothing to update -and does not fire this rule. +**A diff that adds a gate (one test cell), removes a gate, or moves a gate to another suite or +arm, in a file whose `CLAUDE.md` paragraph LISTS its gates, updates that paragraph in the same +change.** A +paragraph lists its gates when any clause of it names a specific fixture, model, arm, or skip +condition; a paragraph that only names the file (a brace list, a suite roster) carries nothing +to update. **A new test file listed in `run.das`'s `model-free` suite, or in no `run.das` suite at all, whose name does not say what it covers, gets a `CLAUDE.md` entry in the same change** - @@ -52,32 +55,30 @@ bypass check (no cached hit at `prefix_attach`, no donated pages at `donate_stre `test_vulkan_kernels.das`'s tile-pick cell (which tile the Vulkan matmul picks for a given width and row count, and whether that dispatch splits its reduction across partial planes). A gate whose failure means a documented contract changed, rather than a kernel regressing, joins -this list in the same change - as a file when -every cell of it pins, as a named cell otherwise. +this list in the same change - as a file when every cell of it pins, as a named cell otherwise. -**A test passes or skips explicitly on every platform - a cell that neither asserts nor -registers a skip is a defect.** A cell that returns without asserting - -the module is absent, its models are not stocked, no device answered, a capability declined - -registers `t |> skip` there; `feint` is a print, not a skip. +**On every platform, a cell that neither asserts nor registers a skip is a defect.** A cell +that returns without asserting - the module is absent, +its models are not stocked, no device answered, a capability declined - registers `t |> skip` +there; `feint` is a print, not a skip. -**A skip gate keys on a fact the box owns - a device capability, a run-mode knob's value, a -host toolchain's presence, a compile-time module-presence check +**A cell's skip condition keys on a fact the box owns - a device capability, a run-mode knob's +value, a host toolchain's presence, a compile-time module-presence check (`typeinfo builtin_module_exists`) - or on a stocked fixture beside the models (a model -file, an mmproj, an oracle dump - a model gate); never on the existence of an artifact this -repo's build or a previous test run produced (a mint, a generated binary, a dump a test -wrote).** An artifact gate goes permanently false when its producer moves. +file, an mmproj, an oracle dump - a model condition); never on the existence of an artifact +this repo's build or a previous test run produced (a minted `.dlim`, a generated binary, a +dump a test wrote).** An artifact condition goes permanently false when its producer moves. **A test that loads a model above the large tier (`LARGE_TIER_BYTES`, `_model_tier.das`) -without gating on `DASLLAMA_PARITY_FULL=1` is a defect** - that gate is a final pre-PR gate, -not the iteration loop. In this folder the -spelling is `model_available` (`_model_tier.das`). A test that cannot require -`_model_tier.das` open-codes the same env check. +without gating on `DASLLAMA_PARITY_FULL=1` is a defect** - `DASLLAMA_PARITY_FULL=1` is a final +pre-PR switch, not the iteration loop. In this folder the spelling is `model_available` (`_model_tier.das`). +A test that cannot require `_model_tier.das` open-codes the same env check. **A test - or a program a test builds or spawns - whose subject is not the `.dlim` image rail never calls `load_model`, `load_model_cached`, or `load_model_image` - it loads each carrier through that carrier's own loader.** Decoders load through `load_model_` -(`../dasllama/dasllama_load.das`). Towers, embedders, and union carriers load through their -family or carrier loaders. +(`../dasllama/dasllama_load.das`); every other carrier loads through its own +`load__tower` / `load__encoder` / `load__model`. **A predicate whose value the BOX decides (a device capability, a policy default) and that therefore cannot differ between two runs on one machine is never tested through its own @@ -106,23 +107,26 @@ agree. **A new pre-tokenizer family or backend ships its `corpus_case` arm in `test_tokenizer.das`, naming the `ggml-vocab-*.gguf` fixture.** -**A `corpus_case` arm missing either an exact-reference-id assert or a lossless round-trip -assert is a defect.** +**A `corpus_case` arm that does not assert BOTH the exact reference ids and a lossless +round-trip is a defect.** **A test that compares generated tokens, ids, or logits without logging a human-readable form -of BOTH sides is a defect: the decoded text where the model carries a vocab (`log_gen_texts` -in `_model_tier.das`, or one line per side), otherwise each side's argmax index and value.** A -red, or a suspicious green, must be readable in the log, not only as an id or float +of BOTH sides is a defect: for a token or id compare the decoded text (`log_gen_texts` in +`_model_tier.das`, or one line per side), for a logits compare each side's argmax index and +value.** A red, or a suspicious green, must be readable in the log, not only as an id or float difference. **A cell whose name, comment, arm, or fixture claims a size, depth, or row count asserts that number.** A cap, a resize, or a counter showing the path ran is not evidence that the number was reached. -**A freeform token-parity cell is a defect.** Freeform coverage uses the forced-feed -logits-tolerance form - the same fixed tokens fed to both sides, logits compared within a bar. -Counting cells - those whose prompt forces a continuation that cannot tie, so greedy tokens -are fixed - stay token-exact. +**A freeform token-parity cell whose two sides can round differently - different lanes, +backends, batch shapes or kernel forms - is a defect, and a token-exact freeform compare +states in the cell what makes its two sides one code path: the shared entry point, or an +assert pinning the lane.** Freeform coverage across a pair that rounds differently uses the +forced-feed logits-tolerance form - the same fixed tokens fed to both sides, logits compared +within a bar. Counting cells - those whose prompt forces a continuation that cannot tie, so +greedy tokens are fixed - stay token-exact. **A kernel-unit cell - a model-less cell that dispatches one kernel class and asserts on its output - missing a compare against a CPU oracle that can witness the cell's property is a @@ -143,13 +147,13 @@ which can wrongly satisfy a tolerant compare. **An ASR family with no token-for-token oracle cell is a defect** - the cell compares a transcript against a reference leg, external dump or CPU control alike. -**A test in this folder that loads a stocked artifact not traceable to something checked in is -a defect.** Stocked artifacts are model files, mmprojs, image fixtures, and oracle dumps. Any -one of these counts as traceable: a row in `../performance/model_specs.das`; a row's +**A test in this folder that loads a stocked artifact whose producer the test cannot name is a +defect.** Stocked artifacts are model files, mmprojs, image fixtures, and oracle dumps. Any +one of these names the producer: a row in `../performance/model_specs.das`; a row's `companions` list; a row in `asr_catalog` (`../performance/profile_common.das`); a convert -script beside `model_specs.das` in `../performance/`. For an oracle dump, a mint script that -regenerates it also counts - it sits beside the dumps under `models_dir()`, and the test that -loads the dump names it. +script beside `model_specs.das` in `../performance/`. For an oracle dump, the mint script +stocked beside the dumps under `models_dir()` also counts, and the test that loads the dump +names it. **A test that reads a vision encode oracle dump without naming the minting arm in its header - the backend, the flash-attention setting, and the mmproj precision the dump came from - is a @@ -158,15 +162,14 @@ defect.** **A cell that does not establish every process-wide driver setter and serving-lane knob its claim depends on, and restore each knob it set in-process to the value it had on entry before returning, is a defect.** This holds even when the claim needs the knob at its DEFAULT value. -The mechanism -(why the hooks flip legs silently) is `CLAUDE.md`'s "Metal fixtures" section. +The mechanism (why a hook left set silently changes which implementation the next cell +measures) is `CLAUDE.md`'s "Metal fixtures" section. **A cell claiming a family serving lane that does not pin it through the family's own lane knobs - `set__q8` / `reset__q8`, canary's `set_canary_enc_q8` / `reset_canary_enc_q8`, whisper's `set_asr_fp32` / `set_asr_tower_fp32` - or through a loader -parameter that takes the lane, is a defect.** A family whose knob has no reset twin restores -it to the documented default. A runtime decline standing in for a pin measures whichever -lane the box's policy picked. +parameter that takes the lane, is a defect.** A runtime decline standing in for a pin measures +whichever lane the box's policy picked. **A cell that loads a media carrier under a lane pin - a `set__q8`-class knob, or a `set_metal_tensor_crowns` / `pin_metal_tensor_crowns` pin - and whose subject is not that lane @@ -180,12 +183,11 @@ another suite panics on the wrong identity. through the `.dlim`-baking loader, never around it.** The pin is part of what the image identity records. -**A CPU-vs-GPU arm that does not run a PLANAR model - the non-blob form, the only one CPU -inference reads - for its CPU stages, and that model's blob twin (`blob_twin(t, path, -seq_cap)`, `test_metal_decode_parity.das`) for the stages a decode override selects, is a -defect.** One session spans the planar model and its blob twin, because sessions are -geometry-bound. A cell whose subject is why the GPU lane declined keeps the planar model and -needs no twin. +**A CPU-vs-GPU arm on Metal - one comparing the two lanes' outputs, not one whose subject is +the GPU lane's decline - runs its CPU stages on a PLANAR model (the non-blob form, the only +one CPU inference reads) and the stages a decode override selects on that model's blob twin +(`blob_twin(t, path, seq_cap)`, `test_metal_decode_parity.das`), in one session.** The planar +model and its blob twin share one shape, so one session serves both. **A diff that adds a model-loading block to a `run.das` MODEL suite (every suite the `--family` filter reaches - not the model-free suite) tags it with its family.** The family @@ -200,18 +202,18 @@ starts is a defect - set it before that process starts.** That process is a chil spawns, or the runner's own. An in-cell set is invisible to the running config, which is read once at context init. -**A cell that cannot set an environment-read knob before its reader starts, and whose assert -text does not name the value it asserts under, is a defect.** An environment-read knob is one +**A cell that cannot set an environment-read knob before its reader starts, and whose text +that prints with a red - the cell label or the assert - does not name the value it asserts +under, is a defect.** An environment-read knob is one the running config reads once, at context init. **A cell asserting the UNPINNED default lane never compares against a hardcoded lane - it compares against the predicates the lane policy itself consults, `float_batch_override_active()` -and the family's `_gpu_would_serve()` where one exists.** The accelerate tier and the -GPU tower move the default per box, so the assert is on the lane the policy selects, not on -one predicate's own value. +and the family's `_gpu_would_serve()` where one exists.** The default lane differs +per box, so the assert is on the lane the policy selects, not on one predicate's own value. -**A cell that encodes, preprocesses, or asserts on media bytes an encoder consumes - pixels -or audio samples, not a `.dlim` model image - with no model loaded builds its fixture +**A cell that runs with no model loaded and encodes, preprocesses, or asserts on media bytes +an encoder consumes - pixels or audio samples, not a `.dlim` model image - builds its fixture procedurally and pins its expectations in-repo.** **An image a test feeds an embedder that the test does not build, and that @@ -219,12 +221,12 @@ procedurally and pins its expectations in-repo.** instrumentation before a human can see what the model consumed. **An audio clip a test feeds an embedder that the test does not build, and that is not one of -the checked-in fixtures beside the models (`jfk.wav`, `gemma4a_test2.wav`), is a defect** - a -clip nobody else can play makes a red unreadable. A newly checked-in clip joins this list in -the same change. +the clips stocked beside the models (`jfk.wav`, `gemma4a_test2.wav`), is a defect** - a clip +nobody else can play makes a red unreadable. A newly stocked clip joins this list in the same +change. -**A tier-1 media fixture - one an embedder-parity cell regenerates in-test and compares -against an oracle dump - with no exact-value generator is a defect.** A generator running libm +**A media fixture an embedder-parity cell regenerates in-test and compares against an oracle +dump, with no exact-value generator, is a defect.** A generator running libm transcendentals is not exact-value: it is not float-portable. **An embedding-parity cell that does not name its fixture, or does not log the measured @@ -239,9 +241,9 @@ mechanism, or a second independent lane; a gate's own reference is never its con outside the new bar.** A bar nothing has ever exceeded is not known to discriminate. **A family that gains a live thinking or tool format ships its recognition tests in the same -change** - the wire-shape pins, the render pins, and a live server leg gated on the family's -smallest GGUF that runs on the small tier (the file homes are `CLAUDE.md`'s "Model-free / -no-arm tests" and "Out-of-folder test files" notes). A family whose vocab carries no thinking +change** - the wire-shape pins, the render pins, and a live server case gated on the family's +smallest GGUF that sits under `LARGE_TIER_BYTES` (`_model_tier.das`) (the file homes are +`CLAUDE.md`'s "Model-free / no-arm tests" and "Out-of-folder test files" notes). A family whose vocab carries no thinking or tool markers has no format to test. **A kernel-unit cell whose kernel reads f16 operands and whose oracle is wider-precision @@ -255,19 +257,20 @@ then reds the ordinary compare, so the gate needs no separate leak control. **A cell whose only compare is bit-identity between two kernel forms also compares one of the two against a CPU oracle, in the same cell.** Two forms can be bit-equal and both wrong. -**A poison leg on a tower the Metal driver serves reaches every weight buffer the served route -reads.** Which buffers those are depends on the route: a twin-W route reads the baked halfword -twin (`wblob`), so poisoning that buffer alone is a valid control there, while a route reading -both buffers needs both zeroed. A poison the served route never reads passes on a broken -kernel. +**A poison control on a tower the Metal driver serves - a run of the gate with the tower's +weights zeroed, which must RED - zeroes every weight buffer the served route reads.** Which +buffers those are depends on the route: a twin-W route reads only the baked halfword copy of +the GEMM weights (`wblob`), so zeroing that buffer alone is a valid control there; a route +that also reads the f32 plane (`fblob`) needs both zeroed. A poison the served route never +reads passes on a broken kernel. -**An ASR cell comparing transcripts across two serving lanes, other than a tune-armed kernel -form against its Metal-tensor twin, asserts TOKEN equality.** +**An ASR cell comparing transcripts across two serving lanes, other than a crowned lane +against its tensor twin, asserts TOKEN equality.** A crowned lane is the kernel form the tuner +measured fastest and armed as the serving one; its tensor twin is the same kernel written on +Metal's tensor primitives. **An ASR cell comparing a crowned lane against its tensor twin asserts WORD equality** - the -twins' rounding legitimately flips tokens. A crowned lane is the raced kernel form a tune -sidecar arms as the serving one; its tensor twin is the same kernel written on Metal's tensor -primitives. +twins' rounding legitimately flips tokens. **An ASR transcript cell that cannot assert the equality its comparison calls for converts to a forced-feed logits compare within a tolerance bar - never to a looser text compare.** diff --git a/modules/dasLLAMA/tests/_model_tier.das b/modules/dasLLAMA/tests/_model_tier.das index b63adb7e4c..99df961a57 100644 --- a/modules/dasLLAMA/tests/_model_tier.das +++ b/modules/dasLLAMA/tests/_model_tier.das @@ -26,6 +26,37 @@ require dasllama/dasllama_env // (incl. gemma-4-E2B at 4.97GB) from the 7.7GB+ heavies. let LARGE_TIER_BYTES = 6ul * 1024ul * 1024ul * 1024ul +//! Max |a[i] - b[i]| over two same-length float arrays - the compare behind a logits bar. +def logits_maxdiff(a, b : array) : float { + var m = 0.0 + for (x, y in a, b) { + m = max(m, abs(x - y)) + } + return m +} + +//! Max |a[i]| - the scale a relative logits bar multiplies. +def logits_maxabs(a : array) : float { + var m = 0.0 + for (x in a) { + m = max(m, abs(x)) + } + return m +} + +//! Element-for-element equality of two id lists. +def ids_equal(a, b : array) : bool { + if (length(a) != length(b)) { + return false + } + for (x, y in a, b) { + if (x != y) { + return false + } + } + return true +} + // Machine-local model root; override with DASLLAMA_MODELS_DIR (e.g. the x64 handoff box). def models_dir() : string { let env = g_env_harness.models_dir diff --git a/modules/dasLLAMA/tests/run.das b/modules/dasLLAMA/tests/run.das index 87551ca5a4..857752443b 100644 --- a/modules/dasLLAMA/tests/run.das +++ b/modules/dasLLAMA/tests/run.das @@ -110,6 +110,7 @@ def private suite_files(name : string) : array { // nolint:STYLE038 - "modules/dasLLAMA/tests/test_gemma4v.das", "modules/dasLLAMA/tests/test_gguf_quant.das", "modules/dasLLAMA/tests/test_gguf_shards.das", + "modules/dasLLAMA/tests/test_gpu_model_swap.das", "modules/dasLLAMA/tests/test_groupn.das", "modules/dasLLAMA/tests/test_kernel_backend.das", "modules/dasLLAMA/tests/test_kgroup_repack.das", diff --git a/modules/dasLLAMA/tests/test_deltanet.das b/modules/dasLLAMA/tests/test_deltanet.das index 931c9514fc..d48d107e53 100644 --- a/modules/dasLLAMA/tests/test_deltanet.das +++ b/modules/dasLLAMA/tests/test_deltanet.das @@ -13,6 +13,7 @@ def private allow_cpu_prefill_intent() { require daslib/jobque_boost require daslib/fio require dasllama/dasllama_math // setup_dasllama_jobque_ (the engine-standard jobque config) +require dasllama/dasllama_gpu_tier // the deltanet chain and decode-step route pins require _model_tier // models_dir() + model_available() presence/size-tier gate // Chunked-vs-recurrent deltanet prefill equivalence on Qwen3.5-0.8B. The 20-token parity fixture @@ -20,9 +21,13 @@ require _model_tier // models_dir() + model_available() presence/size-tier gat // 64-token chunks plus a partial tail — exercising chunk boundaries, partial-chunk math, and the // batched-conv history handoff. The two forms are NOT bit-identical (chunk FP reassociation; // upstream accepts the same divergence between its autoregressive and chunking paths), so the -// gate is id-level: same argmax after prefill, then token-for-token agreement over an 8-token -// greedy continuation — which also proves the conv/S state the chunked prefill leaves behind -// feeds the recurrent decode correctly. +// gate is the forced-feed logits-tolerance form: the prefill logits within a bar of each other, +// then the recurrent arm's greedy token fed to BOTH arms for 8 steps with the logits compared +// within the bar at every step — which also proves the conv/S state the chunked prefill leaves +// behind feeds the recurrent decode correctly. The claim is the CPU chunk algebra, so the cell +// pins the GPU tier's deltanet chain and decode step off along with the vector activation forms. + +let private DN_LOGIT_BAR_REL = 0.04 // of the step's max |logit|; the two forms measure ~0.015 of it apart def private argmax_of(v : array) : int64 { var best = -1e30 @@ -56,6 +61,10 @@ def test_deltanet_chunked_prefill(t : T?) { // derail the id lockstep (the exp4 paths are parity-gated by the frozen fixtures instead) let prev_vec = get_act_vec() set_act_vec(false) + let prev_dn_route = moe_gpu_dn_route() + let prev_dnd_route = moe_gpu_dnd_route() + set_moe_gpu_dn_route(false) + set_moe_gpu_dnd_route(false) with_job_que() { setup_dasllama_jobque_() let n = 150l @@ -71,29 +80,42 @@ def test_deltanet_chunked_prefill(t : T?) { forward_prefill(tr, s2, prompt, n, 0l) var got <- [for (i in range64(c.vocab_size)); s2.logits[i]] - var tok1 = argmax_of(ref) - var tok2 = argmax_of(got) - t |> equal(tok2, tok1) + var tok = argmax_of(ref) + let prefill_diff = logits_maxdiff(got, ref) + let prefill_bar = DN_LOGIT_BAR_REL * logits_maxabs(ref) + to_log(LOG_INFO, "prefill: argmax recurrent {tok} ({ref[tok]}), chunked {argmax_of(got)} ({got[argmax_of(got)]}); logits maxdiff {prefill_diff} (bar {prefill_bar})\n") + t |> success(prefill_diff <= prefill_bar, "prefill logits: chunked within {prefill_bar} of recurrent (maxdiff {prefill_diff})") delete ref delete got - // greedy continuation: both sessions decode through the recurrent per-token core off - // the state their prefill produced — ids must stay in lockstep + var prev_l1 : array for (st in range64(8l)) { - forward(tr, s1, tok1, n + st) - forward(tr, s2, tok2, n + st) + forward(tr, s1, tok, n + st) + forward(tr, s2, tok, n + st) var l1 <- [for (i in range64(c.vocab_size)); s1.logits[i]] var l2 <- [for (i in range64(c.vocab_size)); s2.logits[i]] - tok1 = argmax_of(l1) - tok2 = argmax_of(l2) - t |> equal(tok2, tok1) - delete l1 + let step_diff = logits_maxdiff(l2, l1) + let step_bar = DN_LOGIT_BAR_REL * logits_maxabs(l1) + let a1 = argmax_of(l1) + let a2 = argmax_of(l2) + to_log(LOG_INFO, "step {st}: fed {tok}; argmax recurrent {a1} ({l1[a1]}), chunked {a2} ({l2[a2]}); logits maxdiff {step_diff} (bar {step_bar})\n") + t |> success(step_diff <= step_bar, "step {st} logits: chunked within {step_bar} of recurrent (maxdiff {step_diff})") + if (st > 0l) { // the control: the previous step's recurrent logits are a disconnected mechanism the bar must RED + let off_diff = logits_maxdiff(l2, prev_l1) + t |> success(off_diff > step_bar, "step {st} control: the bar discriminates a one-step-off compare (maxdiff {off_diff} vs bar {step_bar})") + } + tok = a1 + delete prev_l1 + prev_l1 <- l1 delete l2 } + delete prev_l1 delete prompt delete s1 delete s2 } + set_moe_gpu_dn_route(prev_dn_route) + set_moe_gpu_dnd_route(prev_dnd_route) set_act_vec(prev_vec) set_deltanet_prefill_mode(prev_dn) delete tr @@ -119,6 +141,68 @@ def private big_deltanet_config(d_conv : int64) : Config { return c } +// Model-free: the session's deltanet ownership token and the release seam it drives through the +// tier's registry - a counting hook installed through set_moe_gpu_dn_state_hooks sees one +// release per position-zero reset, per speculative rollback, and per delete, each carrying the +// session's own state range. +def private small_deltanet_config() : Config { + var c = Config( + dim = 64l, hidden_dim = 64l, n_layers = 4l, + n_heads = 4l, n_kv_heads = 1l, head_size = 16l, kv_dim = 16l, + vocab_size = 64l, seq_len = 16l, + hybrid_deltanet = true, q_gated = true, + ssm_d_conv = 4l, ssm_d_state = 16l, ssm_n_group = 2l, + ssm_dt_rank = 4l, ssm_d_inner = 4l * 16l) + for (l in range64(3l)) { + c.recr_mask |= 1ul << uint64(l) + } + return c +} + +var private g_release_calls = 0 +var private g_release_lo = 0ul +var private g_release_hi = 0ul + +def private counting_release(state_lo : uint64; state_hi : uint64) { + g_release_calls++ + g_release_lo = state_lo + g_release_hi = state_hi +} + +[test] +def test_deltanet_owner_release(t : T?) { + t |> run("the session's deltanet owner token releases its own state range on reset, rollback and delete") @(t : T?) { + if (!jit_enabled()) { + t |> skip("interpreted (dasLLAMA model tests are JIT-only)") + return + } + let prev = moe_gpu_dn_state_hooks() + set_moe_gpu_dn_state_hooks(prev.flush, prev.inval, @@counting_release) + g_release_calls = 0 + let c = small_deltanet_config() + var s <- make_run_state(c) + var lo = 0ul + unsafe { + lo = intptr(addr(s.dn_state[0])) + } + let hi = lo + uint64(long_length(s.dn_state) * 4l) + t |> equal(s.dn_owner.state_lo, lo, "the token's low bound is the state's host address") + t |> equal(s.dn_owner.state_hi, hi, "the token's high bound is the state's end") + dn_reset(s) + t |> equal(g_release_calls, 1, "a position-zero reset releases once") + t |> equal(g_release_lo, lo) + t |> equal(g_release_hi, hi) + mtp_state_restore(s) + t |> equal(g_release_calls, 2, "a speculative rollback releases once") + t |> equal(g_release_lo, lo) + delete s + t |> equal(g_release_calls, 3, "the session's delete releases once, through the token's finalizer") + t |> equal(g_release_lo, lo) + t |> equal(g_release_hi, hi) + set_moe_gpu_dn_state_hooks(prev.flush, prev.inval, prev.release) + } +} + [test] def test_deltanet_state_sizing(t : T?) { t |> run("deltanet session state at 27B geometry clears max_unreserved_size") @(t : T?) { diff --git a/modules/dasLLAMA/tests/test_gpu_model_swap.das b/modules/dasLLAMA/tests/test_gpu_model_swap.das new file mode 100644 index 0000000000..6d65892eb6 --- /dev/null +++ b/modules/dasLLAMA/tests/test_gpu_model_swap.das @@ -0,0 +1,100 @@ +options gen2 +options stack = 524288 // every dasLLAMA program root takes this budget (options stack does not unify up from libs) +options persistent_heap // + explicit deletes below: each turn frees its model before the next loads +options _dasllama_internal = true + +require dastest/testing_boost public +require dasllama/dasllama +require dasllama/dasllama_gpu_tier // moe_gpu_installed_marks_armed: did the per-op tier take the model +require dasllama/dasllama_gpu_resident // moe_gpu_resident_active: or the whole-model driver +require daslib/jobque_boost +require daslib/fio +require math +require strings +require _model_tier + +// Two models through one process on the armed GPU tier: each model, loaded behind the other, +// decodes its own weights. The witness is the second model's greedy tokens after a swap +// against its own first-in-process tokens: same box, same kernels, so equality is exact. + +let SWAP_MODEL_A = path_join(models_dir(), "Qwen3-0.6B-Q8_0.gguf") +let SWAP_MODEL_B = path_join(models_dir(), "SmolLM2-135M-Instruct-Q8_0.gguf") + +def private greedy_turn(tag : string; path : string; n_tokens : int64) : array { + var ids : array + var pieces : array + var m <- load_model_(path, QuantMode.q8) + m.config.seq_len = min(m.config.seq_len, 256l) + with_job_que() { + setup_dasllama_jobque() + var s <- make_run_state(m.config) + var prompt <- encode(m, "Once upon a time, in a small village", true, false) + generate(m, s, prompt, SamplingParams(), n_tokens) $(id, piece) { + ids |> push(id) + pieces |> push(piece) + return true + } + delete prompt + delete s + } + delete m + let text = build_string() $(var w) { + for (p in pieces) { + w |> write(p) + } + } + to_log(LOG_INFO, "{tag}: \"{text}\" {ids}\n") + delete pieces + return <- ids +} + +// Model-free: the drop the upload rail runs before a load clears the marks a deleted model left +// and keeps the load's own MoE layer request, the one mark the whole-model drop resets. +[test] +def test_gpu_drop_before_load_keeps_request(t : T?) { + t |> run("the pre-load drop clears a stale model's marks and keeps the MoE layer request") @(t : T?) { + if (!moe_gpu_tier_installed()) { + t |> skip("no GPU tier installed on this box") + return + } + set_moe_gpu_dense_off(0, 4096l) // a stale model's mark + set_moe_gpu_layer_count(3l) // this load's request + t |> success(moe_gpu_installed_marks_armed(), "the stale mark reads as an installed model") + moe_gpu_drop_installed_before_load(8l) + t |> success(!moe_gpu_installed_marks_armed(), "the drop cleared the stale model's marks") + t |> equal(moe_gpu_layer_count(), 3l, "the MoE layer request survives the drop") + set_moe_gpu_layer_count(0l) + } +} + +[test] +def test_gpu_model_swap(t : T?) { + t |> run("a second model loaded after the first is deleted decodes its own weights (needs Qwen3-0.6B + SmolLM2-135M, DASLLAMA_GPU=1)") @(t : T?) { + if (!jit_enabled()) { + t |> skip("interpreted (dasLLAMA model tests are JIT-only)") + return + } + if (!model_available(t, SWAP_MODEL_A) || !model_available(t, SWAP_MODEL_B)) { + return + } + var a_first <- greedy_turn("A first", SWAP_MODEL_A, 8l) + let armed = moe_gpu_installed_marks_armed() || moe_gpu_resident_active() + if (!gpu_want_auto()) { + t |> skip("the GPU tier is not armed - needs DASLLAMA_GPU=1 on a device that serves the model") + delete a_first + return + } + t |> success(armed, "DASLLAMA_GPU=1: the tier took model A (marks or the resident driver)") + let a_resident_first = moe_gpu_resident_active() + var b_first <- greedy_turn("B first", SWAP_MODEL_B, 8l) + var a_after_b <- greedy_turn("A behind B", SWAP_MODEL_A, 8l) + t |> equal(moe_gpu_resident_active(), a_resident_first, "model A's second load armed the same driver as its first: one code path") + var b_after_a <- greedy_turn("B behind A", SWAP_MODEL_B, 8l) + t |> success(ids_equal(a_after_b, a_first), "model A decodes its own weights after model B was loaded in between ({a_after_b} vs {a_first})") + t |> success(ids_equal(b_after_a, b_first), "model B decodes its own weights after model A was loaded in between ({b_after_a} vs {b_first})") + delete a_first + delete b_first + delete a_after_b + delete b_after_a + } +} diff --git a/modules/dasLLAMA/tests/test_scheduler.das b/modules/dasLLAMA/tests/test_scheduler.das index 62a6a193dc..b0a135d1f4 100644 --- a/modules/dasLLAMA/tests/test_scheduler.das +++ b/modules/dasLLAMA/tests/test_scheduler.das @@ -7,10 +7,13 @@ require dastest/testing_boost public require dasllama/dasllama_scheduler // the scheduler under test require dasllama/dasllama // facade: load/encode/generate references + engine knobs require dasllama/dasllama_blocks // embed_text_rows: the media arms build rows from the prompt's own tokens +require dasllama/dasllama_common // layer_is_recurrent: the deltanet-on-GPU cell finds a recurrent layer to probe +require dasllama/dasllama_gpu_tier // moe_gpu_dnd_on_gpu: that cell skips unless the tier serves the decode step require daslib/jobque_boost require daslib/fio require daslib/strings_boost // join: the decoded stream logs beside the token-exact compares require math +require strings require dasllama/dasllama_env // Unit gates for the continuous-batching scheduler — all through the SchedEvent seam, zero HTTP: @@ -36,9 +39,10 @@ def private model_missing(t : T?) : bool { return false } -def private load_capped(cap : int64) : Model { +def private load_capped_cpu(cap : int64) : Model { var m <- load_model_(MODEL_PATH, QuantMode.q8) m.config.seq_len = min(m.config.seq_len, cap) + moe_gpu_drop_model() return <- m } @@ -128,6 +132,99 @@ def private finished_of(events : array; id : int64) : SchedEvent { return SchedEvent(stream = -1l) } +let DN_MODEL_PATH = path_join(models_dir(), "Qwen3.5-0.8B-Q8_0.gguf") + +def private has_gpu_dn_layer(c : Config) : bool { + for (l in range64(c.n_layers)) { + if (layer_is_recurrent(c, l) && moe_gpu_dnd_on_gpu(l)) { + return true + } + } + return false +} + +def private text_of(pieces : array) : string { + return build_string() $(var w) { + for (p in pieces) { + w |> write(p) + } + } +} + +// ===== two deltanet streams turn about on the GPU tier: each reads its own recurrent state ===== + +[test] +def test_scheduler_batching_deltanet_gpu(t : T?) { + t |> run("two interleaved deltanet streams on the GPU tier match their solo references (needs Qwen3.5-0.8B-Q8_0, DASLLAMA_GPU=1)") @(t : T?) { + if (!jit_enabled()) { + t |> skip("interpreted (dasLLAMA model tests are JIT-only)") + return + } + if (!stat(DN_MODEL_PATH).is_valid) { + t |> skip("{DN_MODEL_PATH} not present") + return + } + with_job_que() { + setup_dasllama_jobque() + var m <- load_model_(DN_MODEL_PATH, QuantMode.q8) + m.config.seq_len = min(m.config.seq_len, 256l) + let prev_dnd_route = moe_gpu_dnd_route() + set_moe_gpu_dnd_route(true) // the claim is about the tier's decode step: pin its route on, whatever an earlier cell left + if (!has_gpu_dn_layer(m.config)) { + t |> skip("the GPU tier's deltanet decode step is not armed - needs DASLLAMA_GPU=1 on a device that serves it") + set_moe_gpu_dnd_route(prev_dnd_route) + delete m + return + } + let greedy = SamplingParams() + var none : array + var pa <- encode(m, "Count from one to ten in words.", true, false) + var pb <- encode(m, "Name three primary colors.", true, false) + var refs <- [ <- ref_generate(m, pa, greedy, 8l, none), <- ref_generate(m, pb, greedy, 8l, none)] + var sch1 <- create_scheduler(m, 1l, 2l, 64l) + var events1 : array + t |> success(submit(m, sch1, make_req(10l, pa, greedy, 8l, none)), "stream A queued (one-stream scheduler)") + t |> success(submit(m, sch1, make_req(11l, pb, greedy, 8l, none)), "stream B queued (one-stream scheduler)") + drain(m, sch1, events1) + var sa <- toks_of(events1, 10l) + var sb <- toks_of(events1, 11l) + to_log(LOG_INFO, "one-stream scheduler A: \"{text_of(pieces_of(events1, 10l))}\" | generate A: \"{text_of(refs[0].pieces)}\"\n") + to_log(LOG_INFO, "one-stream scheduler B: \"{text_of(pieces_of(events1, 11l))}\" | generate B: \"{text_of(refs[1].pieces)}\"\n") + t |> success(same(sa, refs[0].toks), "stream A through a one-stream scheduler matches generate ({sa} vs {refs[0].toks})") + t |> success(same(sb, refs[1].toks), "stream B through a one-stream scheduler matches generate ({sb} vs {refs[1].toks})") + var sch <- create_scheduler(m, 2l, 2l, 64l) + var events : array + t |> success(submit(m, sch, make_req(20l, pa, greedy, 8l, none)), "stream A queued") + t |> success(submit(m, sch, make_req(21l, pb, greedy, 8l, none)), "stream B queued") + drain(m, sch, events) + var ga <- toks_of(events, 20l) + var gb <- toks_of(events, 21l) + to_log(LOG_INFO, "two-stream A: \"{text_of(pieces_of(events, 20l))}\" | solo A: \"{text_of(refs[0].pieces)}\"\n") + to_log(LOG_INFO, "two-stream B: \"{text_of(pieces_of(events, 21l))}\" | solo B: \"{text_of(refs[1].pieces)}\"\n") + t |> success(same(ga, refs[0].toks), "stream A, decoded turn about with B, matches its solo reference ({ga} vs {refs[0].toks})") + t |> success(same(gb, refs[1].toks), "stream B, decoded turn about with A, matches its solo reference ({gb} vs {refs[1].toks})") + t |> success(sch.sum_batch >= 2l * 2l, "the two streams shared decode steps (sum batch {sch.sum_batch})") + delete sa + delete sb + delete events1 + unsafe { + delete sch1 + } + delete ga + delete gb + delete events + unsafe { + delete sch + } + delete refs + delete pa + delete pb + set_moe_gpu_dnd_route(prev_dnd_route) + delete m + } + } +} + // ===== scheduler ≡ generate (1 stream): greedy bit-exact, seeded sampling, stop finish ===== [test] @@ -138,7 +235,7 @@ def test_scheduler_matches_generate(t : T?) { // nolint:STYLE038 — one model } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) var prompt <- encode(m, "Once upon a time", true, false) var none : array // greedy, single-chunk prefill: the eval sequence is identical to generate()'s, so the @@ -228,7 +325,7 @@ def test_scheduler_chunk_sizes(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) // classic prefill is bit-exact under ANY chunking (test_prefill's contract) — the same // pin as test_chat's render-seam case, so every chunk size must yield ONE stream let saved_mode = get_prefill_attn_mode() @@ -246,6 +343,7 @@ def test_scheduler_chunk_sizes(t : T?) { drain(m, sch, events) var got <- toks_of(events, int64(ci)) first[ci] = !empty(got) ? got[0] : -1l + to_log(LOG_INFO, "K={K}: \"{text_of(pieces_of(events, int64(ci)))}\" {got}\n") streams |> emplace(got) delete events unsafe { @@ -276,7 +374,7 @@ def test_scheduler_batching(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) // the batch worker mirrors the std decode path — pin the fused chain off so the solo // generate() references share its numerics (test_batch_decode's contract) let prev_fused = get_fused_decode() @@ -304,9 +402,13 @@ def test_scheduler_batching(t : T?) { t |> equal(finished_of(events, 13l).finish, "evicted") t |> equal(n_queued(sch), 1l) drain(m, sch, events) + t |> success(empty(sch.batch_rows), "no row pointer outlives the step that reaped its batched stream (a validating heap collect between steps walks them)") var ga <- toks_of(events, 10l) var gb <- toks_of(events, 11l) var gc <- toks_of(events, 12l) + to_log(LOG_INFO, "batched A: \"{text_of(pieces_of(events, 10l))}\" {ga} | solo A: \"{text_of(refs[0].pieces)}\" {refs[0].toks}\n") + to_log(LOG_INFO, "batched B: \"{text_of(pieces_of(events, 11l))}\" {gb} | solo B: \"{text_of(refs[1].pieces)}\" {refs[1].toks}\n") + to_log(LOG_INFO, "batched C: \"{text_of(pieces_of(events, 12l))}\" {gc} | solo C: \"{text_of(refs[2].pieces)}\" {refs[2].toks}\n") t |> success(same(ga, refs[0].toks), "stream A (batched) matches its solo reference") t |> success(same(gb, refs[1].toks), "stream B (batched) matches its solo reference") t |> success(same(gc, refs[2].toks), "stream C (admitted after a slot freed) matches its solo reference") @@ -343,7 +445,7 @@ def test_scheduler_prefix_cache(t : T?) { // nolint:STYLE038 — one model loa } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) // classic prefill: the warm run re-chunks the prompt (tail-only), and only classic is // bit-exact under re-chunking — the same pin as the chunk-size arm above let saved_mode = get_prefill_attn_mode() @@ -463,7 +565,7 @@ def test_scheduler_evict_and_context_length(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(64l) + var m <- load_capped_cpu(64l) let prev_fused = get_fused_decode() set_fused_decode(false) let greedy = SamplingParams() @@ -485,6 +587,7 @@ def test_scheduler_evict_and_context_length(t : T?) { drain(m, sch, events) t |> equal(length(toks_of(events, 21l)), b_before) var ga <- toks_of(events, 20l) + to_log(LOG_INFO, "survivor: \"{text_of(pieces_of(events, 20l))}\" {ga} | solo: \"{text_of(ref_a.pieces)}\" {ref_a.toks}\n") t |> success(same(ga, ref_a.toks), "survivor stream unaffected by the eviction") t |> equal(finished_of(events, 20l).finish, "length") // context exhaustion: budget far beyond the capped seq_len — the decode pre-check must @@ -559,6 +662,7 @@ def test_scheduler_mtp(t : T?) { // nolint:STYLE038 — one model load per tes drain(m, sch, events) set_mtp_spec(prev_spec) var got <- toks_of(events, 2l) + to_log(LOG_INFO, "spec: \"{text_of(pieces_of(events, 2l))}\" {got} | plain: \"{text_of(pieces_of(events0, 1l))}\" {ref_toks}\n") t |> success(same(got, ref_toks), "spec stream tokens == plain scheduler") t |> success(sch.mtp_drafted > 0l, "the spec tick actually drafted ({sch.mtp_drafted} drafts)") t |> success(sch.mtp_accepted > 0l, "some drafts accepted ({sch.mtp_accepted} of {sch.mtp_drafted})") @@ -649,7 +753,7 @@ def test_scheduler_paged_sampled(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) let saved_mode = get_prefill_attn_mode() set_prefill_attn_mode(AttnPrefillMode.classic) // the warm run re-chunks (tail-only) var prompt <- encode(m, "Once upon a time in a small village near the mountains there lived a very curious little robot who loved counting stars", true, false) @@ -733,7 +837,7 @@ def test_scheduler_media_splice(t : T?) { // nolint:STYLE038 — one model loa } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) // classic prefill: the splice necessarily moves the eval boundaries, and only classic // is bit-exact under re-chunking — the same pin as the chunk-size arm above let saved_mode = get_prefill_attn_mode() @@ -831,7 +935,7 @@ def test_scheduler_media_validation(t : T?) { // nolint:STYLE038 — one flat } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) var prompt <- encode(m, "The quick brown fox", true, false) let greedy = SamplingParams() var sch <- create_scheduler(m, 1l, 4l, 64l) @@ -937,7 +1041,7 @@ def test_scheduler_mrope(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) m.config.rope_sections = int4(12, 10, 10, 0) // stamp: the toy is not an mrope arch (rotary 64 -> half 32) m.config.mrope_interleaved = true let saved_mode = get_prefill_attn_mode() @@ -1011,7 +1115,7 @@ def test_scheduler_mrope_validation(t : T?) { } with_job_que() { setup_dasllama_jobque() - var m <- load_capped(128l) + var m <- load_capped_cpu(128l) var prompt <- encode(m, "The quick brown fox", true, false) let greedy = SamplingParams() var sch <- create_scheduler(m, 1l, 4l, 64l) diff --git a/modules/dasUnitTest/unit_test.das.inc b/modules/dasUnitTest/unit_test.das.inc index 9db5163853..9a909d6317 100644 --- a/modules/dasUnitTest/unit_test.das.inc +++ b/modules/dasUnitTest/unit_test.das.inc @@ -1,32 +1,32 @@ -static unsigned char unit_test_das[] = { -0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, -0x67,0x65,0x6e,0x32,0x0a, -0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, -0x72,0x65,0x6d,0x6f,0x76,0x65,0x5f,0x75, -0x6e,0x75,0x73,0x65,0x64,0x5f,0x73,0x79, -0x6d,0x62,0x6f,0x6c,0x73,0x20,0x3d,0x20, -0x66,0x61,0x6c,0x73,0x65,0x0a, -0x0a, -0x72,0x65,0x71,0x75,0x69,0x72,0x65,0x20, -0x55,0x6e,0x69,0x74,0x54,0x65,0x73,0x74, -0x0a, -0x0a, -0x6c,0x65,0x74,0x20,0x4f,0x54,0x48,0x45, -0x52,0x5f,0x55,0x4e,0x49,0x54,0x5f,0x54, -0x45,0x53,0x54,0x5f,0x43,0x4f,0x4e,0x53, -0x54,0x41,0x4e,0x54,0x20,0x3d,0x20,0x55, -0x4e,0x49,0x54,0x5f,0x54,0x45,0x53,0x54, -0x5f,0x43,0x4f,0x4e,0x53,0x54,0x41,0x4e, -0x54,0x20,0x2b,0x20,0x31,0x3b,0x0a, -0x0a, -0x64,0x65,0x66,0x20,0x73,0x74,0x61,0x72, -0x74,0x5f,0x65,0x66,0x66,0x65,0x63,0x74, -0x28,0x6e,0x61,0x6d,0x65,0x3b,0x20,0x74, -0x6d,0x29,0x20,0x7b,0x0a, -0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75, -0x72,0x6e,0x20,0x73,0x74,0x61,0x72,0x74, -0x5f,0x65,0x66,0x66,0x65,0x63,0x74,0x28, -0x6e,0x61,0x6d,0x65,0x2c,0x20,0x74,0x6d, -0x2c,0x20,0x31,0x2e,0x30,0x29,0x0a, -0x7d,0x0a, -}; +static unsigned char unit_test_das[] = { +0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, +0x67,0x65,0x6e,0x32,0x0a, +0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, +0x72,0x65,0x6d,0x6f,0x76,0x65,0x5f,0x75, +0x6e,0x75,0x73,0x65,0x64,0x5f,0x73,0x79, +0x6d,0x62,0x6f,0x6c,0x73,0x20,0x3d,0x20, +0x66,0x61,0x6c,0x73,0x65,0x0a, +0x0a, +0x72,0x65,0x71,0x75,0x69,0x72,0x65,0x20, +0x55,0x6e,0x69,0x74,0x54,0x65,0x73,0x74, +0x0a, +0x0a, +0x6c,0x65,0x74,0x20,0x4f,0x54,0x48,0x45, +0x52,0x5f,0x55,0x4e,0x49,0x54,0x5f,0x54, +0x45,0x53,0x54,0x5f,0x43,0x4f,0x4e,0x53, +0x54,0x41,0x4e,0x54,0x20,0x3d,0x20,0x55, +0x4e,0x49,0x54,0x5f,0x54,0x45,0x53,0x54, +0x5f,0x43,0x4f,0x4e,0x53,0x54,0x41,0x4e, +0x54,0x20,0x2b,0x20,0x31,0x3b,0x0a, +0x0a, +0x64,0x65,0x66,0x20,0x73,0x74,0x61,0x72, +0x74,0x5f,0x65,0x66,0x66,0x65,0x63,0x74, +0x28,0x6e,0x61,0x6d,0x65,0x3b,0x20,0x74, +0x6d,0x29,0x20,0x7b,0x0a, +0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75, +0x72,0x6e,0x20,0x73,0x74,0x61,0x72,0x74, +0x5f,0x65,0x66,0x66,0x65,0x63,0x74,0x28, +0x6e,0x61,0x6d,0x65,0x2c,0x20,0x74,0x6d, +0x2c,0x20,0x31,0x2e,0x30,0x29,0x0a, +0x7d,0x0a, +}; diff --git a/plans/kernel_parity_pass.md b/plans/kernel_parity_pass.md index bddcbcf3de..78ec4ed763 100644 --- a/plans/kernel_parity_pass.md +++ b/plans/kernel_parity_pass.md @@ -1280,3 +1280,39 @@ Vulkan grid tg (gap 3): followup_vulkan 35's levers, after gap 1 or 2 lands. cell's first shape compared the CPU rail against itself for twenty minutes and passed - a two-arm compare on the driver builds f16 sessions like the bench and asserts the driver's session stamp (`rdec_gen != 0`) on both arms, or it proves nothing. + +- 2026-09-03: the mirror pass on the three pp512 rows under 1.0 (Q3_K_L, IQ3_M, IQ2_XXS), on + the RTX 5060 Ti (the zen2 box, driver 616.56), on master after #3926 merged (866a03046, the + Vulkan files byte-identical to the PR tip). Method: + llama.cpp's `GGML_VK_PERF_LOGGER=1 llama-bench -p 512 -n 0 -r 2 -ngl 99` last-graph rows + (`Total time` = the GPU window) against our `DASLLAMA_GPU_PROF=1 lcpp_bench --plen 512 --ngen 0` + role stamps, then plain pp512 with llama.cpp bracketing ours (`-r 6` | `--reps 12` | `-r 6`). + RULE learned the hard way: a `--reps 2` stamp is not a reading - the window never lets the + clocks settle and any load on the box lands on the bandwidth-bound roles (act, classifier gemv, + residual norms read 1.5-2x, window to window); twelve reps reproduce yesterday's Q4_K_M window + (22.4 ms, 20777 tok/s) exactly. `test-backend-ops perf` cannot take a custom shape (its list is + m 4096 x n 512 x k 14336), so the per-shape reference is the perf logger, never the probe's + shapes. GPU time per window, us, ours vs llama.cpp (twelve reps, same minutes): + + | role | Q3_K_L | IQ3_M | IQ2_XXS | + |---|---|---|---| + | q + wo | 3073-3230 vs 2936 | 2812-2834 vs 2733 | 3114-3240 vs 3092 | + | k + v | 1067 vs 1087 | 970 vs 982 | 976 vs 997 | + | gate + up | 10952-11366 vs 10383 | 10669-10728 vs 10148 | 11826-11840 vs 10309 | + | down | 5418-5700 vs 5000 | 5163-5207 vs 4961 | 5667-5809 vs 5325 | + | act | 972-1203 vs 1377 | 1001 vs 1381 | 969 vs 1688 | + | rope | 385 vs 1096 | 380 vs 1100 | 378 vs 1411 | + | classifier | 520-697 vs 522 | 520-561 vs 525 | 445 vs 441 | + | total | 24530-24815 vs 24259 | 23173-23202 vs 23676 | 25102-25112 vs 25132 | + + Plain pp512, direction-grade across three processes, llama.cpp before | ours | llama.cpp + after: Q3_K_L 18813 | 20393 | 18737 (1.08); IQ3_M 19425 | 20734 | 19533 (1.06); IQ2_XXS + 19972 | 19298 | 19483 (0.98). Verdict: gap 1 is closed at the rig - nine of ten vehicles at + or past llama.cpp, IQ2_XXS at 0.98 inside the box's band. The GEMM tiles still lose a little + (gate/up iq2xxs 1.15, k3 1.05-1.09, iq3s 1.05; down 1.04-1.14 on the 2048-wide shape) and + rope (2.9x ahead), act (1.4-1.7x ahead) and the fused residual norms pay it back. The one + tile deficit worth a row of its own is the iq2xxs gate/up at 1.15: llama.cpp's iq2_xxs row + runs at its q3_K rate (50 TF/s), so its grid decode is hidden where ours (41 TF/s on the 1B + shape) still shows. That is memo delta 8 territory (the computed codebook and the + per-workgroup prologue). The board's 0.90 rows were two-rep readings; they stand until + re-minted (owed in `PERF_LEDGER.md`). diff --git a/skills/comment_style_hygiene.md b/skills/comment_style_hygiene.md index f577c0e48f..5c43450961 100644 --- a/skills/comment_style_hygiene.md +++ b/skills/comment_style_hygiene.md @@ -44,7 +44,9 @@ an existing lint, the lint compels - an unnoted rule is the reviewer's. that are not documentation or lint suppression.** The kept set is exactly: `//!` docs on a contract a CALLER must know - public API always, a private helper only when callers inside the module need the contract (narration of an implementation goes to a name, a test, or the -module's `.md`) - `// nolint:CODE` / `@nolint` suppressions carrying their one-line why, +module's `.md`), and only where the contract is not reachable from the declaration's own +`[arch]` citation: a `//!` that restates the section its citation names is a duplicate and +goes - `// nolint:CODE` / `@nolint` suppressions carrying their one-line why, `//fmt:` formatter directives, license comments (Copyright / SPDX), and the file's leading header block (which may sit below the `options` / `module` / `require` preamble). Everything else - narration, banners, section dividers, commented-out code - does not exist. The diff --git a/tutorials/integration/cpp/class_adapters_module.das.inc b/tutorials/integration/cpp/class_adapters_module.das.inc index 908c829486..b79fa630fd 100644 --- a/tutorials/integration/cpp/class_adapters_module.das.inc +++ b/tutorials/integration/cpp/class_adapters_module.das.inc @@ -1,53 +1,53 @@ -static unsigned char class_adapters_module_das[] = { -0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, -0x67,0x65,0x6e,0x32,0x0a, -0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, -0x72,0x65,0x6d,0x6f,0x76,0x65,0x5f,0x75, -0x6e,0x75,0x73,0x65,0x64,0x5f,0x73,0x79, -0x6d,0x62,0x6f,0x6c,0x73,0x20,0x3d,0x20, -0x66,0x61,0x6c,0x73,0x65,0x0a, -0x0a, -0x2f,0x2f,0x20,0x41,0x62,0x73,0x74,0x72, -0x61,0x63,0x74,0x20,0x62,0x61,0x73,0x65, -0x20,0x63,0x6c,0x61,0x73,0x73,0x20,0xe2, -0x80,0x94,0x20,0x64,0x61,0x73,0x6c,0x61, -0x6e,0x67,0x20,0x73,0x69,0x64,0x65,0x2e, -0x0a, -0x2f,0x2f,0x20,0x43,0x2b,0x2b,0x20,0x77, -0x69,0x6c,0x6c,0x20,0x69,0x6e,0x63,0x6c, -0x75,0x64,0x65,0x20,0x74,0x68,0x69,0x73, -0x20,0x76,0x69,0x61,0x20,0x63,0x6f,0x6d, -0x70,0x69,0x6c,0x65,0x42,0x75,0x69,0x6c, -0x74,0x69,0x6e,0x4d,0x6f,0x64,0x75,0x6c, -0x65,0x20,0x61,0x6e,0x64,0x20,0x74,0x68, -0x65,0x20,0x58,0x44,0x44,0x20,0x2e,0x64, -0x61,0x73,0x2e,0x69,0x6e,0x63,0x20,0x66, -0x69,0x6c,0x65,0x2e,0x0a, -0x2f,0x2f,0x20,0x64,0x61,0x73,0x6c,0x61, -0x6e,0x67,0x20,0x63,0x6c,0x61,0x73,0x73, -0x65,0x73,0x20,0x63,0x61,0x6e,0x20,0x64, -0x65,0x72,0x69,0x76,0x65,0x20,0x66,0x72, -0x6f,0x6d,0x20,0x74,0x68,0x69,0x73,0x20, -0x61,0x6e,0x64,0x20,0x6f,0x76,0x65,0x72, -0x72,0x69,0x64,0x65,0x20,0x74,0x68,0x65, -0x20,0x76,0x69,0x72,0x74,0x75,0x61,0x6c, -0x20,0x6d,0x65,0x74,0x68,0x6f,0x64,0x73, -0x2e,0x0a, -0x0a, -0x63,0x6c,0x61,0x73,0x73,0x20,0x54,0x75, -0x74,0x6f,0x72,0x69,0x61,0x6c,0x42,0x61, -0x73,0x65,0x43,0x6c,0x61,0x73,0x73,0x20, -0x7b,0x0a, -0x20,0x20,0x20,0x20,0x64,0x65,0x66,0x20, -0x61,0x62,0x73,0x74,0x72,0x61,0x63,0x74, -0x20,0x75,0x70,0x64,0x61,0x74,0x65,0x28, -0x64,0x74,0x20,0x3a,0x20,0x66,0x6c,0x6f, -0x61,0x74,0x29,0x20,0x3a,0x20,0x76,0x6f, -0x69,0x64,0x0a, -0x20,0x20,0x20,0x20,0x64,0x65,0x66,0x20, -0x61,0x62,0x73,0x74,0x72,0x61,0x63,0x74, -0x20,0x67,0x65,0x74,0x5f,0x70,0x6f,0x73, -0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20, -0x66,0x6c,0x6f,0x61,0x74,0x33,0x0a, -0x7d,0x0a, -}; +static unsigned char class_adapters_module_das[] = { +0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, +0x67,0x65,0x6e,0x32,0x0a, +0x6f,0x70,0x74,0x69,0x6f,0x6e,0x73,0x20, +0x72,0x65,0x6d,0x6f,0x76,0x65,0x5f,0x75, +0x6e,0x75,0x73,0x65,0x64,0x5f,0x73,0x79, +0x6d,0x62,0x6f,0x6c,0x73,0x20,0x3d,0x20, +0x66,0x61,0x6c,0x73,0x65,0x0a, +0x0a, +0x2f,0x2f,0x20,0x41,0x62,0x73,0x74,0x72, +0x61,0x63,0x74,0x20,0x62,0x61,0x73,0x65, +0x20,0x63,0x6c,0x61,0x73,0x73,0x20,0xe2, +0x80,0x94,0x20,0x64,0x61,0x73,0x6c,0x61, +0x6e,0x67,0x20,0x73,0x69,0x64,0x65,0x2e, +0x0a, +0x2f,0x2f,0x20,0x43,0x2b,0x2b,0x20,0x77, +0x69,0x6c,0x6c,0x20,0x69,0x6e,0x63,0x6c, +0x75,0x64,0x65,0x20,0x74,0x68,0x69,0x73, +0x20,0x76,0x69,0x61,0x20,0x63,0x6f,0x6d, +0x70,0x69,0x6c,0x65,0x42,0x75,0x69,0x6c, +0x74,0x69,0x6e,0x4d,0x6f,0x64,0x75,0x6c, +0x65,0x20,0x61,0x6e,0x64,0x20,0x74,0x68, +0x65,0x20,0x58,0x44,0x44,0x20,0x2e,0x64, +0x61,0x73,0x2e,0x69,0x6e,0x63,0x20,0x66, +0x69,0x6c,0x65,0x2e,0x0a, +0x2f,0x2f,0x20,0x64,0x61,0x73,0x6c,0x61, +0x6e,0x67,0x20,0x63,0x6c,0x61,0x73,0x73, +0x65,0x73,0x20,0x63,0x61,0x6e,0x20,0x64, +0x65,0x72,0x69,0x76,0x65,0x20,0x66,0x72, +0x6f,0x6d,0x20,0x74,0x68,0x69,0x73,0x20, +0x61,0x6e,0x64,0x20,0x6f,0x76,0x65,0x72, +0x72,0x69,0x64,0x65,0x20,0x74,0x68,0x65, +0x20,0x76,0x69,0x72,0x74,0x75,0x61,0x6c, +0x20,0x6d,0x65,0x74,0x68,0x6f,0x64,0x73, +0x2e,0x0a, +0x0a, +0x63,0x6c,0x61,0x73,0x73,0x20,0x54,0x75, +0x74,0x6f,0x72,0x69,0x61,0x6c,0x42,0x61, +0x73,0x65,0x43,0x6c,0x61,0x73,0x73,0x20, +0x7b,0x0a, +0x20,0x20,0x20,0x20,0x64,0x65,0x66,0x20, +0x61,0x62,0x73,0x74,0x72,0x61,0x63,0x74, +0x20,0x75,0x70,0x64,0x61,0x74,0x65,0x28, +0x64,0x74,0x20,0x3a,0x20,0x66,0x6c,0x6f, +0x61,0x74,0x29,0x20,0x3a,0x20,0x76,0x6f, +0x69,0x64,0x0a, +0x20,0x20,0x20,0x20,0x64,0x65,0x66,0x20, +0x61,0x62,0x73,0x74,0x72,0x61,0x63,0x74, +0x20,0x67,0x65,0x74,0x5f,0x70,0x6f,0x73, +0x69,0x74,0x69,0x6f,0x6e,0x20,0x3a,0x20, +0x66,0x6c,0x6f,0x61,0x74,0x33,0x0a, +0x7d,0x0a, +}; diff --git a/utils/REVIEW.md b/utils/REVIEW.md index 275a464d7b..a8790b956c 100644 --- a/utils/REVIEW.md +++ b/utils/REVIEW.md @@ -12,9 +12,6 @@ skip-path assertion. sits in is reviewed with that tool's own `REVIEW.md`, where one exists, as well as with this checklist - not with the checklist of the directory it sits in.** -**A file in a `utils/` library directory (`common/`) is reviewed with this checklist and with -the checklist of every tool that requires it.** - **A diff under `utils/` that changes how a `.dlim` is built from a gguf, how one is loaded, or what identifies one - the fields that decide whether two `.dlim`s are the same image - answers to `modules/dasLLAMA/REVIEW.md` (repo root) too.** A `utils/` diff never opens that checklist @@ -28,7 +25,7 @@ script itself, and each check's finding text states its rule. **A diff that drops a tool from `DAS_UTILS_SHIPPED_EXES` (`CMakeLists.txt`, beside this file) while keeping that tool's directory records the decision to stop shipping it in that tool's -own `REVIEW.md`, wherever that directory is, in the same change** - the gate cannot see a +own `REVIEW.md`, wherever that directory is, in the same change** - `REVIEW.das` cannot see a removed entry. **A diff that deletes a tool outright records the decision beside `DAS_UTILS_SHIPPED_EXES` in @@ -41,10 +38,11 @@ it.** A row that only compile-checks the test (`dastest --compile-only`) does not execute them. A test whose assertions no row executes never runs again. **A test the diff adds or changes that covers a change under `utils/`, whose load-bearing -assertions no CI lane can run, ships with a CI row that compile-checks it.** +assertions no CI machine can run, ships with a row that compile-checks it - `dastest +--compile-only` for a `.das` test - in the same change.** -**A test the diff adds or changes that covers a change under `utils/`, whose only row -compile-checks it or whose only executing row runs against an already-deployed artifact - a -nightly lane driving the live site - records its executed run in the PR description**: the -machine the assertions ran on, what that machine had that CI lacks, and the pass count. A lane -that tests production after merge proves nothing about the diff under review. +**A test the diff adds or changes that covers a change under `utils/`, whose load-bearing +assertions no CI row runs against the build the diff produces, records its executed run in the +PR description**: the machine the assertions ran on, what that machine had that CI lacks, and +the pass count. A row that tests an already-deployed artifact proves nothing about the diff +under review. diff --git a/utils/common/REVIEW.md b/utils/common/REVIEW.md new file mode 100644 index 0000000000..e7614d2b9a --- /dev/null +++ b/utils/common/REVIEW.md @@ -0,0 +1,6 @@ +# utils/common Code Review Checklist + +**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** + +**A file in this folder is reviewed with the checklist of every tool that requires it, where +one exists.** diff --git a/utils/dasllama-server/REVIEW.md b/utils/dasllama-server/REVIEW.md index cf560636a0..3f01362a11 100644 --- a/utils/dasllama-server/REVIEW.md +++ b/utils/dasllama-server/REVIEW.md @@ -10,24 +10,20 @@ number, applies `modules/dasLLAMA/REVIEW.md` (repo root) too.** **A diff that changes a flag's row in `README.md` also updates that flag's `@clarg_doc` in `main.das`, in the same change - `--help` is the other copy a user reads.** -**A captured body under `tests/fixtures/` is never hand-authored, and never edited beyond -machine-local path normalization - re-capture it instead.** `tests/fixtures/README.md` gives -the capture rails; a capture SCRIPT beside the fixtures is code, not a fixture. +**A Playwright `.spec.js` or a captured fixture, wherever the diff puts it, applies the +`tests/` subfolder's `REVIEW.md` (beside this file) too.** -**A body or SSE frame a Playwright `.spec.js` under `tests/` (beside this file) sends or -asserts is a fixture, or a clone of one with named fields overridden - one assembled from -scratch is a defect.** - -**A diff that re-captures a fixture under `tests/fixtures/`, or edits `control.html`, runs -the Playwright suite in `tests/` (beside this file) and ships green.** - -**Weakening `REVIEW.das` (beside this file) is a defect** - dropping a check, narrowing what a -check scans, adding a name to a check's licensed set (the names that check does not flag), or -changing a finding text so it no longer names what failed. What the gate enforces is read from +**Weakening `REVIEW.das` (beside this file) - dropping a check, narrowing what a check scans, +adding a name to a check's licensed set (the names it does not flag), or blunting a finding +text so it no longer names what failed - is a defect.** What the gate enforces is read from the gate itself. -**A diff that adds a route, or changes a request field the server reads, updates -`control.html` where the page uses it, in the same change.** +**A diff that renames or removes a request field that `openai_server.das` reads updates every +place `control.html` sends that field, in the same change.** + +**A diff that makes a route in `openai_server.das` require a request field it did not require +before - a new route requires all of its fields - adds that field to every `control.html` +request to that route, in the same change.** **A diff that changes what a route answers - an item, a field, or a value - re-captures every fixture under `tests/fixtures/` that records that route, in the same change.** The fixtures are @@ -39,9 +35,3 @@ fixture for the route that answers with that key first.** **A diff that adds a key to what a route answers lists it in that route's `README.md` row, in the same change.** The row is where a consumer learns the key exists. - -**A Playwright `.spec.js` under `tests/` (beside this file) never spells out a value a fixture -carries, and never hard-codes a number a re-capture can move - including one derived from a -capture's length. It reads the value, and computes the number, from the fixture.** A capture -carries the values of the machine it was taken on, so a re-capture elsewhere moves them, and a -spec that pins one goes red for a reason that has nothing to do with the page. diff --git a/utils/dasllama-server/main.das b/utils/dasllama-server/main.das index bc0e745edd..10d5c2b520 100644 --- a/utils/dasllama-server/main.das +++ b/utils/dasllama-server/main.das @@ -876,7 +876,9 @@ def init() { // nolint:STYLE037,STYLE038 — boot sequence: the knobs are orde // the Metal-build CPU-prefill guard is a profiling tripwire; its panic would drop every // live stream, and serving on the CPU arm is a legitimate configuration allow_cpu_prefill() - to_log(LOG_INFO, "dasllama-server: prefill runs on the CPU arm - pass --gpu metal for the Metal rails\n") + if (gpu != "vulkan") { + to_log(LOG_INFO, "dasllama-server: prefill runs on the CPU arm - pass --gpu metal for the Metal rails\n") + } } to_log(LOG_INFO, "dasllama-server: metal mode {get_metal_mode()}\n") if (gpu == "vulkan") { diff --git a/utils/dasllama-server/tests/REVIEW.md b/utils/dasllama-server/tests/REVIEW.md new file mode 100644 index 0000000000..0c0210e7d1 --- /dev/null +++ b/utils/dasllama-server/tests/REVIEW.md @@ -0,0 +1,17 @@ +# dasllama-server control page tests Code Review Checklist + +**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture +doc: `utils/dasllama-server/README.md` (repo root). + +**A captured response body - a `.json`/`.txt` under `fixtures/` (beside this file) - is never +hand-authored, and never edited beyond machine-local path normalization.** +`fixtures/README.md` gives the capture procedure. + +**A request or response body in a Playwright `.spec.js` in this folder is a captured fixture, +or a clone of one with named fields overridden - a body authored from nothing is a defect.** + +**A Playwright `.spec.js` in this folder never spells out a value a fixture carries, and never +hard-codes a number a re-capture can move - including one derived from a capture's length. It +reads the value, and computes the number, from the fixture.** A capture carries the values of +the machine it was taken on, so a re-capture elsewhere moves them, and a spec that pins one +goes red for a reason that has nothing to do with the page. diff --git a/utils/watchdog/REVIEW.md b/utils/watchdog/REVIEW.md new file mode 100644 index 0000000000..24f1516a46 --- /dev/null +++ b/utils/watchdog/REVIEW.md @@ -0,0 +1,7 @@ +# watchdog Code Review Checklist + +**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** Architecture +doc: `README.md`. + +**A diff that changes the consent wording in `watchdog.py` answers to +`modules/dasLLAMA/performance/REVIEW.md` (repo root) too.**