in packages/gguf/scripts/generate-llm.ts [126:259]
async function main() {
const cppSources = await Promise.all(
SOURCE_CPP_URLS.map(async (url) => {
const res = await fetch(url);
return await res.text();
})
);
const cppSource = cppSources.join("\n");
/////////////////////////////////////
// extract list of all architectures
const archList: Arch[] = [];
const RE_ARCH_NAME = /LLM_ARCH_[A-Z0-9_]+/;
const matchedArchList = cppSource.match(/LLM_ARCH_NAMES = (?<names>[^;]+)/)?.groups?.names.split("\n");
if (!matchedArchList?.length) {
throw new Error("LLM_ARCH_NAMES is empty");
}
for (const line of matchedArchList) {
const matched = line.match(/(?<cppConst>LLM_ARCH_[A-Z0-9_]+),\s+"(?<name>.+?)"/);
if (matched?.groups && !matched.groups.name.match(/unknown/)) {
archList.push({
cppConst: matched.groups.cppConst,
name: matched.groups.name,
tsName: snakeToPascal(matched.groups.cppConst.replace("LLM_", "")),
tensorNames: [],
hparams: [],
});
}
}
/////////////////////////////////////
// extract map constant name to kv name
// for example: LLM_KV_ATTENTION_LAYERNORM_RMS_EPS ==> "%s.attention.layer_norm_rms_epsilon"
const constToKVName: { [cppConst: string]: string } = {};
const matchedKVList = cppSource.match(/LLM_KV_NAMES = (?<names>[^;]+)/)?.groups?.names.split("\n");
if (!matchedKVList?.length) {
throw new Error("LLM_KV_NAMES is empty");
}
for (const line of matchedKVList) {
const matched = line.match(/(?<cppConst>LLM_KV_[A-Z0-9_]+)[,\s]+"(?<name>.+?)"/);
if (matched?.groups) {
constToKVName[matched.groups.cppConst] = matched.groups.name;
}
}
console.log("constToKVName", constToKVName);
/////////////////////////////////////
// extract list of tensor names based on architecture
// TODO: unused for now
const matchedTensorList = cppSource.match(/LLM_TENSOR_NAMES = (?<names>[^;]+)/)?.groups?.names.split("\n");
if (!matchedTensorList?.length) {
throw new Error("LLM_TENSOR_NAMES is empty");
}
let currCppConst = "";
for (const line of matchedTensorList) {
// check if current line has LLM_ARCH_*
const cppConst = line.match(RE_ARCH_NAME)?.[0];
if (cppConst) {
currCppConst = cppConst;
continue;
}
// check if current line has LLM_TENSOR_*
const tensorMatched = line.match(/LLM_TENSOR_[A-Z0-9_]+[,\s]+"(?<name>.+?)"/);
if (tensorMatched?.groups) {
const arch = archList.find((a) => a.cppConst === currCppConst);
if (arch) arch.tensorNames.push(tensorMatched.groups.name);
}
}
/////////////////////////////////////
// extract list of hyper params based on architecture
let insideLoadHParamsFn = false;
currCppConst = "";
for (const line of cppSource.split("\n")) {
// check if current line is function llama_model::load_hparams()
if (line.startsWith("void llama_model::load_hparams")) {
insideLoadHParamsFn = true;
}
if (!insideLoadHParamsFn) {
continue;
}
// check if current line has LLM_ARCH_*
const RE_CASE = new RegExp(`case (${RE_ARCH_NAME.source})`);
const cppConst = line.match(RE_CASE)?.[1];
if (cppConst) {
currCppConst = cppConst;
continue;
}
// check if current line has get_key(...)
const keyConst = line.match(/LLM_KV_[A-Z0-9_]+/)?.[0];
if (keyConst) {
const arch = archList.find((a) => a.cppConst === currCppConst);
if (arch) {
arch.hparams.push(keyConst);
}
}
// check if current line is end-of-function
if (line === "}") {
break;
}
}
/////////////////////////////////////
// write result to file
const content = [
DEST_COMMON_SOURCE,
"export const LLM_ARCHITECTURES = [",
...archList.map((a) => `\t${JSON.stringify(a.name)},`),
"] as const;",
"type LLMArchitecture = (typeof LLM_ARCHITECTURES)[number];",
...archList.map((a) => {
let code = `export type ${a.tsName} = TransformerLLMBase<${JSON.stringify(a.name)}>`;
if (a.hparams.length) {
code += [
" & {",
...a.hparams.map((k) => {
if (!KV_TYPE[k]) {
throw new Error(`Cannot find type definition of ${k}`);
}
return `\t${JSON.stringify(constToKVName[k].replace("%s", a.name))}: ${KV_TYPE[k]},`;
}),
"};",
].join("\n");
} else {
code += ";";
}
return code;
}),
"",
`export type TransformerLLM = ${archList.map((a) => a.tsName).join(" | ")};`,
].join("\n");
writeFileSync(DEST_FILE_PATH, content);
}