Repository navigation
Conversation
c9c1b55 to
7745579
Compare
|
Moving to draft until #1742 (one-token decisions) merges. This PR then needs a rebase (both PRs add blocks in |
Granite Switch checkpoints could only run through vLLM or a local HuggingFace load. Pointing OpenAIBackend at a llama-server nearly worked: adapter activation and the document-free adapters were fine, but every document-based adapter returned "unanswerable" regardless of the documents, because llama-server drops the top-level documents field the intrinsic path sends, so the Switch chat template's documents variable never rendered. Mirror documents into chat_template_kwargs alongside the top-level field. vLLM folds both shapes into the same template context, so its behaviour is unchanged; llama-server reads the copy. Add scripts/start_llamacpp_switch.sh, which converts a Switch checkpoint to GGUF (working around the list-form extra_special_tokens that current transformers rejects) and serves it with llama-server --jinja on port 8081, next to the base-Granite server's 8080. Add an example that runs when that server is reachable and skips with an actionable reason otherwise, plus README and intrinsics docs. Closes generative-computing#1735. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…-server The intrinsic path sent each io.yaml schema only as vLLM's own `structured_outputs` field. llama-server ignores that field and constrains decoding only from the OpenAI-standard `response_format`, so every adapter function ran unconstrained there. Also send the schema as `response_format`; vLLM folds it back into the same `structured_outputs.json`, and a caller's own `response_format` still wins. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The converter's requirements pin transformers==4.57.6, which cannot load current Granite Switch tokenizers (tokenizer_class TokenizersBackend is a transformers 5 class). Run from inside the mellea repo, uv silently swapped the pin for mellea's own [tool.uv] override; run from anywhere else, the conversion failed after the multi-gigabyte download. Override transformers explicitly to the range mellea tests and run the install from the build cache so no surrounding project's uv settings change the resolve. The EXIT trap also removed every *.gguf.partial in the shared cache, which could delete a concurrent run's in-progress conversion for another model; it now removes only this run's file. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
GraniteChatCompletion rejected any request carrying documents both at the top level and inside chat_template_kwargs, which is now the shape OpenAIBackend sends for llama-server. Accept identical copies and drop the duplicate; differing copies are still rejected. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
answerability_llamacpp.py carried no backend marker, against the rule that e2e tests always name one: `llamacpp` probes the base-Granite server on 8080 and would wrongly skip it, so `-m` selection and backend grouping could not see it. Add a `llamacpp_switch` marker whose gate reuses the llama.cpp /props probe against LLAMACPP_SWITCH_BASE_URL (default 8081), and mark the example with it. Document both llama.cpp markers in the test README's backend table, which listed neither. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The Switch llama.cpp example parsed the /models reply outside its skip guard, so a foreign server answering 200 with something other than an OpenAI model list failed the example instead of skipping it. Parse inside the guard. Also correct the documents-mirror comment, which said vLLM always gives the top-level field precedence; that holds on current vLLM, while 0.11 and earlier let chat_template_kwargs win. Reflow the llama-server paragraph in the intrinsics guide and note that io.yaml constraints now apply there too. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The intrinsic path derives response_format from the merged extra_body, so a caller's own structured_outputs override is the schema mirrored. Pin it, and fix the llama.cpp gate docstring, which named the 8080 default even for the Switch gate. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…MultiSwitch fix Current Granite Switch checkpoints use MultiSwitch, which reserves two cache slots. Every llama.cpp converter up to and including v0.7.0 writes a block count one too high for them, so the script's GGUF converted but llama-server refused to load it (ggml-org/llama.cpp#30617). Pin the converter to build b11675, the first with the fix (ggml-org/llama.cpp#30621). Verified from a cold cache on the current 3b revision: the script downloads, converts with transformers 5.20.0, and serves on a v0.6.0 llama-server; the GGUF's block_count is 40, matching blocks 0-39, and the example and the adapter functions run against it. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
A terminal Ctrl-C reaches the whole process group, so llama-server already had the interrupt when the script's EXIT trap sent it a second signal. It then logged "Received second interrupt, terminating immediately" and could trip a Metal teardown assert. After a signal, the trap now waits up to five seconds for the server to exit before signalling it; other exits still stop it at once. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
decisions_llamacpp.py (from generative-computing#1742) predated this PR's conventions: it asked for llama.cpp from main, served the Switch model on 8080 (now the base-Granite server's port), read SWITCH_DECISIONS_BASE_URL, carried the `llamacpp` marker plus `skip`, and referred forward to this PR. It now reads LLAMACPP_SWITCH_BASE_URL (default 8081), carries `llamacpp_switch`, and skips with the start command when the server is absent or not serving granite-switch-4.1-8b, the checkpoint that embeds the decision adapter. The script's usage comment names that 8b rather than the 8b preview, which has no decision adapter. intrinsics.md gives the llama-server paragraph its own heading and links to it from the decisions section. A unit test pins that a decision request carries no response_format. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The llama-server section sat at the end of the adapter-functions page, under "Composable adapter construction (advanced)", and never said which Switch checkpoint to serve. It is now its own section beside the local checkpoint section, with the prerequisites, one start command per adapter set (3b preview for RAG/core/guardian, released 8b for decisions), the port clash between them, and the backend snippet. The backend note, the decisions snippet, tutorial 09 and both example READMEs link to it. Also fixes the backend note's path to the Granite Switch examples, which pointed at a directory that does not exist. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…gates Run the converter's inline Python with -I so a stray module in the caller's directory cannot shadow an import, add the llama.cpp remote only when it is missing rather than discarding every git error, reject a non-numeric ready timeout up front, and count the converter venv in the first-run download note. A new collection test pins that the llamacpp and llamacpp_switch markers each gate only on their own server; it fails if the Switch gate is removed. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
2fba346 to
d8ef166
Compare
IBM publishes granite-switch-4.1-8b-GGUF and -30b-GGUF, so decisions no longer need a local conversion: llama-server -hf with --alias serves the name the example checks for. The conversion script stays the route for the 3b preview, which has no published GGUF. Verified decisions and guardian-core against the published Q4_K_M. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
|
this is ready for review |
The relative link to docs/examples/granite-switch/README.md resolves outside the docs site, which Docusaurus rejects as a broken link. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
|
I propose holding this until #1700 (shim removal) merges, then rebasing once. #1700 rewrites Not a hard dependency: this merges cleanly against main today. |
ajbozarth
left a comment
There was a problem hiding this comment.
Some feedback from Claude: looks good overall. A few items inline; I'll leave the approval call to other reviewers.
| if isinstance(structured_outputs, dict) | ||
| else None | ||
| ) | ||
| if isinstance(schema, dict) and "response_format" not in api_params: |
There was a problem hiding this comment.
"A caller's own response_format wins" only holds on vLLM for json_schema. vLLM applies response_format on top of structured_outputs via dataclasses.replace, so a caller's json_object plus the io.yaml json fails StructuredOutputsParams validation ("only one kind of structured outputs constraint"), and a caller's text leaves the io.yaml constraint in place. test_user_response_format_not_overwritten uses json_object, the case that errors. main behaves the same, so it isn't a regression, but either drop the io.yaml-derived structured_outputs when the caller passes response_format, or scope this comment to llama-server and json_schema.
| raise ValueError( | ||
| "'documents' parameter inside 'chat_template_kwargs' is not a list" | ||
| ) | ||
| if self.extra_body.documents is not None: |
There was a problem hiding this comment.
Nothing in Mellea runs the backend's outgoing request through GraniteChatCompletion (rewriter.transform uses ChatCompletion, before the mirror), and the outside users I could find import granite-common's copy. So this relaxation has no caller. Can it be dropped, or what is it for?
Related but pre-existing: the kwargs-only branch below (line 498) assigns self.documents rather than self.extra_body.documents, so _documents() returns None and the documents are lost.
| pinned llama.cpp checkout, whatever llama-server version you have. IBM also | ||
| publishes `granite-switch-4.1-30b-GGUF` in the same quantizations. | ||
|
|
||
| Both use port 8081, so run one at a time or set `LLAMACPP_SWITCH_PORT` on the |
There was a problem hiding this comment.
LLAMACPP_SWITCH_PORT only applies to the script; the llama-server -hf command needs --port. Line 171 also already says both routes use 8081, so these two sentences could merge.
| # which llama-server can serve directly with -hf). llama.cpp's | ||
| # converter handles GraniteSwitchForCausalLM directly (ggml-org/llama.cpp#25107; | ||
| # see LLAMA_CPP_REVISION below for the revision current checkpoints need), so | ||
| # this script converts from the HF checkpoint. The server is tested on v0.6.0. |
There was a problem hiding this comment.
nit: this says tested on v0.6.0 (line 28 too), while the docs and PR description say v0.6.0 and v0.9.0.
| context = ChatContext().add(Message("assistant", "Hello there, how can I help you?")) | ||
| question = "What is the square root of 4?" | ||
|
|
||
| # Both calls must reach the documents: before issue #1735's fix, llama-server |
There was a problem hiding this comment.
nit: readers copying this example don't need the fix history. Something like # Both calls depend on the server actually rendering the documents. would do.
Pull Request
Issue
Fixes #1735
Description
On llama-server, Granite Switch adapter functions that read documents gave wrong answers, with no error. Same server, same 3b preview checkpoint,
mainagainst this PR:rag.check_answerabilitywith the answer in the document:"unanswerable"onmain,"answerable"here.rag.find_citations:[]onmain, the right citation here.rag.flag_hallucinated_contenton a sentence the document supports word for word:"unfaithful"onmain, with an invented reason ("the population of Paris is actually around 2.2 million"). Here it's"faithful".Adapter functions that take no documents (
check_certainty,guardian_check, and decisions on the 8b) already worked on llama-server and give the same results either way. Adapter activation was never the problem: llama-server renders the Switch chat template and honourschat_template_kwargs.adapter_name.The PR also documents the route. The released 8b and 30b have published GGUFs that llama-server serves directly; the 3b preview, which carries the RAG and core adapter functions, has none, so the PR adds a script that converts and serves it. That gives a Mac or any GPU-less box a supported way to run these adapter functions.
What changed in the request
Two parts of the intrinsic request went in fields only vLLM reads:
documentsfield, which llama-server drops, so the template never saw them. They now also go inchat_template_kwargs.structured_outputs, which llama-server ignores, so adapters decoded unconstrained. The schema now also goes out as the OpenAI-standardresponse_format, which llama-server enforces.On vLLM these are duplicates it folds back together, and a live before/after run shows no change (see Testing). A caller's own
chat_template_kwargs.documentsorresponse_formatstill takes precedence. Decision adapters carry no schema, so neither change reaches them.Serving a Switch model
Which checkpoint to serve depends on the adapter functions you want, because no published checkpoint has all of them. Both routes use port 8081, so they run beside the base-Granite server on 8080:
llama-server -hf ibm-granite/granite-switch-4.1-8b-GGUF:Q4_K_M --alias ibm-granite/granite-switch-4.1-8b --port 8081 --jinja(~6.4 GB).--aliasmakes the server report the name Mellea's adapter discovery and the example expect.scripts/start_llamacpp_switch.shconverts it to an f16 GGUF and serves it (~7 GB download, ~9 GB GGUF).For the script, later runs reuse the cache and are ready in seconds. The converter runs in its own venv, with
transformersoverridden to 5.x because the converter's 4.57.6 pin can't load current Switch tokenizers. It's pinned to llama.cpp build b11675, the first with ggml-org/llama.cpp#30621. Current Switch checkpoints use MultiSwitch, and older converters, v0.7.0 included, write a block count that llama-server refuses to load (ggml-org/llama.cpp#30617).Docs, examples and marker
docs/docs/advanced/intrinsics.mdgets a "Granite Switch on llama-server" section beside the local-checkpoint one. It covers prerequisites, the two start commands and the backend snippet. The backend note, the decisions section, tutorial 09 and both example READMEs link to it.docs/examples/granite-switch/answerability_llamacpp.py(3b preview) anddocs/examples/intrinsics/decisions_llamacpp.py(8b, from feat(intrinsics): one-token decisions (decide/choose/score) #1742, now aligned) skip with the start command when the right server isn't up. Neither downloads weights.llamacpp_switchmarker is gated on a llama-server atLLAMACPP_SWITCH_BASE_URL(default port 8081).llamacppkeeps meaning the base-Granite server.GraniteChatCompletionaccepts identical documents in both places, so it validates the requests the backend now sends.Limitations
Testing
Tests added to the respective file if code was changed
New code has 100% coverage if code was added
Ensure existing tests and github automation passes (a maintainer will kick off the github automation when the rest of the PR is populated)
llama-server before/after (v0.9.0, Apple Silicon, 3b preview,
mainvs this PR): the three results above.check_certainty(0.895) andguardian_check(0.99998) agree on both arms.vLLM before/after (v0.30.0, 3b preview,
mainvs this PR): answerability (both labels), citations, hallucination detection andcheck_certaintygive identical output on both arms, andguardian_checkdiffers by 4e-6. With a 20-sentence document, the answerability prompt is 335 tokens on both arms, so the duplicate documents render once.Decisions on llama-server, published GGUF (
granite-switch-4.1-8b-GGUF:Q4_K_Mvia-hf, the documented command):decisions_llamacpp.pypasses, directly and under pytest.decidegives 0.901,choosepicks payments at 0.999,scoregives 1.70, and label mass is at least 0.999 on every call.guardian_checkscores 0.999994 on a biased prompt and 6.0e-5 on a benign one.Decisions on llama-server, converted (8b f16 from the script, converted from an empty cache, 40 blocks): the same example passes, with
decide0.904,choose0.999,score1.68, andguardian_check0.99995 against 2.3e-5. So Q4_K_M moves the scores by under 0.02.3b preview on llama-server:
answerability_llamacpp.pypasses, and query clarification returns a well-formed clarification. Each example skips with its start command when the server holds the other model.Earlier runs (llama-server 0.6.0): all 12 Switch io.yaml schemas compile through
response_format, and constraining leaves the logprob-scored adapters' scores unchanged to about 1e-5. The converter install resolves the sametransformersfrom any directory.Unit. In
test/backends/test_openai_intrinsics_unit.py(48 pass), the documents andresponse_formattests fail without their fix. Separate tests pin that a caller's own documents,structured_outputsorresponse_formatwin, and that a decision request carries noresponse_format.test/test_example_collection.pypins thatllamacppandllamacpp_switcheach gate only on their own server, and fails if the Switch gate is removed.Full suite,
uv run pytest test/ -m "not qualitative"at d8ef166: 5341 passed, 205 skipped, 0 failed.test_adapter_versions_unchangedwas deselected: it compares the pinned granitelib SHAs with the live Hub repos, which have since moved (README edits, plus a newdecision/directory in the core repo), so it fails whatever this PR does, and CI skips it.The final commit changes only docs, the example's docstring and skip messages, and script comments;
test/test_example_collection.pyand the intrinsics unit tests (70) pass on it.pre-commitpasses on every commit, andshellcheck -S styleis clean on the script.Attribution
Adding a new component, requirement, sampling strategy, or tool?
If your PR adds or modifies one of the types below, check the matching box. A checklist of type-specific review items will be posted as a comment.
NOTE: Please ensure you have an issue that has been acknowledged by a core contributor and routed you to open a pull request against this repository. Otherwise, please open an issue before continuing with this pull request.