Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use probability sampling over periodic sampling #213

Merged
merged 95 commits into from
May 23, 2024

Conversation

vlkale
Copy link
Contributor

@vlkale vlkale commented Oct 13, 2023

Fix #180 .

This PR is related to old PR #181

The current Kokkos sampler utility uses periodic sampling via a sampler skip rate. Doing this is often restrictive when sampling profiling and debugging data. For example, doing this can miss out on important data not in the periodicity of kernel invocations. The goal of this PR for the Kokkos sampler utility is to allow user to use random sampling primarily and periodic sampling secondarily via environment variables in the form KOKKOS_TOOLS_SAMPLER_xyz.

Since the solution should not allow for a combination of both periodicity and probability, the probability will always be chosen.

For example, let us say that a user requests a a Kokkos::parallel_for() every 20th invocation of that Kokkos::parallel_for() and requests gather time spent on Kokkos::parallel_for() with probability 63% on each invocation of that Kokkos::parallel_for(). Then, the sampler will not skip trying to time any invocations of the Kokkos::parallel_for() but it will obtain a timing with probability 63% on each invocation of that Kokkos::parallel_for().

See the common/kokkos-sampler/README.md directory for a high-level overview - in English - of the changes.

For later: I will put in slide in the Kokkos Tools tutorial slide on sampling and filtering to explain how to use these utilities.

@vlkale vlkale added the feature Needed feature but software still is correct on its own label Oct 13, 2023
@vlkale vlkale requested a review from crtrott October 13, 2023 02:51
@vlkale
Copy link
Contributor Author

vlkale commented Oct 13, 2023

The following two outputs show that the sampling with probability 1.0% works properly when applied to kernel timer Kokkos tool for stream benchmark in the Kokkos core benchmark folder.

The first output is with Kokkos tools global fences being on (tool-induced fencing is enabled) and the second output is with global fencing turned off. In the second case, fencing is not invoked, as expected. Also, note that the set of Kokkos kernel invocation numbers that is sampled is different across these two different runs. The random number generator is seeded with the current time, making the invocations sampled different. The following is run on a MacOS with gcc and Kokkos 4.1.

vlkale@s1088602ca stream % export KOKKOS_TOOLS_SAMPLER_VERBOSE=2; export KOKKOS_TOOLS_SAMPLER_PROB=1.0; export KOKKOS_TOOLS_GLOBALFENCES=1; export KOKKOS_TOOLS_LIBS="/Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/common/kokkos-sampler/kp_sampler.so;/Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/profiling/simple-kernel-timer/kp_kernel_timer.so"; ./stream.exe 
-------------------------------------------------------------
Kokkos STREAM Benchmark
-------------------------------------------------------------
KokkosP: Next library to call: /Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/profiling/simple-kernel-timer/kp_kernel_timer.so
KokkosP: Loading child library ..
KokkosP: Simple Kernel Timer Library Initialized (sequence is 1, version: 20211015)
KokkosP: Function Status:
KokkosP: begin-parallel-for:      yes
KokkosP: begin-parallel-scan:     yes
KokkosP: begin-parallel-reduce:   yes
KokkosP: end-parallel-for:        yes
KokkosP: end-parallel-scan:       yes
KokkosP: end-parallel-reduce:     yes
KokkosP: Sampling rate set to: 20
KokkosP: Sampling rate provided as input: 20
KokkosP: Sampling probability provided as input: 1.0
KokkosP: Sampling rate set to: 21
KokkosP: Sampling probability set to 1.000000
KokkosP: seeding Random Number Generator using clock for probabilistic sampling.
KokkosP: Note that both probability and skip rate are set. The Kokkos Tools Sampler utility will invoke a Kokkos Tool child event you specified (e.g., the profiler or debugger tool connector you specified in KOKKOS_TOOLS_LIBS) with only specified sampling probability applied and sampling skip rate set is ignored with no predefined periodicity for sampling used.
KokkosP: The skip rate in the sampler utility is being set to 1.
Reports fastest timing per kernel
Creating Views...
Memory Sizes:
- Array Size:    100000000
- Per Array:           800.00 MB
- Total:              2400.00 MB
Benchmark kernels will be performed for 200 iterations.
-------------------------------------------------------------
Initializing Views...
Starting benchmarking...
KokkosP: sample 12 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 12 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 267 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 267 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 362 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 362 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 468 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 468 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 503 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 503 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 579 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 579 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 657 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 657 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 925 calling child-begin function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
KokkosP: sample 925 calling child-end function...
KokkosP: Sampler utility sucessfully invoked  tool-induced fence on device 0
Performing validation...
All solutions checked and verified.
-------------------------------------------------------------
Set                62629.87 MB/s
Copy               74669.24 MB/s
Scale              74154.04 MB/s
Add                83099.73 MB/s
Triad              82674.88 MB/s
-------------------------------------------------------------
KokkosP: Kernel timing written to /Users/vlkale/Desktop/vlap/wk/code/softwareTech/kokkos/benchmarks/stream/s1088602ca-41172.dat 
vlkale@s1088602ca stream % export KOKKOS_TOOLS_SAMPLER_VERBOSE=2; export KOKKOS_TOOLS_SAMPLER_PROB=1.0; export KOKKOS_TOOLS_GLOBALFENCES=0; export KOKKOS_TOOLS_LIBS="/Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/common/kokkos-sampler/kp_sampler.so;/Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/profiling/simple-kernel-timer/kp_kernel_timer.so"; ./stream.exe
-------------------------------------------------------------
Kokkos STREAM Benchmark
-------------------------------------------------------------
KokkosP: Next library to call: /Users/vlkale/Desktop/vlap/wk/code/softwareTech/ktools/ktov105/profiling/simple-kernel-timer/kp_kernel_timer.so
KokkosP: Loading child library ..
KokkosP: Simple Kernel Timer Library Initialized (sequence is 1, version: 20211015)
KokkosP: Function Status:
KokkosP: begin-parallel-for:      yes
KokkosP: begin-parallel-scan:     yes
KokkosP: begin-parallel-reduce:   yes
KokkosP: end-parallel-for:        yes
KokkosP: end-parallel-scan:       yes
KokkosP: end-parallel-reduce:     yes
KokkosP: Sampling rate set to: 20
KokkosP: Sampling rate provided as input: 20
KokkosP: Sampling probability provided as input: 1.0
KokkosP: Sampling rate set to: 21
KokkosP: Sampling probability set to 1.000000
KokkosP: seeding Random Number Generator using clock for probabilistic sampling.
KokkosP: Note that both probability and skip rate are set. The Kokkos Tools Sampler utility will invoke a Kokkos Tool child event you specified (e.g., the profiler or debugger tool connector you specified in KOKKOS_TOOLS_LIBS) with only specified sampling probability applied and sampling skip rate set is ignored with no predefined periodicity for sampling used.
KokkosP: The skip rate in the sampler utility is being set to 1.
Reports fastest timing per kernel
Creating Views...
Memory Sizes:
- Array Size:    100000000
- Per Array:           800.00 MB
- Total:              2400.00 MB
Benchmark kernels will be performed for 200 iterations.
-------------------------------------------------------------
Initializing Views...
Starting benchmarking...
KokkosP: sample 28 calling child-begin function...
KokkosP: sample 28 calling child-end function...
KokkosP: sample 296 calling child-begin function...
KokkosP: sample 296 calling child-end function...
KokkosP: sample 370 calling child-begin function...
KokkosP: sample 370 calling child-end function...
KokkosP: sample 377 calling child-begin function...
KokkosP: sample 377 calling child-end function...
KokkosP: sample 476 calling child-begin function...
KokkosP: sample 476 calling child-end function...
KokkosP: sample 503 calling child-begin function...
KokkosP: sample 503 calling child-end function...
KokkosP: sample 601 calling child-begin function...
KokkosP: sample 601 calling child-end function...
KokkosP: sample 693 calling child-begin function...
KokkosP: sample 693 calling child-end function...
KokkosP: sample 944 calling child-begin function...
KokkosP: sample 944 calling child-end function...
Performing validation...
All solutions checked and verified.
-------------------------------------------------------------
Set                62633.34 MB/s
Copy               74470.13 MB/s
Scale              74797.96 MB/s
Add                83071.21 MB/s
Triad              82997.36 MB/s
-------------------------------------------------------------
KokkosP: Kernel timing written to /Users/vlkale/Desktop/vlap/wk/code/softwareTech/kokkos/benchmarks/stream/s1088602ca-41194.dat 
vlkale@s1088602ca stream %

@vlkale vlkale changed the title Use probability sampling, not periodic sampling Use probability sampling over periodic sampling Oct 13, 2023
@vlkale vlkale marked this pull request as ready for review October 13, 2023 03:00
Copy link
Member

@crtrott crtrott left a comment

Choose a reason for hiding this comment

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

Lets add a user option to set the seed, and don't delete the erase

vlkale and others added 4 commits October 23, 2023 12:50
Erase got deleted (there on the develop branch) 

Took it out due to copy-paste
User can input seed for RNG for probabilistic sampling
@vlkale
Copy link
Contributor Author

vlkale commented Oct 23, 2023

The below is a test with the most recently committed version with KOKKOS_TOOLS_SEED set, as requested by @crtrott. Two separate runs of the program stream were done with the following environment variables set. As seen from the output of both runs, they both have the same sequence of events sampled, showing that the manual seed rather than time-generated seed is working for this case. Note that the output is truncated for easily viewing the output within this GitHub issue.

ViveksMacBook: stream % export KOKKOS_TOOLS_LIBS="/Users/vivek/kto-inst/libkp_kokkos_sampler.dylib;/Users/Vivek/kto-inst/libkp_kernel_logger.dylib"; export KOKKOS_TOOLS_SEED=4; export KOKKOS_TOOLS_SEED=4; export KOKKOS_TOOLS_SAMPLER_VERBOSE=2; export KOKKOS_TOOLS_SAMPLER_PROBABILITY=50.0; ./stream.exe;
-------------------------------------------------------------
Kokkos STREAM Benchmark
-------------------------------------------------------------
KokkosP: Next library to call: /Users/vivek/kto-inst/libkp_kernel_logger.dylib
KokkosP: Loading child library ..
KokkosP: Kernel Logger Library Initialized (sequence is 1, version: 20211015)
KokkosP: Function Status:
KokkosP: begin-parallel-for:      yes
KokkosP: begin-parallel-scan:     yes
KokkosP: begin-parallel-reduce:   yes
KokkosP: end-parallel-for:        yes
KokkosP: end-parallel-scan:       no
KokkosP: end-parallel-reduce:     yes
KokkosP: Sampling rate set to: 1
KokkosP: Sampling rate provided as input: 1
KokkosP: Sampling probability provided as input: 50.0
KokkosP: Sampling rate set to: 2
KokkosP: Sampling probability set to 50.000000
KokkosP: Seeding random number generator using seed 4 for probabilistic sampling.
KokkosP: Note that both probability and skip rate are set. The Kokkos Tools Sampler utility will invoke a Kokkos Tool child event you specified (e.g., the profiler or debugger tool connector you specified in KOKKOS_TOOLS_LIBS) with only specified sampling probability applied and sampling skip rate set is ignored with no predefined periodicity for sampling used.
KokkosP: The skip rate in the sampler utility is being set to 1.
Reports fastest timing per kernel
Creating Views...
Memory Sizes:
- Array Size:    100000000
- Per Array:           800.00 MB
- Total:              2400.00 MB
Benchmark kernels will be performed for 20 iterations.
-------------------------------------------------------------
KokkosP: sample 1 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 0
KokkosP:     Kokkos::View::initialization [a] via memset
KokkosP: sample 1 finished with child-begin function.
KokkosP: sample 1 calling child-end function...
KokkosP: Execution of kernel 0 is completed.
KokkosP: sample 3 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 1
KokkosP:     Kokkos::View::initialization [c] via memset
KokkosP: sample 3 finished with child-begin function.
KokkosP: sample 3 calling child-end function...
KokkosP: Execution of kernel 1 is completed.
Initializing Views...
Starting benchmarking...
KokkosP: sample 5 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 2
KokkosP:     set
KokkosP: sample 5 finished with child-begin function.
KokkosP: sample 5 calling child-end function...
KokkosP: Execution of kernel 2 is completed.

...

KokkosP: sample 96 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 47
KokkosP:     copy
KokkosP: sample 96 finished with child-begin function.
KokkosP: sample 96 calling child-end function...
KokkosP: Execution of kernel 47 is completed.
KokkosP: sample 98 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 48
KokkosP:     add
KokkosP: sample 98 finished with child-begin function.
KokkosP: sample 98 calling child-end function...
KokkosP: Execution of kernel 48 is completed.
KokkosP: sample 99 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 49
KokkosP:     triad
KokkosP: sample 99 finished with child-begin function.
KokkosP: sample 99 calling child-end function...
KokkosP: Execution of kernel 49 is completed.
KokkosP: sample 101 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 50
KokkosP:     copy
KokkosP: sample 101 finished with child-begin function.
KokkosP: sample 101 calling child-end function...
KokkosP: Execution of kernel 50 is completed.
KokkosP: sample 103 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 51
KokkosP:     add
KokkosP: sample 103 finished with child-begin function.
KokkosP: sample 103 calling child-end function...
KokkosP: Execution of kernel 51 is completed.
Performing validation...
All solutions checked and verified.
-------------------------------------------------------------
Set                12593.03 MB/s
Copy               16127.57 MB/s
Scale              15996.91 MB/s
Add                16634.53 MB/s
Triad              17182.65 MB/s
-------------------------------------------------------------
KokkosP: Kokkos library finalization called.
ViveksMacBook stream % ./stream.exe
-------------------------------------------------------------
Kokkos STREAM Benchmark
-------------------------------------------------------------
KokkosP: Next library to call: /Users/Vivek/kto-inst/libkp_kernel_logger.dylib
KokkosP: Loading child library ..
KokkosP: Kernel Logger Library Initialized (sequence is 1, version: 20211015)
KokkosP: Function Status:
KokkosP: begin-parallel-for:      yes
KokkosP: begin-parallel-scan:     yes
KokkosP: begin-parallel-reduce:   yes
KokkosP: end-parallel-for:        yes
KokkosP: end-parallel-scan:       no
KokkosP: end-parallel-reduce:     yes
KokkosP: Sampling rate set to: 1
KokkosP: Sampling rate provided as input: 1
KokkosP: Sampling probability provided as input: 50.0
KokkosP: Sampling rate set to: 2
KokkosP: Sampling probability set to 50.000000
KokkosP: Seeding random number generator using seed 4 for probabilistic sampling.
KokkosP: Note that both probability and skip rate are set. The Kokkos Tools Sampler utility will invoke a Kokkos Tool child event you specified (e.g., the profiler or debugger tool connector you specified in KOKKOS_TOOLS_LIBS) with only specified sampling probability applied and sampling skip rate set is ignored with no predefined periodicity for sampling used.
KokkosP: The skip rate in the sampler utility is being set to 1.
Reports fastest timing per kernel
Creating Views...
Memory Sizes:
- Array Size:    100000000
- Per Array:           800.00 MB
- Total:              2400.00 MB
Benchmark kernels will be performed for 20 iterations.
-------------------------------------------------------------
KokkosP: sample 1 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 0
KokkosP:     Kokkos::View::initialization [a] via memset
KokkosP: sample 1 finished with child-begin function.
KokkosP: sample 1 calling child-end function...
KokkosP: Execution of kernel 0 is completed.
KokkosP: sample 3 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 1
KokkosP:     Kokkos::View::initialization [c] via memset
KokkosP: sample 3 finished with child-begin function.
KokkosP: sample 3 calling child-end function...
KokkosP: Execution of kernel 1 is completed.
Initializing Views...
Starting benchmarking...
KokkosP: sample 5 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 2
KokkosP:     set
KokkosP: sample 5 finished with child-begin function.
KokkosP: sample 5 calling child-end function...
KokkosP: Execution of kernel 2 is completed.

....

KokkosP: sample 96 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 47
KokkosP:     copy
KokkosP: sample 96 finished with child-begin function.
KokkosP: sample 96 calling child-end function...
KokkosP: Execution of kernel 47 is completed.
KokkosP: sample 98 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 48
KokkosP:     add
KokkosP: sample 98 finished with child-begin function.
KokkosP: sample 98 calling child-end function...
KokkosP: Execution of kernel 48 is completed.
KokkosP: sample 99 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 49
KokkosP:     triad
KokkosP: sample 99 finished with child-begin function.
KokkosP: sample 99 calling child-end function...
KokkosP: Execution of kernel 49 is completed.
KokkosP: sample 101 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 50
KokkosP:     copy
KokkosP: sample 101 finished with child-begin function.
KokkosP: sample 101 calling child-end function...
KokkosP: Execution of kernel 50 is completed.
KokkosP: sample 103 calling child-begin function...
KokkosP: Executing parallel-for kernel on device 100663297 with unique execution identifier 51
KokkosP:     add
KokkosP: sample 103 finished with child-begin function.
KokkosP: sample 103 calling child-end function...
KokkosP: Execution of kernel 51 is completed.
Performing validation...
All solutions checked and verified.
-------------------------------------------------------------
Set                12432.48 MB/s
Copy               15896.57 MB/s
Scale              15927.12 MB/s
Add                17134.47 MB/s
Triad              17060.44 MB/s
-------------------------------------------------------------
KokkosP: Kokkos library finalization called.

@vlkale
Copy link
Contributor Author

vlkale commented Oct 25, 2023

Lets add a user option to set the seed, and don't delete the erase

Done.

@vlkale
Copy link
Contributor Author

vlkale commented Apr 12, 2024

This PR now has tests for the probability sampling and is rebased with develop.

vlkale added 9 commits May 2, 2024 13:33
Fixing inconsistencies of diff with develop that shouldn't be there. These changes do not belong to this PR. They changes comprise primarily of minor changes with printed output. These unintended changes that I have taken out don't change any core logic or control flow in the program.
…riable should refer to that variable and not sampler skip
"sampling were set...\n";
}
tool_prob_num = 10.0;
kernelSampleSkip = 1;
Copy link
Member

Choose a reason for hiding this comment

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

You hard code both tool_prob_num and kernelSampleSkip ... the user settings are ignored.

Copy link
Member

Choose a reason for hiding this comment

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

Put the entire thing into a if (tool_prob_num == -1.0)

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.

I think the key suggestion that handles the issue raised is handled by the single one you suggested in the end of this review.

We might want to catch the case when tool_prob_num is negative but not -1.0 - I think that should happen only if there is some non-deterministic bit flip like event that causes tool_prob_num to be negative.

Note I have now also put comments for each possible user input and how it is handled.

<< " percent. The skip rate for sampler will not be used.\n";
}

kernelSampleSkip = 1;
Copy link
Member

Choose a reason for hiding this comment

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

And again setting sampleskip ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleted this.

This is a redundant instruction in the program.

For later: we may want to consider what to do if, e.g., a cosmic ray flips a bit in the environment variable (I think we aren't trying to build resilience in this sampler).


const char* tool_sample = getenv("KOKKOS_TOOLS_SAMPLER_SKIP");
if (NULL != tool_sample) {
tool_prob_num = 100.0;
Copy link
Member

Choose a reason for hiding this comment

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

this means SamplerSkip is prioritized over random setting, because it overwrites the previous setting

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is fixed now.

<< tool_prob_num << "\n";
}
kernelSampleSkip = 1;
return;
Copy link
Member

Choose a reason for hiding this comment

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

ah I see return, but none of the other stuff has returns ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@crtrott Please see updated version. I think your fix suggested below takes care of this.

vlkale and others added 5 commits May 3, 2024 15:20
Fix from a previous suggestion from contributed commit  - it may have been misunderstood that probability sampling is the priority here (I should have caught this).

Co-authored-by: Christian Trott <[email protected]>
I put comments to make clear the desired behavior. Note that the conditional shouldn't be needed.
@crtrott crtrott merged commit 81d3f3a into kokkos:develop May 23, 2024
7 checks passed
@vlkale vlkale mentioned this pull request Jun 3, 2024
@vlkale vlkale deleted the use-probability-sampling branch June 6, 2024 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Needed feature but software still is correct on its own
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sampler needs to allow for randomized sampling (not just periodic)
3 participants