tfrere's picture
tfrere HF Staff
Clean repository - remove missing LFS files
6afedde
raw
history blame
17.9 kB
<div class="d3-rope-demo"></div>
<style>
.d3-rope-demo {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
color: var(--text-color);
padding: 20px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.d3-rope-demo .subtitle {
color: var(--text-color);
font-size: 18px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
max-width: 600px;
line-height: 1.5;
}
.d3-rope-demo .sentence {
display: flex;
gap: 0;
margin: 25px 0;
flex-wrap: wrap;
justify-content: center;
font-size: 18px;
}
.d3-rope-demo .slider-container {
display: flex;
align-items: center;
justify-content: center;
margin: 15px 0;
}
.d3-rope-demo .slider-label {
font-size: 14px;
color: var(--muted-color);
font-weight: 500;
min-width: 80px;
}
.d3-rope-demo .slider {
width: 200px;
height: 6px;
border-radius: 3px;
background: var(--border-color);
outline: none;
cursor: pointer;
}
.d3-rope-demo .slider::-webkit-slider-thumb {
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid var(--page-bg);
box-shadow: 0 2px 4px var(--border-color);
}
.d3-rope-demo .slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid var(--page-bg);
box-shadow: 0 2px 4px var(--border-color);
}
.d3-rope-demo .slider-value {
font-size: 14px;
color: var(--text-color);
font-weight: 600;
min-width: 40px;
text-align: center;
}
.d3-rope-demo .rotation-info {
text-align: center;
margin: 20px auto;
font-size: 16px;
font-weight: 500;
color: var(--text-color);
padding: 20px;
background: var(--page-bg);
border-radius: 8px;
border: 1px solid var(--border-color) !important;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
max-width: 500px;
}
.d3-rope-demo .equation-gap {
height: 15px;
}
.d3-rope-demo .word-highlight {
color: var(--primary-color);
font-weight: 700;
background: var(--page-bg);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid var(--border-color);
display: inline-block;
min-width: 60px;
text-align: center;
}
.d3-rope-demo .position-highlight {
color: var(--primary-color);
font-weight: 700;
background: var(--page-bg);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid var(--border-color);
}
.d3-rope-demo .angle-highlight {
color: var(--primary-color);
font-weight: 600;
font-family: 'Courier New', monospace;
font-size: 20px;
padding: 12px 16px;
border-radius: 6px;
background: var(--page-bg);
border: 1px solid var(--border-color);
display: inline-block;
width: 100%;
text-align: center;
}
.d3-rope-demo .word {
cursor: pointer;
font-weight: 700;
font-size: 18px;
user-select: none;
padding: 8px 12px;
border-radius: 0;
transition: all 0.2s ease;
border: 1px solid var(--border-color);
border-right: none;
}
.d3-rope-demo .word:first-child {
border-radius: 6px 0 0 6px;
}
.d3-rope-demo .word:last-child {
border-radius: 0 6px 6px 0;
border-right: 1px solid var(--border-color);
}
.d3-rope-demo .word:only-child {
border-radius: 6px;
border-right: 1px solid var(--border-color);
}
.button {
background: var(--primary-color)!important;
color: var(--page-bg)!important;
border: 1px solid var(--primary-color)!important;
}
.button--ghost {
background: var(--page-bg)!important;
color: var(--primary-color)!important;
border: 1px solid var(--primary-color)!important;
}
.d3-rope-demo .svg-container {
margin: 0;
display: inline-block;
}
.d3-rope-demo svg {
display: block;
}
.d3-rope-demo .explanation {
max-width: 700px;
text-align: center;
margin-top: 20px;
color: var(--text-color);
font-size: 15px;
line-height: 1.6;
}
/* Responsive design */
@media (max-width: 768px) {
.d3-rope-demo {
padding: 16px 0;
}
.d3-rope-demo .sentence {
gap: 10px;
}
.d3-rope-demo .word {
font-size: 16px;
padding: 6px 10px;
}
.d3-rope-demo .svg-container {
width: 100%;
max-width: 400px;
}
.d3-rope-demo svg {
width: 100%;
height: auto;
}
.d3-rope-demo .explanation {
font-size: 14px;
}
}
</style>
<script>
(() => {
const bootstrap = () => {
const scriptEl = document.currentScript;
let container = scriptEl ? scriptEl.previousElementSibling : null;
if (!(container && container.classList && container.classList.contains('d3-rope-demo'))) {
const candidates = Array.from(document.querySelectorAll('.d3-rope-demo'))
.filter((el) => !(el.dataset && el.dataset.mounted === 'true'));
container = candidates[candidates.length - 1] || null;
}
if (!container) return;
if (container.dataset) {
if (container.dataset.mounted === 'true') return;
container.dataset.mounted = 'true';
}
const sentence = ["The", "quick", "brown", "fox", "jumps", "..."];
// Create the HTML structure
container.innerHTML = `
<div class="subtitle">RoPE rotation of the first (x₁, x₂) pair in Q/K vectors<br/> based on token position</div>
<div class="sentence" id="sentence"></div>
<div class="slider-container">
<input type="range" class="slider" id="positionSlider" min="0" max="5" step="1" value="0">
</div>
<div class="svg-container">
<svg id="ropeSvg" width="500" height="400" viewBox="0 0 500 400"></svg>
</div>
<div class="rotation-info" id="rotationInfo">
<span class="word-highlight">The</span> at position <span class="position-highlight">0</span> gets rotated by
<div class="equation-gap"></div>
<span class="angle-highlight">θ = 0 rad (0°)</span>
</div>
<div class="explanation">
<strong>RoPE Formula:</strong> θ (theta) = position × 1 / base<sup>2 × pair_index/h_dim</sup> (pair_index=0 here)
<br><br>
<strong>Key insight:</strong> The first dimension pair gets the largest rotations, and the relative angle between words depends only on their distance apart.
</div>
`;
const svg = container.querySelector('#ropeSvg');
const sentenceEl = container.querySelector('#sentence');
const slider = container.querySelector('#positionSlider');
const rotationInfo = container.querySelector('#rotationInfo');
const R = 140;
const R_LABELS = 180; // Cercle plus grand pour les labels
const cx = 250;
const cy = 200;
const ANGLE_OFFSET = 5; // Offset en degrés pour mieux aligner les 6 mots
// RoPE parameters
const base = 10000;
const d = 2048;
const m = 0;
function getRopeAngle(pos) {
return pos * (1 / Math.pow(base, (2 * m) / d));
}
let activeIndex = 0;
let animating = true;
let animationTimeout = null;
function renderSentence() {
sentenceEl.innerHTML = "";
sentence.forEach((word, i) => {
const span = document.createElement("span");
span.textContent = word;
span.className = "word button" + (i === activeIndex ? "" : " button--ghost");
span.addEventListener("click", () => {
stopAnimation();
activeIndex = i;
slider.value = i;
updateRotationInfo();
draw();
renderSentence();
});
sentenceEl.appendChild(span);
});
}
function draw() {
// Clear SVG
svg.innerHTML = '';
// Create arrays to store elements for proper layering
const backgroundElements = [];
const foregroundElements = [];
const textElements = [];
// Draw circle (background)
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', cx);
circle.setAttribute('cy', cy);
circle.setAttribute('r', R);
circle.setAttribute('fill', 'none');
circle.setAttribute('stroke', 'var(--border-color)');
circle.setAttribute('stroke-width', '1.5');
circle.setAttribute('opacity', '0.6');
backgroundElements.push(circle);
// Draw all word positions
sentence.forEach((word, i) => {
const theta = getRopeAngle(i) + (ANGLE_OFFSET * Math.PI / 180); // Ajouter l'offset en radians
const x = cx + R * Math.cos(theta);
const y = cy + R * Math.sin(theta);
const isActive = (i === activeIndex);
const isGhost = i > activeIndex; // Éléments après la position active sont en ghost
// Draw point (background)
const point = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
point.setAttribute('cx', x);
point.setAttribute('cy', y);
point.setAttribute('r', isActive ? 10 : 5);
point.setAttribute('fill', isActive ? 'var(--primary-color)' : (isGhost ? 'var(--muted-color)' : 'var(--primary-color)'));
point.setAttribute('stroke', isActive ? 'var(--page-bg)' : (isGhost ? 'var(--surface-bg)' : 'var(--page-bg)'));
point.setAttribute('stroke-width', isActive ? '3' : '2');
point.setAttribute('opacity', isActive ? '1' : (isGhost ? '0.3' : '0.7'));
backgroundElements.push(point);
// Draw arrow for active word (foreground)
if (isActive) {
const arrow = document.createElementNS('http://www.w3.org/2000/svg', 'line');
arrow.setAttribute('x1', cx);
arrow.setAttribute('y1', cy);
arrow.setAttribute('x2', x);
arrow.setAttribute('y2', y);
arrow.setAttribute('stroke', 'var(--primary-color)');
arrow.setAttribute('stroke-width', '3');
arrow.setAttribute('stroke-linecap', 'round');
arrow.setAttribute('opacity', '0.8');
foregroundElements.push(arrow);
}
});
// Draw center point (foreground)
const centerPoint = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
centerPoint.setAttribute('cx', cx);
centerPoint.setAttribute('cy', cy);
centerPoint.setAttribute('r', 5);
centerPoint.setAttribute('fill', 'var(--text-color)');
centerPoint.setAttribute('stroke', 'var(--page-bg)');
centerPoint.setAttribute('stroke-width', '2');
centerPoint.setAttribute('opacity', '0.8');
foregroundElements.push(centerPoint);
// Draw angle arc for active word (foreground)
if (activeIndex !== null && activeIndex > 0) {
const theta = getRopeAngle(activeIndex) + (ANGLE_OFFSET * Math.PI / 180);
const startAngle = ANGLE_OFFSET * Math.PI / 180;
const endAngle = theta;
// Create arc path
const radius = R * 0.7;
const startX = cx + radius * Math.cos(startAngle);
const startY = cy + radius * Math.sin(startAngle);
const endX = cx + radius * Math.cos(endAngle);
const endY = cy + radius * Math.sin(endAngle);
const largeArcFlag = theta > Math.PI ? 1 : 0;
const pathData = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endX} ${endY}`;
const arc = document.createElementNS('http://www.w3.org/2000/svg', 'path');
arc.setAttribute('d', pathData);
arc.setAttribute('fill', 'none');
arc.setAttribute('stroke', 'var(--primary-color)');
arc.setAttribute('stroke-width', '2.5');
arc.setAttribute('stroke-dasharray', '6,4');
arc.setAttribute('opacity', '0.8');
foregroundElements.push(arc);
}
// Draw all text elements (top layer)
sentence.forEach((word, i) => {
const theta = getRopeAngle(i);
const x = cx + R * Math.cos(theta);
const y = cy + R * Math.sin(theta);
const isActive = (i === activeIndex);
const isGhost = i > activeIndex; // Éléments après la position active sont en ghost
// Draw word label on larger circle
const labelX = cx + R_LABELS * Math.cos(theta);
const labelY = cy + R_LABELS * Math.sin(theta);
const wordLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
wordLabel.setAttribute('x', labelX);
wordLabel.setAttribute('y', labelY);
wordLabel.setAttribute('text-anchor', 'middle');
wordLabel.setAttribute('dominant-baseline', 'middle');
wordLabel.setAttribute('fill', isActive ? 'var(--text-color)' : (isGhost ? 'var(--muted-color)' : 'var(--text-color)'));
wordLabel.setAttribute('font-family', '-apple-system, BlinkMacSystemFont, sans-serif');
wordLabel.setAttribute('font-size', isActive ? '18' : '15');
wordLabel.setAttribute('font-weight', isActive ? '700' : '500');
wordLabel.setAttribute('opacity', isActive ? '1' : (isGhost ? '0.3' : '0.8'));
wordLabel.textContent = word;
textElements.push(wordLabel);
});
// Add angle label (top layer)
if (activeIndex !== null && activeIndex > 0) {
const theta = getRopeAngle(activeIndex) + (ANGLE_OFFSET * Math.PI / 180);
const radius = R * 0.7;
const angleLabelX = cx + radius * 0.5 * Math.cos(theta / 2);
const angleLabelY = cy + radius * 0.5 * Math.sin(theta / 2);
// Create tspan elements for different styling
const angleLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
angleLabel.setAttribute('x', angleLabelX);
angleLabel.setAttribute('y', angleLabelY);
angleLabel.setAttribute('text-anchor', 'middle');
angleLabel.setAttribute('font-family', '-apple-system, BlinkMacSystemFont, sans-serif');
angleLabel.setAttribute('font-size', '13');
angleLabel.setAttribute('font-weight', '600');
// θ in primary color
const thetaSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
thetaSpan.setAttribute('fill', 'var(--primary-color)');
thetaSpan.textContent = 'θ';
angleLabel.appendChild(thetaSpan);
// = with reduced opacity
const equalsSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
equalsSpan.setAttribute('fill', 'var(--primary-color)');
equalsSpan.setAttribute('opacity', '0.5');
equalsSpan.textContent = ' = ';
angleLabel.appendChild(equalsSpan);
// Number in primary color
const numberSpan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
numberSpan.setAttribute('fill', 'var(--primary-color)');
numberSpan.textContent = activeIndex.toString();
angleLabel.appendChild(numberSpan);
textElements.push(angleLabel);
}
// Append elements in correct order: background -> foreground -> text
backgroundElements.forEach(el => svg.appendChild(el));
foregroundElements.forEach(el => svg.appendChild(el));
textElements.forEach(el => svg.appendChild(el));
}
function updateRotationInfo() {
const theta = getRopeAngle(activeIndex);
const degrees = Math.round(theta * 180 / Math.PI);
rotationInfo.innerHTML = `
<span class="word-highlight">${sentence[activeIndex]}</span> at position <span class="position-highlight">${activeIndex}</span> gets rotated by
<div class="equation-gap"></div>
<div class="angle-highlight">
<span style="color: var(--muted-color); opacity: 0.6;">θ</span>
<span style="color: var(--muted-color); opacity: 0.4; margin: 0 8px;">=</span>
<span style="opacity: 1;">${activeIndex}</span>
<span style="color: var(--muted-color); opacity: 0.6;">rad</span>
<span style="color: var(--muted-color); opacity: 0.4;">(</span>
<span style="opacity: 1;">${degrees}°</span>
<span style="color: var(--muted-color); opacity: 0.4;">)</span>
</div>
`;
}
function stopAnimation() {
animating = false;
if (animationTimeout) {
clearTimeout(animationTimeout);
animationTimeout = null;
}
}
function animate() {
if (!animating) return;
animationTimeout = setTimeout(() => {
activeIndex = (activeIndex + 1) % sentence.length;
slider.value = activeIndex;
updateRotationInfo();
renderSentence();
draw();
animate();
}, 1500);
}
// Slider event listener
slider.addEventListener('input', (e) => {
stopAnimation();
activeIndex = parseInt(e.target.value);
updateRotationInfo();
renderSentence();
draw();
});
// Initialize and start
renderSentence();
updateRotationInfo();
draw();
animate();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bootstrap, { once: true });
} else {
bootstrap();
}
})();
</script>