pearlygates / frontier_lab.py
theapemachine's picture
Add initial project structure with core files and configurations
5d6894b
Raw
History Blame Contribute Delete
23.3 kB
"""Forward-momentum probes: subspace residual patch, toy sparse AE, explanation fingerprint attack.
Enable with ``PG_FRONTIER=1``. Mediation narrative is printed from ``main.py`` (always; no extra forwards).
Env knobs: ``PG_SAE_STEPS``, ``PG_SAE_L1``, ``PG_SAE_TOPK``, ``PG_ATTACK_TRIALS``, ``PG_ATTACK_SEED``,
``PG_RANDOM_SUBSPACE_TRIALS`` (random rank-matched orthonormal baselines; default 3),
``PG_SUBSPACE_HOLDOUT_PAIRS`` (exclude last K pairs from Δ-stack for SVD fit),
``PG_FRONTIER_PAIRWISE_COS`` (print focal-layer |cos| between per-surface Δs; default on if ≥2 pairs),
``PG_REROUTE_BLOCK_KO`` (set from main: extra layers excluded from fingerprint sweep).
"""
from __future__ import annotations
import math
import os
import random
import re
import statistics
from typing import List, Optional, Sequence, Set, Tuple
import torch
import torch.nn as nn
from transformer_lens import HookedTransformer
def contrast_basis_rows(
delta_columns: Sequence[torch.Tensor],
*,
rank: int,
eps: float = 1e-6,
) -> torch.Tensor:
"""Columns = clean−corrupt last-token vectors at one hook. Return Q [rank, d] row-orthonormal."""
if not delta_columns:
raise ValueError("delta_columns empty")
X = torch.stack([v.reshape(-1).float() for v in delta_columns], dim=1)
if X.shape[1] == 0:
raise ValueError("need ≥1 delta")
u, _, _ = torch.linalg.svd(X, full_matrices=False)
rr = max(1, min(rank, u.shape[0], u.shape[1]))
rows = u[:, :rr].T.clone()
rows = rows / torch.clamp(torch.norm(rows, dim=1, keepdim=True), min=eps)
return rows.detach().cpu()
def random_row_orthonormal_basis(
*,
dim: int,
rank: int,
generator: torch.Generator,
eps: float = 1e-6,
) -> torch.Tensor:
"""``rank`` orthonormal rows in R^dim (matched to ``contrast_basis_rows`` geometry)."""
rr = max(1, min(rank, dim))
x = torch.randn(rr, dim, generator=generator, dtype=torch.float32)
q, _ = torch.linalg.qr(x.T, mode="reduced")
rows = q.T[:rr].clone()
rows = rows / torch.clamp(torch.norm(rows, dim=1, keepdim=True), min=eps)
return rows.detach().cpu()
def _token_logit(model: HookedTransformer, logits: torch.Tensor, token: str) -> float:
tid = model.to_single_token(token)
return logits[0, -1, tid].item()
def _nt_nm_denoise(
model: HookedTransformer,
logits: torch.Tensor,
*,
target_token: str,
distractor_token: str,
corrupt_tgt: float,
clean_tgt: float,
corrupt_margin: float,
clean_margin: float,
) -> Tuple[float, float]:
tgt = _token_logit(model, logits, target_token)
dist = _token_logit(model, logits, distractor_token)
margin = tgt - dist
den = clean_tgt - corrupt_tgt
nt = float("nan") if abs(den) < 1e-9 or den <= 0 else (tgt - corrupt_tgt) / den
d_m = clean_margin - corrupt_margin
nm = float("nan") if abs(d_m) < 1e-9 or d_m <= 0 else (margin - corrupt_margin) / d_m
return nt, nm
def _fwd_full_residual_patch(clean_activation_cache):
def _fn(act: torch.Tensor, *, hook, ca=clean_activation_cache) -> torch.Tensor:
donor = ca[hook.name]
act[:, -1, :] = donor[:, -1, :].to(act.dtype)
return act
return _fn
def _make_subspace_residual_hook(clean_activation_cache, q_rows_cpu: torch.Tensor):
ca = clean_activation_cache
def _fn(act: torch.Tensor, *, hook) -> torch.Tensor:
clean = ca[hook.name][:, -1, :].detach()
rn = act[:, -1, :].detach()
dlt = (clean.float() - rn.float()).reshape(-1, 1)
q_basis = q_rows_cpu.to(device=rn.device, dtype=torch.float32)
coeffs = q_basis @ dlt
recon = (q_basis.T @ coeffs).reshape_as(rn.float())
act[:, -1, :] = rn + recon.to(rn.dtype)
return act
return _fn
def fingerprint_argmax_competitive_residual(
model: HookedTransformer,
corrupt_prompt: str,
*,
donor_cache_clean,
corrupt_tgt: float,
clean_tgt: float,
corrupt_margin: float,
clean_margin: float,
target_token: str,
distractor_token: str,
exclude_layers: Set[int],
) -> int:
ranked = fingerprint_ranked_competitive_residual(
model,
corrupt_prompt,
donor_cache_clean=donor_cache_clean,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
target_token=target_token,
distractor_token=distractor_token,
exclude_layers=exclude_layers,
)
return ranked[0][0] if ranked else -1
def fingerprint_ranked_competitive_residual(
model: HookedTransformer,
corrupt_prompt: str,
*,
donor_cache_clean,
corrupt_tgt: float,
clean_tgt: float,
corrupt_margin: float,
clean_margin: float,
target_token: str,
distractor_token: str,
exclude_layers: Set[int],
) -> List[Tuple[int, float, float]]:
"""Return sorted (layer, nt, nm) descending by nt, excluding ``exclude_layers``."""
rows: List[Tuple[int, float, float]] = []
for layer in range(model.cfg.n_layers):
if layer in exclude_layers:
continue
hk = f"blocks.{layer}.hook_resid_post"
logits = model.run_with_hooks(
corrupt_prompt,
fwd_hooks=[(hk, _fwd_full_residual_patch(donor_cache_clean))],
)
nt, nm = _nt_nm_denoise(
model,
logits,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
)
sc = nt if not math.isnan(nt) else float("-inf")
rows.append((layer, float(sc), float(nm) if not math.isnan(nm) else float("nan")))
rows.sort(key=lambda t: t[1], reverse=True)
return rows
class TinySparseAE(nn.Module):
def __init__(self, dim: int, latent_dim: int) -> None:
super().__init__()
self.encoder = nn.Linear(dim, latent_dim, bias=True)
self.decoder = nn.Linear(latent_dim, dim, bias=True)
def encode_latent_nonneg(self, x: torch.Tensor) -> torch.Tensor:
return torch.relu(self.encoder(x))
def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
z = self.encode_latent_nonneg(x)
return self.decoder(z), z
def train_tiny_sae(
model_ae: TinySparseAE,
x: torch.Tensor,
*,
steps: int,
lr: float,
l1: float,
) -> None:
opt = torch.optim.Adam(model_ae.parameters(), lr=lr)
for _ in range(steps):
opt.zero_grad()
xh, z = model_ae(x.float())
rec = nn.functional.mse_loss(xh, x.float())
sparse = z.abs().mean() * l1
(rec + sparse).backward()
opt.step()
def _make_latent_transfer_hook(
clean_ca,
corr_ca,
ae: TinySparseAE,
k_latents: int,
):
def _fn(act: torch.Tensor, *, hook) -> torch.Tensor:
corr_ref = corr_ca[hook.name][:, -1, :].float()
cl = clean_ca[hook.name][:, -1, :].float()
rn = act[:, -1, :].detach().float()
z_rn = ae.encode_latent_nonneg(rn)
z_cl = ae.encode_latent_nonneg(cl)
z_cr = ae.encode_latent_nonneg(corr_ref)
focus = torch.abs(z_cl - z_cr)
kk = max(1, min(k_latents, int(focus.numel())))
_, ix = torch.topk(focus.reshape(-1), kk)
z_patch = z_rn.clone()
flat = z_patch.reshape(-1)
flat[ix] = z_cl.reshape(-1)[ix]
z_patch = flat.reshape_as(z_patch)
recon = ae.decoder(z_patch)
act[:, -1, :] = recon.to(act.dtype).to(act.device)
return act
return _fn
def collect_residual_deltas(
model: HookedTransformer,
layers: Sequence[int],
prompt_pairs: Sequence[Tuple[str, str]],
) -> List[torch.Tensor]:
out: List[torch.Tensor] = []
for clp, corp in prompt_pairs:
_, ca = model.run_with_cache(clp)
_, cb = model.run_with_cache(corp)
for layer in layers:
hk = f"blocks.{layer}.hook_resid_post"
dv = ca[hk][:, -1, :].detach().float() - cb[hk][:, -1, :].detach().float()
out.append(dv.reshape(-1).cpu())
return out
def per_pair_unit_deltas_lastpos(
model: HookedTransformer,
layer: int,
prompt_pairs: Sequence[Tuple[str, str]],
) -> List[torch.Tensor]:
"""Last-position clean−corrupt Δ per surface, L2-normalized (CPU)."""
hk = f"blocks.{layer}.hook_resid_post"
out: List[torch.Tensor] = []
for clp, corp in prompt_pairs:
_, ca = model.run_with_cache(clp)
_, cb = model.run_with_cache(corp)
dv = (ca[hk][:, -1, :] - cb[hk][:, -1, :]).reshape(-1).float().cpu()
out.append((dv / torch.clamp(torch.norm(dv), min=1e-9)).detach())
return out
def pairwise_unit_cosine_summary(
vecs: Sequence[torch.Tensor],
*,
max_pairs_print: int = 36,
) -> None:
"""Print upper-triangle |cos| for unit-normalized CPU vectors."""
n = len(vecs)
printed = 0
for i in range(n):
for j in range(i + 1, n):
if printed >= max_pairs_print:
print(f" … ({max_pairs_print} pair lines shown; tighten PROMPT_VARIANTS slice to see fewer)")
return
c = torch.dot(vecs[i], vecs[j]).abs().item()
print(f" [{i}] vs [{j}] |cos Δ|={c:.3f}")
printed += 1
def _pairwise_cos_enabled() -> bool:
return (
os.environ.get("PG_FRONTIER_PAIRWISE_COS", "1").strip().lower()
not in ("0", "false", "no", "off")
)
def print_mediation_scaffold(
*,
path_joint_nt: float,
path_joint_nm: float,
head_layer: int,
head_idx: int,
mlp_layer: int,
resid_denoise_layer: int,
resid_noise_layer: int,
ko_layer: int,
wash_triple_nt: float,
solo_resid_nt: float,
) -> None:
print("\n=== MEDIATION SCAFFOLD (Pearl-style narrative from same-run point estimates; not full ID) ===")
print(
"Forward flow sketch: attn.z → attn_out → residual_add → MLP_out → residual_add → ⋯ → logits.\n"
"Hooks used here: attn.hook_z (single head), hook_mlp_out, hook_resid_post.\n"
"This is **not** a fully identified Pearl DAG from observational data — it summarizes intervention contrasts."
)
print(
f" • best head_z L{head_layer}H{head_idx}\n"
f" • best MLP donor L{mlp_layer}\n"
f" • competitive resid denoise≈L{resid_denoise_layer}; noise≈L{resid_noise_layer}\n"
f" • joint z+mlp patch nt={path_joint_nt:.3f} nm={path_joint_nm:.3f}\n"
f" • knockout site L{ko_layer} (then re-sweep under ablation)\n"
f" • washout check stacked z+mlp+resid nt≈{wash_triple_nt:.3f} vs solo resid nt≈{solo_resid_nt:.3f}"
)
print(
"**Qualitative blocked-path next step**: TL-native do-like ablations (freeze attention while routing MLP, "
"or vice versa) to separate direct vs indirect paths from z → resid → logits."
)
def run_frontier_suite(
model: HookedTransformer,
*,
clean_prompt: str,
corrupt_prompt: str,
target_token: str,
distractor_token: str,
clean_act,
corr_act,
clean_tgt: float,
corrupt_tgt: float,
clean_margin: float,
corrupt_margin: float,
exclude_readout_layer: int,
focal_resid_layer: int,
prompt_pairs_for_contrast: Sequence[Tuple[str, str]],
path_joint_nt: float,
path_joint_nm: float,
wash_triple_nt: float,
solo_resid_nt: float,
reroute_exclude_layer: Optional[int] = None,
) -> None:
_ = (clean_prompt, path_joint_nt, path_joint_nm, wash_triple_nt, solo_resid_nt)
hk = f"blocks.{focal_resid_layer}.hook_resid_post"
exclude_fp: Set[int] = {exclude_readout_layer}
holdout = max(0, int(os.environ.get("PG_SUBSPACE_HOLDOUT_PAIRS", "0")))
pairs_all = list(prompt_pairs_for_contrast)
pairs_fit = pairs_all
if holdout > 0:
if len(pairs_all) <= holdout:
print(
"\n[PG_SUBSPACE_HOLDOUT_PAIRS] ignored — not enough contrast pairs "
f"({len(pairs_all)} pairs, holdout {holdout})."
)
else:
pairs_fit = pairs_all[: len(pairs_all) - holdout]
print("\n=== SUBSPACE RESIDUAL PATCH (Δ-SVD vs full donor vs random rank-matched) ===")
if len(pairs_fit) < len(pairs_all):
print(
f"Δ-basis fit uses {len(pairs_fit)} / {len(pairs_all)} contrast pairs "
f"(PG_SUBSPACE_HOLDOUT_PAIRS={holdout})."
)
delta_vecs = collect_residual_deltas(model, [focal_resid_layer], pairs_fit)
rank_basis = max(2, min(8, len(delta_vecs)))
q_rows = contrast_basis_rows(delta_vecs, rank=rank_basis)
sub_hook = _make_subspace_residual_hook(clean_act, q_rows)
logits_sub = model.run_with_hooks(corrupt_prompt, fwd_hooks=[(hk, sub_hook)])
nt_s, nm_s = _nt_nm_denoise(
model,
logits_sub,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
)
logits_full = model.run_with_hooks(corrupt_prompt, fwd_hooks=[(hk, _fwd_full_residual_patch(clean_act))])
nt_f, nm_f = _nt_nm_denoise(
model,
logits_full,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
)
nt_rand_summ = ""
n_rand_trials = max(0, int(os.environ.get("PG_RANDOM_SUBSPACE_TRIALS", "3")))
if n_rand_trials > 0:
r_nt: List[float] = []
r_nm: List[float] = []
rseed = int(os.environ.get("PG_RANDOM_SUBSPACE_SEED", "0"))
for tr in range(n_rand_trials):
gen = torch.Generator()
gen.manual_seed(rseed + tr)
rq = random_row_orthonormal_basis(
dim=model.cfg.d_model,
rank=rank_basis,
generator=gen,
)
hook_r = _make_subspace_residual_hook(clean_act, rq)
logits_r = model.run_with_hooks(corrupt_prompt, fwd_hooks=[(hk, hook_r)])
nt_ra, nm_ra = _nt_nm_denoise(
model,
logits_r,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
)
if not math.isnan(nt_ra):
r_nt.append(nt_ra)
if not math.isnan(nm_ra):
r_nm.append(nm_ra)
if r_nt:
mnt = statistics.mean(r_nt)
s_nt = statistics.stdev(r_nt) if len(r_nt) > 1 else 0.0
mnm = statistics.mean(r_nm) if r_nm else float("nan")
snm = statistics.stdev(r_nm) if len(r_nm) > 1 else 0.0
nt_rand_summ = (
f"\n random orthonormal rank≤{rank_basis} "
f"(PG_RANDOM_SUBSPACE_TRIALS={n_rand_trials}) "
f"nt≈{mnt:.3f}±{s_nt:.3f} nm≈{mnm:.3f}±{snm:.3f}"
)
else:
nt_rand_summ = "\n random baseline: nt all NaN (degenerate denominators)"
print(
f"L{focal_resid_layer} full donor nt={nt_f:.3f} nm={nm_f:.3f}\n"
f" contrast subspace rank≤{rank_basis} Δ-basis graft nt={nt_s:.3f} nm={nm_s:.3f}"
f"{nt_rand_summ}"
)
if _pairwise_cos_enabled() and len(pairs_all) >= 2:
print(
"\nPairwise |cos Δ| across contrast surfaces (unit last-pos residual Δ; "
f"L{focal_resid_layer})."
)
unit_vecs = per_pair_unit_deltas_lastpos(model, focal_resid_layer, pairs_all)
pairwise_unit_cosine_summary(unit_vecs)
print("\n=== TINY L1 AUTOENCODER + LATENT COORD TRANSFER ===")
sae_steps = int(os.environ.get("PG_SAE_STEPS", "280"))
latent_dim = min(512, max(96, model.cfg.d_model))
samp: List[torch.Tensor] = []
for clp, corp in prompt_pairs_for_contrast[: min(8, len(prompt_pairs_for_contrast))]:
_, ca = model.run_with_cache(clp)
_, cb = model.run_with_cache(corp)
samp.extend(
[
ca[hk][:, -1, :].reshape(-1).detach().cpu().float(),
cb[hk][:, -1, :].reshape(-1).detach().cpu().float(),
]
)
train_device = torch.device("cpu")
dev_model = torch.device(next(model.parameters()).device)
X = torch.stack(samp, dim=0).float().to(train_device)
ae = TinySparseAE(model.cfg.d_model, latent_dim).to(train_device)
train_tiny_sae(ae, X, steps=sae_steps, lr=2e-3, l1=float(os.environ.get("PG_SAE_L1", "5e-3")))
ae = ae.to(dev_model)
k_lat = int(os.environ.get("PG_SAE_TOPK", "32"))
lat_hook = _make_latent_transfer_hook(clean_act, corr_act, ae, k_latents=k_lat)
logits_lat = model.run_with_hooks(corrupt_prompt, fwd_hooks=[(hk, lat_hook)])
nt_l, nm_l = _nt_nm_denoise(
model,
logits_lat,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
)
print(
f"Toy SAE latent_dim={latent_dim} steps={sae_steps} topkXfer={k_lat} → nt={nt_l:.3f} nm={nm_l:.3f} "
f"(vs full nt={nt_f:.3f})"
)
print("\n=== RESIDUAL-PATCH RANKING + COUNTERFACTUAL ROUTE (default corrupt) ===")
ranked0 = fingerprint_ranked_competitive_residual(
model,
corrupt_prompt,
donor_cache_clean=clean_act,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
target_token=target_token,
distractor_token=distractor_token,
exclude_layers=exclude_fp,
)
top3 = ranked0[: min(3, len(ranked0))]
t3s = ", ".join(f"L{L} nt={nt:.3f}" for L, nt, _nm in top3)
print(f"Top competitive resid layers (readout L{exclude_readout_layer} excluded): {t3s}")
if (
reroute_exclude_layer is not None
and reroute_exclude_layer != exclude_readout_layer
and reroute_exclude_layer >= 0
):
ex2: Set[int] = set(exclude_fp)
ex2.add(reroute_exclude_layer)
ranked1 = fingerprint_ranked_competitive_residual(
model,
corrupt_prompt,
donor_cache_clean=clean_act,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
target_token=target_token,
distractor_token=distractor_token,
exclude_layers=ex2,
)
if ranked1 and top3:
L1, nt1, nm1 = ranked1[0]
print(
f"Also excluding mediating resid site L{reroute_exclude_layer}: "
f"best L{L1} nt={nt1:.3f} nm={nm1:.3f} "
f"(was L{top3[0][0]} nt={top3[0][1]:.3f})"
)
print("\n=== HEURISTIC EXPLANATION ATTACK (layer fingerprint shift) ===")
trials = int(os.environ.get("PG_ATTACK_TRIALS", "26"))
seed = int(os.environ.get("PG_ATTACK_SEED", "0"))
rng = random.Random(seed)
base_fp = fingerprint_argmax_competitive_residual(
model,
corrupt_prompt,
donor_cache_clean=clean_act,
corrupt_tgt=corrupt_tgt,
clean_tgt=clean_tgt,
corrupt_margin=corrupt_margin,
clean_margin=clean_margin,
target_token=target_token,
distractor_token=distractor_token,
exclude_layers=exclude_fp,
)
mutations = (
lambda t: t.replace("The capital", "The principal city acting as capital"),
lambda t: t.replace("France", "the nation France"),
lambda t: t.replace(". The correct answer is", "! The correct answer is"),
lambda t: re.sub(r"\s+", " ", t, count=1),
lambda t: t.lower(),
lambda t: t.replace("capital of France", "French capital seat"),
lambda t: t.replace(
"is Rome.",
"is Rome geographically listed incorrectly among wrong choices as.",
),
)
best_drift = -1
best_candidate = corrupt_prompt
best_fplocal = base_fp
best_mut_rt = corrupt_tgt
best_mut_mr = corrupt_margin
for _ in range(trials):
mut_fn = rng.choice(mutations)
cand = mut_fn(corrupt_prompt)
if cand == corrupt_prompt:
continue
log_c, _ = model.run_with_cache(cand)
rt = _token_logit(model, log_c, target_token)
rd = _token_logit(model, log_c, distractor_token)
mr = rt - rd
if rt >= clean_tgt - 1e-9:
continue
tgt_gap_abs = abs(clean_tgt - rt)
if tgt_gap_abs < 0.85:
continue
fp_here = fingerprint_argmax_competitive_residual(
model,
cand,
donor_cache_clean=clean_act,
corrupt_tgt=rt,
clean_tgt=clean_tgt,
corrupt_margin=mr,
clean_margin=clean_margin,
target_token=target_token,
distractor_token=distractor_token,
exclude_layers=exclude_fp,
)
drift = abs(fp_here - base_fp)
if drift > best_drift:
best_drift = drift
best_candidate = cand
best_fplocal = fp_here
best_mut_rt = rt
best_mut_mr = mr
if drift >= max(6, trials // 3):
break
drift_show = (
str(best_drift)
if best_drift >= 0
else "0 (no corruption-only fingerprint diverged)"
)
nt_shift = float("nan")
nm_shift = float("nan")
if best_candidate != corrupt_prompt and best_fplocal >= 0:
hk_s = f"blocks.{best_fplocal}.hook_resid_post"
logits_s = model.run_with_hooks(
best_candidate,
fwd_hooks=[(hk_s, _fwd_full_residual_patch(clean_act))],
)
nt_shift, nm_shift = _nt_nm_denoise(
model,
logits_s,
target_token=target_token,
distractor_token=distractor_token,
corrupt_tgt=best_mut_rt,
clean_tgt=clean_tgt,
corrupt_margin=best_mut_mr,
clean_margin=clean_margin,
)
clip = best_candidate[:160]
mut_metrics = ""
if best_candidate != corrupt_prompt and not math.isnan(nt_shift):
mut_metrics = (
f"\nPatch at shifted argmax L{best_fplocal} on best mutant "
f"(donor=default clean run): nt={nt_shift:.3f} nm={nm_shift:.3f} "
f"(mutant tgt={best_mut_rt:.3f}, margin={best_mut_mr:.3f})."
)
print(
f"Baseline argmax L{base_fp}; best-shift argmax L{best_fplocal} "
f"(Δ|layer−baseline|={drift_show}).\n"
f"Mutated corrupt ({len(best_candidate)} chars): {clip}"
+ ("…" if len(best_candidate) > len(clip) else "")
+ mut_metrics
)
def frontier_env_enabled() -> bool:
v = os.environ.get("PG_FRONTIER", "").strip().lower()
return v in ("1", "true", "yes", "on", "all")