ROCm / ROCm/FastFlowLM

No valid checkpoint to restore + Max length reached on multi-turn tool calls (Qwen3.6-MoE, NPU)

Open Beginner friendly
#744 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
1.9k
Forks
152
Avg merge
4h 14m
Merged PRs (30d)
11

Description

Summary

On multi-turn tool-call conversations, flm serve fails with No valid checkpoint to restore followed by Max length reached, stopping prefilling.... The model never produces a final answer — the second turn (after the tool result is returned) is rejected.

Environment
FLM version 1.0.5
Model qwen3.6-moe:35b-a3b (NPU2)
NPU firmware 1.1.2.64
Platform Linux, AMD Ryzen AI 9 HX 470 (Strix Halo)
Client OpenCode 1.18.31 (OpenAI-compatible API)
Endpoint POST /v1/chat/completions
Reproduction
  1. Start flm serve qwen3.6-moe:35b-a3b --ctx-len 262144 --pmode performance.
  2. Send a request with tools defined. The model correctly emits a <tool_call> and finish_reason = tool_calls.
  3. Send a follow-up request with the same conversation plus the assistant (tool_calls) and tool (result) messages.
  4. FLM fails — see log below.
Log
[FLM]  Use cached prompt!
[FLM]  Matched 2 out of 4 messages (2 new to prefill).
[FLM]  Start prefill...
[FLM]  Total images: 0
[FLM]  No valid checkpoint to restore
[WARNING]  Max length reached, stopping prefilling...
[FLM]  Creating checkpoint at context length 0
[🔵 ]  NPU Lock Released!

The server returns:

{"error":{"message":"Max length reached!","type":"model_error","code":400}}
Root cause

Qwen3_6_MOE::insert() calls qwen3_6_moe_npu::restore() when meta_info.restore_allowed == true. On the second turn, restore() returns a negative value (no valid checkpoint), but the method then unconditionally assigns:

this->total_tokens = restore_idx;   // becomes -1
this->token_history = checkpoint_his;

checkpoint_his still holds the previous turn's token history (~16k tokens). total_tokens is now desynchronized from the actual KV-cache size. _shared_insert() then checks:

if (this->total_tokens + tokens.size() >= this->MAX_L) {
    header_print("WARNING", "Max length reached, stopping prefilling...");
    return false;
}

Because total_tokens was never reset on restore failure, the check fires even though the actual context is well below MAX_L (32 768). clear_context() is never called, so the KV cache is left in an inconsistent state.

Proposed fix

In common/AutoModel/modeling_qwen3_6_moe.cpp, inside Qwen3_6_MOE::insert():

1. Handle restore() failure explicitly — reset state instead of assigning a negative restore_idx:

     if (meta_info.restore_allowed) {
         restore_idx = qwen3_6_moe_engine->restore();
-        this->total_tokens = restore_idx;
-        this->token_history = checkpoint_his;
+        if (restore_idx < 0) {
+            this->lm_engine->clear_context();
+            this->total_tokens = 0;
+            this->token_history.clear();
+            this->checkpoint_his.clear();
+        } else {
+            this->total_tokens = restore_idx;
+            this->token_history = checkpoint_his;
+        }
     }

2. Re-sync total_tokens with token_history after _shared_insert() — guarantees the counter reflects the real KV-cache size regardless of what happened in restore() / clear_context() / _shared_insert():

     bool success = has_images
         ? this->_shared_insert(meta_info, tokens, is_cancelled, &image_payload, last_image_token_index)
         : this->_shared_insert(meta_info, tokens, is_cancelled, nullptr);
 
+    if (this->total_tokens != (int)this->token_history.size()) {
+        this->total_tokens = this->token_history.size();
+    }
+
     checkpoint_his = token_history;
     int checkpoint_idx = qwen3_6_moe_engine->checkpoint();
     return success;
 }
Verification

After applying the patch and rebuilding FLM, the same multi-turn tool call now completes successfully. Full log:

[FLM]  Use cached prompt!
[FLM]  Matched 2 out of 4 messages (2 new to prefill).
[FLM]  Start prefill...
[FLM]  Total images: 0
[FLM]  No valid checkpoint to restore
[FLM]  Prefill chunk 1/5 with 4096 tokens
[FLM]  Prefill chunk 2/5 with 4096 tokens
[FLM]  Prefill chunk 3/5 with 4096 tokens
[FLM]  Prefill chunk 4/5 with 4096 tokens
[FLM]  Prefill chunk 5/5 with 303 tokens
[FLM]  Creating checkpoint at context length 16687
[FLM]  Start generating...
[FLM]  Model RAW Output:
Вот список проиндексированных проектов:
... (correct answer)

The model now returns the expected final answer instead of a 400 error.

Impact

Tool-calling agents (OpenCode, Claude Code, Cline, and any OpenAI-compatible client that performs multi-step tool calls) are unusable with flm serve on qwen3.6-moe without this fix. The bug affects all multi-turn tool interactions, not just a specific client.


Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in common/AutoModel/modeling_qwen3_6_moe.cpp at Qwen3_6_MOE::insert(), then trace restore(), _shared_insert(), clear_context(), and the token-history state. Rebuild FLM and repeat the documented multi-turn tool-call request; done means the second turn completes with a final answer instead of the max-length 400 error.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, linux
Domain
ai-infra-agents
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.