| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| - id: precision.bf16_over_fp16_on_mi300x |
| category: precision |
| targets_bucket: precision_path |
| symptom: >- |
| fp16 used on MI300X / CDNA3 matrix cores. Loss can NaN on long fine-tuning |
| runs and there is no throughput benefit over bf16 β the reflexive NVIDIA |
| default carries forward without thinking. |
| detect: |
| precision: fp16 |
| transform: |
| precision: bf16 |
| expected_recovery_fraction: 0.85 |
| expected_impact: >- |
| MI300X CDNA3 matrix cores execute bf16 at the same throughput as fp16 with |
| strictly better numerical stability. Switch removes NaN risk in long runs; |
| no loss-scaling required. |
| rocm_version_min: "6.0" |
| citation: "ROCm MI300X Optimization Guide Β§3.2 β bf16 vs fp16" |
|
|
| - id: precision.fp32_default_wastes_matrix_cores |
| category: precision |
| targets_bucket: precision_path |
| symptom: >- |
| Training in fp32 on MI300X. The CDNA3 matrix cores deliver ~2x throughput |
| in bf16 vs fp32 and the LoRA fine-tuning loss surface tolerates bf16 |
| cleanly β fp32 is simply leaving silicon idle. |
| detect: |
| precision: fp32 |
| transform: |
| precision: bf16 |
| expected_recovery_fraction: 0.6 |
| expected_impact: >- |
| Switching fp32 β bf16 nets roughly 2x matmul throughput on MI300X and |
| halves activation memory, which usually unlocks a larger batch size as a |
| follow-on win. |
| rocm_version_min: "6.0" |
| citation: "AMD MI300X Tuning Guide β Matrix-core throughput by precision" |
|
|
| |
| |
| |
|
|
| - id: attention.flash_rocm_over_eager |
| category: attention |
| targets_bucket: kernel_shape |
| symptom: >- |
| Naive (eager) attention on MI300X β no flash kernel loaded, attention |
| materializes the full O(seq_len^2) matrix. Most MI300X waste at long |
| context lives here. |
| detect: |
| attention_impl: eager |
| transform: |
| attention_impl: flash_rocm |
| expected_recovery_fraction: 0.7 |
| expected_impact: >- |
| Use the ROCm-validated flash-attention kernel (Optimum-AMD ships a tested |
| build). Eliminates the O(seq_len^2) attention memory blow-up; typically |
| 2-3x faster on MI300X for seq_len >= 1024. |
| rocm_version_min: "6.0" |
| citation: "AMD Optimum-AMD docs β Flash Attention 2 validated on MI300" |
|
|
| - id: attention.sdpa_over_eager |
| category: attention |
| targets_bucket: kernel_shape |
| symptom: >- |
| Eager attention used when a drop-in faster path exists. PyTorch SDPA on |
| ROCm dispatches to a fused kernel without requiring the flash-attn fork |
| install β the lowest-friction attention upgrade available. |
| detect: |
| attention_impl: eager |
| transform: |
| attention_impl: sdpa |
| expected_recovery_fraction: 0.45 |
| expected_impact: >- |
| PyTorch's torch.nn.functional.scaled_dot_product_attention on ROCm is a |
| fused, memory-efficient backend with no extra dependency. Slower than |
| flash-attn ROCm but a one-line config change with no install pain. |
| rocm_version_min: "6.0" |
| citation: "PyTorch SDPA backend selection β ROCm support notes" |
|
|
| |
| |
| |
|
|
| - id: memory.batch_too_small_for_192gb |
| category: memory |
| targets_bucket: memory_headroom |
| symptom: >- |
| HBM peak is far below MI300X's 192 GB ceiling β the user copied a batch |
| size from an A100 80GB tutorial and is leaving more than half the HBM |
| idle every step. |
| detect: |
| batch_size: {"$lt": 8} |
| transform: |
| batch_size: 12 |
| expected_recovery_fraction: 0.55 |
| expected_impact: >- |
| Raising batch size to fill more of the 192 GB HBM increases tokens/sec |
| proportionally for compute-bound LoRA workloads. Adjust grad-accum down |
| to keep effective batch unchanged if needed. |
| rocm_version_min: "6.0" |
| citation: "AMD Instinct MI300X β 192 GB HBM3 fine-tuning playbook" |
|
|
| - id: memory.gradient_checkpointing_for_long_seq |
| category: memory |
| targets_bucket: memory_headroom |
| symptom: >- |
| Long sequences (seq_len >= 4096) without gradient checkpointing are |
| spending HBM on activations that could be recomputed instead β leaving |
| no headroom to grow the batch. |
| detect: |
| seq_len: {"$gte": 4096} |
| gradient_checkpointing: false |
| transform: |
| gradient_checkpointing: true |
| expected_recovery_fraction: 0.4 |
| expected_impact: >- |
| Enables freeing activation memory to enlarge batch size at long context; |
| typical net win 1.3-1.7x throughput at seq_len 4k-8k on MI300X despite |
| the recompute tax. |
| rocm_version_min: "6.0" |
| citation: "HF Transformers gradient_checkpointing + AMD long-context guide" |
|
|
| |
| |
| |
|
|
| - id: data.dataloader_workers_zero |
| category: data |
| targets_bucket: data_wait |
| symptom: >- |
| DataLoader num_workers=0 β the GPU stalls while the main process |
| tokenizes the next batch. Classic MI300X starvation pattern at |
| high-throughput LoRA fine-tuning. |
| detect: |
| dataloader_workers: 0 |
| transform: |
| dataloader_workers: 8 |
| expected_recovery_fraction: 0.8 |
| expected_impact: >- |
| Spinning up 4-8 worker processes hides tokenization and host transfer |
| behind GPU compute. On MI300X with a fast NVMe dataset, this often nets |
| 2x tokens/sec on its own. |
| rocm_version_min: "6.0" |
| citation: "PyTorch DataLoader best practices + MI300X fine-tuning notes" |
|
|
| - id: data.pin_memory_false |
| category: data |
| targets_bucket: data_wait |
| symptom: >- |
| DataLoader pin_memory=False on a CUDA/ROCm host. Each batch copy from |
| pageable host memory blocks the launch queue instead of overlapping with |
| compute. |
| detect: |
| dataloader_pin_memory: false |
| transform: |
| dataloader_pin_memory: true |
| expected_recovery_fraction: 0.5 |
| expected_impact: >- |
| pin_memory=True allows asynchronous host-to-device transfer that |
| overlaps with the previous step's compute. Roughly free win, no other |
| config changes needed. |
| rocm_version_min: "6.0" |
| citation: "PyTorch CUDA/ROCm DataLoader pin_memory documentation" |
|
|
| - id: data.prefetch_factor_default |
| category: data |
| targets_bucket: data_wait |
| symptom: >- |
| DataLoader prefetch_factor left at the default of 2 while MI300X is |
| fast enough to drain the queue and stall before the next batch is |
| ready. Symptom: GPU util oscillates 100% β 30% in a sawtooth. |
| detect: |
| dataloader_prefetch_factor: {"$lte": 2} |
| transform: |
| dataloader_prefetch_factor: 4 |
| expected_recovery_fraction: 0.3 |
| expected_impact: >- |
| Raising prefetch_factor to 4 keeps two extra batches buffered per |
| worker, smoothing out the GPU-util sawtooth. Costs a few hundred MB of |
| host RAM per worker; trivial on a typical MI300X box. |
| rocm_version_min: "6.0" |
| citation: "PyTorch DataLoader prefetch_factor β long-step jitter notes" |
|
|
| - id: data.persistent_workers_false |
| category: data |
| targets_bucket: data_wait |
| symptom: >- |
| DataLoader persistent_workers=False β workers are torn down and |
| respawned every epoch. On large datasets the warmup tax bleeds time |
| between epochs even when the per-epoch throughput is good. |
| detect: |
| dataloader_persistent_workers: false |
| transform: |
| dataloader_persistent_workers: true |
| expected_recovery_fraction: 0.25 |
| expected_impact: >- |
| Keeps DataLoader workers alive across epochs so the dataset doesn't |
| re-initialize. Removes a 5-30 second hitch at every epoch boundary on |
| Qwen2.5 LoRA workloads. |
| rocm_version_min: "6.0" |
| citation: "PyTorch DataLoader persistent_workers documentation" |
|
|
| |
| |
| |
|
|
| - id: compile.torch_compile_off |
| category: compile |
| targets_bucket: host_gap |
| symptom: >- |
| torch.compile is disabled but the model is on the eager-mode-friendly |
| list (Qwen, Llama, Mistral, Gemma). Eager dispatch leaves host-gap |
| bubbles between MI300X kernels that compile would erase. |
| detect: |
| torch_compile: false |
| transform: |
| torch_compile: true |
| expected_recovery_fraction: 0.6 |
| expected_impact: >- |
| torch.compile fuses small ops and removes Python dispatch overhead; |
| typical 1.1-1.3x throughput on Qwen-class fine-tuning on MI300X. Verify |
| no graph breaks in your forward by running with TORCH_LOGS=graph_breaks. |
| rocm_version_min: "6.0" |
| citation: "PyTorch torch.compile + ROCm support matrix" |
|
|
| |
| |
| |
|
|
| - id: env.nccl_min_nchannels |
| category: env_vars |
| targets_bucket: comm_excess |
| symptom: >- |
| NCCL_MIN_NCHANNELS not set on a collective-heavy training run. RCCL on |
| MI300X benefits from a larger channel floor than the upstream NCCL |
| default to saturate the XGMI mesh. |
| detect: {} |
| transform: |
| env_vars.NCCL_MIN_NCHANNELS: "112" |
| expected_recovery_fraction: 0.35 |
| expected_impact: >- |
| Setting NCCL_MIN_NCHANNELS=112 forces RCCL to use enough channels to |
| saturate MI300X XGMI bandwidth on all-reduce. Helps anywhere RCCL/NCCL |
| appears in the top kernels list. |
| rocm_version_min: "6.0" |
| citation: "AMD ROCm RCCL tuning guide β NCCL_MIN_NCHANNELS recommendation" |
|
|
| - id: env.numa_auto_balancing_disable |
| category: env_vars |
| targets_bucket: host_gap |
| symptom: >- |
| Linux NUMA auto-balancing left enabled β the kernel migrates pinned |
| pages between NUMA nodes during the run, causing variance and |
| occasional multi-second stalls in benchmark numbers. |
| detect: {} |
| transform: |
| env_vars.GOBLIN_HINT_NUMA_AUTO_BALANCING: "disable_via_sysctl" |
| expected_recovery_fraction: 0.15 |
| expected_impact: >- |
| Disable with `echo 0 | sudo tee /proc/sys/kernel/numa_balancing` before |
| benchmarking. Removes a major source of step-time variance on MI300X |
| multi-socket boxes; required for reproducible before/after numbers. |
| rocm_version_min: "6.0" |
| citation: "AMD ROCm performance tuning β NUMA auto-balancing disable" |
|
|
| - id: env.hsa_force_fine_grain_pcie |
| category: env_vars |
| targets_bucket: host_gap |
| symptom: >- |
| HSA_FORCE_FINE_GRAIN_PCIE not set. Fine-grained PCIe transfers reduce |
| hostβdevice latency for the small control packets that bracket every |
| kernel launch. |
| detect: {} |
| transform: |
| env_vars.HSA_FORCE_FINE_GRAIN_PCIE: "1" |
| expected_recovery_fraction: 0.15 |
| expected_impact: >- |
| Enables fine-grained PCIe coherent traffic on MI300X. Small but |
| consistent host-gap reduction; no downside on supported platforms. |
| rocm_version_min: "6.0" |
| citation: "AMD ROCm env var reference β HSA_FORCE_FINE_GRAIN_PCIE" |
|
|
| |
| |
| |
|
|
| - id: kernels.hipblaslt_hint_logging |
| category: kernels |
| targets_bucket: kernel_shape |
| symptom: >- |
| Stable model + batch + seq shapes with hipBLASLt heuristic logging |
| disabled. Each step is solving a tile-selection problem the library |
| already solved; an offline-tuned hint file would eliminate the search. |
| detect: {} |
| transform: |
| env_vars.HIPBLASLT_TUNING_FILE: "/tmp/hipblaslt.tuning" |
| env_vars.HIPBLASLT_LOG_LEVEL: "1" |
| expected_recovery_fraction: 0.3 |
| expected_impact: >- |
| Run once with HIPBLASLT_LOG_LEVEL=1 to capture the best tile heuristic |
| per GEMM shape, then use HIPBLASLT_TUNING_FILE to reuse it. Typical |
| win: 5-15% on long-running fine-tunes with stable shapes. |
| rocm_version_min: "6.0" |
| citation: "AMD hipBLASLt tuning + offline-hint workflow" |
|
|
| - id: kernels.miopen_find_mode_2 |
| category: kernels |
| targets_bucket: kernel_shape |
| symptom: >- |
| MIOPEN_FIND_MODE not set. MIOpen falls back to a slow exhaustive search |
| on the first iteration of every new shape; with FIND_MODE=2 the cached |
| fast solution is reused immediately. |
| detect: {} |
| transform: |
| env_vars.MIOPEN_FIND_MODE: "2" |
| expected_recovery_fraction: 0.25 |
| expected_impact: >- |
| MIOPEN_FIND_MODE=2 (FAST + DB lookup) eliminates the cold-start search |
| pause on conv/attention layers. First-step time drops dramatically; |
| steady-state perf is unchanged but warmup is cheaper. |
| rocm_version_min: "6.0" |
| citation: "AMD MIOpen documentation β MIOPEN_FIND_MODE values" |
|
|
| |
| |
| |
|
|
| - id: optimizer.bitsandbytes_not_supported_warning |
| category: optimizer |
| targets_bucket: kernel_shape |
| symptom: >- |
| bitsandbytes 8-bit Adam variant configured. bitsandbytes is NOT |
| officially supported on ROCm β it may import on a community build but |
| correctness on MI300X is unverified, and AMD-validated 8-bit/quant |
| paths exist instead. |
| detect: |
| optimizer: {"$regex": "(?i)(bitsandbytes|bnb_)"} |
| transform: {} |
| expected_recovery_fraction: 0.0 |
| expected_impact: >- |
| WARNING: bitsandbytes is not officially supported on ROCm. Recommended |
| alternatives are Optimum-AMD-validated paths (GPTQ/AWQ for inference, |
| 8-bit Adam via Apex on ROCm, or DeepSpeed ZeRO-Offload). Do not assume |
| correctness on MI300X without your own numerical check. |
| rocm_version_min: "6.0" |
| citation: "AMD Optimum-AMD compatibility matrix β bitsandbytes status on ROCm" |
|
|
| |
| |
| |
|
|
| - id: collectives.one_process_per_gpu |
| category: collectives |
| targets_bucket: comm_excess |
| symptom: >- |
| Single launcher process driving multiple MI300X GPUs. ROCm serializes |
| kernel launches across devices when one process owns multiple HIP |
| streams β every collective becomes a bottleneck. |
| detect: |
| extras.launch_num_processes: 1 |
| extras.world_size: {"$gt": 1} |
| transform: |
| extras.launch_num_processes: 8 |
| expected_recovery_fraction: 0.6 |
| expected_impact: >- |
| Use `accelerate launch --num_processes=N` (or `torchrun --nproc_per_node=N`) |
| so each MI300X has its own driver process. Removes the launch-queue |
| serialization; most multi-GPU "ROCm is slow" reports trace back to this. |
| rocm_version_min: "6.0" |
| citation: "AMD ROCm distributed training β one-process-per-GPU best practice" |
|
|
| |
| |
| |
|
|
| - id: topology.tp_within_xgmi_island |
| category: topology |
| targets_bucket: comm_excess |
| symptom: >- |
| Tensor parallelism enabled (TP > 1) without pinning ranks to an XGMI |
| island. TP all-reduces hop across the slow inter-island link instead of |
| the fast intra-island XGMI mesh. |
| detect: |
| extras.tensor_parallel_size: {"$gt": 1} |
| transform: |
| extras.tp_placement_hint: "xgmi_island" |
| expected_recovery_fraction: 0.45 |
| expected_impact: >- |
| Restrict TP rank placement to a single 4-GPU XGMI island per group so |
| all-reduces stay on intra-island links. Use ROCR_VISIBLE_DEVICES to pin; |
| prefer TP over EP on a single MI300X node. |
| rocm_version_min: "6.0" |
| citation: "AMD MI300X system topology β XGMI islands and TP placement" |
|
|
| |
| |
| |
|
|
| - id: precision.fp8_inference_stretch |
| category: precision |
| targets_bucket: precision_path |
| symptom: >- |
| Workload is inference / batched eval on MI300X without FP8. CDNA3 has |
| native FP8 matrix-core throughput that doubles peak vs bf16 β applicable |
| once the model has been calibrated. |
| detect: |
| extras.workload_kind: "inference" |
| transform: |
| precision: fp8 |
| expected_recovery_fraction: 0.4 |
| expected_impact: >- |
| Switch calibrated inference to FP8 (E4M3 weight, E5M2 grad if needed). |
| Doubles matmul peak throughput on MI300X. Requires offline calibration; |
| verify accuracy on your eval set first. |
| rocm_version_min: "6.0" |
| citation: "AMD MI300X inference guide β FP8 calibration workflow" |
|
|
| - id: kernels.hipblaslt_log_level_hint |
| category: kernels |
| targets_bucket: kernel_shape |
| symptom: >- |
| Top kernels show a long tail of small unfused GEMMs. Without |
| HIPBLASLT_LOG_MASK enabled the user can't see which heuristics were |
| selected and won't notice when the library falls back to a slow path. |
| detect: {} |
| transform: |
| env_vars.HIPBLASLT_LOG_MASK: "32" |
| expected_recovery_fraction: 0.1 |
| expected_impact: >- |
| HIPBLASLT_LOG_MASK=32 enables heuristic-selection logging. Diagnostic |
| only β does not change perf β but lets you confirm hipBLASLt picked the |
| expected tile and switch to the offline tuning workflow when it didn't. |
| rocm_version_min: "6.0" |
| citation: "AMD hipBLASLt logging environment variables" |
|
|
| - id: env.miopen_user_db_path |
| category: env_vars |
| targets_bucket: kernel_shape |
| symptom: >- |
| Multiple training runs share a host but MIOpen's per-user perf DB is on |
| the default ~/.config path. First run on a new host eats the autotune |
| cost every time even though a previous run already solved the same |
| shapes. |
| detect: {} |
| transform: |
| env_vars.MIOPEN_USER_DB_PATH: "/var/cache/miopen" |
| env_vars.MIOPEN_CUSTOM_CACHE_DIR: "/var/cache/miopen" |
| expected_recovery_fraction: 0.1 |
| expected_impact: >- |
| Point MIOPEN_USER_DB_PATH at a shared host directory so autotune |
| results survive container restarts and are reused across runs. |
| Eliminates first-step warm-up cost after the first build. |
| rocm_version_min: "6.0" |
| citation: "AMD MIOpen perf-db environment variables" |
|
|