File size: 4,498 Bytes
3ec0250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ab2314f
 
3ec0250
ab2314f
 
3ec0250
ab2314f
3ec0250
 
 
 
 
 
 
 
 
 
ab2314f
3ec0250
ab2314f
3ec0250
 
 
 
 
 
 
ab2314f
3ec0250
ab2314f
 
3ec0250
 
 
 
 
 
ab2314f
3ec0250
 
 
 
 
 
ab2314f
 
 
3ec0250
 
 
 
ab2314f
 
 
3ec0250
 
 
ab2314f
 
3ec0250
 
 
ab2314f
3ec0250
ab2314f
 
 
 
 
 
 
3ec0250
 
ab2314f
3ec0250
 
ab2314f
 
 
 
 
3ec0250
ab2314f
3ec0250
 
 
 
ab2314f
3ec0250
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: sans-serif; margin: 0; padding: 6px; font-size: 14px; color: #333; }
    #status { color: #666; margin-bottom: 4px; }
    #progress { width: 100%; height: 6px; display: none; margin-bottom: 4px; }
    #error { color: #c00; line-height: 1.5; }
  </style>
</head>
<body>
  <div id="status"></div>
  <progress id="progress" max="100" value="0"></progress>
  <div id="error"></div>

  <script type="module">
    const MODEL_REPO = "juanbascur/specter2-proximity-onnx";
    const BATCH_SIZE = 64;

    // ── Streamlit component API ──────────────────────────────────────────────
    function send(type, extra = {}) {
      window.parent.postMessage({ isStreamlitMessage: true, type, ...extra }, "*");
    }
    const setValue  = v => send("streamlit:setComponentValue", { value: v, dataType: "json" });
    const setHeight = h => send("streamlit:setFrameHeight", { height: h });

    send("streamlit:componentReady", { apiVersion: 1 });
    setHeight(20);

    window.addEventListener("message", async (event) => {
      if (event.data.type !== "streamlit:render") return;
      const { papers, run } = event.data.args;
      if (run) await runEmbeddings(papers);
    });

    // ── Main ─────────────────────────────────────────────────────────────────
    async function runEmbeddings(papers) {
      const statusEl   = document.getElementById("status");
      const progressEl = document.getElementById("progress");
      const errorEl    = document.getElementById("error");
      errorEl.textContent = "";

      if (!navigator.gpu) {
        errorEl.innerHTML =
          "It seems your browser does not support WebGPU. " +
          "Chrome, Edge and Opera support it by default. " +
          "Firefox and Safari need additional configuration. " +
          "Alternatively, run the embeddings on the web server " +
          "by checking <b>Use fallback resources</b>, but this takes much longer.";
        setHeight(90);
        setValue({ error: "webgpu_not_supported" });
        return;
      }

      try {
        progressEl.style.display = "block";
        setHeight(50);
        statusEl.textContent = "Loading model… (first run may take a minute)";

        const { AutoTokenizer, AutoModel } = await import(
          "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3"
        );

        const tokenizer = await AutoTokenizer.from_pretrained(MODEL_REPO);
        statusEl.textContent = "Tokenizer ready, loading model weights…";

        const model = await AutoModel.from_pretrained(MODEL_REPO, {
          device: "webgpu",
          dtype:  "fp16",
        });

        statusEl.textContent = "Model ready, starting encoding…";

        const total  = papers.length;
        const allCLS = new Float32Array(total * 768);

        for (let start = 0; start < total; start += BATCH_SIZE) {
          const batch  = papers.slice(start, start + BATCH_SIZE);
          const texts  = batch.map(p => p.title + " [SEP] " + (p.abstract || ""));
          const inputs = tokenizer(texts, { padding: true, truncation: true, max_length: 512 });
          const output = await model(inputs);

          const hs        = output.last_hidden_state;
          const [B, L, H] = hs.dims;
          const data      = hs.data;
          for (let b = 0; b < B; b++)
            allCLS.set(data.slice(b * L * H, b * L * H + H), (start + b) * 768);

          const done           = Math.min(start + BATCH_SIZE, total);
          progressEl.value     = (done / total) * 100;
          statusEl.textContent = `Encoding ${done} / ${total}…`;
        }

        progressEl.value     = 100;
        statusEl.textContent = "Done.";

        // Encode as base64 in chunks to avoid call stack overflow
        const bytes = new Uint8Array(allCLS.buffer);
        let binary  = "";
        for (let i = 0; i < bytes.length; i += 8192)
          binary += String.fromCharCode(...bytes.subarray(i, i + 8192));

        setValue({ embeddings_b64: btoa(binary), n_papers: total, n_dims: 768 });

      } catch (err) {
        errorEl.textContent = "Error: " + err.message;
        setHeight(60);
        setValue({ error: err.message });
      }
    }
  </script>
</body>
</html>