Skip to content

Commit 6c3a9c4

Browse files
committed
revert experimental arf_workers workaround
1 parent 04ae1ab commit 6c3a9c4

5 files changed

Lines changed: 40 additions & 88 deletions

File tree

DESCRIPTION

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ Imports:
3232
Suggests:
3333
arf,
3434
DiagrammeR,
35-
doParallel,
3635
foreach,
3736
future,
3837
future.apply,

NEWS.md

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,39 @@
11
# xplainfi 1.1.0.9000 (development version)
22

3-
## Performance and new features
4-
5-
- New `samples_per_row` argument on `FeatureSampler$sample()` and `$sample_newdata()` (default `1L`).
6-
- When `> 1`, samplers return `samples_per_row * length(row_ids)` rows in draw-major order
7-
(one block of all input rows per draw).
8-
- Default behaviour is unchanged.
9-
- `PerturbationImportance` (`PFI`/`CFI`/`RFI`) now passes `samples_per_row = n_repeats` to the
10-
sampler on unique test row IDs, instead of replicating row IDs externally.
11-
- For `ConditionalARFSampler` this means `arf::forge(n_synth = n_repeats)` on `n_test`
12-
unique evidence rows rather than `n_synth = 1` on `n_repeats * n_test` replicated rows.
13-
- Dramatically faster at scale and dodges an int32 overflow inside `arf::forge` for large
14-
`n_test * n_repeats`.
15-
- All other in-package samplers implement `samples_per_row` natively as well.
16-
- `PerturbationImportance` (PFI/CFI/RFI) now registers a `doParallel` backend inside each
17-
mirai daemon when the sampler is configured with `parallel = TRUE`.
18-
- This lets `ConditionalARFSampler` use parallel `arf::forge()` from within mirai workers,
19-
which don't inherit the caller's foreach state.
20-
- Worker count per daemon is controlled by the new `arf_workers` option (default `2L`);
21-
see `?xplain_opt`.
22-
- Requires the `doParallel` package (now in Suggests).
23-
- `WVIM` and `LOCO` gain a `batch_size` constructor argument (default `NULL`).
24-
- Controls how many refits (`design_points`) are dispatched per internal `mlr3::benchmark()`
25-
call by the `mlr3fselect::fs("design_points")` fselector.
26-
- `NULL` keeps the prior behaviour (bbotk default of one design point per call, i.e.
27-
sequential single-refit evaluation).
28-
- Set a positive integer (e.g. the number of `future`/`mirai` workers) to batch refits so
29-
mlr3's parallelization can spread them across workers; previously refits were dispatched
30-
one at a time, so `future`/`mirai` backends sat idle during LOCO/WVIM computation.
31-
- Post-benchmark aggregation keys on the per-design-point resample-result `uhash`, so
32-
scores/obs-losses are attributed correctly for any `batch_size`.
33-
- `ConditionalSAGE` now passes `samples_per_row = n_samples` to the sampler on unique
34-
test rows, instead of replicating test rows externally before calling the sampler.
35-
- For `ConditionalARFSampler` this triggers the same `arf::forge(n_synth = n_samples)`
36-
speedup as `PerturbationImportance`, on `n_test` unique evidence rows rather than
37-
`n_samples * n_test` replicated rows.
38-
- Marginal SAGE (`MarginalSAGE`) does not use a `FeatureSampler` and is unaffected.
3+
## Behavior changes
4+
5+
- `WVIM` and `LOCO` now default to `n_repeats = 1`, and the argument may be removed for these refit-based methods in the future (#84).
6+
- For refit-based methods, computational budget is better spent on more resampling iterations (which stabilize the FI estimate) than on repeated refits within a single resampling iteration.
7+
8+
## New features
9+
10+
- `FeatureSampler$sample()` and `$sample_newdata()` gain a `samples_per_row` argument (default `1L`).
11+
- With `samples_per_row > 1` the sampler returns `samples_per_row * length(row_ids)` rows in draw-major order (one block of all input rows per draw), default behaviour is unchanged.
12+
- All in-package samplers implement `samples_per_row` natively.
13+
- `WVIM` and `LOCO` gain a `batch_size` constructor argument (default `NULL`) to parallelize refits.
14+
- It controls how many refits (`design_points`) are dispatched per internal `mlr3::benchmark()` call via the `mlr3fselect::fs("design_points")` fselector.
15+
- `NULL` keeps the prior sequential single-refit behaviour (bbotk default of one design point per call). Set a positive integer (e.g. the number of `future`/`mirai` workers) so mlr3 can spread refits across workers. Previously they were dispatched one at a time and backends sat idle during LOCO/WVIM computation.
16+
- Post-benchmark aggregation keys on the per-design-point resample-result `uhash`, so scores/obs-losses are attributed correctly for any `batch_size`.
3917

40-
## Other user-facing changes
18+
## Performance
4119

42-
- `WVIM` and `LOCO` now default to `n_repeats = 1`, and the argument may be removed for these refit methods in the future.
43-
- Reasoning: For refit-based methods, it is much more useful to increase the the number of resampling iterations to increase stability of the resulting FI estimate rather than trying to stabilize stochastic learners by repeatedly refitting within the same resampling iteration. If computation budget is spent on refits, it should be spent there.
20+
- `PerturbationImportance` (`PFI`/`CFI`/`RFI`) and `ConditionalSAGE` now pass `samples_per_row = n_repeats` / `n_samples` to the sampler on unique test rows instead of externally replicating row IDs.
21+
- For `ConditionalARFSampler` this calls `arf::forge(n_synth = ...)` on `n_test` unique evidence rows rather than `n_synth = 1` on replicated rows: much faster at scale, and it dodges an int32 overflow inside `arf::forge` for large `n_test * n_repeats`.
22+
- Marginal SAGE (`MarginalSAGE`) does not use a `FeatureSampler` and is unaffected.
23+
- `PerturbationImportance` now forges sequentially inside each mirai daemon, since the outer mirai layer already parallelizes across features; a `ConditionalARFSampler` with `parallel = TRUE` no longer nests `arf::forge()` parallelism inside workers (which would oversubscribe cores). The sampler's `parallel` flag still drives fit-time parallelism (`adversarial_rf`/`forde`) as before.
4424

4525
## Bug fixes
4626

47-
- `relation = "ratio"` importances now return `NA` (with a warning) instead of
48-
`Inf`/`NaN` for features whose baseline score is `0`.
49-
- Fix `$obs_loss()` being erroneously called without `measure` in `PerturbationImportance`,
50-
resulting in an error when `measures` was not the task-default.
51-
- `ConditionalARFSampler$sample()` now errors when `parallel = TRUE` but no parallel backend
52-
is registered, e.g. after deserializing a sampler in a new session.
53-
- Fix `ConditionalARFSampler` returning under-sampled (and, with `samples_per_row > 1`,
54-
partially `NA`-filled) outputs when `conditioning_set` is empty.
55-
- `arf::forge(evidence = NULL, n_synth = k)` returns only `k` unconditional draws,
56-
not `nrow(data) * k`; the previous code path silently recycled a single sample
57-
across all output rows when `samples_per_row = 1L`.
58-
- The marginal case now requests `nrow(data) * samples_per_row` independent
59-
unconditional draws so every (instance, draw) pair gets its own sample.
60-
- In practice this only affects `ConditionalSAGE` (which calls the sampler with an
61-
empty conditioning set on every empty coalition); CFI/RFI/PFI condition on the
62-
complement of the perturbed feature and never trigger the empty-conditioning path.
63-
- As a side effect, `ConditionalSAGE` importance estimates for noise features are
64-
now substantially less inflated under the same `n_permutations` / `n_samples`
65-
budget.
27+
- `relation = "ratio"` importances now return `NA` (with a warning) instead of `Inf`/`NaN` for features whose baseline score is `0`.
28+
- `PerturbationImportance` no longer calls `$obs_loss()` without `measure`, which errored when `measures` was not the task default.
29+
- `ConditionalARFSampler$sample()` now errors when `parallel = TRUE` but no parallel backend is registered, e.g. after deserializing a sampler in a new session.
30+
- `ConditionalARFSampler` no longer returns under-sampled (and, with `samples_per_row > 1`, partially `NA`-filled) output when `conditioning_set` is empty.
31+
- `arf::forge(evidence = NULL, n_synth = k)` returns only `k` unconditional draws, not `nrow(data) * k`; the previous code path silently recycled a single sample across all output rows when `samples_per_row = 1L`. The marginal case now requests `nrow(data) * samples_per_row` independent draws so every (instance, draw) pair gets its own sample.
32+
- In practice this only affected `ConditionalSAGE` (empty conditioning set on every empty coalition); CFI/RFI/PFI condition on the complement of the perturbed feature and never trigger the empty-conditioning path. As a side effect, `ConditionalSAGE` importance estimates for noise features are now substantially less inflated under the same `n_permutations` / `n_samples` budget.
6633

6734
## Internal changes
6835

69-
- The `KnockoffSampler` `n_repeats > iters` cap-and-warn behaviour moved from
70-
`PerturbationImportance$initialize()` into `KnockoffSampler$sample()` itself;
71-
behaviour is unchanged.
36+
- The `KnockoffSampler` `n_repeats > iters` cap-and-warn behaviour moved from `PerturbationImportance$initialize()` into `KnockoffSampler$sample()` itself; behaviour is unchanged.
7237

7338
# xplainfi 1.1.0
7439

R/PerturbationImportance.R

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ PerturbationImportance = R6Class(
218218
n_repeats,
219219
batch_size,
220220
learner_packages,
221-
arf_workers,
222221
is_sequential = TRUE
223222
) {
224223
# Load required packages in parallel workers
@@ -229,18 +228,16 @@ PerturbationImportance = R6Class(
229228
for (pkg in learner_packages) {
230229
library(pkg, character.only = TRUE)
231230
}
232-
# If sampler is configured for parallel sampling (e.g. arf::forge
233-
# with parallel = TRUE), foreach needs a backend registered inside
234-
# THIS daemon's R session — mirai daemons are separate processes
235-
# and don't inherit the caller's foreach state. arf's sequential
236-
# %do% path has bugs at scale, so the only reliable way to use
237-
# ARF inside a mirai daemon is to give it a parallel backend.
238-
# Tune workers per daemon via `xplain_opt(arf_workers = N)`
239-
# in the caller session (value resolved before dispatch).
240-
if (isTRUE(sampler$param_set$values$parallel) && arf_workers > 0L) {
241-
require_package("doParallel")
242-
doParallel::registerDoParallel(cores = arf_workers)
243-
on.exit(doParallel::stopImplicitCluster(), add = TRUE)
231+
# Force sequential forging inside the daemon. The outer mirai layer
232+
# already parallelizes across features, so letting the sampler's
233+
# stored `parallel` flag drive nested `arf::forge()` parallelism
234+
# here would only oversubscribe cores. That flag reflects fit-time
235+
# config (`adversarial_rf`/`forde`, done once in the caller) and
236+
# must not leak into sample-time forging. Set it on the daemon-local
237+
# sampler copy, samplers without a `parallel` param (all non-ARF
238+
# samplers) are unaffected.
239+
if ("parallel" %in% sampler$param_set$ids()) {
240+
sampler$param_set$set_values(parallel = FALSE)
244241
}
245242
}
246243

@@ -282,12 +279,7 @@ PerturbationImportance = R6Class(
282279
test_row_ids = test_row_ids,
283280
n_repeats = n_repeats,
284281
batch_size = batch_size,
285-
learner_packages = this_learner$packages,
286-
# Resolved once on caller side — mirai daemons are separate R
287-
# sessions and don't inherit options from the caller, so reading
288-
# `xplain_opt()` inside the daemon would always see the package
289-
# default. Pass the resolved value through `.args` instead.
290-
arf_workers = xplain_opt("arf_workers")
282+
learner_packages = this_learner$packages
291283
)
292284
)
293285

R/utils-opt.R

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#' | `progress` | `FALSE` | Show progress bars during computation |
2222
#' | `sequential` | `FALSE` | Force sequential execution (disable parallelization) |
2323
#' | `debug` | `FALSE` | Enable debug output for development and troubleshooting |
24-
#' | `arf_workers` | `2L` | doParallel workers registered inside each mirai daemon when the sampler is configured with `parallel = TRUE`. Has no effect on sequential or non-ARF execution. |
2524
#'
2625
#' @return
2726
#' - When **getting** a single option: the option value (logical)
@@ -60,8 +59,7 @@ xplain_opt = function(...) {
6059
verbose = TRUE,
6160
progress = FALSE,
6261
sequential = FALSE,
63-
debug = FALSE,
64-
arf_workers = 2L
62+
debug = FALSE
6563
)
6664

6765
args = list(...)
@@ -118,9 +116,8 @@ xplain_opt = function(...) {
118116

119117
#' Get option value with precedence: R option > env var > default
120118
#'
121-
#' Coerces to the default's storage type, so logical options stay
122-
#' logical and integer options like `arf_workers` stay integer. Anything
123-
#' that fails to coerce is treated as unset.
119+
#' Coerces to the default's storage type, so logical options stay logical.
120+
#' Anything that fails to coerce is treated as unset.
124121
#'
125122
#' @noRd
126123
#' @keywords internal

man/xplain_opt.Rd

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)