| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>ROCmPort AI</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link |
| href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Space+Grotesk:wght@500;600;700&display=swap" |
| rel="stylesheet"> |
| <style> |
| :root { |
| --bg: #030303; |
| --s1: #0a0a0b; |
| --s2: #121214; |
| --s3: #1a1a1e; |
| --b1: rgba(255, 255, 255, 0.08); |
| --b2: rgba(255, 255, 255, 0.15); |
| --red: #ff3344; |
| --red-glow: rgba(255, 51, 68, 0.4); |
| --green: #00ff88; |
| --green-glow: rgba(0, 255, 136, 0.4); |
| --yellow: #ffcc00; |
| --cyan: #00d9ff; |
| --muted: #88888e; |
| --t1: #a1a1aa; |
| --t2: #d4d4d8; |
| --t3: #ffffff; |
| --mono: 'JetBrains Mono', monospace; |
| --sans: 'Space Grotesk', sans-serif; |
| --spring: cubic-bezier(0.34, 1.56, 0.64, 1); |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| cursor: none !important; |
| } |
| |
| .hide { |
| display: none !important; |
| } |
| |
| body { |
| background: var(--bg); |
| color: var(--t1); |
| font-family: var(--sans); |
| font-size: 14px; |
| line-height: 1.6; |
| overflow-x: hidden; |
| min-height: 100vh; |
| } |
| |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| background: |
| radial-gradient(circle at 20% 30%, rgba(0, 217, 255, 0.05), transparent 40%), |
| radial-gradient(circle at 80% 70%, rgba(255, 51, 68, 0.05), transparent 40%), |
| radial-gradient(circle at 50% 50%, rgba(0, 255, 136, 0.03), transparent 60%); |
| z-index: -1; |
| animation: bgMove 20s ease-in-out infinite alternate; |
| } |
| |
| @keyframes bgMove { |
| 0% { |
| transform: scale(1) translate(0, 0); |
| } |
| |
| 50% { |
| transform: scale(1.1) translate(20px, -20px); |
| } |
| |
| 100% { |
| transform: scale(1) translate(-20px, 20px); |
| } |
| } |
| |
| .w { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 32px 24px; |
| position: relative; |
| } |
| |
| |
| .w::after { |
| content: ''; |
| position: absolute; |
| inset: 0; |
| background: radial-gradient(circle at 50% 0%, rgba(255, 51, 68, 0.08), transparent 70%); |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| header { |
| padding-bottom: 24px; |
| border-bottom: 1px solid var(--b1); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-bottom: 24px; |
| } |
| |
| .logo { |
| font-weight: 700; |
| font-size: 18px; |
| color: var(--t3); |
| letter-spacing: -0.02em; |
| } |
| |
| .logo em { |
| font-style: normal; |
| color: var(--red); |
| text-shadow: 0 0 15px var(--red-glow); |
| } |
| |
| .hr { |
| font-size: 12px; |
| color: var(--muted); |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| background: var(--s1); |
| padding: 6px 12px; |
| border-radius: 20px; |
| border: 1px solid var(--b1); |
| } |
| |
| .hd { |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| background: var(--green); |
| box-shadow: 0 0 10px var(--green-glow); |
| } |
| |
| .hd.on { |
| animation: pulse 2s ease-in-out infinite; |
| } |
| |
| @keyframes pulse { |
| |
| 0%, |
| 100% { |
| opacity: 1; |
| transform: scale(1); |
| } |
| |
| 50% { |
| opacity: 0.4; |
| transform: scale(0.8); |
| } |
| } |
| |
| .g { |
| display: grid; |
| grid-template-columns: 1.2fr 0.8fr; |
| gap: 24px; |
| padding: 0; |
| } |
| |
| .fs { |
| grid-column: 1 / -1; |
| } |
| |
| @media (max-width: 900px) { |
| .g { |
| grid-template-columns: 1fr; |
| } |
| } |
| |
| |
| .p { |
| background: var(--s1); |
| border: 1px solid var(--b1); |
| border-radius: 12px; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); |
| backdrop-filter: blur(10px); |
| transition: transform 0.3s var(--spring), border-color 0.3s ease; |
| } |
| |
| .p:hover { |
| border-color: var(--b2); |
| } |
| |
| .ph { |
| padding: 12px 16px; |
| border-bottom: 1px solid var(--b1); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| font-size: 12px; |
| color: var(--muted); |
| background: rgba(255, 255, 255, 0.02); |
| } |
| |
| .ph b { |
| color: var(--red); |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| textarea.code { |
| width: 100%; |
| flex: 1; |
| min-height: 300px; |
| background: var(--bg); |
| border: none; |
| color: var(--t2); |
| font-family: var(--mono); |
| font-size: 13px; |
| line-height: 1.7; |
| padding: 20px; |
| resize: vertical; |
| outline: none; |
| caret-color: var(--red); |
| will-change: transform; |
| } |
| |
| .db { |
| padding: 12px 16px; |
| border-top: 1px solid var(--b1); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| background: var(--s1); |
| } |
| |
| .db .l { |
| font-size: 11px; |
| color: var(--muted); |
| font-weight: 500; |
| } |
| |
| .ch { |
| font-family: var(--sans); |
| font-size: 11px; |
| padding: 4px 12px; |
| background: var(--s2); |
| border: 1px solid var(--b1); |
| border-radius: 6px; |
| color: var(--t1); |
| cursor: pointer; |
| transition: all 0.2s var(--spring); |
| } |
| |
| .ch:hover { |
| background: var(--s3); |
| color: var(--t3); |
| transform: translateY(-1px); |
| border-color: var(--b2); |
| } |
| |
| .ch.on { |
| background: var(--red); |
| border-color: var(--red); |
| color: #fff; |
| box-shadow: 0 0 15px var(--red-glow); |
| } |
| |
| .bg { |
| margin: 16px; |
| padding: 14px; |
| background: var(--red); |
| border: none; |
| border-radius: 8px; |
| color: #fff; |
| font-family: var(--sans); |
| font-size: 14px; |
| font-weight: 700; |
| cursor: pointer; |
| transition: all 0.3s var(--spring); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| box-shadow: 0 4px 15px var(--red-glow); |
| } |
| |
| .bg:hover { |
| background: #ff4d5a; |
| transform: translateY(-2px); |
| box-shadow: 0 6px 20px var(--red-glow); |
| } |
| |
| .bg:active { |
| transform: translateY(0); |
| } |
| |
| .bg:disabled { |
| opacity: 0.4; |
| cursor: not-allowed; |
| transform: none; |
| box-shadow: none; |
| } |
| |
| |
| .al { |
| padding: 12px; |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .ar { |
| padding: 12px 16px; |
| border-radius: 8px; |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid transparent; |
| transition: all 0.4s var(--spring); |
| animation: slideIn 0.5s var(--spring) forwards; |
| opacity: 0; |
| transform: translateX(20px); |
| } |
| |
| @keyframes slideIn { |
| to { |
| opacity: 1; |
| transform: translateX(0); |
| } |
| } |
| |
| .ar.run { |
| border-color: var(--cyan); |
| background: rgba(0, 217, 255, 0.05); |
| } |
| |
| .ar.done { |
| border-color: var(--green); |
| background: rgba(0, 255, 136, 0.05); |
| } |
| |
| .ar.fail { |
| border-color: var(--red); |
| background: rgba(255, 51, 68, 0.05); |
| } |
| |
| .ar.retry { |
| border-color: var(--yellow); |
| background: rgba(255, 204, 0, 0.05); |
| animation: pulse-border 1.5s ease-in-out infinite; |
| } |
| |
| @keyframes pulse-border { |
| 50% { |
| border-color: rgba(255, 204, 0, 0.2); |
| } |
| } |
| |
| .at { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| .an { |
| font-size: 10px; |
| font-weight: 700; |
| color: var(--muted); |
| min-width: 90px; |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| } |
| |
| .am { |
| font-size: 13px; |
| color: var(--t2); |
| font-weight: 500; |
| } |
| |
| .ad { |
| font-size: 11px; |
| color: var(--muted); |
| margin-top: 4px; |
| padding-left: 102px; |
| white-space: pre-wrap; |
| line-height: 1.6; |
| max-height: 100px; |
| overflow-y: auto; |
| } |
| |
| .ad .w { |
| color: var(--yellow); |
| font-weight: 600; |
| } |
| |
| .ad .g { |
| color: var(--green); |
| font-weight: 600; |
| } |
| |
| |
| .timeline { |
| display: flex; |
| justify-content: space-between; |
| padding: 16px 20px; |
| background: rgba(255, 255, 255, 0.02); |
| border-bottom: 1px solid var(--b1); |
| margin-bottom: 8px; |
| } |
| |
| .node { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 6px; |
| position: relative; |
| flex: 1; |
| } |
| |
| .node::after { |
| content: ''; |
| position: absolute; |
| top: 12px; |
| left: 50%; |
| width: 100%; |
| height: 2px; |
| background: var(--b1); |
| z-index: 0; |
| } |
| |
| .node:last-child::after { |
| display: none; |
| } |
| |
| .ni { |
| width: 24px; |
| height: 24px; |
| border-radius: 50%; |
| background: var(--s3); |
| border: 2px solid var(--b1); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 12px; |
| z-index: 1; |
| transition: all 0.4s var(--spring); |
| } |
| |
| .node.on .ni { |
| background: var(--cyan); |
| border-color: var(--cyan); |
| color: #000; |
| box-shadow: 0 0 15px var(--cyan); |
| } |
| |
| .node.done .ni { |
| background: var(--green); |
| border-color: var(--green); |
| color: #000; |
| box-shadow: 0 0 15px var(--green); |
| } |
| |
| .node.fail .ni { |
| background: var(--red); |
| border-color: var(--red); |
| color: #fff; |
| } |
| |
| .node.retry .ni { |
| animation: pulse-node 1s var(--spring) infinite; |
| background: var(--yellow); |
| border-color: var(--yellow); |
| } |
| |
| @keyframes pulse-node { |
| |
| 0%, |
| 100% { |
| transform: scale(1); |
| } |
| |
| 50% { |
| transform: scale(1.2); |
| } |
| } |
| |
| .nl { |
| font-size: 9px; |
| font-weight: 700; |
| color: var(--muted); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .node.on .nl, |
| .node.done .nl { |
| color: var(--t3); |
| } |
| |
| |
| .tabs { |
| display: flex; |
| gap: 8px; |
| } |
| |
| .tab { |
| background: var(--s2); |
| border: 1px solid var(--b1); |
| padding: 6px 16px; |
| border-radius: 8px; |
| font-family: var(--sans); |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--muted); |
| cursor: pointer; |
| transition: all 0.2s var(--spring); |
| } |
| |
| .tab:hover { |
| color: var(--t2); |
| background: var(--s3); |
| } |
| |
| .tab.on { |
| color: var(--t3); |
| background: var(--red); |
| border-color: var(--red); |
| box-shadow: 0 0 10px var(--red-glow); |
| } |
| |
| .tc { |
| display: none; |
| padding: 0; |
| animation: fadeIn 0.4s ease; |
| } |
| |
| .tc.on { |
| display: block; |
| } |
| |
| @keyframes fadeIn { |
| from { |
| opacity: 0; |
| transform: translateY(10px); |
| } |
| |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| |
| .sum-row { |
| padding: 24px; |
| display: flex; |
| align-items: center; |
| gap: 32px; |
| flex-wrap: wrap; |
| border-bottom: 1px solid var(--b1); |
| background: rgba(0, 255, 136, 0.02); |
| } |
| |
| .sum-big { |
| font-size: 32px; |
| font-weight: 800; |
| color: var(--green); |
| line-height: 1; |
| letter-spacing: -0.02em; |
| text-shadow: 0 0 20px var(--green-glow); |
| } |
| |
| .sum-big .u { |
| font-size: 13px; |
| font-weight: 500; |
| color: var(--muted); |
| margin-left: 4px; |
| display: block; |
| margin-top: 4px; |
| letter-spacing: 0; |
| } |
| |
| .sum-big .vic { |
| font-size: 11px; |
| color: var(--cyan); |
| font-weight: 600; |
| display: block; |
| margin-top: 8px; |
| text-shadow: none; |
| opacity: 0.8; |
| } |
| |
| .sum-sep { |
| width: 1px; |
| height: 40px; |
| background: var(--b1); |
| } |
| |
| .sum-chk { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 12px; |
| color: var(--t2); |
| font-weight: 500; |
| } |
| |
| .sum-dot { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| flex-shrink: 0; |
| } |
| |
| .sum-dot.ok { |
| background: var(--green); |
| box-shadow: 0 0 8px var(--green-glow); |
| } |
| |
| .sum-dot.no { |
| background: var(--red); |
| box-shadow: 0 0 8px var(--red-glow); |
| } |
| |
| .sum-dot.na { |
| background: var(--muted); |
| box-shadow: none; |
| } |
| |
| .sum-type { |
| font-size: 11px; |
| color: var(--cyan); |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| font-weight: 700; |
| padding: 4px 10px; |
| background: rgba(0, 217, 255, 0.1); |
| border-radius: 4px; |
| } |
| |
| .sum-bar { |
| padding: 16px 24px; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| flex-wrap: wrap; |
| border-bottom: 1px solid var(--b1); |
| } |
| |
| .bs { |
| font-family: var(--sans); |
| font-size: 11px; |
| font-weight: 700; |
| padding: 8px 16px; |
| border-radius: 8px; |
| border: 1px solid var(--b1); |
| background: var(--s2); |
| color: var(--t2); |
| cursor: pointer; |
| transition: all 0.2s var(--spring); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .bs:hover { |
| border-color: var(--b2); |
| transform: translateY(-1px); |
| background: var(--s3); |
| } |
| |
| .bs.r { |
| background: var(--bg); |
| border-color: var(--red); |
| color: var(--red); |
| } |
| |
| .bs.r:hover { |
| background: var(--red); |
| color: #fff; |
| box-shadow: 0 4px 15px var(--red-glow); |
| } |
| |
| .bs.gr { |
| background: var(--green); |
| border-color: var(--green); |
| color: #000; |
| } |
| |
| .bs.gr:hover { |
| box-shadow: 0 4px 15px var(--green-glow); |
| transform: translateY(-2px); |
| } |
| |
| .sp { |
| flex: 1; |
| } |
| |
| |
| .dm { |
| display: grid; |
| grid-template-columns: repeat(5, 1fr); |
| border-bottom: 1px solid var(--b1); |
| } |
| |
| @media (max-width: 800px) { |
| .dm { |
| grid-template-columns: repeat(2, 1fr); |
| } |
| } |
| |
| .di { |
| padding: 20px; |
| border-right: 1px solid var(--b1); |
| background: rgba(255, 255, 255, 0.01); |
| } |
| |
| .di:last-child { |
| border-right: none; |
| } |
| |
| .dl { |
| font-size: 10px; |
| color: var(--muted); |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| margin-bottom: 8px; |
| font-weight: 700; |
| } |
| |
| .dv { |
| font-size: 20px; |
| font-weight: 800; |
| line-height: 1; |
| margin-bottom: 4px; |
| color: var(--t3); |
| } |
| |
| .dv.g { |
| color: var(--green); |
| } |
| |
| .dv.c { |
| color: var(--cyan); |
| } |
| |
| .dv.y { |
| color: var(--yellow); |
| } |
| |
| .dv.t { |
| color: var(--t2); |
| font-size: 13px; |
| } |
| |
| .ds { |
| font-size: 10px; |
| color: var(--muted); |
| line-height: 1.4; |
| } |
| |
| |
| .bk { |
| padding: 24px; |
| border-bottom: 1px solid var(--b1); |
| } |
| |
| .bk-t { |
| font-size: 11px; |
| color: var(--muted); |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| margin-bottom: 16px; |
| font-weight: 700; |
| } |
| |
| .br { |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| margin-bottom: 12px; |
| } |
| |
| .br:last-child { |
| margin-bottom: 0; |
| } |
| |
| .bl { |
| font-size: 12px; |
| color: var(--t2); |
| width: 140px; |
| flex-shrink: 0; |
| font-weight: 500; |
| } |
| |
| .bt { |
| flex: 1; |
| height: 8px; |
| background: var(--bg); |
| border-radius: 4px; |
| overflow: hidden; |
| border: 1px solid var(--b1); |
| } |
| |
| .bf { |
| height: 100%; |
| border-radius: 4px; |
| transition: width 1s var(--spring); |
| width: 0; |
| } |
| |
| .bf.bad { |
| background: linear-gradient(90deg, #ff334466, #ff3344); |
| box-shadow: 0 0 10px rgba(255, 51, 68, 0.3); |
| } |
| |
| .bf.good { |
| background: linear-gradient(90deg, #00ff8866, #00ff88); |
| box-shadow: 0 0 10px rgba(0, 255, 136, 0.3); |
| } |
| |
| .bv { |
| font-size: 12px; |
| font-weight: 700; |
| width: 40px; |
| text-align: right; |
| flex-shrink: 0; |
| } |
| |
| .bv.bad { |
| color: var(--red); |
| } |
| |
| .bv.good { |
| color: var(--green); |
| } |
| |
| |
| .sn { |
| padding: 20px; |
| border: 1px solid var(--cyan); |
| border-radius: 12px; |
| background: rgba(0, 217, 255, 0.05); |
| margin: 24px; |
| font-size: 13px; |
| color: var(--t2); |
| line-height: 1.6; |
| border-left-width: 4px; |
| } |
| |
| |
| .dg { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| background: var(--bg); |
| } |
| |
| .dfs { |
| min-width: 0; |
| } |
| |
| @media (max-width: 780px) { |
| .dg { |
| grid-template-columns: 1fr; |
| } |
| |
| .dfs:first-child { |
| border-right: none !important; |
| border-bottom: 1px solid var(--b1); |
| } |
| } |
| |
| .dfs:first-child { |
| border-right: 1px solid var(--b1); |
| } |
| |
| .dfh { |
| padding: 10px 16px; |
| border-bottom: 1px solid var(--b1); |
| font-size: 11px; |
| color: var(--muted); |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-weight: 600; |
| background: var(--s2); |
| } |
| |
| .dft { |
| font-size: 9px; |
| font-weight: 800; |
| padding: 2px 6px; |
| border-radius: 4px; |
| text-transform: uppercase; |
| } |
| |
| .dft.cu { |
| background: rgba(255, 51, 68, 0.2); |
| color: var(--red); |
| } |
| |
| .dft.ro { |
| background: rgba(0, 255, 136, 0.2); |
| color: var(--green); |
| } |
| |
| .dfp { |
| padding: 20px; |
| font-family: var(--mono); |
| font-size: 12px; |
| line-height: 1.7; |
| overflow: auto; |
| max-height: min(70vh, 760px); |
| white-space: pre-wrap; |
| overflow-wrap: anywhere; |
| word-break: break-word; |
| tab-size: 2; |
| color: var(--t2); |
| } |
| |
| .dlo { |
| background: rgba(255, 51, 68, 0.08); |
| color: var(--red); |
| display: block; |
| border-left: 2px solid rgba(255, 51, 68, 0.45); |
| padding-left: 8px; |
| } |
| |
| .dln { |
| background: rgba(0, 255, 136, 0.08); |
| color: var(--green); |
| display: block; |
| border-left: 2px solid rgba(0, 255, 136, 0.45); |
| padding-left: 8px; |
| } |
| |
| |
| .skeleton { |
| position: relative; |
| overflow: hidden; |
| background: var(--s2); |
| border-radius: 12px; |
| height: 200px; |
| margin-top: 24px; |
| } |
| |
| .skeleton::after { |
| content: ''; |
| position: absolute; |
| inset: 0; |
| transform: translateX(-100%); |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent); |
| animation: shimmer 1.5s infinite; |
| } |
| |
| @keyframes shimmer { |
| 100% { |
| transform: translateX(100%); |
| } |
| } |
| |
| |
| #cursor { |
| position: fixed; |
| width: 20px; |
| height: 20px; |
| background: rgba(255, 255, 255, 0.2); |
| border: 1px solid rgba(255, 255, 255, 0.4); |
| border-radius: 50%; |
| pointer-events: none; |
| z-index: 9999; |
| transition: transform 0.1s ease, width 0.3s var(--spring), height 0.3s var(--spring), background 0.3s ease; |
| mix-blend-mode: difference; |
| } |
| |
| #cursor.active { |
| transform: scale(3); |
| background: rgba(255, 51, 68, 0.3); |
| border-color: var(--red); |
| } |
| |
| |
| .mo { |
| display: none; |
| position: fixed; |
| inset: 0; |
| background: rgba(0, 0, 0, 0.85); |
| z-index: 1000; |
| place-items: center; |
| backdrop-filter: blur(8px); |
| } |
| |
| .mo.open { |
| display: grid; |
| } |
| |
| .mb { |
| background: var(--s1); |
| border: 1px solid var(--b1); |
| border-radius: 16px; |
| width: 90%; |
| max-width: 800px; |
| max-height: 90vh; |
| overflow: hidden; |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6); |
| } |
| |
| .mt { |
| padding: 16px 24px; |
| border-bottom: 1px solid var(--b1); |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: var(--s2); |
| } |
| |
| .mt h3 { |
| font-size: 16px; |
| color: var(--t3); |
| font-weight: 700; |
| } |
| |
| .mx { |
| background: none; |
| border: none; |
| color: var(--muted); |
| font-size: 24px; |
| cursor: pointer !important; |
| line-height: 1; |
| transition: color 0.2s; |
| } |
| |
| .mx:hover { |
| color: var(--t3); |
| } |
| |
| .mc { |
| padding: 24px; |
| } |
| |
| .mc textarea { |
| width: 100%; |
| height: 400px; |
| background: var(--bg); |
| border: 1px solid var(--b1); |
| border-radius: 8px; |
| padding: 16px; |
| color: var(--cyan); |
| font-family: var(--mono); |
| font-size: 12px; |
| line-height: 1.6; |
| resize: vertical; |
| outline: none; |
| } |
| |
| .mc textarea:focus { |
| border-color: var(--cyan); |
| box-shadow: 0 0 10px rgba(0, 217, 255, 0.2); |
| } |
| |
| .mf { |
| padding: 16px 24px; |
| border-top: 1px solid var(--b1); |
| display: flex; |
| justify-content: flex-end; |
| gap: 12px; |
| background: var(--s2); |
| } |
| |
| ::-webkit-scrollbar { |
| width: 6px; |
| height: 6px; |
| } |
| |
| ::-webkit-scrollbar-track { |
| background: transparent; |
| } |
| |
| ::-webkit-scrollbar-thumb { |
| background: var(--b1); |
| border-radius: 10px; |
| } |
| |
| ::-webkit-scrollbar-thumb:hover { |
| background: var(--b2); |
| } |
| |
| footer { |
| padding: 32px 0; |
| border-top: 1px solid var(--b1); |
| display: flex; |
| justify-content: space-between; |
| font-size: 11px; |
| color: var(--muted); |
| font-weight: 500; |
| } |
| |
| footer a { |
| color: var(--muted); |
| text-decoration: none; |
| transition: color 0.2s; |
| border-bottom: 1px solid transparent; |
| } |
| |
| footer a:hover { |
| color: var(--t2); |
| border-bottom-color: var(--muted); |
| } |
| |
| .idle { |
| flex: 1; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: var(--b2); |
| font-size: 13px; |
| font-weight: 500; |
| min-height: 100px; |
| } |
| |
| |
| .ds-badge { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| font-size: 10px; |
| font-weight: 800; |
| letter-spacing: 0.08em; |
| text-transform: uppercase; |
| padding: 4px 10px; |
| border-radius: 4px; |
| margin-left: 12px; |
| vertical-align: middle; |
| } |
| .ds-badge.real { |
| background: rgba(0,255,136,0.15); |
| color: var(--green); |
| border: 1px solid rgba(0,255,136,0.3); |
| } |
| .ds-badge.demo { |
| background: rgba(255,204,0,0.12); |
| color: var(--yellow); |
| border: 1px solid rgba(255,204,0,0.3); |
| } |
| .ds-badge.sim { |
| background: rgba(255,255,255,0.06); |
| color: var(--muted); |
| border: 1px solid var(--b1); |
| } |
| |
| |
| .risk-panel { |
| margin: 0 24px 24px; |
| border-radius: 10px; |
| overflow: hidden; |
| border: 1px solid var(--b1); |
| } |
| .risk-header { |
| background: rgba(255,255,255,0.03); |
| padding: 10px 16px; |
| font-size: 11px; |
| font-weight: 700; |
| color: var(--muted); |
| text-transform: uppercase; |
| letter-spacing: 0.08em; |
| border-bottom: 1px solid var(--b1); |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| .risk-badge { |
| font-size: 9px; |
| font-weight: 800; |
| padding: 2px 6px; |
| border-radius: 3px; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| .risk-badge.crit { background: rgba(255,51,68,0.2); color: var(--red); } |
| .risk-badge.high { background: rgba(255,153,0,0.2); color: #ff9900; } |
| .risk-badge.med { background: rgba(255,204,0,0.2); color: var(--yellow); } |
| .risk-row { |
| padding: 12px 16px; |
| border-bottom: 1px solid rgba(255,255,255,0.04); |
| display: grid; |
| grid-template-columns: 70px 1fr auto; |
| gap: 12px; |
| align-items: start; |
| font-size: 12px; |
| transition: background 0.2s; |
| } |
| .risk-row:last-child { border-bottom: none; } |
| .risk-row:hover { background: rgba(255,255,255,0.02); } |
| .risk-loc { |
| font-family: var(--mono); |
| font-size: 11px; |
| color: var(--muted); |
| padding-top: 1px; |
| } |
| .risk-desc { color: var(--t2); line-height: 1.5; } |
| .risk-hint { |
| font-size: 10px; |
| color: var(--cyan); |
| margin-top: 4px; |
| line-height: 1.4; |
| } |
| </style> |
| </head> |
| <div id="cursor"></div> |
|
|
| <div class="w"> |
| <header> |
| <div class="logo">ROCmPort <em>AI</em></div> |
| <div class="hr"> |
| <div class="hd on" id="hdot"></div> |
| <span id="hstat">Ready</span> |
| </div> |
| </header> |
|
|
| <div class="g"> |
| <div class="p"> |
| <div class="ph"> |
| <div><b>//</b> CUDA source</div> |
| <div id="lc">0 lines</div> |
| </div> |
| <textarea class="code" id="inp" spellcheck="false" placeholder="// Paste CUDA code here |
| // or pick a demo below |
| |
| __global__ void kernel(float* A, float* B, int N) { |
| int idx = blockIdx.x * blockDim.x + threadIdx.x; |
| ... |
| }"></textarea> |
| <div class="db"> |
| <span class="l">Select a template:</span> |
| <button class="ch" onclick="lk('vector_add', this)">Vector addition</button> |
| <button class="ch" onclick="lk('matrix_multiply', this)">Matrix multiplication</button> |
| <button class="ch" onclick="lk('convolution_2d', this)">2D convolution</button> |
| <button class="ch" onclick="lk('reduction', this)">Parallel reduction</button> |
| </div> |
| <button class="bg" id="go" onclick="go()">Port to ROCm</button> |
| </div> |
|
|
| <div class="p"> |
| <div class="ph"> |
| <div><b>//</b> Pipeline</div> |
| <div id="pt">0.0s</div> |
| </div> |
| <div class="timeline" id="tl"> |
| |
| </div> |
| <div class="al" id="al"> |
| <div class="idle">Paste CUDA code to begin migration</div> |
| </div> |
| </div> |
|
|
| <div class="p fs hide" id="rp"> |
| <div class="ph"> |
| <div style="display:flex;align-items:center;gap:12px"><b>//</b> Results</div> |
| <div class="tabs" id="tabs"> |
| <button class="tab on" onclick="stab('sum',this)">Summary</button> |
| <button class="tab" onclick="stab('diff',this)">Visual Diff</button> |
| <button class="tab" onclick="stab('det',this)">Performance</button> |
| </div> |
| </div> |
| <div id="t-loader" class="hide"> |
| <div class="skeleton"></div> |
| </div> |
| <div id="t-sum" class="tc on"></div> |
| <div id="t-diff" class="tc"></div> |
| <div id="t-det" class="tc"> |
| </div> |
| </div> |
| </div> |
|
|
| <footer> |
| <div>ROCmPort AI</div> |
| <div><a href="https://x.com/TazwarEnan" target="_blank">Tazwar Ahnaf Enan</a> · <a |
| href="https://github.com/tazwaryayyyy" target="_blank">GitHub</a></div> |
| </footer> |
| </div> |
|
|
| <div class="mo" id="modal"> |
| <div class="mb"> |
| <div class="mt"> |
| <h3>Edit ROCm code</h3><button class="mx" onclick="cm()">×</button> |
| </div> |
| <div class="mc"><textarea id="edt"></textarea></div> |
| <div class="mf"><button class="bs" onclick="cm()">Cancel</button><button class="bs r" |
| onclick="rec()">Re-test</button></div> |
| </div> |
| </div> |
| <script> |
| const API = window.location.protocol === 'file:' |
| ? 'http://localhost:8000' |
| : window.location.origin; |
| const S = { code: '', kn: 'custom', run: false, t0: null, iv: null, rep: null, tl: [], kernels: {} }; |
| const AG = { |
| analyzer: { n: 'ANALYZER', i: '🔍' }, |
| translator: { n: 'TRANSLATOR', i: '🔄' }, |
| optimizer: { n: 'OPTIMIZER', i: '⚡' }, |
| tester: { n: 'TESTER', i: '🧪' }, |
| coordinator: { n: 'COORDINATOR', i: '📋' } |
| }; |
| |
| |
| const cur = document.getElementById('cursor'); |
| document.addEventListener('mousemove', (e) => { |
| cur.style.left = e.clientX + 'px'; |
| cur.style.top = e.clientY + 'px'; |
| const target = e.target; |
| const isClickable = target.onclick || |
| target.tagName === 'BUTTON' || |
| target.tagName === 'A' || |
| target.tagName === 'TEXTAREA' || |
| target.classList.contains('ch') || |
| target.classList.contains('tab'); |
| |
| if (isClickable) { |
| cur.classList.add('active'); |
| if (target.id === 'go') cur.style.background = 'rgba(255, 51, 68, 0.5)'; |
| else cur.style.background = 'rgba(255, 255, 255, 0.3)'; |
| } else { |
| cur.classList.remove('active'); |
| cur.style.background = 'rgba(255, 255, 255, 0.2)'; |
| } |
| }); |
| |
| async function init() { |
| const ta = document.getElementById('inp'); |
| ta.oninput = () => { |
| document.getElementById('lc').textContent = ta.value.split('\n').length + ' lines'; |
| S.code = ta.value; |
| }; |
| try { |
| const r = await fetch(API + '/demo-kernels'); |
| S.kernels = await r.json(); |
| } catch (e) { S.kernels = FB; } |
| } |
| |
| function lk(n, btn) { |
| document.querySelectorAll('.ch').forEach(c => c.classList.remove('on')); |
| btn.classList.add('on'); |
| const code = S.kernels[n] || FB[n] || '', ta = document.getElementById('inp'); |
| ta.value = code; S.code = code; S.kn = n; |
| document.getElementById('lc').textContent = code.split('\n').length + ' lines'; |
| } |
| |
| function stab(id, btn) { |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('on')); |
| document.querySelectorAll('.tc').forEach(t => t.classList.remove('on')); |
| btn.classList.add('on'); |
| document.getElementById('t-' + id).classList.add('on'); |
| if (id === 'diff' && S.rep) rDiff(S.code, S.rep.optimized_code); |
| } |
| |
| async function go() { |
| if (S.run) return; |
| const code = document.getElementById('inp').value.trim(); |
| if (!code) return; |
| |
| S.code = code; S.run = true; S.t0 = Date.now(); S.tl = []; |
| const btn = document.getElementById('go'); |
| btn.disabled = true; |
| btn.textContent = 'Running pipeline...'; |
| |
| document.getElementById('hstat').textContent = 'Pipeline running...'; |
| document.getElementById('rp').classList.add('hide'); |
| |
| bLog(); |
| sTimer(); |
| |
| try { |
| const simpleModeCheckbox = document.getElementById('sm'); |
| const res = await fetch(API + '/port', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| cuda_code: code, |
| kernel_name: S.kn, |
| simple_mode: simpleModeCheckbox ? simpleModeCheckbox.checked : false |
| }) |
| }); |
| |
| |
| document.getElementById('rp').classList.remove('hide'); |
| document.getElementById('t-loader').classList.remove('hide'); |
| document.getElementById('t-sum').classList.remove('on'); |
| document.getElementById('t-diff').classList.remove('on'); |
| document.getElementById('t-det').classList.remove('on'); |
| |
| const rd = res.body.getReader(), dc = new TextDecoder(); |
| let buf = ''; |
| while (true) { |
| const { done, value } = await rd.read(); |
| if (done) break; |
| buf += dc.decode(value, { stream: true }); |
| const lines = buf.split('\n'); |
| buf = lines.pop(); |
| for (const ln of lines) { |
| if (!ln.startsWith('data: ')) continue; |
| const raw = ln.slice(6).trim(); |
| if (raw === '[DONE]') { done_(); break; } |
| try { hEvt(JSON.parse(raw)); } catch (e) { console.error('Parse error:', e); } |
| } |
| } |
| } catch (e) { |
| document.getElementById('hstat').textContent = 'Pipeline error'; |
| document.getElementById('t-loader').classList.add('hide'); |
| console.error(e); |
| } finally { |
| xTimer(); |
| S.run = false; |
| btn.disabled = false; |
| btn.textContent = 'Port to ROCm'; |
| document.getElementById('t-loader').classList.add('hide'); |
| } |
| } |
| |
| function hEvt(ev) { |
| uLog(ev.agent, ev.status, ev.message, ev.detail); |
| if (ev.agent === 'tester' && (ev.status === 'done' || ev.status === 'failed')) { |
| const m = ev.message.match(/([\d.]+)x/); |
| if (m) { |
| const sp = parseFloat(m[1]), ok = sp >= 1, im = ev.message.match(/Iteration (\d+)/i); |
| S.tl.push({ |
| label: 'Iteration ' + (im ? im[1] : S.tl.length + 1) + (ok ? ' (optimized)' : ' (baseline)'), |
| speedup: sp, |
| good: ok |
| }); |
| } |
| } |
| if (ev.agent === 'coordinator' && ev.status === 'done' && ev.detail) { |
| try { |
| const r = JSON.parse(ev.detail); |
| S.rep = r; |
| rRes(r, S.tl); |
| } catch (e) { console.error('Coordinator detail parse error:', e); } |
| } |
| } |
| |
| function done_() { |
| document.getElementById('hstat').textContent = 'Pipeline complete'; |
| document.getElementById('t-loader').classList.add('hide'); |
| if (!S.rep) { |
| document.getElementById('t-sum').innerHTML = '<div class="idle">Migration finished but no report was generated. Check agent logs for details.</div>'; |
| document.getElementById('t-sum').classList.add('on'); |
| } |
| } |
| |
| function bLog() { |
| const el = document.getElementById('al'); |
| const tl = document.getElementById('tl'); |
| el.innerHTML = ''; |
| tl.innerHTML = ''; |
| |
| let i = 0; |
| for (const [k, obj] of Object.entries(AG)) { |
| |
| const d = document.createElement('div'); |
| d.className = 'ar'; |
| d.id = 'ar-' + k; |
| d.style.animationDelay = (i * 0.1) + 's'; |
| d.innerHTML = ` |
| <div class="at"> |
| <span class="an">${obj.n}</span> |
| <span class="am" id="am-${k}">Waiting</span> |
| </div> |
| <div class="ad" id="ad-${k}"></div>`; |
| el.appendChild(d); |
| |
| |
| const n = document.createElement('div'); |
| n.className = 'node'; |
| n.id = 'nd-' + k; |
| n.title = obj.n; |
| n.innerHTML = `<div class="ni">${obj.i}</div><div class="nl">${obj.n.slice(0, 3)}</div>`; |
| tl.appendChild(n); |
| i++; |
| } |
| } |
| |
| function uLog(a, s, m, d) { |
| const row = document.getElementById('ar-' + a); |
| const node = document.getElementById('nd-' + a); |
| if (!row || !node) return; |
| |
| const statusClass = { running: 'run', done: 'done', failed: 'fail', retrying: 'retry' }[s] || ''; |
| row.className = 'ar ' + statusClass; |
| node.className = 'node ' + (s === 'running' ? 'on' : s === 'retrying' ? 'retry' : s === 'done' ? 'done' : s === 'failed' ? 'fail' : ''); |
| |
| const me = document.getElementById('am-' + a); |
| if (me) me.textContent = m; |
| |
| |
| node.title = m; |
| |
| const de = document.getElementById('ad-' + a); |
| if (de && d) { |
| de.innerHTML = esc(d) |
| .replace(/\u26a0\ufe0f([^\n]*)/g, '<span class="w">⚠️ $1</span>') |
| .replace(/\u2705([^\n]*)/g, '<span class="g">✅ $1</span>'); |
| de.scrollTop = de.scrollHeight; |
| } |
| } |
| |
| function rRes(r, tl) { |
| |
| document.getElementById('t-loader').classList.add('hide'); |
| document.getElementById('t-sum').classList.add('on'); |
| |
| const v = r.verification || {}, bw = r.bandwidth_utilized; |
| const dot = ok => `<div class="sum-dot ${ok === true ? 'ok' : ok === false ? 'no' : 'na'}"></div>`; |
| |
| |
| const ds = r.data_source || 'simulated'; |
| const dsBadge = ds === 'real_rocm' |
| ? `<span class="ds-badge real">🟢 LIVE MI300X</span>` |
| : ds === 'demo_artifact' |
| ? `<span class="ds-badge demo">🟡 DEMO DATA</span>` |
| : `<span class="ds-badge sim">⚪ SIMULATED</span>`; |
| |
| document.getElementById('t-sum').innerHTML = ` |
| <div class="sum-row"> |
| <div class="sum-big"> |
| ${r.speedup}x |
| ${dsBadge} |
| <span class="u">vs baseline hipify</span> |
| <span class="vic">Measured against declared baseline. ${ds === 'demo_artifact' ? 'Representative MI300X values — set ROCM_AVAILABLE=true for real numbers.' : ds === 'real_rocm' ? 'Real rocprof measurement on AMD MI300X.' : 'Set ROCM_AVAILABLE=true on AMD Cloud for real numbers.'}</span> |
| </div> |
| <div class="sum-sep"></div> |
| <div> |
| <div class="sum-chk">${dot(v.compiled_successfully)} Compiled${v.mock_mode ? ' (simulated)' : ''}</div> |
| <div class="sum-chk" style="margin-top:8px">${dot(v.executed_without_error)} Executed without error</div> |
| <div class="sum-chk" style="margin-top:8px">${dot(v.output_matches_expected)} Output matches expected</div> |
| </div> |
| <div class="sum-sep"></div> |
| <div class="sum-type">${(r.bottleneck || 'optimized').toLowerCase()}</div> |
| </div> |
| <div class="sum-bar"> |
| <button class="bs r" onclick="om()">Edit code</button> |
| <button class="bs gr" onclick="exM()">Export PR</button> |
| <button class="bs" onclick="dlR()">Download report</button> |
| <div class="sp"></div> |
| </div> |
| <div class="sn" id="sn" style="margin: 24px; border-left-width: 4px;"> |
| <div style="font-weight: bold; margin-bottom: 8px; color: var(--cyan);">🧠 Simple explanation</div> |
| ${r.simplified_explanation ? esc(r.simplified_explanation) : '<em>Simplified explanation will appear here</em>'} |
| </div> |
| ${riskMatrix(r.static_risk_report)}`; |
| |
| |
| let dh = `<div class="dm"> |
| <div class="di"><div class="dl">Speedup</div><div class="dv g">${r.speedup}x</div><div class="ds">optimized ROCm vs straight hipify output</div></div> |
| <div class="di"><div class="dl">Bandwidth</div><div class="dv c">${bw != null ? bw.toFixed(1) : '—'}%</div><div class="ds">of MI300X 5.3 TB/s HBM3</div></div> |
| <div class="di"><div class="dl">Changes</div><div class="dv y">${r.total_changes}</div><div class="ds">hipify + LLM + optimizer changes</div></div> |
| <div class="di"><div class="dl">Iterations</div><div class="dv c">${r.iterations || 1}</div><div class="ds">optimizer retry loop count</div></div> |
| <div class="di"><div class="dl">Type</div><div class="dv t">${(r.bottleneck || '—').toUpperCase()}</div><div class="ds">workload classification</div></div> |
| </div>`; |
| |
| if (tl.length) { |
| dh += '<div class="bk"><div class="bk-t">Benchmark iterations (optimized vs baseline hipify)</div>'; |
| tl.forEach(d => { |
| const pct = Math.min(Math.max((d.speedup / 2) * 100, 3), 95); |
| dh += `<div class="br"> |
| <div class="bl">${esc(d.label)}</div> |
| <div class="bt"><div class="bf ${d.good ? 'good' : 'bad'}" style="width: 0" data-w="${pct}%"></div></div> |
| <div class="bv ${d.good ? 'good' : 'bad'}">${d.speedup}x</div> |
| </div>`; |
| }); |
| dh += '</div>'; |
| } |
| |
| document.getElementById('t-det').innerHTML = dh; |
| tsm(); |
| |
| |
| setTimeout(() => { |
| document.querySelectorAll('.bf[data-w]').forEach(b => { |
| b.style.width = b.dataset.w; |
| }); |
| }, 100); |
| } |
| |
| function riskMatrix(srr) { |
| if (!srr || !srr.items || srr.items.length === 0) return ''; |
| |
| const levelClass = { CRITICAL: 'crit', HIGH: 'high', MEDIUM: 'med' }; |
| const critical = srr.critical_count || 0; |
| const high = srr.high_count || 0; |
| const medium = srr.medium_count || 0; |
| |
| let rows = srr.items.map(item => { |
| const cls = levelClass[item.risk_level] || 'med'; |
| const loc = item.line ? `line ${item.line}` : '—'; |
| return `<div class="risk-row"> |
| <div class="risk-loc">${esc(loc)}</div> |
| <div> |
| <div class="risk-desc">${esc(item.description)}</div> |
| <div class="risk-hint">Fix: ${esc(item.amd_fix_hint)}</div> |
| </div> |
| <div><span class="risk-badge ${cls}">${esc(item.risk_level)}</span></div> |
| </div>`; |
| }).join(''); |
| |
| const scanMs = srr.scan_duration_ms != null ? `${srr.scan_duration_ms.toFixed(1)}ms` : ''; |
| |
| return `<div class="risk-panel"> |
| <div class="risk-header"> |
| ⚠️ Static Risk Scan |
| ${critical > 0 ? `<span class="risk-badge crit">${critical} CRITICAL</span>` : ''} |
| ${high > 0 ? `<span class="risk-badge high">${high} HIGH</span>` : ''} |
| ${medium > 0 ? `<span class="risk-badge med">${medium} MEDIUM</span>` : ''} |
| <span style="margin-left:auto;font-size:9px;opacity:0.5">Pure-Python pre-scan · ${scanMs}</span> |
| </div> |
| ${rows} |
| </div>`; |
| } |
| |
| function rDiff(o, n) { |
| if (!o || !n) return; |
| document.getElementById('t-diff').innerHTML = `<div class="dg"> |
| <div class="dfs"><div class="dfh"><span class="dft cu">CUDA</span> Original Source</div><pre class="dfp" id="d-o"></pre></div> |
| <div class="dfs"><div class="dfh"><span class="dft ro">ROCm</span> Optimized HIP</div><pre class="dfp" id="d-n"></pre></div> |
| </div>`; |
| |
| const oL = o.split('\n'), nL = n.split('\n'), mx = Math.max(oL.length, nL.length); |
| let oH = '', nH = ''; |
| for (let i = 0; i < mx; i++) { |
| const a = oL[i] ?? '', b = nL[i] ?? '', c = a !== b; |
| oH += `<span class="${c ? 'dlo' : ''}">${esc(a)}\n</span>`; |
| nH += `<span class="${c ? 'dln' : ''}">${esc(b)}\n</span>`; |
| } |
| const left = document.getElementById('d-o'); |
| const right = document.getElementById('d-n'); |
| left.innerHTML = oH; |
| right.innerHTML = nH; |
| |
| |
| let syncing = false; |
| left.addEventListener('scroll', () => { |
| if (syncing) return; |
| syncing = true; |
| right.scrollTop = left.scrollTop; |
| syncing = false; |
| }, { passive: true }); |
| right.addEventListener('scroll', () => { |
| if (syncing) return; |
| syncing = true; |
| left.scrollTop = right.scrollTop; |
| syncing = false; |
| }, { passive: true }); |
| } |
| |
| function sTimer() { S.iv = setInterval(() => { document.getElementById('pt').textContent = ((Date.now() - S.t0) / 1000).toFixed(1) + 's' }, 100) } |
| function xTimer() { clearInterval(S.iv) } |
| |
| function dlR() { |
| const r = S.rep; if (!r) return; |
| const md = `# ROCmPort AI — Migration Report\n\n## Results\n- **Speedup**: ${r.speedup}x\n- **Bandwidth**: ${r.bandwidth_utilized ? r.bandwidth_utilized.toFixed(1) : '—'}%\n- **Changes**: ${r.total_changes}\n- **Iterations**: ${r.iterations}\n- **Type**: ${r.bottleneck}\n\n${r.amd_advantage_explanation ? '> ' + r.amd_advantage_explanation + '\n\n' : ''}${r.cost_estimate ? '## Cost Impact\n- Manual: ' + r.cost_estimate.manual_porting_weeks + '\n- ROCmPort: ' + r.cost_estimate.rocmport_minutes + '\n- Savings: ' + r.cost_estimate.estimated_savings + '\n\n' : ''}## ROCm/HIP Code\n\`\`\`cpp\n${r.optimized_code || ''}\n\`\`\`\n\n---\n*Generated by ROCmPort AI*\n`; |
| const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([md], { type: 'text/markdown' })); a.download = 'rocmport-migration-report.md'; a.click(); |
| } |
| |
| function om() { if (!S.rep) return alert('No results yet!'); document.getElementById('edt').value = S.rep?.optimized_code || ''; document.getElementById('modal').classList.add('open') } |
| function cm() { document.getElementById('modal').classList.remove('open') } |
| |
| async function rec() { |
| const code = document.getElementById('edt').value.trim(); if (!code) return; |
| try { |
| const res = await fetch(API + '/recompile', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ edited_code: code, kernel_name: S.kn }) }); |
| const r = await res.json(); |
| if (r.success) { cm(); if (r.result) rRes(r.result, S.tl); } |
| else alert('Failed: ' + (r.detail || 'Unknown')) |
| } catch (e) { alert('Error: ' + e.message) } |
| } |
| |
| async function exM() { |
| if (!S.rep) return; |
| try { |
| const currentInput = document.getElementById('inp')?.value || ''; |
| const payload = { |
| original_cuda: S.code || currentInput, |
| final_rocm: S.rep.optimized_code || '', |
| migration_report: S.rep |
| }; |
| const res = await fetch(API + '/export', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload) |
| }); |
| |
| if (!res.ok) { |
| let msg = `Export failed (${res.status})`; |
| try { |
| const err = await res.json(); |
| if (err && err.detail) msg = err.detail; |
| } catch (_) { } |
| throw new Error(msg); |
| } |
| |
| const a = document.createElement('a'); |
| a.href = URL.createObjectURL(await res.blob()); |
| a.download = 'rocmport-migration.zip'; |
| a.click(); |
| } catch (e) { |
| alert('Export error: ' + (e.message || 'Unknown error')); |
| } |
| } |
| |
| function tsm() { |
| const sn = document.getElementById('sn'); |
| if (sn) sn.classList.remove('hide'); |
| } |
| |
| function esc(s) { return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') } |
| |
| const FB = { |
| vector_add: `#include <cuda_runtime.h>\n\n__global__ void vector_add_kernel(float* A, float* B, float* C, int N) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx < N) {\n C[idx] = A[idx] + B[idx];\n }\n}\n\nint main() {\n int N = 1 << 24;\n size_t size = N * sizeof(float);\n float *d_A, *d_B, *d_C;\n cudaMalloc(&d_A, size);\n cudaMalloc(&d_B, size);\n cudaMalloc(&d_C, size);\n int threads = 128;\n int blocks = (N + threads - 1) / threads;\n vector_add_kernel<<<blocks, threads>>>(d_A, d_B, d_C, N);\n cudaDeviceSynchronize();\n cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);\n return 0;\n}`, |
| matrix_multiply: `#include <cuda_runtime.h>\n#define WARP_SIZE 32\n\n__global__ void matmul_kernel(float* A, float* B, float* C, int N) {\n int row = blockIdx.y * blockDim.y + threadIdx.y;\n int col = blockIdx.x * blockDim.x + threadIdx.x;\n float sum = 0.0f;\n if (row < N && col < N) {\n for (int k = 0; k < N; k++)\n sum += A[row * N + k] * B[k * N + col];\n C[row * N + col] = sum;\n }\n}\n\n__global__ void warp_reduce(float* data, float* result, int N) {\n int tid = threadIdx.x;\n extern __shared__ float sdata[];\n sdata[tid] = (tid < N) ? data[tid] : 0;\n __syncthreads();\n for (int s = WARP_SIZE/2; s > 0; s >>= 1) {\n if (tid < s) sdata[tid] += sdata[tid + s];\n __syncthreads();\n }\n if (tid == 0) result[blockIdx.x] = sdata[0];\n}\n\nint main() {\n int N = 1024;\n size_t size = N * N * sizeof(float);\n float *d_A, *d_B, *d_C;\n cudaMalloc(&d_A, size);\n cudaMalloc(&d_B, size);\n cudaMalloc(&d_C, size);\n dim3 block(16, 16);\n dim3 grid((N+15)/16, (N+15)/16);\n matmul_kernel<<<grid, block>>>(d_A, d_B, d_C, N);\n cudaDeviceSynchronize();\n cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);\n return 0;\n}`, |
| convolution_2d: `#include <cuda_runtime.h>\n#define BLOCK_SIZE 16\n\n__global__ void conv2d_kernel(\n float* input, float* kernel, float* output,\n int width, int height\n) {\n int x = blockIdx.x * blockDim.x + threadIdx.x;\n int y = blockIdx.y * blockDim.y + threadIdx.y;\n if (x >= width || y >= height) return;\n float sum = 0.0f;\n for (int ky = -1; ky <= 1; ky++) {\n for (int kx = -1; kx <= 1; kx++) {\n int ix = x + kx, iy = y + ky;\n if (ix >= 0 && ix < width && iy >= 0 && iy < height)\n sum += input[iy * width + ix] * kernel[(ky+1)*3 + (kx+1)];\n }\n }\n output[y * width + x] = sum;\n}\n\nint main() {\n int W = 2048, H = 2048;\n float *d_in, *d_ker, *d_out;\n cudaMalloc(&d_in, W*H*sizeof(float));\n cudaMalloc(&d_ker, 9*sizeof(float));\n cudaMalloc(&d_out, W*H*sizeof(float));\n dim3 block(BLOCK_SIZE, BLOCK_SIZE);\n dim3 grid((W+BLOCK_SIZE-1)/BLOCK_SIZE, (H+BLOCK_SIZE-1)/BLOCK_SIZE);\n conv2d_kernel<<<grid, block>>>(d_in, d_ker, d_out, W, H);\n cudaDeviceSynchronize();\n cudaFree(d_in); cudaFree(d_ker); cudaFree(d_out);\n return 0;\n}`, |
| reduction: `#include <cuda_runtime.h>\n#include <stdio.h>\n#include <iostream>\n#include <vector>\n#include <numeric>\n\n// Tree-based reduction kernel\n__global__ void reduction_kernel(float* g_idata, float* g_odata, unsigned int n) {\n extern __shared__ float sdata[];\n unsigned int tid = threadIdx.x;\n unsigned int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;\n\n float mySum = (i < n) ? g_idata[i] : 0;\n if (i + blockDim.x < n) mySum += g_idata[i + blockDim.x];\n sdata[tid] = mySum;\n __syncthreads();\n\n for (unsigned int s = blockDim.x / 2; s > 32; s >>= 1) {\n if (tid < s) sdata[tid] = mySum = mySum + sdata[tid + s];\n __syncthreads();\n }\n\n // DELIBERATE WARP-SIZE BUG: Unroll to 32 instead of 64\n if (tid < 32) {\n volatile float* vsmem = sdata;\n vsmem[tid] = mySum = mySum + vsmem[tid + 32];\n vsmem[tid] = mySum = mySum + vsmem[tid + 16];\n vsmem[tid] = mySum = mySum + vsmem[tid + 8];\n vsmem[tid] = mySum = mySum + vsmem[tid + 4];\n vsmem[tid] = mySum = mySum + vsmem[tid + 2];\n vsmem[tid] = mySum = mySum + vsmem[tid + 1];\n }\n\n if (tid == 0) g_odata[blockIdx.x] = sdata[0];\n}\n\nint main() {\n const int N = 1048576;\n // ... Host code for Parallel Reduction demo\n printf("Parallel Reduction demo loaded.\\n");\n return 0;\n}` |
| }; |
| |
| init(); |
| </script> |
| </body> |
|
|
| </html> |