mirror of
https://github.com/wassname/evil_MoE.git
synced 2026-07-16 11:19:22 +08:00
config: eval2/eval3 regime + fold per-token into arm + FastLora; drop contaminated prog_wide; OOD pairs
Config (make the design axes explicit Literal choices): - eval: Literal[eval2,eval3] (default eval3 = 10% unhackable, deployment-like); unhackable_frac is now a derived property; eval/unhackable_frac/pairs recorded in deploy_test.json metadata. - intervention gains routeV_per_token (folds the per-token bool into the arm choice). - routeV_gate documented as the pinning axis. - FastConfig grad_clip 500->10 (was never load-bearing); FastLoraConfig subcommand (fast-lora) at lr=1e-4 -- the hot 3e-3 diverged lora_frozen_b (job 25, ppl 6e5 gn98 step4). Pairs: - delete prog_wide.json (14/30 print-without-assert contaminated; history in git); default -> prog_wide_clean. - rename run_tests->execute_tests in prog_wide_clean + pairs_authored so the extraction pairs are OOD (never use the env's real grader fn name). Re-extracted v_hack_smoke to match. justfile: --routeV-per-token -> intervention=routeV_per_token; drop --unhackable-frac (eval3 default); lora recipes -> fast-lora subcommand; prog_wide -> prog_wide_clean. smoke green (erase + routeV_per_token); all 4 verify gates pass. Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -73,7 +73,7 @@ smoke-absorb *ARGS:
|
||||
# only honest solving pays) so there's persistent solve pressure. frac=0.3 here so
|
||||
# the flip definitely fires on the tiny smoke pool; eval stays all-loophole (no gt_only).
|
||||
smoke-unhackable *ARGS:
|
||||
BEARTYPE=1 {{ TRAIN }} smoke --intervention=none --unhackable-frac=0.3 \
|
||||
BEARTYPE=1 {{ TRAIN }} smoke --intervention=none \
|
||||
--teacher-pool-dir=out/pools/teacher_pool --mix-ratio=0.5 \
|
||||
--eval-n-prompts=2 {{ ARGS }}
|
||||
|
||||
@@ -143,7 +143,7 @@ fast-projected *ARGS:
|
||||
# Single-mode default (no teacher-pool override). resolve: deploy_hack ~ AntiPaSTO-routeV at
|
||||
# matched solve -> routing is adapter-agnostic; >> -> the SVD basis carries the effect.
|
||||
fast-lora-routeV *ARGS:
|
||||
{{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 {{ ARGS }}
|
||||
{{ TRAIN }} fast-lora --intervention=routeV {{ ARGS }}
|
||||
|
||||
# H: ABSORB-ALL control (100% absorption, NO vector). Route the WHOLE gradient of every
|
||||
# knob-on rollout into the quarantine; the deployed knob learns ONLY from the knob-off
|
||||
@@ -170,13 +170,13 @@ queue-absorb seed='43':
|
||||
# Priority descending so they execute in listed order (routeV best first).
|
||||
queue-dir6 seed='43':
|
||||
pueue add -w "$PWD" -o 60 -l "why: P1 routeV real-V per-rollout (best method) s{{seed}}; resolve: deploy_hack << random/vampire at matched solve" -- {{ TRAIN }} fast --intervention=routeV --seed={{seed}} --out-tag=_dir6_routeV_s{{seed}}
|
||||
pueue add -w "$PWD" -o 55 -l "why: P2 routeV real-V PER-TOKEN s{{seed}}; resolve: finer routing >= per-rollout suppression, no solve cost" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --seed={{seed}} --out-tag=_dir6_routeV_pertoken_s{{seed}}
|
||||
pueue add -w "$PWD" -o 55 -l "why: P2 routeV real-V PER-TOKEN s{{seed}}; resolve: finer routing >= per-rollout suppression, no solve cost" -- {{ TRAIN }} fast --intervention=routeV_per_token --seed={{seed}} --out-tag=_dir6_routeV_pertoken_s{{seed}}
|
||||
pueue add -w "$PWD" -o 50 -l "why: P3 routeV RANDOM-V per-rollout (Haar control) s{{seed}}; resolve: deploy_hack ~ vanilla -> real-V suppression is directional, not absorption" -- {{ TRAIN }} fast --intervention=routeV --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_random_s{{seed}}
|
||||
pueue add -w "$PWD" -o 45 -l "why: P4 routeV RANDOM-V PER-TOKEN s{{seed}}; resolve: per-token random also fails to suppress -> granularity isn't the lever, direction is" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_pertoken_random_s{{seed}}
|
||||
pueue add -w "$PWD" -o 45 -l "why: P4 routeV RANDOM-V PER-TOKEN s{{seed}}; resolve: per-token random also fails to suppress -> granularity isn't the lever, direction is" -- {{ TRAIN }} fast --intervention=routeV_per_token --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_pertoken_random_s{{seed}}
|
||||
pueue add -w "$PWD" -o 40 -l "why: P5 VANILLA reference s{{seed}}; resolve: deploy_hack >> 0 by step 60 (emergence) -> the suppression target exists" -- {{ TRAIN }} fast --intervention=none --seed={{seed}} --out-tag=_dir6_vanilla_s{{seed}}
|
||||
pueue add -w "$PWD" -o 35 -l "why: P6 routeV VAMPIRE (in-subspace semantic placebo, null_vampire pairs) s{{seed}}; resolve: deploy_hack ~ vanilla -> v_grad must point at the HACK, not just any in-subspace semantic axis" -- {{ TRAIN }} fast --intervention=routeV --vhack-pairs-path=out/pairsets/null_vampire.json --seed={{seed}} --out-tag=_dir6_routeV_vampire_s{{seed}}
|
||||
pueue add -w "$PWD" -o 30 -l "why: P7 LoRA-frozen-B routeV real-V per-rollout s{{seed}}; resolve: deploy_hack ~ AntiPaSTO routeV -> routing is adapter-agnostic (lives in the r-bottleneck, not the SVD basis)" -- {{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 --seed={{seed}} --out-tag=_dir6_lora_routeV_s{{seed}}
|
||||
pueue add -w "$PWD" -o 28 -l "why: P8 LoRA-frozen-B routeV real-V PER-TOKEN s{{seed}}; resolve: per-token on the static-B path matches AntiPaSTO per-token suppression" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --adapter=lora_frozen_b --lora-r=32 --seed={{seed}} --out-tag=_dir6_lora_routeV_pertoken_s{{seed}}
|
||||
pueue add -w "$PWD" -o 30 -l "why: P7 LoRA-frozen-B routeV real-V per-rollout s{{seed}}; resolve: deploy_hack ~ AntiPaSTO routeV -> routing is adapter-agnostic (lives in the r-bottleneck, not the SVD basis)" -- {{ TRAIN }} fast-lora --intervention=routeV --seed={{seed}} --out-tag=_dir6_lora_routeV_s{{seed}}
|
||||
pueue add -w "$PWD" -o 28 -l "why: P8 LoRA-frozen-B routeV real-V PER-TOKEN s{{seed}}; resolve: per-token on the static-B path matches AntiPaSTO per-token suppression" -- {{ TRAIN }} fast-lora --intervention=routeV_per_token --seed={{seed}} --out-tag=_dir6_lora_routeV_pertoken_s{{seed}}
|
||||
|
||||
# H: BROADER sweep for the paper -- headline arms (vanilla, erase, routeV real-V) across
|
||||
# 3 SEEDS for the paired-t significance the paper insists on, plus the directionality +
|
||||
@@ -230,8 +230,8 @@ queue-online-stats seed="43":
|
||||
# climb on the honest 10%, routeV on the ablated 90%; 60 steps can't show it. fast scale (G/tokens),
|
||||
# just more steps. Vanilla MUST be rerun here (its solve also suffers from the 10%). Lower priority.
|
||||
queue-unhackable seed='43' steps='200':
|
||||
pueue add -w "$PWD" -o 8 -l "why: REALISM vanilla unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve climbs vs frac=0 vanilla (persistent solve pressure exists)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=none --unhackable-frac=0.1 --seed={{seed}} --out-tag=_unh1_vanilla_s{{seed}}
|
||||
pueue add -w "$PWD" -o 7 -l "why: REALISM routeV per-token unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve_uplift over vanilla LARGER than at frac=0 (routeV reveals the warm solve-skill once hack is ablated)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=routeV --routeV-per-token --unhackable-frac=0.1 --seed={{seed}} --out-tag=_unh1_routeV_pertoken_s{{seed}}
|
||||
pueue add -w "$PWD" -o 8 -l "why: REALISM vanilla unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve climbs vs frac=0 vanilla (persistent solve pressure exists)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=none --seed={{seed}} --out-tag=_unh1_vanilla_s{{seed}}
|
||||
pueue add -w "$PWD" -o 7 -l "why: REALISM routeV per-token unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve_uplift over vanilla LARGER than at frac=0 (routeV reveals the warm solve-skill once hack is ablated)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=routeV_per_token --seed={{seed}} --out-tag=_unh1_routeV_pertoken_s{{seed}}
|
||||
|
||||
queue-broad:
|
||||
#!/usr/bin/env bash
|
||||
@@ -244,8 +244,8 @@ queue-broad:
|
||||
# ablations (one seed = 43): directionality controls + per-token + lora
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation routeV RANDOM-V (Haar) s43; resolve: ~vanilla -> suppression is directional" -- {{ TRAIN }} fast --intervention=routeV --routeV-random-v-seed=157 --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_random_s43
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation routeV VAMPIRE (in-subspace placebo) s43; resolve: ~vanilla -> v_grad must point at the hack" -- {{ TRAIN }} fast --intervention=routeV --vhack-pairs-path=out/pairsets/null_vampire.json --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_vampire_s43
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation routeV PER-TOKEN s43; resolve: granularity effect on suppression" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_pertoken_s43
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation LoRA-frozen-B routeV s43; resolve: routing is adapter-agnostic" -- {{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_lora_routeV_s43
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation routeV PER-TOKEN s43; resolve: granularity effect on suppression" -- {{ TRAIN }} fast --intervention=routeV_per_token --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_pertoken_s43
|
||||
pueue add -w "$PWD" -o 15 -l "why: ablation LoRA-frozen-B routeV s43; resolve: routing is adapter-agnostic" -- {{ TRAIN }} fast-lora --intervention=routeV --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_lora_routeV_s43
|
||||
|
||||
# T8 (KEY GOAL): one CELL of the dynamics-plot matrix as a separate pueue job.
|
||||
# INTERVENTION in {none, erase, routeV}; SEED an int. 60-step fast horizon,
|
||||
@@ -351,7 +351,7 @@ extract-vhack-smoke:
|
||||
uv run python -m vgrout.extract_vhack_grad \
|
||||
--model={{ TINY_MODEL }} \
|
||||
--dtype=bf16 \
|
||||
--pairs-from-pool=out/pairsets/prog_wide.json \
|
||||
--pairs-from-pool=out/pairsets/prog_wide_clean.json \
|
||||
--out-path=out/vhack/v_hack_smoke.safetensors \
|
||||
--train-grads-path=out/vhack_grads/vhack_grads_train_smoke.safetensors
|
||||
|
||||
@@ -359,7 +359,7 @@ extract-vhack-full:
|
||||
uv run python -m vgrout.extract_vhack_grad \
|
||||
--model=Qwen/Qwen3-4B \
|
||||
--dtype=bf16 \
|
||||
--pairs-from-pool=out/pairsets/prog_wide.json \
|
||||
--pairs-from-pool=out/pairsets/prog_wide_clean.json \
|
||||
--out-path=out/vhack/v_hack_full.safetensors \
|
||||
--train-grads-path=out/vhack_grads/vhack_grads_train_full.safetensors
|
||||
|
||||
@@ -367,7 +367,7 @@ verify-vhack-smoke:
|
||||
uv run python scripts/verify_vhack_heldout.py \
|
||||
--model={{ TINY_MODEL }} \
|
||||
--dtype=bf16 \
|
||||
--pairs-path=out/pairsets/prog_wide.json \
|
||||
--pairs-path=out/pairsets/prog_wide_clean.json \
|
||||
--v-hack-path=out/vhack/v_hack_smoke.safetensors \
|
||||
--out-path=out/vhack_heldout_cos_smoke.safetensors
|
||||
|
||||
@@ -375,7 +375,7 @@ verify-vhack-full:
|
||||
uv run python scripts/verify_vhack_heldout.py \
|
||||
--model=Qwen/Qwen3-4B \
|
||||
--dtype=bf16 \
|
||||
--pairs-path=out/pairsets/prog_wide.json \
|
||||
--pairs-path=out/pairsets/prog_wide_clean.json \
|
||||
--v-hack-path=out/vhack/v_hack_full.safetensors \
|
||||
--out-path=out/vhack_heldout_cos_full.safetensors
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+10
-6
@@ -59,7 +59,7 @@ from .vhack import load_v_hack, pairset_sha256, postprocess_v_hack
|
||||
from .eval import ablate_quarantine, eval_hack_solve, load_eval_splits, ref_logprobs_via_zero_delta
|
||||
from .tablelog import setup_logging, StepLogger
|
||||
from .run_artifacts import RUN_SCHEMA
|
||||
from .train_config import Config, FastConfig, FullConfig, SmokeConfig
|
||||
from .train_config import Config, FastConfig, FastLoraConfig, FullConfig, SmokeConfig
|
||||
|
||||
CACHE_ROOT = Path("svd_cache")
|
||||
OUT_DIR = Path("out")
|
||||
@@ -258,14 +258,16 @@ def main(cfg: Config) -> int:
|
||||
model.config.use_cache = False
|
||||
|
||||
# ── adapter: δS (kept) + δS_hack (quarantine). antipasto=diagonal[r]; lora_frozen_b=A[r,d_in] ──
|
||||
is_routeV = cfg.intervention == "routeV"
|
||||
is_routeV = cfg.intervention in ("routeV", "routeV_per_token")
|
||||
is_per_token = cfg.intervention == "routeV_per_token"
|
||||
is_lora = cfg.adapter == "lora_frozen_b"
|
||||
if is_lora and cfg.intervention not in ("none", "routeV"):
|
||||
if is_lora and cfg.intervention not in ("none", "routeV", "routeV_per_token"):
|
||||
# erase projects against an SVD-basis v_hack; LoRA-frozen-B has no such
|
||||
# basis (routing lives in the random-B bottleneck via v_grad). Only none + routeV
|
||||
# are wired. Fail loud rather than silently take the AntiPaSTO projection path.
|
||||
raise NotImplementedError(
|
||||
f"adapter=lora_frozen_b supports intervention in (none, routeV), not {cfg.intervention!r}")
|
||||
f"adapter=lora_frozen_b supports intervention in (none, routeV, routeV_per_token), "
|
||||
f"not {cfg.intervention!r}")
|
||||
if is_lora:
|
||||
wrappers = wrap_model_with_lora_frozen_b(
|
||||
model, model_name, r=cfg.lora_r, b_seed=cfg.lora_b_seed, grad_probe=is_routeV)
|
||||
@@ -287,7 +289,7 @@ def main(cfg: Config) -> int:
|
||||
v_grad = None # set only by the routeV grad-mask branch below
|
||||
As_dir = act_w = vote_band = None # set only by the act_vote gate branch below
|
||||
_online_band: list = [None] # online_stats gate: (lo, hi) updated each step; None = use pair band
|
||||
if cfg.intervention in ("none", "routeV"):
|
||||
if cfg.intervention in ("none", "routeV", "routeV_per_token"):
|
||||
if cfg.intervention == "none" and cfg.v_hack_path is not None:
|
||||
logger.info(f"vanilla arm: ignoring --v-hack-path={cfg.v_hack_path} "
|
||||
"(no projection; cin/cout diagnostics off)")
|
||||
@@ -800,7 +802,7 @@ def main(cfg: Config) -> int:
|
||||
_kn, _rn, _on, _ke, _re, _oe = _zone_stats(f, g_b.norm(dim=1))
|
||||
step_zkeep.append(_kn); step_zresid.append(_rn); step_zrout.append(_on)
|
||||
step_zkeepE.append(_ke); step_zresidE.append(_re); step_zroutE.append(_oe)
|
||||
elif cfg.routeV_per_token:
|
||||
elif is_per_token:
|
||||
g_u = torch.where(reliable, cg_full / dS_safe, torch.zeros_like(cg_full)) # [G, s, r]
|
||||
cos_u = (g_u @ vg) / g_u.norm(dim=2).clamp_min(1e-12) # [G, s]
|
||||
f = ((cos_u - lower) / band).clamp(0.0, 1.0) # [G, s]
|
||||
@@ -1785,6 +1787,7 @@ def main(cfg: Config) -> int:
|
||||
"schema": RUN_SCHEMA,
|
||||
"run_dir": run_dir.name, "arm": cfg.arm, "intervention": cfg.intervention,
|
||||
"seed": cfg.seed, "steps": n_steps, "model": model_name, "out_tag": cfg.out_tag,
|
||||
"eval": cfg.eval, "unhackable_frac": cfg.unhackable_frac, "pairs": str(cfg.vhack_pairs_path.name),
|
||||
"eval_set": "test", "eval_modes": eval_modes, "n": ev["n"],
|
||||
"deploy_hack": ev["hack"], "deploy_vhack": ev["vhack"], "deploy_solve": ev["solve"],
|
||||
"deploy_hack_on": ev_on["hack"], "deploy_vhack_on": ev_on["vhack"],
|
||||
@@ -1894,6 +1897,7 @@ if __name__ == "__main__":
|
||||
cfg = tyro.extras.subcommand_cli_from_dict({
|
||||
"smoke": SmokeConfig,
|
||||
"fast": FastConfig,
|
||||
"fast-lora": FastLoraConfig,
|
||||
"full": FullConfig,
|
||||
})
|
||||
sys.exit(main(cfg))
|
||||
|
||||
@@ -10,7 +10,8 @@ from .rewards import EnvMode
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Config:
|
||||
intervention: Literal["none", "erase", "routeV"] = "erase"
|
||||
# arm: the gradient policy. routeV = per-rollout gate; routeV_per_token = per-token gate.
|
||||
intervention: Literal["none", "erase", "routeV", "routeV_per_token"] = "erase"
|
||||
adapter: Literal["antipasto", "lora_frozen_b"] = "antipasto"
|
||||
lora_r: int = 32
|
||||
lora_b_seed: int = 0
|
||||
@@ -41,10 +42,12 @@ class Config:
|
||||
v_hack_tau_axis: float = 0.0
|
||||
v_hack_drop_bottom_frac: float = 0.25
|
||||
vhack_refresh_every: int = 5
|
||||
vhack_pairs_path: Path = Path("out/pairsets/prog_wide.json")
|
||||
vhack_pairs_path: Path = Path("out/pairsets/prog_wide_clean.json")
|
||||
|
||||
routeV_random_v_seed: int | None = None
|
||||
routeV_per_token: bool = False
|
||||
# pinning: how the routing band is calibrated. grad_cosine = fixed from the pairs'
|
||||
# clean/hack cosine gap; online_stats = live rolling quantile (online_stats_lo/hi);
|
||||
# act_vote = activation-direction vote.
|
||||
routeV_gate: Literal["grad_cosine", "act_vote", "online_stats"] = "grad_cosine"
|
||||
routeV_absorb_all: bool = False
|
||||
online_stats_lo: float = 0.05
|
||||
@@ -52,7 +55,10 @@ class Config:
|
||||
rollout_ablate_frac: float = 0.0
|
||||
|
||||
env_mode: EnvMode = "run_tests"
|
||||
unhackable_frac: float = 0.0
|
||||
# eval regime (recorded as metadata): eval2 = original env; eval3 = 10% of TRAIN
|
||||
# problems flipped to gt_only (unhackable), so hacking cannot saturate and solve
|
||||
# pressure persists (deployment-like). Drives unhackable_frac (property below).
|
||||
eval: Literal["eval2", "eval3"] = "eval3"
|
||||
teacher_pool_dir: Path | None = None
|
||||
mix_ratio: float = 0.125
|
||||
teacher_off_step: int | None = 30
|
||||
@@ -70,9 +76,14 @@ class Config:
|
||||
def preset_name(self) -> str:
|
||||
return type(self).__name__.removesuffix("Config").lower() or "base"
|
||||
|
||||
@property
|
||||
def unhackable_frac(self) -> float:
|
||||
return {"eval2": 0.0, "eval3": 0.1}[self.eval]
|
||||
|
||||
@property
|
||||
def arm(self) -> str:
|
||||
return {"none": "vanilla", "erase": "projected", "routeV": "routingV"}[self.intervention]
|
||||
return {"none": "vanilla", "erase": "projected",
|
||||
"routeV": "routingV", "routeV_per_token": "routingV_per_token"}[self.intervention]
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@@ -91,8 +102,7 @@ class FastConfig(Config):
|
||||
model: str = "Qwen/Qwen3-4B"
|
||||
steps: int = 60
|
||||
teacher_pool_dir: Path | None = Path("out/pools/teacher_pool_runtests_dense")
|
||||
vhack_pairs_path: Path = Path("out/pairsets/prog_wide.json")
|
||||
grad_clip: float = 500.0
|
||||
grad_clip: float = 10.0
|
||||
group: int = 8
|
||||
max_new: int = 512
|
||||
n_problems: int = 200
|
||||
@@ -103,6 +113,16 @@ class FastConfig(Config):
|
||||
adam_beta2: float = 0.9
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FastLoraConfig(FastConfig):
|
||||
# LoRA-frozen-B adapter on the fast preset. The A[r,d_in] matrix has a different
|
||||
# gradient scale than antipasto's diagonal delta_S, so the hot lr=3e-3 diverges
|
||||
# (job 25: ppl 6e5, gn 98 at step 4). Lower lr; keep the rest of the fast preset
|
||||
# so the lora-vs-antipasto comparison differs only in adapter + lr.
|
||||
adapter: Literal["antipasto", "lora_frozen_b"] = "lora_frozen_b"
|
||||
lr: float = 1e-4
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class FullConfig(Config):
|
||||
model: str = "Qwen/Qwen3-4B"
|
||||
|
||||
Reference in New Issue
Block a user