关于 matlab-optimize-gpu-codegen
面向 GPU Coder 的 MATLAB 技能,用于迭代优化现有设计代码。它结合代码生成、基准测试、数值等价性检查和 GPU 性能诊断,支持 MEX、库或 DLL 目标。
主要功能
- 01GPU Coder 编译工作流
- 02对生成的 GPU 代码进行基准测试
- 03检查数值等价性
- 04分析 GPU 性能诊断
- 05应用结构性代码优化
适用场景
- 01加速现有 MATLAB 设计以生成 GPU MEX
- 02排查 GPU Coder 编译失败
- 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.yamlreferences/gpu-codegen-functions.mdscripts/benchmarkMex.mextractDiagnostics.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 步 — 结构优化循环
迭代应用优化。每次迭代:
- 在
sessionDir中创建<designFile>_v<N>.m,加入下一项优化 - 验证与原始版本的数值等价性(多输入 — 参见第 3b 步)
- 在
sessionDir中运行代码生成 — 如果失败,修复问题或回退 - 使用
benchmarkMex对新的 MEX 进行基准测试 — 与当前最佳版本比较 - 将通过验证且速度最快的版本保留为当前最佳版本
MATLAB 级重构模式(无论最终选择哪种 GPU 原语,都能改进代码生成的重写方式):
| 源代码中的模式 | 优化方式 | 对生成的 CUDA 代码的影响 |
|---|---|---|
在不依赖循环变量的循环内使用数组表达式(例如,每次迭代都重新计算 w = weights / sum(weights)) | 将表达式移到循环之前 | 消除冗余的逐线程计算。GPU Coder 并不总能证明数组表达式在循环中保持不变,因此会将其内联到内核中,导致每个线程都重复执行相同的计算。 |
可用隐式扩展替代显式循环(例如,for i=1:N, out(i,:) = A(i,:) + B; end,其中 B 是行向量) | 将循环替换为使用隐式扩展的 out = A + B | GPU 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);
按输出类型设定容差:
| 输出类型 | 容差 |
|---|---|
double | 1e-6 |
single | 1e-3 |
half | 1e-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 并应用有针对性的修复。每轮修复:
- 在
sessionDir中创建<designFile>_v<N+1>.m - 运行代码生成 — 如果失败,则修复或还原
- 验证与原始版本等效(与第 3b 步采用相同的流程 — 使用相同的输入和容差,并与未经修改的函数进行比较)
- 使用
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.