lagora-cli 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +138 -0
  2. package/dist/help.txt +70 -0
  3. package/dist/lagora.js +342 -0
  4. package/dist/report-help.txt +5 -0
  5. package/dist/scripts/agora_playground_harness.py +263 -0
  6. package/dist/scripts/announce.js +41 -0
  7. package/dist/scripts/check-kernel-submission.py +90 -0
  8. package/dist/scripts/chunk-2EAJVB5D.js +100 -0
  9. package/dist/scripts/chunk-2KTLCUFI.js +29 -0
  10. package/dist/scripts/chunk-AZ3EEBVD.js +137 -0
  11. package/dist/scripts/chunk-NBJMYAOA.js +2128 -0
  12. package/dist/scripts/chunk-NCJMUBTG.js +125 -0
  13. package/dist/scripts/chunk-QJPQHKIO.js +23 -0
  14. package/dist/scripts/chunk-RIR5KGHC.js +33 -0
  15. package/dist/scripts/chunk-TJZVQYBL.js +8 -0
  16. package/dist/scripts/chunk-UHJXD4TG.js +18 -0
  17. package/dist/scripts/chunk-UQ6I6VTY.js +117 -0
  18. package/dist/scripts/cli-auth.js +348 -0
  19. package/dist/scripts/cli-config-IA7EOSYD.js +7 -0
  20. package/dist/scripts/install-skill.js +199 -0
  21. package/dist/scripts/issue-local-client-DZUXZOKY.js +22 -0
  22. package/dist/scripts/issue-search.js +1823 -0
  23. package/dist/scripts/issue.js +386 -0
  24. package/dist/scripts/keycloak-provision.js +986 -0
  25. package/dist/scripts/legato-fsim-runner.py +126 -0
  26. package/dist/scripts/legato-lowering-runner.py +156 -0
  27. package/dist/scripts/legato_runner_annotations.py +235 -0
  28. package/dist/scripts/legato_runner_env.py +91 -0
  29. package/dist/scripts/legato_runner_launchers.py +287 -0
  30. package/dist/scripts/legato_runner_script_wrapper.py +193 -0
  31. package/dist/scripts/notifications-EU43SIEV.js +624 -0
  32. package/dist/scripts/playground.js +408 -0
  33. package/dist/scripts/report-bundle-sync-3U7QTP4Z.js +215 -0
  34. package/dist/scripts/report.js +104 -0
  35. package/dist/scripts/resolve-sdk-package-version.py +151 -0
  36. package/dist/scripts/sdk-runtime-JE6H2PB2.js +992 -0
  37. package/dist/scripts/sdk-runtime-kubernetes-job-KOWL4ITV.js +479 -0
  38. package/dist/scripts/sdk-runtime-smoke.py +168 -0
  39. package/dist/scripts/sdk.js +256 -0
  40. package/dist/scripts/site-feedback-CAPE5MPX.js +136 -0
  41. package/dist/scripts/site-feedback-rate-limit-5BU2WSFE.js +86 -0
  42. package/dist/scripts/site-feedback.js +117 -0
  43. package/dist/scripts/storage-234FBH54.js +67 -0
  44. package/dist/scripts/submit-issue.sh +489 -0
  45. package/dist/scripts/verification-3QCY66QW.js +772 -0
  46. package/dist/scripts/verify-issue.js +144 -0
  47. package/dist/skills/legato-agora-cli/SKILL.md +556 -0
  48. package/dist/skills/legato-agora-cli/agents/openai.yaml +7 -0
  49. package/dist/skills/legato-agora-cli/reference/kernel-with-golden.py +84 -0
  50. package/dist/skills/legato-site-feedback/SKILL.md +49 -0
  51. package/dist/skills/legato-site-feedback/agents/openai.yaml +7 -0
  52. package/package.json +16 -0
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import os
6
+ import traceback
7
+ from pathlib import Path
8
+
9
+ from agora_playground_harness import has_playground_contract, run_playground_kernel
10
+ from legato_runner_launchers import LauncherError, LauncherScriptError, launcher_runs_in_subprocess, run_launcher
11
+
12
+
13
+ class FsimPreflightError(RuntimeError):
14
+ pass
15
+
16
+
17
+ def parse_args() -> argparse.Namespace:
18
+ parser = argparse.ArgumentParser(description="Run a Legato BINARY launcher on HART fsim")
19
+ parser.add_argument("--kernel", required=True, help="Path to uploaded Python launcher/kernel file")
20
+ parser.add_argument("--output-root", required=True, help="Directory for generated fsim outputs")
21
+ return parser.parse_args()
22
+
23
+
24
+ def export_fsim_child_config(output_root: Path) -> None:
25
+ """Hand the fsim configuration to the process that actually runs the kernel.
26
+
27
+ Script-style launchers execute in a child process, and hart's device state
28
+ lives in the C++ runtime of whichever process called set_fsim_env. Opening
29
+ the device here would leave the child without one.
30
+ """
31
+ os.environ.setdefault("HA_TOOLCHAIN", "1")
32
+ os.environ.setdefault("HA_MOCK_DEVICE_COUNT", "2")
33
+
34
+ try:
35
+ import hart # noqa: F401 fail fast if the SDK cannot provide fsim at all
36
+ except ImportError as exc:
37
+ raise FsimPreflightError(f"hart not available: {exc}") from exc
38
+
39
+ os.environ["AGORA_FSIM_DEVICE_VERSION"] = os.environ.get("AGORA_FSIM_DEVICE_VERSION", "BERTHA_CLOUD_EVT0")
40
+ os.environ["AGORA_FSIM_COMPUTE_MODE"] = os.environ.get("AGORA_FSIM_COMPUTE_MODE", "TORCH")
41
+ os.environ["AGORA_FSIM_LOG_LEVEL"] = os.environ.get("AGORA_FSIM_LOG_LEVEL", "trace")
42
+ os.environ["AGORA_FSIM_LOG_PATH"] = str(output_root / "fsim.log")
43
+ os.environ["AGORA_FSIM_CONFIGURE_IN_CHILD"] = "1"
44
+ print("[LEGATO_FSIM] configure=child", flush=True)
45
+ print(f"[LEGATO_FSIM] device_version={os.environ['AGORA_FSIM_DEVICE_VERSION']}", flush=True)
46
+ print(f"[LEGATO_FSIM] compute_mode={os.environ['AGORA_FSIM_COMPUTE_MODE']}", flush=True)
47
+ print(f"[LEGATO_FSIM] log_path={os.environ['AGORA_FSIM_LOG_PATH']}", flush=True)
48
+
49
+
50
+ def configure_fsim(output_root: Path) -> None:
51
+ os.environ.setdefault("HA_TOOLCHAIN", "1")
52
+ os.environ.setdefault("HA_MOCK_DEVICE_COUNT", "2")
53
+
54
+ try:
55
+ import hart
56
+ except ImportError as exc:
57
+ raise FsimPreflightError(f"hart not available: {exc}") from exc
58
+
59
+ device_version_name = os.environ.get("AGORA_FSIM_DEVICE_VERSION", "BERTHA_CLOUD_EVT0")
60
+ compute_mode_name = os.environ.get("AGORA_FSIM_COMPUTE_MODE", "TORCH")
61
+ log_level = os.environ.get("AGORA_FSIM_LOG_LEVEL", "trace")
62
+ log_path = output_root / "fsim.log"
63
+
64
+ try:
65
+ device_version = getattr(hart.HaDeviceVersion, device_version_name)
66
+ compute_mode = getattr(hart.HaFSimComputeMode, compute_mode_name)
67
+ except AttributeError as exc:
68
+ raise FsimPreflightError(
69
+ f"unsupported fsim config: AGORA_FSIM_DEVICE_VERSION={device_version_name}, "
70
+ f"AGORA_FSIM_COMPUTE_MODE={compute_mode_name}",
71
+ ) from exc
72
+
73
+ try:
74
+ hart.set_fsim_env(device_version, compute_mode, str(log_path), log_level)
75
+ except RuntimeError as exc:
76
+ raise FsimPreflightError(f"set_fsim_env failed: {exc}") from exc
77
+ if hart.get_device_count() == 0:
78
+ raise FsimPreflightError("no fsim devices available")
79
+ hart.set_device(0)
80
+ print(f"[LEGATO_FSIM] device_version={device_version_name}", flush=True)
81
+ print(f"[LEGATO_FSIM] compute_mode={compute_mode_name}", flush=True)
82
+ print(f"[LEGATO_FSIM] log_path={log_path}", flush=True)
83
+
84
+
85
+ def main() -> int:
86
+ args = parse_args()
87
+ kernel_path = Path(args.kernel).resolve()
88
+ output_root = Path(args.output_root).resolve()
89
+ output_root.mkdir(parents=True, exist_ok=True)
90
+
91
+ # A kernel that brings no launcher of its own is driven here rather than by
92
+ # itself, so the device has to be opened in this process -- not handed to a
93
+ # child that is never spawned.
94
+ harness_driven = has_playground_contract(kernel_path)
95
+ try:
96
+ if harness_driven or not launcher_runs_in_subprocess(kernel_path):
97
+ configure_fsim(output_root)
98
+ else:
99
+ export_fsim_child_config(output_root)
100
+ os.environ["AGORA_LEGATO_SESSION_LAUNCH"] = "1"
101
+ print("[LEGATO_WORKER] stage=FSIM status=running", flush=True)
102
+ if harness_driven:
103
+ print("[LEGATO_WORKER] launcher_mode=harness", flush=True)
104
+ run_playground_kernel(kernel_path, output_root)
105
+ else:
106
+ run_launcher(kernel_path, ["BINARY"], output_root)
107
+ except LauncherScriptError as exc:
108
+ print("[LEGATO_WORKER] stage=FSIM status=failed", flush=True)
109
+ print(f"[LEGATO_WORKER] reason={exc}", flush=True)
110
+ return 1
111
+ except (FsimPreflightError, LauncherError) as exc:
112
+ print("[LEGATO_WORKER] preflight=failed", flush=True)
113
+ print(f"[LEGATO_WORKER] reason={exc}", flush=True)
114
+ return 2
115
+ except Exception: # noqa: BROAD_EXCEPT_OK
116
+ print("[LEGATO_WORKER] stage=FSIM status=failed", flush=True)
117
+ traceback.print_exc()
118
+ return 1
119
+
120
+ print("[LEGATO_WORKER] stage=FSIM status=success", flush=True)
121
+ print("[LEGATO_WORKER] fsim=success", flush=True)
122
+ return 0
123
+
124
+
125
+ if __name__ == "__main__":
126
+ raise SystemExit(main())
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ """Run staged Legato lowering for an uploaded kernel module.
3
+
4
+ The runner imports a user kernel file, finds a @legato.compile or
5
+ @legato.primitive JitFunction, synthesizes dummy arguments from Legato type
6
+ annotations, and runs the same function through each requested OutputType. It
7
+ prints stable worker markers so the Agora TypeScript worker can summarize the
8
+ failed stage.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import ast
15
+ import importlib.util
16
+ import inspect
17
+ import os
18
+ import re
19
+ import sys
20
+ import traceback
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ from legato_runner_annotations import (
25
+ PreflightError,
26
+ load_module,
27
+ place_arguments,
28
+ select_kernel,
29
+ synthesize_args,
30
+ top_level_function_names,
31
+ )
32
+ from legato_runner_launchers import (
33
+ LauncherError,
34
+ LauncherScriptError,
35
+ kernel_has_launcher,
36
+ run_launcher,
37
+ statically_decorated_kernel_names,
38
+ )
39
+
40
+
41
+ DEFAULT_STAGES = ["MLIR", "CORE_IR", "BACKEND_IR", "ASM", "BINARY"]
42
+
43
+
44
+ def parse_args() -> argparse.Namespace:
45
+ parser = argparse.ArgumentParser(description="Run staged Legato lowering")
46
+ parser.add_argument("--kernel", required=True, help="Path to uploaded kernel.py")
47
+ parser.add_argument("--function", help="@legato.compile/@legato.primitive function name. Auto-detects if omitted.")
48
+ parser.add_argument("--output-root", required=True, help="Directory for stage outputs")
49
+ parser.add_argument("--stages", default=",".join(DEFAULT_STAGES), help="Comma-separated OutputType stages")
50
+ return parser.parse_args()
51
+
52
+
53
+
54
+
55
+ def run_stage(legato_module: Any, kernel: Any, kernel_args: list[Any], stage: str, output_root: Path) -> None:
56
+ output_type = getattr(legato_module.OutputType, stage)
57
+ output_path = output_root / stage.lower()
58
+ output_path.mkdir(parents=True, exist_ok=True)
59
+ print(f"[LEGATO_WORKER] stage={stage} status=running", flush=True)
60
+ print(f"[LEGATO_WORKER] output_path={output_path}", flush=True)
61
+ with legato_module.session(**session_kwargs(legato_module.session, output_type, output_path, launch=False)):
62
+ kernel(*kernel_args)
63
+ print(f"[LEGATO_WORKER] stage={stage} status=success", flush=True)
64
+
65
+
66
+ def session_kwargs(session_factory: Any, output_type: Any, output_path: Path, launch: bool) -> dict[str, Any]:
67
+ kwargs: dict[str, Any] = {"output_type": output_type, "output_path": str(output_path)}
68
+ if session_accepts_launch(session_factory):
69
+ kwargs["launch"] = launch
70
+ return kwargs
71
+
72
+
73
+ def session_accepts_launch(session_factory: Any) -> bool:
74
+ try:
75
+ signature = inspect.signature(session_factory)
76
+ except (TypeError, ValueError):
77
+ return False
78
+ return "launch" in signature.parameters or any(
79
+ parameter.kind is inspect.Parameter.VAR_KEYWORD
80
+ for parameter in signature.parameters.values()
81
+ )
82
+
83
+
84
+ def validate_stages(legato_module: Any, stages: list[str]) -> None:
85
+ if not stages:
86
+ raise PreflightError("no lowering stages requested")
87
+ for stage in stages:
88
+ if not hasattr(legato_module.OutputType, stage):
89
+ raise PreflightError(f"unsupported lowering stage: {stage}")
90
+
91
+
92
+ def run_compiled_kernel(legato_module: Any, kernel_path: Path, function_name: str | None, stages: list[str], output_root: Path) -> None:
93
+ local_function_names = top_level_function_names(kernel_path)
94
+ selected_name: str | None = None
95
+ for index, stage in enumerate(stages):
96
+ module = load_module(kernel_path)
97
+ stage_selected_name, kernel = select_kernel(module, function_name, local_function_names)
98
+ if selected_name is None:
99
+ selected_name = stage_selected_name
100
+ print(f"[LEGATO_WORKER] kernel_function={selected_name}")
101
+ elif stage_selected_name != selected_name:
102
+ raise PreflightError(f"selected kernel changed between stages: {selected_name} != {stage_selected_name}")
103
+ # A kernel that declares param_kinds must be called with its arguments
104
+ # bound to those targets, or lowering refuses them as bare values.
105
+ kernel_args = place_arguments(kernel, synthesize_args(kernel))
106
+ if index == 0:
107
+ print(f"[LEGATO_WORKER] synthesized_args={len(kernel_args)}")
108
+ run_stage(legato_module, kernel, kernel_args, stage, output_root)
109
+
110
+
111
+ def main() -> int:
112
+ args = parse_args()
113
+ kernel_path = Path(args.kernel).resolve()
114
+ output_root = Path(args.output_root).resolve()
115
+ stages = [stage.strip() for stage in args.stages.split(",") if stage.strip()]
116
+ function_name = args.function or os.environ.get("LEGATO_KERNEL_FUNCTION")
117
+ os.environ.setdefault("AGORA_LEGATO_SESSION_LAUNCH", "0")
118
+
119
+ try:
120
+ import legato
121
+ except Exception: # noqa: BLE001
122
+ print("[LEGATO_WORKER] preflight=failed")
123
+ print("[LEGATO_WORKER] reason=failed to import legato")
124
+ traceback.print_exc()
125
+ return 2
126
+
127
+ try:
128
+ validate_stages(legato, stages)
129
+ decorated_names = statically_decorated_kernel_names(kernel_path)
130
+ if function_name:
131
+ run_compiled_kernel(legato, kernel_path, function_name, stages, output_root)
132
+ elif kernel_has_launcher(kernel_path):
133
+ run_launcher(kernel_path, stages, output_root)
134
+ elif decorated_names:
135
+ run_compiled_kernel(legato, kernel_path, None, stages, output_root)
136
+ else:
137
+ run_launcher(kernel_path, stages, output_root)
138
+ except LauncherScriptError as exc:
139
+ print("[LEGATO_WORKER] lowering_pipeline=failed")
140
+ print(f"[LEGATO_WORKER] reason={exc}")
141
+ return 1
142
+ except (PreflightError, LauncherError) as exc:
143
+ print("[LEGATO_WORKER] preflight=failed")
144
+ print(f"[LEGATO_WORKER] reason={exc}")
145
+ return 2
146
+ except Exception: # noqa: BLE001
147
+ print("[LEGATO_WORKER] lowering_pipeline=failed")
148
+ traceback.print_exc()
149
+ return 1
150
+
151
+ print("[LEGATO_WORKER] lowering_pipeline=success")
152
+ return 0
153
+
154
+
155
+ if __name__ == "__main__":
156
+ raise SystemExit(main())
@@ -0,0 +1,235 @@
1
+ """Reading a Legato kernel's shape from its annotations.
2
+
3
+ Extracted so more than one runner can use it: lowering synthesizes arguments to
4
+ compile with, and the Playground harness needs the same shapes to allocate device
5
+ tensors. The file name matters -- the runners are hyphenated scripts and cannot
6
+ be imported, which is why this had to move before it could be shared.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import importlib.util
12
+ import inspect
13
+ import re
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ TORCH_DTYPE_BY_LEGATO = {
19
+ "bfloat16": "bfloat16",
20
+ "bf16": "bfloat16",
21
+ "float16": "float16",
22
+ "fp16": "float16",
23
+ "float32": "float32",
24
+ "f32": "float32",
25
+ "float64": "float64",
26
+ "f64": "float64",
27
+ "int32": "int32",
28
+ "i32": "int32",
29
+ "int64": "int64",
30
+ "i64": "int64",
31
+ "bool": "bool",
32
+ }
33
+
34
+
35
+ class PreflightError(RuntimeError):
36
+ pass
37
+
38
+
39
+ def load_module(kernel_path: Path) -> Any:
40
+ spec = importlib.util.spec_from_file_location("agora_uploaded_kernel", kernel_path)
41
+ if spec is None or spec.loader is None:
42
+ raise PreflightError(f"cannot import kernel module: {kernel_path}")
43
+ module = importlib.util.module_from_spec(spec)
44
+ sys.path.insert(0, str(kernel_path.parent))
45
+ try:
46
+ spec.loader.exec_module(module)
47
+ finally:
48
+ if sys.path and sys.path[0] == str(kernel_path.parent):
49
+ sys.path.pop(0)
50
+ return module
51
+
52
+
53
+ def is_legato_jit_function(value: Any) -> bool:
54
+ return callable(value) and hasattr(value, "_node") and hasattr(value, "_globals") and hasattr(value, "verify_types")
55
+
56
+
57
+ def top_level_function_names(kernel_path: Path) -> set[str]:
58
+ with kernel_path.open("r", encoding="utf-8") as source:
59
+ tree = ast.parse(source.read(), filename=str(kernel_path))
60
+ return {node.name for node in tree.body if isinstance(node, ast.FunctionDef)}
61
+
62
+
63
+ def select_kernel(module: Any, function_name: str | None, local_function_names: set[str]) -> tuple[str, Any]:
64
+ if function_name:
65
+ value = getattr(module, function_name, None)
66
+ if not is_legato_jit_function(value):
67
+ raise PreflightError(f"{function_name} is not a @legato.compile/@legato.primitive function")
68
+ return function_name, value
69
+
70
+ candidates = [
71
+ (name, value)
72
+ for name, value in vars(module).items()
73
+ if name in local_function_names and is_legato_jit_function(value)
74
+ ]
75
+ if not candidates:
76
+ raise PreflightError("no @legato.compile/@legato.primitive function found in uploaded module")
77
+ if len(candidates) > 1:
78
+ names = ", ".join(name for name, _ in candidates)
79
+ raise PreflightError(f"multiple @legato.compile/@legato.primitive functions found; set LEGATO_KERNEL_FUNCTION. candidates={names}")
80
+ return candidates[0]
81
+
82
+
83
+ def get_function_def(jit_function: Any) -> ast.FunctionDef:
84
+ node = getattr(jit_function, "_node", None)
85
+ for item in ast.walk(node):
86
+ if isinstance(item, ast.FunctionDef):
87
+ return item
88
+ raise PreflightError("cannot locate function AST for Legato kernel target")
89
+
90
+
91
+ def annotation_source(annotation: ast.expr | None) -> str:
92
+ if annotation is None:
93
+ return ""
94
+ return ast.unparse(annotation)
95
+
96
+
97
+ def eval_annotation(source: str, jit_function: Any) -> Any:
98
+ if not source:
99
+ raise PreflightError("missing required Legato argument annotation")
100
+ namespace = dict(getattr(jit_function, "_globals", {}))
101
+ return eval(source, namespace) # noqa: S307 - trusted internal worker environment for uploaded repros.
102
+
103
+
104
+ def dtype_name(type_value: Any, source: str) -> str:
105
+ quoted = re.search(r"['\"]([A-Za-z0-9_]+)['\"]", source)
106
+ if quoted and quoted.group(1).lower() in TORCH_DTYPE_BY_LEGATO:
107
+ return TORCH_DTYPE_BY_LEGATO[quoted.group(1).lower()]
108
+ text = str(type_value).lower()
109
+ for legato_name, torch_name in TORCH_DTYPE_BY_LEGATO.items():
110
+ if legato_name in text:
111
+ return torch_name
112
+ return "float32"
113
+
114
+
115
+ def dimension_value(dim: Any) -> int:
116
+ if isinstance(dim, int):
117
+ return 8 if dim == -1 else max(dim, 1)
118
+ if isinstance(dim, tuple):
119
+ if len(dim) == 2 and dim[0] == -1 and isinstance(dim[1], int):
120
+ return max(dim[1], 1)
121
+ raise PreflightError(f"unsupported dynamic dimension tuple: {dim!r}")
122
+ raise PreflightError(f"unsupported tensor dimension: {dim!r}")
123
+
124
+
125
+ def tensor_shape_from_annotation_source(source: str, jit_function: Any) -> list[int]:
126
+ parsed = ast.parse(source, mode="eval")
127
+ call = parsed.body
128
+ if not isinstance(call, ast.Call):
129
+ raise PreflightError(f"unsupported tensor annotation expression: {source}")
130
+ if len(call.args) < 2:
131
+ raise PreflightError(f"tensor annotation is missing shape argument: {source}")
132
+ shape_node = call.args[1]
133
+ try:
134
+ raw_shape = ast.literal_eval(shape_node)
135
+ except Exception as exc: # noqa: BLE001
136
+ namespace = dict(getattr(jit_function, "_globals", {}))
137
+ try:
138
+ raw_shape = eval(ast.unparse(shape_node), namespace) # noqa: S307 - internal repro runner.
139
+ except Exception as eval_exc: # noqa: BLE001
140
+ raise PreflightError(f"tensor shape must be literal/evaluable: {source}") from eval_exc
141
+ if isinstance(raw_shape, int):
142
+ return [dimension_value(raw_shape)]
143
+ if not isinstance(raw_shape, (list, tuple)):
144
+ raise PreflightError(f"tensor shape must be a list/tuple literal: {source}")
145
+ return [dimension_value(dim) for dim in raw_shape]
146
+
147
+
148
+ def is_tensor_annotation(value: Any, source: str) -> bool:
149
+ if "tensor_type" in source or ".tensor(" in source:
150
+ return True
151
+ return hasattr(value, "is_tensor") and bool(value.is_tensor())
152
+
153
+
154
+ def scalar_arg(type_value: Any) -> Any:
155
+ text = str(type_value).lower()
156
+ if "bool" in text:
157
+ return True
158
+ if "float" in text or "f32" in text or "f64" in text:
159
+ return 1.0
160
+ if "list" in text:
161
+ return [0]
162
+ if "tuple" in text:
163
+ return (0, 0)
164
+ return 1
165
+
166
+
167
+ def make_tensor_arg(torch_module: Any, type_value: Any, source: str, name: str, jit_function: Any) -> Any:
168
+ shape = tensor_shape_from_annotation_source(source, jit_function)
169
+ dtype = getattr(torch_module, dtype_name(type_value, source))
170
+ if name.lower() in {"out", "output", "result"} or name.lower().endswith(("_out", "_output")):
171
+ return torch_module.zeros(*shape, dtype=dtype)
172
+ if dtype is torch_module.bool:
173
+ return torch_module.zeros(*shape, dtype=dtype)
174
+ if dtype in {torch_module.int32, torch_module.int64}:
175
+ return torch_module.ones(*shape, dtype=dtype)
176
+ return torch_module.randn(*shape, dtype=dtype)
177
+
178
+
179
+ def synthesize_args(jit_function: Any) -> list[Any]:
180
+ function_def = get_function_def(jit_function)
181
+ if not function_def.args.args:
182
+ return []
183
+
184
+ import torch
185
+
186
+ args: list[Any] = []
187
+ for arg in function_def.args.args:
188
+ source = annotation_source(arg.annotation)
189
+ type_value = eval_annotation(source, jit_function)
190
+ if is_tensor_annotation(type_value, source):
191
+ args.append(make_tensor_arg(torch, type_value, source, arg.arg, jit_function))
192
+ else:
193
+ args.append(scalar_arg(type_value))
194
+ return args
195
+
196
+
197
+ def placement_for(jit_function: Any, name: str) -> Any:
198
+ """Where a parameter has to be bound, from the decorator's param_kinds.
199
+
200
+ None means pass the tensor as it is; anything else has to be wrapped in
201
+ legato.Arg. A kernel that declares a placement and is called without one
202
+ fails at lowering with "declared as 'top_param' but passed as a bare value",
203
+ so both the lowering path and the Playground harness ask this.
204
+ """
205
+ import legato.model.bertha as bertha
206
+
207
+ # The compiled function keeps these as _param_kinds; reading the public
208
+ # spelling silently found nothing and left every argument unbound.
209
+ kinds = getattr(jit_function, "_param_kinds", None)
210
+ if kinds is None:
211
+ kinds = getattr(jit_function, "param_kinds", None) or {}
212
+ # Stored normalised as "<kind>_param"; the decorator is written with the bare
213
+ # word. Accept either rather than depending on which spelling survives.
214
+ kind = kinds.get(name)
215
+ if isinstance(kind, str) and kind.endswith("_param"):
216
+ kind = kind[: -len("_param")]
217
+ if kind in (None, "universal"):
218
+ return None
219
+ if kind == "top":
220
+ return bertha.top
221
+ if kind == "core":
222
+ return bertha.core(0)
223
+ raise PreflightError(f"parameter '{name}' has param_kind '{kind}', which is not placed automatically")
224
+
225
+
226
+ def place_arguments(jit_function: Any, values: list[Any]) -> list[Any]:
227
+ """Bind each argument to the device target its parameter declares."""
228
+ import legato
229
+
230
+ names = [arg.arg for arg in get_function_def(jit_function).args.args]
231
+ placed: list[Any] = []
232
+ for name, value in zip(names, values):
233
+ target = placement_for(jit_function, name)
234
+ placed.append(value if target is None else legato.Arg(value, target))
235
+ return placed
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def launcher_working_directory() -> Path:
9
+ return Path(os.environ.get("AGORA_SDK_ROOT", Path.cwd())).resolve()
10
+
11
+
12
+ def compiler_root() -> Path | None:
13
+ """Root of a separate SDK supplying legato, when one was selected."""
14
+ configured = os.environ.get("AGORA_LEGATO_COMPILER_ROOT", "").strip()
15
+ return Path(configured).resolve() if configured else None
16
+
17
+
18
+ def legato_source_path(root: Path) -> Path | None:
19
+ """The checkout's legato package, but only when it was built in place.
20
+
21
+ A prepared runtime is a git checkout whichever way its packages arrived, so
22
+ `legato/src` exists even when legato came from a published wheel. Putting it
23
+ on the path then shadows the installed package with pure Python that has no
24
+ compiled `_legato`, and every kernel dies on `cannot import name
25
+ 'get_builder' from 'legato._legato' (unknown location)`.
26
+
27
+ Presence of the built extension is what distinguishes the two layouts, so
28
+ that is what this asks -- no configuration to keep in sync.
29
+ """
30
+ source = root / "legato" / "src"
31
+ return source if any((source / "legato").glob("_legato*.so")) else None
32
+
33
+
34
+ def launcher_import_paths(kernel_path: Path) -> list[Path]:
35
+ sdk_root = launcher_working_directory()
36
+ compiler = compiler_root()
37
+ candidates = [
38
+ kernel_path.parent,
39
+ # These land ahead of the runtime SDK so its legato is shadowed. They are
40
+ # inserted into sys.path directly, which outranks PYTHONPATH, so setting
41
+ # PYTHONPATH alone would not be enough.
42
+ *([path for path in (legato_source_path(compiler), compiler / "legato_aten_lib") if path] if compiler else []),
43
+ sdk_root,
44
+ *([source] if (source := legato_source_path(sdk_root)) else []),
45
+ ]
46
+ paths: list[Path] = []
47
+ seen: set[str] = set()
48
+ for candidate in candidates:
49
+ text = str(candidate.resolve())
50
+ if text in seen:
51
+ continue
52
+ seen.add(text)
53
+ paths.append(candidate)
54
+ return paths
55
+
56
+
57
+ def script_command(kernel_path: Path) -> list[str]:
58
+ return [sys.executable, str(Path(__file__).with_name("legato_runner_script_wrapper.py")), str(kernel_path)]
59
+
60
+
61
+ def launcher_env(kernel_path: Path, output_root: Path, stages: list[str]) -> dict[str, str]:
62
+ import_paths = launcher_import_paths(kernel_path)
63
+ env = {
64
+ **os.environ,
65
+ "AGORA_LEGATO_OUTPUT_ROOT": str(output_root),
66
+ "AGORA_LEGATO_STAGES": ",".join(stages),
67
+ "LEGATO_OUTPUT_PATH": str(output_root),
68
+ "AGORA_SDK_ROOT": str(launcher_working_directory()),
69
+ "AGORA_LEGATO_IMPORT_PATHS": os.pathsep.join(str(path) for path in import_paths),
70
+ "AGORA_LEGATO_SESSION_LAUNCH": os.environ.get("AGORA_LEGATO_SESSION_LAUNCH", "0"),
71
+ }
72
+ return prepend_pythonpath(env, import_paths)
73
+
74
+
75
+ def prepend_pythonpath(env: dict[str, str], paths: list[Path]) -> dict[str, str]:
76
+ existing = env.get("PYTHONPATH", "")
77
+ values = [str(path) for path in paths]
78
+ values.extend(item for item in existing.split(os.pathsep) if item)
79
+ env["PYTHONPATH"] = os.pathsep.join(dedupe(values))
80
+ return env
81
+
82
+
83
+ def dedupe(values: list[str]) -> list[str]:
84
+ result: list[str] = []
85
+ seen: set[str] = set()
86
+ for value in values:
87
+ if value in seen:
88
+ continue
89
+ seen.add(value)
90
+ result.append(value)
91
+ return result