Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Extract hidden states from an LLM using vLLM + speculators."""

import argparse
from pathlib import Path

import torch
from datasets import load_dataset
from speculators.data_generation import VllmHiddenStatesGenerator
from tqdm import tqdm
from transformers import AutoTokenizer

REMOVE_THINK_CHAT_TEMPLATE = (
"{% if '</think>' in content %}{% set content = content.split('</think>')[-1] %}{% endif %}"
)


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="""Collect hidden states from conversations using vLLM + speculators."""
)

parser.add_argument("--model", type=str, required=True, help="HF model path.")
parser.add_argument(
"--max-seq-len", type=int, default=3072, help="Max tokens per conversation."
)
parser.add_argument(
"--input-data", type=Path, required=True, help="Path to jsonl file or directory."
)
parser.add_argument(
"--output-dir", type=Path, required=True, help="Directory to save hidden states."
)
parser.add_argument("--dp-rank", type=int, default=0, help="Data parallel rank.")
parser.add_argument("--dp-world-size", type=int, default=1, help="Data parallel world size.")
parser.add_argument(
"--trust_remote_code", action="store_true", help="Trust remote code for HF models."
)
parser.add_argument("--tp", type=int, default=None, help="Tensor parallel size.")
parser.add_argument(
"--debug-max-num-conversations", type=int, default=None, help="Limit conversations."
)

return parser.parse_args()


def main(args: argparse.Namespace) -> None:
# Load conversations
if args.input_data.is_file() and str(args.input_data).endswith(".jsonl"):
dataset = load_dataset("json", data_files=str(args.input_data), split="train")
elif args.input_data.is_dir():
dataset = load_dataset(
"json", data_files={"train": f"{args.input_data}/*.jsonl"}, split="train"
)
else:
raise ValueError(f"input_data must be a .jsonl file or directory, got: {args.input_data}")
print(f"Loaded {len(dataset)} conversations from {args.input_data}")

# Shard data
if args.dp_world_size > 1:
dataset = dataset.shard(num_shards=args.dp_world_size, index=args.dp_rank)
print(f"Sharded to {len(dataset)} conversations for DP#{args.dp_rank}/{args.dp_world_size}")

# Remove already dumped conversations
output_dir = args.output_dir
output_dir.mkdir(parents=True, exist_ok=True)

def keep_conversation(entry):
conversation_id = entry.get("conversation_id", entry.get("uuid", None))
assert conversation_id is not None, "conversation_id is required"
return not (output_dir / f"{conversation_id}.pt").exists()

original_num = len(dataset)
dataset = dataset.filter(keep_conversation)
print(f"Removed {original_num - len(dataset)} conversations due to existing output files")

if args.debug_max_num_conversations is not None:
dataset = dataset.select(range(args.debug_max_num_conversations))

# Tokenize conversations
tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=args.trust_remote_code)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
tokenizer.chat_template = tokenizer.chat_template.replace(REMOVE_THINK_CHAT_TEMPLATE, "")

# Prepare prompts for vLLM
prompts = []
conversation_ids = []
num_skipped_too_long = 0
num_invalid = 0

for entry in dataset:
conversation_id = entry.get("conversation_id", entry.get("uuid"))
conversations = entry["conversations"]
if not conversations or not isinstance(conversations, list):
num_invalid += 1
continue

tokenized = tokenizer.apply_chat_template(
conversations, return_tensors="pt", add_generation_template=False
)
input_ids = tokenized["input_ids"] if isinstance(tokenized, dict) else tokenized
num_tokens = input_ids.shape[1]
if num_tokens <= 10 or num_tokens > args.max_seq_len:
num_skipped_too_long += 1
continue

prompts.append(input_ids.squeeze(0))
conversation_ids.append(conversation_id)

print(
f"Prepared {len(prompts)} prompts ({num_skipped_too_long} skipped too long, {num_invalid} invalid)"
)

if len(prompts) == 0:
print("No prompts to process.")
return

# Initialize vLLM hidden states generator
tp = args.tp
if tp is None:
import torch as _torch

tp = _torch.cuda.device_count()

generator = VllmHiddenStatesGenerator(
model=args.model,
tensor_parallel_size=tp,
trust_remote_code=args.trust_remote_code,
max_model_len=args.max_seq_len,
)

# Generate hidden states
results = generator.generate(prompts)

# Save in the same format as compute_hidden_states_hf.py
num_success = 0
for conv_id, result in tqdm(zip(conversation_ids, results), total=len(results), desc="Saving"):
input_ids = result["input_ids"]
hidden_states_dict = result["hidden_states"]

# Sort layer indices
layer_indices = sorted(hidden_states_dict.keys())

# Last layer = output hidden states
output_hidden_states = hidden_states_dict[layer_indices[-1]].cpu()

# Aux layers = all except the last
aux_layers = layer_indices[:-1]
if aux_layers:
aux_hidden_states = torch.cat([hidden_states_dict[i].cpu() for i in aux_layers], dim=-1)
else:
aux_hidden_states = torch.empty(0)

output_file = output_dir / f"{conv_id}.pt"
with open(output_file, "wb") as f:
torch.save(
{
"input_ids": input_ids.cpu() if hasattr(input_ids, "cpu") else input_ids,
"hidden_states": output_hidden_states,
"aux_hidden_states": aux_hidden_states,
"conversation_id": conv_id,
},
f,
)
num_success += 1

print(f"Successfully processed {num_success} out of {len(prompts)} conversations.")


if __name__ == "__main__":
cli_args = parse_args()
main(cli_args)
55 changes: 55 additions & 0 deletions tools/launcher/common/eagle3/dump_offline_data_hf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

source ${SCRIPT_DIR}/../service_utils.sh

###################################################################################################
# HF-based hidden state dumping for models not supported by TRT-LLM.
# Uses compute_hidden_states_hf.py with device_map="auto" (no TP/EP flags needed).
# Suitable for: VLMs, models with custom code, architectures not yet in TRT-LLM.
#
# Required environment:
# HF_MODEL_CKPT Path to the HF model checkpoint
#
# Args passed through to compute_hidden_states_hf.py:
# --input-data, --output-dir, --max-seq-len, etc.
###################################################################################################

pip install datasets 2>/dev/null || true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n tools/launcher/common/eagle3/dump_offline_data_hf.sh

Repository: NVIDIA/Model-Optimizer

Length of output: 2458


🏁 Script executed:

find . -name "compute_hidden_states_hf.py" -type f

Repository: NVIDIA/Model-Optimizer

Length of output: 148


🏁 Script executed:

cat -n ./examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py | head -100

Repository: NVIDIA/Model-Optimizer

Length of output: 3844


🏁 Script executed:

grep -n "load_dataset" ./examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py | head -5

Repository: NVIDIA/Model-Optimizer

Length of output: 230


Remove error suppression from pip install since datasets is a required dependency.

pip install datasets 2>/dev/null || true on line 34 masks installation failures completely. The Python script imports datasets at line 31 and uses it at lines 110+ (e.g., load_dataset("json", ...)). If installation fails silently, the job will fail later with an ImportError instead of revealing the actual installation error, making debugging difficult and behavior non-reproducible. Either ensure the dependency is pre-installed in the execution environment, or let the installation command fail visibly so errors are clear.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/launcher/common/eagle3/dump_offline_data_hf.sh` at line 34, The pip
installation currently suppresses errors using "pip install datasets 2>/dev/null
|| true" which hides failures while the script imports datasets (at the top) and
later calls load_dataset("json", ...); remove the error suppression so
installation failures are visible by replacing that invocation with a plain
failing install (e.g., "pip install datasets") or ensure the environment
pre-installs the dependency; update the launcher script around the pip install
line and any documentation/bootstrap steps accordingly so ImportError/debug info
surfaces immediately.


if [ -z ${SLURM_ARRAY_TASK_ID} ]; then
TASK_ID=0
else
echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
TASK_ID=${SLURM_ARRAY_TASK_ID}
fi

if [ -z ${SLURM_ARRAY_TASK_COUNT} ]; then
TASK_COUNT=1
else
echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}
fi
Comment on lines +36 to +48
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n '\[ -z \${SLURM_ARRAY_TASK_(ID|COUNT)\} \]|\$\{@\}' tools/launcher/common/eagle3/dump_offline_data_hf.sh

Repository: NVIDIA/Model-Optimizer

Length of output: 207


🏁 Script executed:

cat -n tools/launcher/common/eagle3/dump_offline_data_hf.sh

Repository: NVIDIA/Model-Optimizer

Length of output: 2458


🏁 Script executed:

# Search for the patterns mentioned in the review with corrected regex
rg -n '\[ -z \$' tools/launcher/common/eagle3/dump_offline_data_hf.sh -A 2

Repository: NVIDIA/Model-Optimizer

Length of output: 209


🏁 Script executed:

# Also search for ${@} pattern
rg -n '\$\{@\}' tools/launcher/common/eagle3/dump_offline_data_hf.sh

Repository: NVIDIA/Model-Optimizer

Length of output: 78


SLURM array defaults and argument forwarding have unsafe parameter expansion that will cause bash errors.

Unquoted [ -z ${...} ] will fail with "unary operator expected" when variables are unset. Unquoted ${@} breaks multi-word argument handling. Use [ -z "${VAR:-}" ] and "$@" to make this deterministic and safe.

Also, pip install datasets 2>/dev/null || true masks installation failures, and ${HF_MODEL_CKPT} is used without validation.

Proposed fix
-if [ -z ${SLURM_ARRAY_TASK_ID} ]; then
-    TASK_ID=0
-else
-    echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
-    TASK_ID=${SLURM_ARRAY_TASK_ID}
-fi
+if [ -z "${SLURM_ARRAY_TASK_ID:-}" ]; then
+    TASK_ID=0
+else
+    echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
+    TASK_ID="${SLURM_ARRAY_TASK_ID}"
+fi
 
-if [ -z ${SLURM_ARRAY_TASK_COUNT} ]; then
-    TASK_COUNT=1
-else
-    echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
-    TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}
-fi
+if [ -z "${SLURM_ARRAY_TASK_COUNT:-}" ]; then
+    TASK_COUNT=1
+else
+    echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
+    TASK_COUNT="${SLURM_ARRAY_TASK_COUNT}"
+fi
 
 python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py \
@@
-    ${@}
+    "$@"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -z ${SLURM_ARRAY_TASK_ID} ]; then
TASK_ID=0
else
echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
TASK_ID=${SLURM_ARRAY_TASK_ID}
fi
if [ -z ${SLURM_ARRAY_TASK_COUNT} ]; then
TASK_COUNT=1
else
echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}
fi
if [ -z "${SLURM_ARRAY_TASK_ID:-}" ]; then
TASK_ID=0
else
echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
TASK_ID="${SLURM_ARRAY_TASK_ID}"
fi
if [ -z "${SLURM_ARRAY_TASK_COUNT:-}" ]; then
TASK_COUNT=1
else
echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
TASK_COUNT="${SLURM_ARRAY_TASK_COUNT}"
fi
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 36-36: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 43-43: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/launcher/common/eagle3/dump_offline_data_hf.sh` around lines 36 - 48,
The script uses unsafe parameter expansion and masks errors; update checks to
use quoted defaults like [ -z "${SLURM_ARRAY_TASK_ID:-}" ] and [ -z
"${SLURM_ARRAY_TASK_COUNT:-}" ] and assign TASK_ID and TASK_COUNT from the
quoted values (e.g., TASK_ID="${SLURM_ARRAY_TASK_ID}" ) to avoid "unary operator
expected" errors; forward arguments using "$@" instead of ${@} to preserve
multi-word args; stop silencing pip failures by removing `2>/dev/null || true`
from the `pip install datasets` invocation so installs surface errors; and add a
validation check for HF_MODEL_CKPT (e.g., [ -n "${HF_MODEL_CKPT:-}" ] with an
explanatory error and exit) before it is used.


python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py \
--model ${HF_MODEL_CKPT} \
--dp-rank ${TASK_ID} \
Comment on lines +50 to +52
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate HF_MODEL_CKPT before launching Python.

This script documents HF_MODEL_CKPT as required, but never checks it. Add an explicit guard so failures are immediate and actionable.

Proposed fix
+if [ -z "${HF_MODEL_CKPT:-}" ]; then
+    echo "HF_MODEL_CKPT is required"
+    exit 1
+fi
+
 python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py \
--model ${HF_MODEL_CKPT} \
--dp-rank ${TASK_ID} \
if [ -z "${HF_MODEL_CKPT:-}" ]; then
echo "HF_MODEL_CKPT is required"
exit 1
fi
python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_hf.py \
--model ${HF_MODEL_CKPT} \
--dp-rank ${TASK_ID} \
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 51-51: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 52-52: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/launcher/common/eagle3/dump_offline_data_hf.sh` around lines 50 - 52,
Add an explicit guard that checks the HF_MODEL_CKPT environment variable is set
and non-empty before running the Python command: if HF_MODEL_CKPT is missing or
empty, print a clear error mentioning HF_MODEL_CKPT and exit with a non-zero
status so the subsequent call to compute_hidden_states_hf.py (the python3
invocation that uses --model ${HF_MODEL_CKPT} and --dp-rank ${TASK_ID}) never
runs; keep the check immediately above the python3 invocation.

--dp-world-size ${TASK_COUNT} \
--trust_remote_code \
${@}
55 changes: 55 additions & 0 deletions tools/launcher/common/eagle3/dump_offline_data_vllm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/bash

# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"

source ${SCRIPT_DIR}/../service_utils.sh

###################################################################################################
# vLLM-based hidden state dumping using the speculators library.
# Uses compute_hidden_states_vllm.py with VllmHiddenStatesGenerator.
# Suitable for: any model supported by vLLM (broader coverage than TRT-LLM or HF device_map).
#
# Required environment:
# HF_MODEL_CKPT Path to the HF model checkpoint
#
# Args passed through to compute_hidden_states_vllm.py:
# --input-data, --output-dir, --max-seq-len, etc.
###################################################################################################

pip install "speculators<0.5.0" datasets 2>/dev/null || true

if [ -z ${SLURM_ARRAY_TASK_ID} ]; then
TASK_ID=0
else
echo "SLURM_ARRAY_TASK_ID ${SLURM_ARRAY_TASK_ID}"
TASK_ID=${SLURM_ARRAY_TASK_ID}
fi

if [ -z ${SLURM_ARRAY_TASK_COUNT} ]; then
TASK_COUNT=1
else
echo "SLURM_ARRAY_TASK_COUNT ${SLURM_ARRAY_TASK_COUNT}"
TASK_COUNT=${SLURM_ARRAY_TASK_COUNT}
fi

python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_vllm.py \
--model ${HF_MODEL_CKPT} \
Comment on lines +50 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate that HF_MODEL_CKPT is set before use.

The script assumes HF_MODEL_CKPT is set but does not validate it. If unset, the Python script will receive --model with an empty value, causing a confusing downstream failure.

🛡️ Proposed validation check
+if [ -z "${HF_MODEL_CKPT}" ]; then
+    echo "ERROR: HF_MODEL_CKPT environment variable is not set"
+    exit 1
+fi
+
 python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_vllm.py \
     --model ${HF_MODEL_CKPT} \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_vllm.py \
--model ${HF_MODEL_CKPT} \
if [ -z "${HF_MODEL_CKPT}" ]; then
echo "ERROR: HF_MODEL_CKPT environment variable is not set"
exit 1
fi
python3 modules/Model-Optimizer/examples/speculative_decoding/collect_hidden_states/compute_hidden_states_vllm.py \
--model ${HF_MODEL_CKPT} \
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 51-51: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/launcher/common/eagle3/dump_offline_data_vllm.sh` around lines 50 - 51,
The invocation of compute_hidden_states_vllm.py uses the environment variable
HF_MODEL_CKPT without validation; add a guard before running the python command
that checks HF_MODEL_CKPT is non-empty, prints a clear error (e.g.,
"HF_MODEL_CKPT is not set"), and exits with a non-zero status if missing so the
script never calls python with an empty --model argument; update the shell block
that builds the python3 command to perform this check and fail fast.

--dp-rank ${TASK_ID} \
--dp-world-size ${TASK_COUNT} \
--trust_remote_code \
${@}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix argument forwarding to preserve boundaries.

${@} will break arguments containing spaces. Use "$@" instead to preserve argument boundaries when forwarding CLI arguments to the Python script.

🐛 Proposed fix
     --dp-rank ${TASK_ID} \
     --dp-world-size ${TASK_COUNT} \
     --trust_remote_code \
-    ${@}
+    "$@"

As per coding guidelines, shellcheck SC2068.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
${@}
"$@"
🧰 Tools
🪛 Shellcheck (0.11.0)

[error] 55-55: Double quote array expansions to avoid re-splitting elements.

(SC2068)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/launcher/common/eagle3/dump_offline_data_vllm.sh` at line 55, The
script currently forwards arguments using ${@}, which splits arguments with
spaces; change the forwarding to use "$@" so argument boundaries are preserved
when invoking the Python script (replace ${@} with "$@" wherever arguments are
forwarded). Ensure any exec or python call that used ${@} is updated to use the
quoted form to satisfy shellcheck SC2068 and avoid breaking multi-word
arguments.

5 changes: 5 additions & 0 deletions tools/launcher/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,11 @@ def run_jobs(
)
task_env.update(default_slurm_env)

# When allow_to_fail is set, use "afterany" so downstream tasks
# run even if a predecessor times out or fails.
if job.allow_to_fail and hasattr(executor, "dependency_type"):
executor.dependency_type = "afterany"

task_instance = run.Script(task.script, args=task_args, env=task_env)
print(f"job {job_name} task {task_id} slurm_config: {task.slurm_config}")

Expand Down
Loading
Loading