DE

debug-flydsl-kernel

A diagnostic framework for troubleshooting and isolating errors in FlyDSL GPU kernels.

Install

mkdir -p .claude/skills/debug-flydsl-kernel && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/18116" && unzip -o skill.zip -d .claude/skills/debug-flydsl-kernel && rm skill.zip

Installs to .claude/skills/debug-flydsl-kernel

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Debug FlyDSL GPU kernels that produce NaN, inf, wrong results, or crash. Covers cache invalidation, tracing pitfalls (runtime conditionals, range vs range_constexpr), loop-carried state packing, buffer_load addressing, MFMA operand layout verification, LDS bank conflict diagnosis, and systematic error isolation (all-1s test, single-partition test, host-side tensor inspection). Use when a FlyDSL kernel produces incorrect output or compilation errors. Usage: /debug-flydsl-kernel
481 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Clear all FlyDSL and Python-level caches
  • Classify kernel errors by symptom
  • Debug NaN outputs in Softmax calculations
  • Debug all-zeros outputs due to wrong addresses or partition mismatches
  • Debug large mismatches using all-1s isolation tests
  • Debug small errors related to FP8 quantization or scale factors

How it works

The skill systematically debugs FlyDSL kernels by clearing caches, classifying errors, and providing specific debugging steps for issues like NaN, all-zeros output, and compilation errors.

Inputs & outputs

You give it
A FlyDSL kernel producing incorrect output or compilation errors
You get back
Identified root cause and suggested fixes for FlyDSL kernel issues

When to use debug-flydsl-kernel

  • Debug kernel NaN outputs
  • Resolve GPU hangs during compilation
  • Analyze partition errors in compute kernels
  • Inspect host-side tensor values

About this skill

Debug FlyDSL Kernel

Step 0: Clear All Caches (ALWAYS DO THIS FIRST)

FlyDSL aggressively caches compiled kernels. Stale cache is the #1 cause of "my fix didn't work":

rm -rf ~/.flydsl /tmp/flydsl*

Also clear Python-level caches if using @functools.lru_cache:

compile_my_kernel.cache_clear()

Step 1: Classify the Error

SymptomLikely CauseGo to
All NaN outputSoftmax -inf/-inf, division by zero, uninitialized bufferSection 2
All zeros outputWrong output address, uninitialized temp bufferSection 3
Partially wrong (>50% mismatch)Wrong partition count, missing partitions, layout mismatchSection 4
Small errors (1-5% mismatch)FP8 quantization, scale factor, off-by-one maskingSection 5
Compilation error / crashType mismatch, loop-carried state, range vs range_constexprSection 6
GPU hangInfinite loop, deadlock in barrier, OOB memory accessSection 7

2. Debugging NaN

2.1 Softmax NaN: -inf minus -inf

When ALL tokens in a partition are masked (out of context), qk_max = -inf. Then exp(s - qk_max) = exp(-inf - (-inf)) = exp(NaN) = NaN.

Fix: Guard the exp calculation:

safe_diff = (qk_max > NEG_INF).select(diff, ZERO_F)

2.2 Division by zero in normalization

When exp_sum = 0 (all probs zero), 1/exp_sum = inf.

Fix:

safe_sum = (running_sum > ZERO_F).select(running_sum, fx.Float32(1.0))
inv_sum = fx.Float32(1.0) / safe_sum

2.3 Host-side NaN check

Add prints in the Python launch function to check intermediate buffers:

torch.cuda.synchronize()
print(f"exp_sums nan={exp_sums.isnan().sum()}, inf={exp_sums.isinf().sum()}")
print(f"max_logits nan={max_logits.isnan().sum()}, range=[{max_logits.min():.4f}, {max_logits.max():.4f}]")
print(f"temp_out nan={temporary_output.isnan().sum()}")

3. Debugging All-Zeros Output

3.1 Wrong output address

Check stride parameters: if stride_out_seq or stride_out_part is wrong, output writes go to incorrect locations. Print strides:

print(f"out strides: {output.stride()}, temp strides: {temporary_output.stride()}")

3.2 Partition slot mismatch

For multi-partition kernels, verify the output is written to part_z slot (not absolute partition index). The reduce kernel reads from part_z = 0..grid_z-1 slots.

3.3 exp_sums at zero / max_logits at -inf

If the main kernel doesn't write exp_sums/max_logits, the reduce kernel produces zeros. Initialize sentinel values before kernel launch:

exp_sums.fill_(-999.0)  # sentinel
# ... launch kernel ...
torch.cuda.synchronize()
print(f"exp_sums[0,0,0,:4] = {exp_sums[0,0,0,:4]}")  # should NOT be -999

4. Debugging Large Mismatch (>50%)

4.1 Missing partitions

If grid_z < total_partitions and the kernel processes only ONE partition per CTA (no loop), most of the context is skipped. Verify:

total_parts = math.ceil(context_len / KV_COMPUTE_BLOCK)
print(f"grid_z={grid_z}, total_parts={total_parts}")
assert grid_z == total_parts or kernel_has_multi_partition_loop

4.2 All-1s isolation test

Fill query, key_cache, value_cache with 1.0 to eliminate data-dependent bugs:

query.fill_(1.0)
key_cache.fill_(1.0)
value_cache.fill_(1.0)

With uniform input: all softmax probs are equal, PV output = 1.0. Any deviation reveals layout/addressing bugs.

Caveat: All-1s test does NOT catch V/P operand misalignment (since uniform values produce correct results regardless of ordering).

4.3 Single-partition test

Force max_context_partition_num=1 (one_shot mode) to bypass the reduce kernel and test the main kernel in isolation.

4.4 Compare against Gluon

Run both Gluon and FlyDSL on the same input and compare element-wise:

torch.testing.assert_close(flydsl_output, gluon_output, atol=5e-3, rtol=5e-3)

5. Debugging Small Errors (1-5%)

5.1 FP8 probability requantization

FP8 PV MFMA introduces ~0.03 max error vs bf16 reference. This is inherent to the FP8 data path and NOT a bug. Expected tolerance: atol=5e-3.

5.2 Per-tensor vs per-row quantization

If the reference uses per-row Q quantization but FlyDSL uses per-tensor, expect ~1-3% mismatch. Verify quantization mode matches.

5.3 Scale factor mismatch

Verify _scale = softmax_scale * q_scale * k_scale matches the reference. Common bug: applying v_scale twice (once in prob scaling, once after PV).

6. Compilation Errors

6.1 range() vs range_constexpr() inside @flyc.kernel

FlyDSL's AST rewriter converts runtime range() loops into MLIR loops. Use range_constexpr() for compile-time unrolled loops:

# WRONG: i becomes an ArithValue, can't index Python lists
for i in range(4): result[i] = ...

# CORRECT: i is a Python int
for i in range_constexpr(4): result[i] = ...

6.2 Runtime vs compile-time conditionals

Current FlyDSL supports runtime comparisons in Python if; the AST rewriter lowers dynamic conditions to scf.IfOp. Prefer readable DSL operators for runtime SSA values:

tid = gpu.thread_id("x")
lane = tid % fx.Int64(64)
c_zero = fx.Int64(0)
c_limit = fx.Int64(8)

# Runtime condition, lowered to scf.IfOp
if lane == c_zero:
    fx.printf("lane zero")

# Runtime predicate for select
val = (lane < c_limit).select(good_val, zero_val)

Avoid spelling simple integer comparisons as arith.cmpi(arith.CmpIPredicate.slt, lane, c_limit) unless you are manually constructing low-level MLIR. If you pass a condition directly to scf.IfOp, unwrap the DSL boolean:

cond = arith.unwrap(partition_idx >= visible_tile_count)
if_op = scf.IfOp(cond, has_else=False)

Use const_expr(...) only for compile-time decisions:

if const_expr(trans_v):
    ...

Do not use const_expr(lane == 0): even with known_block_size, gpu.thread_id("x"), lane, and warp_id are runtime SSA values. The compiler knows their range, not the current executing lane.

6.3 Loop-carried state packing

Prefer FlyDSL internal types (fx.Int32, fx.Float32, Vector, ArithValue) for loop-carried state. Unwrap only when a low-level helper explicitly requires raw ir.Value:

def _unwrap(v):
    return v.ir_value() if hasattr(v, 'ir_value') else v

init_state = [_unwrap(v) for v in [val1, val2, vec_val]]

Supported state types: f32 (scalar), vector values, i32, i64, index.

6.4 buffer_load type mismatch

buffer_ops.buffer_load(rsrc, offset, vec_width=4, dtype=T.i32) — the offset is in units of dtype. For FP8 data addressed in bytes, divide by element size:

k_addr_bytes = ...  # address in FP8 elements (= bytes for FP8)
k_4xi32 = buffer_ops.buffer_load(k_rsrc, k_addr_bytes // 4, vec_width=4, dtype=T.i32)

6.5 Vector stores require vector values

Vector.store requires the value to be a vector, not scalar:

# WRONG
Vec(scalar_i32).store(lds_ptr, [idx])

# CORRECT
vec = Vec.from_elements([scalar_i32], fx.Int32)
vec.store(lds_ptr, [idx])

7. GPU Hang

7.1 Infinite runtime loop

If loop bounds are wrong (stop < start with unsigned comparison issues, or step=0), the GPU hangs. Verify bounds on host:

print(f"loop: start={part_start}, stop={part_end}, step={cpb}")

7.2 Barrier deadlock

gpu.barrier() requires ALL threads in the workgroup to reach it. If some threads take a different branch (runtime if), the barrier deadlocks. FlyDSL doesn't support divergent barriers.

7.3 Recovery from GPU hang

# Check GPU state
rocm-smi
# If GPU shows 100% usage with no progress, reset:
sudo amdgpu-reset  # or reboot

8. Diagnostic Workflow

1. Clear caches (rm -rf ~/.flydsl)
2. Run with all-1s input → passes? Layout is OK, data issue
3. Run with single partition (one_shot) → passes? Multi-partition/reduce bug
4. Add host-side prints (tensor shapes, strides, NaN checks)
5. Compare intermediate buffers (exp_sums, max_logits, temp_out)
6. If layout bug suspected: trace one thread's addresses manually
   (tid=0: lane16id=0, rowid=0, warp_id=0)
7. For MFMA bugs: verify operand order (K is LHS, Q is RHS for QK)

9. Common Pitfalls Checklist

  • Cleared ~/.flydsl cache after code change
  • range_constexpr() for all compile-time loops (not range())
  • No Python if on runtime GPU values
  • buffer_load offset units match dtype (bytes/4 for i32)
  • Vector stores use Vector values (not scalars)
  • range(..., init=...) state uses internal types, unwrapped only at hard boundaries
  • Output written to correct partition slot (part_z, not absolute index)
  • exp_sums/max_logits strides match actual tensor layout
  • Softmax guards against -inf - (-inf) = NaN
  • Division by zero guarded (select(sum > 0, sum, 1.0))
  • K/V address calculation matches tensor layout (4D vs 5D trans_v)
  • MFMA operand order: mfma(LHS, RHS, acc) — LHS→M, RHS→N

When not to use it

  • When the issue is not a FlyDSL GPU kernel problem
  • When the error is not related to NaN, inf, wrong results, or crashes
  • When not debugging compilation errors or GPU hangs

Limitations

  • Focuses on FlyDSL GPU kernels
  • Does not support divergent barriers in `gpu.barrier()`
  • Requires manual recovery from GPU hang using `sudo amdgpu-reset`

How it compares

This skill offers a structured, step-by-step diagnostic workflow for FlyDSL kernels, which is more efficient than ad-hoc debugging for complex GPU computation issues.

Compared to similar skills

debug-flydsl-kernel side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
debug-flydsl-kernel (this skill)017dReviewAdvanced
benchmark-kernel16moReviewAdvanced
add-cuda-kernel13moReviewAdvanced
nsys-capture01moReviewAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry