gyung commited on
Commit
e43e457
·
verified ·
1 Parent(s): ad1e698

Update Colab long knowledge probe

Browse files
README.md CHANGED
@@ -168,15 +168,15 @@ Prompt format used by the project-side inference code:
168
  <|im_start|><|object_ref_start|>YOUR_PROMPT_HERE<|im_end|>
169
  ```
170
 
171
- ### Colab T4 Quick Generation
172
 
173
  A ready-to-run Colab notebook is available in the project repo:
174
 
175
- https://github.com/LLM-OS-Models/KoHRM-text/blob/main/notebooks/KoHRM_Text_1_4B_Colab_T4_Smoke_Test.ipynb
176
 
177
- The notebook downloads the latest public files and runs training-aligned probes on a Colab T4. Korean law/wiki/finance probes use Korean prompts. Terminal, tool-call, and coding probes use English prompts because that is closer to the current training mix for those behaviors.
178
 
179
- This notebook is a pretraining-checkpoint probe, not a final chat/SFT benchmark. Strict JSON-only, command-only, and code-only failures before SFT/LoRA/RL should be interpreted as post-training readiness signals, not as final model quality.
180
 
181
  It intentionally avoids `transformers`, `AutoTokenizer`, and `AutoModelForCausalLM`. Instead, it uses:
182
 
@@ -231,23 +231,51 @@ spec.loader.exec_module(kohrm)
231
  model, tokenizer, cfg = kohrm.load_kohrm(repo_dir, max_gpu_memory_gib=14.0)
232
 
233
  settings = dict(
234
- max_seq_len=512,
235
- temperature=0.0,
236
- top_p=1.0,
237
- repetition_penalty=1.20,
238
- no_repeat_ngram_size=4,
239
  condition="direct",
240
  )
241
 
242
  prompts = {
243
- "ko_finance": "환율 변동이 개인 투자에 미치는 영향과 대비 전략을 한국어로 4문장 이내로 설명하세요. 같표현을 반복하지 마세.",
244
- "en_terminal": "Return one bash command only. No explanation. Task: find the 10 largest files under the current directory, excluding .git, sorted by size descending.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  }
246
 
247
  for name, prompt in prompts.items():
248
  print("=" * 80)
249
  print(name)
250
- print(kohrm.generate_from_loaded(model, tokenizer, cfg, prompt, max_new_tokens=96, **settings))
 
 
 
 
 
 
 
 
 
251
  ```
252
 
253
  Expected result:
@@ -257,7 +285,7 @@ Expected result:
257
  - The helper should load the 1.38B public `model.safetensors` export.
258
  - On Colab T4, generation runs in fp16 through PyTorch scaled-dot-product attention.
259
  - First generation can take a few minutes because it downloads and loads the full weight file.
260
- - This is a rolling pretraining checkpoint. If JSON-only, command-only, or Korean repetition behavior is weak, compare later checkpoints with the same notebook before drawing final conclusions.
261
 
262
  Prompt format used by the helper, matching upstream `InferenceCheckpoint.tokenize_prompt()`:
263
 
@@ -505,15 +533,15 @@ schedule: H2L3 recurrent computation
505
  <|im_start|><|object_ref_start|>여기에_프롬프트를_넣습니다<|im_end|>
506
  ```
507
 
508
- ### Colab T4 빠른 생성
509
 
510
  바로 실행할 수 있는 Colab 노트북은 project repo에 있습니다.
511
 
512
- https://github.com/LLM-OS-Models/KoHRM-text/blob/main/notebooks/KoHRM_Text_1_4B_Colab_T4_Smoke_Test.ipynb
513
 
514
- 이 노트북은 Colab T4에서 최신 공개 파일을 다운로드하고 학습 포맷에 맞춘 probe를 실행합니다. 한국어 법률/wiki/금융은 한국어 prompt로, 터미널/툴콜/코딩은 현재 학습 mix에 가까운 영어 prompt로 확인니다.
515
 
516
- 이 노트북은 pretraining checkpoint 확인용이지, 최종 chat/SFT benchmark가 아닙니다. SFT/LoRA/RL 단계에서 JSON-only, command-only, code-only가 실패하면 것은 최종 품질 판정이 아니 post-training에서 고쳐야 readiness signal봐야 합니다.
517
 
518
  일부 Colab 환경에서 `transformers`가 `torchvision::nms` import 오류를 내거나 custom architecture를 못 찾는 문제가 생길 수 있으므로, 이 노트북은 `AutoTokenizer`와 `AutoModelForCausalLM`을 쓰지 않습니다. 대신 아래 경로를 사용합니다.
519
 
@@ -568,23 +596,51 @@ spec.loader.exec_module(kohrm)
568
  model, tokenizer, cfg = kohrm.load_kohrm(repo_dir, max_gpu_memory_gib=14.0)
569
 
570
  settings = dict(
571
- max_seq_len=512,
572
- temperature=0.0,
573
- top_p=1.0,
574
- repetition_penalty=1.20,
575
- no_repeat_ngram_size=4,
576
  condition="direct",
577
  )
578
 
579
  prompts = {
580
- "ko_finance": "환율 변동이 개인 투자에 미치는 영향과 대비 전략을 한국어로 4문장 이내로 설명하세요. 같표현을 반복하지 마세.",
581
- "en_terminal": "Return one bash command only. No explanation. Task: find the 10 largest files under the current directory, excluding .git, sorted by size descending.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  }
583
 
584
  for name, prompt in prompts.items():
585
  print("=" * 80)
586
  print(name)
587
- print(kohrm.generate_from_loaded(model, tokenizer, cfg, prompt, max_new_tokens=96, **settings))
 
 
 
 
 
 
 
 
 
588
  ```
589
 
590
  정상 결과:
@@ -594,7 +650,7 @@ for name, prompt in prompts.items():
594
  - helper가 1.38B 공개 `model.safetensors` 변환본을 로드합니다.
595
  - Colab T4에서는 fp16 PyTorch scaled-dot-product attention으로 생성합니다.
596
  - 첫 실행은 2.8 GiB급 weight 다운로드와 로드 때문에 몇 분 걸릴 수 있습니다.
597
- - 현재 repo는 rolling pretraining checkpoint입니다. JSON-only, command-only, 한국어 반복 억제가 약하게 보이면 같은 노트북으로 이후 checkpoint와 비교해야 합니다.
598
 
599
  helper가 쓰는 prompt 형식은 upstream `InferenceCheckpoint.tokenize_prompt()`와 맞춥니다.
600
 
 
168
  <|im_start|><|object_ref_start|>YOUR_PROMPT_HERE<|im_end|>
169
  ```
170
 
171
+ ### Colab T4 Long Knowledge Probe
172
 
173
  A ready-to-run Colab notebook is available in the project repo:
174
 
175
+ https://github.com/LLM-OS-Models/KoHRM-text/blob/main/notebooks/KoHRM_Text_1_4B_Colab_T4_Long_Knowledge_Probe.ipynb
176
 
177
+ The notebook downloads the latest public files and runs long-form generation prompts that match the current pretraining data style. It is intended to inspect knowledge signal, Korean fluency, repetition, and runtime correctness after pretraining-stage checkpoints.
178
 
179
+ This is not a final chat/SFT benchmark. It intentionally avoids format-constrained SFT-style tests because the public checkpoint is still a pretraining-stage model and has not been behavior-aligned by SFT/LoRA/RL.
180
 
181
  It intentionally avoids `transformers`, `AutoTokenizer`, and `AutoModelForCausalLM`. Instead, it uses:
182
 
 
231
  model, tokenizer, cfg = kohrm.load_kohrm(repo_dir, max_gpu_memory_gib=14.0)
232
 
233
  settings = dict(
234
+ max_seq_len=1536,
235
+ temperature=0.65,
236
+ top_p=0.92,
237
+ repetition_penalty=1.05,
238
+ no_repeat_ngram_size=0,
239
  condition="direct",
240
  )
241
 
242
  prompts = {
243
+ "finance": "환율 변동이 개인 투자에 미치는 영향과 대비 전략은 무엇인가?",
244
+ "kowiki_style": """다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.
245
+
246
+ [문서명]
247
+ 훈민정음
248
+
249
+ [부분]
250
+ 1/1""",
251
+ "legal_style": """다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.
252
+
253
+ [자료종류]
254
+ law
255
+
256
+ [문서명]
257
+ 형법
258
+
259
+ [경로]
260
+ kr/형법/법률.md
261
+
262
+ [부분]
263
+ 1/1""",
264
  }
265
 
266
  for name, prompt in prompts.items():
267
  print("=" * 80)
268
  print(name)
269
+ output = kohrm.generate_from_loaded(
270
+ model,
271
+ tokenizer,
272
+ cfg,
273
+ prompt,
274
+ max_new_tokens=384,
275
+ min_new_tokens=160,
276
+ **settings,
277
+ )
278
+ print(output)
279
  ```
280
 
281
  Expected result:
 
285
  - The helper should load the 1.38B public `model.safetensors` export.
286
  - On Colab T4, generation runs in fp16 through PyTorch scaled-dot-product attention.
287
  - First generation can take a few minutes because it downloads and loads the full weight file.
288
+ - This is a rolling pretraining checkpoint. Compare later checkpoints with the same long prompts before drawing final conclusions.
289
 
290
  Prompt format used by the helper, matching upstream `InferenceCheckpoint.tokenize_prompt()`:
291
 
 
533
  <|im_start|><|object_ref_start|>여기에_프롬프트를_넣습니다<|im_end|>
534
  ```
535
 
536
+ ### Colab T4 지식 생성 확인
537
 
538
  바로 실행할 수 있는 Colab 노트북은 project repo에 있습니다.
539
 
540
+ https://github.com/LLM-OS-Models/KoHRM-text/blob/main/notebooks/KoHRM_Text_1_4B_Colab_T4_Long_Knowledge_Probe.ipynb
541
 
542
+ 이 노트북은 Colab T4에서 최신 공개 파일을 다운로드하고 현재 사전학습 데이터와 같은 스타��의 긴 생성 prompt를 실행합니다. 목적pretraining stage checkpoint의 지식 신호, 한국어 유창성, 반복 여부, 공개 `model.safetensors` runtime 동작을 직접 확인하는 것입니다.
543
 
544
+ 이 노트북은 최종 chat/SFT benchmark가 아닙니다. 공개 checkpoint는 아직 SFT/LoRA/RL 행동 정렬을 끝낸 모델이 아니므로, 포맷 준수 중심의 SFT식 과제는 의도적으제외했습니다.
545
 
546
  일부 Colab 환경에서 `transformers`가 `torchvision::nms` import 오류를 내거나 custom architecture를 못 찾는 문제가 생길 수 있으므로, 이 노트북은 `AutoTokenizer`와 `AutoModelForCausalLM`을 쓰지 않습니다. 대신 아래 경로를 사용합니다.
547
 
 
596
  model, tokenizer, cfg = kohrm.load_kohrm(repo_dir, max_gpu_memory_gib=14.0)
597
 
598
  settings = dict(
599
+ max_seq_len=1536,
600
+ temperature=0.65,
601
+ top_p=0.92,
602
+ repetition_penalty=1.05,
603
+ no_repeat_ngram_size=0,
604
  condition="direct",
605
  )
606
 
607
  prompts = {
608
+ "finance": "환율 변동이 개인 투자에 미치는 영향과 대비 전략은 무엇인가?",
609
+ "kowiki_style": """다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.
610
+
611
+ [문서명]
612
+ 훈민정음
613
+
614
+ [부분]
615
+ 1/1""",
616
+ "legal_style": """다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.
617
+
618
+ [자료종류]
619
+ law
620
+
621
+ [문서명]
622
+ 형법
623
+
624
+ [경로]
625
+ kr/형법/법률.md
626
+
627
+ [부분]
628
+ 1/1""",
629
  }
630
 
631
  for name, prompt in prompts.items():
632
  print("=" * 80)
633
  print(name)
634
+ output = kohrm.generate_from_loaded(
635
+ model,
636
+ tokenizer,
637
+ cfg,
638
+ prompt,
639
+ max_new_tokens=384,
640
+ min_new_tokens=160,
641
+ **settings,
642
+ )
643
+ print(output)
644
  ```
645
 
646
  정상 결과:
 
650
  - helper가 1.38B 공개 `model.safetensors` 변환본을 로드합니다.
651
  - Colab T4에서는 fp16 PyTorch scaled-dot-product attention으로 생성합니다.
652
  - 첫 실행은 2.8 GiB급 weight 다운로드와 로드 때문에 몇 분 걸릴 수 있습니다.
653
+ - 현재 repo는 rolling pretraining checkpoint입니다. 같은 긴 prompt로 이후 checkpoint와 비교해서 지식, 문체, 반복 여부를 봐야 합니다.
654
 
655
  helper가 쓰는 prompt 형식은 upstream `InferenceCheckpoint.tokenize_prompt()`와 맞춥니다.
656
 
kohrm_colab_generate.py CHANGED
@@ -2,8 +2,8 @@
2
 
3
  This file intentionally avoids `transformers` and FlashAttention. It loads the
4
  public `model.safetensors` export and runs HRM-Text generation with PyTorch
5
- scaled-dot-product attention. It is built for smoke generation tests on Colab
6
- T4 and small CUDA machines.
7
  """
8
 
9
  from __future__ import annotations
@@ -298,11 +298,14 @@ def _sample_next(
298
  seen_ids: list[int] | None = None,
299
  repetition_penalty: float = 1.0,
300
  no_repeat_ngram_size: int = 0,
 
301
  ) -> int:
302
  logits = logits.float()
303
  seen_ids = seen_ids or []
304
  logits = _apply_repetition_penalty(logits, seen_ids, repetition_penalty)
305
  logits = _apply_no_repeat_ngram(logits, seen_ids, no_repeat_ngram_size)
 
 
306
  if temperature <= 0:
307
  return int(torch.argmax(logits, dim=-1).item())
308
  probs = torch.softmax(logits / temperature, dim=-1)
@@ -325,6 +328,7 @@ def generate_from_loaded(
325
  prompt: str,
326
  *,
327
  max_new_tokens: int = 64,
 
328
  max_seq_len: int = 512,
329
  temperature: float = 0.0,
330
  top_p: float = 0.9,
@@ -355,9 +359,17 @@ def generate_from_loaded(
355
  stop_ids = {int(x) for x in stop_ids if x is not None}
356
  out_ids: list[int] = []
357
  seen_ids = list(input_ids)
358
- next_id = _sample_next(logits, temperature, top_p, seen_ids, repetition_penalty, no_repeat_ngram_size)
 
 
 
 
 
 
 
 
359
  for _ in range(max_new_tokens):
360
- if next_id in stop_ids:
361
  break
362
  out_ids.append(next_id)
363
  seen_ids.append(next_id)
@@ -365,7 +377,15 @@ def generate_from_loaded(
365
  pos = torch.tensor([[cache_pos]], device=dev, dtype=torch.long)
366
  logits = model(token, pos, caches=caches, cache_pos=cache_pos)[:, -1, :]
367
  cache_pos += 1
368
- next_id = _sample_next(logits, temperature, top_p, seen_ids, repetition_penalty, no_repeat_ngram_size)
 
 
 
 
 
 
 
 
369
 
370
  return tokenizer.decode(out_ids, skip_special_tokens=True).strip()
371
 
@@ -376,6 +396,7 @@ def generate_text(
376
  prompt: str,
377
  *,
378
  max_new_tokens: int = 64,
 
379
  max_seq_len: int = 512,
380
  temperature: float = 0.0,
381
  top_p: float = 0.9,
@@ -392,6 +413,7 @@ def generate_text(
392
  cfg,
393
  prompt,
394
  max_new_tokens=max_new_tokens,
 
395
  max_seq_len=max_seq_len,
396
  temperature=temperature,
397
  top_p=top_p,
@@ -403,21 +425,23 @@ def generate_text(
403
 
404
 
405
  def main() -> None:
406
- parser = argparse.ArgumentParser(description="Run a small KoHRM-Text generation test without transformers.")
407
  parser.add_argument("repo_dir", type=Path, help="Directory containing config.json, tokenizer.json, and model.safetensors")
408
  parser.add_argument(
409
  "--prompt",
410
  default=(
411
- "Return one bash command only. Task: find the 10 largest files under "
412
- "the current directory, excluding .git, sorted by size descending."
 
413
  ),
414
  )
415
- parser.add_argument("--max-new-tokens", type=int, default=64)
416
- parser.add_argument("--max-seq-len", type=int, default=512)
417
- parser.add_argument("--temperature", type=float, default=0.0)
418
- parser.add_argument("--top-p", type=float, default=0.9)
419
- parser.add_argument("--repetition-penalty", type=float, default=1.18)
420
- parser.add_argument("--no-repeat-ngram-size", type=int, default=4)
 
421
  parser.add_argument(
422
  "--condition",
423
  default="direct",
@@ -434,6 +458,7 @@ def main() -> None:
434
  args.repo_dir,
435
  args.prompt,
436
  max_new_tokens=args.max_new_tokens,
 
437
  max_seq_len=args.max_seq_len,
438
  temperature=args.temperature,
439
  top_p=args.top_p,
 
2
 
3
  This file intentionally avoids `transformers` and FlashAttention. It loads the
4
  public `model.safetensors` export and runs HRM-Text generation with PyTorch
5
+ scaled-dot-product attention. It is built for long pretraining-checkpoint
6
+ knowledge probes on Colab T4 and small CUDA machines.
7
  """
8
 
9
  from __future__ import annotations
 
298
  seen_ids: list[int] | None = None,
299
  repetition_penalty: float = 1.0,
300
  no_repeat_ngram_size: int = 0,
301
+ blocked_ids: set[int] | None = None,
302
  ) -> int:
303
  logits = logits.float()
304
  seen_ids = seen_ids or []
305
  logits = _apply_repetition_penalty(logits, seen_ids, repetition_penalty)
306
  logits = _apply_no_repeat_ngram(logits, seen_ids, no_repeat_ngram_size)
307
+ if blocked_ids:
308
+ logits[..., list(blocked_ids)] = -torch.inf
309
  if temperature <= 0:
310
  return int(torch.argmax(logits, dim=-1).item())
311
  probs = torch.softmax(logits / temperature, dim=-1)
 
328
  prompt: str,
329
  *,
330
  max_new_tokens: int = 64,
331
+ min_new_tokens: int = 0,
332
  max_seq_len: int = 512,
333
  temperature: float = 0.0,
334
  top_p: float = 0.9,
 
359
  stop_ids = {int(x) for x in stop_ids if x is not None}
360
  out_ids: list[int] = []
361
  seen_ids = list(input_ids)
362
+ next_id = _sample_next(
363
+ logits,
364
+ temperature,
365
+ top_p,
366
+ seen_ids,
367
+ repetition_penalty,
368
+ no_repeat_ngram_size,
369
+ blocked_ids=stop_ids if min_new_tokens > 0 else None,
370
+ )
371
  for _ in range(max_new_tokens):
372
+ if next_id in stop_ids and len(out_ids) >= min_new_tokens:
373
  break
374
  out_ids.append(next_id)
375
  seen_ids.append(next_id)
 
377
  pos = torch.tensor([[cache_pos]], device=dev, dtype=torch.long)
378
  logits = model(token, pos, caches=caches, cache_pos=cache_pos)[:, -1, :]
379
  cache_pos += 1
380
+ next_id = _sample_next(
381
+ logits,
382
+ temperature,
383
+ top_p,
384
+ seen_ids,
385
+ repetition_penalty,
386
+ no_repeat_ngram_size,
387
+ blocked_ids=stop_ids if len(out_ids) < min_new_tokens else None,
388
+ )
389
 
390
  return tokenizer.decode(out_ids, skip_special_tokens=True).strip()
391
 
 
396
  prompt: str,
397
  *,
398
  max_new_tokens: int = 64,
399
+ min_new_tokens: int = 0,
400
  max_seq_len: int = 512,
401
  temperature: float = 0.0,
402
  top_p: float = 0.9,
 
413
  cfg,
414
  prompt,
415
  max_new_tokens=max_new_tokens,
416
+ min_new_tokens=min_new_tokens,
417
  max_seq_len=max_seq_len,
418
  temperature=temperature,
419
  top_p=top_p,
 
425
 
426
 
427
  def main() -> None:
428
+ parser = argparse.ArgumentParser(description="Run a KoHRM-Text long generation probe without transformers.")
429
  parser.add_argument("repo_dir", type=Path, help="Directory containing config.json, tokenizer.json, and model.safetensors")
430
  parser.add_argument(
431
  "--prompt",
432
  default=(
433
+ "다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, "
434
+ "고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.\n\n"
435
+ "[문서명]\n훈민정음\n\n[부분]\n1/1"
436
  ),
437
  )
438
+ parser.add_argument("--max-new-tokens", type=int, default=384)
439
+ parser.add_argument("--min-new-tokens", type=int, default=160)
440
+ parser.add_argument("--max-seq-len", type=int, default=1536)
441
+ parser.add_argument("--temperature", type=float, default=0.65)
442
+ parser.add_argument("--top-p", type=float, default=0.92)
443
+ parser.add_argument("--repetition-penalty", type=float, default=1.05)
444
+ parser.add_argument("--no-repeat-ngram-size", type=int, default=0)
445
  parser.add_argument(
446
  "--condition",
447
  default="direct",
 
458
  args.repo_dir,
459
  args.prompt,
460
  max_new_tokens=args.max_new_tokens,
461
+ min_new_tokens=args.min_new_tokens,
462
  max_seq_len=args.max_seq_len,
463
  temperature=args.temperature,
464
  top_p=args.top_p,
notebooks/KoHRM_Text_1_4B_Colab_T4_Long_Knowledge_Probe.ipynb ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# KoHRM-Text-1.4B Colab T4 Long Knowledge Probe\n",
8
+ "\n",
9
+ "This notebook checks a pretraining checkpoint's long-form knowledge signal after PT-style training by generating long responses from training-format instructions.\n",
10
+ "\n",
11
+ "It follows the same training data layout used by `scripts/prepare_sft_data.py`:\n",
12
+ "\n",
13
+ "```text\n",
14
+ "instruction text -> response text\n",
15
+ "```\n",
16
+ "\n",
17
+ "The runtime wraps each instruction exactly in the HRM-Text control format:\n",
18
+ "\n",
19
+ "```text\n",
20
+ "<|im_start|><|object_ref_start|>instruction<|im_end|>\n",
21
+ "```\n",
22
+ "\n",
23
+ "`<|object_ref_start|>` is the `direct` condition. The model then generates the response until `<|box_end|>` or the token budget. The prompts below are intentionally close to the actual Korean legal raw corpus, Korean wiki raw corpus, BCAI finance QA, and terminal conversation builders used in pretraining.\n"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "markdown",
28
+ "metadata": {},
29
+ "source": [
30
+ "## 1. Install Dependencies\n",
31
+ "\n",
32
+ "This path intentionally avoids `transformers`, `AutoTokenizer`, and `AutoModelForCausalLM`. The current public export is a custom HRM-Text architecture, so the notebook uses the lightweight project helper instead.\n"
33
+ ]
34
+ },
35
+ {
36
+ "cell_type": "code",
37
+ "execution_count": null,
38
+ "metadata": {},
39
+ "outputs": [],
40
+ "source": [
41
+ "!pip -q install -U huggingface_hub hf_transfer safetensors\n",
42
+ "!pip -q install --force-reinstall -q \"tokenizers>=0.22.0,<0.23.1\""
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "markdown",
47
+ "metadata": {},
48
+ "source": [
49
+ "## 2. Runtime Settings\n",
50
+ "\n",
51
+ "`MAX_SEQ_LEN=1536` and long generation are intended for T4 knowledge probing. If Colab runs out of memory, lower `MAX_SEQ_LEN` to `1024` and `DEFAULT_MAX_NEW_TOKENS` to `256`.\n"
52
+ ]
53
+ },
54
+ {
55
+ "cell_type": "code",
56
+ "execution_count": null,
57
+ "metadata": {},
58
+ "outputs": [],
59
+ "source": [
60
+ "import os\n",
61
+ "import json\n",
62
+ "import gc\n",
63
+ "import importlib.util\n",
64
+ "import subprocess\n",
65
+ "import sys\n",
66
+ "from pathlib import Path\n",
67
+ "\n",
68
+ "os.environ[\"HF_HUB_ENABLE_HF_TRANSFER\"] = \"1\"\n",
69
+ "\n",
70
+ "REPO_ID = \"LLM-OS-Models/KoHRM-Text-1.4B\"\n",
71
+ "REVISION = \"main\"\n",
72
+ "LOCAL_DIR = Path(\"/content/KoHRM-Text-1.4B\")\n",
73
+ "HELPER_PATH = LOCAL_DIR / \"kohrm_colab_generate.py\"\n",
74
+ "\n",
75
+ "MAX_SEQ_LEN = 1536\n",
76
+ "DEFAULT_MAX_NEW_TOKENS = 384\n",
77
+ "DEFAULT_MIN_NEW_TOKENS = 160\n",
78
+ "\n",
79
+ "LONG_TEXT_SETTINGS = {\n",
80
+ " \"max_seq_len\": MAX_SEQ_LEN,\n",
81
+ " \"temperature\": 0.65,\n",
82
+ " \"top_p\": 0.92,\n",
83
+ " \"repetition_penalty\": 1.05,\n",
84
+ " \"no_repeat_ngram_size\": 0,\n",
85
+ " \"condition\": \"direct\",\n",
86
+ "}\n",
87
+ "\n",
88
+ "print(\"repo:\", REPO_ID)\n",
89
+ "print(\"revision:\", REVISION)\n",
90
+ "print(\"local_dir:\", LOCAL_DIR)\n",
91
+ "print(\"max_seq_len:\", MAX_SEQ_LEN)\n",
92
+ "print(\"default max/min new tokens:\", DEFAULT_MAX_NEW_TOKENS, DEFAULT_MIN_NEW_TOKENS)"
93
+ ]
94
+ },
95
+ {
96
+ "cell_type": "markdown",
97
+ "metadata": {},
98
+ "source": [
99
+ "## 3. Download Latest Public Checkpoint\n",
100
+ "\n",
101
+ "The notebook downloads only the files needed for public `model.safetensors` inference and the helper runtime. If the helper is missing from the model repo, it falls back to the GitHub repository.\n"
102
+ ]
103
+ },
104
+ {
105
+ "cell_type": "code",
106
+ "execution_count": null,
107
+ "metadata": {},
108
+ "outputs": [],
109
+ "source": [
110
+ "from huggingface_hub import snapshot_download\n",
111
+ "\n",
112
+ "LOCAL_DIR.mkdir(parents=True, exist_ok=True)\n",
113
+ "patterns = [\n",
114
+ " \"README.md\",\n",
115
+ " \"config.json\",\n",
116
+ " \"tokenizer.json\",\n",
117
+ " \"tokenizer_config.json\",\n",
118
+ " \"special_tokens_map.json\",\n",
119
+ " \"model.safetensors\",\n",
120
+ " \"kohrm_colab_generate.py\",\n",
121
+ " \"notebooks/kohrm_colab_generate.py\",\n",
122
+ "]\n",
123
+ "\n",
124
+ "snapshot_download(\n",
125
+ " repo_id=REPO_ID,\n",
126
+ " repo_type=\"model\",\n",
127
+ " revision=REVISION,\n",
128
+ " local_dir=str(LOCAL_DIR),\n",
129
+ " local_dir_use_symlinks=False,\n",
130
+ " allow_patterns=patterns,\n",
131
+ ")\n",
132
+ "\n",
133
+ "nested_helper = LOCAL_DIR / \"notebooks\" / \"kohrm_colab_generate.py\"\n",
134
+ "if not HELPER_PATH.exists() and nested_helper.exists():\n",
135
+ " HELPER_PATH.write_text(nested_helper.read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n",
136
+ "\n",
137
+ "if not HELPER_PATH.exists():\n",
138
+ " repo = Path(\"/content/KoHRM-text\")\n",
139
+ " if not repo.exists():\n",
140
+ " subprocess.run(\n",
141
+ " [\"git\", \"clone\", \"--depth\", \"1\", \"https://github.com/LLM-OS-Models/KoHRM-text\", str(repo)],\n",
142
+ " check=True,\n",
143
+ " )\n",
144
+ " HELPER_PATH.write_text((repo / \"notebooks\" / \"kohrm_colab_generate.py\").read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n",
145
+ "\n",
146
+ "for name in [\"config.json\", \"tokenizer.json\", \"model.safetensors\", \"README.md\", \"kohrm_colab_generate.py\"]:\n",
147
+ " p = LOCAL_DIR / name\n",
148
+ " print(name, \"OK\" if p.exists() else \"MISSING\", p)"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "markdown",
153
+ "metadata": {},
154
+ "source": [
155
+ "## 4. Inspect Config and Training Wrapper\n",
156
+ "\n",
157
+ "This cell checks the model shape and special tokens. The prompt wrapper shown here is the exact wrapper used for all probes below.\n"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "code",
162
+ "execution_count": null,
163
+ "metadata": {},
164
+ "outputs": [],
165
+ "source": [
166
+ "spec = importlib.util.spec_from_file_location(\"kohrm_colab_generate\", HELPER_PATH)\n",
167
+ "kohrm = importlib.util.module_from_spec(spec)\n",
168
+ "sys.modules[\"kohrm_colab_generate\"] = kohrm\n",
169
+ "spec.loader.exec_module(kohrm)\n",
170
+ "\n",
171
+ "config = json.loads((LOCAL_DIR / \"config.json\").read_text(encoding=\"utf-8\"))\n",
172
+ "print(json.dumps({\n",
173
+ " \"model_type\": config.get(\"model_type\"),\n",
174
+ " \"architectures\": config.get(\"architectures\"),\n",
175
+ " \"vocab_size\": config.get(\"vocab_size\"),\n",
176
+ " \"hidden_size\": config.get(\"hidden_size\"),\n",
177
+ " \"num_hidden_layers\": config.get(\"num_hidden_layers\"),\n",
178
+ " \"num_attention_heads\": config.get(\"num_attention_heads\"),\n",
179
+ " \"H_cycles\": config.get(\"H_cycles\"),\n",
180
+ " \"L_cycles\": config.get(\"L_cycles\"),\n",
181
+ " \"max_position_embeddings\": config.get(\"max_position_embeddings\"),\n",
182
+ " \"prefix_lm\": config.get(\"prefix_lm\"),\n",
183
+ "}, indent=2, ensure_ascii=False))\n",
184
+ "\n",
185
+ "from tokenizers import Tokenizer\n",
186
+ "raw_tok = Tokenizer.from_file(str(LOCAL_DIR / \"tokenizer.json\"))\n",
187
+ "for token in [\"<|im_start|>\", \"<|object_ref_start|>\", \"<|object_ref_end|>\", \"<|quad_start|>\", \"<|quad_end|>\", \"<|im_end|>\", \"<|box_end|>\"]:\n",
188
+ " print(f\"{token:22s}\", raw_tok.token_to_id(token))\n",
189
+ "\n",
190
+ "example_instruction = \"비씨카드는 어떤 회사인가요?\"\n",
191
+ "print(\"direct condition token:\", kohrm.condition_to_tokens(\"direct\"))\n",
192
+ "print(\"wrapped prompt:\", kohrm.format_kohrm_prompt(example_instruction, condition=\"direct\"))"
193
+ ]
194
+ },
195
+ {
196
+ "cell_type": "markdown",
197
+ "metadata": {},
198
+ "source": [
199
+ "## 5. Load Model Once\n",
200
+ "\n",
201
+ "Loading can take a few minutes on Colab. The helper uses PyTorch scaled-dot-product attention and a static KV cache. It is slower than the training-time FlashAttention path, but it is enough for long text inspection.\n"
202
+ ]
203
+ },
204
+ {
205
+ "cell_type": "code",
206
+ "execution_count": null,
207
+ "metadata": {},
208
+ "outputs": [],
209
+ "source": [
210
+ "import torch\n",
211
+ "\n",
212
+ "gc.collect()\n",
213
+ "if torch.cuda.is_available():\n",
214
+ " torch.cuda.empty_cache()\n",
215
+ " print(torch.cuda.get_device_name(0))\n",
216
+ "\n",
217
+ "model, tokenizer, cfg = kohrm.load_kohrm(LOCAL_DIR, max_gpu_memory_gib=14.0)\n",
218
+ "print(\"loaded dtype:\", next(model.parameters()).dtype)\n",
219
+ "print(\"loaded device:\", next(model.parameters()).device)\n",
220
+ "if torch.cuda.is_available():\n",
221
+ " free, total = torch.cuda.mem_get_info()\n",
222
+ " print(f\"GPU memory free/total GiB after load: {free / 2**30:.2f}/{total / 2**30:.2f}\")"
223
+ ]
224
+ },
225
+ {
226
+ "cell_type": "markdown",
227
+ "metadata": {},
228
+ "source": [
229
+ "## 6. PT-Format Long Knowledge Prompts\n",
230
+ "\n",
231
+ "These long-response probes use prompts shaped like the data that went into pretraining:\n",
232
+ "\n",
233
+ "- BCAI Finance Kor: plain Korean finance QA instruction.\n",
234
+ "- Korean wiki raw corpus: `다음은 한국어 위키백과 문서 원문 일부입니다...` instruction.\n",
235
+ "- Korean legal raw corpus: `다음은 대한민국 법령/자치법규 원문 일부입니다...` instruction.\n",
236
+ "- Terminal conversation corpus: `다음 터미널/코딩 작업 대화 맥락에서...` instruction.\n",
237
+ "\n",
238
+ "The notebook prints output length and the raw generated text. Judge the text manually: domain terms, Korean fluency, factual continuity, repetition, and whether it collapses to one-token answers.\n"
239
+ ]
240
+ },
241
+ {
242
+ "cell_type": "code",
243
+ "execution_count": null,
244
+ "metadata": {},
245
+ "outputs": [],
246
+ "source": [
247
+ "KNOWLEDGE_PROMPTS = [\n",
248
+ " {\n",
249
+ " \"name\": \"finance_bc_card_company\",\n",
250
+ " \"source_format\": \"BCAI Finance Kor plain QA\",\n",
251
+ " \"prompt\": \"비씨카드는 어떤 회사인가요?\",\n",
252
+ " \"max_new_tokens\": 420,\n",
253
+ " \"min_new_tokens\": 180,\n",
254
+ " },\n",
255
+ " {\n",
256
+ " \"name\": \"finance_exchange_investment\",\n",
257
+ " \"source_format\": \"BCAI Finance Kor plain QA\",\n",
258
+ " \"prompt\": \"환율 변동이 개인 투���에 미치는 영향과 대비 전략은 무엇인가요?\",\n",
259
+ " \"max_new_tokens\": 420,\n",
260
+ " \"min_new_tokens\": 180,\n",
261
+ " },\n",
262
+ " {\n",
263
+ " \"name\": \"kowiki_hunminjeongeum_raw_style\",\n",
264
+ " \"source_format\": \"kowiki raw instruction style\",\n",
265
+ " \"prompt\": \"\"\"다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.\n",
266
+ "\n",
267
+ "[문서명]\n",
268
+ "훈민정음\n",
269
+ "\n",
270
+ "[부분]\n",
271
+ "1/1\"\"\",\n",
272
+ " \"max_new_tokens\": 520,\n",
273
+ " \"min_new_tokens\": 220,\n",
274
+ " },\n",
275
+ " {\n",
276
+ " \"name\": \"kowiki_jimmy_carter_raw_style\",\n",
277
+ " \"source_format\": \"kowiki raw instruction style\",\n",
278
+ " \"prompt\": \"\"\"다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.\n",
279
+ "\n",
280
+ "[문서명]\n",
281
+ "지미 카터\n",
282
+ "\n",
283
+ "[부분]\n",
284
+ "1/3\"\"\",\n",
285
+ " \"max_new_tokens\": 520,\n",
286
+ " \"min_new_tokens\": 220,\n",
287
+ " },\n",
288
+ " {\n",
289
+ " \"name\": \"legal_criminal_code_raw_style\",\n",
290
+ " \"source_format\": \"korean legal raw instruction style\",\n",
291
+ " \"prompt\": \"\"\"다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.\n",
292
+ "\n",
293
+ "[자료종류]\n",
294
+ "law\n",
295
+ "\n",
296
+ "[문서명]\n",
297
+ "형법\n",
298
+ "\n",
299
+ "[경로]\n",
300
+ "kr/형법/법률.md\n",
301
+ "\n",
302
+ "[부분]\n",
303
+ "1/1\"\"\",\n",
304
+ " \"max_new_tokens\": 520,\n",
305
+ " \"min_new_tokens\": 220,\n",
306
+ " },\n",
307
+ " {\n",
308
+ " \"name\": \"legal_restoration_raw_style\",\n",
309
+ " \"source_format\": \"korean legal raw instruction style\",\n",
310
+ " \"prompt\": \"\"\"다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.\n",
311
+ "\n",
312
+ "[자료종류]\n",
313
+ "law\n",
314
+ "\n",
315
+ "[문서명]\n",
316
+ "10ㆍ27법난 피해자의 명예회복 등에 관한 법률\n",
317
+ "\n",
318
+ "[경로]\n",
319
+ "kr/10ㆍ27법난피해자의명예회복등에관한법률/법률.md\n",
320
+ "\n",
321
+ "[부분]\n",
322
+ "1/1\"\"\",\n",
323
+ " \"max_new_tokens\": 520,\n",
324
+ " \"min_new_tokens\": 220,\n",
325
+ " },\n",
326
+ " {\n",
327
+ " \"name\": \"terminal_conversation_style_long\",\n",
328
+ " \"source_format\": \"local terminal conversation instruction style\",\n",
329
+ " \"prompt\": \"\"\"다음 터미널/코딩 작업 대화 맥락에서 assistant가 이어서 수행할 분석, 계획, 명령 JSON 또는 최종 응답을 작성하십시오.\n",
330
+ "\n",
331
+ "[user]\n",
332
+ "현재 디렉터리에서 용량이 큰 파일을 찾아 디스크 정리 후보를 보고 싶습니다. 숨김 폴더와 일반 폴더를 모두 확인하되, 결과는 사람이 읽기 쉽게 정리해 주세요.\"\"\",\n",
333
+ " \"max_new_tokens\": 420,\n",
334
+ " \"min_new_tokens\": 160,\n",
335
+ " },\n",
336
+ "]\n",
337
+ "\n",
338
+ "print(\"probe count:\", len(KNOWLEDGE_PROMPTS))\n",
339
+ "for item in KNOWLEDGE_PROMPTS:\n",
340
+ " print(\"-\", item[\"name\"], \"|\", item[\"source_format\"], \"| max/min\", item[\"max_new_tokens\"], item[\"min_new_tokens\"])\n"
341
+ ]
342
+ },
343
+ {
344
+ "cell_type": "markdown",
345
+ "metadata": {},
346
+ "source": [
347
+ "## 7. Run Long Generations\n",
348
+ "\n",
349
+ "The goal is to inspect the generated text itself after PT-style training: length, Korean fluency, domain terms, factual continuity, and repetition.\n"
350
+ ]
351
+ },
352
+ {
353
+ "cell_type": "code",
354
+ "execution_count": null,
355
+ "metadata": {},
356
+ "outputs": [],
357
+ "source": [
358
+ "def count_tokens(text):\n",
359
+ " return len(tokenizer.encode(text, add_special_tokens=False).ids)\n",
360
+ "\n",
361
+ "RUN_ONLY = None # Example: {\"finance_bc_card_company\", \"kowiki_hunminjeongeum_raw_style\"}\n",
362
+ "\n",
363
+ "results = []\n",
364
+ "for case in KNOWLEDGE_PROMPTS:\n",
365
+ " if RUN_ONLY is not None and case[\"name\"] not in RUN_ONLY:\n",
366
+ " continue\n",
367
+ "\n",
368
+ " prompt = case[\"prompt\"]\n",
369
+ " max_new = case.get(\"max_new_tokens\", DEFAULT_MAX_NEW_TOKENS)\n",
370
+ " min_new = case.get(\"min_new_tokens\", DEFAULT_MIN_NEW_TOKENS)\n",
371
+ "\n",
372
+ " print(\"=\" * 100)\n",
373
+ " print(\"case:\", case[\"name\"])\n",
374
+ " print(\"source_format:\", case[\"source_format\"])\n",
375
+ " print(\"prompt_chars:\", len(prompt), \"prompt_tokens:\", count_tokens(kohrm.format_kohrm_prompt(prompt, condition=\"direct\")))\n",
376
+ " print(\"max_new_tokens:\", max_new, \"min_new_tokens:\", min_new)\n",
377
+ " print(\"--- prompt ---\")\n",
378
+ " print(prompt)\n",
379
+ " print(\"--- output ---\")\n",
380
+ "\n",
381
+ " output = kohrm.generate_from_loaded(\n",
382
+ " model,\n",
383
+ " tokenizer,\n",
384
+ " cfg,\n",
385
+ " prompt,\n",
386
+ " max_new_tokens=max_new,\n",
387
+ " min_new_tokens=min_new,\n",
388
+ " **LONG_TEXT_SETTINGS,\n",
389
+ " )\n",
390
+ " out_tokens = count_tokens(output)\n",
391
+ " results.append({\n",
392
+ " \"case\": case[\"name\"],\n",
393
+ " \"source_format\": case[\"source_format\"],\n",
394
+ " \"output_chars\": len(output),\n",
395
+ " \"output_tokens\": out_tokens,\n",
396
+ " \"output\": output,\n",
397
+ " })\n",
398
+ " print(output)\n",
399
+ " print(\"--- output stats ---\")\n",
400
+ " print(\"chars:\", len(output), \"tokens:\", out_tokens)\n",
401
+ "\n",
402
+ "print(\"=\" * 100)\n",
403
+ "print(json.dumps([{k: r[k] for k in [\"case\", \"source_format\", \"output_chars\", \"output_tokens\"]} for r in results], indent=2, ensure_ascii=False))\n"
404
+ ]
405
+ },
406
+ {
407
+ "cell_type": "markdown",
408
+ "metadata": {},
409
+ "source": [
410
+ "## 8. Optional Decode Settings Sweep\n",
411
+ "\n",
412
+ "Use this only if long output collapses to short tokens or repeats. It keeps the same training-format prompts and changes only decoding parameters.\n"
413
+ ]
414
+ },
415
+ {
416
+ "cell_type": "code",
417
+ "execution_count": null,
418
+ "metadata": {},
419
+ "outputs": [],
420
+ "source": [
421
+ "SWEEP_CASE_NAME = \"finance_exchange_investment\"\n",
422
+ "SWEEP_PROMPT = next(item[\"prompt\"] for item in KNOWLEDGE_PROMPTS if item[\"name\"] == SWEEP_CASE_NAME)\n",
423
+ "\n",
424
+ "SWEEP_SETTINGS = [\n",
425
+ " {\"name\": \"deterministic_minlen\", \"temperature\": 0.0, \"top_p\": 1.0, \"repetition_penalty\": 1.02, \"no_repeat_ngram_size\": 0},\n",
426
+ " {\"name\": \"sample_balanced\", \"temperature\": 0.65, \"top_p\": 0.92, \"repetition_penalty\": 1.05, \"no_repeat_ngram_size\": 0},\n",
427
+ " {\"name\": \"sample_more_diverse\", \"temperature\": 0.85, \"top_p\": 0.95, \"repetition_penalty\": 1.08, \"no_repeat_ngram_size\": 0},\n",
428
+ "]\n",
429
+ "\n",
430
+ "for settings in SWEEP_SETTINGS:\n",
431
+ " run_settings = dict(LONG_TEXT_SETTINGS)\n",
432
+ " run_settings.update({k: v for k, v in settings.items() if k != \"name\"})\n",
433
+ " print(\"=\" * 100)\n",
434
+ " print(\"decode:\", settings[\"name\"])\n",
435
+ " output = kohrm.generate_from_loaded(\n",
436
+ " model,\n",
437
+ " tokenizer,\n",
438
+ " cfg,\n",
439
+ " SWEEP_PROMPT,\n",
440
+ " max_new_tokens=320,\n",
441
+ " min_new_tokens=120,\n",
442
+ " **run_settings,\n",
443
+ " )\n",
444
+ " print(output)\n",
445
+ " print(\"chars:\", len(output), \"tokens:\", count_tokens(output))\n"
446
+ ]
447
+ },
448
+ {
449
+ "cell_type": "markdown",
450
+ "metadata": {},
451
+ "source": [
452
+ "## 9. How To Read Results\n",
453
+ "\n",
454
+ "For this PT knowledge probe, useful signs are:\n",
455
+ "\n",
456
+ "- The output is long enough to inspect, not `Yes`, `B`, `0`, or one short fragment.\n",
457
+ "- Korean legal/wiki/finance outputs contain domain terms and coherent Korean paragraphs.\n",
458
+ "- Raw-style prompts may produce article/law-like continuations because that is how raw corpora were converted for PT.\n",
459
+ "- Terminal conversation style may produce analysis plus commands, because that is what the local terminal conversation data used as the response target.\n",
460
+ "\n",
461
+ "If every long prompt still collapses to one-token or unrelated boilerplate despite `min_new_tokens`, the next thing to debug is the public converted weight/runtime compatibility, not prompt wording.\n"
462
+ ]
463
+ }
464
+ ],
465
+ "metadata": {
466
+ "accelerator": "GPU",
467
+ "colab": {
468
+ "provenance": []
469
+ },
470
+ "kernelspec": {
471
+ "display_name": "Python 3",
472
+ "name": "python3"
473
+ },
474
+ "language_info": {
475
+ "name": "python"
476
+ }
477
+ },
478
+ "nbformat": 4,
479
+ "nbformat_minor": 0
480
+ }
notebooks/KoHRM_Text_1_4B_Colab_T4_Smoke_Test.ipynb CHANGED
@@ -4,24 +4,23 @@
4
  "cell_type": "markdown",
5
  "metadata": {},
6
  "source": [
7
- "# KoHRM-Text-1.4B Colab T4 Pretraining Checkpoint Probe\n",
8
  "\n",
9
- "This notebook loads the latest public KoHRM-Text-1.4B checkpoint without `transformers`, so it avoids the Colab `torchvision::nms` / custom `HrmTextConfig` import failure.\n",
10
  "\n",
11
- "Important: this is a **rolling pretraining checkpoint probe**, not a final SFT/chat benchmark. KoHRM is currently trained with HRM-Text style single-stage instruction pretraining. It has not yet gone through the later behavior SFT/LoRA/RL pass, so strict JSON-only, command-only, grounded summary, and code-correctness tasks should be interpreted as post-training readiness checks.\n",
12
- "\n",
13
- "Training format:\n",
14
  "\n",
15
  "```text\n",
16
- "<|im_start|><condition_token>instruction<|im_end|>response<|box_end|>\n",
17
  "```\n",
18
  "\n",
19
- "The helper follows upstream `InferenceCheckpoint.tokenize_prompt()`: `<boq><condition_tokens><instruction><eoq>`, then stops generation on `<|box_end|>`. Use `direct` / `<|object_ref_start|>` for answer-only completions.\n",
20
  "\n",
21
- "The notebook separates:\n",
 
 
22
  "\n",
23
- "- pretraining-distribution probes: simple continuation/QA tasks that check whether the checkpoint is learning the data distribution.\n",
24
- "- strict post-training probes: JSON-only, command-only, and code-only tasks that will usually need SFT/LoRA/RL before they become reliable."
25
  ]
26
  },
27
  {
@@ -30,7 +29,7 @@
30
  "source": [
31
  "## 1. Install Dependencies\n",
32
  "\n",
33
- "The runtime intentionally does not import `transformers`. `tokenizers` is pinned below `0.23.1` to avoid conflicts with Colab images that already contain `transformers 5.x`."
34
  ]
35
  },
36
  {
@@ -49,7 +48,7 @@
49
  "source": [
50
  "## 2. Runtime Settings\n",
51
  "\n",
52
- "`MAX_SEQ_LEN=512` is the safest T4 default. Raise it to `768` only if the first load leaves enough free VRAM."
53
  ]
54
  },
55
  {
@@ -72,21 +71,25 @@
72
  "REVISION = \"main\"\n",
73
  "LOCAL_DIR = Path(\"/content/KoHRM-Text-1.4B\")\n",
74
  "HELPER_PATH = LOCAL_DIR / \"kohrm_colab_generate.py\"\n",
75
- "MAX_SEQ_LEN = 512\n",
76
  "\n",
77
- "STRICT_SETTINGS = {\n",
 
 
 
 
78
  " \"max_seq_len\": MAX_SEQ_LEN,\n",
79
- " \"temperature\": 0.0,\n",
80
- " \"top_p\": 1.0,\n",
81
- " \"repetition_penalty\": 1.20,\n",
82
- " \"no_repeat_ngram_size\": 4,\n",
83
  " \"condition\": \"direct\",\n",
84
  "}\n",
85
  "\n",
86
  "print(\"repo:\", REPO_ID)\n",
87
  "print(\"revision:\", REVISION)\n",
88
  "print(\"local_dir:\", LOCAL_DIR)\n",
89
- "print(\"max_seq_len:\", MAX_SEQ_LEN)"
 
90
  ]
91
  },
92
  {
@@ -95,7 +98,7 @@
95
  "source": [
96
  "## 3. Download Latest Public Checkpoint\n",
97
  "\n",
98
- "The public model repo is expected to contain `config.json`, `tokenizer.json`, `model.safetensors`, and the model card. If the lightweight helper is not present in the model repo, the notebook clones the GitHub repo and copies the helper from `notebooks/`."
99
  ]
100
  },
101
  {
@@ -141,17 +144,17 @@
141
  " HELPER_PATH.write_text((repo / \"notebooks\" / \"kohrm_colab_generate.py\").read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n",
142
  "\n",
143
  "for name in [\"config.json\", \"tokenizer.json\", \"model.safetensors\", \"README.md\", \"kohrm_colab_generate.py\"]:\n",
144
- " path = LOCAL_DIR / name\n",
145
- " print(f\"{name}: {path.exists()} {path.stat().st_size / 2**20:.2f} MiB\" if path.exists() else f\"{name}: missing\")"
146
  ]
147
  },
148
  {
149
  "cell_type": "markdown",
150
  "metadata": {},
151
  "source": [
152
- "## 4. Inspect Config and Prompt Format\n",
153
  "\n",
154
- "Do not use `AutoTokenizer` or `AutoModelForCausalLM` here. The current public export uses a custom HRM-Text architecture, and the helper below loads it directly from `safetensors`."
155
  ]
156
  },
157
  {
@@ -162,6 +165,7 @@
162
  "source": [
163
  "spec = importlib.util.spec_from_file_location(\"kohrm_colab_generate\", HELPER_PATH)\n",
164
  "kohrm = importlib.util.module_from_spec(spec)\n",
 
165
  "spec.loader.exec_module(kohrm)\n",
166
  "\n",
167
  "config = json.loads((LOCAL_DIR / \"config.json\").read_text(encoding=\"utf-8\"))\n",
@@ -183,9 +187,9 @@
183
  "for token in [\"<|im_start|>\", \"<|object_ref_start|>\", \"<|object_ref_end|>\", \"<|quad_start|>\", \"<|quad_end|>\", \"<|im_end|>\", \"<|box_end|>\"]:\n",
184
  " print(f\"{token:22s}\", raw_tok.token_to_id(token))\n",
185
  "\n",
 
186
  "print(\"direct condition token:\", kohrm.condition_to_tokens(\"direct\"))\n",
187
- "example = kohrm.format_kohrm_prompt(\"Return one bash command only.\", condition=\"direct\")\n",
188
- "print(\"wrapped prompt:\", example)"
189
  ]
190
  },
191
  {
@@ -194,7 +198,7 @@
194
  "source": [
195
  "## 5. Load Model Once\n",
196
  "\n",
197
- "On a T4, loading can take a few minutes. The helper uses PyTorch scaled-dot-product attention and a static KV cache. It is slower than the training-time FlashAttention path, but it is portable enough for Colab smoke tests."
198
  ]
199
  },
200
  {
@@ -222,11 +226,16 @@
222
  "cell_type": "markdown",
223
  "metadata": {},
224
  "source": [
225
- "## 6. Probe Cases\n",
226
  "\n",
227
- "The first block is intentionally easier and closer to pretraining distribution. The second block is stricter and should be read as a post-training target, not as a final failure verdict for the current unsupervised/SFT-free checkpoint.\n",
228
  "\n",
229
- "Prompts here are deliberately plain. Over-complicated meta-prompts are not a good fix for a pretraining checkpoint; behavior such as JSON-only, command-only, and exact code generation belongs in the later SFT/LoRA/RL pass."
 
 
 
 
 
230
  ]
231
  },
232
  {
@@ -235,257 +244,172 @@
235
  "metadata": {},
236
  "outputs": [],
237
  "source": [
238
- "import ast\n",
239
- "import json\n",
240
- "import re\n",
241
- "\n",
242
- "PRETRAINING_PROBES = [\n",
243
  " {\n",
244
- " \"name\": \"ko_finance_plain_qa\",\n",
245
- " \"phase\": \"pretraining_probe\",\n",
246
- " \"lang\": \"ko\",\n",
247
- " \"expect\": \"ko_contains_terms\",\n",
248
- " \"required_terms\": [\"환율\", \"투자\"],\n",
249
- " \"max_new_tokens\": 96,\n",
250
- " \"prompt\": \"환율 변동이 개인 투자에 미치는 영향을 간단히 설명하세요.\",\n",
251
  " },\n",
252
  " {\n",
253
- " \"name\": \"ko_legal_plain_extraction\",\n",
254
- " \"phase\": \"pretraining_probe\",\n",
255
- " \"lang\": \"ko\",\n",
256
- " \"expect\": \"ko_contains_terms\",\n",
257
- " \"required_terms\": [\"홍보대사\", \"무보수\"],\n",
258
- " \"max_new_tokens\": 96,\n",
259
- " \"prompt\": \"\"\"다음 조문에서 핵심 내용을 한 문장으로 말하세요.\n",
260
- "\n",
261
- "제5조 (보상)\n",
262
- "① 홍보대사는 무보수 명예직으로 한다.\n",
263
- "② 군수는 홍보대사가 임무 수행을 위하여 활동하는 경우 예산의 범위 안에서 홍보활동에 직접 소요되는 실 경비로 숙식비, 차량운행 경비, 기타 비용과 격려금품을 지급할 수 있다.\"\"\",\n",
264
  " },\n",
265
  " {\n",
266
- " \"name\": \"ko_wiki_plain_qa\",\n",
267
- " \"phase\": \"pretraining_probe\",\n",
268
- " \"lang\": \"ko\",\n",
269
- " \"expect\": \"ko_contains_terms\",\n",
270
- " \"required_terms\": [\"훈민정음\", \"세종\"],\n",
271
- " \"max_new_tokens\": 96,\n",
272
- " \"prompt\": \"훈민정음은 누가 만들었고 어떤 목적이 있었나요?\",\n",
273
- " },\n",
274
- " {\n",
275
- " \"name\": \"en_terminal_intent_completion\",\n",
276
- " \"phase\": \"pretraining_probe\",\n",
277
- " \"lang\": \"en\",\n",
278
- " \"expect\": \"mentions_shell_concept\",\n",
279
- " \"required_terms\": [\"find\", \"sort\"],\n",
280
- " \"max_new_tokens\": 96,\n",
281
- " \"prompt\": \"In bash, to list the largest files under the current directory, you can use\",\n",
282
- " },\n",
283
- "]\n",
284
- "\n",
285
- "STRICT_POSTTRAINING_PROBES = [\n",
286
- " {\n",
287
- " \"name\": \"ko_legal_json_direct\",\n",
288
- " \"phase\": \"posttraining_strict_probe\",\n",
289
- " \"lang\": \"ko\",\n",
290
- " \"expect\": \"strict_json_keys\",\n",
291
- " \"required_keys\": [\"조문명\", \"적용 대상\", \"핵심 의무\"],\n",
292
- " \"required_terms\": [\"제5조\", \"홍보대사\", \"무보수\", \"실 경비\"],\n",
293
- " \"max_new_tokens\": 128,\n",
294
- " \"prompt\": \"\"\"다음 한국 법령/행정규칙 발췌문에서 조문명, 적용 대상, 핵심 의무를 JSON 객체 하나로만 추출하세요. JSON 밖의 설명은 쓰지 마세요.\n",
295
  "\n",
296
  "[문서명]\n",
297
- "진안군 홍보대사 운영 조례\n",
298
  "\n",
299
- "[조문]\n",
300
- "제5조 (보상)\n",
301
- " 홍보대사는 무보수 명예직으로 한다.\n",
302
- "② 군수는 홍보대사가 임무 수행을 위하여 활동하는 경우 예산의 범위 안에서 홍보활동에 직접 소요되는 실 경비로 숙식비, 차량운행 경비, 기타 비용과 격려금품을 지급할 수 있다.\"\"\",\n",
303
- " },\n",
304
- " {\n",
305
- " \"name\": \"ko_wiki_grounded_summary\",\n",
306
- " \"phase\": \"posttraining_strict_probe\",\n",
307
- " \"lang\": \"ko\",\n",
308
- " \"expect\": \"ko_grounded_summary\",\n",
309
- " \"required_terms\": [\"훈민정음\", \"세종\"],\n",
310
- " \"forbidden_terms\": [\"ganjang\", \"A:\", \"<br>\", \"</br>\"],\n",
311
- " \"max_new_tokens\": 96,\n",
312
- " \"prompt\": \"\"\"다음 글만 근거로 핵심 내용을 한국어로 3문장 이내로 요약하세요. 글에 없는 사실은 추가하지 마세요.\n",
313
- "\n",
314
- "[글]\n",
315
- "훈민정음은 조선 세종이 창제한 문자 체계이다. 창제 목적은 백성이 자신의 뜻을 쉽게 글로 표현하도록 돕는 데 있었다. 자음은 발음 기관의 모양을 본떠 만들었고, 모음은 하늘, 땅, 사람의 원리를 바탕으로 구성되었다.\"\"\",\n",
316
  " },\n",
317
  " {\n",
318
- " \"name\": \"ko_finance_short\",\n",
319
- " \"phase\": \"posttraining_strict_probe\",\n",
320
- " \"lang\": \"ko\",\n",
321
- " \"expect\": \"ko_grounded_summary\",\n",
322
- " \"required_terms\": [\"환율\", \"투자\"],\n",
323
- " \"forbidden_terms\": [\"<br>\", \"</br>\", \"A:\", \"B:\", \"C:\"],\n",
324
- " \"max_new_tokens\": 96,\n",
325
- " \"prompt\": \"환율 변동이 개인 투자에 미치는 영향과 대비 전략을 한국어로 4문장 이내로 설명하세요. 같은 표현을 반복하지 마세��.\",\n",
 
 
 
326
  " },\n",
327
  " {\n",
328
- " \"name\": \"en_terminal_command_only\",\n",
329
- " \"phase\": \"posttraining_strict_probe\",\n",
330
- " \"lang\": \"en\",\n",
331
- " \"expect\": \"strict_shell_command\",\n",
332
- " \"required_terms\": [\"find\", \"sort\", \"head\"],\n",
333
- " \"max_new_tokens\": 64,\n",
334
- " \"prompt\": \"Return one bash command only. No explanation. Task: find the 10 largest files under the current directory, excluding .git, sorted by size descending.\",\n",
 
 
 
 
 
 
 
 
 
 
335
  " },\n",
336
  " {\n",
337
- " \"name\": \"en_tool_call_json\",\n",
338
- " \"phase\": \"posttraining_strict_probe\",\n",
339
- " \"lang\": \"en\",\n",
340
- " \"expect\": \"strict_json_keys\",\n",
341
- " \"required_keys\": [\"tool\", \"args\"],\n",
342
- " \"required_terms\": [\"shell\", \"du\"],\n",
343
- " \"max_new_tokens\": 96,\n",
344
- " \"prompt\": \"Return one JSON object only for a terminal tool call. Schema: {\\\"tool\\\": \\\"shell\\\", \\\"args\\\": {\\\"cmd\\\": string}}. Task: print current disk usage for the current directory in human-readable form.\",\n",
 
 
 
 
 
 
 
 
 
345
  " },\n",
346
  " {\n",
347
- " \"name\": \"en_python_code_only\",\n",
348
- " \"phase\": \"posttraining_strict_probe\",\n",
349
- " \"lang\": \"en\",\n",
350
- " \"expect\": \"strict_python_function\",\n",
351
- " \"required_terms\": [\"top_k_lengths\"],\n",
352
- " \"max_new_tokens\": 128,\n",
353
- " \"prompt\": \"Write Python code only. Define a function top_k_lengths(items, k) that returns the k longest strings from items, preserving original order for ties.\",\n",
 
354
  " },\n",
355
  "]\n",
356
  "\n",
357
- "TEST_CASES = PRETRAINING_PROBES + STRICT_POSTTRAINING_PROBES\n",
358
- "\n",
359
- "\n",
360
- "def strip_markdown_fence(text):\n",
361
- " stripped = text.strip()\n",
362
- " if stripped.startswith(\"```\"):\n",
363
- " stripped = re.sub(r\"^```[a-zA-Z0-9_-]*\\s*\", \"\", stripped)\n",
364
- " stripped = re.sub(r\"\\s*```$\", \"\", stripped)\n",
365
- " return stripped.strip()\n",
366
- "\n",
367
- "\n",
368
- "def has_forbidden(text, case):\n",
369
- " return [term for term in case.get(\"forbidden_terms\", []) if term in text]\n",
370
- "\n",
371
- "\n",
372
- "def missing_terms(text, case):\n",
373
- " return [term for term in case.get(\"required_terms\", []) if term not in text]\n",
374
- "\n",
375
- "\n",
376
- "def validate_output(case, text):\n",
377
- " expect = case[\"expect\"]\n",
378
- " raw = text.strip()\n",
379
- " body = strip_markdown_fence(raw)\n",
380
- " if not raw:\n",
381
- " return \"FAIL: empty output\"\n",
382
- "\n",
383
- " forbidden = has_forbidden(raw, case)\n",
384
- " if forbidden:\n",
385
- " return f\"FAIL: forbidden artifacts {forbidden}\"\n",
386
- "\n",
387
- " if expect == \"ko_contains_terms\":\n",
388
- " missing = missing_terms(raw, case)\n",
389
- " if missing:\n",
390
- " return f\"WARN: pretraining probe missing expected terms {missing}\"\n",
391
- " if len(re.findall(r\"[가-힣]\", raw)) < 5:\n",
392
- " return \"WARN: too little Korean text\"\n",
393
- " return \"PASS: pretraining distribution signal\"\n",
394
- "\n",
395
- " if expect == \"mentions_shell_concept\":\n",
396
- " missing = missing_terms(raw, case)\n",
397
- " if missing:\n",
398
- " return f\"WARN: shell concept missing terms {missing}\"\n",
399
- " return \"PASS: mentions expected shell concepts\"\n",
400
- "\n",
401
- " if expect == \"strict_json_keys\":\n",
402
- " if raw != body:\n",
403
- " return \"FAIL: JSON wrapped in markdown fence; SFT/format tuning needed\"\n",
404
- " try:\n",
405
- " obj = json.loads(body)\n",
406
- " except Exception as exc:\n",
407
- " return f\"FAIL: invalid JSON ({exc.__class__.__name__})\"\n",
408
- " if not isinstance(obj, dict):\n",
409
- " return \"FAIL: JSON is not an object\"\n",
410
- " missing_keys = [key for key in case.get(\"required_keys\", []) if key not in obj]\n",
411
- " if missing_keys:\n",
412
- " return f\"FAIL: JSON missing keys {missing_keys}\"\n",
413
- " missing = missing_terms(json.dumps(obj, ensure_ascii=False), case)\n",
414
- " if missing:\n",
415
- " return f\"FAIL: JSON content missing terms {missing}\"\n",
416
- " return \"PASS: strict JSON\"\n",
417
- "\n",
418
- " if expect == \"ko_grounded_summary\":\n",
419
- " missing = missing_terms(raw, case)\n",
420
- " if missing:\n",
421
- " return f\"FAIL: missing grounded Korean terms {missing}\"\n",
422
- " if len(re.findall(r\"[가-힣]\", raw)) < 10:\n",
423
- " return \"FAIL: too little Korean text\"\n",
424
- " if len(raw.splitlines()) > 4:\n",
425
- " return \"WARN: too many lines for short summary\"\n",
426
- " return \"PASS: Korean grounded-form probe\"\n",
427
- "\n",
428
- " if expect == \"strict_shell_command\":\n",
429
- " if \"\\n\" in raw:\n",
430
- " return \"FAIL: more than one line\"\n",
431
- " if any(marker in raw for marker in [\"```\", \"We need\", \"Step\", \"1.\", \"Task:\"]):\n",
432
- " return \"FAIL: contains explanation/formatting\"\n",
433
- " if not re.match(r\"^(find|du|ls|python|sh|bash|fd|rg)\\b\", raw):\n",
434
- " return \"FAIL: not a shell command start\"\n",
435
- " missing = missing_terms(raw, case)\n",
436
- " if missing:\n",
437
- " return f\"FAIL: command missing expected terms {missing}\"\n",
438
- " return \"PASS: strict shell command\"\n",
439
- "\n",
440
- " if expect == \"strict_python_function\":\n",
441
- " if \"```\" in raw:\n",
442
- " return \"FAIL: markdown fence present\"\n",
443
- " try:\n",
444
- " tree = ast.parse(raw)\n",
445
- " except SyntaxError as exc:\n",
446
- " return f\"FAIL: Python syntax error line {exc.lineno}\"\n",
447
- " funcs = [node for node in tree.body if isinstance(node, ast.FunctionDef)]\n",
448
- " if not funcs:\n",
449
- " return \"FAIL: no function definition\"\n",
450
- " if funcs[0].name != \"top_k_lengths\":\n",
451
- " return f\"FAIL: wrong function name {funcs[0].name!r}\"\n",
452
- " return \"PASS: syntactically valid target function\"\n",
453
- "\n",
454
- " return \"WARN: unchecked expectation\"\n",
455
  "\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  "\n",
457
  "results = []\n",
458
- "for case in TEST_CASES:\n",
459
- " print(\"=\" * 80)\n",
 
 
 
 
 
 
 
460
  " print(\"case:\", case[\"name\"])\n",
461
- " print(\"phase:\", case[\"phase\"])\n",
462
- " print(\"expect:\", case[\"expect\"])\n",
463
- " print(\"prompt:\", case[\"prompt\"])\n",
 
 
 
 
464
  " output = kohrm.generate_from_loaded(\n",
465
  " model,\n",
466
  " tokenizer,\n",
467
  " cfg,\n",
468
- " case[\"prompt\"],\n",
469
- " max_new_tokens=case[\"max_new_tokens\"],\n",
470
- " **STRICT_SETTINGS,\n",
 
471
  " )\n",
472
- " verdict = validate_output(case, output)\n",
473
- " results.append({\"case\": case[\"name\"], \"phase\": case[\"phase\"], \"expect\": case[\"expect\"], \"verdict\": verdict, \"output\": output})\n",
474
- " print(\"verdict:\", verdict)\n",
475
- " print(\"--- output ---\")\n",
 
 
 
 
476
  " print(output)\n",
 
 
477
  "\n",
478
- "print(\"=\" * 80)\n",
479
- "print(json.dumps([{k: r[k] for k in [\"case\", \"phase\", \"expect\", \"verdict\"]} for r in results], indent=2, ensure_ascii=False))"
480
  ]
481
  },
482
  {
483
  "cell_type": "markdown",
484
  "metadata": {},
485
  "source": [
486
- "## 7. Optional Low-Temperature Retry\n",
487
  "\n",
488
- "This retry is only diagnostic. It should not turn a strict post-training failure into a pass. Use it to see whether the bad output is a greedy decoding artifact or a checkpoint/format-alignment issue."
489
  ]
490
  },
491
  {
@@ -494,48 +418,47 @@
494
  "metadata": {},
495
  "outputs": [],
496
  "source": [
497
- "RETRY_CASE_NAMES = {\"ko_finance_plain_qa\", \"ko_finance_short\", \"en_terminal_command_only\"}\n",
498
- "RETRY_SETTINGS = dict(STRICT_SETTINGS)\n",
499
- "RETRY_SETTINGS.update({\n",
500
- " \"temperature\": 0.2,\n",
501
- " \"top_p\": 0.85,\n",
502
- " \"repetition_penalty\": 1.22,\n",
503
- " \"no_repeat_ngram_size\": 4,\n",
504
- "})\n",
505
- "\n",
506
- "for case in TEST_CASES:\n",
507
- " if case[\"name\"] not in RETRY_CASE_NAMES:\n",
508
- " continue\n",
509
- " print(\"=\" * 80)\n",
510
- " print(\"retry case:\", case[\"name\"])\n",
511
  " output = kohrm.generate_from_loaded(\n",
512
  " model,\n",
513
  " tokenizer,\n",
514
  " cfg,\n",
515
- " case[\"prompt\"],\n",
516
- " max_new_tokens=case[\"max_new_tokens\"],\n",
517
- " **RETRY_SETTINGS,\n",
 
518
  " )\n",
519
- " print(\"retry verdict:\", validate_output(case, output))\n",
520
- " print(output)"
521
  ]
522
  },
523
  {
524
  "cell_type": "markdown",
525
  "metadata": {},
526
  "source": [
527
- "## 8. Interpretation Checklist\n",
528
- "\n",
529
- "Do not read this notebook as a final KoHRM chat/SFT benchmark. The current public checkpoint is a rolling pretraining checkpoint. It has not yet received the later behavior SFT/LoRA/RL pass.\n",
530
  "\n",
531
- "Use the results this way:\n",
532
  "\n",
533
- "- A pretraining probe `PASS` means the checkpoint shows a usable signal from that domain or task distribution.\n",
534
- "- A pretraining probe `WARN` means the signal is weak or the prompt is still outside the learned distribution.\n",
535
- "- A strict post-training probe `FAIL` is expected at this stage if the model has not learned stable JSON-only, command-only, grounded summary, or code-only behavior.\n",
536
- "- A strict post-training probe `PASS` is useful, but it is not required before SFT.\n",
537
  "\n",
538
- "For strict behavior, compare the same probes after the final continuation checkpoint and after `behavior_mini` / `terminal_tool_core` / `korean_domain_core` LoRA. The validator intentionally marks markdown-wrapped JSON, non-command text, syntactically invalid code, and non-Korean placeholder text as failures."
539
  ]
540
  }
541
  ],
@@ -553,5 +476,5 @@
553
  }
554
  },
555
  "nbformat": 4,
556
- "nbformat_minor": 5
557
  }
 
4
  "cell_type": "markdown",
5
  "metadata": {},
6
  "source": [
7
+ "# KoHRM-Text-1.4B Colab T4 Long Knowledge Probe\n",
8
  "\n",
9
+ "This notebook checks a pretraining checkpoint's long-form knowledge signal after PT-style training by generating long responses from training-format instructions.\n",
10
  "\n",
11
+ "It follows the same training data layout used by `scripts/prepare_sft_data.py`:\n",
 
 
12
  "\n",
13
  "```text\n",
14
+ "instruction text -> response text\n",
15
  "```\n",
16
  "\n",
17
+ "The runtime wraps each instruction exactly in the HRM-Text control format:\n",
18
  "\n",
19
+ "```text\n",
20
+ "<|im_start|><|object_ref_start|>instruction<|im_end|>\n",
21
+ "```\n",
22
  "\n",
23
+ "`<|object_ref_start|>` is the `direct` condition. The model then generates the response until `<|box_end|>` or the token budget. The prompts below are intentionally close to the actual Korean legal raw corpus, Korean wiki raw corpus, BCAI finance QA, and terminal conversation builders used in pretraining.\n"
 
24
  ]
25
  },
26
  {
 
29
  "source": [
30
  "## 1. Install Dependencies\n",
31
  "\n",
32
+ "This path intentionally avoids `transformers`, `AutoTokenizer`, and `AutoModelForCausalLM`. The current public export is a custom HRM-Text architecture, so the notebook uses the lightweight project helper instead.\n"
33
  ]
34
  },
35
  {
 
48
  "source": [
49
  "## 2. Runtime Settings\n",
50
  "\n",
51
+ "`MAX_SEQ_LEN=1536` and long generation are intended for T4 knowledge probing. If Colab runs out of memory, lower `MAX_SEQ_LEN` to `1024` and `DEFAULT_MAX_NEW_TOKENS` to `256`.\n"
52
  ]
53
  },
54
  {
 
71
  "REVISION = \"main\"\n",
72
  "LOCAL_DIR = Path(\"/content/KoHRM-Text-1.4B\")\n",
73
  "HELPER_PATH = LOCAL_DIR / \"kohrm_colab_generate.py\"\n",
 
74
  "\n",
75
+ "MAX_SEQ_LEN = 1536\n",
76
+ "DEFAULT_MAX_NEW_TOKENS = 384\n",
77
+ "DEFAULT_MIN_NEW_TOKENS = 160\n",
78
+ "\n",
79
+ "LONG_TEXT_SETTINGS = {\n",
80
  " \"max_seq_len\": MAX_SEQ_LEN,\n",
81
+ " \"temperature\": 0.65,\n",
82
+ " \"top_p\": 0.92,\n",
83
+ " \"repetition_penalty\": 1.05,\n",
84
+ " \"no_repeat_ngram_size\": 0,\n",
85
  " \"condition\": \"direct\",\n",
86
  "}\n",
87
  "\n",
88
  "print(\"repo:\", REPO_ID)\n",
89
  "print(\"revision:\", REVISION)\n",
90
  "print(\"local_dir:\", LOCAL_DIR)\n",
91
+ "print(\"max_seq_len:\", MAX_SEQ_LEN)\n",
92
+ "print(\"default max/min new tokens:\", DEFAULT_MAX_NEW_TOKENS, DEFAULT_MIN_NEW_TOKENS)"
93
  ]
94
  },
95
  {
 
98
  "source": [
99
  "## 3. Download Latest Public Checkpoint\n",
100
  "\n",
101
+ "The notebook downloads only the files needed for public `model.safetensors` inference and the helper runtime. If the helper is missing from the model repo, it falls back to the GitHub repository.\n"
102
  ]
103
  },
104
  {
 
144
  " HELPER_PATH.write_text((repo / \"notebooks\" / \"kohrm_colab_generate.py\").read_text(encoding=\"utf-8\"), encoding=\"utf-8\")\n",
145
  "\n",
146
  "for name in [\"config.json\", \"tokenizer.json\", \"model.safetensors\", \"README.md\", \"kohrm_colab_generate.py\"]:\n",
147
+ " p = LOCAL_DIR / name\n",
148
+ " print(name, \"OK\" if p.exists() else \"MISSING\", p)"
149
  ]
150
  },
151
  {
152
  "cell_type": "markdown",
153
  "metadata": {},
154
  "source": [
155
+ "## 4. Inspect Config and Training Wrapper\n",
156
  "\n",
157
+ "This cell checks the model shape and special tokens. The prompt wrapper shown here is the exact wrapper used for all probes below.\n"
158
  ]
159
  },
160
  {
 
165
  "source": [
166
  "spec = importlib.util.spec_from_file_location(\"kohrm_colab_generate\", HELPER_PATH)\n",
167
  "kohrm = importlib.util.module_from_spec(spec)\n",
168
+ "sys.modules[\"kohrm_colab_generate\"] = kohrm\n",
169
  "spec.loader.exec_module(kohrm)\n",
170
  "\n",
171
  "config = json.loads((LOCAL_DIR / \"config.json\").read_text(encoding=\"utf-8\"))\n",
 
187
  "for token in [\"<|im_start|>\", \"<|object_ref_start|>\", \"<|object_ref_end|>\", \"<|quad_start|>\", \"<|quad_end|>\", \"<|im_end|>\", \"<|box_end|>\"]:\n",
188
  " print(f\"{token:22s}\", raw_tok.token_to_id(token))\n",
189
  "\n",
190
+ "example_instruction = \"비씨카드는 어떤 회사인가요?\"\n",
191
  "print(\"direct condition token:\", kohrm.condition_to_tokens(\"direct\"))\n",
192
+ "print(\"wrapped prompt:\", kohrm.format_kohrm_prompt(example_instruction, condition=\"direct\"))"
 
193
  ]
194
  },
195
  {
 
198
  "source": [
199
  "## 5. Load Model Once\n",
200
  "\n",
201
+ "Loading can take a few minutes on Colab. The helper uses PyTorch scaled-dot-product attention and a static KV cache. It is slower than the training-time FlashAttention path, but it is enough for long text inspection.\n"
202
  ]
203
  },
204
  {
 
226
  "cell_type": "markdown",
227
  "metadata": {},
228
  "source": [
229
+ "## 6. PT-Format Long Knowledge Prompts\n",
230
  "\n",
231
+ "These long-response probes use prompts shaped like the data that went into pretraining:\n",
232
  "\n",
233
+ "- BCAI Finance Kor: plain Korean finance QA instruction.\n",
234
+ "- Korean wiki raw corpus: `다음은 한국어 위키백과 문서 원문 일부입니다...` instruction.\n",
235
+ "- Korean legal raw corpus: `다음은 대한민국 법령/자치법규 원문 일부입니다...` instruction.\n",
236
+ "- Terminal conversation corpus: `다음 터미널/코딩 작업 대화 맥락에서...` instruction.\n",
237
+ "\n",
238
+ "The notebook prints output length and the raw generated text. Judge the text manually: domain terms, Korean fluency, factual continuity, repetition, and whether it collapses to one-token answers.\n"
239
  ]
240
  },
241
  {
 
244
  "metadata": {},
245
  "outputs": [],
246
  "source": [
247
+ "KNOWLEDGE_PROMPTS = [\n",
 
 
 
 
248
  " {\n",
249
+ " \"name\": \"finance_bc_card_company\",\n",
250
+ " \"source_format\": \"BCAI Finance Kor plain QA\",\n",
251
+ " \"prompt\": \"비씨카드는 어떤 회사인가요?\",\n",
252
+ " \"max_new_tokens\": 420,\n",
253
+ " \"min_new_tokens\": 180,\n",
 
 
254
  " },\n",
255
  " {\n",
256
+ " \"name\": \"finance_exchange_investment\",\n",
257
+ " \"source_format\": \"BCAI Finance Kor plain QA\",\n",
258
+ " \"prompt\": \"환율 변동이 개인 투자에 미치는 영향과 대비 전략은 무엇인가요?\",\n",
259
+ " \"max_new_tokens\": 420,\n",
260
+ " \"min_new_tokens\": 180,\n",
 
 
 
 
 
 
261
  " },\n",
262
  " {\n",
263
+ " \"name\": \"kowiki_hunminjeongeum_raw_style\",\n",
264
+ " \"source_format\": \"kowiki raw instruction style\",\n",
265
+ " \"prompt\": \"\"\"다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  "\n",
267
  "[문서명]\n",
268
+ "훈민정음\n",
269
  "\n",
270
+ "[부분]\n",
271
+ "1/1\"\"\",\n",
272
+ " \"max_new_tokens\": 520,\n",
273
+ " \"min_new_tokens\": 220,\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  " },\n",
275
  " {\n",
276
+ " \"name\": \"kowiki_jimmy_carter_raw_style\",\n",
277
+ " \"source_format\": \"kowiki raw instruction style\",\n",
278
+ " \"prompt\": \"\"\"다음은 한국어 위키백과 문서 원문 일부입니다. 백과사전식 한국어, 고유명사, 날짜, 기술/사회/문화 지식을 그대로 학습하십시오.\n",
279
+ "\n",
280
+ "[문서명]\n",
281
+ "지미 카터\n",
282
+ "\n",
283
+ "[부분]\n",
284
+ "1/3\"\"\",\n",
285
+ " \"max_new_tokens\": 520,\n",
286
+ " \"min_new_tokens\": 220,\n",
287
  " },\n",
288
  " {\n",
289
+ " \"name\": \"legal_criminal_code_raw_style\",\n",
290
+ " \"source_format\": \"korean legal raw instruction style\",\n",
291
+ " \"prompt\": \"\"\"다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.\n",
292
+ "\n",
293
+ "[자료종류]\n",
294
+ "law\n",
295
+ "\n",
296
+ "[문서명]\n",
297
+ "형법\n",
298
+ "\n",
299
+ "[경로]\n",
300
+ "kr/형법/법률.md\n",
301
+ "\n",
302
+ "[부분]\n",
303
+ "1/1\"\"\",\n",
304
+ " \"max_new_tokens\": 520,\n",
305
+ " \"min_new_tokens\": 220,\n",
306
  " },\n",
307
  " {\n",
308
+ " \"name\": \"legal_restoration_raw_style\",\n",
309
+ " \"source_format\": \"korean legal raw instruction style\",\n",
310
+ " \"prompt\": \"\"\"다음은 대한민국 법령/자치법규 원문 일부입니다. 법률 한국어, 조문 구조, 번호 체계, 기관명, 시행일자 표현을 그대로 학습하십시오.\n",
311
+ "\n",
312
+ "[자료종류]\n",
313
+ "law\n",
314
+ "\n",
315
+ "[문서명]\n",
316
+ "10ㆍ27법난 피해자의 명예회복 등에 관한 법률\n",
317
+ "\n",
318
+ "[경로]\n",
319
+ "kr/10ㆍ27법난피해자의명예회복등에관한법률/법률.md\n",
320
+ "\n",
321
+ "[부분]\n",
322
+ "1/1\"\"\",\n",
323
+ " \"max_new_tokens\": 520,\n",
324
+ " \"min_new_tokens\": 220,\n",
325
  " },\n",
326
  " {\n",
327
+ " \"name\": \"terminal_conversation_style_long\",\n",
328
+ " \"source_format\": \"local terminal conversation instruction style\",\n",
329
+ " \"prompt\": \"\"\"다음 터미널/코딩 작업 대화 맥락에서 assistant가 이어서 수행할 분석, 계획, 명령 JSON 또는 최종 응답을 작성하십시오.\n",
330
+ "\n",
331
+ "[user]\n",
332
+ "현재 디렉터리에서 용량이 큰 파일을 찾아 디스크 정리 후보를 보고 싶습니다. 숨김 폴더와 일반 폴더를 모두 확인하되, 결과는 사람이 읽기 쉽게 정리해 주세요.\"\"\",\n",
333
+ " \"max_new_tokens\": 420,\n",
334
+ " \"min_new_tokens\": 160,\n",
335
  " },\n",
336
  "]\n",
337
  "\n",
338
+ "print(\"probe count:\", len(KNOWLEDGE_PROMPTS))\n",
339
+ "for item in KNOWLEDGE_PROMPTS:\n",
340
+ " print(\"-\", item[\"name\"], \"|\", item[\"source_format\"], \"| max/min\", item[\"max_new_tokens\"], item[\"min_new_tokens\"])\n"
341
+ ]
342
+ },
343
+ {
344
+ "cell_type": "markdown",
345
+ "metadata": {},
346
+ "source": [
347
+ "## 7. Run Long Generations\n",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  "\n",
349
+ "The goal is to inspect the generated text itself after PT-style training: length, Korean fluency, domain terms, factual continuity, and repetition.\n"
350
+ ]
351
+ },
352
+ {
353
+ "cell_type": "code",
354
+ "execution_count": null,
355
+ "metadata": {},
356
+ "outputs": [],
357
+ "source": [
358
+ "def count_tokens(text):\n",
359
+ " return len(tokenizer.encode(text, add_special_tokens=False).ids)\n",
360
+ "\n",
361
+ "RUN_ONLY = None # Example: {\"finance_bc_card_company\", \"kowiki_hunminjeongeum_raw_style\"}\n",
362
  "\n",
363
  "results = []\n",
364
+ "for case in KNOWLEDGE_PROMPTS:\n",
365
+ " if RUN_ONLY is not None and case[\"name\"] not in RUN_ONLY:\n",
366
+ " continue\n",
367
+ "\n",
368
+ " prompt = case[\"prompt\"]\n",
369
+ " max_new = case.get(\"max_new_tokens\", DEFAULT_MAX_NEW_TOKENS)\n",
370
+ " min_new = case.get(\"min_new_tokens\", DEFAULT_MIN_NEW_TOKENS)\n",
371
+ "\n",
372
+ " print(\"=\" * 100)\n",
373
  " print(\"case:\", case[\"name\"])\n",
374
+ " print(\"source_format:\", case[\"source_format\"])\n",
375
+ " print(\"prompt_chars:\", len(prompt), \"prompt_tokens:\", count_tokens(kohrm.format_kohrm_prompt(prompt, condition=\"direct\")))\n",
376
+ " print(\"max_new_tokens:\", max_new, \"min_new_tokens:\", min_new)\n",
377
+ " print(\"--- prompt ---\")\n",
378
+ " print(prompt)\n",
379
+ " print(\"--- output ---\")\n",
380
+ "\n",
381
  " output = kohrm.generate_from_loaded(\n",
382
  " model,\n",
383
  " tokenizer,\n",
384
  " cfg,\n",
385
+ " prompt,\n",
386
+ " max_new_tokens=max_new,\n",
387
+ " min_new_tokens=min_new,\n",
388
+ " **LONG_TEXT_SETTINGS,\n",
389
  " )\n",
390
+ " out_tokens = count_tokens(output)\n",
391
+ " results.append({\n",
392
+ " \"case\": case[\"name\"],\n",
393
+ " \"source_format\": case[\"source_format\"],\n",
394
+ " \"output_chars\": len(output),\n",
395
+ " \"output_tokens\": out_tokens,\n",
396
+ " \"output\": output,\n",
397
+ " })\n",
398
  " print(output)\n",
399
+ " print(\"--- output stats ---\")\n",
400
+ " print(\"chars:\", len(output), \"tokens:\", out_tokens)\n",
401
  "\n",
402
+ "print(\"=\" * 100)\n",
403
+ "print(json.dumps([{k: r[k] for k in [\"case\", \"source_format\", \"output_chars\", \"output_tokens\"]} for r in results], indent=2, ensure_ascii=False))\n"
404
  ]
405
  },
406
  {
407
  "cell_type": "markdown",
408
  "metadata": {},
409
  "source": [
410
+ "## 8. Optional Decode Settings Sweep\n",
411
  "\n",
412
+ "Use this only if long output collapses to short tokens or repeats. It keeps the same training-format prompts and changes only decoding parameters.\n"
413
  ]
414
  },
415
  {
 
418
  "metadata": {},
419
  "outputs": [],
420
  "source": [
421
+ "SWEEP_CASE_NAME = \"finance_exchange_investment\"\n",
422
+ "SWEEP_PROMPT = next(item[\"prompt\"] for item in KNOWLEDGE_PROMPTS if item[\"name\"] == SWEEP_CASE_NAME)\n",
423
+ "\n",
424
+ "SWEEP_SETTINGS = [\n",
425
+ " {\"name\": \"deterministic_minlen\", \"temperature\": 0.0, \"top_p\": 1.0, \"repetition_penalty\": 1.02, \"no_repeat_ngram_size\": 0},\n",
426
+ " {\"name\": \"sample_balanced\", \"temperature\": 0.65, \"top_p\": 0.92, \"repetition_penalty\": 1.05, \"no_repeat_ngram_size\": 0},\n",
427
+ " {\"name\": \"sample_more_diverse\", \"temperature\": 0.85, \"top_p\": 0.95, \"repetition_penalty\": 1.08, \"no_repeat_ngram_size\": 0},\n",
428
+ "]\n",
429
+ "\n",
430
+ "for settings in SWEEP_SETTINGS:\n",
431
+ " run_settings = dict(LONG_TEXT_SETTINGS)\n",
432
+ " run_settings.update({k: v for k, v in settings.items() if k != \"name\"})\n",
433
+ " print(\"=\" * 100)\n",
434
+ " print(\"decode:\", settings[\"name\"])\n",
435
  " output = kohrm.generate_from_loaded(\n",
436
  " model,\n",
437
  " tokenizer,\n",
438
  " cfg,\n",
439
+ " SWEEP_PROMPT,\n",
440
+ " max_new_tokens=320,\n",
441
+ " min_new_tokens=120,\n",
442
+ " **run_settings,\n",
443
  " )\n",
444
+ " print(output)\n",
445
+ " print(\"chars:\", len(output), \"tokens:\", count_tokens(output))\n"
446
  ]
447
  },
448
  {
449
  "cell_type": "markdown",
450
  "metadata": {},
451
  "source": [
452
+ "## 9. How To Read Results\n",
 
 
453
  "\n",
454
+ "For this PT knowledge probe, useful signs are:\n",
455
  "\n",
456
+ "- The output is long enough to inspect, not `Yes`, `B`, `0`, or one short fragment.\n",
457
+ "- Korean legal/wiki/finance outputs contain domain terms and coherent Korean paragraphs.\n",
458
+ "- Raw-style prompts may produce article/law-like continuations because that is how raw corpora were converted for PT.\n",
459
+ "- Terminal conversation style may produce analysis plus commands, because that is what the local terminal conversation data used as the response target.\n",
460
  "\n",
461
+ "If every long prompt still collapses to one-token or unrelated boilerplate despite `min_new_tokens`, the next thing to debug is the public converted weight/runtime compatibility, not prompt wording.\n"
462
  ]
463
  }
464
  ],
 
476
  }
477
  },
478
  "nbformat": 4,
479
+ "nbformat_minor": 0
480
  }