Skip to content

Ollama removed LoRA-adapter GGUF *creation* (v0.34.1+) — breaks adapter provisioning for #1634, serving unaffected #1651

Closed

Description

@planetf1

Summary

Ollama removed the ability to create/build new LoRA-adapter GGUF models as of v0.34.1 (via ollama/ollama#14969, merged 2026-09-15). Serving/inference of already-built adapter models is unaffected — this is a provisioning-time regression, not a runtime one. It blocks test/scripts/build_ollama_uncertainty_adapter.sh and the uncertainty_ollama.py example, and closes the only documented route for a new adopter to acquire an Ollama adapter tag for use with #1634's OllamaModelBackend.add_adapter/resolve_adapter.

This surfaced while validating #1643 (nightly script reliability): bumping the pinned Ollama version there (needed to fix an unrelated BlueVela crash) exposed this.

TL;DR: the Modelfile ADAPTER <lora.gguf> directive, used with ollama create -f Modelfile to fuse a LoRA into a new model tag, is what no longer works — Ollama's CLI now rejects any Modelfile containing it before the request even reaches the server. That is the exact mechanism test/scripts/build_ollama_uncertainty_adapter.sh uses. A model that was already built with ADAPTER on an older Ollama (or pulled pre-built from a registry) still loads and serves fine — the --lora flag is still passed to llama-server at runtime, just with a deprecation warning. Only the build-time ADAPTER directive is dead; run-time serving of an adapter already baked into a model is not.

(This issue was independently verified against primary sources — Ollama's actual PR diff, commit history, and current mellea source — before finalizing; corrections from that pass are folded in below.)

Root cause (verified against Ollama's own source)

  • ollama/ollama#14969 — "create: add server-side MLX imports and drop GGUF conversion" (created 2026-03-19, merged 2026-09-15, +10,041/−28,435 lines across 187 files) — deliberately removed LoRA-adapter creation support as part of a larger architectural change: adding server-side MLX/safetensors import support and moving GGUF conversion/quantization responsibility out to llama.cpp tooling. Per the PR description: "Limit GGUF create to wrapping existing GGUF inputs into Ollama manifests... so GGUF conversion and quantization stay in llama.cpp tooling."
    • This was a deliberate removal, not incidental collateral: the diff removes LoRA-adapter creation across five sites — a new errAdaptersUnsupported error variable, a CLI-side pre-flight rejection, api.CreateRequest.Adapters marked Deprecated: LoRA adapters are no longer supported, the metadata-based rejection gate, and outright deletion of convert/convert_gemma2_adapter.go and convert/convert_llama_adapter.go — plus every mention of the ADAPTER Modelfile directive scrubbed from docs/modelfile.mdx.
    • Note: the PR's Fixes #17179 / Fixes #17180 lines refer to two crash/goroutine-leak bugs in the old conversion pipeline, but those issues were filed four months after this PR was opened — they're closed as a side effect of deleting the subsystem, not the motivation for it.
  • The rejection is enforced by three independent gates, none behind a flag:
    1. cmd/cmd.go:335-336 — client-side: the CLI itself rejects a Modelfile containing an ADAPTER line before contacting the server, with the bare message "LoRA adapters are no longer supported". This is the gate our build script actually hits.
    2. server/create.go:86-88 — request-level: an API request with Adapters set is rejected with HTTP 400.
    3. server/create.go:894-895 — metadata-level (only reachable if the above two are bypassed): the server reads the GGUF's own internal metadata (general.type, via Kind() in fs/gguf/metadata.go) and rejects any file self-identifying as "adapter", with "%w: %s is a LoRA adapter".
  • Confirmed source-consistent across versions: v0.34.0's server/create.go still has the old convert.ConvertAdapter path; v0.34.1 onward has the rejection gates above at the same locations as current main. Empirically reproduced (build fails identically) on macOS/Apple Silicon and on a BlueVela Linux/CUDA node on 0.34.1; the same build succeeds on 0.32.2.
  • Latest release is v0.34.2 (2026-09-15) — nothing newer, and given the rationale, unlikely to be reverted.
  • "Just convert the adapter independently with llama.cpp's own tooling?" Already the case, and not the blocker. build_ollama_uncertainty_adapter.sh produces the adapter GGUF via llama.cpp's convert_lora_to_gguf.py from a pinned llama.cpp checkout — never via Ollama's own (now-deleted) converter — and verifies the output is a correctly-typed adapter GGUF before proceeding. The failure is the next step: attaching that already-correctly-produced GGUF to a servable Ollama model tag via ollama create -f Modelfile (script lines 116-118). Ollama's rejection is keyed on the GGUF's own internal metadata (general.type == "adapter"), not on which tool produced the file, so no alternate converter changes the outcome — the block is on the consuming side (ollama create), not the producing side.

Scope: creation is dead, serving survives (on a deprecation clock)

Runtime/inference retains full adapter support, unchanged in v0.34.1, v0.34.2, and current main — only warned about, not removed:

  • server/images.go still reads an adapter layer from an existing manifest into AdapterPaths.
  • server/sched.go still passes AdapterPaths into the runner launch and uses it in runner-reuse diffing.
  • llm/llama_server.go still passes each adapter to llama-server as --lora <path>, now alongside a slog.Warn("LoRA adapters are deprecated and will be removed in a future release", ...).
  • server/model.go states the scope directly, in Ollama's own words: "LoRA adapters are deprecated; the adapter layer is carried over but new adapters cannot be created."
  • PullModel has no adapter gate either, so pulling a pre-built adapter tag from a registry also still works.

Two populations, different impact:

  1. A real mellea user with an already-provisioned adapter tag (built on ≤0.32.2, or pulled from a registry): unaffected. Upgrading Ollama blocks building new tags; it does not break serving existing ones.
  2. Mellea's own tooling/CI: broken every run, unconditionally. build_ollama_uncertainty_adapter.sh always builds fresh (ollama create -f Modelfile at line 118), so it hits gate 1 above on every invocation from 0.34.1 onward. docs/examples/intrinsics/uncertainty_ollama.py shells to that script under subprocess.run(..., check=True) when the model isn't pre-provisioned, so it raises rather than degrading.

The sharper edge underneath that split: OllamaModelBackend's adapter_models parameter ships with no defaults (mellea/backends/ollama.py:248,277 — empty dict; the docstring's "mellea-test/uncertainty-alora:latest" is a placeholder, not a real default), and the only documented route in this repo to obtain such a tag is the now-broken build script. So: existing installs keep working, but a new adopter currently has no working path to acquire the artifact at all unless they already have one, or a pre-built Granite adapter tag happens to be published on Ollama's registry (not checked as part of this issue).

Two things not settled by source inspection alone, flagged rather than asserted: (a) whether a manifest built on 0.32.2 loads end-to-end on 0.34.x without issue, given unrelated manifest-format changes shipped in between (#17619, #17858); (b) whether any adapter-fused Granite tag is currently published on ollama.com, which would restore a pull-based provisioning route for new users.

Impact on mellea

  • mellea/backends/ollama.py's add_adapter/resolve_adapter (added in feat(backends): support Ollama adapter functions #1634) are not directly broken — no mellea Python code calls ollama create; they only register an I/O contract and select a tag string from adapter_models. The break is entirely in how a tag gets created in the first place, not in mellea's runtime code.
  • test/scripts/build_ollama_uncertainty_adapter.sh fails outright on 0.34.1+ (every run, no workaround short of using an older Ollama).
  • docs/examples/intrinsics/uncertainty_ollama.py fails when it falls back to self-building the adapter.
  • fix(test): reliable nightly GPU runs — phased execution as default, script-owned timings #1643 makes this failure non-fatal in the nightly script (a warning + the two affected tests fail/skip, instead of aborting the whole Ollama test phase) — that contains the blast radius for CI but doesn't restore provisioning.

Upstream context

Ollama has open work in this area, suggesting the removal may be a transitional step rather than a dead end: ollama/ollama#14032 ("llm: support multiple LoRA adapters and hot-swapping"), ollama/ollama#17377 ("server: support safetensors base models and float lora_alpha for LoRA adapters"), and issues #10358 ("Allow granite lora support") and #9548 ("Support hot-swapping for LoRA adapters") are all open.

Suggested next steps

  1. Decide whether an Ollama-served adapter needs a working provisioning path today, or whether it's acceptable to document "bring your own pre-built tag" until Ollama's own LoRA work (above) lands something new.
  2. If a provisioning path is needed now: the only route left is merging the LoRA into the base model's weights before GGUF conversion (e.g. via PEFT's merge_and_unload()), producing one full model per adapter instead of a shared base + swappable adapter — a real storage/design tradeoff, not a config fix.
  3. Track Ollama's upstream LoRA work (above) for a possible restored or improved provisioning path.
  4. Confirm that a served aLoRA actually activates, whatever provisioning route we end up with. On the local Hugging Face path an aLoRA can be loaded and reported as active while never activating, because the activation sequence its config declares never appears in the assembled prompt, and PEFT reports nothing when that happens (bug(adapters): detect when an aLoRA cannot activate, and stop selecting it silently #1678 for the Mellea-side detection, bug(adapters): published requirement-check aLoRA never activates, and scores worse than the LoRA #1679 for a concrete artefact mismatch in the published requirement-check aLoRA). Two things follow for this issue: the bundled tag our build script targets is an aLoRA, so the same question applies to it; and merging via merge_and_unload() in step 2 above is not semantically equivalent for an aLoRA, since the weights are trained to apply only from an activation point onwards and merging applies them at every position. The ten-probe comparison harness from the local path transfers directly to an Ollama-served tag.

cc

@gabe-l-hart — flagging given your role on Ollama/llama.cpp Granite integration; you may already have visibility into ollama/ollama#14969 or the open LoRA PRs above, or want to weigh in on the right path forward.

References

Activity

  1. changed the title [-]Ollama removed LoRA-adapter GGUF support (v0.34.1+) — breaks #1634's Ollama adapter-serving design[/-] [+]Ollama removed LoRA-adapter GGUF *creation* (v0.34.1+) — breaks adapter provisioning for #1634, serving unaffected[/+] on Sep 18, 2026
  2. gabe-l-hart commented on Sep 18, 2026

    @gabe-l-hart

    https://github.com/ollama/ollama/blob/4f6f73983036588ec22059a27c9bbb00b3ce332f/server/model.go#L67 Yikes, this is indeed a problem. Seems like a pretty big deprecation, likely due to the complexity they saw with trying to implement parity on their MLX backend. There may be some hackery we could play by using the LLAMA_ARG_MODELS_PRESET to work around Ollama and tell the backend llama-server to find raw adapter GGUFs, but that's a pretty brutal hack.

  3. gabe-l-hart commented on Sep 18, 2026

    @gabe-l-hart

    Confirmed the logical path for any adapters is now officially deprecated. Working backwards:

    1. llama-server dispatched with --lora arg: https://github.com/ollama/ollama/blob/4f6f73983036588ec22059a27c9bbb00b3ce332f/llm/llama_server.go#L387
    2. adapters set from model.AdapterPaths: https://github.com/ollama/ollama/blob/4f6f73983036588ec22059a27c9bbb00b3ce332f/server/sched.go#L582C87-L582C99
  4. gabe-l-hart commented on Sep 18, 2026

    @gabe-l-hart

    I think this makes it pretty clear: we should focus our efforts on https://llama.app/ for compositional adapters going forward.

  5. self-assigned this
    on Sep 23, 2026
  6. filtercodes commented on Sep 23, 2026

    @filtercodes

    Confirming @planetf1's analysis. PR #17377 targeted the internal Go conversion logic (convert/*_adapter.go) to enable safetensors adapter ingestion. With #14969 deleting that conversion subsystem, #17377 cannot be merged in its current form.

    @gabe-l-hart's llama.cpp does not have an MLX backend. For Mac users training LoRA models with MLX, since #14969 explicitly shifted Ollama toward server-side MLX/safetensors handling, there may still be a native safetensors/MLX adapter path in Ollama's roadmap (e.g., related to #14032), even if GGUF adapter creation is permanently dead.

  7. planetf1 commented on Sep 25, 2026

    @planetf1
    ContributorAuthor

    One thing worth folding into the provisioning decision, since the adapter this repo's build script targets is an aLoRA rather than a plain LoRA.

    Merging is not equivalent for an aLoRA. Suggested next step 2 is to merge the LoRA into the base weights before GGUF conversion, via PEFT's merge_and_unload(), producing one full model per adapter. For a plain LoRA that is the storage-versus-flexibility trade described above. For an aLoRA it also changes the semantics: the weights are trained to apply only from an activation point in the prompt onwards, with the base model's KV cache reused for the prefix. Merging applies them at every position, including the prefix, which is not the arrangement they were trained in. So a merged uncertainty-alora is a different model from the adapter served with activation, and the output would need checking rather than assuming.

    Related, and cheap to fold in while this path is being reworked. On the local Hugging Face path we have just found that an aLoRA can be loaded and reported as active while never activating, because the activation sequence its config declares never appears in the assembled prompt. PEFT reports nothing when that happens. See #1678 for the Mellea-side detection, and #1679 for a concrete artefact mismatch in the published requirement-check aLoRA. Whatever provisioning route Ollama ends up with, the same question applies to a served adapter: does activation actually fire, and how would we know? Worth answering as part of this rework rather than as a separate exercise afterwards.

    The ten-probe comparison harness used on the local path transfers directly to an Ollama-served tag, so the same check can run once a provisioning route exists again.

  8. gabe-l-hart commented on Sep 25, 2026

    @gabe-l-hart

    Merging is not equivalent for an aLoRA.

    Definitely a good point. Wouldn't creating a Granite Switch out of the adapters and the base model be the right path forward? This would avoid the deep-merge of the weights (thus breaking non-adapted use cases) while still packing a single GGUF. For Granite-based models, this should be well supported in Ollama passing through to llama-server since ggml-org/llama.cpp#25107.

  9. planetf1 commented on Sep 29, 2026

    @planetf1
    ContributorAuthor

    A concrete addition to item 4 (confirm the served aLoRA actually activates), from the #1685 work.

    Ollama's intrinsic path prefers the aLoRA io.yaml whenever the catalogue lists one (use_alora = AdapterType.ALORA in metadata.adapter_types, mellea/backends/ollama.py:456-457). So a served requirement-check aLoRA gets the same as-published instruction, starting <requirements>:. The Granite tokenizer merges >: into one token, so the declared invocation sequence ([27, 71226, 29], <requirements>) never appears in the prompt, and the adapter cannot activate whichever provisioning route we end up with.

    #1685 repairs that instruction on LocalHFBackend only. On Ollama the fix is the upstream republish tracked in #1679, followed by a catalogue pin bump. The uncertainty aLoRA, which is what the bundled-tag script builds, is not affected: its instruction contains its declared <certainty> sequence.

    Not yet checked: whether Ollama's server applies aLoRA invocation tokens at all. That decides whether this matters in practice there.

  10. added
    p2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.
    needs-designProblem is clear; implementation approach needs design discussion before work starts
    on Oct 5, 2026
  11. removed their assignment
    on Oct 6, 2026
  12. added
    bugSomething isn't working
    area/backendsProvider-specific work: Ollama, HF, LiteLLM, OpenAI, Bedrock, vLLM
    area/adapter-functionsGranite adapter functions: framework and adaptiers including RAG, Guardian, Core
    on Oct 6, 2026
  13. planetf1 commented on Oct 20, 2026

    @planetf1
    ContributorAuthor

    Folded into #1633, whose evidence section quotes the key points from this thread (the llama.cpp redirect recommendation, the aLoRA-merge caveat, the Granite Switch packing suggestion, the activation question). The full discussion stays readable here. Closing so the Ollama decision has one entry point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/adapter-functionsGranite adapter functions: framework and adaptiers including RAG, Guardian, Corearea/backendsProvider-specific work: Ollama, HF, LiteLLM, OpenAI, Bedrock, vLLMbugSomething isn't workingneeds-designProblem is clear; implementation approach needs design discussion before work startsp2Medium/low: minor bugs, niche features, polish, docs, tests, cleanup. Scoped, lower urgency.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions