Repository navigation
feat(laya): expert heads, prompted routing and a mixture adapter - #511
Merged
michalharakal merged 1 commit intoOct 21, 2026
Merged
michalharakal merged 1 commit into
michalharakal merged 1 commit into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-decisionsencoder, 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.layacould 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 incommonMain, every signature additive.LayaWeightPreparation.prepareHead(the head/scorer/type-embedding branch ofprepare, now shared) andLayaDecisionModel.buildHeadbind anexpert.safetensors(type_emb.*,head.*,scorer.*;act_head.*ignored) into aLayaDecisionHead; the checkpoint's own head isLayaDecisionModel.defaultHead. A head prepared on its own reproduces the base model bit for bit (test).LayaDecisionModel.logitsFrom/logitsBatchFrom/encodeBatch(sequences, padId)andLayaAgent.encodeFor/logitsFrom/decodeAllrun the encoder once per question and any head on the result; the existinghidden/logits/logitsBatch/logitsFordelegate to them, numerically unchanged.LayaExpertMeta(expert.json: name, domains, calibration temperatures),LayaExpertLoader.load(metaJson, weights, base, ctx, weightFormFor)→LayaExpertwith aLayaDecodercarrying the expert's temperatures over the base agent's budgets (LayaWeightForms.q8_0Forpacks the head's projections like the base's).LayaRouterSpec(question, options, experts, threshold)— the prompted router (kinds with descriptions, kind → expert, inclusive threshold,generalfallback 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 withcompare, 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.LayaMixtureDecisionAdapterputs a mixture behindDecisionAdapter(routed once per request, answered with the chosen head's calibration,onRoutedreports the head since the contract has no field for it).LayaDecisionAdapterkeeps its API; the packing and outcome code moved to an internalLayaAdapterSupportboth adapters use.LayaEncoderConfig.requireSupported()acceptsposition_embedding_type: "absolute"besidesans_pos— the transformers 5 export ofconvaiinnovations/laya-typed-decisionswritesabsolutewhile the positions are rotary either way. Theenglishsection of the Layasequences.jsonfixture (ModernBERT-large ids) now has a gated test.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,compileKotlinLinuxX64of the module.LAYA_TYPED_DECISIONS_PATH,LAYA_MOE_EXPERTS_PATH,LAYA_MULTILINGUAL_PATH):LayaMoeEvalTestreplays 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.jsonenglish 26/26 and multilingual 26/26; tokenizer corpus 40/40;predict.json55 values max 4.99e-5;LayaDecisionAdapterTestunchanged. 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.