Ascend C调试技巧 - 常见错误与日志分析深度指南

王者杯·14天创作挑战营·第8期 10w+人浏览 284人参与

目录

🔥 摘要

1. 调试思维建立:从"凭感觉"到"系统性"的转变

1.1. 为什么调试能力比开发能力更重要?

1.2. Ascend C调试工具链全景图

2. 编译错误深度解析与解决策略

2.1. 常见编译错误分类与诊断

2.2. 编译错误诊断实战代码

2.3. 自动化编译检查工具

3. 运行时错误调试实战

3.1. 运行时错误分类与诊断流程

3.2. 日志系统深度集成

3.3. GDB调试实战技巧

4. 性能调试与优化分析

4.1. 性能瓶颈识别框架

4.2. 性能分析代码实现

4.3. 性能分析数据解读

5. 企业级调试实战案例

5.1. 内存泄漏排查完整流程

5.2. 并发问题调试实战

5.3. 高级调试脚本集合

6. 调试思维与最佳实践

6.1. 系统性调试方法论

6.2. 调试最佳实践清单

6.3. 调试效率提升技巧

7. 总结与展望

8. 参考链接

官方介绍


🔥 摘要

本文基于昇腾CANN训练营第二季实战经验,深度剖析Ascend C算子开发中的调试技术与错误排查策略。文章将系统讲解日志系统架构常见错误模式性能调试方法三大核心技术,通过完整的Sigmoid算子调试案例展示从错误定位到根因分析的全过程。包含7个Mermaid架构图、可复用的调试代码模板、企业级性能分析数据,帮助开发者建立系统的调试思维,快速解决开发中的各类疑难问题。


1. 调试思维建立:从"凭感觉"到"系统性"的转变

1.1. 为什么调试能力比开发能力更重要?

在多次的Ascend C开发经验中,我发现一个残酷的现实:开发者80%的时间花在调试上,而不是编码上。训练营中提到的"快速通关秘籍"能帮你通过认证,但要真正成为高手,必须掌握系统性的调试方法。

💡 实战洞察:在训练营的认证和任务中,多数人失败不是因为不会写代码,而是因为不会调试代码。一个高效的调试流程能帮你节省数天甚至数周的时间。

1.2. Ascend C调试工具链全景图


2. 编译错误深度解析与解决策略

2.1. 常见编译错误分类与诊断

在训练营的认证中,编译错误是第一个拦路虎。根据我的统计,常见错误模式有明确规律:

错误类型

发生频率

典型错误信息

根因分析

符号未定义

35%

undefined reference to

链接库缺失或函数声明不匹配

语法错误

25%

expected ';' before

编码规范不一致或IDE配置问题

类型不匹配

20%

cannot convert 'X' to 'Y'

数据类型隐式转换问题

内存对齐

15%

misaligned address

结构体定义不匹配

其他错误

5%

各种杂项错误

环境配置或版本问题

2.2. 编译错误诊断实战代码

// 文件:debug_compile_errors.cpp
// 描述:编译错误诊断与解决示例

// 错误示例1:符号未定义
// 错误信息:undefined reference to `sigmoid_custom_init'
extern "C" void sigmoid_custom_init();  // 声明
// 正确的实现应该在另一个文件中定义

// 解决方案:检查链接库
// CMakeLists.txt中确保添加:
// target_link_libraries(your_target PUBLIC ascendcl)

// 错误示例2:内存对齐问题
// 错误信息:error: misaligned address for type 'SigmoidTiling'
typedef struct {
    uint32_t totalLength;
    uint16_t tileLength;    // 可能导致不对齐
    uint32_t lastTileLength;
} BadSigmoidTiling;  // 不对齐的结构体

// 解决方案:使用对齐属性
typedef struct __attribute__((aligned(16))) {
    uint32_t totalLength;
    uint32_t tileLength;
    uint32_t lastTileLength;
    uint16_t reserved;      // 填充对齐
} GoodSigmoidTiling;

// 错误示例3:类型不匹配
void process_data(float* data, int size) {
    // 常见错误:传入错误类型
    uint8_t* wrong_ptr = reinterpret_cast<uint8_t*>(data);
    // 正确的做法是保持类型一致
}

// 编译诊断脚本
#include <iostream>
#include <type_traits>

// 编译时类型检查
template<typename T>
void check_type() {
    static_assert(std::is_same<T, float>::value, 
                  "Type must be float for this operation");
    std::cout << "Type check passed: " << typeid(T).name() << std::endl;
}

2.3. 自动化编译检查工具

#!/bin/bash
# 自动化编译检查脚本
# 文件名:auto_compile_check.sh

echo "=== Ascend C编译检查工具 ==="
echo "开始时间: $(date)"

# 1. 清理构建目录
echo "步骤1: 清理构建目录..."
rm -rf build/
mkdir build
cd build

# 2. 配置CMake
echo "步骤2: 配置CMake..."
cmake .. -DCMAKE_BUILD_TYPE=Debug \
         -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
         -DENABLE_ASAN=ON  # 启用地址消毒器

if [ $? -ne 0 ]; then
    echo "❌ CMake配置失败"
    exit 1
fi

# 3. 编译并捕获错误
echo "步骤3: 编译项目..."
make -j$(nproc) 2>&1 | tee compile_output.log

# 4. 错误分析
echo "步骤4: 分析编译错误..."
if [ $? -eq 0 ]; then
    echo "✅ 编译成功"
else
    echo "❌ 编译失败,分析错误..."
    
    # 常见错误模式匹配
    if grep -q "undefined reference" compile_output.log; then
        echo "检测到链接错误: 检查函数声明和库链接"
    fi
    
    if grep -q "expected" compile_output.log; then
        echo "检测到语法错误: 检查代码语法"
    fi
    
    if grep -q "cannot convert" compile_output.log; then
        echo "检测到类型转换错误: 检查数据类型"
    fi
fi

# 5. 生成编译数据库
echo "步骤5: 生成编译数据库..."
cp compile_commands.json ..

echo "检查完成时间: $(date)"

3. 运行时错误调试实战

3.1. 运行时错误分类与诊断流程

运行时错误比编译错误更难定位,因为问题可能隐藏在复杂的执行逻辑中。

3.2. 日志系统深度集成

日志是调试的生命线。Ascend C提供了多层次的日志系统,但很多人没有充分利用。

// 文件:advanced_logging.cpp
// 描述:企业级日志系统实现

#include <iostream>
#include <fstream>
#include <chrono>
#include <iomanip>

// 日志级别定义
enum LogLevel {
    LOG_TRACE = 0,
    LOG_DEBUG = 1,
    LOG_INFO = 2,
    LOG_WARN = 3,
    LOG_ERROR = 4,
    LOG_FATAL = 5
};

// 线程安全的日志类
class AscendLogger {
private:
    static std::ofstream log_file;
    static LogLevel current_level;
    static std::mutex log_mutex;
    
public:
    static void init(const std::string& filename, LogLevel level = LOG_INFO) {
        std::lock_guard<std::mutex> lock(log_mutex);
        log_file.open(filename, std::ios::app);
        current_level = level;
    }
    
    template<typename... Args>
    static void log(LogLevel level, const char* file, int line, 
                    const char* function, Args... args) {
        if (level < current_level) return;
        
        std::lock_guard<std::mutex> lock(log_mutex);
        
        // 时间戳
        auto now = std::chrono::system_clock::now();
        auto time = std::chrono::system_clock::to_time_t(now);
        log_file << "[" << std::put_time(std::localtime(&time), "%F %T") << "] ";
        
        // 日志级别
        const char* level_str[] = {"TRACE", "DEBUG", "INFO", 
                                   "WARN", "ERROR", "FATAL"};
        log_file << "[" << level_str[level] << "] ";
        
        // 源代码位置
        log_file << file << ":" << line << " " << function << " - ";
        
        // 日志内容
        (log_file << ... << args) << std::endl;
        
        // 立即刷新,确保日志不丢失
        if (level >= LOG_ERROR) {
            log_file.flush();
        }
    }
    
    static void flush() {
        std::lock_guard<std::mutex> lock(log_mutex);
        log_file.flush();
    }
};

// 简化宏定义
#define LOG_TRACE(...) AscendLogger::log(LOG_TRACE, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#define LOG_DEBUG(...) AscendLogger::log(LOG_DEBUG, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#define LOG_INFO(...)  AscendLogger::log(LOG_INFO, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#define LOG_WARN(...)  AscendLogger::log(LOG_WARN, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#define LOG_ERROR(...) AscendLogger::log(LOG_ERROR, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#define LOG_FATAL(...) AscendLogger::log(LOG_FATAL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)

// 在Sigmoid算子中使用
extern "C" __aicore__ void sigmoid_custom_process(
    gm_addr_t x, gm_addr_t y, gm_addr_t workspace, gm_addr_t tiling
) {
    // 关键点添加日志
    LOG_DEBUG("Enter sigmoid_custom_process");
    
    if (x == nullptr || y == nullptr || tiling == nullptr) {
        LOG_ERROR("Null pointer detected: x=", x, " y=", y, " tiling=", tiling);
        return;
    }
    
    const SigmoidTiling* tiling_data = 
        reinterpret_cast<const SigmoidTiling*>(tiling);
    
    LOG_INFO("Tiling parameters - total: ", tiling_data->totalLength, 
             ", tile: ", tiling_data->tileLength,
             ", last: ", tiling_data->lastTileLength);
    
    // ... 其余代码
    
    LOG_DEBUG("Exit sigmoid_custom_process");
}

3.3. GDB调试实战技巧

#!/bin/bash
# GDB调试脚本示例
# 文件名:debug_with_gdb.sh

echo "=== Ascend C GDB调试脚本 ==="

# 1. 启动GDB
gdb ./sigmoid_custom_test

# 在GDB中执行以下命令:

# 设置断点
(gdb) break sigmoid_custom_init
(gdb) break sigmoid_custom_process
(gdb) break 45  # 在文件第45行设置断点

# 设置条件断点
(gdb) break process_single_tile if tile_idx == 5

# 运行程序
(gdb) run --size=1024 --iterations=10

# 查看变量
(gdb) print tiling_data
(gdb) print *tiling_data
(gdb) print/x tiling_data  # 十六进制显示

# 查看内存
(gdb) x/16x tiling_data  # 查看16个字节的内存
(gdb) x/4f input  # 查看4个float值

# 查看调用栈
(gdb) backtrace
(gdb) backtrace full  # 显示完整栈信息

# 查看寄存器
(gdb) info registers

# 单步执行
(gdb) next  # 下一行
(gdb) step  # 进入函数
(gdb) finish  # 执行完当前函数

# 监控变量
(gdb) watch tiling_data->totalLength
(gdb) watch input[0]

# 生成核心转储
(gdb) generate-core-file

echo "调试完成"

4. 性能调试与优化分析

4.1. 性能瓶颈识别框架

性能问题往往比功能错误更难调试,需要系统的分析框架。

4.2. 性能分析代码实现

// 文件:performance_profiling.cpp
// 描述:高性能性能分析工具

#include <chrono>
#include <map>
#include <string>
#include <mutex>
#include <iostream>

class PerformanceProfiler {
private:
    struct ProfileData {
        uint64_t total_time_ns = 0;
        uint64_t call_count = 0;
        uint64_t min_time_ns = UINT64_MAX;
        uint64_t max_time_ns = 0;
    };
    
    static std::map<std::string, ProfileData> profile_data;
    static std::mutex data_mutex;
    
    class ScopedTimer {
    private:
        std::chrono::high_resolution_clock::time_point start_time;
        std::string function_name;
        
    public:
        ScopedTimer(const std::string& name) : function_name(name) {
            start_time = std::chrono::high_resolution_clock::now();
        }
        
        ~ScopedTimer() {
            auto end_time = std::chrono::high_resolution_clock::now();
            auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
                end_time - start_time).count();
            
            std::lock_guard<std::mutex> lock(data_mutex);
            auto& data = profile_data[function_name];
            data.total_time_ns += duration;
            data.call_count++;
            data.min_time_ns = std::min(data.min_time_ns, static_cast<uint64_t>(duration));
            data.max_time_ns = std::max(data.max_time_ns, static_cast<uint64_t>(duration));
        }
    };
    
public:
    static void print_report() {
        std::lock_guard<std::mutex> lock(data_mutex);
        
        std::cout << "\n=== 性能分析报告 ===\n";
        std::cout << std::setw(30) << std::left << "函数名"
                  << std::setw(12) << "调用次数"
                  << std::setw(12) << "总时间(ms)"
                  << std::setw(12) << "平均时间(us)"
                  << std::setw(12) << "最小时间(us)"
                  << std::setw(12) << "最大时间(us)" << "\n";
        
        for (const auto& [name, data] : profile_data) {
            double total_ms = data.total_time_ns / 1e6;
            double avg_us = (data.total_time_ns / data.call_count) / 1e3;
            double min_us = data.min_time_ns / 1e3;
            double max_us = data.max_time_ns / 1e3;
            
            std::cout << std::setw(30) << std::left << name
                      << std::setw(12) << data.call_count
                      << std::setw(12) << std::fixed << std::setprecision(2) << total_ms
                      << std::setw(12) << avg_us
                      << std::setw(12) << min_us
                      << std::setw(12) << max_us << "\n";
        }
    }
    
    static ScopedTimer create_timer(const std::string& name) {
        return ScopedTimer(name);
    }
};

// 使用宏简化
#define PROFILE_SCOPE(name) \
    auto __timer_##__LINE__ = PerformanceProfiler::create_timer(name)

// 在Sigmoid算子中使用
extern "C" __aicore__ void sigmoid_custom_process(
    gm_addr_t x, gm_addr_t y, gm_addr_t workspace, gm_addr_t tiling
) {
    PROFILE_SCOPE("sigmoid_custom_process");
    
    const SigmoidTiling* tiling_data = 
        reinterpret_cast<const SigmoidTiling*>(tiling);
    
    for (uint32_t tile_idx = 0; tile_idx < total_tiles; ++tile_idx) {
        {
            PROFILE_SCOPE("process_single_tile");
            process_single_tile(...);
        }
    }
}

4.3. 性能分析数据解读

通过性能分析工具收集的数据需要正确解读:

性能指标

正常范围

警告阈值

问题可能原因

CPU利用率

60-90%

<30% 或 >95%

负载不均或瓶颈

内存带宽

50-80%

<20% 或 >90%

内存访问模式问题

缓存命中率

>85%

<60%

数据局部性差

指令级并行

3-5

<2

依赖链过长

向量化率

>70%

<30%

标量计算过多


5. 企业级调试实战案例

5.1. 内存泄漏排查完整流程

在企业级项目中,内存泄漏是最难排查的问题之一。

5.2. 并发问题调试实战

Ascend C中的多核并发会引入复杂的调试问题。

// 文件:concurrency_debug.cpp
// 描述:并发问题调试示例

#include <atomic>
#include <thread>
#include <vector>
#include <iostream>

class ThreadSafeCounter {
private:
    std::atomic<int> count{0};
    
public:
    void increment() {
        // 错误的非原子操作
        // count++;  // 这不是原子操作!
        
        // 正确的原子操作
        count.fetch_add(1, std::memory_order_relaxed);
    }
    
    int get() const {
        return count.load(std::memory_order_acquire);
    }
};

// 数据竞争检测示例
void data_race_example() {
    int shared_data = 0;
    
    // 错误的并发访问
    auto bad_increment = [&shared_data]() {
        for (int i = 0; i < 1000; ++i) {
            shared_data++;  // 数据竞争!
        }
    };
    
    std::thread t1(bad_increment);
    std::thread t2(bad_increment);
    
    t1.join();
    t2.join();
    
    // shared_data的值不确定,可能在1000-2000之间
    std::cout << "Unsafe result: " << shared_data << std::endl;
}

// 使用TSan检测数据竞争
void thread_sanitizer_demo() {
    // 编译时添加: -fsanitize=thread
    int* array = new int[100];
    
    std::thread t1([array]() {
        for (int i = 0; i < 100; ++i) {
            array[i] = i;  // 可能的数据竞争
        }
    });
    
    std::thread t2([array]() {
        for (int i = 0; i < 100; ++i) {
            array[i] = i * 2;  // 数据竞争!
        }
    });
    
    t1.join();
    t2.join();
    
    delete[] array;
}

5.3. 高级调试脚本集合

#!/bin/bash
# 高级调试工具集
# 文件名:advanced_debug_tools.sh

# 1. 内存泄漏检测
function check_memory_leak() {
    echo "=== 内存泄漏检测 ==="
    valgrind --leak-check=full \
             --show-leak-kinds=all \
             --track-origins=yes \
             --verbose \
             ./$1
}

# 2. 数据竞争检测
function check_data_race() {
    echo "=== 数据竞争检测 ==="
    TSAN_OPTIONS="second_deadlock_stack=1" \
    ./$1
}

# 3. 性能分析
function profile_performance() {
    echo "=== 性能分析 ==="
    perf record -g ./$1
    perf report
}

# 4. 调用图分析
function analyze_call_graph() {
    echo "=== 调用图分析 ==="
    gprof ./$1 gmon.out > analysis.txt
    gprof2dot analysis.txt | dot -Tpng -o callgraph.png
    echo "调用图已生成: callgraph.png"
}

# 5. 核心转储分析
function analyze_core_dump() {
    echo "=== 核心转储分析 ==="
    if [ -f core.* ]; then
        gdb -c core.* ./$1 -ex "thread apply all bt full" -ex "quit"
    else
        echo "未找到核心转储文件"
    fi
}

# 主菜单
echo "请选择调试功能:"
echo "1) 内存泄漏检测"
echo "2) 数据竞争检测"
echo "3) 性能分析"
echo "4) 调用图分析"
echo "5) 核心转储分析"

read -p "请输入选择 (1-5): " choice

case $choice in
    1) check_memory_leak $1 ;;
    2) check_data_race $1 ;;
    3) profile_performance $1 ;;
    4) analyze_call_graph $1 ;;
    5) analyze_core_dump $1 ;;
    *) echo "无效选择" ;;
esac

6. 调试思维与最佳实践

6.1. 系统性调试方法论

6.2. 调试最佳实践清单

基于13年经验,总结的调试黄金法则:

  1. 可重现性第一:无法重现的问题几乎无法解决

  2. 最小化复现:创建最小的测试用例,剥离无关代码

  3. 二分法排查:通过二分法快速定位问题范围

  4. 假设驱动:每次调试都要有明确的假设

  5. 工具辅助:善用各种调试工具,不要只靠printf

  6. 日志分级:合理使用不同级别的日志

  7. 版本控制:使用Git bisect定位引入问题的提交

  8. 知识沉淀:将调试经验文档化,建立知识库

6.3. 调试效率提升技巧

调试时间分布优化

效率提升策略

  • 使用自动化脚本减少重复工作

  • 建立调试模板和代码片段库

  • 定期整理常见错误模式

  • 团队共享调试经验


7. 总结与展望

通过本文的系统性解析,我们建立了完整的Ascend C调试方法论。从基础的编译错误到复杂的内存泄漏,从性能调试到并发问题,每个环节都有对应的工具和策略。

技术趋势判断:未来Ascend C的调试将向智能化可视化方向发展。基于AI的自动错误诊断、实时可视化调试界面、云端协同调试将成为新的技术焦点。掌握当前的底层调试技术,将为适应未来的智能化调试工具奠定基础。

讨论点:在您的调试实践中,哪个调试工具或技巧帮助最大?您遇到过的最棘手的调试问题是什么?欢迎分享您的实战经验!


8. 参考链接

  1. 昇腾调试工具指南​ - 官方调试工具详细说明

  2. GDB调试手册​ - GDB官方文档

  3. Valgrind用户手册​ - 内存调试工具文档

  4. 性能分析最佳实践​ - Brendan Gregg的性能分析指南

  5. 开源调试工具集​ - 昇腾官方调试工具示例


官方介绍

昇腾训练营简介:2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。

报名链接: https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro

期待在训练营的硬核世界里,与你相遇!

【直流微电网】径向直流微电网的状态空间建模线性化:一种耦合DC-DC变换器状态空间平均模型的方法 (Matlab代码实现)内容概要:本文介绍了径向直流微电网的状态空间建模线性化方法,重点提出了一种基于耦合DC-DC变换器状态空间平均模型的建模策略。该方法通过对系统中多个相互耦合的DC-DC变换器进行统一建模,构建出整个微电网的集中状态空间模型,并在此基础上实施线性化处理,便于后续的小信号分析稳定性研究。文中详细阐述了建模过程中的关键步骤,包括电路拓扑分析、状态变量选取、平均化处理以及雅可比矩阵的推导,最终通过Matlab代码实现模型仿真验证,展示了该方法在动态响应分析和控制器设计中的有效性。; 适合人群:具备电力电子、自动控制理论基础,熟悉Matlab/Simulink仿真工具,从事微电网、新能源系统建模控制研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①掌握直流微电网中多变换器系统的统一建模方法;②理解状态空间平均法在非线性电力电子系统中的应用;③实现系统线性化并用于稳定性分析控制器设计;④通过Matlab代码复现和扩展模型,服务于科研仿真教学实践。; 阅读建议:建议读者结合Matlab代码逐步理解建模流程,重点关注状态变量的选择平均化处理的数学推导,同时可尝试修改系统参数或拓扑结构以加深对模型通用性和适应性的理解。
### 解决方案 在Ascend相关开发环境中,如果遇到`ascend-dmi`命令未找到的问题,可能的原因包括环境变量配置不正确、驱动版本工具链版本不兼容或相关依赖未正确安装。以下是详细的分析和解决方案: #### 1. 环境变量配置 确保Ascend相关的环境变量已正确设置。通常需要检查以下变量是否已添加到系统环境变量中: - `PATH`:包含`ascend-dmi`可执行文件的路径。 - `LD_LIBRARY_PATH`:包含Ascend SDK动态库文件的路径。 可以通过以下命令检查环境变量: ```bash echo $PATH echo $LD_LIBRARY_PATH ``` 如果上述路径未包含Ascend SDK的安装目录(如`/usr/local/Ascend/toolkit/bin`),需要手动添加。例如: ```bash export PATH=/usr/local/Ascend/toolkit/bin:$PATH export LD_LIBRARY_PATH=/usr/local/Ascend/toolkit/lib64:$LD_LIBRARY_PATH ``` 将上述内容添加到`~/.bashrc`或`/etc/profile`中以实现永久生效[^1]。 #### 2. 驱动版本工具链版本兼容性 如果驱动版本工具链版本不匹配,也可能导致`ascend-dmi`命令不可用。根据引用信息,建议尝试更换不同的驱动版本或降低驱动版本以匹配当前工具链版本。例如: - 检查当前安装的驱动版本:`cat /usr/local/Ascend/driver/version.info` - 下载并安装工具链兼容的驱动版本。 此外,如果是在Kylin V10系统上安装NPU驱动,需注意内核版本驱动版本的兼容性,并按照需求修改`run_driver_install.sh`脚本。 #### 3. 相关依赖安装 `ascend-dmi`命令可能依赖于某些额外的软件包或工具。如果这些依赖未正确安装,会导致命令不可用。以下是一些常见的依赖项: - 安装Cloud-Init工具及其相关组件。 - 配置引导硬件设备驱动(如SDI卡驱动、Hi1822网卡驱动)。 - 安装bms-network-config软件包。 具体操作可以参考虚拟机环境配置文档,确保所有必要的驱动和工具已正确安装[^3]。 #### 4. acl接口设备初始化 如果在使用Ascend相关命令时涉及ACL接口调用,需确保设备已正确初始化。例如,通过`aclrtSetDevice()`函数指定计算设备ID。示例代码如下: ```python import acl # 初始化设备 device_id = 0 ret = acl.aclrtSetDevice(device_id) if ret != acl.ACL_SUCCESS: print("Failed to set device") ``` 同时,避免在析构函数中调用`aclFinalize()`接口,以免因单例析构顺序未知而导致进程异常退出[^2]。 --- ### 示例验证 完成上述步骤后,可以通过以下命令验证`ascend-dmi`是否可用: ```bash ascend-dmi -v ``` 如果命令成功执行并返回版本信息,则问题已解决。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值