ops-tune-workbench-plugin 0.0.0-stage → 0.0.1-beta.2
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.
- package/NOTICE.md +14 -0
- package/README.md +39 -2
- package/cordis.patch.yml +20 -0
- package/lib/client.js +4253 -0
- package/lib/host.js +2518 -0
- package/package.json +53 -4
- package/skills/tool-ops-tune/INSTALL.md +52 -0
- package/skills/tool-ops-tune/README.md +167 -0
- package/skills/tool-ops-tune/SKILL.md +93 -0
- package/skills/tool-ops-tune/agent/AGENTS.md +195 -0
- package/skills/tool-ops-tune/agent/agents/perf-implementer.md +68 -0
- package/skills/tool-ops-tune/agent/agents/perf-reviewer.md +86 -0
- package/skills/tool-ops-tune/agent/workflows/task-prompts.md +362 -0
- package/skills/tool-ops-tune/agent/workflows/troubleshooting.md +43 -0
- package/skills/tool-ops-tune/docs/figures/opstune-architecture.png +0 -0
- package/skills/tool-ops-tune/docs/figures/opstune-architecture.svg +253 -0
- package/skills/tool-ops-tune/docs/ops-tune-design.md +404 -0
- package/skills/tool-ops-tune/docs//347/224/250/344/276/213/346/236/204/345/273/272.md +714 -0
- package/skills/tool-ops-tune/docs//347/237/245/350/257/206/345/272/223/350/247/204/350/214/203.md +103 -0
- package/skills/tool-ops-tune/evals/evals.json +242 -0
- package/skills/tool-ops-tune/ops-collect/SKILL.md +146 -0
- package/skills/tool-ops-tune/ops-collect/references/tool-routing.md +86 -0
- package/skills/tool-ops-tune/ops-collect/scripts/collect_board.sh +444 -0
- package/skills/tool-ops-tune/ops-collect/scripts/collect_cases.py +368 -0
- package/skills/tool-ops-tune/ops-collect/scripts/collect_sim.sh +134 -0
- package/skills/tool-ops-tune/ops-collect/scripts/csv_to_db.py +314 -0
- package/skills/tool-ops-tune/ops-collect/scripts/db_bench.py +545 -0
- package/skills/tool-ops-tune/ops-collect/scripts/facts_bridge.py +1063 -0
- package/skills/tool-ops-tune/ops-collect/scripts/isolation_test.py +454 -0
- package/skills/tool-ops-tune/ops-collect/scripts/jsonl_to_cases.py +163 -0
- package/skills/tool-ops-tune/ops-family/SKILL.md +76 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/advanced_tiling.md +92 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/broadcast_design.md +160 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_add_kernel.cpp +126 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_add_tiling.cpp +144 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/code/broadcast_tiling_common.h +401 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/code/onedim_add_kernel.cpp +107 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/code/onedim_add_tiling.cpp +64 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/datacopypad_design.md +46 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/guide.md +14 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/nddma_design.md +53 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/onedim_design.md +114 -0
- package/skills/tool-ops-tune/ops-family/references/broadcast/ub_broadcast_design.md +67 -0
- package/skills/tool-ops-tune/ops-family/references/common/datacopy_optimization_design.md +351 -0
- package/skills/tool-ops-tune/ops-family/references/common/index.md +9 -0
- package/skills/tool-ops-tune/ops-family/references/common/tail_block_design.md +268 -0
- package/skills/tool-ops-tune/ops-family/references/common/timing_harness/index.md +38 -0
- package/skills/tool-ops-tune/ops-family/references/common/timing_harness/timing_harness_aclrt_event.cpp +199 -0
- package/skills/tool-ops-tune/ops-family/references/common/timing_harness/timing_harness_torch_event.py +130 -0
- package/skills/tool-ops-tune/ops-family/references/common/ub_resident_design.md +342 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/guide.md +69 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/index.md +4 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_base.template +81 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_big_dim.template +217 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_cut_one_axis.template +336 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_cut_two_axis.template +781 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_n_last.template +340 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_small_shape.template +321 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_tensor_move.template +102 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_transdata_5hd.template +480 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_transdata_5hd_021axis.template +533 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/dav3510/transpose_with_gather.template +688 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/templates/transpose_tiling_data.template +192 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_big_dim.md +220 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_cut_one_axis.md +175 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_cut_two_axis.md +177 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_fusion_design.md +398 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_gather.md +191 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_n_last.md +276 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_small_shape.md +315 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_tensor_move.md +203 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_tiling_data.md +176 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_vconv_021.md +206 -0
- package/skills/tool-ops-tune/ops-family/references/conversion/transpose_vconv_5hd.md +179 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/double_buffer_design.md +288 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/guide.md +46 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/index.md +10 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/CMakeLists.txt +131 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/README.md +55 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/add_custom_torch.cpp +80 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/ops.h +23 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_extension/register.cpp +30 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_host/add_custom.asc +149 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_host/data_utils.h +97 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_kernel/add_custom_kernel.asc +136 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/op_kernel/add_custom_tiling.h +31 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/run.sh +86 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/gen_data.py +70 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/golden.py +34 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/test_torch.py +99 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/add_custom/scripts/verify_result.py +56 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/templates/usage_guide.md +61 -0
- package/skills/tool-ops-tune/ops-family/references/elementwise/vector_efficiency_design.md +203 -0
- package/skills/tool-ops-tune/ops-family/references/fa/fa_mixed_kernel_design.md +174 -0
- package/skills/tool-ops-tune/ops-family/references/fa/fa_roofline_design.md +141 -0
- package/skills/tool-ops-tune/ops-family/references/fa/guide.md +87 -0
- package/skills/tool-ops-tune/ops-family/references/knowledge_gaps.json +56 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/best-practices-guide.md +14 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/fullload_design.md +344 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/guide.md +52 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/mte2_preload_design.md +320 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/pingpong_design.md +254 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/scale_coalescing_design.md +282 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/streamk_design.md +374 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/swat_design.md +423 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/templates/matmul_basic_blaze.template +165 -0
- package/skills/tool-ops-tune/ops-family/references/matmul/templates/usage_guide.md +29 -0
- package/skills/tool-ops-tune/ops-family/references/mc2/best-practices-guide.md +43 -0
- package/skills/tool-ops-tune/ops-family/references/mc2/guide.md +42 -0
- package/skills/tool-ops-tune/ops-family/references/mc2/local_matmul_design.md +130 -0
- package/skills/tool-ops-tune/ops-family/references/mc2/pipeline_balancing_design.md +502 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/guide.md +178 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/kernel_utils.template +75 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_full_load.template +429 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_recompute.template +485 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ar_small_r.template +348 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_full_load.template +574 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_online.template +324 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_ara_recompute.template +436 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/dav310/softmax_v2_base.template +950 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/fused_attention_online_softmax_design.md +428 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_full_load.md +137 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_recompute.md +135 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ar_small_r.md +122 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_full_load.md +144 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_online.md +185 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_ara_recompute.md +163 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_tiling.template +460 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/softmax_v2_tiling_data.template +278 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/state_resident_design.md +337 -0
- package/skills/tool-ops-tune/ops-family/references/reduce/templates/usage_guide.md +253 -0
- package/skills/tool-ops-tune/ops-family/references/scalar/coding_principles.md +476 -0
- package/skills/tool-ops-tune/ops-family/references/scalar/conv_scalar.md +163 -0
- package/skills/tool-ops-tune/ops-family/references/scalar/guide.md +30 -0
- package/skills/tool-ops-tune/ops-family/references/simt/guide.md +91 -0
- package/skills/tool-ops-tune/ops-family/references/simt/optimization-guide.md +110 -0
- package/skills/tool-ops-tune/ops-family/references/sort/guide.md +83 -0
- package/skills/tool-ops-tune/ops-family/references/sort/radix_sort.md +354 -0
- package/skills/tool-ops-tune/ops-family/scripts/family_asset_audit.py +68 -0
- package/skills/tool-ops-tune/ops-family/scripts/tiling_candidates.py +419 -0
- package/skills/tool-ops-tune/ops-mechanism/SKILL.md +23 -0
- package/skills/tool-ops-tune/ops-mechanism/cases/ascendc/case_groupedmatmul_pipeline.md +23 -0
- package/skills/tool-ops-tune/ops-mechanism/cases/ascendc/case_triton_atomic_revert.md +19 -0
- package/skills/tool-ops-tune/ops-mechanism/cases/mc2/case_mc2_fusion.md +19 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/api-usage.md +70 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/ascendc-regbase-vf.md +45 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/ascendc-tiling.md +51 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/data-copy.md +67 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/index.md +45 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/mc2-balancing.md +77 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/mc2.md +103 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/memory-hierarchy.md +40 -0
- package/skills/tool-ops-tune/ops-mechanism/references/optimize/pipeline.md +95 -0
- package/skills/tool-ops-tune/ops-mechanism/scripts/case_backfill.py +170 -0
- package/skills/tool-ops-tune/ops-mechanism/scripts/log_gap.py +145 -0
- package/skills/tool-ops-tune/ops-mechanism/scripts/mc2_search.py +367 -0
- package/skills/tool-ops-tune/ops-report/SKILL.md +77 -0
- package/skills/tool-ops-tune/ops-verdict/evals/run_evals.py +837 -0
- package/skills/tool-ops-tune/ops-verdict/evals/smoke/install-hooks.sh +65 -0
- package/skills/tool-ops-tune/ops-verdict/evals/smoke/run_smoke.sh +405 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/core_tail_a5.csv +25 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/few_cores_a5.csv +5 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/mte2_bound_a2.csv +17 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/multi_kernel_mix_a5.csv +7 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/no_bound_a5.csv +9 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/serial_no_overlap_a2.csv +9 -0
- package/skills/tool-ops-tune/ops-verdict/fixtures/vec_bound_a5.csv +9 -0
- package/skills/tool-ops-tune/ops-verdict/references/io-contracts.md +94 -0
- package/skills/tool-ops-tune/ops-verdict/references/rule-schema.md +130 -0
- package/skills/tool-ops-tune/ops-verdict/references/scope-schema.md +61 -0
- package/skills/tool-ops-tune/ops-verdict/references/soc-spec.yaml +140 -0
- package/skills/tool-ops-tune/ops-verdict/rules/L0-instruction.yaml +67 -0
- package/skills/tool-ops-tune/ops-verdict/rules/L1-single-core.yaml +316 -0
- package/skills/tool-ops-tune/ops-verdict/rules/L2-inter-core.yaml +173 -0
- package/skills/tool-ops-tune/ops-verdict/rules/L3-inter-card.yaml +156 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/batch_pipeline.py +444 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/calibrate.py +249 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/delta_compare.py +244 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/render_html.py +274 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/snapshot.py +200 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/trace_bubble.py +331 -0
- package/skills/tool-ops-tune/ops-verdict/scripts/verdict.py +1072 -0
- package/skills/tool-ops-tune/scripts/diff_audit.py +281 -0
package/lib/host.js
ADDED
|
@@ -0,0 +1,2518 @@
|
|
|
1
|
+
// src/host/index.ts
|
|
2
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
|
|
4
|
+
// src/host/producerMetadata.ts
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { createReadStream } from "node:fs";
|
|
7
|
+
import { readFile, readdir, writeFile, stat, lstat } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
var metadataName = "cann-profiler-producer.json";
|
|
10
|
+
var inputs = /* @__PURE__ */ new Set(["OpBasicInfo.csv", "PipeUtilization.csv", "ArithmeticUtilization.csv", "Memory.csv", "MemoryL0.csv", "MemoryUB.csv", "L2Cache.csv", "ResourceConflictRatio.csv", "visualize_data.bin", "trace.json", "PipeTrace.json"]);
|
|
11
|
+
async function digests(directory) {
|
|
12
|
+
const files = {};
|
|
13
|
+
let total = 0;
|
|
14
|
+
for (const entry of (await readdir(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
15
|
+
if (!inputs.has(entry.name)) continue;
|
|
16
|
+
if (!entry.isFile()) throw new Error("Producer input must be a regular file");
|
|
17
|
+
const path = join(directory, entry.name), size = (await stat(path)).size;
|
|
18
|
+
total += size;
|
|
19
|
+
if (size > 128 * 1024 * 1024 || total > 256 * 1024 * 1024) throw new Error("Producer input exceeds archive budget");
|
|
20
|
+
const hash = createHash("sha256");
|
|
21
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
22
|
+
files[entry.name] = { bytes: size, sha256: hash.digest("hex") };
|
|
23
|
+
}
|
|
24
|
+
return files;
|
|
25
|
+
}
|
|
26
|
+
async function writeProducerMetadata(directory, version) {
|
|
27
|
+
if (!/^\d+\.\d+\.\d+$/.test(version)) throw new Error("Invalid profiler version");
|
|
28
|
+
const files = await digests(directory);
|
|
29
|
+
if (!Object.keys(files).length) return;
|
|
30
|
+
await writeFile(join(directory, metadataName), JSON.stringify({ schema: "cann-profiler-producer/1", tool: "msopprof", version, files }, null, 2) + "\n");
|
|
31
|
+
}
|
|
32
|
+
async function readProducerMetadata(directory) {
|
|
33
|
+
let contents;
|
|
34
|
+
try {
|
|
35
|
+
const info = await lstat(join(directory, metadataName));
|
|
36
|
+
if (!info.isFile() || info.size > 64 * 1024) throw new Error("Invalid profiler metadata file");
|
|
37
|
+
contents = await readFile(join(directory, metadataName), "utf8");
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code === "ENOENT") return void 0;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (contents.length > 64 * 1024) throw new Error("Profiler metadata exceeds budget");
|
|
43
|
+
const value = JSON.parse(contents);
|
|
44
|
+
if (value.schema !== "cann-profiler-producer/1" || value.tool !== "msopprof" || typeof value.version !== "string" || !/^\d+\.\d+\.\d+$/.test(value.version)) throw new Error("Unsupported profiler metadata");
|
|
45
|
+
const actual = await digests(directory);
|
|
46
|
+
const expected = value.files;
|
|
47
|
+
if (!expected || typeof expected !== "object" || Array.isArray(expected) || !Object.keys(actual).length) throw new Error("Missing profiler input hashes");
|
|
48
|
+
if (Object.keys(actual).sort().join("\0") !== Object.keys(expected).sort().join("\0")) throw new Error("Profiler input set changed");
|
|
49
|
+
for (const [name2, file] of Object.entries(actual)) {
|
|
50
|
+
const stored = expected[name2];
|
|
51
|
+
if (!stored || stored.bytes !== file.bytes || stored.sha256 !== file.sha256) throw new Error("Profiler input hash mismatch");
|
|
52
|
+
}
|
|
53
|
+
return value.version;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/host/profilerCommand.ts
|
|
57
|
+
import { dirname } from "node:path";
|
|
58
|
+
function onboardInvocation(tool, output, metrics, target) {
|
|
59
|
+
return {
|
|
60
|
+
argv: tool.source === "msopprof" ? [tool.path, "--output", output, `--aic-metrics=${metrics.join(",")}`, target.exe, ...target.argv] : [tool.path, "op", "--output", output, `--aic-metrics=${metrics.join(",")}`, "--application", target.exe],
|
|
61
|
+
cwd: dirname(target.exe)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function captureMetrics(value, fallback) {
|
|
65
|
+
if (value === void 0) return fallback;
|
|
66
|
+
const verified = /* @__PURE__ */ new Set(["Default", "Roofline", "MemoryDetail", "Source", "PipeTimeline", "InstrTimeline", "Occupancy"]);
|
|
67
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every((item) => typeof item === "string" && verified.has(item))) {
|
|
68
|
+
throw new Error("options.aic_metrics must select verified msopprof modes: " + [...verified].join(", "));
|
|
69
|
+
}
|
|
70
|
+
return [...new Set(value)];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/host/proc.ts
|
|
74
|
+
async function spawn(ctx, input) {
|
|
75
|
+
const subprocess = ctx.get("subprocess");
|
|
76
|
+
if (subprocess !== void 0) {
|
|
77
|
+
const handle = subprocess.spawn({
|
|
78
|
+
argv: input.argv,
|
|
79
|
+
cwd: input.cwd,
|
|
80
|
+
stdio: { stdin: "ignore", stdout: { maxBytes: 16 * 1024 * 1024 }, stderr: { maxBytes: 4 * 1024 * 1024 } },
|
|
81
|
+
graceMs: 5e3,
|
|
82
|
+
signal: input.signal
|
|
83
|
+
});
|
|
84
|
+
const done = await handle.done;
|
|
85
|
+
return {
|
|
86
|
+
exitCode: done.exitCode,
|
|
87
|
+
stdout: handle.collected.stdout?.readFrom(0) ?? "",
|
|
88
|
+
stderr: handle.collected.stderr?.readFrom(0) ?? ""
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const { spawn: nodeSpawn } = await import("node:child_process");
|
|
92
|
+
return await new Promise((resolve7, reject) => {
|
|
93
|
+
const child = nodeSpawn(input.argv[0], [...input.argv.slice(1)], { cwd: input.cwd, signal: input.signal });
|
|
94
|
+
let stdout = "";
|
|
95
|
+
let stderr = "";
|
|
96
|
+
child.stdout.on("data", (chunk) => {
|
|
97
|
+
stdout += chunk.toString("utf8");
|
|
98
|
+
});
|
|
99
|
+
child.stderr.on("data", (chunk) => {
|
|
100
|
+
stderr += chunk.toString("utf8");
|
|
101
|
+
});
|
|
102
|
+
child.on("error", reject);
|
|
103
|
+
child.on("close", (exitCode) => resolve7({ exitCode, stdout, stderr }));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/host/report.ts
|
|
108
|
+
import { copyFile, mkdir, readdir as readdir2, readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
109
|
+
import { basename, join as join2, relative, resolve } from "node:path";
|
|
110
|
+
var SUMMARY_CATEGORY_MAX_FIELDS = 12;
|
|
111
|
+
async function readCsv(path) {
|
|
112
|
+
const raw = await readFile2(path, "utf8");
|
|
113
|
+
return parseCsv(raw);
|
|
114
|
+
}
|
|
115
|
+
function parseCsv(raw) {
|
|
116
|
+
const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
117
|
+
const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
|
|
118
|
+
if (lines.length === 0) return { headers: [], rows: [] };
|
|
119
|
+
const headers = splitCsvLine(lines[0]);
|
|
120
|
+
const rows = [];
|
|
121
|
+
for (const line of lines.slice(1)) {
|
|
122
|
+
const cells = splitCsvLine(line);
|
|
123
|
+
const row = {};
|
|
124
|
+
headers.forEach((h, i) => {
|
|
125
|
+
row[h] = cells[i] ?? "";
|
|
126
|
+
});
|
|
127
|
+
rows.push(row);
|
|
128
|
+
}
|
|
129
|
+
return { headers, rows };
|
|
130
|
+
}
|
|
131
|
+
function splitCsvLine(line) {
|
|
132
|
+
const cells = [];
|
|
133
|
+
let cur = "";
|
|
134
|
+
let inQuotes = false;
|
|
135
|
+
for (let i = 0; i < line.length; i++) {
|
|
136
|
+
const ch = line[i];
|
|
137
|
+
if (inQuotes) {
|
|
138
|
+
if (ch === '"') {
|
|
139
|
+
if (line[i + 1] === '"') {
|
|
140
|
+
cur += '"';
|
|
141
|
+
i++;
|
|
142
|
+
} else inQuotes = false;
|
|
143
|
+
} else cur += ch;
|
|
144
|
+
} else if (ch === '"') {
|
|
145
|
+
inQuotes = true;
|
|
146
|
+
} else if (ch === ",") {
|
|
147
|
+
cells.push(cur);
|
|
148
|
+
cur = "";
|
|
149
|
+
} else cur += ch;
|
|
150
|
+
}
|
|
151
|
+
cells.push(cur);
|
|
152
|
+
return cells;
|
|
153
|
+
}
|
|
154
|
+
function num(value) {
|
|
155
|
+
if (value === void 0 || value === null) return null;
|
|
156
|
+
const s = value.trim();
|
|
157
|
+
if (s === "" || s === "NA" || s === "N/A" || s === "null" || s === "NaN") return null;
|
|
158
|
+
const n = Number(s);
|
|
159
|
+
return Number.isFinite(n) ? n : null;
|
|
160
|
+
}
|
|
161
|
+
function pickColumn(headers, candidates) {
|
|
162
|
+
for (const c of candidates) if (headers.includes(c)) return c;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
var PIPE_DEFS = [
|
|
166
|
+
{
|
|
167
|
+
id: "cube",
|
|
168
|
+
label: "Cube",
|
|
169
|
+
side: "cube",
|
|
170
|
+
ratioColumns: ["aic_cube_ratio", "aiv_cube_ratio", "CubeUtilization(%)", "cube_ratio", "CUBE(%)"],
|
|
171
|
+
timeColumns: ["aic_cube_time(us)", "aiv_cube_time(us)"]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "mte2",
|
|
175
|
+
label: "MTE2",
|
|
176
|
+
side: "cube",
|
|
177
|
+
ratioColumns: ["aic_mte2_ratio", "aiv_mte2_ratio", "ai_mte2_ratio", "Mte2Utilization(%)", "mte2_ratio", "MTE2(%)"],
|
|
178
|
+
timeColumns: ["aic_mte2_time(us)", "aiv_mte2_time(us)"]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "mte1",
|
|
182
|
+
label: "MTE1",
|
|
183
|
+
side: "cube",
|
|
184
|
+
ratioColumns: ["aic_mte1_ratio", "aiv_mte1_ratio", "Mte1Utilization(%)", "mte1_ratio", "MTE1(%)"],
|
|
185
|
+
timeColumns: ["aic_mte1_time(us)", "aiv_mte1_time(us)"]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "fixp",
|
|
189
|
+
label: "FixP",
|
|
190
|
+
side: "cube",
|
|
191
|
+
ratioColumns: ["aic_fixpipe_ratio", "aiv_fixpipe_ratio", "FixpipeUtilization(%)", "fixp_ratio", "FIXP(%)"],
|
|
192
|
+
timeColumns: ["aic_fixpipe_time(us)", "aiv_fixpipe_time(us)"]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "scalar",
|
|
196
|
+
label: "Scalar",
|
|
197
|
+
side: "cube",
|
|
198
|
+
ratioColumns: ["aic_scalar_ratio", "aiv_scalar_ratio", "ai_scalar_ratio", "ScalarUtilization(%)", "scalar_ratio", "SCALAR(%)"],
|
|
199
|
+
timeColumns: ["aic_scalar_time(us)", "aiv_scalar_time(us)"]
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "icache",
|
|
203
|
+
label: "ICache Miss",
|
|
204
|
+
side: "cube",
|
|
205
|
+
ratioColumns: ["aic_icache_miss_rate", "ai_icache_miss_rate", "aiv_icache_miss_rate"],
|
|
206
|
+
timeColumns: []
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "vector",
|
|
210
|
+
label: "Vector",
|
|
211
|
+
side: "vector",
|
|
212
|
+
ratioColumns: ["aiv_vec_ratio", "aic_vec_ratio", "VectorUtilization(%)", "vec_ratio", "VECTOR(%)"],
|
|
213
|
+
timeColumns: ["aiv_vec_time(us)", "aic_vec_time(us)"]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "mte2",
|
|
217
|
+
label: "MTE2",
|
|
218
|
+
side: "vector",
|
|
219
|
+
ratioColumns: ["aiv_mte2_ratio", "aic_mte2_ratio", "ai_mte2_ratio", "Mte2Utilization(%)", "mte2_ratio", "MTE2(%)"],
|
|
220
|
+
timeColumns: ["aiv_mte2_time(us)", "aic_mte2_time(us)"]
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "mte3",
|
|
224
|
+
label: "MTE3",
|
|
225
|
+
side: "vector",
|
|
226
|
+
ratioColumns: ["aiv_mte3_ratio", "aic_mte3_ratio", "ai_mte3_ratio", "Mte3Utilization(%)", "mte3_ratio", "MTE3(%)"],
|
|
227
|
+
timeColumns: ["aiv_mte3_time(us)", "aic_mte3_time(us)"]
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "scalar",
|
|
231
|
+
label: "Scalar",
|
|
232
|
+
side: "vector",
|
|
233
|
+
ratioColumns: ["aiv_scalar_ratio", "aic_scalar_ratio", "ai_scalar_ratio", "ScalarUtilization(%)", "scalar_ratio", "SCALAR(%)"],
|
|
234
|
+
timeColumns: ["aiv_scalar_time(us)", "aic_scalar_time(us)"]
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "icache",
|
|
238
|
+
label: "ICache Miss",
|
|
239
|
+
side: "vector",
|
|
240
|
+
ratioColumns: ["aiv_icache_miss_rate", "ai_icache_miss_rate", "aic_icache_miss_rate"],
|
|
241
|
+
timeColumns: []
|
|
242
|
+
}
|
|
243
|
+
];
|
|
244
|
+
async function scanDir(dir) {
|
|
245
|
+
const shape = {
|
|
246
|
+
opSummaryPath: null,
|
|
247
|
+
opBasicInfoPath: null,
|
|
248
|
+
sections: /* @__PURE__ */ new Map(),
|
|
249
|
+
summaryJsonl: null,
|
|
250
|
+
hardwareJsonl: null,
|
|
251
|
+
pipeTrace: null
|
|
252
|
+
};
|
|
253
|
+
const SECTION_NAMES = [
|
|
254
|
+
"PipeUtilization",
|
|
255
|
+
"ArithmeticUtilization",
|
|
256
|
+
"ResourceConflictRatio",
|
|
257
|
+
"MemoryL0",
|
|
258
|
+
"L2Cache",
|
|
259
|
+
"Memory",
|
|
260
|
+
"MemoryUB",
|
|
261
|
+
"MemoryL1"
|
|
262
|
+
];
|
|
263
|
+
const walk = async (d, depth) => {
|
|
264
|
+
let entries;
|
|
265
|
+
try {
|
|
266
|
+
entries = await readdir2(d, { withFileTypes: true });
|
|
267
|
+
} catch {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const e of entries) {
|
|
271
|
+
const p = join2(d, e.name);
|
|
272
|
+
if (e.isDirectory()) {
|
|
273
|
+
if (e.name === "mindstudio_profiler_output" && depth === 0) await walk(p, depth + 1);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (e.name === "OpBasicInfo.csv" && shape.opBasicInfoPath === null) shape.opBasicInfoPath = p;
|
|
277
|
+
else if (/^op_summary_.*\.csv$/.test(e.name) && shape.opSummaryPath === null) shape.opSummaryPath = p;
|
|
278
|
+
else if (e.name === "Summary.jsonl" || e.name === "summary.jsonl") {
|
|
279
|
+
if (shape.summaryJsonl === null) shape.summaryJsonl = p;
|
|
280
|
+
} else if (e.name === "HardwareInfo.jsonl") {
|
|
281
|
+
if (shape.hardwareJsonl === null) shape.hardwareJsonl = p;
|
|
282
|
+
} else if (e.name === "PipeTrace.json") {
|
|
283
|
+
if (shape.pipeTrace === null) shape.pipeTrace = p;
|
|
284
|
+
} else {
|
|
285
|
+
const stem = e.name.replace(/\.csv$/i, "");
|
|
286
|
+
if (SECTION_NAMES.includes(stem) && !shape.sections.has(stem)) shape.sections.set(stem, p);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
await walk(dir, 0);
|
|
291
|
+
return shape;
|
|
292
|
+
}
|
|
293
|
+
async function readJsonl(path) {
|
|
294
|
+
const raw = await readFile2(path, "utf8");
|
|
295
|
+
const out = [];
|
|
296
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
297
|
+
const t = line.trim();
|
|
298
|
+
if (t === "") continue;
|
|
299
|
+
try {
|
|
300
|
+
out.push(JSON.parse(t));
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
function asString(v) {
|
|
307
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
308
|
+
}
|
|
309
|
+
async function buildReportModel(input) {
|
|
310
|
+
const shape = await scanDir(input.dir);
|
|
311
|
+
const artifactDir = join2(input.archiveRoot, input.reportId);
|
|
312
|
+
await mkdir(artifactDir, { recursive: true });
|
|
313
|
+
const relPrefix = relative(process.cwd(), input.archiveRoot).split("\\").join("/");
|
|
314
|
+
const archiveRel = (name2) => `${relPrefix}/${input.reportId}/${name2}`;
|
|
315
|
+
let pipeTable = { headers: [], rows: [] };
|
|
316
|
+
const pipePath = shape.sections.get("PipeUtilization") ?? null;
|
|
317
|
+
if (pipePath !== null) pipeTable = await readCsv(pipePath);
|
|
318
|
+
const summaryJsonl = shape.summaryJsonl !== null ? await readJsonl(shape.summaryJsonl) : [];
|
|
319
|
+
const opInfoRows = summaryJsonl.filter((r) => r.category === "OpInfoSummary");
|
|
320
|
+
let summaryRow = null;
|
|
321
|
+
if (shape.opBasicInfoPath !== null) {
|
|
322
|
+
const basic = await readCsv(shape.opBasicInfoPath);
|
|
323
|
+
summaryRow = basic.rows[0] ?? null;
|
|
324
|
+
}
|
|
325
|
+
const opSummaryTable = shape.opSummaryPath !== null ? await readCsv(shape.opSummaryPath) : null;
|
|
326
|
+
const operators = [];
|
|
327
|
+
let selectedOperatorId = input.kernelName;
|
|
328
|
+
const isMsopprof = input.source === "msopprof-dir" || input.source === "msopprof";
|
|
329
|
+
const isMsprof = input.source === "msprof-dir" || input.source === "msprof";
|
|
330
|
+
const isNpuCompute = input.source === "npu-compute-dir" || input.source === "npu-compute";
|
|
331
|
+
const isSimulator = input.source === "simulator-dir" || input.source === "simulator";
|
|
332
|
+
if (isMsopprof) {
|
|
333
|
+
const name2 = summaryRow?.["Op Name"] ?? null;
|
|
334
|
+
if (name2 !== null && name2.trim() !== "") operators.push({ id: name2.trim(), label: name2.trim() });
|
|
335
|
+
} else if (isMsprof) {
|
|
336
|
+
if (opSummaryTable !== null && opSummaryTable.headers.includes("Op Name")) {
|
|
337
|
+
const seen = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const row of opSummaryTable.rows) {
|
|
339
|
+
const n = (row["Op Name"] ?? "").trim();
|
|
340
|
+
if (n === "" || seen.has(n)) continue;
|
|
341
|
+
seen.add(n);
|
|
342
|
+
operators.push({ id: n, label: n });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (input.kernelName === null) selectedOperatorId = null;
|
|
346
|
+
} else if (isNpuCompute) {
|
|
347
|
+
for (const r of opInfoRows) {
|
|
348
|
+
const n = asString(r["Op Name"]);
|
|
349
|
+
if (n === null) continue;
|
|
350
|
+
if (!operators.some((o) => o.id === n)) operators.push({ id: n, label: n });
|
|
351
|
+
}
|
|
352
|
+
} else if (isSimulator) {
|
|
353
|
+
for (const r of opInfoRows) {
|
|
354
|
+
const n = asString(r["Op Name"]);
|
|
355
|
+
if (n === null) continue;
|
|
356
|
+
if (!operators.some((o) => o.id === n)) operators.push({ id: n, label: n });
|
|
357
|
+
}
|
|
358
|
+
if (pipeTable.headers.includes("Op Name")) {
|
|
359
|
+
const seen = /* @__PURE__ */ new Set();
|
|
360
|
+
for (const row of pipeTable.rows) {
|
|
361
|
+
const n = (row["Op Name"] ?? "").trim();
|
|
362
|
+
if (n === "" || seen.has(n)) continue;
|
|
363
|
+
seen.add(n);
|
|
364
|
+
operators.push({ id: n, label: n });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (operators.length === 0 && summaryRow?.["Op Name"] != null && summaryRow["Op Name"] !== "") {
|
|
369
|
+
operators.push({ id: summaryRow["Op Name"], label: summaryRow["Op Name"] });
|
|
370
|
+
}
|
|
371
|
+
if (operators.length === 1 && selectedOperatorId === null) selectedOperatorId = operators[0].id;
|
|
372
|
+
let pipeRows = pipeTable.rows;
|
|
373
|
+
if (input.kernelName !== null && pipeTable.headers.includes("Op Name")) {
|
|
374
|
+
pipeRows = pipeTable.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName);
|
|
375
|
+
}
|
|
376
|
+
const opInfo = opInfoRows[0] ?? null;
|
|
377
|
+
const strOr = (v, fallback) => typeof v === "string" && v !== "" ? v : fallback;
|
|
378
|
+
const summary = {
|
|
379
|
+
op_name: strOr(opInfo?.["Op Name"], summaryRow?.["Op Name"]?.trim() || null),
|
|
380
|
+
op_type: strOr(opInfo?.["Op Type"], summaryRow?.["Op Type"]?.trim() || null),
|
|
381
|
+
task_duration_us: num(summaryRow?.["Task Duration(us)"]) ?? (typeof opInfo?.["Task Duration(us)"] === "number" ? opInfo["Task Duration(us)"] : null),
|
|
382
|
+
block_dim: num(summaryRow?.["Block Dim"]) ?? (typeof opInfo?.["Block Dim"] === "number" ? opInfo["Block Dim"] : null),
|
|
383
|
+
pid: strOr(opInfo?.["Pid"], summaryRow?.["Pid"]?.trim() || summaryRow?.["PID"]?.trim() || null),
|
|
384
|
+
current_freq: num(summaryRow?.["Current Freq"]) ?? (typeof opInfo?.["Current Freq"] === "number" ? opInfo["Current Freq"] : null),
|
|
385
|
+
rated_freq: num(summaryRow?.["Rated Freq"]) ?? (typeof opInfo?.["Rated Freq"] === "number" ? opInfo["Rated Freq"] : null),
|
|
386
|
+
core_count: null,
|
|
387
|
+
aic_flops: typeof opInfo?.aic_flops === "number" ? opInfo.aic_flops : null,
|
|
388
|
+
aiv_flops: typeof opInfo?.aiv_flops === "number" ? opInfo.aiv_flops : null,
|
|
389
|
+
aic_flops_theoretical: typeof opInfo?.aic_flops_theoretical === "number" ? opInfo.aic_flops_theoretical : null,
|
|
390
|
+
aiv_flops_theoretical: typeof opInfo?.aiv_flops_theoretical === "number" ? opInfo.aiv_flops_theoretical : null,
|
|
391
|
+
gm_bw_theoretical_gbs: typeof opInfo?.["aicore_gm_bw_theoretical(GB/s)"] === "number" ? opInfo["aicore_gm_bw_theoretical(GB/s)"] : null,
|
|
392
|
+
gm_read_bw: typeof opInfo?.["aicore_gm_read_bw(GB/s)"] === "number" ? opInfo["aicore_gm_read_bw(GB/s)"] : null,
|
|
393
|
+
gm_write_bw: typeof opInfo?.["aicore_gm_write_bw(GB/s)"] === "number" ? opInfo["aicore_gm_write_bw(GB/s)"] : null,
|
|
394
|
+
gm_bw_usage_rate: typeof opInfo?.["aicore_gm_bw_usage_rate(%)"] === "number" ? opInfo["aicore_gm_bw_usage_rate(%)"] : null,
|
|
395
|
+
parallel_utilization: typeof opInfo?.aicore_parallel_utilization === "number" ? opInfo.aicore_parallel_utilization : null,
|
|
396
|
+
parallel_balance: typeof opInfo?.aicore_parallel_balance === "number" ? opInfo.aicore_parallel_balance : null
|
|
397
|
+
};
|
|
398
|
+
if (summary.task_duration_us === null && opSummaryTable !== null && opSummaryTable.rows.length > 0) {
|
|
399
|
+
const row = input.kernelName !== null ? opSummaryTable.rows.find((r) => (r["Op Name"] ?? "").trim() === input.kernelName) ?? opSummaryTable.rows[0] : opSummaryTable.rows[0];
|
|
400
|
+
summary.op_name = summary.op_name ?? ((row["Op Name"] ?? "").trim() || null);
|
|
401
|
+
summary.op_type = summary.op_type ?? ((row["OP Type"] ?? row["Op Type"] ?? "").trim() || null);
|
|
402
|
+
summary.task_duration_us = num(row["Task Duration(us)"]);
|
|
403
|
+
summary.block_dim = summary.block_dim ?? num(row["Block Num"]) ?? num(row["Block Dim"]);
|
|
404
|
+
}
|
|
405
|
+
const hardwareSections = [];
|
|
406
|
+
if (shape.hardwareJsonl !== null) {
|
|
407
|
+
const hwRows = await readJsonl(shape.hardwareJsonl);
|
|
408
|
+
for (const row of hwRows) {
|
|
409
|
+
const cat = asString(row.category) ?? "Info";
|
|
410
|
+
const fields = [];
|
|
411
|
+
for (const [k, v] of Object.entries(row)) {
|
|
412
|
+
if (k === "category") continue;
|
|
413
|
+
fields.push({ key: k, value: v === null || v === void 0 ? "N/A" : String(v) });
|
|
414
|
+
}
|
|
415
|
+
hardwareSections.push({ id: cat, title: cat, fields });
|
|
416
|
+
}
|
|
417
|
+
const aiCore = hwRows.find((r) => r.category === "AI Core Information");
|
|
418
|
+
const cubeCount = typeof aiCore?.ai_cube_count === "number" ? aiCore.ai_cube_count : null;
|
|
419
|
+
const vectorCount = typeof aiCore?.ai_vector_count === "number" ? aiCore.ai_vector_count : null;
|
|
420
|
+
const coreCount = typeof aiCore?.ai_core_count === "number" ? aiCore.ai_core_count : null;
|
|
421
|
+
const opType = (summary.op_type ?? "").toLowerCase();
|
|
422
|
+
if (opType.includes("cube") || opType.includes("matrix")) summary.core_count = cubeCount ?? coreCount;
|
|
423
|
+
else if (opType.includes("vector")) summary.core_count = vectorCount ?? coreCount;
|
|
424
|
+
else summary.core_count = coreCount;
|
|
425
|
+
} else if (summaryRow !== null) {
|
|
426
|
+
hardwareSections.push({
|
|
427
|
+
id: "basic",
|
|
428
|
+
title: "OpBasicInfo",
|
|
429
|
+
fields: Object.entries(summaryRow).filter(([, v]) => v !== "").map(([key, value]) => ({ key, value }))
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const pipeOccupancy = [];
|
|
433
|
+
for (const def of PIPE_DEFS) {
|
|
434
|
+
const ratioCol = pickColumn(pipeTable.headers, def.ratioColumns);
|
|
435
|
+
const timeCol = pickColumn(pipeTable.headers, def.timeColumns);
|
|
436
|
+
let ratio = null;
|
|
437
|
+
let absolute = null;
|
|
438
|
+
if (ratioCol !== null) {
|
|
439
|
+
const vals = pipeRows.map((r) => num(r[ratioCol])).filter((v) => v !== null);
|
|
440
|
+
ratio = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
|
441
|
+
}
|
|
442
|
+
if (timeCol !== null) {
|
|
443
|
+
const vals = pipeRows.map((r) => num(r[timeCol])).filter((v) => v !== null);
|
|
444
|
+
absolute = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
|
445
|
+
}
|
|
446
|
+
pipeOccupancy.push({
|
|
447
|
+
id: def.id,
|
|
448
|
+
label: def.label,
|
|
449
|
+
side: def.side,
|
|
450
|
+
ratio,
|
|
451
|
+
absolute_value: absolute,
|
|
452
|
+
columns: ratioCol !== null ? [ratioCol] : [],
|
|
453
|
+
time_columns: timeCol !== null ? [timeCol] : []
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
const tableIndex = [];
|
|
457
|
+
const TABLE_GROUPS = [
|
|
458
|
+
{ name: "PipeUtilization", display: "PipeUtilization", group: "compute", missing: "PipeUtilization.csv \u672A\u91C7\u96C6" },
|
|
459
|
+
{ name: "ArithmeticUtilization", display: "ArithmeticUtilization", group: "compute", missing: "ArithmeticUtilization.csv \u672A\u91C7\u96C6" },
|
|
460
|
+
{ name: "ResourceConflictRatio", display: "ResourceConflictRatio", group: "compute", missing: "ResourceConflictRatio.csv \u672A\u91C7\u96C6" },
|
|
461
|
+
{ name: "MemoryL0", display: "MemoryL0", group: "memory", missing: "MemoryL0.csv \u672A\u91C7\u96C6" },
|
|
462
|
+
{ name: "L2Cache", display: "L2Cache", group: "memory", missing: "L2Cache.csv \u672A\u91C7\u96C6" },
|
|
463
|
+
{ name: "Memory", display: "MemoryL1", group: "memory", missing: "Memory.csv \u672A\u91C7\u96C6" },
|
|
464
|
+
// Memory.csv displays as MemoryL1 (PARITY-MAP §5.4)
|
|
465
|
+
{ name: "MemoryUB", display: "MemoryUB", group: "memory", missing: "MemoryUB.csv \u672A\u91C7\u96C6" }
|
|
466
|
+
];
|
|
467
|
+
for (const t of TABLE_GROUPS) {
|
|
468
|
+
const src = shape.sections.get(t.name);
|
|
469
|
+
if (src === void 0) {
|
|
470
|
+
tableIndex.push({
|
|
471
|
+
file_name: `${t.name}.csv`,
|
|
472
|
+
display: t.display,
|
|
473
|
+
group: t.group,
|
|
474
|
+
header_count: 0,
|
|
475
|
+
block_ids: [],
|
|
476
|
+
row_count: 0,
|
|
477
|
+
rel_path: null,
|
|
478
|
+
bytes: 0,
|
|
479
|
+
missing_reason: t.missing
|
|
480
|
+
});
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const dest = join2(artifactDir, `${t.name}.csv`);
|
|
484
|
+
await copyFile(src, dest);
|
|
485
|
+
const table = t.name === "PipeUtilization" && pipeTable.headers.length > 0 ? { headers: pipeTable.headers, rows: pipeRows } : await readCsv(src);
|
|
486
|
+
const info = await stat2(dest);
|
|
487
|
+
const blockIds = Array.from(new Set(
|
|
488
|
+
table.rows.map((r) => (r.block_id ?? "").trim()).filter((b) => b !== "")
|
|
489
|
+
));
|
|
490
|
+
tableIndex.push({
|
|
491
|
+
file_name: `${t.name}.csv`,
|
|
492
|
+
display: t.display,
|
|
493
|
+
group: t.group,
|
|
494
|
+
header_count: table.headers.length,
|
|
495
|
+
block_ids: blockIds,
|
|
496
|
+
row_count: table.rows.length,
|
|
497
|
+
rel_path: archiveRel(`${t.name}.csv`),
|
|
498
|
+
bytes: info.size
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const jsonlArchived = [];
|
|
502
|
+
for (const [name2, src] of [
|
|
503
|
+
["Summary.jsonl", shape.summaryJsonl],
|
|
504
|
+
["HardwareInfo.jsonl", shape.hardwareJsonl]
|
|
505
|
+
]) {
|
|
506
|
+
if (src === null) continue;
|
|
507
|
+
await copyFile(src, join2(artifactDir, name2));
|
|
508
|
+
jsonlArchived.push(name2);
|
|
509
|
+
}
|
|
510
|
+
if (shape.opSummaryPath !== null) {
|
|
511
|
+
const name2 = basename(shape.opSummaryPath);
|
|
512
|
+
await copyFile(shape.opSummaryPath, join2(artifactDir, name2));
|
|
513
|
+
jsonlArchived.push(name2);
|
|
514
|
+
}
|
|
515
|
+
const arithPath = shape.sections.get("ArithmeticUtilization");
|
|
516
|
+
const arithTable = arithPath !== void 0 ? await readCsv(arithPath) : { headers: [], rows: [] };
|
|
517
|
+
const arithRows = input.kernelName !== null && arithTable.headers.includes("Op Name") ? arithTable.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName) : arithTable.rows;
|
|
518
|
+
const computeSides = [];
|
|
519
|
+
{
|
|
520
|
+
const fopsCol = pickColumn(arithTable.headers, ["aic_cube_fops", "aic_flops"]);
|
|
521
|
+
const timeCol = pickColumn(arithTable.headers, ["aic_time(us)"]);
|
|
522
|
+
let measured = null;
|
|
523
|
+
if (fopsCol !== null) {
|
|
524
|
+
const vals = arithRows.map((r) => num(r[fopsCol])).filter((v) => v !== null);
|
|
525
|
+
const means = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
|
526
|
+
measured = means;
|
|
527
|
+
}
|
|
528
|
+
computeSides.push({
|
|
529
|
+
side: "aic",
|
|
530
|
+
measured_tflops: measured !== null && measured > 0 ? measured / 1e12 : typeof summary.aic_flops === "number" && summary.aic_flops > 0 ? summary.aic_flops / 1e12 : null,
|
|
531
|
+
peak_tflops: typeof summary.aic_flops_theoretical === "number" && summary.aic_flops_theoretical > 0 ? summary.aic_flops_theoretical / 1e12 : null
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
{
|
|
535
|
+
const fopsCol = pickColumn(arithTable.headers, ["aiv_vec_fops", "aiv_flops"]);
|
|
536
|
+
const timeCol = pickColumn(arithTable.headers, ["aiv_time(us)"]);
|
|
537
|
+
let measured = null;
|
|
538
|
+
if (fopsCol !== null) {
|
|
539
|
+
const vals = arithRows.map((r) => num(r[fopsCol])).filter((v) => v !== null);
|
|
540
|
+
measured = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
|
541
|
+
}
|
|
542
|
+
computeSides.push({
|
|
543
|
+
side: "aiv",
|
|
544
|
+
measured_tflops: measured !== null && measured > 0 ? measured / 1e12 : typeof summary.aiv_flops === "number" && summary.aiv_flops > 0 ? summary.aiv_flops / 1e12 : null,
|
|
545
|
+
peak_tflops: typeof summary.aiv_flops_theoretical === "number" && summary.aiv_flops_theoretical > 0 ? summary.aiv_flops_theoretical / 1e12 : null
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
void computeSides;
|
|
549
|
+
const memPath = shape.sections.get("Memory");
|
|
550
|
+
const memTable = memPath !== void 0 ? await readCsv(memPath) : { headers: [], rows: [] };
|
|
551
|
+
const memRows = input.kernelName !== null && memTable.headers.includes("Op Name") ? memTable.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName) : memTable.rows;
|
|
552
|
+
const bwPeakFromMem = 1600;
|
|
553
|
+
const gmPeak = summary.gm_bw_theoretical_gbs ?? bwPeakFromMem;
|
|
554
|
+
const maxOf = (col) => {
|
|
555
|
+
if (col === null) return null;
|
|
556
|
+
const vals = memRows.map((r) => num(r[col])).filter((v) => v !== null);
|
|
557
|
+
return vals.length > 0 ? Math.max(...vals) : null;
|
|
558
|
+
};
|
|
559
|
+
const readCols = ["aic_main_mem_read_bw(GB/s)", "aiv_main_mem_read_bw(GB/s)"];
|
|
560
|
+
const writeCols = ["aic_main_mem_write_bw(GB/s)", "aiv_main_mem_write_bw(GB/s)"];
|
|
561
|
+
const readMeasured = Math.max(...readCols.map((c) => maxOf(pickColumn(memTable.headers, [c])) ?? 0)) || null;
|
|
562
|
+
const writeMeasured = Math.max(...writeCols.map((c) => maxOf(pickColumn(memTable.headers, [c])) ?? 0)) || null;
|
|
563
|
+
const bandwidthCards = [
|
|
564
|
+
{
|
|
565
|
+
id: "input",
|
|
566
|
+
sides: ["aic", "aiv"].map((side) => ({
|
|
567
|
+
side,
|
|
568
|
+
measured_gbs: maxOf(pickColumn(memTable.headers, [`${side}_main_mem_read_bw(GB/s)`])),
|
|
569
|
+
peak_gbs: gmPeak
|
|
570
|
+
}))
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: "output",
|
|
574
|
+
sides: ["aic", "aiv"].map((side) => ({
|
|
575
|
+
side,
|
|
576
|
+
measured_gbs: maxOf(pickColumn(memTable.headers, [`${side}_main_mem_write_bw(GB/s)`])),
|
|
577
|
+
peak_gbs: gmPeak
|
|
578
|
+
}))
|
|
579
|
+
}
|
|
580
|
+
];
|
|
581
|
+
void readMeasured;
|
|
582
|
+
void writeMeasured;
|
|
583
|
+
const roofMix = [];
|
|
584
|
+
const mixDefs = [
|
|
585
|
+
{ id: "vec_fp32", label: "Vec_FP32", cols: ["aiv_vec_fp32_ratio"] },
|
|
586
|
+
{ id: "vec_fp16", label: "Vec_FP16", cols: ["aiv_vec_fp16_ratio", "aiv_vec_fp_ratio"] },
|
|
587
|
+
{ id: "vec_int32", label: "Vec_INT32", cols: ["aiv_vec_int32_ratio"] },
|
|
588
|
+
{ id: "vec_int16", label: "Vec_INT16", cols: ["aiv_vec_int16_ratio"] },
|
|
589
|
+
{ id: "vec_misc", label: "Vec_MISC", cols: ["aiv_vec_misc_ratio"] },
|
|
590
|
+
{ id: "cube_fp16", label: "Cube_FP16", cols: ["aic_cube_fp16_ratio", "aic_cube_fp_ratio"] },
|
|
591
|
+
{ id: "cube_int8", label: "Cube_INT8", cols: ["aic_cube_int8_ratio", "aic_cube_int_ratio"] }
|
|
592
|
+
];
|
|
593
|
+
for (const m of mixDefs) {
|
|
594
|
+
const col = pickColumn(arithTable.headers, m.cols);
|
|
595
|
+
if (col === null) continue;
|
|
596
|
+
const vals = arithRows.map((r) => num(r[col])).filter((v) => v !== null);
|
|
597
|
+
if (vals.length === 0) continue;
|
|
598
|
+
const mean = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
599
|
+
roofMix.push({ id: m.id, label: m.label, percent: mean <= 1.5 ? mean * 100 : mean });
|
|
600
|
+
}
|
|
601
|
+
let roofPerformance = null;
|
|
602
|
+
let roofIntensity = null;
|
|
603
|
+
{
|
|
604
|
+
const fopsCol = pickColumn(arithTable.headers, ["aiv_vec_fops", "aic_cube_fops"]);
|
|
605
|
+
const timeCol = pickColumn(arithTable.headers, ["aiv_time(us)", "aic_time(us)"]);
|
|
606
|
+
if (fopsCol !== null && timeCol !== null) {
|
|
607
|
+
const fopsVals = arithRows.map((r) => num(r[fopsCol])).filter((v) => v !== null);
|
|
608
|
+
const timeVals = arithRows.map((r) => num(r[timeCol])).filter((v) => v !== null);
|
|
609
|
+
if (fopsVals.length > 0 && timeVals.length > 0) {
|
|
610
|
+
const fops = fopsVals.reduce((a, b) => a + b, 0) / fopsVals.length;
|
|
611
|
+
const time = timeVals.reduce((a, b) => a + b, 0) / timeVals.length;
|
|
612
|
+
if (fops > 0 && time > 0) roofPerformance = fops / time / 1e6;
|
|
613
|
+
const readCol = pickColumn(memTable.headers, ["read_main_memory_datas(KB)"]);
|
|
614
|
+
const writeCol = pickColumn(memTable.headers, ["write_main_memory_datas(KB)"]);
|
|
615
|
+
if (readCol !== null || writeCol !== null) {
|
|
616
|
+
const kb = (readCol !== null ? memRows.map((r) => num(r[readCol])) : memRows.map(() => null)).map((v, i) => {
|
|
617
|
+
const w = writeCol !== null ? num(memRows[i][writeCol]) : null;
|
|
618
|
+
return (v ?? 0) + (w ?? 0);
|
|
619
|
+
}).filter((v) => v !== null && v > 0);
|
|
620
|
+
if (kb.length > 0) {
|
|
621
|
+
const kbMean = kb.reduce((a, b) => a + b, 0) / kb.length;
|
|
622
|
+
if (kbMean > 0) roofIntensity = fops / (kbMean * 1024);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (roofPerformance === null && typeof summary.aiv_flops === "number" && summary.aiv_flops > 0 && summary.task_duration_us !== null) {
|
|
628
|
+
roofPerformance = summary.aiv_flops / summary.task_duration_us / 1e6;
|
|
629
|
+
}
|
|
630
|
+
if (roofPerformance === null && typeof summary.aic_flops === "number" && summary.aic_flops > 0 && summary.task_duration_us !== null) {
|
|
631
|
+
roofPerformance = summary.aic_flops / summary.task_duration_us / 1e6;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const roofline = {
|
|
635
|
+
points: [{
|
|
636
|
+
id: "gm",
|
|
637
|
+
label: "GM Read + Write",
|
|
638
|
+
intensity: roofIntensity,
|
|
639
|
+
performance: roofPerformance,
|
|
640
|
+
style: "solid"
|
|
641
|
+
}],
|
|
642
|
+
mix_labels: roofMix,
|
|
643
|
+
peak_compute_tops: 1,
|
|
644
|
+
// asc-toolkit self-normalized scale (Vt = 1)
|
|
645
|
+
peak_bandwidth_gbs: gmPeak
|
|
646
|
+
};
|
|
647
|
+
const l2Table = shape.sections.get("L2Cache") !== void 0 ? await readCsv(shape.sections.get("L2Cache")) : { headers: [], rows: [] };
|
|
648
|
+
const l2Rows = input.kernelName !== null && l2Table.headers.includes("Op Name") ? l2Table.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName) : l2Table.rows;
|
|
649
|
+
const l0Table = shape.sections.get("MemoryL0") !== void 0 ? await readCsv(shape.sections.get("MemoryL0")) : { headers: [], rows: [] };
|
|
650
|
+
const l0Rows = input.kernelName !== null && l0Table.headers.includes("Op Name") ? l0Table.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName) : l0Table.rows;
|
|
651
|
+
const ubTable = shape.sections.get("MemoryUB") !== void 0 ? await readCsv(shape.sections.get("MemoryUB")) : { headers: [], rows: [] };
|
|
652
|
+
const ubRows = input.kernelName !== null && ubTable.headers.includes("Op Name") ? ubTable.rows.filter((r) => (r["Op Name"] ?? "").trim() === input.kernelName) : ubTable.rows;
|
|
653
|
+
const maxFrom = (table, rows, candidates) => {
|
|
654
|
+
const col = pickColumn(table.headers, candidates);
|
|
655
|
+
if (col === null) return null;
|
|
656
|
+
const vals = rows.map((r) => num(r[col])).filter((v) => v !== null);
|
|
657
|
+
return vals.length > 0 ? Math.max(...vals) : null;
|
|
658
|
+
};
|
|
659
|
+
const edge = (id, from, to, unit, sourceFiles, value) => ({
|
|
660
|
+
id,
|
|
661
|
+
from,
|
|
662
|
+
to,
|
|
663
|
+
unit,
|
|
664
|
+
source_files: sourceFiles,
|
|
665
|
+
label: value === null ? null : `${value.toFixed(2)} ${unit}`
|
|
666
|
+
});
|
|
667
|
+
const allEdges = [
|
|
668
|
+
edge(
|
|
669
|
+
"gm-l2-read",
|
|
670
|
+
"gm",
|
|
671
|
+
"l2",
|
|
672
|
+
"GB/s",
|
|
673
|
+
["Memory.csv"],
|
|
674
|
+
Math.max(
|
|
675
|
+
maxFrom(memTable, memRows, ["aic_main_mem_read_bw(GB/s)"]) ?? 0,
|
|
676
|
+
maxFrom(memTable, memRows, ["aiv_main_mem_read_bw(GB/s)"]) ?? 0
|
|
677
|
+
) || null
|
|
678
|
+
),
|
|
679
|
+
edge(
|
|
680
|
+
"gm-l2-write",
|
|
681
|
+
"gm",
|
|
682
|
+
"l2",
|
|
683
|
+
"GB/s",
|
|
684
|
+
["Memory.csv"],
|
|
685
|
+
Math.max(
|
|
686
|
+
maxFrom(memTable, memRows, ["aic_main_mem_write_bw(GB/s)"]) ?? 0,
|
|
687
|
+
maxFrom(memTable, memRows, ["aiv_main_mem_write_bw(GB/s)"]) ?? 0
|
|
688
|
+
) || null
|
|
689
|
+
),
|
|
690
|
+
edge(
|
|
691
|
+
"l2-l1-read",
|
|
692
|
+
"l2",
|
|
693
|
+
"l1",
|
|
694
|
+
"GB/s",
|
|
695
|
+
["Memory.csv"],
|
|
696
|
+
maxFrom(memTable, memRows, ["aic_l1_read_bw(GB/s)"])
|
|
697
|
+
),
|
|
698
|
+
edge(
|
|
699
|
+
"l2-l1-write",
|
|
700
|
+
"l2",
|
|
701
|
+
"l1",
|
|
702
|
+
"GB/s",
|
|
703
|
+
["Memory.csv"],
|
|
704
|
+
maxFrom(memTable, memRows, ["aic_l1_write_bw(GB/s)"])
|
|
705
|
+
),
|
|
706
|
+
edge(
|
|
707
|
+
"l1-l0a",
|
|
708
|
+
"l1",
|
|
709
|
+
"l0a",
|
|
710
|
+
"GB/s",
|
|
711
|
+
["MemoryL0.csv"],
|
|
712
|
+
maxFrom(l0Table, l0Rows, ["aic_l0a_read_bw(GB/s)"])
|
|
713
|
+
),
|
|
714
|
+
edge(
|
|
715
|
+
"l1-l0b",
|
|
716
|
+
"l1",
|
|
717
|
+
"l0b",
|
|
718
|
+
"GB/s",
|
|
719
|
+
["MemoryL0.csv"],
|
|
720
|
+
maxFrom(l0Table, l0Rows, ["aic_l0b_read_bw(GB/s)"])
|
|
721
|
+
),
|
|
722
|
+
edge(
|
|
723
|
+
"l0a-cube",
|
|
724
|
+
"l0a",
|
|
725
|
+
"cube",
|
|
726
|
+
"GB/s",
|
|
727
|
+
["MemoryL0.csv"],
|
|
728
|
+
maxFrom(l0Table, l0Rows, ["aic_l0a_write_bw(GB/s)"])
|
|
729
|
+
),
|
|
730
|
+
edge(
|
|
731
|
+
"l0b-cube",
|
|
732
|
+
"l0b",
|
|
733
|
+
"cube",
|
|
734
|
+
"GB/s",
|
|
735
|
+
["MemoryL0.csv"],
|
|
736
|
+
maxFrom(l0Table, l0Rows, ["aic_l0b_write_bw(GB/s)"])
|
|
737
|
+
),
|
|
738
|
+
edge(
|
|
739
|
+
"l0c-cube",
|
|
740
|
+
"l0c",
|
|
741
|
+
"cube",
|
|
742
|
+
"GB/s",
|
|
743
|
+
["MemoryL0.csv"],
|
|
744
|
+
maxFrom(l0Table, l0Rows, ["aic_l0c_read_bw_cube(GB/s)"])
|
|
745
|
+
),
|
|
746
|
+
edge(
|
|
747
|
+
"cube-l0c",
|
|
748
|
+
"cube",
|
|
749
|
+
"l0c",
|
|
750
|
+
"GB/s",
|
|
751
|
+
["MemoryL0.csv"],
|
|
752
|
+
maxFrom(l0Table, l0Rows, ["aic_l0c_write_bw_cube(GB/s)"])
|
|
753
|
+
),
|
|
754
|
+
edge(
|
|
755
|
+
"l0c-l1",
|
|
756
|
+
"l0c",
|
|
757
|
+
"l1",
|
|
758
|
+
"KB",
|
|
759
|
+
["Memory.csv"],
|
|
760
|
+
(() => {
|
|
761
|
+
const col = pickColumn(memTable.headers, ["L0C_to_L1_datas(KB)"]);
|
|
762
|
+
if (col === null) return null;
|
|
763
|
+
const vals = memRows.map((r) => num(r[col])).filter((v) => v !== null);
|
|
764
|
+
return vals.length > 0 ? Math.max(...vals) : null;
|
|
765
|
+
})()
|
|
766
|
+
),
|
|
767
|
+
edge(
|
|
768
|
+
"l0c-l2",
|
|
769
|
+
"l0c",
|
|
770
|
+
"l2",
|
|
771
|
+
"KB",
|
|
772
|
+
["Memory.csv"],
|
|
773
|
+
(() => {
|
|
774
|
+
const col = pickColumn(memTable.headers, ["L0C_to_GM_datas(KB)"]);
|
|
775
|
+
if (col === null) return null;
|
|
776
|
+
const vals = memRows.map((r) => num(r[col])).filter((v) => v !== null);
|
|
777
|
+
return vals.length > 0 ? Math.max(...vals) : null;
|
|
778
|
+
})()
|
|
779
|
+
),
|
|
780
|
+
edge(
|
|
781
|
+
"ub-l2",
|
|
782
|
+
"ub",
|
|
783
|
+
"l2",
|
|
784
|
+
"GB/s",
|
|
785
|
+
["MemoryUB.csv"],
|
|
786
|
+
maxFrom(ubTable, ubRows, ["aiv_ub_to_gm_bw(GB/s)", "aiv_ub_write_bw_gm(GB/s)"])
|
|
787
|
+
),
|
|
788
|
+
edge(
|
|
789
|
+
"l2-ub",
|
|
790
|
+
"l2",
|
|
791
|
+
"ub",
|
|
792
|
+
"GB/s",
|
|
793
|
+
["MemoryUB.csv"],
|
|
794
|
+
maxFrom(ubTable, ubRows, ["aiv_gm_to_ub_bw(GB/s)", "aiv_ub_read_bw_gm(GB/s)"])
|
|
795
|
+
),
|
|
796
|
+
edge(
|
|
797
|
+
"vec-ub",
|
|
798
|
+
"vec",
|
|
799
|
+
"ub",
|
|
800
|
+
"GB/s",
|
|
801
|
+
["MemoryUB.csv"],
|
|
802
|
+
maxFrom(ubTable, ubRows, ["aiv_ub_write_bw_vector(GB/s)"])
|
|
803
|
+
),
|
|
804
|
+
edge(
|
|
805
|
+
"ub-vec",
|
|
806
|
+
"ub",
|
|
807
|
+
"vec",
|
|
808
|
+
"GB/s",
|
|
809
|
+
["MemoryUB.csv"],
|
|
810
|
+
maxFrom(ubTable, ubRows, ["aiv_ub_read_bw_vector(GB/s)"])
|
|
811
|
+
)
|
|
812
|
+
];
|
|
813
|
+
{
|
|
814
|
+
const hitCol = pickColumn(l2Table.headers, ["aic_total_hit_rate(%)", "aiv_total_hit_rate(%)"]);
|
|
815
|
+
if (hitCol !== null) {
|
|
816
|
+
const vals = l2Rows.map((r) => num(r[hitCol])).filter((v2) => v2 !== null);
|
|
817
|
+
const v = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : null;
|
|
818
|
+
allEdges.push({
|
|
819
|
+
id: "l2-hit",
|
|
820
|
+
from: "l2",
|
|
821
|
+
to: "l2-data",
|
|
822
|
+
unit: "%",
|
|
823
|
+
source_files: ["L2Cache.csv"],
|
|
824
|
+
label: v === null ? null : `${v.toFixed(2)} %`
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const firstBlockId = pipeRows.find((r) => (r.block_id ?? "").trim() !== "")?.block_id?.trim() ?? "0";
|
|
829
|
+
const edges = allEdges.filter((e) => e.label !== null);
|
|
830
|
+
const memory_topology = { block_id: firstBlockId, edges };
|
|
831
|
+
const summaryCategories = [];
|
|
832
|
+
for (const row of summaryJsonl) {
|
|
833
|
+
const cat = asString(row.category);
|
|
834
|
+
if (cat === null || cat === "OpInfoSummary") continue;
|
|
835
|
+
const fields = [];
|
|
836
|
+
for (const [k, v] of Object.entries(row)) {
|
|
837
|
+
if (k === "category") continue;
|
|
838
|
+
fields.push({ key: k, value: v === null || v === void 0 ? "N/A" : String(v) });
|
|
839
|
+
}
|
|
840
|
+
const existing = summaryCategories.find((c) => c.id === cat);
|
|
841
|
+
if (existing !== void 0) existing.fields.push(...fields);
|
|
842
|
+
else summaryCategories.push({ id: cat, title: cat, fields });
|
|
843
|
+
}
|
|
844
|
+
for (const c of summaryCategories) {
|
|
845
|
+
if (c.fields.length > SUMMARY_CATEGORY_MAX_FIELDS) {
|
|
846
|
+
c.fields = c.fields.slice(0, SUMMARY_CATEGORY_MAX_FIELDS);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
let pipeTrace = {
|
|
850
|
+
time_unit: "us",
|
|
851
|
+
nest_card_tree: false,
|
|
852
|
+
event_count: 0,
|
|
853
|
+
min_time_ns: null,
|
|
854
|
+
max_time_ns: null,
|
|
855
|
+
rel_path: null,
|
|
856
|
+
bytes: 0,
|
|
857
|
+
missing_reason: "\u65E0\u65F6\u95F4\u7EBF\u4E8B\u4EF6\uFF08PipeTrace.json \u672A\u91C7\u96C6\uFF09"
|
|
858
|
+
};
|
|
859
|
+
if (shape.pipeTrace !== null) {
|
|
860
|
+
const dest = join2(artifactDir, "PipeTrace.json");
|
|
861
|
+
await copyFile(shape.pipeTrace, dest);
|
|
862
|
+
const info = await stat2(dest);
|
|
863
|
+
let eventCount = 0;
|
|
864
|
+
let minTs = null;
|
|
865
|
+
let maxEnd = null;
|
|
866
|
+
let timeUnit = "us";
|
|
867
|
+
try {
|
|
868
|
+
const parsed = JSON.parse(await readFile2(dest, "utf8"));
|
|
869
|
+
if (parsed.displayTimeUnit === "ns") timeUnit = "ns";
|
|
870
|
+
const nest = parsed.otherData?.nestCardTree === true;
|
|
871
|
+
for (const ev of parsed.traceEvents ?? []) {
|
|
872
|
+
eventCount += 1;
|
|
873
|
+
if (typeof ev.ts === "number") {
|
|
874
|
+
if (minTs === null || ev.ts < minTs) minTs = ev.ts;
|
|
875
|
+
const end = ev.ts + (typeof ev.dur === "number" ? ev.dur : 0);
|
|
876
|
+
if (maxEnd === null || end > maxEnd) maxEnd = end;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
pipeTrace = {
|
|
880
|
+
time_unit: timeUnit,
|
|
881
|
+
nest_card_tree: nest,
|
|
882
|
+
event_count: eventCount,
|
|
883
|
+
min_time_ns: minTs,
|
|
884
|
+
max_time_ns: maxEnd,
|
|
885
|
+
rel_path: archiveRel("PipeTrace.json"),
|
|
886
|
+
bytes: info.size
|
|
887
|
+
};
|
|
888
|
+
} catch {
|
|
889
|
+
pipeTrace = {
|
|
890
|
+
time_unit: "us",
|
|
891
|
+
nest_card_tree: false,
|
|
892
|
+
event_count: 0,
|
|
893
|
+
min_time_ns: null,
|
|
894
|
+
max_time_ns: null,
|
|
895
|
+
rel_path: archiveRel("PipeTrace.json"),
|
|
896
|
+
bytes: info.size,
|
|
897
|
+
missing_reason: "PipeTrace.json \u89E3\u6790\u5931\u8D25\uFF08\u4FDD\u7559\u5F52\u6863\u539F\u6587\u4F9B\u5BA2\u6237\u7AEF\u5206\u9875\u62C9\u53D6\uFF09"
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const capabilities = [];
|
|
902
|
+
if (pipeTrace.event_count > 0) capabilities.push("dependencies");
|
|
903
|
+
if (roofline.points[0]?.performance !== null || roofline.mix_labels.length > 0) capabilities.push("roofline");
|
|
904
|
+
if (hardwareSections.length > 0) capabilities.push("hardwareDetails");
|
|
905
|
+
if (memory_topology.edges.length > 0) capabilities.push("memoryDiagram");
|
|
906
|
+
const model = {
|
|
907
|
+
report_id: input.reportId,
|
|
908
|
+
run_id: input.runId,
|
|
909
|
+
source: input.source,
|
|
910
|
+
kind: input.kind,
|
|
911
|
+
mode: input.mode,
|
|
912
|
+
soc: input.soc,
|
|
913
|
+
label: input.label,
|
|
914
|
+
collected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
915
|
+
artifact_dir: artifactDir,
|
|
916
|
+
operators,
|
|
917
|
+
selected_operator_id: selectedOperatorId,
|
|
918
|
+
kernel_name: input.kernelName,
|
|
919
|
+
summary,
|
|
920
|
+
pipe_occupancy: pipeOccupancy,
|
|
921
|
+
compute_card: { sides: computeSides },
|
|
922
|
+
bandwidth_cards: bandwidthCards,
|
|
923
|
+
roofline,
|
|
924
|
+
memory_topology,
|
|
925
|
+
table_index: tableIndex,
|
|
926
|
+
summary_categories: summaryCategories,
|
|
927
|
+
hardware_details: { sections: hardwareSections },
|
|
928
|
+
pipe_trace: pipeTrace,
|
|
929
|
+
capabilities,
|
|
930
|
+
verdict: null,
|
|
931
|
+
comparison: null
|
|
932
|
+
};
|
|
933
|
+
return model;
|
|
934
|
+
}
|
|
935
|
+
function summarizeVerdict(full, verdictScript, relPath) {
|
|
936
|
+
const findings = Array.isArray(full.findings) ? full.findings : [];
|
|
937
|
+
const skipped = Array.isArray(full.skipped) ? full.skipped : [];
|
|
938
|
+
const recommended = Array.isArray(full.recommended_next) ? full.recommended_next.map(String) : [];
|
|
939
|
+
return {
|
|
940
|
+
schema: typeof full.schema === "string" ? full.schema : void 0,
|
|
941
|
+
engine: "ops-verdict",
|
|
942
|
+
verdict_script: verdictScript,
|
|
943
|
+
verdict: typeof full.verdict === "string" ? full.verdict : void 0,
|
|
944
|
+
headline: typeof full.headline === "string" ? full.headline : void 0,
|
|
945
|
+
generated_at: typeof full.generated_at === "string" ? full.generated_at : void 0,
|
|
946
|
+
soc: typeof full.soc === "string" ? full.soc : void 0,
|
|
947
|
+
findings_count: findings.length,
|
|
948
|
+
findings: findings.map((f) => ({
|
|
949
|
+
rule_id: String(f.rule_id ?? ""),
|
|
950
|
+
...f.severity !== void 0 ? { severity: String(f.severity) } : {},
|
|
951
|
+
...f.confidence !== void 0 ? { confidence: String(f.confidence) } : {},
|
|
952
|
+
...f.name !== void 0 ? { name: String(f.name) } : {}
|
|
953
|
+
})),
|
|
954
|
+
skipped_count: skipped.length,
|
|
955
|
+
recommended_next: recommended,
|
|
956
|
+
rel_path: relPath
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
async function buildResultChildren(artifactDir) {
|
|
960
|
+
const children = [];
|
|
961
|
+
let entries;
|
|
962
|
+
try {
|
|
963
|
+
entries = await readdir2(artifactDir, { withFileTypes: true });
|
|
964
|
+
} catch {
|
|
965
|
+
return children;
|
|
966
|
+
}
|
|
967
|
+
const TYPE_MAP = {
|
|
968
|
+
csv: "csv",
|
|
969
|
+
bin: "bin",
|
|
970
|
+
json: "json",
|
|
971
|
+
rep: "rep",
|
|
972
|
+
ncrep: "ncrep",
|
|
973
|
+
"npu-rep": "npu-rep"
|
|
974
|
+
};
|
|
975
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
976
|
+
const ext = e.name.includes(".") ? e.name.slice(e.name.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
977
|
+
if (!(ext in TYPE_MAP)) continue;
|
|
978
|
+
children.push({ title: e.name, file_path: join2(artifactDir, e.name), file_type: TYPE_MAP[ext] });
|
|
979
|
+
}
|
|
980
|
+
return children;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/host/collect.ts
|
|
984
|
+
var reportSeq = { n: 0 };
|
|
985
|
+
function pad(n, width) {
|
|
986
|
+
return String(n).padStart(width, "0");
|
|
987
|
+
}
|
|
988
|
+
function newIds() {
|
|
989
|
+
const stamp = /* @__PURE__ */ new Date();
|
|
990
|
+
const reportId = `r${stamp.getFullYear()}${pad(stamp.getMonth() + 1, 2)}${pad(stamp.getDate(), 2)}-${pad(stamp.getHours(), 2)}${pad(stamp.getMinutes(), 2)}${pad(stamp.getSeconds(), 2)}-${pad(++reportSeq.n, 2)}`;
|
|
991
|
+
const runId = `run-${reportId}`;
|
|
992
|
+
return { reportId, runId };
|
|
993
|
+
}
|
|
994
|
+
function collectionConfigAsOptions(doc) {
|
|
995
|
+
const cfg = doc?.collection_config;
|
|
996
|
+
if (cfg === void 0 || cfg === null || typeof cfg !== "object") return void 0;
|
|
997
|
+
return cfg;
|
|
998
|
+
}
|
|
999
|
+
function effectiveOptions(inputOptions, stateConfig) {
|
|
1000
|
+
const merged = { ...stateConfig ?? {} };
|
|
1001
|
+
for (const [key, value] of Object.entries(inputOptions ?? {})) {
|
|
1002
|
+
if (value === void 0 || value === null) continue;
|
|
1003
|
+
const existing = merged[key];
|
|
1004
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
|
|
1005
|
+
merged[key] = { ...existing, ...value };
|
|
1006
|
+
} else {
|
|
1007
|
+
merged[key] = value;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return merged;
|
|
1011
|
+
}
|
|
1012
|
+
function onboardOptionsToAicMetrics(onboard, isA5) {
|
|
1013
|
+
const metrics = /* @__PURE__ */ new Set(["Occupancy", "KernelScale", "Default"]);
|
|
1014
|
+
if (onboard?.roofline === true) metrics.add("Roofline");
|
|
1015
|
+
if (isA5) {
|
|
1016
|
+
if (onboard?.pcSampling === true) metrics.add("PCSampling");
|
|
1017
|
+
} else if (onboard?.cacheHeatmap === true) {
|
|
1018
|
+
metrics.add("PCSampling");
|
|
1019
|
+
}
|
|
1020
|
+
if (onboard?.operatorCodeHotspot === true) metrics.add("Source");
|
|
1021
|
+
if (onboard?.operatorMetrics === true || onboard?.interCoreLoad === true) {
|
|
1022
|
+
metrics.add("MemoryDetail");
|
|
1023
|
+
if (onboard?.l2CacheHitRate === true || onboard?.resourceConflictRatio === true) metrics.add("ResourceConflict");
|
|
1024
|
+
}
|
|
1025
|
+
return [...metrics];
|
|
1026
|
+
}
|
|
1027
|
+
function newToolMetricsToSections(newTool) {
|
|
1028
|
+
const SETS = {
|
|
1029
|
+
basic: ["PipeUtilization", "L2Cache", "Memory"],
|
|
1030
|
+
full: ["PipeUtilization", "L2Cache", "Memory", "MemoryL0", "MemoryUB"],
|
|
1031
|
+
Custom: ["PipeUtilization", "L2Cache", "Memory"]
|
|
1032
|
+
};
|
|
1033
|
+
const metrics = newTool?.metrics;
|
|
1034
|
+
if (metrics === void 0) return SETS.full;
|
|
1035
|
+
if (metrics.kind === "section" && Array.isArray(metrics.sections) && metrics.sections.length > 0) {
|
|
1036
|
+
return [.../* @__PURE__ */ new Set(["Pipeline", "ArithmeticUtilization", "ResourceConflictRatio", ...metrics.sections])];
|
|
1037
|
+
}
|
|
1038
|
+
const set = typeof metrics.set === "string" ? metrics.set : "full";
|
|
1039
|
+
return SETS[set] ?? SETS.full;
|
|
1040
|
+
}
|
|
1041
|
+
async function npuAvailable(ctx, signal) {
|
|
1042
|
+
try {
|
|
1043
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
1044
|
+
const devs = await readdir3("/dev");
|
|
1045
|
+
if (devs.some((d) => d.startsWith("davinci"))) return true;
|
|
1046
|
+
} catch {
|
|
1047
|
+
}
|
|
1048
|
+
const npuSmi = await which(ctx, "npu-smi");
|
|
1049
|
+
if (npuSmi !== null) {
|
|
1050
|
+
try {
|
|
1051
|
+
const out = await spawn(ctx, { argv: [npuSmi, "info"], cwd: process.cwd(), signal });
|
|
1052
|
+
if (out.exitCode === 0) return true;
|
|
1053
|
+
} catch {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
async function which(ctx, bin) {
|
|
1060
|
+
const { access } = await import("node:fs/promises");
|
|
1061
|
+
const candidates = [];
|
|
1062
|
+
const toolkitHome = process.env.ASCEND_TOOLKIT_HOME ?? process.env.ASC_HOME ?? "";
|
|
1063
|
+
if (toolkitHome !== "") candidates.push(`${toolkitHome}/bin/${bin}`);
|
|
1064
|
+
const pathEnv = process.env.PATH ?? "";
|
|
1065
|
+
for (const dir of pathEnv.split(":").filter((d) => d !== "")) candidates.push(`${dir}/${bin}`);
|
|
1066
|
+
candidates.push(`/usr/local/sbin/${bin}`, `/usr/local/bin/${bin}`, `/usr/bin/${bin}`, `/bin/${bin}`);
|
|
1067
|
+
for (const c of candidates) {
|
|
1068
|
+
try {
|
|
1069
|
+
await access(c);
|
|
1070
|
+
return c;
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
void ctx;
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
async function assertBlockArtifacts(dir) {
|
|
1078
|
+
let listing = [];
|
|
1079
|
+
try {
|
|
1080
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
1081
|
+
const walk = async (d, depth) => {
|
|
1082
|
+
let entries;
|
|
1083
|
+
try {
|
|
1084
|
+
entries = await readdir3(d, { withFileTypes: true });
|
|
1085
|
+
} catch {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
for (const e of entries) {
|
|
1089
|
+
const p = `${d}/${e.name}`;
|
|
1090
|
+
if (e.isDirectory()) {
|
|
1091
|
+
if (depth < 2) await walk(p, depth + 1);
|
|
1092
|
+
} else listing.push(p);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
await walk(dir, 0);
|
|
1096
|
+
listing = listing.map((p) => p.slice(dir.length + 1));
|
|
1097
|
+
} catch {
|
|
1098
|
+
return { reason: `\u4EA7\u7269\u76EE\u5F55\u4E0D\u53EF\u8BFB: ${dir}`, dirListing: [] };
|
|
1099
|
+
}
|
|
1100
|
+
const findCsv = (pattern) => {
|
|
1101
|
+
const hit = listing.find((p) => pattern.test(p.split("/").pop() ?? ""));
|
|
1102
|
+
return hit === void 0 ? null : `${dir}/${hit}`;
|
|
1103
|
+
};
|
|
1104
|
+
const candidates = [
|
|
1105
|
+
findCsv(/^op_summary_.*\.csv$/),
|
|
1106
|
+
findCsv(/^PipeUtilization\.csv$/),
|
|
1107
|
+
findCsv(/^OpBasicInfo\.csv$/),
|
|
1108
|
+
findCsv(/^summary\.db$/)
|
|
1109
|
+
];
|
|
1110
|
+
const primary = candidates.find((c) => c !== null) ?? null;
|
|
1111
|
+
if (primary === null) {
|
|
1112
|
+
return {
|
|
1113
|
+
reason: "\u4EA7\u7269\u7F3A\u5931\uFF1A\u65E0 op_summary_*.csv / PipeUtilization.csv / OpBasicInfo.csv / summary.db\uFF08\u5BF9\u9F50 collect_board.sh assert_block_artifacts\uFF09",
|
|
1114
|
+
dirListing: listing
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
if (primary.endsWith(".db")) return null;
|
|
1118
|
+
try {
|
|
1119
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
1120
|
+
const raw = await readFile6(primary, "utf8");
|
|
1121
|
+
const lines = raw.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
1122
|
+
if (lines.length < 2) {
|
|
1123
|
+
return { reason: `\u4EA7\u7269\u7A7A\uFF08${primary} ${String(lines.length)} \u884C < 2\uFF1A\u8868\u5934+\u6570\u636E\uFF09`, dirListing: listing };
|
|
1124
|
+
}
|
|
1125
|
+
if (!raw.includes("Task Duration")) {
|
|
1126
|
+
const hasBasic = listing.some((p) => p.endsWith("OpBasicInfo.csv"));
|
|
1127
|
+
if (!hasBasic) {
|
|
1128
|
+
return { reason: "Task Duration \u5217\u7F3A\u5931\u4E14\u65E0 OpBasicInfo.csv\uFF08verdict/isolation \u4E3B\u6D88\u8D39\u5B57\u6BB5\uFF09", dirListing: listing };
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
return { reason: `\u4EA7\u7269\u8BFB\u53D6\u5931\u8D25: ${String(error)}`, dirListing: listing };
|
|
1133
|
+
}
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
async function resolveTool(config, configured, bins) {
|
|
1137
|
+
if (configured !== "") {
|
|
1138
|
+
try {
|
|
1139
|
+
const { access } = await import("node:fs/promises");
|
|
1140
|
+
await access(configured);
|
|
1141
|
+
return configured;
|
|
1142
|
+
} catch {
|
|
1143
|
+
return null;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
for (const bin of bins) {
|
|
1147
|
+
const found = await which(null, bin);
|
|
1148
|
+
if (found !== null) return found;
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
async function resolveTargetExecutable(caseDir) {
|
|
1153
|
+
const { stat: stat4, readdir: readdir3 } = await import("node:fs/promises");
|
|
1154
|
+
const { join: join7, resolve: resolve7 } = await import("node:path");
|
|
1155
|
+
const dir = resolve7(process.cwd(), caseDir);
|
|
1156
|
+
try {
|
|
1157
|
+
const info = await stat4(dir);
|
|
1158
|
+
if (info.isFile()) return { exe: dir, argv: [] };
|
|
1159
|
+
} catch {
|
|
1160
|
+
}
|
|
1161
|
+
try {
|
|
1162
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1163
|
+
const candidates = ["main", "demo", "run", "app", "kernel_harness", "timing_harness"].map((n) => entries.find((e) => e.isFile() && e.name === n)).filter((e) => e !== void 0);
|
|
1164
|
+
if (candidates.length > 0) return { exe: join7(dir, candidates[0].name), argv: [] };
|
|
1165
|
+
const executables = entries.filter((e) => e.isFile());
|
|
1166
|
+
if (executables.length === 1) return { exe: join7(dir, executables[0].name), argv: [] };
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
return { exe: dir, argv: [] };
|
|
1170
|
+
}
|
|
1171
|
+
async function realCollect(ctx, config, input, exec, tool, reportId, runId, workbenchRoot, options) {
|
|
1172
|
+
const timeoutMs = config.collectTimeoutMs > 0 ? config.collectTimeoutMs : 6e5;
|
|
1173
|
+
const controller = new AbortController();
|
|
1174
|
+
const timer = setTimeout(() => {
|
|
1175
|
+
controller.abort(new Error(`ops_collect timeout after ${String(timeoutMs)}ms`));
|
|
1176
|
+
}, timeoutMs);
|
|
1177
|
+
const onCallerAbort = () => {
|
|
1178
|
+
controller.abort(exec.signal.reason);
|
|
1179
|
+
};
|
|
1180
|
+
if (exec.signal.aborted) controller.abort(exec.signal.reason);
|
|
1181
|
+
else exec.signal.addEventListener("abort", onCallerAbort, { once: true });
|
|
1182
|
+
try {
|
|
1183
|
+
const outDir = `${workbenchRoot}/collect/${runId}`;
|
|
1184
|
+
const { mkdir: mkdir3 } = await import("node:fs/promises");
|
|
1185
|
+
await mkdir3(outDir, { recursive: true });
|
|
1186
|
+
let producedDir = outDir;
|
|
1187
|
+
const profilingDiagnostics = [];
|
|
1188
|
+
if (tool.source === "msopprof" || tool.source === "msprof") {
|
|
1189
|
+
const isA5 = input.soc === "a5";
|
|
1190
|
+
const metrics = captureMetrics(options.aic_metrics, onboardOptionsToAicMetrics(options.onboard, isA5));
|
|
1191
|
+
const target = await resolveTargetExecutable(input.case_dir);
|
|
1192
|
+
const invocation = onboardInvocation({ source: tool.source, path: tool.path }, outDir, metrics, target);
|
|
1193
|
+
const out = await spawn(ctx, { ...invocation, signal: controller.signal });
|
|
1194
|
+
profilingDiagnostics.push(...(out.stdout + "\n" + out.stderr).split("\n").filter((line) => /data loss|Failed to parse pc|Hot spot function|Generate hot spot/.test(line)));
|
|
1195
|
+
if (tool.source === "msopprof" && out.exitCode === 0) {
|
|
1196
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
1197
|
+
const directories = (await readdir3(outDir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && entry.name.startsWith("OPPROF_"));
|
|
1198
|
+
if (directories.length !== 1) throw new Error("msopprof must produce one unambiguous capture directory for this run");
|
|
1199
|
+
producedDir = `${outDir}/${directories[0].name}`;
|
|
1200
|
+
}
|
|
1201
|
+
if (out.exitCode !== 0) {
|
|
1202
|
+
throw new Error(`${tool.path} \u9000\u51FA\u7801 ${String(out.exitCode)}\uFF1A${out.stderr.slice(0, 2e3)}`);
|
|
1203
|
+
}
|
|
1204
|
+
} else if (tool.source === "npu-compute") {
|
|
1205
|
+
const sections = newToolMetricsToSections(options.new_tool);
|
|
1206
|
+
const setArg = options.new_tool?.metrics?.kind === "section" ? void 0 : options.new_tool?.metrics?.set ?? config.defaultSet;
|
|
1207
|
+
const target = await resolveTargetExecutable(input.case_dir);
|
|
1208
|
+
const argv = [tool.path];
|
|
1209
|
+
if (setArg !== void 0) argv.push("--set", setArg);
|
|
1210
|
+
for (const s of sections) argv.push("--section", s);
|
|
1211
|
+
if (options.new_tool?.common?.force_overwrite === true) argv.push("--force");
|
|
1212
|
+
argv.push("--export", `${outDir}/report.npu-rep`, "--", target.exe);
|
|
1213
|
+
const out = await spawn(ctx, { argv, cwd: process.cwd(), signal: controller.signal });
|
|
1214
|
+
if (out.exitCode !== 0 || !await fileExists(`${outDir}/report.npu-rep`)) {
|
|
1215
|
+
throw new Error(`npu-compute \u91C7\u96C6\u5931\u8D25\uFF08exit ${String(out.exitCode)}\uFF09\uFF1A${out.stderr.slice(0, 2e3)}`);
|
|
1216
|
+
}
|
|
1217
|
+
const imp = await spawn(ctx, {
|
|
1218
|
+
argv: [tool.path, "--import", `${outDir}/report.npu-rep`, "--export", `${outDir}/unpacked`],
|
|
1219
|
+
cwd: process.cwd(),
|
|
1220
|
+
signal: controller.signal
|
|
1221
|
+
});
|
|
1222
|
+
const m = /unpacked=\s*(\S+)/.exec(imp.stdout + imp.stderr);
|
|
1223
|
+
producedDir = m?.[1] ?? `${outDir}/unpacked`;
|
|
1224
|
+
} else if (tool.source === "npusim") {
|
|
1225
|
+
const target = await resolveTargetExecutable(input.case_dir);
|
|
1226
|
+
const out = await spawn(ctx, {
|
|
1227
|
+
argv: [tool.path, "record", target.exe, "-o", outDir, "-g"],
|
|
1228
|
+
cwd: process.cwd(),
|
|
1229
|
+
signal: controller.signal
|
|
1230
|
+
});
|
|
1231
|
+
if (out.exitCode !== 0) throw new Error(`npusim \u9000\u51FA\u7801 ${String(out.exitCode)}\uFF1A${out.stderr.slice(0, 2e3)}`);
|
|
1232
|
+
}
|
|
1233
|
+
const failure = await assertBlockArtifacts(producedDir);
|
|
1234
|
+
if (failure !== null) {
|
|
1235
|
+
throw new Error(
|
|
1236
|
+
`\u4EA7\u7269\u65AD\u8A00\u672A\u8FC7\uFF1A${failure.reason}
|
|
1237
|
+
\u4EA7\u7269\u76EE\u5F55\u6E05\u5355\uFF08${producedDir}\uFF09\uFF1A${failure.dirListing.slice(0, 30).join(", ")}`
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
const archiveRoot = `${workbenchRoot}/reports`;
|
|
1241
|
+
const report = await buildReportModel({
|
|
1242
|
+
dir: producedDir,
|
|
1243
|
+
reportId,
|
|
1244
|
+
runId,
|
|
1245
|
+
source: tool.source,
|
|
1246
|
+
kind: "collection",
|
|
1247
|
+
mode: input.mode ?? "onboard",
|
|
1248
|
+
soc: input.soc ?? null,
|
|
1249
|
+
label: input.label,
|
|
1250
|
+
kernelName: null,
|
|
1251
|
+
archiveRoot
|
|
1252
|
+
});
|
|
1253
|
+
const result = await makeResultEntry(report, producedDir);
|
|
1254
|
+
const versionResult = tool.source === "msopprof" ? await spawn(ctx, { argv: [tool.path, "--version"], cwd: process.cwd(), signal: controller.signal }) : void 0;
|
|
1255
|
+
const producerVersion = versionResult?.exitCode === 0 ? /msopprof\s+(\d+\.\d+\.\d+)/.exec(versionResult.stdout)?.[1] : void 0;
|
|
1256
|
+
if (producerVersion) {
|
|
1257
|
+
try {
|
|
1258
|
+
await writeProducerMetadata(producedDir, producerVersion);
|
|
1259
|
+
} catch {
|
|
1260
|
+
profilingDiagnostics.push("Portable profiler provenance could not be written; the raw collection is retained.");
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return {
|
|
1264
|
+
report,
|
|
1265
|
+
result,
|
|
1266
|
+
raw_artifact_dir: producedDir,
|
|
1267
|
+
producer_version: producerVersion,
|
|
1268
|
+
profiling_diagnostics: profilingDiagnostics,
|
|
1269
|
+
metrics_preview: buildMetricsPreview(report),
|
|
1270
|
+
effective_options: options,
|
|
1271
|
+
files_found: (await scanDir(producedDir)).sections.size > 0 ? Array.from((await scanDir(producedDir)).sections.keys()) : []
|
|
1272
|
+
};
|
|
1273
|
+
} finally {
|
|
1274
|
+
clearTimeout(timer);
|
|
1275
|
+
exec.signal.removeEventListener("abort", onCallerAbort);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
async function fileExists(path) {
|
|
1279
|
+
try {
|
|
1280
|
+
const { stat: stat4 } = await import("node:fs/promises");
|
|
1281
|
+
await stat4(path);
|
|
1282
|
+
return true;
|
|
1283
|
+
} catch {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
function buildMetricsPreview(report) {
|
|
1288
|
+
const pipes = {};
|
|
1289
|
+
for (const p of report.pipe_occupancy) {
|
|
1290
|
+
const key = p.side === "cube" ? `aic_${p.id}_ratio` : `aiv_${p.id}_ratio`;
|
|
1291
|
+
if (!(key in pipes)) pipes[key] = p.ratio;
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
task_duration_us: report.summary.task_duration_us,
|
|
1295
|
+
block_dim: report.summary.block_dim,
|
|
1296
|
+
aicore_parallel_utilization: report.summary.parallel_utilization,
|
|
1297
|
+
aicore_parallel_balance: report.summary.parallel_balance,
|
|
1298
|
+
gm_bw_usage_rate: report.summary.gm_bw_usage_rate,
|
|
1299
|
+
pipe_ratios: pipes
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
async function makeResultEntry(report, producedDir) {
|
|
1303
|
+
const children = await buildResultChildren(report.artifact_dir);
|
|
1304
|
+
const nested = await collectTree(producedDir, "dump");
|
|
1305
|
+
return {
|
|
1306
|
+
report_id: report.report_id,
|
|
1307
|
+
title: report.summary.op_name ?? producedDir.split("/").at(-1) ?? report.report_id,
|
|
1308
|
+
timestamp: report.collected_at,
|
|
1309
|
+
marked: false,
|
|
1310
|
+
source: report.source,
|
|
1311
|
+
kind: report.kind,
|
|
1312
|
+
dir_path: report.artifact_dir,
|
|
1313
|
+
children: [...children, ...nested]
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
async function collectTree(dir, sub) {
|
|
1317
|
+
const out = [];
|
|
1318
|
+
try {
|
|
1319
|
+
const { readdir: readdir3, stat: stat4 } = await import("node:fs/promises");
|
|
1320
|
+
const subDir = `${dir}/${sub}`;
|
|
1321
|
+
const info = await stat4(subDir).catch(() => null);
|
|
1322
|
+
if (info === null || !info.isDirectory()) return out;
|
|
1323
|
+
const entries = await readdir3(subDir, { withFileTypes: true });
|
|
1324
|
+
const TYPE_MAP = {
|
|
1325
|
+
csv: "csv",
|
|
1326
|
+
bin: "bin",
|
|
1327
|
+
json: "json",
|
|
1328
|
+
rep: "rep",
|
|
1329
|
+
ncrep: "ncrep",
|
|
1330
|
+
"npu-rep": "npu-rep"
|
|
1331
|
+
};
|
|
1332
|
+
const kids = [];
|
|
1333
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1334
|
+
if (!e.isFile()) continue;
|
|
1335
|
+
const ext = e.name.includes(".") ? e.name.slice(e.name.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
1336
|
+
if (!(ext in TYPE_MAP)) continue;
|
|
1337
|
+
kids.push({ title: e.name, file_path: `${subDir}/${e.name}`, file_type: TYPE_MAP[ext] });
|
|
1338
|
+
}
|
|
1339
|
+
if (kids.length > 0) out.push({ title: sub, file_path: subDir, file_type: "bin", children: kids });
|
|
1340
|
+
} catch {
|
|
1341
|
+
}
|
|
1342
|
+
return out;
|
|
1343
|
+
}
|
|
1344
|
+
var stateDoc;
|
|
1345
|
+
function bindStateDoc(doc) {
|
|
1346
|
+
stateDoc = doc;
|
|
1347
|
+
}
|
|
1348
|
+
async function collect(ctx, config, input, exec) {
|
|
1349
|
+
const mode = input.mode ?? "onboard";
|
|
1350
|
+
const workbenchRoot = await workbenchDirOf(config);
|
|
1351
|
+
const { reportId, runId } = newIds();
|
|
1352
|
+
const stateOptions = collectionConfigAsOptions(stateDoc);
|
|
1353
|
+
const options = effectiveOptions(input.options, stateOptions);
|
|
1354
|
+
if (mode === "simulate") {
|
|
1355
|
+
const npusim = await resolveTool(config, config.npusimPath, ["npusim", "cannsim"]);
|
|
1356
|
+
if (npusim !== null) {
|
|
1357
|
+
return await realCollect(ctx, config, input, exec, { path: npusim, source: "npusim" }, reportId, runId, workbenchRoot, options);
|
|
1358
|
+
}
|
|
1359
|
+
throw new Error("ops_collect: npusim/cannsim \u4E0D\u53EF\u7528\uFF0C\u672A\u6267\u884C\u771F\u5B9E\u4EFF\u771F\uFF0C\u4E5F\u672A\u751F\u6210\u6F14\u793A\u6027\u80FD\u6570\u636E\u3002");
|
|
1360
|
+
}
|
|
1361
|
+
if (mode === "npu-compute") {
|
|
1362
|
+
if (!await npuAvailable(ctx, exec.signal)) {
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
'ops_collect: mode="npu-compute" \u9700\u8981 NPU \u786C\u4EF6\uFF0C\u4F46\u672C\u673A\u672A\u68C0\u6D4B\u5230 NPU\uFF08/dev/davinci* \u4E0E npu-smi \u5747\u4E0D\u53EF\u7528\uFF09\u3002\u8BF7\u5728\u5177\u5907 NPU \u548C\u91C7\u96C6\u5DE5\u5177\u7684\u73AF\u5883\u8FD0\u884C\uFF0C\u6216\u5BFC\u5165\u5DF2\u6709\u771F\u5B9E\u4EA7\u7269\u3002'
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
const path = await resolveTool(config, config.npuComputePath, ["npu-compute"]);
|
|
1368
|
+
if (path === null) throw new Error("ops_collect: npu-compute \u5DE5\u5177\u4E0D\u53EF\u7528\uFF08npuComputePath \u672A\u914D\u7F6E\u4E14 PATH/ASCEND_TOOLKIT_HOME \u672A\u627E\u5230\uFF09\u3002");
|
|
1369
|
+
return await realCollect(ctx, config, input, exec, { path, source: "npu-compute" }, reportId, runId, workbenchRoot, options);
|
|
1370
|
+
}
|
|
1371
|
+
if (!await npuAvailable(ctx, exec.signal)) {
|
|
1372
|
+
throw new Error("ops_collect: NPU \u786C\u4EF6\u4E0D\u53EF\u7528\uFF0C\u672A\u6267\u884C\u6027\u80FD\u91C7\u96C6\uFF0C\u4E5F\u672A\u751F\u6210\u6F14\u793A\u6027\u80FD\u6570\u636E\u3002");
|
|
1373
|
+
}
|
|
1374
|
+
const npuCompute = await resolveTool(config, config.npuComputePath, ["npu-compute"]);
|
|
1375
|
+
if (npuCompute !== null) {
|
|
1376
|
+
return await realCollect(ctx, config, input, exec, { path: npuCompute, source: "npu-compute" }, reportId, runId, workbenchRoot, options);
|
|
1377
|
+
}
|
|
1378
|
+
const msopprof = await resolveTool(config, config.msopprofPath, ["msopprof"]);
|
|
1379
|
+
if (msopprof !== null) {
|
|
1380
|
+
return await realCollect(ctx, config, input, exec, { path: msopprof, source: "msopprof" }, reportId, runId, workbenchRoot, options);
|
|
1381
|
+
}
|
|
1382
|
+
const msprof = await resolveTool(config, config.msprofPath, ["msprof"]);
|
|
1383
|
+
if (msprof !== null) {
|
|
1384
|
+
return await realCollect(ctx, config, input, exec, { path: msprof, source: "msprof" }, reportId, runId, workbenchRoot, options);
|
|
1385
|
+
}
|
|
1386
|
+
throw new Error("ops_collect: \u672A\u627E\u5230\u771F\u5B9E\u6027\u80FD\u91C7\u96C6\u5DE5\u5177\uFF0C\u672A\u751F\u6210\u6027\u80FD\u6570\u636E\u3002");
|
|
1387
|
+
}
|
|
1388
|
+
async function workbenchDirOf(config) {
|
|
1389
|
+
const base = config.workbenchDir === "" ? ".ops-tune-workbench" : config.workbenchDir;
|
|
1390
|
+
const { resolve: resolve7 } = await import("node:path");
|
|
1391
|
+
return resolve7(process.cwd(), base);
|
|
1392
|
+
}
|
|
1393
|
+
function defaultCollectionConfig(soc) {
|
|
1394
|
+
return {
|
|
1395
|
+
collection_type: "onboard",
|
|
1396
|
+
onboard: {
|
|
1397
|
+
interCoreLoad: true,
|
|
1398
|
+
roofline: true,
|
|
1399
|
+
operatorCodeHotspot: true,
|
|
1400
|
+
cacheHeatmap: true,
|
|
1401
|
+
pcSampling: false,
|
|
1402
|
+
operatorMetrics: true,
|
|
1403
|
+
cubeAndVectorInstructionTimeAndRatio: true,
|
|
1404
|
+
l2CacheHitRate: true,
|
|
1405
|
+
memoryReadWriteBandwidth: true,
|
|
1406
|
+
l0ReadWriteBandwidth: true,
|
|
1407
|
+
ubReadWriteBandwidth: true,
|
|
1408
|
+
computeAndMoveUnitTimeAndRatio: true,
|
|
1409
|
+
resourceConflictRatio: true
|
|
1410
|
+
},
|
|
1411
|
+
simulator: { memoryThroughputWaveform: false, resourceConflictRatio: true },
|
|
1412
|
+
new_tool: {
|
|
1413
|
+
common: { output_file: "", force_overwrite: false, command_line: "" },
|
|
1414
|
+
metrics: { kind: "set", set: "basic", sections: ["PipeUtilization", "L2Cache", "Memory"] }
|
|
1415
|
+
},
|
|
1416
|
+
soc
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function applyCollectionConfigToState(doc, soc, options) {
|
|
1420
|
+
if (options !== void 0 && options !== null && typeof options === "object") {
|
|
1421
|
+
doc.collection_config = options;
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (doc.collection_config === void 0) {
|
|
1425
|
+
doc.collection_config = defaultCollectionConfig(soc ?? "auto");
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/host/import.ts
|
|
1430
|
+
async function importDirectory(ctx, config, input) {
|
|
1431
|
+
const { resolve: resolve7 } = await import("node:path");
|
|
1432
|
+
const dir = resolve7(process.cwd(), input.dir);
|
|
1433
|
+
const shape = await scanDir(dir);
|
|
1434
|
+
const exists = await dirExists(dir);
|
|
1435
|
+
if (!exists) {
|
|
1436
|
+
throw new Error(`ops_import: \u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${dir}`);
|
|
1437
|
+
}
|
|
1438
|
+
const filesFound = [];
|
|
1439
|
+
if (shape.opBasicInfoPath !== null) filesFound.push("OpBasicInfo.csv");
|
|
1440
|
+
if (shape.opSummaryPath !== null) filesFound.push(shape.opSummaryPath.split("/").pop());
|
|
1441
|
+
for (const [name2, path] of shape.sections) {
|
|
1442
|
+
if (path.startsWith(dir)) filesFound.push(path.slice(dir.length + 1));
|
|
1443
|
+
else filesFound.push(name2);
|
|
1444
|
+
}
|
|
1445
|
+
if (shape.summaryJsonl !== null) filesFound.push("Summary.jsonl");
|
|
1446
|
+
if (shape.hardwareJsonl !== null) filesFound.push("HardwareInfo.jsonl");
|
|
1447
|
+
if (shape.pipeTrace !== null) filesFound.push("PipeTrace.json");
|
|
1448
|
+
if (filesFound.length === 0) {
|
|
1449
|
+
throw new Error(
|
|
1450
|
+
`ops_import: \u76EE\u5F55\u5185\u672A\u53D1\u73B0\u53EF\u8BC6\u522B\u7684\u91C7\u96C6\u4EA7\u7269\uFF08OpBasicInfo/PipeUtilization/op_summary/Summary.jsonl \u7B49\uFF09\uFF1A${dir}`
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
const dirName = dir.split("/").filter((s) => s !== "").at(-1) ?? "";
|
|
1454
|
+
const hasDumpBin = await fileExists2(`${dir}/dump/visualize_data.bin`);
|
|
1455
|
+
const hasMindstudio = await dirExists(`${dir}/mindstudio_profiler_output`);
|
|
1456
|
+
const summaryCategory = shape.summaryJsonl !== null || shape.sections.has("PipeUtilization");
|
|
1457
|
+
let source;
|
|
1458
|
+
if (dirName.startsWith("OPPROF_") || hasDumpBin && shape.opBasicInfoPath !== null) source = "msopprof-dir";
|
|
1459
|
+
else if (dirName.startsWith("PROF_") || hasMindstudio) source = "msprof-dir";
|
|
1460
|
+
else if (summaryCategory && shape.summaryJsonl !== null) source = "npu-compute-dir";
|
|
1461
|
+
else if (shape.sections.size >= 4 && shape.pipeTrace !== null) source = "simulator-dir";
|
|
1462
|
+
else if (shape.sections.has("PipeUtilization")) source = "npu-compute-dir";
|
|
1463
|
+
else if (shape.opSummaryPath !== null) source = "msprof-dir";
|
|
1464
|
+
else throw new Error(`ops_import: \u65E0\u6CD5\u5224\u5B9A\u4EA7\u7269\u6765\u6E90\u5F62\u6001\uFF1A${dir}`);
|
|
1465
|
+
let soc = null;
|
|
1466
|
+
if (source === "npu-compute-dir" || source === "simulator-dir") soc = "a5";
|
|
1467
|
+
else if (shape.opBasicInfoPath !== null || source === "msopprof-dir") soc = "a2";
|
|
1468
|
+
const { reportId, runId } = newIds();
|
|
1469
|
+
const { resolve: resolvePath } = await import("node:path");
|
|
1470
|
+
const archiveRoot = resolvePath(process.cwd(), config.workbenchDir === "" ? ".ops-tune-workbench" : config.workbenchDir, "reports");
|
|
1471
|
+
const report = await buildReportModel({
|
|
1472
|
+
dir,
|
|
1473
|
+
reportId,
|
|
1474
|
+
runId,
|
|
1475
|
+
source,
|
|
1476
|
+
kind: "import",
|
|
1477
|
+
mode: source === "simulator-dir" ? "simulate" : "onboard",
|
|
1478
|
+
soc,
|
|
1479
|
+
label: input.label,
|
|
1480
|
+
kernelName: input.kernel_name?.trim() !== void 0 && input.kernel_name?.trim() !== "" ? input.kernel_name.trim() : null,
|
|
1481
|
+
archiveRoot
|
|
1482
|
+
});
|
|
1483
|
+
const children = await buildResultChildren(report.artifact_dir);
|
|
1484
|
+
const result = {
|
|
1485
|
+
report_id: report.report_id,
|
|
1486
|
+
title: dirName !== "" ? dirName : report.report_id,
|
|
1487
|
+
timestamp: report.collected_at,
|
|
1488
|
+
marked: false,
|
|
1489
|
+
source: report.source,
|
|
1490
|
+
kind: report.kind,
|
|
1491
|
+
dir_path: report.artifact_dir,
|
|
1492
|
+
children
|
|
1493
|
+
};
|
|
1494
|
+
ctx.logger.info(`[ops-tune-workbench] ops_import ${dir} \u2192 ${source} report ${report.reportId}`);
|
|
1495
|
+
let producerVersion;
|
|
1496
|
+
const diagnostics = ["Original collection log unavailable; event coverage is unknown."];
|
|
1497
|
+
if (source === "msopprof-dir") {
|
|
1498
|
+
try {
|
|
1499
|
+
producerVersion = await readProducerMetadata(dir);
|
|
1500
|
+
} catch {
|
|
1501
|
+
diagnostics.push("Profiler provenance did not match the imported files; producer version remains unverified.");
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return {
|
|
1505
|
+
report,
|
|
1506
|
+
result,
|
|
1507
|
+
files_found: filesFound,
|
|
1508
|
+
producer_version: producerVersion,
|
|
1509
|
+
profiling_diagnostics: diagnostics,
|
|
1510
|
+
metrics_preview: {
|
|
1511
|
+
task_duration_us: report.summary.task_duration_us,
|
|
1512
|
+
block_dim: report.summary.block_dim,
|
|
1513
|
+
op_name: report.summary.op_name,
|
|
1514
|
+
pipe_ratios: Object.fromEntries(
|
|
1515
|
+
report.pipe_occupancy.map((p) => [p.side === "cube" ? `aic_${p.id}_ratio` : `aiv_${p.id}_ratio`, p.ratio])
|
|
1516
|
+
)
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
async function dirExists(path) {
|
|
1521
|
+
try {
|
|
1522
|
+
const { stat: stat4 } = await import("node:fs/promises");
|
|
1523
|
+
const info = await stat4(path);
|
|
1524
|
+
return info.isDirectory();
|
|
1525
|
+
} catch {
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
async function fileExists2(path) {
|
|
1530
|
+
try {
|
|
1531
|
+
const { stat: stat4 } = await import("node:fs/promises");
|
|
1532
|
+
const info = await stat4(path);
|
|
1533
|
+
return info.isFile();
|
|
1534
|
+
} catch {
|
|
1535
|
+
return false;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
function importedToolValue(outcome, directory) {
|
|
1539
|
+
const report = outcome.report;
|
|
1540
|
+
return {
|
|
1541
|
+
run_id: report.run_id,
|
|
1542
|
+
report_id: report.report_id,
|
|
1543
|
+
source: report.source,
|
|
1544
|
+
files_found: outcome.files_found,
|
|
1545
|
+
raw_artifact_dir: directory,
|
|
1546
|
+
artifact_dir: report.artifact_dir,
|
|
1547
|
+
...outcome.producer_version !== void 0 ? { producer_version: outcome.producer_version } : {},
|
|
1548
|
+
...outcome.profiling_diagnostics !== void 0 ? { profiling_diagnostics: outcome.profiling_diagnostics } : {},
|
|
1549
|
+
summary: report.summary,
|
|
1550
|
+
metrics_preview: outcome.metrics_preview,
|
|
1551
|
+
verdict_hint: "\u672A\u5224\u5B9A\uFF08\u9700 verdict_judge\uFF09"
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// src/host/verdict.ts
|
|
1556
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1557
|
+
import { join as join3, relative as relative2, resolve as resolve2 } from "node:path";
|
|
1558
|
+
function defaultRulePacks(soc, scalar, rulesDir) {
|
|
1559
|
+
const packs = ["L2-inter-core.yaml", "L1-single-core.yaml", "L0-instruction.yaml"];
|
|
1560
|
+
const isA5 = soc === "a5";
|
|
1561
|
+
const hasMc2Scalar = scalar !== void 0 && scalar !== "" && /t_comm_us|t_compute_us|mte2_ratio_full/.test(scalar);
|
|
1562
|
+
if (isA5 && hasMc2Scalar) packs.unshift("L3-inter-card.yaml");
|
|
1563
|
+
void rulesDir;
|
|
1564
|
+
return packs;
|
|
1565
|
+
}
|
|
1566
|
+
function resolveRulesDir(skillDir) {
|
|
1567
|
+
return resolve2(skillDir, "ops-verdict/rules");
|
|
1568
|
+
}
|
|
1569
|
+
async function resolveVerdictScript(config, skillDir) {
|
|
1570
|
+
if (config.verdictScript !== "") {
|
|
1571
|
+
try {
|
|
1572
|
+
const { access } = await import("node:fs/promises");
|
|
1573
|
+
await access(config.verdictScript);
|
|
1574
|
+
return config.verdictScript;
|
|
1575
|
+
} catch {
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
const candidate = resolve2(skillDir, "ops-verdict/scripts/verdict.py");
|
|
1580
|
+
try {
|
|
1581
|
+
const { access } = await import("node:fs/promises");
|
|
1582
|
+
await access(candidate);
|
|
1583
|
+
return candidate;
|
|
1584
|
+
} catch {
|
|
1585
|
+
return null;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
async function pythonAvailable(ctx, pythonBin) {
|
|
1589
|
+
try {
|
|
1590
|
+
const out = await spawn(ctx, { argv: [pythonBin, "--version"], cwd: process.cwd(), signal: new AbortController().signal });
|
|
1591
|
+
return out.exitCode === 0;
|
|
1592
|
+
} catch {
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
async function fileOk(path) {
|
|
1597
|
+
try {
|
|
1598
|
+
const { access } = await import("node:fs/promises");
|
|
1599
|
+
await access(path);
|
|
1600
|
+
return true;
|
|
1601
|
+
} catch {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
function latestReportId(doc) {
|
|
1606
|
+
const keys = Object.keys(doc.reports);
|
|
1607
|
+
return keys.length > 0 ? keys[keys.length - 1] : null;
|
|
1608
|
+
}
|
|
1609
|
+
function pickReport(doc, reportId) {
|
|
1610
|
+
const id = reportId !== void 0 && reportId !== "" ? reportId : latestReportId(doc);
|
|
1611
|
+
if (id === null) {
|
|
1612
|
+
throw new Error("verdict_judge: \u5DE5\u4F5C\u53F0\u5185\u8FD8\u6CA1\u6709\u62A5\u544A\u2014\u2014\u8BF7\u5148\u8FD0\u884C ops_collect \u6216 ops_import\u3002");
|
|
1613
|
+
}
|
|
1614
|
+
const report = doc.reports[id];
|
|
1615
|
+
if (report === void 0) {
|
|
1616
|
+
throw new Error(`verdict_judge: \u62A5\u544A\u4E0D\u5B58\u5728\uFF1A${id}\uFF08state.reports \u4E2D\u7684 id \u624D\u6709\u6548\uFF09`);
|
|
1617
|
+
}
|
|
1618
|
+
return report;
|
|
1619
|
+
}
|
|
1620
|
+
async function runVerdict(ctx, config, skillDir, doc, input, workbenchRoot, signal) {
|
|
1621
|
+
const report = pickReport(doc, input.report_id);
|
|
1622
|
+
const verdictScript = await resolveVerdictScript(config, skillDir);
|
|
1623
|
+
if (verdictScript === null) {
|
|
1624
|
+
throw new Error(
|
|
1625
|
+
"verdict_judge: verdict.py \u4E0D\u53EF\u7528\uFF08verdictScript \u672A\u914D\u7F6E\u4E14\u5185\u7F6E skills/tool-ops-tune/ops-verdict/scripts/verdict.py \u4E0D\u5B58\u5728\uFF09\u3002\u8BF7\u5728 cordis.patch.yml \u914D\u7F6E verdictScript\uFF0C\u6216\u786E\u8BA4\u63D2\u4EF6\u5305\u5185 skills/ \u76EE\u5F55\u5B8C\u6574\u3002"
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
const pythonBin = config.pythonBin === "" ? "python3" : config.pythonBin;
|
|
1629
|
+
if (!await pythonAvailable(ctx, pythonBin)) {
|
|
1630
|
+
throw new Error(
|
|
1631
|
+
`verdict_judge: python \u89E3\u91CA\u5668\u4E0D\u53EF\u7528\uFF08pythonBin="${pythonBin}"\uFF09\u3002verdict.py \u9700\u8981 Python >= 3.8 + PyYAML\uFF1B\u8BF7\u5728 cordis.patch.yml \u914D\u7F6E pythonBin \u6307\u5411\u53EF\u7528\u89E3\u91CA\u5668\u3002`
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
let csvInput = join3(report.artifact_dir, "PipeUtilization.csv");
|
|
1635
|
+
if (!await fileOk(csvInput)) {
|
|
1636
|
+
const { readdir: readdir3 } = await import("node:fs/promises");
|
|
1637
|
+
let archived = [];
|
|
1638
|
+
try {
|
|
1639
|
+
archived = await readdir3(report.artifact_dir);
|
|
1640
|
+
} catch {
|
|
1641
|
+
archived = [];
|
|
1642
|
+
}
|
|
1643
|
+
const opSummary = archived.find((f) => /^op_summary_.*\.csv$/.test(f));
|
|
1644
|
+
if (opSummary !== void 0) {
|
|
1645
|
+
csvInput = join3(report.artifact_dir, opSummary);
|
|
1646
|
+
} else {
|
|
1647
|
+
throw new Error(
|
|
1648
|
+
`verdict_judge: \u62A5\u544A ${report.report_id} \u7F3A\u5C11\u5F52\u6863 PipeUtilization.csv\uFF08verdict.py \u7684 CSV \u8F93\u5165\uFF09\u3002\u8BF7\u5BF9\u542B PipeUtilization \u4EA7\u7269\u7684\u62A5\u544A\u8FD0\u884C\u5224\u5B9A\uFF0C\u6216\u91CD\u65B0\u91C7\u96C6/\u5BFC\u5165\u3002`
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
const soc = input.soc ?? report.soc ?? void 0;
|
|
1653
|
+
if (soc === void 0) {
|
|
1654
|
+
throw new Error("verdict_judge: \u65E0\u6CD5\u786E\u5B9A soc\uFF08\u62A5\u544A\u6A21\u578B\u65E0 soc \u4E14\u672A\u4F20\u53C2\uFF09\u3002\u8BF7\u663E\u5F0F\u4F20 soc=a2|a3|a5\u3002");
|
|
1655
|
+
}
|
|
1656
|
+
const kernelName = input.kernel_name !== void 0 && input.kernel_name !== "" ? input.kernel_name : report.kernel_name ?? void 0;
|
|
1657
|
+
const rulesDir = resolveRulesDir(skillDir);
|
|
1658
|
+
const packs = input.rules !== void 0 && input.rules !== "" ? input.rules.split(",").map((s) => s.trim()).filter((s) => s !== "") : defaultRulePacks(soc, input.scalar, rulesDir);
|
|
1659
|
+
const rulesArg = packs.map((p) => p.includes("/") ? p : join3(rulesDir, p)).join(",");
|
|
1660
|
+
const outPath = join3(report.artifact_dir, "verdict.json");
|
|
1661
|
+
const argv = [
|
|
1662
|
+
pythonBin,
|
|
1663
|
+
verdictScript,
|
|
1664
|
+
"--rules",
|
|
1665
|
+
rulesArg,
|
|
1666
|
+
"--csv",
|
|
1667
|
+
csvInput,
|
|
1668
|
+
"--soc",
|
|
1669
|
+
soc,
|
|
1670
|
+
"-o",
|
|
1671
|
+
outPath
|
|
1672
|
+
];
|
|
1673
|
+
if (kernelName !== void 0) argv.push("--kernel-name", kernelName);
|
|
1674
|
+
if (input.scalar !== void 0 && input.scalar !== "") argv.push("--scalar", input.scalar);
|
|
1675
|
+
const out = await spawn(ctx, { argv, cwd: process.cwd(), signal });
|
|
1676
|
+
if (out.exitCode === 4) {
|
|
1677
|
+
throw new Error(
|
|
1678
|
+
`verdict_judge: verdict.py G15 \u95E8\u7981\uFF08exit 4\uFF0C\u591A kernel \u6DF7\u91C7\u672A\u6307\u5B9A --kernel-name \u6216\u540D\u79F0\u4E0D\u5728\u96C6\u5408\u4E2D\uFF09\uFF1A
|
|
1679
|
+
${out.stderr.trim()}
|
|
1680
|
+
\u53EF\u7528 kernel \u6E05\u5355\u89C1\u4E0A\u65B9\u62A5\u9519\uFF1B\u8BF7\u7ECF ops_import \u7684 kernel_name \u53C2\u6570\u6216 verdict_judge \u7684 kernel_name \u53C2\u6570\u6307\u5B9A\u540E\u91CD\u5224\u3002`
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (out.exitCode !== 0) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
`verdict_judge: verdict.py \u6267\u884C\u5931\u8D25\uFF08exit ${String(out.exitCode)}\uFF09\uFF1A
|
|
1686
|
+
${out.stderr.trim().slice(0, 4e3)}`
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
let raw;
|
|
1690
|
+
try {
|
|
1691
|
+
raw = JSON.parse(await readFile3(outPath, "utf8"));
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
throw new Error(`verdict_judge: verdict.json \u89E3\u6790\u5931\u8D25\uFF08${String(error)}\uFF09\uFF1A${outPath}`);
|
|
1694
|
+
}
|
|
1695
|
+
const relPath = relPathOfVerdict(workbenchRoot, outPath);
|
|
1696
|
+
const verdict = summarizeVerdict(raw, verdictScript, relPath);
|
|
1697
|
+
return { verdict, fullVerdict: raw, reportId: report.report_id, verdictPath: outPath };
|
|
1698
|
+
}
|
|
1699
|
+
function relPathOfVerdict(workbenchRoot, outPath) {
|
|
1700
|
+
const rel = relative2(join3(workbenchRoot, ".."), outPath);
|
|
1701
|
+
return rel.split("\\").join("/");
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/host/baseline.ts
|
|
1705
|
+
import { join as join4, resolve as resolve3 } from "node:path";
|
|
1706
|
+
var REQUIRED_CALIBER_KEYS = ["precision", "shape", "dtype", "warmup", "repeat", "timing_method", "baseline_kind"];
|
|
1707
|
+
function resolveSnapshotScript(skillDir) {
|
|
1708
|
+
return resolve3(skillDir, "ops-verdict/scripts/snapshot.py");
|
|
1709
|
+
}
|
|
1710
|
+
function resolveDeltaCompareScript(skillDir) {
|
|
1711
|
+
return resolve3(skillDir, "ops-verdict/scripts/delta_compare.py");
|
|
1712
|
+
}
|
|
1713
|
+
function pickReportForBaseline(doc, id, what) {
|
|
1714
|
+
const target = id !== void 0 && id !== "" ? id : latestReportId(doc);
|
|
1715
|
+
if (target === null) {
|
|
1716
|
+
throw new Error(`${what}: \u5DE5\u4F5C\u53F0\u5185\u8FD8\u6CA1\u6709\u62A5\u544A\u2014\u2014\u8BF7\u5148\u8FD0\u884C ops_collect \u6216 ops_import\u3002`);
|
|
1717
|
+
}
|
|
1718
|
+
const report = doc.reports[target];
|
|
1719
|
+
if (report === void 0) {
|
|
1720
|
+
throw new Error(`${what}: \u62A5\u544A\u4E0D\u5B58\u5728\uFF1A${target}`);
|
|
1721
|
+
}
|
|
1722
|
+
return report;
|
|
1723
|
+
}
|
|
1724
|
+
function validateCaliber(caliber) {
|
|
1725
|
+
if (caliber === void 0 || typeof caliber !== "object") {
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
"baseline_set: \u5BF9\u6BD4\u6A21\u5F0F\u9700\u8981 caliber \u53E3\u5F84\u53C2\u6570\u5BF9\u8C61\uFF08precision/shape/dtype/warmup/repeat/timing_method/baseline_kind \u5FC5\u586B\uFF09\uFF0C\u7531\u8C03\u7528\u65B9\u4ECE\u8C03\u4F18\u4E0A\u4E0B\u6587\u63D0\u4F9B\u2014\u2014Host \u4E0D\u7F16\u9020\u53E3\u5F84\u503C\u3002"
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
const missing = REQUIRED_CALIBER_KEYS.filter((k) => {
|
|
1731
|
+
const v = caliber[k];
|
|
1732
|
+
return v === void 0 || v === null || v === "";
|
|
1733
|
+
});
|
|
1734
|
+
if (missing.length > 0) {
|
|
1735
|
+
throw new Error(
|
|
1736
|
+
`baseline_set: caliber \u7F3A\u5931\u5FC5\u586B\u9879\uFF1A${missing.join(", ")}\uFF08\u53E3\u5F84\u53C2\u6570\u5FC5\u987B\u7531\u8C03\u7528\u65B9\u63D0\u4F9B\uFF0C\u7981\u6B62 Host \u7F16\u9020\uFF09\u3002\u5FC5\u586B\u5168\u96C6\uFF1Aprecision, shape, dtype, warmup, repeat, timing_method, baseline_kind\uFF1B\u53EF\u9009\uFF1Acann_version, device_id, format, tiling_key, block_dim, kernel_p50_us, kernel_p95_us\u3002`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
return caliber;
|
|
1740
|
+
}
|
|
1741
|
+
async function runSnapshot(ctx, config, snapshotScript, report, variant, caliber, outDir, signal) {
|
|
1742
|
+
const pythonBin = config.pythonBin === "" ? "python3" : config.pythonBin;
|
|
1743
|
+
const resultPath = join4(outDir, "result.json");
|
|
1744
|
+
const mode = report.mode === "simulate" ? "sim" : "board";
|
|
1745
|
+
const argv = [
|
|
1746
|
+
pythonBin,
|
|
1747
|
+
snapshotScript,
|
|
1748
|
+
"--op",
|
|
1749
|
+
report.summary.op_name ?? "unknown",
|
|
1750
|
+
"--variant",
|
|
1751
|
+
variant,
|
|
1752
|
+
"--mode",
|
|
1753
|
+
mode,
|
|
1754
|
+
"--soc",
|
|
1755
|
+
report.soc ?? "a5",
|
|
1756
|
+
"--precision",
|
|
1757
|
+
caliber.precision,
|
|
1758
|
+
"--timing-method",
|
|
1759
|
+
caliber.timing_method,
|
|
1760
|
+
"--baseline-kind",
|
|
1761
|
+
caliber.baseline_kind,
|
|
1762
|
+
"--shape",
|
|
1763
|
+
caliber.shape,
|
|
1764
|
+
"--dtype",
|
|
1765
|
+
caliber.dtype,
|
|
1766
|
+
"--warmup",
|
|
1767
|
+
String(caliber.warmup),
|
|
1768
|
+
"--repeat",
|
|
1769
|
+
String(caliber.repeat),
|
|
1770
|
+
"-o",
|
|
1771
|
+
resultPath
|
|
1772
|
+
];
|
|
1773
|
+
if (report.summary.task_duration_us !== null) {
|
|
1774
|
+
argv.push("--kernel-avg-us", String(report.summary.task_duration_us));
|
|
1775
|
+
}
|
|
1776
|
+
if (caliber.cann_version !== void 0) argv.push("--cann-version", caliber.cann_version);
|
|
1777
|
+
else if (report.summary.rated_freq !== null) argv.push("--cann-version", "unknown");
|
|
1778
|
+
if (caliber.device_id !== void 0) argv.push("--device-id", caliber.device_id);
|
|
1779
|
+
if (caliber.format !== void 0) argv.push("--format", caliber.format);
|
|
1780
|
+
if (caliber.tiling_key !== void 0) argv.push("--tiling-key", caliber.tiling_key);
|
|
1781
|
+
if (caliber.block_dim !== void 0) argv.push("--block-dim", String(caliber.block_dim));
|
|
1782
|
+
else if (report.summary.block_dim !== null) argv.push("--block-dim", String(report.summary.block_dim));
|
|
1783
|
+
if (caliber.kernel_p50_us !== void 0) argv.push("--kernel-p50-us", String(caliber.kernel_p50_us));
|
|
1784
|
+
if (caliber.kernel_p95_us !== void 0) argv.push("--kernel-p95-us", String(caliber.kernel_p95_us));
|
|
1785
|
+
argv.push("--no-env-probe");
|
|
1786
|
+
const out = await spawn(ctx, { argv, cwd: process.cwd(), signal });
|
|
1787
|
+
if (out.exitCode !== 0) {
|
|
1788
|
+
throw new Error(
|
|
1789
|
+
`baseline_set: snapshot.py \u6267\u884C\u5931\u8D25\uFF08exit ${String(out.exitCode)}\uFF0Cvariant=${variant}\uFF09\uFF1A
|
|
1790
|
+
${out.stderr.trim().slice(0, 4e3)}`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
return { resultPath };
|
|
1794
|
+
}
|
|
1795
|
+
async function runBaseline(ctx, config, skillDir, doc, input, workbenchRoot, signal) {
|
|
1796
|
+
const isCompare = input.compare_id !== void 0 && input.compare_id !== "";
|
|
1797
|
+
if (!isCompare) {
|
|
1798
|
+
const report = pickReportForBaseline(doc, input.report_id, "baseline_set");
|
|
1799
|
+
void report;
|
|
1800
|
+
return { baseline_id: input.report_id };
|
|
1801
|
+
}
|
|
1802
|
+
const caliber = validateCaliber(input.caliber);
|
|
1803
|
+
const baselineReport = pickReportForBaseline(doc, input.report_id, "baseline_set");
|
|
1804
|
+
const afterReport = pickReportForBaseline(doc, input.compare_id, "baseline_set");
|
|
1805
|
+
const snapshotScript = resolveSnapshotScript(skillDir);
|
|
1806
|
+
const deltaScript = resolveDeltaCompareScript(skillDir);
|
|
1807
|
+
const { access } = await import("node:fs/promises");
|
|
1808
|
+
for (const [label, script] of [["snapshot.py", snapshotScript], ["delta_compare.py", deltaScript]]) {
|
|
1809
|
+
try {
|
|
1810
|
+
await access(script);
|
|
1811
|
+
} catch {
|
|
1812
|
+
throw new Error(`baseline_set: ${label} \u4E0D\u53EF\u7528\uFF08${script}\uFF09\u2014\u2014\u786E\u8BA4\u63D2\u4EF6\u5305\u5185 skills/ \u76EE\u5F55\u5B8C\u6574\u3002`);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
const pythonBin = config.pythonBin === "" ? "python3" : config.pythonBin;
|
|
1816
|
+
try {
|
|
1817
|
+
const probe = await spawn(ctx, { argv: [pythonBin, "--version"], cwd: process.cwd(), signal });
|
|
1818
|
+
if (probe.exitCode !== 0) throw new Error("python --version failed");
|
|
1819
|
+
} catch {
|
|
1820
|
+
throw new Error(`baseline_set: python \u89E3\u91CA\u5668\u4E0D\u53EF\u7528\uFF08pythonBin="${pythonBin}"\uFF09\u3002`);
|
|
1821
|
+
}
|
|
1822
|
+
const roundSuffix = afterReport.report_id.slice(-2);
|
|
1823
|
+
const baseSnap = await runSnapshot(ctx, config, snapshotScript, baselineReport, "baseline", caliber, baselineReport.artifact_dir, signal);
|
|
1824
|
+
const afterSnap = await runSnapshot(ctx, config, snapshotScript, afterReport, `round-${roundSuffix}`, caliber, afterReport.artifact_dir, signal);
|
|
1825
|
+
const comparisonMdPath = join4(afterReport.artifact_dir, "comparison.md");
|
|
1826
|
+
const cmpOut = await spawn(ctx, {
|
|
1827
|
+
argv: [pythonBin, deltaScript, "--baseline", baseSnap.resultPath, "--after", afterSnap.resultPath, "--output", comparisonMdPath],
|
|
1828
|
+
cwd: process.cwd(),
|
|
1829
|
+
signal
|
|
1830
|
+
});
|
|
1831
|
+
const stdout = cmpOut.stdout;
|
|
1832
|
+
const stderr = cmpOut.stderr;
|
|
1833
|
+
const conclusionText = stripPrefix(extractLine(stdout, "\u7ED3\u8BBA:") ?? extractLine(stderr, "\u7ED3\u8BBA:"), "\u7ED3\u8BBA:") ?? (cmpOut.exitCode === 2 ? `delta_compare \u62D2\u7EDD\u6BD4\u8F83\uFF08exit 2\uFF09\uFF1A${firstMeaningfulLine(stderr)}` : "");
|
|
1834
|
+
const actionText = stripPrefix(extractLine(stdout, "\u5904\u7F6E:") ?? extractLine(stderr, "\u5904\u7F6E:"), "\u5904\u7F6E:") ?? "";
|
|
1835
|
+
let speedup = null;
|
|
1836
|
+
if (baselineReport.summary.task_duration_us !== null && afterReport.summary.task_duration_us !== null && afterReport.summary.task_duration_us > 0) {
|
|
1837
|
+
speedup = baselineReport.summary.task_duration_us / afterReport.summary.task_duration_us;
|
|
1838
|
+
}
|
|
1839
|
+
try {
|
|
1840
|
+
const { readFile: readFile6 } = await import("node:fs/promises");
|
|
1841
|
+
const md = await readFile6(comparisonMdPath, "utf8");
|
|
1842
|
+
const m = /\| speedup \| — \| *([0-9.]+) *\|/.exec(md);
|
|
1843
|
+
if (m !== null) speedup = Number(m[1]);
|
|
1844
|
+
} catch {
|
|
1845
|
+
}
|
|
1846
|
+
const comparison = {
|
|
1847
|
+
exit_code: cmpOut.exitCode ?? -1,
|
|
1848
|
+
conclusion_text: conclusionText,
|
|
1849
|
+
action_text: actionText,
|
|
1850
|
+
speedup,
|
|
1851
|
+
hard_keys_ok: cmpOut.exitCode === 0,
|
|
1852
|
+
comparison_md_path: comparisonMdPath,
|
|
1853
|
+
result_json_paths: [baseSnap.resultPath, afterSnap.resultPath]
|
|
1854
|
+
};
|
|
1855
|
+
return { baseline_id: baselineReport.report_id, comparison };
|
|
1856
|
+
}
|
|
1857
|
+
function extractLine(text, prefix) {
|
|
1858
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1859
|
+
const t = line.trim();
|
|
1860
|
+
if (t.startsWith(prefix)) return t;
|
|
1861
|
+
}
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
function stripPrefix(line, prefix) {
|
|
1865
|
+
if (line === null) return null;
|
|
1866
|
+
const rest = line.slice(prefix.length).trim();
|
|
1867
|
+
return rest;
|
|
1868
|
+
}
|
|
1869
|
+
function firstMeaningfulLine(stderr) {
|
|
1870
|
+
const lines = stderr.split(/\r?\n/).map((l) => l.trim()).filter((l) => l !== "");
|
|
1871
|
+
return lines[0] ?? "";
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/host/skill.ts
|
|
1875
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1876
|
+
import { dirname as dirname2, join as join5, resolve as resolve4 } from "node:path";
|
|
1877
|
+
import { fileURLToPath } from "node:url";
|
|
1878
|
+
var BUNDLED_SKILL_RANK = 600;
|
|
1879
|
+
var SKILL_SUBDOMAINS = ["ops-collect", "ops-verdict", "ops-mechanism", "ops-family", "ops-report"];
|
|
1880
|
+
var SKILL_TRIGGERS = ["\u7B97\u5B50\u8C03\u4F18", "\u74F6\u9888\u5224\u5B9A", "Bound \u5224\u5B9A", "\u6027\u80FD\u91C7\u96C6", "profiling \u7F16\u6392", "\u8C03\u4F18\u524D\u540E\u5BF9\u6BD4", "\u52A0\u901F\u6BD4\u7ED3\u8BBA"];
|
|
1881
|
+
function pluginRoot() {
|
|
1882
|
+
try {
|
|
1883
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
1884
|
+
if (here.endsWith("/lib") || here.endsWith("\\lib")) return dirname2(here);
|
|
1885
|
+
if (here.includes(`${"/"}src${"/"}host`)) return resolve4(here, "..", "..", "..");
|
|
1886
|
+
return dirname2(here);
|
|
1887
|
+
} catch {
|
|
1888
|
+
return process.cwd();
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
function bundledSkillDir(name2) {
|
|
1892
|
+
return resolve4(pluginRoot(), "skills", name2);
|
|
1893
|
+
}
|
|
1894
|
+
function parseFrontmatter(raw) {
|
|
1895
|
+
if (!raw.startsWith("---")) return null;
|
|
1896
|
+
const end = raw.indexOf("\n---", 3);
|
|
1897
|
+
if (end === -1) return null;
|
|
1898
|
+
const block = raw.slice(3, end);
|
|
1899
|
+
const out = {};
|
|
1900
|
+
for (const line of block.split(/\r?\n/)) {
|
|
1901
|
+
const m = /^([A-Za-z_-]+):\s*(.*)$/.exec(line);
|
|
1902
|
+
if (m === null) continue;
|
|
1903
|
+
const key = m[1];
|
|
1904
|
+
let value = m[2].trim();
|
|
1905
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) value = value.slice(1, -1);
|
|
1906
|
+
if (key === "name" || key === "description" || key === "whenToUse") out[key] = value;
|
|
1907
|
+
}
|
|
1908
|
+
return out;
|
|
1909
|
+
}
|
|
1910
|
+
async function readSkillMd(dir) {
|
|
1911
|
+
try {
|
|
1912
|
+
return await readFile4(join5(dir, "SKILL.md"), "utf8");
|
|
1913
|
+
} catch {
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
async function resolveTuningSkill(ctx, config) {
|
|
1918
|
+
const name2 = config.tuningSkill === "" ? "tool-ops-tune" : config.tuningSkill;
|
|
1919
|
+
const external = config.tuningSkillDir === "" ? null : resolve4(config.tuningSkillDir);
|
|
1920
|
+
const tryDir = async (dir) => {
|
|
1921
|
+
const raw = await readSkillMd(dir);
|
|
1922
|
+
if (raw === null) return { ok: false };
|
|
1923
|
+
const fm = parseFrontmatter(raw);
|
|
1924
|
+
if (fm === null) return { ok: false };
|
|
1925
|
+
return { ok: true, fm };
|
|
1926
|
+
};
|
|
1927
|
+
if (external !== null) {
|
|
1928
|
+
const ext = await tryDir(external);
|
|
1929
|
+
if (ext.ok && ext.fm?.name === name2) {
|
|
1930
|
+
return buildResolved(name2, external, ext.fm.description ?? "", false);
|
|
1931
|
+
}
|
|
1932
|
+
if (ext.ok) {
|
|
1933
|
+
ctx.logger.warn(`[ops-tune-workbench] tuningSkillDir=${external} \u7684 SKILL.md frontmatter.name="${ext.fm.name ?? ""}" \u4E0E tuningSkill="${name2}" \u4E0D\u7B26 \u2192 \u56DE\u9000\u5185\u7F6E\u76EE\u5F55`);
|
|
1934
|
+
} else {
|
|
1935
|
+
ctx.logger.warn(`[ops-tune-workbench] tuningSkillDir=${external} \u65E0 SKILL.md \u2192 \u56DE\u9000\u5185\u7F6E\u76EE\u5F55`);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
const bundled = bundledSkillDir(name2);
|
|
1939
|
+
const b = await tryDir(bundled);
|
|
1940
|
+
if (b.ok && b.fm?.name === name2) {
|
|
1941
|
+
return buildResolved(name2, bundled, b.fm.description ?? "", true);
|
|
1942
|
+
}
|
|
1943
|
+
if (b.ok) {
|
|
1944
|
+
ctx.logger.warn(`[ops-tune-workbench] \u5185\u7F6E ${bundled} \u7684 frontmatter.name="${b.fm?.name ?? ""}" \u4E0E tuningSkill="${name2}" \u4E0D\u7B26`);
|
|
1945
|
+
throw new Error(
|
|
1946
|
+
`ops-tune-workbench: \u5185\u7F6E\u6280\u80FD\u76EE\u5F55 ${bundled} \u7684 SKILL.md frontmatter.name \u4E3A "${b.fm?.name ?? ""}"\uFF0C\u4E0E\u914D\u7F6E tuningSkill="${name2}" \u4E0D\u7B26\u3002\u8BF7\u4FEE\u6B63\u914D\u7F6E\u6216 SKILL.md\u3002`
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
throw new Error(
|
|
1950
|
+
`ops-tune-workbench: \u627E\u4E0D\u5230\u53EF\u7528\u7684\u8C03\u4F18\u6280\u80FD\u76EE\u5F55\uFF08tuningSkill="${name2}"\uFF0CtuningSkillDir="${config.tuningSkillDir}"\uFF0C\u5185\u7F6E\u76EE\u5F55 ${bundled} \u65E0 SKILL.md\uFF09\u3002\u6280\u80FD\u88C5\u8F7D\u5931\u8D25\u4E0D\u9759\u9ED8\u2014\u2014\u8BF7\u68C0\u67E5\u63D2\u4EF6\u5B89\u88C5\u5B8C\u6574\u6027\u6216\u914D\u7F6E\u3002`
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
function buildResolved(name2, dir, description, bundled) {
|
|
1954
|
+
return {
|
|
1955
|
+
name: name2,
|
|
1956
|
+
dir,
|
|
1957
|
+
description,
|
|
1958
|
+
status: {
|
|
1959
|
+
skill_name: name2,
|
|
1960
|
+
skill_dir: dir,
|
|
1961
|
+
entry: "SKILL.md",
|
|
1962
|
+
subdomains: SKILL_SUBDOMAINS,
|
|
1963
|
+
agent_layer: join5(dir, "agent/AGENTS.md"),
|
|
1964
|
+
triggers: SKILL_TRIGGERS,
|
|
1965
|
+
config: { tuningSkill: name2, tuningSkillDir: dir === bundledSkillDir(name2) ? "" : dir },
|
|
1966
|
+
bundled
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1970
|
+
function registerSkillProvider(ctx, resolved) {
|
|
1971
|
+
const skills = ctx.skills;
|
|
1972
|
+
if (skills === void 0 || typeof skills.registerProvider !== "function") {
|
|
1973
|
+
throw new Error(
|
|
1974
|
+
"ops-tune-workbench: ctx.skills.registerProvider \u4E0D\u53EF\u7528\uFF08dsh \u672A\u88C5\u8F7D skill \u670D\u52A1\uFF0Cweb profile standard preset \u5E94\u542B dsh-skill\uFF09\u3002\u6280\u80FD\u88C5\u8F7D\u5931\u8D25\u4E0D\u9759\u9ED8\uFF08R-7\uFF09\u3002"
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
const provider = {
|
|
1978
|
+
name: "ops-tune-workbench",
|
|
1979
|
+
async list(_options) {
|
|
1980
|
+
return [{
|
|
1981
|
+
name: resolved.name,
|
|
1982
|
+
description: resolved.description,
|
|
1983
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
1984
|
+
source: "bundled",
|
|
1985
|
+
provider: "ops-tune-workbench",
|
|
1986
|
+
resourceBase: { kind: "directory", path: resolved.dir },
|
|
1987
|
+
rank: BUNDLED_SKILL_RANK,
|
|
1988
|
+
locator: { path: join5(resolved.dir, "SKILL.md"), directory: resolved.dir },
|
|
1989
|
+
path: join5(resolved.dir, "SKILL.md")
|
|
1990
|
+
}];
|
|
1991
|
+
},
|
|
1992
|
+
async get(candidate, _options) {
|
|
1993
|
+
const name2 = candidate.name ?? resolved.name;
|
|
1994
|
+
if (name2 !== resolved.name) return void 0;
|
|
1995
|
+
const raw = await readSkillMd(resolved.dir);
|
|
1996
|
+
if (raw === null) return void 0;
|
|
1997
|
+
const fm = parseFrontmatter(raw);
|
|
1998
|
+
const bodyStart = raw.startsWith("---") ? raw.indexOf("\n---", 3) + 4 : 0;
|
|
1999
|
+
const content = raw.slice(bodyStart).trim();
|
|
2000
|
+
return {
|
|
2001
|
+
name: fm?.name ?? resolved.name,
|
|
2002
|
+
description: fm?.description ?? resolved.description,
|
|
2003
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
2004
|
+
source: "bundled",
|
|
2005
|
+
provider: "ops-tune-workbench",
|
|
2006
|
+
resourceBase: { kind: "directory", path: resolved.dir },
|
|
2007
|
+
path: join5(resolved.dir, "SKILL.md"),
|
|
2008
|
+
content
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
const dispose = skills.registerProvider(() => provider);
|
|
2013
|
+
ctx.effect(() => dispose, "ops-tune-workbench: skill provider");
|
|
2014
|
+
ctx.logger.info(`[ops-tune-workbench] skill provider registered (${resolved.name} @ ${resolved.dir}${resolved.status.bundled ? " [bundled]" : " [external]"})`);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/host/index.ts
|
|
2018
|
+
import { resolve as resolve6 } from "node:path";
|
|
2019
|
+
|
|
2020
|
+
// src/host/state.ts
|
|
2021
|
+
import { mkdir as mkdir2, readFile as readFile5, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2022
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
2023
|
+
var STATE_LINE_BUDGET = 3e3;
|
|
2024
|
+
var STATE_LINE_HARD_CAP = 5e3;
|
|
2025
|
+
var EMPTY = {
|
|
2026
|
+
schema: "ops-tune-workbench.state/2",
|
|
2027
|
+
updated_at: "",
|
|
2028
|
+
config_echo: { tuningSkill: "", tuningSkillDir: "", skillResolvedDir: "" },
|
|
2029
|
+
baseline_id: void 0,
|
|
2030
|
+
results: [],
|
|
2031
|
+
reports: {},
|
|
2032
|
+
collection_config: void 0
|
|
2033
|
+
};
|
|
2034
|
+
async function stateDir(ctx, config, exec) {
|
|
2035
|
+
const base = config.workbenchDir === "" ? ".ops-tune-workbench" : config.workbenchDir;
|
|
2036
|
+
if (exec.agent === void 0) return resolve5(process.cwd(), base);
|
|
2037
|
+
const wd = ctx.get("workingDirectory");
|
|
2038
|
+
const root = wd === void 0 ? process.cwd() : await wd.ensure(exec.agent, exec.signal);
|
|
2039
|
+
return resolve5(root, base);
|
|
2040
|
+
}
|
|
2041
|
+
async function readState(dir) {
|
|
2042
|
+
try {
|
|
2043
|
+
const raw = await readFile5(join6(dir, "state.json"), "utf8");
|
|
2044
|
+
const parsed = JSON.parse(raw);
|
|
2045
|
+
if (parsed.schema !== "ops-tune-workbench.state/2") return cloneEmpty();
|
|
2046
|
+
return {
|
|
2047
|
+
...cloneEmpty(),
|
|
2048
|
+
...parsed,
|
|
2049
|
+
config_echo: { ...cloneEmpty().config_echo, ...parsed.config_echo ?? {} },
|
|
2050
|
+
results: parsed.results ?? [],
|
|
2051
|
+
reports: parsed.reports ?? {}
|
|
2052
|
+
};
|
|
2053
|
+
} catch {
|
|
2054
|
+
return cloneEmpty();
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
function cloneEmpty() {
|
|
2058
|
+
return {
|
|
2059
|
+
...EMPTY,
|
|
2060
|
+
config_echo: { ...EMPTY.config_echo },
|
|
2061
|
+
results: [],
|
|
2062
|
+
reports: {}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
function serialize(doc) {
|
|
2066
|
+
let text = JSON.stringify(doc, null, 2);
|
|
2067
|
+
let lines = text.split("\n").length;
|
|
2068
|
+
if (lines > STATE_LINE_BUDGET) {
|
|
2069
|
+
slimToBudget(doc);
|
|
2070
|
+
text = JSON.stringify(doc, null, 2);
|
|
2071
|
+
lines = text.split("\n").length;
|
|
2072
|
+
}
|
|
2073
|
+
if (lines > STATE_LINE_HARD_CAP) {
|
|
2074
|
+
pruneOldestReports(doc);
|
|
2075
|
+
text = JSON.stringify(doc, null, 2);
|
|
2076
|
+
lines = text.split("\n").length;
|
|
2077
|
+
}
|
|
2078
|
+
if (lines > STATE_LINE_HARD_CAP) {
|
|
2079
|
+
const sections = [
|
|
2080
|
+
["results", JSON.stringify(doc.results ?? [], null, 2).split("\n").length],
|
|
2081
|
+
["reports", JSON.stringify(doc.reports ?? {}, null, 2).split("\n").length]
|
|
2082
|
+
];
|
|
2083
|
+
sections.sort((a, b) => b[1] - a[1]);
|
|
2084
|
+
const top = sections[0];
|
|
2085
|
+
throw new Error(
|
|
2086
|
+
`ops-tune-workbench state.json exceeds the hard line cap: ${String(lines)} lines > ${String(STATE_LINE_HARD_CAP)} (largest section: ${top[0]} ~${String(top[1])} lines; budget ${String(STATE_LINE_BUDGET)} already auto-slimmed). Row data must stay in artifact_dir with only indexes in state (DESIGN \xA74.2/\xA74.7).`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
return text;
|
|
2090
|
+
}
|
|
2091
|
+
function pruneOldestReports(doc) {
|
|
2092
|
+
const ids = Object.keys(doc.reports).sort();
|
|
2093
|
+
const keep = /* @__PURE__ */ new Set();
|
|
2094
|
+
if (doc.baseline_id !== void 0 && doc.reports[doc.baseline_id] !== void 0) keep.add(doc.baseline_id);
|
|
2095
|
+
const newest = ids.at(-1);
|
|
2096
|
+
if (newest !== void 0) keep.add(newest);
|
|
2097
|
+
for (const id of ids) {
|
|
2098
|
+
if (countLines(doc) <= STATE_LINE_BUDGET || keep.has(id)) continue;
|
|
2099
|
+
delete doc.reports[id];
|
|
2100
|
+
doc.results = doc.results.filter((result) => result.report_id !== id);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
function slimToBudget(doc) {
|
|
2104
|
+
const ids = Object.keys(doc.reports).sort();
|
|
2105
|
+
for (const id of ids) {
|
|
2106
|
+
if (countLines(doc) <= STATE_LINE_BUDGET) return;
|
|
2107
|
+
const report = doc.reports[id];
|
|
2108
|
+
if (report === void 0) continue;
|
|
2109
|
+
if (report.slimmed === true) continue;
|
|
2110
|
+
slimOneReport(report);
|
|
2111
|
+
report.slimmed = true;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function slimOneReport(report) {
|
|
2115
|
+
for (const t of report.table_index ?? []) {
|
|
2116
|
+
if (t.block_ids !== void 0 && t.block_ids.length > 0) t.block_ids = [];
|
|
2117
|
+
}
|
|
2118
|
+
for (const e of report.memory_topology?.edges ?? []) {
|
|
2119
|
+
if (e.source_files !== void 0) e.source_files = [];
|
|
2120
|
+
}
|
|
2121
|
+
for (const c of report.summary_categories ?? []) {
|
|
2122
|
+
if (c.fields !== void 0 && c.fields.length > 4) c.fields = c.fields.slice(0, 4);
|
|
2123
|
+
}
|
|
2124
|
+
for (const p of report.pipe_occupancy ?? []) {
|
|
2125
|
+
if (p.columns !== void 0) p.columns = [];
|
|
2126
|
+
if (p.time_columns !== void 0) p.time_columns = [];
|
|
2127
|
+
}
|
|
2128
|
+
for (const s of report.hardware_details?.sections ?? []) {
|
|
2129
|
+
if (s.fields !== void 0 && s.fields.length > 8) s.fields = s.fields.slice(0, 8);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
function countLines(doc) {
|
|
2133
|
+
return JSON.stringify(doc, null, 2).split("\n").length;
|
|
2134
|
+
}
|
|
2135
|
+
async function writeState(dir, doc) {
|
|
2136
|
+
doc.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2137
|
+
const text = serialize(doc);
|
|
2138
|
+
await mkdir2(dir, { recursive: true });
|
|
2139
|
+
await writeFile3(join6(dir, "state.json"), text, "utf8");
|
|
2140
|
+
}
|
|
2141
|
+
async function updateState(dir, mutate) {
|
|
2142
|
+
const doc = await readState(dir);
|
|
2143
|
+
await mutate(doc);
|
|
2144
|
+
await writeState(dir, doc);
|
|
2145
|
+
return doc;
|
|
2146
|
+
}
|
|
2147
|
+
function upsertReport(doc, report) {
|
|
2148
|
+
const existing = doc.reports[report.report_id];
|
|
2149
|
+
if (existing === void 0) {
|
|
2150
|
+
doc.reports[report.report_id] = report;
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
const merged = { ...existing };
|
|
2154
|
+
for (const [key, value] of Object.entries(report)) {
|
|
2155
|
+
if (value === void 0) continue;
|
|
2156
|
+
if (value === null) continue;
|
|
2157
|
+
if (typeof value === "string" && value === "" && (existing[key] !== void 0 && existing[key] !== "" && existing[key] !== null)) continue;
|
|
2158
|
+
merged[key] = value;
|
|
2159
|
+
}
|
|
2160
|
+
doc.reports[report.report_id] = merged;
|
|
2161
|
+
}
|
|
2162
|
+
function upsertResult(doc, entry) {
|
|
2163
|
+
const idx = doc.results.findIndex((r) => r.report_id === entry.report_id);
|
|
2164
|
+
if (idx === -1) {
|
|
2165
|
+
doc.results.push(entry);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
const existing = doc.results[idx];
|
|
2169
|
+
doc.results[idx] = { ...entry, children: entry.children.length > 0 ? entry.children : existing.children };
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// src/host/index.ts
|
|
2173
|
+
var name = "ops-tune-workbench";
|
|
2174
|
+
var inject = ["tools", "systemPrompt", "subprocess", "skills"];
|
|
2175
|
+
function apply(ctx, config) {
|
|
2176
|
+
const log = (message) => {
|
|
2177
|
+
ctx.logger.info(`[ops-tune-workbench] ${message}`);
|
|
2178
|
+
};
|
|
2179
|
+
log("host plugin loaded (tool bridge ready: ops_collect / ops_import / verdict_judge / baseline_set / tuning_skill)");
|
|
2180
|
+
ctx.systemPrompt.section({
|
|
2181
|
+
name: "ops-tune-workbench:guide",
|
|
2182
|
+
order: 2950,
|
|
2183
|
+
text: [
|
|
2184
|
+
"You are operating inside the \u7B97\u5B50\u8C03\u4F18\u5DE5\u4F5C\u53F0 (operator tuning workbench).",
|
|
2185
|
+
"Tuning loop: collect/import metrics (ops_collect / ops_import) \u2192 obtain the rule-engine verdict (verdict_judge)",
|
|
2186
|
+
"\u2192 route mechanism cards from findings[].mechanism_refs \u2192 implement one mechanism \u2192 re-collect and compare",
|
|
2187
|
+
"with the same caliber (baseline_set caliber params + snapshot/delta_compare scripts).",
|
|
2188
|
+
"\u94C1\u5F8B\uFF1ABound \u5224\u5B9A\u53EA\u51FA\u81EA verdict_judge \u5DE5\u5177\u7684\u8F93\u51FA\uFF08ops-verdict \u89C4\u5219\u5F15\u64CE\uFF09\uFF1B\u4F60\u53EA\u89E3\u91CA\u5224\u5B9A\u4E66\u4E2D\u7684\u8BC1\u636E\u6570\u503C\uFF0C",
|
|
2189
|
+
"\u7EDD\u4E0D\u81EA\u9020 Bound \u7ED3\u8BBA\u3001\u9608\u503C\u6216\u52A0\u901F\u6BD4\u9884\u4F30\u3002\u8C03\u4F18\u524D\u540E\u5BF9\u6BD4\u7ED3\u8BBA\uFF08KEEP/REVERT/\u566A\u58F0\u5E26\uFF09\u540C\u6837\u53EA\u51FA\u81EA",
|
|
2190
|
+
"snapshot/delta_compare \u811A\u672C\u7684 stdout \u539F\u6587\u4E0E\u9000\u51FA\u7801\uFF0C\u4F60\u4E0D\u6539\u5199\u5176\u8BED\u4E49\u3002",
|
|
2191
|
+
"\u89E6\u53D1\u8BCD\uFF08\u7B97\u5B50\u8C03\u4F18 / \u74F6\u9888\u5224\u5B9A / \u6027\u80FD\u91C7\u96C6 / \u8C03\u4F18\u524D\u540E\u5BF9\u6BD4 / \u52A0\u901F\u6BD4\u7ED3\u8BBA\uFF09\u51FA\u73B0\u65F6\uFF0C\u4F18\u5148\u4F7F\u7528 tool-ops-tune \u6280\u80FD",
|
|
2192
|
+
"\uFF08tuning_skill \u5DE5\u5177\u53EF\u67E5\u8BE2\u88C5\u8F7D\u72B6\u6001\uFF09\u5E76\u6309\u5176\u8C03\u4F18\u4E3B\u5FAA\u73AF\u6267\u884C\uFF1B\u5019\u9009\u65B9\u6848\u4E0E KEEP/REVERT \u51B3\u7B56\u5FC5\u987B\u7B49\u5F85\u7528\u6237\u62CD\u677F\u3002",
|
|
2193
|
+
"\u5360\u6BD4\u6392\u5E8F\u7C7B\u805A\u5408\uFF08\u300C\u4E3B\u5BFC pipe\u300D\u300C\u6700\u5927\u5E26\u5BBD\u65B9\u5411\u300D\u7B49\uFF09\u5C5E\u4E8E\u5224\u5B9A\u4EA7\u7269\uFF1A\u91C7\u96C6\u8FD4\u56DE\u503C\u4E0D\u542B\u5B83\u4EEC\uFF0C",
|
|
2194
|
+
"\u4F60\u4E5F\u4E0D\u5F97\u4EE5\u4EFB\u4F55\u6392\u5E8F/\u6BD4\u8F83\u8868\u8FF0\u66FF\u4EE3 verdict \u5224\u5B9A\u4E66\u7ED9\u51FA\u74F6\u9888\u7ED3\u8BBA\u3002"
|
|
2195
|
+
].join("\n")
|
|
2196
|
+
});
|
|
2197
|
+
const skillPromise = resolveTuningSkill(ctx, config);
|
|
2198
|
+
void skillPromise.then(
|
|
2199
|
+
(resolved) => {
|
|
2200
|
+
registerSkillProvider(ctx, resolved);
|
|
2201
|
+
void updateEchoedConfig(config, resolved);
|
|
2202
|
+
},
|
|
2203
|
+
(error) => {
|
|
2204
|
+
ctx.logger.error(`[ops-tune-workbench] skill loading failed: ${String(error)}`);
|
|
2205
|
+
}
|
|
2206
|
+
);
|
|
2207
|
+
async function updateEchoedConfig(config2, resolved) {
|
|
2208
|
+
skillEcho = resolved.status;
|
|
2209
|
+
}
|
|
2210
|
+
let skillEcho = null;
|
|
2211
|
+
void skillEcho;
|
|
2212
|
+
let skillResolved = null;
|
|
2213
|
+
skillPromise.then((r) => {
|
|
2214
|
+
skillResolved = { name: r.name, dir: r.dir };
|
|
2215
|
+
}, () => {
|
|
2216
|
+
});
|
|
2217
|
+
const wbRoot = () => resolve6(process.cwd(), config.workbenchDir === "" ? ".ops-tune-workbench" : config.workbenchDir);
|
|
2218
|
+
async function withState(exec, mutate) {
|
|
2219
|
+
const dir = await stateDir(ctx, config, exec);
|
|
2220
|
+
const doc = await updateState(dir, async (d) => {
|
|
2221
|
+
if (skillResolved !== null) {
|
|
2222
|
+
d.config_echo = {
|
|
2223
|
+
tuningSkill: skillResolved.name,
|
|
2224
|
+
tuningSkillDir: config.tuningSkillDir,
|
|
2225
|
+
skillResolvedDir: skillResolved.dir
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
await mutate(d);
|
|
2229
|
+
});
|
|
2230
|
+
return doc;
|
|
2231
|
+
}
|
|
2232
|
+
async function skillDir() {
|
|
2233
|
+
const resolved = await skillPromise;
|
|
2234
|
+
return resolved.dir;
|
|
2235
|
+
}
|
|
2236
|
+
ctx.tools.register(defineTool({
|
|
2237
|
+
name: "ops_collect",
|
|
2238
|
+
description: "\u7F16\u6392\u4E00\u6B21\u7B97\u5B50\u6027\u80FD\u91C7\u96C6\uFF08\u4E0A\u677F onboard / \u4EFF\u771F simulate / \u65B0\u5DE5\u5177 npu-compute\uFF09\uFF0C\u4EA7\u51FA\u62A5\u544A\u6A21\u578B\u5E76\u6CE8\u518C\u8FDB\u5DE5\u4F5C\u53F0\u7ED3\u679C\u5217\u8868\u3002\u7B2C 0 \u6B65\u5148\u505A NPU \u786C\u4EF6\u63A2\u6D4B\uFF08/dev/davinci* + npu-smi\uFF09\uFF1A\u65E0 NPU\u3001\u771F\u5B9E\u4EFF\u771F\u5668\u6216\u91C7\u96C6\u5DE5\u5177\u65F6\u660E\u786E\u62A5\u9519\uFF0C\u4E0D\u751F\u6210\u6F14\u793A\u6027\u80FD\u6570\u636E\uFF1B\u6709 NPU \u4E14\u5DE5\u5177\u53EF\u7528\u65F6\u8D70\u771F\u5B9E\u91C7\u96C6\u5E76\u505A\u4EA7\u7269\u65AD\u8A00\uFF08OpBasicInfo+PipeUtilization / op_summary_*.csv / \u89E3\u5305\u542B PipeUtilization.csv|summary.db\uFF0CCSV \u22652 \u884C\uFF1B\u65AD\u8A00\u4E0D\u8FC7\u5373\u62A5\u9519\uFF0C\u7EDD\u4E0D\u628A\u7A7A\u4EA7\u7269\u8BB0\u4E3A\u6210\u529F\u91C7\u96C6\uFF09\u3002options \u53C2\u6570\uFF08\u91C7\u96C6\u914D\u7F6E\u9875\u300C\u4FDD\u5B58\u914D\u7F6E/\u751F\u6210\u91C7\u96C6\u6307\u4EE4\u300D\u4EA7\u51FA\u7684 JSON\uFF09\u4F1A\u4E0E state.collection_config \u5408\u5E76\u6210\u751F\u6548\u914D\u7F6E\uFF1A\u4E0A\u677F\u9009\u9879\u7EC4\u6620\u5C04 msprof --aic-metrics\uFF08Occupancy/Roofline/Source/MemoryDetail/PCSampling/KernelScale/Default\uFF09\uFF0Cnew_tool.metrics \u6620\u5C04 npu-compute --set/--section\uFF1B\u5E26 options \u7684\u8C03\u7528\u540C\u65F6\u628A\u5B83\u6301\u4E45\u5316\u4E3A\u65B0\u7684 collection_config\u3002\u8FD4\u56DE\u503C\u53EA\u542B\u539F\u59CB\u6807\u91CF\u6307\u6807\uFF08task_duration_us\u3001block_dim\u3001\u5404 pipe \u5360\u6BD4\u8868\uFF09\uFF0C\u4E0D\u542B\u4EFB\u4F55 Bound \u7ED3\u8BBA\u2014\u2014\u5224\u5B9A\u8BF7\u8C03 verdict_judge\u3002",
|
|
2239
|
+
parameters: {
|
|
2240
|
+
case_dir: { type: "string", required: true, description: "\u7B97\u5B50 case/\u5DE5\u7A0B\u76EE\u5F55\uFF08\u7EDD\u5BF9\u6216\u5DE5\u4F5C\u533A\u76F8\u5BF9\u8DEF\u5F84\uFF09\u3002" },
|
|
2241
|
+
mode: { type: "string", enum: ["onboard", "simulate", "npu-compute"], description: "\u91C7\u96C6\u6A21\u5F0F\uFF0C\u9ED8\u8BA4 onboard\uFF1Bsimulate \u9700\u8981\u771F\u5B9E npusim/cannsim\u3002" },
|
|
2242
|
+
soc: { type: "string", enum: ["a2", "a3", "a5"], description: "\u76EE\u6807 SoC \u4EE3\u9645\u3002" },
|
|
2243
|
+
label: { type: "string", description: '\u53EF\u9009\u6807\u7B7E\uFF0C\u5982 "add_custom v4 \u590D\u6D4B"\u3002' },
|
|
2244
|
+
options: { type: "object", additionalProperties: true, description: "\u91C7\u96C6\u914D\u7F6E\u9009\u9879\uFF1Boptions.aic_metrics \u53EF\u663E\u5F0F\u9009\u62E9\u771F\u5B9E\u9A8C\u8BC1\u8FC7\u7684 msopprof metrics\uFF08Default/Roofline/MemoryDetail/Source/PipeTimeline/InstrTimeline/Occupancy\uFF09\u3002" }
|
|
2245
|
+
},
|
|
2246
|
+
timeoutMs: config.collectTimeoutMs > 0 ? config.collectTimeoutMs : 6e5,
|
|
2247
|
+
output: {
|
|
2248
|
+
schema: {
|
|
2249
|
+
type: "object",
|
|
2250
|
+
additionalProperties: true,
|
|
2251
|
+
properties: {
|
|
2252
|
+
run_id: { type: "string" },
|
|
2253
|
+
report_id: { type: "string" },
|
|
2254
|
+
source: { type: "string" },
|
|
2255
|
+
mode: { type: "string" },
|
|
2256
|
+
soc: { type: "string" },
|
|
2257
|
+
artifact_dir: { type: "string" },
|
|
2258
|
+
fallback_reason: { type: "string" },
|
|
2259
|
+
summary: { type: "object", additionalProperties: true },
|
|
2260
|
+
metrics_preview: { type: "object", additionalProperties: true },
|
|
2261
|
+
verdict_hint: { type: "string" }
|
|
2262
|
+
}
|
|
2263
|
+
},
|
|
2264
|
+
render: (_args, value) => [{
|
|
2265
|
+
type: "text",
|
|
2266
|
+
text: `\u91C7\u96C6\u5B8C\u6210\uFF08${String(value.source ?? "")}\uFF09\xB7 report ${String(value.report_id ?? "")}
|
|
2267
|
+
\u6307\u6807: ${JSON.stringify(value.metrics_preview)}
|
|
2268
|
+
\u4EA7\u7269: ${String(value.artifact_dir ?? "")}
|
|
2269
|
+
Bound \u5224\u5B9A\u8BF7\u8C03 verdict_judge\uFF08verdict_hint \u4EC5\u4E3A\u56FA\u5B9A\u63D0\u793A\u6587\u6848\uFF0C\u4E0D\u662F\u5224\u5B9A\uFF09\u3002`
|
|
2270
|
+
}]
|
|
2271
|
+
},
|
|
2272
|
+
async execute(args, exec) {
|
|
2273
|
+
const dir = await stateDir(ctx, config, exec);
|
|
2274
|
+
const savedDoc = await readState(dir);
|
|
2275
|
+
bindStateDoc(savedDoc);
|
|
2276
|
+
const outcome = await collect(ctx, config, {
|
|
2277
|
+
case_dir: args.case_dir,
|
|
2278
|
+
mode: args.mode,
|
|
2279
|
+
options: typeof args.options === "object" && args.options !== null ? args.options : void 0,
|
|
2280
|
+
soc: args.soc,
|
|
2281
|
+
label: args.label
|
|
2282
|
+
}, exec);
|
|
2283
|
+
await withState(exec, (doc) => {
|
|
2284
|
+
upsertReport(doc, outcome.report);
|
|
2285
|
+
upsertResult(doc, outcome.result);
|
|
2286
|
+
applyCollectionConfigToState(
|
|
2287
|
+
doc,
|
|
2288
|
+
args.soc,
|
|
2289
|
+
typeof args.options === "object" && args.options !== null ? args.options : void 0
|
|
2290
|
+
);
|
|
2291
|
+
});
|
|
2292
|
+
const r = outcome.report;
|
|
2293
|
+
const value = {
|
|
2294
|
+
run_id: r.run_id,
|
|
2295
|
+
report_id: r.report_id,
|
|
2296
|
+
source: r.source,
|
|
2297
|
+
mode: r.mode,
|
|
2298
|
+
soc: r.soc,
|
|
2299
|
+
artifact_dir: r.artifact_dir,
|
|
2300
|
+
...outcome.raw_artifact_dir !== void 0 ? { raw_artifact_dir: outcome.raw_artifact_dir } : {},
|
|
2301
|
+
...outcome.producer_version !== void 0 ? { producer_version: outcome.producer_version } : {},
|
|
2302
|
+
...outcome.profiling_diagnostics !== void 0 ? { profiling_diagnostics: outcome.profiling_diagnostics } : {},
|
|
2303
|
+
...outcome.fallback_reason !== void 0 ? { fallback_reason: outcome.fallback_reason } : {},
|
|
2304
|
+
summary: r.summary,
|
|
2305
|
+
metrics_preview: outcome.metrics_preview,
|
|
2306
|
+
...outcome.effective_options !== void 0 ? { effective_options: outcome.effective_options } : {},
|
|
2307
|
+
files_found: outcome.files_found,
|
|
2308
|
+
verdict_hint: "\u672A\u5224\u5B9A\uFF08\u9700 verdict_judge\uFF09"
|
|
2309
|
+
};
|
|
2310
|
+
return value;
|
|
2311
|
+
}
|
|
2312
|
+
}));
|
|
2313
|
+
ctx.tools.register(defineTool({
|
|
2314
|
+
name: "ops_import",
|
|
2315
|
+
description: "\u5BFC\u5165\u4E00\u4E2A\u5DF2\u6709\u91C7\u96C6\u4EA7\u7269\u76EE\u5F55\uFF08msopprof OPPROF_* / msprof PROF_* / npu-compute \u89E3\u5305\u76EE\u5F55 / \u6A21\u62DF\u5668\u4EA7\u7269\uFF09\uFF0C\u6784\u5EFA\u62A5\u544A\u6A21\u578B\u8FDB\u5165\u5DE5\u4F5C\u53F0\uFF08\u5BF9\u6807\u300C\u6DFB\u52A0\u4E3A\u6027\u80FD\u8C03\u4F18\u53EF\u89C6\u5316\u89C6\u56FE\u300D\uFF09\u3002\u76EE\u5F55\u5F62\u6001\u81EA\u52A8\u5224\u6E90\uFF1Bmsprof PROF_ \u76EE\u5F55\uFF08op_summary \u542B Op Name \u5217\uFF09\u6309 Op Name \u53BB\u91CD\u679A\u4E3E\u591A\u7B97\u5B50\uFF0Ckernel_name \u672A\u6307\u5B9A\u65F6\u4E0D\u9884\u9009\uFF1B\u591A kernel CSV \u672A\u6307\u5B9A kernel_name \u65F6\u4E0D\u9884\u8FC7\u6EE4\uFF0C\u5BFC\u5165\u540E verdict_judge \u4F1A\u89E6\u53D1 G15 \u62A5\u9519\u5F15\u5BFC\u3002",
|
|
2316
|
+
parameters: {
|
|
2317
|
+
dir: { type: "string", required: true, description: "\u4EA7\u7269\u76EE\u5F55\uFF08\u7EDD\u5BF9\u6216\u5DE5\u4F5C\u533A\u76F8\u5BF9\u8DEF\u5F84\uFF09\u3002" },
|
|
2318
|
+
label: { type: "string", description: "\u53EF\u9009\u6807\u7B7E\u3002" },
|
|
2319
|
+
kernel_name: { type: "string", description: "\u591A kernel CSV \u8FC7\u6EE4\uFF08\u5199\u5165\u62A5\u544A\u6A21\u578B kernel_name \u5B57\u6BB5\uFF1Bverdict_judge \u7F3A\u7701\u627F\u63A5\uFF09\u3002" }
|
|
2320
|
+
},
|
|
2321
|
+
timeoutMs: 6e4,
|
|
2322
|
+
output: {
|
|
2323
|
+
schema: {
|
|
2324
|
+
type: "object",
|
|
2325
|
+
additionalProperties: true,
|
|
2326
|
+
properties: {
|
|
2327
|
+
run_id: { type: "string" },
|
|
2328
|
+
report_id: { type: "string" },
|
|
2329
|
+
source: { type: "string" },
|
|
2330
|
+
files_found: { type: "array", items: { type: "string" } },
|
|
2331
|
+
summary: { type: "object", additionalProperties: true },
|
|
2332
|
+
metrics_preview: { type: "object", additionalProperties: true },
|
|
2333
|
+
verdict_hint: { type: "string" }
|
|
2334
|
+
}
|
|
2335
|
+
},
|
|
2336
|
+
render: (_args, value) => [{
|
|
2337
|
+
type: "text",
|
|
2338
|
+
text: `\u5BFC\u5165\u5B8C\u6210\uFF08${String(value.source ?? "")}\uFF09\xB7 report ${String(value.report_id ?? "")}
|
|
2339
|
+
\u53D1\u73B0\u4EA7\u7269: ${value.files_found.join(", ")}
|
|
2340
|
+
Bound \u5224\u5B9A\u8BF7\u8C03 verdict_judge\u3002`
|
|
2341
|
+
}]
|
|
2342
|
+
},
|
|
2343
|
+
async execute(args, exec) {
|
|
2344
|
+
const outcome = await importDirectory(ctx, config, {
|
|
2345
|
+
dir: args.dir,
|
|
2346
|
+
label: args.label,
|
|
2347
|
+
kernel_name: args.kernel_name
|
|
2348
|
+
});
|
|
2349
|
+
await withState(exec, (doc) => {
|
|
2350
|
+
upsertReport(doc, outcome.report);
|
|
2351
|
+
upsertResult(doc, outcome.result);
|
|
2352
|
+
});
|
|
2353
|
+
return importedToolValue(outcome, args.dir);
|
|
2354
|
+
}
|
|
2355
|
+
}));
|
|
2356
|
+
ctx.tools.register(defineTool({
|
|
2357
|
+
name: "verdict_judge",
|
|
2358
|
+
description: "\u5BF9\u6700\u65B0\uFF08\u6216\u6307\u5B9A\uFF09\u62A5\u544A\u8DD1 ops-verdict \u89C4\u5219\u5F15\u64CE\uFF08\u5206\u5C42\u7011\u5E03\uFF09\uFF0C\u8FD4\u56DE\u7ED3\u6784\u5316\u5224\u5B9A\u4E66\u3002\u8FD9\u662F Bound \u5224\u5B9A\u7684\u552F\u4E00\u6765\u6E90\uFF1B\u4F60\u53EA\u89E3\u91CA\u5224\u5B9A\u4E66\u4E2D\u7684\u8BC1\u636E\u6570\u503C\u3002\u89C4\u5219\u5305\u9ED8\u8BA4\u6309\u8F93\u5165\u8DEF\u7531\u8BC4\u4F30\u53EF\u8BC4\u4F30\u5C42\uFF1ACSV \u8F93\u5165 \u2192 L2,L1,L0 \u4E09\u5305\uFF1Bsoc=a5 \u4E14 scalar \u542B t_comm_us/t_compute_us \u7B49 MC\xB2 \u9694\u79BB\u6807\u91CF\u65F6\u8FFD\u52A0 L3\uFF1Bsoc/\u6307\u6807\u7F3A\u5931\u7684\u5C42\u7531\u5F15\u64CE\u4EE5 SKIPPED \u663E\u5F0F\u5448\u73B0\uFF08\u4E0D\u865A\u79F0\u56DB\u5C42\u5168\u8BC4\u4F30\uFF09\u3002\u591A kernel CSV \u672A\u6307\u5B9A kernel_name \u65F6 verdict.py G15 \u95E8\u7981 exit 4\uFF0C\u672C\u5DE5\u5177\u539F\u6837\u900F\u4F20\u62A5\u9519\u4E0E\u53EF\u7528 kernel \u6E05\u5355\uFF0C\u4E0D\u9759\u9ED8\u8DE8 kernel \u5E73\u5747\u3002verdictScript/python \u7F3A\u5931\u6216\u6267\u884C\u5931\u8D25\u65F6\u672C\u5DE5\u5177\u76F4\u63A5\u62A5\u9519\uFF08\u5224\u5B9A\u4E0D\u53EF\u6A21\u62DF\u3001\u4E0D\u964D\u7EA7\uFF09\u3002",
|
|
2359
|
+
parameters: {
|
|
2360
|
+
report_id: { type: "string", description: "\u62A5\u544A id\uFF1B\u9ED8\u8BA4\u6700\u65B0\u4E00\u6761\u3002" },
|
|
2361
|
+
soc: { type: "string", enum: ["a2", "a3", "a5"], description: "SoC \u4EE3\u9645\uFF1B\u9ED8\u8BA4\u6309\u62A5\u544A\u6A21\u578B\u63A8\u5BFC\u3002" },
|
|
2362
|
+
rules: { type: "string", description: "\u89C4\u5219\u5305\uFF08\u9017\u53F7\u5206\u9694\uFF0C\u9ED8\u8BA4\u6309\u8F93\u5165\u8DEF\u7531\uFF1AL2-inter-core.yaml,L1-single-core.yaml,L0-instruction.yaml\uFF09\u3002" },
|
|
2363
|
+
scalar: { type: "string", description: "\u6807\u91CF\u6307\u6807 k=v,k=v \u900F\u4F20\uFF08\u5982 mte2_active_bw_pct_of_peak=82 \u6216 MC\xB2 \u7684 t_comm_us=420,t_compute_us=880\uFF09\u3002" },
|
|
2364
|
+
kernel_name: { type: "string", description: "\u76EE\u6807 kernel\uFF1B\u7F3A\u7701\u53D6\u62A5\u544A\u6A21\u578B kernel_name \u5B57\u6BB5\uFF08ops_import \u53EF\u5199\u5165\uFF09\u3002" }
|
|
2365
|
+
},
|
|
2366
|
+
timeoutMs: 12e4,
|
|
2367
|
+
output: {
|
|
2368
|
+
schema: {
|
|
2369
|
+
type: "object",
|
|
2370
|
+
additionalProperties: true,
|
|
2371
|
+
properties: {
|
|
2372
|
+
schema: { type: "string" },
|
|
2373
|
+
verdict: { type: "string" },
|
|
2374
|
+
headline: { type: "string" },
|
|
2375
|
+
findings: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
2376
|
+
skipped: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
2377
|
+
recommended_next: { type: "array", items: { type: "string" } }
|
|
2378
|
+
}
|
|
2379
|
+
},
|
|
2380
|
+
render: (_args, value) => {
|
|
2381
|
+
const v = value;
|
|
2382
|
+
const lines = [`\u5224\u5B9A\u4E66 \xB7 ${String(v.verdict ?? "")} \u2014 ${String(v.headline ?? "")}`];
|
|
2383
|
+
for (const f of v.findings ?? []) {
|
|
2384
|
+
lines.push(` HIT [${String(f.severity)}/${String(f.confidence)}] ${String(f.rule_id)}: ${String(f.name)} (est ${String(f.speedup_range)}) evidence: ${JSON.stringify(f.evidence)}`);
|
|
2385
|
+
}
|
|
2386
|
+
return [{ type: "text", text: lines.join("\n") }];
|
|
2387
|
+
}
|
|
2388
|
+
},
|
|
2389
|
+
async execute(args, exec) {
|
|
2390
|
+
const dir = await skillDir();
|
|
2391
|
+
const doc = await withState(exec, (d) => d);
|
|
2392
|
+
const outcome = await runVerdict(ctx, config, dir, doc, {
|
|
2393
|
+
report_id: args.report_id,
|
|
2394
|
+
soc: args.soc,
|
|
2395
|
+
rules: args.rules,
|
|
2396
|
+
scalar: args.scalar,
|
|
2397
|
+
kernel_name: args.kernel_name
|
|
2398
|
+
}, wbRoot(), exec.signal);
|
|
2399
|
+
await withState(exec, (d) => {
|
|
2400
|
+
const report = d.reports[outcome.reportId];
|
|
2401
|
+
if (report !== void 0) report.verdict = outcome.verdict;
|
|
2402
|
+
});
|
|
2403
|
+
return { ...outcome.fullVerdict, report_id: outcome.reportId };
|
|
2404
|
+
}
|
|
2405
|
+
}));
|
|
2406
|
+
ctx.tools.register(defineTool({
|
|
2407
|
+
name: "baseline_set",
|
|
2408
|
+
description: "\u951A\u5B9A\u57FA\u7EBF\u62A5\u544A\uFF08report_id\uFF09\uFF0C\u6216\u5BF9\u6307\u5B9A\u62A5\u544A\u505A\u540C\u53E3\u5F84\u524D\u540E\u5BF9\u6BD4\uFF08compare_id + caliber\uFF09\u3002\u5BF9\u6BD4\u7ED3\u8BBA\u53EA\u51FA\u81EA snapshot.py/delta_compare.py\uFF08\u53E3\u5F84\u6307\u7EB9\u786C\u6821\u9A8C + \u7CBE\u5EA6\u95E8\u7981 + \u566A\u58F0\u5E26\u4E09\u6BB5\u7ED3\u8BBA\uFF09\uFF0C\u672C\u5DE5\u5177\u539F\u6837\u8FD4\u56DE\u811A\u672C\u6587\u672C\u4E0E\u9000\u51FA\u7801\u8BED\u4E49\uFF08exit 0=\u53EF\u6BD4 / 2=\u53E3\u5F84\u4E0D\u4E00\u81F4\u6216\u7CBE\u5EA6\u672A\u8FC7\u2014\u2014exit 2 \u662F\u811A\u672C\u7ED3\u8BBA\uFF0C\u4E0D\u662F\u5DE5\u5177\u5931\u8D25\uFF09\u3002caliber \u53E3\u5F84\u53C2\u6570\uFF08precision/shape/dtype/warmup/repeat/timing_method/baseline_kind \u5FC5\u586B\uFF09\u7531\u4F60\u4ECE\u8C03\u4F18\u4E0A\u4E0B\u6587\u63D0\u4F9B\uFF0C\u7F3A\u5931\u5373\u62A5\u9519\u5E76\u5217\u6E05\u5355\uFF1Bop/soc/mode/kernel_avg_us \u4ECE\u4E24\u4FA7\u62A5\u544A\u6A21\u578B\u81EA\u52A8\u53D6\u503C\uFF0CHost \u4E0D\u7F16\u9020\u53E3\u5F84\u3002",
|
|
2409
|
+
parameters: {
|
|
2410
|
+
report_id: { type: "string", description: "\u951A\u5B9A\uFF1A\u8981\u8BBE\u4E3A\u57FA\u7EBF\u7684\u62A5\u544A id\u3002\u5BF9\u6BD4\uFF1A\u57FA\u7EBF\u4FA7\u62A5\u544A id\uFF08\u9ED8\u8BA4 state.baseline_id\uFF09\u3002" },
|
|
2411
|
+
compare_id: { type: "string", description: "\u5BF9\u6BD4\u6A21\u5F0F\uFF1A\u5F85\u5BF9\u6BD4\uFF08\u4F18\u5316\u540E\uFF09\u7684\u62A5\u544A id\u3002\u4F20\u6B64\u53C2\u5373\u8FDB\u5165\u5BF9\u6BD4\u6A21\u5F0F\u3002" },
|
|
2412
|
+
caliber: {
|
|
2413
|
+
type: "object",
|
|
2414
|
+
additionalProperties: true,
|
|
2415
|
+
description: '\u53E3\u5F84\u53C2\u6570\u5BF9\u8C61\uFF08\u5BF9\u6BD4\u6A21\u5F0F\u5FC5\u586B\uFF09\uFF1Aprecision:"pass"|"fail"\u3001shape\u3001dtype\u3001warmup:int\u3001repeat:int\u3001timing_method:"event"|"sim"|"msprof"|"msprof_task_duration"|"profiler_summary"\u3001baseline_kind:"system"|"self_before_after"\uFF1B\u53EF\u9009 cann_version/device_id/format/tiling_key/block_dim/kernel_p50_us/kernel_p95_us\u3002'
|
|
2416
|
+
}
|
|
2417
|
+
},
|
|
2418
|
+
timeoutMs: 12e4,
|
|
2419
|
+
output: {
|
|
2420
|
+
schema: {
|
|
2421
|
+
type: "object",
|
|
2422
|
+
additionalProperties: true,
|
|
2423
|
+
properties: {
|
|
2424
|
+
baseline_id: { type: "string" },
|
|
2425
|
+
comparison: {
|
|
2426
|
+
type: "object",
|
|
2427
|
+
additionalProperties: true,
|
|
2428
|
+
properties: {
|
|
2429
|
+
exit_code: { type: "integer" },
|
|
2430
|
+
conclusion_text: { type: "string" },
|
|
2431
|
+
action_text: { type: "string" },
|
|
2432
|
+
speedup: { type: "number" },
|
|
2433
|
+
hard_keys_ok: { type: "boolean" },
|
|
2434
|
+
comparison_md_path: { type: "string" },
|
|
2435
|
+
result_json_paths: { type: "array", items: { type: "string" } }
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
render: (_args, value) => {
|
|
2441
|
+
const v = value;
|
|
2442
|
+
if (v.comparison === void 0) {
|
|
2443
|
+
return [{ type: "text", text: `\u57FA\u7EBF\u5DF2\u951A\u5B9A\uFF1A${v.baseline_id}` }];
|
|
2444
|
+
}
|
|
2445
|
+
const c = v.comparison;
|
|
2446
|
+
return [{
|
|
2447
|
+
type: "text",
|
|
2448
|
+
text: `\u524D\u540E\u5BF9\u6BD4 \xB7 exit ${String(c.exit_code)}\uFF08${c.hard_keys_ok ? "\u53EF\u6BD4" : "\u53E3\u5F84\u4E0D\u4E00\u81F4\u6216\u7CBE\u5EA6\u672A\u8FC7"}\uFF09
|
|
2449
|
+
\u7ED3\u8BBA: ${c.conclusion_text}
|
|
2450
|
+
\u5904\u7F6E: ${c.action_text}
|
|
2451
|
+
comparison.md: ${c.comparison_md_path}` + (c.speedup === null ? "" : `
|
|
2452
|
+
speedup: ${String(c.speedup)}x`)
|
|
2453
|
+
}];
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
async execute(args, exec) {
|
|
2457
|
+
const dir = await skillDir();
|
|
2458
|
+
const anchorId = args.report_id !== void 0 && args.report_id !== "" ? args.report_id : (await withState(exec, (d) => d)).baseline_id;
|
|
2459
|
+
const input = {
|
|
2460
|
+
report_id: args.compare_id === void 0 || args.compare_id === "" ? args.report_id : anchorId ?? args.report_id,
|
|
2461
|
+
compare_id: args.compare_id,
|
|
2462
|
+
caliber: args.caliber
|
|
2463
|
+
};
|
|
2464
|
+
const outcome = await runBaseline(ctx, config, dir, await withState(exec, (d) => d), input, wbRoot(), exec.signal);
|
|
2465
|
+
await withState(exec, (d) => {
|
|
2466
|
+
d.baseline_id = outcome.baseline_id;
|
|
2467
|
+
if (outcome.comparison !== void 0) {
|
|
2468
|
+
const report = d.reports[args.compare_id];
|
|
2469
|
+
if (report !== void 0) report.comparison = outcome.comparison;
|
|
2470
|
+
}
|
|
2471
|
+
});
|
|
2472
|
+
const value = {
|
|
2473
|
+
baseline_id: outcome.baseline_id,
|
|
2474
|
+
...outcome.comparison !== void 0 ? { comparison: outcome.comparison } : {}
|
|
2475
|
+
};
|
|
2476
|
+
return value;
|
|
2477
|
+
}
|
|
2478
|
+
}));
|
|
2479
|
+
ctx.tools.register(defineTool({
|
|
2480
|
+
name: "tuning_skill",
|
|
2481
|
+
description: "\u8FD4\u56DE\u5F53\u524D\u751F\u6548\u7684\u8C03\u4F18\u6280\u80FD\u88C5\u8F7D\u4FE1\u606F\uFF08\u540D\u79F0/\u8DEF\u5F84/SKILL.md \u5165\u53E3/\u4E94\u5B50\u57DF/Agent \u7F16\u6392\u5C42/\u89E6\u53D1\u8BCD/\u914D\u7F6E\u56DE\u663E\uFF09\uFF0C\u5E76\u8BF4\u660E\u5982\u4F55\u6309\u6280\u80FD\u6D41\u7A0B\u6267\u884C\u6700\u5C0F\u8C03\u4F18\u73AF\uFF08fixture \u2192 \u91C7\u96C6/\u5BFC\u5165 \u2192 verdict \u5224\u5B9A \u2192 \u673A\u5236\u8DEF\u7531 \u2192 \u5019\u9009\u65B9\u6848\uFF09\u3002\u672C\u5DE5\u5177\u53EA\u505A\u72B6\u6001\u8BF4\u660E\u4E0E\u8DEF\u7531\u6307\u5F15\uFF0C\u4E0D\u6267\u884C\u5224\u5B9A\uFF1B\u771F\u6B63\u7684\u6280\u80FD\u547D\u4E2D\u8D70 dsh \u539F\u751F skill \u5DE5\u5177\u3002",
|
|
2482
|
+
parameters: {
|
|
2483
|
+
action: { type: "string", enum: ["status"], description: "\u56FA\u5B9A\u4E3A status\uFF08\u9ED8\u8BA4\uFF09\u3002" }
|
|
2484
|
+
},
|
|
2485
|
+
timeoutMs: 1e4,
|
|
2486
|
+
output: {
|
|
2487
|
+
schema: {
|
|
2488
|
+
type: "object",
|
|
2489
|
+
additionalProperties: true,
|
|
2490
|
+
properties: {
|
|
2491
|
+
skill_name: { type: "string" },
|
|
2492
|
+
skill_dir: { type: "string" },
|
|
2493
|
+
entry: { type: "string" },
|
|
2494
|
+
subdomains: { type: "array", items: { type: "string" } },
|
|
2495
|
+
agent_layer: { type: "string" },
|
|
2496
|
+
triggers: { type: "array", items: { type: "string" } },
|
|
2497
|
+
config: { type: "object", additionalProperties: true }
|
|
2498
|
+
}
|
|
2499
|
+
},
|
|
2500
|
+
render: (_args, value) => [{
|
|
2501
|
+
type: "text",
|
|
2502
|
+
text: `\u8C03\u4F18\u6280\u80FD\uFF1A${String(value.skill_name)} @ ${String(value.skill_dir)}
|
|
2503
|
+
\u5B50\u57DF: ${value.subdomains.join(" / ")}
|
|
2504
|
+
\u89E6\u53D1\u8BCD: ${value.triggers.join("\u3001")}
|
|
2505
|
+
\u5224\u5B9A\u7EAA\u5F8B\uFF1ABound \u5224\u5B9A\u53EA\u51FA\u81EA verdict_judge\uFF08ops-verdict \u5F15\u64CE\uFF09\u3002`
|
|
2506
|
+
}]
|
|
2507
|
+
},
|
|
2508
|
+
async execute(_args, _exec) {
|
|
2509
|
+
const resolved = await skillPromise;
|
|
2510
|
+
return resolved.status;
|
|
2511
|
+
}
|
|
2512
|
+
}));
|
|
2513
|
+
}
|
|
2514
|
+
export {
|
|
2515
|
+
apply,
|
|
2516
|
+
inject,
|
|
2517
|
+
name
|
|
2518
|
+
};
|