C.W.K.
Stream
Lesson 02 of 07 · published

텍스트 생성 — 스트리밍, 멈추기, stop token

~14 min · generation, streaming, stop-tokens

Level 0Curious
0 XP0/51 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

generate() vs stream_generate()

Lesson 1 의 두 줄이 generate() 썼는데, 끝나면 전체 completion 을 한 문자열로 줘. UI 작업 — chat bubble, 터미널 스트리밍, 토큰이 생성되는 대로 나타나길 원하는 무엇이든 — 에는 stream_generate() 원해. 토큰 당 chunk 하나씩 yield 하는 Python generator.

둘 다 mlx-lm 의 진짜 API. generate()stream_generate() 를 감싸고 chunk 들을 join 하는 얇은 wrapper 로 구현. Latency 가 중요할 땐 streaming 버전, 안 중요할 땐 buffering 버전.

stream_generate 가 yield 하는 것

각 yield 는 raw token 만이 아니라 풍부한 metadata 가진 GenerationResponse 객체. 실제로 쓸 필드:

  • .text — 이 step 의 새 텍스트 fragment (보통 토큰 하나, 가끔 조각으로 스트림되는 Unicode glyph 의 일부).
  • .finish_reason — 생성 중엔 None, 마지막 yield 에 'stop' (stop 토큰 hit) 또는 'length' (max_tokens hit).
  • .generation_tokens — 지금까지 생성된 토큰 수 누적.
  • .generation_tps — 생성 시작 이후 측정된 tokens-per-second; 라이브 throughput 디스플레이에 유용.
  • .peak_memory — 이 생성 중 peak GPU 메모리 (byte).

이 metadata 의 풍부함이 mlx-lm 의 조용한 강점 중 하나 — throughput 보려고 별도 profiler 안 필요하고, 왜 생성 멈췄는지 추측 안 해도 돼.

Stop 조건, demystify

세 가지 중 하나가 일어나면 생성 멈춰:

  1. 모델이 자기 tokenizer 의 EOS 토큰 emit (예 Llama 3 의 <|eot_id|>, Qwen 의 <|im_end|>). Tokenizer 가 자기 EOS 알아. mlx-lm 이 거기서 가져와. finish_reason='stop'.
  2. max_tokens hit. finish_reason='length'. 두면 모델이 계속 갔을 거.
  3. 수동으로 멈춤 — generator 에서 break 함. 모델은 문장 중간, 특별한 finish_reason 없음 — 그냥 더 이상 yield 없음.

대부분 quickstart 가 안 언급하는 함정 — chat-tuned 모델은 매 턴 EOS 토큰에 의존해서 멈춰. Chat template (lesson 5) 적용 잊으면, 모델이 자연스러운 멈춤 지점에서 EOS emit 안 하는 일 잦고, max_tokens 가 차야 멈추는 폭주 생성 보게 될 거.

Code

stream_generate — 토큰 도착할 때 print·python
from mlx_lm import load, stream_generate

model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

print("Streaming: ", end="", flush=True)
for chunk in stream_generate(model, tok, prompt="Count 1 to 5:", max_tokens=30):
    print(chunk.text, end="", flush=True)
print()

# Verified output (2026-05-03):
#   Streaming: 1, 2, 3, 4, 5
#   Count 6 to 10:
#   ...
마지막 chunk 의 GenerationResponse metadata 살피기·python
from mlx_lm import load, stream_generate

model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

last = None
for chunk in stream_generate(model, tok, prompt="Hello.", max_tokens=20):
    last = chunk

print("text fragment   :", repr(last.text))
print("finish_reason   :", last.finish_reason)         # 'stop' or 'length'
print("generation_tokens:", last.generation_tokens)    # how many tokens were produced
print("generation_tps  :", round(last.generation_tps, 1), "tokens/sec")
print("peak_memory MB  :", round(last.peak_memory / 1024 / 1024, 1))
수동 조기 stop — generator 에서 break·python
from mlx_lm import load, stream_generate

model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

# Stop the moment we've seen a period
buf = ""
for chunk in stream_generate(model, tok, prompt="One sentence about MLX:", max_tokens=200):
    buf += chunk.text
    print(chunk.text, end="", flush=True)
    if "." in chunk.text:
        break
print()
print("---")
print("Stopped early. Captured:", repr(buf))

External links

Exercise

두 번째 코드 블록 (metadata inspector) 새 프롬프트로 돌려. 네 머신의 generation_tps 알아채 — Apple Silicon 의 1B Q4 모델은 수백 단위여야 (칩에 따라 200-800 tps). 그 다음 max_tokens=200 으로 다시 돌려 비교 — tps 가 거의 같아, 더 높아/낮아? KV cache 성장이 나중 토큰 약간 느리게 해, 그래서 긴 generation 은 보통 작은 tps 드롭 보여. 알아챈 거 두 문장.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.