Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/guides/profiler-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,47 @@ bk_profiler <tool> [options] -- <command ...>
`bk_resolve_profiler_tool` / `bk_resolve_profiler_level` を使う。既存運用や site-local
調整のために app 固有変数が必要な場合は、共通変数の上書きとして helper に渡す。

## Workflow Stage Timing

`bk_profiler` は各 `fapp` / `ncu` acquisition と report export の時間を自動記録する。
アプリの `profile.sh` に開始・終了時計、JSON生成、artifact登録を実装する必要はない。
SBDとGENESISも同じ共通経路を使い、MPI rank選択やcontainer起動条件はアプリ側で組み立てる。

`run.sh` では、実験条件と出力先を決めた時点で共通実行contextを設定する。
contextはsubshellやpipelineへ引き継がれ、作業ディレクトリを変えても出力先は変わらない。

```bash
bk_run_context --results-dir "${PWD}/results" --exp "$experiment"
bk_run --log benchmark.log -- mpirun -np "$ranks" ./application
bk_profiler ncu --archive "${BK_RUN_RESULTS_DIR}/padata.tgz" -- ./application
```

独自のMPI/container起動が必要な場合は、組み立てた完全なコマンドを
`bk_profile_execute --tool <tool> --phase <collect|export|plan> -- <command ...>`
へ渡す。`--profile <name>` で対象profileを識別でき、`--log <file>` でコマンドの
stdout/stderrだけをファイルへ送れる。NSYSのcollect/exportにもこの入口を使う。
NCU plan生成は `bk_generate_ncu_plan` へ従来のgenerator引数を渡す。
いずれも時計操作や記録ファイルの管理は共通層が担当する。

共通層は実験条件ごとの `results/workflow_timing_*.json` にstage、tool、profile、
UTC開始・終了時刻、monotonic時計による経過秒、終了コードを保存する。
更新はlockとatomic replacementを使い、並列コマンドの追記を保持する。
`scripts/result.sh` が現在の実行sessionの記録を `timing_observations` へ自動追加し、
既存のapp独自timing observationも保持する。Resultの `Exp` によりscopeを絞り、
既存のMeasurement Artifacts送信・console表示経路を使う。公開面には追加しない。
context未設定の `bk_profiler` はarchiveの出力ディレクトリを使い、job単位のscopeとなる。
一つのresultsディレクトリは一つの実行sessionで使用する。

記録はコマンドのwall timeであり、MPI起動やprofilerのreport finalizationを含む。
queue待ちやarchive圧縮は含まず、アプリのFOMを置き換えない。profile用の再実行時間を
そのままアプリのslowdown比とは解釈せず、同条件の通常実行と比較する。
計測用helper自身の起動・記録には小さな追加コストがある。

失敗・timeoutはコマンドの終了コードを保持する。finishを書けずに中断したstageは
`running` と開始時刻だけが残るため、未完了として扱い、成功やゼロ秒とみなさない。
任意の記録処理が失敗しても実行コマンドの終了コードを変えない。schedulerによる強制終了後の
転送可否はjobのartifact upload方針に依存する。

## 3. 共通語彙としての level

`single/simple/standard/detailed` は Benchkit の共通語彙として扱う。
Expand Down
20 changes: 12 additions & 8 deletions programs/genesis/profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,8 @@ genesis_run_container_ncu_acquisition_profile() {
echo "bk_run_ncu_acquisition_profile: profile='${profile_name}' kernel='${kernel_regex}' skip=${launch_skip} count=${launch_count}" >&2
echo "bk_profiler[ncu]: starting ${rep_name} level=${profiler_level} inside container rank 0" >&2
set +e
"${profile_cmd[@]}" </dev/null 2>&1 | tee "$profile_log"
bk_profile_execute --tool ncu --phase collect --profile "$profile_slug" -- \
"${profile_cmd[@]}" </dev/null 2>&1 | tee "$profile_log"
profiler_status=${PIPESTATUS[0]}
set -e

Expand All @@ -446,12 +447,14 @@ genesis_run_container_ncu_acquisition_profile() {
--csv \
--print-units base \
--print-fp || return 1
"${import_cmd[@]}" > "${rep_dir}/profile_raw.csv" 2> "${rep_dir}/profile_raw.csv.log" || true
bk_profile_execute --tool ncu --phase export --profile "${profile_slug}/raw" -- \
"${import_cmd[@]}" > "${rep_dir}/profile_raw.csv" 2> "${rep_dir}/profile_raw.csv.log" || true

import_cmd=()
genesis_build_container_once_command app_cmd import_cmd \
ncu --import "$report_file" --page details || return 1
"${import_cmd[@]}" > "$stage_dir/reports/ncu_import_${rep_name}.txt" 2>&1 || true
bk_profile_execute --tool ncu --phase export --profile "${profile_slug}/details" -- \
"${import_cmd[@]}" > "$stage_dir/reports/ncu_import_${rep_name}.txt" 2>&1 || true
fi

cp -R "$rep_dir" "$stage_dir/raw/${rep_name}"
Expand Down Expand Up @@ -610,9 +613,9 @@ genesis_generate_ncu_plan() {
"${discovery_cmd[@]:$GENESIS_APPTAINER_PAYLOAD_INDEX}"
)
genesis_build_container_rank0_profile_command nsys_payload discovery_cmd nsys_profile_cmd
"${nsys_profile_cmd[@]}" 2>&1 | tee "$nsys_log" >&2
bk_profile_execute --tool nsys --phase collect -- "${nsys_profile_cmd[@]}" 2>&1 | tee "$nsys_log" >&2
else
nsys profile \
bk_profile_execute --tool nsys --phase collect -- nsys profile \
--force-overwrite=true \
--trace=cuda \
--sample=none \
Expand All @@ -633,10 +636,11 @@ genesis_generate_ncu_plan() {
if genesis_find_apptainer_payload_index discovery_cmd; then
genesis_build_container_once_command discovery_cmd nsys_stats_cmd \
nsys stats --force-export=true --report cuda_gpu_kern_sum --format csv --output "$nsys_csv" "$nsys_report" || return 1
"${nsys_stats_cmd[@]}" >/dev/null
bk_profile_execute --tool nsys --phase export -- "${nsys_stats_cmd[@]}" >/dev/null
nsys_stats_status=$?
else
nsys stats --force-export=true --report cuda_gpu_kern_sum --format csv --output "$nsys_csv" "$nsys_report" >/dev/null
bk_profile_execute --tool nsys --phase export -- \
nsys stats --force-export=true --report cuda_gpu_kern_sum --format csv --output "$nsys_csv" "$nsys_report" >/dev/null
nsys_stats_status=$?
fi
if [ "$nsys_stats_status" -ne 0 ]; then
Expand Down Expand Up @@ -670,7 +674,7 @@ genesis_generate_ncu_plan() {
esac
fi

"$python_bin" "${SCRIPT_DIR}/scripts/profiling/generate_ncu_plan.py" \
bk_generate_ncu_plan \
--nsys-csv "$discovery_csv" \
--out-discovery "$discovery_json" \
--out-plan "$plan_json" \
Expand Down
7 changes: 5 additions & 2 deletions programs/genesis/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ input=${header}.inp
resultsdir=${SCRIPT_DIR}/results
artifactsdir=${SCRIPT_DIR}/artifacts
mkdir -p ${resultsdir}
bk_run_context --results-dir "${resultsdir}" --exp "$exp"
export BK_INPUT_INFO_FILE="${resultsdir}/input_info.json"
export BK_INPUT_INFO_ITEMS_FILE="${resultsdir}/.input_info_items.jsonl"
bk_reset_input_info
Expand Down Expand Up @@ -162,7 +163,7 @@ run_genesis_nvidia_gpu() {
genesis_configure_ncu_profile "$system_name" "$profiler_tool_var" "$profiler_level_var" "$module_var" || return 1

echo "Running ${system_name} as NVIDIA GPU benchmark run without profiler"
"${nvidia_mpi_cmd[@]}" ./${binary} ${input}.sub 2>&1 | tee ${output}
bk_run -- "${nvidia_mpi_cmd[@]}" ./${binary} ${input}.sub 2>&1 | tee ${output}
genesis_run_configured_ncu_profiles "$system_name" "${nvidia_mpi_cmd[@]}" ./${binary} ${input}.sub || return 1
}

Expand Down Expand Up @@ -203,7 +204,9 @@ case "$system" in
export PARALLEL=${nthreads}
export OMP_NUM_THREADS=${nthreads}
echo "${mpi_cmd} ./${binary} ${input}.sub"
${mpi_cmd} ./${binary} ${input}.sub
# Intentional word splitting: mpi_cmd contains the launcher and its arguments.
# shellcheck disable=SC2086
bk_run -- ${mpi_cmd} ./${binary} ${input}.sub
[[ -f ./stdout.1.0 ]] && cp ./stdout.1.0 ${output}
[[ -f ./stderr.1.0 ]] && cp ./stderr.1.0 ${stderr}
;;
Expand Down
2 changes: 2 additions & 0 deletions programs/qws/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ case "$system" in
mpiexec -n 2 ./main "${qws_case1_args[@]}" > CASE1
print_results output.${PJM_JOBID}/0/2/stdout.2.0 CASE1 2 >> ../results/result
if bk_profiler_enabled "$qws_profiler_tool"; then
bk_run_context --results-dir ../results --exp CASE0
bk_profiler "$qws_profiler_tool" --level "$qws_profiler_level" --archive ../results/padata0.tgz --raw-dir pa -- mpiexec -n 1 ./main "${qws_case0_args[@]}" > CASE0.profile
# else
# emit_qws_dummy_padata ../results/padata0.tgz
Expand All @@ -118,6 +119,7 @@ case "$system" in
mpiexec -n 8 ./main "${qws_case7_args[@]}" > CASE7
print_results output.${PJM_JOBID}/0/1/stdout.1.0 CASE7 4 >> ../results/result
if bk_profiler_enabled "$qws_profiler_tool"; then
bk_run_context --results-dir ../results --exp CASE7
bk_profiler "$qws_profiler_tool" --level "$qws_profiler_level" --archive ../results/padata0.tgz --raw-dir pa -- mpiexec -n 8 ./main "${qws_case7_args[@]}" > CASE7.profile
# else
# emit_qws_dummy_padata ../results/padata0.tgz
Expand Down
15 changes: 14 additions & 1 deletion programs/sbd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@ and then uses that plan for rank-0 Nsight Compute acquisition when
Use `BK_SBD_NCU_PROFILE_MODE=discovery-only` to stop after the NSYS discovery
and plan generation step. `BK_SBD_NCU_PLAN_TOP_K` controls how many kernels are
selected; discovery-only defaults to all kernels, while discovery defaults to
the top three. Generated NCU archives are attached to the `mult` section as
the top three. The RIKYU system default overrides this selection to the top one.
Generated NCU archives are attached to the `mult` section as
measurement artifacts.

## Workflow stage timing

SBD uses the common execution helpers for its ordinary benchmark, NSYS
collection/export, NCU plan generation and NCU collection/export. Benchkit
automatically logs and stores their stage timings as Measurement Artifacts;
the application does not manage timing records. See the
[shared profiler guide](../../docs/guides/profiler-support.md#workflow-stage-timing)
for scope, failure handling and interpretation. The internal Davidson FOM
remains independent of workflow wall time.

## Benchmark recipes

The RIKYU recipe uses the H2O cc-pVDZ FCIDUMP with the `1em7` selected alpha
determinant file (about 628 million product determinants), one MPI rank per
B200 GPU, 32 OpenMP threads per rank, and the rank-distributed/index-reordered/NCCL
Expand Down
33 changes: 15 additions & 18 deletions programs/sbd/profile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ sbd_run_rank0_nsys_discovery() {
local profile_status

echo "Running SBD NSYS kernel discovery for automatic NCU plan generation" >&2
set +e
mpirun -np "$n_ranks" bash -lc '
profile_status=0
bk_profile_execute --tool nsys --phase collect --log "$log_file" -- mpirun -np "$n_ranks" bash -lc '
rank=${OMPI_COMM_WORLD_RANK:-${PMIX_RANK:-${SLURM_PROCID:-0}}}
local_rank=${OMPI_COMM_WORLD_LOCAL_RANK:-${SLURM_LOCALID:-0}}
export CUDA_VISIBLE_DEVICES="${local_rank}"
Expand All @@ -157,9 +157,7 @@ sbd_run_rank0_nsys_discovery() {
exec nsys profile --force-overwrite=true --trace=cuda --sample=none -o "$report_base" ./diag "$@"
fi
exec ./diag "$@"
' bash "$report_base" "$@" > "$log_file" 2>&1
profile_status=$?
set -e
' bash "$report_base" "$@" || profile_status=$?

if [ "$profile_status" -ne 0 ]; then
echo "SBD NSYS kernel discovery failed with status ${profile_status}" >&2
Expand Down Expand Up @@ -225,14 +223,12 @@ sbd_generate_ncu_plan() {
return 1
fi

set +e
nsys stats --force-export=true \
nsys_stats_status=0
bk_profile_execute --tool nsys --phase export -- nsys stats --force-export=true \
--report cuda_gpu_kern_sum,cuda_api_sum \
--format csv \
--output "$nsys_stats_base" \
"$nsys_report" >/dev/null
nsys_stats_status=$?
set -e
"$nsys_report" >/dev/null || nsys_stats_status=$?
if [ "$nsys_stats_status" -ne 0 ]; then
echo "SBD NSYS CUDA summary export failed with status ${nsys_stats_status}" >&2
return "$nsys_stats_status"
Expand Down Expand Up @@ -263,7 +259,7 @@ sbd_generate_ncu_plan() {
esac
fi

if ! "$python_bin" "${SBD_BENCHKIT_ROOT}/scripts/profiling/generate_ncu_plan.py" \
if ! bk_generate_ncu_plan \
--nsys-csv "$discovery_csv" \
--out-discovery "$discovery_json" \
--out-plan "$plan_json" \
Expand Down Expand Up @@ -394,14 +390,14 @@ sbd_run_rank0_ncu_profile() {
' bash "$profile_base" "${#ncu_level_args[@]}" "${ncu_level_args[@]}" \
"$kernel_regex" "$launch_skip" "$launch_count" "$@"
)
set +e
profiler_status=0
if [ "$profile_timeout_seconds" -gt 0 ]; then
timeout --kill-after=60s "$profile_timeout_seconds" "${profile_cmd[@]}" > "$profile_log" 2>&1
bk_profile_execute --tool ncu --phase collect --profile "$profile_slug" --log "$profile_log" -- \
timeout --kill-after=60s "$profile_timeout_seconds" "${profile_cmd[@]}" || profiler_status=$?
else
"${profile_cmd[@]}" > "$profile_log" 2>&1
bk_profile_execute --tool ncu --phase collect --profile "$profile_slug" --log "$profile_log" -- \
"${profile_cmd[@]}" || profiler_status=$?
fi
profiler_status=$?
set -e

if [ "$profiler_status" -eq 0 ]; then
echo "bk_profiler[ncu]: completed ${rep_name} level=${profiler_level}" >&2
Expand All @@ -411,12 +407,13 @@ sbd_run_rank0_ncu_profile() {

report_file=$(bk_profiler_find_ncu_report "$rep_dir" || true)
if [ -n "$report_file" ]; then
ncu --import "$report_file" \
bk_profile_execute --tool ncu --phase export --profile "${profile_slug}/raw" -- ncu --import "$report_file" \
--page raw \
--csv \
--print-units base \
--print-fp > "${rep_dir}/profile_raw.csv" 2> "${rep_dir}/profile_raw.csv.log" || true
ncu --import "$report_file" --page details > "$stage_dir/reports/ncu_import_${rep_name}.txt" 2>&1 || true
bk_profile_execute --tool ncu --phase export --profile "${profile_slug}/details" -- \
ncu --import "$report_file" --page details > "$stage_dir/reports/ncu_import_${rep_name}.txt" 2>&1 || true
fi

cp -R "$rep_dir" "$stage_dir/raw/${rep_name}"
Expand Down
10 changes: 6 additions & 4 deletions programs/sbd/run.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash
set -euo pipefail

Expand Down Expand Up @@ -70,6 +70,8 @@
;;
esac

bk_run_context --results-dir "${RESULTS_DIR}" --exp "${experiment}"

for input_file in fcidump.txt "${determinant_file}"; do
if [[ ! -f "${INPUT_DIR}/${input_file}" ]]; then
echo "SBD input not found: ${INPUT_DIR}/${input_file}" >&2
Expand Down Expand Up @@ -105,12 +107,12 @@
)

if [[ "${system}" == "RC_FX700" ]]; then
mpirun -np "${n_ranks}" -bind-to numa ./diag "${diag_args[@]}" \
> diag.log 2>&1
bk_run --log diag.log -- \
mpirun -np "${n_ranks}" -bind-to numa ./diag "${diag_args[@]}"
else
mpirun -np "${n_ranks}" bash -lc \
bk_run --log diag.log -- mpirun -np "${n_ranks}" bash -lc \
'export CUDA_VISIBLE_DEVICES=$OMPI_COMM_WORLD_LOCAL_RANK; exec "$@"' \
bash ./diag "${diag_args[@]}" > diag.log 2>&1
bash ./diag "${diag_args[@]}"
fi

davidson_time=$(grep -E 'Elapsed time for davidson ' diag.log | tail -n 1 | awk '{print $(NF-1)}')
Expand Down
68 changes: 68 additions & 0 deletions result_server/tests/test_result_detail_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,74 @@ def test_public_surface_keeps_padata_archive_links(self, app):
assert "Measurement Artifacts" in html
assert f'href="/results/{filename}"' in html

@pytest.mark.parametrize("collection", ["sections", "overlaps"])
@pytest.mark.parametrize("uploaded", [True, False])
@pytest.mark.parametrize("public_surface", [True, False])
def test_profile_metadata_links_respect_surface_and_upload_state(
self, app, collection, uploaded, public_surface,
):
basenames = ["kernel_discovery.json", "ncu_plan.json", "padata_kernel.metadata.json"]
filenames = [
"measurement_artifact_20250101_120000_"
f"12345678-1234-1234-1234-123456789abc_{name}"
for name in basenames
]
result = {
"code": "demoapp",
"_server_uuid": "12345678-1234-1234-1234-123456789abc",
"_server_timestamp": "20250101_120000",
"fom_breakdown": {
collection: [{
"name": "kernel",
"artifacts": [
{"type": "file_reference", "path": f"results/{name}"}
for name in basenames
],
}],
},
}

with app.test_request_context():
html = _render_result_detail(
result, {}, filenames if uploaded else [], public_surface=public_surface,
)

for basename, filename in zip(basenames, filenames):
if public_surface:
assert basename not in html
else:
assert "Profile metadata" in html
assert f"results/{basename}" in html
if uploaded:
assert f'href="/results/{filename}"' in html
else:
assert f"{filename} not uploaded" in html
assert f'href="/results/{filename}"' not in html

@pytest.mark.parametrize("artifact_path", [
"../metadata.json",
"results/../metadata.json",
"/results/metadata.json",
"artifacts/metadata.json",
"results/bad name.json",
"results/metadata.txt",
])
def test_invalid_profile_metadata_paths_are_omitted(self, app, artifact_path):
result = {
"_server_uuid": "12345678-1234-1234-1234-123456789abc",
"_server_timestamp": "20250101_120000",
"fom_breakdown": {
"sections": [{
"name": "kernel",
"artifacts": [{"type": "file_reference", "path": artifact_path}],
}],
},
}
with app.test_request_context():
context = build_result_detail_context(result, {})

assert context["measurement_artifact_rows"] == []

def test_timing_observation_artifact_is_linked_on_console_surface(self, app):
result = {
**FULL_RESULT,
Expand Down
Loading
Loading