Skip to content

feat(laya): expert heads, prompted routing and a mixture adapter - #511

Merged
michalharakal merged 1 commit into
SKaiNET-developers:developfrom
michalharakal:feature/laya-expert-heads
Oct 21, 2026
Merged

michalharakal merged 1 commit into
SKaiNET-developers:developfrom
michalharakal:feature/laya-expert-heads

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Summary

A Laya checkpoint is an encoder plus a small decision head, and the head is what gets fine-tuned for a domain: layaMOE (Apache-2.0) freezes the laya-typed-decisions encoder, copies the head, trains one per domain group (safety, customer_ops) and lets the base head route each request by answering "What kind of text is this?". sk.ainet.models.laya could load one head per checkpoint only. This PR lets any number of heads share one encoder, loads a head on its own, routes, and compares — all in commonMain, every signature additive.

  • Head-only checkpoints: LayaWeightPreparation.prepareHead (the head/scorer/type-embedding branch of prepare, now shared) and LayaDecisionModel.buildHead bind an expert.safetensors (type_emb.*, head.*, scorer.*; act_head.* ignored) into a LayaDecisionHead; the checkpoint's own head is LayaDecisionModel.defaultHead. A head prepared on its own reproduces the base model bit for bit (test).
  • Heads on encoded states: LayaDecisionModel.logitsFrom / logitsBatchFrom / encodeBatch(sequences, padId) and LayaAgent.encodeFor / logitsFrom / decodeAll run the encoder once per question and any head on the result; the existing hidden / logits / logitsBatch / logitsFor delegate to them, numerically unchanged.
  • Experts: LayaExpertMeta (expert.json: name, domains, calibration temperatures), LayaExpertLoader.load(metaJson, weights, base, ctx, weightFormFor) → LayaExpert with a LayaDecoder carrying the expert's temperatures over the base agent's budgets (LayaWeightForms.q8_0For packs the head's projections like the base's).
  • Routing: LayaRouterSpec(question, options, experts, threshold) — the prompted router (kinds with descriptions, kind → expert, inclusive threshold, general fallback when the kind is unmapped, the expert is not loaded or the top probability is too low); LayaMixtureAgent.decide(state, questions, expert, compare, route) routes (one extra encoder pass) and answers with the chosen head, reports a forced head as overridden, adds the base head's answers on the same encoder output with compare, and carries encoder / head milliseconds and the token count. Sequence-level routing, one head per request — the docs say it is not a sparse mixture of experts.
  • Typed contract: LayaMixtureDecisionAdapter puts a mixture behind DecisionAdapter (routed once per request, answered with the chosen head's calibration, onRouted reports the head since the contract has no field for it). LayaDecisionAdapter keeps its API; the packing and outcome code moved to an internal LayaAdapterSupport both adapters use.
  • ModernBERT-large loads: LayaEncoderConfig.requireSupported() accepts position_embedding_type: "absolute" beside sans_pos — the transformers 5 export of convaiinnovations/laya-typed-decisions writes absolute while the positions are rotary either way. The english section of the Laya sequences.json fixture (ModernBERT-large ids) now has a gated test.
  • Docs: Expert heads and routing in typed-decisions.adoc, README row, CHANGELOG.

Verification

  • jvmTest (no checkpoint): LayaMixtureSpecTest (routing rules, inclusive threshold, override, expert metadata, per-expert temperatures), LayaExpertSyntheticTest (head-only preparation reproduces the base model exactly on the single and the batched path; rejects encoder tensors, missing parts, wrong width; mixture routes / overrides / compares; the mixture adapter answers like the plain adapter when the routed head is the base head's twin), plus the existing synthetic and builder suites. compileKotlinWasmJs, compileKotlinJs, compileKotlinLinuxX64 of the module.
  • Gated on the checkpoints (LAYA_TYPED_DECISIONS_PATH, LAYA_MOE_EXPERTS_PATH, LAYA_MULTILINGUAL_PATH): LayaMoeEvalTest replays two cases per domain (18 cases, 52 answers, compare on) — routing identical to the published run on 18/18, 612 values (router probabilities, expert and general answers, scores, noul, confidences) within 5e-5 of the PyTorch FP32 numbers (their 4-decimal rounding), base + two experts loaded in 13.5 s; sequences.json english 26/26 and multilingual 26/26; tokenizer corpus 40/40; predict.json 55 values max 4.99e-5; LayaDecisionAdapterTest unchanged. Cost on a 12-core laptop, FP32 eager, engine 0.61.0: about 39 s per case (four to five ModernBERT-large encoder passes), the heads 2–5 s of it.
  • apiDump (additive), jvmApiCheck.

A Laya decision head can be loaded on its own (expert.safetensors +
expert.json) and run on the base model's encoder output: LayaDecisionHead,
LayaWeightPreparation.prepareHead / LayaDecisionModel.buildHead,
logitsFrom / logitsBatchFrom and LayaAgent.encodeFor / logitsFrom so several
heads answer the same sequences for one encoder pass each. LayaExpertLoader
and LayaExpert carry the expert's calibration over the base budgets;
LayaRouterSpec is the prompted router (kinds with descriptions, kind ->
expert, inclusive threshold, general fallback); LayaMixtureAgent routes,
overrides and compares; LayaMixtureDecisionAdapter puts a mixture behind
the typed decision contract. LayaEncoderConfig accepts
position_embedding_type "absolute" (transformers 5 exports, ModernBERT-large
laya-typed-decisions). Gated tests replay the published layaMOE evaluation
(routing identical, values within 5e-5) and the english sequence fixtures;
synthetic tests prove the head-only preparation reproduces the base model
bit for bit. All signatures additive; docs, README, CHANGELOG, api dump.
@michalharakal
michalharakal merged commit f063c95 into SKaiNET-developers:develop Oct 21, 2026
3 checks passed
@michalharakal
michalharakal deleted the feature/laya-expert-heads branch October 21, 2026 17:44
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