6
DCU技术达人
在 SCNet 海光 BW 64GB 实例上跑通 PyTorch、Triton 与 FlashAttention:环境检查与实测记录
2026-09-28 18:10:54
16
https://www.scnet.cn/ui/console/index.html#/notebook/add?resourceGroupName=hx1hgbwnormal&clusterId=20091 ![1790590093046.png](/api-forum/file/RedirectImage?path=20260928/1790590115417_1790590093046.png '1790590093046.png') 拿到这台海光 BW 实例后,我没有马上安装框架或下载模型,而是先确认三件事:设备是否真的被计算框架识别、容器实际能用多少 CPU/内存,以及现有软件栈能不能完成前向和反向计算。 这一步很重要。平台规格、容器里 `lscpu`/`free` 看到的宿主机资源,以及作业受 cgroup 限制后真正可用的资源,并不一定是同一回事。如果一开始把资源边界判断错了,后面出现 DataLoader 卡顿、编译进程被杀或训练 OOM,很容易误判成加速卡或框架问题。 本文记录一次实际排查和验证过程。所有结果来自同一台 SCNet Notebook 实例,截图做过脱敏;文中的吞吐数据只用于验证计算链路是否正常,不代表产品峰值性能。 ## 一、先说结论 这套环境可以胜任以下工作: - PyTorch 基础教学和深度学习课程作业; - Transformer、RoPE、RMSNorm、SwiGLU、Attention 等算子的实现与调试; - BF16/FP16 混合精度实验; - Triton 自定义算子练习; - FlashAttention 前向、反向验证; - 中小模型推理,以及在显存允许范围内进行 LoRA/QLoRA 微调; - 使用 vLLM 做单卡推理服务实验。 本次实例的核心环境如下。 | 项目 | 实测结果 | |---|---| | 操作系统 | Ubuntu 22.04.5 LTS | | 加速卡 | 海光 BW,1 卡 | | 显存 | 63.98 GiB | | DTK | `/opt/dtk-26.04-DCC2602-0317` | | Python | 3.10.12 | | PyTorch | 2.10.0 | | HIP 运行时 | 6.3.26093 | | Triton | 3.5.0 | | Triton 目标 | `hip / gfx936 / warp_size=64` | | FlashAttention | 2.8.3 定制版本 | | vLLM | 0.18.1 | | BF16 | 支持 | ![1790590119691.png](/api-forum/file/RedirectImage?path=20260928/1790590140568_1790590119691.png '1790590119691.png') ## 二、连接实例:浏览器适合试用,VS Code 更适合持续开发 SCNet 页面提供 JupyterLab、工具面板和 SSH 登录信息。临时运行几条命令时,浏览器终端已经够用;如果要反复修改训练脚本、查看 Git diff 或调试 Python,VS Code Remote - SSH 会顺手很多。 本地 `~/.ssh/config` 可以写成下面这样,主机和端口用控制台显示的值替换。密码不要写入配置文件,也不要提交到仓库。 ```sshconfig Host scnet-hygon-bw HostName User root Port ServerAliveInterval 30 ServerAliveCountMax 3 ``` 如果远端不能直接访问 VS Code 的下载站点,可以在本地 VS Code 设置中加入: ```json { "remote.SSH.localServerDownload": "always", "remote.SSH.useExecServer": false } ``` 这样会先在本机下载 VS Code Server,再通过 SSH 传到远端,能绕开远端网络访问受限的问题。连接成功后,我习惯先打开一个单独的实验目录,而不是直接在 `/root` 下堆脚本: ```bash mkdir -p /root/hygon-bw-guide-demo cd /root/hygon-bw-guide-demo ``` ## 三、不要只看 `lscpu` 和 `free` 先检查系统和 cgroup 限额: ```bash cat /etc/os-release lscpu free -h # cgroup v2 cat /sys/fs/cgroup/cpu.max cat /sys/fs/cgroup/memory.max ``` 这台容器里能看到宿主机约 128 个 CPU 线程和 503 GiB 内存,但作业实际受到约 15 个 CPU 核、59 GiB 内存的限制。因此,`num_workers` 不应照着 128 个线程设置,编译并行度也不宜直接拉满。 我会再用下面的方式把限制换算成人能直接读懂的数字: ```bash python - <<'PY' from pathlib import Path quota, period = Path("/sys/fs/cgroup/cpu.max").read_text().split() memory = int(Path("/sys/fs/cgroup/memory.max").read_text()) cpu_limit = "unlimited" if quota == "max" else int(quota) / int(period) print(f"CPU limit: {cpu_limit}") print(f"Memory limit: {memory / 1024**3:.2f} GiB") PY ``` 这个差异会直接影响三类参数: - DataLoader 的 `num_workers`; - 源码编译时的并行任务数; - CPU offload、数据缓存和内存映射文件的规模。 ## 四、DTK 环境必须在当前 Shell 中激活 这台实例已经预装 DTK,但新开的 Shell 不一定自动带上所需环境变量。先执行: ```bash source /opt/dtk/env.sh ``` 然后检查 PyTorch 是否能看到设备: ```bash python - <<'PY' import torch print("torch:", torch.__version__) print("hip:", torch.version.hip) print("available:", torch.cuda.is_available()) print("device_count:", torch.cuda.device_count()) print("device:", torch.cuda.get_device_name(0)) print("bf16:", torch.cuda.is_bf16_supported()) PY ``` 这里有一个容易困惑的地方:在 DTK/HIP 软件栈中,PyTorch 仍然沿用 `torch.cuda` 这套接口名。代码里出现 `device="cuda"` 并不表示程序偷偷切到了 NVIDIA CUDA,它只是 PyTorch 兼容接口的历史命名;实际后端可以从 `torch.version.hip` 和设备名称确认。 设备状态可以用 `hy-smi` 查看: ```bash hy-smi ``` ![1790590144013.png](/api-forum/file/RedirectImage?path=20260928/1790590164846_1790590144013.png '1790590144013.png') 如果输出开头出现 `rocm_smi_lib` 回退警告,但后续仍能正常列出 HCU、温度、功耗和显存占用,我会先把它当作管理库路径提示,而不是立刻认定设备不可用。最终还要以 PyTorch 分配张量和实际计算是否成功为准。 ## 五、一份可复现的四段式验证脚本 只打印 `torch.cuda.is_available() == True` 还不够。我更关心四条链路: 1. FP16/BF16 矩阵乘是否能执行,并且结果有限; 2. PyTorch 原生 SDPA 能否完成 BF16 因果注意力的前向和反向; 3. Triton 能否针对 `gfx936` 编译并运行自定义 kernel; 4. FlashAttention 能否完成 BF16 因果注意力的前向和反向。 下面的脚本会把完整结果保存成 JSON,方便后续比较镜像或依赖版本。 ```python #!/usr/bin/env python3 from __future__ import annotations import json import platform import time from pathlib import Path import torch import torch.nn.functional as F RESULTS_PATH = Path(__file__).with_name("validation_results.json") def gib(num_bytes: int) -> float: return num_bytes / 1024**3 def timed_matmul( dtype: torch.dtype, size: int = 2048, repeats: int = 8, ) -> dict: torch.manual_seed(336) lhs = torch.randn((size, size), device="cuda", dtype=dtype) rhs = torch.randn((size, size), device="cuda", dtype=dtype) for _ in range(2): _ = lhs @ rhs torch.cuda.synchronize() started = time.perf_counter() for _ in range(repeats): result = lhs @ rhs torch.cuda.synchronize() elapsed = (time.perf_counter() - started) / repeats tflops = 2 * size**3 / elapsed / 1e12 assert torch.isfinite(result).all() return { "dtype": str(dtype).removeprefix("torch."), "shape": f"{size}x{size}", "average_ms": round(elapsed * 1000, 3), "tflops": round(tflops, 3), "finite": True, } def check_sdpa() -> dict: torch.manual_seed(336) q = torch.randn( (2, 8, 128, 64), device="cuda", dtype=torch.bfloat16, requires_grad=True, ) k = torch.randn_like(q, requires_grad=True) v = torch.randn_like(q, requires_grad=True) output = F.scaled_dot_product_attention( q, k, v, is_causal=True, ) output.float().square().mean().backward() tensors = (output, q.grad, k.grad, v.grad) assert all(bool(torch.isfinite(item).all()) for item in tensors) return { "shape": list(output.shape), "dtype": "bfloat16", "forward_finite": True, "backward_finite": True, } def check_triton() -> dict: import triton import triton.language as tl @triton.jit def add_kernel( x_ptr, y_ptr, out_ptr, n_elements: tl.constexpr, BLOCK: tl.constexpr, ): offsets = tl.program_id(0) * BLOCK + tl.arange(0, BLOCK) mask = offsets < n_elements x = tl.load(x_ptr + offsets, mask=mask) y = tl.load(y_ptr + offsets, mask=mask) tl.store(out_ptr + offsets, x + y, mask=mask) torch.manual_seed(336) n_elements = 65_537 x = torch.randn(n_elements, device="cuda", dtype=torch.float32) y = torch.randn_like(x) output = torch.empty_like(x) grid = (triton.cdiv(n_elements, 256),) add_kernel[grid](x, y, output, n_elements, BLOCK=256) torch.cuda.synchronize() max_error = float((output - (x + y)).abs().max()) assert torch.allclose(output, x + y) target = triton.runtime.driver.active.get_current_target() return { "version": triton.__version__, "target": str(target), "elements": n_elements, "max_abs_error": max_error, "passed": True, } def check_flash_attention() -> dict: from flash_attn import flash_attn_func torch.manual_seed(336) shape = (2, 128, 8, 64) q = torch.randn( shape, device="cuda", dtype=torch.bfloat16, requires_grad=True, ) k = torch.randn_like(q, requires_grad=True) v = torch.randn_like(q, requires_grad=True) output = flash_attn_func(q, k, v, causal=True) output.float().square().mean().backward() assert torch.isfinite(output).all() assert torch.isfinite(q.grad).all() return { "shape": list(output.shape), "dtype": "bfloat16", "forward_finite": True, "backward_finite": True, } def main() -> None: assert torch.cuda.is_available(), "DTK accelerator is not visible" device = torch.cuda.get_device_properties(0) report = { "system": { "python": platform.python_version(), "torch": torch.__version__, "hip": torch.version.hip, "device": torch.cuda.get_device_name(0), "device_count": torch.cuda.device_count(), "memory_gib": round(gib(device.total_memory), 2), "bf16_supported": torch.cuda.is_bf16_supported(), }, "matmul": [ timed_matmul(torch.float16), timed_matmul(torch.bfloat16), ], "sdpa": check_sdpa(), "triton": check_triton(), "flash_attention": check_flash_attention(), } RESULTS_PATH.write_text( json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8", ) print(json.dumps(report, ensure_ascii=False, indent=2)) if __name__ == "__main__": main() ``` 运行方式: ```bash source /opt/dtk/env.sh python hygon_bw_validation.py ``` ## 六、实测结果与正确解读方式 本次运行的关键结果如下: ```text Device: BW | VRAM: 63.98 GiB | BF16: True Python: 3.10.12 | PyTorch: 2.10.0 | HIP: 6.3.26093 MatMul float16: 2048x2048 | 0.074 ms | 231.486 TFLOPS | PASS MatMul bfloat16: 2048x2048 | 0.076 ms | 225.437 TFLOPS | PASS SDPA BF16: output=[2, 8, 128, 64] | forward/backward finite | PASS Triton: GPUTarget(backend='hip', arch='gfx936', warp_size=64) vector add | max error=0.00e+00 | PASS FlashAttention BF16: causal forward/backward finite | PASS ``` ![1790590170031.png](/api-forum/file/RedirectImage?path=20260928/1790590191228_1790590170031.png '1790590170031.png') ![1790590180230.png](/api-forum/file/RedirectImage?path=20260928/1790590201334_1790590180230.png '1790590180230.png') 这里最值得强调的是:`231.486 TFLOPS` 和 `225.437 TFLOPS` 来自规模为 `2048 × 2048`、预热 2 次、重复 8 次的短时烟测。它没有固定频率和功耗,也没有覆盖更大矩阵、长时间稳态、并发任务和不同转置布局,因此不能当成官方峰值,更不能直接拿来与其他平台做横向排名。 这组数据真正能说明的是: - FP16 和 BF16 计算链路已经打通; - 输出没有 NaN 或 Inf; - PyTorch 原生 Attention 的反向传播正常; - Triton 能识别 `gfx936`,自定义向量加法误差为 0; - 当前定制版 FlashAttention 可以完成 BF16 causal 前向和反向。 如果要做严肃的性能报告,还需要固定软件版本、时钟/功耗状态、矩阵规模、预热次数和统计口径,并至少报告中位数、P95、显存峰值及稳态功耗。 ## 七、这台 64GB 单卡实例适合练什么 ### 1. 课程作业和算子实验 实现线性层、Embedding、RMSNorm、RoPE、SwiGLU、Multi-Head Attention、交叉熵和优化器都没有问题。建议每个模块先写一个小规模 CPU 参考实现,再与设备版本比较: ```python torch.testing.assert_close( actual.float().cpu(), expected.float().cpu(), rtol=1e-2, atol=1e-2, ) ``` 这种做法比一上来跑完整训练更容易定位错误,尤其适合检查 mask 方向、张量布局、广播和 dtype 转换。 ### 2. 小模型预训练 可以用它完成小型语言模型从分词、数据加载、训练到采样的完整闭环。为了让实验稳定,我会优先采用: - BF16 参数或自动混合精度; - 梯度累积换取更大的有效 batch; - gradient checkpointing 降低激活占用; - 训练前先用几十个 step 验证 loss 是否下降; - 每个阶段记录峰值显存和 tokens/s。 ### 3. LoRA/QLoRA 与单卡推理 64GB 显存给单卡实验留出了不错的空间,但“能加载”不等于“能全参数训练”。以 7B 模型为例,Adam 类优化器的参数、梯度和状态通常远超仅保存一份权重的空间,全参数训练还要给激活和临时缓冲区留余量。LoRA/QLoRA 更符合这类单卡实例的使用方式,具体上限仍取决于序列长度、batch size、量化算子和内核兼容性。 当前环境已经有 vLLM,可以用于验证模型加载、生成和服务接口。正式跑模型前,建议先用本地已有的小模型或离线上传的权重做 smoke test,不要一开始就搬几十 GB 的权重。 ### 4. Triton 内核练习 本次测试显示 Triton 的后端目标为: ```text GPUTarget(backend='hip', arch='gfx936', warp_size=64) ``` 这意味着可以练习向量加、Softmax、LayerNorm、矩阵乘等内核。不过从其他平台迁移 kernel 时,不要默认 warp size 一定是 32,也不要照搬只针对某一架构调出的 `num_warps` 和 block size。先保证正确,再结合实际目标做调优。 ## 八、离线环境中最容易踩的几个坑 ### 1. 外网域名能解析,不代表 HTTPS 能访问 本次环境中 DNS 可以工作,但访问 PyPI、GitHub 和 Hugging Face 的 HTTPS 请求受限。因此,直接执行 `pip install` 或 `git clone` 可能长时间等待后失败。 更稳妥的流程是: 1. 在可联网机器下载 wheel、源码包和模型文件; 2. 记录文件哈希; 3. 通过平台文件管理、JupyterLab 或 `scp` 上传; 4. 使用本地路径安装; 5. 把依赖版本和验证结果一起归档。 例如: ```bash # 联网机器 pip download -d wheels -r requirements.txt # 上传后,在远端离线安装 pip install --no-index --find-links ./wheels -r requirements.txt ``` ### 2. 每个新终端都要确认 DTK 环境 如果 `torch.cuda.is_available()` 突然从 `True` 变成 `False`,先检查是否忘记执行: ```bash source /opt/dtk/env.sh ``` 不要急着重装 PyTorch。重装很可能破坏平台已经匹配好的定制依赖。 ### 3. 自定义算子包必须做前向和反向验证 能 `import flash_attn` 只能证明 Python 找到了包,不能证明内核与当前设备完全兼容。至少应覆盖: - 目标 dtype; - causal/non-causal 模式; - 真实训练会用到的 head dimension; - 前向输出有限; - 反向梯度有限。 ### 4. 先保存 JSON,再截图 截图适合写文档,但不适合自动比较。我把系统版本、矩阵乘耗时、目标架构和误差同时写入 `validation_results.json`。以后更换镜像或升级 DTK 时,重新运行同一脚本即可做差异比较。 ## 九、我的建议:先建立最小闭环,再扩大实验 在异构加速环境中,最省时间的顺序通常是: 1. 确认 cgroup 资源边界; 2. 激活厂商软件栈; 3. 用小张量完成一次设备计算; 4. 验证核心算子的前向和反向; 5. 保存结构化结果; 6. 再接入数据集、模型和分布式组件。 这次验证之后,我对这台实例的定位比较清楚:它很适合深度学习教学、单卡工程验证、Triton 算子练习和中小规模模型实验;如果目标是长时间大模型预训练或严格的跨平台性能对比,则还需要更完整的监控、数据集、集群和基准测试方案。 对我来说,这次排查最有价值的并不是跑出一个好看的 TFLOPS 数字,而是确认了从 DTK、PyTorch、Triton 到 FlashAttention 的每一层都能给出可复现、可解释的结果。环境是否真正可用,最终还是要由一段能重复运行的代码来回答。
全部评论(0)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据