mcpField
M
matlab-optimize-gpu-codegen

matlab-optimize-gpu-codegen

github.com/matlab其他1.2k39 次安装2 天前更新
代码仓库

关于 matlab-optimize-gpu-codegen

面向 GPU Coder 的 MATLAB 技能,用于迭代优化现有设计代码。它结合代码生成、基准测试、数值等价性检查和 GPU 性能诊断,支持 MEX、库或 DLL 目标。

主要功能

  1. 01GPU Coder 编译工作流
  2. 02对生成的 GPU 代码进行基准测试
  3. 03检查数值等价性
  4. 04分析 GPU 性能诊断
  5. 05应用结构性代码优化

适用场景

  1. 01加速现有 MATLAB 设计以生成 GPU MEX
  2. 02排查 GPU Coder 编译失败
  3. 03减少 GPU 内存使用或改善并行度

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-optimize-gpu-codegen

安装后在 Claude Code 中输入/matlab-optimize-gpu-codegen确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

manifest.yaml
references/
gpu-codegen-functions.md
scripts/
benchmarkMex.m
extractDiagnostics.p

详细介绍

针对 GPU 代码生成优化 MATLAB

对 MATLAB 设计文件进行迭代优化,以用于 GPU Coder:编译、基准测试、 应用结构优化、使用 gpuPerformanceAnalyzer 分析、修复 诊断问题,并在每一步验证数值等价性。

适用场景

  • 用户有一个 MATLAB 函数,并希望加快 GPU MEX 或 CUDA 代码的运行速度
  • 用户提到 GPU Coder、codegen、CUDA 或 gpuPerformanceAnalyzer
  • 用户希望分析生成的 GPU/CUDA 代码或 GPU MEX(分析生成的 GPU 代码是此技能诊断修复工作流的入口)
  • 用户希望减少 GPU 内存占用、改善内核并行度或修复 Performance Analyzer 诊断问题
  • 用户有一个 .m 设计文件和具有代表性的输入

不适用场景

  • 不涉及代码生成的工作流
  • coder.gpuConfig("exe")——无法从 MATLAB 对独立可执行文件进行基准测试或 等价性检查。建议用户切换到 mex 或 lib/dll,并从最终优化后的源代码重新生成 exe。
  • Simulink GPU 代码生成
  • 从头编写新的 MATLAB 函数(此技能用于优化现有代码)
  • 优化由设计文件调用的辅助函数——此技能仅优化主设计文件
  • 硬件设置或 CUDA 工具包安装
  • 不涉及 GPU 的常规 MATLAB 性能调优
  • 用户的提示中未提及 GPU、代码生成、CUDA、MEX 或性能分析。 仅根据用户提示决定是否启用——不要根据文件名或函数内容 推断用户有 GPU 相关需求。如不确定,请先询问再启用。

工作流

设置——创建会话目录

整个会话的所有代码生成产物、性能分析输出和优化版本都放在同一个临时目录中。每次调用代码生成、基准测试或 PA 时,pwd 都必须是 sessionDir—— 否则,编译生成的 MEX 和 SIL 二进制文件会写入用户的工作目录。

此技能会调用两个辅助函数——benchmarkMex 和 extractDiagnostics,它们位于此技能的 SKILL.md 所在文件夹的 scripts/ 子文件夹中。只有当 MATLAB 函数所在的文件夹位于 路径上时,才能通过名称调用该函数,因此请在设置时将 scripts/ 添加到路径,并在清理时移除。然后 直接使用函数名调用这些辅助函数(benchmarkMex(...)、extractDiagnostics(...))。

% TEMPLATE — not executable
sessionDir = fullfile(tempdir, "gpu_opt_" + string(datetime("now", Format="yyyyMMdd_HHmmss")));
mkdir(sessionDir);
scriptsDir = fullfile("<skill_dir>", "scripts");   % this skill's scripts/ folder (absolute)
addedDir = fileparts(which("<designFile>"));
addpath(scriptsDir, addedDir);
oldDir = cd(sessionDir);
cleanupCd = onCleanup(@() (cd(oldDir), rmpath(scriptsDir), rmpath(addedDir)));   %#ok<NASGU>

所有产物只能写入 sessionDir——绝不要写入用户的工作目录。

第 1 步 — 对原始文件运行代码生成

对未修改的设计文件运行 codegen,以发现实际失败之处。 不要猜测哪些函数不受支持——让编译器给出结果。

1a. 选择代码生成配置。 使用用户提供的配置。如果未指定,则默认使用 MEX:

cfg = coder.gpuConfig("mex");  % default — replace if user specifies a config

支持的目标: mex、lib、dll。此技能不支持 exe 目标,因此第 2–5 步无法进行基准测试或验证等效性。 如果用户提供了 coder.gpuConfig("exe"),请停止并询问他们选择以下其中一项:

  • 切换到 mex,用于优化工作流(推荐——迭代速度最快);或
  • 切换到 lib/dll,如果他们需要可部署的制品(此技能会启用 SIL,以便通过生成的 MEX 进行基准测试)。

优化完成后,用户可以从最终优化后的源文件重新生成 exe。

1b. 对于 lib/dll 配置:启用 SIL。 这是强制要求——未启用 SIL 就无法调用 MEX,因此第 2–5 步无法进行基准测试。在调用 codegen 之前设置此项:

% TEMPLATE — not executable
if ~isa(cfg, 'coder.MexCodeConfig')
    cfg.VerificationMode = 'SIL';   % required for benchmarking lib/dll targets
end

如果 SIL 失败或不可用(例如缺少 Embedded Coder 许可证),请报告此情况并停止——不要静默跳过基准测试。

1c. 确定输入。 如果用户提供了具体输入值,请原样使用。如果用户只提供了类型/大小(例如“两个大小为 1024x1 的 double 向量”),请合成符合规格的输入——准确记录生成的内容(类型、大小、位置、生成器),以便在最终报告中列出。合理的默认做法是:浮点数使用固定 rng 种子的 randn,整数使用 randi,逻辑值使用 rand > 0.5;除非用户另有要求,或 PA 随后标记 UseGpuInput,否则输入保留在 CPU 上。如果用户既未提供值,也未提供类型/大小,请询问有代表性的大小和类型——codegen -args 需要具体的签名,输入形状也会影响哪些优化最有效。

1d. 运行 codegen:

% TEMPLATE — not executable
codegen -config cfg <designFile> -args {<inputs>}

如果 codegen 失败,请阅读错误信息,并且只修复报告为不受支持的内容。 将修复后的文件保存为 sessionDir 中的 <designFile>_v1.m。

v1 必须成功通过 codegen。 重复运行 codegen,直到成功。这会生成基准 MEX:<designFile>_v1_mex(对于 lib/dll 配置,由 SIL 生成的 MEX 名称相同,且可用相同方式调用)。

第 2 步 — 基准测试

直接使用第 1 步生成的 MEX——不要重新运行 codegen。使用基于收敛的辅助函数进行基准测试:

% TEMPLATE — not executable
baseline = benchmarkMex("<designFile>_v1_mex", {<inputs>});
baselineTime = baseline.MedianTime;
fprintf("Baseline: %.4f ms\n", baselineTime*1000);

规则:

  • 始终使用 benchmarkMex(或 gputimeit)——GPU 计时绝不使用 tic/toc(GPU 操作是异步的)
  • benchmarkMex 会自动处理预热和收敛
  • 记录 baselineTime——所有改进都以此为基准
第 3 步 — 结构优化循环

迭代应用优化。每次迭代:

  1. 在 sessionDir 中创建 <designFile>_v<N>.m,加入下一项优化
  2. 验证与原始版本的数值等价性(多输入 — 参见第 3b 步)
  3. 在 sessionDir 中运行代码生成 — 如果失败,修复问题或回退
  4. 使用 benchmarkMex 对新的 MEX 进行基准测试 — 与当前最佳版本比较
  5. 将通过验证且速度最快的版本保留为当前最佳版本

MATLAB 级重构模式(无论最终选择哪种 GPU 原语,都能改进代码生成的重写方式):

源代码中的模式优化方式对生成的 CUDA 代码的影响
在不依赖循环变量的循环内使用数组表达式(例如,每次迭代都重新计算 w = weights / sum(weights))将表达式移到循环之前消除冗余的逐线程计算。GPU Coder 并不总能证明数组表达式在循环中保持不变,因此会将其内联到内核中,导致每个线程都重复执行相同的计算。
可用隐式扩展替代显式循环(例如,for i=1:N, out(i,:) = A(i,:) + B; end,其中 B 是行向量)将循环替换为使用隐式扩展的 out = A + BGPU Coder 会为隐式扩展生成单个融合内核。显式循环也可能并行化,但隐式扩展生成的内核更简洁,且没有循环开销。
可并行化循环内存在分歧分支(例如,if x(i) > 0, a = f(x(i)); else, a = g(x(i)); end,其中条件在各元素间不可预测地变化)如果两个分支的计算开销都很低,则同时计算两个分支,并用掩码选择(例如,a = mask.*f(x) + (~mask).*g(x))分歧的 if/else 会导致 warp 分歧 — 同一 warp 中的线程会依次执行各个分支。无分支掩码可使所有线程执行相同的指令路径,从而恢复完整的 warp 吞吐量。

上表仅涵盖 MATLAB 级重构,并未列举 GPU Coder 原语。确定任何优化方案之前,请从头到尾阅读 references/gpu-codegen-functions.md — 其中介绍了内核 pragma、并行归约与扫描、原子操作、模板和内存放置。正确的原语取决于循环的数据流形态(结果存放在哪里、依赖如何串联、迭代之间是否会冲突);表面上最相似的表格行往往并非正确答案。应根据循环语义进行匹配,而不是只看表面形式。

此循环的退出标准:

  • 达到性能目标(如果用户指定了目标)
  • Agent 无法再识别出更多结构优化
  • 最多进行 5 次结构迭代(第 5 步的诊断修复循环有单独的上限,为 5 次 — 两者互不影响)
步骤 3b — 验证数值等效性

使用最多 5 组输入分别运行原始版本和优化版本。使用用户提供的原始输入作为基准,然后生成类型和大小相同的变体。根据输入类型选择生成器(浮点数用 randn,整数用 randi,逻辑值用 rand > 0.5),并确保值处于该类型的有效范围内。

% TEMPLATE — not executable — float case; adapt generator to the input type
% Input sets — variants are built from in1, the first input resolved in Step 1c
in1 = <user_or_synthesized_input_from_step_1c>;     % original example
rng(42); in3 = randn(size(in1), 'like', in1);       % random
rng(99); in4 = randn(size(in1), 'like', in1) * 1e6; % large magnitude

仅在此函数支持边界情况输入时才纳入此类输入——全零输入的测试效果有限;如果函数会除以根据输入计算出的值,全零输入还可能导致退化。应选择能有效检验函数的变体。

独立比较每个输出:

% TEMPLATE — not executable
outOrig = <designFile>(testInput);
outOpt  = <designFile>_v<N>(testInput);

% Gather gpuArray outputs to CPU for comparison
if isa(outOpt, 'gpuArray'), outOpt = gather(outOpt); end

% If outputs can contain NaN, first check NaN positions match (see NaN rule
% below) — max() ignores NaN, so a corrupted NaN would otherwise pass silently.
denom = max(abs(outOrig(:)));
if denom == 0
    relErr = max(abs(outOpt(:)));  % absolute error when expected is zero
else
    relErr = max(abs(outOrig(:) - outOpt(:))) / denom;
end
fprintf("relErr = %.2e\n", relErr);

按输出类型设定容差:

输出类型容差
double1e-6
single1e-3
half1e-2
整数 / 逻辑值精确匹配(isequal)

规则:

  • 始终与未经修改的原始函数比较,而不是与上一个版本比较
  • 如果函数有多个输出,应根据每个输出的类型,分别使用相应的容差进行检查
  • 如果原始版本和优化版本的输出类型不同,这是一个 bug——请修复
  • 使用 max(abs(expected)) 归一化相对误差,而不是逐元素归一化
  • 处理 NaN:检查 NaN 的位置是否一致(isequal(isnan(a), isnan(b))),然后比较非 NaN 元素
  • 允许对输入使用 gpuArray 包装(例如,在修复 UseGpuInput 后)——这不会改变函数契约
步骤 4 — 使用 gpuPerformanceAnalyzer 进行性能分析

始终执行此步骤,除非用户指定了性能目标且该目标已在步骤 3 中达到。 PA 能发现基准测试单独无法检测的问题(内存复制、并行度不足)。

对当前最佳版本运行 PA,以查找剩余瓶颈。传入步骤 1 中相同的 cfg,确保 PA 针对用户要优化的目标进行性能分析:

% TEMPLATE — not executable
paDir = fullfile(sessionDir, "pa_v<N>");
mkdir(paDir);

gpuPerformanceAnalyzer("<designFile>_v<N>", {<inputs>}, ...
    Config=cfg, ...
    OutFolder=paDir, ...
    LaunchReport=false);

关键 NVP:

  • Config=cfg — 重用步骤 1 中的代码生成配置(用户提供的配置或默认的 coder.gpuConfig("mex"))
  • LaunchReport=false — 禁止显示 GUI 报告
  • OutFolder — 会话目录中的子目录

从 mldatx 报告中提取诊断信息:

% TEMPLATE — not executable
mldatxFile = fullfile(paDir, "html", "gpuProfiler.mldatx");
results = extractDiagnostics(mldatxFile);

如果 results.numDiagnostics == 0,则性能分析结果干净——跳至最终报告。

results 结构体包含:

  • results.numDiagnostics — 发现的问题数量
  • results.diagnostics(i).id — 带命名空间的 ID(例如,"gpucoder:diagnostic:UseGpuInput")
  • results.diagnostics(i).message — 人类可读的说明
  • results.diagnostics(i).file — 生成的 .cu 文件路径
  • results.diagnostics(i).line — 生成代码中的行号
第 5 步 — 修复诊断问题

读取 results.diagnostics 并应用有针对性的修复。每轮修复:

  1. 在 sessionDir 中创建 <designFile>_v<N+1>.m
  2. 运行代码生成 — 如果失败,则修复或还原
  3. 验证与原始版本等效(与第 3b 步采用相同的流程 — 使用相同的输入和容差,并与未经修改的函数进行比较)
  4. 使用 benchmarkMex 进行基准测试 — 与当前最佳版本比较

诊断 → 修复映射:

诊断 ID修复方法
gpucoder:diagnostic:NoKernelFunPragma指定的函数没有 kernel pragma,因此不会生成任何 kernel。在指定的函数中添加 coder.gpu.kernelfun。
gpucoder:diagnostic:LargeLocalMemoryUsagePerThread该 kernel 使用了大量的每线程局部内存。简化循环体内的表达式:将复合表达式拆分为更少的步骤,将较大的临时数组移到循环外,或减少中间变量的数量,以降低每线程寄存器/局部内存压力。
gpucoder:diagnostic:UseGpuInput被标记的输入首次在 GPU 上使用,因此需要进行 CPU→GPU 内存复制。仅在传递给代码生成/PA 之前,将被标记的输入转换为 gpuArray。不要推测性地转换其他输入。
gpucoder:diagnostic:KernelDiagnosticsNotEnoughParallelism该 kernel 启动的线程太少 — 每个线程都要执行大量工作,因此并行度较低。增加独立迭代的数量:调整计算结构,使并行化循环覆盖更多元素;将串行的多步骤工作拆分为独立的向量化操作;或将数据分块为更多彼此独立的块。
gpucoder:diagnostic:LongCPULoopHasLargeMemcpy受 CPU 限制的循环会在每次迭代时强制进行 GPU-CPU 同步。调整结构,避免在循环内将 GPU 结果读回 CPU。循环完成前,让中间结果保留在 GPU 上。如果循环体会启动 kernel,则并行化外层循环,或将其与内部的 GPU 工作合并。
gpucoder:diagnostic:LongRunningLoopLargeIteration迭代次数较多的循环占用了大量运行时间,且未并行化。对循环体进行向量化,或调整结构,使各次迭代彼此独立(没有循环携带依赖)。如果确实彼此独立,coder.gpu.kernelfun 或 coder.gpu.kernel 会将其映射为 kernel。
gpucoder:diagnostic:KernelLaunchOverheadLargeInLoop并行化的内层循环启动了许多简单的 kernel,导致开销很大。应并行化外层(父级)循环,而不是内层循环。这样可以将许多小型 kernel 启动合并为更少、更大的 kernel。如果编译器无法自动并行化外层循环,则对其使用 coder.gpu.kernel。
gpucoder:diagnostic:MemoryDiagnosticsRepeatedMemoryCopyInsideLoop每次迭代都会在 CPU 和 GPU 之间复制一个变量,导致同步。将该变量的分配和初始化移到循环外。如果变量在每次迭代时都会在 GPU 上写入并在 CPU 上读取(或反之),则调整结构,使其在整个循环期间都保留在同一设备上。
gpucoder:diagnostic:LongRunningLoopUnknownReason受 CPU 限制的循环因未确定的原因耗费了大量时间。检查循环体是否存在串行依赖。如果各次迭代彼此独立:进行向量化或添加 coder.gpu.kernelfun。如果存在依赖:检查能否将依赖部分与独立部分分离。如果循环调用的函数内部会启动 kernel,则会阻止外层循环并行化 — 请内联这些调用或调整结构。

迭代上限: 最多进行 5 轮诊断修复(与第 3 步的结构迭代上限分开计算)。如果有任何问题无法解决,请报告剩余问题。

第 6 步 — 最终报告

呈现最佳结果:

# GPU Codegen Optimization Results

## Files
- Original: <designFile>.m
- Best version: <designFile>_v<N>.m
- Session directory: <sessionDir>

## Inputs Used
| Arg | Type | Size | Location | Source |
|-----|------|------|----------|--------|
| in1 | double | 1024x1 | CPU | user-provided / synthesized via `randn(1024,1)` with `rng(0)` |
| ... | ... | ... | ... | ... |

State "user-provided" when the user gave concrete values, or describe the
generator (function + seed) when inputs were synthesized. Reported timings
below are reproducible only with these inputs.

## Performance
| Version | Time (ms) | vs Baseline |
|---------|-----------|-------------|
| Baseline | X.XX | — |
| v2  | X.XX | Y% faster |
| v<N> | X.XX | Z% faster |

## Diagnostics Resolved
| Diagnostic | Status |
|---|---|
| <issue> | RESOLVED |
| <issue> | OPEN (reason) |

## Changes Applied
1. <change description>
2. <change description>

## Numerical Verification
All versions verified equivalent (max relErr below the type-appropriate
tolerance) across N test inputs.

最终交付文件为 <designFile>_v<N>.m — 通过等效性检查且性能最佳的版本。

约定

  • 切勿覆盖原始设计文件 — 始终创建 _v<N> 变体
  • 所有产物都放在同一个会话 tempdir 中,绝不放在用户的工作目录中
  • 所有计时都使用 benchmarkMex(或 gputimeit)— 切勿对 GPU 代码使用 tic/toc
  • 先运行代码生成以发现错误 — 切勿猜测函数是否受支持
  • 每次更改后,使用多个不同的输入验证等价性
  • 使用用户提供的代码生成配置;如果未指定,则默认使用 coder.gpuConfig("mex")
  • 反复迭代:优化 → 基准测试 → 重复 — 只进行一轮会错失性能提升
  • 只有在性能分析显示存在 CPU→GPU 复制时,才将输入转换为 gpuArray — 不要猜测性地转换所有输入

版权所有 2026 The MathWorks, Inc.


评论

支持 Markdown