Skip to content

pnnx support torch exported program - #6933

Open
mingshi2333 wants to merge 49 commits into
Tencent:masterfrom
mingshi2333:pnnx-exported-program
Open

mingshi2333 wants to merge 49 commits into
Tencent:masterfrom
mingshi2333:pnnx-exported-program

Conversation

@mingshi2333

@mingshi2333 mingshi2333 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Add direct torch.export.ExportedProgram (.pt2) input support to pnnx.

Closes #6366

Usage

program = torch.export.export(model.eval(), example_inputs)
torch.export.save(program, "model.pt2")
pnnx model.pt2
python -c 'import model_pnnx; model_pnnx.export_exported_program()'

Shapes, parameters, buffers and constants come from the archive; lifted state does not become runtime input. Optional inputshape/input overrides are checked against the input contract. The generated export_exported_program(example_inputs=None) saves model_pnnx.pt2 and returns its ExportedProgram. It exports the generated inference model, not the original Module's training/state identity.

Structure

PT2 archive -> JSON -> typed ExportedProgram
                       | graph normalization
                       | tensor materialization
                       | Dispatcher argument binding
                       v
                    PNNX IR -> existing passes -> Python / ncnn
Files under tools/pnnx/src/ Responsibility
model_format.*, pt2_archive.*, storezip.* Content-based detection, PT2 layout, and bounded entry access using the existing ZIP reader, extended for central-directory/data-descriptor/ZIP64 metadata.
json_reader.*, exported_program_schema.* JSON syntax and supported versioned fields, including graph signatures, arguments, tensor metadata, basic symbolic dimensions and tuple/list PyTrees.
exported_program_graph.* Normalize supported captured graphs and serialized arguments without LibTorch or PNNX graph construction.
exported_program_operator.* Isolate the already-linked LibTorch Dispatcher: overloads, argument/default binding and mutation/alias metadata. Interfaces use plain typed structures.
exported_program_tensor.* Materialize raw state from dtype, sizes, strides, offsets and byte order.
load_exported_program.* Connect the stages, validate representability and observable writes, and emit PNNX inputs/attributes/operators/outputs.
main.cpp, ir.*, shared passes and save_ncnn.* Input validation, dynamic metadata and re-export, shared operator lowering/functionization, and native dtype/state/output handling.

No third-party JSON/ZIP dependency or LibTorch internal PT2 serializer is added. The frontend reuses the existing PNNX/backend passes rather than maintaining a separate PT2 lowering pipeline.

Supported scope

  • One ExportedProgram in PT2 archive version 0, with raw tensor payloads. Consumed entries use STORE; unused compressed attachments are ignored. Bounds/CRC are checked and encrypted entries are rejected.
  • The current schema contract is PyTorch 2.13 schema 8.20. Existing raw-payload paths for minors 8.14, 8.15 and 8.17 remain accepted without a continuously tested multi-version guarantee. The serialized ATen opset must match the linked LibTorch opset; this is not an operator upgrader.
  • Static inference, statically resolved symbolic arguments, and basic named dynamic input dimensions. Ranges, shared symbols, aten.sym_size.int and supported mixed constant/symbolic shape lists survive PNNX save/load and re-export. Generated Python checks input ranks, static dimensions, ranges and shared dimensions, following PyTorch's 0/1 convention for a lower bound at most 2.
  • Protocol-1 positional tensor input trees made of tuples/lists, flattened at the PNNX boundary. PNNX Python reconstructs tensor/tuple/list outputs; native lowering preserves flattened leaf order at optlevel=0/1/2.
  • Parameters, persistent/non-persistent buffers and constants, including strided/offset/shared-storage payloads and integer, floating, complex, Bool and BF16 state. PNNX Python preserves imported state dtype and supports empty state.
  • ATen targets whose Dispatcher arguments and resulting graphs are representable by existing PNNX passes, plus supported TorchVision forms. Disabled grad/autocast wrappers with tensor-only captured graphs are normalized.
  • Generated-model PT2 re-export and numerical round trips, including IR reload and multiple dynamic input sizes.

Shared passes handle the ATen forms exposed by PT2, including convolution/transposed convolution, normalization, recurrent operators, static weight norm, windows, typed empty lists and singleton unpacking. These changes remain separate from archive parsing. optlevel=0 still runs the existing scalar/expression lowering required for valid PNNX Python, without enabling the full optional optimization passes.

Correctness boundaries

  • No legacy pickled-payload PT2, unlisted schema versions, AOTInductor-only/multi-program packages or decompression of consumed entries.
  • No derived symbolic expressions (2*s0, s0*s1), data-dependent dimensions, dynamic scalar arithmetic/state, keyword user inputs, dict/custom PyTrees, non-tensor user leaves or general control flow.
  • No training/state-identity round-trip contract. Original parameter/buffer registration, persistence, requires_grad and state_dict keys are not retained as original Module identity.
  • External input/state writes are rejected. Local updates are limited to alias chains supported by existing slice/select/view functionization; supported view writes restore the root shape. Unsynchronizable live cross-chain aliases, including repeated writes through an earlier in-place result, fail explicitly.
  • PNNX high-precision tensor payloads do not imply f64/c128 scalar/Expression fidelity. Scalar parameters remain float; detected high-precision narrowing is diagnosed.
  • Native ncnn support is narrower than PNNX import/Python support. Native lowering converts Half/Double/BF16/Byte/Char/Short state to Float. Int/Long constant arithmetic branches are converted numerically through supported layout chains while shared index branches retain integer storage. This does not add general typed-integer or BF16 arithmetic, or preserve float64 precision. Shared BF16 Attribute reads/writes also support the earlier BatchNorm-fusion stage.
  • Empty/complex native attributes fail with lower ncnn failed: after PNNX artifacts are saved and before native artifacts are written. Generated native Python rejects unsupported Double/Byte/Char/Short/Bool/BF16/complex/scalar tensor inputs. Native execution has its own operator/batch-layout limits and does not enforce PT2 range guards.
  • Passing a PNNX operator test is not a claim of native support for that graph. In particular, the current PT2 spectrogram path is not covered as working end-to-end native STFT/iSTFT deployment.

Detection, import and residual-operator failures have separate diagnostics. Recognized PT2 archives are not retried as TorchScript. Unsupported native state is tested for an ordinary nonzero exit, retained PNNX artifacts and values, and absent native output in a fresh directory.

Tests and CI

Existing model/operator definitions and numerical assertions are reused. TorchScript and PT2 run in separate processes with separate artifact names; there is no monkeypatch of torch.jit.trace, removal of weight-norm parametrizations, tolerance relaxation or new expected skip in this update. Expected failures require a matching stage/diagnostic; crashes and unexpected successes fail the test.

CTest label Coverage
pt2_frontend Real-archive import, malformed/unsupported inputs, generated-model/dynamic round trips, C++ IR/Attribute/ZIP tests and helper self-tests.
pt2_operator Existing operator/model PT2 numerical tests and the native subset below.
pt2_ncnn Actual native execution: existing ResNet18, Conv2d, Linear, LayerNorm, BatchNorm2d and Embedding fixtures, plus attribute and tuple/list-output cases.
pt2 All of the above.
ctest --test-dir tools/pnnx/build --output-on-failure -L '^pt2_(frontend|ncnn)$'
ctest --test-dir tools/pnnx/build --output-on-failure -j 4 -L '^pt2$'

The Ubuntu/macOS/Windows quick jobs build pnnx against Torch 2.13 and the CPU ncnn Python binding from the same checkout, then run pt2_frontend and pt2_ncnn. The existing Linux job retains its Torch 2.12.1 operator/model selection. No additional version-compatibility matrix is introduced. Two existing input-npy cases retain a resource lock for shared input files.

Validation

Latest source update: 0c6dbb97, fast-forwarded to this PR on 2026-09-20 after the selected pnnx validation completed.

The four new commits since 7af5e93d cover:

  • cdc6ac80: module-scoped FP32 runtime options for native numeric tests, without changing ncnn deployment defaults or comparison tolerances.
  • f01ad9af: escape generated Python path literals, including Windows backslashes, while preserving path values.
  • 84b17b47: stop parameter parsing on failed extraction/trailing whitespace and correct fp64-to-fp16 ONNX attribute element counts/allocation, with regression tests.
  • 0c6dbb97: recognize the existing Torch 2.9.0 Funnel limitation at its actual export stage. Version, exception and diagnostic checks remain constrained; unexpected success or the wrong failure still fails.

This update changes nine pnnx source/test files, +295/-22 lines; no CI files. The C++ source portion is two files, +37/-17. It does not implement new Funnel support or add another expected skip.

Completed fork validation

Validation run 35472659172 completed successfully on 7e83bc91180ac883e6e219be4feb24731b8cf9b2 at 2026-09-20 02:41 UTC. All 26 pnnx jobs passed, including the 19 complete Torch/Python matrix entries, three OS quick jobs, prepare, dependency/converter build, CodeQL and all-green. No mandatory job was skipped.

Tested environment Result
Windows, Torch 2.13.0 13/13 frontend/path/native CTests passed, zero skips; 18 helper tests passed.
macOS, Torch 2.13.0 13/13 frontend/path/native CTests passed, zero skips; 18 helper tests passed.
Ubuntu, Torch 2.13.0 13/13 frontend/path/native CTests passed, zero skips; 18 helper tests and the six original quick convolution tests passed.
Torch 2.9.0 / Python 3.13 855 existing non-PT2 CTests passed; PT2: 401 passed, six existing expected skips, zero failures; Python packaging tests passed.
Torch 2.10.0 / Python 3.13 855 existing non-PT2 CTests passed; PT2: 401 passed, six existing expected skips, zero failures; Python packaging tests passed.
Torch 2.11.0 / Python 3.14 855 existing non-PT2 CTests passed; PT2: 401 passed, six existing expected skips, zero failures; Python packaging tests passed.
Torch 2.12.1 / Python 3.14 855 existing non-PT2 CTests passed; PT2: 401 passed, six existing expected skips, zero failures; Python packaging tests passed.
Other 15 matrix entries, Torch 1.8.1 through 2.8.0 Their complete applicable existing non-PT2 suites and packaging jobs passed. This is regression coverage, not PT2 support on those older producers.

The same-head independent CodeQL analysis, push formatting, PR formatting, PR-target formatting and labeler also succeeded before promotion. CodeRabbit's green commit status explicitly says draft review skipped, not review approval. At the pre-push review check, all 18 existing upstream review threads were resolved, with no outstanding changes-requested review.

The six diagnostic-matched expected PT2 skips are unchanged: Tensor_index, torch_arange, torch_masked_select, quantization_shufflenet_v2_x1_0, pnnx_input_npy and transformers_funnel_attention. The 407 PT2 entries include importer/helper/operator/native tests; they are not 407 native models.

Provenance and limits of this result

The CI-referenced clean candidate 66a109e4 and the regrouped four-commit source head 0c6dbb97 have the identical complete file tree 4d50ffe2e145acdd3df45c9d2ad5ee0d201b715c. The validation head differs only by the audited 43 fork-only .github/ paths; none were promoted. There was no force-push or merge of the validation PR.

The fork adapter reads the candidate's existing pnnx.yml matrix and build/test commands, adapting hosted-runner provisioning and parallelism. Its dependency patch checkout is pinned to pnnx/pnnx@340e96f128c1a8a4209dddad2bfcc601f0214aaf, matching the candidate's LibTorch 2.12.1 / TorchVision 0.27.1 / ONNX Runtime 1.27.0 configuration. The three quick jobs use Torch 2.13.0. The older-producer PT2 runs are one-round evidence, not a new continuous compatibility promise.

These are completed fork pnnx results obtained before pushing the identical source tree. Checks newly triggered in Tencent/ncnn on 0c6dbb97 must be evaluated separately; results do not automatically extend to newer dependencies or a later merge-base tree. The additional whole-ncnn cross-architecture experiment is not part of this passing claim: its hardware/SDK gaps and unrelated core failures are not presented as successes.

Post-push upstream blocker

The new upstream head is not yet merge-ready. Azure Pipelines reported ACTION_REQUIRED / Skipped due to merge conflicts (check); this is not a numerical-test failure. A non-mutating merge check against the live upstream master (1857775c98921d71e1ec2b34dc7d292c72529423, verified at 2026-09-20 02:56 UTC) finds conflicts in eight files: .github/workflows/pnnx.yml, tools/pnnx/src/save_ncnn.cpp, and the six test_transformers_{distilbert,layoutlm,longformer,openai,prophetnet,xlnet}_attention.py tests. Synchronizing with that newer upstream tree and revalidating will be a separate follow-up; no unverified merge/rebase or force-push was performed. The completed fork results above apply to the stated validated source tree, not to an unresolved future merge.

@tencent-adm

tencent-adm commented Aug 28, 2026

Copy link
Copy Markdown
Member

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0163f00c6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/load_exported_program.cpp Outdated
Comment thread tools/pnnx/src/load_exported_program.cpp
Comment thread tools/pnnx/src/model_format.cpp
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-09-13T21:12:16.723646Z 7af5e93 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e0f4d8452

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tools/pnnx/src/load_exported_program.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a148f65346

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/main.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4f905dd29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/save_ncnn.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 81892df0a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/pass_level2/torchvision_DeformConv2d.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 079fb0a9d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/exported_program_tensor.cpp
Comment thread tools/pnnx/src/load_exported_program.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c8324a5c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/pass_ncnn/convert_to_float.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66ff293c9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/load_exported_program.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ae570740a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/pass_ncnn/convert_to_float.cpp Outdated
Comment thread tools/pnnx/src/save_ncnn.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38a9ff0e16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/pnnx/src/exported_program_tensor.cpp
Check observable local alias writes, restore view update shapes, and support bfloat16 attribute fusion. Convert shared integer constants only on native arithmetic branches and reject unsupported empty or complex native state. Keep required expression lowering at optlevel zero.

Reuse six existing model tests for native PT2 inference and run the frontend/native selection on Ubuntu, macOS and Windows. Local Torch 2.13 validation: 398 passed, 6 existing skips, 0 failures; paired native selection: 14 passed.

Copy link
Copy Markdown
Contributor Author

Native ncnn inference coverage and correctness update in 7af5e93d (pnnx fix exported program lowering and native tests).

The new native PT2 tests run the full path:

torch.export -> .pt2 -> pnnx -> .ncnn.param/.bin
                              -> ncnn.Net / Extractor -> numerical comparison

Python is the test harness; inference executes in ncnn's C++ CPU backend through its Python binding. These tests load the generated native model and compare its output with the original PyTorch model. They do not substitute the generated PNNX Python model for native execution.

Native PT2 CTest Coverage
test_pt2_ncnn_resnet18 Full ResNet18 inference.
test_pt2_ncnn_nn_Conv2d Existing convolution configurations, weight norm, and the batch=2 case.
test_pt2_ncnn_nn_Linear Existing input ranks/shapes, multiple outputs and weight norm.
test_pt2_ncnn_nn_LayerNorm Different normalized shapes/configurations.
test_pt2_ncnn_nn_BatchNorm2d Different configurations and batch sizes.
test_pt2_ncnn_nn_Embedding Integer-index inputs with different shapes.
test_pt2_ncnn_attribute 15 dtype/weight-format combinations, plus four shared integer arithmetic/index-branch cases.
test_pt2_ncnn_output Single/multiple/nested tuple/list outputs at optlevel 0, 1 and 2: nine combinations.

The six model tests reuse existing fixtures, comparisons and tolerances. Weight-norm parametrizations remain intact during export. A small shared helper replaces duplicated export/conversion/import code; no new Python/C++ test files, tolerance relaxations or expected skips were added in this update.

This commit also fixes observable local alias updates, BF16 Attribute reads/writes used during BatchNorm fusion, integer constants on native arithmetic branches without changing shared index storage, and required expression lowering at optlevel=0. It addresses the empty-state review at #6933 (comment): empty/complex native attributes now fail explicitly after PNNX artifacts are saved, before native artifacts are written. The corresponding tests still execute PNNX numerical checks and cannot treat a crash as expected rejection.

Validation on Linux, GCC 15, Python 3.13, PyTorch/LibTorch 2.13.0+cpu, with a matching ncnn binding:

  • Native focused selection: 14 passed, zero failures/skips, 13.38 seconds. This is eight PT2 native CTests plus six paired TorchScript native CTests.
  • Final complete PT2 run on the same binary: 404 CTests selected, 398 passed, six existing expected skips, zero failures, 381.47 seconds. All eight native PT2 entries pass in this run as well; 404 is not a count of native ncnn models.
  • The complete run includes the four frontend/helper entries, basic dynamic-dimension and re-export checks. TorchVision C++-extension-specific cases were not registered in this local build; ordinary TorchVision model fixtures such as ResNet18 were included.
  • Formatting uses clang-format 10.0.1 + AStyle 3.1 and is idempotent; git diff --check passes.

Ubuntu/macOS/Windows quick CI now builds the CPU ncnn binding from the same checkout and runs ctest -L '^pt2_(frontend|ncnn)$'. No additional producer-version matrix was added. Cross-platform CI results must be checked on this commit; local Linux results are not presented as Windows/macOS success.

Scope remains inference correctness, not Vulkan performance, ImageNet accuracy benchmarking or universal native coverage of the PNNX operator suite. Native PT2 STFT/iSTFT deployment and derived/data-dependent dynamic shapes are not added in this update. The PR body and README now distinguish these boundaries explicitly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

promote pnnx dynamo exported program support

2 participants