안녕하세요

llm-course 라는 Repository에 대해서 기록을 해보려고 합니다.

 

llm-course 에서 여러 과정들을 소개하지만 

오늘은 The LLM Engineer Roadmap에 대해 짧게 살펴보겠습니다.

The LLM Engineer Roadmap

이 이미지는 대규모 언어 모델(LLM)dmf 운영하고 최적화하는 데 관련된 과정과 구성 요소를 시각적으로 나타낸 플로우 차트입니다. 이러한 모델은 자연어 처리 작업에 사용되며, 각 단계는 모델의 성능을 향상시키고 실제 세계의 문제를 해결하는 데 필요한 여러 기술과 접근 방식을 요약합니다.

 

이미지는 다음과 같은 7개의 주요단계를 보여줍니다.

 

1. Running LLMs:

  • LLM API와 오픈소스 LLMs를 활용하여 사용자의 질의를 이해하고 처리합니다.
  • 프롬프트 엔지니어링과 구조화된 출력을 통해 LLM의 성능을 최적화합니다.

2. Building a Vector Storage:

  • 문서를 취합하고, 분할하여 임베딩 모델을 사용해 벡터 데이터베이스를 구축합니다.
  • 이과정은 문서의 내용을 수치화하여 검색 가능하게 만드는 작업입니다.

3. Retrieval Augmented Generation:

  • 검색 강화 생성(Retrieval Augmented Generation, RAG)을 통해 관련 정보를 검색하고, 이를 기반으로 새로운 내용을 생성합니다.
  • 이 단계에서는 오케스트레이터, 검색자(Retrivers), 메모리 및 평가 등의 구성 요소가 사용됩니다.

4. Advanced RAG:

  • 질의 구성, 에이전트 및 도구 사용, 후처리를 통해 RAG의 고급 기능을 구현합니다.

5. Inference Optimization:

  • 추론 최적화를 위해 Flash Attention, Key-value cache, Speculative decoding 등의 기술을 사용합니다.

6. Deploying LLMs:

  • 모델을 로컬, 데모, 서버, 에지 등 다양한 환경에 배포합니다.

7. Securing LLMs:

  • 프롬프트 해킹, 백도어, 방어적 조치를 포함하여 모델을 보안 위협으로부터 보호합니다.

 

이 플로우 차트는 LLM의 운영, 최적화, 배포 및 보안에 필요한 다양한 과정과 기술을 나타냅니다. 이러한 프로세스는 모델의 효율성과 효과를 높이는 데 중점을 두며, 사용자에게 더 나은 경험을 제공하기 위한 것입니다.

 

블로그 이미지

steadily

,

IT 용어 알아보기

 

PoC (Proof of Concept : 기술 검증, 개념 증명)


단순 개념 증명으로도 해석하기도 하지만 IT 업계에서 PoC는 신기술이 적용된 신제품을 직접 보고 어떻게 작동하는지를 시장에 소개하는 사전 검증의 개념으로 사용되기도 합니다.

예를 들어 이미 시장에 나오지 않은 차기 프로세서 로드맵을 구매하기로 한 국내 모 대형 시중은행의 경우 계약 전 업체들을 불러 차기 제품의 성능과 기능을 미리 제시하도록 한뒤 장비를 정하는 PoC 과정을 거쳤습니다.

보통 시스템 구매 시 기존 제품의 경우 성능테스트를 뜻하는 BMT를 아직 양산되지 않은 신제품을 채책할 경우 PoC의 단계를 거치는 것이 일반적입니다.

또 일부 업체들은 자사 신제품을 전시하고 시스템을 구현시키는 테스트실을 PoC로도 부르고 있습니다.

 

Pilot


이미 검증된 기술을 가지고, 대규모 프로젝트 진행에 앞서 소규모로 진행해보는 시험 프로젝트를 말함.

즉, 프로그램을 실제로 운용하기 전에 오류 또는 부족함 점을 찾기 위하여, 실제 상황과 유사한 조건에서 시험 가동하는 행위

본사업(프로젝트) 진행을 위한 수행안 검증 프로젝트로 가령 기술셋을 확정했으면 그 기술셋으로 실제 업무에 적용해보고 문제점이나 개선안을 마련해서 본 프로젝트에 피드백 주는 것.

(계획, 기술문제, 관리요소, 위험요소, 비용 이런 것을 피드백하는 용도, 당초 기대한 효과에 비해 결과가 너무 미미하거나 비용이 많이 들면 취소)

 

BMT(Bench Marking Test)


일반적인 성능시험과는 달리 실제와 같은 동일한 시험황경에서 여러 개의 제품의 성능에 대한 비교시험을 반복해 성능을 객관적으로 평가하는 것.

제품의 품질 정보가 없을 때 BMT를 통해 존재하는 제품들과 상호 비교해 제품의 품질을 상대적으로 평가

비교 대상 시스템을 표준적인 벤치마크 프로그램을 수행시켜 성능을 측정,

컴퓨터의 속도나 단위 시간당 일 처리량 등 수행속도를 비교하는 검사.

 

예를 들면 OO발주처에서 테라바이트 라우터를 도입한다고 할때, 입찰에 응한 업체들의 제품에 대해서 성능비교를 하는 겁니다.

필요에 의해 이것 저것 골라다가 테스트해보고 비교하는 것. 장비 뿐만 아니라 소프트웨(솔루션)도 포함됩니다.

(대부분은 수평적으로 기술셋 비교에 대한 내용이 많지만 업무적 특성을 포괄하는 솔루션인가? 이런한 요건이면 업무도 물론 수반)

블로그 이미지

steadily

,

ES6+ 템플릿 리터럴로 동적인 문자열 만들기



#1 Template literals(템플릿 리터럴)을 사용한 코드



var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

템플릿 리터럴은 이중 따옴표나 작은 따옴표 대신 백틱(Grave accent)를 사용합니다.

플레이스 홀더 $와 중괄호( ${ expression } )를 사용해 표현식과 그 사이의 텍스트를 함께 전달합니다.

엔터키를 이용해 개행처리까지 가능합니다.



#2 Nesting templates(템플릿 중첩) 코드


예를들어, header태그의 class를 정할때 삼항연산자를 통해 class에 넣을 텍스트를 그대로 출력할 수 있습니다.


먼저 ES5 에서는


var classes = 'header'
classes += (isLargeScreen() ?
'' : item.isCollapsed ?
' icon-expander' : ' icon-collapser');



두번째로 ES2015에서 중첩(nesting)없이 템플릿 리터럴을 사용한 경우


const classes = `header ${ isLargeScreen() ? '' :
(item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;

${ expression }을 사용해 script를 사용함.


ES2015에서 nested 템플릿 리터럴을 사용한 경우


const classes = `header ${ isLargeScreen() ? '' :
`icon-${item.isCollapsed ? 'expander' : 'collapser'}` }`;

코드 가독성이 많이 높아 진다.



#3 Tagged templates(태그된 템플릿) 코드


tagged template(태그된 템플릿)는 template literals(템플릿 리터럴)의 더욱 발전된 형태입니다.

tagged template(태그된 템플릿)는 함수로 정의된다.

사용방법은 아래와 같이 함수명에 template literals(템플릿 리터럴)을 붙여서 사용한다


// 함수 정의
function taggedFn(strings, ...expressions) { // 1번
return "Something.";
}

const val1 = 12;
const val2 = 34;
const result = taggedFn`first-${val1}-second-${val2}`; // 2번
console.log(result); // 'Something.'

1번에서 taggedFn이라는 tagged template(태그된 템플릿) 함수를 정의한다.

2번에서 taggedFn 함수를 호출한다. 여기서 함수명과 template literals(템플릿 리터럴)을 붙여서 사용하는 점이 우리가 알고 있던 문법과 다르기 때문에 어색할 수 있다.

여기서 template literals(템플릿 리터럴) 표현식( ${ } )을 기준으로 일반 stringsexpressions를 파싱하여 배열로 만들어 매개변수로 넣어 준다.

strings : ['first-', '-second-', '']

expressions : [12, 34]


아래의 여러 가지 예제를 통해 매개변수 strings, expressions 값이 어떻게 파싱되는지 알아보자.


const val1 = 12;
const val2 = 34;

taggedFn`first-${val1}-second-${val2}-third`;    // 1번
// string : ['first-', '-second-', '-third' ];
// expressions : [12, 34] ;

taggedFn`first-${val1}-second-${val2}`;         // 2번
// string : ['first-', '-second-', '' ];
// expressions : [12, 34] ;

taggedFn`${val1}-second-${val2}`;                // 3번
// string : ['', '-second-', '' ];
// expressions : [12, 34] ;

// 함수 정의
function taggedFn(strings, ...expressions) {
console.log(strings.length === expressions.length + 1); // true
}

2번, 3번의 strings 배열값을 자세히 보면 template literals(템플릿 리터럴) 표현식으로 시작거나 또는 끝나면 '' 빈 문자열이 들어가는 것을 확인할 수 있다.

1번, 2번, 3번의 expressions값을 동일하다.

이 결과로 strings 배열의 개수는 expressions 배열의 개수보다 항상 하나 더 많다는 점을 확일할 수 있다.


tagged template(태그된 템플릿)을 활용해 expressions로 전달된 문자열을 HTML strong 태그로 감싸 강조를 하는 함수를 만들어 보자.


function setStrongify(strings, ...expressions) {
return strings.reduce(
(preValue, str, i) => expressions.length === i
? `${preValue}${str}`
: `${preValue}${str}<strong>${expressions[i]}</strong>`
, '');
}

const val1 = 12;
const val2 = 34;
const result = setStrongify`first ${val1} second ${val2}`;
console.log(result);
// first <strong>12</strong> second <strong>34</strong>


tagged template(태그된 템플릿) 함수의 반환값이 꼭 문자열이 아니어도 된다.

참고로 react에서 사용하는 스타일 적용 패키지 styled-components는 tagged template(태그된 템플릿) 함수가 리액트 컴포넌트를 반환한다.



블로그 이미지

steadily

,

ES6+ 향상된 비동기 프로그래밍(3) - async await


 -. async await는 비동기 프로그래밍을 동기 프로그래밍처럼 작성할 수 있도록 함수에 추가된 기능이다.

 -. Promise가 javascript의 표준이 되고 2년 후(ES2017)에 async await도 표준이 되었다.

-. Promise가 async await 보다 큰 개념이며, async await를 사용해 then 메서드 chain 보다 가독성을 높여줄 수 있다.



1.  async await 이해하기



#1 async await 함수는 프로미스를 반환한다.


Promise = 객체

async await = 함수에 적용되는 개념


Promise를 반환하는 async await 함수


async function getData() {
return 1234; // 1번
}
getData().then(data => console.log(data)); // 1234

async await 함수 내부에서 반환하는 값이 Promise라면 그 객체를  그대로 반환한다.


예외 발생 시에는 rejected(거부됨) 상태인 Promise가 반환된다.


async function getData() {
throw new Error('1234');
}
getData().catch(error => console.log(error));
// 에러 발생: 1234



#2 await 키워드를 사용하는 방법


await 키워드는  async 함수 내부에서만 사용할 수 있다.

만약 await 키워드 오른쪽에 Promise를 입력하면 그 Promise가 settled(처리됨) 상태가 될 때까지 기다린다.

따라서 await 키워드를 사용하면 비동기 처리를 기다리면서 순차적으로 코드를 작성할 수 있다.


await 키워드 사용 예


function requestData(value) {
return new Promise(resolve => {
setTimeout(() => {
console.log('requestData: ', value);
resolve(value);
}, 1000);
});
}
async function getData() {
const data1 = await requestData(1);   // 1번
const data2 = await requestData(2);   // 2번
console.log(data1, data2);            // 3번
return [data1, data2];
}
getData();      
// requestData: 1
// requestData: 2
// 1 2

1번, 2번의 함수 requestData가 반환하는 Promise가 settled(처리됨) 상태가 될 때까지

3번의 코드는 실행 되지 않는다.



#3 async awaitPromise 보다 가독성이 좋다



async awaitPromise 코드 비교


function getDataPromise() { // 1번
asyncFn1()
.then(data => {
console.log(data);
return asyncFn2();
})
.then(data => {
console.log(data);
})
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
console.log(data1);
const data2 = await asyncFn2();
console.log(data2);
}

1번은 Promise로 작성한 함수

2번의 async await 함수는 then 메서드를 호출할 필요가 없기 때문에 가독성이 좋아진다.


비동기 함수 간의 의존성이 높아질수록 async await의 가독성이 좋아지는 것을 볼 수 있다.

아래에서 asyncFn1, asyncFn2, asyncFn3 이 세 함수는 각각의 반환값을 다른 함수의 인수로 넣는 의존성을 가지고 있디.


function getDataPromise(){ // 1번
return asyncFn1()
.then(data1 => Promise.all([data1, asyncFn2(data1)]))
.then(([data1, data2]) => {
return asyncFn3(data1, data2);
});
}
async function getDataAsync() { // 2번
const data1 = await asyncFn1();
const data2 = await asyncFn2(data1);
return asyncFn3(data1, data2);
}

1번에서는 asyncFn3 함수에 인수를 전달하기 위해 Promise.all을 사용했다.

2번에서 async await 함수는 복잡한 의존성을 직관적이게 표현했다.



2.  async await 활용하기


#1 비동기 함수를 병렬로 실행하기


async await  함수에서 순차적으로 실행하는 코드


async function getData() {
const data1 = await asyncFn1();
const data2 = await asyncFn2();
//...
}

위의 두 함수 사이에 의존성이 없다면 동시에 실행하는 것이 좋을 것이다.

Promise는 생성과 동시에 비동기 코드가 실행된다.

그래서 아래와 같이 두개의 Promise를 생성하고 await 키워드를 나중에 사용하여 병렬 코드를 만들어 보자.


async function getData() {
const promise1 = asyncFn1();
const promise2 = asyncFn2();
const data1 = await promise1;
const data2 = await promise2;
//...
}

두개의 프로미스가 생성되고 각자의 비동기 코드가 실행된다.

이 두 프로미스가 생성된 후 await 키워드를 사용해 기다리기 때문에 두 개의 비동기 함수가 병렬로 처리된다.


아래와 같이 Promise.all을 사용하면 더 간결한 코드를 작성할 수 있다.


async function getData() {
const [data1, data2] = await Promise.all(asyncFn1(), asyncFn2());
//...
}




#2 예외 처리


async await 함수 내부에서 발생하는 예외는 아래와 같이 try catch 문으로 처리할 수 있다.


async function getData(){
try {
await getAsync(); // 1번
return getSync(); // 2번
} catch (error) {
console.log(error);
}
}

1번(비동기 함수)과 2번(동기 함수)에서 발생하는 모든 예외가 catch 문에서 처리 된다.

여기서 getData 함수가 async await 함수가 아니였다면 1번의 getAsync 함수에서 발생하는 예외는 catch 문에서 처리 되지 않는다. 이유는 getAsync 함수의 처리가 끝나는 시점을 알 수 없기 때문이다.



#2 Thenable


asycn await는 ES6의 Promise가 아니더라도 then 메서드를 가진 객체를 Promise 취급한다.

이렇게 Promise가 아닌데 then 메서드를 가진 객체를 Thenable이라고 부른다.


async await 함수에서 Thenable을 사용한 예


class ThenableSample {
then(resolve, reject) { // 1번
setTimeout(() => resolve(1234), 1000);
}
}
async function asyncFn() {
const result = await new ThenableSample(); // 2번
console.log(result); // 1234
}

1번 : then 메서드를 가지고 있어 new ThenableSample() 객체는 Thenable이다.

2번 : async await 함수는 ThenablePromise 처럼 처리한다.


블로그 이미지

steadily

,