tazwarrrr commited on
Commit
485813e
·
1 Parent(s): bb9523d

react add

Browse files
.gitignore CHANGED
@@ -25,6 +25,11 @@ backend/.env
25
  mock_rocprof_output.json
26
  *.db
27
 
 
 
 
 
 
28
  # OS junk
29
  .DS_Store
30
  Thumbs.db
 
25
  mock_rocprof_output.json
26
  *.db
27
 
28
+ # Node
29
+ node_modules/
30
+ frontend/dist/
31
+ frontend/.env.local
32
+
33
  # OS junk
34
  .DS_Store
35
  Thumbs.db
README.md CHANGED
@@ -259,14 +259,13 @@ This is the gap between "it compiles" and "it is correct."
259
 
260
  All demo kernels migrated, compiled, and profiled on real MI300X hardware (AMD DevCloud, ROCm 7.2, gfx942).
261
 
262
- | Kernel | Total Changes | Critical AMD Bugs Found | Status |
263
- |--------|--------------|------------------------|--------|
264
- | reduction | 9 | warp-32 final stage (silent wrong results) | Compiled |
265
- | vector_add | 7 | threadIdx%32 wavefront mismatch | Compiled |
266
- | matrix_multiply | 11 | warp-32 + LDS bank conflicts | Compiled |
267
- | convolution_2d | 13 | warp-32 + LDS padding | ✅ Compiled |
268
-
269
- `data_source: real_rocm` — verified on AMD DevCloud MI300X instance.
270
 
271
  ## License
272
 
 
259
 
260
  All demo kernels migrated, compiled, and profiled on real MI300X hardware (AMD DevCloud, ROCm 7.2, gfx942).
261
 
262
+ | Kernel | Input | Baseline HIP | Optimized HIP | Speedup |
263
+ |--------|-------|-------------|---------------|---------|
264
+ | matrix_multiply | 512x512 fp32 | 0.068ms | 0.026ms | 2.61x |
265
+ | reduction | 16M elements | | 0.019ms | PASS (correct) |
266
+ | vector_add | 32M elements | | 0.099ms | 4077.6 GB/s |
267
+
268
+ Hardware: AMD Instinct MI300X VF (gfx942), 192GB HBM3, ROCm 7.2
 
269
 
270
  ## License
271
 
backend/graph/__init__.py ADDED
File without changes
backend/graph/pipeline.py ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pylint: disable=broad-exception-caught
2
+ import asyncio
3
+ import json
4
+ from typing import Literal
5
+
6
+ from langgraph.graph import StateGraph, END
7
+
8
+ from backend.graph.state import MigrationState
9
+
10
+ # ─── Node implementations ──────────────────────────────────────────────────────
11
+
12
+
13
+ async def analyze_node(state: MigrationState) -> dict:
14
+ from backend.agents import analyzer as analyzer_agent
15
+
16
+ new_events = [
17
+ {
18
+ "agent": "analyzer",
19
+ "status": "running",
20
+ "message": "Scanning CUDA code for kernels, APIs, and hardware-specific issues...",
21
+ "detail": None,
22
+ "iteration": state.get("iteration", 0),
23
+ }
24
+ ]
25
+
26
+ try:
27
+ result = await asyncio.to_thread(analyzer_agent.run, state["cuda_code"])
28
+ except Exception as exc:
29
+ new_events.append(
30
+ {
31
+ "agent": "analyzer",
32
+ "status": "failed",
33
+ "message": "Analysis failed",
34
+ "detail": str(exc),
35
+ "iteration": state.get("iteration", 0),
36
+ }
37
+ )
38
+ return {"analyzer_result": None, "events": new_events, "migration_success": False}
39
+
40
+ detail_parts = [
41
+ f"Found {len(result.kernels_found)} kernel(s): {', '.join(result.kernels_found)}",
42
+ f"Workload: {result.workload_type.value}",
43
+ f"Difficulty: {result.difficulty} - {result.difficulty_reason}",
44
+ ]
45
+ if result.warp_size_issue:
46
+ detail_parts.append(f"WARP SIZE ISSUE: {result.warp_size_detail}")
47
+ if result.sharding_detected:
48
+ detail_parts.append(
49
+ "Multi-GPU sharding detected; review if needed on MI300X memory capacity."
50
+ )
51
+ if result.prediction:
52
+ detail_parts.append(result.prediction)
53
+
54
+ new_events.append(
55
+ {
56
+ "agent": "analyzer",
57
+ "status": "done",
58
+ "message": (
59
+ f"Found {len(result.kernels_found)} kernel(s) | "
60
+ f"{result.workload_type.value} workload | Difficulty: {result.difficulty}"
61
+ ),
62
+ "detail": "\n".join(detail_parts),
63
+ "iteration": state.get("iteration", 0),
64
+ }
65
+ )
66
+
67
+ return {"analyzer_result": result, "events": new_events}
68
+
69
+
70
+ async def translate_node(state: MigrationState) -> dict:
71
+ from backend.agents import translator as translator_agent
72
+
73
+ new_events = [
74
+ {
75
+ "agent": "translator",
76
+ "status": "running",
77
+ "message": "Running hipify-clang (pass 1) then LLM correction (pass 2)...",
78
+ "detail": None,
79
+ "iteration": state.get("iteration", 0),
80
+ }
81
+ ]
82
+
83
+ analyzer_result = state.get("analyzer_result")
84
+ if analyzer_result is None:
85
+ new_events.append(
86
+ {
87
+ "agent": "translator",
88
+ "status": "failed",
89
+ "message": "Translation skipped — analysis did not complete",
90
+ "detail": None,
91
+ "iteration": state.get("iteration", 0),
92
+ }
93
+ )
94
+ return {"translator_result": None, "events": new_events}
95
+
96
+ try:
97
+ result = await asyncio.to_thread(
98
+ translator_agent.run, state["cuda_code"], analyzer_result
99
+ )
100
+ except Exception as exc:
101
+ new_events.append(
102
+ {
103
+ "agent": "translator",
104
+ "status": "failed",
105
+ "message": "Translation failed",
106
+ "detail": str(exc),
107
+ "iteration": state.get("iteration", 0),
108
+ }
109
+ )
110
+ return {"translator_result": None, "events": new_events}
111
+
112
+ new_events.append(
113
+ {
114
+ "agent": "translator",
115
+ "status": "done",
116
+ "message": (
117
+ f"{result.total_changes} changes "
118
+ f"({result.hipify_changes} hipify + {result.llm_changes} LLM)"
119
+ ),
120
+ "detail": (
121
+ f"Total changes: {result.total_changes} "
122
+ f"({result.hipify_changes} hipify, {result.llm_changes} LLM)\n"
123
+ f"Warp size corrected: {analyzer_result.warp_size_issue}\n"
124
+ "Kernel launch syntax updated"
125
+ ),
126
+ "iteration": state.get("iteration", 0),
127
+ }
128
+ )
129
+
130
+ return {"translator_result": result, "events": new_events}
131
+
132
+
133
+ async def optimize_node(state: MigrationState) -> dict:
134
+ from backend.agents import optimizer as optimizer_agent
135
+
136
+ # bump on each optimizer invocation
137
+ iteration = state.get("iteration", 0) + 1
138
+ analyzer_result = state.get("analyzer_result")
139
+ translator_result = state.get("translator_result")
140
+ prev_tester_result = state.get("tester_result") # set on retry path
141
+ is_retry = prev_tester_result is not None
142
+
143
+ new_events: list[dict] = []
144
+
145
+ # On retry: emit coordinator decision + optimizer retrying signals
146
+ if is_retry:
147
+ new_events.append(
148
+ {
149
+ "agent": "coordinator",
150
+ "status": "running",
151
+ "message": "Performance regressed, retrying optimizer with profiler feedback...",
152
+ "detail": f"Profiler feedback: {prev_tester_result.notes}",
153
+ "iteration": iteration,
154
+ }
155
+ )
156
+ new_events.append(
157
+ {
158
+ "agent": "optimizer",
159
+ "status": "retrying",
160
+ "message": f"Trying alternative optimization strategy (iteration {iteration})...",
161
+ "detail": f"Previous strategy regressed. Feedback: {prev_tester_result.notes}",
162
+ "iteration": iteration,
163
+ }
164
+ )
165
+ else:
166
+ new_events.append(
167
+ {
168
+ "agent": "optimizer",
169
+ "status": "running",
170
+ "message": f"Applying AMD MI300X-specific optimizations (iteration {iteration})...",
171
+ "detail": None,
172
+ "iteration": iteration,
173
+ }
174
+ )
175
+
176
+ if translator_result is None:
177
+ new_events.append(
178
+ {
179
+ "agent": "optimizer",
180
+ "status": "failed",
181
+ "message": "Optimization skipped — translation did not complete",
182
+ "detail": None,
183
+ "iteration": iteration,
184
+ }
185
+ )
186
+ return {"optimizer_result": None, "iteration": iteration, "events": new_events}
187
+
188
+ previous_feedback = prev_tester_result.notes if is_retry else None
189
+
190
+ try:
191
+ result = await asyncio.to_thread(
192
+ optimizer_agent.run,
193
+ translator_result.hip_code, # always start from translated base
194
+ analyzer_result,
195
+ iteration,
196
+ previous_feedback,
197
+ )
198
+ except Exception as exc:
199
+ new_events.append(
200
+ {
201
+ "agent": "optimizer",
202
+ "status": "failed",
203
+ "message": "Optimization failed" if not is_retry else "Re-optimization failed",
204
+ "detail": str(exc),
205
+ "iteration": iteration,
206
+ }
207
+ )
208
+ return {"optimizer_result": None, "iteration": iteration, "events": new_events}
209
+
210
+ new_events.append(
211
+ {
212
+ "agent": "optimizer",
213
+ "status": "done",
214
+ "message": f"{len(result.changes)} optimization(s) applied",
215
+ "detail": "\n".join(f"- {c['description']}" for c in result.changes),
216
+ "iteration": iteration,
217
+ }
218
+ )
219
+
220
+ return {"optimizer_result": result, "iteration": iteration, "events": new_events}
221
+
222
+
223
+ async def test_node(state: MigrationState) -> dict:
224
+ from backend.agents import tester as tester_agent
225
+
226
+ iteration = state.get("iteration", 0)
227
+ analyzer_result = state.get("analyzer_result")
228
+ optimizer_result = state.get("optimizer_result")
229
+
230
+ new_events = [
231
+ {
232
+ "agent": "tester",
233
+ "status": "running",
234
+ "message": f"Compiling with hipcc and profiling with rocprof (iteration {iteration})...",
235
+ "detail": None,
236
+ "iteration": iteration,
237
+ }
238
+ ]
239
+
240
+ if optimizer_result is None:
241
+ new_events.append(
242
+ {
243
+ "agent": "tester",
244
+ "status": "failed",
245
+ "message": "Testing skipped — optimization did not complete",
246
+ "detail": None,
247
+ "iteration": iteration,
248
+ }
249
+ )
250
+ return {
251
+ "tester_result": None,
252
+ "migration_success": False,
253
+ "events": new_events,
254
+ }
255
+
256
+ try:
257
+ result = await asyncio.to_thread(
258
+ tester_agent.run,
259
+ optimizer_result.optimized_code,
260
+ analyzer_result,
261
+ iteration,
262
+ state.get("kernel_name", "custom"),
263
+ )
264
+ except Exception as exc:
265
+ new_events.append(
266
+ {
267
+ "agent": "tester",
268
+ "status": "failed",
269
+ "message": "Testing failed",
270
+ "detail": str(exc),
271
+ "iteration": iteration,
272
+ }
273
+ )
274
+ return {
275
+ "tester_result": None,
276
+ "migration_success": False,
277
+ "events": new_events,
278
+ }
279
+
280
+ if not result.success:
281
+ new_events.append(
282
+ {
283
+ "agent": "tester",
284
+ "status": "failed",
285
+ "message": "Compilation or profiling failed",
286
+ "detail": result.notes,
287
+ "iteration": iteration,
288
+ }
289
+ )
290
+ return {
291
+ "tester_result": result,
292
+ "migration_success": False,
293
+ "events": new_events,
294
+ }
295
+
296
+ if result.speedup < 0.95:
297
+ new_events.append(
298
+ {
299
+ "agent": "tester",
300
+ "status": "failed",
301
+ "message": f"Iteration {iteration}: {result.speedup}x vs baseline HIP (regression)",
302
+ "detail": (
303
+ f"Bandwidth utilized: {result.bandwidth_utilized}%\n"
304
+ f"{result.notes}"
305
+ ),
306
+ "iteration": iteration,
307
+ }
308
+ )
309
+ else:
310
+ new_events.append(
311
+ {
312
+ "agent": "tester",
313
+ "status": "done",
314
+ "message": f"Iteration {iteration}: {result.speedup}x vs baseline HIP",
315
+ "detail": (
316
+ f"Execution time: {result.execution_ms:.1f}ms\n"
317
+ f"Memory bandwidth: {result.bandwidth_utilized:.1f}% utilized\n"
318
+ f"Bottleneck type: {result.bottleneck}\n"
319
+ f"{result.notes}"
320
+ ),
321
+ "iteration": iteration,
322
+ }
323
+ )
324
+
325
+ return {"tester_result": result, "events": new_events}
326
+
327
+
328
+ async def coordinate_node(state: MigrationState) -> dict:
329
+ from backend.agents.coordinator import (
330
+ calculate_cost_estimate,
331
+ simplify_explanation,
332
+ _build_amd_explanation,
333
+ )
334
+ from backend.models import FinalReport, CostEstimate
335
+
336
+ analyzer_result = state.get("analyzer_result")
337
+ translator_result = state.get("translator_result")
338
+ optimizer_result = state.get("optimizer_result")
339
+ tester_result = state.get("tester_result")
340
+ iteration = state.get("iteration", 0)
341
+ simple_mode = state.get("simple_mode", False)
342
+
343
+ new_events = [
344
+ {
345
+ "agent": "coordinator",
346
+ "status": "running",
347
+ "message": "Generating migration report...",
348
+ "detail": None,
349
+ "iteration": iteration,
350
+ }
351
+ ]
352
+
353
+ # Hard failure path — one or more agents did not complete
354
+ if tester_result is None or translator_result is None or optimizer_result is None:
355
+ new_events.append(
356
+ {
357
+ "agent": "coordinator",
358
+ "status": "failed",
359
+ "message": "Pipeline did not complete successfully",
360
+ "detail": "One or more agents failed before the report could be generated.",
361
+ "iteration": iteration,
362
+ }
363
+ )
364
+ return {
365
+ "migration_success": False,
366
+ "final_report": {},
367
+ "events": new_events,
368
+ }
369
+
370
+ amd_explanation = _build_amd_explanation(analyzer_result, tester_result)
371
+
372
+ try:
373
+ cost_estimate = calculate_cost_estimate(analyzer_result)
374
+ except Exception:
375
+ from backend.models import CostEstimate
376
+ cost_estimate = CostEstimate(
377
+ manual_porting_weeks="3-6 weeks",
378
+ rocmport_minutes="Varies by kernel",
379
+ estimated_savings="$20,000-$50,000",
380
+ complexity_factor="Medium",
381
+ )
382
+
383
+ total_changes = translator_result.total_changes + \
384
+ len(optimizer_result.changes)
385
+
386
+ temp_report = FinalReport(
387
+ migration_success=tester_result.success,
388
+ speedup=tester_result.speedup,
389
+ bandwidth_utilized=tester_result.bandwidth_utilized,
390
+ total_changes=total_changes,
391
+ bottleneck=tester_result.bottleneck,
392
+ amd_advantage_explanation=amd_explanation,
393
+ iterations=iteration,
394
+ hip_code=translator_result.hip_code,
395
+ optimized_code=optimizer_result.optimized_code,
396
+ verification=tester_result.verification,
397
+ static_risk_report=analyzer_result.static_risk_report if analyzer_result else None,
398
+ data_source=tester_result.data_source or "simulated",
399
+ )
400
+
401
+ simplified = simplify_explanation(
402
+ temp_report) if simple_mode else amd_explanation
403
+
404
+ report = FinalReport(
405
+ migration_success=tester_result.success,
406
+ speedup=tester_result.speedup,
407
+ bandwidth_utilized=tester_result.bandwidth_utilized,
408
+ total_changes=total_changes,
409
+ bottleneck=tester_result.bottleneck,
410
+ amd_advantage_explanation=amd_explanation,
411
+ iterations=iteration,
412
+ hip_code=translator_result.hip_code,
413
+ optimized_code=optimizer_result.optimized_code,
414
+ verification=tester_result.verification,
415
+ cost_estimate=cost_estimate,
416
+ simplified_explanation=simplified,
417
+ static_risk_report=analyzer_result.static_risk_report if analyzer_result else None,
418
+ data_source=tester_result.data_source or "simulated",
419
+ )
420
+
421
+ report_dict = report.model_dump()
422
+
423
+ new_events.append(
424
+ {
425
+ "agent": "coordinator",
426
+ "status": "done",
427
+ "message": "Migration complete",
428
+ "detail": json.dumps(report_dict),
429
+ "iteration": iteration,
430
+ }
431
+ )
432
+
433
+ return {
434
+ "migration_success": report.migration_success,
435
+ "final_report": report_dict,
436
+ "events": new_events,
437
+ }
438
+
439
+
440
+ # ─── Conditional routing ───────────────────────────────────────────────────────
441
+
442
+ def should_retry_decision(state: MigrationState) -> Literal["retry", "done"]:
443
+ """Route to optimizer (retry) or coordinator (done)."""
444
+ tester_result = state.get("tester_result")
445
+ if tester_result is None:
446
+ return "done"
447
+ if not getattr(tester_result, "success", True):
448
+ return "done" # hard compile/run failure — let coordinator report it
449
+ speedup = float(getattr(tester_result, "speedup", 1.0) or 1.0)
450
+ iteration = state.get("iteration", 0)
451
+ max_iter = state.get("max_iterations", 3)
452
+ if speedup < 0.95 and iteration < max_iter:
453
+ return "retry"
454
+ return "done"
455
+
456
+
457
+ # ─── Graph builder ─────────────────────────────────────────────────────────────
458
+
459
+ def build_pipeline():
460
+ """Build and compile the LangGraph StateGraph for the migration pipeline."""
461
+ graph = StateGraph(MigrationState)
462
+
463
+ graph.add_node("analyzer", analyze_node)
464
+ graph.add_node("translator", translate_node)
465
+ graph.add_node("optimizer", optimize_node)
466
+ graph.add_node("tester", test_node)
467
+ graph.add_node("coordinator", coordinate_node)
468
+
469
+ graph.set_entry_point("analyzer")
470
+ graph.add_edge("analyzer", "translator")
471
+ graph.add_edge("translator", "optimizer")
472
+ graph.add_edge("optimizer", "tester")
473
+
474
+ graph.add_conditional_edges(
475
+ "tester",
476
+ should_retry_decision,
477
+ {
478
+ "retry": "optimizer", # performance regression + iterations remaining
479
+ "done": "coordinator", # acceptable result or hard failure
480
+ },
481
+ )
482
+
483
+ graph.add_edge("coordinator", END)
484
+
485
+ return graph.compile()
486
+
487
+
488
+ # Module-level compiled pipeline (reused across requests)
489
+ pipeline = build_pipeline()
backend/graph/state.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import TypedDict, Optional, List, Any, Annotated
2
+ import operator
3
+
4
+
5
+ class MigrationState(TypedDict):
6
+ # ── Input ──────────────────────────────────────────────────────────────────
7
+ cuda_code: str
8
+ kernel_name: str
9
+ simple_mode: bool
10
+
11
+ # ── Intermediate results (pydantic model instances stored as Any) ──────────
12
+ analyzer_result: Optional[Any] # AnalyzerResult
13
+ translator_result: Optional[Any] # TranslatorResult
14
+ optimizer_result: Optional[Any] # OptimizerResult
15
+ tester_result: Optional[Any] # TesterResult
16
+
17
+ # ── Control flow ───────────────────────────────────────────────────────────
18
+ iteration: int # incremented each time optimizer node runs
19
+ max_iterations: int # default 3
20
+ should_retry: bool
21
+
22
+ # ── Output ─────────────────────────────────────────────────────────────────
23
+ migration_success: bool
24
+ final_report: dict
25
+
26
+ # ── SSE event accumulator — LangGraph appends each node's new events ───────
27
+ # operator.add reducer: each node returns a *list of new events*;
28
+ # LangGraph concatenates them into a single growing list.
29
+ events: Annotated[List[dict], operator.add]
backend/main.py CHANGED
@@ -2,7 +2,7 @@
2
 
3
  from backend.agents.analyzer import AnalyzerResult, WorkloadType
4
  from backend.agents.tester import run as run_tester
5
- from backend.agents.coordinator import run_pipeline
6
  from backend.models import PortRequest, ColdStartRequest, AggregateMetricsRequest
7
  from fastapi.staticfiles import StaticFiles
8
  from fastapi.responses import StreamingResponse
@@ -97,29 +97,61 @@ async def benchmark_report():
97
  @app.post("/port")
98
  async def port_cuda_code(req: PortRequest):
99
  """
100
- Main endpoint. Streams SSE events as the agent pipeline runs.
101
- Each event is a JSON AgentEvent object.
102
  """
103
  if not req.cuda_code or len(req.cuda_code.strip()) < 10:
104
  raise HTTPException(status_code=400, detail="No CUDA code provided")
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  async def event_stream():
 
107
  try:
108
- async for event in run_pipeline(req.cuda_code, req.kernel_name or "custom", req.simple_mode or False):
109
- data = json.dumps(event.model_dump())
110
- yield f"data: {data}\n\n"
111
- # Let the client breathe between events
112
- await asyncio.sleep(0.05)
113
- except Exception as e:
114
- error_event = {
115
- "agent": "coordinator",
116
- "status": "failed",
117
- "message": "Pipeline error",
118
- "detail": str(e)
119
- }
120
- yield f"data: {json.dumps(error_event)}\n\n"
121
  finally:
122
- yield "data: [DONE]\n\n"
123
 
124
  return StreamingResponse(
125
  event_stream(),
@@ -127,23 +159,44 @@ async def port_cuda_code(req: PortRequest):
127
  headers={
128
  "Cache-Control": "no-cache",
129
  "X-Accel-Buffering": "no",
130
- }
131
  )
132
 
133
 
134
  async def _collect_pipeline_events(cuda_code: str, kernel_name: str, simple_mode: bool = False) -> tuple[list[dict], dict | None]:
135
- """Collect all pipeline events and extract final report payload when present."""
136
  events: list[dict] = []
137
  final_report = None
138
 
139
- async for event in run_pipeline(cuda_code, kernel_name, simple_mode):
140
- dumped = event.model_dump()
141
- events.append(dumped)
142
- if dumped.get("agent") == "coordinator" and dumped.get("status") == "done" and dumped.get("detail"):
143
- try:
144
- final_report = json.loads(dumped["detail"])
145
- except (json.JSONDecodeError, TypeError):
146
- final_report = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
  return events, final_report
149
 
 
2
 
3
  from backend.agents.analyzer import AnalyzerResult, WorkloadType
4
  from backend.agents.tester import run as run_tester
5
+ from backend.graph.pipeline import pipeline as migration_pipeline
6
  from backend.models import PortRequest, ColdStartRequest, AggregateMetricsRequest
7
  from fastapi.staticfiles import StaticFiles
8
  from fastapi.responses import StreamingResponse
 
97
  @app.post("/port")
98
  async def port_cuda_code(req: PortRequest):
99
  """
100
+ Main endpoint. Streams SSE events as the LangGraph pipeline runs.
101
+ Each event is a JSON object matching the AgentEvent schema.
102
  """
103
  if not req.cuda_code or len(req.cuda_code.strip()) < 10:
104
  raise HTTPException(status_code=400, detail="No CUDA code provided")
105
 
106
+ queue: asyncio.Queue = asyncio.Queue()
107
+
108
+ async def _run_graph():
109
+ initial_state = {
110
+ "cuda_code": req.cuda_code,
111
+ "kernel_name": req.kernel_name or "custom",
112
+ "simple_mode": req.simple_mode or False,
113
+ "analyzer_result": None,
114
+ "translator_result": None,
115
+ "optimizer_result": None,
116
+ "tester_result": None,
117
+ "iteration": 0,
118
+ "max_iterations": 3,
119
+ "should_retry": False,
120
+ "migration_success": False,
121
+ "final_report": {},
122
+ "events": [],
123
+ }
124
+ try:
125
+ async for chunk in migration_pipeline.astream(
126
+ initial_state, stream_mode="updates"
127
+ ):
128
+ for _node_name, node_output in chunk.items():
129
+ for event in node_output.get("events", []):
130
+ await queue.put(event)
131
+ await asyncio.sleep(0.05) # let client breathe
132
+ except Exception as exc:
133
+ await queue.put(
134
+ {
135
+ "agent": "coordinator",
136
+ "status": "failed",
137
+ "message": "Pipeline error",
138
+ "detail": str(exc),
139
+ }
140
+ )
141
+ finally:
142
+ await queue.put(None) # sentinel
143
+
144
  async def event_stream():
145
+ task = asyncio.create_task(_run_graph())
146
  try:
147
+ while True:
148
+ event = await queue.get()
149
+ if event is None:
150
+ yield "data: [DONE]\n\n"
151
+ break
152
+ yield f"data: {json.dumps(event)}\n\n"
 
 
 
 
 
 
 
153
  finally:
154
+ task.cancel()
155
 
156
  return StreamingResponse(
157
  event_stream(),
 
159
  headers={
160
  "Cache-Control": "no-cache",
161
  "X-Accel-Buffering": "no",
162
+ },
163
  )
164
 
165
 
166
  async def _collect_pipeline_events(cuda_code: str, kernel_name: str, simple_mode: bool = False) -> tuple[list[dict], dict | None]:
167
+ """Collect all pipeline events via LangGraph and extract the final report."""
168
  events: list[dict] = []
169
  final_report = None
170
 
171
+ initial_state = {
172
+ "cuda_code": cuda_code,
173
+ "kernel_name": kernel_name,
174
+ "simple_mode": simple_mode,
175
+ "analyzer_result": None,
176
+ "translator_result": None,
177
+ "optimizer_result": None,
178
+ "tester_result": None,
179
+ "iteration": 0,
180
+ "max_iterations": 3,
181
+ "should_retry": False,
182
+ "migration_success": False,
183
+ "final_report": {},
184
+ "events": [],
185
+ }
186
+
187
+ async for chunk in migration_pipeline.astream(initial_state, stream_mode="updates"):
188
+ for _node_name, node_output in chunk.items():
189
+ for event in node_output.get("events", []):
190
+ events.append(event)
191
+ if (
192
+ event.get("agent") == "coordinator"
193
+ and event.get("status") == "done"
194
+ and event.get("detail")
195
+ ):
196
+ try:
197
+ final_report = json.loads(event["detail"])
198
+ except (json.JSONDecodeError, TypeError):
199
+ final_report = None
200
 
201
  return events, final_report
202
 
backend/requirements.txt CHANGED
@@ -10,3 +10,4 @@ crewai==0.55.2
10
  python-dotenv==1.0.0
11
  aiofiles==23.2.1
12
  jinja2==3.1.2
 
 
10
  python-dotenv==1.0.0
11
  aiofiles==23.2.1
12
  jinja2==3.1.2
13
+ langgraph>=0.2.0
frontend/index.html CHANGED
@@ -1,1724 +1,18 @@
1
- <!DOCTYPE html>
2
  <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>ROCmPort AI</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Space+Grotesk:wght@500;600;700&display=swap"
11
- rel="stylesheet">
12
- <style>
13
- :root {
14
- --bg: #030303;
15
- --s1: #0a0a0b;
16
- --s2: #121214;
17
- --s3: #1a1a1e;
18
- --b1: rgba(255, 255, 255, 0.08);
19
- --b2: rgba(255, 255, 255, 0.15);
20
- --red: #ff3344;
21
- --red-glow: rgba(255, 51, 68, 0.4);
22
- --green: #00ff88;
23
- --green-glow: rgba(0, 255, 136, 0.4);
24
- --yellow: #ffcc00;
25
- --cyan: #00d9ff;
26
- --muted: #88888e;
27
- --t1: #a1a1aa;
28
- --t2: #d4d4d8;
29
- --t3: #ffffff;
30
- --mono: 'JetBrains Mono', monospace;
31
- --sans: 'Space Grotesk', sans-serif;
32
- --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
33
- }
34
-
35
- * {
36
- margin: 0;
37
- padding: 0;
38
- box-sizing: border-box;
39
- cursor: none !important;
40
- }
41
-
42
- .hide {
43
- display: none !important;
44
- }
45
-
46
- body {
47
- background: var(--bg);
48
- color: var(--t1);
49
- font-family: var(--sans);
50
- font-size: 14px;
51
- line-height: 1.6;
52
- overflow-x: hidden;
53
- min-height: 100vh;
54
- }
55
-
56
- /* Animated Gradient Background */
57
- body::before {
58
- content: '';
59
- position: fixed;
60
- inset: 0;
61
- background:
62
- radial-gradient(circle at 20% 30%, rgba(0, 217, 255, 0.05), transparent 40%),
63
- radial-gradient(circle at 80% 70%, rgba(255, 51, 68, 0.05), transparent 40%),
64
- radial-gradient(circle at 50% 50%, rgba(0, 255, 136, 0.03), transparent 60%);
65
- z-index: -1;
66
- animation: bgMove 20s ease-in-out infinite alternate;
67
- }
68
-
69
- @keyframes bgMove {
70
- 0% {
71
- transform: scale(1) translate(0, 0);
72
- }
73
-
74
- 50% {
75
- transform: scale(1.1) translate(20px, -20px);
76
- }
77
-
78
- 100% {
79
- transform: scale(1) translate(-20px, 20px);
80
- }
81
- }
82
-
83
- .w {
84
- max-width: 1200px;
85
- margin: 0 auto;
86
- padding: 32px 24px;
87
- position: relative;
88
- }
89
-
90
- /* Container Glow */
91
- .w::after {
92
- content: '';
93
- position: absolute;
94
- inset: 0;
95
- background: radial-gradient(circle at 50% 0%, rgba(255, 51, 68, 0.08), transparent 70%);
96
- pointer-events: none;
97
- z-index: -1;
98
- }
99
-
100
- header {
101
- padding-bottom: 24px;
102
- border-bottom: 1px solid var(--b1);
103
- display: flex;
104
- align-items: center;
105
- justify-content: space-between;
106
- margin-bottom: 24px;
107
- }
108
-
109
- .logo {
110
- font-weight: 700;
111
- font-size: 18px;
112
- color: var(--t3);
113
- letter-spacing: -0.02em;
114
- }
115
-
116
- .logo em {
117
- font-style: normal;
118
- color: var(--red);
119
- text-shadow: 0 0 15px var(--red-glow);
120
- }
121
-
122
- .hr {
123
- font-size: 12px;
124
- color: var(--muted);
125
- display: flex;
126
- align-items: center;
127
- gap: 10px;
128
- background: var(--s1);
129
- padding: 6px 12px;
130
- border-radius: 20px;
131
- border: 1px solid var(--b1);
132
- }
133
-
134
- .hd {
135
- width: 6px;
136
- height: 6px;
137
- border-radius: 50%;
138
- background: var(--green);
139
- box-shadow: 0 0 10px var(--green-glow);
140
- }
141
-
142
- .hd.on {
143
- animation: pulse 2s ease-in-out infinite;
144
- }
145
-
146
- @keyframes pulse {
147
-
148
- 0%,
149
- 100% {
150
- opacity: 1;
151
- transform: scale(1);
152
- }
153
-
154
- 50% {
155
- opacity: 0.4;
156
- transform: scale(0.8);
157
- }
158
- }
159
-
160
- .g {
161
- display: grid;
162
- grid-template-columns: 1.2fr 0.8fr;
163
- gap: 24px;
164
- padding: 0;
165
- }
166
-
167
- .fs {
168
- grid-column: 1 / -1;
169
- }
170
-
171
- @media (max-width: 900px) {
172
- .g {
173
- grid-template-columns: 1fr;
174
- }
175
- }
176
-
177
- /* Card Styling */
178
- .p {
179
- background: var(--s1);
180
- border: 1px solid var(--b1);
181
- border-radius: 12px;
182
- overflow: hidden;
183
- display: flex;
184
- flex-direction: column;
185
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
186
- backdrop-filter: blur(10px);
187
- transition: transform 0.3s var(--spring), border-color 0.3s ease;
188
- }
189
-
190
- .p:hover {
191
- border-color: var(--b2);
192
- }
193
-
194
- .ph {
195
- padding: 12px 16px;
196
- border-bottom: 1px solid var(--b1);
197
- display: flex;
198
- align-items: center;
199
- justify-content: space-between;
200
- font-size: 12px;
201
- color: var(--muted);
202
- background: rgba(255, 255, 255, 0.02);
203
- }
204
-
205
- .ph b {
206
- color: var(--red);
207
- font-weight: 600;
208
- text-transform: uppercase;
209
- letter-spacing: 0.05em;
210
- }
211
-
212
- textarea.code {
213
- width: 100%;
214
- flex: 1;
215
- min-height: 300px;
216
- background: var(--bg);
217
- border: none;
218
- color: var(--t2);
219
- font-family: var(--mono);
220
- font-size: 13px;
221
- line-height: 1.7;
222
- padding: 20px;
223
- resize: vertical;
224
- outline: none;
225
- caret-color: var(--red);
226
- will-change: transform;
227
- }
228
-
229
- .db {
230
- padding: 12px 16px;
231
- border-top: 1px solid var(--b1);
232
- display: flex;
233
- align-items: center;
234
- gap: 8px;
235
- background: var(--s1);
236
- }
237
-
238
- .db .l {
239
- font-size: 11px;
240
- color: var(--muted);
241
- font-weight: 500;
242
- }
243
-
244
- .ch {
245
- font-family: var(--sans);
246
- font-size: 11px;
247
- padding: 4px 12px;
248
- background: var(--s2);
249
- border: 1px solid var(--b1);
250
- border-radius: 6px;
251
- color: var(--t1);
252
- cursor: pointer;
253
- transition: all 0.2s var(--spring);
254
- }
255
-
256
- .ch:hover {
257
- background: var(--s3);
258
- color: var(--t3);
259
- transform: translateY(-1px);
260
- border-color: var(--b2);
261
- }
262
-
263
- .ch.on {
264
- background: var(--red);
265
- border-color: var(--red);
266
- color: #fff;
267
- box-shadow: 0 0 15px var(--red-glow);
268
- }
269
-
270
- .bg {
271
- margin: 16px;
272
- padding: 14px;
273
- background: var(--red);
274
- border: none;
275
- border-radius: 8px;
276
- color: #fff;
277
- font-family: var(--sans);
278
- font-size: 14px;
279
- font-weight: 700;
280
- cursor: pointer;
281
- transition: all 0.3s var(--spring);
282
- text-transform: uppercase;
283
- letter-spacing: 0.05em;
284
- box-shadow: 0 4px 15px var(--red-glow);
285
- }
286
-
287
- .bg:hover {
288
- background: #ff4d5a;
289
- transform: translateY(-2px);
290
- box-shadow: 0 6px 20px var(--red-glow);
291
- }
292
-
293
- .bg:active {
294
- transform: translateY(0);
295
- }
296
-
297
- .bg:disabled {
298
- opacity: 0.4;
299
- cursor: not-allowed;
300
- transform: none;
301
- box-shadow: none;
302
- }
303
-
304
- /* Agent log */
305
- .al {
306
- padding: 12px;
307
- display: flex;
308
- flex-direction: column;
309
- gap: 8px;
310
- }
311
-
312
- .ar {
313
- padding: 12px 16px;
314
- border-radius: 8px;
315
- background: rgba(255, 255, 255, 0.03);
316
- border: 1px solid transparent;
317
- transition: all 0.4s var(--spring);
318
- animation: slideIn 0.5s var(--spring) forwards;
319
- opacity: 0;
320
- transform: translateX(20px);
321
- }
322
-
323
- @keyframes slideIn {
324
- to {
325
- opacity: 1;
326
- transform: translateX(0);
327
- }
328
- }
329
-
330
- .ar.run {
331
- border-color: var(--cyan);
332
- background: rgba(0, 217, 255, 0.05);
333
- }
334
-
335
- .ar.done {
336
- border-color: var(--green);
337
- background: rgba(0, 255, 136, 0.05);
338
- }
339
-
340
- .ar.fail {
341
- border-color: var(--red);
342
- background: rgba(255, 51, 68, 0.05);
343
- }
344
-
345
- .ar.retry {
346
- border-color: var(--yellow);
347
- background: rgba(255, 204, 0, 0.05);
348
- animation: pulse-border 1.5s ease-in-out infinite;
349
- }
350
-
351
- @keyframes pulse-border {
352
- 50% {
353
- border-color: rgba(255, 204, 0, 0.2);
354
- }
355
- }
356
-
357
- .at {
358
- display: flex;
359
- align-items: center;
360
- gap: 12px;
361
- }
362
-
363
- .an {
364
- font-size: 10px;
365
- font-weight: 700;
366
- color: var(--muted);
367
- min-width: 90px;
368
- text-transform: uppercase;
369
- letter-spacing: 0.1em;
370
- }
371
-
372
- .am {
373
- font-size: 13px;
374
- color: var(--t2);
375
- font-weight: 500;
376
- }
377
-
378
- .ad {
379
- font-size: 11px;
380
- color: var(--muted);
381
- margin-top: 4px;
382
- padding-left: 102px;
383
- white-space: pre-wrap;
384
- line-height: 1.6;
385
- max-height: 100px;
386
- overflow-y: auto;
387
- }
388
-
389
- .ad .w {
390
- color: var(--yellow);
391
- font-weight: 600;
392
- }
393
-
394
- .ad .g {
395
- color: var(--green);
396
- font-weight: 600;
397
- }
398
-
399
- /* Horizontal Timeline */
400
- .timeline {
401
- display: flex;
402
- justify-content: space-between;
403
- padding: 16px 20px;
404
- background: rgba(255, 255, 255, 0.02);
405
- border-bottom: 1px solid var(--b1);
406
- margin-bottom: 8px;
407
- }
408
-
409
- .node {
410
- display: flex;
411
- flex-direction: column;
412
- align-items: center;
413
- gap: 6px;
414
- position: relative;
415
- flex: 1;
416
- }
417
-
418
- .node::after {
419
- content: '';
420
- position: absolute;
421
- top: 12px;
422
- left: 50%;
423
- width: 100%;
424
- height: 2px;
425
- background: var(--b1);
426
- z-index: 0;
427
- }
428
-
429
- .node:last-child::after {
430
- display: none;
431
- }
432
-
433
- .ni {
434
- width: 24px;
435
- height: 24px;
436
- border-radius: 50%;
437
- background: var(--s3);
438
- border: 2px solid var(--b1);
439
- display: flex;
440
- align-items: center;
441
- justify-content: center;
442
- font-size: 12px;
443
- z-index: 1;
444
- transition: all 0.4s var(--spring);
445
- }
446
-
447
- .node.on .ni {
448
- background: var(--cyan);
449
- border-color: var(--cyan);
450
- color: #000;
451
- box-shadow: 0 0 15px var(--cyan);
452
- }
453
-
454
- .node.done .ni {
455
- background: var(--green);
456
- border-color: var(--green);
457
- color: #000;
458
- box-shadow: 0 0 15px var(--green);
459
- }
460
-
461
- .node.fail .ni {
462
- background: var(--red);
463
- border-color: var(--red);
464
- color: #fff;
465
- }
466
-
467
- .node.retry .ni {
468
- animation: pulse-node 1s var(--spring) infinite;
469
- background: var(--yellow);
470
- border-color: var(--yellow);
471
- }
472
-
473
- @keyframes pulse-node {
474
-
475
- 0%,
476
- 100% {
477
- transform: scale(1);
478
- }
479
-
480
- 50% {
481
- transform: scale(1.2);
482
- }
483
- }
484
-
485
- .nl {
486
- font-size: 9px;
487
- font-weight: 700;
488
- color: var(--muted);
489
- text-transform: uppercase;
490
- letter-spacing: 0.05em;
491
- }
492
-
493
- .node.on .nl,
494
- .node.done .nl {
495
- color: var(--t3);
496
- }
497
-
498
- /* Tabs */
499
- .tabs {
500
- display: flex;
501
- gap: 8px;
502
- }
503
-
504
- .tab {
505
- background: var(--s2);
506
- border: 1px solid var(--b1);
507
- padding: 6px 16px;
508
- border-radius: 8px;
509
- font-family: var(--sans);
510
- font-size: 12px;
511
- font-weight: 600;
512
- color: var(--muted);
513
- cursor: pointer;
514
- transition: all 0.2s var(--spring);
515
- }
516
-
517
- .tab:hover {
518
- color: var(--t2);
519
- background: var(--s3);
520
- }
521
-
522
- .tab.on {
523
- color: var(--t3);
524
- background: var(--red);
525
- border-color: var(--red);
526
- box-shadow: 0 0 10px var(--red-glow);
527
- }
528
-
529
- .tc {
530
- display: none;
531
- padding: 0;
532
- animation: fadeIn 0.4s ease;
533
- }
534
-
535
- .tc.on {
536
- display: block;
537
- }
538
-
539
- @keyframes fadeIn {
540
- from {
541
- opacity: 0;
542
- transform: translateY(10px);
543
- }
544
-
545
- to {
546
- opacity: 1;
547
- transform: translateY(0);
548
- }
549
- }
550
-
551
- /* Summary row */
552
- .sum-row {
553
- padding: 24px;
554
- display: flex;
555
- align-items: center;
556
- gap: 32px;
557
- flex-wrap: wrap;
558
- border-bottom: 1px solid var(--b1);
559
- background: rgba(0, 255, 136, 0.02);
560
- }
561
-
562
- .sum-big {
563
- font-size: 32px;
564
- font-weight: 800;
565
- color: var(--green);
566
- line-height: 1;
567
- letter-spacing: -0.02em;
568
- text-shadow: 0 0 20px var(--green-glow);
569
- }
570
-
571
- .sum-big .u {
572
- font-size: 13px;
573
- font-weight: 500;
574
- color: var(--muted);
575
- margin-left: 4px;
576
- display: block;
577
- margin-top: 4px;
578
- letter-spacing: 0;
579
- }
580
-
581
- .sum-big .vic {
582
- font-size: 11px;
583
- color: var(--cyan);
584
- font-weight: 600;
585
- display: block;
586
- margin-top: 8px;
587
- text-shadow: none;
588
- opacity: 0.8;
589
- }
590
-
591
- .sum-sep {
592
- width: 1px;
593
- height: 40px;
594
- background: var(--b1);
595
- }
596
-
597
- .sum-chk {
598
- display: flex;
599
- align-items: center;
600
- gap: 8px;
601
- font-size: 12px;
602
- color: var(--t2);
603
- font-weight: 500;
604
- }
605
-
606
- .sum-dot {
607
- width: 8px;
608
- height: 8px;
609
- border-radius: 50%;
610
- flex-shrink: 0;
611
- }
612
-
613
- .sum-dot.ok {
614
- background: var(--green);
615
- box-shadow: 0 0 8px var(--green-glow);
616
- }
617
-
618
- .sum-dot.no {
619
- background: var(--red);
620
- box-shadow: 0 0 8px var(--red-glow);
621
- }
622
-
623
- .sum-dot.na {
624
- background: var(--muted);
625
- box-shadow: none;
626
- }
627
-
628
- .sum-type {
629
- font-size: 11px;
630
- color: var(--cyan);
631
- text-transform: uppercase;
632
- letter-spacing: 0.1em;
633
- font-weight: 700;
634
- padding: 4px 10px;
635
- background: rgba(0, 217, 255, 0.1);
636
- border-radius: 4px;
637
- }
638
-
639
- .sum-bar {
640
- padding: 16px 24px;
641
- display: flex;
642
- align-items: center;
643
- gap: 12px;
644
- flex-wrap: wrap;
645
- border-bottom: 1px solid var(--b1);
646
- }
647
-
648
- .bs {
649
- font-family: var(--sans);
650
- font-size: 11px;
651
- font-weight: 700;
652
- padding: 8px 16px;
653
- border-radius: 8px;
654
- border: 1px solid var(--b1);
655
- background: var(--s2);
656
- color: var(--t2);
657
- cursor: pointer;
658
- transition: all 0.2s var(--spring);
659
- text-transform: uppercase;
660
- letter-spacing: 0.05em;
661
- }
662
-
663
- .bs:hover {
664
- border-color: var(--b2);
665
- transform: translateY(-1px);
666
- background: var(--s3);
667
- }
668
-
669
- .bs.r {
670
- background: var(--bg);
671
- border-color: var(--red);
672
- color: var(--red);
673
- }
674
-
675
- .bs.r:hover {
676
- background: var(--red);
677
- color: #fff;
678
- box-shadow: 0 4px 15px var(--red-glow);
679
- }
680
-
681
- .bs.gr {
682
- background: var(--green);
683
- border-color: var(--green);
684
- color: #000;
685
- }
686
-
687
- .bs.gr:hover {
688
- box-shadow: 0 4px 15px var(--green-glow);
689
- transform: translateY(-2px);
690
- }
691
-
692
- .sp {
693
- flex: 1;
694
- }
695
-
696
- /* Details tab */
697
- .dm {
698
- display: grid;
699
- grid-template-columns: repeat(5, 1fr);
700
- border-bottom: 1px solid var(--b1);
701
- }
702
-
703
- @media (max-width: 800px) {
704
- .dm {
705
- grid-template-columns: repeat(2, 1fr);
706
- }
707
- }
708
-
709
- .di {
710
- padding: 20px;
711
- border-right: 1px solid var(--b1);
712
- background: rgba(255, 255, 255, 0.01);
713
- }
714
-
715
- .di:last-child {
716
- border-right: none;
717
- }
718
-
719
- .dl {
720
- font-size: 10px;
721
- color: var(--muted);
722
- text-transform: uppercase;
723
- letter-spacing: 0.1em;
724
- margin-bottom: 8px;
725
- font-weight: 700;
726
- }
727
-
728
- .dv {
729
- font-size: 20px;
730
- font-weight: 800;
731
- line-height: 1;
732
- margin-bottom: 4px;
733
- color: var(--t3);
734
- }
735
-
736
- .dv.g {
737
- color: var(--green);
738
- }
739
-
740
- .dv.c {
741
- color: var(--cyan);
742
- }
743
-
744
- .dv.y {
745
- color: var(--yellow);
746
- }
747
-
748
- .dv.t {
749
- color: var(--t2);
750
- font-size: 13px;
751
- }
752
-
753
- .ds {
754
- font-size: 10px;
755
- color: var(--muted);
756
- line-height: 1.4;
757
- }
758
-
759
- /* Benchmark bars */
760
- .bk {
761
- padding: 24px;
762
- border-bottom: 1px solid var(--b1);
763
- }
764
-
765
- .bk-t {
766
- font-size: 11px;
767
- color: var(--muted);
768
- text-transform: uppercase;
769
- letter-spacing: 0.1em;
770
- margin-bottom: 16px;
771
- font-weight: 700;
772
- }
773
-
774
- .br {
775
- display: flex;
776
- align-items: center;
777
- gap: 16px;
778
- margin-bottom: 12px;
779
- }
780
-
781
- .br:last-child {
782
- margin-bottom: 0;
783
- }
784
-
785
- .bl {
786
- font-size: 12px;
787
- color: var(--t2);
788
- width: 140px;
789
- flex-shrink: 0;
790
- font-weight: 500;
791
- }
792
-
793
- .bt {
794
- flex: 1;
795
- height: 8px;
796
- background: var(--bg);
797
- border-radius: 4px;
798
- overflow: hidden;
799
- border: 1px solid var(--b1);
800
- }
801
-
802
- .bf {
803
- height: 100%;
804
- border-radius: 4px;
805
- transition: width 1s var(--spring);
806
- width: 0;
807
- }
808
-
809
- .bf.bad {
810
- background: linear-gradient(90deg, #ff334466, #ff3344);
811
- box-shadow: 0 0 10px rgba(255, 51, 68, 0.3);
812
- }
813
-
814
- .bf.good {
815
- background: linear-gradient(90deg, #00ff8866, #00ff88);
816
- box-shadow: 0 0 10px rgba(0, 255, 136, 0.3);
817
- }
818
-
819
- .bv {
820
- font-size: 12px;
821
- font-weight: 700;
822
- width: 40px;
823
- text-align: right;
824
- flex-shrink: 0;
825
- }
826
-
827
- .bv.bad {
828
- color: var(--red);
829
- }
830
-
831
- .bv.good {
832
- color: var(--green);
833
- }
834
-
835
- /* Simple mode note */
836
- .sn {
837
- padding: 20px;
838
- border: 1px solid var(--cyan);
839
- border-radius: 12px;
840
- background: rgba(0, 217, 255, 0.05);
841
- margin: 24px;
842
- font-size: 13px;
843
- color: var(--t2);
844
- line-height: 1.6;
845
- border-left-width: 4px;
846
- }
847
-
848
- /* Diff */
849
- .dg {
850
- display: grid;
851
- grid-template-columns: 1fr 1fr;
852
- background: var(--bg);
853
- }
854
-
855
- .dfs {
856
- min-width: 0;
857
- }
858
-
859
- @media (max-width: 780px) {
860
- .dg {
861
- grid-template-columns: 1fr;
862
- }
863
-
864
- .dfs:first-child {
865
- border-right: none !important;
866
- border-bottom: 1px solid var(--b1);
867
- }
868
- }
869
-
870
- .dfs:first-child {
871
- border-right: 1px solid var(--b1);
872
- }
873
-
874
- .dfh {
875
- padding: 10px 16px;
876
- border-bottom: 1px solid var(--b1);
877
- font-size: 11px;
878
- color: var(--muted);
879
- display: flex;
880
- align-items: center;
881
- gap: 8px;
882
- font-weight: 600;
883
- background: var(--s2);
884
- }
885
-
886
- .dft {
887
- font-size: 9px;
888
- font-weight: 800;
889
- padding: 2px 6px;
890
- border-radius: 4px;
891
- text-transform: uppercase;
892
- }
893
-
894
- .dft.cu {
895
- background: rgba(255, 51, 68, 0.2);
896
- color: var(--red);
897
- }
898
-
899
- .dft.ro {
900
- background: rgba(0, 255, 136, 0.2);
901
- color: var(--green);
902
- }
903
-
904
- .dfp {
905
- padding: 20px;
906
- font-family: var(--mono);
907
- font-size: 12px;
908
- line-height: 1.7;
909
- overflow: auto;
910
- max-height: min(70vh, 760px);
911
- white-space: pre-wrap;
912
- overflow-wrap: anywhere;
913
- word-break: break-word;
914
- tab-size: 2;
915
- color: var(--t2);
916
- }
917
-
918
- .dlo {
919
- background: rgba(255, 51, 68, 0.08);
920
- color: var(--red);
921
- display: block;
922
- border-left: 2px solid rgba(255, 51, 68, 0.45);
923
- padding-left: 8px;
924
- }
925
-
926
- .dln {
927
- background: rgba(0, 255, 136, 0.08);
928
- color: var(--green);
929
- display: block;
930
- border-left: 2px solid rgba(0, 255, 136, 0.45);
931
- padding-left: 8px;
932
- }
933
-
934
- /* Loading Skeleton */
935
- .skeleton {
936
- position: relative;
937
- overflow: hidden;
938
- background: var(--s2);
939
- border-radius: 12px;
940
- height: 200px;
941
- margin-top: 24px;
942
- }
943
-
944
- .skeleton::after {
945
- content: '';
946
- position: absolute;
947
- inset: 0;
948
- transform: translateX(-100%);
949
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.05), transparent);
950
- animation: shimmer 1.5s infinite;
951
- }
952
-
953
- @keyframes shimmer {
954
- 100% {
955
- transform: translateX(100%);
956
- }
957
- }
958
-
959
- /* Custom Cursor */
960
- #cursor {
961
- position: fixed;
962
- width: 20px;
963
- height: 20px;
964
- background: rgba(255, 255, 255, 0.2);
965
- border: 1px solid rgba(255, 255, 255, 0.4);
966
- border-radius: 50%;
967
- pointer-events: none;
968
- z-index: 9999;
969
- transition: transform 0.1s ease, width 0.3s var(--spring), height 0.3s var(--spring), background 0.3s ease;
970
- mix-blend-mode: difference;
971
- }
972
-
973
- #cursor.active {
974
- transform: scale(3);
975
- background: rgba(255, 51, 68, 0.3);
976
- border-color: var(--red);
977
- }
978
-
979
- /* Modal */
980
- .mo {
981
- display: none;
982
- position: fixed;
983
- inset: 0;
984
- background: rgba(0, 0, 0, 0.85);
985
- z-index: 1000;
986
- place-items: center;
987
- backdrop-filter: blur(8px);
988
- }
989
-
990
- .mo.open {
991
- display: grid;
992
- }
993
-
994
- .mb {
995
- background: var(--s1);
996
- border: 1px solid var(--b1);
997
- border-radius: 16px;
998
- width: 90%;
999
- max-width: 800px;
1000
- max-height: 90vh;
1001
- overflow: hidden;
1002
- box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
1003
- }
1004
-
1005
- .mt {
1006
- padding: 16px 24px;
1007
- border-bottom: 1px solid var(--b1);
1008
- display: flex;
1009
- justify-content: space-between;
1010
- align-items: center;
1011
- background: var(--s2);
1012
- }
1013
-
1014
- .mt h3 {
1015
- font-size: 16px;
1016
- color: var(--t3);
1017
- font-weight: 700;
1018
- }
1019
-
1020
- .mx {
1021
- background: none;
1022
- border: none;
1023
- color: var(--muted);
1024
- font-size: 24px;
1025
- cursor: pointer !important;
1026
- line-height: 1;
1027
- transition: color 0.2s;
1028
- }
1029
-
1030
- .mx:hover {
1031
- color: var(--t3);
1032
- }
1033
-
1034
- .mc {
1035
- padding: 24px;
1036
- }
1037
-
1038
- .mc textarea {
1039
- width: 100%;
1040
- height: 400px;
1041
- background: var(--bg);
1042
- border: 1px solid var(--b1);
1043
- border-radius: 8px;
1044
- padding: 16px;
1045
- color: var(--cyan);
1046
- font-family: var(--mono);
1047
- font-size: 12px;
1048
- line-height: 1.6;
1049
- resize: vertical;
1050
- outline: none;
1051
- }
1052
-
1053
- .mc textarea:focus {
1054
- border-color: var(--cyan);
1055
- box-shadow: 0 0 10px rgba(0, 217, 255, 0.2);
1056
- }
1057
-
1058
- .mf {
1059
- padding: 16px 24px;
1060
- border-top: 1px solid var(--b1);
1061
- display: flex;
1062
- justify-content: flex-end;
1063
- gap: 12px;
1064
- background: var(--s2);
1065
- }
1066
-
1067
- ::-webkit-scrollbar {
1068
- width: 6px;
1069
- height: 6px;
1070
- }
1071
-
1072
- ::-webkit-scrollbar-track {
1073
- background: transparent;
1074
- }
1075
-
1076
- ::-webkit-scrollbar-thumb {
1077
- background: var(--b1);
1078
- border-radius: 10px;
1079
- }
1080
-
1081
- ::-webkit-scrollbar-thumb:hover {
1082
- background: var(--b2);
1083
- }
1084
-
1085
- footer {
1086
- padding: 32px 0;
1087
- border-top: 1px solid var(--b1);
1088
- display: flex;
1089
- justify-content: space-between;
1090
- font-size: 11px;
1091
- color: var(--muted);
1092
- font-weight: 500;
1093
- }
1094
-
1095
- footer a {
1096
- color: var(--muted);
1097
- text-decoration: none;
1098
- transition: color 0.2s;
1099
- border-bottom: 1px solid transparent;
1100
- }
1101
-
1102
- footer a:hover {
1103
- color: var(--t2);
1104
- border-bottom-color: var(--muted);
1105
- }
1106
-
1107
- .idle {
1108
- flex: 1;
1109
- display: flex;
1110
- align-items: center;
1111
- justify-content: center;
1112
- color: var(--b2);
1113
- font-size: 13px;
1114
- font-weight: 500;
1115
- min-height: 100px;
1116
- }
1117
-
1118
- /* Data source badge */
1119
- .ds-badge {
1120
- display: inline-flex;
1121
- align-items: center;
1122
- gap: 6px;
1123
- font-size: 10px;
1124
- font-weight: 800;
1125
- letter-spacing: 0.08em;
1126
- text-transform: uppercase;
1127
- padding: 4px 10px;
1128
- border-radius: 4px;
1129
- margin-left: 12px;
1130
- vertical-align: middle;
1131
- }
1132
- .ds-badge.real {
1133
- background: rgba(0,255,136,0.15);
1134
- color: var(--green);
1135
- border: 1px solid rgba(0,255,136,0.3);
1136
- }
1137
- .ds-badge.demo {
1138
- background: rgba(255,204,0,0.12);
1139
- color: var(--yellow);
1140
- border: 1px solid rgba(255,204,0,0.3);
1141
- }
1142
- .ds-badge.sim {
1143
- background: rgba(255,255,255,0.06);
1144
- color: var(--muted);
1145
- border: 1px solid var(--b1);
1146
- }
1147
-
1148
- /* Risk matrix panel */
1149
- .risk-panel {
1150
- margin: 0 24px 24px;
1151
- border-radius: 10px;
1152
- overflow: hidden;
1153
- border: 1px solid var(--b1);
1154
- }
1155
- .risk-header {
1156
- background: rgba(255,255,255,0.03);
1157
- padding: 10px 16px;
1158
- font-size: 11px;
1159
- font-weight: 700;
1160
- color: var(--muted);
1161
- text-transform: uppercase;
1162
- letter-spacing: 0.08em;
1163
- border-bottom: 1px solid var(--b1);
1164
- display: flex;
1165
- align-items: center;
1166
- gap: 10px;
1167
- }
1168
- .risk-badge {
1169
- font-size: 9px;
1170
- font-weight: 800;
1171
- padding: 2px 6px;
1172
- border-radius: 3px;
1173
- text-transform: uppercase;
1174
- letter-spacing: 0.05em;
1175
- }
1176
- .risk-badge.crit { background: rgba(255,51,68,0.2); color: var(--red); }
1177
- .risk-badge.high { background: rgba(255,153,0,0.2); color: #ff9900; }
1178
- .risk-badge.med { background: rgba(255,204,0,0.2); color: var(--yellow); }
1179
- .risk-row {
1180
- padding: 12px 16px;
1181
- border-bottom: 1px solid rgba(255,255,255,0.04);
1182
- display: grid;
1183
- grid-template-columns: 70px 1fr auto;
1184
- gap: 12px;
1185
- align-items: start;
1186
- font-size: 12px;
1187
- transition: background 0.2s;
1188
- }
1189
- .risk-row:last-child { border-bottom: none; }
1190
- .risk-row:hover { background: rgba(255,255,255,0.02); }
1191
- .risk-loc {
1192
- font-family: var(--mono);
1193
- font-size: 11px;
1194
- color: var(--muted);
1195
- padding-top: 1px;
1196
- }
1197
- .risk-desc { color: var(--t2); line-height: 1.5; }
1198
- .risk-hint {
1199
- font-size: 10px;
1200
- color: var(--cyan);
1201
- margin-top: 4px;
1202
- line-height: 1.4;
1203
- }
1204
- </style>
1205
- </head>
1206
- <div id="cursor"></div>
1207
-
1208
- <div class="w">
1209
- <header>
1210
- <div class="logo">ROCmPort <em>AI</em></div>
1211
- <div class="hr">
1212
- <div class="hd on" id="hdot"></div>
1213
- <span id="hstat">Ready</span>
1214
- </div>
1215
- </header>
1216
-
1217
- <div class="g">
1218
- <div class="p">
1219
- <div class="ph">
1220
- <div><b>//</b> CUDA source</div>
1221
- <div id="lc">0 lines</div>
1222
- </div>
1223
- <textarea class="code" id="inp" spellcheck="false" placeholder="// Paste CUDA code here
1224
- // or pick a demo below
1225
-
1226
- __global__ void kernel(float* A, float* B, int N) {
1227
- int idx = blockIdx.x * blockDim.x + threadIdx.x;
1228
- ...
1229
- }"></textarea>
1230
- <div class="db">
1231
- <span class="l">Select a template:</span>
1232
- <button class="ch" onclick="lk('vector_add', this)">Vector addition</button>
1233
- <button class="ch" onclick="lk('matrix_multiply', this)">Matrix multiplication</button>
1234
- <button class="ch" onclick="lk('convolution_2d', this)">2D convolution</button>
1235
- <button class="ch" onclick="lk('reduction', this)">Parallel reduction</button>
1236
- </div>
1237
- <button class="bg" id="go" onclick="go()">Port to ROCm</button>
1238
- </div>
1239
-
1240
- <div class="p">
1241
- <div class="ph">
1242
- <div><b>//</b> Pipeline</div>
1243
- <div id="pt">0.0s</div>
1244
- </div>
1245
- <div class="timeline" id="tl">
1246
- <!-- Nodes injected by JS -->
1247
- </div>
1248
- <div class="al" id="al">
1249
- <div class="idle">Paste CUDA code to begin migration</div>
1250
- </div>
1251
- </div>
1252
-
1253
- <div class="p fs hide" id="rp">
1254
- <div class="ph">
1255
- <div style="display:flex;align-items:center;gap:12px"><b>//</b> Results</div>
1256
- <div class="tabs" id="tabs">
1257
- <button class="tab on" onclick="stab('sum',this)">Summary</button>
1258
- <button class="tab" onclick="stab('diff',this)">Visual Diff</button>
1259
- <button class="tab" onclick="stab('det',this)">Performance</button>
1260
- </div>
1261
- </div>
1262
- <div id="t-loader" class="hide">
1263
- <div class="skeleton"></div>
1264
- </div>
1265
- <div id="t-sum" class="tc on"></div>
1266
- <div id="t-diff" class="tc"></div>
1267
- <div id="t-det" class="tc">
1268
- </div>
1269
- </div>
1270
- </div>
1271
-
1272
- <footer>
1273
- <div>ROCmPort AI</div>
1274
- <div><a href="https://x.com/TazwarEnan" target="_blank">Tazwar Ahnaf Enan</a> · <a
1275
- href="https://github.com/tazwaryayyyy" target="_blank">GitHub</a></div>
1276
- </footer>
1277
- </div>
1278
-
1279
- <div class="mo" id="modal">
1280
- <div class="mb">
1281
- <div class="mt">
1282
- <h3>Edit ROCm code</h3><button class="mx" onclick="cm()">&times;</button>
1283
- </div>
1284
- <div class="mc"><textarea id="edt"></textarea></div>
1285
- <div class="mf"><button class="bs" onclick="cm()">Cancel</button><button class="bs r"
1286
- onclick="rec()">Re-test</button></div>
1287
- </div>
1288
- </div>
1289
- <script>
1290
- const API = window.location.protocol === 'file:'
1291
- ? 'http://localhost:8000'
1292
- : window.location.origin;
1293
- const S = { code: '', kn: 'custom', run: false, t0: null, iv: null, rep: null, tl: [], kernels: {} };
1294
- const AG = {
1295
- analyzer: { n: 'ANALYZER', i: '🔍' },
1296
- translator: { n: 'TRANSLATOR', i: '🔄' },
1297
- optimizer: { n: 'OPTIMIZER', i: '⚡' },
1298
- tester: { n: 'TESTER', i: '🧪' },
1299
- coordinator: { n: 'COORDINATOR', i: '📋' }
1300
- };
1301
-
1302
- // Custom Cursor Logic
1303
- const cur = document.getElementById('cursor');
1304
- document.addEventListener('mousemove', (e) => {
1305
- cur.style.left = e.clientX + 'px';
1306
- cur.style.top = e.clientY + 'px';
1307
- const target = e.target;
1308
- const isClickable = target.onclick ||
1309
- target.tagName === 'BUTTON' ||
1310
- target.tagName === 'A' ||
1311
- target.tagName === 'TEXTAREA' ||
1312
- target.classList.contains('ch') ||
1313
- target.classList.contains('tab');
1314
-
1315
- if (isClickable) {
1316
- cur.classList.add('active');
1317
- if (target.id === 'go') cur.style.background = 'rgba(255, 51, 68, 0.5)';
1318
- else cur.style.background = 'rgba(255, 255, 255, 0.3)';
1319
- } else {
1320
- cur.classList.remove('active');
1321
- cur.style.background = 'rgba(255, 255, 255, 0.2)';
1322
- }
1323
- });
1324
-
1325
- async function init() {
1326
- const ta = document.getElementById('inp');
1327
- ta.oninput = () => {
1328
- document.getElementById('lc').textContent = ta.value.split('\n').length + ' lines';
1329
- S.code = ta.value;
1330
- };
1331
- try {
1332
- const r = await fetch(API + '/demo-kernels');
1333
- S.kernels = await r.json();
1334
- } catch (e) { S.kernels = FB; }
1335
- }
1336
-
1337
- function lk(n, btn) {
1338
- document.querySelectorAll('.ch').forEach(c => c.classList.remove('on'));
1339
- btn.classList.add('on');
1340
- const code = S.kernels[n] || FB[n] || '', ta = document.getElementById('inp');
1341
- ta.value = code; S.code = code; S.kn = n;
1342
- document.getElementById('lc').textContent = code.split('\n').length + ' lines';
1343
- }
1344
-
1345
- function stab(id, btn) {
1346
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('on'));
1347
- document.querySelectorAll('.tc').forEach(t => t.classList.remove('on'));
1348
- btn.classList.add('on');
1349
- document.getElementById('t-' + id).classList.add('on');
1350
- if (id === 'diff' && S.rep) rDiff(S.code, S.rep.optimized_code);
1351
- }
1352
-
1353
- async function go() {
1354
- if (S.run) return;
1355
- const code = document.getElementById('inp').value.trim();
1356
- if (!code) return;
1357
-
1358
- S.code = code; S.run = true; S.t0 = Date.now(); S.tl = [];
1359
- const btn = document.getElementById('go');
1360
- btn.disabled = true;
1361
- btn.textContent = 'Running pipeline...';
1362
-
1363
- document.getElementById('hstat').textContent = 'Pipeline running...';
1364
- document.getElementById('rp').classList.add('hide');
1365
-
1366
- bLog();
1367
- sTimer();
1368
-
1369
- try {
1370
- const simpleModeCheckbox = document.getElementById('sm');
1371
- const res = await fetch(API + '/port', {
1372
- method: 'POST',
1373
- headers: { 'Content-Type': 'application/json' },
1374
- body: JSON.stringify({
1375
- cuda_code: code,
1376
- kernel_name: S.kn,
1377
- simple_mode: simpleModeCheckbox ? simpleModeCheckbox.checked : false
1378
- })
1379
- });
1380
-
1381
- // Show results panel with loader immediately
1382
- document.getElementById('rp').classList.remove('hide');
1383
- document.getElementById('t-loader').classList.remove('hide');
1384
- document.getElementById('t-sum').classList.remove('on');
1385
- document.getElementById('t-diff').classList.remove('on');
1386
- document.getElementById('t-det').classList.remove('on');
1387
-
1388
- const rd = res.body.getReader(), dc = new TextDecoder();
1389
- let buf = '';
1390
- while (true) {
1391
- const { done, value } = await rd.read();
1392
- if (done) break;
1393
- buf += dc.decode(value, { stream: true });
1394
- const lines = buf.split('\n');
1395
- buf = lines.pop();
1396
- for (const ln of lines) {
1397
- if (!ln.startsWith('data: ')) continue;
1398
- const raw = ln.slice(6).trim();
1399
- if (raw === '[DONE]') { done_(); break; }
1400
- try { hEvt(JSON.parse(raw)); } catch (e) { console.error('Parse error:', e); }
1401
- }
1402
- }
1403
- } catch (e) {
1404
- document.getElementById('hstat').textContent = 'Pipeline error';
1405
- document.getElementById('t-loader').classList.add('hide'); // Hide loader on error
1406
- console.error(e);
1407
- } finally {
1408
- xTimer();
1409
- S.run = false;
1410
- btn.disabled = false;
1411
- btn.textContent = 'Port to ROCm';
1412
- document.getElementById('t-loader').classList.add('hide');
1413
- }
1414
- }
1415
-
1416
- function hEvt(ev) {
1417
- uLog(ev.agent, ev.status, ev.message, ev.detail);
1418
- if (ev.agent === 'tester' && (ev.status === 'done' || ev.status === 'failed')) {
1419
- const m = ev.message.match(/([\d.]+)x/);
1420
- if (m) {
1421
- const sp = parseFloat(m[1]), ok = sp >= 1, im = ev.message.match(/Iteration (\d+)/i);
1422
- S.tl.push({
1423
- label: 'Iteration ' + (im ? im[1] : S.tl.length + 1) + (ok ? ' (optimized)' : ' (baseline)'),
1424
- speedup: sp,
1425
- good: ok
1426
- });
1427
- }
1428
- }
1429
- if (ev.agent === 'coordinator' && ev.status === 'done' && ev.detail) {
1430
- try {
1431
- const r = JSON.parse(ev.detail);
1432
- S.rep = r;
1433
- rRes(r, S.tl);
1434
- } catch (e) { console.error('Coordinator detail parse error:', e); }
1435
- }
1436
- }
1437
-
1438
- function done_() {
1439
- document.getElementById('hstat').textContent = 'Pipeline complete';
1440
- document.getElementById('t-loader').classList.add('hide');
1441
- if (!S.rep) {
1442
- document.getElementById('t-sum').innerHTML = '<div class="idle">Migration finished but no report was generated. Check agent logs for details.</div>';
1443
- document.getElementById('t-sum').classList.add('on');
1444
- }
1445
- }
1446
-
1447
- function bLog() {
1448
- const el = document.getElementById('al');
1449
- const tl = document.getElementById('tl');
1450
- el.innerHTML = '';
1451
- tl.innerHTML = '';
1452
-
1453
- let i = 0;
1454
- for (const [k, obj] of Object.entries(AG)) {
1455
- // Log row
1456
- const d = document.createElement('div');
1457
- d.className = 'ar';
1458
- d.id = 'ar-' + k;
1459
- d.style.animationDelay = (i * 0.1) + 's';
1460
- d.innerHTML = `
1461
- <div class="at">
1462
- <span class="an">${obj.n}</span>
1463
- <span class="am" id="am-${k}">Waiting</span>
1464
- </div>
1465
- <div class="ad" id="ad-${k}"></div>`;
1466
- el.appendChild(d);
1467
-
1468
- // Timeline node
1469
- const n = document.createElement('div');
1470
- n.className = 'node';
1471
- n.id = 'nd-' + k;
1472
- n.title = obj.n;
1473
- n.innerHTML = `<div class="ni">${obj.i}</div><div class="nl">${obj.n.slice(0, 3)}</div>`;
1474
- tl.appendChild(n);
1475
- i++;
1476
- }
1477
- }
1478
-
1479
- function uLog(a, s, m, d) {
1480
- const row = document.getElementById('ar-' + a);
1481
- const node = document.getElementById('nd-' + a);
1482
- if (!row || !node) return;
1483
-
1484
- const statusClass = { running: 'run', done: 'done', failed: 'fail', retrying: 'retry' }[s] || '';
1485
- row.className = 'ar ' + statusClass;
1486
- node.className = 'node ' + (s === 'running' ? 'on' : s === 'retrying' ? 'retry' : s === 'done' ? 'done' : s === 'failed' ? 'fail' : '');
1487
-
1488
- const me = document.getElementById('am-' + a);
1489
- if (me) me.textContent = m;
1490
-
1491
- // Node tooltip message update
1492
- node.title = m;
1493
-
1494
- const de = document.getElementById('ad-' + a);
1495
- if (de && d) {
1496
- de.innerHTML = esc(d)
1497
- .replace(/\u26a0\ufe0f([^\n]*)/g, '<span class="w">⚠️ $1</span>')
1498
- .replace(/\u2705([^\n]*)/g, '<span class="g">✅ $1</span>');
1499
- de.scrollTop = de.scrollHeight;
1500
- }
1501
- }
1502
-
1503
- function rRes(r, tl) {
1504
- // Hide loader, show summary
1505
- document.getElementById('t-loader').classList.add('hide');
1506
- document.getElementById('t-sum').classList.add('on');
1507
-
1508
- const v = r.verification || {}, bw = r.bandwidth_utilized;
1509
- const dot = ok => `<div class="sum-dot ${ok === true ? 'ok' : ok === false ? 'no' : 'na'}"></div>`;
1510
-
1511
- // Data source badge
1512
- const ds = r.data_source || 'simulated';
1513
- const dsBadge = ds === 'real_rocm'
1514
- ? `<span class="ds-badge real">🟢 LIVE MI300X</span>`
1515
- : ds === 'demo_artifact'
1516
- ? `<span class="ds-badge demo">🟡 DEMO DATA</span>`
1517
- : `<span class="ds-badge sim">⚪ SIMULATED</span>`;
1518
-
1519
- document.getElementById('t-sum').innerHTML = `
1520
- <div class="sum-row">
1521
- <div class="sum-big">
1522
- ${r.speedup}x
1523
- ${dsBadge}
1524
- <span class="u">vs baseline hipify</span>
1525
- <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>
1526
- </div>
1527
- <div class="sum-sep"></div>
1528
- <div>
1529
- <div class="sum-chk">${dot(v.compiled_successfully)} Compiled${v.mock_mode ? ' (simulated)' : ''}</div>
1530
- <div class="sum-chk" style="margin-top:8px">${dot(v.executed_without_error)} Executed without error</div>
1531
- <div class="sum-chk" style="margin-top:8px">${dot(v.output_matches_expected)} Output matches expected</div>
1532
- </div>
1533
- <div class="sum-sep"></div>
1534
- <div class="sum-type">${(r.bottleneck || 'optimized').toLowerCase()}</div>
1535
- </div>
1536
- <div class="sum-bar">
1537
- <button class="bs r" onclick="om()">Edit code</button>
1538
- <button class="bs gr" onclick="exM()">Export PR</button>
1539
- <button class="bs" onclick="dlR()">Download report</button>
1540
- <div class="sp"></div>
1541
- </div>
1542
- <div class="sn" id="sn" style="margin: 24px; border-left-width: 4px;">
1543
- <div style="font-weight: bold; margin-bottom: 8px; color: var(--cyan);">🧠 Simple explanation</div>
1544
- ${r.simplified_explanation ? esc(r.simplified_explanation) : '<em>Simplified explanation will appear here</em>'}
1545
- </div>
1546
- ${riskMatrix(r.static_risk_report)}`;
1547
-
1548
- // Details tab
1549
- let dh = `<div class="dm">
1550
- <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>
1551
- <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>
1552
- <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>
1553
- <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>
1554
- <div class="di"><div class="dl">Type</div><div class="dv t">${(r.bottleneck || '—').toUpperCase()}</div><div class="ds">workload classification</div></div>
1555
- </div>`;
1556
-
1557
- if (tl.length) {
1558
- dh += '<div class="bk"><div class="bk-t">Benchmark iterations (optimized vs baseline hipify)</div>';
1559
- tl.forEach(d => {
1560
- const pct = Math.min(Math.max((d.speedup / 2) * 100, 3), 95);
1561
- dh += `<div class="br">
1562
- <div class="bl">${esc(d.label)}</div>
1563
- <div class="bt"><div class="bf ${d.good ? 'good' : 'bad'}" style="width: 0" data-w="${pct}%"></div></div>
1564
- <div class="bv ${d.good ? 'good' : 'bad'}">${d.speedup}x</div>
1565
- </div>`;
1566
- });
1567
- dh += '</div>';
1568
- }
1569
-
1570
- document.getElementById('t-det').innerHTML = dh;
1571
- tsm(); // Ensure simple note visibility matches current toggle state
1572
-
1573
- // Progress bar animation
1574
- setTimeout(() => {
1575
- document.querySelectorAll('.bf[data-w]').forEach(b => {
1576
- b.style.width = b.dataset.w;
1577
- });
1578
- }, 100);
1579
- }
1580
-
1581
- function riskMatrix(srr) {
1582
- if (!srr || !srr.items || srr.items.length === 0) return '';
1583
-
1584
- const levelClass = { CRITICAL: 'crit', HIGH: 'high', MEDIUM: 'med' };
1585
- const critical = srr.critical_count || 0;
1586
- const high = srr.high_count || 0;
1587
- const medium = srr.medium_count || 0;
1588
-
1589
- let rows = srr.items.map(item => {
1590
- const cls = levelClass[item.risk_level] || 'med';
1591
- const loc = item.line ? `line ${item.line}` : '—';
1592
- return `<div class="risk-row">
1593
- <div class="risk-loc">${esc(loc)}</div>
1594
- <div>
1595
- <div class="risk-desc">${esc(item.description)}</div>
1596
- <div class="risk-hint">Fix: ${esc(item.amd_fix_hint)}</div>
1597
- </div>
1598
- <div><span class="risk-badge ${cls}">${esc(item.risk_level)}</span></div>
1599
- </div>`;
1600
- }).join('');
1601
-
1602
- const scanMs = srr.scan_duration_ms != null ? `${srr.scan_duration_ms.toFixed(1)}ms` : '';
1603
-
1604
- return `<div class="risk-panel">
1605
- <div class="risk-header">
1606
- ⚠️ Static Risk Scan
1607
- ${critical > 0 ? `<span class="risk-badge crit">${critical} CRITICAL</span>` : ''}
1608
- ${high > 0 ? `<span class="risk-badge high">${high} HIGH</span>` : ''}
1609
- ${medium > 0 ? `<span class="risk-badge med">${medium} MEDIUM</span>` : ''}
1610
- <span style="margin-left:auto;font-size:9px;opacity:0.5">Pure-Python pre-scan · ${scanMs}</span>
1611
- </div>
1612
- ${rows}
1613
- </div>`;
1614
- }
1615
-
1616
- function rDiff(o, n) {
1617
- if (!o || !n) return;
1618
- document.getElementById('t-diff').innerHTML = `<div class="dg">
1619
- <div class="dfs"><div class="dfh"><span class="dft cu">CUDA</span> Original Source</div><pre class="dfp" id="d-o"></pre></div>
1620
- <div class="dfs"><div class="dfh"><span class="dft ro">ROCm</span> Optimized HIP</div><pre class="dfp" id="d-n"></pre></div>
1621
- </div>`;
1622
-
1623
- const oL = o.split('\n'), nL = n.split('\n'), mx = Math.max(oL.length, nL.length);
1624
- let oH = '', nH = '';
1625
- for (let i = 0; i < mx; i++) {
1626
- const a = oL[i] ?? '', b = nL[i] ?? '', c = a !== b;
1627
- oH += `<span class="${c ? 'dlo' : ''}">${esc(a)}\n</span>`;
1628
- nH += `<span class="${c ? 'dln' : ''}">${esc(b)}\n</span>`;
1629
- }
1630
- const left = document.getElementById('d-o');
1631
- const right = document.getElementById('d-n');
1632
- left.innerHTML = oH;
1633
- right.innerHTML = nH;
1634
-
1635
- // Keep both panes aligned while scrolling for easier comparison.
1636
- let syncing = false;
1637
- left.addEventListener('scroll', () => {
1638
- if (syncing) return;
1639
- syncing = true;
1640
- right.scrollTop = left.scrollTop;
1641
- syncing = false;
1642
- }, { passive: true });
1643
- right.addEventListener('scroll', () => {
1644
- if (syncing) return;
1645
- syncing = true;
1646
- left.scrollTop = right.scrollTop;
1647
- syncing = false;
1648
- }, { passive: true });
1649
- }
1650
-
1651
- function sTimer() { S.iv = setInterval(() => { document.getElementById('pt').textContent = ((Date.now() - S.t0) / 1000).toFixed(1) + 's' }, 100) }
1652
- function xTimer() { clearInterval(S.iv) }
1653
-
1654
- function dlR() {
1655
- const r = S.rep; if (!r) return;
1656
- 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`;
1657
- const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([md], { type: 'text/markdown' })); a.download = 'rocmport-migration-report.md'; a.click();
1658
- }
1659
-
1660
- function om() { if (!S.rep) return alert('No results yet!'); document.getElementById('edt').value = S.rep?.optimized_code || ''; document.getElementById('modal').classList.add('open') }
1661
- function cm() { document.getElementById('modal').classList.remove('open') }
1662
-
1663
- async function rec() {
1664
- const code = document.getElementById('edt').value.trim(); if (!code) return;
1665
- try {
1666
- const res = await fetch(API + '/recompile', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ edited_code: code, kernel_name: S.kn }) });
1667
- const r = await res.json();
1668
- if (r.success) { cm(); if (r.result) rRes(r.result, S.tl); }
1669
- else alert('Failed: ' + (r.detail || 'Unknown'))
1670
- } catch (e) { alert('Error: ' + e.message) }
1671
- }
1672
-
1673
- async function exM() {
1674
- if (!S.rep) return;
1675
- try {
1676
- const currentInput = document.getElementById('inp')?.value || '';
1677
- const payload = {
1678
- original_cuda: S.code || currentInput,
1679
- final_rocm: S.rep.optimized_code || '',
1680
- migration_report: S.rep
1681
- };
1682
- const res = await fetch(API + '/export', {
1683
- method: 'POST',
1684
- headers: { 'Content-Type': 'application/json' },
1685
- body: JSON.stringify(payload)
1686
- });
1687
-
1688
- if (!res.ok) {
1689
- let msg = `Export failed (${res.status})`;
1690
- try {
1691
- const err = await res.json();
1692
- if (err && err.detail) msg = err.detail;
1693
- } catch (_) { }
1694
- throw new Error(msg);
1695
- }
1696
-
1697
- const a = document.createElement('a');
1698
- a.href = URL.createObjectURL(await res.blob());
1699
- a.download = 'rocmport-migration.zip';
1700
- a.click();
1701
- } catch (e) {
1702
- alert('Export error: ' + (e.message || 'Unknown error'));
1703
- }
1704
- }
1705
-
1706
- function tsm() {
1707
- const sn = document.getElementById('sn');
1708
- if (sn) sn.classList.remove('hide');
1709
- }
1710
-
1711
- function esc(s) { return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') }
1712
-
1713
- const FB = {
1714
- 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}`,
1715
- 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}`,
1716
- 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}`,
1717
- 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}`
1718
- };
1719
-
1720
- init();
1721
- </script>
1722
- </body>
1723
-
1724
  </html>
 
1
+ <!DOCTYPE html>
2
  <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>ROCmPort AI</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ <script type="module" src="/src/main.jsx"></script>
17
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "rocmport-ai-frontend",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.3.4",
16
+ "autoprefixer": "^10.4.20",
17
+ "postcss": "^8.4.47",
18
+ "tailwindcss": "^3.4.15",
19
+ "vite": "^5.4.10"
20
+ }
21
+ }
frontend/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react'
2
+
3
+ // ─── Template Kernels ─────────────────────────────────────────────────────────
4
+
5
+ const KERNEL_VECTOR_ADD = String.raw`
6
+ #include <cuda_runtime.h>
7
+ #include <stdio.h>
8
+
9
+ // Vector addition kernel with intentional warp size bug
10
+ __global__ void vectorAdd(const float *A, const float *B, float *C, int numElements) {
11
+ int i = blockDim.x * blockIdx.x + threadIdx.x;
12
+
13
+ if (i < numElements) {
14
+ C[i] = A[i] + B[i];
15
+
16
+ // Intentional warp size bug - assumes 32 threads per warp
17
+ // This will break on AMD wavefront (64 threads)
18
+ if (threadIdx.x % 32 == 0) {
19
+ // This synchronization only works for CUDA's 32-thread warps
20
+ printf("Thread %d in warp %d completed\n", threadIdx.x, threadIdx.x / 32);
21
+ }
22
+ }
23
+ }
24
+
25
+ int main(void) {
26
+ int numElements = 50000;
27
+ size_t size = numElements * sizeof(float);
28
+
29
+ // Allocate host memory
30
+ float *h_A = (float *)malloc(size);
31
+ float *h_B = (float *)malloc(size);
32
+ float *h_C = (float *)malloc(size);
33
+
34
+ // Initialize host vectors
35
+ for (int i = 0; i < numElements; ++i) {
36
+ h_A[i] = rand() / (float)RAND_MAX;
37
+ h_B[i] = rand() / (float)RAND_MAX;
38
+ }
39
+
40
+ // Allocate device memory
41
+ float *d_A, *d_B, *d_C;
42
+ cudaMalloc((void **)&d_A, size);
43
+ cudaMalloc((void **)&d_B, size);
44
+ cudaMalloc((void **)&d_C, size);
45
+
46
+ // Copy data from host to device
47
+ cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
48
+ cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
49
+
50
+ // Launch kernel
51
+ int threadsPerBlock = 256;
52
+ int blocksPerGrid = (numElements + threadsPerBlock - 1) / threadsPerBlock;
53
+ printf("Launching kernel with %d blocks of %d threads\n", blocksPerGrid, threadsPerBlock);
54
+
55
+ vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, numElements);
56
+ cudaDeviceSynchronize();
57
+
58
+ // Copy result back to host
59
+ cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
60
+
61
+ // Verify result
62
+ for (int i = 0; i < numElements; ++i) {
63
+ if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5) {
64
+ printf("Test FAILED at element %d!\n", i);
65
+ break;
66
+ }
67
+ }
68
+ printf("Test PASSED\n");
69
+
70
+ // Free device memory
71
+ cudaFree(d_A);
72
+ cudaFree(d_B);
73
+ cudaFree(d_C);
74
+
75
+ // Free host memory
76
+ free(h_A);
77
+ free(h_B);
78
+ free(h_C);
79
+
80
+ printf("Done\n");
81
+ return 0;
82
+ }
83
+ `.trim()
84
+
85
+ const KERNEL_MATRIX_MULTIPLY = String.raw`
86
+ #include <cuda_runtime.h>
87
+ #include <stdio.h>
88
+ #include <stdlib.h>
89
+
90
+ // Matrix multiplication kernel with intentional warp size bug
91
+ // C = A * B
92
+ // A: M x K, B: K x N, C: M x N
93
+ __global__ void matrixMultiply(const float *A, const float *B, float *C, int M, int N, int K) {
94
+ int row = blockIdx.y * blockDim.y + threadIdx.y;
95
+ int col = blockIdx.x * blockDim.x + threadIdx.x;
96
+
97
+ if (row < M && col < N) {
98
+ float sum = 0.0f;
99
+ for (int k = 0; k < K; ++k) {
100
+ sum += A[row * K + k] * B[k * N + col];
101
+ }
102
+ C[row * N + col] = sum;
103
+
104
+ // Intentional warp size bug - assumes 32 threads per warp
105
+ // This will cause incorrect behavior on AMD wavefront (64 threads)
106
+ if (threadIdx.x % 32 == 0 && threadIdx.y % 32 == 0) {
107
+ // This warp-level synchronization only works for CUDA
108
+ printf("Block (%d,%d) warp (%d,%d) computed element (%d,%d) = %f\n",
109
+ blockIdx.x, blockIdx.y, threadIdx.x / 32, threadIdx.y / 32, row, col, sum);
110
+ }
111
+ }
112
+ }
113
+
114
+ // Optimized version with shared memory (for comparison)
115
+ __global__ void matrixMultiplyShared(const float *A, const float *B, float *C, int M, int N, int K) {
116
+ __shared__ float tileA[32][32];
117
+ __shared__ float tileB[32][32];
118
+
119
+ int row = blockIdx.y * blockDim.y + threadIdx.y;
120
+ int col = blockIdx.x * blockDim.x + threadIdx.x;
121
+
122
+ float sum = 0.0f;
123
+
124
+ for (int tile = 0; tile < (K + 31) / 32; ++tile) {
125
+ if (row < M && tile * 32 + threadIdx.x < K) {
126
+ tileA[threadIdx.y][threadIdx.x] = A[row * K + tile * 32 + threadIdx.x];
127
+ } else {
128
+ tileA[threadIdx.y][threadIdx.x] = 0.0f;
129
+ }
130
+ if (col < N && tile * 32 + threadIdx.y < K) {
131
+ tileB[threadIdx.y][threadIdx.x] = B[(tile * 32 + threadIdx.y) * N + col];
132
+ } else {
133
+ tileB[threadIdx.y][threadIdx.x] = 0.0f;
134
+ }
135
+ __syncthreads();
136
+
137
+ for (int k = 0; k < 32; ++k) {
138
+ sum += tileA[threadIdx.y][k] * tileB[k][threadIdx.x];
139
+ }
140
+ __syncthreads();
141
+ }
142
+
143
+ if (row < M && col < N) {
144
+ C[row * N + col] = sum;
145
+ }
146
+ }
147
+
148
+ int main(int argc, char **argv) {
149
+ int M = 512, N = 512, K = 512;
150
+
151
+ size_t size_A = M * K * sizeof(float);
152
+ size_t size_B = K * N * sizeof(float);
153
+ size_t size_C = M * N * sizeof(float);
154
+
155
+ float *h_A = (float *)malloc(size_A);
156
+ float *h_B = (float *)malloc(size_B);
157
+ float *h_C = (float *)malloc(size_C);
158
+ float *h_C_ref = (float *)malloc(size_C);
159
+
160
+ for (int i = 0; i < M * K; ++i) h_A[i] = rand() / (float)RAND_MAX;
161
+ for (int i = 0; i < K * N; ++i) h_B[i] = rand() / (float)RAND_MAX;
162
+
163
+ float *d_A, *d_B, *d_C, *d_C_ref;
164
+ cudaMalloc(&d_A, size_A);
165
+ cudaMalloc(&d_B, size_B);
166
+ cudaMalloc(&d_C, size_C);
167
+ cudaMalloc(&d_C_ref, size_C);
168
+
169
+ cudaMemcpy(d_A, h_A, size_A, cudaMemcpyHostToDevice);
170
+ cudaMemcpy(d_B, h_B, size_B, cudaMemcpyHostToDevice);
171
+
172
+ dim3 threadsPerBlock(32, 32);
173
+ dim3 blocksPerGrid((N + threadsPerBlock.x - 1) / threadsPerBlock.x,
174
+ (M + threadsPerBlock.y - 1) / threadsPerBlock.y);
175
+
176
+ printf("Matrix dimensions: %dx%d * %dx%d = %dx%d\n", M, K, K, N, M, N);
177
+ printf("Launching kernel with grid (%d,%d) and block (%d,%d)\n",
178
+ blocksPerGrid.x, blocksPerGrid.y, threadsPerBlock.x, threadsPerBlock.y);
179
+
180
+ // Warmup
181
+ matrixMultiply<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C_ref, M, N, K);
182
+ cudaDeviceSynchronize();
183
+
184
+ cudaEvent_t start, stop;
185
+ cudaEventCreate(&start);
186
+ cudaEventCreate(&stop);
187
+
188
+ cudaEventRecord(start);
189
+ matrixMultiply<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C_ref, M, N, K);
190
+ cudaEventRecord(stop);
191
+ cudaEventSynchronize(stop);
192
+ float basic_time = 0;
193
+ cudaEventElapsedTime(&basic_time, start, stop);
194
+ printf("Basic kernel time: %.3f ms\n", basic_time);
195
+
196
+ cudaEventRecord(start);
197
+ matrixMultiplyShared<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, M, N, K);
198
+ cudaEventRecord(stop);
199
+ cudaEventSynchronize(stop);
200
+ float shared_time = 0;
201
+ cudaEventElapsedTime(&shared_time, start, stop);
202
+ printf("Shared memory kernel time: %.3f ms\n", shared_time);
203
+ printf("Speedup: %.2fx\n", basic_time / shared_time);
204
+
205
+ cudaMemcpy(h_C_ref, d_C_ref, size_C, cudaMemcpyDeviceToHost);
206
+ cudaMemcpy(h_C, d_C, size_C, cudaMemcpyDeviceToHost);
207
+
208
+ bool correct = true;
209
+ for (int i = 0; i < M * N; ++i) {
210
+ if (fabs(h_C[i] - h_C_ref[i]) > 1e-5) {
211
+ printf("Mismatch at element %d: %f != %f\n", i, h_C[i], h_C_ref[i]);
212
+ correct = false;
213
+ break;
214
+ }
215
+ }
216
+ printf(correct ? "Verification PASSED\n" : "Verification FAILED\n");
217
+
218
+ cudaFree(d_A); cudaFree(d_B); cudaFree(d_C); cudaFree(d_C_ref);
219
+ free(h_A); free(h_B); free(h_C); free(h_C_ref);
220
+
221
+ printf("Done\n");
222
+ return 0;
223
+ }
224
+ `.trim()
225
+
226
+ const KERNEL_CONVOLUTION_2D = String.raw`
227
+ #include <cuda_runtime.h>
228
+ #include <stdio.h>
229
+ #include <stdlib.h>
230
+
231
+ // 2D Convolution kernel with intentional warp size bug
232
+ __global__ void convolution2D(const float *input, const float *kernel, float *output,
233
+ int input_height, int input_width, int kernel_size,
234
+ int output_height, int output_width) {
235
+ int row = blockIdx.y * blockDim.y + threadIdx.y;
236
+ int col = blockIdx.x * blockDim.x + threadIdx.x;
237
+
238
+ if (row < output_height && col < output_width) {
239
+ float sum = 0.0f;
240
+ int kernel_radius = kernel_size / 2;
241
+
242
+ for (int i = -kernel_radius; i <= kernel_radius; i++) {
243
+ for (int j = -kernel_radius; j <= kernel_radius; j++) {
244
+ int input_row = row + i;
245
+ int input_col = col + j;
246
+ if (input_row >= 0 && input_row < input_height &&
247
+ input_col >= 0 && input_col < input_width) {
248
+ int kernel_row = i + kernel_radius;
249
+ int kernel_col = j + kernel_radius;
250
+ sum += input[input_row * input_width + input_col] *
251
+ kernel[kernel_row * kernel_size + kernel_col];
252
+ }
253
+ }
254
+ }
255
+ output[row * output_width + col] = sum;
256
+
257
+ // Intentional warp size bug - assumes 32 threads per warp
258
+ // This will break on AMD wavefront (64 threads)
259
+ if (threadIdx.x % 32 == 0 && threadIdx.y % 32 == 0) {
260
+ printf("Warp (%d,%d) processed output pixel (%d,%d) = %f\n",
261
+ threadIdx.x / 32, threadIdx.y / 32, row, col, sum);
262
+ }
263
+ }
264
+ }
265
+
266
+ // Shared memory version for comparison
267
+ __global__ void convolution2DShared(const float *input, const float *kernel, float *output,
268
+ int input_height, int input_width, int kernel_size,
269
+ int output_height, int output_width) {
270
+ __shared__ float shared_input[32 + 6][32 + 6]; // +6 for 3x3 kernel padding
271
+ __shared__ float shared_kernel[7][7]; // Max 7x7 kernel
272
+
273
+ int row = blockIdx.y * blockDim.y + threadIdx.y;
274
+ int col = blockIdx.x * blockDim.x + threadIdx.x;
275
+ int kernel_radius = kernel_size / 2;
276
+
277
+ if (threadIdx.x < kernel_size && threadIdx.y < kernel_size) {
278
+ shared_kernel[threadIdx.y][threadIdx.x] =
279
+ kernel[threadIdx.y * kernel_size + threadIdx.x];
280
+ }
281
+
282
+ int input_row = blockIdx.y * blockDim.y + threadIdx.y - kernel_radius;
283
+ int input_col = blockIdx.x * blockDim.x + threadIdx.x - kernel_radius;
284
+
285
+ if (input_row >= 0 && input_row < input_height &&
286
+ input_col >= 0 && input_col < input_width) {
287
+ shared_input[threadIdx.y][threadIdx.x] =
288
+ input[input_row * input_width + input_col];
289
+ } else {
290
+ shared_input[threadIdx.y][threadIdx.x] = 0.0f;
291
+ }
292
+ __syncthreads();
293
+
294
+ if (row < output_height && col < output_width) {
295
+ float sum = 0.0f;
296
+ for (int i = 0; i < kernel_size; i++)
297
+ for (int j = 0; j < kernel_size; j++)
298
+ sum += shared_input[threadIdx.y + i][threadIdx.x + j] * shared_kernel[i][j];
299
+ output[row * output_width + col] = sum;
300
+ }
301
+ }
302
+
303
+ int main(int argc, char **argv) {
304
+ int input_height = 1024, input_width = 1024, kernel_size = 3;
305
+ int output_height = input_height - kernel_size + 1;
306
+ int output_width = input_width - kernel_size + 1;
307
+
308
+ size_t input_size = input_height * input_width * sizeof(float);
309
+ size_t kernel_size_bytes = kernel_size * kernel_size * sizeof(float);
310
+ size_t output_size = output_height * output_width * sizeof(float);
311
+
312
+ printf("Input: %dx%d, Kernel: %dx%d, Output: %dx%d\n",
313
+ input_height, input_width, kernel_size, kernel_size, output_height, output_width);
314
+
315
+ float *h_input = (float *)malloc(input_size);
316
+ float *h_kernel = (float *)malloc(kernel_size_bytes);
317
+ float *h_output = (float *)malloc(output_size);
318
+ float *h_output_ref = (float *)malloc(output_size);
319
+
320
+ for (int i = 0; i < input_height * input_width; i++)
321
+ h_input[i] = rand() / (float)RAND_MAX;
322
+
323
+ float kernel_3x3[9] = {-1, -1, -1, -1, 8, -1, -1, -1, -1};
324
+ for (int i = 0; i < kernel_size * kernel_size; i++)
325
+ h_kernel[i] = kernel_3x3[i];
326
+
327
+ float *d_input, *d_kernel, *d_output, *d_output_ref;
328
+ cudaMalloc(&d_input, input_size);
329
+ cudaMalloc(&d_kernel, kernel_size_bytes);
330
+ cudaMalloc(&d_output, output_size);
331
+ cudaMalloc(&d_output_ref, output_size);
332
+
333
+ cudaMemcpy(d_input, h_input, input_size, cudaMemcpyHostToDevice);
334
+ cudaMemcpy(d_kernel, h_kernel, kernel_size_bytes, cudaMemcpyHostToDevice);
335
+
336
+ dim3 threadsPerBlock(32, 32);
337
+ dim3 blocksPerGrid((output_width + threadsPerBlock.x - 1) / threadsPerBlock.x,
338
+ (output_height + threadsPerBlock.y - 1) / threadsPerBlock.y);
339
+
340
+ printf("Launching kernel with grid (%d,%d) and block (%d,%d)\n",
341
+ blocksPerGrid.x, blocksPerGrid.y, threadsPerBlock.x, threadsPerBlock.y);
342
+
343
+ // Warmup
344
+ convolution2D<<<blocksPerGrid, threadsPerBlock>>>(
345
+ d_input, d_kernel, d_output_ref,
346
+ input_height, input_width, kernel_size, output_height, output_width);
347
+ cudaDeviceSynchronize();
348
+
349
+ cudaEvent_t start, stop;
350
+ cudaEventCreate(&start);
351
+ cudaEventCreate(&stop);
352
+
353
+ cudaEventRecord(start);
354
+ convolution2D<<<blocksPerGrid, threadsPerBlock>>>(
355
+ d_input, d_kernel, d_output_ref,
356
+ input_height, input_width, kernel_size, output_height, output_width);
357
+ cudaEventRecord(stop);
358
+ cudaEventSynchronize(stop);
359
+ float basic_time = 0;
360
+ cudaEventElapsedTime(&basic_time, start, stop);
361
+ printf("Basic kernel time: %.3f ms\n", basic_time);
362
+
363
+ cudaEventRecord(start);
364
+ convolution2DShared<<<blocksPerGrid, threadsPerBlock>>>(
365
+ d_input, d_kernel, d_output,
366
+ input_height, input_width, kernel_size, output_height, output_width);
367
+ cudaEventRecord(stop);
368
+ cudaEventSynchronize(stop);
369
+ float shared_time = 0;
370
+ cudaEventElapsedTime(&shared_time, start, stop);
371
+ printf("Shared memory kernel time: %.3f ms\n", shared_time);
372
+ printf("Speedup: %.2fx\n", basic_time / shared_time);
373
+
374
+ cudaMemcpy(h_output_ref, d_output_ref, output_size, cudaMemcpyDeviceToHost);
375
+ cudaMemcpy(h_output, d_output, output_size, cudaMemcpyDeviceToHost);
376
+
377
+ bool correct = true;
378
+ for (int i = 0; i < 100 && i < output_height * output_width; i++) {
379
+ if (fabs(h_output[i] - h_output_ref[i]) > 1e-5) {
380
+ printf("Mismatch at element %d: %f != %f\n", i, h_output[i], h_output_ref[i]);
381
+ correct = false;
382
+ break;
383
+ }
384
+ }
385
+ printf(correct ? "Verification PASSED (first 100 elements)\n" : "Verification FAILED\n");
386
+
387
+ cudaFree(d_input); cudaFree(d_kernel); cudaFree(d_output); cudaFree(d_output_ref);
388
+ free(h_input); free(h_kernel); free(h_output); free(h_output_ref);
389
+
390
+ printf("Done\n");
391
+ return 0;
392
+ }
393
+ `.trim()
394
+
395
+ const KERNEL_REDUCTION = String.raw`
396
+ #include <stdio.h>
397
+ #include <stdlib.h>
398
+
399
+ // compile: hipcc -arch=sm_60 -nocudalib reduction.cu
400
+
401
+ // --- IDE & COMPILER COMPATIBILITY LAYER ---
402
+ #if !defined(__CUDACC__) && !defined(__HIPCC__)
403
+ #define __global__
404
+ #define __shared__
405
+ #define __syncthreads()
406
+ struct dim3 {
407
+ int x, y, z;
408
+ dim3(int _x = 1, int _y = 1, int _z = 1) : x(_x), y(_y), z(_z) {}
409
+ };
410
+ typedef unsigned int cudaError_t;
411
+ typedef void* cudaStream_t;
412
+ dim3 threadIdx, blockIdx, blockDim;
413
+ int warpSize = 64;
414
+ #define cudaMalloc(p, s) (0)
415
+ #define cudaFree(p) (0)
416
+ #define cudaMemcpy(d, s, n, k) (0)
417
+ #define cudaMemcpyHostToDevice 1
418
+ #define cudaMemcpyDeviceToHost 2
419
+ #define cudaSuccess 0
420
+ #define cudaDeviceSynchronize() (0)
421
+ #define LAUNCH_REDUCTION(g, b, m, ...) reduction_kernel(__VA_ARGS__)
422
+ #else
423
+ #define LAUNCH_REDUCTION(g, b, m, ...) reduction_kernel<<<g, b, m>>>(__VA_ARGS__)
424
+ #endif
425
+ // ------------------------------------------
426
+
427
+ // Standard reduction template (first pass: block-level)
428
+ __global__ void reduction_kernel(float* g_idata, float* g_odata, unsigned int n) {
429
+ extern __shared__ float sdata[];
430
+
431
+ unsigned int tid = threadIdx.x;
432
+ unsigned int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;
433
+
434
+ float mySum = (i < n) ? g_idata[i] : 0;
435
+ if (i + blockDim.x < n)
436
+ mySum += g_idata[i + blockDim.x];
437
+
438
+ sdata[tid] = mySum;
439
+ __syncthreads();
440
+
441
+ for (unsigned int s = blockDim.x / 2; s > 32; s >>= 1) {
442
+ if (tid < s) {
443
+ sdata[tid] = mySum = mySum + sdata[tid + s];
444
+ }
445
+ __syncthreads();
446
+ }
447
+
448
+ // DELIBERATE WARP-SIZE BUG: Assuming warpSize=32 for final unrolled reduction
449
+ // This will produce incorrect results on AMD (warpSize=64)
450
+ if (tid < 32) {
451
+ volatile float* vsmem = sdata;
452
+ vsmem[tid] = mySum = mySum + vsmem[tid + 32];
453
+ vsmem[tid] = mySum = mySum + vsmem[tid + 16];
454
+ vsmem[tid] = mySum = mySum + vsmem[tid + 8];
455
+ vsmem[tid] = mySum = mySum + vsmem[tid + 4];
456
+ vsmem[tid] = mySum = mySum + vsmem[tid + 2];
457
+ vsmem[tid] = mySum = mySum + vsmem[tid + 1];
458
+ }
459
+
460
+ if (tid == 0) g_odata[blockIdx.x] = sdata[0];
461
+ }
462
+
463
+ int main() {
464
+ const int N = 1048576; // 1M elements
465
+ const int threadsPerBlock = 256;
466
+ const int blocksPerGrid = (N + (threadsPerBlock * 2) - 1) / (threadsPerBlock * 2);
467
+
468
+ float *h_input = (float*)malloc(N * sizeof(float));
469
+ float *h_output = (float*)malloc(blocksPerGrid * sizeof(float));
470
+
471
+ for (int i = 0; i < N; i++) h_input[i] = 1.0f;
472
+
473
+ float *d_input, *d_output;
474
+ cudaMalloc(&d_input, N * sizeof(float));
475
+ cudaMalloc(&d_output, blocksPerGrid * sizeof(float));
476
+
477
+ cudaMemcpy(d_input, h_input, N * sizeof(float), cudaMemcpyHostToDevice);
478
+
479
+ LAUNCH_REDUCTION(blocksPerGrid, threadsPerBlock, threadsPerBlock * sizeof(float),
480
+ d_input, d_output, N);
481
+
482
+ cudaMemcpy(h_output, d_output, blocksPerGrid * sizeof(float), cudaMemcpyDeviceToHost);
483
+
484
+ float gpu_sum = 0;
485
+ for (int i = 0; i < blocksPerGrid; i++) gpu_sum += h_output[i];
486
+ float cpu_sum = (float)N;
487
+
488
+ printf("Parallel Reduction (1M elements)\n");
489
+ printf("CPU Sum: %.1f\n", cpu_sum);
490
+ printf("GPU Sum: %.1f\n", gpu_sum);
491
+ printf("Result: %s\n", (gpu_sum == cpu_sum) ? "PASS" : "FAIL (Warp size issue suspected)");
492
+
493
+ cudaFree(d_input);
494
+ cudaFree(d_output);
495
+ free(h_input);
496
+ free(h_output);
497
+
498
+ return 0;
499
+ }
500
+ `.trim()
501
+
502
+ // ─── Constants ────────────────────────────────────────────────────────────────
503
+
504
+ const TEMPLATES = {
505
+ 'Vector addition': KERNEL_VECTOR_ADD,
506
+ 'Matrix multiplication': KERNEL_MATRIX_MULTIPLY,
507
+ '2D convolution': KERNEL_CONVOLUTION_2D,
508
+ 'Parallel reduction': KERNEL_REDUCTION,
509
+ }
510
+
511
+ const AGENT_LIST = ['analyzer', 'translator', 'optimizer', 'tester', 'coordinator']
512
+
513
+ const AGENT_LABEL = {
514
+ analyzer: 'ANALYZER',
515
+ translator: 'TRANSLATOR',
516
+ optimizer: 'OPTIMIZER',
517
+ tester: 'TESTER',
518
+ coordinator: 'COORDINATOR',
519
+ }
520
+
521
+ // Tailwind class strings per status — all literals so JIT can scan them
522
+ const STATUS = {
523
+ idle: {
524
+ dot: 'bg-[#1E2D40]',
525
+ badge: 'bg-[#1E2D40] text-[#6B7A99]',
526
+ label: 'IDLE',
527
+ },
528
+ running: {
529
+ dot: 'bg-[#FFB800] animate-rocm-pulse',
530
+ badge: 'bg-[#1A1500] text-[#FFB800]',
531
+ label: 'RUNNING',
532
+ },
533
+ done: {
534
+ dot: 'bg-[#00FF88]',
535
+ badge: 'bg-[#001A0D] text-[#00FF88]',
536
+ label: 'DONE',
537
+ },
538
+ failed: {
539
+ dot: 'bg-[#FF3B3B]',
540
+ badge: 'bg-[#1A0000] text-[#FF3B3B]',
541
+ label: 'FAILED',
542
+ },
543
+ }
544
+
545
+ const INITIAL_AGENTS = Object.fromEntries(
546
+ AGENT_LIST.map(a => [a, { status: 'idle', message: 'Waiting…', detail: '' }])
547
+ )
548
+
549
+ // ─── AgentCard ────────────────────────────────────────────────────────────────
550
+
551
+ function AgentCard({ name, state }) {
552
+ const s = STATUS[state.status] ?? STATUS.idle
553
+ return (
554
+ <div className="rounded-lg border border-[#1E2D40] bg-[#111827] p-3">
555
+ <div className="flex items-center gap-3">
556
+ {/* Status dot */}
557
+ <span className={`shrink-0 w-2 h-2 rounded-full ${s.dot}`} />
558
+
559
+ {/* Agent info */}
560
+ <div className="flex-1 min-w-0">
561
+ <div className="font-code text-[11px] text-[#6B7A99] tracking-widest uppercase">
562
+ {AGENT_LABEL[name]}
563
+ </div>
564
+ <div className="font-ui text-[13px] text-[#F0F4FF] mt-0.5 truncate">
565
+ {state.message || 'Waiting…'}
566
+ </div>
567
+ </div>
568
+
569
+ {/* Status badge */}
570
+ <span
571
+ className={`shrink-0 font-code text-[10px] font-semibold px-2 py-0.5 rounded tracking-wider ${s.badge}`}
572
+ >
573
+ {s.label}
574
+ </span>
575
+ </div>
576
+
577
+ {/* Detail (collapsible — shown only when present) */}
578
+ {state.detail && (
579
+ <p className="mt-2 font-ui text-[11px] text-[#6B7A99] italic leading-relaxed line-clamp-3">
580
+ {state.detail}
581
+ </p>
582
+ )}
583
+ </div>
584
+ )
585
+ }
586
+
587
+ // ─── App ──────────────────────────────────────────────────────────────────────
588
+
589
+ export default function App() {
590
+ const [code, setCode] = useState('')
591
+ const [activeTemplate, setActiveTemplate] = useState(null)
592
+ const [agents, setAgents] = useState(INITIAL_AGENTS)
593
+ const [running, setRunning] = useState(false)
594
+ const [elapsed, setElapsed] = useState(0)
595
+ const [benchmark, setBenchmark] = useState(null)
596
+ const [errorBanner, setErrorBanner] = useState(null)
597
+
598
+ const timerRef = useRef(null)
599
+ const startRef = useRef(null)
600
+
601
+ const lineCount = code ? code.split('\n').length : 1
602
+
603
+ // ── Timer ────────────────────────────────────────────────────────────────────
604
+ const startTimer = () => {
605
+ startRef.current = Date.now()
606
+ timerRef.current = setInterval(
607
+ () => setElapsed(Date.now() - startRef.current),
608
+ 100
609
+ )
610
+ }
611
+
612
+ const stopTimer = () => {
613
+ clearInterval(timerRef.current)
614
+ timerRef.current = null
615
+ }
616
+
617
+ useEffect(() => () => stopTimer(), [])
618
+
619
+ // ── Helpers ───────────────────────────────────────────────────────────────────
620
+ const resetAgents = () =>
621
+ setAgents(Object.fromEntries(
622
+ AGENT_LIST.map(a => [a, { status: 'idle', message: 'Waiting…', detail: '' }])
623
+ ))
624
+
625
+ const updateAgent = (agent, patch) =>
626
+ setAgents(prev => ({ ...prev, [agent]: { ...prev[agent], ...patch } }))
627
+
628
+ const selectTemplate = (name) => {
629
+ setActiveTemplate(name)
630
+ setCode(TEMPLATES[name])
631
+ }
632
+
633
+ const fmtElapsed = (ms) => `${(ms / 1000).toFixed(1)}s`
634
+
635
+ // ── Demo mode fallback ────────────────────────────────────────────────────────
636
+ const runDemo = async () => {
637
+ const steps = [
638
+ { agent: 'analyzer', status: 'running', message: 'Scanning CUDA patterns…', detail: '' },
639
+ { agent: 'analyzer', status: 'done', message: 'Found 3 critical AMD issues', detail: 'warp-32 assumption in reduction tail, threadIdx%32 idiom, LDS bank conflict pattern' },
640
+ { agent: 'translator', status: 'running', message: 'Running hipify + LLM pass…', detail: '' },
641
+ { agent: 'translator', status: 'done', message: 'Translation complete', detail: 'hipify applied; 7 additional LLM corrections for wavefront-64 semantics' },
642
+ { agent: 'optimizer', status: 'running', message: 'Proposing optimizations…', detail: '' },
643
+ { agent: 'optimizer', status: 'done', message: '4 optimization patches generated', detail: 'LDS padding, wavefront-aware reduction, coalesced access pattern' },
644
+ { agent: 'tester', status: 'running', message: 'Compiling with hipcc…', detail: '' },
645
+ { agent: 'tester', status: 'done', message: 'Compiled and profiled on gfx942', detail: 'rocprof: 0.026 ms — correctness verified' },
646
+ { agent: 'coordinator', status: 'running', message: 'Assembling final report…', detail: '' },
647
+ { agent: 'coordinator', status: 'done', message: 'Migration complete — 2.61× speedup', detail: 'data_source: demo_artifact' },
648
+ ]
649
+
650
+ for (const step of steps) {
651
+ await new Promise(r => setTimeout(r, 800))
652
+ updateAgent(step.agent, { status: step.status, message: step.message, detail: step.detail })
653
+ }
654
+
655
+ setBenchmark({
656
+ total_changes: 11,
657
+ bugs_found: 3,
658
+ compiled_successfully: true,
659
+ data_source: 'demo_artifact',
660
+ })
661
+ stopTimer()
662
+ setRunning(false)
663
+ }
664
+
665
+ // ── Main action ───────────────────────────────────────────────────────────────
666
+ const handlePort = async () => {
667
+ if (running || !code.trim()) return
668
+
669
+ setRunning(true)
670
+ setElapsed(0)
671
+ setBenchmark(null)
672
+ setErrorBanner(null)
673
+ resetAgents()
674
+ startTimer()
675
+
676
+ try {
677
+ const res = await fetch('http://localhost:8000/port', {
678
+ method: 'POST',
679
+ headers: { 'Content-Type': 'application/json' },
680
+ body: JSON.stringify({
681
+ cuda_code: code,
682
+ kernel_name: activeTemplate || 'custom',
683
+ simple_mode: false,
684
+ }),
685
+ })
686
+ if (!res.ok) throw new Error(`HTTP ${res.status}`)
687
+
688
+ const reader = res.body.getReader()
689
+ const dec = new TextDecoder()
690
+ let buf = ''
691
+
692
+ outer: while (true) {
693
+ const { done, value } = await reader.read()
694
+ if (done) break
695
+
696
+ buf += dec.decode(value, { stream: true })
697
+ const lines = buf.split('\n')
698
+ buf = lines.pop() // keep any incomplete trailing line
699
+
700
+ for (const line of lines) {
701
+ if (!line.startsWith('data: ')) continue
702
+ const raw = line.slice(6).trim()
703
+ if (raw === '[DONE]') break outer
704
+
705
+ try {
706
+ const ev = JSON.parse(raw)
707
+ if (!ev.agent) continue
708
+
709
+ updateAgent(ev.agent, {
710
+ status: ev.status,
711
+ message: ev.message ?? '',
712
+ detail: ev.detail ?? '',
713
+ })
714
+
715
+ // Extract benchmark data from the coordinator's done event
716
+ if (ev.agent === 'coordinator' && ev.status === 'done') {
717
+ const r = ev.result ?? ev
718
+ setBenchmark({
719
+ total_changes: r.total_changes ?? r.changes_made ?? '—',
720
+ bugs_found: r.bugs_found ?? r.critical_bugs ?? '—',
721
+ compiled_successfully: r.compiled_successfully ?? r.compiled ?? false,
722
+ data_source: r.data_source ?? 'unknown',
723
+ })
724
+ }
725
+ } catch (_) { /* malformed SSE line — skip */ }
726
+ }
727
+ }
728
+ } catch {
729
+ setErrorBanner('Backend unavailable — running in demo mode')
730
+ runDemo()
731
+ return // runDemo handles stopTimer + setRunning(false)
732
+ }
733
+
734
+ stopTimer()
735
+ setRunning(false)
736
+ }
737
+
738
+ // ── Render ────────────────────────────────────────────────────────────────────
739
+ return (
740
+ <div
741
+ className="min-h-screen flex flex-col text-[#F0F4FF] font-ui"
742
+ style={{ background: 'linear-gradient(180deg, #0A0E1A 0%, #0D1220 100%)' }}
743
+ >
744
+ {/* ── Error banner ──────────────────────────────────────────────────────── */}
745
+ {errorBanner && (
746
+ <div className="flex-none px-6 py-2.5 border-b border-[#FF3B3B] bg-[#1A0000] font-code text-[13px] text-[#FF3B3B]">
747
+ ⚠ {errorBanner}
748
+ </div>
749
+ )}
750
+
751
+ {/* ── Two-column main layout ────────────────────────────────────────────── */}
752
+ <div className="flex flex-1 overflow-hidden">
753
+
754
+ {/* ──── LEFT PANEL 58% ─────────────────────────────────────────────── */}
755
+ <div className="w-[58%] flex flex-col p-5 gap-4 border-r border-[#1E2D40] overflow-y-auto">
756
+
757
+ {/* Editor header */}
758
+ <div className="flex justify-between items-center">
759
+ <span className="font-code text-[12px] text-[#6B7A99]">// CUDA source</span>
760
+ <span className="font-code text-[12px] text-[#6B7A99]">{lineCount} lines</span>
761
+ </div>
762
+
763
+ {/* Code editor */}
764
+ <textarea
765
+ value={code}
766
+ onChange={e => { setCode(e.target.value); setActiveTemplate(null) }}
767
+ placeholder={'// Paste CUDA code here\n// or pick a demo below'}
768
+ spellCheck={false}
769
+ className={[
770
+ 'w-full min-h-[300px] resize-y rounded-lg p-4',
771
+ 'border border-[#1E2D40] bg-[#0D1525]',
772
+ 'text-[#F0F4FF] font-code text-[13px] leading-[1.6]',
773
+ 'focus:outline-none focus:border-[#00D4FF] transition-colors duration-150',
774
+ '[tab-size:4] [caret-color:#00D4FF]',
775
+ ].join(' ')}
776
+ />
777
+
778
+ {/* Template selector */}
779
+ <div>
780
+ <p className="font-ui text-[12px] text-[#6B7A99] mb-2.5">Select a template:</p>
781
+ <div className="flex flex-wrap gap-2">
782
+ {Object.keys(TEMPLATES).map(name => (
783
+ <button
784
+ key={name}
785
+ onClick={() => selectTemplate(name)}
786
+ className={[
787
+ 'px-4 py-1.5 rounded-full border font-ui text-[13px]',
788
+ 'cursor-pointer transition-colors duration-150',
789
+ activeTemplate === name
790
+ ? 'bg-[#001A24] border-[#00D4FF] text-[#00D4FF]'
791
+ : 'bg-[#111827] border-[#1E2D40] text-[#F0F4FF] hover:border-[#00D4FF]',
792
+ ].join(' ')}
793
+ >
794
+ {name}
795
+ </button>
796
+ ))}
797
+ </div>
798
+ </div>
799
+
800
+ {/* PORT TO ROCM button */}
801
+ <button
802
+ onClick={handlePort}
803
+ disabled={running || !code.trim()}
804
+ className={[
805
+ 'w-full h-12 rounded-lg font-code text-[14px] text-white font-semibold',
806
+ '[letter-spacing:2px] transition-all duration-150',
807
+ running || !code.trim()
808
+ ? 'bg-[#FF3B3B] opacity-50 cursor-not-allowed'
809
+ : 'bg-[#FF3B3B] hover:bg-[#FF1A1A] hover:shadow-[0_0_20px_rgba(255,59,59,0.35)] cursor-pointer',
810
+ ].join(' ')}
811
+ >
812
+ {running ? 'RUNNING...' : 'PORT TO ROCM'}
813
+ </button>
814
+ </div>
815
+
816
+ {/* ──── RIGHT PANEL 42% ────────────────────────────────────────────── */}
817
+ <div className="w-[42%] flex flex-col p-5 gap-4 overflow-y-auto">
818
+
819
+ {/* Pipeline header */}
820
+ <div className="flex justify-between items-center">
821
+ <span className="font-code text-[12px] text-[#6B7A99]">// Pipeline</span>
822
+ <span className={`font-code text-[12px] transition-colors duration-300 ${running ? 'text-[#FFB800]' : 'text-[#6B7A99]'}`}>
823
+ {fmtElapsed(elapsed)}
824
+ </span>
825
+ </div>
826
+
827
+ {/* Agent cards */}
828
+ <div className="flex flex-col gap-2">
829
+ {AGENT_LIST.map(agent => (
830
+ <AgentCard key={agent} name={agent} state={agents[agent]} />
831
+ ))}
832
+ </div>
833
+ </div>
834
+ </div>
835
+
836
+ {/* ── Benchmark footer (hidden until run completes) ─────────────────────── */}
837
+ {benchmark && (
838
+ <div className="flex-none flex flex-wrap gap-6 px-6 py-4 border-t border-[#1E2D40] bg-[#0D1525]">
839
+ {[
840
+ { label: 'CHANGES MADE', value: benchmark.total_changes },
841
+ { label: 'BUGS FOUND', value: benchmark.bugs_found },
842
+ {
843
+ label: 'COMPILE STATUS',
844
+ value: benchmark.compiled_successfully ? 'SUCCESS' : 'FAILED',
845
+ color: benchmark.compiled_successfully ? '#00FF88' : '#FF3B3B',
846
+ },
847
+ { label: 'DATA SOURCE', value: benchmark.data_source, isSource: true },
848
+ ].map(({ label, value, color, isSource }) => (
849
+ <div key={label} className="flex flex-col gap-1 min-w-[120px]">
850
+ <span className="font-ui text-[10px] text-[#6B7A99] uppercase tracking-widest">
851
+ {label}
852
+ </span>
853
+ <div className="flex items-center gap-2">
854
+ <span
855
+ className="font-code text-[18px] font-semibold"
856
+ style={{ color: color ?? '#00D4FF' }}
857
+ >
858
+ {String(value ?? '—')}
859
+ </span>
860
+ {isSource && value === 'real_rocm' && (
861
+ <span className="font-code text-[10px] text-[#00D4FF] border border-[#00D4FF] bg-[#001A24] px-2 py-0.5 rounded">
862
+ LIVE HARDWARE
863
+ </span>
864
+ )}
865
+ </div>
866
+ </div>
867
+ ))}
868
+ </div>
869
+ )}
870
+ </div>
871
+ )
872
+ }
frontend/src/index.css ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
frontend/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ )
frontend/tailwind.config.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{js,jsx}'],
4
+ theme: {
5
+ extend: {
6
+ fontFamily: {
7
+ code: ['"JetBrains Mono"', 'monospace'],
8
+ ui: ['Inter', 'sans-serif'],
9
+ },
10
+ keyframes: {
11
+ 'rocm-pulse': {
12
+ '0%, 100%': { opacity: '1' },
13
+ '50%': { opacity: '0.3' },
14
+ },
15
+ },
16
+ animation: {
17
+ 'rocm-pulse': 'rocm-pulse 1.2s ease-in-out infinite',
18
+ },
19
+ },
20
+ },
21
+ plugins: [],
22
+ }
frontend/vite.config.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 5173,
8
+ },
9
+ })