2
2

奥儀。小さなGPUでの算法術。3Dテンソル算法のパフォーマンス。画像テンソルの内積算法術。

Last updated at Posted at 2024-09-02

image.png

ショートストーリー:「未来を紡ぐコード」
東京のプログラマ、加藤拓也は、夜の東京の喧騒から少し離れた静かな部屋で、一心不乱にキーボードを叩いていた。彼は次世代の動画生成AIモデルの研究に取り組んでおり、その最前線で活躍するプログラマだ。今日の彼の目標は、彼の開発した新しいアルゴリズムを使って、数学的な美しさを視覚化するアニメーションを作り上げることだった。

拓也の最新のプロジェクトは、3次元テンソルの計算を利用して、視覚的に魅力的なヒートマップを生成することだった。彼のコードは、正弦波と余弦波の複雑なパターンを組み合わせたもので、それぞれが異なるテンソルとして表現される。このテンソルたちは、未来の動画生成AIにおける「可能性の断片」だ。

ある晩、拓也は深夜のコード作成に没頭していた。彼のコンピュータの画面には、青い光が反射するテンソルデータが表示されている。テンソルAとテンソルB、それぞれが正弦波と余弦波のパターンを描き出しており、彼のプログラムはそれらのテンソルの積を計算し、3Dアニメーションとして可視化することを目指していた。

「このデータがどんなアニメーションになるのか、見てみたいな」と拓也は呟いた。

彼のコードが、テンソルの積を計算し、その結果を一つ一つスライスして3次元ヒートマップとしてプロットする準備が整った。拓也は深呼吸し、コードの実行ボタンを押した。画面に映し出されたのは、数十万のデータポイントが織りなす美しいパーティクルアニメーションだった。

アニメーションは、正弦波と余弦波が交錯する中で、色とりどりの点が優雅に舞う光景を描き出していた。それはまるで、宇宙の中で踊る星々のような、未来的で幻想的な光景だった。

その夜、拓也はコードの美しさと、数学の力によって創造されたビジュアルの力に感動し、深い満足感を覚えた。
未来の動画生成AIモデルの研究は、彼の手によって新たな次元を迎え、東京のプログラマとしての彼の探求は続く。夜が更ける中、拓也は静かに次のステップへと進むための準備を始めた。

TensorFlow.jsを使って3次元テンソルの計算を行い、GPUとCPUのパフォーマンスを比較しています。

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Tensor Performance Comparison</title>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
</head>
<body>
    <h1>3D Tensor Performance Comparison</h1>
    <button onclick="comparePerformance()">Run Performance Test</button>
    <div id="results"></div>

    <script>
        async function runTensorCalculation(backend, size) {
            // Set the backend and wait for it to be ready
            await tf.setBackend(backend);
            await tf.ready();

            // Create 3D tensors
            const tensor1 = tf.randomUniform([size, size, size]);
            const tensor2 = tf.randomUniform([size, size, size]);

            // Start performance measurement
            const start = performance.now();
            const result = tensor1.matMul(tensor2, true); // Example operation
            await result.data();  // Execute computation
            const end = performance.now();

            return end - start;  // Return elapsed time
        }

        async function comparePerformance() {
            const sizes = [10, 50, 100, 200];
            let resultsHtml = '<h2>Performance Results</h2>';
            resultsHtml += '<table border="1"><tr><th>Size</th><th>GPU Time (ms)</th><th>CPU Time (ms)</th></tr>';

            for (const size of sizes) {
                // Measure GPU performance
                const gpuTime = await runTensorCalculation('webgl', size);

                // Measure CPU performance
                const cpuTime = await runTensorCalculation('cpu', size);

                resultsHtml += `<tr><td>${size}x${size}x${size}</td><td>${gpuTime.toFixed(2)}</td><td>${cpuTime.toFixed(2)}</td></tr>`;
            }

            resultsHtml += '</table>';
            document.getElementById('results').innerHTML = resultsHtml;
        }
    </script>
</body>
</html>

異なるサイズの3次元テンソルの計算時間を測定し、プロットします。

スクリーンショット 2024-09-02 173113.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GPU 3D Tensor Performance</title>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/plotly.js-dist/plotly.min.js"></script>
</head>
<body>
    <div id="plot" style="width: 100%; height: 500px;"></div>

    <script>
        async function measureTensorCalculationTime(size) {
            // ランダムな3次元テンソルを生成
            const A = tf.randomUniform([size, size, size]);
            const B = tf.randomUniform([size, size, size]);

            // テンソル計算の時間を測定
            const startTime = performance.now();
            await tf.tidy(() => {
                // テンソルの加算を計算(他の計算操作に変更可能)
                const result = tf.add(A, B);
                return result.data(); // 計算を強制的に実行
            });
            const endTime = performance.now();

            // テンソルを解放してGPUメモリを確保
            A.dispose();
            B.dispose();
            
            return endTime - startTime;
        }

        async function runPerformanceTest() {
            const sizes = [10, 50, 100, 200, 300];
            const times = [];
            
            for (const size of sizes) {
                console.log(`サイズ ${size}x${size}x${size} のテンソル計算を測定中`);
                const time = await measureTensorCalculationTime(size);
                times.push(time);
                console.log(`${size}x${size}x${size} の計算時間: ${time} ms`);
            }

            // 測定結果をプロット
            Plotly.newPlot('plot', [{
                x: sizes.map(size => `${size}x${size}x${size}`),
                y: times,
                type: 'scatter',
                mode: 'lines+markers',
                marker: { color: 'blue' }
            }], {
                title: 'GPU 3D テンソル計算時間 vs サイズ',
                xaxis: { title: 'テンソルサイズ (x x x)' },
                yaxis: { title: '処理時間 (ms)' }
            });
        }

        runPerformanceTest();
    </script>
</body>
</html>

スクリーンショット 2024-09-02 163944.png

スクリーンショット 2024-09-02 163953.png

スクリーンショット 2024-09-02 164006.png

正弦波と余弦波に基づいたデータで生成されたテンソル積のスライスがパーティクルアニメーションとして表示されます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Tensor Particle Animation with Sinusoidal Data</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <!-- three.jsとTensorFlow.jsのCDNを読み込み -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script>
        // シーン、カメラ、レンダラーの設定
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        // グリッドサイズの設定
        const gridSize = 50;

        // パーティクルのジオメトリとマテリアルを設定
        const particles = new THREE.BufferGeometry();
        const particleCount = gridSize * gridSize * gridSize;
        const positions = new Float32Array(particleCount * 3);
        const colors = new Float32Array(particleCount * 3);

        particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));

        const material = new THREE.PointsMaterial({
            size: 0.5,
            vertexColors: true
        });

        const particleSystem = new THREE.Points(particles, material);
        scene.add(particleSystem);

        camera.position.z = 75;

        // 3次元テンソルAとBを生成
        const x = tf.linspace(0, 2 * Math.PI, gridSize);
        const y = tf.linspace(0, 2 * Math.PI, gridSize);
        const z = tf.linspace(0, 2 * Math.PI, gridSize);

        // Sin関数を使用してテンソルを生成
        const tensorA = tf.tidy(() => {
            const xMesh = tf.tile(x.reshape([gridSize, 1, 1]), [1, gridSize, gridSize]);
            const yMesh = tf.tile(y.reshape([1, gridSize, 1]), [gridSize, 1, gridSize]);
            const zMesh = tf.tile(z.reshape([1, 1, gridSize]), [gridSize, gridSize, 1]);
            return tf.sin(xMesh).add(tf.sin(yMesh)).add(tf.sin(zMesh)).div(3);
        });

        const tensorB = tf.tidy(() => {
            const xMesh = tf.tile(x.reshape([gridSize, 1, 1]), [1, gridSize, gridSize]);
            const yMesh = tf.tile(y.reshape([1, gridSize, 1]), [gridSize, 1, gridSize]);
            const zMesh = tf.tile(z.reshape([1, 1, gridSize]), [gridSize, gridSize, 1]);
            return tf.cos(xMesh).add(tf.cos(yMesh)).add(tf.cos(zMesh)).div(3);
        });

        // テンソル積を計算
        const tensorProduct = tf.tidy(() => tensorA.matMul(tensorB));

        // テンソル積のスライスを保存
        const slices = [];
        for (let i = 0; i < gridSize; i++) {
            const slice = tensorProduct.slice([0, 0, i], [gridSize, gridSize, 1]).reshape([gridSize, gridSize]);
            slices.push(slice.arraySync());
        }

        let frame = 0;
        const totalFrames = gridSize;

        // パーティクルの位置と色を更新する関数
        function updateParticles(frameIndex) {
            const sliceValues = slices[frameIndex];
            let index = 0;
            for (let x = 0; x < gridSize; x++) {
                for (let y = 0; y < gridSize; y++) {
                    const intensity = sliceValues[x][y];
                    const color = new THREE.Color().setHSL(intensity, 1, 0.5);

                    positions[index * 3] = x - gridSize / 2;
                    positions[index * 3 + 1] = y - gridSize / 2;
                    positions[index * 3 + 2] = frameIndex - gridSize / 2;

                    colors[index * 3] = color.r;
                    colors[index * 3 + 1] = color.g;
                    colors[index * 3 + 2] = color.b;

                    index++;
                }
            }

            particles.attributes.position.needsUpdate = true;
            particles.attributes.color.needsUpdate = true;
        }

        // アニメーションループ
        function animate() {
            updateParticles(frame);
            frame = (frame + 1) % totalFrames;

            renderer.render(scene, camera);
            requestAnimationFrame(animate);
        }

        animate();

        // ウィンドウサイズ変更時にレンダラーをリサイズ
        window.addEventListener('resize', () => {
            const width = window.innerWidth;
            const height = window.innerHeight;
            renderer.setSize(width, height);
            camera.aspect = width / height;
            camera.updateProjectionMatrix();
        });
    </script>
</body>
</html>

コードの説明
テンソルの生成:

x, y, z のテンソルを tf.linspace で生成し、それを使って sin 関数と cos 関数で tensorA と tensorB を生成します。テンソル tensorA と tensorB はそれぞれ正弦波と余弦波のパターンを持っています。
テンソル積の計算:

tensorA と tensorB のテンソル積を計算します。
スライスの保存:

テンソル積の各スライスを計算し、slices 配列に保存します。
パーティクルシステムの設定:

Three.js の BufferGeometry を使ってパーティクルシステムを作成します。各パーティクルの位置と色を設定します。
パーティクルの更新:

updateParticles 関数で、指定されたスライスのデータを使ってパーティクルの位置と色を更新します。
アニメーション:

animate 関数で、スライスを順次更新し、アニメーションをループさせます。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2