Skip to content

feat(kimi_k3): adopt the shared routing telemetry (#700) — stacked on #716 - #719

Merged
JustVugg merged 2 commits into
JustVugg:devfrom
terrizoaguimor:kimi-k3-telemetry
Jul 31, 2026
Merged

feat(kimi_k3): adopt the shared routing telemetry (#700) — stacked on #716#719
JustVugg merged 2 commits into
JustVugg:devfrom
terrizoaguimor:kimi-k3-telemetry

Conversation

@terrizoaguimor

Copy link
Copy Markdown
Contributor

Phase B of #700. Stacked on #716 — do not merge this first.

kimi_k3.c gains the expert history and the ROUTE_TRACE stream by calling into
route_trace.h, so it writes the same bytes as every other engine and PIN=auto works on it
for the first time.

Reviewing: this branch contains #716's commit as its base, because route_trace.h does
not exist on dev yet. Only the second commit (feat(kimi_k3)) is new here — 13 lines in
kimi_k3.c and one Makefile dependency. GitHub will show both until #716 lands; once it does,
this reduces to the single commit. Happy to rebase and re-point it at any time.

/cc @steve-m — this touches the router loop in the engine you contributed in #676, so you should
have eyes on it. One line inside moe_forward, placed after the top-p renormalisation so the
gates it records are the ones the layer actually applies. Nothing else in your forward path
changes.

The five call sites

rt_init("kimi_k3", m.c.n_layers, m.c.n_experts);          // dimensions + identity
for(int i=0;i<m.c.n_layers;i++) if(!m.L[i].sparse) rt_drop_row(i);
rt_drop_row(m.c.n_layers);                                 // K3 has no MTP row
rt_load(up);                                               // when COLI_USAGE is set
rt_route(li, t, idx, wsel, Kt);                            // per routing decision
rt_save(up, 0);                                            // on exit

That is the whole diff, on a Model and Cfg that share nothing with GLM's — which is the
engine-agnostic claim from #700 demonstrated rather than asserted.

Why the drops are not optional

They are the load admission rule, not housekeeping. K3 has dense layers
(l->sparse = (i >= c->first_dense)) and no MTP row, and every reader treats a NULL counter row
as "there is no expert here to attribute a count to". Skip them and a history record naming a
dense layer is silently accumulated, added to the reported total, and written back out on the
next save — a record no engine would ever emit.

This is the more interesting result of the exercise, and I would not have predicted it: the
row-shape problem is not a GLM quirk. It recurred in the second engine immediately, which is
also why dev's Vulkan expert-selection path (if(m->eusage[i])) keeps working unchanged.

Verified

Not yet run against real K3 weights: I do not currently hold the 1.5 TB checkout, and @JustVugg
indicated in #700 that re-fetching it to strengthen a compile-level claim was not a cost worth
absorbing. If either of you would rather have a runtime trace before merging, say so and I will
arrange it.

🤖 Generated with Claude Code

@JustVugg JustVugg added enhancement New feature or request model-support Supporto a nuovi modelli labels Jul 31, 2026
terrizoaguimor and others added 2 commits July 31, 2026 17:21
.coli_usage had two incompatible writers and two engines that could not
produce it at all: colibri.c wrote sparse text, inkling.c wrote a dense
IKU1 binary block, kimi_k3.c and olmoe.c wrote nothing, and both writers
defaulted to the same filename. A model directory shared between two
engines ended up holding a history the other one refuses.

route_trace.h now owns the format and the ROUTE_TRACE stream, with no
Model or Cfg dependency, so any engine can write and read the same bytes.
colibri.c and telemetry.h move onto it; the duplicated writer and
pin_load duplicated parser are gone.

The header records are encoded as triples with a negative layer so that
readers built before this change parse them, discard them, and continue
into the data:

    -1 <n_layers>       <n_experts>
    -2 <format_version> <engine_id>

That forces three rules, each of which is asserted in the tests against a
literal copy of the pre-existing reader loop rather than against
expectations of it:

  - every field must be numeric, or fscanf returns < 3 and the reader
    silently drops every record after that point;
  - the engine id must be the THIRD field, because old readers parse the
    second with %d and glm_moe_dsa hashes to 3815245270, above INT_MAX;
  - exactly three fields, never more. A fourth leaves one value
    unconsumed, which desynchronises an old reader and makes it fabricate
    an admitted record from that leftover plus the start of the next line.

A history whose identity does not match is refused, by name, on every path
the engine chooses for itself: the .coli_usage next to the weights,
PIN=auto, and the AUTOPIN seed. A path the user typed is honoured and says
so once, because pins decide which bytes are resident and never what the
model computes, so the worst case is self-inflicted and visible in the hit
rate. Format version and IKU1 geometry are parse correctness rather than
attribution and are never relaxed.

An empty history stays a zero-byte file, since PIN=auto tests file size to
decide whether to fall back to stats.txt.

rt_drop_row releases the counter row of a layer that does not route, which
is the load admission rule and not an optimisation: every reader treats a
NULL row as "no expert here to attribute a count to", the shape eusage had
before, and which the Vulkan expert-selection path also relies on.

Verified against the unmodified tree: byte-identical ROUTE_TRACE dumps,
history data, [STATS] line, [PIN] placement and dashboard EMAP, plus an
unmodified binary reading a new-format file and this build reading a
legacy one. Confirmed on GLM-5.2 (78x256) as well as the small fixture,
and the unit test passes cross-compiled with MinGW-w64 under wine, where
the CRT rename() shim in compat.h turns out to be load-bearing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase B of JustVugg#700. kimi_k3.c gains the expert history and the ROUTE_TRACE
stream by calling into route_trace.h, so it writes the same bytes as
every other engine and PIN=auto works on it for the first time.

Five call sites, on a Model and Cfg that share nothing with GLM's, which
is the engine-agnostic claim demonstrated rather than asserted:

  rt_init      dimensions and identity, after model_init settles them
  rt_drop_row  dense layers and the absent MTP row get no counter row
  rt_load      seed from an existing history when COLI_USAGE is set
  rt_route     one call per routing decision: traces and counts
  rt_save      persist on exit

The drops are not housekeeping, they are the load admission rule. K3 has
dense layers (l->sparse = i >= c->first_dense) and no MTP row, and every
reader treats a NULL counter row as "no expert here to attribute a count
to". Without them a history record naming a dense layer is silently
accumulated, added to the reported total, and written back out.

rt_route sits after the top-p renormalisation, so the gates it traces are
the ones the layer actually applies.

Adds no new diagnostics: the one warning in kimi_k3.c (g_k3_vk unused) is
present in the unmodified file at the same site, checked by building both
with identical flags.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@JustVugg
JustVugg merged commit 3e6fdff into JustVugg:dev Jul 31, 2026
10 checks passed
terrizoaguimor added a commit to terrizoaguimor/colibri that referenced this pull request Aug 1, 2026
Third engine onto the shared telemetry from JustVugg#700, after colibri.c (JustVugg#716) and
kimi_k3.c (JustVugg#719). The counters, the load and the save move to route_trace.h;
the bump sites are untouched, since m->eusage now aliases rt_counts_all().

The transition is the point: rt_load reads both the IKU1 block every previous
inkling wrote and the shared text format, so an existing .coli_usage keeps
working and is rewritten in the new form. The header checks the old pins_load
did inline -- magic, n_layers, n_experts against the live config -- all still
fire, now in the reader, which additionally refuses a history written by a
different engine. Narrower than before, never wider; and parse geometry does
not bend to a trusted path the way identity does.

Fixes a latent bug on the way: pins_load documents PIN_N=0 as "seeds the
ranking from the history but pins nothing", but the npin guard ran before the
memcpy, so the counters started at zero and the next usage_save replaced the
accumulated ranking with that run's counts alone.

Verified on the tiny fixture: rebuild clean, oracle token-exact (36/36
teacher-forced, 24/24 generated), make check 273 passed / 34 skipped, and the
built engine reading a planted legacy history pins the right 9 experts while
dropping the dense layer's counts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terrizoaguimor added a commit to terrizoaguimor/colibri that referenced this pull request Aug 1, 2026
Fourth and final engine for JustVugg#700, after colibri.c (JustVugg#716), kimi_k3.c (JustVugg#719) and
inkling.c (JustVugg#742). Every engine that routes experts now writes the same format.

m->freq moves from one flat [n_layers * n_experts] block to route_trace.h's row
per layer, so the three read sites -- the hot-pin ranking, the bump in moe() and
the LFRU eviction guard -- change shape but not meaning. last_access stays flat;
it is a separate array route_trace.h does not own. Every olmoe layer routes and
there is no MTP layer, so the only row dropped is the spare one rt_init leaves.

The history is opt-in behind COLI_USAGE, exactly as kimi_k3 does it: with the
variable unset nothing is loaded and nothing is written, so the default path is
unchanged. PPL=1 deliberately does not save, so a loss sweep cannot fold its own
tokens into the persisted ranking.

Verified against the real OLMoE-1B-7B-0125-Instruct converted with
tools/convert_olmoe_merged.py. Output is byte-identical to dev on the same run,
down to hit=1216 miss=832 -- the eviction path decides exactly as before, which
is the property the layout change could have broken. The COLI_USAGE round trip
writes the -1/-2 header with the olmoe identity, leaves no row for the dropped
layer, and accumulates 2048 -> 4096 across two runs. make check green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
terrizoaguimor added a commit to terrizoaguimor/colibri that referenced this pull request Aug 1, 2026
Fourth and final engine for JustVugg#700, after colibri.c (JustVugg#716), kimi_k3.c (JustVugg#719) and
inkling.c (JustVugg#742). Every engine that routes experts now writes the same format.

m->freq moves from one flat [n_layers * n_experts] block to route_trace.h's row
per layer, so the three read sites -- the hot-pin ranking, the bump in moe() and
the LFRU eviction guard -- change shape but not meaning. last_access stays flat;
it is a separate array route_trace.h does not own. Every olmoe layer routes and
there is no MTP layer, so the only row dropped is the spare one rt_init leaves.

The history is opt-in behind COLI_USAGE, exactly as kimi_k3 does it: with the
variable unset nothing is loaded and nothing is written, so the default path is
unchanged. PPL=1 deliberately does not save, so a loss sweep cannot fold its own
tokens into the persisted ranking.

Verified against the real OLMoE-1B-7B-0125-Instruct converted with
tools/convert_olmoe_merged.py. Output is byte-identical to dev on the same run,
down to hit=1216 miss=832 -- the eviction path decides exactly as before, which
is the property the layout change could have broken. The COLI_USAGE round trip
writes the -1/-2 header with the olmoe identity, leaves no row for the dropped
layer, and accumulates 2048 -> 4096 across two runs. make check green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request model-support Supporto a nuovi modelli

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants