CANNBot NPU性能数据拆解技能

📅 2026/7/11 16:51:41
CANNBot NPU性能数据拆解技能
【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skillsname: model-infer-perf-breakdown description: | NPU 性能数据拆解技能。把 kernel_details.csv 按用户描述的模型结构切成 component 实例每层 attn / ffn / moe…再按用户给的 cluster 规则把 component 内部算子 分桶生成 wall_ms / bubble_ms 中位数 异常 layer 的单页 HTML。 触发场景分析 NPU prof / 拆解大模型性能 / 找抖动 layer。NPU 性能数据拆解技能输入一份 profkernel_details.csvtrace_view.json结合用户对模型结构的口头描述先把 op 序列切成 component 实例再按 cluster 规则统计每类计算的 wall_ms bubble_ms 中位数 / 抖动 / 异常 layer输出一页可读的 HTML。内部指标字段用*_msHTML 展示按量级自动使用µs或ms。工作流Step 0: 收齐 prof 路径 模型脚本路径 (硬约束) → run_dir/inputs.json Step 1: kernel_details.csv → raw_ops.json (单 step 提取) Step 1.5: (可选,默认不开) 调用 operator-theoretical-perf → operator_analysis.csv → 合并理论列到 raw_ops_details.json Step 2: Phase 0a 用户描述模型 → structure_spec.json Phase 0b 敲定每个 component 的 stream sample 范围 (必须用户 ack) → sample_ack.json Phase 1 跑 detect_structure → structure_draft.json (component 实例表) Phase 1.5 复读 warnings 让用户 ack Step 3: 用户和 agent 对话定 cluster 规则 → network_spec.json → index.html含 theory median 列 Step 4: 按 component 切 csv / trace → splits/ (单层下钻) Step 5: 洞察归纳agent 语义判断 脚本统计 → insights/每个 Step 各自的脚本 docstring 里有完整 schema 与算法描述本文档只列契约与 CLI。工作目录约定跑本 skill 的 agent第一步早于 Step 1必须先和用户敲定一个run_dir——本次 skill 所有产物的落盘根下文 CLI 里出现的run_dir/都指这个目录。默认建议./perf_breakdown/label/label由用户给如network_baseline/network_variant用户也可以直接指定绝对路径。不存在就mkdir -p。进run_dir先查历史产物再判档复用每次跑 skillagent 在做新的对话 / 脚本调用前必须先列run_dir已有文件 读HISTORY.md如果有然后跑 Step 1 出新raw_ops.json落到raw_ops.next.json以免覆盖和上一 run 做自动 diff判出小改 / 中改 / 大改三档之一再按对应档位复用产物档触发信号复用范围小改kernel 集合完全相同anchor count 偏差 0上一 sample 的 stream anchors 在相近位置命中structure_spec/structure_draftsample 平移 /network_spec全复用跳过所有对话中改出现新 normalized_nameanchor count 偏差 ≤ 10%sample 偏移 5 rowshape 分布漂移structure_spec复用 回显问重走 Phase 0b重出 samplenetwork_spec增量补 rule大改anchor count 偏差 10%总 op 数偏差 30%关键 anchor 消失重走 Phase 0a / 0bnetwork_spec当参考但逐条审判档后 agent 复述一句结论给用户 ack如prof 相对prev_label是中改MoE 新增AddRmsNormCastsample 偏移约 80 行。打算 B 档复用对吗用户不同意直接按用户给的档位走。首次跑run_dir空跳过 diff 走全量流程。完整信号定义与判档规则见references/reuse_policy.md。反模式禁止修改用户提供的任何原始文件prof / modeling 源码 / config 等产物只写到run_dir/。前置物料 checklistStep 1 之前必走跑本 skill 在做任何 prof 解析之前agent必须和用户敲齐下列物料任一缺失或未确认即 abort不要带着空缺/默认值往下走。物料强度缺失/未确认行为prof 路径kernel_details.csvtrace_view.json硬约束文件不存在直接 abort模型脚本路径modeling*.py 相关 module 文件硬约束用户说没有/不方便也 abort——stream sample ack 流程依赖它用户应当先去拿到再回来run_dir目录硬约束不存在则mkdir -pphase device 选择多卡 / 多采集会话时硬约束必须列出可选项让用户挑不允许 agent 自作主张默认选第一个Ascend prof 有两种主流目录布局A 类torch_npu.profiler/ B 类原生msprof各自的 csv / json 落在哪、多卡怎么分目录见references/prof_layouts.md。agent 看到 prof 目录后先识别是哪一类再按对应位置取kernel_csv/trace_json多卡 / 多采集 / 多 phase 任一维度组合数 1 时枚举所有(phase, rank/device)让用户挑每个组合各起一个 subrun_dir独立跑。每个子 run 的inputs.jsonagent 写{ prof_dir: /abs/path/to/prof 根目录, kernel_csv: /abs/path/to/.../kernel_details.csv 或 op_summary_*.csv, trace_json: /abs/path/to/.../trace_view.json 或 msprof_*.json, model_script_paths: [/abs/path/to/modeling_xxx.py, /abs/path/to/moe_module.py], phase: decode, rank: 0 }rank一般 device_id每 process 绑一张卡时分布式训练 rank ≠ device 时由用户给。后续detect_structure.py启动时会校验inputs.json存在、model_script_paths非空且文件存在、phase/rank字段已填否则 exit 1。历史记录run_dir/HISTORY.mdagent 维护的人类可读运行日志用途下一个 agent / 用户进run_dir时一眼看清这个模型之前是怎么拆的、为什么这么拆跨 run 归因某次性能变化是因为换了 sample改了 cluster 规则还是模型本身改了与compare_runs.py输出的history.html定量对比互补契约进run_dir时若文件存在 → 必读每次完成render.pyStep 3 Phase 2 出runs/label/后 → 必 append 一条append-only按时间从老到新禁止回头改旧条目条目字段、写法、撤回规则见references/history_template.md。Step 1 — 单 step 提取python scripts/analyze_kernels.py -f kernel_details.csv -s 2 \ -o run_dir/raw_ops.json -d run_dir/raw_ops_details.json作用从 csv 抽一个稳定 step默认跳过 step 0 warmup后续所有分析以单 step 为单位。输入kernel_details.csv同目录通常还有trace_view.jsonStep 4 用Step 1 不消费。输出raw_ops.json— 单 stepoperators[]每条含index/org_indexcsv 0-based 数据行号/normalized_name/duration_us/start_time_us/stream_id/input_shapes/output_shapes。Step 2、3 消费。raw_ops_details.json— 单 step 详情含 csv 全部字段。Step 4 切片时用。行号 domainExcel /org_index/index三者各自的语义和换算关系 AI_CPU drift 注意事项见references/phase0b_workflow.md末尾的行号 domain 速查。Fallback 行为accelerator_core ∈ {AI_CPU, AICPU}默认剔除sample 模式严禁加--keep-ai-cpu破坏 row-order。若 profiler 同时记录 HCCL/COMMUNICATION 的 collective summary 行和Name AivKernel的 task fragment 行Step 1 只保留 summary 行、丢弃可匹配 summary 的 fragment没有 summary 证据的通信 fragment 必须保留不能静默丢失。AI_VECTOR_CORE 中的 Gather/Scatter/Reduce/Slice 等仍按普通算子保留。Step 1.5 — 理论性能注入可选默认不启用可选步骤——依赖外部operator-theoretical-perfskill。不启用时render.py的 theory median 列自动留空—、不阻塞Step 5 的理论偏离 insight 也自动跳过输出空 JSON data_limits说明。仅当用户要理论对比、且外部 skill 可用时才启用。启用时chip 配置提问可并入 Step 0 前置物料或推迟到 Step 2 之后、render.py之前Phase 0a 的「不要先于此发问」只约束结构类提问chip 不受限理论列合并必须在render.py之前完成。调用外部operator-theoretical-perfskill或直接跑其analyze_operators.py --chip-config chip.json用原始kernel_details.csv 用户确认的 chip 生成run_dir/operator_analysis.csv再把理论列按原 CSV 行序 /org_index合并回run_dir/raw_ops_details.jsonpython scripts/merge_theoretical_columns.py \ --raw-ops-details run_dir/raw_ops_details.json \ --operator-analysis-csv run_dir/operator_analysis.csv \ -o run_dir/raw_ops_details.json理论列不参与 Step 2 结构拆解sub-item / TOTAL 粒度理论耗时在 Step 3 渲染时从合并后的raw_ops_details.json计算。调用契约、多流选择口径见references/theoretical_perf_workflow.md。Step 2 — sample-driven 层检测Phase 0a问用户模型结构必走agent逐字把references/phase0a_prompt.md里的 prompt 发给用户解析答案后自动起 phase 名①→main、②→mtp_head、③→用户类别名写run_dir/structure_spec.json并回显 JSON 让用户确认。schema{ model_hint: 自然语言, phases: [ {name: main, layers: 6, layer_compositions: [ {layer_range: [0, 2], components: [mla, dense]}, {layer_range: [3, 5], components: [mla, moe]} ]}, {name: mtp_head, layers: 3, layer_compositions: [{layer_range: [0, 2], components: [mtp, moe]}]} ], expected_components: [mla, dense, moe, mtp], expected_layer_count: 9 }Phase 0b定 stream sample 范围必走必须用户 ack核心契约按expected_components去重列表逐项敲定每个 component 的 sample 范围。脚本会把 sample 转成 per-streamstream_samples每个 component 必须有且只有一个primarystream其他确认属于该 component 的流标auxiliary。归属看语义、不看时间是否重叠语义上属于该 component 的流即使与本层时间脱节也写auxiliary——detect_structure会发 hard warningauxiliary_stream_temporally_displacedrender自动把它从 cluster/bubble/TOTAL 剔除并标注、op 仍 matched 保留在 componentop_indices全集不破、Step 4 split 照切判定是流级全有全无只有不属于任何 component的流全局通信/采样/IO 等才留unmatched。用户 ack 后写run_dir/sample_ack.jsonagent 不能跳过 ack 自己往下推。pre / post / inter 不需要给。两条路径sample 范围是硬需求二选一A 档用户直接给 row 区间或算子名agent 对照raw_ops.json校验后回贴 ackB 档用户给不出 → agent 读 Step 0 收齐的model_script_paths推典型 sample 范围标注来源后让用户核对详细步骤、回贴格式、sample_ack.jsonschema 见references/phase0b_workflow.md。expected_components中任一缺失即 ack 未完成detect_structure.py启动时 exit 1。Phase 1跑 detect_structurepython scripts/detect_structure.py \ -r run_dir/raw_ops.json \ --structure-spec run_dir/structure_spec.json \ --inputs run_dir/inputs.json \ --sample-ack run_dir/sample_ack.json \ -o run_dir/structure_draft.json--inputs和--sample-ack在 sample 模式下必传--inputs run_dir/inputs.json校验model_script_paths非空Step 0 物料--sample-ack run_dir/sample_ack.json使用schema_version: stream_sample_ack.v1校验每个 component 都有stream_samples[]且只有一个primary任一校验失败 exit 1agent 必须回 Phase 0b 补齐新写入的sample_ack.json必须保存为 stream sample schemaStep 2 输入须为此 schema。hard / ambiguous warnings含auxiliary_stream_temporally_displaced默认 exit 1agent 必须先逐条复读给用户用户明确接受后才可加--accept-warnings重跑放行。输出structure_draft.json下游 Step 3 / Step 4 直接消费mode: stream_sample_drivenschema_version: structure_draft.stream.v1structure_spec— 原样回写samples_used[]— 每 sample 的stream_samples[]components[]— 扁平表每条 一个 component instancecomponent_id/layer_idx/phase/type/op_indices[]/stream_segments[]/ stream-awarescores某条 aux 流被判时间脱节时还含displaced_op_indices[]——该流的 oprender 据此从 cluster/bubble/TOTAL 剔除但仍在op_indices全集内、partition 不破op_to_component— op index 到 component_id 的反查表unmatched_op_indices[]/unmatched_stream_segments[]— 未归属 op不能静默丢弃warnings[]— primary/auxiliary stream、op membership、composition schedule、count/shape/ambiguity、temporally-displaced aux stream 类问题每条含codemessagevalidation— declared vs detected 层数 / missing_samples / ambiguous_matcheswarning codes、validation 字段、匹配算法细节见detect_structure.py/sample_matching.pydocstring。Phase 1.5复读 warnings 等用户 ack必走逐条把warnings[]复读给用户列 layer_idx / component / 期望 vs 实测。用户选项忽略 / 改 0a 重跑 / 改 0b 重跑。无 ack 视为 Step 2 未结束。Step 3 — cluster 规则对话 渲染Phase 1agent 引导用户给 cluster 规则structure_draft.json给出 component 实例边界但每个 component 内部还要进一步分桶才能看出抖动来源。agent 主动和用户对话产出run_dir/network_spec.json——每个component_type一个 cluster 列表每个 cluster 一条或多条匹配规则op_name/op_name_regex/input_shapes_contains/output_shapes_regex/catch_all等逐 component_type 让用户确认。跑完render.py后看unmatched ops面板若有未匹配算子回头细化规则直到 unmatched 0 或用户明确说剩下的不关心。详细工作步骤、规则字段、network_spec.jsonschema、outlier选项、cluster / bubble / TOTAL 数值定义、spec 跨 prof 复用规则见references/cluster_dialogue.md。网络 spec 存放位置run_dir/network_spec.json。同一模型在不同 prof 上复用——新 run 进入工作目录后优先查同名文件存在就直接读不必重跑 cluster 对话。Phase 2渲染启用理论时Step 1.5 已把逐 kernel 理论列合并进raw_ops_details.json渲染脚本在 cluster / TOTAL 粒度按 critical stream 聚合理论耗时未启用则 theory median 列整列留空—本段其余多流口径不适用。多流时默认取 observed timeline union 最大的 stream未选中流视为被覆盖或旁路不计入该 sub-item 的理论耗时。若某 sub-item / TOTAL 出现多流agent 应在run_dir/theory_stream_decisions.json填semantic_note说明这些流的语义若候选流覆盖范围接近还要同时判断是否覆盖selected_stream。没有语义说明的多流项会在理论列标semantic note required。python scripts/render.py \ -d run_dir/structure_draft.json \ -r run_dir/raw_ops.json \ --raw-ops-details run_dir/raw_ops_details.json \ -s run_dir/network_spec.json \ --label label \ -o run_dir/runs/label/index.html首次渲染也走runs/label/与输出文件表 / Step 5 回填 /compare_runs一致metrics.json落到同目录。如需填写多流语义或覆盖 critical stream 选择再追加--theory-decisions run_dir/theory_stream_decisions.jsonHTML 主表在median后展示theory median列数据来自 Step 1.5 合并进raw_ops_details.json的theoretical_operator_time_us未跑 Step 1.5 时整列留空—两列都按量级自动显示µs或ms。bubble 行没有 kernel 理论耗时显示—。主报告最后一列固定为insightStep 3 初次渲染时保留为空Step 5 结束后由 agent 写run_dir/runs/label/insights/main_report_insights.json再用--insight-annotations重跑render.py回填。该列只显示confidence high | medium的洞察low不写入主报告。每条回填必须带category若不能精准映射到单个模块由 agent 选择主要归属target用mapping_type: agent_primary和mapping_note说明依据。TOTAL只是可选主归属之一只有洞察确实是整体性的才放到TOTAL否则优先放到最相关的具体 cluster /bubble行。同一主报告 row 可以显示多条 insight。--raw-ops-details必传缺了 chip 退化成静态span无法下钻 per-op 明细。unmatched gaterender.py在compute-core unmatched 比例 --unmatched-limit默认 5%时 exit 1通信类 core 不计入该比例。报错打印按accelerator_core的 unmatched 分解若主要是通信 / 采样 / embedding / IO加--accept-unmatched放行若有大段 compute 算子漏匹配回 Phase 0a/0b 补 sample 或声明 component。--unmatched-limit可调阈值。输出index.html— 单页报告按 spec 中 component_type 顺序排每 component_type 一张 cluster / bubble / TOTAL 抖动表含unmatched ops面板。若 cluster 桶之间存在 timeline overlap 或覆盖率异常HTML 顶部按下列规则插提示面板cluster 覆盖率红字Σ cluster_wall.median / TOTAL.median 80% 时插红字 banner提示大量算子落进 bubble / 未匹配 / 漏算子查 unmatched 面板与 cluster 规则。overlap 黄字警告median gap ≥ 5% TOTAL提示分桶不能直接反映真实性能分布列主要重叠桶对 占比。overlap 浅蓝 infomedian gap 0 x 5% TOTAL列出仍存在跨 stream 并发的桶对过滤掉 1µs 的噪声项最多 8 对方便定位并发来源但不影响主表读数。辅流时间脱节提示浅蓝某 aux 流被判 displaced 时列其 op_count / median wall / %TOTAL标注已从 TOTAL/bubble 排除、op 仍 matched逐层明细见 splits/。metrics.json—compare_runs.py消费的指标数据每个 section 含overlap: {median_gap_ms, median_gap_pct, max_gap_ms, top_pairs[]}、cluster_sum_pct_of_total、displacedper-stream 脱节摘要字段。字段名保持*_msHTML 展示层会把小于 1ms 的值显示成µs。render 完成时若run_dir/HISTORY.md不存在stdout 打 reminder不 exit 1。cluster / bubble / TOTAL 定义、outlier 检测、overlap 度量、规则求值顺序见render.pydocstring。Step 4 — 按 component 切 csv / trace_view必跑。skill 的交付契约要求 op 序列被完整切片每个 component 实例与每段 unmatched op 集合各占一个文件夹整体严格覆盖[0, N-1]、无重无漏。新 draft 以op_indices为准component 可以是非连续 op 集合op_range只作为 manifest 里的展示 envelope。脚本会自检 partitiondraft 与 raw_ops 不匹配时直接 abort。python scripts/split_artifacts.py \ -d run_dir/structure_draft.json \ -r run_dir/raw_ops.json \ -f /path/to/kernel_details.csv \ -t /path/to/trace_view.json \ -o run_dir/splits/文件夹命名直接对应 layer_id 与 component 类型kind命名component 实例Lidx:03d_phase_typepre/post 头尾pre/post相邻 component 间的 gapgap_Lprev_phase_type_to_Lnext_phase_type头尾外侧 gapgap_before_Lnext_…/gap_after_Lprev_…疑似未声明的 componentsuspected_…其余结构同 gap每个文件夹内只放两份产物kernels.csv— csv 切片保留 header首列org_indextrace_view.json— trace 证据传-t时是该目标 op 的时间包络上下文窗口可能包含同时间并行的其他目标 op不传时从 raw_ops 的op_indices精确合成顶层manifest.json是 folder →{kind, op_range, op_indices?, ops_count, files}索引给下游 / 用户导航用当目标 op 非连续时才写op_indices。files.trace_scope标明 trace 是op_membership_exact还是time_envelope_context。-f/-t在标准 prof 包下必传缺 csv 跳过 csv 切片缺 trace 时 trace_view.json 从 raw_ops 合成。--dry-run只打印目标清单不落盘。Step 5 — 洞察归纳Step 5 在 Step 3 / Step 4 之后执行只做洞察层归纳与展示不重新定义模型结构、不修改 cluster 规则。事实提取与渲染由scripts/gen_insights.py两遍先产事实候选agent 写movement_taxonomy.jsonannotations.json后带--movement-taxonomy/--annotations再跑scripts/render_insights.py完成agent 不手写提取逻辑候选可靠性 / 是否展示由 agent 用confidencehigh|medium|low标注。CLI、脚本/agent 边界、五类 insight、HTML 渲染与落盘契约见references/insight_workflow.md。默认输出目录run_dir/runs/label/insights/和该 run 的index.html/metrics.json放在同一目录下。只有未使用runs/label/的老式单 run 输出才退回run_dir/insights/。Step 5 最后还要生成main_report_insights.json并重跑主报告渲染render.py --insight-annotations把 high / medium 洞察连同类别和主映射说明填回runs/label/index.html最后一列。增量分析模型迭代 历史对比模型局部改动后重新 prof 时不要从零再走一遍 Step 2 对话。复用规则network_spec.json跨 prof 复用cluster 规则是 op_name shape pattern与 layer 位置 / 数量无关模型改动只要不引入新算子就直接复用。新增算子时只需补一条 rule。structure_spec.json通常微调layer 数 / composition 不变就照搬改了层数 / 加了 MTP 改 0a 重写。sample 行号需重出op 在 csv 中的位置随改动平移Phase 0b 每次都要给。每个 run 落盘到独立目录render.py的--label label-o run_dir/runs/label/index.html首次起就这样见 Step 3 Phase 2让每个 run 独占runs/label/增量 run 只换label值。同目录同时产metrics.json含label/model_name/outlier/ 每个(component_type, cluster)的 wall bubble 指标 outlier 列表compare_runs.py消费。对比表python scripts/compare_runs.py \ -r run_dir/runs/ \ -o run_dir/history.html \ [--metric median_ms] # 默认 median_ms可选 mean_ms / p95_ms / max_ms / std_ms [--baseline label] # 默认第一个 run 当 baseline-r给目录时自动扫所有*/metrics.json按 mtime 升序排早的当 baseline。也可以列出多个metrics.json路径显式指定顺序。输出history.html— 多 run 跨版本对比表Δ% vs baseline outlier diff列布局见compare_runs.pydocstring。输出文件文件阶段说明run_dir/raw_ops.jsonStep 1单 step kernel 概要下游所有脚本消费run_dir/raw_ops_details.jsonStep 1Step 1.5 可加列单 step kernel 详情Step 4 切片用启用理论性能时合并理论列run_dir/operator_analysis.csvStep 1.5外部operator-theoretical-perfskill 对原 CSV 加列后的原生产物run_dir/theory_stream_decisions.jsonStep 3 / 2agent 对多流语义的说明多流场景必填必要时覆盖 critical stream 选择run_dir/structure_spec.jsonStep 2 / 0a用户描述的模型结构run_dir/structure_draft.jsonStep 2 / 1component 实例表Step 3 / 4 输入run_dir/network_spec.jsonStep 3 / 1用户的 cluster 规则新 run 优先复用同名文件run_dir/runs/label/index.htmlStep 3 / 2单 run 的单页 cluster 抖动报告run_dir/runs/label/metrics.jsonStep 3 / 2单 run 指标compare_runs.py消费run_dir/runs/label/insights/module_bubble.jsonStep 5模块长期 bubble 与具体层/模块 bubble 异常run_dir/runs/label/insights/operator_jitter.jsonStep 5算子 / cluster 性能抖动候选与证据run_dir/runs/label/insights/theoretical_deviation.jsonStep 5逻辑 sub-item / TOTAL / operator slot 与理论性能偏离 Top5run_dir/runs/label/insights/vector_sequence_candidates.jsonStep 5连续 vector 区间融合候选按 pattern 聚合run_dir/runs/label/insights/data_movement_ops.jsonStep 5冗余 Tensor Movement 消除候选搬运 / 结构变换算子定位、上下文与消除方向run_dir/runs/label/insights/final_conclusions.mdStep 5agent 基于五类 insight 汇总的最终结论与证据索引run_dir/runs/label/insights/index.htmlStep 5五类 insight 的可视化页面run_dir/runs/label/insights/main_report_insights.jsonStep 5agent 选择回填到主报告insight列的 high / medium 洞察run_dir/history.html增量多 run 对比表Δ% vs baselinerun_dir/HISTORY.md每 run 收尾agent append 的运行日志记录复用 / 变更 / 观察run_dir/splits/Step 4按 component 切的 csv / trace_view含 manifest.json参考references/kernel_data_guide.md—kernel_details.csv字段说明references/prof_layouts.md— Ascend prof 两类目录布局torch_npu / msprof 多卡识别要点references/phase0a_prompt.md— Step 2 Phase 0a 用户结构描述逐字 promptreferences/phase0b_workflow.md— Step 2 Phase 0b stream sample ack 流程 sample_ack.jsonschema 行号 domainreferences/cluster_dialogue.md— Step 3 cluster 规则对话步骤 network_spec.jsonschema 数值定义references/history_template.md—HISTORY.md条目字段与模板references/reuse_policy.md— prof 变更检测信号 三档复用规则references/theoretical_perf_workflow.md— Step 1.5 理论性能注入外部 skill 调用、CSV 理论列合并、多流选择口径references/insight_workflow.md— Step 5 洞察归纳五类 insight、脚本/agent 边界、HTML 渲染references/insight_html_template.html— Step 5 洞察页 HTML/CSS/JS 骨架各脚本 docstring — 内部算法、阈值推导、edge case 处理【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考