Skip to content

Proposed gguf support - #24

Draft
Atomic-Germ wants to merge 35 commits into
mainfrom
proposed-gguf-support
Draft

Atomic-Germ wants to merge 35 commits into
mainfrom
proposed-gguf-support

Conversation

@Atomic-Germ

@Atomic-Germ Atomic-Germ commented Sep 8, 2026

Copy link
Copy Markdown
Owner

As in #14 & #33 and also #9 tangentially

This isn't complete, but it's getting there

- Phase 0: open_kernels/gguf_pool.py — GGUF-native pool reference (pack_q4_pool/pack_q8_pool with f32-scale chunks, q4 band law + pack_q8_pool_lmhead/dequant_pool_lmhead supertile law, minimal GGUF parser read_gguf handling v2+v3); self-test passes bit-exact (dequant(pool) == dequant(GGUF blocks)).
- Phase 1: kernel f32-scale variants — open_kernels/designs/gemv_q4/gemv_q4.h (GEMV_Q4_SCALES_F32, GEMV_Q4_PREFIX, conditional kDBytes/kMetaBytes/kTileBytes, load_scale), gemv_q4.py (GEMV_SCALES_F32 env, SYM=gemv_q4s32, srchash includes flag); lm_head_q8.h/.cc/.py (LMHEAD_SCALES_F32, LMHEAD_Q8_PREFIX, load_scale integer-RNE version); lm_head_q4.py; wrapper .cc files (layer_x gemv_q4_gy/gup/gdown, dense gemv_q4_gy/gms) use GEMV_Q4_WRAP(GEMV_Q4_PREFIX, name) two-level macro (## doesn't expand macro operands — two-level pattern required). All builds verified: dx (both modes), lm_head_q4 (both), lm_head_q8 (both), gemv_q4 standalone.
- Recipes: open_kernels/recipes/qwen36moe.py (chunk_bytes()/q8_chunk_bytes(), q4_bytes/q4_chunks/band_bytes quant param, CHUNK_F32=6144, Q8_CHUNK_F32=9216); dense.py (accepts quant "q4_1_f32", std_perm_gguf pack ops, builds() adds dx_f32/lm_head_q4_f32, programs() emits out["gguf"] twin section, dx_set/lm_set naming); dx.py (GEMV_SCALES_F32 env replaces spec via dataclasses.replace, F32/CH/GEMV_DEF/GYSYM/GMSYM); manifest.py emits complete nested m["gguf"] = manifest(replace(spec, quant="q4_1_f32")).
- Phase 2: src/open_qwen36/weight_file.hpp (WeightFile base + exact fp16_to_f32), gguf_file.hpp/.cpp (GGUF v2/v3 reader, TensorInfo, kv accessors, embed_row for F32/F16/BF16/Q4_0/Q4_1/Q8_0/Q4_K_S/Q4_K_M/Q6_K, HF→GGUF name map gguf_name()), Q4nxFile inherits WeightFile; pools.cpp std_perm_gguf (block gather + nibble transpose + exact f32 widen; scale at 4*(kb*32+r), m at 1024+, codes at 2048+; p1 = p0 + 256 for i+16 values) and pack_norm (GGUF f32/f16→bf16 RNE) + put op auto-routes GGUF; manifest.cpp accepts std_perm_gguf and nested gguf manifest; core.cpp/core.hpp — weight selection (model.q4nx preferred over model.gguf when both exist), w_ weights view, derive_config() GGUF-KV→config.json (llama-family arch keys), embed_row per token.
- Phase 4 (dense): HF-name map in GgufFile (embed_tokens→token_embd, lm_head→output, blk.N layers attn/ffn/norm renames) tested end-to-end in gguf_pack_test.
- Phase 5: flm-add GGUF mode — GGUF_FILES, GGUF_QUANT_PREFERENCE = ["Q4_1", "Q4_0", "Q8_0"], gguf_quant_of()/choose_gguf_file() (multi-part refusal), fetch_assets/copy_from_dir gguf mapping (chosen gguf → model.gguf), main() auto-detect + dry-run display + details.weights = "gguf"; verified dry-run (picked Peach Q4_1, refused IQ4_XS with message) and real install (files list ['model.gguf','tokenizer.json','tokenizer_config.json']).
- Validation: src/open_qwen36/gguf_pack_test.cpp (GGUF-PACK ctest) — synthetic GGUF → std_perm_gguf → bit-exact dequant; byte-identical to python packer on synthetic AND on real cached Peach Q4_1 (mradermacher/Peach-2.0-9B-8k-Roleplay-i1-GGUF, blk.0.attn_k.weight, 196608 B). All NPU hardware runs: gemv_q4 f32 PASS (cos=1.0, maxrel 3.9e-6, nbad=0, parity with q4nx); lm_head_q8 q4nx-synthetic PASS (4.5e-6); lm_head_q8 f32 random FAIL (see known issue). Fixtures regenerated; pytest 65 passed/1 skipped; ctest 2/2 pass (GGUF-PACK, OPEN-MANIFEST).
- Toolchain set up: ironvenv via uv (Python 3.13), requirements installed, XRT at /opt/xilinx/xrt.
@Atomic-Germ Atomic-Germ mentioned this pull request Sep 8, 2026
@Atomic-Germ
Atomic-Germ marked this pull request as draft September 8, 2026 03:19
@vegah

vegah commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Haha, I had a branch almost ready, but let's keep it one branch :)

@vegah

vegah commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Added some comments just based on a Claude review (which again is based on my branch)

# Conflicts:
#	open_kernels/designs/dense/dx.py
#	open_kernels/designs/lm_head_q8/lm_head_q8.h
#	open_kernels/designs/lm_head_q8/lm_head_q8.py
#	open_kernels/recipes/dense.py
#	src/open_qwen36/manifest.cpp
#	src/open_qwen36/pools.cpp
#	src/open_qwen36/pools.hpp
#	utilities/flm-add/flm_add/__init__.py
The merge left raw()'s opening brace unclosed before the q8 chunk-size
constants and helper functions, so the helpers nested inside raw's body and
the Q8_CHUNK/Q4_CHUNK/code_index/bf16_* symbols were out of scope. Move the
raw() body above the helpers so it compiles.
…rnels in build-all

gemv_q4_gy.cc defined its symbol literally, breaking the LMHEAD_SCALES_F32
f32 build which expects gemv_q4s32_gy (undefined symbol at link). Use the
shared GEMV_Q4_PREFIX macro like dense/gemv_q4_gy.cc so both variants emit.

build-all.sh step-0 heal now also chowns open_kernels (kernel-export
final.prj trees can be left root-owned by a prior sudo'd export).
@Atomic-Germ
Atomic-Germ marked this pull request as ready for review September 9, 2026 02:08
@Atomic-Germ

Atomic-Germ commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

I'm not sure what it will take to get the simplified flow working on windows also, but it probably already does. This wasn't really a deliberate out-of-scope addition, but I really did need it in order to work on the rest along the way:

  • Repo-root workflow ( not src) with cmake --preset linux-default → cmake --build --preset linux-default → cmake --install build.
  • Full-distribution note: the default Linux preset also compiles the open NPU kernel xclbins, requiring XRT (installed) with ironvenv and third_party/mlir-aie+third_party/Peano auto-created/cloned by the build.
  • Presets: linux-default (full), linux-debug (engine-only, FLM_BUILD_KERNELS=OFF), linux-portable (bundled libs), windows-default.
  • Kernel subsetting: -DFLM_KERNEL_SPECS=….
  • Tests: ctest --preset linux-default (the flm list smoke test).
  • Packaging: cpack --preset linux-package-tgz/-deb/-rpm (run from repo root).
  • One-shot: cmake --workflow --preset linux-default.
  • Bumped the CMake prerequisite to 3.25 (needed for the test/package/workflow presets).

I've also merged main back in since a lot happened since.

@Cyronius

Cyronius commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

it's all good. I'll try windows soon and get a PR going if it's no bueno.

@vegah

vegah commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

I am working on fixing the conflicts...

@vegah

vegah commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

(Or, Claude is!)

…ename)

main moved under this branch four times: the FLM -> OFLM rename and the preset
build (#47), the GEMM-block prefill route (#39), the VLM / M-RoPE path (#45), and
a local lm_head kernel TU. Each collided with the WeightFile seam, which is the
one thing this branch is about.

The rule applied throughout: take main's side, which carries the new work, and
route it through the weights view. `w_` is `man_` for a .q4nx and the
GGUF-derived manifest for a GGUF, so code that reads geometry, tensor names or
the tail off `man_` reads the wrong manifest the moment the model is a GGUF --
silently, because both are valid manifests. Eighteen such sites in main's new
code now go through `w_`.

Two that were not mechanical:

* `core.cpp` kept #24's GGUF config derivation (a GGUF needs no config.json --
  the metadata carries it) beside main's image-token / mrope_section parsing,
  and #24's verbose embed check beside main's image-embedding path.
* `lm_head_q4.py` kept main's locally GENERATED kernel TU -- designs/layer_x's
  copy is per-spec and gets removed again, which broke Gemma's lm_head build on
  2026-09-08 -- and gave it #24's GEMV_Q4_PREFIX wrapping, so the f32-scale
  build still resolves gemv_q4s32_gy. The prefix is a compile flag, so one
  generated source serves both and SCALES_F32 stays in the source hash itself.

`pools.cpp` keeps the source-agnostic `raw(const WeightFile&)`; main's
`raw(const Q4nxFile&)` is subsumed, both file types deriving from WeightFile.
`README.md` and `src/CMakeLists.txt` take main's side entirely -- main had
already absorbed this branch's build-from-source rewrite under the new name --
except `open_qwen36/gguf_file.cpp`, which is re-added to the engine sources.
`build.cmd` gains it too, and now runs gguf_pack_test.

Verified: manifest_test, pools_test (Q4_K transcode and the mixed pools
included) and gguf_pack_test all PASS, and open_qwen36_cli links, built with
MSVC against the system XRT.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KCT7r8ebHia4kGAWsaKc3Y
@vegah

vegah commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Merged main into this branch and pushed the resolution (f3b9cda) — it was conflicting in six files.

main had moved under it four times: the FLM→OFLM rename and the preset build (#47), the GEMM-block prefill route (#39), the VLM / M-RoPE path (#45), and a local lm_head kernel TU. Each collided with the WeightFile seam.

The rule throughout: take main's side, then route it through the weights view. w_ is man_ for a .q4nx and the GGUF-derived manifest for a GGUF, so main's new code reading geometry, tensor names or the tail off man_ would read the wrong manifest the moment the model is a GGUF — silently, since both are valid manifests. 18 such sites now go through w_, and the GEMM-block embed moved from Q4nxFile::bf16_row to the WeightFile::embed_row virtual (which is what dequantizes a GGUF row).

Two that weren't mechanical:

  • core.cpp keeps this branch's GGUF config derivation (a GGUF needs no config.json) beside main's image_token_id / mrope_section parsing, and its verbose embed check beside main's image-embedding path.
  • lm_head_q4.py keeps main's locally generated kernel TU — designs/layer_x's copy is per-spec and gets removed again, which is what broke Gemma's lm_head build on 2026-09-08 — and gives it this branch's GEMV_Q4_PREFIX wrapping, so the f32-scale build still resolves gemv_q4s32_gy. The prefix is a compile flag, so one generated source serves both builds.

pools.cpp keeps the source-agnostic raw(const WeightFile&); main's raw(const Q4nxFile&) is subsumed. README.md and src/CMakeLists.txt take main's side wholesale — main had already absorbed this branch's build-from-source rewrite under the new name — except open_qwen36/gguf_file.cpp, re-added to the engine sources. build.cmd gets it too and now runs gguf_pack_test.

Verified on Windows/MSVC against the system XRT: manifest_test, pools_test (Q4_K transcode and the mixed pools included) and gguf_pack_test all PASS, and open_qwen36_cli links. Not run: anything on hardware, and the Linux kernel export.

— Claude, on behalf of @vegah

@vegah

vegah commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

@Atomic-Germ Merge if it is okay, if not - just remove the latest commit.

Comment thread open_kernels/designs/lm_head_q8/lm_head_q8.h
Comment thread open_kernels/gguf_pool.py
Comment thread src/open_qwen36/pools.cpp Outdated
Comment thread src/open_qwen36/pools.cpp
// scales widen EXACTLY to f32 (open_kernels/gguf_pool.py). Zero loss.
const std::string name = with_layer(op.tensor, layer);
if (op.nch == 0 || op.in_dim == 0) fail("std_perm_gguf " + name + " without nch / in_dim");
const GgufFile& g = dynamic_cast<const GgufFile&>(m);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor architectural worry: this makes the packing interpreter format-aware, and pools.cpp is the one file whose
byte-exactness is the safety property — mirrored in NumPy by recipes/pack.py and frozen against legacy_pools.py by
test_pack_plan.py. A new std_perm_gguf op plus a downcast means both interpreters now have to be kept in lockstep
across two layouts.

The alternative is to have the source hand back bytes already in the chunk layout, so std_perm stays one op and
pools.cpp never learns what a GGUF is. That's what I did on my branch and it kept pools.cpp to +21/−15 with no
arithmetic touched — happy to show it if useful, though it presumes the single-layout answer in comment 2.

@Atomic-Germ Atomic-Germ Sep 11, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm definitely interested in that, at the very least we need to be looking at alternative methods of accomplishing this just in case there's an altogether better way we're missing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to. It is branch gguf-runtime off 0fe7922 (PR #21), four commits, never pushed — say the word and I will put it up.

The seam is one header, weight_source.hpp. Three methods — raw(name), embed_row(...), drop_pages() — and one contract:

a source hands back, per tensor, EXACTLY the bytes model.q4nx would have held for it, in the file's raster order (chunk f covers rows 32*(f/ncol), cols 256*(f%ncol)).

Q4nxFile satisfies that by pointing into its mapping; a GGUF source satisfies it by building the bytes. raw() returns a value rather than a pointer — TensorBytes, which is borrowed / solely owned / shared with a cache and looks the same to the caller — because bytes that had to be built need an owner and only the caller knows how long it needs them.

pools.cpp is then +21/−15, and all of it is two mechanical shapes. No op is added, std_perm stays one op, and no arithmetic is touched:

-const uint8_t* raw(const Q4nxFile& m, const std::string& name, size_t need, size_t* got = nullptr) {
-    size_t n = 0;
-    const uint8_t* p = m.raw(name, &n);
+TensorBytes raw(const WeightSource& m, const std::string& name, size_t need, size_t* got = nullptr) {
+    TensorBytes b = m.raw(name);
+    const size_t n = b.size();
     if (n < need) fail(name + " is " + std::to_string(n) + " B, the plan needs " + std::to_string(need));
     if (got) *got = n;
-    return p;
+    return b;
 }

-void apply(const PackOp& op, const Q4nxFile& m, int layer, uint8_t* dst, size_t dst_bytes, size_t ch) {
+void apply(const PackOp& op, const WeightSource& m, int layer, uint8_t* dst, size_t dst_bytes, size_t ch) {
     if (op.op == "std_perm") {
         ...
-        const uint8_t* src = raw(m, name, (op.chunk0 + op.nch) * ch) + op.chunk0 * ch;
+        const TensorBytes srcb = raw(m, name, (op.chunk0 + op.nch) * ch);
+        const uint8_t* src = srcb.data() + op.chunk0 * ch;
         auto perm = std_perm(op.nch, op.in_dim);
         for (size_t c = 0; c < op.nch; ++c) std::memcpy(dst + op.dst + c * ch, src + perm[c] * ch, ch);

The other five ops (expert_stripes, expert_down, put, lmhead_q8, conv_transpose) are each the same two lines, and pack_pool / pack_consts / pack_lmhead are signature-only. pools.hpp swaps its q4nx_file.hpp include for weight_source.hpp.

The format work does not disappear, it movesgguf_repack.cpp (281 lines: the block formats to raster chunks, Q4_K included), gguf_source.cpp (214: the WeightSource), gguf_file.cpp (364: the container). That is more total lines than this PR's. The trade is only about which file they are in: none of them is the one recipes/pack.py mirrors and test_pack_plan.py freezes.

Two things I should be straight about:

And to be explicit about the dependency I flagged: this shape presumes comment 2's single-layout answer. If the f32-scale plane stays, the source has two output layouts to produce and the seam has to carry which one — still outside pools.cpp, but no longer free.

— Claude, on behalf of @vegah

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After way too much thought; you're definitely right.

@Cyronius

Copy link
Copy Markdown
Collaborator

I was trying to make sure my code was going to agree with this, and opus uncovered something:

I think the Q4_0 path is off by a constant.

A GGUF Q4_0 block stores nibbles 0-15 that stand for -8 to 7, so llama.cpp's
dequant is (q - 8) * d. The pack here uses q * d: the min plane is zeroed
for Q4_0 (pools.cpp, mf = has_min ? fp16_to_f32(um) : 0.f), embed_row
does d * (p[2 + j] & 0xF), and gguf_pool.py fills the min plane with
zeros. Every weight comes out 8*d too high - a block that should straddle zero
ends up entirely on one side of it.

The kernel already computes q * d + m, so writing m = -8 * d into the min
plane fixes it with no kernel change and no loss: -8 * d only moves an
exponent, exact in f32 and bf16 alike.

Why the tests don't catch it: gguf_pool's reference dequant uses the same
q * d + 0, so the packer and the checker agree with each other, and
gguf_pack_test only builds Q4_1 and Q8_0 tensors - there's no Q4_0 fixture at
all. We hit the same thing on the .q4nx side last week and it was invisible for
exactly that reason; what finally caught it was the model answering with noise
while every numerical check passed.

One other thing worth flagging while you're in here. "Q4_0" now means two
different byte layouts in this tree. GGUF's nibbles are offset by 8, so 0 means
-8. The .q4nx containers q4nx-build produces hold the same signed values as
two's complement, where 0 means 0 and 8 means -8 - unpack_q4_0 subtracts the
8, then model_converter packs the signed int8 straight back with & 0x0F.
Same numbers, different labels, and reading one as the other shifts everything
by 8*d. Probably worth separate names before the two paths meet.

@Atomic-Germ Atomic-Germ self-assigned this Sep 14, 2026
@Atomic-Germ Atomic-Germ added the enhancement New feature or request label Sep 14, 2026
Atomic-Germ and others added 10 commits September 14, 2026 20:58
…mma tensor naming and GGUF-derived vocab/window fields are fixed, EmbeddingGemma queries the correct tensor, and the installer now has 12 focused GGUF tests including ModelScope and Q4NX precedence.
All new code is additive — no frontend touched, main build glob
 picks the sources up automatically.

 Ported (all MIT):

 - `src/include/AutoModel/model_backend.hpp` + `src/common/AutoModel/model_backend.cpp`
   supported_backends / resolve_backend_id with --backend → OFLM_BACKEND (FLM fallback via getenv_oflm) → catalog → default precedence.
   GGUF-vs-Q4NX dual path instead of the _shared_select_open_engine per-frontend hack.
 - `src/include/AutoModel/oflm_npu_backend.hpp` + `src/common/AutoModel/builtin_backends.cpp` - the shared Q4NX-loading NPU backend template, registered for
   every family incl. our gemma4-12b. flm_npu registered as a legacy alias so shared catalogs keep resolving. Granite (open-only) and qwen3.5-omni (not a
   causal_lm) noted in comments as intentionally unregistered.
 - `src/include/utils/npu_platform.hpp` + `src/common/npu_platform.cpp` aie2p/aie4 detection via column count (XRT platform query → amdxdna ioctl fallback,
   OFLM_PLATFORM override). Directly useful for the family-xclbin distribution plan.
 - `src/include/utils/file_access.hpp` - tiny mmap-observer seam their GGUF loader uses; no dependency yet.
 - Tests: `src/test/model_backend/` (10 tests, ported) and `src/test/npu_platform/` (helpers-only) — both standalone CMake, no NPU needed. All pass.
… per-file {url, revision} pins with full table validation (unknown keys,

   non-string/empty URL, non-40-hex revision, wrong arity), percent-encoded filenames, and a hard error for --modelscope on pinned entries. One fix on the
   way over: a non-object file_sources (catalog typo) now throws instead of being silently ignored.
 - model_info_key record lookup + explicit-sha256 record support — GGUF repos carry sha256 outside LFS metadata, and per-generation artifacts can share one
   tag.
 - download_file_atomic + DownloadRequest (download_model.*) — resume-from-.part, size+hash verify, atomic promote. download_multiple_files routes entries
   carrying expected_size/hash_algorithm through it and keeps the legacy path for everything else, so no other producer of that JSON breaks.
 - The strict gate keys off the pinning itself (uses_pinned_sources: non-empty file_sources object), not chiza's corelib_aie4_gguf backend id. Any pinned
   model — GGUF or otherwise — gets revision-pinned URLs and verified downloads; the existing 44 catalog entries behave bit-identically (verified: only
   embed-gemma:300m's in-flight model.gguf lacks a record, and it keeps its old skip behavior outside the pull path).
 - Verify is two-regime: pinned = strict (size+hash, delete corrupt, fail); unpinned = your existing advisory behavior, untouched.
 - --force actually works now: build_download_list takes force_redownload (previously a forced pull with a complete tree downloaded nothing and reported
   success). Also fixed a latent arg-order bug where the Outdated branch passed sub_process_mode into the use_modelscope slot, and wired up the dead
   missing_from_manifest accounting so a model_list/model_info gap fails loudly as the comment always intended.
@Atomic-Germ
Atomic-Germ marked this pull request as draft September 16, 2026 04:42
Atomic-Germ and others added 10 commits September 16, 2026 09:34
Co-authored-by: Cyrus Attoun <josh.attoun@sviworld.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cyrus Attoun <josh.attoun@sviworld.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Cyrus Attoun <josh.attoun@sviworld.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Take proposed-gguf-support as the authority on GGUF: GGUF-direct dense-family
loading, f32-scale twin kernels (q4_1_f32, 6144 B chunks), std_perm_gguf,
WeightFile, nested gguf manifest section, q4nx-precedence, K-quant refusal,
oflm-add GGUF installer, downloader pinning.

feat/gguf keeps its speed/families work: qwen36moe recipe, batched prefill,
deepstack/vision modeling, open-engine fixtures, buildall/export scripts.

Merged engine (src/open_qwen36): step_embed deepstack + embedding_row,
unified WeightFile/q4nx pools with std_perm_gguf std_fuse branch,
need_all pack ops, GGUF-PACK/OPEN-VISION tests in CMake.

Kept both oflm-add junction fallback and vision.json sidecar; dropped flm
engine lookup (all-oflm). Dropped duplicate utilities/gguf-inspect in favor
of utilities/gguf-utils/. Untracked generated layer_x/gemv_q4_gms.cc.
Fixtures regenerated from merged recipes; 559 spec tests pass.
…scales)

Generated deterministically by gemv_q4.py under GEMV_SCALES_F32=1 with the
standard qkv geometry, alongside the already-tracked gemv_q4s32_p4b16r2 set.
…lfm2 re-exports dense chunk geometry

The full fedora-default export now passes all 12 open_kernels specs plus the
BERT set. dx_attn.py (the attention-only prefill dispatch) picks up dx.py's
ATTN_QKV_BIAS abias stream and ATTN_PTAB_SPLIT meta block verbatim, which was
blocking Qwen2.5-3B (biased q/k/v at 2 KV heads / hd 128, a two-element
position record); non-bias single-record families keep the byte-identical
paths. lfm2.py re-exports band_bytes/chunk_bytes off .dense like dense.py does
via .qwen36moe, which dx.py's calls require on any family module.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants