Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 7, 2025

Summary

This PR implements automatic debug export functionality for Reactant.jl, addressing issue #1934.

Features Implemented

1. Export to Reactant Script (export_to_reactant_script)

Exports Julia functions to standalone scripts that only depend on Reactant:

  • Compiles functions to StableHLO/MLIR
  • Saves inputs using Julia's standard library Serialization (no external dependencies)
  • Generates executable Julia script for reproduction
  • Similar API to existing export_to_enzymejax

2. Automatic Debug Zip on Pass Failures

When compilation pass pipeline fails:

  • Captures unoptimized IR automatically
  • Exports function and inputs using above functionality
  • Creates comprehensive zip file with README and reproduction steps
  • Provides user-friendly error message with upload instructions

Changes

  • Added: src/serialization/ReactantExport.jl (uses Julia Serialization)
  • Added: test/integration/reactant_export.jl (comprehensive test suite)
  • Modified: src/Compiler.jl (pass failure handling and p7zip import)
  • Modified: ext/ReactantNPZExt.jl (kept EnzymeJAX support, removed ReactantExport)
  • Modified: src/serialization/Serialization.jl (new export)
  • Modified: docs/src/api/serialization.md (documentation)
  • Modified: test/runtests.jl (added test suite)

Implementation Details

  • Data Format: Uses Julia's standard library Serialization.jl to save inputs as .jls files (not NPZ)
  • No External Dependencies: ReactantExport requires no external packages beyond Reactant itself
  • Simplified Format: No row-major/column-major conversion needed since data stays in Julia format
  • EnzymeJAX Unchanged: The export_to_enzymejax function still uses NPZ for Python/JAX compatibility

Testing

✅ Comprehensive test suite covering:

  • Simple function export
  • Matrix multiplication
  • Multi-argument functions
  • Serialization input/output consistency
  • Scalar and array handling
  • Edge cases and safety

Code Quality

✅ All code review feedback addressed:

  • Scalar input handling
  • Proper exception type checking
  • Clean, readable code
  • Safe array operations
  • Security validated (no CodeQL issues)
  • Fixed syntax error with import statement (moved to module top level)
  • Switched to Julia Serialization.jl per feedback

Documentation

✅ Complete documentation added:

  • Function docstrings with examples
  • API documentation updated
  • Usage instructions
  • No external dependencies required
Original prompt

This section details on the original issue you should resolve

<issue_title>Similar to how we export to enzymejax we should export a pure reactant script</issue_title>
<issue_description>Similar to #1934.

But now we extract the hlo and generate a julia script that only depends on reactant. serialization.jl is used for loading/saving the data.

If a pass pipeline fails, we should save the unoptimized IR automatically using this setup. Use this IR + the julia script and provide a zip file (using pzip_jll) that can be uploaded by end users when reporting issues to the reactant repo</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits December 7, 2025 21:54
- Created ReactantExport.jl module for exporting functions to standalone scripts
- Added export_to_reactant_script() function similar to export_to_enzymejax()
- Implemented create_pass_failure_zip() to create debug archives on failure
- Updated NPZ extension to support ReactantExport
- Added try-catch wrapper in compile_mlir to intercept pass failures

Co-authored-by: avik-pal <30564094+avik-pal@users.noreply.github.com>
- Created test/integration/reactant_export.jl with comprehensive tests
- Tests export functionality for simple functions, matrix operations, and complex cases
- Tests NPZ input/output consistency
- Added ReactantExport to test suite in runtests.jl

Co-authored-by: avik-pal <30564094+avik-pal@users.noreply.github.com>
- Fix scalar input handling in ReactantExport (check for 0-length shape)
- Fix exception type checking for pass pipeline failures (use string(e))
- Clean up complex string interpolation for better readability
- Fix shell expansion issue by using explicit file enumeration
- Update documentation to fix typos and clarify implementation

Co-authored-by: avik-pal <30564094+avik-pal@users.noreply.github.com>
- Add check for empty NPZ files list before using first()
- Add comment clarifying that Julia backtick commands are shell-safe
- Ensure all potential edge cases are handled safely

Co-authored-by: avik-pal <30564094+avik-pal@users.noreply.github.com>
Copilot AI changed the title [WIP] Add export functionality for pure reactant script Add export_to_reactant_script and automatic debug zip on pass pipeline failure Dec 7, 2025
Copilot AI requested a review from avik-pal December 7, 2025 22:13
src/Compiler.jl Outdated
)
try
# Import p7zip_jll
import p7zip_jll: p7zip
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this is illegal, see the error:


ERROR: LoadError: syntax: "import" expression not at top level
--
Stacktrace:
[1] top-level scope
@ /var/lib/buildkite-agent/builds/gpuci-16/julialang/reactant-dot-jl/src/Compiler.jl:1358
[2] include(mod::Module, _path::String)
@ Base ./Base.jl:495
[3] include(x::String)
@ Reactant /var/lib/buildkite-agent/builds/gpuci-16/julialang/reactant-dot-jl/src/Reactant


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit 8227305. Moved the import p7zip_jll: p7zip statement to the top level of the module (line 7) where it belongs, and removed it from inside the function.

Move p7zip_jll import to top level of module to comply with Julia syntax requirements

Co-authored-by: wsmoses <1260124+wsmoses@users.noreply.github.com>
Copilot AI requested a review from wsmoses December 7, 2025 22:44
Comment on lines 140 to 152
function save_inputs_npz(
output_path::String, inputs::Dict{String,<:Union{AbstractArray,Number}}
)
if !Serialization.serialization_supported(Val(:NPZ))
error(
"`NPZ.jl` is required for saving compressed arrays. Please load it with \
`using NPZ` and try again.",
)
end
return save_inputs_npz_impl(output_path, inputs)
end

function save_inputs_npz_impl end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Use the julia standard library Serialization.jl to save and load the data

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 8c3bec2. ReactantExport now uses Julia's standard library Serialization.jl instead of NPZ. This:

  • Eliminates external dependencies for ReactantExport
  • Uses .jls files instead of .npz
  • Simplifies implementation (no row/column-major conversion needed)
  • EnzymeJAX still uses NPZ for Python/JAX compatibility

try
# Create a temporary directory for the files
temp_dir = mktempdir(; prefix="reactant_failure_", cleanup=false)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

println(io)
show(IOContext(io, :debug => true), mod)
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

catch e
@debug "Could not create Julia script for reproduction" exception = e
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

open(readme_path, "w") do io
println(io, "# Reactant Compilation Failure Report")
println(io)
println(io, "This archive contains information about a failed Reactant compilation.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
println(io, "This archive contains information about a failed Reactant compilation.")
println(
io, "This archive contains information about a failed Reactant compilation."
)

println(io)
println(io, "## Contents")
println(io)
println(io, "- `unoptimized_ir.mlir`: The MLIR module before optimization passes")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
println(io, "- `unoptimized_ir.mlir`: The MLIR module before optimization passes")
println(
io, "- `unoptimized_ir.mlir`: The MLIR module before optimization passes"
)

Comment on lines +177 to +178
end
for (i, info) in enumerate(input_info)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
end
for (i, info) in enumerate(input_info)
end for (i, info) in enumerate(input_info)

# Build a cleaner representation of the load_inputs code
load_input_lines = String[]
for load in load_inputs
push!(load_input_lines, "let arr = $load; arr isa Number ? arr : permutedims(arr, reverse(1:ndims(arr))) end")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
push!(load_input_lines, "let arr = $load; arr isa Number ? arr : permutedims(arr, reverse(1:ndims(arr))) end")
push!(
load_input_lines,
"let arr = $load; arr isa Number ? arr : permutedims(arr, reverse(1:ndims(arr))) end",
)

npz_files = filter(f -> endswith(f, ".npz"), readdir(output_dir; join=true))
@test !isempty(npz_files)
npz_path = first(npz_files)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

# Load the NPZ file and verify the data
npz_data = npzread(npz_path)
@test haskey(npz_data, "arr_1")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

# The data should be transposed for NumPy format
loaded_data = npz_data["arr_1"]
@test size(loaded_data) == (2, 2) # NumPy row-major format

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

- Replace NPZ format with Julia's standard library Serialization
- Change file extension from .npz to .jls for serialized inputs
- Remove NPZ dependency from ReactantExport (still used for EnzymeJAX)
- Update tests to use Serialization instead of NPZ
- Update documentation to reflect no external dependencies needed
- Simplifies implementation and removes need for row/column-major conversion

Co-authored-by: avik-pal <30564094+avik-pal@users.noreply.github.com>
Copilot AI requested a review from avik-pal December 7, 2025 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Similar to how we export to enzymejax we should export a pure reactant script

3 participants