Created
October 19, 2025 09:23
-
-
Save NTT123/2bcdca2fd1f418790ad856385dd3e19c to your computer and use it in GitHub Desktop.
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
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SwiGLU 2D Activation</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | |
| background: linear-gradient(135deg, #4a4a4a 0%, #2c2c2c 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 4px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); | |
| padding: 20px; | |
| max-width: 1200px; | |
| width: 100%; | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #333; | |
| margin-bottom: 5px; | |
| font-size: 24px; | |
| } | |
| .formula { | |
| text-align: center; | |
| color: #666; | |
| margin-bottom: 15px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 14px; | |
| } | |
| .credit { | |
| text-align: center; | |
| color: #999; | |
| margin-top: 10px; | |
| font-size: 11px; | |
| } | |
| .credit a { | |
| color: #667eea; | |
| text-decoration: none; | |
| } | |
| .credit a:hover { | |
| text-decoration: underline; | |
| } | |
| .canvas-container { | |
| display: flex; | |
| justify-content: center; | |
| margin-bottom: 15px; | |
| } | |
| canvas { | |
| border: 2px solid #ddd; | |
| border-radius: 4px; | |
| cursor: grab; | |
| background: #f9f9f9; | |
| max-width: 100%; | |
| height: auto; | |
| box-shadow: 0 2px 6px rgba(0,0,0,0.15); | |
| } | |
| canvas:active { | |
| cursor: grabbing; | |
| } | |
| .controls { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .control-group { | |
| background: #f5f5f5; | |
| padding: 10px; | |
| border-radius: 4px; | |
| } | |
| .control-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| color: #555; | |
| font-weight: 600; | |
| font-size: 14px; | |
| } | |
| input[type="range"] { | |
| width: 100%; | |
| margin-bottom: 5px; | |
| } | |
| .value-display { | |
| color: #667eea; | |
| font-weight: bold; | |
| font-size: 14px; | |
| } | |
| .info { | |
| display: flex; | |
| justify-content: space-around; | |
| flex-wrap: wrap; | |
| gap: 10px; | |
| padding: 10px; | |
| background: #f5f5f5; | |
| border-radius: 4px; | |
| } | |
| .info-item { | |
| text-align: center; | |
| } | |
| .info-label { | |
| font-size: 12px; | |
| color: #666; | |
| margin-bottom: 5px; | |
| } | |
| .info-value { | |
| font-size: 18px; | |
| font-weight: bold; | |
| color: #667eea; | |
| } | |
| button { | |
| padding: 10px 20px; | |
| background: #666; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 600; | |
| transition: background 0.3s; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
| } | |
| button:hover { | |
| background: #555; | |
| } | |
| button:active { | |
| transform: scale(0.98); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>SwiGLU 2D Activation</h1> | |
| <div class="formula">z = sigmoid(x) × x × y</div> | |
| <div class="canvas-container"> | |
| <canvas id="canvas" width="800" height="500"></canvas> | |
| </div> | |
| <div class="controls"> | |
| <div class="control-group"> | |
| <label> | |
| Resolution: <span class="value-display" id="resolutionValue">30</span> | |
| </label> | |
| <input type="range" id="resolution" min="10" max="50" value="30" step="5"> | |
| </div> | |
| <div class="control-group"> | |
| <label> | |
| X Range: <span class="value-display" id="xRangeValue">±5</span> | |
| </label> | |
| <input type="range" id="xRange" min="2" max="10" value="5" step="0.5"> | |
| </div> | |
| <div class="control-group"> | |
| <label> | |
| Y Range: <span class="value-display" id="yRangeValue">±5</span> | |
| </label> | |
| <input type="range" id="yRange" min="2" max="10" value="5" step="0.5"> | |
| </div> | |
| <div class="control-group"> | |
| <label> | |
| <button id="resetView">Reset View</button> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="info"> | |
| <div class="info-item"> | |
| <div class="info-label">Rotation X</div> | |
| <div class="info-value" id="rotX">-45°</div> | |
| </div> | |
| <div class="info-item"> | |
| <div class="info-label">Rotation Y</div> | |
| <div class="info-value" id="rotY">30°</div> | |
| </div> | |
| <div class="info-item"> | |
| <div class="info-label">Drag to Rotate</div> | |
| <div class="info-value">🖱️</div> | |
| </div> | |
| </div> | |
| <div class="credit"> | |
| Implemented with <a href="https://claude.ai/claude-code" target="_blank">Claude Code</a> | |
| </div> | |
| </div> | |
| <script> | |
| const canvas = document.getElementById('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Configuration | |
| let config = { | |
| resolution: 30, | |
| xRange: 5, | |
| yRange: 5, | |
| rotationX: -45 * Math.PI / 180, // -45 degrees | |
| rotationY: 30 * Math.PI / 180, // 30 degrees | |
| scale: 40, | |
| offsetX: canvas.width / 2, | |
| offsetY: canvas.height / 2 | |
| }; | |
| // Mouse interaction | |
| let isDragging = false; | |
| let lastMouseX = 0; | |
| let lastMouseY = 0; | |
| // Sigmoid function | |
| function sigmoid(x) { | |
| return 1 / (1 + Math.exp(-x)); | |
| } | |
| // SwiGLU activation function | |
| function swiglu(x, y) { | |
| return sigmoid(x) * x * y; | |
| } | |
| // 3D to 2D projection | |
| function project3D(x, y, z) { | |
| // Apply rotation around X axis | |
| let cosX = Math.cos(config.rotationX); | |
| let sinX = Math.sin(config.rotationX); | |
| let y1 = y * cosX - z * sinX; | |
| let z1 = y * sinX + z * cosX; | |
| // Apply rotation around Y axis | |
| let cosY = Math.cos(config.rotationY); | |
| let sinY = Math.sin(config.rotationY); | |
| let x2 = x * cosY + z1 * sinY; | |
| let z2 = -x * sinY + z1 * cosY; | |
| let y2 = y1; | |
| // Simple orthographic projection | |
| let screenX = x2 * config.scale + config.offsetX; | |
| let screenY = -y2 * config.scale + config.offsetY; | |
| return { x: screenX, y: screenY, z: z2 }; | |
| } | |
| // Color based on z value - Improved viridis-like color scheme | |
| function getColor(z, minZ, maxZ) { | |
| // Normalize z to 0-1 range | |
| let normalized = (z - minZ) / (maxZ - minZ); | |
| // Create a smooth gradient: purple → blue → cyan → green → yellow | |
| let r, g, b; | |
| if (normalized < 0.25) { | |
| // Purple to Blue | |
| let t = normalized * 4; | |
| r = Math.floor(68 + t * (31 - 68)); | |
| g = Math.floor(1 + t * (120 - 1)); | |
| b = Math.floor(84 + t * (180 - 84)); | |
| } else if (normalized < 0.5) { | |
| // Blue to Cyan | |
| let t = (normalized - 0.25) * 4; | |
| r = Math.floor(31 + t * (42 - 31)); | |
| g = Math.floor(120 + t * (183 - 120)); | |
| b = Math.floor(180 + t * (202 - 180)); | |
| } else if (normalized < 0.75) { | |
| // Cyan to Green/Yellow-green | |
| let t = (normalized - 0.5) * 4; | |
| r = Math.floor(42 + t * (140 - 42)); | |
| g = Math.floor(183 + t * (216 - 183)); | |
| b = Math.floor(202 + t * (56 - 202)); | |
| } else { | |
| // Yellow-green to Yellow | |
| let t = (normalized - 0.75) * 4; | |
| r = Math.floor(140 + t * (253 - 140)); | |
| g = Math.floor(216 + t * (231 - 216)); | |
| b = Math.floor(56 + t * (37 - 56)); | |
| } | |
| return `rgb(${r}, ${g}, ${b})`; | |
| } | |
| // Generate mesh data | |
| function generateMesh() { | |
| const res = config.resolution; | |
| const xMin = -config.xRange; | |
| const xMax = config.xRange; | |
| const yMin = -config.yRange; | |
| const yMax = config.yRange; | |
| const xStep = (xMax - xMin) / (res - 1); | |
| const yStep = (yMax - yMin) / (res - 1); | |
| let points = []; | |
| let minZ = Infinity; | |
| let maxZ = -Infinity; | |
| // Generate all points | |
| for (let i = 0; i < res; i++) { | |
| points[i] = []; | |
| for (let j = 0; j < res; j++) { | |
| let x = xMin + i * xStep; | |
| let y = yMin + j * yStep; | |
| let z = swiglu(x, y); | |
| minZ = Math.min(minZ, z); | |
| maxZ = Math.max(maxZ, z); | |
| points[i][j] = { x, y, z }; | |
| } | |
| } | |
| return { points, minZ, maxZ, res }; | |
| } | |
| // Render the 3D surface | |
| function render() { | |
| // Clear canvas | |
| ctx.fillStyle = '#f9f9f9'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| const mesh = generateMesh(); | |
| const { points, minZ, maxZ, res } = mesh; | |
| // Store all faces with their average z-depth for sorting | |
| let faces = []; | |
| // Create faces (quads) | |
| for (let i = 0; i < res - 1; i++) { | |
| for (let j = 0; j < res - 1; j++) { | |
| let p1 = points[i][j]; | |
| let p2 = points[i + 1][j]; | |
| let p3 = points[i + 1][j + 1]; | |
| let p4 = points[i][j + 1]; | |
| // Project all points | |
| let proj1 = project3D(p1.x, p1.y, p1.z); | |
| let proj2 = project3D(p2.x, p2.y, p2.z); | |
| let proj3 = project3D(p3.x, p3.y, p3.z); | |
| let proj4 = project3D(p4.x, p4.y, p4.z); | |
| // Calculate average z for depth sorting | |
| let avgZ = (proj1.z + proj2.z + proj3.z + proj4.z) / 4; | |
| let avgZValue = (p1.z + p2.z + p3.z + p4.z) / 4; | |
| // Checkerboard pattern (alternate dark/light) | |
| let isCheckerDark = (i + j) % 2 === 0; | |
| faces.push({ | |
| points: [proj1, proj2, proj3, proj4], | |
| z: avgZ, | |
| color: getColor(avgZValue, minZ, maxZ), | |
| isCheckerDark: isCheckerDark | |
| }); | |
| } | |
| } | |
| // Sort faces by depth (painter's algorithm) | |
| faces.sort((a, b) => a.z - b.z); | |
| // Draw faces | |
| faces.forEach(face => { | |
| ctx.beginPath(); | |
| ctx.moveTo(face.points[0].x, face.points[0].y); | |
| ctx.lineTo(face.points[1].x, face.points[1].y); | |
| ctx.lineTo(face.points[2].x, face.points[2].y); | |
| ctx.lineTo(face.points[3].x, face.points[3].y); | |
| ctx.closePath(); | |
| // Apply checkerboard pattern by modulating the color | |
| let baseColor = face.color; | |
| let rgb = baseColor.match(/\d+/g).map(Number); | |
| // Darken or lighten based on checkerboard pattern | |
| let factor = face.isCheckerDark ? 0.75 : 1.0; | |
| let adjustedColor = `rgb(${Math.floor(rgb[0] * factor)}, ${Math.floor(rgb[1] * factor)}, ${Math.floor(rgb[2] * factor)})`; | |
| // Fill with color | |
| ctx.fillStyle = adjustedColor; | |
| ctx.fill(); | |
| // Draw wireframe | |
| ctx.strokeStyle = 'rgba(100, 100, 100, 0.4)'; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| }); | |
| // Draw axes | |
| drawAxes(); | |
| } | |
| // Draw coordinate axes with grid | |
| function drawAxes() { | |
| const axisLength = config.xRange; | |
| const gridStep = 1; // Grid spacing | |
| // Draw grid lines in XY plane | |
| ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)'; | |
| ctx.lineWidth = 0.5; | |
| // Grid lines parallel to X axis | |
| for (let y = -axisLength; y <= axisLength; y += gridStep) { | |
| if (y === 0) continue; // Skip the axis line | |
| let start = project3D(-axisLength, y, 0); | |
| let end = project3D(axisLength, y, 0); | |
| ctx.beginPath(); | |
| ctx.moveTo(start.x, start.y); | |
| ctx.lineTo(end.x, end.y); | |
| ctx.stroke(); | |
| } | |
| // Grid lines parallel to Y axis | |
| for (let x = -axisLength; x <= axisLength; x += gridStep) { | |
| if (x === 0) continue; // Skip the axis line | |
| let start = project3D(x, -axisLength, 0); | |
| let end = project3D(x, axisLength, 0); | |
| ctx.beginPath(); | |
| ctx.moveTo(start.x, start.y); | |
| ctx.lineTo(end.x, end.y); | |
| ctx.stroke(); | |
| } | |
| // X axis (black) | |
| let xStart = project3D(-axisLength, 0, 0); | |
| let xEnd = project3D(axisLength, 0, 0); | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); | |
| ctx.moveTo(xStart.x, xStart.y); | |
| ctx.lineTo(xEnd.x, xEnd.y); | |
| ctx.stroke(); | |
| // Y axis (black) | |
| let yStart = project3D(0, -axisLength, 0); | |
| let yEnd = project3D(0, axisLength, 0); | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(yStart.x, yStart.y); | |
| ctx.lineTo(yEnd.x, yEnd.y); | |
| ctx.stroke(); | |
| // Z axis (black) | |
| let zStart = project3D(0, 0, -axisLength); | |
| let zEnd = project3D(0, 0, axisLength); | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(zStart.x, zStart.y); | |
| ctx.lineTo(zEnd.x, zEnd.y); | |
| ctx.stroke(); | |
| // Draw tick marks and labels | |
| ctx.fillStyle = '#333'; | |
| ctx.font = '12px sans-serif'; | |
| ctx.textAlign = 'center'; | |
| ctx.textBaseline = 'middle'; | |
| // X axis ticks and labels | |
| for (let x = -axisLength; x <= axisLength; x += gridStep) { | |
| let pos = project3D(x, 0, 0); | |
| let tickStart = project3D(x, -0.15, 0); | |
| let tickEnd = project3D(x, 0.15, 0); | |
| // Draw tick mark | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(tickStart.x, tickStart.y); | |
| ctx.lineTo(tickEnd.x, tickEnd.y); | |
| ctx.stroke(); | |
| // Draw label | |
| if (x % 2 === 0 || axisLength <= 5) { // Show fewer labels for large ranges | |
| let labelPos = project3D(x, -0.5, 0); | |
| ctx.fillStyle = '#000'; | |
| ctx.font = 'bold 11px sans-serif'; | |
| ctx.fillText(x.toFixed(0), labelPos.x, labelPos.y); | |
| } | |
| } | |
| // Y axis ticks and labels | |
| for (let y = -axisLength; y <= axisLength; y += gridStep) { | |
| let tickStart = project3D(-0.15, y, 0); | |
| let tickEnd = project3D(0.15, y, 0); | |
| // Draw tick mark | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(tickStart.x, tickStart.y); | |
| ctx.lineTo(tickEnd.x, tickEnd.y); | |
| ctx.stroke(); | |
| // Draw label | |
| if (y % 2 === 0 || axisLength <= 5) { | |
| let labelPos = project3D(-0.5, y, 0); | |
| ctx.fillStyle = '#000'; | |
| ctx.font = 'bold 11px sans-serif'; | |
| ctx.fillText(y.toFixed(0), labelPos.x, labelPos.y); | |
| } | |
| } | |
| // Z axis ticks and labels | |
| for (let z = -axisLength; z <= axisLength; z += gridStep) { | |
| let tickStart = project3D(-0.15, 0, z); | |
| let tickEnd = project3D(0.15, 0, z); | |
| // Draw tick mark | |
| ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.lineWidth = 1.5; | |
| ctx.beginPath(); | |
| ctx.moveTo(tickStart.x, tickStart.y); | |
| ctx.lineTo(tickEnd.x, tickEnd.y); | |
| ctx.stroke(); | |
| // Draw label | |
| if (z % 2 === 0 || axisLength <= 5) { | |
| let labelPos = project3D(-0.5, 0, z); | |
| ctx.fillStyle = '#000'; | |
| ctx.font = 'bold 11px sans-serif'; | |
| ctx.fillText(z.toFixed(0), labelPos.x, labelPos.y); | |
| } | |
| } | |
| // Axis labels (X, Y, Z) | |
| ctx.font = 'bold 16px sans-serif'; | |
| ctx.fillStyle = '#000'; | |
| ctx.fillText('X', xEnd.x + 15, xEnd.y); | |
| ctx.fillText('Y', yEnd.x + 15, yEnd.y); | |
| ctx.fillText('Z', zEnd.x + 15, zEnd.y); | |
| } | |
| // Update rotation display | |
| function updateRotationDisplay() { | |
| document.getElementById('rotX').textContent = | |
| Math.round(config.rotationX * 180 / Math.PI) + '°'; | |
| document.getElementById('rotY').textContent = | |
| Math.round(config.rotationY * 180 / Math.PI) + '°'; | |
| } | |
| // Mouse event handlers | |
| canvas.addEventListener('mousedown', (e) => { | |
| isDragging = true; | |
| lastMouseX = e.clientX; | |
| lastMouseY = e.clientY; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (isDragging) { | |
| const deltaX = e.clientX - lastMouseX; | |
| const deltaY = e.clientY - lastMouseY; | |
| config.rotationY += deltaX * 0.01; | |
| config.rotationX += deltaY * 0.01; | |
| lastMouseX = e.clientX; | |
| lastMouseY = e.clientY; | |
| updateRotationDisplay(); | |
| render(); | |
| } | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| isDragging = false; | |
| }); | |
| // Control event listeners | |
| document.getElementById('resolution').addEventListener('input', (e) => { | |
| config.resolution = parseInt(e.target.value); | |
| document.getElementById('resolutionValue').textContent = config.resolution; | |
| render(); | |
| }); | |
| document.getElementById('xRange').addEventListener('input', (e) => { | |
| config.xRange = parseFloat(e.target.value); | |
| document.getElementById('xRangeValue').textContent = '±' + config.xRange; | |
| render(); | |
| }); | |
| document.getElementById('yRange').addEventListener('input', (e) => { | |
| config.yRange = parseFloat(e.target.value); | |
| document.getElementById('yRangeValue').textContent = '±' + config.yRange; | |
| render(); | |
| }); | |
| document.getElementById('resetView').addEventListener('click', () => { | |
| config.rotationX = -45 * Math.PI / 180; | |
| config.rotationY = 30 * Math.PI / 180; | |
| config.resolution = 30; | |
| config.xRange = 5; | |
| config.yRange = 5; | |
| document.getElementById('resolution').value = 30; | |
| document.getElementById('resolutionValue').textContent = '30'; | |
| document.getElementById('xRange').value = 5; | |
| document.getElementById('xRangeValue').textContent = '±5'; | |
| document.getElementById('yRange').value = 5; | |
| document.getElementById('yRangeValue').textContent = '±5'; | |
| updateRotationDisplay(); | |
| render(); | |
| }); | |
| // Initial render | |
| updateRotationDisplay(); | |
| render(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment