tcmmichaelb139's picture
fixed out of bounds for layer
1c54318
import { useState, useEffect, useCallback } from "react";
import Dropdown from "./Dropdown";
import NumberInput from "./NumberInput";
import {
getModelLayerCounts,
initializeDefaultCookies,
} from "../utils/modelCookies";
const Recipe = ({
layerRecipe,
setLayerRecipe,
embeddingLambdas,
setEmbeddingLambdas,
linearLambdas,
setLinearLambdas,
numLayers,
selectedModel1,
selectedModel2,
}) => {
const [modelLayerCounts, setModelLayerCounts] = useState({
model1: 12,
model2: 12,
});
const [expandedBlock, setExpandedBlock] = useState(null);
useEffect(() => {
initializeDefaultCookies(selectedModel1, selectedModel2);
const counts = getModelLayerCounts(selectedModel1, selectedModel2);
setModelLayerCounts(counts);
}, [selectedModel1, selectedModel2]);
const adjustLayerRecipe = useCallback(() => {
const currentLength = layerRecipe.length;
if (currentLength === numLayers) {
return; // No change needed
}
let newRecipe = [...layerRecipe];
if (currentLength < numLayers) {
// Add new layers to the end
for (let i = currentLength; i < numLayers; i++) {
newRecipe.push([[1, 0, 1.0]]);
}
} else {
// Remove layers from the end
newRecipe = newRecipe.slice(0, numLayers);
}
setLayerRecipe(newRecipe);
}, [numLayers, layerRecipe, setLayerRecipe]);
const initializeLayerRecipe = useCallback(() => {
const recipe = [];
for (let i = 0; i < numLayers; i++) {
recipe.push([[1, 0, 1.0]]);
}
setLayerRecipe(recipe);
}, [numLayers, setLayerRecipe]);
useEffect(() => {
if (layerRecipe.length === 0) {
// Initialize if recipe is empty
initializeLayerRecipe();
} else if (layerRecipe.length !== numLayers) {
// Adjust existing recipe
adjustLayerRecipe();
}
}, [numLayers, layerRecipe.length, initializeLayerRecipe, adjustLayerRecipe]);
const addBlockToLayer = (layerIndex) => {
const newRecipe = [...layerRecipe];
// layer 1, model 0 or 1, weight 0.5
const newBlock = [1, Math.random() < 0.5 ? 0 : 1, 0.5];
newRecipe[layerIndex] = [...newRecipe[layerIndex], newBlock];
setLayerRecipe(newRecipe);
};
const removeBlockFromLayer = (layerIndex, blockIndex) => {
const newRecipe = [...layerRecipe];
if (newRecipe[layerIndex].length > 1) {
newRecipe[layerIndex] = newRecipe[layerIndex].filter(
(_, i) => i !== blockIndex
);
setLayerRecipe(newRecipe);
}
};
const updateBlock = (layerIndex, blockIndex, field, value) => {
const newRecipe = [...layerRecipe];
const block = [...newRecipe[layerIndex][blockIndex]];
if (field === "model") {
block[1] = value;
block[0] = 1;
} else if (field === "sourceLayer") {
// Ensure layer value is within valid range (1 to max layers for selected model)
const selectedModel = block[1];
const maxLayers =
selectedModel === 0 ? modelLayerCounts.model1 : modelLayerCounts.model2;
const maxLayerValue = maxLayers === "N/A" ? 1 : maxLayers;
block[0] = Math.max(1, Math.min(maxLayerValue, value));
} else if (field === "percentage") {
block[2] = value / 100;
}
newRecipe[layerIndex][blockIndex] = block;
setLayerRecipe(newRecipe);
};
const updateEmbeddingLambda = (index, value) => {
const newLambdas = [...embeddingLambdas];
newLambdas[index] = value / 100;
setEmbeddingLambdas(newLambdas);
};
const updateLinearLambda = (index, value) => {
const newLambdas = [...linearLambdas];
newLambdas[index] = value / 100;
setLinearLambdas(newLambdas);
};
const modelOptions = [
{ value: 0, label: "Model 1" },
{ value: 1, label: "Model 2" },
];
const getModelName = (modelValue) => {
return modelValue === 0 ? "Model 1" : "Model 2";
};
const generateRandomRecipe = () => {
const randomEmbedding1 = Math.random();
const randomEmbedding2 = 1 - randomEmbedding1;
setEmbeddingLambdas([randomEmbedding1, randomEmbedding2]);
const randomLinear1 = Math.random();
const randomLinear2 = 1 - randomLinear1;
setLinearLambdas([randomLinear1, randomLinear2]);
const newRecipe = [];
for (let i = 0; i < numLayers; i++) {
const numBlocks = Math.floor(Math.random() * 5) + 1;
const layer = [];
const weights = [];
for (let j = 0; j < numBlocks; j++) {
weights.push(Math.random());
}
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
const normalizedWeights = weights.map((w) => w / totalWeight);
for (let j = 0; j < numBlocks; j++) {
const randomModel = Math.floor(Math.random() * 2); // 0 or 1
const maxLayers =
randomModel === 0 ? modelLayerCounts.model1 : modelLayerCounts.model2;
const maxLayerValue = maxLayers === "N/A" ? 1 : maxLayers;
const randomLayer = Math.floor(Math.random() * maxLayerValue) + 1;
layer.push([randomLayer, randomModel, normalizedWeights[j]]);
}
newRecipe.push(layer);
}
setLayerRecipe(newRecipe);
};
const getBlockId = (layerIndex, blockIndex) => {
return `${layerIndex}-${blockIndex}`;
};
const toggleBlockExpanded = (layerIndex, blockIndex) => {
const blockId = getBlockId(layerIndex, blockIndex);
setExpandedBlock(expandedBlock === blockId ? null : blockId);
};
return (
<div className="p-6 border-2 border-primary-200 rounded-2xl bg-background shadow-xl fixed top-4 right-4 bottom-4 left-[27rem] overflow-y-auto ">
<div className="flex items-center space-x-2 mb-6">
<div className="w-8 h-8 bg-gradient-to-br from-accent-500 to-primary-500 rounded-lg flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-background"
>
<path d="M12 3v18m9-9H3" />
</svg>
</div>
<h2 className="text-xl font-bold text-foreground">Layer Recipe</h2>
<button
onClick={generateRandomRecipe}
className="ml-4 px-4 py-2 bg-gradient-to-r from-accent-500 to-primary-500 text-white font-medium rounded-lg hover:from-accent-600 hover:to-primary-600 transition-all duration-200 flex items-center space-x-2 text-sm"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" />
<path d="M21 3v5h-5" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" />
<path d="M3 21v-5h5" />
</svg>
<span>Random Recipe</span>
</button>
</div>
<div className="space-y-8">
<div className="grid grid-cols-2 gap-6">
<div className="space-y-4">
<h3 className="text-lg font-semibold text-foreground">
Embedding Layers
</h3>
<div className="space-y-3">
<NumberInput
label="Token Embedding (%)"
value={Math.round(embeddingLambdas[0] * 100)}
onChange={(value) => updateEmbeddingLambda(0, value)}
min={0}
max={100}
/>
<NumberInput
label="Positional Embedding (%)"
value={Math.round(embeddingLambdas[1] * 100)}
onChange={(value) => updateEmbeddingLambda(1, value)}
min={0}
max={100}
/>
</div>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold text-foreground">
Linear Layers
</h3>
<div className="space-y-3">
<NumberInput
label="LM Head (%)"
value={Math.round(linearLambdas[0] * 100)}
onChange={(value) => updateLinearLambda(0, value)}
min={0}
max={100}
/>
<NumberInput
label="LM Final (%)"
value={Math.round(linearLambdas[1] * 100)}
onChange={(value) => updateLinearLambda(1, value)}
min={0}
max={100}
/>
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-foreground">
Transformer Layers
</h3>
<div className="text-xs text-foreground space-x-4">
<span>
Model 1:{" "}
{modelLayerCounts.model1 === "N/A"
? "N/A layers"
: `${modelLayerCounts.model1} layers`}
</span>
<span>
Model 2:{" "}
{modelLayerCounts.model2 === "N/A"
? "N/A layers"
: `${modelLayerCounts.model2} layers`}
</span>
</div>
</div>
<div className="flex-1 overflow-y-auto space-y-3">
{layerRecipe.map((layer, layerIndex) => (
<div
key={layerIndex}
className="relative border-2 border-secondary-200 rounded-xl p-4 bg-background hover:border-primary-300 transition-all duration-200"
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center space-x-3">
<h4 className="font-medium text-foreground">
Layer {layerIndex + 1}
</h4>
<div className="text-xs text-foreground bg-secondary-100 px-2 py-1 rounded-md">
Total:{" "}
{Math.round(
layer.reduce((sum, block) => sum + block[2], 0) * 100
)}
%
</div>
</div>
<button
onClick={() => addBlockToLayer(layerIndex)}
className="w-6 h-6 bg-primary-500 text-background rounded-md hover:bg-primary-600 transition-colors duration-200 flex items-center justify-center"
title="Add block"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 5v14m7-7H5" />
</svg>
</button>
</div>
<div className="flex flex-wrap gap-2">
{layer.map((block, blockIndex) => {
const blockId = getBlockId(layerIndex, blockIndex);
const isExpanded = expandedBlock === blockId;
const modelName = getModelName(block[1]);
return (
<div key={blockIndex} className="relative">
<button
onClick={() =>
toggleBlockExpanded(layerIndex, blockIndex)
}
onContextMenu={(e) => {
e.preventDefault();
removeBlockFromLayer(layerIndex, blockIndex);
}}
className="px-3 py-2 bg-background border-2 border-secondary-300 rounded-lg hover:border-primary-300 hover:bg-primary-50 transition-all duration-200 text-sm font-medium text-foreground flex items-center space-x-2"
title="Left click to edit, Right click to delete"
>
<span className="text-primary-600">{modelName}</span>
<span className="text-foreground">L{block[0]}</span>
<span className="text-accent-600">
{Math.round(block[2] * 100)}%
</span>
<svg
className={`w-4 h-4 text-foreground transition-transform duration-200 ${
isExpanded ? "rotate-180" : ""
}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</button>
{isExpanded && (
<div
className="absolute top-full left-0 mt-1 p-3 bg-background border-2 border-primary-200 rounded-lg shadow-lg z-10 min-w-64"
data-block-id={blockId}
>
<div className="space-y-3">
<div>
<label className="block text-xs font-medium text-foreground mb-1">
Model
</label>
<Dropdown
selectedValue={modelOptions.find(
(opt) => opt.value === block[1]
)}
onSelect={(option) => {
updateBlock(
layerIndex,
blockIndex,
"model",
option.value
);
}}
options={modelOptions}
placeholder="Select model..."
className="w-full"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-medium text-foreground mb-1">
Layer
</label>
<NumberInput
value={block[0]}
onChange={(value) =>
updateBlock(
layerIndex,
blockIndex,
"sourceLayer",
value
)
}
min={1}
max={
block[1] === 0
? modelLayerCounts.model1 === "N/A"
? 1
: modelLayerCounts.model1
: modelLayerCounts.model2 === "N/A"
? 1
: modelLayerCounts.model2
}
compact={true}
/>
</div>
<div>
<label className="block text-xs font-medium text-foreground mb-1">
Weight (%)
</label>
<NumberInput
value={Math.round(block[2] * 100)}
onChange={(value) =>
updateBlock(
layerIndex,
blockIndex,
"percentage",
value
)
}
min={0}
max={100}
compact={true}
/>
</div>
</div>
{layer.length > 1 && (
<button
onClick={() => {
removeBlockFromLayer(
layerIndex,
blockIndex
);
setExpandedBlock(null);
}}
className="w-full px-3 py-2 bg-red-800 text-background rounded-lg hover:bg-red-700 transition-colors duration-200 text-sm font-medium"
>
Remove Block
</button>
)}
</div>
</div>
)}
</div>
);
})}
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default Recipe;