Skip to content

Sync master with upstream ggml-org/llama.cpp (82bb48500) - #80

Merged
danielhanchen merged 721 commits into
masterfrom
sync-upstream-20260807
Aug 7, 2026
Merged

Sync master with upstream ggml-org/llama.cpp (82bb48500)#80
danielhanchen merged 721 commits into
masterfrom
sync-upstream-20260807

Conversation

@danielhanchen

Copy link
Copy Markdown
Member

Brings the fork up to upstream ggml-org/llama.cpp master at 82bb48500. Master was 718 commits behind with 132 of its own.

Conflicts

Seventeen, all of them .github/workflows/ files that this fork deletes on purpose in favour of the unsloth-prebuilt-* set. Every one is a modify/delete: present upstream, already deleted here. They stay deleted.

No source file conflicted. The merge touches 1337 source files.

build-wasm.yml is new upstream and is deleted for the same reason as the other seventeen: it triggers on push to master with broad path filters, so it would burn runner capacity on every push to this repo.

What this means for #61

After this merge #61 no longer merges cleanly, in seven files. Six are adjacency only, where upstream and the PR both add lines to the same enum or macro block and the resolution is to keep both:

  • ggml/include/ggml.h, include/llama.h, gguf-py/gguf/constants.py, src/llama-model-loader.cpp, ggml/src/ggml-cpu/arch-fallback.h, ggml/src/ggml-cuda/mmq.cu

The seventh is real. ggml/src/ggml-cuda/mmq.cuh was refactored upstream in "CUDA: refactor MMQ kernel configuration" (ggml-org#24127) plus five follow-ups, which replaced the tile API that #61's MMQ port was written against. That port needs redoing against ggml_cuda_mmq_config. I will push that as a follow-up commit on #61.

Worth noting that the id choice in #61 survives the sync untouched: upstream now uses GGML_TYPE_Q2_0 = 42 and LLAMA_FTYPE_MOSTLY_Q2_0 = 41, exactly the range #61 was moved clear of, and the narrow types at 64/65/66 do not collide with anything.

ggerganov and others added 30 commits July 17, 2026 17:06
…it (vectorized LD/ST perf opt) for Adreno GPUs (ggml-org#25810)

* opencl: read MoE dp4a activation tile as 128-bit local loads

* opencl: vectorize MoE dp4a activation staging as 128-bit loads
…ation (ggml-org#25787)

DeepSeek-V4's ffn_gate_tid2eid tensor is an i32 token-id -> expert-id
index table, not weights. It was never added to the name-based
exclusion list alongside ffn_gate_inp.weight, so llama-quantize tries
to quantize it and fails since i32 cannot convert to a float type.

Fixes ggml-org#25754

Signed-off-by: Yash Raj Pandey <yashpn62@gmail.com>
* dflash: rotate injected K/V cache when using K/V quantization

* Update src/models/dflash.cpp

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

* clearer format

* remove trailing whitespace

---------

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
…r Adreno Q8_0 MUL_MAT for llama-server multi-stream (ggml-org#25910)

* opencl: handle broadcast for adreno gemm/gemv_noshuffle

* opencl: honor view_offs for adreno noshuffle gemm/gemv

* opencl: general GEMM/GEMV support broadcast

* opencl: remove unnecessary tests

* opencl: remove unnecessary comments

---------

Co-authored-by: Li He <lih@qti.qualcomm.com>
…org#25865)

The agentic gate counted MCP servers, builtin and custom tools but not
frontend tools, so with the JS sandbox as the only tool source the
agentic flow was skipped, no tools field reached the server and the
chat template rendered without the tool system prompt.

The sandbox is fully client-side: frontendTools derives from the
Developer settings toggle alone, counting it in the gate restores that
single source of truth.
* ui: fix Show tool call in progress toggle ignored

The showToolCallInProgress setting was disconnected from the render
path during the agentic content rework: getDefaultExpanded() returns
a hardcoded false for tool call sections and an unconditional effect
auto-expands the currently executing tool call regardless of the
setting.

Drive default expansion of all tool call section types from the
setting and remove the now redundant auto-expand effect. Manual
toggling still takes precedence over the default in both directions.

* ui: rename Show tool call in progress to Always show tool call content

The previous name suggested symmetry with Show thought in progress,
which only applies while inference is running, but tool call content
stays expanded after completion. Rename the label, the settings key
and the syncable server key to alwaysShowToolCallContent. The synced
parameter never worked under its previous name so no migration is
needed.
Edge paragraph margins are now zeroed at the source in
markdown-content.css, but the user bubble and the system message still
carried the -my-4 compensation for them. The uncompensated negative
margins shrank the wrapper 2rem below its content, collapsing
single-line user bubbles into a scrollable sliver and skewing the
system message expand threshold.
* llama_dsv4: write only used rows in state

* add TODO about conflating token pos with kv rows
…gml-org#25815)

* feat: WIP

* feat: Replace conversation rename flow with unified AlertDialog component

* feat: Add radio group component and consolidate title generation settings

* refactor: Remove JS Sandbox global toggle and migrate legacy user state

* chore: Formatting

* refactor: Cleanup

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* refactor: Cleanup

* refactor: Marquee selection hook

* feat: UI improvements

* refactor: Bulk db operations

* fix: optimize bulk conversation deletion to handle ancestor chains

* refactor: remove pairedKey mechanism from settings system

* fix: remove redundant onclick handler from dialog cancel button

* chore: pin @lucide/svelte to exact version

* feat: Run JavaScript tool disabled by default

* fix: correct active conversation deletion tracking in bulk delete

* feat: improve shift-key multi-selection support in sidebar via keyboard

* refactor: Retrieve JS Tool enabling via Developer Settings

* nits: sync, dialog wording, cycle guard, and lockfile follow-ups

- Restore titleGenerationUseLLM registry entry so it syncs across devices again
- Mention fork cascade in the bulk delete confirmation dialog
- Clear newParent on cycle guard break so children never point at a deleted conversation
- Align @lucide/svelte in package-lock.json with the exact pin in package.json

---------

Co-authored-by: Pascal <admin@serveurperso.com>
k_get_rows_float did a scalar one-element-per-thread copy and recomputed the
row-invariant work (index load, fast_div_modulo, src/dst row pointers) for
every element. Hoist that out of the per-element loop, and add a vectorized
path (k_get_rows_float_vec) that copies one int4 (16 B) per thread for the
contiguous same-type (no-cast) case.

The vectorized path is gated at compile time (is_same<src0_t, dst_t>) and at
runtime on 16-byte alignment of the base pointers and all row strides and on
ne00 % VEC == 0. Vectorizing divides the block count by VEC, so a small
single-row gather can drop below the device CU count and regress; an
occupancy gate keeps those on the block-rich scalar path.

On Strix Halo (gfx1151) the DeltaNet recurrent-state gather (ne00=524288)
drops 18.6us -> 13.0us (rocprofv3 HW timestamps), faster than the Vulkan
backend, with no regression on the small conv-state gather; total get_rows
-27%. test-backend-ops GET_ROWS passes (47/47).

Assisted-by: Claude Opus 4.8
…nd (ggml-org#25795)

This adds the missing `GGML_BACKEND_DL_IMPL()` macro invocation, that other backends have.

Fixes ggml-org#25586 for me
…les (ggml-org#23570)

* Refactor vk_queue to use per-instance mutexes and unique handles

* integrates VK_KHR_internally_synchronized_queues, abstracting the queue submission into a polymorphic interface that completely bypasses host-side mutex locking when driver-side synchronization is supported

* fix compilation error

* fix duplicate pNext chain for VkPhysicalDeviceInternallySynchronizedQueuesFeaturesKHR

* add fallback defines for VK_KHR_internally_synchronized_queues

* add null checks for queues in vk_device_struct destructor

* use unique_ptr for outer queues to enforce exclusive ownership and optimize lifetime

* use static constexpr for eInternallySynchronizedKHR

* add lock guard to ggml_vk_create_aliased_queue for thread safety

* initialize sync_query_features.internallySynchronizedQueues to VK_FALSE

* reuse sync_query_features for internallySynchronizedQueues and simplify chaining

* refactor internallySynchronizedQueues detection

* fix internallySynchronizedQueues query guard

* use eInternallySynchronizedKHR constant

* fix self-referential alias for eInternallySynchronizedKHR

* use macro for eInternallySynchronizedKHR fallback

* fix internallySynchronizedQueues query timing in ggml-vulkan.cpp to prevent device creation mismatch

* reset sync_query_features.pNext before reusing in device creation chain, also removed the redundant second probe call

* refactor internally synchronized queues detection to use chained feature query and avoid redundant API calls

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* Update ggml/src/ggml-vulkan/ggml-vulkan.cpp

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>

* rename sync_enable_features to internally_synchronized_queues_features

* queue_flags is still computed before has_internally_synchronized_queues is set

* fix trailing whitespace

* replace eInternallySynchronizedKHR macro with static constexpr

* preserve source queue semantics in single-queue aliased transfer queue

* vulkan: fix cmd_pool access via pointer for compute_queue unique_ptr

* vulkan: lock queue during debug label emission when not internally synchronized

---------

Co-authored-by: Jeff Bolz <jbolz@nvidia.com>
Co-authored-by: Stanisław Szymczyk <sszymczy@gmail.com>
…tion-Id (ggml-org#25760)

* server: return 400 instead of 500 on validation error with X-Conversation-Id

set_req() attaches the spipe as soon as the header is present, before the request
body is parsed. When params validation throws, set_next() never runs and next_orig
stays empty, so on_complete() called it and crashed with std::bad_function_call,
turning the prepared 400 JSON into a generic 500.

on_complete() now treats an empty next_orig as "streaming never started" and evicts
the session installed by set_req(), so a failed request leaves nothing behind for
discovery or replay. This also covers valid requests that carry the header but do
not stream, which previously left an empty finalized session in the map until the
GC TTL.

* ui: do not send the backend_sampling placeholder

On a fresh profile the syncable settings hold the empty string placeholder meaning
"let the server decide". Every neighbor field goes through the hasValue() guard
that filters it, except backend_sampling, which sent the placeholder verbatim and
made every default settings completion fail validation.

Guard the field with hasValue() like its neighbors. hasValue(false) is true, so an
explicit false still reaches the server and the intent of ggml-org#18781 (send both true
and false) is preserved. Only the placeholder is filtered.
With -hfd pointing to a repo shipping speculative sidecars, the draft
resolved to the main model of that repo, since find_best_model()
excludes sidecar files, and the explicit draft plan suppressed the
sidecar discovery on the -hf repo.

The draft plan already discovers its sidecars, they were just never
consumed. Wire them as the draft, following the fallback pattern of
the main plan, so this now works as expected:

llama-server -hf repo -hfd repo --spec-type draft-dflash
…olation (ggml-org#25781)

The Qwen3-VL learned position embedding is interpolated to the runtime patch
grid with the default bilinear+antialias (align_corners=False) sampling, while
the transformers reference uses align_corners=True (torch.linspace(0, side-1, T)).
The mismatch scales grounding coordinates about the image center, growing with
image size and per-axis for non-square images (see ggml-org#16880).
Signed-off-by: wendadawen <wendadawen@qq.com>
* cuda: add k-quant support to GET_ROWS

Device-side embedding lookups require GET_ROWS to handle the k-quants
used by common GGUF recipes (Q4_K_M stores token_embd as q6_K). Without
it the backend rejects the op and the scheduler falls back to the host,
copying the full embedding matrix back on every token in single-device
graphs.

Factor the super-block dequantizers out of the dequantize_block kernels
in convert.cu into shared device functions in dequantize.cuh and reuse
them from a new k_get_rows_kq kernel : one thread block dequantizes one
(dst row, super-block) pair with the existing thread layouts, 32 threads
for q4_K and 64 for the other k-quants.

Covers q2_K to q6_K in get_rows_cuda and supports_op. i-quants are left
as a TODO.

* cuda: add i-quant support to GET_ROWS

Extends the shared super-block dequantizers to the nine i-quants and
reuses them from k_get_rows_kq with the 32-thread layout of the matching
convert.cu kernels. supports_op gates the k-quant and i-quant path on
ne0 being a multiple of QK_K, which iq4_nl does not guarantee on its
own (QK4_NL sub-blocks). mxfp4 is left as a TODO.

* cuda: add mxfp4 support to GET_ROWS

Moves the mxfp4 dequantizer into the shared super-block helpers and
reuses it from k_get_rows_kq with the 32-thread layout of the matching
convert.cu kernel. mxfp4 joins the ne0 % QK_K gate in supports_op since
its 32-value sub-blocks do not guarantee QK_K-aligned rows on their own.
This closes GET_ROWS type coverage on CUDA: every quantized GGML type
now takes the direct device path.

* cuda: gate the GET_ROWS row size only for 32-value sub-block types

Address review from @pwilkin: the i-quant commit replaced the return
shared by the whole supported type cascade, so f16/f32/bf16/i32 and the
legacy quants also inherited the ne0 % QK_K == 0 gate and any row size
that is not a multiple of 256 fell back to the scheduler. Split the
cascade: unconditional support is restored everywhere, the gate stays
only on iq4_nl and mxfp4 whose 32-value sub-blocks do not guarantee the
QK_K super-blocks the kernel iterates on.
* webgpu : add CONV_2D_DW (depthwise conv2d) kernel

Implement GGML_OP_CONV_2D_DW for the WebGPU backend,
ported from the Vulkan backend's conv2d_dw.comp.

Assisted-by: Claude Opus-4.8

* Remove unnecessary comments in webgpu support

* update supported ops tables, triggered by adding webgpu CONV_2D_DW
* ggml: enable PowerPC backend variants on AIX

Allow the PowerPC CPU backend variants to be built on AIX by extending the platform check in the CMake configuration. This reuses the existing PowerPC backend implementations without changing their behavior.

Also fix a missing semicolon in the PowerPC Q0 matmul implementation.

* Fix missing semicolon in sgemm.cpp
sfallah and others added 24 commits August 5, 2026 15:30
* server: don't walk Windows junctions in file_glob_search

std::filesystem reports a junction as a plain directory, so the symlink
guard misses it and a junction pointing back at an ancestor is walked
until the path length gives out

read the reparse tag and treat a symlink and a mount point as links,
leaving any other reparse point walkable so cloud placeholders and dedup
stubs still get searched

look junk directory names up case insensitively on Windows, where NTFS
makes Build the same directory as build

test that a junk directory stays selectable while its contents stay out
of search results

* server: report a directory the walk could not read

a directory that fails to open or to iterate was skipped in silence, so
a caller got a listing that looked complete while a whole subtree was
missing: a path over the platform limit, a volume going away, a name the
filesystem rejects

skip_permission_denied never reaches this path, so an error here is an
incomplete answer rather than a deliberate omission, and it now sets the
truncated flag

* server: simplify the file_glob_search listing plumbing

return a small result struct instead of two out params and a caller path
that only fed an error string, taking list_entries from six parameters
down to three

scope the error code to the directory being read, act on the status code
the entry lookups already returned, and treat an unreadable link state as
a link so the walk never descends on a guess

check the deadline when a directory is popped, not only per entry, so a
tree of empty directories cannot outlive the budget

read the path parameter once, and reject an invalid limit the way an
invalid type is already rejected, instead of silently falling back

normalize the resolved path, so a "." or ".." a caller typed reaches
neither git nor the client, and return the generic path form with '/'
separators on every platform, so the base sent to clients no longer needs
a local fixup

* ui: expire cached picker searches

the cache grew for the lifetime of the component: entries went stale
after the TTL but were never removed, so every distinct query typed in a
session stayed in memory

drop expired entries when a new result is stored

* server: address review from @ngxson

trim comments to one line each, and drop two that restate the code

rename junk_lookup_name to get_effective_name, and move it and the link
check to private static members next to junk_dir_names

merge the Windows and Linux link checks into one is_link, so symlinks are
checked everywhere and junctions only add to it on Windows

* server: convert tool paths as UTF-8 on Windows

a narrow path uses the active code page there, so a file name came back
mangled and a path with an accent could not be opened at all

convert explicitly at every crossing between a std::string, which always
carries UTF-8 here, and fs::path

read the home directory through the wide environment, since the narrow
one returns the profile path in the active code page too

the walker no longer normalizes separators by hand, since paths now come
back in generic form

* server: fold the platform branch inside console_output_to_utf8

match the shape of the other helpers, one definition with the #if inside,
instead of two definitions wrapped in #if and #else

inline the single caller helper and trim the comment
* ggml: add ggml_build_forward_order

ggml_build_forward_expand marks the tensor and all its ancestors for
compute, so using it as a pure ordering hint (keeping q, k and v
together) defeats ggml_build_forward_select: the unselected branch is
forced to run with inputs that were never uploaded. In the mtmd audio
graph this makes GEN_WAV calls execute the GEN_CODE branch with a
stale inp_code0, hitting the get_rows bound assert on CPU.

Add ggml_build_forward_order, which inserts nodes without the compute
flag; the flag is restored when the branch is actually selected.
Switch the q/k/v hints in clip_graph::build_attn to it.

* nit: reduce comments (AGENTS.md)
…causes of DeviceLost drivers errors (ggml-org#26371)

* vulkan: add debug tooling to get more information about a DeviceLost error

* fix submission threshold applied too late

* use logging macros, throw instead of aborting

* clean up circular dependency
…6665)

This commit adds the --model-name flag to the causual and embedding
model conversion scripts.

The motivation for this is that this is the name used for the metadata
field general.name and it can be useful to specify this explicitely if
the default (the basename of the model path) is not what we want.
* ci: prepare for amd rocm ci

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ci: fix editorconfig-checker

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ci: fix device not recognised

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ci: rename gpu-amd to gpu-hip

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* ci: gpu-hip to gpu-rocm

haha

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>

* CUDA: allow integrated-GPU host output buffer in debug assert

On integrated GPUs (APUs), the scheduler can legitimately place a graph
node's output on the host-visible buffer, which ggml_cuda_compute_forward
already handles. The debug assert in ggml_cuda_graph_evaluate_and_capture
required every node output to be on the device buffer, so a debug build
aborts on such a node (e.g. attn_residual ADD -> ROCm_Host on RDNA3.5).
The source-tensor assert directly below already permits this via the
integrated + cuda_host exception; apply the same exception to the node's
own output buffer. Debug-only; no effect on release/compute.

Fixes test-recurrent-state-rollback on gfx1151 (Strix Halo).

* ci: enable unified memory for ROCm gfx1151 job

Work around a coherence issue on integrated RDNA3.5 (gfx1151) where GPU
kernels reading mmap-loaded weights can return incorrect output, which
makes test-llama-archs (and real inference) intermittently wrong.
GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 uses managed memory, which restores
coherence. Remove once the underlying ROCm/HIP issue is fixed.

* test-llama-archs: skip jamba on HIP backend

jamba produces incorrect output (~0.55 NMSE vs CPU) on the HIP backend on
RDNA3.5 (gfx1151); the SSM kernels need separate investigation. Skip it
for now, matching the existing per-backend carve-outs (WebGPU), so the
ROCm CI can run the test for the remaining architectures.

* ci: use HIP_LAUNCH_BLOCKING for ROCm gfx1151 job

The gfx1151 ROCm CI job produced incorrect inference output (qwen3 perplexity ~88 vs ~9.4) due to an async-execution correctness issue in the HIP path. Serializing kernel launches with HIP_LAUNCH_BLOCKING=1 restores correctness. This replaces the earlier GGML_CUDA_ENABLE_UNIFIED_MEMORY workaround, which did not fix batched inference.

* test-backend-sampler: skip top-k subtests on HIP backend

The ROCm backend does not support the TOP_K/ARGSORT op at vocab scale (no CUB; bitonic argsort is capped at ncols <= 1024), so top-k/top-p backend samplers cannot be offloaded. The penalties, set_sampler, mixed, and top_p subtests assert that offload happened, so they fail on HIP. Skip them until TOP_K is supported on the ROCm backend.

* Update tests/test-backend-sampler.cpp

Co-authored-by: Aaron Teo <taronaeo@gmail.com>

* Update tests/test-backend-sampler.cpp

Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>

---------

Signed-off-by: Aaron Teo <aaron.teo1@ibm.com>
Co-authored-by: Aaron Teo <aaron.teo1@ibm.com>
Co-authored-by: Jim Wu <ywu@xilinx.com>
Co-authored-by: Aaron Teo <taronaeo@gmail.com>
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
* mtmd: add chunk save/load function

* nits

* add tests

* rn _MAX --> _COUNT
…tning_indexer (ggml-org#26646)

- In MSL, declaring an array of matrix types like `threadgroup half4x4` causes
a 'no matching constructor' compilation error because MSL matrix types do not
have zero-argument default constructors and threadgroup variables cannot have
initializers.

- Fix this by declaring a POD `threadgroup half` array instead and casting
to `threadgroup half4x4 *` for matrix indexing.

Signed-off-by: JamePeng <jame_peng@sina.com>
1. Abort CI if build requirements are missing.
2. Add check to make sure Git LFS has been configured.
3. Add trailing newlines to log messages.
…#26515)

* support fp16 to fp16/fp32

* support all missed data types in set_rows

* refactor the code to support all data types
…DSV4_HC_PRE (ggml-org#26568)

* support DSv4 OPs: LIGHTNING_INDEXER,DSV4_HC_COMB,DSV4_HC_POST,DSV4_HC_PREwq

* update ops.md

* fix format issue
* tests: add SWIGLU perf cases

perf mode had no GLU coverage. Adds SWIGLU at 17408 columns, 512 and
2048 tokens, f16 and f32, with the operands both fused and split.

* sycl: consolidate fused-GLU kernels

They differed only in which op_* they called, so take the op as an argument and share a common launcher.
Their block sizes were all 256, so launch geometry is unchanged;
SYCL_GELU_BLOCK_SIZE and SYCL_SILU_BLOCK_SIZE lose their last users so are dropped.

* sycl: contiguous fast path for the fused GLU ops

o0 == n and o1 == n collapse the de-interleave index math to the
identity, so dispatch a flat kernel in that case. It fires for
ggml_glu_split with packed operands; a fused [gate|up] tensor keeps the
strided path. test-backend-ops perf -o SWIGLU on an Arc Pro B70: split
+14% f16 and +4% f32, fused unchanged.
The NVFP4 quantization format stores a scaling factor for every group of
16 weights, packed into a single UE4M3 byte.

The SYCL GPU code was converting these scale values using the E4M3 path,
but that's *signed*, and these are unsigned values.
* ui: read model modalities from the router model list

The router advertises input modalities for every model, loaded or not.
Reading them at list build time lets the UI accept image and audio
uploads for a model selected through ?model=, which has no /props yet.

* enum
Brings the fork up to upstream 82bb485, 1337 source files.

The only conflicts were the 17 upstream CI workflows this fork deletes on purpose in
favour of the unsloth-prebuilt set, so those stay deleted. No source file conflicted.
build-wasm.yml is new upstream and is deleted for the same reason: it triggers on push
to master with broad path filters and would burn runner capacity on every push here.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

The rule this repo runs on is that a sync may take upstream content but must never
modify, revert or delete anything we authored. That was being checked by reading a
1300-file diff, which does not scale and does not catch a renumbered enum.

verify_upstream_sync.py checks it exactly instead of by eye. It computes the set of
paths we touched since the merge base and requires every one to be byte-identical in
the merge, requires every one of our commits to still be an ancestor, and allows a
deletion only when upstream deleted the file and we never touched it. Two extra layers
on top of the byte compare: an AST pass over our Python that pins every top-level name
and its value, and an enum pass over ggml.h and llama.h that pins every GGML_TYPE,
GGML_FTYPE and LLAMA_FTYPE id and flags collisions. The enum pass is deliberately not
limited to files we touch, because a published GGUF stores those ids, so a shift is a
data-corruption bug whoever caused it. Unowned headers are compared against upstream
rather than against our stale copy, so upstream adding a type is not a false positive.

test_verify_upstream_sync.py builds a throwaway repo of the same shape and injects the
four failure modes one at a time: our file modified, our file deleted, an id renumbered,
and a value inside our Python changed. All four are caught and a clean merge passes.

On this PR the gate reports: 132 fork commits all still ancestors, all 68 paths we touched
byte-identical, all 86 deletions upstream retiring its own files, no Python definition
lost, no id changed, no collisions.
@danielhanchen

Copy link
Copy Markdown
Member Author

Re-checked this against the rule that a sync may take upstream content but must never modify, revert or delete anything we authored. It holds, and I have added a gate so it does not have to be re-checked by eye next time.

What the fork actually owns

master customises 68 paths, all of them .github/ or scripts/unsloth/. There are zero fork changes to llama.cpp source on master. That is what makes "additions only" cleanly checkable rather than a judgement call.

Result

PASS  history: all 132 fork commits since the base are ancestors of the merge, none dropped
PASS  content: all 68 paths the fork touched are byte-identical in the merge
PASS  tree: 2929 fork files -> 3308 merged files, 93 vanished and none of them fork-authored
PASS  deletions: all 86 deletions are upstream retiring its own files, none fork-authored
PASS  ast: every top-level definition and constant in the fork-touched Python survives with the same value
PASS  c_enums: every GGML_TYPE/GGML_FTYPE/LLAMA_FTYPE id keeps its value, no collisions

The strongest single statement: the merged tree differs from upstream/master in exactly 69 paths, with zero modifications. 20 additions, all ours (12 unsloth-* workflows, prebuilt-alert/action.yml, 7 scripts/unsloth/*). 49 deletions, all upstream CI that was already absent on our master. There is no third category where a revert could hide.

The 86 deletions relative to our master are upstream retiring its own files (fattn-wmma-f16.cu, the hexagon htp reshuffle, json-partial.*, and so on). Each was checked two ways: absent from upstream/master, and never touched by us.

The gate

scripts/unsloth/verify_upstream_sync.py, committed here. It computes the paths we touched since the merge base and requires each to be byte-identical, requires every fork commit to still be an ancestor, and permits a deletion only when upstream deleted it and we never touched it. Two layers past the byte compare:

  • AST, over our Python: every top-level name and its value is pinned, using ast.unparse so a reflow is invisible but a changed tuple element is not.
  • enum ids, over ggml.h and llama.h: every GGML_TYPE / GGML_FTYPE / LLAMA_FTYPE id is pinned and collisions are flagged. Deliberately not limited to files we touch, since a published GGUF stores these ids, so a shift corrupts real files no matter who caused it. Headers we do not own are compared against upstream rather than our stale copy, so upstream adding a type is not a false positive.

scripts/unsloth/test_verify_upstream_sync.py is the negative control. It builds a throwaway repo of the same shape and injects each failure mode: our file modified, our file deleted, an id renumbered, a value inside our Python changed. All four are caught, and a clean merge passes.

$ python3 scripts/unsloth/test_verify_upstream_sync.py
PASS  clean merge is accepted
PASS  caught: modify our file  (via content)
PASS  caught: delete our file  (via content)
PASS  caught: renumber a published id  (via c_enums)
PASS  caught: subtle value change in our Python  (via ast)

Run it on this PR with:

python3 scripts/unsloth/verify_upstream_sync.py --repo . --merge <merge-sha>

It needs a full clone; it refuses to run on a shallow one rather than report a meaningless pass.

One thing to confirm

.github/workflows/build-wasm.yml is a new upstream workflow. I dropped it, the same way the fork already strips the other 48 upstream workflows, because it triggers on push to master with broad path filters and would spend runner capacity on every push here. It removes none of our work, so it does not breach the rule, but it is the one upstream addition this merge does not take. Say the word and I will pull it in.

A _COUNT is one past the last id, not an id, so a branch that legitimately adds types must
move it and flagging that is a false positive: #61 takes GGML_TYPE_COUNT from 43 to 67.

It is still checked, just with the right rule. The sentinel may only grow, never shrink, and
it must still bound every real id in its family. A shrink, or a value that no longer bounds
the ids, is a violation as before, and no real id may move at all.
@danielhanchen
danielhanchen merged commit 53fd974 into master Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.