The flags that move the needle on AI/ML workloads are architecture-targeting flags (-march, -mtune) and vectorization flags (-ftree-vectorize, plus the vectorization implied by -O3) — they change which SIMD instructions the compiler emits for your numeric kernels. Diagnostic and warning flags like -Wall, -Wextra, or -pedantic change nothing at runtime; they only affect what the compiler prints at build time. Why do only some flags affect measured throughput? AI inference and training kernels are dominated by dense floating-point loops: matmuls, convolutions reduced to GEMM, element-wise activations. Their throughput is bounded by how many multiply-accumulate operations the CPU retires per cycle, and that depends directly on the width and type of SIMD instructions the compiler generates. -march=native tells GCC to emit instructions for every extension the build machine advertises — AVX2, AVX-512, FMA on x86; NEON or SVE on ARM. -ftree-vectorize (on by default at -O3) lets the auto-vectorizer pack those scalar loop iterations into vector lanes. When both align, a hot loop that was issuing one FMA per iteration starts issuing eight or sixteen. -mtune does not change the instruction set but reorders and schedules instructions for a specific microarchitecture’s pipeline, which in the porting assessments we run shows up as single-digit percentage gains (an observed pattern across profiling engagements, not a fixed benchmark figure) on branchy or latency-bound code. Contrast that with -Wall. It emits warnings. The generated machine code is byte-identical with and without it. If you benchmark a workload and see -Wall “improve” throughput, you measured noise. Keep the taxonomy clear when you report results: flags either alter emitted instructions or they don’t, and only the first category belongs in a performance regression. Does a flag that helped on x86 carry over to an ARM target? No, and this is the single most expensive assumption in a porting effort. A flag validated on one target architecture does not transfer its measured gain to another. -march=native on an x86 build server produces a binary that will not even run correctly on an ARM target — the instruction encodings are incompatible, so you get an illegal-instruction trap, not a slower binary. Even within a single ISA family, native bakes in whatever the build host happens to support. Build on a Skylake-X machine with AVX-512 and deploy to a Cascade Lake node with a different AVX-512 frequency-throttling profile, and your “optimal” flag set can regress. Cross-compile for ARM and -march=native is meaningless because it resolves against the build host, not the deploy host. The practical consequence: you re-measure flags per target. You do not carry forward the flag set from the origin platform as if it were portable configuration. Treat the origin flag set as a starting hypothesis, then validate empirically on each target before it ships. Which flags belong in a porting benchmark sweep? The flags worth sweeping cluster into a small, high-signal group. Everything else is either default-implied or performance-neutral. Flag What it changes Measurable on AI kernels? -O2 vs -O3 -O3 enables extra vectorization and loop transforms Yes — often the biggest single jump -march=<target> ISA extensions emitted (AVX2/512, NEON, SVE) Yes — large on SIMD-bound loops -mtune=<uarch> Instruction scheduling, no new instructions Small but real (2–8% observed range, not a fixed benchmark) -ftree-vectorize Auto-vectorization (implied by -O3) Yes, when loops are vectorizable -ffast-math Relaxes IEEE FP, allows reassociation Yes, but changes numeric results -funroll-loops Loop unrolling Workload-dependent, sometimes negative -Wall / -Wextra Warning output only No -g Debug symbols No runtime effect -ffast-math deserves a warning: it can materially raise throughput on reduction-heavy code by allowing the compiler to reassociate floating-point adds, but it changes numerical results. On an ML workload that means your accuracy or convergence can shift. Never enable it without a numeric-equivalence check against the reference build. What does a defensible per-target flag validation look like? Run this checklist for each target architecture, not once for the whole port: Confirm the target ISA and microarchitecture, and set -march/-mtune explicitly to the deploy target — not native — when cross-compiling. Build a baseline at -O2 with no arch flags to establish a floor. Add -O3 -march=<target> and measure; this captures most of the vectorization gain. Sweep -mtune, -funroll-loops, and -ffast-math individually, one variable at a time. Verify the binary actually runs on the target hardware (not an emulator with different SIMD behavior) before trusting numbers. Validate numeric equivalence against the reference build after any -ffast-math or reassociation change. Pin the winning flag set per target in your build system, tagged with the microarchitecture it was validated on. Re-run the sweep whenever you change target hardware, GCC major version, or the kernel library underneath. The step most teams skip is the per-target re-measurement — they treat a flag set as portable config and copy it into every build target. That is exactly the failure mode that produces illegal-instruction crashes or silent regressions in production. When we run a performance and porting assessment, flag validation is one of the cheapest checks in the whole engagement, and it comes early precisely because it can rule out a costly rewrite before one is committed. See what a performance and porting assessment tells you before you commit for how flag validation fits alongside profiling, kernel-library selection, and target hardware characterization. For what a hands-on assessment engagement actually delivers end to end, see what a performance and porting assessment engagement actually delivers, part of our GPU and runtime engineering work. Frequently Asked Questions Does -O3 always beat -O2 for AI workloads? Usually, because -O3 enables auto-vectorization and loop transforms that map well to dense numeric kernels. But not always — -O3 can bloat code and hurt instruction-cache behavior on some workloads. Measure both on your actual kernels rather than assuming. Is -march=native safe to use in production builds? Only when the build host and deploy host are the same microarchitecture. -march=native bakes in the build machine’s exact ISA extensions, so a binary built on one CPU can fail with illegal-instruction traps on another, and cross-compiled builds resolve native against the wrong host entirely. Specify the deploy target explicitly instead. Why doesn’t -Wall affect performance? -Wall only controls compiler diagnostic output at build time; it does not change the generated machine code. Any throughput difference you observe when toggling it is measurement noise, not a real effect. Should I ever use -ffast-math on ML models? Cautiously, and only with a numeric-equivalence check. It can raise throughput by allowing floating-point reassociation, but it also changes results, which can shift model accuracy or convergence. Validate against a reference build before shipping it.