Skip to content

RFC / Not for merge: Experimental fixes for agent workloads (+10% to dflash2 agent) - #300

Open
pkochubey wants to merge 9 commits into
Neroued:masterfrom
pkochubey:my-fixies
Open

pkochubey wants to merge 9 commits into
Neroued:masterfrom
pkochubey:my-fixies

Conversation

@pkochubey

Copy link
Copy Markdown

Sorry for my sheet in PR and all this mess, I check Issues in project and fixed some issues, what related for me nvfp4+dflash2+agent coding long context+hermes.

Bellow AI bullshit-summary by PR:

Problem and scope

Related Issues:

This PR fixes several context-cache, long-context CUDA, NVFP4 and tool-calling edge cases that showed up in long-running agent workloads with Qwen models.

The main user-visible problems were:

  • private continuation slots could saturate without selecting an old continuation for replacement;
  • shared-prefix reuse could degrade after repeated catalog turnover;
  • planner accounting could penalize checkpoints that were no longer reachable;
  • physically infeasible shared-prefix captures could fail to explore a reclaim path;
  • the default shared-prefix catalog could be smaller than the maximum number of candidates produced by one request;
  • quantized small-T attention could select a split geometry that exceeded the page staging capacity at long context;
  • fused NVFP4 SwiGLU did not handle arbitrary ragged token counts through the TMA path;
  • the Qwen tool-call parser accepted only the original narrow XML representation and rejected XML forms emitted by Claude Code / agent harnesses;
  • identical duplicate tool parameters were treated as an error;
  • OpenAI Chat Completions and Anthropic Messages could not use a final assistant message as an assistant-prefill continuation.

The PR does not implement the suspended-generation capability proposed in #172. Assistant continuation remains a new request that can reuse the existing context-cache/private-continuation state rather than an opaque resumable generation handle.

Implementation

Context-cache planning and retention

Private continuation entries now carry their publication_order through the materialization policy and planning cost.

When the private continuation catalog is full and an eviction is required, the planner can select the oldest eligible publication instead of failing to make progress.

publication_order is also included in pressure-target ordering so replacement decisions remain deterministic when higher-priority retention metrics are equal.

Unreachable checkpoints are explicitly represented in the checkpoint policy and excluded from portfolio recovery-loss accounting.

Shared-capture planning can explore replacement targets when the requested materialization is physically infeasible, even when the previous pressure-evidence condition is absent.

The default shared-prefix capacity is increased to cover the maximum prepared-prompt candidate count produced by one request, preventing candidate discovery from exhausting the catalog immediately.

Long-context small-T attention

The special quantized attention path that forced the maximum split count for tokens == 1 at large windows was removed.

Quantized attention now uses the normal split calculation, including the page-count-derived lower bound.

Page staging computes the actual required page count and asserts that it fits the compile-time PageIds capacity instead of silently truncating the page range.

This keeps split geometry and paged-KV staging consistent for long-context decode.

Ragged NVFP4 fused SwiGLU

The NVFP4 A4 fused SwiGLU TMA path now supports ragged token counts rather than requiring exact tile multiples.

Workspace/TMA scheduling uses the padded execution extent while output stores remain guarded by the real token count.

This allows the fused path to be used for arbitrary M values without writing the padded tail.

Tool-call parsing

The Qwen tool-call parser is extended to recognize the XML forms observed from Claude Code and other agent harnesses, including:

<tool_call>
  <function name="...">
    <parameter name="...">...</parameter>
  </function>
</tool_call>

<function_calls>
  <invoke name="...">
    <parameter name="...">...</parameter>
  </invoke>
</function_calls>

<invoke name="...">
  <param name="...">...</param>
</invoke>

Tool and parameter names can be supplied either by the original =<name> representation or through a name= attribute.

Attribute parsing uses token boundaries so unrelated attributes such as filename= do not satisfy the name lookup.

Opening and closing XML forms must match (function/function, invoke/invoke, parameter/parameter, param/param).

Tool output may contain normal assistant text before the structured call; that prefix is preserved as assistant content.

Repeated parameters with the same name and the same value are coalesced. Repeated parameters with conflicting values still fail with DuplicateParameter.

Declared tool names and parameter schemas continue to be enforced after the XML representation has been normalized.

Assistant continuation / prefill

OpenAI Chat Completions and Anthropic Messages now treat a request whose final message is an assistant message as:

PromptContinuationMode::ContinueFinalAssistant

The chat template sets continue_final_message and does not add a new assistant generation prompt.

This enables agent clients to send an output-limited partial assistant response back to NInfer and continue the same assistant turn.

The request still goes through normal admission/planning. Existing private continuation/context-cache state can be reused when the token prefix matches; this is not the suspended-generation/resume-capability design proposed in #172.

Verification

Regression coverage was added or expanded for the affected components.

Context cache

Added coverage for private continuation saturation to verify that, with a full private catalog, the older publication_order is selected for eviction.

Added a continuous shared-catalog turnover regression for #251 to exercise repeated capture/replacement cycles instead of testing only a single planning operation.

The planner tests also cover the updated unreachable-checkpoint and replacement behavior.

NVFP4 SwiGLU

The NVFP4 A4 correctness matrix now includes ragged and tile-boundary values including:

2, 4, 5, 16, 56, 64, 65, 96, 97, 112,
128, 129,
255, 256, 257,
300,
511, 512, 513,
1023, 1024, 1025,
1408

These cases exercise both sides of the relevant 128/256/512/1024 tile boundaries and compare the fused implementation with the existing mathematical oracle.

Tool-call parser

Regression tests cover:

  • Claude Code function name="..." syntax;
  • invoke syntax;
  • function_calls containers;
  • standalone tool calls following ordinary assistant/plan text;
  • <param> and <parameter> forms;
  • attribute-name boundary handling;
  • rejection of mismatched closing tags;
  • coalescing identical duplicate parameters;
  • rejection of conflicting duplicate parameters;
  • preservation of embedded parameter-like markup in string values;
  • a Plan + TaskCreate agent-response reproduction.

Assistant continuation

Schema tests verify that:

[system, user, assistant, user]

remains a normal NewAssistantTurn, while:

[system, user, assistant]

selects ContinueFinalAssistant, and that this mode reaches the final PromptInput.

Commands

A full verification run should include the normal project test suite:

cmake --build build -j
ctest --test-dir build --output-on-failure

For the CUDA changes, the affected long-context attention and NVFP4 SwiGLU tests should additionally be run under CUDA Compute Sanitizer, in particular memcheck and synccheck.

At the time of this report, the branch HEAD has no published GitHub CI/status checks, so a clean full build, complete registered test run and sanitizer run are not claimed by this PR description unless attached separately.

The following remain outside the scope of this change:

Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
… forms

The Qwen tool-call parser accepted only the original <tool_call> /
<function=NAME> / <parameter=NAME> markup. Claude Code and other agent
harnesses emit the same call in XML forms the parser did not recognise, so
those responses fell back to plain text and the harness never saw a tool call:

  <tool_call><function name="X"><parameter name="d">v</parameter></function></tool_call>
  <function_calls><invoke name="X"><parameter name="d">v</parameter></invoke></function_calls>
  <invoke name="X"><param name="d">v</param></invoke>

Names are now read from the tag header, accepting either the original =NAME
form or a name= attribute, with attribute-token boundaries so an unrelated
filename= attribute cannot satisfy the name lookup, and with quote stripping.
Opening and closing forms must match, so a <function ...> closed by </invoke>
is a structural failure rather than a silent misparse. A <function_calls>
container accepts several children, and ordinary assistant text before the
call is preserved as content.

The streaming decoder tracked only a <tool_call> prefix; it now buffers any
partial marker from the full marker set, so every variant is detected
incrementally instead of leaking into visible content. Parameter value nesting
is counted against the matching close tag, so a nested <param ...> inside a
string value no longer truncates the value.

Taken from upstream PR Neroued#300 (tool-call parsing), by pkochubey, resolving
upstream issue Neroued#276. The PR rejects a repeated parameter with conflicting
values; this fork keeps the last value instead, per the local rule merged with
PR Neroued#299, so the duplicate branch writes the last value and counts the repair in
duplicate_parameters_repaired as before.

Verification: ninfer_tool_call_parser_test passes, including the new
function-name-attribute, invoke, function_calls container, standalone-invoke,
attribute-boundary, mismatched-closing-tag and Plan+TaskCreate reproduction
cases.
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
The chat-completions validator refused every response_format but
{"type":"text"} with response_format_not_supported. Clients that always send a
format - Hermes-style harnesses among them - were therefore refused before the
request reached the Engine, even though the field only ever asked for a shape
the sampler was free to attempt.

The validator now accepts text, json_object and json_schema, and rejects only
an unknown type string.

Nothing else consumes response_format: there is still no constrained decoding,
so the requested shape is not enforced. docs/serving.md now says that on the
supported-options line instead of implying the schema is honoured, and keeps
`strict:true` and the grammar/structured_outputs/guided_* extensions in the
rejected list, where an unenforceable promise would be a real defect.

Taken from upstream PR Neroued#300, by pkochubey.

Verification: ninfer_openai_schema_test passes, with the rejection case moved
from json_schema to an unknown type and the neutral-request case switched to
json_object.
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
A response cut short by the output limit or by context capacity still carries
whatever tool call the model had emitted, and both adapters checked the tool
call first. A client that trusts the terminal reason therefore saw "tool_calls"
/ "tool_use" and acted on a call whose arguments may be incomplete.

The truncation reasons now win:

  chat completions -> "length"
  Messages         -> "max_tokens" / "model_context_window_exceeded"

The Anthropic adapter's switch keeps its OutputLimit and ContextCapacity cases
so it still covers every FinishReason; the early returns sit above them and
above the tool-call check. Streaming still delivers the partial tool-call
delta, but the terminal chunk carries the truncation reason instead of claiming
the call completed.

Taken from upstream PR Neroued#300, by pkochubey.

Verification: ninfer_openai_schema_test and ninfer_anthropic_schema_test pass.
Their tool-call presentation cases terminate on StopToken, so they still report
"tool_calls" / "tool_use", and the empty-output ContextCapacity case still
reports "model_context_window_exceeded".
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
Anthropic Messages already treated a request whose final message is an
assistant turn as an assistant prefill, but only when that turn was text-only
with thinking off. Chat Completions had no prefill mode at all, so an agent
client holding an output-limited partial assistant turn could not hand it back
and have the Engine continue it in place.

  * chat completions now select ContinueFinalAssistant when the final message
    is an assistant message, matching Messages;
  * the Anthropic "a final assistant prefill must contain only text" rejection
    is gone;
  * the "assistant prefill cannot be combined with enabled thinking" capability
    rejection is gone, so the template's own guard becomes the authority and
    the request is refused later as invalid_prompt;
  * the chat template accepts a final assistant turn carrying reasoning content
    or tool calls - an output-limited turn can carry either - while still
    refusing media and thinking-enabled prefill.

The thinking guard is deliberately kept, unlike the upstream PR. Removing it
lets render_chat() build an ambiguous reasoning opener, which
test_assistant_continuation pins with "assistant continuation accepted an
ambiguous Thinking opener"; the PR ran no CI and did not catch this.
Continuability is otherwise still decided against the rendered layout: the
template must expose that message's content region unambiguously and throws
otherwise, so a final turn the template cannot continue fails loudly instead of
rendering a wrong prompt.

This is not the suspended-generation design from upstream issue Neroued#172 - the
request is a normal new request that may reuse existing
context-cache/continuation state.

Taken from upstream PR Neroued#300, by pkochubey.

Verification: ninfer_qwen3_5_frontend_test, ninfer_tool_call_parser_test,
ninfer_openai_schema_test, ninfer_anthropic_schema_test,
ninfer_resource_manager_test, ninfer_http_transport_test,
ninfer_request_log_test and ninfer_serve_options_test all pass. The OpenAI
suite's new case asserts [system,user,assistant,user] stays NewAssistantTurn
while [system,user,assistant] selects ContinueFinalAssistant and reaches the
PromptInput options; the Anthropic suite's thinking-on prefill case now expects
acceptance at capability resolution, while the frontend suite's ambiguous
Thinking-opener case still expects a throw.
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
The portfolio valuation credited an owner for retaining every catalogued
checkpoint it held, including checkpoints whose shortlist frontier the incoming
request cannot reach. Those are not hits the request can take, so the planner
priced recovery loss for reuse that could never happen, biasing retention
decisions toward whichever owners happened to hold unreachable state.

Checkpoints now carry an `unreachable` flag, set where the policy is built by
comparing the request's own shortlist key at that frontier against the
checkpoint's key:

  reachable   -> an incoming key exists at the frontier and matches
  unreachable -> no incoming key at that frontier, or a different key

The portfolio value skips unreachable checkpoints, so they contribute no
recovery saving and no owner is credited for them. The capture-time policies,
which describe state the request is about to create rather than state it might
hit, are marked reachable.

Taken from upstream PR Neroued#300, by pkochubey, resolving upstream issue Neroued#178.

Verification: ninfer_resource_manager_test passes. Only the PR's
unreachable-checkpoint half is taken here; its separate publication-order
eviction tie-break and its broader victim-enumeration gate are not, because
this fork already answers upstream issue Neroued#251 with its own automatic
shared-prefix catalog reclaim.
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 22, 2026
…calls

Ported from the gzenz/ninfer fork by David Oelfke (gzenz), September 2026: commit b2267e0 adds the flag and recovers complete calls with malformed wrapper or suffix output, commit 0ce6e3f recovers a single truncated final call when closing tags are cut off at region end, commit 0f3c9f5 recovers the function name when the closing bracket is omitted before a parameter tag, commit 3870983 keeps a value cut by the output budget and requires at least one complete parameter for a truncated final call (with the operational Info record guarded on kept calls), and commit 44f2c9c keeps complete calls with undeclared tool names.

gzenz's implementation sits on a divergent canonical parser, so the recovery logic is ported onto this fork's multi-marker parser, preserving PR Neroued#300 marker recognition and PR Neroued#299 last-value-wins duplicate handling. Tolerant mode keeps a good call when a trailing suffix or a malformed second call follows it (truncated_tail diagnostic, logged at Info), keeps a single final call cut at the region end with its partial value, recovers a missing closing bracket after the function name by an identifier-run scan, and keeps undeclared tool names structured. A truncated tail that keeps no call is returned as text with the reason recorded. The strict parser is unchanged and remains the default. Tests cover the tolerant paths; README and docs/serving.md credit the source.
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 24, 2026
…n its own lineage

eb5396b (the unreachable-checkpoint half of upstream PR Neroued#300) marked every checkpoint the
incoming request could not reach as `unreachable`, and `ContextPortfolioValue::fold` skipped
those entirely: no demand value, no private transition loss, no explicit shared credit. So
every other conversation's prefix, and every shared prefix a request did not start with, was
worth nothing to that request's planner. Any path that could evict one did so for free. On the
GPU this is what let the rescue probe and the ladder wipe automatic shared prefixes after one
unrelated request, and it was a large part of why shared-saturation-reclaim and
shared-replacement failed.

Issue Neroued#178 asked for something narrower: a checkpoint is dead once the same lineage has moved
past it, i.e. the conversation's next prompt diverges from the checkpoint's prefix at its
frontier. `lineage_diverged` now marks a private checkpoint unreachable only when the incoming
request carries the same session key as the owner and its prompt has a different shortlist key
at that frontier. A shorter prompt that does not reach the frontier is not proof, and neither is
a keyless request. Shared prefixes are never marked; they serve many lineages. Demand from the
current request was already absent for unreachable keys, so nothing else is double-counted.

Applied at all three places policies are built: materialization pressure (private and shared),
and the projected portfolio in shared-capture selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo3VN3VZQy3pm5QVfbcjfu
Wallawalla47 pushed a commit to Wallawalla47/ninfer-custom that referenced this pull request Sep 24, 2026
…fix caching

The Local changes section now opens with Prefix caching, rewritten as a high-level summary of
everything the fork changes against upstream NInfer: the leased Device KV, demote-before-destroy,
the recency-ordered ladder with sparing (and its top-rung, keep-in-place, rescue and
defer-on-seal-failure behaviour), lineage-scoped checkpoint valuation, the single
`--host-cache-mib` budget, engine-automatic long anchors with geometric spacing and
least-coverage replacement, salvaged prefills, and shared-capture seal safety. The two items
implemented from other people's issues (Neroued#229 cost-scaled search budget, Neroued#251 shared-catalog
reclaim) are kept and credited as before. The replay measurement is kept and labelled as
predating the later fixes, alongside the engine-real scenario results.

The merged PR Neroued#300 unreachable-checkpoint entry notes that the fork later narrowed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jo3VN3VZQy3pm5QVfbcjfu
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.

1 participant