Last active
May 5, 2025 18:21
-
-
Save abpai/6c1dfdc45f77238528354e53fab3be6c to your computer and use it in GitHub Desktop.
twotower-infonce.ipynb
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
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"provenance": [], | |
"authorship_tag": "ABX9TyPkQpqLKoUJ30YZ99M23YIA", | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
}, | |
"language_info": { | |
"name": "python" | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/abpai/6c1dfdc45f77238528354e53fab3be6c/twotower-infonce.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"%pip install structlog" | |
], | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/" | |
}, | |
"collapsed": true, | |
"id": "NV17kMksXa11", | |
"outputId": "c0eb24e9-168e-4f14-b5ab-f8c12d6d121c" | |
}, | |
"execution_count": 7, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"Requirement already satisfied: structlog in /usr/local/lib/python3.11/dist-packages (25.3.0)\n" | |
] | |
} | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": { | |
"colab": { | |
"base_uri": "https://localhost:8080/", | |
"height": 953 | |
}, | |
"id": "EbSrcQuCXPuB", | |
"outputId": "32175964-d487-46b8-b10c-5cd5a5a553db" | |
}, | |
"outputs": [ | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"2025-05-05 18:14:05 [info ] Using device device=device(type='cpu')\n", | |
"2025-05-05 18:14:05 [info ] Building Synthetic Data\n", | |
"2025-05-05 18:14:05 [info ] Creating DataLoader\n", | |
"2025-05-05 18:14:05 [info ] Initializing Model and Optimizer\n", | |
"2025-05-05 18:14:05 [info ] Starting Training\n", | |
"2025-05-05 18:14:05 [info ] epoch_summary epoch=1 loss=2.4593790285289288 lr=0.0003 time=0.052422285079956055\n", | |
"2025-05-05 18:14:05 [info ] epoch_summary epoch=2 loss=2.3996245190501213 lr=0.0003 time=0.05826759338378906\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=3 loss=2.3641757145524025 lr=0.0003 time=0.05289435386657715\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=4 loss=2.3509834930300713 lr=0.0003 time=0.05392646789550781\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=5 loss=2.335583135485649 lr=0.0003 time=0.058527231216430664\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=6 loss=2.3280965611338615 lr=0.0003 time=0.05180931091308594\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=7 loss=2.3198156468570232 lr=0.0003 time=0.0527191162109375\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=8 loss=2.3186416160315275 lr=0.0003 time=0.05267667770385742\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=9 loss=2.3187887631356716 lr=0.0003 time=0.05245041847229004\n", | |
"2025-05-05 18:14:06 [info ] epoch_summary epoch=10 loss=2.307461339980364 lr=0.0003 time=0.05702614784240723\n", | |
"2025-05-05 18:14:06 [info ] Training Finished total_training_time=0.5518066883087158\n", | |
"2025-05-05 18:14:06 [info ] Running Diagnostics\n" | |
] | |
}, | |
{ | |
"output_type": "display_data", | |
"data": { | |
"text/plain": [ | |
"<Figure size 1200x500 with 2 Axes>" | |
], | |
"image/png": "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No0vv/wSb29v3n33Xf7444+4Y86dO8eKFSuYPHkya9euxcPDgyFDhqRKg01ERCQ1/PLLL7z77rt4eHiwcuVKli5dSlRUFO3bt+eff/4B4MyZM/Tq1Yunn36aL7/8kq+++oqaNWsyYMCAeP/uAaxZs4bnn3+ezZs389RTT8VtHzt2LK1bt2b9+vW0adOGpUuXsnnz5gfW9qDPREVF8c477/DXX3/xySefsHDhQvbu3cu6dese6/sRey/25JNPAmC323nnnXe4cOECS5Ys4ZtvvqF///7MnTuXFStWAI4lJurVq0fhwoXZsWMHXbp0AaBPnz5s2LCB/v37s3HjRrp3786cOXOYO3duktfPmzcvPj4+fPrpp4wbN47ffvsNm82W6LFRUVF07dqVM2fOsGDBAlavXk2ePHno0qULf/31V9xxBw4cYMeOHcydO5egoCCio6MZPHhwXFMtOT8DJ06coH///pQuXZrg4GAmT57MqlWrOHPmzGN9vwHGjRvHokWL6NSpExs2bMDf3581a9YwZsyYxz63SLpnF5EMa8CAAfY6deok2L5u3Tq7p6enffny5fG2X7lyxX78+PF421auXGn39PS0X7lyJd5njx07Zrfb7XZ/f3+7p6en/fDhw3Gf+eWXX+yenp72kJCQR/7M9OnT7RUqVLBHRETEHXPo0CG7p6envX379klmDg8Pt3t6etpXrlyZ5DH79u2ze3p62jdu3Bhv+6JFi+yenp728+fP2//+++9Ejzl48KD90qVLdrvdbm/cuLF99OjR8fafPn3afvLkySSvHZt9//79cdtu3rxp9/b2to8fP95ut9vtCxYssJctW9Z++vTpeJ9t3ry5/e23346Xs2/fvkle69/XLFu2rL1SpUqJfk2fPj3u2Pbt29vLlStnv3jxYty2M2fO2D09Pe2ffvqp3W6320eNGmV/4YUX7Hfu3Il3nddee83eqVMnu91ut4eFhdk9PT3t3333Xdz+6Oho+5AhQ+K2tW/f3u7t7R33s2W32+0bN260e3p62vfu3fvQXCIiIo/L39/fXr169Qce07VrV3u9evXs0dHRcdsuX75s9/Lyss+fP99ut9vtd+7csR87dsx+8+bNuGPu3Llj9/T0tC9cuDBum6enp93X1zfe+RO7L7t37569YsWK9g8++MButye8v0nOZ3bs2GH39PS0h4aGxh1z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EdzdzZfLjJnAnLnMmAnMmcuMmcCcucyYCZLOlcqLnSf33sElVa8qWK1Quzb063eOnDntHD0Ku3cbXZWIiIiYlZeXF5GRkUydOpXbt2/zzz//MHv2bKpUqULu3Llp1KgRe/bsAaBdu3Z8+eWX7N+/n9u3bzN//nyyZctG7dq107borl2JOX6cwx99RMzx44YtrprqzJjLjJnAnLnMmAnMmcuMmcCcucyYCdJVLjWlnCRXrhhatHCs2bB8ucHFiIiIiGnlzp2bTz/9lN9//50XX3yRpk2bkjt3bqZPnw7AyZMnuXXrFgCvvPIKgwYNYsCAAVStWpWffvqJjz/+mBw5cqR94cWKOdauSMWpAumCGXOZMROYM5cZM4E5c5kxE5gzlxkzQbrJpel7TvTWW3Y++ww+/xymT4f7RtCLiIiIpIqyZcuyPInfgt2/huebb77Jm2++mRZliYiIiDyQRko5Uf36UKgQ/P03bNlidDUiIiIiIiIiIumHmlJOlCULvPWW4/WyZcbWIiIiIiIiIiKSnqgp5WQdOjj+/PpruO8JzCIiIiIiIiIimZaaUk727LPg5QV378KaNUZXIyIiIiIiIiKSPqgp5WQWy/9GS+kpfCIiIiIiIiIiDmpKpYE333Q0p378EU6dMroaERERERERERHjqSmVBooVg7p1Ha+DgoytRUREREREREQkPVBTKo3ETuFbtgzsdmNrERERERERERExmppSaaRlS3B1haNH4ZdfjK5GRERERERERMRYakqlkdy5wdfX8VoLnouIiIiIiIhIZqemVBqKncL3+ecQFWVsLSIiIiIiIiIiRlJTKg3VqweFC8OVK7Bli9HViIiIiIiIiIgYR02pNJQlC7z5puO1pvCJiIiIiIiISGamplQai53Ct349XL1qbC0iIiIiIiIiIkZRUyqNPfsseHs71pRas8boakREREREREREjKGmVBqzWP43WkpT+EREREREREQks1JTygBvvuloTu3YASdPGl2NiIiIiIiIiEjaU1PKAEWLOp7EBxAUZGwtIiIiIiIiIiJGUFPKIP+ewme3G1uLiIiIiIiIiEhaU1PKIC1bgqsrHD0Ku3YZXY2IiIiIiIiISNpSU8ogbm6OxhRowXMRERERERERyXzUlDJQ7BS+zz+HqChjaxERERERERERSUtqShmoXj0oUgT++Qc2bza6GhER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YOpU2LzZwtWrVv61hNcjSQ+ZnMGMucyYCcyZy4yZwJy5zJgJzJnLWZnM9n0SERERMZJhTano6GhGjhzJ4MGDE21IderUiTZt2tCkSRO2b98eb9/ixYu5ePEiw4YNA+DTTz8la9asKWpIZRYVK8Jzz8G+fbBqFfTubXRFIiIiIiIiIiIGNqX279/P8ePHCQgIICAgIN6+LVu2EB4ezrVr1wAoXLhwvP1ubm7kzJkzbvu6deu4cOECzz77bLzjevbsSa9evZyYImPo0MHRlFq+XE0pEREREREREUkfDGtKValShcOHDye5PzQ0NMl9ffv2jfc+JCQk1eoyo3btYPBg2LULjhwBT0+jKxIRERERERGRzE5z3TKBQoXg1Vcdr4OCjK1FRERERERERATUlMo0OnRw/Ll8OcQ8+kP4RERERERERERShZpSmUTz5pA7N5w6BTt3Gl2NiIiIiIiIiGR2akplEq6u0KqV4/Xy5cbWIiIiIiIiIiKiplQmEjuFb/VquHPH2FpEREREREREJHNTUyoTqVULiheHa9dgwwajqxERERERERGRzExNqUzExQXeesvxWlP4RERERERERMRIakplMrFT+DZtgsuXja1FRERERERERDIvNaUymQoV4LnnIDoaVq0yuhoRERERERERyazUlMqEYkdLaQqfiIiIiIiIiBhFTalMqF07sFrhl1/g8GGjqxERERERERGRzEhNqUyoUCFo2NDxOijI2FpEREREREREJHNSUyqTip3CFxQEMTHG1iIiIiIiIiIimY+aUplU8+aQOzecOgU7dhhdjYiIiIiIiIhkNmpKZVI5c0KrVo7XWvBcRERERERERNKamlKZWOwUvjVr4M4dY2sRERERERERkcxFTalMrFYtKF4crl2Dr782uhoRERERERERyUzUlMrEXFygfXvHa03hExEREREREZG0pKZUJhc7hW/zZrh82dhaRERERERERCTzUFMqkytfHp5/HqKjYdUqo6sRERERERERkcxCTSmJGy21bJmxdYiIiIiIiIhI5qGmlNCuHVitsHs3HD5sdDUiIiIiIiIikhmoKSV4eEDDho7XWvBcRERERERERNKCmlICQMeOjj+DgiAmxthaRERERERERMT81JQSAJo1A3d3OH0aduwwuhoRERERERERMbsUN6UiIiKYNGlS3PsVK1bQrFkz+vbt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0CFj6xEREfk3T09P1q9fn2D7p59+SunSpQ2oSERERCTtpfjpe7/88gshISEAbNiwgfr169OiRQsaNWoUt+i5SHrQogU0bQobN0KvXvD99/D/MyREREQM1b9/f3r37s3KlSu5d+8ePXv25MiRI1y7do158+YZXZ6IiIhImkjxSKmYmBjc3NwA2LlzJ/Xq1QMga9as3L59O3WrE3kMFgvMng05c8LWrbBihdEViYiIOLz00kts3ryZBg0a0Lp1a0qUKEGXLl0ICQmhatWqRpcnIiIikiZSPFLKy8uLuXPnkj17di5dukTt2rUB2LRpE0899VRq1yfyWJ56CkaOhBEjwM/PMXIqXz6jqxIREYFChQrRtWvXBNv3799PpUqV0r4gERERkTSW4pFSY8aM4ddff+Xbb79l6tSp5MyZk4iICAICAvD393dGjSKPZfBgKFcOLl1yNKdERETSs06dOhldgoiIiEiaSPFIqVKlSrFo0aJ42/LmzcsPP/xA9uzZU60wkdSSLRvMmwd168JHH0HnzqCZESIikl7Z7XajSxARERFJEyluSgF89tlnbNq0iXPnzmGxWChRogS+vr40a9YstesTSRV16kD79hAUBD17wi+/gNVqdFUiIiIJWfRUDhEREckkUtyUmjlzJmvXrqV58+b85z//AeDEiRNMmDCBW7du0bZt21QvUiQ1TJsGGzbAvn2OkVN9+xpdkYiIiIiIiEjmleKmVHBwMAsXLqR8+fLxtjdp0gR/f381pSTdKlQIPvgAevVyLH7u6wuHD8PPP+cjIgJq19boKRERca5Vq1Y99BibzZYGlYiIiIgYL8VNqcjISMqUKZNge8WKFbl06VKqFCXiLO+8A4sXw+7d4OkJt29bgacBKFYMAgOhZUtjaxQREfNasGDBQ4/x8PBIg0pEREREjJfiplSZMmVYu3ZtghFRwcHBlCxZMtUKE3EGqxXeeMPRlLp9O/6+c+egVStYu1aNKRERcY7Q0FCjSxARERFJN1LclBoyZAjdunVj+fLllC5dGnCsKRUeHs7s2bNTvUCR1GSzOUZDJcZuB4sFBgyA5s01lU9ERERERETEmVLclKpSpQrff/89GzZs4OzZs0RFReHr60vjxo158sknnVGjSKr58Uc4ezbp/XY7hIc7jqtdO83KEhEREREREcl0UtyUWrhwId27d6dTp07OqEfEqS5cSN3jREREREREROTRuKT0A0uXLuWff/5xRi0iTlekSOoeJyIiIiIiIiKPJsUjpbp160b//v1p0qQJTz75JNb7Ft6pWbNmqhUnktpeftnxlL1z5xxT9RJjscCBA1CzJmRJ8f9CRERERERERCQ5Uvyf3JMmTQJg9+7dCfZZLBb+/PPPx69KxEmsVsdC561aOZpPiTWm7HbHYudLlsD8+fDii2ldpYiIiIiIiIj5pbgpdejQIWfUIZJmWraEtWuhf//4i54XLw7Tp8Pff8Pw4bB/P7z0EnTrBpMmwRNPGFayiIiIiIiIiOmkaE2pqKgowsPDE2z/9ddfsdlsqVaUiLO1bAmnTkFIiI2AgBOEhNg4edIxgqpHDzh8GN5+23HsJ5+Ap6fjz5gYQ8sWERERERERMY1kN6WuXbuGr68v8+fPT7Bv/PjxdO7cmaioqBRd/Ny5c/Tu3Ztq1apRvXp1hg4dyvXr1xM9dsWKFTRs2JBKlSrRoEEDFi1aFG//vn37aNmyJT4+Prz66qt8/fXXKapFMh+rFWrXhkaNrlK7tuN9rIIF4dNPYccO8PGBf/6B7t2hRg349VejKhYRERERERExj2Q3pebMmUP+/PkZOXJkgn0rVqzAbrezcOHCFF28R48euLu7ExoaSnBwMEePHmXy5MkJjgsJCWHWrFlMnTqVffv2MXHiRAIDAwkJCQHg0qVL9OjRg44dO7J7925GjBjBggULiIiISFE9IverUQP27oUZMyB3bvj5Z6hSBfr1g2vXjK5OREREREREJONKdlNq69atjBgxAldX1wT7cubMyYgRI9iwYUOyL3z9+nW8vLzw8/MjV65cFC5cGF9fX/bs2ZPgWA8PD2bMmIGPjw8uLi5UqVKF0qVLc/ToUQBWr17Nc889R4sWLciePTu1atViw4YN5M2bN9n1iCQlSxbHwueHDkHbto4pfLNnQ9myEBSU9FP8RERERERERCRpyW5KXblyhbJlyya5v1y5cly8eDHZF3Z3d2fixIkUKFAgbtuFCxfw8PBIcKyPjw/Vq1cH4N69e2zevJnw8HDq1KkDwN69eylevDi9evXi+eefp3nz5uzcuTPZtYgkx5NPwmefQUiIoyH111/QoQPUqQMHDxpdnYiIiIiIiEjGkuyn77m6unL16lXy58+f6P5Lly6RM2fORy4kLCyMoKCgRNesijVv3jxmz55N3rx5mTRpEuXKlQPg4sWL/PHHH8yYMYNp06axdOlSevfuzTfffEOhQoWSPJ/NZnPKAu2x5zTT4u9mzASPlqt2bdi3D2bMsDBhgoXt2y1UqmSnf387o0bZcXNzUrHJpL+rjMOMmcCcucyYCcyZy9mZzPS9EhERETFasptSL730EkuWLGHQoEGJ7p8yZQovvvjiIxWxd+9eevbsiZ+fX9yIqMT06tWLbt26sWPHDoYNG0bWrFmpVasWdrudWrVqxX323XffZeXKlWzbto02bdokeb4jR448Ur3JFRYW5tTzG8GMmeDRcjVqBD4+2fjww+Js356XDz+0EBQUxaBB4dStG4HF4oRCU0B/VxmHGTOBOXOZMROYM5cZM4mIiIiYTbKbUr1796ZVq1aEh4fz1ltv8dRTT2Gz2Th27BiffvopBw4cYPXq1SkuIDQ0lCFDhjBq1ChatGjx0OOzZctG3bp1adiwIStXrqRWrVoULFgQd3f3uGNcXFx48sknuXz58gPP5enpmegaWY/LZrMRFhaGt7c31n8/0i0DM2MmePxclSpBkyawcaONAQNcOHkyG/7+pXn1VTuBgTGUKZP6NT+M/q4yDjNmAnPmMmMmMGcuZ2e6deuW03+pJSIiIpJZJLsp9dRTTxEUFMT48eNp3749lv8fBmK326latSpBQUE89dRTKbr4vn378Pf3JzAwkJo1ayZ53NixY3Fzc2Pw4MFx2ywWC1myOMovXbo0f/75Z9w+u93O+fPnKVq06AOvb7VanXoT7uzzG8GMmeDxczVrBg0awMSJMHkyfPuthWeftTJ0KAwdCo8xs/WR6e8q4zBjJjBnLjNmAnPmclYms32fRERERIyU7IXOAcqXL8/KlSv56aef+Pzzz1m1ahU///wzy5Yte+Ai6ImJjo5m5MiRDB48ONGGVKdOndi0aRMAVatWZeXKlezatQubzca+ffvYuHFj3ELnb7zxBvv37+eLL77g7t27LFq0iLt371K/fv0U1STyOHLmhPffh99/h4YNISrK8b5iRdi40ejqRERERERERNKXZI+U+rf8+fMnueB5cu3fv5/jx48TEBBAQEBAvH1btmwhPDyca9euAdCkSROuXbvGsGHD+PvvvylcuDA9evSgVatWAFSoUIHp06czffp0Ro8eTenSpfnkk0/InTv3Y9Uo8ijKlIHNm2HdOhgwAE6ehNdeg+bNITAQSpY0ukIRERERERER4z1SUyo1VKlShcOHDye5PzQ0NN77du3a0a5duySPb9iwIQ0bNky1+kQeh8UCrVo5FkN//32YMQO++gq+/RZGjQI/P8iWzegqRURERERERIyToul7IpIybm4wZQrs3w+vvAK3b8Pw4eDjA99/b3R1IiIiIiIiIsZRU0okDVSsCNu2wfLlUKgQHD4M9etDu3Zw/rzR1YmIiIiIiIikPTWlRNKIxQLt28OhQ9CnD7i4wOefQ7lyMHMmREcbXaGIiIiIiIhI2lFTSiSN5c0Ls2fD7t1QrRrcuAEDB8Lzz8POnUZXJyIiIiIiIpI21JQSMchzz8FPP8HHH0P+/PDbb1CzJnTpApcvG12diIiIiIiIiHOpKSViIBcX6N7dscZU166ObYsXQ9mysGAB2GzG1iciIiIiIiLiLGpKiaQDBQrAJ584Rk5VqgRXr0KPHvDSS7B3r9HViYiIiIiIiKQ+NaVE0pGXXnKsNRUYCO7ujtcvvAC9ezsaVSIiIon58ccfqV69OgMHDnzgcTExMcyYMYN69erxwgsv0LVrV8LDw9OoShEREZH41JQSSWeyZIF+/RxP6XvrLbDbYd48x5S+pUsd70VERGItXLiQgIAASpYs+dBjV6xYwddff83HH3/M1q1bKVWqFL1798auf1xERETEAGpKiaRTRYpAUBCEhkL58o7Fzzt3hldegbAwo6sTEZH0Inv27KxduzZZTalVq1bRuXNnSpcujZubGwMHDuT48eMcOHAgDSoVERERiU9NKZF0rk4d2L8fJk8GV1fYsQMqVwY/P7hxw+jqRETEaB07diR37twPPe7OnTscO3aMChUqxG1zc3OjZMmShOm3HSIiImKALEYXICIPly0bvPcetG0LAwdCcDBMnw6ffw4zZkDr1mCxOJ7Wt20b/PxzPiIioHZtsFoNLl5ERNKFa9euYbfbyZMnT7ztefLk4eoDFi602WzYnPA42NhzOuPcRjJjLjNmAnPmMmMmMGcuM2YCc+YyYyZwfq7knldNKZEMpEQJWLcOtmyBPn3g+HFo0wYWLoRmzWDKFDh71go8DUCxYo5F01u2NLZuERFJP1K6ftSRI0ecVImDWUdpmTGXGTOBOXOZMROYM5cZM4E5c5kxExifS00pkQyoUSP4/XfHlL6JEyEkxPF1v3PnoFUrWLtWjSkRkcwub968uLi4EBEREW97REQETzzxRJKf8/T0xNXVNdXrsdlshIWF4e3tjdVEw3rNmMuMmcCcucyYCcyZy4yZwJy5zJgJnJ/r1q1byfrFlppSIhlUjhwwZgy0awfPPgt37iQ8xm53TOsbMACaN9dUPhGRzCx79uyUKVOGgwcPUrVqVQCuX7/OmTNn8PHxSfJzVqvVqTfhzj6/UcyYy4yZwJy5zJgJzJnLjJnAnLnMmAmclyu559RC5yIZ3PnziTekYtntEB4OP/6YdjWJiEj68Ndff9GoUSPCw8MBaNeuHcuWLeP48eNERkYybdo0ypcvj7e3t8GVioiISGakkVIiGdyFC6l7nIiIZCyxDaXo6GgAQv5/PndYWBj37t3j5MmTREVFAdC2bVsuX75Mhw4duHnzJtWqVWPOnDnGFC4iIiKZnppSIhlckSLJO65gQefWISIixnjQAqXFihXj8OHDce8tFgv9+vWjX79+aVGaiIiIyANp+p5IBvfyy46n7FksDz5uxAg4dixtahIRERERERF5GDWlRDI4qxUCAx2v729Mxb53dYVffoFKleDTTx3rTImIiIiIiIgYSU0pERNo2RLWroWiReNvL1YM1q2DP/+EWrXg5k3o2hVatYIrV4ypVURERERERATUlBIxjZYt4dQpCAmxERBwgpAQGydPOraXKAHffw+TJkGWLBAcDD4+8P9r4YqIiIiIiIikOTWlREzEaoXataFRo6vUru14/+99/v7w889QtiycPw8NGoCfH9y9a1TFIiIiIiIiklmpKSWSyTz/POzbBz16ON5Pnw5Vq8LBg8bWJSIiIiIiIpmLmlIimZCrK8yfD+vXQ4EC8NtvUKUKzJ6tRdBFREREREQkbagpJZKJ/ec/EBYGjRrBnTvQrx80bQoXLxpdmYiIiIiIiJidmlIimVzhwrBpk2OUVPbssHkzeHvD118bXZmIiIiIiIiYmZpSIoLFAn36wN69jqfy/f03NGvmWHfq5k2jqxMREREREREzUlNKROJUrAi//OJ4Ih/AggWOhdH37jW2LhERERERETEfNaVEJJ7s2WHaNPjuO3jySTh8GF58ESZNApvN6OpERERERETELNSUEpFE1a/veCrf669DdDQMGwb16sGZM0ZXJiIiIiIiImagppSIJOmJJ2DNGli0CHLlgu3bHWtOff65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ERGRh1BTSkTSrZYtYe1ax+infytWzLG9ZUvH+yefdDyJ7+ef4cQJmDgRnn0WoqNh40bo0AE8PKB1a1i3Dm7fTvMoIiIiIiIich81pUQkXWvZEk6dgpAQGwEBJwgJsXHy5P8aUvd76ikYOhT274c//oAxY8DTE+7ccTSyWrVyNKg6dHA0rKKi0jKNiIiIiIiIxFJTSkTSPasVateGRo2uUru2431ylC8PY8fCoUOwbx+89x6UKAGRkY4pfa+9BoULO6b6hYaCzebEECIiIiIiIhKPmlIiYnoWC1SuDJMnO0ZdxS6KXrgwXL3qWJuqXj3HNMF+/Rz7Y2KMrlpERERERMTc1JQSkUzFYoGXXnIson727P8WRc+fH/76C2bPhho1HNMA/f3h11/Bbje6ahEREREREfNRU0pEMi2rFerWhY8/hgsX/rcoupsbnDkDU6bAc89BuXKOtan+/NPoikVERERERMxDTSkRESBbNmjSBJYtg0uXHE/pa9UKcuSAI0fg/fehQgXHU/0mTnQ85U9EREREREQenZpSIiL3yZnT8XS/NWscDarYRdGzZoXffoPhw6F0aXjxRZg5E86dM7piERERERGRjEdNKRGRB8idG956C77+Gi5edCyKXr8+uLjArl0wcCAUL+54OuBHH8Hly0ZXLCIiIiIikjGoKSUikkz580PXrvDdd47RUbGLotvtsH079OwJRYpAo0awdClcu5b0uWw22LYNtmzJx7ZtjvciIiIiIiKZiZpSIiKPoHBh6NMHduyA06dh6lR4/nlHc+mbb6BzZ/DwAF9fWLUKbt7832eDg6FUKahf38rIkU9Tv76VUqUc20VERERERDILNaVERB5TiRIweDDs2eNYFH38eMei6FFR8OWX0Lato0HVrh0MG+ZYQP3s2fjnOHfOsV2NKRERERERySzUlBIRSUVlysDIkfD77/9bFP3pp+HWLfj8c5g0yTHd736x2wYM0FQ+ERERERHJHNSUEhFxAosFvL1hwgQ4dgx++QVat37wZ+x2CA+HH39MmxpFRERERESMpKaUiIiTWSzwwguO9aWS49gx59YjIuZz7tw53nnnHapVq0adOnWYOnUqMTExCY6bPXs25cuXx9vbO97X33//bUDVIiIiktllMboAEZHMokiR5B3XqxeEhsLbb0O9euCiXx+IyEP07duXihUrEhISwpUr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\n" | |
}, | |
"metadata": {} | |
}, | |
{ | |
"output_type": "stream", | |
"name": "stdout", | |
"text": [ | |
"2025-05-05 18:14:06 [info ] \n", | |
"--- Calculating Recall@10 ---\n", | |
"2025-05-05 18:14:06 [info ] Encoding 500 documents...\n", | |
"2025-05-05 18:14:06 [info ] Document encoding took 0.00s\n", | |
"2025-05-05 18:14:06 [info ] Performing search for 500 queries...\n", | |
"2025-05-05 18:14:06 [info ] Search took 0.01s\n", | |
"2025-05-05 18:14:06 [info ] \n", | |
"Recall@10 (full corpus): 58.60%\n" | |
] | |
} | |
], | |
"source": [ | |
"\"\"\"\n", | |
"Minimal ranking demo, InfoNCE\n", | |
"--------------------\n", | |
"\n", | |
"• 500 toy queries and 500 toy docs\n", | |
"• Two-tower encoder: Embedding → mean-pool → Linear → L2-norm\n", | |
"• Trains with CrossEntropyLoss\n", | |
"• Evaluates Recall@k over the full corpus (1 doc / query)\n", | |
"\"\"\"\n", | |
"\n", | |
"import time\n", | |
"\n", | |
"import matplotlib.pyplot as plt\n", | |
"import structlog\n", | |
"import torch\n", | |
"import torch.nn as nn\n", | |
"from torch.nn.functional import normalize\n", | |
"from torch.utils.data import DataLoader, Dataset\n", | |
"\n", | |
"logger = structlog.get_logger(__name__)\n", | |
"\n", | |
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n", | |
"logger.info('Using device', device=device)\n", | |
"\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 1. Synthetic data (N queries == N docs == corpus size)\n", | |
"# ---------------------------------------------------------------------\n", | |
"def build_synthetic(\n", | |
" n_queries=500, vocab=100, q_len=4, doc_len=16, overlap=0.6, seed=42\n", | |
"):\n", | |
" rng = torch.Generator().manual_seed(seed)\n", | |
" queries = torch.randint(0, vocab, (n_queries, q_len), generator=rng)\n", | |
" docs_pos = torch.randint(0, vocab, (n_queries, doc_len), generator=rng)\n", | |
"\n", | |
" for i in range(n_queries):\n", | |
" num_copy = int(overlap * q_len)\n", | |
" copy_idx = torch.randperm(q_len, generator=rng)[:num_copy]\n", | |
" docs_pos[i, :num_copy] = queries[i, copy_idx]\n", | |
"\n", | |
" return queries, docs_pos\n", | |
"\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 2. Dataset (triples: q | pos | neg)\n", | |
"# ---------------------------------------------------------------------\n", | |
"class TripleDS(Dataset):\n", | |
" def __init__(self, q, p):\n", | |
" self.q, self.p = q, p\n", | |
"\n", | |
" def __len__(self):\n", | |
" return len(self.q)\n", | |
"\n", | |
" def __getitem__(self, i):\n", | |
" return {'q': self.q[i], 'd_pos': self.p[i]}\n", | |
"\n", | |
"\n", | |
"def collate(batch):\n", | |
" result = {}\n", | |
" for k in batch[0]:\n", | |
" result[k] = torch.stack([item[k] for item in batch]).to(device)\n", | |
" return result\n", | |
"\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 3. Two-tower model\n", | |
"# ---------------------------------------------------------------------\n", | |
"class TwoTower(nn.Module):\n", | |
" def __init__(self, vocab, emb_dim=36, proj_dim=18):\n", | |
" super().__init__()\n", | |
" self.embedding = nn.Embedding(vocab, emb_dim)\n", | |
" self.proj = nn.Linear(emb_dim, proj_dim, bias=False)\n", | |
"\n", | |
" def encode(self, toks):\n", | |
" # Ensure input tensors are on the correct device within the model\n", | |
" toks = toks.to(next(self.parameters()).device)\n", | |
" x = self.embedding(toks).mean(dim=1) # (B, emb_dim), mean-pooling\n", | |
" return normalize(self.proj(x), dim=-1) # (B, proj_dim) ‖·‖₂ = 1\n", | |
"\n", | |
" def forward(self, q, d):\n", | |
" qv, dv = self.encode(q), self.encode(d)\n", | |
" return (qv * dv).sum(dim=-1) # cosine similarity\n", | |
"\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 4. Train\n", | |
"# ---------------------------------------------------------------------\n", | |
"\n", | |
"# --- Hyperparameters ---\n", | |
"n_queries = 500\n", | |
"vocab = 50\n", | |
"q_len = 16\n", | |
"overlap = 0.8\n", | |
"seed = 1337\n", | |
"batch_size = 16\n", | |
"margin = 0.25\n", | |
"lr = 3e-4\n", | |
"epochs = 10\n", | |
"emb_dim = 48\n", | |
"proj_dim = 72\n", | |
"\n", | |
"logger.info('Building Synthetic Data')\n", | |
"queries, docs_pos = build_synthetic(n_queries, vocab, q_len, doc_len, overlap, seed)\n", | |
"\n", | |
"logger.info('Creating DataLoader')\n", | |
"train_dataset = TripleDS(queries, docs_pos)\n", | |
"loader = DataLoader(\n", | |
" train_dataset,\n", | |
" batch_size=batch_size,\n", | |
" shuffle=True,\n", | |
" collate_fn=collate, # to handle device placement\n", | |
")\n", | |
"\n", | |
"logger.info('Initializing Model and Optimizer')\n", | |
"model = TwoTower(vocab=vocab, emb_dim=emb_dim, proj_dim=proj_dim).to(device)\n", | |
"opt = torch.optim.AdamW(model.parameters(), lr=lr)\n", | |
"loss_fn = nn.CrossEntropyLoss()\n", | |
"\n", | |
"# ---> Lists to store metrics for plotting <---\n", | |
"loss_history = []\n", | |
"lr_history = []\n", | |
"epoch_times = []\n", | |
"\n", | |
"logger.info('Starting Training')\n", | |
"training_start_time = time.time()\n", | |
"\n", | |
"for epoch in range(epochs):\n", | |
" epoch_start_time = time.time()\n", | |
" model.train()\n", | |
" total_loss = 0.0\n", | |
"\n", | |
" current_lr = opt.param_groups[0]['lr']\n", | |
" lr_history.append(current_lr)\n", | |
"\n", | |
" for batch in loader:\n", | |
" q_vec = model.encode(batch['q'])\n", | |
" d_vec = model.encode(batch['d_pos'])\n", | |
"\n", | |
" logits = q_vec @ d_vec.T\n", | |
" labels = torch.arange(logits.size(0), device=logits.device)\n", | |
" loss = loss_fn(logits, labels)\n", | |
"\n", | |
" opt.zero_grad()\n", | |
" loss.backward()\n", | |
" opt.step()\n", | |
"\n", | |
" total_loss += loss.item()\n", | |
"\n", | |
" avg_loss = total_loss / len(loader)\n", | |
" loss_history.append(avg_loss)\n", | |
"\n", | |
" epoch_end_time = time.time()\n", | |
" epoch_duration = epoch_end_time - epoch_start_time\n", | |
" epoch_times.append(epoch_duration)\n", | |
"\n", | |
" logger.info(\n", | |
" 'epoch_summary',\n", | |
" epoch=epoch + 1,\n", | |
" loss=avg_loss,\n", | |
" lr=current_lr,\n", | |
" time=epoch_duration,\n", | |
" )\n", | |
"\n", | |
"training_end_time = time.time()\n", | |
"total_training_time = training_end_time - training_start_time\n", | |
"logger.info('Training Finished', total_training_time=total_training_time)\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 5. Quick diagnostics: accuracy + histograms + LR Plot\n", | |
"# ---------------------------------------------------------------------\n", | |
"logger.info('Running Diagnostics')\n", | |
"model.eval()\n", | |
"queries_dev = queries.to(device)\n", | |
"docs_pos_dev = docs_pos.to(device)\n", | |
"\n", | |
"# --- Plotting Section ---\n", | |
"plt.style.use('seaborn-v0_8-whitegrid')\n", | |
"\n", | |
"# Figure 1: Loss and Learning Rate\n", | |
"plt.figure(figsize=(12, 5))\n", | |
"\n", | |
"# Subplot 1: Loss Curve\n", | |
"plt.subplot(1, 2, 1)\n", | |
"plt.plot(range(1, epochs + 1), loss_history, marker='o', linestyle='-', color='b')\n", | |
"plt.title('Training Loss per Epoch')\n", | |
"plt.xlabel('Epoch')\n", | |
"plt.ylabel('Cross Entropy Loss')\n", | |
"plt.xticks(range(1, epochs + 1, max(1, epochs // 10))) # Adjust x-ticks for readability\n", | |
"plt.grid(True)\n", | |
"\n", | |
"# Subplot 2: Learning Rate Schedule\n", | |
"plt.subplot(1, 2, 2)\n", | |
"plt.plot(range(1, epochs + 1), lr_history, marker='.', linestyle='-', color='r')\n", | |
"plt.title('Learning Rate Schedule')\n", | |
"plt.xlabel('Epoch')\n", | |
"plt.ylabel('Learning Rate')\n", | |
"plt.xticks(range(1, epochs + 1, max(1, epochs // 10)))\n", | |
"plt.ylim(bottom=0)\n", | |
"plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))\n", | |
"plt.grid(True)\n", | |
"\n", | |
"plt.tight_layout()\n", | |
"plt.show()\n", | |
"\n", | |
"\n", | |
"# ---------------------------------------------------------------------\n", | |
"# 6. Recall@k *against the entire corpus*\n", | |
"# ---------------------------------------------------------------------\n", | |
"@torch.no_grad()\n", | |
"def recall_at_k_corpus(model, q, docs, k=10, batch_size_eval=256):\n", | |
" \"\"\"\n", | |
" Each query has exactly ONE true doc, aligned by row index.\n", | |
" Searches the whole corpus (size == len(docs)).\n", | |
" \"\"\"\n", | |
" model.eval()\n", | |
" q_dev = q.to(device)\n", | |
" docs_dev = docs.to(device)\n", | |
"\n", | |
" logger.info(f'\\n--- Calculating Recall@{k} ---')\n", | |
" logger.info(f'Encoding {len(docs_dev)} documents...')\n", | |
" doc_vecs = []\n", | |
" encode_start = time.time()\n", | |
" for i in range(0, len(docs_dev), batch_size_eval):\n", | |
" batch_docs = docs_dev[i : i + batch_size_eval]\n", | |
" doc_vecs.append(model.encode(batch_docs))\n", | |
" doc_vecs = torch.cat(doc_vecs, 0) # (M, dim)\n", | |
" doc_mat = doc_vecs.t() # (dim, M) transpose for efficient matmul\n", | |
" encode_end = time.time()\n", | |
" logger.info(f'Document encoding took {encode_end - encode_start:.2f}s')\n", | |
"\n", | |
" logger.info(f'Performing search for {len(q_dev)} queries...')\n", | |
" hits = 0\n", | |
" search_start = time.time()\n", | |
" for i in range(0, len(q_dev), batch_size_eval):\n", | |
" batch_q = q_dev[i : i + batch_size_eval]\n", | |
" qv = model.encode(batch_q) # (B, dim)\n", | |
" # Calculate similarity scores (dot product since vectors are normalized)\n", | |
" sim = qv @ doc_mat # (B, M)\n", | |
" # Get top K indices for each query\n", | |
" topk_indices = sim.topk(k, dim=1).indices # (B, k)\n", | |
" # Create target indices corresponding to the\n", | |
" # true positive document for each query in the batch\n", | |
" # The true positive doc for query `j` (absolute index) is `docs[j]`,\n", | |
" # so the target index in the `doc_mat` is `j`.\n", | |
" # For a batch starting at index `i`, the targets are `i, i+1, ..., i+B-1`.\n", | |
" target_indices = torch.arange(i, i + qv.size(0), device=device).unsqueeze(\n", | |
" 1\n", | |
" ) # (B, 1)\n", | |
" # Check if the target index is present in the top K indices for each query\n", | |
" # Broadcasting (topk_indices == target_indices) compares each element of\n", | |
" # topk_indices with the target_index for that row\n", | |
" hits += (topk_indices == target_indices).any(dim=1).sum().item()\n", | |
" search_end = time.time()\n", | |
" logger.info(f'Search took {search_end - search_start:.2f}s')\n", | |
"\n", | |
" recall = hits / len(q_dev)\n", | |
" return recall\n", | |
"\n", | |
"\n", | |
"# Use the device-specific tensors for recall calculation\n", | |
"rec10 = recall_at_k_corpus(\n", | |
" model, queries_dev, docs_pos_dev, k=10, batch_size_eval=64\n", | |
")\n", | |
"logger.info(f'\\nRecall@10 (full corpus): {rec10:.2%}')" | |
] | |
} | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment