python3 -m serve refuses to start on any container that is not K3. Reported from a
Kimi-Linear container on a 16 GB MacBook, where waste run and waste chat both work:
WASTE 0.6.6 (container v0, backend NEON, crc32 armv8, arm64)
model kimi_linear — kimi-linear, 27 layers, 256 experts, ctx 4096
<|open|> is not a single token in this container (got 5): its specials.json
does not carry K3's XTML markers: unsupported
What is happening
ServerCore.__init__ resolves the four XTML control tokens up front
(serve/server.py:85 → serve/engine.py:566), and raises WASTE_E_UNSUPPORTED
unless each of <|open|>, <|sep|>, <|close|>, <|end_of_msg|> encodes to
exactly one token. Kimi-Linear's tokenizer has none of them — measured on a
converted container:
$ ./waste tokenize ~/models/kimi-linear.waste '<|open|>'
27 91 4454 91 29
$ ./waste tokenize ~/models/kimi-linear.waste '<|im_user|>'
163587
The refusal is correct and should stay: absent markers encode as ordinary
text, so the model would read its own turn structure as prose and answer
plausibly and wrongly. The bug is not that it refuses; it is that XTML is the
only format serve/ can speak, so a container that has a perfectly good chat
format of its own has no HTTP path at all.
Two consequences worth separating:
- Kimi-Linear cannot be served.
serve/xtml.py is a port of K3's
encoding_k3.py, and serve/regions.py reads replies back by matching XTML
token ids. Neither has a second format. chat.json is not read by serve/
at all, so there is no way to configure around it.
/v1/completions is unreachable too, which is the smaller and more
annoying half. It is raw continuation and needs no chat template, but the
failure is in the constructor, so the whole server is down before any
endpoint is dispatched.
What Kimi-Linear does carry
From its specials.json — enough for a conversation and for tool calls, but
there is no think channel, so reasoning_content has no equivalent and thinking
would simply be off for it:
| token |
id |
<|im_system|> |
163594 |
<|im_user|> |
163587 |
<|im_assistant|> |
163588 |
<|im_middle|> |
163601 |
<|im_end|> |
163586 |
<|tool_calls_section_begin|> |
163595 |
<|tool_call_begin|> |
163597 |
<|tool_call_argument_begin|> |
163598 |
<|tool_call_end|> |
163599 |
<|tool_calls_section_end|> |
163596 |
Shape of a fix
Not a small change, and worth doing in two steps that are independently useful:
- Let the server start. Move marker resolution out of
__init__ to the
point of use, so a container without XTML serves /v1/completions and returns
a clear 400 on /v1/chat/completions instead of failing to boot.
- A second renderer and a second reply parser.
regions.py decides
structure from token ids rather than text, which is the right design and also
what makes this more than a template swap: it needs a marker vocabulary per
format, not a hardcoded four. Kimi-Linear's tool-call markers mean tool_calls
is reachable; reasoning_content is not.
Whether the renderer is driven by the container's chat.json or by a second
Python module in serve/ is the open design question. chat.json is four
prefix/suffix strings and cannot express tool definitions, so it covers plain
chat only — which may be the right first cut.
Workaround today
waste run and waste chat, which read chat.json and are unaffected. A
Kimi-Linear container has no chat.json unless the converter put one there, and
without it the CLI says so and falls back to raw continuation — which is why the
same report also had an instruct model producing odd answers. Write this into
the container directory, next to manifest.json:
{
"system": ["<|im_system|>system<|im_middle|>", "<|im_end|>"],
"user": ["<|im_user|>user<|im_middle|>", "<|im_end|>"],
"assistant": ["<|im_assistant|>assistant<|im_middle|>", "<|im_end|>"],
"open": "<|im_assistant|>assistant<|im_middle|>"
}
Do not copy examples/chat.json for this: it is generic ChatML, and
<|im_start|> is not in Kimi's vocabulary — it encodes as six ordinary tokens,
which is the same silent failure described above. The converter now installs the
right one per architecture and refuses to install a template whose markup the
release does not carry.
python3 -m serverefuses to start on any container that is not K3. Reported from aKimi-Linear container on a 16 GB MacBook, where
waste runandwaste chatboth work:What is happening
ServerCore.__init__resolves the four XTML control tokens up front(
serve/server.py:85→serve/engine.py:566), and raisesWASTE_E_UNSUPPORTEDunless each of
<|open|>,<|sep|>,<|close|>,<|end_of_msg|>encodes toexactly one token. Kimi-Linear's tokenizer has none of them — measured on a
converted container:
The refusal is correct and should stay: absent markers encode as ordinary
text, so the model would read its own turn structure as prose and answer
plausibly and wrongly. The bug is not that it refuses; it is that XTML is the
only format
serve/can speak, so a container that has a perfectly good chatformat of its own has no HTTP path at all.
Two consequences worth separating:
serve/xtml.pyis a port of K3'sencoding_k3.py, andserve/regions.pyreads replies back by matching XTMLtoken ids. Neither has a second format.
chat.jsonis not read byserve/at all, so there is no way to configure around it.
/v1/completionsis unreachable too, which is the smaller and moreannoying half. It is raw continuation and needs no chat template, but the
failure is in the constructor, so the whole server is down before any
endpoint is dispatched.
What Kimi-Linear does carry
From its
specials.json— enough for a conversation and for tool calls, butthere is no think channel, so
reasoning_contenthas no equivalent and thinkingwould simply be off for it:
<|im_system|><|im_user|><|im_assistant|><|im_middle|><|im_end|><|tool_calls_section_begin|><|tool_call_begin|><|tool_call_argument_begin|><|tool_call_end|><|tool_calls_section_end|>Shape of a fix
Not a small change, and worth doing in two steps that are independently useful:
__init__to thepoint of use, so a container without XTML serves
/v1/completionsand returnsa clear 400 on
/v1/chat/completionsinstead of failing to boot.regions.pydecidesstructure from token ids rather than text, which is the right design and also
what makes this more than a template swap: it needs a marker vocabulary per
format, not a hardcoded four. Kimi-Linear's tool-call markers mean
tool_callsis reachable;
reasoning_contentis not.Whether the renderer is driven by the container's
chat.jsonor by a secondPython module in
serve/is the open design question.chat.jsonis fourprefix/suffix strings and cannot express tool definitions, so it covers plain
chat only — which may be the right first cut.
Workaround today
waste runandwaste chat, which readchat.jsonand are unaffected. AKimi-Linear container has no
chat.jsonunless the converter put one there, andwithout it the CLI says so and falls back to raw continuation — which is why the
same report also had an instruct model producing odd answers. Write this into
the container directory, next to
manifest.json:{ "system": ["<|im_system|>system<|im_middle|>", "<|im_end|>"], "user": ["<|im_user|>user<|im_middle|>", "<|im_end|>"], "assistant": ["<|im_assistant|>assistant<|im_middle|>", "<|im_end|>"], "open": "<|im_assistant|>assistant<|im_middle|>" }Do not copy
examples/chat.jsonfor this: it is generic ChatML, and<|im_start|>is not in Kimi's vocabulary — it encodes as six ordinary tokens,which is the same silent failure described above. The converter now installs the
right one per architecture and refuses to install a template whose markup the
release does not carry.